This is the end of dma_tb.sv, lines 1535-2030. Paste it after part 2 in the same testbench.sv tab. The page ends with the add-on files that build on the bench.
Section | What it adds | Explained in |
|---|---|---|
Predictor and comparators | The separate predictor and the built-in in-order and class comparators | |
Checking tests | The tests for coverage, comparators and the zero-length hole | |
Verdict server and waivers | The one-line verdict that survives a fatal, and a narrow report catcher | |
Watchdog and broken pieces | The activity watchdog, and the deliberately broken parts the debug playbooks use | |
| The top module: clock, reset, interfaces, the DUT and |
// not-run: needs the UVM library. Paste into EDA Playground with UVM 1.2 selected. This is lines 1535-2030 of dma_tb.sv.
// ---------------------------------------------------------------- checking: predictor and comparators
`uvm_analysis_imp_decl(_pred_apb)
`uvm_analysis_imp_decl(_pred_in)
// The reference model as its own component: it watches register traffic and
// input words, and publishes one expected output item per input word.
class dma_predictor extends uvm_component;
`uvm_component_utils(dma_predictor)
uvm_analysis_imp_pred_apb #(apb_item, dma_predictor) apb_export;
uvm_analysis_imp_pred_in #(axis_item, dma_predictor) in_export;
uvm_analysis_port #(axis_item) exp_ap;
bit [31:0] key;
function new(string name, uvm_component parent);
super.new(name, parent);
apb_export = new("apb_export", this);
in_export = new("in_export", this);
exp_ap = new("exp_ap", this);
endfunction
// The model itself: one virtual function, so a derived predictor (a buggy one
// for a demo, or one that calls a C model) changes only this.
virtual function bit [31:0] predict(bit [31:0] data);
return data ^ key;
endfunction
virtual function void write_pred_apb(apb_item t);
if (t.write && !t.slverr && t.addr == KEY) key = t.data;
endfunction
virtual function void write_pred_in(axis_item t);
axis_item e = axis_item::type_id::create("exp");
e.data = predict(t.data);
e.last = t.last;
exp_ap.write(e);
endfunction
endclass
// Used only by the comparator-trap demo: predicts with the wrong key.
class dma_predictor_buggy extends dma_predictor;
`uvm_component_utils(dma_predictor_buggy)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function bit [31:0] predict(bit [31:0] data);
return data ^ ~key;
endfunction
endclass
// A comparison policy for uvm_in_order_comparator: compare the data field.
class axis_data_comp;
static function bit comp(axis_item a, axis_item b);
return a.data == b.data;
endfunction
endclass
typedef uvm_in_order_comparator #(axis_item, axis_data_comp, uvm_class_converter #(axis_item),
uvm_class_pair #(axis_item, axis_item)) axis_in_order_comparator;
typedef uvm_in_order_class_comparator #(axis_item) axis_class_comparator;
// The block env plus a separate predictor, a built-in comparator and coverage.
// use_class_cmp (config_db, default 0) swaps in uvm_in_order_class_comparator,
// which compares items with compare(): axis_item has no do_compare, so that
// comparator calls every pair a match.
class dma_env_check extends dma_env;
`uvm_component_utils(dma_env_check)
dma_predictor pred;
axis_in_order_comparator cmp;
axis_class_comparator class_cmp;
dma_coverage cov;
bit use_class_cmp;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
void'(uvm_config_db#(bit)::get(this, "", "use_class_cmp", use_class_cmp));
pred = dma_predictor::type_id::create("pred", this);
if (use_class_cmp) class_cmp = axis_class_comparator::type_id::create("class_cmp", this);
else cmp = axis_in_order_comparator::type_id::create("cmp", this);
cov = dma_coverage::type_id::create("cov", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
apb.ap.connect(pred.apb_export);
in_agt.ap.connect(pred.in_export);
if (use_class_cmp) begin
pred.exp_ap.connect(class_cmp.before_export); // expected
out_agt.ap.connect(class_cmp.after_export); // actual
end else begin
pred.exp_ap.connect(cmp.before_export);
out_agt.ap.connect(cmp.after_export);
end
apb.ap.connect(cov.apb_export);
in_agt.ap.connect(cov.in_export);
endfunction
function void report_phase(uvm_phase phase);
if (use_class_cmp)
`uvm_info("CMP", $sformatf("class comparator: %0d matches, %0d mismatches", class_cmp.m_matches, class_cmp.m_mismatches), UVM_LOW)
else
`uvm_info("CMP", $sformatf("in-order comparator: %0d matches, %0d mismatches", cmp.m_matches, cmp.m_mismatches), UVM_LOW)
endfunction
endclass
`ifdef DMA_DPI_MODEL
// The reference model in C (dma_model.c), called through DPI.
import "DPI-C" function int unsigned dma_c_model(input int unsigned data, input int unsigned key);
class dma_predictor_dpi extends dma_predictor;
`uvm_component_utils(dma_predictor_dpi)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function bit [31:0] predict(bit [31:0] data);
return dma_c_model(data, key);
endfunction
endclass
`endif
// ---------------------------------------------------------------- checking tests
// Three random jobs with the checking env: the scoreboard and the separate
// predictor + built-in comparator must agree, and coverage reports its holes.
class dma_check_test extends dma_rand_test;
`uvm_component_utils(dma_check_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_check::get_type());
super.build_phase(phase);
endfunction
endclass
// The comparator trap: a predictor with the wrong key feeds a class comparator.
// Every prediction is wrong, yet the class comparator, which relies on
// axis_item::compare() (and axis_item has no do_compare), calls each one a match.
class dma_cmp_trap_test extends dma_smoke_test;
`uvm_component_utils(dma_cmp_trap_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_check::get_type());
dma_predictor::type_id::set_type_override(dma_predictor_buggy::get_type());
uvm_config_db#(bit)::set(this, "env", "use_class_cmp", 1);
super.build_phase(phase);
endfunction
endclass
// Closing a coverage hole: random jobs never use LEN = 0 (dma_job_cfg allows
// 1 to 16), so the len=0 plan item stays empty. This directed test closes it:
// a zero-length job must finish at once with DONE and no data.
class dma_zero_len_test extends dma_base_test;
`uvm_component_utils(dma_zero_len_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_check::get_type());
super.build_phase(phase);
endfunction
// No data moves, so the scoreboard's "nothing was compared" error is expected
// here, and only here: demote that one ID on that one component, on purpose.
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
env.sb.set_report_severity_id_override(UVM_ERROR, "SB_EMPTY", UVM_INFO);
endfunction
task run_phase(uvm_phase phase);
bit [31:0] status, count;
phase.raise_objection(this);
reg_write(KEY, 32'h1357_9BDF);
reg_write(LEN, 0);
reg_write(CTRL, 32'h3); // GO with no data to move
wait (irq_vif.irq === 1'b1);
reg_read(STATUS, status);
reg_read(COUNT, count);
if (status[2:0] !== 3'b010) `uvm_error("ZERO", $sformatf("expected DONE only, STATUS=%08h", status))
if (count !== 0) `uvm_error("ZERO", $sformatf("expected COUNT=0, got %0d", count))
reg_write(STATUS, 32'h2);
`uvm_info("ZERO", "zero-length job finished at once with DONE", UVM_LOW)
#50ns;
phase.drop_objection(this);
endtask
endclass
`ifdef DMA_DPI_MODEL
// The same random jobs, predicted by the C model.
class dma_dpi_test extends dma_check_test;
`uvm_component_utils(dma_dpi_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
dma_predictor::type_id::set_type_override(dma_predictor_dpi::get_type());
super.build_phase(phase);
endfunction
endclass
`endif
// ---------------------------------------------------------------- reporting and debug: verdict server and waivers
// A report server that always ends the log with one verdict line, even when the
// run stops on a fatal (the library calls report_summarize on the way out).
class dma_report_server extends uvm_default_report_server;
function new(string name = "dma_report_server");
super.new(name);
endfunction
virtual function void report_summarize(UVM_FILE file = 0);
int n_err = get_severity_count(UVM_ERROR);
int n_fat = get_severity_count(UVM_FATAL);
super.report_summarize(file);
$display("TEST VERDICT: %s (UVM_ERROR=%0d, UVM_FATAL=%0d)",
(n_err == 0 && n_fat == 0) ? "PASS" : "FAIL", n_err, n_fat);
endfunction
endclass
// Installs the verdict server. Call it from a test's constructor or build_phase.
function automatic void install_verdict_server();
dma_report_server srv = new();
uvm_report_server::set_server(srv);
endfunction
// A narrow waiver: demotes one message ID whose text matches one pattern, tags
// it with the tracking ticket, and counts what it waived.
class dma_waiver_catcher extends uvm_report_catcher;
string waive_id;
string pattern = "*";
string ticket;
int unsigned waived;
function new(string name = "dma_waiver_catcher");
super.new(name);
endfunction
function action_e catch();
if (get_severity() == UVM_ERROR && get_id() == waive_id && uvm_is_match(pattern, get_message())) begin
set_severity(UVM_INFO);
set_message({"[WAIVED ", ticket, "] ", get_message()});
waived++;
end
return THROW;
endfunction
endclass
// ---------------------------------------------------------------- reporting and debug: activity watchdog
`uvm_analysis_imp_decl(_wd_apb)
`uvm_analysis_imp_decl(_wd_in)
`uvm_analysis_imp_decl(_wd_out)
// Ends a stalled test early: if no monitor reports any traffic for quiet_limit
// while someone still holds the run phase open, it is a hang, not a slow test.
class dma_activity_watchdog extends uvm_component;
`uvm_component_utils(dma_activity_watchdog)
uvm_analysis_imp_wd_apb #(apb_item, dma_activity_watchdog) apb_export;
uvm_analysis_imp_wd_in #(axis_item, dma_activity_watchdog) in_export;
uvm_analysis_imp_wd_out #(axis_item, dma_activity_watchdog) out_export;
time quiet_limit = 2us;
time last_activity;
string last_what = "nothing yet";
function new(string name, uvm_component parent);
super.new(name, parent);
apb_export = new("apb_export", this);
in_export = new("in_export", this);
out_export = new("out_export", this);
endfunction
virtual function void write_wd_apb(apb_item t); last_activity = $time; last_what = t.convert2string(); endfunction
virtual function void write_wd_in(axis_item t); last_activity = $time; last_what = "input beat"; endfunction
virtual function void write_wd_out(axis_item t); last_activity = $time; last_what = "output beat"; endfunction
task run_phase(uvm_phase phase);
forever begin
#(quiet_limit / 4);
if ($time - last_activity > quiet_limit &&
phase.get_objection().get_objection_total(uvm_root::get()) > 0)
`uvm_fatal("WATCHDOG", $sformatf("no bus activity for %0t while the test is still running; last activity at %0t: %s",
$time - last_activity, last_activity, last_what))
end
endtask
endclass
// ---------------------------------------------------------------- reporting and debug: deliberately broken pieces
// A driver that forgets item_done. Used only by the hang playbook.
class axis_driver_noack extends axis_driver;
`uvm_component_utils(axis_driver_noack)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
vif.tvalid <= 1'b0;
@(posedge vif.rst_n);
forever begin
seq_item_port.get_next_item(req);
@(posedge vif.clk);
vif.tdata <= req.data;
vif.tlast <= req.last;
vif.tvalid <= 1'b1;
do @(posedge vif.clk); while (!vif.tready);
vif.tvalid <= 1'b0;
// BUG: no seq_item_port.item_done() here
end
endtask
endclass
// ---------------------------------------------------------------- reporting and debug tests
// The verdict line on a passing run.
class dma_verdict_test extends dma_smoke_test;
`uvm_component_utils(dma_verdict_test)
function new(string name, uvm_component parent);
super.new(name, parent);
install_verdict_server();
endfunction
endclass
// The verdict line still appears when the run stops on a fatal.
class dma_verdict_fatal_test extends dma_timeout_test;
`uvm_component_utils(dma_verdict_fatal_test)
function new(string name, uvm_component parent);
super.new(name, parent);
install_verdict_server();
endfunction
endclass
// A known issue, waived narrowly. KEY is written while the DMA is busy: the
// DMA ignores it, but the scoreboard (bench limitation, ticket DMA-142) takes
// the new key at once and reports the remaining words as mismatches. The
// catcher waives exactly those messages, on the scoreboard only.
class dma_waiver_test extends dma_base_test;
`uvm_component_utils(dma_waiver_test)
dma_waiver_catcher catcher;
function new(string name, uvm_component parent);
super.new(name, parent);
install_verdict_server();
endfunction
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
catcher = new("catcher");
catcher.waive_id = "SB_MISMATCH";
catcher.ticket = "DMA-142";
uvm_report_cb::add(env.sb, catcher); // this component only
endfunction
task run_phase(uvm_phase phase);
axis_pkt_seq pkt = axis_pkt_seq::type_id::create("pkt");
phase.raise_objection(this);
reg_write(KEY, 32'h0000_00FF);
reg_write(LEN, 8);
reg_write(CTRL, 32'h3);
if (!pkt.randomize() with { n == 8; }) `uvm_fatal("RAND", "axis_pkt_seq randomize failed")
fork
pkt.start(env.in_agt.sqr);
join_none
#150ns;
reg_write(KEY, 32'hFFFF_0000); // ignored by the busy DMA, believed by the scoreboard
wait (irq_vif.irq === 1'b1);
reg_write(STATUS, 32'h6);
#100ns;
phase.drop_objection(this);
endtask
function void report_phase(uvm_phase phase);
`uvm_info("WAIVER", $sformatf("%0d message(s) waived under %s", catcher.waived, catcher.ticket), UVM_LOW)
endfunction
endclass
// A stalled job: the DMA is started but no data is ever sent. The watchdog
// ends the run about 2 us after the last bus activity, long before the
// 20 us global timeout.
class dma_stall_watchdog_test extends dma_base_test;
`uvm_component_utils(dma_stall_watchdog_test)
dma_activity_watchdog wd;
function new(string name, uvm_component parent);
super.new(name, parent);
install_verdict_server();
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
wd = dma_activity_watchdog::type_id::create("wd", this);
uvm_root::get().set_timeout(20us, 1);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
env.apb.ap.connect(wd.apb_export);
env.in_agt.ap.connect(wd.in_export);
env.out_agt.ap.connect(wd.out_export);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
reg_write(KEY, 32'h1);
reg_write(LEN, 4);
reg_write(CTRL, 32'h3);
wait (irq_vif.irq === 1'b1); // never comes: no data was sent
phase.drop_objection(this);
endtask
endclass
// Hang playbook: the stream driver forgets item_done.
class dma_hang_noack_test extends dma_smoke_test;
`uvm_component_utils(dma_hang_noack_test)
function new(string name, uvm_component parent);
super.new(name, parent);
install_verdict_server();
endfunction
function void build_phase(uvm_phase phase);
axis_driver::type_id::set_type_override(axis_driver_noack::get_type());
super.build_phase(phase);
uvm_root::get().set_timeout(5us, 1);
endfunction
endclass
// Early-end playbook: the packet is started in the background and the test
// drops its objection at once. The run ends before the data moves.
class dma_early_end_test extends dma_base_test;
`uvm_component_utils(dma_early_end_test)
function new(string name, uvm_component parent);
super.new(name, parent);
install_verdict_server();
endfunction
task run_phase(uvm_phase phase);
axis_pkt_seq pkt = axis_pkt_seq::type_id::create("pkt");
phase.raise_objection(this);
reg_write(KEY, 32'h0000_1111);
reg_write(LEN, 8);
reg_write(CTRL, 32'h3);
if (!pkt.randomize() with { n == 8; }) `uvm_fatal("RAND", "axis_pkt_seq randomize failed")
fork
pkt.start(env.in_agt.sqr);
join_none
phase.drop_objection(this); // BUG: nothing waits for the packet
endtask
endclass
// False-pass playbook: the same early end, after someone "quietened" the
// scoreboard's end-of-test errors for the whole env. The verdict says PASS
// although the test compared nothing.
class dma_false_pass_test extends dma_early_end_test;
`uvm_component_utils(dma_false_pass_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
env.sb.set_report_severity_id_override(UVM_ERROR, "SB_EMPTY", UVM_INFO); // BUG: unconditional,
env.sb.set_report_severity_id_override(UVM_ERROR, "SB_LEFTOVER", UVM_INFO); // unreviewed waivers
endfunction
endclass
endpackage
// ============================ top ============================
`ifndef DMA_NO_TOP
module top;
import uvm_pkg::*;
import dma_pkg::*;
logic clk = 0;
always #5 clk = ~clk; // 100 MHz
rst_if rst (clk);
wire rst_n = rst.rst_n;
initial begin
rst.rst_n = 1'b0;
repeat (4) @(posedge clk);
rst.rst_n <= 1'b1;
end
apb_if apb (clk, rst_n);
axis_if s_ax (clk, rst_n);
axis_if m_ax (clk, rst_n);
irq_if irqi (clk);
// The output side accepts every beat unless a test adds a reactive slave
// (axis_sink_agent), whose driver then drives TREADY.
initial m_ax.tready = 1'b1;
dma_dut dut (
.clk, .rst_n,
.paddr(apb.paddr), .psel(apb.psel), .penable(apb.penable), .pwrite(apb.pwrite),
.pwdata(apb.pwdata), .prdata(apb.prdata), .pready(apb.pready), .pslverr(apb.pslverr),
.s_tdata(s_ax.tdata), .s_tvalid(s_ax.tvalid), .s_tlast(s_ax.tlast), .s_tready(s_ax.tready),
.m_tdata(m_ax.tdata), .m_tvalid(m_ax.tvalid), .m_tlast(m_ax.tlast), .m_tready(m_ax.tready),
.irq(irqi.irq)
);
initial begin
uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.env.apb.*", "vif", apb);
uvm_config_db#(virtual axis_if)::set(null, "uvm_test_top.env.in_agt.*", "vif", s_ax);
uvm_config_db#(virtual axis_if)::set(null, "uvm_test_top.env.out_agt.*", "vif", m_ax);
uvm_config_db#(virtual irq_if)::set(null, "uvm_test_top", "irq_vif", irqi);
uvm_config_db#(virtual irq_if)::set(null, "uvm_test_top.env.irq.*", "vif", irqi);
uvm_config_db#(virtual rst_if)::set(null, "uvm_test_top*", "rst_vif", rst);
run_test("dma_smoke_test");
end
endmodule
`endifLater sections add files that build on dma_tb.sv. Each one includes it, so keep the full dma_tb.sv (all three parts) in its own file beside them. On EDA Playground, add a second file with the + button in the testbench pane and name it dma_tb.sv.
File | What it adds | Full listing |
|---|---|---|
| Two DMAs in a chain, reusing the block env twice, with an end-to-end checker. Run | |
| The register layer: a register model, an APB adapter, an explicit predictor and a backdoor. Run | |
| The data path as a C model, for | |
| The TLM lessons' tests: TLM-1 ports, FIFOs and channels, the DMA scoreboard on analysis FIFOs ( | TLM basics and TLM 2.0 |
| A fault-injection top: forces one stuck-at fault inside the DUT, chosen by | Below; explained in Functional safety |
// not-run: needs the UVM library. Paste into EDA Playground with UVM 1.2 selected.
// dma_fault_tb.sv - a fault-injection campaign on the DMA: the block bench, plus a
// top that forces one stuck-at fault inside the design, chosen by +FAULT=<n>.
// Run every fault against a test and count how many the bench detects. Compile
// with dma_dut.sv; this file includes dma_tb.sv (keep both side by side).
// Script: scripts/tools/uvm-bench/fault-campaign.sh
`define DMA_NO_TOP
`include "dma_tb.sv"
module top;
import uvm_pkg::*;
import dma_pkg::*;
logic clk = 0;
always #5 clk = ~clk;
rst_if rst (clk);
wire rst_n = rst.rst_n;
initial begin
rst.rst_n = 1'b0;
repeat (4) @(posedge clk);
rst.rst_n <= 1'b1;
end
apb_if apb (clk, rst_n);
axis_if s_ax (clk, rst_n);
axis_if m_ax (clk, rst_n);
irq_if irqi (clk);
initial m_ax.tready = 1'b1;
dma_dut dut (
.clk, .rst_n,
.paddr(apb.paddr), .psel(apb.psel), .penable(apb.penable), .pwrite(apb.pwrite),
.pwdata(apb.pwdata), .prdata(apb.prdata), .pready(apb.pready), .pslverr(apb.pslverr),
.s_tdata(s_ax.tdata), .s_tvalid(s_ax.tvalid), .s_tlast(s_ax.tlast), .s_tready(s_ax.tready),
.m_tdata(m_ax.tdata), .m_tvalid(m_ax.tvalid), .m_tlast(m_ax.tlast), .m_tready(m_ax.tready),
.irq(irqi.irq)
);
// The fault list. Each entry is one stuck-at fault on one internal signal,
// applied after reset and held for the whole run.
int fault_id;
initial begin
if ($value$plusargs("FAULT=%d", fault_id) && fault_id != 0) begin
@(posedge rst_n);
case (fault_id)
1: force dut.key[0] = 1'b0; // KEY bit 0 stuck at 0
2: force dut.m_tdata[31] = 1'b1; // output data bit 31 stuck at 1
3: force dut.count[3] = 1'b0; // COUNT bit 3 stuck at 0
4: force dut.irq_en = 1'b0; // interrupt enable stuck at 0
5: force dut.err = 1'b0; // ERR flag stuck at 0
6: force dut.done = 1'b1; // DONE flag stuck at 1
7: force dut.m_tlast = 1'b0; // output TLAST stuck at 0
8: force dut.len[4] = 1'b0; // LEN bit 4 stuck at 0
default: $fatal(1, "unknown fault %0d", fault_id);
endcase
$display("FAULT %0d injected", fault_id);
end
end
initial begin
uvm_config_db#(virtual apb_if)::set(null, "uvm_test_top.env.apb.*", "vif", apb);
uvm_config_db#(virtual axis_if)::set(null, "uvm_test_top.env.in_agt.*", "vif", s_ax);
uvm_config_db#(virtual axis_if)::set(null, "uvm_test_top.env.out_agt.*", "vif", m_ax);
uvm_config_db#(virtual irq_if)::set(null, "uvm_test_top", "irq_vif", irqi);
uvm_config_db#(virtual rst_if)::set(null, "uvm_test_top*", "rst_vif", rst);
uvm_root::get().set_timeout(50us, 1); // a fault that hangs the DMA must still end the run
run_test("dma_rand_test");
end
endmodule