Memory Design

Simple Dual Port RAM — Verilog Guide

One dedicated write port, one dedicated read port — both can operate simultaneously. Covers single-clock and dual-clock (async) modes, collision handling, BRAM inference, and a live read/write simulator.

SDP RAMSingle ClockDual ClockAsync ModeCollisionBRAM

Simple Dual Port RAM — Block Diagram

Simple Dual Port RAM Shared memory array WRITE PORT READ PORT waddr din we / wclk raddr re / rclk dout waddr din wclk raddr rclk dout WRITE and READ can occur simultaneously to different (or same) addresses

Single Port vs SDP vs True Dual Port

Single Port RAM

  • 1 shared R/W port
  • Read OR write per cycle
  • Smallest area
  • Use when R/W don't overlap

Simple Dual Port ✓

  • 1 write + 1 read port
  • Simultaneous R+W
  • Separate addresses
  • Line buffers, FIFOs

True Dual Port RAM

  • 2 full R/W ports
  • Each port can R or W
  • Most area/power
  • Register files, caches

Verilog — Single Clock SDP RAM

Same clock for both ports. Write and read happen on the same posedge. The read port sees old data (read-first) when both hit the same address.

// Simple Dual Port RAM — Single Clock
// One write port, one read port, shared clock
// Write and read CAN be simultaneous to DIFFERENT addresses
module sdp_ram_1clk #(
  parameter DATA_W = 8,
  parameter ADDR_W = 8   // 256 locations
)(
  input  wire              clk,
  // Write port
  input  wire              we,
  input  wire [ADDR_W-1:0] waddr,
  input  wire [DATA_W-1:0] din,
  // Read port
  input  wire              re,
  input  wire [ADDR_W-1:0] raddr,
  output reg  [DATA_W-1:0] dout
);
  reg [DATA_W-1:0] mem [0:(2**ADDR_W)-1];

  // Single always block — write and read on same clock edge
  always @(posedge clk) begin
    if (we)
      mem[waddr] <= din;          // write: non-blocking
    if (re)
      dout <= mem[raddr];         // read: non-blocking, read-first on collision
  end
endmodule

Write-First Variant (same address R/W sees new data)

// SDP RAM — Single Clock, Write-First
// When waddr == raddr AND we==1, dout = din (new data)
module sdp_ram_1clk_wf #(
  parameter DATA_W = 8,
  parameter ADDR_W = 8
)(
  input  wire              clk,
  input  wire              we,
  input  wire [ADDR_W-1:0] waddr,
  input  wire [DATA_W-1:0] din,
  input  wire [ADDR_W-1:0] raddr,
  output reg  [DATA_W-1:0] dout
);
  reg [DATA_W-1:0] mem [0:(2**ADDR_W)-1];

  always @(posedge clk) begin
    if (we)
      mem[waddr] <= din;
    // Write-first: bypass new data when addresses match
    if (we && (waddr == raddr))
      dout <= din;           // forward new data directly
    else
      dout <= mem[raddr];    // normal read
  end
endmodule

Verilog — Dual Clock SDP RAM (Async)

Write port on wclk, read port on rclk — two independent clocks. This is the core primitive behind most async FIFO implementations. Each port has its own always block.

// Simple Dual Port RAM — Dual Clock (Asynchronous between clocks)
// Write port: wclk domain  |  Read port: rclk domain
// Both share the same memory array — synthesis infers dual-clock BRAM
module sdp_ram_2clk #(
  parameter DATA_W = 8,
  parameter ADDR_W = 8
)(
  // Write port
  input  wire              wclk,
  input  wire              we,
  input  wire [ADDR_W-1:0] waddr,
  input  wire [DATA_W-1:0] din,
  // Read port
  input  wire              rclk,
  input  wire              re,
  input  wire [ADDR_W-1:0] raddr,
  output reg  [DATA_W-1:0] dout
);
  reg [DATA_W-1:0] mem [0:(2**ADDR_W)-1];

  // Write port — wclk domain
  always @(posedge wclk) begin
    if (we)
      mem[waddr] <= din;    // non-blocking write
  end

  // Read port — rclk domain
  always @(posedge rclk) begin
    if (re)
      dout <= mem[raddr];   // non-blocking registered read
  end

  // NOTE: simultaneous access to same address from both clocks
  // is a collision — behavior is tool-defined (typically X / undefined)
  // Use Gray-coded pointers (async FIFO) to prevent this
endmodule

With Enable and Byte-Write

// SDP RAM — Dual Clock, Byte-Enable Write, Chip-Enable Read
module sdp_ram_2clk_be #(
  parameter DATA_W = 32,
  parameter ADDR_W = 10
)(
  input  wire              wclk,
  input  wire [3:0]        we,       // per-byte write enables
  input  wire [ADDR_W-1:0] waddr,
  input  wire [DATA_W-1:0] din,
  input  wire              rclk,
  input  wire              re,       // read enable
  input  wire [ADDR_W-1:0] raddr,
  output reg  [DATA_W-1:0] dout
);
  reg [DATA_W-1:0] mem [0:(2**ADDR_W)-1];

  // Write port — byte granularity
  always @(posedge wclk) begin
    if (we[0]) mem[waddr][ 7: 0] <= din[ 7: 0];
    if (we[1]) mem[waddr][15: 8] <= din[15: 8];
    if (we[2]) mem[waddr][23:16] <= din[23:16];
    if (we[3]) mem[waddr][31:24] <= din[31:24];
  end

  // Read port — full word, chip-enabled
  always @(posedge rclk) begin
    if (re)
      dout <= mem[raddr];   // registered read, non-blocking
  end
endmodule

Collision Handling

ScenarioSingle ClockDual Clock
waddr ≠ raddrNo collision — both operate normallyNo collision — independent
waddr = raddr, we=1Read-first: dout = OLD data
Write-first: dout = din
Undefined (X) — avoid in design
we=0 (read only)Normal read — no collision possibleNormal read — no collision
PreventionWrite-first forwarding or protocolGray-code FIFO pointers or protocol guarantee

Port Description

PortClock DomainDescription
wclk / clkWriteClock for write port. In single-clock mode, shared with read port.
weWriteWrite enable — 1 = write din to waddr on rising edge of wclk
waddr[A-1:0]WriteWrite address — selects memory location to write
din[W-1:0]WriteWrite data — captured on posedge wclk when we=1
rclkReadClock for read port. Same as wclk in single-clock mode.
reReadRead enable — gates the output register update (optional)
raddr[A-1:0]ReadRead address — selects memory location to read
dout[W-1:0]ReadRegistered read data — valid one rclk cycle after raddr is presented

Interactive Simulator — 16×8 SDP RAM

Write port and read port operate independently. Both share the 16×8 memory.

↑ Write Port

↓ Read Port

Memory (16 × 8 bits):
Log:

Other Memory Types

FAQ

Does a simple dual port RAM support simultaneous read and write to the same address?

In single-clock mode: yes, but the read result depends on the mode (read-first or write-first). In dual-clock mode: the result is undefined — you must guarantee by protocol that no collision occurs, typically using Gray-coded FIFO pointers that prevent both ports from accessing the same address simultaneously.

What is the read latency of an SDP RAM?

One rclk cycle — you present raddr on cycle N, and dout is valid on cycle N+1. This is the synchronous (registered) read latency. If you need lower latency, you can add a bypass/forwarding mux in RTL that checks if raddr == waddr during a write and feeds din directly to the read output — at the cost of combinational path delay.