Create RAM in FPGA using VHDL
I was trying to implement dual port RAM, guided by this excellent blog post . However, ModelSim gives the following warning on compilation:
** Warning: fifo_ram.vhdl(24): (vcom-1236) Shared variables must be of a protected type.
I also cannot create this as a wave, indicating to me that the variable is not recognized using my code below.
How can I correctly declare this variable as a "protected" type? Also, as a more general question about shared variables - is this variable shared between all objects in the design?
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;
entity fifo_ram is
generic (data : natural := 8;
addr : natural := 16);
port (w_clk : in std_logic;
w_en : in std_logic;
w_addr : in std_logic_vector (addr-1 downto 0);
w_data : in std_logic_vector (data-1 downto 0);
--
r_clk : in std_logic;
r_rdy : in std_logic;
r_addr : in std_logic_vector (addr-1 downto 0);
r_data : out std_logic_vector (data-1 downto 0));
end fifo_ram;
architecture rtl of fifo_ram is
-- shared memory
type mem_type is array ( (2**addr) - 1 downto 0 ) of std_logic_vector(data-1 downto 0);
shared variable mem : mem_type;
begin
write: process (w_clk)
begin
if (rising_edge(w_clk)) then
if (w_en = '1') then
mem(conv_integer(w_addr)) := w_data;
end if;
end if;
end process write;
end architecture;
----------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;
entity tb_fifo is
generic (data : natural := 8;
addr : natural := 16);
end entity;
architecture testbed of tb_fifo is
signal tb_w_clk, tb_w_en : std_logic := '0';
signal tb_w_addr : std_logic_vector (addr-1 downto 0);
signal tb_w_data : std_logic_vector (data-1 downto 0);
signal tb_r_clk, tb_r_rdy : std_logic := '0';
signal tb_r_addr : std_logic_vector (addr-1 downto 0);
signal tb_r_data : std_logic_vector (data-1 downto 0);
begin
dut : entity work.fifo_ram(rtl)
port map(tb_w_clk, tb_w_en, tb_w_addr, tb_w_data,
tb_r_clk, tb_r_rdy, tb_r_addr, tb_r_data);
wclock : process is
begin
tb_w_clk <= '1';
wait for 10 ns;
tb_w_clk <= '0';
wait for 10 ns;
end process wclock;
wdata : process is
begin
tb_w_addr <= x"FFFF";
tb_w_data <= x"AA";
wait for 100 ns;
tb_w_en <= '1';
wait for 70 ns;
tb_w_en <= '0';
wait;
end process wdata;
end architecture;
source to share
OK, after going through the blog post, I now understand why they are using a shared variable instead of signals. This is because multiple processes are assigned to this variable, which is not possible with reg in Verilog or in VHDL signal. In this case, the synthesizer will generate an error reporting multiple mem drivers. But in order to use a shared variable in this case, you have to declare it protected. What you need to do is declare a protected datatype and then encapsulate your mem variable inside it, similar to classes in object oriented languages. Here is an example of a protected data type:
type mem_envelope is protected -- protected type declaration
variable mem : mem_type;
function GetVal( addr : integer ) return std_logic_vector(data - 1 downto 0);
function SetVal( addr : integer; val : std_logic_vector(data - 1 downto 0) ) return boolean; --may be used to indicate whether write was successfull or not
end protected mem_envelope;
Then declare a variable sharede of type mem_envelope and use the GetVal and SetVal functions to read / write the values ββin memory inside your processes.
source to share
Another way to implement True-Dual-Port (TDP) RAM is to use one process with two clocks.
signal ram : ram_t;
signal a1_reg : unsigned(A_BITS-1 downto 0);
signal a2_reg : unsigned(A_BITS-1 downto 0);
....
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 <= ram(to_integer(a1_reg)); -- returns new data
q2 <= ram(to_integer(a2_reg)); -- returns new data
It's even synthesized with Xilinx instruments. Altera tools need the altsyncram macro to properly recognize TDP-RAM.
Source: PoC.mem.ocram.tdp
source to share