A scoreboard needs to know "a 3-beat packet with these words arrived". The interface offers only wires: a valid, a ready, a data bus and a last bit, changing every cycle. Somewhere between the two, something must watch the wires and decide which cycles were real transfers, which were stalls, where each packet began and ended, and what it contained. Get that wrong and every check built on top is wrong too.
How does a testbench turn signal activity into transactions it can check?
The obvious monitor records the data whenever valid is high. During a stall, valid stays high for several cycles with the same word, so that monitor records the word several times. It also gets packet boundaries wrong if it looks at last outside a transfer. A monitor must apply exactly the protocol's definition of a transfer, and nothing else.
Passive: the monitor drives nothing. It can be attached to any instance of the interface, including inside the design, without changing behaviour.
Transfer condition: it samples only in cycles where the protocol says a transfer happens (here, valid and ready both high).
Framing: it accumulates beats and closes a transaction exactly where the protocol says one ends (here, a beat with last).
Output: one transaction object per completed transaction, with its fields and a timestamp, sent to scoreboards and coverage.
No checking of its own beyond framing: rule checks belong to the checker (lesson 15.3); the monitor reports what happened.
The producer sends a 3-beat packet (10, 20, 30) with a stall in the middle, then a 2-beat packet (40, 50). The monitor counts beats and sums the words, and reports each packet's length and sum when it sees last.
// DCLink step 12b: a passive monitor. It only watches the hop's signals (it drives
// nothing), counts beats on each transfer (valid && ready), and when it sees last it
// reports one reconstructed packet: its length and the sum of its words. A stall
// (valid high, ready low) is not a beat. The producer sends a 3-beat packet with a
// stall in the middle, then a 2-beat packet; the checker compares the monitor's
// packets with what the producer intended to send.
module tb;
logic clk = 0;
always #5 clk = ~clk;
initial begin $dumpfile("wave.vcd"); $dumpvars(0, tb); end
int cyc = 0;
always @(posedge clk) cyc <= cyc + 1;
// Producer: beats {10, 20, 30 last} then {40, 50 last}; the receiver stalls in cycle 3.
logic vld = 0, last = 0, rdy = 0;
logic [7:0] data = 0;
int k = 0;
logic [7:0] words [5] = '{8'd10, 8'd20, 8'd30, 8'd40, 8'd50};
logic lasts [5] = '{0, 0, 1, 0, 1};
always_comb rdy = (cyc != 3);
always @(posedge clk) begin
if (k < 5 && (!vld || rdy)) begin vld <= 1; data <= words[k]; last <= lasts[k]; k <= k + 1; end
else if (vld && rdy) begin vld <= 0; last <= 0; data <= 0; end
end
// Passive monitor
logic [3:0] beat_cnt = 0;
logic [9:0] sum = 0;
logic pkt_done = 0;
logic [3:0] pkt_len = 0;
logic [9:0] pkt_sum = 0;
always @(posedge clk) begin
pkt_done <= 0;
if (vld && rdy) begin
if (last) begin
pkt_done <= 1; pkt_len <= beat_cnt + 1; pkt_sum <= sum + data;
beat_cnt <= 0; sum <= 0;
end else begin
beat_cnt <= beat_cnt + 1; sum <= sum + data;
end
end
end
int n_pkts = 0, bad = 0;
always @(posedge clk) if (pkt_done) begin
n_pkts++;
if (n_pkts == 1 && !(pkt_len == 3 && pkt_sum == 60)) bad++;
if (n_pkts == 2 && !(pkt_len == 2 && pkt_sum == 90)) bad++;
end
initial begin
#160;
if (n_pkts == 2 && bad == 0) $display("PASS: monitor rebuilt two packets (3 beats, sum 60; 2 beats, sum 90) and did not count the stall");
else $display("FAIL: packets %0d bad %0d", n_pkts, bad);
$finish;
end
endmoduleHow to read it, slot by slot:
Slots 0 to 1: words 0x0a and 0x14 transfer (vld and rdy high); beat_cnt counts 0, 1.
Slot 2: word 0x1e, the packet's last beat, is offered but rdy is low: a stall. beat_cnt stays at 2 because no transfer happened.
Slot 3: 0x1e transfers. Slot 4: pkt_done, with pkt_len 3 and pkt_sum 0x3c.
Slots 4 to 5: 0x28 and 0x32 (last) transfer; slot 6: pkt_done with length 2, sum 0x5a.
Pitfall | Effect | Avoid by |
|---|---|---|
Sampling on valid alone | Stalled words counted several times | Sample on the full transfer condition |
Sampling at the wrong time | Races with signal updates | Sample on the clock edge, with a defined sampling point |
Assuming in-order responses | Responses matched to the wrong requests | Carry IDs and tags into the transaction (lesson 15.5) |
Ignoring reset | Half-built transactions survive a reset | Discard partial transactions on reset |
Environment | Monitors |
|---|---|
UVM | A passive monitor in each agent rebuilds transactions and broadcasts them to scoreboards and coverage through analysis ports (fact T30) |
Protocol analysers (lab) | The same job on real hardware: capture signals, decode transactions (lesson 15.9) |
DCLink | Step 12b, this monitor |
A monitor (illustrative) on a valid/ready interface sampled whenever valid was high. Tests with an always-ready consumer passed. When a back-pressuring consumer was added, the scoreboard reported duplicated words, and the team spent two days suspecting the design. The monitor was the bug: it counted stalled cycles as transfers.
Slot 2 was a stall, so no beat was counted; the stalled word transfers in slot 3, and the packet is reported in slot 4.
So it can be attached anywhere, including inside the design, without changing behaviour.
A passive monitor rebuilds transactions from signals by sampling only on the protocol's transfer condition.
It frames transactions exactly as the protocol defines them, counting no stalled cycles.
It is a second implementation of the framing: test it with stalls, back-to-back transfers and resets.