Registers
Until now, all the constructs we have seen outside the blinky examples have been combinational (pure “functions”, in software terms). However, only combinational circuits does not get you very far; at some point you need to store some state. State in hardware is stored in registers, and unlike Verilog and VHDL where what is and is not a register is inferred based on the structure of the code, Spade has a dedicated construct for registers.
The simplest register looks like this:
reg(clk) value = next;
On every rising edge of the clk signal, the value of the register will update to the current value of the next signal.
With this, we can build a counter
reg(clk) value = value +. 1;
Note that unlike a let binding, the variable defined by a reg statement is visible to itself allowing you to create a register that depends on itself.
One obvious question about this counter is what value it will start counting at. Since this wasn’t specified above, it will be initialized to an explicit undefined value in simulation, and whatever the hardware ends up doing in hardware. In almost every case where you have a register that depends on itself, you need to give it an explicit initial value which is usually done with a reset. That looks like this:
reg(clk) value reset(rst: 0) = value +. 1;
This syntax often takes a bit getting used to, generally it helps to read it as “the register value is reset whenever the rst signal is true. It gets reset to 0, and if it is not being reset, its value is value + 1”.
In this case, the compiler will not be able to infer how many bits the value signal needs. In many cases, the compiler can infer it from context, but sometimes you have to specify the type explicitly, which like let bindings, is done with : type after the name:
reg(clk) value: uint<8> reset(rst: 0) = value +. 1;
This is also the reason we have to use +. and not +. Whatever type value has, value + 1 will be one bit larger, but since value + 1 needs to be stored in the register containing the value, it needs to be truncated back down.
Exercise
This register will count from 0 to 255, relying on the wrapping behaviour of +. to go back to 0. Modify the example to make the counter count up to 100 before going back to 0.
Initial Values
For hardware which supports it (in general FPGAs and not ASICs), you can specify an initial value which gets set at startup without an explicit reset signal.
reg(clk) value initial(0) = value +. 1;
While avoiding having to define and pass around an explicit reset signal is sometimes convenient, it does make the code less portable. Therefore, it is often best to use the initial value to define a reset signal for use by the rest of the design:
reg(clk) rst initial(true) = false;
Before the first clock cycle, this will initialize the rst value to true which can then be used to reset the rest of the circuit. After the first cycle, the rst signal will switch to false, starting up the rest of the circuit.