Transparent Logo

Chisel Registers Tutorial

dff

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 !

Leave a Reply

Your email address will not be published. Required fields are marked *

Chisel Registers Tutorial

dark blue dot

Summary

Share it !

Get my Ebook ?

ebook_hero-home

Jumpstart you FPGA journey by

• Understanding the place of FPGA in industry
• Learn about internal composition of an FPGA
• A simple beginner friendly project
• An overview of the FPGA workflow
ebook_banner_11