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.
Simple Dual Port RAM — Block Diagram
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
| Scenario | Single Clock | Dual Clock |
|---|---|---|
| waddr ≠ raddr | No collision — both operate normally | No collision — independent |
| waddr = raddr, we=1 | Read-first: dout = OLD data Write-first: dout = din | Undefined (X) — avoid in design |
| we=0 (read only) | Normal read — no collision possible | Normal read — no collision |
| Prevention | Write-first forwarding or protocol | Gray-code FIFO pointers or protocol guarantee |
Port Description
| Port | Clock Domain | Description |
|---|---|---|
| wclk / clk | Write | Clock for write port. In single-clock mode, shared with read port. |
| we | Write | Write enable — 1 = write din to waddr on rising edge of wclk |
| waddr[A-1:0] | Write | Write address — selects memory location to write |
| din[W-1:0] | Write | Write data — captured on posedge wclk when we=1 |
| rclk | Read | Clock for read port. Same as wclk in single-clock mode. |
| re | Read | Read enable — gates the output register update (optional) |
| raddr[A-1:0] | Read | Read address — selects memory location to read |
| dout[W-1:0] | Read | Registered 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
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.