Generic register with load

The VHDL code presented below models a synchronous parallel register with a load signal.

The register width is unconstrained (data_in and data_out don’t have a declared size). In previous versions of this code, I used generics to create a module with a configurable size. Using unconstrained ports instead of generics greatly improves the cleanliness and modularity of the code. The size of these signals will be known when the module is instantiated, in this case, by the test bench.

The block was written according to Xilinx’s recommendation for register implementation with synchronous reset (active low). The load signal is also usually named en (enable) since when asserted, the data at the register input is sampled on the rising clock edge and presented at the output.

library ieee;
  use ieee.std_logic_1164.all;

entity generic_reg is
  port (
    rstn      : in  std_logic;
    clk       : in  std_logic; 

    -- inputs
    data_in   : in  std_logic_vector;
    load      : in  std_logic; 
    
    -- outputs
    data_out  : out std_logic_vector
    
  );
end entity;

architecture rtl of generic_reg is
  -- normalize the unconstrained input
  alias data_in_norm : std_logic_vector(data_in'length - 1 downto 0) is data_in; -- normalized unconstrained input
begin
  
  reg_pr : process (clk) 
	begin
    if (rising_edge(clk)) then
      if (rstn = '0') then
        data_out <= (others=>'0');
      elsif (load = '1') then
        data_out <= data_in_norm;
      end if;
    end if;
  end process reg_pr;
  
end architecture;

On the testbench, two instances of generic_reg are instantiated. Notice how easy is to define two registers of different sizes, just by instantiating them with signals of different sizes (in our case, 4 for the first register and 8 for the second).

library ieee;
    use ieee.std_logic_1164.all;
    use std.textio.all;
    
entity tb_reg is
end entity;

architecture test of tb_reg is

    constant PERIOD  : time   := 20 ns;
    constant DATA_W  : natural := 4;
	
    signal clk       : std_logic := '0';
    signal rstn      : std_logic := '0';
    signal load      : std_logic := '0';
    signal data_in1  : std_logic_vector (3 downto 0);
    signal data_out1 : std_logic_vector (data_in1'range);
    signal data_in2  : std_logic_vector (7 downto 0);
    signal data_out2 : std_logic_vector (data_in2'range);
    signal endSim	 : boolean   := false;

  component generic_reg  is
    port (
      clk: 		in std_logic;
      rstn: 	in std_logic;
      
      -- inputs
      data_in:	in std_logic_vector;
      load: 	in std_logic;
      
      -- outputs
      data_out: out std_logic_vector
    );
  end component;
    

begin
    clk     <= not clk after PERIOD/2;
    rstn    <= '1' after  PERIOD*10;

	-- End the simulation
	process 
	begin
	
		wait until (rstn = '1');
		wait until (rising_edge(clk));

		data_in1 <= x"A";
		data_in2 <= x"7C";
		load	<= '1';
		wait until (rising_edge(clk));
		load	<= '0';
		wait until (rising_edge(clk));

		data_in1 <= x"5";
		load	<= '1';
		wait until (rising_edge(clk));
		load	<= '0';
		wait until (rising_edge(clk));
		wait until (rising_edge(clk));
		endSim  <= true;
	end	process;	
		
	-- End the simulation
	process 
	begin
		if (endSim) then
			assert false 
				report "End of simulation." 
				severity failure; 
		end if;
		wait until (rising_edge(clk));
	end process;	

  reg_inst1 : generic_reg
    port map (
        clk      => clk,
        rstn	   => rstn,		
        data_in  => data_in1,
        load     => load,	
        data_out => data_out1
    );
    
  reg_inst2 : generic_reg
    port map (
        clk      => clk,
        rstn	   => rstn,		
        data_in  => data_in2,
        load     => load,	
        data_out => data_out2
    );
                                         
end architecture;                                         

Now let’s take a look at the simulation results:

Notice that even given that data_in1 and data_in2 are undefined at the beginning of the simulation, the output of the registers is well defined, because of the logic involving rstn. Wether using a reset signal for registers or not, is an application decision. Xilinx recommends avoiding using reset signals unless is mandatory for the application. As a general recommendation, control signals are reset, while datapath signals are not reset.

As a final note, for this example, the load signal is common for both registers. The reason why data_out2 does not change upon the second load assertion (while data_out1 does), is that data_in2 has not changed (while data_in1 did).

The source code and the testbench used to generate the waveform can be found on GitHub

2 thoughts on “Generic register with load

  1. This code is VHDL 1993 compliant. Unconstrained ports were introduced with VHDL 1993. VHDL 2008 introduced unconstrained types.

Leave a Reply

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