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.clkcontrols timing for read/write.we(write enable) controls when data is written.addrselects the memory location.data_inis the input data to write.data_outis 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
endmoduleExample
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
endmoduleOutput
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
initialblock 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.