Two engineers argue over DCRouter's credit checking. One wants an assertion on the internal credit counter: it can never exceed 4, and it changes by exactly the sends and returns each cycle. The other refuses: the counter's name and width are implementation details, and the check will break the day a designer restructures the logic. Both are right, and the decision is one every testbench makes for every piece of state.
Should checks look inside the design, or only at its pins?
One obvious answer is "only the pins": the specification describes the pins, and a pins-only testbench survives any legal redesign. But lesson 3.2 showed state the pins hide, and lesson 3.3 showed bugs masked before they reach the pins. The opposite answer, "check everything inside", sees bugs at their source but ties the testbench to one implementation and may check the design against itself. Neither extreme works alone.
Approach | Looks at | Strength | Weakness |
|---|---|---|---|
Black box | Inputs and outputs only, against the specification | Independent of the implementation; survives redesign; checks what users see | Blind to hidden and masked state; failures surface late and far from the cause |
White box | Internal signals and state | Sees bugs at the source; precise failure points; reaches hidden state | Tied to the implementation; breaks on legal changes; risks checking the design against itself |
Grey box | Chosen internal interfaces, status registers and documented internal points | Most of white box's visibility at stable, documented points | Needs agreed, stable observation points |
State or rule | Choice | Why |
|---|---|---|
Packet data, order, out_err (R3, R5, R8) | Black box: the scoreboard | Fully specified at the pins; independent of the arbiter design |
Credits never overrun the sink (R7) | Black box: count flits in the sink | The sink can see an overrun directly |
Credit counter consistent every cycle (R7) | White box, owned by the designer | Hidden state; a designer-written assertion moves with the RTL |
FIFO never overflows (R1) | Grey box: a checker on the FIFO's push/pop interface | A stable, documented sub-block boundary |
Lock released only after a tail (R3) | Grey box: STATUS bits plus out_last | STATUS is documented, so checks on it survive redesign |
Grey-box checks are often attached without editing the RTL, using SystemVerilog bind to place a checker module inside a design instance:
// snippet: a grey-box checker bound into every DCRouter input FIFO (R1)
module fifo_chk (input logic clk, rst, push, pop);
int occ = 0; // the checker's own count, not the FIFO's
always @(posedge clk) occ <= rst ? 0 : occ + int'(push) - int'(pop);
// never a ninth entry: no push without a pop while 8 entries are held
assert property (@(posedge clk) disable iff (rst) !(push && !pop && occ == 8));
endmodule
bind dcr_fifo fifo_chk u_chk (.clk(clk), .rst(rst), .push(push), .pop(pop));Bound into a DCRouter testbench that fills an input FIFO, this checker passes on the clean design and, with BUG_1 (full rises one entry late), fails inside that FIFO in the cycle the ninth flit is pushed, long before the black-box scoreboard sees a corrupted flit at an output. It depends only on the FIFO's port names, a documented interface, not on how the FIFO is built.
Note what it does not use: the FIFO's own full flag. A first draft asserted !(push && full), and with BUG_1 it could never fail, because the bug is in full itself. A check built from the signal under suspicion checks the design against itself.
The scoreboard is black box: end-to-end checks against the specification stay independent of the implementation.
Internal assertions belong to designers: they know the intent of internal state and keep the checks current as the RTL changes.
Grey box at stable boundaries: sub-block interfaces, documented status, standard internal buses.
Never derive expected values from internal state: an expected value computed from the design's own signals checks the design against itself (lesson 2.1).
Practice | Purpose |
|---|---|
Black-box scoreboards plus designer-written internal assertions | Independence end to end, precision inside |
Bound checkers on sub-block interfaces | Grey-box visibility without editing the RTL |
Rule: no expected value computed from DUT internals | Avoid checking the design against itself |
A testbench (illustrative) predicted each output by reading the design's internal arbitration grant and copying whatever the granted input held. When the arbiter granted the wrong input, the prediction followed it, and the scoreboard agreed with every mistake. The check looked inside the design for its expected answer, so it could never disagree with it. Peeking for visibility is fine; peeking for the right answer is not.
Data, order and out_err are fully specified at the pins, so checking them there is independent of how the router is built.
It takes its expected answer from the design under test, so a wrong grant produces a matching wrong prediction and the check can never fail.
Black box is independent but blind to hidden state; white box sees everything but is tied to one implementation.
Grey box looks at stable, documented internal points, often bound in without editing the RTL.
Look inside for visibility, never for the expected answer.