Search code examples
vhdlram

Implementing simple dual port block ram in VHDL not performing as expected


I have been trying to implement a simple dual port block RAM in VHDL but it does not yield the expected results in simulation. Here is the code:

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;

entity rams is
generic ( g_adress_width: integer:= 18;
          g_ram_size: integer:= 1000
        );
 port(
    clka : in std_logic;
    clkb : in std_logic;
    wea : in std_logic;
    web : in std_logic;
    addra : in std_logic_vector(g_adress_width-1 downto 0);
    addrb : in std_logic_vector(g_adress_width-1 downto 0);
    dia : in std_logic_vector(15 downto 0);
    dib : in std_logic_vector(15 downto 0);
    doa : out std_logic_vector(15 downto 0);
    dob : out std_logic_vector(15 downto 0));
end rams;
architecture syn of rams is
    type ram_type is array (g_ram_size-1 downto 0) of std_logic_vector(15 downto 0);
     signal RAM : ram_type; 
begin
 process (CLKA)
 begin
    if CLKA'event and CLKA = '1' then
            DOA <= RAM(conv_integer(ADDRA));
            if WEA = '1' then --always 0
                RAM(conv_integer(ADDRA)) <= DIA; --does not execute
        end if;
    end if;
 end process;

 process (CLKB)
 begin
 if CLKB'event and CLKB = '1' then
        DOB <= RAM(conv_integer(ADDRB));
        if WEB = '1' then
            RAM(conv_integer(ADDRB)) <= DIB;
    end if;
 end if;

end process;
end syn; 

And here is the simulation:

Simulation

Both clka and clkb are connected to the same clock. I give some arbitrary values (unsigned 300 and 355) to dib.

Basically, doa is always reading so I expect it to be undefined until something has been written to those block ram addresses with dib, but it is showing undefined values always.

What I expect to happen is for doa to read 300 when addra is at 0 again and to read 355 when addra is at 15. Something like this (pardon my paint skills):

enter image description here

Would appreciate it if someone could point me in the right direction of what I am doing wrong. Thanks.

EDIT: Code is modified to this and it works now (thanks to Paebbels solution):

     signal RAM : ram_type; 
begin
 process (CLKA)
 begin
    if CLKA'event and CLKA = '1' then
            DOA <= RAM(to_integer(unsigned(ADDRA)));
            if WEA = '1' then --always 0
                    RAM(to_integer(unsigned(ADDRA))) <= DIA; --does not happen
        end if;

    end if;

    if CLKA'event and CLKA = '1' then
        DOB <= RAM(to_integer(unsigned(ADDRB)));
        if WEB = '1' then
            RAM(to_integer(unsigned(ADDRB))) <= DIB;
    end if;
 end if;

 end process;
end syn; 

Solution

  • This description of a dual-clock RAM is wrong. You need to use either:

    • a process with two clocks, or
    • a shared variable.

    Using one signal and two processes ist not correct. It creates multiple drives on a signal. This in turn creates a multiple source problem. While your simulation will work, because of the resolved type std_logic_vector in your user defined array type, synthesis will fail.

    In addition, to allow inference of BlockRAMs, you need to represent the internal structure of BlockRAMs in you VHDL code. This means you need to add pipeline registers on the address path.

    You should read UG901 - Vivado Synthesis Guide and search for "RAM HDL Coding Techniques".

    Further more, you should use package numeric_std instead of std_logic_unsigned, which is not an official IEEE package.


    A working true-dual-port (TDP) BlockRAM implementation can be found in the PoC-Library: PoC.mem.ocram.tdp. This implementation will also work on Altera/Intel FPGAs and on Lattice FPGAs.

        -- RAM can be inferred correctly only if '-use_new_parser yes' is enabled in XST options
        subtype word_t is std_logic_vector(D_BITS - 1 downto 0);
        type    ram_t  is array(0 to DEPTH - 1) of word_t;
    
        signal ram          : ram_t;
        signal a1_reg       : unsigned(A_BITS-1 downto 0);
        signal a2_reg       : unsigned(A_BITS-1 downto 0);
    
    begin
    
        process (clk1, clk2)
        begin   -- process
            if rising_edge(clk1) then
                if ce1 = '1' then
                    if we1 = '1' then
                        ram(to_integer(a1)) <= d1;
                    end if;
    
                    a1_reg <= a1;
                end if;
            end if;
    
            if rising_edge(clk2) then
                if ce2 = '1' then
                    if we2 = '1' then
                        ram(to_integer(a2)) <= d2;
                    end if;
    
                    a2_reg <= a2;
                end if;
            end if;
        end process;
    
        q1 <= (others => 'X') when SIMULATION and is_x(std_logic_vector(a1_reg)) else
                    ram(to_integer(a1_reg));        -- returns new data
        q2 <= (others => 'X') when SIMULATION and is_x(std_logic_vector(a2_reg)) else
                    ram(to_integer(a2_reg));        -- returns new data