0
0
VerilogHow-ToBeginner · 4 min read

Verilog Code for Dual Port RAM: Syntax and Example

A dual port RAM in Verilog allows simultaneous read and write operations on two ports. It typically uses two separate address, data, and control signals for each port. You can implement it using always @(posedge clk) blocks with separate read and write logic for each port.
📐

Syntax

A dual port RAM module usually has two sets of ports: Port A and Port B. Each port has its own address, data input, data output, write enable, and clock signals. The RAM memory is declared as a reg array. The always @(posedge clk) blocks handle read and write operations for each port independently.

  • clk_a, clk_b: Clock signals for each port.
  • we_a, we_b: Write enable signals for each port.
  • addr_a, addr_b: Address inputs for each port.
  • din_a, din_b: Data inputs for writing.
  • dout_a, dout_b: Data outputs for reading.
verilog
module dual_port_ram (
    input wire clk_a,
    input wire we_a,
    input wire [3:0] addr_a,
    input wire [7:0] din_a,
    output reg [7:0] dout_a,

    input wire clk_b,
    input wire we_b,
    input wire [3:0] addr_b,
    input wire [7:0] din_b,
    output reg [7:0] dout_b
);

    reg [7:0] ram [0:15]; // 16 locations of 8-bit memory

    // Port A operations
    always @(posedge clk_a) begin
        if (we_a) begin
            ram[addr_a] <= din_a; // Write data
            dout_a <= din_a;      // Output written data
        end else begin
            dout_a <= ram[addr_a]; // Read data
        end
    end

    // Port B operations
    always @(posedge clk_b) begin
        if (we_b) begin
            ram[addr_b] <= din_b; // Write data
            dout_b <= din_b;      // Output written data
        end else begin
            dout_b <= ram[addr_b]; // Read data
        end
    end

endmodule
💻

Example

This example shows a simple dual port RAM with 16 memory locations of 8 bits each. Port A and Port B can read or write independently on their own clock edges. When we is high, data is written; otherwise, data is read from the given address.

verilog
module tb_dual_port_ram();
    reg clk_a = 0, clk_b = 0;
    reg we_a, we_b;
    reg [3:0] addr_a, addr_b;
    reg [7:0] din_a, din_b;
    wire [7:0] dout_a, dout_b;

    dual_port_ram ram_inst(
        .clk_a(clk_a), .we_a(we_a), .addr_a(addr_a), .din_a(din_a), .dout_a(dout_a),
        .clk_b(clk_b), .we_b(we_b), .addr_b(addr_b), .din_b(din_b), .dout_b(dout_b)
    );

    // Clock generation
    always #5 clk_a = ~clk_a;
    always #7 clk_b = ~clk_b;

    initial begin
        // Write 0xAA to address 3 on port A
        we_a = 1; addr_a = 4'd3; din_a = 8'hAA;
        // Write 0x55 to address 5 on port B
        we_b = 1; addr_b = 4'd5; din_b = 8'h55;
        #10;

        // Disable write, read from address 3 and 5
        we_a = 0; addr_a = 4'd3;
        we_b = 0; addr_b = 4'd5;
        #20;

        $display("Port A read data: %h", dout_a); // Expect AA
        $display("Port B read data: %h", dout_b); // Expect 55
        $finish;
    end
endmodule
Output
Port A read data: aa Port B read data: 55
⚠️

Common Pitfalls

Common mistakes when writing dual port RAM include:

  • Using the same clock for both ports without proper synchronization can cause data corruption.
  • Not handling simultaneous writes to the same address can lead to undefined behavior.
  • Forgetting to register outputs can cause glitches.
  • Mixing blocking and non-blocking assignments incorrectly in always blocks.

Always use non-blocking assignments (<=) inside clocked always blocks for proper simulation and synthesis.

verilog
/* Wrong: Blocking assignment in clocked block */
always @(posedge clk_a) begin
    if (we_a) begin
        ram[addr_a] = din_a; // Blocking assignment - not recommended
        dout_a = din_a;
    end else begin
        dout_a = ram[addr_a];
    end
end

/* Right: Non-blocking assignment in clocked block */
always @(posedge clk_a) begin
    if (we_a) begin
        ram[addr_a] <= din_a; // Non-blocking assignment
        dout_a <= din_a;
    end else begin
        dout_a <= ram[addr_a];
    end
end
📊

Quick Reference

Dual Port RAM Signals:

  • clk_a, clk_b: Separate clocks for each port.
  • we_a, we_b: Write enable signals.
  • addr_a, addr_b: Address inputs.
  • din_a, din_b: Data inputs for writing.
  • dout_a, dout_b: Data outputs for reading.

Best Practices: Use non-blocking assignments, avoid simultaneous writes to the same address, and register outputs.

Key Takeaways

Dual port RAM allows simultaneous read/write on two independent ports with separate clocks.
Use non-blocking assignments inside clocked always blocks for correct behavior.
Avoid simultaneous writes to the same address to prevent data corruption.
Register outputs to avoid glitches and ensure stable data.
Separate read and write logic for each port improves clarity and synthesis.