HDLbits
Start Practicing

RTL Design Patterns/Memories

Single-Port RAM

easy
memoryramsingle-port

The basic building block

Almost every on-chip buffer that needs to be written at runtime (as opposed to a ROM’s fixed content) starts from this shape: one address, one data-in port, a write-enable, and a registered data-out. It’s called “single-port” because there is exactly one address bus shared by both the read and the write — you cannot read one location and write a different one in the same cycle (that requires two ports; see the Dual-Port RAM problem in this category).

Interface

Signal Direction Width Description
clk input 1 Clock.
addr input AW Shared address for both read and write.
din input DW Data to write when we is high.
we input 1 Write-enable.
dout output DW Registered read data for addr, one cycle later.

Read-during-write behavior

Because addr is shared, it’s possible to assert we and effectively “read” the same address in the same cycle. This design uses the standard read-old-data convention (sometimes called “no-change” or “read-before-write” behavior in vendor memory-compiler terminology): dout reflects whatever was in mem[addr] before this cycle’s write, and the new data only becomes visible on a subsequent read. This is the default behavior most FPGA block-RAM primitives synthesize to when read and write share a clock and address, and it’s a common source of confusion for anyone expecting write-forwarding — so it’s called out explicitly here and tested directly.

Cycle-by-cycle example

Cycle addr din we dout (this cycle, i.e. result of previous cycle’s access)
0 10 — 0 (whatever was previously registered)
1 10 0xA5 1 old mem[10] (write takes effect after this cycle)
2 10 — 0 0xA5 (the write from cycle 1 is now visible)

Correctness constraints

  • dout must be a registered (one-cycle-delayed) view of mem[addr], not combinational.
  • On a cycle where we is asserted for addr, dout must reflect the pre-write (“old”) contents of that address, not the value being written this same cycle.
  • Once a write has completed, all subsequent reads (with we low) of that address must return the newly written value, not the original one.
  • The memory must support the full address range implied by AW (i.e. 2^AW distinct locations), each independently readable/writable.