0
0
VerilogHow-ToBeginner · 4 min read

How to Use Block RAM in FPGA with Verilog: Simple Guide

To use block RAM in FPGA with Verilog, you define a memory array and control read/write operations using clock and enable signals. Typically, you declare a reg array for storage and use synchronous logic to read and write data on clock edges.
📐

Syntax

Block RAM in Verilog is usually modeled as a reg array with synchronous read and write controlled by clock and enable signals.

Key parts:

  • reg [DATA_WIDTH-1:0] mem [0:DEPTH-1]; declares the memory array.
  • clk controls timing for read/write.
  • we (write enable) controls when data is written.
  • addr selects the memory location.
  • data_in is the input data to write.
  • data_out is the output data read from memory.
verilog
module block_ram (
    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 [0:15]; // 16 locations of 8-bit width

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

endmodule
💻

Example

This example shows a simple block RAM with 16 addresses and 8-bit data width. It writes data when we is high and reads data every clock cycle.

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

    block_ram bram (
        .clk(clk),
        .we(we),
        .addr(addr),
        .data_in(data_in),
        .data_out(data_out)
    );

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

    initial begin
        // Write 0xAA to address 3
        we = 1; addr = 4'd3; data_in = 8'hAA;
        #10;
        // Write 0x55 to address 7
        addr = 4'd7; data_in = 8'h55;
        #10;
        we = 0; // Stop writing

        // Read from address 3
        addr = 4'd3;
        #10;
        $display("Read from addr 3: %h", data_out);

        // Read from address 7
        addr = 4'd7;
        #10;
        $display("Read from addr 7: %h", data_out);

        // Read from address 0 (not written, default 0)
        addr = 4'd0;
        #10;
        $display("Read from addr 0: %h", data_out);

        $finish;
    end
endmodule
Output
Read from addr 3: aa Read from addr 7: 55 Read from addr 0: 00
⚠️

Common Pitfalls

Common mistakes when using block RAM in Verilog include:

  • Reading and writing in the same clock cycle without proper timing can cause unexpected data.
  • Not using synchronous logic (clocked always block) can lead to simulation vs hardware mismatches.
  • Forgetting to initialize memory if needed, which can cause unknown values.
  • Using combinational read logic instead of synchronous read can cause timing issues on FPGA.
verilog
/* Wrong: combinational read (not recommended for block RAM) */
module wrong_bram(
    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 [0:15];

    always @(posedge clk) begin
        if (we) mem[addr] <= data_in;
    end

    always @(*) begin
        data_out = mem[addr]; // Combinational read
    end
endmodule

/* Right: synchronous read and write */
module right_bram(
    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 [0:15];

    always @(posedge clk) begin
        if (we) mem[addr] <= data_in;
        data_out <= mem[addr];
    end
endmodule
📊

Quick Reference

Tips for using block RAM in Verilog:

  • Use synchronous read and write inside a clocked always @(posedge clk) block.
  • Declare memory as reg [width-1:0] mem [0:depth-1].
  • Control writes with a write enable signal.
  • Use FPGA vendor tools to infer block RAM from your code.
  • Initialize memory if needed using initial block or vendor-specific attributes.

Key Takeaways

Define block RAM as a reg array and use synchronous logic for read/write.
Control writing with a write enable signal and read data on clock edges.
Avoid combinational reads to ensure FPGA block RAM inference and timing correctness.
Initialize memory if your design requires known startup values.
Use FPGA vendor tools to confirm your code infers block RAM correctly.