We have been tasked with creating a register bank that can dual read, but only single write. At the moment I've got it all working apart from the dual read. Could someone point me in the right direction?

library IEEE;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values

entity RegisterBank is
     Generic(N : integer := 16;
     M: integer := 16);
    Port ( CLK : in  STD_LOGIC;
           Din : in  STD_LOGIC_VECTOR(N downto 0);
              ReadNum : in STD_LOGIC_VECTOR(M downto 0);
              WriteNum : in STD_LOGIC_VECTOR(M downto 0);
              ReadEnable : in STD_LOGIC;
              WriteEnable : in STD_LOGIC;
           Dout : out  STD_LOGIC_VECTOR(N downto 0));
end RegisterBank;

architecture Behavioral of RegisterBank is
    Component RegisterN port(
        D: in STD_LOGIC_VECTOR(N downto 0);
        Q: in STD_LOGIC_VECTOR(N downto 0);
        Load, CLK: in STD_LOGIC);
    end Component;
    Type RegisterArray is Array(N downto 0) of STD_LOGIC_VECTOR(N downto 0);
    Signal RegisterSignal: RegisterArray;
    Signal W: STD_LOGIC_VECTOR(N downto 0);
    Reg0: RegisterN port map(Din, RegisterSignal(0), W(0), CLK);
    Gen: for i in N downto 1 generate
        RegN: RegisterN port map(Din, RegisterSignal(i), W(i), CLK);
    end generate Gen;
    RegisterSignal(0) <= STD_LOGIC_VECTOR(TO_UNSIGNED(0, M));
    process(CLK) begin
        if RISING_EDGE(CLK) then
            if WriteEnable='1' then
                RegisterSignal(TO_INTEGER(UNSIGNED(WriteNum))) <= Din;
            end if;
            if ReadEnable='1' then
                Dout <= RegisterSignal(TO_INTEGER(UNSIGNED(ReadNum)));
                Dout <= (others => 'Z');
            end if;
        end if;
    end process;

end Behavioral;
  • \$\begingroup\$ There are a couple of oddities in the design description. ` N downto 0` gives a word width of 17 just as M downto 0 gives a 17 + 1 bit address space (262,144) which doesn't match RegisterArray specified with N downto 0 (17). The hazard is simulation time errors if the indexes (WRiteNum, ReadNum) go out of range for RegisterSignal. All this RegisterN stuff (including W) looks superfluous. RegisterSignal declares the register array. \$\endgroup\$
    – user8352
    Feb 22, 2014 at 19:42
  • \$\begingroup\$ Ah sorry, should've explained better! It's supposed to be parameterizable. So we're supposed to use a for generate loop. Also, W is required to port map to Load isn't it? As for the Reg0, that was just a test, to try and set the first register to 0 but it won't work because it'll cause multiple signals. I don't understand how I can use just RegisterSignal to generate N amount of registers? \$\endgroup\$
    – SRG3006
    Feb 23, 2014 at 17:07

2 Answers 2


Here's a parametrized version of your register file (with the names changed slightly) without using generate statements. (Note, I haven't tested it, it looks like it will work).

library ieee; 
use ieee.std_logic_1164.all; 
use ieee.numeric_std.all;

entity regfile is
        N:      natural := 16;  -- word width in bits
        M:      natural := 4    --  Address bits, number of words = 2**M 

    port ( 
        clk:        in   std_logic;
        Din:        in   std_logic_vector (N-1 downto 0);
        RdAddrA:    in   std_logic_vector (M-1 downto 0);
        RdAddrB:    in   std_logic_vector (M-1 downto 0);
        WrAddr:     in   std_logic_vector (M-1 downto 0);
        RdEnabA:    in   std_logic;
        RdEnabB:    in   std_logic;
        WrEnab:     in   std_logic;
        DoutA:      out  std_logic_vector (N-1 downto 0);
        DoutB:      out  std_logic_vector (N-1 downto 0)
    ); end entity;

architecture behave of regfile is

    type regfile_array is array (natural range 0 to 2**M-1) of 
                               std_logic_vector ( N-1 downto 0);
    signal regfile: regfile_array;
    process(clk) begin
        if rising_edge(clk) then
            if WrEnab = '1' then
                regfile(to_integer(unsigned(WrAddr))) <= Din;
            end if;
            if RdEnabA = '1' then
                DoutA <= regfile(to_integer(unsigned(RdAddrA)));
                DoutA <= (others => 'Z');
            end if;
            if RdEnabB = '1' then
                DoutB <= regfile(to_integer(unsigned(RdAddrB)));
                DoutB <= (others => 'Z');
            end if;
        end if;
    end process;

end architecture;

If nothing else it shows how to add a second output port. Note that a read port on a memory is a big multiplexer. Other lessons it teaches are how to get the parameters N and M right assuming you wanted a 16 bit wide register file with a depth of 16 words (2**M). Power of two size implications come from making those read multiplexers (and write steering) symmetrical (in terms of delays).

Notice the relationship between the number of address bits (M) and the number of words (2**M). The -1 stuff is to give 0 as a bit or word index (the latter N).

The idea here is that I'm not doing you homework, rather showing you what's wrong.

generate statement based model

Tying the number of address bits to the array size would prevent you from getting an index range error during simulation should you 'increment' WriteNum, ReadNumA, or ReadNumB beyond the decimal equivalent of 17 because the way you modeled it you have 18 address bits and 17 registers and only need 5 bits (or 4 if you really intended 16 registers).

To use a generate statement to use (in your case) RegisterSignal as wires connected to the output of those RegisterNs instead of holding the content of those registers (RegisterSignal(2**M-1 downto 0), you need to remove the write portion of the process statement.

Without having seen nor written a working VHDL description of RegisterN you need to control which one you write to by load steering (which you incompletely do with W). Note you also have to get you generate statement straight.

Assuming you didn't want 2**(M+1) registers for M := 16, and rather intended to have 16 registers (the default generic number), you need a generic way of decoding WriteNum of any length to W bits, and to specify those W bits as inputs to their respective RegisterN instantiations.

W doesn't currently relate to `M':

Signal W: STD_LOGIC_VECTOR(N downto 0);

It should related to the number of registers (and for a power of 2 that is 2**M):

signal W: std_logic_vector(2**M-1 downto 0);

(And again the -1 allows us to include 0 as an identity value in the set 2**M).

So our generate statement would instantiate a RegisterN for each register, with Din hooked up as the input universally, as is CLK and Qs actual and RegisterSignals actuals like you show.

Your generate statement's iteration scheme could be for natural range i in 2**M-1 downto 0 generate

Besides getting the registers to number of W',WriteNumandReadNumbits write the other bit you are missing is write enable steering (whichW(i)` is true).

The easiest thing to do would be to create a concurrent signal assignment statement inside the generate loop with:

    W(i) <= '1'  when 
                    TO_INTEGER(UNSIGNED(WriteNum)) = i and WriteEnable= '1' 

Which will generate a 'recognizer' for the matching WriteNum address and make W(TO_INTEGER(UNSIGNED(WriteNum)) high ('1') when you write to that RegisterN.

Can you think of something more compact?

So to summarize. Use 'N' and 'M' correctly Noting the entity RegisterN also needs to have N as a generic or supplied constant). Remove the write part of the process statement and add a concurrent statement in the generate statement to produce the individual RegisterN loads.


To start with, your entity is going to need a second set of ReadNum, ReadEnable and Dout ports. Is this enough of a hint?

  • \$\begingroup\$ and there's a way to save the second READNUM port if you don't need to write in the same cycle. \$\endgroup\$
    – user16324
    Feb 22, 2014 at 17:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.