Memories in Verilog
Introduction¶
Memory is a fundamental building block in digital design — used to store data, coefficients, or states across clock cycles. In Verilog, memory modeling allows designers to describe RAM, ROM, and register arrays at various abstraction levels — from behavioral to gate-level.
This article explores how to model different types of memories, initialize them, simulate read/write operations, and infer synthesizable memory in hardware.
Introduction to Memories¶
In Verilog, memory elements are typically arrays of registers (reg data type). They represent a collection of storage locations, each holding a word of fixed width.
Declaration Syntax¶
reg [data_width-1:0] memory_array [0:depth-1];
- data_width → number of bits in each word
- depth → number of words in memory
Example¶
reg [7:0] mem [0:15]; // 16x8 memory
This example defines a memory with 16 locations, each 8 bits wide.
Types of Memories¶
There are three major types of memories commonly modeled in Verilog:
| Memory Type | Description | Access | Synthesizable |
|---|---|---|---|
| ROM (Read-Only Memory) | Pre-initialized, no writes | Read-only | Yes |
| RAM (Random Access Memory) | Read and write operations | Read/Write | Yes |
| Register Array | Used for small storage or pipelining | Parallel-access | Yes |
Modeling Read and Write Operations¶
Simple Read/Write Memory Example¶
module simple_ram (
input clk,
input we, // write enable
input [3:0] addr,
input [7:0] data_in,
output reg [7:0] data_out
);
reg [7:0] mem [0:15]; // 16x8 memory
always @(posedge clk) begin
if (we)
mem[addr] <= data_in; // Write operation
else
data_out <= mem[addr]; // Read operation
end
endmodule
Explanation:
- Memory is updated only on the positive edge of clk.
- When we (write enable) is 1, the data at data_in is written into mem[addr].
- When we is 0, the memory content at addr is read to data_out.
Read-Only Memory (ROM)¶
ROM can be modeled by initializing the memory with predefined contents. It is typically read-only during simulation.
Example: 8x8 ROM
module rom_example (
input [2:0] addr,
output reg [7:0] data
);
reg [7:0] rom [0:7]; // 8x8 ROM
initial begin
rom[0] = 8'hA1;
rom[1] = 8'hB2;
rom[2] = 8'hC3;
rom[3] = 8'hD4;
rom[4] = 8'hE5;
rom[5] = 8'hF6;
rom[6] = 8'h07;
rom[7] = 8'h18;
end
always @(*) begin
data = rom[addr];
end
endmodule
Alternatively, ROM can be initialized from an external file.
Using $readmemh or $readmemb
initial begin
$readmemh("rom_data.hex", rom);
end
- $readmemh → reads hexadecimal file
- $readmemb → reads binary file
This allows designers to easily load data from files instead of manually assigning values.
Dual-Port Memory¶
Many designs (like DSPs and FIFOs) use dual-port memories to allow simultaneous read/write operations from two ports.
Example: Dual-Port RAM
module dual_port_ram (
input clk,
input we_a, we_b,
input [3:0] addr_a, addr_b,
input [7:0] data_a, data_b,
output reg [7:0] q_a, q_b
);
reg [7:0] mem [0:15];
always @(posedge clk) begin
if (we_a)
mem[addr_a] <= data_a;
q_a <= mem[addr_a];
if (we_b)
mem[addr_b] <= data_b;
q_b <= mem[addr_b];
end
endmodule
This design enables independent read/write operations at both ports A and B.
Asynchronous vs. Synchronous Memory¶
| Type | Read Behavior | Example Use |
|---|---|---|
| Synchronous | Output changes only on clock edge | RAM used in FPGA |
| Asynchronous | Output updates immediately on address change | Combinational ROM |
Example: Asynchronous Read Memory
always @(*) begin
data_out = mem[addr]; // No clock required
end
Synchronous memories are typically synthesizable, while asynchronous models are mainly for simulation.
Memory Initialization¶
Initialization ensures that memory has defined contents before simulation begins.
Inline Initialization
reg [7:0] mem [0:3] = '{8'h11, 8'h22, 8'h33, 8'h44};
File-Based Initialization
initial begin
$readmemh("init.hex", mem);
end
File-based initialization is especially useful for large memories or ROMs.
Behavioral Memory Models (Simulation-Only)¶
Behavioral models provide quick verification before hardware synthesis.
Example: Behavioral RAM
module ram_behavioral;
reg [7:0] mem [0:31];
reg [7:0] data_out;
integer i;
initial begin
for (i = 0; i < 32; i = i + 1)
mem[i] = 0;
end
task write(input [4:0] addr, input [7:0] data);
mem[addr] = data;
endtask
function [7:0] read(input [4:0] addr);
read = mem[addr];
endfunction
endmodule
These constructs are non-synthesizable but help validate logic functionality early in design.
Synthesis Considerations¶
When synthesizing memory designs:
Allowed
- Indexed access:
mem[address] - Single clock domain synchronous reads/writes
- Proper write-enable and clock control
Avoid
- Non-constant indices in for loops
- Asynchronous writes
- Mixed blocking and non-blocking assignments in the same block
Common FPGA Mapping
- Simple RAM -> Block RAM (BRAM)
- Small arrays -> LUT RAM
- ROM -> Initialized LUTs or BRAM
Example: Synchronous RAM Testbench¶
module tb_sync_ram;
reg clk, we;
reg [3:0] addr;
reg [7:0] din;
wire [7:0] dout;
simple_ram uut (.clk(clk), .we(we), .addr(addr), .data_in(din), .data_out(dout));
always #5 clk = ~clk;
initial begin
clk = 0; we = 0;
#10 we = 1; addr = 4'b0001; din = 8'hAA;
#10 we = 1; addr = 4'b0010; din = 8'hBB;
#10 we = 0; addr = 4'b0001;
#10 we = 0; addr = 4'b0010;
#10 $finish;
end
initial begin
$monitor(\"Time=%0t Addr=%b WE=%b Din=%h Dout=%h\", $time, addr, we, din, dout);
end
endmodule
This testbench writes and reads memory values while displaying results.
Modeling Multi-Dimensional Memories¶
Verilog allows multidimensional memory arrays useful in image buffers or matrices.
reg [7:0] mem [0:3][0:7]; // 4x8 two-dimensional memory
Access with:
mem[row][col] = data;
Although supported in simulation, multi-dimensional synthesis is tool-dependent — check FPGA/ASIC tool documentation.
Advanced Memory Features¶
Error Detection (Parity/ECC)¶
You can append a parity bit to each word for error detection.
reg [8:0] mem [0:15]; // 8 data bits + 1 parity bit
Burst Access¶
You can design burst reads/writes by incrementing the address automatically.
addr = addr + 1; // Sequential burst read/write
Memory Banking¶
Large memories can be split into smaller banks to improve access speed or support parallelism.
Best Practices¶
- Use non-blocking (<=) assignments inside clocked processes
- Initialize memories before simulation
- Separate read and write logic
- Use parameterized designs for reusability
- Prefer $readmemh for large memory initialization
Summary¶
Memory modeling in Verilog provides the foundation for designing register files, caches, buffers, and storage arrays. By understanding how to declare, read/write, and initialize memory arrays, designers can simulate complex storage systems accurately and infer efficient hardware in synthesis.
Key Takeaways:
- Use
reg [width-1:0] mem [0:depth-1]for declaration. - Initialize using
initialblock or$readmemh. - Use
always @(posedge clk)for synchronous reads/writes. - Verify functionality using testbenches with
$monitoror$display.