A true dual-port RAM with one port on each clock moves bulk data without any synchroniser on the datapath: the write port stores, the read port fetches. But a RAM is storage, not a protocol. The addresses, the write-read ordering, and the full and empty decisions still cross somewhere, and every one of them needs its own safe crossing.
Solved by the RAM | Still needs a crossing |
|---|---|
Data bits never pass through synchronisers | Write and read pointers or addresses (Gray or handshake) |
Arbitrary access order on each port | Full and empty: the reader must not read an empty slot |
Wide words at speed | Write-to-read ordering: the reader must not fetch a slot being written |
Same-address read and write. Reading an address in the same cycle it is written returns old data, new data, or a mixture depending on the memory. The protocol must exclude it: write-then-read sequencing, separate valid flags per slot, or a bypass register.
Stale control. A write pointer that crosses late lets the reader fetch a slot whose data has not landed, or re-fetch a slot already overwritten. Pointer crossings need the same Gray and synchroniser discipline as a FIFO.
Reset. Both ports start from an agreed empty state; a one-sided reset orphans the other side's idea of which slots are valid.
Random access is needed: lookup tables, coefficient banks, or mailboxes where the reader picks slots out of order.
Producer and consumer rates are matched by protocol (valid flags, credits) rather than by FIFO backpressure.
Never as a shortcut around pointer discipline: a dual-clock RAM with unsynchronised address or valid crossing is a torn bus with memory behind it.
Two crossings: the flag crosses through a synchroniser (single bit, held until acknowledged), and the data crosses by stability (written before the flag, unread until the flag, held until the reader is done). The RAM itself is just storage; the flag plus the hold promise is the crossing.