Speed of the E31 at its I/O pins

Hi Jack, I wonder what is the speed of your E31 CPU running at 320MHz at the I/O pins.
What is the maximum available toggling frequency on its I/O pins ?
We have some fast control applications in mind with your E31. Thanks. Pavel

There are logical constraints, and physical.

Logically, if you can use the PWM or other HW to drive the pins, then you could achieve CPU frequency / 2. If you have to bit bang with software, the fastest you can achieve is CPU frequency / 15 (using atomics).

Physically, the FE310’s I/Os were constrained at 100MHz, and the Arduino headers aren’t designed for high speed I/O.

Practically, we are easily bit-banging the control for WS2812 LEDs (aka NeoPixels) which have real-time requirements of ± 150 ns with just C code loops (no hand-crafted assembly).

Hi! I’m a newbie on the RISC-V architecture (done STM32, AVR and a tiny bit of ARM), so sorry for asking dumb questions…

Can you post a link to the WS2812 code?

Or…

Can you post a snippet of the C code loops you used?

How do you determine the # of iterations needed? (by formula or empirically?)

What is the best way to disable interrupts (if any are enabled)? Is their a nice way to do this (e.g. a pre-defined macro in a header somwhere)

Can you post a link to the WS2812 code?
Or…
Can you post a snippet of the C code loops you used?
How do you determine the # of iterations needed? (by formula or empirically?)

The code snippet below is experimental and has some issues (notably the first loop iteration takes too long). It was mostly determined empirically how much overhead the loop and AMO added. The general strategy in the code below is to just use the mcycle counter and our knowledge of the CPU frequency. This doesn’t work if the CPU is running slow, but it works fine at 65MHz (which is what the Arty Dev Kit is running at) and higher.

I think it would be fun to port something like the FastLED library, but haven’t had time yet. There are myriad other ways this could be done, either with SW or the PWM/SPI hardware.

#include "platform.h"
#include <stdint.h>
#include <stdatomic.h>

#define DATA_PIN 22 // This maps to Cinco Data Pin 6

#define NUM_PIXELS 10

#define CORE_CLK_MHZ 65 // This is what is used on Arty Dev Kit. 

#define OR_MASK   ((1 << DATA_PIN))
#define AND_MASK  (~(1 << DATA_PIN))

// These times are from Adafruit's corrected timings, which 
// can be found at https://learn.adafruit.com/assets/10706

#define TIME1_HIGH 800
#define TIME1_LOW  450
#define TIME0_HIGH 400
#define TIME0_LOW  850
#define TIME_TOTAL 1250
#define TIME_RESET 50000

// It takes 5 cycles to do an AMO, plus the pipeline latency.
#define AMO_CYCLES 5

// It takes a few cycles to do the loop
#define LOOP_CYCLES 10

const unsigned nsec_per_cycle = 1000 / CORE_CLK_MHZ;

// It would be more correct to detect 32-bit rollover
static uint32_t rdcyclelo()
{
  uint32_t lo;
  __asm__ __volatile__ ("csrr %0, mcycle\n\t"
                        : "=r" (lo));
  return lo;
}

static void sleep_until(uint32_t end) {
  while (rdcyclelo () < end)
    ;
}

typedef struct pixel {
  uint8_t g;
  uint8_t r;
  uint8_t b;
} PIXEL;

PIXEL pixels[NUM_PIXELS];

volatile uint32_t* gpio_port = (volatile uint32_t*) (GPIO_BASE_ADDR + GPIO_OUTPUT_VAL);

void send_bytes (const uint8_t * bytes, uint32_t len) {
  uint32_t start = 0;
  uint32_t end;
  const uint8_t*  curr_ptr;
  for (curr_ptr = bytes; curr_ptr < bytes + len ; curr_ptr++){
    uint8_t curr = *curr_ptr;
    for (int i = 7; i >= 0; i--) {
      start = rdcyclelo();
      end = start + TIME_TOTAL/nsec_per_cycle - AMO_CYCLES - LOOP_CYCLES;             
      atomic_fetch_or(gpio_port, OR_MASK);
      uint32_t mid;                                               
      if (curr & (1 << i)) {                                         
        mid = TIME1_HIGH/nsec_per_cycle;
      } else {                                                    
        mid = TIME0_HIGH/nsec_per_cycle;
      }                                                           
      sleep_until (start + mid - AMO_CYCLES);                          
      atomic_fetch_and(gpio_port, AND_MASK);                      
      sleep_until(end);                             
    }                                                             
  }                                                         
}

static void send_reset() {
  atomic_fetch_and(gpio_port, AND_MASK);
  uint32_t now = rdcyclelo();             
  sleep_until (now + TIME_RESET / nsec_per_cycle); 
}

int main(void)
{
  // Set up the Data Pin
  GPIO_REG(GPIO_OUTPUT_EN) |= OR_MASK;
  PIXEL * pixel;
  for (pixel = pixels; pixel < (pixels + NUM_PIXELS); pixel++){
    pixel->g = 0x00;
    pixel->r = 0x30;
    pixel->b = 0x00;
  }

  // Transmit Pixels
  uint8_t * raw_grb = (uint8_t *) pixels;

  send_bytes(raw_grb, NUM_PIXELS * sizeof(PIXEL));
  send_reset();
 
return (0);
}

What is the best way to disable interrupts (if any are enabled)? Is their a nice way to do this (e.g. a pre-defined macro in a header somwhere)

See encoding.h: https://github.com/sifive/freedom-e-sdk/blob/master/bsp/env/encoding.h

You can globally disable interrupts using the MIE bit in the MSTATUS register.

#include "encoding.h"
...

// Globally disable interrupts 
clear_csr(mstatus, MSTATUS_MIE);
...

// Re-enable interrupts:
set_csr(mstatus, MSTATUS_MIE);

Or you could use swap_csr if you want to save and restore the interrupt enable status.