Output Generation
First of all, the cause of the bug mentioned in the end of the last chapter was an incorrect equality check of the duration when transitioning between states. It should be
if duration == trunc(t_bit-1) {
instead of
if duration == t_bit {
Now that our state machine works, we have done most of the heavy lifting. We still need to translate our control signal into an actual LED output, which is what we'll work on next.
Since this will require no internal state, and is fairly simple logic we'll represent
it as a function. We'll also represent color as a struct with r
, g
and b
values. The output is a single bool
, the actual control signal to be passed to
the LEDs.
struct Color {
r: int<8>,
g: int<8>,
b: int<8>
}
fn output_gen(control: OutputControl<Color>, t: Timing) -> bool {
// TODO
}
The ret output is easy, it is simply a low signal. The 0 and 1 signals are a
bit more complex. The output should be 1 initially, and then transition to 0 at
t0l
or t1l
depending on if the current bit is a 0 or a 1.
The OutputControl::Led
has information about which of the 24 bits should be
emitted. To translate that into a bit value, we'll concatenate the color
channels, and "index" the correct bit. (Currently, Spade does not support bit indexing,
so we'll extract the bits using shifts and masks instead)
This logic can be written as follows:
struct Color {
r: int<8>,
g: int<8>,
b: int<8>
}
fn output_gen(control: OutputControl<Color>, t: Timing) -> bool {
let t0h = t.us0_4;
let t1h = t.us0_8;
match control {
OutputControl::Ret => false,
OutputControl::Led$(payload: color, bit, duration) => {
let color_concat = (color.g `concat` color.r `concat` color.b);
let val = ((color_concat >> sext((23-bit))) & 1) == 1;
let step_time = if val {t1h} else {t0h};
if duration > step_time {
false
}
else {
true
}
}
}
}
Testing
Again, it is good practice to test the module. Testing it is very similar to the state machine, except here we don't have a clock. Instead, we'll set a signal value, advance the simulation by a tiny time step, and assert the output. Here is an example of the test bench. Feel free to extend it with more tests that you think are reasonable. Here it might also be helpful to define some helper functions which check that a specific input gives a specific waveform, for example.
# top=main::output_gen
from spade import *
@cocotb.test()
async def ret_works(dut):
s = SpadeExt(dut)
s.i.t = """Timing$(
us280: 2800,
us0_4: 40,
us0_8: 80,
us0_45: 45,
us0_85: 85,
us1_25: 125,
)"""
s.i.control = "OutputControl::Ret()"
await Timer(1, units='ps')
s.o.assert_eq("false")
@cocotb.test()
async def one_at_bit_0(dut):
s = SpadeExt(dut)
s.i.t = """Timing$(
us280: 2800,
us0_4: 40,
us0_8: 80,
us0_45: 45,
us0_85: 85,
us1_25: 125,
)"""
# Sending 1 @ bit 0, time 0
s.i.control = "OutputControl::Led(Color$(g: 0b1000_0000, r: 0, b: 0), 0, 0)"
await Timer(1, units='ps')
s.o.assert_eq("true")
# Sending 1 @ bit 0, time 40
s.i.control = "OutputControl::Led(Color$(g: 0b1000_0000, r: 0, b: 0), 0, 40)"
await Timer(1, units='ps')
s.o.assert_eq("true")
# Sending 1 @ bit 0, time 80
s.i.control = "OutputControl::Led(Color$(g: 0b1000_0000, r: 0, b: 0), 0, 80)"
await Timer(1, units='ps')
s.o.assert_eq("true")
# Sending 1 @ bit 0, time 81
s.i.control = "OutputControl::Led(Color$(g: 0b1000_0000, r: 0, b: 0), 0, 81)"
await Timer(1, units='ps')
s.o.assert_eq("false")
If you want to see a more fleshed out test test, have a look at https://gitlab.com/TheZoq2/ws2812-spade/-/blob/e3ede5d50abf176f0ea5f0dcf6bfdcfb8b2228d8/test/output_gen.py.
With our tests now passing, we can finally run the code in hardware, which we will discuss in the next and final section of this chapter.