Intel® Quartus® Prime Software
Intel® Quartus® Prime Design Software, Design Entry, Synthesis, Simulation, Verification, Timing Analysis, System Design (Platform Designer, formerly Qsys)
17049 Discussions

How to instantiate a dual RAM with different widths on the R/W port

Altera_Forum
Honored Contributor II
4,801 Views

Hi, 

the dual RAM: 

Write port:128×16-bits 

reading port: 2048×1-bit 

 

I pefer HDL design input such VHDL or Verilog, please DO NOT tell me to instantiate this dual RAM by megawizard:) 

Thanks a lot!
0 Kudos
14 Replies
Altera_Forum
Honored Contributor II
2,971 Views

Altera altsyncram MegaFunction is actually the low level primitive used by Quartus, when inferring RAM from HDL code, you may want to check the physical mapping of your designs. It can be also instantiated in HDL directly, which is, as far I'm aware of, the only way to create dual port RAM with different width. At least, I'm usually going this way. As far as I understand, you don't want to hear about.

0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

Ive had this very problem myself: 

 

Solution: Either use the megawizard or include the altera_mf library and instantiate an altsyncram manually. Unfortunately this is no way to infer an altsyncram with different sized in and out ports, and had it confirmed from altera themselves. I have raised it as an enhancement request, so if enough people ask for it, maybe it will appear one day. 

 

As a point of note: Xilinx, synopsis and Mentor tools cant do it either.
0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

I must admit, that I don't even see how a dual port RAM with different port widths could be declared unequivocally in VHDL. Do you have a suggestion?

0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

Something like this? (this infers dual port ram fine when addr_a/b and data_a/q_b sizes match) : 

 

entity test_build is port ( clka : in std_logic; clkb : in std_logic; addr_a : in natural range 0 to 127; data_a : in std_logic_vector(63 downto 0); addr_b : in natural range 0 to 511; q_b : out std_logic_vector(15 downto 0) ); end entity; architecture rtl of test_build is type mem_t is array(0 to 511) of std_logic_vector(15 downto 0); signal mem : mem_t; signal addr_b_r : integer; begin process(clka) begin if rising_edge(clka) then mem(addr_a*4) <= data_a(15 downto 0); mem(addr_a*4+1) <= data_a(31 downto 16); mem(addr_a*4+2) <= data_a(47 downto 32); mem(addr_a*4+3) <= data_a(63 downto 48); end if; end process; process(clkb) begin if rising_edge(clkb) then addr_b_r <= addr_b; end if; end process; q_b <= mem(addr_b_r); --alt_ram : altsyncram --generic map ( -- Width_a => 64, -- width_b => 16, -- -- widthad_a => 7, -- widthad_b => 9 -- --) --port map ( -- -- clock0 => clka, -- clock1 => clkb, -- -- data_a => data_a, -- address_a => std_logic_vector(to_unsigned(addr_a, 7) ), -- -- address_b => std_logic_vector(to_unsigned(addr_b, 9) ), -- q_b => q_b -- --); end architecture rtl;
0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

I am not sure if this would work 

 

 

type mem_t is array(0 to 511) of std_logic_vector(15 downto 0); 

signal mem : mem_t; 

 

 

Won't this still infer a memory that is 16 bits wide on both ports? 

 

I guess the inference rules for memories are ambiguous and tool dependent. 

 

The "not to be named" mega-function wizard seems the safest way;)
0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

This is the point, you cannot currently infer a memory with missmatched in/out port sizes. The code was one suggested way it "might" be done. 

 

The synthesiser would have to look at the behaviour around the code rather than the declarations, to infer the memory correctly.
0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

Thank you for elaborating the idea. Thinking about similar options, I see a problem that it would blow up the rules of "how to infer RAM from HDL" considerably. They are partly difficult yet, e.g. regarding the registered/unregistered features of internal RAM and handling of simultanous write accesses. Thus I think, it may an acceptable idea to leave it as is, requiring explicite altsyncram instantiation for the complex cases.

0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

Example from application note 226: 

 

