Clock Domain Crossing with Valid and Ready
High-speed Systems-on-Chip (SoCs) frequently connect IP cores that operate at entirely different clock frequencies. Standard streaming protocols, such as AXI Stream, rely on Valid and Ready handshakes to control data flow. When these streams cross asynchronous clock boundaries, a simple multi-stage synchronizer is insufficient because it cannot safely transport the multi-bit data or guarantee the handshake integrity without dropping or duplicating words.
The module bridges a transmit domain (tx) and a receive domain (rx) using a 4-entry asynchronous FIFO. It accepts data on the transmit side when tx_valid and tx_ready are both high, and outputs data on the receive side when rx_valid and rx_ready are both high. Because it uses a skid buffer architecture internally, it allows continuous, back-to-back data flow without stalling when the source and destination frequencies match, only asserting backpressure (tx_ready goes low) when the destination domain stops reading.
Timing and reset behaviour: • Both clocks (tx_clk and rx_clk) trigger on the positive edge. • Both resets (tx_rst_n and rx_rst_n) are asynchronous and active-low. • When tx_rst_n is 0, tx_ready is immediately forced to 0. • When rx_rst_n is 0, rx_valid and rx_data are immediately forced to 0. • To ensure deterministic testing, rx_data must also be forced to 8'h00 whenever rx_valid is 0. • The internal memory capacity is exactly 4 entries.
Trace of a single synchronized transaction:
Cycle 1: tx_rst_n=0, rx_rst_n=0 → tx_ready=0, rx_valid=0, rx_data=8'h00
Cycle 2: tx_rst_n=1, rx_rst_n=1 → tx_ready=1, rx_valid=0, rx_data=8'h00
Cycle 3: tx_clk=1, tx_valid=1, tx_data=8'hAA → tx_ready=1 (write accepted)
Cycle 4: rx_clk=1 → rx_valid=0 (first sync stage captures TX pointer)
Cycle 5: rx_clk=1 → rx_valid=1, rx_data=8'hAA (second sync stage captures TX pointer, FIFO not empty)
Cycle 6: rx_clk=1, rx_ready=1 → rx_valid=0 (read accepted, FIFO becomes empty){ "signal": [
{ "name": "tx_clk", "wave": "p......" },
{ "name": "tx_valid", "wave": "010...." },
{ "name": "tx_data", "wave": "x=x....", "data": ["AA"] },
{},
{ "name": "rx_clk", "wave": "p......" },
{ "name": "rx_sync", "wave": "0..1..." },
{ "name": "rx_valid", "wave": "0..1.0." },
{ "name": "rx_ready", "wave": "0...10." },
{ "name": "rx_data", "wave": "0..==0.", "data": ["AA", "AA"] }
], "head": { "text": "A single write synchronizing across domains, followed by a read." } }| Signal | Direction | Width | Description | |--------|-----------|-------|-------------| | tx_clk | input | 1 | Transmit domain positive-edge clock | | tx_rst_n | input | 1 | Transmit domain asynchronous active-low reset | | tx_valid | input | 1 | Transmit data valid | | tx_ready | output | 1 | Transmit ready (1 when FIFO is not full) | | tx_data | input | 8 | Transmit data payload | | rx_clk | input | 1 | Receive domain positive-edge clock | | rx_rst_n | input | 1 | Receive domain asynchronous active-low reset | | rx_valid | output | 1 | Receive data valid (1 when FIFO is not empty) | | rx_ready | input | 1 | Receive ready | | rx_data | output | 8 | Receive data payload |
Constraints
To ensure cycle-accurate testability against the automated testbench, you must implement the following exact architecture: • Use a 4-entry by 8-bit memory array. • Maintain a 3-bit binary write pointer and a 3-bit binary read pointer, both initializing to 0. • The memory is indexed using the lower 2 bits of the respective binary pointer. • The binary pointer and its Gray-coded equivalent must be updated simultaneously on the same clock edge (i.e., the Gray register captures the Gray-coded value of the *next* binary pointer). • Use exactly a 2-stage shift register to synchronize the Gray-coded read pointer into the TX domain. • Use exactly a 2-stage shift register to synchronize the Gray-coded write pointer into the RX domain. • The FIFO is considered full (tx_ready = 0) when the current TX Gray pointer matches the synchronized RX Gray pointer, but with the top two bits inverted. • The FIFO is considered empty (rx_valid = 0) when the current RX Gray pointer exactly matches the synchronized TX Gray pointer. • tx_ready and rx_valid must be evaluated combinationally based on the current pointers.
Topics
Solve this problem
Write the module in Verilog, SystemVerilog or VHDL. Your submission is compiled and simulated against a real testbench — you get the waveform back, not a stored answer.
This problem is part of Codiode Pro. The statement above is free to read.
The circuit builder and code editor need a desktop screen. On a phone, read the problem here and open it on a laptop to solve.