0
0
VerilogHow-ToBeginner · 4 min read

How to Model RAM in Verilog: Syntax and Example

To model RAM in Verilog, use a reg array to represent memory locations and implement read/write logic inside an always block triggered by clock edges. Use an address input to select memory locations and control signals like we (write enable) to manage data storage.
📐

Syntax

Modeling RAM in Verilog typically involves declaring a reg array to hold data, an address input to select memory cells, and control signals like write enable (we) and clock (clk). The always @(posedge clk) block is used to update memory on clock edges.

  • reg [DATA_WIDTH-1:0] mem_array [0:DEPTH-1];: Declares the memory array.
  • input [ADDR_WIDTH-1:0] addr;: Address input to select memory location.
  • input we;: Write enable signal.
  • input clk;: Clock signal for synchronous operation.
  • always @(posedge clk): Block to handle read/write on clock edge.
verilog
module ram_model (
    input wire clk,
    input wire we,
    input wire [3:0] addr,
    input wire [7:0] data_in,
    output reg [7:0] data_out
);

    reg [7:0] mem_array [0:15]; // 16 locations of 8-bit RAM

    always @(posedge clk) begin
        if (we) begin
            mem_array[addr] <= data_in; // Write data
        end
        data_out <= mem_array[addr]; // Read data
    end

endmodule
💻

Example

This example shows a simple 16x8 RAM module with synchronous read and write. When we is high at the clock's rising edge, the input data is stored at the given address. The output always shows the data at the current address.

verilog
module testbench();
    reg clk = 0;
    reg we;
    reg [3:0] addr;
    reg [7:0] data_in;
    wire [7:0] data_out;

    ram_model ram (
        .clk(clk),
        .we(we),
        .addr(addr),
        .data_in(data_in),
        .data_out(data_out)
    );

    always #5 clk = ~clk; // 10 time units clock period

    initial begin
        // Write 8'hAA to address 3
        we = 1; addr = 4'd3; data_in = 8'hAA;
        #10;
        // Write 8'h55 to address 7
        addr = 4'd7; data_in = 8'h55;
        #10;
        // Disable write, read address 3
        we = 0; addr = 4'd3;
        #10;
        $display("Read from addr 3: %h", data_out);
        // Read address 7
        addr = 4'd7;
        #10;
        $display("Read from addr 7: %h", data_out);
        $finish;
    end
endmodule
Output
Read from addr 3: aa Read from addr 7: 55
⚠️

Common Pitfalls

Common mistakes when modeling RAM in Verilog include:

  • Using blocking assignments (=) instead of non-blocking (<=) inside always @(posedge clk) blocks, which can cause simulation mismatches.
  • Not synchronizing read and write operations to the clock, leading to glitches.
  • Forgetting to declare the memory array size correctly, causing synthesis errors.
  • Reading and writing the same address without proper timing, which can cause unexpected data.
verilog
/* Wrong way: blocking assignment in clocked block */
always @(posedge clk) begin
    if (we) begin
        mem_array[addr] = data_in; // Wrong: blocking assignment
    end
    data_out = mem_array[addr];
end

/* Right way: non-blocking assignment */
always @(posedge clk) begin
    if (we) begin
        mem_array[addr] <= data_in; // Correct: non-blocking
    end
    data_out <= mem_array[addr];
end
📊

Quick Reference

  • Use reg [width-1:0] mem_array [0:depth-1]; to declare RAM.
  • Use always @(posedge clk) for synchronous read/write.
  • Use non-blocking assignments (<=) inside clocked blocks.
  • Control writes with a we (write enable) signal.
  • Read data is usually registered on clock edge for stability.

Key Takeaways

Model RAM using a reg array with synchronous read/write inside an always @(posedge clk) block.
Use non-blocking assignments (<=) for memory updates to avoid simulation issues.
Control writes with a write enable (we) signal to prevent unintended data changes.
Declare memory size clearly with address and data widths matching your design.
Always test RAM behavior with a testbench to verify read and write operations.