The four-phase handshake spends half its time returning to zero. When control traffic is frequent enough for that to hurt, the two-phase variant drops the return-to-zero: every toggle of req, in either direction, is a new event, and every toggle of ack answers one. Throughput roughly doubles, but the protocol state is now the history of toggles rather than visible levels, which makes review, debug and verification harder.
Source toggles req to offer each word and waits for ack to toggle in answer; destination toggles ack to answer each req toggle it receives. Data stability follows the same rule as before: stable from before the req toggle until after the matching ack toggle is seen. The compact sketch below shows the source side only:
// snippet: two-phase source (destination mirrors it with req/ack swapped).
// typedef enum logic [1:0] {IDLE, WAIT} state_t;
// IDLE: if (send_valid) begin data <= send_data; req <= ~req; state <= WAIT; end
// WAIT: if (ack_sync != ack_seen) begin ack_seen <= ack_sync; state <= IDLE; endAspect | Four-phase | Two-phase |
|---|---|---|
Crossings per word | Four (req up, ack up, req down, ack down) | Two (req toggle, ack toggle) |
Throughput | About one word per 2N + 2M cycles | About one word per N + M cycles (see the latency lesson) |
Idle state on the wires | req = 0, ack = 0: visible and checkable | Whatever the last toggles left: history-dependent |
Debug from a waveform | Each phase is recognisable | Must compare against previous values |
Reset story | Reset to req = 0, ack = 0 on both sides | Both sides must agree on the starting toggle parity |
Two-phase roughly halves the round-trip cost per word, but adds no pipelining: words still go one at a time, each waiting for its acknowledge. For genuinely higher throughput the answer is not a faster handshake but a FIFO. Choose two-phase when control words arrive steadily at a rate between what four-phase sustains and what justifies a FIFO.
A structural review confirms the same items as for four-phase (synchronised single-bit controls, held-stable data, reset agreement) plus one extra: the reset values of req, ack and their synchronised copies must match on both sides, or the first toggle after reset is misread. Verification must cover the first transfer after reset explicitly, since parity bugs live exactly there.
Four-phase idles at known levels, so each phase is recognisable on sight. Two-phase idles at whatever the last toggles left, so every edge must be interpreted against the previous value.