Inferring RAM
The following sections provide example for simple and true dual-port inferencing.
Simple Dual-Port Memory Examples
The following example infers a 512 x 8 RAM. Because both writes and reads are
performed with a blocking statement and the write occurs before the read in the
always block, the software infers a simple dual ported RAM in
WRITE_FIRST mode.
module ram512x8_sp(wdata, addr, clk, we, rdata);
parameter AWIDTH = 9;
parameter DWIDTH = 8;
localparam DEPTH = 1 << AWIDTH;
localparam MAX_DATA = (1<<DWIDTH)-1;
input [DWIDTH-1:0] wdata;
input [AWIDTH-1:0] addr;
input clk, we;
output reg [DWIDTH-1:0] rdata;
reg [DWIDTH-1:0] mem [DEPTH-1:0];
// Blocking Statement and order indicates write before read
always@(posedge clk) begin
if (we) begin
mem[addr] = wdata;
end
rdata = mem[addr];
end
endmodule
If the read and write clocks are different, the software configures the memory primitive as READ_UNKNOWN. That is, if you read and write to the same address at the same time, the read data is indeterministic.
module ram512x8_sp(wdata, addr, rclk, re, wclk, we, rdata);
parameter AWIDTH = 9;
parameter DWIDTH = 8;
localparam DEPTH = 1 << AWIDTH;
localparam MAX_DATA = (1<<DWIDTH)-1;
input [DWIDTH-1:0] wdata;
input [AWIDTH-1:0] addr;
input rclk, re, wclk, we;
output reg [DWIDTH-1:0] rdata;
reg [DWIDTH-1:0] mem [DEPTH-1:0];
// different read and write clock, forces READ_UNKNOWN mode
always@(posedge wclk) begin
if (we) begin
mem[addr] = wdata;
end
end
always@(posedge rclk) begin
if (re) begin
rdata = mem[addr];
end
end
endmodule
// 16-bit wide, 512 depth, byte-enabled
// fits into 1 Titanium 10K blockram
module ram10_be1 #(
parameter integer wrAddressWidth = 9, // 512 depth
parameter integer wrDataWidth = 16, // 16-bit wide
parameter integer wrMaskWidth = 2,
parameter integer rdAddressWidth = 9,
parameter integer rdDataWidth = 16
)(
input wr_clk,
input wr_en,
input [wrMaskWidth-1:0] wr_mask,
input [wrAddressWidth-1:0] wr_addr,
input [wrDataWidth-1:0] wr_data,
input rd_clk,
input rd_en,
input [rdAddressWidth-1:0] rd_addr,
output [rdDataWidth-1:0] rd_data
);
reg [wrDataWidth-1:0] ram_block [(2**wrAddressWidth)-1:0];
integer i;
localparam COL_WIDTH = wrDataWidth/wrMaskWidth;
always @ (posedge wr_clk) begin
if(wr_en) begin
for(i=0;i<wrMaskWidth;i=i+1) begin
if(wr_mask[i]) begin // byte-enable
ram_block[wr_addr][i*COL_WIDTH +: COL_WIDTH] <= wr_data[i*COL_WIDTH +:COL_WIDTH];
end
end
end
end
reg [rdDataWidth-1:0] ram_rd_data;
always @ (posedge rd_clk) begin
if(rd_en) begin
ram_rd_data <= ram_block[rd_addr];
end
end
assign rd_data = ram_rd_data;
endmodule
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
-- 512 x 32, with 4 byte-enable signals
entity Memory is
generic (ADDR_WIDTH: integer := 9);
Port ( DBOut : out STD_LOGIC_VECTOR (31 downto 0);
DBIn : in STD_LOGIC_VECTOR (31 downto 0);
AdrBus : in STD_LOGIC_VECTOR (ADDR_WIDTH-1 downto 0);
ENA : in STD_LOGIC;
WREN : in STD_LOGIC_VECTOR (3 downto 0);
CLK : in STD_LOGIC
);
end Memory;
architecture Behavioral of Memory is
constant SIZE : natural := 2**ADDR_WIDTH;
type tRam is array (0 to SIZE-1) of STD_LOGIC_VECTOR (31 downto 0);
subtype tWord is std_logic_vector(31 downto 0);
signal ram : tRam;
signal DOA,DIA : tWord;
signal WEA : STD_LOGIC_VECTOR (3 downto 0);
begin
DIA<=DBIn;
DBOut<=DOA;
WEA<=WREN;
process(clk)
variable adr : integer;
begin
if rising_edge(clk) then
if ena = '1' then
adr := to_integer(unsigned(AdrBus));
for i in 0 to 3 loop
if WEA(i) = '1' then
ram(adr)((i+1)*8-1 downto i*8)<= DIA((i+1)*8-1 downto i*8);
end if;
end loop;
DOA <= ram(adr);
end if;
end if;
end process;
end Behavioral;
True Dual-Port Memory Examples
In true dual-port RAM, the two ports have independent read and write functions. Each port supports different write modes. The following example shows how to implement a 512 x 8 RAM block with READ_FIRST write mode for port A and WRITE_FIRST mode for port B.
module ram512x8_tdp_mix (wdataA, addrA, clkA, weA, rdataA, wdataB, addrB, clkB, weB, rdataB);
parameter AWIDTH = 9;
parameter DWIDTH = 8;
localparam DEPTH = 1 << AWIDTH;
localparam MAX_DATA = (1<<DWIDTH)-1;
input [DWIDTH-1:0] wdataA, wdataB;
input [AWIDTH-1:0] addrA, addrB;
input clkA, weA;
input clkB, weB;
output reg [DWIDTH-1:0] rdataA, rdataB;
reg [DWIDTH-1:0] mem [DEPTH-1:0];
integer i;
initial begin
// The memory is initialized with
// decreasing values startingfrom MAX_DATA
for (i=0;i<DEPTH;i=i+1)
mem[i] = MAX_DATA - i;
end
always@(posedge clkA) begin
// Use blocking assignments to for read-first
rdataA = mem[addrA];
if (weA) begin
mem[addrA] = wdataA;
end
end
always@(posedge clkB) begin
// Use blocking assignments to force write-first
if (weB) begin
mem[addrB] = wdataB;
end
rdataB = mem[addrB];
end
endmodule
Initializing RAM
Initialize the memory content with the Verilog HDL $readmemh or
$readmemb routines.
module ram_256x16 (wdata, waddr, wclk, we, raddr, rclk, re, rdata);
localparam addr_width = 8;
localparam data_width = 16;
input [data_width-1:0] wdata;
input [addr_width-1:0] waddr, raddr;
input wclk, we;
input rclk, re;
output reg [data_width-1:0] rdata;
reg [data_width-1:0] mem [(1<<addr_width)-1:0];
integer i;
initial begin
// Initialize memory with external file
$readmemh("ram256x16b.inithex", mem);
end
always@(posedge wclk) begin
if (we)
mem[waddr] <= wdata;
end
always@(posedge rclk) begin
if (re)
rdata <= mem[raddr];
end
endmodule // ram_256x16
The memory file should be simple hexadecimal numbers ($readmemh) or
binary numbers ($readmemb) without any comments or prefixes.
FE
FD
FC
FB
FA
F9
F8
F7
F6
F5
...
Inferring Output Registers
Synthesis packs registers that immediately follow the read data into the output registers of the BRAM if the control logic is compatible:
- The read clock is the same as the register clock signal.
- Enables:
- Trion FPGAs—The register must always be enabled (no explicit clock enable control).
- Titanium FPGAs—The register's clock enable must be the same as the BRAM's read enable signal.
- Resets:
- Trion FPGAs—The register cannot have a reset signal.
- Titanium FPGAs—If the read port has a reset signal, and if the register has a reset signal, they must match. Additionally, the Titanium output register only supports asynchronous reset logic.
Address Enable (Titanium)
The Titanium BRAM supports an address enable feature. However, synthesis does not infer these signals;for inferred BRAM these signals are always tied high. To use the address enable, instantiate the primitive (EFX_RAM10 and EFX_DPRAM10),
Resetting RAM (Titanium)
Titanium RAM supports a reset option on the RAM output and output register.
module ram10_arst (wdata, waddr, wclk, wclke, rclk, we, re, raddr, rdata, rst);
parameter AWIDTH = 11; // 2048 depth
parameter DWIDTH = 4; // 4-bit wide
localparam DEPTH = 1 << AWIDTH;
input [DWIDTH-1:0] wdata;
input [AWIDTH-1:0] waddr, raddr;
input wclk, wclke, we, rclk, re, rst;
output reg [DWIDTH-1:0] rdata;
reg [DWIDTH-1:0] mem [DEPTH-1:0];
always@(posedge wclk) begin
if (wclke) begin
if (we)
mem[waddr] <= wdata;
end
end
always@(posedge rclk or posedge rst) begin
if (rst)
rdata <= 0;
else if (re)
rdata <= mem[raddr];
end
endmodule