Forum Discussion

BLee15's avatar
BLee15
Icon for Occasional Contributor rankOccasional Contributor
7 years ago

Inferring RAM with read enable

To learn VHDL, I'm implementing my own custom CPU with VHDL.

I'm implementing memory-mapped IO, which access traditional RAM and various I/O peripherals as same manner from the viewpoint of user code.

As multiple peripherals will share data bus, I implemented read enable signal and use hi-Z state like this:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.utility.all;
 
entity onchip_ram is
	generic (
		addr_width: integer;
		data_width: integer
	);
	port (
		clock: in std_logic;
		addr: in std_logic_vector(addr_width-1 downto 0);
		rq: out std_logic_vector(data_width-1 downto 0);
		wq: in std_logic_vector(data_width-1 downto 0);
		re: in std_logic;
		we: in std_logic
	);
end;
 
architecture rtl of onchip_ram is
	type memory_t is array(2**addr_width-1 downto 0) of std_logic_vector(data_width-1 downto 0);
	
	signal ram: memory_t;
	signal addr_num: integer;
begin
	addr_num <= decode_unsigned(addr);
	process(clock)
	begin
		if rising_edge(clock) then
			if std_match(we, '1') then
				ram(addr_num) <= wq;
			end if;
			if std_match(re, '1') then
				rq <= ram(addr_num);
			else
				rq <= (others => 'Z');
			end if;
		end if;
	end process;
end;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.utility.all;
 
entity data_memory_controller is
	port (
		clock: in std_logic;
		addr: in std_logic_vector(31 downto 0);
		rq: out std_logic_vector(31 downto 0);
		wq: in std_logic_vector(31 downto 0);
		re: in std_logic;
		we: in std_logic;
		
		ledr: out std_logic_vector(9 downto 0);
		sw: in std_logic_vector(9 downto 0);
		hex0: out std_logic_vector(0 to 7);
		hex1: out std_logic_vector(0 to 7);
		hex2: out std_logic_vector(0 to 7);
		hex3: out std_logic_vector(0 to 7);
		hex4: out std_logic_vector(0 to 7);
		hex5: out std_logic_vector(0 to 7)
	);
end;
 
architecture rtl of data_memory_controller is	
	signal c0, c1, c2, c3: std_logic;
begin
	c0 <= '1' when std_match(addr, "000000000000000000000000--------") else '0';
	c1 <= '1' when std_match(addr, "10000000000000000000000000000000") else '0';
	c2 <= '1' when std_match(addr, "10000000000000000000000000000001") else '0';
	c3 <= '1' when std_match(addr, "1000000000000000000000000000001-") else '0';
	
	onchip_ram_c: entity work.onchip_ram generic map(
		addr_width => 8,
		data_width => 32
	) port map (
		clock => clock,
		addr => addr(7 downto 0),
		rq => rq,
		wq => wq,
		re => re and c0,
		we => we and c0
	);
	
	ledr_c: entity work.ledr_controller port map(
		clock => clock,
		rq => rq,
		wq => wq,
		re => re and c1,
		we => we and c1,
		ledr => ledr
	);
	
	sw_c: entity work.sw_controller port map(
		clock => clock,
		rq => rq,
		re => re and c2,
		sw => sw
	);
	
	hex_c: entity work.hex_controller port map(
		clock => clock,
		addr => addr(0 downto 0),
		rq => rq,
		wq => wq,
		re => re and c3,
		we => we and c3,
		hex0 => hex0,
		hex1 => hex1,
		hex2 => hex2,
		hex3 => hex3,
		hex4 => hex4,
		hex5 => hex5
	);
end;

This successfully infers on-chip RAM and compile has been successful, with the following warning: "Warning (13046): Tri-state node(s) do not directly drive top-level pin(s)" and 32 instances of "Warning (13048): Converted tri-state node "data_memory_controller:data_memory_controller_c|rq[<31 downto 0>]" into a selector"

To elimate this warning, I tried to change to wired-or: if chip is not read enabled, output 0's, and then, or-ing all perpheral outputs.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.utility.all;
 
entity onchip_ram is
	generic (
		addr_width: integer;
		data_width: integer
	);
	port (
		clock: in std_logic;
		addr: in std_logic_vector(addr_width-1 downto 0);
		rq: out std_logic_vector(data_width-1 downto 0);
		wq: in std_logic_vector(data_width-1 downto 0);
		re: in std_logic;
		we: in std_logic
	);
