CodiodeCodiode
Home
Problem Solving
Skill Tracks
My Assignments
Contests
Leaderboard
Community
Settings
Codiode/Problems/Sequential Logic

Multi-Bit Crossing Using Holding Register

MediumVerilog / SystemVerilogBuild

Configuration buses passing between clock domains cannot use Gray codes because their bits do not change sequentially. A multi-bit clock domain crossing requires a holding register protocol. The source domain locks the data in a register, signals the destination domain to read it, and waits for an acknowledgment before accepting new data. This prevents the destination domain from sampling the bus while it is transitioning, avoiding catastrophic metastable states.

The module implements a 4-phase handshake synchronizer between a source domain (clk_a) and a destination domain (clk_b). When the source domain is ready (ready_a is high) and valid_in is asserted, data_in is captured into a holding register. A request signal is then asserted and synchronized into the destination domain. Upon detecting the synchronized request going high, the destination domain captures the holding register into data_out, asserts valid_out for exactly one clk_b cycle, and asserts an acknowledge signal. This acknowledge is synchronized back to the source domain, which then deasserts the request. Once the destination domain drops the acknowledge and the source domain sees the drop, ready_a is reasserted to accept new data.

Both clock domains trigger on the positive edge. Resets are asynchronous and active-low. On reset, data_out and valid_out must be 0, and ready_a must be 1. All internal synchronizers and state machines must reset to 0. The internal holding register must only change when ready_a is high and valid_in is high.

Worked Trace: Cycle 1: rst_n_a=0, rst_n_b=0 → ready_a=1, valid_out=0, data_out=0 Cycle 2: rst_n_a=1, rst_n_b=1 → ready_a=1 (Idle state) Cycle 3: valid_in=1, data_in=170 → ready_a=1 (Inputs applied) Cycle 4: valid_in=0 → ready_a=0 (Source domain loads data into holding register, asserts request) Cycle 5: valid_in=0 → ready_a=0 (Destination domain synchronizer flop 1 captures request) Cycle 6: valid_in=0 → ready_a=0 (Destination domain synchronizer flop 2 captures request) Cycle 7: valid_in=0 → valid_out=1, data_out=170 (Destination domain detects request edge, outputs data, asserts acknowledge) Cycle 8: valid_in=0 → valid_out=0 (Source domain synchronizer flop 1 captures acknowledge) Cycle 9: valid_in=0 → ready_a=0 (Source domain synchronizer flop 2 captures acknowledge, drops request) Cycle 10: valid_in=0 → ready_a=0 (Destination domain synchronizer flop 1 captures dropped request) Cycle 11: valid_in=0 → ready_a=0 (Destination domain synchronizer flop 2 captures dropped request, drops acknowledge) Cycle 12: valid_in=0 → ready_a=0 (Source domain synchronizer flop 1 captures dropped acknowledge) Cycle 13: valid_in=0 → ready_a=1 (Source domain synchronizer flop 2 captures dropped acknowledge, returns to Idle)

flowchart LR
    RESET(( )) -->|reset| IDLE
    IDLE(["IDLE ★"]):::out -->|valid_in=1| WAIT_ACK
    IDLE -->|valid_in=0| IDLE
    WAIT_ACK((WAIT_ACK)) -->|ack_sync=1| WAIT_DEACK
    WAIT_ACK -->|ack_sync=0| WAIT_ACK
    WAIT_DEACK((WAIT_DEACK)) -->|ack_sync=0| IDLE
    WAIT_DEACK -->|ack_sync=1| WAIT_DEACK
    classDef out fill:#6C5CE7,stroke:#5B4FE8,color:#fff
{ "signal": [
  { "name": "clk",        "wave": "p............" },
  { "name": "valid_in",   "wave": "0.10........." },
  { "name": "data_in",    "wave": "x.==xxxxxxxxx", "data": ["0xAA", "0xAA"] },
  { "name": "ready_a",    "wave": "1..0........1" },
  { "name": "req_a",      "wave": "0..1....0...." },
  { "name": "req_b_sync", "wave": "0....1....0.." },
  { "name": "valid_out",  "wave": "0.....10....." },
  { "name": "data_out",   "wave": "x.....=......", "data": ["0xAA"] },
  { "name": "ack_b",      "wave": "0.....1...0.." },
  { "name": "ack_a_sync", "wave": "0.......1...0" }
], "head": { "text": "Cycle-by-cycle 4-phase handshake (clocks simplified to simultaneous ticks)." } }

| Signal | Direction | Width | Description | |--------|-----------|-------|-------------| | clk_a | input | 1 | Source domain clock; positive-edge triggered | | rst_n_a | input | 1 | Source domain asynchronous active-low reset | | clk_b | input | 1 | Destination domain clock; positive-edge triggered | | rst_n_b | input | 1 | Destination domain asynchronous active-low reset | | data_in | input | 8 | Data bus to be transferred across domains | | valid_in | input | 1 | Asserted high to initiate transfer when ready_a is 1 | | ready_a | output | 1 | High when source domain is ready to accept new data | | data_out | output | 8 | Transferred data in destination domain | | valid_out| output | 1 | Pulsed high for one clk_b cycle when data_out updates |

Constraints

  • Clock edges must be strictly positive edge.
  • Resets must be strictly asynchronous and active-low.
  • Cross-domain signals must be synchronized using exactly two flip-flops in series.
  • The multi-bit data bus data_in must never be synchronized directly.
  • The internal holding register must remain perfectly stable until the 4-phase handshake completes.

Topics

FSMClock Domain CrossingSynchronizerHandshake

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.

Sign in to solveSee what Pro unlocks

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.

Related problems

  • Basic D Flip FlopEasy
  • Debug: Missing Edge in Sensitivity ListMedium
  • Read After Write Hazard DetectionEasy
  • Parameterized Interface with ModportsHard
  • Struct Array PipelineHard
  • T Flip Flop from D Flip Flop TemplateEasy
  • Recursive Generate Reduction TreeHard
  • Four Stage Shift RegisterEasy

Browse all problems · Learning tracks