Hello FPGAmigos! In the two previous articles, we introduced you to the Chisel operators and the multiplexer, which are essential building blocks for logic in digital design. However, we haven’t discussed memory yet. Today, we will explore the options Chisel provides for managing registers. If you’re unfamiliar with DFFs and how memory works in FPGAs, check out my article on What is a DFF?
I. Reg and RegNext
This is the most basic one. It just create a register:
val reg_0 = Reg(UInt())
val reg_1 = Reg(UInt(8.W))
reg_1 is a 8 bits register intepreted as an unsigned integer. reg_0 is a register that store a unsigned integer. The width of the register will be infered, meaning it is chisel that will decide the width based on the usage reg_0. If you connect it to a 64 bits wire then the register will be 64 bits as follow:
val my_wire = Wire(UInt(64.W))
val my_reg = Reg(UInt())
my_reg := my_wire
As a convinience function, chisel introduce RegNext to create a register from a another signal faster:
val my_wire = Wire(UInt(64.W))
val my_reg = RegNext(my_wire)
When should you use Reg and RegNext? Reg and RegNext create a register without initializing it, which is often regarded as bad practice. In most cases, you would want to initialize your register to ensure deterministic behavior and ease the verification process. If you choose not to initialize it, you should have a reason such as resource optimization (no reset line), an implicit reset scheme that places your design in a known state without needing to reset this register, or your register is necessarily written before being read. Perhaps, you might want non-deterministic behavior (although, I must admit, I don’t know of any examples). If you know more reasons not to initialize your register, please share them in the comments.
II. RegInit
RegInit create a register and take an initial value as parameter.
val my_reg = RegInit(0.U(8.W))
When should you use RegInit? I believe it should be the default choice. As mentioned earlier, initializing your registers is generally considered good practice for ensuring deterministic behavior.
III. RegEnable
RegEnable is the same as RegInit, except that it provides an enable signal to allow or prevent the copy from D to Q. If you are unfamiliar with the register enable concept, don’t hesitate to check my article on What is a DFF?
DON’T do that :
val my_wire = Wire(UInt(8.W))
val my_enable = Wire(Bool())
val my_reg = RegInit(0.U(8.W))
// Don't
when(my_enable){
my_reg:=my_wire
}
Use RegEnable instead :
val my_wire = Wire(UInt(8.W))
val my_enable = Wire(Bool())
val my_reg_en = RegEnable(my_wire, 0.U(8.W), my_enable)
Here, the first parameter is ‘d’, the signal to be copied, the second is the initial value, and the third is the ‘enable’ signal.
When should you use RegEnable? You should use it when you want to control whether your register copies the ‘d’ input to ‘q’ or not.
The simplest example of this is the Toggle Flip-Flop(TFF), which toggle between 0 and 1 when ‘enable’ is asserted. ‘D’ and ‘Q’ are connected with an inverter, and the change of state is controlled by the ‘enable’ signal.
class TFF extends Module{
val io = IO(new Bundle{
val out = Output(Bool())
val enable = Input(Bool())
})
io.out := ~RegEnable(io.out, 0.U(1.W), io.enable)
}
Here is the cleansed generated Verilog :
module TFF(
input clock,
input reset,
output io_out,
input io_enable
);
reg io_out_r;
assign io_out = ~io_out_r;
always @(posedge clock) begin
if (reset) begin
io_out_r <= 1'h0; // it is assignment not inferior or equal (code parser problem)
end else if (io_enable) begin
io_out_r <= io_out;
end
end
endmodule
IV. ShiftRegister Vs ShiftRegisters
ShiftRegister
When should you use a ShiftRegister? ShiftRegister should be employed when you aim to delay a signal by ‘n’ cycles through a shift register, and there is no requirement for accessing the intermediate register of the shift register. If your input signal consists of ‘m’ bits, the output will mirror this with ‘m’ bits, replicating the input signal after a delay of ‘n’ cycles.
First parameter is the signal to delay, second the number of cycle, third the reset value, fourth the enable signal.
class Delay extends Module{
val io = IO(new Bundle{
val in = Input(Bool())
val en = Input(Bool())
val out = Output(Bool())
})
val my_shift = ShiftRegister(io.in, 3, 0.U, io.en)
io.out := my_shift
}
In this example, the output signal mirrors the input signal but arrives 3 cycles later. The shift register is activated by the ‘io.en’ signal and defaults to a reset value of zero.
Here is the cleansed generated Verilog :
module Delay(
input clock,
input reset,
input io_in,
input io_en,
output io_out
);
reg my_shift_r;
reg my_shift_r_1;
reg my_shift;
assign io_out = my_shift;
always @(posedge clock) begin
if (reset) begin
my_shift_r <= 1'h0;// it is assignment not inferior or equal (code parser problem)
end else if (io_en) begin
my_shift_r <= io_in;
end
if (reset) begin
my_shift_r_1 <= 1'h0;
end else if (io_en) begin
my_shift_r_1 <= my_shift_r;
end
if (reset) begin
my_shift <= 1'h0;
end else if (io_en) begin
my_shift <= my_shift_r_1;
end
end
endmodule
ShiftRegisters
When is the use of ShiftRegisters appropriate ? The use of ShiftRegisters is suitable when you need to access the intermediate registers of your shift register, such as in the case of a deserializer.
class DeSerializer extends Module{
val io = IO(new Bundle{
val in = Input(Bool())
val en = Input(Bool())
val out = Output(UInt(3.W))
})
val my_shift = ShiftRegisters(io.in, 3, 0.U(1.W), io.en)
io.out := Cat(my_shift)
}
In this context, ShiftRegisters yield a data sequence wherein each piece of data corresponds to an intermediate register. By using the ‘Cat’ function, I am able to construct a 3-bit word.
Here is the cleansed generated Verilog :
module DeSerializer(
input clock,
input reset,
input io_in,
input io_en,
output [2:0] io_out
);
reg my_shift_0;
reg my_shift_1;
reg my_shift_2;
wire [1:0] io_out_hi = {my_shift_0,my_shift_1};
assign io_out = {io_out_hi,my_shift_2};
always @(posedge clock) begin
if (reset) begin
my_shift_0 <= 1'h0;// it is assignment not inferior or equal (code parser problem)
end else if (io_en) begin
my_shift_0 <= io_in;
end
if (reset) begin
my_shift_1 <= 1'h0;
end else if (io_en) begin
my_shift_1 <= my_shift_0;
end
if (reset) begin
my_shift_2 <= 1'h0;
end else if (io_en) begin
my_shift_2 <= my_shift_1;
end
end
endmodule
Conclusion
In conclusion, Chisel provides a robust and flexible toolkit for managing registers, catering to a range of design needs. Reg and RegNext are straightforward but offer no initial state, while RegInit provides initialization for deterministic behavior. RegEnable allows control over the copy operation from ‘d’ to ‘q’. ShiftRegister and ShiftRegisters help you delay a signal by n cycles, the second giving you access to the intermediate register of the shift register.
If you have any question, don’t hesitate to ask in the comment sections. If you like, please comment, share and bookmark ! stay tuned for the next article about Chisel !