How to write a parameterized universal shift register in verilog

A parameterized universal shift register in Verilog

what is a shift register?

A shift register is a very basic design element very commonly used in digital designs generally for the purposes of delaying a certain data by a certain number of clock cycles. These are commonly called the SISO (Serial Input Serial Output) shift registers. There are also another kind called the SIPO (Serial Input Parallel Output) shift registers that are rarely used in applications like some exotic counter circuits. The ultimate version of the shift-register would be a PIPO(parallel-in-parallel-out) shift register which can be made to shift in either directions. The PIPO Shift Register is the most generalized form of a shift register and all the others mentioned above are just special cases of it. So let's code it!

A universal parallel-in-parallel-out shift register

`timescale 1ns / 1ps

module universal_shift_register #(parameter WIDTH = 8, parameter SIZE = 3) (
input clk,                          //clock
input ce,                           //clock enable
input rst,                          //reset
input load,                         //load
input dir,                          //dir=0 => right_shift dir=1> left_shift
input [WIDTH-1:0] data_in,          //serial data input         
input [WIDTH*SIZE-1:0] din,         //parallel data input
output [WIDTH-1:0] data_out,        //serial data output
output [WIDTH*SIZE-1:0] dout        //parallel data output
);
reg [WIDTH-1:0] sr [SIZE-1:0];
reg load_delayed;
reg load_pulse;
    
//******************************************************************************    
//Generating a load_pulse which lasts only one clock cycle long 
//This prevents uncertainity as to what value is loaded owing to 
//random duration of the incoming 'load' signal.
//eg. if 'load' is controlled by a user button we do not know exactly for 
// how many cycles the button press is going to last. The value of 'din'
//could change in this period giving unexpected behaviour
//               ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  
// clk         : ┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──
//               ┐                       ┌─────────────────────────────┐           
// load        : └───────────────────────┘                             └───────────
//               ┐                             ┌─────────────────────────────┐     
// load_delayed: └─────────────────────────────┘                             └─────
//               ┐                       ┌─────┐                                   
// load_pulse  : └───────────────────────┘     └───────────────────────────────────    
    
always@(posedge clk or posedge rst)
begin
   if(rst)
   begin
        load_delayed <= 0;
        load_pulse <= 0;
   end
   else
   begin
       load_delayed <= load;
       load_pulse <= load && ~load_delayed;
   end
    end
//*********************************************************************************
generate
genvar i;
for(i=0;i<SIZE;i=i+1)
begin:block
   always@(posedge clk or posedge rst)
    begin
       if(rst)
            begin
                sr[i] <= 'd0;
            end
        else if(ce)
          begin
              if(load_pulse)                                    //whenever we detect a load pulse, we load the register with new data
              begin
                  sr[i] <= din[WIDTH*(i+1)-1:WIDTH*i];
              end
              else
              begin
              if(dir)
              begin
                if(i == SIZE-1)
                begin
                    sr[i] <= data_in;                            //when there is no load pulse and ce = 1 we keep shifting the data
                end
                else
                sr[i] <= sr[i+1];
              end
              else
              begin
                if(i == 'd0)
                begin
                      sr[i] <= data_in;
                end
                else
                begin
                    sr[i] <= sr[i-1];
                end
              end
              end
          end
        else
        begin
            sr[i] <= 'd0;
        end
    end
  assign dout[WIDTH*(i+1) - 1: WIDTH*i] = sr[i];
end
endgenerate
    assign data_out = dir ? sr[0] : sr[SIZE-1];                     
endmodule

As already mentioned, the above Parallel-in-Parallel out shift register with multi-direction capabilities is rarely used in practice. It is used in some exotic circuits like Linear Feedback Shift Register (LFSR) based circuits and Pseudo-random-sequence-generators. However, a simpler version of this circuit, i.e. the Serial-Input-Serial-Output-shift register is very commonly used for purposes like introducing delays in the data-path and pipelining designs with too much combinational logic.

The Implementation

The below code implements a universal SISO shift register

`timescale 1ns / 1ps

module variable_shift_register #(parameter WIDTH = 8, parameter SIZE = 3) (
input clk,                                  //clock
input ce,                                   //clock enable                        
input rst,                                  //reset
input [WIDTH-1:0] data_in,                  //data in
output [WIDTH-1:0] data_out                 //data out
);
    
reg [WIDTH-1:0] sr [SIZE-1:0];            //the register that holds the data

generate
genvar i;
for(i = 0;i < SIZE;i = i + 1)
begin
    always@(posedge clk or posedge rst)
    begin
        if(rst)                          
        begin
            sr[i] <= 'd0;
        end
        else
        begin
            if(ce)                            //the shift register operates only when the clock-enable is active
            begin
                if(i == 'd0)
                begin
                    sr[i] <= data_in;
                end
                else
                begin
                    sr[i] <= sr[i-1];
                end
            end
        end
    end
end
endgenerate
assign data_out = sr[SIZE-1];
endmodule

Example usage scenarios

Rotation of bits in a register

universal_shift_register #(.WIDTH(3),.SIZE(1)) sr(
    .clk(clk),
    .ce(ce),
    .rst(rst),
    .load(load),
    .dir(0),
    .data_in(data_out),
    .din(din),
    .data_out(data_out),          //data out is connected to data_in creating a loop, with the dir input we can control which 
    						    //direction we shift the data being fed to the register.
    .dout(dout)
);


                    din[2]               din[1]                din[0]
                       +                   +                     +
                       |                   |                     |
                       |                   |                     |
                  +----v-----+        +----v-----+         +-----v----+
                  |          |        |          |         |          |
            +----->          +-------->          +--------->          +----+
            |     |          |        |          |         |          |    |
            |     +----------+        +----------+         +----------+    |
            |                                                              |
            +--------------------------------------------------------------+

Variable delay module

variable_shift_register #(.WIDTH(3),.SIZE(4)) sr(
    .clk(clk),
    .ce(ce),
    .rst(rst),
    .data_in(input_data[3:0]),
    .data_out(input_data_delayed[3:0]),          
);


                  +----------+        +----------+         +----------+
                  |          |        |          |         |          |
 input_data +----->          +-------->          +--------->          +----> input_data_delayed_by_3_clocks
                  |          |        |          |         |          |    
                  +----------+        +----------+         +----------+    

All the source Verilog files and their test benches can be found at the Github Repo

Batman

I'm Batman, a silent nerd and a watchful engineer obsessed with great Technology. Get in touch via the Discord community for this site

Like what you are reading? Let me send the latest posts right to your inbox!

Free. No spam. Unsubscribe anytime you wish.