CXL · Module 30
Integration Review Checklist
Two correct blocks, one broken interface. Eleven review dimensions — multi-bit domain crossings, reset release order, shared parameters, handshake semantics, byte order, width adaptation, credit ownership, decode overlap, gate quiescence, tie-off defaults and revision skew — each with the contract at risk and the evidence to demand from both teams.
Every review in this module so far has examined one thing: an architecture, an RTL block, a testbench, a coherency protocol, a measurement. This chapter examines the space between two things, and it is the only review where both subjects can pass their own review and the result still fails.
The review question this chapter turns on, asked once per dimension:
Whose assumption is this, and does the other side know about it?
An integration defect is not a bug in either block. It is a contract that was never written down, read differently by two teams who were each internally consistent, and discovered at the point where their work meets.
1. Nobody Owns The Space Between
The structural reason integration is hard is an ownership gap, and it is worth stating plainly before any of the review items.
| Question | Owner |
|---|---|
| Is block A correct? | A's designer, A's DV, A's review |
| Is block B correct? | B's designer, B's DV, B's review |
| Do A and B agree about the interface? | often nobody |
Both blocks pass. Both DV environments are green. Neither team is wrong. The defect lives in a shared assumption that neither environment models, because each one models its own side and stubs the other.
That is why every model in this chapter is built twice as two READINGS of a shared contract rather than as a correct build and a broken one. The weak build is not a mistake anybody would defend in isolation — it is a defensible reading of an under-specified contract.
2. How To Use This Chapter
Each of the eleven review dimensions below is a working review item, and every one answers the same eight questions:
| Facet | What it settles |
|---|---|
| Under review | the shared assumption being examined |
| Contract at risk | what breaks if the two sides differ |
| Where it lives | which side's code, or neither |
| Evidence to demand | what the reviewer should ask both teams for |
| What escapes | the failure that reaches integration |
| How DV proves it | the stimulus that needs both sides present |
| Telemetry | what exposes the disagreement after tapeout |
| Misleading evidence | what makes two disagreeing blocks look fine |
3. The One-Sentence Model
An integration review is sound when both blocks passed their own reviews, when every multi-bit domain crossing is qualified by a single synchronised signal, when the reset release order is stated and enforced, when every shared parameter is compared at the boundary, when the address map is disjoint, and when every defaulted input publishes the value it is running with — and "both blocks passed" is bit 0.
4. What This Chapter Owns
| Ground | Owner |
|---|---|
| Reviewing the architecture before RTL exists | 30.1 |
| Reviewing the RTL inside one block | 30.2 |
| Reviewing the environment that judges one block | 30.3 |
| Reviewing coherency invariants across agents | 30.4 |
| Reviewing the numbers a design publishes | 30.5 |
| Reviewing a failing link | 30.7 |
| Reviewing the boundary between two blocks | this chapter |
The boundary with 30.2 is the one worth stating. That chapter reviews a register assigned twice inside one block. This one reviews a register whose meaning is assigned twice, once by each side — and no amount of reading either block finds it.
5. Teaching-Model Boundary And Source Discipline
Every model in this chapter is a teaching model. Each isolates one integration property so it can be examined, mutated and broken on purpose. None is a production CXL controller, a clock-domain-crossing library, or an implementation of any specification flow.
Nothing here states a normative CXL detail. No opcode, packet layout, bit position, field width, response encoding, snoop encoding, retry rule, timeout constant, latency figure, revision number or register definition from the specification appears anywhere in this chapter. The review dimensions — domain crossing, reset order, parameter agreement, handshake semantics — are general integration properties that any multi-block design must satisfy, and they are examined in their general form deliberately, so the technique transfers.
| Claim class | How it is marked |
|---|---|
| General integration reasoning | stated plainly |
| Teaching abstraction | declared in the model header |
| Illustrative parameter | every concrete figure in a model or table |
| Simulator-derived result | quoted from a run and asserted |
| Derived arithmetic | shown with its inputs |
One modelling choice needs stating up front. Metastability cannot be simulated, so the domain-crossing model does not try. It models the consequence — a word assembled from two source values when bits resolve at different times — using an explicit skew input on a single clock. That is a teaching abstraction, declared in the model header, and it isolates the property a review can actually act on.
6. Review Item 1 — Does The Synchroniser Cover The Whole Bus?
Under review. Every signal crossing a clock domain.
Contract at risk. That the destination sees a value the source actually held.
Where it lives. The number of synchronisers, against the number of bits.
The failure. A two-flop synchroniser makes one bit safe to sample in a foreign domain. It does nothing for a bus. Each bit resolves independently, so on the cycle the source changes several bits at once the destination can latch a combination that never existed — a value assembled from the old word and the new one.
// RTL 1 - the synchroniser protects one bit, and the bus has eight.
//
// A two-flop synchroniser makes ONE bit safe to sample in a foreign clock
// domain. It does nothing for a bus. Each bit resolves independently, so on the
// cycle the source changes several bits at once the destination can latch a
// combination that never existed on the source side - a value assembled from
// the old word and the new one.
//
// BAD : one synchroniser per bit on a multi-bit bus
// GOOD : synchronise a single qualifier, and pass the bus through a
// structure that is stable while the qualifier crosses
//
// TEACHING MODEL. Both domains are modelled on one clock with an explicit skew
// input, because the point is the ASSEMBLY of a value that never existed, not
// metastability itself - which cannot be simulated. It is not a production CXL
// block and contains no opcode, layout, encoding or timing from any
// specification.
module domain_hop #(parameter int PER_BIT_SYNC = 0) (
input logic clk, rst_n,
input logic [7:0] src_val,
input logic src_update, skew_bit3, sample_now,
output logic [7:0] dst_val, stable_val, truth_val,
output logic value_existed, hop_ok,
output logic [7:0] n_samples, n_impossible,
output logic cdc_err
);
logic [7:0] prev_q, cur_q, dst_q, stable_q;
logic [7:0] assembled;
// What the source actually held, before and after the update.
assign truth_val = cur_q;
// The per-bit crossing: bit 3 lands one cycle late relative to the rest, so
// the destination assembles a word from two different source values.
assign assembled = {cur_q[7:4], skew_bit3 ? prev_q[3] : cur_q[3], cur_q[2:0]};
assign dst_val = dst_q;
assign stable_val = stable_q;
// The word the destination holds is one the source genuinely presented.
assign value_existed = (dst_q == cur_q) || (dst_q == prev_q);
assign hop_ok = value_existed;
// SAFETY VIOLATION: the destination latched a word that never existed on the
// source side - neither the old value nor the new one.
assign cdc_err = sample_now && !value_existed;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
prev_q <= 8'd0; cur_q <= 8'd0; dst_q <= 8'd0; stable_q <= 8'd0;
n_samples <= 8'd0; n_impossible <= 8'd0;
end else begin
if (src_update) begin
prev_q <= cur_q;
cur_q <= src_val;
end
// The whole review point. Per-bit synchronisers let the skewed bit
// through on its own; the qualified path holds the whole word.
dst_q <= (PER_BIT_SYNC != 0) ? assembled : cur_q;
stable_q <= cur_q;
if (sample_now) begin
n_samples <= n_samples + 8'd1;
if (!value_existed) n_impossible <= n_impossible + 8'd1;
end
end
end
endmoduleThe measurement. The source holds F0, then updates to 0F. One bit arrives a cycle late:
source F0 then 0F, bit 3 late : qualified=0f per_bit=07F0 has bit 3 clear and 0F has it set. A destination taking bit 3 from the old word and the rest from the new one assembles 0000_0111 = 07 — a value the source never held in either state. The qualified path delivers the whole new word; the per-bit path delivers a word that never existed.
The run also drives the benign case: with the two source words equal, the skew assembles the same value and nothing is impossible. A skew is only dangerous while two words differ, which is why a slow-changing bus can carry this defect for years.
Evidence to demand. For every crossing, how many bits and how many synchronisers. If the second number is not one, ask what qualifies the bus.
What escapes. A configuration word, an address, a length field or a pointer read as a value that was never written — presenting as data corruption far from the crossing.
How DV proves it. Change several bits in one cycle and skew one of them. A test that changes one bit at a time cannot find this, and one-bit-at-a-time is what a directed test naturally does.
Telemetry. A count of destination values outside the legal set, where one exists. For an arbitrary bus there is none, which is exactly why this must be caught in review.
Misleading evidence. A synchroniser on every bit, which looks more careful than one synchroniser and is the defect.
Figure 1 — the per-bit path is the one that looks more careful. Eight synchronisers protect eight bits individually and protect the word not at all. The qualified path has one synchroniser and delivers a value that existed.
7. Review Item 2 — Who Leaves Reset First?
Under review. Every reset in a multi-block design.
Contract at risk. That no interface is used before both ends of it exist.
Where it lives. Nowhere — which is the point. Each block's reset is correct on its own.
The failure. A consumer that leaves reset first will see a producer that is still resetting, and whatever it samples in that window is not a protocol violation by either side — it is a window nobody specified.
// RTL 2 - two blocks, two resets, and nobody owns the order.
//
// Each block's reset is correct on its own. What no block owns is the ORDER in
// which they release. A consumer that leaves reset first will see a producer
// that is still resetting, and whatever it samples in that window is not a
// protocol violation by either side - it is a window nobody specified.
//
// BAD : two independent resets, each locally correct
// GOOD : a stated release order, enforced by a sequencer, and a checkable
// "both out of reset" qualifier that gates the interface
//
// TEACHING MODEL. Sequential.
// Safety : no interface traffic is honoured while either side is in reset.
module reset_ordering #(parameter int NO_RELEASE_ORDER = 0) (
input logic clk, rst_n,
input logic release_a, release_b, traffic, report_now,
output logic out_a, out_b, both_ready, traffic_honoured,
output logic [7:0] n_traffic, n_honoured, n_unsafe,
output logic rst_err
);
logic a_q, b_q;
assign out_a = a_q;
assign out_b = b_q;
// The truth, computed the same way in BOTH builds.
assign both_ready = a_q && b_q;
// The whole review point: whether the interface is gated on both sides being
// out of reset, or only on the local side.
assign traffic_honoured = (NO_RELEASE_ORDER != 0) ? (traffic && b_q)
: (traffic && both_ready);
// SAFETY VIOLATION: traffic was honoured while one side was still resetting.
assign rst_err = traffic_honoured && !both_ready;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
a_q <= 1'b0; b_q <= 1'b0;
n_traffic <= 8'd0; n_honoured <= 8'd0; n_unsafe <= 8'd0;
end else begin
if (release_a) a_q <= 1'b1;
if (release_b) b_q <= 1'b1;
if (traffic) begin
n_traffic <= n_traffic + 8'd1;
if (traffic_honoured) n_honoured <= n_honoured + 8'd1;
if (rst_err) n_unsafe <= n_unsafe + 8'd1;
end
end
end
endmoduleThe measurement. Block B releases, block A does not, traffic arrives:
B released, A still in reset : both_gated=0 local_gated=1The local-gate build honours traffic into a block that is still resetting. It is not wrong about anything it can see: B is out of reset, and B is the block it is part of. The both-sides build refuses, and the run asserts that once A releases, both builds honour the same traffic legitimately.
Evidence to demand. The release order, written down, and the qualifier that enforces it. "They come out of reset together" is a claim about timing that nothing in either block guarantees.
What escapes. A first transaction lost, corrupted or double-counted on every power-up, in a window too narrow to notice and too repeatable to be random.
How DV proves it. Release the two ends in each order and drive traffic in the gap. A single-block environment cannot construct this, which is why it survives both reviews.
Telemetry. A sticky "traffic seen while not both ready" bit. It must read permanently zero, and it costs one flop.
8. Review Item 3 — Do The Two Sides Agree On The Parameter?
Under review. Every parameter, define or constant appearing on both sides of an interface.
Contract at risk. The width, depth or count the interface is built around.
Where it lives. Two instantiations, in two files, usually in two repositories.
The failure. A parameter on both sides of an interface is a shared contract, and nothing in the language enforces that the two instantiations agree. Each block elaborates cleanly, each is internally consistent, and the mismatch is visible only where they meet.
// RTL 3 - two blocks built from the same parameter, with different values.
//
// A parameter that appears on both sides of an interface is a shared contract,
// and nothing in the language enforces that the two instantiations agree. Each
// block elaborates cleanly, each is internally consistent, and the mismatch is
// visible only where the two meet - usually as a truncated field or an
// off-by-a-power-of-two address.
//
// BAD : two instantiations, two parameter values, no check
// GOOD : publish each side's value and compare them at the boundary
//
// TEACHING MODEL.
module param_agreement #(parameter int SKIP_THE_COMPARE = 0) (
input logic clk, rst_n,
input logic check_now,
input logic [7:0] width_a, width_b, payload,
output logic [7:0] delivered, truncated_by, agreed_width,
output logic agreed, reported_ok,
output logic [7:0] n_checks, n_mismatch,
output logic cfg_err
);
logic [7:0] narrow;
assign agreed = (width_a == width_b);
// Whatever the two sides agree on is the width that actually applies: the
// narrower one, because the wider side's extra bits have nowhere to go.
assign narrow = (width_a < width_b) ? width_a : width_b;
assign agreed_width = narrow;
assign delivered = (payload > narrow) ? narrow : payload;
assign truncated_by = (payload > narrow) ? (payload - narrow) : 8'd0;
// The whole review point.
assign reported_ok = (SKIP_THE_COMPARE != 0) ? 1'b1 : agreed;
// SAFETY VIOLATION: the two sides disagree and the boundary reports agreement.
assign cfg_err = check_now && !agreed && reported_ok;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_checks <= 8'd0; n_mismatch <= 8'd0;
end else if (check_now) begin
n_checks <= n_checks + 8'd1;
if (!agreed) n_mismatch <= n_mismatch + 8'd1;
end
end
endmoduleThe measurement. Side A built for 16, side B for 12, a payload of 14:
A=16 B=12 payload=14 : agreed=0 delivered=12 truncated=2 reported=1The narrower side is what actually applies, so 14 is delivered as 12 and two units are silently truncated. Both builds compute the same applied width and the same truncation; only one of them reports that the sides disagree.
The run drives the case where they agree too — and a payload of 14 into an agreed width of 12 still truncates by two. That is a sizing decision, not a mismatch, and keeping the two findings separate is the point of publishing both numbers.
Evidence to demand. Both values, side by side, as numbers. Not the parameter name — the value each side elaborated with.
What escapes. A field truncated at the boundary, or an address wrong by a power of two, presenting as a data bug in whichever block is easier to blame.
Telemetry. Publish each side's value in a readable register and compare them in hardware. A one-bit mismatch flag is the cheapest integration check that exists.
9. Review Item 4 — What Does ready Mean?
Under review. Every two-wire handshake.
Contract at risk. Whether a beat transferred.
Where it lives. Two documents, each internally consistent.
The failure. One side believes ready means "I will take it if you present it". The other believes it means "I have taken it". Both are defensible readings of two wires, and where they meet a beat is dropped every time the consumer deasserts ready after asserting it.
// RTL 4 - two readings of the same handshake.
//
// One side believes `ready` means "I will take it if you present it". The other
// believes `ready` means "I have taken it". Both are defensible readings of a
// two-wire handshake, both blocks are internally consistent, and where they
// meet a beat is dropped every time the consumer deasserts ready in the cycle
// after asserting it.
//
// BAD : each side documents its own reading, neither reads the other's
// GOOD : one written rule - a beat transfers when valid AND ready are both
// high on the same rising edge - and a lost-beat counter that proves it
//
// TEACHING MODEL. Sequential.
// Safety : every beat the producer counts as sent is a beat the consumer
// counts as received.
module handshake_reading #(parameter int READY_MEANS_WILLING = 0) (
input logic clk, rst_n,
input logic valid, ready, report_now,
output logic producer_sends, consumer_takes,
output logic [7:0] n_sent, n_taken, n_lost,
output logic agreed, hs_err
);
// The consumer's reading is fixed: a beat transfers on valid AND ready.
assign consumer_takes = valid && ready;
// The whole review point: the producer's reading of the same two wires.
assign producer_sends = (READY_MEANS_WILLING != 0) ? valid : (valid && ready);
assign agreed = (n_sent == n_taken);
// SAFETY VIOLATION: the producer believes it sent more than the consumer took.
assign hs_err = report_now && (n_sent != n_taken);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_sent <= 8'd0; n_taken <= 8'd0; n_lost <= 8'd0;
end else begin
if (producer_sends) n_sent <= n_sent + 8'd1;
if (consumer_takes) n_taken <= n_taken + 8'd1;
if (producer_sends && !consumer_takes) n_lost <= n_lost + 8'd1;
end
end
endmoduleThe measurement. valid held for three cycles with ready high on one:
3 cycles of valid, 1 of ready : strict_sent=1 willing_sent=3 taken=1One beat transferred and the willing reading counts three sends. Two beats are lost that neither side calls an error — the producer believes it sent them and the consumer never saw them, and there is no counter anywhere that disagrees unless somebody compares the two.
Evidence to demand. One written rule: a beat transfers when valid and ready are both high on the same rising edge. And a lost-beat counter that proves both sides implement it.
What escapes. Silent data loss proportional to how often the consumer toggles ready — which is to say, proportional to load.
How DV proves it. Hold valid and pulse ready for one cycle in three. A test with ready tied high transfers every beat and both readings agree.
Telemetry. Sent and taken, published by both sides. Their difference is the number of lost beats, and either number alone proves nothing.
10. Review Item 5 — Which End Is Bit Zero?
Under review. Every payload crossing a boundary.
Contract at risk. That the bytes arrive in the order they were sent.
Where it lives. A convention, which is exactly the kind of thing two teams settle differently without either noticing.
// RTL 5 - the same byte, numbered from the other end.
//
// Bit and byte ordering is a convention, and a convention is exactly the kind
// of thing two teams settle differently without either noticing. Nothing
// elaborates wrongly, no assertion fires, and the payload arrives with its
// bytes in the wrong order - which looks like data corruption rather than an
// integration defect, and is debugged as one.
//
// BAD : each side applies its own convention
// GOOD : one written convention at the boundary, and a known pattern that
// is asymmetric under reversal so the mistake cannot hide
//
// TEACHING MODEL.
module byte_order #(parameter int REVERSE_AT_BOUNDARY = 0) (
input logic clk, rst_n,
input logic transfer, check_now,
input logic [15:0] payload,
output logic [15:0] delivered, expected, reversed,
output logic order_ok, is_symmetric,
output logic [7:0] n_transfers, n_wrong,
output logic order_err
);
// Byte-swapped form of the same word.
assign reversed = {payload[7:0], payload[15:8]};
// The convention the boundary is specified to use.
assign expected = payload;
// The whole review point.
assign delivered = (REVERSE_AT_BOUNDARY != 0) ? reversed : payload;
assign order_ok = (delivered == expected);
// A palindromic pattern cannot detect a reversal. Publishing this is the
// review point about test patterns, not a property of the design.
assign is_symmetric = (payload[15:8] == payload[7:0]);
// SAFETY VIOLATION: the delivered word is not the word that was sent.
assign order_err = check_now && !order_ok;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_transfers <= 8'd0; n_wrong <= 8'd0;
end else if (transfer) begin
n_transfers <= n_transfers + 8'd1;
if (!order_ok) n_wrong <= n_wrong + 8'd1;
end
end
endmoduleThe measurement. A payload of A1B2 across a boundary that reverses:
payload A1B2 : straight=a1b2 reversed=b2a1 symmetric=0Nothing elaborates wrongly and no assertion fires. The payload arrives with its bytes swapped, which looks like data corruption rather than an integration defect — and is debugged as one, usually for a while.
The test pattern is part of the review
The run drives C3C3 as well, and the model publishes whether a pattern is palindromic:
A symmetric pattern cannot detect a reversal. C3C3 reversed is C3C3, the reversing build looks entirely correct, and its error count does not move. A test pattern of repeated bytes — which is the easiest pattern to generate — makes this defect invisible.
Evidence to demand. The convention, written once, at the boundary. And the test pattern, which must be asymmetric under reversal.
What escapes. Byte-swapped payloads, diagnosed as corruption.
Telemetry. A known asymmetric pattern in a scratch register, readable from both sides. Reading it back the wrong way round is a one-instruction diagnosis.
11. Review Item 6 — Where Does The Remainder Go?
Under review. Every width converter, packer and unpacker.
Contract at risk. That every beat accepted is eventually emitted.
Where it lives. The end-of-transfer path.
The failure. A narrow-to-wide adapter accumulates beats until it has a full wide word. When a transfer ends on a partial word, that remainder has to go somewhere: flushed with a byte-enable, held for the next transfer, or silently dropped. The third requires no code at all, which is why it is the common one.
// RTL 6 - the width converter, and the beat that is left over.
//
// A narrow-to-wide adapter accumulates beats until it has a full wide word.
// When the transfer ends on a partial word, that remainder has to go somewhere:
// flushed with a byte-enable, held for the next transfer, or silently dropped.
// The third is the common one, because it requires no code at all.
//
// BAD : emit only on a full word; the remainder is never emitted
// GOOD : flush the partial word at end-of-transfer, with a valid-byte count
//
// TEACHING MODEL. Sequential.
// State remembered : the partial word and how many narrow beats it holds.
// Safety : every narrow beat accepted is eventually emitted.
module width_adapt #(parameter int DROP_REMAINDER = 0) (
input logic clk, rst_n,
input logic beat_valid, end_of_transfer, report_now,
input logic [3:0] beat_data,
output logic [7:0] wide_out,
output logic [1:0] held_count,
output logic wide_valid, flushed, remainder_lost,
output logic [7:0] n_in, n_out_beats, n_dropped,
output logic residue_err
);
logic [7:0] acc_q;
logic [1:0] cnt_q;
logic full, partial_at_end;
assign wide_out = acc_q;
assign held_count = cnt_q;
assign full = beat_valid && (cnt_q == 2'd1);
assign partial_at_end = end_of_transfer && (cnt_q != 2'd0);
// The whole review point: what happens to a partial word at the end.
assign flushed = (DROP_REMAINDER != 0) ? 1'b0 : partial_at_end;
assign wide_valid = full || flushed;
assign remainder_lost = (DROP_REMAINDER != 0) && partial_at_end;
// SAFETY VIOLATION: narrow beats were accepted and never emitted.
assign residue_err = report_now && (n_in != (n_out_beats + {6'd0, cnt_q}));
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
acc_q <= 8'd0; cnt_q <= 2'd0;
n_in <= 8'd0; n_out_beats <= 8'd0; n_dropped <= 8'd0;
end else begin
if (beat_valid) begin
n_in <= n_in + 8'd1;
acc_q <= {acc_q[3:0], beat_data};
cnt_q <= (cnt_q == 2'd1) ? 2'd0 : (cnt_q + 2'd1);
end
// Each emitted wide word carries two narrow beats; a flush carries the
// one that is held.
if (full) n_out_beats <= n_out_beats + 8'd2;
else if (flushed) n_out_beats <= n_out_beats + {6'd0, cnt_q};
if (end_of_transfer) begin
if (remainder_lost) n_dropped <= n_dropped + {6'd0, cnt_q};
cnt_q <= 2'd0;
end
end
end
endmoduleThe measurement. Three narrow beats into a two-to-one adapter:
3 beats in : flushing_flush=1 dropping_lost=1Three in and two out. The flushing build emits the remainder and the conservation equation holds — three beats in, three accounted for. The dropping build emits two, loses one, and nothing reports it.
The run drives the even case too: four beats make two whole words, nothing is held, and the dropping build loses nothing. A transfer length that happens to be even is the case that hides this, and round numbers are what directed tests use.
Evidence to demand. What happens to a partial word at end-of-transfer, in one sentence, plus the conservation equation: beats in equals beats out plus beats held.
What escapes. The tail of every odd-length transfer, silently.
How DV proves it. An odd-length transfer, and a continuous conservation check. Even lengths cannot find it.
Telemetry. Beats in, beats out, beats held — and the equation evaluated in hardware.
12. Review Item 7 — Who Initialises The Credits?
Under review. Every credit-based or token-based interface.
Contract at risk. That advertised capacity matches real capacity.
Where it lives. The reset behaviour of two blocks, neither of which can see the other's.
The failure. A credit interface needs somebody to load the initial count, and exactly one somebody. If both sides do it, capacity is advertised twice and the producer sends more than the queue can hold. If neither does, the producer holds zero credits and the link never starts — which presents as a dead interface rather than as an integration defect.
// RTL 7 - who initialises the credits?
//
// A credit-based interface needs somebody to load the initial credit count at
// reset, and exactly one somebody. If both sides do it, the consumer's capacity
// is advertised twice and the producer sends more than the queue can hold. If
// neither does, the producer holds zero credits and the link never starts -
// which presents as a dead interface rather than as an integration defect.
//
// BAD : "the other side initialises the credits"
// GOOD : one named owner, and a published credit count both sides can read
//
// TEACHING MODEL. Sequential.
// Safety : advertised credits never exceed the consumer's real capacity.
// Liveness : the link starts - ASSUMING somebody initialises.
module credit_owner #(parameter int BOTH_INITIALISE = 0) (
input logic clk, rst_n,
input logic init_a, init_b, send, credit_return, report_now,
input logic [7:0] capacity,
output logic [7:0] credits, n_sends, n_refused, n_overrun,
output logic can_send, overrun, link_dead,
output logic credit_err
);
logic [7:0] cr_q;
logic loaded_q;
assign credits = cr_q;
assign can_send = send && (cr_q != 8'd0);
// The consumer cannot hold more than its capacity, whatever was advertised.
assign overrun = (cr_q > capacity);
assign link_dead = loaded_q && (cr_q == 8'd0) && (capacity != 8'd0);
// SAFETY VIOLATION: more credits are outstanding than the consumer can hold.
assign credit_err = report_now && overrun;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cr_q <= 8'd0; loaded_q <= 1'b0;
n_sends <= 8'd0; n_refused <= 8'd0; n_overrun <= 8'd0;
end else begin
// The whole review point, stated explicitly. An initialisation LOADS the
// capacity, so a re-initialisation after a retrain reloads rather than
// accumulating. The two-owner defect is that the second owner ADDS its
// advertisement on top of the first one's.
if (init_a) begin cr_q <= capacity; loaded_q <= 1'b1; end
else if (init_b && (BOTH_INITIALISE != 0)) begin
cr_q <= cr_q + capacity; loaded_q <= 1'b1;
end else if (init_b && (BOTH_INITIALISE == 0)) begin
loaded_q <= 1'b1; // the non-owner acknowledges and loads nothing
end else begin
case ({can_send, credit_return})
2'b10: cr_q <= cr_q - 8'd1;
2'b01: cr_q <= (cr_q == 8'hFF) ? 8'hFF : (cr_q + 8'd1);
default: cr_q <= cr_q;
endcase
end
if (send && (cr_q != 8'd0)) n_sends <= n_sends + 8'd1;
if (send && (cr_q == 8'd0)) n_refused <= n_refused + 8'd1;
if (overrun) n_overrun <= n_overrun + 8'd1;
end
end
endmoduleThe measurement. A consumer capacity of 4, with both sides initialising:
capacity 4, both sides init : one_owner=4 two_owners=8Four more beats can be sent than there is anywhere to put them. The single-owner build advertises 4 and stays within capacity; the two-owner build advertises 8 and is in overrun from the moment it starts.
The model carried an unstated contract of its own
Writing this model surfaced the chapter's own subject one level down. cr_q <= cr_q + capacity and cr_q <= capacity are identical from reset, because the counter is zero there. They differ on a re-initialisation — a retrain, a soft reset, a renegotiation — and the model had never said which it meant.
It now says. An initialisation loads the capacity, so a retrain reloads rather than accumulating; the two-owner defect is that the second owner adds on top of the first. The run drives a double initialisation and asserts the count stays at 4.
Evidence to demand. The named owner, and what a re-initialisation does to an existing count. Two questions, and the second one is the one nobody asks.
What escapes. A queue overrun under load, or a link that never starts — and the second is diagnosed as a bring-up problem for days.
Telemetry. Advertised credits and real capacity, both readable. A dead-link bit for the case where nobody loaded anything.
13. Review Item 8 — Do Two Decoders Claim The Same Address?
Under review. The address map.
Contract at risk. That one request reaches one target.
Where it lives. Two decoders, each correct against its own region.
The failure. Where regions overlap, both decoders assert, two targets respond to one request, and the requester gets whichever answer arrives first — stable in simulation and dependent on routing in silicon.
// RTL 8 - two decoders that both claim the same address.
//
// Each block's address decode is correct against its own region. What no block
// owns is the question of whether the regions overlap. Where they do, both
// decoders assert, two targets respond to one request, and the requester gets
// whichever answer arrives first - which is stable in simulation and depends on
// routing in silicon.
//
// BAD : each block decodes its own region
// GOOD : one map, an overlap check across every pair, and a
// multiple-hit detector that must read permanently zero
//
// TEACHING MODEL.
module decode_overlap #(parameter int NO_OVERLAP_CHECK = 0) (
input logic clk, rst_n,
input logic request, report_now,
input logic [7:0] addr, a_lo, a_hi, b_lo, b_hi,
output logic hit_a, hit_b, multiple_hit, decode_ok,
output logic [7:0] n_requests, n_multiple, n_unclaimed,
output logic decode_err
);
logic regions_overlap;
assign hit_a = request && (addr >= a_lo) && (addr <= a_hi);
assign hit_b = request && (addr >= b_lo) && (addr <= b_hi);
assign multiple_hit = hit_a && hit_b;
// Whether the MAP itself is malformed, independent of any one request.
assign regions_overlap = (a_lo <= b_hi) && (b_lo <= a_hi);
// The whole review point: whether the boundary reports a malformed map.
assign decode_ok = (NO_OVERLAP_CHECK != 0) ? 1'b1 : !regions_overlap;
// SAFETY VIOLATION: two targets claimed one address and the map reported fine.
assign decode_err = report_now && multiple_hit && decode_ok;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_requests <= 8'd0; n_multiple <= 8'd0; n_unclaimed <= 8'd0;
end else if (request) begin
n_requests <= n_requests + 8'd1;
if (multiple_hit) n_multiple <= n_multiple + 8'd1;
if (!hit_a && !hit_b) n_unclaimed <= n_unclaimed + 8'd1;
end
end
endmoduleThe measurement. Region A covers 0x10–0x3F and region B covers 0x30–0x5F:
A=10..3F B=30..5F addr=38 : hit_a=1 hit_b=1 checked_ok=0 unchecked_ok=1Both decoders claim 0x38. Both builds detect the multiple hit; only one of them reports that the map itself is malformed, which is the finding that fixes every address in the overlap rather than the one that was tested.
The run drives two boundary cases the campaign demanded. Regions that are merely adjacent — 0x2F and 0x30 — do not overlap and both builds agree. Regions that touch at exactly one address do overlap, and that single-address case is the one a < instead of a <= gets wrong.
Evidence to demand. The full map, and an overlap check across every pair — not a spot check on the addresses somebody happened to test.
What escapes. Two targets answering one request, with the winner decided by physical routing.
Telemetry. A multiple-hit counter. Permanently zero, and it costs an AND gate per decoder pair.
14. Review Item 9 — Is It Quiescent, Or Just Quiet?
Under review. Every clock gate, power gate and low-power entry.
Contract at risk. That responses still owed will arrive.
Where it lives. The gate decision, which is almost never made by the block being gated.
The failure. A gate applied while responses are outstanding does not corrupt anything. It simply stops them arriving, and the requester waits for a response from a block that is no longer clocked. The symptom is a hang and the cause is in a different file.
// RTL 9 - the block that was clock-gated with work still in it.
//
// Power management is an integration concern because the block being gated is
// rarely the block that decides to gate it. A gate applied while responses are
// still outstanding does not corrupt anything: it simply stops them arriving,
// and the requester waits for a response from a block that is no longer
// clocked. The symptom is a hang, and the cause is in a different file.
//
// BAD : gate on an idle signal that means "no new requests"
// GOOD : gate on quiescence - no new requests AND nothing outstanding - and
// publish the outstanding count so the gate decision is reviewable
//
// TEACHING MODEL. Sequential.
// Safety : the block is never gated with work outstanding.
module gate_quiescence #(parameter int GATE_ON_IDLE_ONLY = 0) (
input logic clk, rst_n,
input logic issue, complete, no_new_requests, gate_req,
output logic [7:0] outstanding, n_gates, n_orphaned,
output logic quiescent, gated, orphan_risk,
output logic gate_err
);
logic [7:0] out_q;
logic gated_q;
assign outstanding = out_q;
// The truth, computed the same way in BOTH builds.
assign quiescent = no_new_requests && (out_q == 8'd0);
// The whole review point: what the gate decision is actually gated on.
assign gated = (GATE_ON_IDLE_ONLY != 0) ? (gate_req && no_new_requests)
: (gate_req && quiescent);
assign orphan_risk = gated && (out_q != 8'd0);
// SAFETY VIOLATION: the block was gated with responses still owed.
assign gate_err = orphan_risk;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out_q <= 8'd0; gated_q <= 1'b0; n_gates <= 8'd0; n_orphaned <= 8'd0;
end else begin
// One assignment to the outstanding counter, computed from both events.
case ({issue, complete})
2'b10: out_q <= out_q + 8'd1;
2'b01: out_q <= (out_q == 8'd0) ? 8'd0 : (out_q - 8'd1);
default: out_q <= out_q;
endcase
gated_q <= gated;
if (gated && !gated_q) n_gates <= n_gates + 8'd1;
if (orphan_risk) n_orphaned <= n_orphaned + 8'd1;
end
end
endmoduleThe measurement. Three requests issued, one completed, the requester stops issuing:
2 outstanding, no new requests : quiescence_gate=0 idle_gate=1"No new requests" is true and the block is not idle. The idle-gated build gates and orphans two responses; the quiescence-gated build refuses until the outstanding count reaches zero, and the run asserts that it then gates legitimately.
Evidence to demand. The gate condition, in full. "Idle" is a word; the outstanding count is a number, and the second one is the condition.
What escapes. A hang after a low-power entry, reproducible only under the traffic pattern that leaves work outstanding at the idle threshold.
Telemetry. The outstanding count at the moment of gating. Permanently zero, and it makes the gate decision reviewable after the fact.
15. Review Item 10 — What Is That Unconnected Port Doing?
Under review. Every input with a default, and every port left unconnected at integration.
Contract at risk. That the block is running the configuration somebody chose.
Where it lives. A default written by the module's author, applied by the integrator's silence.
// RTL 10 - the port nobody connected, and the default that changed the design.
//
// An input left unconnected takes its default. A default is a decision, and it
// is usually made by whoever wrote the module rather than by whoever
// instantiated it. The integration is silent: the design elaborates, simulates
// and synthesises, and behaves as configured by a value nobody chose.
//
// BAD : an input with a default, left unconnected at integration
// GOOD : publish the value the block is actually running with, and check it
// against the value the integrator intended
//
// TEACHING MODEL.
module tie_off_default #(parameter int TRUST_THE_DEFAULT = 0) (
input logic clk, rst_n,
input logic connected, check_now,
input logic [7:0] intended_mode, wired_mode, default_mode,
output logic [7:0] effective_mode, reported_mode,
output logic agreed, observable,
output logic [7:0] n_checks, n_silent,
output logic tie_err
);
// What the block is actually running with.
assign effective_mode = connected ? wired_mode : default_mode;
// The whole review point: whether the block publishes it.
assign reported_mode = (TRUST_THE_DEFAULT != 0) ? intended_mode : effective_mode;
assign observable = (reported_mode == effective_mode);
assign agreed = (effective_mode == intended_mode);
// SAFETY-OF-EVIDENCE VIOLATION: the block is running one configuration and
// reporting another, so no integrator can tell.
assign tie_err = check_now && !agreed && !observable;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_checks <= 8'd0; n_silent <= 8'd0;
end else if (check_now) begin
n_checks <= n_checks + 8'd1;
if (!agreed && !observable) n_silent <= n_silent + 8'd1;
end
end
endmoduleThe measurement. The integrator intended mode 3 and left the port unconnected:
intended=3 default=1 unconnected : effective=1 published=1 echoed=3Both builds run mode 1. The behaviour is identical; the difference is what the block says it is running. The publishing build reports 1 and the mismatch is visible to anybody who looks; the echoing build reports 3 — the intent — and no integrator can tell.
The integration is silent. The design elaborates, simulates and synthesises, and behaves as configured by a value nobody chose.
Evidence to demand. For every defaulted input, the value the block is actually running with, readable at runtime. Not the default in the source — the effective value in the instance.
What escapes. A fleet running a configuration nobody selected, where a bug that reproduces on one build and not another cannot be localised because the build cannot be read back.
Telemetry. A configuration-report register carrying every effective value. This item is telemetry, which is why it is the cheapest of the eleven to fix and among the most often missing.
16. Review Item 11 — Was The Peer Upgraded?
Under review. Every interface whose two ends can ship on different schedules.
Contract at risk. That a feature used is a feature the peer supports.
Where it lives. The negotiation, or its absence.
The failure. A block is upgraded and its neighbour is not. The interface is backward-compatible, which is true and is not the same as saying nothing changes: the newer side must detect the older peer and fall back, and a fallback that is not negotiated is a fallback that does not happen.
// RTL 11 - two revisions of one interface, integrated together.
//
// A block is upgraded and its neighbour is not. The interface is
// backward-compatible, which is true and is not the same as saying nothing
// changes: the newer side must DETECT the older peer and fall back, and a
// fallback that is not negotiated is a fallback that does not happen.
//
// BAD : assume the peer is the same revision
// GOOD : exchange revisions, operate at the minimum, and publish the
// negotiated revision so the fallback is visible
//
// TEACHING MODEL. Revision numbers here are illustrative integers, not CXL
// specification revisions.
module revision_skew #(parameter int SKIP_NEGOTIATION = 0) (
input logic clk, rst_n,
input logic negotiate, use_feature, report_now,
input logic [7:0] rev_a, rev_b, feature_needs_rev,
output logic [7:0] operating_rev, n_negotiations, n_unsupported,
output logic compatible, feature_ok, degraded,
output logic skew_err
);
logic [7:0] lower;
assign lower = (rev_a < rev_b) ? rev_a : rev_b;
// The whole review point: the revision the link actually operates at.
assign operating_rev = (SKIP_NEGOTIATION != 0) ? rev_a : lower;
assign degraded = (lower < rev_a) || (lower < rev_b);
assign compatible = (operating_rev >= feature_needs_rev);
assign feature_ok = use_feature && compatible;
// SAFETY VIOLATION: a feature was used that the peer's revision cannot
// support, because the link never negotiated down to it.
assign skew_err = use_feature && (lower < feature_needs_rev) && feature_ok;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_negotiations <= 8'd0; n_unsupported <= 8'd0;
end else begin
if (negotiate) n_negotiations <= n_negotiations + 8'd1;
if (skew_err) n_unsupported <= n_unsupported + 8'd1;
end
end
endmoduleThe measurement. Side A at revision 3, side B at revision 1, a feature needing revision 2:
A=3 B=1 feature needs 2 : negotiated_ok=0 unnegotiated_ok=1Negotiating down operates at revision 1 and refuses the feature. Skipping negotiation operates at A's own 3, believes the feature is available, and uses something the peer cannot support. The run also drives the boundary — a peer exactly at the revision the feature requires, which is compatible — and the matched case where both sides agree and the feature is legitimately available.
Evidence to demand. The negotiated revision, published, and what the newer side does when it is lower than its own. "It is backward compatible" describes the specification, not the implementation.
What escapes. A feature used against a peer that cannot honour it, on exactly the mixed-revision systems that upgrades create.
Telemetry. The operating revision, readable from both ends, and a degraded-link flag. A fleet with mixed revisions and no way to read which is a fleet nobody can partition.
17. The Review Assembled
Eleven dimensions, one summary — and the trap this module has found at every level, in its integration form.
// RTL 12 - an integration review assembled. Eleven review dimensions, one
// summary. "Both blocks passed their own review" is bit 0: two correct blocks,
// and one sixth of an integration review.
module intg_review_signoff #(parameter int BLOCKS_PASSED_IS_PROOF = 0) (
input logic clk, rst_n,
input logic review,
input logic blocks_passed, domains_qualified, resets_ordered,
input logic params_compared, map_disjoint, ties_published,
output logic [5:0] fail_mask,
output logic [15:0] conditions_met, sound_pct,
output logic sound,
output logic [7:0] n_reviews, n_sound, n_claimed,
output logic intg_err
);
logic [31:0] s_q;
logic truly_sound, claimed;
assign fail_mask[0] = ~blocks_passed;
assign fail_mask[1] = ~domains_qualified;
assign fail_mask[2] = ~resets_ordered;
assign fail_mask[3] = ~params_compared;
assign fail_mask[4] = ~map_disjoint;
assign fail_mask[5] = ~ties_published;
assign conditions_met = {15'd0, blocks_passed} + {15'd0, domains_qualified}
+ {15'd0, resets_ordered} + {15'd0, params_compared}
+ {15'd0, map_disjoint} + {15'd0, ties_published};
assign s_q = ({16'd0, conditions_met} * 32'd100) / 32'd6;
// No clamp: six one-bit values over six cannot exceed a hundred.
assign sound_pct = s_q[15:0];
assign truly_sound = (fail_mask == 6'd0);
assign claimed = (BLOCKS_PASSED_IS_PROOF != 0) ? blocks_passed : truly_sound;
assign sound = claimed;
assign intg_err = review && !truly_sound && claimed;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_reviews <= 8'd0; n_sound <= 8'd0; n_claimed <= 8'd0;
end else if (review) begin
n_reviews <= n_reviews + 8'd1;
if (truly_sound) n_sound <= n_sound + 8'd1;
if (claimed) n_claimed <= n_claimed + 8'd1;
end
end
endmoduleThe measurement. Two views of the same integration review:
mask=000010 met=5 sound=83% / mask=111110 met=1 sound=16%The first line is a real review with one finding open — bit 1, a multi-bit crossing that is not qualified. Five of six conditions established, one item to close.
The second line is what this chapter exists to prevent. Bit 0 is clear and every other bit is set: both blocks passed their own reviews, and nothing else was checked. No crossing qualified, no reset order stated, no parameter compared, no map checked, no tie-off published. Sixteen percent of an integration review, reported as an integration review — and it is the most defensible-sounding sign-off in this module, because everything it says is true.
Figure 3 — bit 0 is the one both teams can evidence. Each of the other five is a statement about the space between the blocks, which is why each needs an owner named before the review starts.
18. Quantitative Reasoning
Every figure here is a teaching parameter or a value derived from one and asserted by the testbench. None is a measurement of a real system, and none is a CXL specification number.
The assembled value. F0 is 1111_0000 and 0F is 0000_1111. Taking bit 3 from the old word and the other seven from the new gives 0000_0111 = 07. Both source words are legal and 07 is neither of them. In general, an n-bit bus with k bits skewed can assemble up to 2^k distinct words, of which at most two are values the source held.
How many synchronisers. A bus of n bits protected per-bit costs 2n flops and protects nothing about the word. A qualified crossing costs 2 flops for the qualifier plus the n flops of the holding register — fewer flops and a correct result at every width above one.
Parameter mismatch, derived. A = 16, B = 12, payload = 14. The applied width is min(16, 12) = 12, and the truncation is 14 − 12 = 2 units, a 14.3 percent loss of the payload. The general form is loss = max(0, payload − min(A, B)), which is why a mismatch is invisible whenever the payload happens to be small.
Handshake readings, derived. Three cycles of valid against one cycle of ready transfers 1 beat. The strict reading counts 1; the willing reading counts 3. The lost count is valid cycles − transfers = 2, and it scales with the fraction of cycles the consumer withholds ready — zero on an idle link and proportional to load on a busy one.
Byte order. A1B2 reversed is B2A1 — two bytes moved, a hundred percent of the payload wrong. C3C3 reversed is C3C3 — zero percent wrong, from the identical defect. A palindromic test pattern has a detection probability of zero here; an asymmetric one has a detection probability of one. That factor is the whole value of choosing the pattern.
The remainder, derived. Three narrow beats into a two-to-one adapter make one whole wide word and a remainder of 1 beat. The flushing build emits 2 words for 3 beats in, and the conservation equation 3 = 2 + 1 holds with the flush counted. The dropping build emits 1 word, and 3 = 2 + 1 fails by one beat — 33 percent of the transfer, silently. The general form is lost = beats mod ratio, which is zero for exactly the transfer lengths a directed test picks.
Credits, derived. A capacity of 4 initialised by one owner advertises 4. Initialised by both it advertises 4 + 4 = 8 — an overclaim of 100 percent, and the producer can have four beats in flight with nowhere to put any of them.
Address overlap, derived. A covers 0x10–0x3F, which is 48 addresses. B covers 0x30–0x5F, also 48. The overlap is 0x30–0x3F = 16 addresses, one third of region A. An access at 0x38 is inside both. Regions that touch at exactly one address overlap by one address, which is the case a < instead of a <= gets wrong and the case a four-address spot check never drives.
Quiescence, derived. Three requests issued and one completed leaves 2 outstanding. "No new requests" is true and the block is not idle; gating orphans two responses, and each orphan is one requester waiting for something that will never arrive.
Tie-off, derived. Intended mode 3, default mode 1, port unconnected: the effective mode is 1. The behaviour difference is 100 percent of the configuration decision, and the observable difference between the two builds is zero — only the published value differs.
Revision skew, derived. A at 3, B at 1, a feature requiring 2. The negotiated revision is min(3, 1) = 1, and 1 is below 2, so the feature is unavailable. Skipping negotiation operates at A's own 3, and 3 is at or above 2, so the feature is believed available. The boundary is a peer at exactly 2, which is compatible — the case that separates a correct comparison from an off-by-one one.
The sign-off arithmetic. Six conditions; five met is 5 x 100 / 6 = 83 percent under integer division, and one met is 100 / 6 = 16 percent.
19. Verification Method
Order of work
compile → inspect warnings → legal baseline → reset → boundaries → simultaneous events → abuse and error cases → configuration contrasts → structural gates → PASS → mutation campaign
A mutation campaign on a failing baseline is invalid, and both campaigns in this chapter ran against a green one. The baseline was re-run after every testbench modification before the campaign was re-run.
Both sides are always present
This is the one methodological difference between this chapter and the rest of Module 30. Every model instantiates both readings of a shared contract against the same stimulus, because an integration defect cannot be exhibited by one side. A single-block environment stubs the other end, and a stub implements whichever reading its author holds — which is precisely the assumption under review.
Independent oracles
| Model | Oracle |
|---|---|
| domain hop | F0 then 0F with bit 3 late → 07 assembled, 0F qualified |
| reset order | B out, A in reset, traffic → local gate honours, both-gate refuses |
| parameter agreement | A 16, B 12, payload 14 → applied 12, truncated 2 |
| handshake reading | 3 valid, 1 ready → 1 taken, 1 strict send, 3 willing sends |
| byte order | A1B2 → a1b2 straight, b2a1 reversed; C3C3 symmetric |
| width adapt | 3 beats, ratio 2 → 2 emitted flushing, 1 lost dropping |
| credit owner | capacity 4 → 4 one owner, 8 two owners; re-init stays 4 |
| decode overlap | 10..3F and 30..5F, addr 38 → both hit; adjacent 2F/30 → neither |
| gate quiescence | 3 issued, 1 done → 2 outstanding, idle gate fires, quiescence gate does not |
| tie-off | intended 3, default 1 → effective 1, published 1, echoed 3 |
| revision skew | A 3, B 1, feature needs 2 → negotiated 1, unavailable |
| sign-off | five of six → 83 percent; one of six → 16 percent |
chkv prints got against expected, which is what lets an oracle be wrong out loud. In this chapter it caught three, all mine, recorded in section 20.
X and Z rejected explicitly
chk(c, …) tests c !== 1'b1, so an X-valued condition fails rather than passing. chkv(got, exp, …) reduces the result and reports an explicit X/Z failure before comparing. That rejection is load-bearing — the chapter after this one owes it three undriven outputs that -Wall did not mention.
Pulses are latched, never sampled
Every evidence output — cdc_err, rst_err, cfg_err, hs_err, order_err, residue_err, credit_err, decode_err, gate_err, tie_err, skew_err, intg_err — is caught by a continuous always @(posedge clk) monitor into a sticky bit, asserted outside any conditional, in a window containing a clock edge.
Stimulus never lands on the active edge, and reset is released after it
step_clk is @(posedge clk); #1;. Reset release lands one delta after the edge, carried forward from the race 30.5 exposed.
Safety, liveness and performance kept apart
Safety — a value delivered across a domain is a value the source held; no traffic is honoured while either side is in reset; advertised credits never exceed real capacity; one request reaches one target; the block is never gated with work outstanding; every beat accepted is eventually emitted.
Liveness — one claim, and it is stated with its assumption. The credit link starts assuming somebody initialises it. That assumption is the review item.
Performance — nothing in this chapter is a performance claim. A dropped beat is a safety failure, not a slow one, and the distinction decides whether a finding blocks a tapeout.
20. Baseline Defects Found Before Mutation
RTL findings — one, and the campaign found it
An unstated contract inside the credit model, described in full in section 12. cr_q <= cr_q + capacity and cr_q <= capacity are identical from reset and differ only on a re-initialisation, and the model had never said which it meant. The chapter's own subject, one level down: a model about two sides disagreeing on a shared contract carried an unstated contract of its own.
Testbench defects — one
The domain-crossing sample window was one cycle early. The assembled word reaches the destination register on the crossing edge, so a sample_now asserted in that cycle reads the value from before it and the impossible-value counter never moved. Moved to the cycle after.
This is the same class of error as the wrong oracles below — not arithmetic, but when. Section 13 of 30.2 names it: a registered value is available the cycle after the condition that produced it, and every off-by-one in this chapter is that sentence unapplied.
Wrong oracles — three, all mine
| Expected | Truth | Why |
|---|---|---|
the benign-skew case delivers 0F | 07 | prev only moves on an update, so the two source words were still different |
wide_out holds 0x12 | 0x23 | wide_out is the live total, not the word emitted one beat earlier |
| the gate counter reads 1 | 0 | it counts a rising edge, so it moves on the next clock, not in the cycle the decision becomes true |
The second is the one worth keeping. wide_out is named for what it outputs, and a register named for what it outputs is not a record of what it output. I read the name instead of the assignment, which is the same mistake in a testbench that a reviewer makes reading a signal list instead of the RTL.
Coverage gaps found by the structural gates
| Gate | Finding | Closed by |
|---|---|---|
outscan | 32 unasserted output nets | value assertions on every one |
banned, excheck, splitcheck, domcheck, displaycheck | none | — |
Compiler-warning findings
Under -Wall the twelve models produce no truncation, select-width, signedness, cast, multiplication-width, shift-width or loop-width warnings. The only warning is a missing timescale on models with no delay constructs — inspected and recorded as benign.
Two width results were reasoned rather than trusted. The width adapter's accumulator is {acc_q[3:0], beat_data} — an 8-bit result from a 4-bit slice and a 4-bit input, exact by construction. The domain-crossing model's assembled is a three-part concatenation that must total exactly eight bits: [7:4] is four, the selected bit is one, [2:0] is three. The tool does not check that a concatenation matches its destination when the widths happen to agree, so both were counted by hand.
Simulator constraints
Icarus Verilog 13.0 rejects ref task arguments, carried forward from every chapter in this module.
21. Mutation Testing
98 mutations attempted, 98 non-equivalent, 98 killed. Zero unexplained survivors, zero equivalent mutants withdrawn.
| Reported separately | Count |
|---|---|
| Mutants attempted | 98 |
| Withdrawn as equivalent | 0 |
| Non-equivalent mutants | 98 |
| Killed | 98 |
| Unexplained survivors | 0 |
| Model | Dimension | Muts |
|---|---|---|
| m1 | domain crossing | 8 |
| m2 | reset order | 7 |
| m3 | parameter agreement | 7 |
| m4 | handshake reading | 7 |
| m5 | byte order | 7 |
| m6 | width adaptation | 9 |
| m7 | credit ownership | 10 |
| m8 | decode overlap | 8 |
| m9 | gate quiescence | 8 |
| m10 | tie-off default | 7 |
| m11 | revision skew | 8 |
| m12 | review sign-off | 12 |
The first campaign, over models 1 to 6, produced zero survivors. The second, over models 7 to 12, produced six, every one classified before anything was changed.
One survivor was a defect in the model, not a gap in the stimulus
The credit mutation turning cr_q <= cr_q + capacity into cr_q <= capacity could not be killed, because the two are identical from reset. They differ only on a second initialisation, which nothing drove.
The correct response was not to add a stimulus case to a model whose meaning was undecided. The model was restructured so the two are explicit — an initialisation loads, the two-owner defect adds on top — and the re-initialisation case was then added and the mutation killed. A mutation that cannot be killed is sometimes telling you the model does not know what it means.
Three were a boundary the stimulus approached and never landed on
| Boundary | What was driven | What was not |
|---|---|---|
| the credit saturation ceiling | ordinary counts | a capacity of 255 with a return on top |
| regions overlapping by one address | a one-address gap between them | regions sharing exactly one address |
| a region's first address | four addresses inside and outside | exactly b_lo |
This is the same family 30.5 produced, and the same rule kills it:
For every comparison in a model, drive the value below it, above it, and exactly on it.
The address-map case is the one that generalises furthest. Two regions that touch at exactly one address do overlap, and a spot check on four convenient addresses is path coverage rather than value coverage.
Two were checkers that did not look
The gate counter was held for two cycles, and the count was asserted after the first and never after the second — which is the only place a per-cycle counter differs from a per-edge one. The tie-off check counter was asserted only at reset, where both builds read zero.
Both are the same shape: a check placed where the two builds agree. It is the shape a vacuous checker takes when nothing about it looks wrong.
The classification rule
Never add an assertion for a survivor before classifying it.
| Class | Means, and what to do |
|---|---|
| Equivalent | no input tells the two apart — withdraw it, never count a kill |
| Stimulus gap | the case is never driven — extend the stimulus |
| Missing checker | the case is driven and nothing looks — add the checker |
| Vacuous checker | the check cannot fail — fix the check, not the design |
| Model ambiguity | the model has not decided what it means — decide, then re-mutate |
| Unreachable | its guard never holds — fix the guard |
| Masked | another mechanism hides it — expose it, or say why you cannot |
| Coincidental | the arithmetic happens to agree — change the stimulus |
| Missing config | the build that differs is never built — instantiate it |
| Other | anything else — state it precisely |
The fifth row is new in this chapter, and it is the one that produced a better model rather than a better testbench.
22. Synthesis And Implementation Reality
A qualified crossing is cheaper than a per-bit one at every width above one. Two flops for the qualifier and a holding register the design already needs, against two flops per bit. The correct structure costs less than the incorrect one, which removes every argument for the incorrect one.
A reset sequencer is a small counter and a comparator, plus one "both released" net routed to the interface gate. It is the cheapest ordering guarantee available, and the alternative is a window whose width depends on reset-tree balancing.
A parameter comparison at the boundary is a register per side and an equality check. For a 16-bit parameter that is 32 flops and a comparator — less than any single one of the defects it catches costs to debug.
A lost-beat counter is one register and a two-input gate. It is the only structure that can distinguish two readings of a handshake in silicon.
Byte-order correctness is free. A reversal is wires. Its cost is entirely in the convention being written down, and it is the only item in this chapter with no gate count at all.
A flush path costs a byte-enable per narrow beat and one comparator. For a four-to-one adapter that is four bits and a small amount of control — against the tail of every odd-length transfer.
A credit-count publication register is the credit counter routed out. It already exists, and this item is the cost of a read port.
An overlap check is a pair of comparators per decoder pair, or nothing at all if the map is checked in a script before elaboration. The script is free and the hardware detector is a handful of gates — and only the second one survives a map edited after the script ran.
An outstanding-transaction counter exists in almost every design that can have transactions outstanding. Gating on it costs a zero-comparison. The expensive version of this item is the one where the counter does not exist, and then it is one register.
A configuration-report register is the effective values, routed to a read address. No logic at all. It is the cheapest item in this chapter and among the most often missing, because nothing fails without it.
A negotiated-revision register is two registers, a minimum, and a comparator. The minimum is two comparators wide at whatever the revision field is.
No area, frequency or power figures appear in this chapter, because none was measured.
23. Silicon Observability
| Telemetry | What it exposes |
|---|---|
| a count of destination values outside the legal set | a multi-bit crossing assembling values the source never held |
| a sticky "traffic while not both ready" bit | an unordered reset release, on any power-up that ever hit the window |
| each side's parameter value, published | the mismatch itself, comparable by anything that can read two registers |
| beats sent and beats taken, from both sides | a disagreement about what ready means, as a difference |
| a known asymmetric pattern in a scratch register | a byte-order reversal, in one read |
| beats in, beats out and beats held | the conservation equation, evaluated in hardware |
| advertised credits against real capacity | an overclaim, and a dead-link bit for the zero-credit case |
| a multiple-hit counter per decoder pair | two targets claiming one address |
| the outstanding count at the moment of gating | a gate decision that is reviewable after the hang |
| the effective value of every defaulted input | a fleet running a configuration nobody chose |
| the negotiated revision and a degraded flag | which half of a mixed-revision fleet a bug lives in |
Five of these must read permanently zero — impossible values, traffic before both-ready, multiple decoder hits, work outstanding at a gate, and lost beats. Each costs a flop or two, and each converts a defect that is debugged for a week into a register read.
The pattern to read on the sent-against-taken pair: the two numbers are equal on a link the consumer never backpressures, which is every link in bring-up. Their difference only appears under load, which is why the counter must exist before the load does.
The configuration report is the one to keep if only one survives area review. It has no logic, it makes every other finding localisable to a specific build, and it is the only item here that helps with defects this chapter does not cover.
24. DebugLabs
Lab 1 — A configuration word is occasionally a value nobody ever wrote
Symptom. A destination block occasionally reads a configuration value that appears in no source, no test and no trace. It happens roughly once per thousand updates and never in a directed test.
Evidence. The source register's history contains only legal values. The destination's history contains one that is not among them. The bus has a two-flop synchroniser on every bit.
Hypothesis. The word is being assembled from two source values.
Investigation. Compare the impossible value against the two source words on either side of the last update, bit by bit. Every bit of the impossible value matches one of them.
Root cause. Per-bit synchronisers on a multi-bit bus. Each bit resolves independently, and on an update where several bits change the destination latches a mixture.
Fix. One synchronised qualifier, with the bus held stable across the crossing.
Prevention. A review rule counting synchronisers against bits. If the count is not one, the crossing is unqualified. And a DV test that changes several bits in one cycle, which a one-bit-at-a-time directed test never does.
Silicon observability. A counter of destination values outside the legal set, where the legal set is enumerable. A single non-zero read is the whole diagnosis.
Lab 2 — The first transaction after every power-up is lost
Symptom. A link drops exactly one transaction per power-up. Never two, never zero. After the first, it runs for weeks.
Evidence. The loss is at power-up only. Both blocks pass their own reset tests. The reset trees are in different hierarchies.
Hypothesis. One block is honouring traffic before the other exists.
Investigation. Capture both reset releases against a free-running counter. B releases three cycles before A, and the first transaction arrives in the gap.
Root cause. No stated release order, and an interface gate qualified on the local side only.
Fix. A sequencer that releases in a stated order, and a "both released" qualifier on the interface.
Prevention. A DV case that releases the two ends in each order and drives traffic in the gap. A single-block environment cannot construct it, which is why it passed both reviews.
Silicon observability. A sticky "traffic seen while not both ready" bit. One flop, and it must read permanently zero.
Lab 3 — A length field is right up to 12 and wrong above it
Symptom. Transfers of 12 units or fewer are correct. Transfers of 13 or more lose the tail. The loss is always exactly the amount above 12.
Evidence. The producer's counters say it sent the full length. The consumer's say it received 12.
Hypothesis. The two sides were built with different widths.
Investigation. Read both elaborated parameter values. One is 16 and one is 12.
Root cause. A shared parameter with two values, and nothing that compares them.
Fix. Set them from one source, and publish both at the boundary with a mismatch flag.
Prevention. Ask for the value each side elaborated with, as a number. The parameter name being identical is what made this invisible.
Silicon observability. Both values in a readable register, and a one-bit mismatch flag. The cheapest integration check that exists.
Lab 4 — Throughput is fine and a fraction of beats never arrive
Symptom. A link loses a small fraction of beats. The fraction grows with load. Neither side reports an error.
Evidence. The producer's sent count exceeds the consumer's received count. Both counters are correct against their own definitions.
Hypothesis. The two sides disagree about what constitutes a transfer.
Investigation. Trace valid and ready together. Beats are lost exactly where ready falls in the cycle after it rose, with valid still high.
Root cause. One side counts a send when valid is asserted; the other transfers only when both are high on the same edge.
Fix. One written rule — a beat transfers when valid and ready are both high on the same rising edge — and a lost-beat counter proving both sides implement it.
Prevention. A DV case holding valid and pulsing ready for one cycle in three. A test with ready tied high makes both readings agree.
Silicon observability. Sent and taken, published by both sides. Either number alone proves nothing; their difference is the defect.
Lab 5 — Payloads arrive with their bytes swapped and only in one direction
Symptom. Data crossing one boundary is byte-reversed. The reverse direction is correct.
Evidence. The reversal is exact and total. The bring-up test suite passes, and it uses a repeated-byte pattern.
Hypothesis. The two sides apply different byte-order conventions, and the test pattern cannot see it.
Investigation. Drive an asymmetric pattern. It comes back reversed. Drive the repeated pattern again. It comes back correct.
Root cause. Two conventions, never written down, at a boundary that reverses.
Fix. One convention at the boundary, applied once.
Prevention. The test pattern is part of the review. A palindromic pattern has a detection probability of zero for this defect, and repeated bytes are the easiest pattern to generate.
Silicon observability. A known asymmetric pattern in a scratch register, readable from both sides. Reading it back the wrong way round is a one-instruction diagnosis.
Lab 6 — Odd-length transfers lose their tail
Symptom. Transfers whose length is a multiple of the width ratio are correct. Every other length loses a beat.
Evidence. Beats in and beats out differ by exactly length mod ratio.
Hypothesis. The adapter has no end-of-transfer path.
Investigation. Trace the accumulator across end-of-transfer. It holds a partial word, and the next transfer overwrites it.
Root cause. Emit-on-full-word with no flush. Writing no flush code produces exactly this, which is why it is common.
Fix. Flush the partial word with a valid-beat count.
Prevention. An odd-length transfer in the regression, and a continuous conservation check: beats in equals beats out plus beats held.
Silicon observability. The three counters and the equation evaluated in hardware.
Lab 7 — A link hangs after entering low power and only under one traffic pattern
Symptom. A requester waits forever for a response. It happens after a low-power entry, under a pattern that leaves work outstanding at the idle threshold.
Evidence. The clock gate asserted. Two responses were owed at that moment. The gating logic is in a different block from the gated one.
Hypothesis. The gate condition means "no new requests", not "nothing outstanding".
Investigation. Capture the outstanding count at the gate assertion. It is two.
Root cause. Gating on idle rather than on quiescence.
Fix. Gate on no-new-requests and an outstanding count of zero, and publish the count at gate time.
Prevention. Read the gate condition in full at review. "Idle" is a word; the outstanding count is a number, and only the number is a condition.
Silicon observability. The outstanding count at the moment of gating. Permanently zero, and it makes a hang diagnosable from a single register.
Lab 8 — A bug reproduces on one build and not on an identical one
Symptom. Two systems built from the same source behave differently. Neither build can be told apart from outside.
Evidence. The block's configuration report shows the intended mode on both. The behaviour differs.
Hypothesis. The report echoes intent rather than the effective value.
Investigation. Read the port connection in the integration netlist. On one system the mode input is unconnected and takes its default.
Root cause. A defaulted input, left unconnected, and a status register that reports what was intended rather than what is in force.
Fix. Publish the effective value, and connect the port.
Prevention. For every defaulted input, a readable register holding the value the instance is running with. Not the default in the source — the effective value in the instance.
Silicon observability. A configuration-report register carrying every effective value. This item is telemetry, which is why it is the cheapest of the eleven to fix.
25. Coverage Reasoning
Functional coverage on one block cannot cover an integration defect, because a single-block environment supplies the other side as a stub, and the stub implements its author's reading of the contract. The assumption under review is the one thing the environment cannot question.
Four coverage models are worth adding to any environment reviewed with this chapter:
Simultaneous-transition coverage on every crossing. Bins on the number of bits that changed in one cycle: one, two, and more than two. The bin a one-bit-at-a-time directed test can never hit is the defect, and it is the bin a random stimulus fills by default.
Reset-order coverage. A cross of release order against traffic presence in the gap. Four cells, of which two are only reachable in a two-block environment — which is the proof that the single-block environments were not enough.
Boundary coverage per comparison. For every comparison in the design, three bins: below, above, and exactly on. Section 21 records three survivors that this model would have caught before the campaign ran, and the address-map case is the one that recurs.
Handshake-shape coverage. A cross of valid duration against ready duty cycle. The cell that separates the two readings is "valid held, ready pulsed", and a test with ready tied high populates none of it.
The bin the weak build cannot hit is the most valuable bin in any model. In section 6 it is "destination value outside the legal set, with no source change". In section 12 it is "advertised credits above real capacity". In section 15 it is "published value differs from effective value". Each is unreachable in the sound build and trivial in the weak one, which makes the coverage report a direct test of the review item.
26. How This Appears In Real Engineering
Per-bit synchronisers on a bus are the commonest CDC finding there is, and they survive because they look more careful than the correct structure. Eight synchronisers are visibly more effort than one.
Reset ordering is not owned because reset is a block-level concern in every block-level review, and the order between blocks appears in no block's specification.
Parameter mismatches survive because the parameter names match. A reviewer reading two instantiations sees the same identifier on both and moves on; the values are in two different defaults files.
Handshake disagreements are almost always documentation defects. Both implementations are internally consistent with their own documents, and the two documents were written in different quarters.
Byte order is settled by convention and rarely written at the boundary, so each side applies the convention its author learned.
A width adapter with no flush is what you get by writing nothing. The full-word path is the obvious path, and the remainder needs code that nobody writes unless somebody asks for it.
Credit ownership is the classic two-team omission: each team assumes the other initialises, and the result is a dead link that is diagnosed as a bring-up problem rather than an integration one.
Address maps are checked by a script, and edited after the script ran. The hardware detector is what survives the edit.
Power-gating decisions are made by a power-management block that cannot see the gated block's outstanding count, unless somebody routed it, and routing it is an integration task.
Tie-off defaults exist so that a module can be instantiated without connecting everything, which is convenient exactly until a fleet is running a configuration nobody chose.
Revision skew is created by every staggered upgrade, and "backward compatible" is a statement about the specification rather than about either implementation.
27. Common Misconceptions
"Every bit has a synchroniser." Then no bit is safe as part of a word. One qualifier, or a structure that holds the bus stable.
"Metastability is the problem." Metastability is resolved by the synchroniser. The problem is that it is resolved independently per bit, which is a data-integrity problem, not a timing one.
"Both blocks come out of reset together." That is a claim about timing that nothing in either block guarantees. State the order and enforce it.
"The parameter is the same, it is the same name." Ask for the value each side elaborated with. A name is not a value.
"ready means it will take it." Or it means it has taken it. Both are defensible, which is why exactly one must be written down.
"The test passes with a repeated pattern." A palindromic pattern cannot detect a reversal. The pattern is part of the test, not a detail of it.
"The adapter works, we tested it." With an even length? Odd lengths are where the remainder goes somewhere.
"The other side initialises the credits." Both sides said that in one case and neither in the other. Name the owner.
"Each decoder is correct." Correct against its own region. Overlap is a property of the map, and the map has no owner.
"The block is idle, we can gate it." Idle means no new requests. Quiescent means nothing outstanding, and only the second one is safe.
"The port is unconnected, so it does nothing." It takes its default, which is a decision somebody else made. It does exactly one thing, and nobody chose it.
"The interface is backward compatible." The specification is. The implementation needs to detect the older peer and fall back, and a fallback that is not negotiated does not happen.
"Both blocks passed review." That is bit 0, and it is worth one sixth of an integration review.
28. Interview And Design-Review Questions
Crossings and reset
1. Why is a two-flop synchroniser insufficient for a bus? It makes one bit safe to sample in a foreign domain. On a bus each bit resolves independently, so the destination can latch a combination of the old and new words.
2. Source goes from F0 to 0F with bit 3 late. What can the destination read? 07 — bit 3 from the old word and the rest from the new. Neither source word.
3. What is the correct structure? Synchronise one qualifier, and hold the bus stable while it crosses. Or a gray code, or an asynchronous FIFO — all of them make one signal the thing that crosses.
4. Why does this defect survive a directed test? A directed test usually changes one bit at a time, and one bit at a time is safe.
5. When is a skew harmless? When the two source words are equal. A slow-changing bus carries this defect for years for exactly that reason.
6. Who owns the reset release order between two blocks? Usually nobody, which is the defect. Each block's reset is locally correct.
7. What does a consumer see if it releases first? A producer that is still resetting, and whatever it samples in that window is not a protocol violation by either side.
8. How do you enforce it? A sequencer with a stated order, and a "both released" qualifier gating the interface.
9. What telemetry proves it never happened? A sticky bit set by traffic seen while not both ready. One flop, permanently zero.
Shared contracts
10. Two blocks share a parameter. What enforces agreement? Nothing in the language. Each elaborates cleanly against its own value.
11. A is 16, B is 12, the payload is 14. What arrives? Twelve. The narrower side is what applies, and two units are truncated silently.
12. What evidence would you ask for in review? Both values, as numbers. Not the parameter name.
13. Give the two readings of ready. "I will take it if you present it" and "I have taken it". Both are defensible readings of two wires.
14. Three cycles of valid, one of ready. How many beats transferred? One. A side counting sends on valid alone counts three.
15. Why does this defect scale with load? Beats are lost when the consumer withholds ready, which it does more often as it gets busier. Zero on an idle link.
16. What single counter distinguishes the two readings in silicon? Sent minus taken. Neither number alone proves anything.
17. Why is a repeated-byte test pattern dangerous? It is palindromic, so a reversal at the boundary produces the identical value. Detection probability zero.
18. What does a byte-order defect look like in the lab? Data corruption, and it is debugged as one — usually for a while.
Structures at the boundary
19. Three beats into a two-to-one adapter. What must happen to the third? It is flushed with a valid-beat count, or held for the next transfer. Dropping it requires no code, which is why it is common.
20. State the conservation equation. Beats in equals beats out plus beats held. Evaluated continuously, it catches every variant of this.
21. Which transfer lengths hide the defect? Multiples of the ratio. Which is what a directed test uses.
22. Who initialises credits, and how many owners are correct? Exactly one, named. Two advertises capacity twice; zero leaves the link dead.
23. What does a zero-credit link look like? A dead interface, diagnosed as a bring-up problem rather than an integration defect.
24. What second question does the credit contract need? What a re-initialisation does to an existing count. A retrain must reload, not accumulate — and that is the question nobody asks.
25. Two decoders are each correct. What can still be wrong? The map. Overlap is a property of the pair and belongs to neither decoder.
26. Regions 10 to 3F and 30 to 5F. What does an access to 38 do? Both decoders assert. Two targets respond, and the requester takes whichever arrives first.
27. Why is that worse in silicon than in simulation? Simulation is deterministic. In silicon the winner depends on physical routing.
28. Regions touching at exactly one address — do they overlap? Yes, by one address. It is the case an off-by-one comparison gets wrong and a spot check never drives.
Power, defaults and revisions
29. Distinguish idle from quiescent. Idle means no new requests. Quiescent means no new requests and nothing outstanding.
30. What happens if you gate on idle with two responses owed? They never arrive. The requester hangs, and the cause is in the block that made the gating decision.
31. Why is that a hard debug? Nothing is corrupted and no protocol is violated. The symptom is a wait, and the cause is in a different file.
32. What register makes it a one-read diagnosis? The outstanding count captured at the moment the gate asserted. A hang gives you no other evidence — the block stopped being clocked, so nothing after that point was recorded — and this one register says whether the gate was legitimate.
33. An input is left unconnected. What is it doing? Taking its default — a decision made by whoever wrote the module, applied by whoever instantiated it staying silent.
34. What should a status register publish, intent or effect? The effective value. A register echoing intent makes the two builds indistinguishable from outside.
35. Why does this matter for a fleet? A bug that reproduces on one build and not another cannot be localised if the build cannot be read back.
36. What does "backward compatible" guarantee about an implementation? Nothing. It describes the specification. The newer side must detect the older peer and fall back.
37. A at revision 3, B at 1, a feature needing 2. What should happen? Operate at 1 and refuse the feature. Skipping negotiation operates at 3 and uses something the peer cannot honour.
38. What is the boundary case there? A peer at exactly the revision the feature requires. Compatible — and the case an off-by-one comparison gets wrong.
Method
39. Why can a single-block environment not find these? It stubs the other side, and the stub implements its author's reading of the contract — which is the assumption under review.
40. What single condition makes a mutation campaign invalid? A failing baseline. Every mutation then fails for the reason the baseline does.
41. A mutation cannot be killed and is not equivalent. Name a third possibility this chapter found. The model has not decided what it means. Two forms of "initialise" were identical from reset and the model had never said which it meant.
42. What is the right response to that? Decide, restructure the model, then re-mutate. Not a stimulus patch on an undecided model.
43. Three survivors here were one family. Which? A boundary the stimulus approached and never landed on.
44. State the rule that kills it. For every comparison, drive below it, above it, and exactly on it.
45. Two survivors were checkers that did not look. What shape did they take? A check placed where the two builds agree — after reset, or after the first cycle of a two-cycle event.
46. Name the single question this chapter's review turns on. Whose assumption is this, and does the other side know about it?
29. Exercises
1 — Design review · Advanced. Builds: reading a crossing for what it protects. You are shown a 24-bit status bus crossing into a second clock domain with a two-flop synchroniser on every bit. Bounded scope: state what is and is not protected, give the value class the destination can read, and specify the replacement structure with its flop count. Hint: compare the flop counts of the two structures before arguing about correctness.
2 — Debug · Advanced. Builds: localising a defect that belongs to neither block. A link loses exactly one transaction per power-up, never more. Bounded scope: give the hypothesis, the two signals you would capture, and the one-flop telemetry that would have made it a register read. Hint: "never more than one" is the shape of a window, not a rate.
3 — Code review · Intermediate. Builds: checking a shared contract by value rather than by name. Two blocks instantiate the same interface with a WIDTH parameter, set from two different defaults files. Bounded scope: state what each block does at elaboration, what happens at the boundary, and the boundary check you would require. Hint: the reviewer's mistake is reading the identifier.
4 — Design · Advanced. Builds: specifying a handshake so two teams cannot read it differently. Write the transfer rule for a two-wire handshake in one sentence, then specify the telemetry that proves both sides implement it. Bounded scope: the rule, the counters, and the test shape that separates the two readings. Hint: ready tied high makes both readings agree.
5 — Verification · Advanced. Builds: choosing a test pattern as a review decision. A boundary may or may not reverse byte order. Bounded scope: give a pattern that cannot detect a reversal, one that always does, and state the detection probability of each. Hint: the easiest pattern to generate is the useless one.
6 — Design · Intermediate. Builds: making a structural property continuously checkable. Specify the end-of-transfer behaviour of a four-to-one width adapter. Bounded scope: state where the remainder goes, write the conservation equation, and give the transfer lengths that must appear in the regression. Hint: the lengths that hide it are the ones a directed test picks.
7 — Design review · Advanced. Builds: finding the contract nobody wrote. Review a credit-based interface between two teams. Bounded scope: list every question that must be answered before either side is implemented, including what a retrain does to an existing count, and say which failure each unanswered question produces. Hint: one of the two failure modes presents as a dead link rather than as a bug.
8 — Verification · Expert. Builds: coverage that reaches a cell one environment cannot. Define a coverage model that would find the reset-ordering defect. Bounded scope: specify the bins, the cross, identify which cells are unreachable in a single-block environment, and say what that unreachability proves. Hint: the proof is which cells cannot be filled, not which were.
30. Summary
An integration defect is not a bug in either block. It is a contract that was never written down, read differently by two teams who were each internally consistent.
A synchroniser per bit protects every bit and no word. One qualifier costs fewer flops and delivers a value that existed.
The reset release order between two blocks belongs to nobody, which is why it must be given an owner explicitly.
A shared parameter is a contract the language does not enforce. Ask for the value each side elaborated with, as a number.
Two wires admit two readings, and exactly one of them must be written down: a beat transfers when valid and ready are both high on the same edge.
A palindromic test pattern cannot detect a byte-order reversal. The pattern is part of the review.
A width adapter with no flush is what writing nothing produces, and it loses the tail of every transfer whose length is not a multiple of the ratio.
Credits need exactly one named owner, and a second question: what a re-initialisation does to an existing count.
Overlap is a property of the map, and the map belongs to neither decoder.
Idle is a word and the outstanding count is a number. Only the number is a gate condition.
An unconnected input is running a configuration somebody else chose. Publish the effective value, not the intent.
"Backward compatible" describes a specification, not an implementation. A fallback that is not negotiated does not happen.
Six conditions, and "both blocks passed" is one of them. A real review with one finding open is 83 percent. Two correct blocks and nothing else is 16.
Continue learning
Related tutorials
- Related topic
Architecture Review Checklist
A working pre-RTL review document. Nine review dimensions — mechanism, authority, state placement, failure domain, ordering, conservation, timeout authority, backpressure and liveness — each with the invariant at risk, the evidence to demand, what escapes if it is wrong, and the telemetry that exposes it after tapeout.
- Related topic
RTL Review Checklist
A working pre-tapeout RTL review document. Nine review dimensions — handshake acceptance, transition completeness, single-driver discipline, identity lifetime, arithmetic width, recovery completeness, retry state, combinational completeness and behavioural telemetry — each with the defect, the code that produces it, what escapes, and the telemetry that exposes it in silicon.
- Related topic
Verification Review Checklist
A working review document for the verification environment itself. Nine review dimensions — oracle independence, unknown-value vacuity, checker reachability, pulse observation, transaction identity, duplicate responses, exact versus bound checking, timeout authority and fairness — each with the escape, the executable contrast, and the campaign discipline that makes a passing regression mean something.
- Related topic
Coherency Review Checklist
A working pre-tapeout coherency review. Nine review dimensions — newest-data authority, writer exclusion, dirty ownership, acknowledgement conservation, stale and duplicate acknowledgements, transient states, same-line concurrency, deadlock against livelock against starvation, and recovery reclamation — each with the invariant, the executable contrast, and the telemetry that exposes it in silicon.
Standards & specifications
- Governing standard
- CXL Specification (CXL Consortium)(opens CXL Consortium in a new tab)
Defines CXL.io, CXL.cache and CXL.mem, and the coherence and memory-pooling behaviour built on them. System design and deployment topology are not mandated.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the CXL curriculum.
