Quartus® Prime Pro Edition User Guide: Design Recommendations

ID 683082
Date 7/08/2024
Public
Document Table of Contents

1.4.2. Inferring ROM Functions from HDL Code

Synthesis tools infer ROMs when a CASE statement exists in which a value is set to a constant for every choice in the CASE statement.

Because small ROMs typically achieve the best performance when they are implemented using the registers in regular logic, each ROM function must meet a minimum size requirement for inference and placement in memory.

For device architectures with synchronous RAM blocks, to infer a ROM block, synthesis must use registers for either the address or the output. When your design uses output registers, synthesis implements registers from the input registers of the RAM block without affecting the functionality of the ROM. If you register the address, the power-up state of the inferred ROM can be different from the HDL design. In this scenario, Quartus® Prime synthesis issues a warning.

The following ROM examples map directly to the Intel FPGA memory architecture.

Verilog HDL Synchronous ROM

module sync_rom (clock, address, data_out);
	input clock;
	input [7:0] address;
	output reg [5:0] data_out;
	reg [5:0] data_out;

	always @ (posedge clock)
	begin
		case (address)
			8'b00000000: data_out = 6'b101111;
			8'b00000001: data_out = 6'b110110;
			...
			8'b11111110: data_out = 6'b000001;
			8'b11111111: data_out = 6'b101010;
		endcase
	end
endmodule

VHDL Synchronous ROM

LIBRARY ieee;
USE ieee.std_logic_1164.all;

ENTITY sync_rom IS
	PORT (
		clock: IN STD_LOGIC;
		address: IN STD_LOGIC_VECTOR(7 downto 0);
		data_out: OUT STD_LOGIC_VECTOR(5 downto 0)
	);
END sync_rom;

ARCHITECTURE rtl OF sync_rom IS
BEGIN
PROCESS (clock)
	BEGIN
	IF rising_edge (clock) THEN
		CASE address IS
			WHEN "00000000" => data_out <= "101111";
			WHEN "00000001" => data_out <= "110110";
			...
			WHEN "11111110" => data_out <= "000001";
			WHEN "11111111" => data_out <= "101010";
			WHEN OTHERS     => data_out <= "101111";
		END CASE;
	END IF;
	END PROCESS;
END rtl;

Verilog HDL Dual-Port Synchronous ROM Using readmemb

module dual_port_rom
#(parameter data_width=8, parameter addr_width=8)
(
	input [(addr_width-1):0] addr_a, addr_b,
	input clk, 
	output reg [(data_width-1):0] q_a, q_b
);
	reg [data_width-1:0] rom[2**addr_width-1:0];

	initial // Read the memory contents in the file
			 //dual_port_rom_init.txt. 
	begin
		$readmemb("dual_port_rom_init.txt", rom);
	end

	always @ (posedge clk)
	begin
		q_a <= rom[addr_a];
		q_b <= rom[addr_b];
	end
endmodule

VHDL Dual-Port Synchronous ROM Using Initialization Function

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity dual_port_rom is
	generic (
		DATA_WIDTH : natural := 8;
		ADDR_WIDTH : natural := 8
	);
	port (
		clk     : in std_logic;
		addr_a  : in natural range 0 to 2**ADDR_WIDTH - 1;
		addr_b  : in natural range 0 to 2**ADDR_WIDTH - 1;
		q_a     : out std_logic_vector((DATA_WIDTH -1) downto 0);
		q_b     : out std_logic_vector((DATA_WIDTH -1) downto 0)
	);
end entity;

architecture rtl of dual_port_rom is
	-- Build a 2-D array type for the ROM
	subtype word_t is std_logic_vector((DATA_WIDTH-1) downto 0);
	type memory_t is array(2**ADDR_WIDTH - 1 downto 0) of word_t;

	function init_rom
		return memory_t is 
		variable tmp : memory_t := (others => (others => '0'));
	begin 
		for addr_pos in 0 to 2**ADDR_WIDTH - 1 loop 
			-- Initialize each address with the address itself
			tmp(addr_pos) := std_logic_vector(to_unsigned(addr_pos, DATA_WIDTH));
		end loop;
		return tmp;
	end init_rom;	 

	-- Declare the ROM signal and specify a default initialization value.
	signal rom : memory_t := init_rom;
begin
	process(clk)
	begin
	if (rising_edge(clk)) then
		q_a <= rom(addr_a);
		q_b <= rom(addr_b);
	end if;
	end process;
end rtl;