A monitor rebuilds transactions from pins and never drives a signal. A monitor that writes the bus observes its own stimulus, and the bench can no longer catch DUT bugs on those pins.
The next listing is an interface only, so this course's checks do not run it. It shows the structural fix: an input-only clocking block, so a drive attempt is a compile error.
// not-run: interface only, no module to run
interface bus_if (input logic clk);
logic valid;
logic ready;
logic last;
logic [31:0] data;
// Driver clocking block: drives valid/data, samples ready
clocking drv_cb @(posedge clk);
output valid, last, data;
input ready;
endclocking
// Monitor clocking block: EVERYTHING is an input
clocking mon_cb @(posedge clk);
input valid, ready, last, data;
endclocking
modport MON (clocking mon_cb); // passive view only
endinterfaceSampling through mon_cb also settles races. The clocking block samples just before the edge, so the monitor sees the values the DUT flops saw.
Pins carry beats and consumers want transactions. The monitor assembles beats into a transaction and publishes it only at the beat tagged last.
Only accepted beats count: a beat belongs to the transaction when valid && ready at the edge. Beats offered during back-pressure are presented again, never double-counted.
Watch accepted: it rises only on edges where both valid and ready are high.
Slot 0: idle, valid is 0, so accepted is 0.
Slot 1: valid and ready are 1 with data a0, so accepted is 1.
Slot 2: ready is 0, so data b0 is offered but not accepted and accepted stays 1.
Slot 3: ready is 1 again, so b0 is accepted and accepted is 2.
Slot 4: data c0 arrives with last 1, so accepted is 3 and done pulses.
Slot 5: valid is 0, so accepted rests at 3 and done is 0.
The next listing is a fragment that uses bus_if from the listing above and has no top module, so this course's checks do not run it. Each consumer gets its own transaction copy over its own mailbox.
// not-run: fragment: uses bus_if from the listing above, no top module
class bus_txn;
int unsigned id;
logic [31:0] data_q[$]; // collected beats
time start_t;
time end_t;
function string convert2string();
return $sformatf("txn id=%0d beats=%0d first=0x%08h last=0x%08h", id,
data_q.size(),
data_q.size() != 0 ? data_q[0] : 32'h00000000,
data_q.size() != 0 ? data_q[$] : 32'h00000000);
endfunction
endclass
class bus_monitor;
virtual bus_if.MON vif;
mailbox #(bus_txn) mbx_scb; // to scoreboard
mailbox #(bus_txn) mbx_cov; // to coverage collector
int unsigned txn_count;
bit stop; // set by env at drain so run() can exit
function new(virtual bus_if.MON vif,
mailbox #(bus_txn) mbx_scb,
mailbox #(bus_txn) mbx_cov);
this.vif = vif;
this.mbx_scb = mbx_scb;
this.mbx_cov = mbx_cov;
endfunction
task run();
while (!stop) begin
bus_txn tr = new();
collect_one(tr);
if (stop) break;
txn_count++;
broadcast(tr);
end
endtask
// IDLE: wait for first accepted beat. COLLECT: gather through last.
task collect_one(bus_txn tr);
// === 1: an X on valid or ready must not count as an accepted beat
do @(vif.mon_cb); while (!(vif.mon_cb.valid === 1 && vif.mon_cb.ready === 1));
tr.start_t = $time;
forever begin
tr.data_q.push_back(vif.mon_cb.data);
if (vif.mon_cb.last) break;
do @(vif.mon_cb); while (!(vif.mon_cb.valid === 1 && vif.mon_cb.ready === 1));
end
tr.end_t = $time;
endtask
function void broadcast(bus_txn tr);
bus_txn cp = new tr; // each consumer gets its own copy
void'(mbx_scb.try_put(tr));
void'(mbx_cov.try_put(cp));
endfunction
endclassChoice | Why |
|---|---|
| A monitor must never stall on a slow consumer and miss pin activity. |
One mailbox per consumer | Consumers read at different speeds; a shared mailbox couples them. |
| It is the ground truth for the end-of-test sanity check. |
| The loop exits at its next iteration. A thread blocked in |
Not necessarily. Two beats may have been offered while ready was low. Compare against accepted beats (valid && ready), not offered ones.
Both consumers received the same handle. Broadcast a separate copy per mailbox so annotation stays private.
A small accepted-beat monitor plus a testbench with one back-pressured beat.
module beat_monitor (
input logic clk, valid, ready, last,
input logic [7:0] data,
output logic [7:0] accepted, // accepted-beat total
output logic [7:0] txn_count, // completed transactions
output logic [7:0] open_beats // beats in the still-open transaction
);
timeunit 1ns; timeprecision 1ps;
initial begin
accepted = 8'd0; txn_count = 8'd0; open_beats = 8'd0;
end
always @(posedge clk) begin
if (valid && ready) begin
accepted <= accepted + 8'd1;
if (last) begin txn_count <= txn_count + 8'd1; open_beats <= 8'd0; end
else open_beats <= open_beats + 8'd1;
end
end
endmodule