Block Preview

Introduction

This block implements a simple dual-port RAM with separate read and write ports, allowing simultaneous access to different addresses. Read and write can operate on independent clock domains.

Read latency: 1 clock cycle.

Same-address collision is READ-FIRST: if READ_ADDR equals WRITE_ADDR on a cycle with WR=1, OUT carries the OLD content of that cell. Read the “Same-address collision” section before building any read-modify-write loop.

Pin Description

IN Input Variable bit BIT
Input data for writing. Width: Bit Width.
Default: Must be connected
WRITE_ADDR Input 16 bit BIT VECTOR
Write address (16 bits). Range: 0 to Memory Size-1.
Default: Must be connected
WR Input 1 bit BIT
Write enable (1-bit). When 1, writes IN to WRITE_ADDR on CLK_WR edge.
Default: Must be connected
READ_ADDR Input 16 bit BIT VECTOR
Read address (16 bits). Range: 0 to Memory Size-1.
Default: Must be connected
CLK_RD Input 1 bit BIT
Read clock. Can be independent from CLK_WR.
Default: Must be connected
CLK_WR Input 1 bit BIT
Write clock. Can be independent from CLK_RD.
Default: Must be connected
RESET Input 1 bit BIT
Active-high reset.
Default: Must be connected
OUT Output Variable bit BIT
Output data from read. Width: Bit Width. Latency: 1 CLK_RD cycle. If READ_ADDR equals WRITE_ADDR on a write cycle it shows the OLD content (read-first); see “Same-address collision”.

Properties

Property window

Memory Size MemorySize

Set the amount of memory in number of words

Number of words. Allowed: 64-65536.

Default: 1024

Options: 64 128 256 512 1024 2048 4096 8192 16384 32756 65536

Bit Width BusWidth

Set the size in bits of the data

Bit width per word. Range: 1-1024.

Default: 1

Range: 1 – 1024

Functional description

Dual-port RAM has independent ports:

  • Write port: WRITE_ADDR, IN, WR, CLK_WR
  • Read port: READ_ADDR, OUT, CLK_RD

Can read and write simultaneously to different addresses, even with different clocks.

Timing

Write: Data written to WRITE_ADDR on CLK_WR rising edge when WR=1. Read: Data from READ_ADDR available 1 cycle after CLK_RD rising edge.

Same-address collision: the memory is READ-FIRST

Writing and reading different addresses in the same clock costs nothing - that is the point of this block. When READ_ADDR equals WRITE_ADDR on a cycle with WR=1, however, OUT carries the OLD content of that cell, not the word being written. The write itself is correct: from the next clock on, that address reads the new word.

This is a property of the Xilinx simple dual-port macro and it cannot be configured away: there is no path from the write data of one port to the read port of the other, so “write-first” does not exist for this topology (the macro’s own design rule checks reject it, in both clocking modes). With independent read and write clocks the notion of “the same cycle” is not even well defined, which is the deeper reason.

What this breaks

Any read-modify-write pipeline in which the read address can catch up with the write address: accumulate-into-memory (mem[a] = mem[a] + x), histogram or bin counters, a running sum or moving average kept in RAM, a delay line with feedback. On every colliding cycle the value read is the one from before the previous write, so the update computes on stale data - systematically, not occasionally, and in exactly the designs that would want write-first.

What to do instead

  • Forward the write data around the memory: register WRITE_ADDR and IN, compare them with READ_ADDR, and multiplex the registered word onto your datapath instead of OUT when they match. One comparator and one multiplexer.
  • Or make the collision impossible: keep the read pointer trailing the write pointer by at least one word, or use a ping-pong scheme in which the half being written is never the half being read. This is the natural shape of the producer-consumer and cross-clock uses below, and those are unaffected.

Typical use cases

  • Ping-pong buffers
  • Cross-clock-domain data storage
  • Producer-consumer queues