From 94729a4f7d2327bff61b1c9b56b904e052620cdc Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 20 Oct 2021 02:20:55 +0200 Subject: [PATCH] ws2812: add support for RISC-V This commit adds support for two new chips using the RISC-V architecture: the FE310 (used in the HiFive 1) and the ESP32-C3 from Espressif. --- ws2812/gen-ws2812.go | 34 +- ws2812/ws2812-asm_tinygoriscv.go | 1118 ++++++++++++++++++++++++++++++ ws2812/ws2812.go | 1 + ws2812/ws2812_tinygoriscv.go | 20 + 4 files changed, 1171 insertions(+), 2 deletions(-) create mode 100644 ws2812/ws2812-asm_tinygoriscv.go create mode 100644 ws2812/ws2812_tinygoriscv.go diff --git a/ws2812/gen-ws2812.go b/ws2812/gen-ws2812.go index b7293f2..2b2bc76 100644 --- a/ws2812/gen-ws2812.go +++ b/ws2812/gen-ws2812.go @@ -45,7 +45,8 @@ type architectureImpl struct { minBaseCyclesT1H int maxBaseCyclesT1H int minBaseCyclesTLD int - template string + valueTemplate string // template for how to pass the 'c' byte to assembly + template string // assembly template } var architectures = map[string]architectureImpl{ @@ -59,6 +60,7 @@ var architectures = map[string]architectureImpl{ minBaseCyclesT1H: 1 + 1 + 2, // shift + branch (taken) + store maxBaseCyclesT1H: 1 + 3 + 2, // shift + branch (taken) + store minBaseCyclesTLD: 1 + 1 + 2, // subtraction + branch + store (in next cycle) + valueTemplate: "uint32(c) << 24", template: ` 1: @ send_bit str {maskSet}, {portSet} @ [2] T0H and T0L start here @@ -72,6 +74,34 @@ var architectures = map[string]architectureImpl{ @DELAY3 subs {i}, #1 @ [1] bne.n 1b @ [1/3] send_bit +`, + }, + "tinygoriscv": { + // Largely based on the SiFive FE310 CPU: + // - stores are 1 cycle + // - branches are 1 or 3 cycles, depending on branch prediction + // - ALU operations are 1 cycle (as on most CPUs) + // Hopefully this generalizes to other chips. + buildTag: "tinygo.riscv32", + minBaseCyclesT0H: 1 + 1 + 1, // shift + branch (not taken) + store + maxBaseCyclesT0H: 1 + 3 + 1, // shift + branch (not taken) + store + minBaseCyclesT1H: 1 + 1 + 1, // shift + branch (taken) + store + maxBaseCyclesT1H: 1 + 3 + 1, // shift + branch (taken) + store + minBaseCyclesTLD: 1 + 1 + 1, // subtraction + branch + store (in next cycle) + valueTemplate: "uint32(c) << 23", + template: ` +1: // send_bit + sw {maskSet}, {portSet} // [1] T0H and T0L start here + @DELAY1 + slli {value}, {value}, 1 // [1] shift value left by 1 + bltz {value}, 2f // [1/3] skip_store + sw {maskClear}, {portClear} // [1] T0H -> T0L transition +2: // skip_store + @DELAY2 + sw {maskClear}, {portClear} // [1] T1H -> T1L transition + @DELAY3 + addi {i}, {i}, -1 // [1] + bnez {i}, 1b // [1/3] send_bit `, }, } @@ -186,7 +216,7 @@ func writeImplementation(f *os.File, arch string, megahertz int) error { fmt.Fprintf(buf, " // T1H: %2d - %2d cycles or %.1fns - %.1fns\n", actualMinCyclesT1H, actualMaxCyclesT1H, actualMinNanosecondsT1H, actualMaxNanosecondsT1H) fmt.Fprintf(buf, " // TLD: %2d - cycles or %.1fns -\n", actualMinCyclesTLD, actualMinNanosecondsTLD) fmt.Fprintf(buf, " mask := interrupt.Disable()\n") - fmt.Fprintf(buf, " value := uint32(c) << 24\n") + fmt.Fprintf(buf, " value := %s\n", archImpl.valueTemplate) asm := archImpl.template asm = strings.ReplaceAll(asm, " @DELAY1\n", strings.Repeat(" nop\n", delay1)) asm = strings.ReplaceAll(asm, " @DELAY2\n", strings.Repeat(" nop\n", delay2)) diff --git a/ws2812/ws2812-asm_tinygoriscv.go b/ws2812/ws2812-asm_tinygoriscv.go new file mode 100644 index 0000000..bcd67bb --- /dev/null +++ b/ws2812/ws2812-asm_tinygoriscv.go @@ -0,0 +1,1118 @@ +//go:build tinygo.riscv32 +// +build tinygo.riscv32 + +package ws2812 + +// Warning: autogenerated file. Instead of modifying this file, change +// gen-ws2812.go and run "go generate". + +import ( + "device" + "runtime/interrupt" +) + +func (d Device) writeByte160(c byte) { + portSet, maskSet := d.Pin.PortMaskSet() + portClear, maskClear := d.Pin.PortMaskClear() + + // Timings: + // T0H: 56 - 58 cycles or 350.0ns - 362.5ns + // T1H: 168 - 170 cycles or 1050.0ns - 1062.5ns + // TLD: 184 - cycles or 1150.0ns - + mask := interrupt.Disable() + value := uint32(c) << 23 + device.AsmFull(` + 1: // send_bit + sw {maskSet}, {portSet} // [1] T0H and T0L start here + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + slli {value}, {value}, 1 // [1] shift value left by 1 + bltz {value}, 2f // [1/3] skip_store + sw {maskClear}, {portClear} // [1] T0H -> T0L transition + 2: // skip_store + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + sw {maskClear}, {portClear} // [1] T1H -> T1L transition + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + addi {i}, {i}, -1 // [1] + bnez {i}, 1b // [1/3] send_bit + `, map[string]interface{}{ + "value": value, + "i": 8, + "maskSet": maskSet, + "portSet": portSet, + "maskClear": maskClear, + "portClear": portClear, + }) + interrupt.Restore(mask) +} + +func (d Device) writeByte320(c byte) { + portSet, maskSet := d.Pin.PortMaskSet() + portClear, maskClear := d.Pin.PortMaskClear() + + // Timings: + // T0H: 112 - 114 cycles or 350.0ns - 356.2ns + // T1H: 336 - 338 cycles or 1050.0ns - 1056.2ns + // TLD: 368 - cycles or 1150.0ns - + mask := interrupt.Disable() + value := uint32(c) << 23 + device.AsmFull(` + 1: // send_bit + sw {maskSet}, {portSet} // [1] T0H and T0L start here + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + slli {value}, {value}, 1 // [1] shift value left by 1 + bltz {value}, 2f // [1/3] skip_store + sw {maskClear}, {portClear} // [1] T0H -> T0L transition + 2: // skip_store + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + sw {maskClear}, {portClear} // [1] T1H -> T1L transition + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + addi {i}, {i}, -1 // [1] + bnez {i}, 1b // [1/3] send_bit + `, map[string]interface{}{ + "value": value, + "i": 8, + "maskSet": maskSet, + "portSet": portSet, + "maskClear": maskClear, + "portClear": portClear, + }) + interrupt.Restore(mask) +} diff --git a/ws2812/ws2812.go b/ws2812/ws2812.go index 101a8ff..a93c9f0 100644 --- a/ws2812/ws2812.go +++ b/ws2812/ws2812.go @@ -2,6 +2,7 @@ package ws2812 // import "tinygo.org/x/drivers/ws2812" //go:generate go run gen-ws2812.go -arch=cortexm 16 48 64 120 168 +//go:generate go run gen-ws2812.go -arch=tinygoriscv 160 320 import ( "errors" diff --git a/ws2812/ws2812_tinygoriscv.go b/ws2812/ws2812_tinygoriscv.go new file mode 100644 index 0000000..5010ea6 --- /dev/null +++ b/ws2812/ws2812_tinygoriscv.go @@ -0,0 +1,20 @@ +//go:build tinygo.riscv32 +// +build tinygo.riscv32 + +package ws2812 + +import "machine" + +// Send a single byte using the WS2812 protocol. +func (d Device) WriteByte(c byte) error { + switch machine.CPUFrequency() { + case 160_000_000: // 160MHz, e.g. esp32c3 + d.writeByte160(c) + return nil + case 320_000_000: // 320MHz, e.g. fe310 + d.writeByte320(c) + return nil + default: + return errUnknownClockSpeed + } +}