LIBRARY ieee; USE ieee.std_logic_1164.all; USE ieee.std_logic_signed.all; ENTITY dualport_ram IS PORT ( data_out : OUT STD_LOGIC_VECTOR (63 DOWNTO 0); data_in : IN STD_LOGIC_VECTOR (63 DOWNTO 0); wr_addr : IN STD_LOGIC_VECTOR (6 DOWNTO 0); rd_addr : IN STD_LOGIC_VECTOR (6 DOWNTO 0); we : IN STD_LOGIC; clk : IN STD_LOGIC); END dualport_ram; ARCHITECTURE ram_infer OF dualport_ram IS TYPE Mem_Type IS ARRAY (127 DOWNTO 0) OF STD_LOGIC_VECTOR (63 DOWNTO 0); SIGNAL mem: Mem_Type; SIGNAL addr_reg: STD_LOGIC_VECTOR (6 DOWNTO 0); BEGIN data_out <= mem (CONV_INTEGER(rd_addr)); PROCESS (clk, we, data_in) BEGIN IF (clk=’1’ AND clk’EVENT) THEN IF (we=’1’) THEN mem(CONV_INTEGER(wr_addr)) <= data_in; END IF; END IF; END PROCESS; END ram_infer;  

For your second port, use an address translation and bit selection from 16 to 64 in an upper module. 

 

Edit: copy and past issue: thanks to Tricky for the feedback.
0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

That will never work - the mem_type stores 8 bit values, while data in is 64 bits wide.

0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

 

--- Quote Start ---  

For your second port, use an address translation and bit selection from 16 to 64 in an upper module. 

 

--- Quote End ---  

 

 

Thats fine, but its not as elegant as the direct instantiation - it will add muxes on the output. Its not terrible, just not as compact.
0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

Quartus II 8.0 fail to infer following code to RAM: 

 

library IEEE; 

use IEEE.STD_LOGIC_1164.ALL; 

use IEEE.STD_LOGIC_ARITH.ALL; 

use IEEE.STD_LOGIC_UNSIGNED.ALL; 

 

entity test_build is 

 

 

port ( 

clka : in std_logic; 

clkb : in std_logic; 

 

addr_a : in std_logic_vector(6 downto 0);--in natural range 0 to 127; 

data_a : in std_logic_vector(63 downto 0); 

 

 

addr_b : in std_logic_vector(8 downto 0 );--in natural range 0 to 511; 

q_b : out std_logic_vector(15 downto 0) 

 

); 

end entity; 

 

architecture rtl of test_build is 

 

-- Build a 2-D array type for the RAM 

type word_t is array (2**9-1 downto 0) of std_logic_vector (15 downto 0); 

 

 

shared variable mem : word_t;  

signal addr_b_r : std_logic_vector(8 downto 0);--integer;  

 

begin 

 

process(clka) 

begin 

if rising_edge(clka) then  

--mem(conv_integer(addr_a)) := data_a; 

mem(conv_integer(addr_a) * 4+0) := data_a(15 downto 0); 

mem(conv_integer(addr_a) * 4+1) := data_a(31 downto 16); 

mem(conv_integer(addr_a) * 4+2) := data_a(47 downto 32); 

mem(conv_integer(addr_a) * 4+3) := data_a(63 downto 48); 

end if; 

end process; 

 

--rd_addr_t <= std_logic_vector(to_unsigned(addr_b, 9) ); 

 

process(clkb) 

begin 

if rising_edge(clkb) then 

addr_b_r <= addr_b; 

q_b <= mem(conv_integer(addr_b_r)); 

 

-- case (addr_b_r(1 downto 0)) is 

-- when "11" => 

-- q_b <= q_t(63 downto 48); 

-- when "10" => 

-- q_b <= q_t(47 downto 32); 

-- when "01" => 

-- q_b <= q_t(31 downto 16); 

-- when "00" => 

-- q_b <= q_t(15 downto 0); 

-- when others => 

-- q_b <= x"0000";  

-- end case;  

 

end if; 

end process; 

 

--q_b <= mem(addr_b_r); 

 

 

--alt_ram : altsyncram 

--generic map ( 

-- Width_a => 64, 

-- width_b => 16, 

--  

-- widthad_a => 7, 

-- widthad_b => 9 

--  

--) 

--port map (  

--  

-- clock0 => clka, 

-- clock1 => clkb, 

--  

-- data_a => data_a, 

-- address_a => std_logic_vector(to_unsigned(addr_a, 7) ), 

--  

-- address_b => std_logic_vector(to_unsigned(addr_b, 9) ), 

-- q_b => q_b 

--  

--); 

 

 

end architecture rtl; 

 

As Tricky posted, it's not a bad idea to add muxes on the output, like the quoted part using "case" above. U need to asure the width of R/W ports is same, as well as the address.
0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

I don't see a purpose of using a shared variable for RAM inference, also I doubt if it's supported in this place by Quartus? It's never mentioned in the Quartus Handbook.  

 

In the above code, you are trying to write to four consecutive RAM addresses simultanously. This can't work in a single clock cycle. 

