Testing in hardware
We are finally at a point where we think the code is correct, and all the pieces are implemented. It’s time to test it on hardware.
To do so, we need to set up a demo entity which instantiates the state and output generators, and selects a nice color for them. We’ll do this in a separate file, called src/hw_test.spade
use lib::state_gen;
use lib::output_gen;
use lib::OutputControl;
use lib::Timing;
use lib::Color;
#[no_mangle(all)]
Entities together with Functions together with entity and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure). and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike Functions together with entity and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure)., entity can contain registers and therefore have state and unlike Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline., they do not have a statically known latency or Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. structure. demo(clk: Type used to represent clock signals., led_pin: inv &Type used to represent 1 bit values) {
reg(clk) is used to define registers which maintain the state of your circuit.
All registers have a Type used to represent clock signals. (clk), a name and a new value after the = which is given as a function of the current value. Registers can also have a Defines the reset value of a register as (trigger: value). When trigger ist rue, the reset is synchronously reset to valuetrigger: value) wich means that the reset is synchronously set to value when trigger is true.
In pipelines, you can also define registers with reg; is used in a Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. to separate stages. When you refer to a variable defined above a reg; statement below a reg; statement you refer to a registered version of the original value. Registers defined with an explicit Type used to represent clock signals. (reg(clk)) are used for state registers both inside and outside pipelines. which are used to separate stages. rst initial(true) = false;
// Our target FPGA, the ecpix5 has a 100 MHz Type used to represent clock signals..
let is used to define a variable. Spade infers the type of most variables from context, but you can also specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.y the type with : <type> before the =. t = Timing$(
us280: 28000,
us0_4: 40,
us0_8: 80,
us0_45: 45,
us0_85: 85,
us1_25: 125,
);
let is used to define a variable. Spade infers the type of most variables from context, but you can also specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.y the type with : <type> before the =. ctrl: OutputControl<Type used to represent unsigned integers. The generic parameter (<N>) specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.ies the number of bits> = inst is required when instantiating entities, units with state, as opposed to Functions together with Entities together with fn and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike fn, entity can contain registers and therefore have state and unlike Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline., they do not have a statically known latency or Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. structure. and Defines a pipeline. The number in in the parentheses is input-to-output latency of the pipeline. are the basic building blocks of Spade circuits. Unlike entities and pipelines, functions have no internal state, they are combinational (pure). which are combinational, or pure in software terms. state_gen(clk, rst, 2, t);
let is used to define a variable. Spade infers the type of most variables from context, but you can also specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.y the type with : <type> before the =. brightness = 64;
let is used to define a variable. Spade infers the type of most variables from context, but you can also specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.y the type with : <type> before the =. colors = [
Color(brightness, 0, 0),
Color(0, brightness, 0),
Color(0, 0, brightness),
Color(0, brightness, brightness),
];
let is used to define a variable. Spade infers the type of most variables from context, but you can also specReturns the value of the first branch if the condition is true, otherwise the second branch.
Note that unlike software languages, Spade does not have conditional execution. You do not conditionally assign values inside if expressiosn, you compute a value and return it.y the type with : <type> before the =. with_color = match ctrl {
OutputControl::Ret => OutputControl::Ret(),
OutputControl::Led$(payload: led_num, bit, duration) => {
OutputControl::Led$(payload: colors[led_num], bit, duration)
},
};
set led_pin = &output_gen(with_color, t);
}
There is not much going on here. Since we’re in a different file, we need to include the stuff defined in the other file. lib refers to the library we are currently building, and since our code is in main.spade, the things we defined are defined directly in the lib:: namespace.
If we had put files in another file insrc, they would have been inlib::filename_without_extension::.
Since our top module, demo, is going to connect to the external world, we’ll mark it as #[no_mangle(all)]. This tells the Spade compiler to name things exactly what they are called in the Spade code. The downside of this is that we might collide with Verilog keywords, and the module demo will not have a namespaced name so if more than one demo unit is defined in the project, there will be a collision. In this case, we will only have one demo, and none of the names we used are keywords in Verilog so things will work just fine.
For the output, we take an inv &bool. inv & is an inverted wire, i.e. a wire where we can set a value using a set statement. We also need to map this and the clock pin to a physical pin on the FPGA, which is done with a pin mapping file. The file type and format of these files varies between FPGAs.
For the families supported by swim, these files are
- ice40:
.pcf - ecp5:
.lpf - gowin:
.cst
When you initialized the project, you should have received one of these files, update it to map the led_pin pin to the pin you intend to use.
The line reg(clk) rst initial(true) = false; generates a reset signal that is active the first clock cycle after the FPGA has started.
To generate the output, we create our timing struct, this time with correct timings for the 100 MHz FPGA we’re targeting. Your FPGA likely runs at a different frequency, to compute the timing values, use the formula
f_clk * tTo go from color index to led value, we store the colors in an array and look up the values for each of the LEDs we’re going to test, and output those signals.
Then we instantiate everything, and finally set the output pin to the resulting value.
We also need to tell the synthesis tool what entity should be our top module; to do so, change the synthesis.top value in swim.toml to demo
[synthesis]
top = "demo"With all that done, we can run swim upload, and look at our new RGB LEDs.
The pattern is static and boring at the moment, so this is a great opportunity to play around a bit and make the LEDs do something more interesting!
All the code for this project can be found at https://gitlab.com/TheZoq2/ws2812-spade.