Two ports writing the same address in the same cycle is a collision. A hardware RAM gives an undefined result: either value, a mix of bits, or a corrupted word.
The behaviour differs between devices, so a design that causes collisions is not portable. Prevent them at the system level, or make them defined in a wrapper.
Option | Cost | When |
|---|---|---|
Design them out: arbitration or address partitioning so ports never touch the same word | None in the RAM | Preferred |
Priority (see the wrapper below) with a flag | A comparator and a mux on the write enable of the lower-priority port | When collisions are rare but must be defined |
Merge (byte enables) so non-overlapping bytes both succeed | Per-byte enables and comparators | Bus masters writing different bytes of the same word |
Assertion only: collisions are a bug | Simulation-only check | Systems where a collision means a design error |
A priority mux makes the RAM slower: the write-enable path now includes an address comparator. If the RAM runs at the limit of its clock, the collision logic can be the critical path.
Move the comparison into the control logic one cycle earlier if necessary.
Same-port read-during-write mode and opposite-port collision behaviour are separate contracts. A common clock alone does not make the opposite port's read valid: AMD block RAM returns old data in the supported READ_FIRST configuration, but the opposite read can be invalid when the writing port uses WRITE_FIRST or NO_CHANGE. The wrapper below specifies old-data reads on both ports; mapping it to a macro requires that exact common-clock cross-port guarantee, or explicit forwarding/collision exclusion.
Its RTL defines old-data reads for same-address read/write collisions on either port, while the flag reports two competing writes and port A wins. That old-data contract must be checked against the target macro; common-clock WRITE_FIRST or NO_CHANGE does not generally guarantee valid opposite-port read data.
The wrapper below makes the collision defined. Here it runs a short directed sequence in which every access goes to address 5, which starts at 0x00.
Each slot is one clock cycle. A request offered in a slot lands at the edge that ends it, so mem5 (the word at address 5) and doutb change one slot later.
Slot 0: port A writes 0x11 and port B writes 0x22 to the same word. collision is high, and in slot 1 the word holds 0x11: port A won.
Slot 2: only port B writes, 0x33. It succeeds, and mem5 is 0x33 in slot 3.
Slot 3: port A writes 0x44 while port B reads. doutb in slot 4 is the old word 0x33, and the new word 0x44 reaches it in slot 5.
A common-clock wrapper where port A wins competing writes and both read outputs return old data. The testbench checks the winner and both directions of cross-port read/write collision. These RTL checks do not establish a physical macro's collision mode; review its configuration and implementation report separately.
// True dual-port RAM that makes the write-write collision DEFINED: port A wins, and a flag is raised.
module ram_tdp_safe #(parameter int AW = 4, parameter int DW = 8) (
input logic clk, // one clock for both ports (typical for a collision-safe design)
input logic wea, web,
input logic [AW-1:0] addra, addrb,
input logic [DW-1:0] dina, dinb,
output logic [DW-1:0] douta, doutb,
output logic collision // both ports write the same address in the same cycle
);
logic [DW-1:0] mem [2**AW];
assign collision = wea && web && (addra == addrb);
// Requires a target with common-clock old-data reads on BOTH ports during a collision.
always_ff @(posedge clk) begin
if (wea) mem[addra] <= dina;
if (web && !(wea && addra == addrb)) mem[addrb] <= dinb; // on a collision port A wins
douta <= mem[addra];
doutb <= mem[addrb];
end
endmodule
Step | Port A | Port B | Result |
|---|---|---|---|
1 | write 0xAA to address 2 | write 0xBB to address 9 | Both writes succeed |
2 | write 0x11 to address 5 | write 0x22 to address 5 | Collision flagged, port A wins: address 5 holds 0x11 |
3 | idle | write 0x33 to address 5 | Port B writes normally: address 5 holds 0x33 |
The testbench checks the RTL wrapper's winner and both directions of cross-port read and write collision. These RTL checks do not establish a physical macro's collision mode; review its configuration and implementation report separately.