end;
 
architecture rtl of onchip_ram is
	type memory_t is array(2**addr_width-1 downto 0) of std_logic_vector(data_width-1 downto 0);
	
	signal ram: memory_t;
	signal addr_num: integer;
begin
	addr_num <= decode_unsigned(addr);
	process(clock)
	begin
		if rising_edge(clock) then
			if std_match(we, '1') then
				ram(addr_num) <= wq;
			end if;
			if std_match(re, '1') then
				rq <= ram(addr_num);
			else
				rq <= (others => '0'); -- Here is changed
			end if;
		end if;
	end process;
end;
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.utility.all;
 
entity data_memory_controller is
	port (
		clock: in std_logic;
		addr: in std_logic_vector(31 downto 0);
		rq: out std_logic_vector(31 downto 0);
		wq: in std_logic_vector(31 downto 0);
		re: in std_logic;
		we: in std_logic;
		
		ledr: out std_logic_vector(9 downto 0);
		sw: in std_logic_vector(9 downto 0);
		hex0: out std_logic_vector(0 to 7);
		hex1: out std_logic_vector(0 to 7);
		hex2: out std_logic_vector(0 to 7);
		hex3: out std_logic_vector(0 to 7);
		hex4: out std_logic_vector(0 to 7);
		hex5: out std_logic_vector(0 to 7)
	);
end;
 
architecture rtl of data_memory_controller is	
	signal c0, c1, c2, c3: std_logic;
	signal q0, q1, q2, q3: std_logic_vector(31 downto 0); -- Here is added
begin
	c0 <= '1' when std_match(addr, "000000000000000000000000--------") else '0';
	c1 <= '1' when std_match(addr, "10000000000000000000000000000000") else '0';
	c2 <= '1' when std_match(addr, "10000000000000000000000000000001") else '0';
	c3 <= '1' when std_match(addr, "1000000000000000000000000000001-") else '0';
	
	onchip_ram_c: entity work.onchip_ram generic map(
		addr_width => 8,
		data_width => 32
	) port map (
		clock => clock,
		addr => addr(7 downto 0),
		rq => q0, -- Here is changed
		wq => wq,
		re => re and c0,
		we => we and c0
	);
	
	ledr_c: entity work.ledr_controller port map(
		clock => clock,
		rq => q1, -- Here is changed
		wq => wq,
		re => re and c1,
		we => we and c1,
		ledr => ledr
	);
	
	sw_c: entity work.sw_controller port map(
		clock => clock,
		rq => q2, -- Here is changed
		re => re and c2,
		sw => sw
	);
	
	hex_c: entity work.hex_controller port map(
		clock => clock,
		addr => addr(0 downto 0),
		rq => q3, -- Here is changed
		wq => wq,
		re => re and c3,
		we => we and c3,
		hex0 => hex0,
		hex1 => hex1,
		hex2 => hex2,
		hex3 => hex3,
		hex4 => hex4,
		hex5 => hex5
	);
	rq <= q0 or q1 or q2 or q3; -- Here is added
end;

However, this failed inferring on-chip RAM with following message: "Info (276007): RAM logic "data_memory_controller:data_memory_controller_c|onchip_ram:onchip_ram_c|ram" is uninferred due to asynchronous read logic"

I'm confusing why using 'Z' for read-enable control infers RAM while using '0' uninfers.

I'm using Quartus Prime Lite 18.0.0, and target device is MAX 10 family.

1 Reply

  • Tricky's avatar
    Tricky
    Icon for Occasional Contributor rankOccasional Contributor

    First of all, FPGAs do not have internal tri-state drivers, so it is only valid to drive an IO pin with a 'Z' state, so they are removed, as the warning indicate.

    Second, your "wired-or" approach: it uses the read-enable as the select line on a mux to select either the memory read data or 0. The problem is, the internal rams do not have such a mux built in them, so it has to create the rams out of LUTs to match the functionality that you wrote.

    I wouldnt do a wired or - you will need a arbitrator to mux between the blocks. (probably just a priority encoder, as each memory should be accessed in a mutually exclusive way?)

    The synthesis handbook has the templates you need to infer rams.

    Also not: why the use of std_match everywhere? why not just re = '1'? FPGAs only have '1' or '0' state. It can be useful to be explicit, rather than allow through '-' state in simulation.