A transaction sorts its fields into three buckets. Solver controls are rand, responses are filled during execution, and metadata is never randomized and never driven.
Bucket | Fields | Who writes it |
|---|---|---|
Rand controls | kind, addr, wdata, gap | The solver, under constraints |
Derived responses | rdata, resp_err | Driver or monitor during execution |
Metadata | id, id_tag, timestamps, source | Constructor and components, never rand |
The solver only ever sees the top bucket. Mixing buckets wastes solver effort and confuses field ownership.
The next listing is not run in this course's checks. It is a class with no top module.
// not-run: class only, no top module
typedef enum {READ, WRITE} kind_e;
class bus_txn;
rand kind_e kind;
rand bit [15:0] addr;
rand bit [31:0] wdata;
rand int unsigned gap;
constraint c_gap { gap inside {[0:8]}; gap dist {0 :/ 6, [1:8] :/ 4}; }
constraint c_align { addr[1:0] == 2'b00; }
bit [31:0] rdata;
bit resp_err;
bit [3:0] id_tag;
static int unsigned next_id;
int unsigned id;
time t_created, t_driven;
string source = "gen";
function new();
id = next_id++;
t_created = $time;
endfunction
function string convert2string();
return $sformatf("[%0d %s@%0t] %s addr=%h wdata=%h rdata=%h gap=%0d",
id, source, t_driven, kind.name(), addr,
wdata, rdata, gap);
endfunction
virtual function bus_txn clone(); // derived types override it
bus_txn c = new();
c.kind = kind; c.addr = addr; c.wdata = wdata; c.gap = gap;
c.source = source;
return c;
endfunction
endclassThe gap field is stimulus: spacing is randomized with back-to-back weighted heavily. The id_tag field is driver scratch space for pipelined issue, not protocol state.
A clone is a new identity with the same stimulus: fresh id, copied fields, copied source for lineage.
clone() is virtual. A derived transaction that adds fields overrides it, so a copy keeps the derived type and its fields.
Every component logs through convert2string, so one id greps across generator, driver, monitor, and scoreboard.
Response fields stay non-rand: randomizing them burns solver effort and hides ownership.
No. The clone is a new object with copied stimulus and copied source, so lineage survives. Each live object still names exactly one transaction at one time.
Spacing is stimulus: back-to-back traffic finds pipeline bugs. Weighting gap in the solver shapes timing intent that the driver then honors.
An id tracker plus a testbench that proves one id per valid cycle, in order.
module txn_id_tracker (
input wire clk,
input wire valid,
output reg [31:0] id_out
);
timeunit 1ns; timeprecision 1ps;
reg [31:0] next_id = 0;
always @(posedge clk) begin
if (valid) begin
id_out <= next_id;
next_id <= next_id + 1;
end
end
endmodule
module tb_txn_ids;
timeunit 1ns; timeprecision 1ps;
localparam int N_TXN = 8;
reg clk = 0;
always #5 clk = ~clk;
reg valid = 0;
wire [31:0] id_out;
txn_id_tracker dut (.*);
integer i;
integer problems;
initial begin
problems = 0;
@(posedge clk); #1;
valid = 1;
for (i = 0; i < N_TXN; i = i + 1) begin
@(posedge clk); #1;
if (id_out !== i) problems = problems + 1;
end
valid = 0;
if (problems == 0) $display("PASS: 8 ids issued in order, one per valid cycle");
else $fatal(1, "FAIL: found %0d unexpected ids", problems);
$finish;
end
endmodule