This is the middle of dma_tb.sv, lines 841-1534. Paste it directly after part 1 in the same testbench.sv tab, and part 3 after it. Part 1 has the run instructions.
Section | What it adds | Explained in |
|---|---|---|
Reactive stream slave | A sink agent that decides when to accept each output beat (backpressure) | |
Reset-aware components | Drivers, monitors and a scoreboard that survive a reset in the middle of a job | |
Interrupt agent | A monitor-only agent for the IRQ line | |
Subsystem pieces and their tests | The pieces dma_subsys_tb.sv builds on, and the tests that use them | |
Coverage | The coverage collector and its covergroups |
// not-run: needs the UVM library. Paste into EDA Playground with UVM 1.2 selected. This is lines 841-1534 of dma_tb.sv.
// ---------------------------------------------------------------- agent patterns: reactive stream slave
// A sink item says how long the slave makes the master wait before taking a beat.
class axis_sink_item extends uvm_sequence_item;
rand int unsigned stall;
constraint stall_c { stall dist { 0 := 5, [1:2] := 3, [3:6] := 2 }; }
`uvm_object_utils(axis_sink_item)
function new(string name = "axis_sink_item");
super.new(name);
endfunction
endclass
typedef uvm_sequencer #(axis_sink_item) axis_sink_sequencer;
// A reactive driver: it waits for the master to offer a beat (TVALID), and only
// then asks its sequence how to answer: hold TREADY low for `stall` clocks, then
// accept the beat.
class axis_sink_driver extends uvm_driver #(axis_sink_item);
`uvm_component_utils(axis_sink_driver)
virtual axis_if vif;
int unsigned stall_clocks;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual axis_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "axis_if not set for this sink driver")
endfunction
task run_phase(uvm_phase phase);
vif.tready <= 1'b0;
forever begin
do @(posedge vif.clk); while (!vif.tvalid); // a beat is on offer
seq_item_port.get_next_item(req); // ask the sequence how to respond
repeat (req.stall) @(posedge vif.clk);
stall_clocks += req.stall;
vif.tready <= 1'b1;
@(posedge vif.clk); // the beat moves on this edge
vif.tready <= 1'b0;
seq_item_port.item_done();
end
endtask
endclass
// The responder: answers every beat, forever. Start it in a fork; it never returns.
class axis_ready_seq extends uvm_sequence #(axis_sink_item);
`uvm_object_utils(axis_ready_seq)
function new(string name = "axis_ready_seq");
super.new(name);
endfunction
task body();
forever begin
axis_sink_item it = axis_sink_item::type_id::create("it");
start_item(it);
if (!it.randomize()) `uvm_fatal("RAND", "axis_sink_item randomize failed")
finish_item(it);
end
endtask
endclass
// The output agent with a slave side: the same monitor as axis_agent, plus a
// sink sequencer and driver that answer the DUT's beats. It always builds the
// slave side; the monitor is all a passive agent would have had.
class axis_sink_agent extends axis_agent;
`uvm_component_utils(axis_sink_agent)
axis_sink_sequencer sink_sqr;
axis_sink_driver sink_drv;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
sink_sqr = axis_sink_sequencer::type_id::create("sink_sqr", this);
sink_drv = axis_sink_driver::type_id::create("sink_drv", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
sink_drv.seq_item_port.connect(sink_sqr.seq_item_export);
endfunction
endclass
// ---------------------------------------------------------------- agent patterns: reset-aware components
// Drivers that survive a reset in the middle of a transfer: when rst_n falls they
// stop driving, park the bus, and finish the item they hold, so the sequencer is
// never left waiting for an item_done.
class apb_driver_ra extends apb_driver;
`uvm_component_utils(apb_driver_ra)
bit holding;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
forever begin
vif.psel <= 1'b0;
vif.penable <= 1'b0;
wait (vif.rst_n === 1'b1);
fork begin
fork
forever begin
seq_item_port.get_next_item(req);
holding = 1;
drive(req);
holding = 0;
seq_item_port.item_done();
end
@(negedge vif.rst_n);
join_any
disable fork; // isolated: kills only the two branches above
end join
if (holding) begin
seq_item_port.item_done();
holding = 0;
end
`uvm_info("RST", "APB driver: reset seen, bus parked", UVM_MEDIUM)
end
endtask
endclass
class axis_driver_ra extends axis_driver;
`uvm_component_utils(axis_driver_ra)
bit holding;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
forever begin
vif.tvalid <= 1'b0;
wait (vif.rst_n === 1'b1);
fork begin
fork
forever begin
axis_item beat;
seq_item_port.get_next_item(req);
holding = 1;
beat = pre_drive(req);
if (beat != null) begin
repeat (beat.idle) @(posedge vif.clk);
@(posedge vif.clk);
vif.tdata <= beat.data;
vif.tlast <= beat.last;
vif.tvalid <= 1'b1;
do @(posedge vif.clk); while (!vif.tready);
vif.tvalid <= 1'b0;
end
holding = 0;
seq_item_port.item_done();
end
@(negedge vif.rst_n);
join_any
disable fork;
end join
if (holding) begin
seq_item_port.item_done();
holding = 0;
end
`uvm_info("RST", "stream driver: reset seen, TVALID dropped", UVM_MEDIUM)
end
endtask
endclass
// A scoreboard that knows about reset: whatever the DUT held is gone, so the
// predictions for it are dropped, and KEY is back to its reset value of 0.
// It flushes when reset ENDS: a beat can complete on the very edge where reset
// is asserted (the monitor reports it in that timestep), and nothing can
// complete while reset is held, so the release edge is the race-free point.
class dma_scoreboard_ra extends dma_scoreboard;
`uvm_component_utils(dma_scoreboard_ra)
virtual rst_if rst_vif;
int unsigned flushed;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual rst_if)::get(this, "", "rst_vif", rst_vif))
`uvm_fatal("NOVIF", "rst_if not set for the scoreboard")
endfunction
task run_phase(uvm_phase phase);
forever begin
@(negedge rst_vif.rst_n);
@(posedge rst_vif.rst_n);
flushed += expected.size();
`uvm_info("SB_RST", $sformatf("reset: %0d prediction(s) dropped", expected.size()), UVM_LOW)
expected.delete();
key = 32'h0;
end
endtask
endclass
// ---------------------------------------------------------------- agent patterns: interrupt agent
class irq_item extends uvm_sequence_item;
bit level;
`uvm_object_utils(irq_item)
function new(string name = "irq_item");
super.new(name);
endfunction
endclass
// A passive interrupt monitor: publishes every change of the line, and triggers
// the global event "dma_irq" on each rising edge, so tests and sequences can wait
// for the interrupt without holding a virtual interface.
class irq_monitor extends uvm_monitor;
`uvm_component_utils(irq_monitor)
virtual irq_if vif;
uvm_analysis_port #(irq_item) ap;
uvm_event irq_ev;
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual irq_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "irq_if not set for the interrupt monitor")
irq_ev = uvm_event_pool::get_global("dma_irq");
endfunction
task run_phase(uvm_phase phase);
bit last = 0;
forever begin
@(posedge vif.clk);
if (vif.irq !== last) begin
irq_item it = irq_item::type_id::create("it");
it.level = vif.irq;
last = vif.irq;
ap.write(it);
if (it.level) irq_ev.trigger();
`uvm_info("IRQ_MON", $sformatf("irq -> %0b", it.level), UVM_MEDIUM)
end
end
endtask
endclass
class irq_agent extends uvm_agent;
`uvm_component_utils(irq_agent)
irq_monitor mon;
uvm_analysis_port #(irq_item) ap;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = irq_monitor::type_id::create("mon", this);
endfunction
function void connect_phase(uvm_phase phase);
ap = mon.ap;
endfunction
endclass
// The block env plus an interrupt agent, swapped in by a factory override.
class dma_env_irq extends dma_env;
`uvm_component_utils(dma_env_irq)
irq_agent irq;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
irq = irq_agent::type_id::create("irq", this);
endfunction
endclass
// ---------------------------------------------------------------- agent patterns: subsystem
// Two DMAs in a chain (dma0's output feeds dma1's input), verified by reusing the
// block env twice, unchanged, plus an end-to-end checker.
class dma_subsys_cfg extends uvm_object;
bit has_e2e_checker = 1; // the end-to-end scoreboard
bit chain_internal = 1; // dma1's input is driven by dma0, so its agent is passive
`uvm_object_utils(dma_subsys_cfg)
function new(string name = "dma_subsys_cfg");
super.new(name);
endfunction
endclass
`uvm_analysis_imp_decl(_key0)
`uvm_analysis_imp_decl(_key1)
`uvm_analysis_imp_decl(_first)
`uvm_analysis_imp_decl(_last)
// Checks the chain as one: every word out of dma1 equals the word into dma0
// XOR both keys. It needs no knowledge of the word in the middle.
class dma_e2e_scoreboard extends uvm_scoreboard;
`uvm_component_utils(dma_e2e_scoreboard)
uvm_analysis_imp_key0 #(apb_item, dma_e2e_scoreboard) key0_export;
uvm_analysis_imp_key1 #(apb_item, dma_e2e_scoreboard) key1_export;
uvm_analysis_imp_first #(axis_item, dma_e2e_scoreboard) first_export;
uvm_analysis_imp_last #(axis_item, dma_e2e_scoreboard) last_export;
bit [31:0] key0, key1;
bit [31:0] expected[$];
int unsigned n_match, n_mismatch;
function new(string name, uvm_component parent);
super.new(name, parent);
key0_export = new("key0_export", this);
key1_export = new("key1_export", this);
first_export = new("first_export", this);
last_export = new("last_export", this);
endfunction
virtual function void write_key0(apb_item t);
if (t.write && !t.slverr && t.addr == KEY) key0 = t.data;
endfunction
virtual function void write_key1(apb_item t);
if (t.write && !t.slverr && t.addr == KEY) key1 = t.data;
endfunction
virtual function void write_first(axis_item t);
expected.push_back(t.data ^ key0 ^ key1);
endfunction
virtual function void write_last(axis_item t);
bit [31:0] exp;
if (expected.size() == 0) begin
`uvm_error("E2E_UNEXP", $sformatf("word %08h out of dma1 with nothing expected", t.data))
n_mismatch++; return;
end
exp = expected.pop_front();
if (t.data !== exp) begin
`uvm_error("E2E_MISMATCH", $sformatf("expected %08h, got %08h", exp, t.data))
n_mismatch++;
end else n_match++;
endfunction
function void check_phase(uvm_phase phase);
if (expected.size() != 0) `uvm_error("E2E_LEFTOVER", $sformatf("%0d word(s) never left dma1", expected.size()))
if (n_match == 0) `uvm_error("E2E_EMPTY", "nothing was compared end to end")
endfunction
function void report_phase(uvm_phase phase);
`uvm_info("E2E", $sformatf("%0d words matched end to end, %0d mismatched", n_match, n_mismatch), UVM_LOW)
endfunction
endclass
class dma_subsys_env extends uvm_env;
`uvm_component_utils(dma_subsys_env)
dma_subsys_cfg cfg;
dma_env dma0, dma1; // the block env, reused as is
dma_e2e_scoreboard e2e;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(dma_subsys_cfg)::get(this, "", "cfg", cfg))
cfg = dma_subsys_cfg::type_id::create("cfg"); // defaults if the test set none
if (cfg.chain_internal)
uvm_config_db#(uvm_active_passive_enum)::set(this, "dma1.in_agt", "is_active", UVM_PASSIVE);
dma0 = dma_env::type_id::create("dma0", this);
dma1 = dma_env::type_id::create("dma1", this);
if (cfg.has_e2e_checker) e2e = dma_e2e_scoreboard::type_id::create("e2e", this);
endfunction
function void connect_phase(uvm_phase phase);
if (cfg.has_e2e_checker) begin
dma0.apb.ap.connect(e2e.key0_export);
dma1.apb.ap.connect(e2e.key1_export);
dma0.in_agt.ap.connect(e2e.first_export);
dma1.out_agt.ap.connect(e2e.last_export);
end
endfunction
endclass
// ---------------------------------------------------------------- agent-pattern and subsystem tests
// The output port gets a reactive slave with random backpressure: an instance
// override swaps the agent at env.out_agt only; env.in_agt keeps axis_agent.
class dma_backpressure_test extends dma_base_test;
`uvm_component_utils(dma_backpressure_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
axis_agent::type_id::set_inst_override(axis_sink_agent::get_type(), "env.out_agt", this);
super.build_phase(phase);
endfunction
task run_phase(uvm_phase phase);
axis_sink_agent sink;
axis_ready_seq rsp = axis_ready_seq::type_id::create("rsp");
dma_job_cfg job = dma_job_cfg::type_id::create("job");
bit [31:0] status;
phase.raise_objection(this);
if (!$cast(sink, env.out_agt)) `uvm_fatal("CAST", "env.out_agt is not an axis_sink_agent")
fork
rsp.start(sink.sink_sqr); // the responder runs for the whole test
join_none
if (!job.randomize() with { len == 12; }) `uvm_fatal("RAND", "dma_job_cfg randomize failed")
run_job(job, 12, status);
if (status[2:1] !== 2'b01) `uvm_error("STATUS", $sformatf("expected DONE only, STATUS=%08h", status))
#200ns;
`uvm_info("BP", $sformatf("the slave stalled the DMA for %0d clocks", sink.sink_drv.stall_clocks), UVM_LOW)
phase.drop_objection(this);
endtask
endclass
// Reset in the middle of a packet: reset-aware drivers park their buses, the
// scoreboard drops what the DUT lost, and a second job afterwards must pass.
class dma_reset_test extends dma_base_test;
`uvm_component_utils(dma_reset_test)
virtual rst_if rst_vif;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
apb_driver::type_id::set_type_override(apb_driver_ra::get_type());
axis_driver::type_id::set_type_override(axis_driver_ra::get_type());
dma_scoreboard::type_id::set_type_override(dma_scoreboard_ra::get_type());
super.build_phase(phase);
if (!uvm_config_db#(virtual rst_if)::get(this, "", "rst_vif", rst_vif))
`uvm_fatal("NOVIF", "rst_if not set for the test")
endfunction
task run_phase(uvm_phase phase);
axis_pkt_seq pkt = axis_pkt_seq::type_id::create("pkt");
dma_job_cfg job = dma_job_cfg::type_id::create("job");
bit [31:0] status;
phase.raise_objection(this);
// Job 1: interrupted by a reset after a few words.
reg_write(KEY, 32'hDEAD_BEEF);
reg_write(LEN, 12);
reg_write(CTRL, 32'h3);
if (!pkt.randomize() with { n == 12; }) `uvm_fatal("RAND", "axis_pkt_seq randomize failed")
fork
pkt.start(env.in_agt.sqr);
join_none
#125ns; // between clock edges, mid-packet
pkt.kill(); // the rest of this packet is no longer wanted
rst_vif.rst_n <= 1'b0;
`uvm_info("RST", "reset asserted in the middle of job 1", UVM_LOW)
repeat (3) @(posedge rst_vif.clk);
rst_vif.rst_n <= 1'b1;
repeat (2) @(posedge rst_vif.clk);
// Job 2: after reset, a whole job must work.
if (!job.randomize() with { len == 5; }) `uvm_fatal("RAND", "dma_job_cfg randomize failed")
run_job(job, 5, status);
if (status[2:1] !== 2'b01) `uvm_error("STATUS", $sformatf("job 2: expected DONE only, STATUS=%08h", status))
#100ns;
phase.drop_objection(this);
endtask
endclass
// The test waits on the interrupt agent's event instead of a virtual interface.
class dma_irq_agent_test extends dma_base_test;
`uvm_component_utils(dma_irq_agent_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
dma_env::type_id::set_type_override(dma_env_irq::get_type());
super.build_phase(phase);
endfunction
task run_phase(uvm_phase phase);
uvm_event irq_ev = uvm_event_pool::get_global("dma_irq");
axis_pkt_seq pkt = axis_pkt_seq::type_id::create("pkt");
bit [31:0] status;
phase.raise_objection(this);
reg_write(KEY, 32'h0F0F_0F0F);
reg_write(LEN, 4);
reg_write(CTRL, 32'h3);
if (!pkt.randomize() with { n == 4; }) `uvm_fatal("RAND", "axis_pkt_seq randomize failed")
fork
pkt.start(env.in_agt.sqr);
join_none
irq_ev.wait_trigger(); // no virtual interface needed here
reg_read(STATUS, status);
`uvm_info("IRQ", $sformatf("interrupt event seen, STATUS=%08h", status), UVM_LOW)
reg_write(STATUS, 32'h6);
#100ns;
phase.drop_objection(this);
endtask
endclass
// Two DMAs in a chain. Each block keeps its own scoreboard; the end-to-end
// checker covers the chain. Run with dma_subsys_tb.sv.
class dma_subsys_test extends uvm_test;
`uvm_component_utils(dma_subsys_test)
dma_subsys_env env;
virtual irq_if irq0_vif, irq1_vif;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
dma_subsys_cfg cfg = dma_subsys_cfg::type_id::create("cfg");
super.build_phase(phase);
uvm_config_db#(dma_subsys_cfg)::set(this, "env", "cfg", cfg);
env = dma_subsys_env::type_id::create("env", this);
if (!uvm_config_db#(virtual irq_if)::get(this, "", "irq0_vif", irq0_vif) ||
!uvm_config_db#(virtual irq_if)::get(this, "", "irq1_vif", irq1_vif))
`uvm_fatal("NOVIF", "irq interfaces not set for the subsystem test")
endfunction
function void end_of_elaboration_phase(uvm_phase phase);
uvm_root::get().print_topology();
endfunction
task program_dma(apb_sequencer sqr, bit [31:0] key, int unsigned len);
apb_rw_seq s;
bit [31:0] vals[3] = '{key, len, 32'h3};
bit [7:0] regs[3] = '{KEY, LEN, CTRL};
foreach (regs[i]) begin
s = apb_rw_seq::type_id::create("s");
s.addr = regs[i]; s.write = 1; s.data = vals[i];
s.start(sqr);
end
endtask
task run_phase(uvm_phase phase);
axis_pkt_seq pkt = axis_pkt_seq::type_id::create("pkt");
phase.raise_objection(this);
program_dma(env.dma1.apb.sqr, 32'h1111_0000, 10); // downstream first: ready before data arrives
program_dma(env.dma0.apb.sqr, 32'h0000_2222, 10);
if (!pkt.randomize() with { n == 10; }) `uvm_fatal("RAND", "axis_pkt_seq randomize failed")
pkt.start(env.dma0.in_agt.sqr);
wait (irq0_vif.irq === 1'b1 && irq1_vif.irq === 1'b1);
#100ns;
phase.drop_objection(this);
endtask
endclass
// ---------------------------------------------------------------- checking: coverage
`uvm_analysis_imp_decl(_cov_apb)
`uvm_analysis_imp_decl(_cov_in)
// Functional coverage for the DMA testplan. Covergroups measure the plan; the
// plan_hits table counts the same plan items by name and prints the ones never
// hit, so every test's log says which plan rows it exercised.
class dma_coverage extends uvm_component;
`uvm_component_utils(dma_coverage)
uvm_analysis_imp_cov_apb #(apb_item, dma_coverage) apb_export;
uvm_analysis_imp_cov_in #(axis_item, dma_coverage) in_export;
bit [15:0] len_reg;
bit irq_en;
bit done_seen, err_seen; // as last read from STATUS
int unsigned beats_in_job;
time last_beat_t;
int unsigned plan_hits[string];
string plan[$] = '{"len=0", "len=1", "len=2-4", "len=5-15", "len=16", "len>16",
"irq_en=0", "irq_en=1", "outcome=done", "outcome=err",
"clear=done", "clear=err", "gap=0", "gap=1-3", "gap=4+",
"tlast=on-time", "tlast=early"};
covergroup job_cg with function sample(bit [15:0] len, bit irq); option.per_instance = 1;
cp_len: coverpoint len {
bins zero = {0};
bins one = {1};
bins few = {[2:4]};
bins many = {[5:15]};
bins max_pkt = {16};
bins over = {[17:$]};
}
cp_irq: coverpoint irq;
len_x_irq: cross cp_len, cp_irq;
endgroup
covergroup status_cg with function sample(bit done, bit err); option.per_instance = 1;
cp_outcome: coverpoint {err, done} {
bins done_only = {2'b01};
bins err_only = {2'b10};
bins both = {2'b11}; // legal: ERR survives GO unless cleared
ignore_bins idle = {2'b00}; // a read before the job ends says nothing
}
endgroup
covergroup clear_cg with function sample(bit [1:0] w1c); option.per_instance = 1;
cp_clear: coverpoint w1c {
bins clear_done = {2'b01};
bins clear_err = {2'b10};
bins clear_both = {2'b11};
}
endgroup
covergroup stream_cg with function sample(int unsigned gap, bit early); option.per_instance = 1;
cp_gap: coverpoint gap {
bins back_to_back = {0};
bins short_gap = {[1:3]};
bins long_gap = {[4:$]};
}
cp_tlast: coverpoint early {
bins on_time = {0};
bins early = {1};
}
endgroup
function new(string name, uvm_component parent);
super.new(name, parent);
apb_export = new("apb_export", this);
in_export = new("in_export", this);
job_cg = new();
status_cg = new();
clear_cg = new();
stream_cg = new();
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
job_cg.set_inst_name({get_full_name(), ".job_cg"}); // distinct names per instance
status_cg.set_inst_name({get_full_name(), ".status_cg"});
clear_cg.set_inst_name({get_full_name(), ".clear_cg"});
stream_cg.set_inst_name({get_full_name(), ".stream_cg"});
endfunction
function void hit(string item);
plan_hits[item]++;
endfunction
function string len_bin(bit [15:0] len);
if (len == 0) return "len=0";
if (len == 1) return "len=1";
if (len <= 4) return "len=2-4";
if (len <= 15) return "len=5-15";
if (len == 16) return "len=16";
return "len>16";
endfunction
virtual function void write_cov_apb(apb_item t);
if (t.slverr) return;
if (t.write && t.addr == LEN) len_reg = t.data[15:0];
if (t.write && t.addr == CTRL) begin
irq_en = t.data[1];
if (t.data[0]) begin // GO: one job with this length
job_cg.sample(len_reg, irq_en);
hit(len_bin(len_reg));
hit(irq_en ? "irq_en=1" : "irq_en=0");
beats_in_job = 0;
end
end
// A clear counts only for a bit that was actually set: writing 1 to an
// already-clear W1C bit exercises nothing.
if (t.write && t.addr == STATUS) begin
bit [1:0] cleared = t.data[2:1] & {err_seen, done_seen};
if (cleared != 0) begin
clear_cg.sample(cleared);
if (cleared[0]) hit("clear=done");
if (cleared[1]) hit("clear=err");
end
done_seen &= !t.data[1];
err_seen &= !t.data[2];
end
if (!t.write && t.addr == STATUS) begin
done_seen = t.rdata[1];
err_seen = t.rdata[2];
if (t.rdata[2:1] != 0) begin
status_cg.sample(t.rdata[1], t.rdata[2]);
hit(t.rdata[2] ? "outcome=err" : "outcome=done");
end
end
endfunction
virtual function void write_cov_in(axis_item t);
int unsigned gap;
bit early;
gap = (beats_in_job == 0) ? 0 : int'(($time - last_beat_t) / 10ns) - 1;
last_beat_t = $time;
beats_in_job++;
early = t.last && (beats_in_job < len_reg);
stream_cg.sample(gap, early);
if (beats_in_job > 1) hit(gap == 0 ? "gap=0" : (gap <= 3 ? "gap=1-3" : "gap=4+"));
if (t.last) hit(early ? "tlast=early" : "tlast=on-time");
endfunction
function void report_phase(uvm_phase phase);
string holes = "";
`uvm_info("COV", $sformatf("covergroups: job %0.1f%%, status %0.1f%%, clear %0.1f%%, stream %0.1f%%",
job_cg.get_inst_coverage(), status_cg.get_inst_coverage(),
clear_cg.get_inst_coverage(), stream_cg.get_inst_coverage()), UVM_LOW)
foreach (plan[i]) if (!plan_hits.exists(plan[i])) holes = {holes, " ", plan[i]};
`uvm_info("COV", $sformatf("plan items hit: %0d of %0d", plan_hits.num(), plan.size()), UVM_LOW)
if (holes != "") `uvm_info("COV", {"plan holes:", holes}, UVM_LOW)
endfunction
endclassContinue with part 3.