@@ -17,16 +17,24 @@ import (
1717// the new assembly implementation - no fiddly timings to calculate and no nops
1818// to count!
1919//
20- // Right now this is specific to Cortex-M chips and assume the following things:
20+ // Right now this is specific to specific chips:
21+ // On Cortex-M chips it assume the following things:
2122// - Arithmetic operations (shift, add, sub) take up 1 clock cycle.
2223// - The nop instruction also takes up 1 clock cycle.
2324// - Store instructions (to the GPIO pins) take up 2 clock cycles.
2425// - Branch instructions can take up 1 to 3 clock cycles. On the Cortex-M0, this
2526// depends on whether the branch is taken or not. On the M4, the documentation
2627// is less clear but it appears the instruction is still 1 to 3 cycles
2728// (possibly including some branch prediction).
28- // It is certainly possible to extend this to other architectures, such as AVR
29- // and RISC-V if needed.
29+ // On RISC-V chips it assumes the following things:
30+ // - Arithmetic operations (shift, add, sub) take up 1 clock cycle.
31+ // - The nop instruction also takes up 1 clock cycle.
32+ // - Store instructions (to the GPIO pins) take up 1 clock cycle.
33+ // - Branch instructions can take up 1 or 3 clock cycles, depending on branch
34+ // prediction. This is based on the SiFive FE310 CPU, but hopefully it
35+ // generalizes to other RISC-V chips as well.
36+
37+ // It is certainly possible to extend this to other architectures, such as AVR as needed.
3038//
3139// Here are two important resources. For the timings:
3240// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
@@ -45,6 +53,7 @@ type architectureImpl struct {
4553 maxBaseCyclesT1H int
4654 minBaseCyclesTLD int
4755 valueTemplate string // template for how to pass the 'c' byte to assembly
56+ funcAttr string // C function attribute (default: always_inline)
4857 template string // assembly template
4958}
5059
@@ -83,13 +92,44 @@ var architectures = map[string]architectureImpl{
8392 // - branches are 1 or 3 cycles, depending on branch prediction
8493 // - ALU operations are 1 cycle (as on most CPUs)
8594 // Hopefully this generalizes to other chips.
86- buildTag : "tinygo.riscv32" ,
95+ buildTag : "tinygo.riscv32 && !esp32c3" ,
96+ minBaseCyclesT0H : 1 + 1 + 1 , // shift + branch (not taken) + store
97+ maxBaseCyclesT0H : 1 + 3 + 1 , // shift + branch (not taken) + store
98+ minBaseCyclesT1H : 1 + 1 + 1 , // shift + branch (taken) + store
99+ maxBaseCyclesT1H : 1 + 3 + 1 , // shift + branch (taken) + store
100+ minBaseCyclesTLD : 1 + 1 + 1 , // subtraction + branch + store (in next cycle)
101+ valueTemplate : "(uint32_t)c << 23" ,
102+ template : `
103+ 1: // send_bit
104+ sw %[maskSet], %[portSet] // [1] T0H and T0L start here
105+ @DELAY1
106+ slli %[value], %[value], 1 // [1] shift value left by 1
107+ bltz %[value], 2f // [1/3] skip_store
108+ sw %[maskClear], %[portClear] // [1] T0H -> T0L transition
109+ 2: // skip_store
110+ @DELAY2
111+ sw %[maskClear], %[portClear] // [1] T1H -> T1L transition
112+ @DELAY3
113+ addi %[i], %[i], -1 // [1]
114+ bnez %[i], 1b // [1/3] send_bit
115+ ` ,
116+ },
117+ "esp32c3" : {
118+ // ESP32-C3 RISC-V core:
119+ // - stores are 1 cycle
120+ // - branches are 1 or 3 cycles
121+ // - ALU operations are 1 cycle
122+ // Uses the same instruction timing as the SiFive FE310, but the
123+ // function is placed in IRAM instead of flash to avoid instruction
124+ // cache miss stalls that would destroy WS2812 timing.
125+ buildTag : "esp32c3" ,
87126 minBaseCyclesT0H : 1 + 1 + 1 , // shift + branch (not taken) + store
88127 maxBaseCyclesT0H : 1 + 3 + 1 , // shift + branch (not taken) + store
89128 minBaseCyclesT1H : 1 + 1 + 1 , // shift + branch (taken) + store
90129 maxBaseCyclesT1H : 1 + 3 + 1 , // shift + branch (taken) + store
91130 minBaseCyclesTLD : 1 + 1 + 1 , // subtraction + branch + store (in next cycle)
92131 valueTemplate : "(uint32_t)c << 23" ,
132+ funcAttr : `__attribute__((section(".iram1"), noinline))` ,
93133 template : `
941341: // send_bit
95135 sw %[maskSet], %[portSet] // [1] T0H and T0L start here
@@ -208,7 +248,11 @@ func writeCAssembly(f *os.File, arch string, megahertz int) error {
208248 // ignore I/O errors.
209249 buf := & bytes.Buffer {}
210250 fmt .Fprintf (buf , "\n " )
211- fmt .Fprintf (buf , "__attribute__((always_inline))\n void ws2812_writeByte%d(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {\n " , megahertz )
251+ funcAttr := archImpl .funcAttr
252+ if funcAttr == "" {
253+ funcAttr = "__attribute__((always_inline))"
254+ }
255+ fmt .Fprintf (buf , "%s\n void ws2812_writeByte%d(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {\n " , funcAttr , megahertz )
212256 fmt .Fprintf (buf , " // Timings:\n " )
213257 fmt .Fprintf (buf , " // T0H: %2d - %2d cycles or %.1fns - %.1fns\n " , actualMinCyclesT0H , actualMaxCyclesT0H , actualMinNanosecondsT0H , actualMaxNanosecondsT0H )
214258 fmt .Fprintf (buf , " // T1H: %2d - %2d cycles or %.1fns - %.1fns\n " , actualMinCyclesT1H , actualMaxCyclesT1H , actualMinNanosecondsT1H , actualMaxNanosecondsT1H )
0 commit comments