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.