if rising_edge(clka) then --mem(conv_integer(addr_a)) := data_a; mem(conv_integer(addr_a) * 4+0) := data_a(15 downto 0); mem(conv_integer(addr_a) * 4+1) := data_a(31 downto 16); mem(conv_integer(addr_a) * 4+2) := data_a(47 downto 32); mem(conv_integer(addr_a) * 4+3) := data_a(63 downto 48); end if; 

If you want to emulate different port width's with equal port width RAM, you must do it the other way around: Define the RAM width the larger width and use individual byteenable signals, when writing to the smaller port is intended
0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

Shared variables should not be synthesizable, but it appears they are in Quartus!.

0 Kudos
Altera_Forum
Honored Contributor II
2,971 Views

Quartus II 8.0 DO infer following code to dual port RAM: 

 

library IEEE; 

use IEEE.STD_LOGIC_1164.ALL; 

use IEEE.STD_LOGIC_ARITH.ALL; 

use IEEE.STD_LOGIC_UNSIGNED.ALL; 

--use ieee.numeric_std.all; 

 

entity test_build is 

generic  

DATA_WIDTH : natural := 64; 

ADDR_WIDTH : natural := 7 

); 

 

port ( 

clka : in std_logic; 

clkb : in std_logic; 

wren : in std_logic := '1'; 

addr_a : in std_logic_vector(ADDR_WIDTH-1 downto 0);--in natural range 0 to 127; 

data_a : in std_logic_vector(DATA_WIDTH-1 downto 0); 

rden : in std_logic := '1';  

addr_b : in std_logic_vector(ADDR_WIDTH-1 downto 0 );--in natural range 0 to 511; 

q_b : out std_logic_vector(15 downto 0) 

 

); 

end entity; 

 

architecture rtl of test_build is 

 

--type mem_t is array(0 to 511) of std_logic_vector(15 downto 0); 

-- Build a 2-D array type for the RAM 

type word_t is array (2**ADDR_WIDTH-1 downto 0) of std_logic_vector (DATA_WIDTH-1 downto 0); 

 

shared variable mem : word_t; 

--attribute ramstyle : string; 

--attribute ramstyle of mem : variable is "no_rw_check"; 

 

signal addr_b_r : std_logic_vector(ADDR_WIDTH-1 downto 0);--integer;  

signal q_t : std_logic_vector(DATA_WIDTH-1 downto 0); 

 

begin 

 

process(clka) 

begin 

if rising_edge(clka) then  

if(wren = '1') then 

mem(conv_integer(addr_a)) := data_a; 

-- mem(conv_integer(addr_a) * 4+0) := data_a(15 downto 0); 

-- mem(conv_integer(addr_a) * 4+1) := data_a(31 downto 16); 

-- mem(conv_integer(addr_a) * 4+2) := data_a(47 downto 32); 

-- mem(conv_integer(addr_a) * 4+3) := data_a(63 downto 48); 

end if; 

end if; 

end process; 

 

process(clkb) 

begin 

if rising_edge(clkb) then 

if(rden = '1') then 

addr_b_r <= addr_b; 

q_t <= mem(conv_integer(addr_b_r)); 

--q_b <= q_t(to_unsigned(addr_b(1 downto 0))); 

--q_b <= mem(conv_integer(addr_b_r)); 

end if; 

case (addr_b_r(1 downto 0)) is 

when "11" => 

q_b <= q_t(63 downto 48); 

when "10" => 

q_b <= q_t(47 downto 32); 

when "01" => 

q_b <= q_t(31 downto 16); 

when "00" => 

q_b <= q_t(15 downto 0); 

when others => 

q_b <= x"0000";  

end case;  

 

end if; 

end process; 

 

--q_b <= mem(addr_b_r); 

 

 

--alt_ram : altsyncram 

--generic map ( 

-- Width_a => 64, 

-- width_b => 16, 

--  

-- widthad_a => 7, 

-- widthad_b => 9 

--  

--) 

--port map (  

--  

-- clock0 => clka, 

-- clock1 => clkb, 

--  

-- data_a => data_a, 

-- address_a => std_logic_vector(to_unsigned(addr_a, 7) ), 

--  

-- address_b => std_logic_vector(to_unsigned(addr_b, 9) ), 

-- q_b => q_b 

--  

--); 

 

 

end architecture rtl; 

 

Well the data width of R/W port must be the same the R/W enable signals are necessary.Even using "Shared variables", it's easy synthesized by Quartus II 8.0! 

Unfortuntly, I can not use"ramstyle " attribution with "no_rw_check" when using "Shared variables", because "ramstyle " ONLY for "signal" NOT for "variables" as follow code frage: 

signal mem : word_t; 

attribute ramstyle : string; 

attribute ramstyle of mem : signal is "no_rw_check";
0 Kudos
Reply