Getting XST (Xilinx' synthesis tool) to infer RAM or ROM that is dual-port requires some tricks.
For some reason, the two ports must be described by separate processes. Furthermore, an unusual VHDL construct, a shared variable
, is needed 1.
Below is a listing of my parameterized module for dual-port RAM. It will successfully infer dual-port RAM, as desired, with XST. Remove the write enable-signals and write logic to get ROM instead of RAM. Specify width and depth with width
and highAddr
(highAddr
is one less than desired depth) generics.
The code
use IEEE.STD_LOGIC_1164.all;
entity genRAM is
generic(
width : integer;
highAddr : integer -- highest address (= size-1)
);
port(
-- Two sets of ports (A and B), each set having ports Adress, Data in,
-- Data out and Write enable:
Aaddr : in integer range 0 to highAddr := 0;
ADI : in std_logic_vector(width-1 downto 0) := (others => '0');
ADO : out std_logic_vector(width-1 downto 0) := (others => '0');
AWE : in std_logic := '0';
Baddr : in integer range 0 to highAddr := 0;
BDI : in std_logic_vector(width-1 downto 0) := (others => '0');
BDO : out std_logic_vector(width-1 downto 0) := (others => '0');
BWE : in std_logic := '0';
clk : in std_logic
);
end genRAM;
architecture arch of genRAM is
subtype TmemWord is bit_vector(width-1 downto 0);
type Tmem is array(0 to highAddr) of TmemWord;
shared variable memory: Tmem;
process(clk) is
begin
if (rising_edge(clk)) then
ADO <= To_StdLogicVector(memory(Aaddr));
if (AWE = '1') then
memory(Aaddr) := To_bitvector(std_logic_vector(ADI));
end if;
end if;
end process;
process(clk) is
begin
if (rising_edge(clk)) then
BDO <= To_StdLogicVector(memory(Baddr));
if (BWE = '1') then
memory(Baddr) := To_bitvector(std_logic_vector(BDI));
end if;
end if;
end process;
end arch;
Read-first and write-first
The code above implements read-first behavior. That means that if address 0x00
contains 0xcafe
and you write 0xbeef
to 0x00
, the cycle after the write will display 0xcafe
on the data-out port ("data is read to output port before being written to memory").
If you desire write-first behaviour, change order of the reading and writing for both processes, below is how it would be for port A:
if (AWE = '1') then
memory(Aaddr) := To_bitvector(std_logic_vector(ADI));
end if;
ADO <= To_StdLogicVector(memory(Aaddr));
In the above case, data-out would display 0xbeef
one cycle after the write ("data is written to memory before reading memory contents to output port").
XST synthesis output
When identifying dual-port RAM, Synthesis should ouput something similar to the following in the synthesis report:
INFO:Xst:3040 - The RAM <memory> will be implemented as a BLOCK RAM,
absorbing the following register(s): <ADO> <BDO>
-----------------------------------------------------------------------
| ram_type | Block | |
-----------------------------------------------------------------------
| Port A |
| aspect ratio | 128-word x 10-bit | |
| mode | write-first | |
| clkA | connected to signal <clk> | rise |
| weA | connected to signal <AWE> | high |
| addrA | connected to signal <Aaddr> | |
| diA | connected to signal <ADI> | |
| doA | connected to signal <ADO> | |
-----------------------------------------------------------------------
| optimization | speed | |
-----------------------------------------------------------------------
| Port B |
| aspect ratio | 128-word x 10-bit | |
| mode | write-first | |
| clkB | connected to signal <clk> | rise |
| weB | connected to signal <BWE> | high |
| addrB | connected to signal <Baddr> | |
| diB | connected to signal <BDI> | |
| doB | connected to signal <BDO> | |
-----------------------------------------------------------------------
| optimization | speed | |
-----------------------------------------------------------------------
Unit <genRAM> synthesized (advanced).
Notes:
- The variable must be accessible through two different processes, and hence must be shared. A signal wouldn't have worked; to implement write-first behaviour in a convenient way, a variable must be used, since the data written must be accessible on the next line of code. ↩
Interesting, so the reason why a shared variable is used is to have write-first behavior! And here I was thinking it was because it was "the only kind of thing that can be shared among two processes".
I have noticed that you don't need two processes nor weird shared variable trickery for a read-first BRAM; you can simply use a regular signal for the memory and make a single process (clkA, clkB) with two separate if rising_edge(clkX) inside. It will work fine for both Vivado and XST for Virtex-6 and Spartan-6 (but NOT for older FPGAs), and also having a single process probably makes things more portable and easier to simulate.
> Interesting, so the reason why a shared variable is used is to have write-first behavior! And here I was thinking it was because it was "the only kind of thing that can be shared among two processes".
It's rather the latter than the former, however, you are correct in saying that a shared variable wouldn't be necessary for read-first behaviour.
However, the main reason that a shared variable is needed is that XST want the two BRAM ports do be described by two separate processes. On the same time, since there is one single memory, the actions of one of the processes must be immediately seen by the other process. The only VHDL construct to immediately share a change of some state between two separate processes is a shared variable.