Every log in the TLM lessons comes from this file. It includes the DMA bench (dma_tb.sv) and adds the TLM tests in their own package, plus a top module, so it compiles with the same two files as every other add-on.
Test | What it shows | Lesson |
|---|---|---|
| A subscriber that owns a port instead of an imp: three writes, zero received, no error | |
|
| |
| A depth-1 | |
| Connection counts checked by resolve_bindings; the request/response and transport channels | |
| The DMA scoreboard on analysis FIFOs, beside the original; a slow compare loses words at end of test; draining fixes it | |
| Generic payload over blocking sockets: annotated delay, byte enables, an address error, an extension and a clone |
EDA Playground: dma_dut.sv in the design tab; dma_tb.sv (all three parts of the bench source, pasted together) and tlm_tb.sv as testbench files; UVM 1.2; compile tlm_tb.sv last and set the top to top.
Pick a test with +UVM_TESTNAME=, for example +UVM_TESTNAME=tlm2_gp_test. Without one, tlm_deaf_test runs.
Locally: UVM_HOME=<uvm>/src scripts/tools/uvm-bench/run.sh scripts/data/uvm-course/bench/tlm_tb.sv +UVM_TESTNAME=dma_fifo_sb_test.
tlm_cardinality_test and tlm_export_port_test end with BUILDERR on purpose: they demonstrate connection errors.
// not-run: needs the UVM library. Paste into EDA Playground with UVM 1.2 selected.
// tlm_tb.sv - the TLM lessons' additions to the DMA bench: small TLM-1 tests (a
// port where an imp belongs, a bounded FIFO, request/response and transport
// channels), the DMA scoreboard rebuilt on analysis FIFOs, and a TLM-2.0
// generic-payload test (blocking sockets, byte enables, an extension).
// Compile with dma_dut.sv; this file includes dma_tb.sv (keep both side by side).
// Run: scripts/tools/uvm-bench/run.sh scripts/data/uvm-course/bench/tlm_tb.sv +UVM_TESTNAME=<test>
`define DMA_NO_TOP
`include "dma_tb.sv"
package tlm_demo_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
import dma_pkg::*;
// ---------------------------------------------------------------- a port where an imp belongs
class talker extends uvm_component;
`uvm_component_utils(talker)
uvm_analysis_port #(apb_item) ap;
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
for (int i = 0; i < 3; i++) begin
apb_item t = apb_item::type_id::create("t");
t.addr = 8'h10 + 4 * i;
#10ns ap.write(t);
end
`uvm_info("TALK", "wrote 3 items", UVM_LOW)
phase.drop_objection(this);
endtask
endclass
// A "subscriber" that owns a port instead of an imp (the lesson's bad example).
class deaf_sb extends uvm_component;
`uvm_component_utils(deaf_sb)
uvm_analysis_port #(apb_item) in_port; // should be an imp
int unsigned n_seen;
function new(string name, uvm_component parent);
super.new(name, parent);
in_port = new("in_port", this);
endfunction
function void write(apb_item t); // never called
n_seen++;
endfunction
function void report_phase(uvm_phase phase);
`uvm_info("DEAF", $sformatf("%0d item(s) received", n_seen), UVM_LOW)
endfunction
endclass
class tlm_deaf_test extends uvm_test;
`uvm_component_utils(tlm_deaf_test)
talker tk;
deaf_sb sb;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
tk = talker::type_id::create("tk", this);
sb = deaf_sb::type_id::create("sb", this);
endfunction
function void connect_phase(uvm_phase phase);
tk.ap.connect(sb.in_port); // compiles, elaborates, delivers nothing
endfunction
endclass
// ---------------------------------------------------------------- a bounded FIFO
class fifo_requester extends uvm_component;
`uvm_component_utils(fifo_requester)
uvm_blocking_put_port #(apb_item) pport;
function new(string name, uvm_component parent);
super.new(name, parent);
pport = new("pport", this);
endfunction
task run_phase(uvm_phase phase);
apb_item req;
phase.raise_objection(this);
for (int i = 0; i < 3; i++) begin
req = apb_item::type_id::create("req");
req.addr = 8'h10 + 4 * i;
pport.put(req); // waits while the fifo is full
`uvm_info("FIFO", $sformatf("put #%0d returned", i), UVM_LOW)
end
#60ns;
phase.drop_objection(this);
endtask
endclass
class fifo_responder extends uvm_component;
`uvm_component_utils(fifo_responder)
uvm_blocking_get_port #(apb_item) gport;
function new(string name, uvm_component parent);
super.new(name, parent);
gport = new("gport", this);
endfunction
task run_phase(uvm_phase phase);
forever begin
apb_item t;
#20ns gport.get(t); // one item every 20 ns
end
endtask
endclass
class tlm_fifo_test extends uvm_test;
`uvm_component_utils(tlm_fifo_test)
fifo_requester req;
fifo_responder rsp;
uvm_tlm_fifo #(apb_item) fifo;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
req = fifo_requester::type_id::create("req", this);
rsp = fifo_responder::type_id::create("rsp", this);
fifo = new("fifo", this); // default depth 1
endfunction
function void connect_phase(uvm_phase phase);
req.pport.connect(fifo.put_export);
rsp.gport.connect(fifo.get_export);
endfunction
endclass
// ---------------------------------------------------------------- request/response and transport channels
class chan_requester extends uvm_component;
`uvm_component_utils(chan_requester)
uvm_master_port #(apb_item, apb_item) mport;
uvm_transport_port #(apb_item, apb_item) tport;
function new(string name, uvm_component parent);
super.new(name, parent);
mport = new("mport", this);
tport = new("tport", this);
endfunction
task run_phase(uvm_phase phase);
apb_item req, rsp;
phase.raise_objection(this);
req = apb_item::type_id::create("req");
req.addr = 8'h10;
mport.put(req); // send the request
mport.get(rsp); // collect the response
`uvm_info("MASTER", $sformatf("response for %02h: rdata=%08h", rsp.addr, rsp.rdata), UVM_LOW)
req = apb_item::type_id::create("req");
req.addr = 8'h04;
tport.transport(req, rsp); // one call does both
`uvm_info("TRANSPORT", $sformatf("response for %02h: rdata=%08h", rsp.addr, rsp.rdata), UVM_LOW)
phase.drop_objection(this);
endtask
endclass
class chan_responder extends uvm_component;
`uvm_component_utils(chan_responder)
uvm_slave_port #(apb_item, apb_item) sport, tsport;
function new(string name, uvm_component parent);
super.new(name, parent);
sport = new("sport", this);
tsport = new("tsport", this);
endfunction
// One loop serves any slave port.
task serve(uvm_slave_port #(apb_item, apb_item) p);
forever begin
apb_item req, rsp;
p.get(req); // take the next request
#10ns;
$cast(rsp, req.clone());
rsp.rdata = 32'hC0DE_0000 | req.addr;
p.put(rsp); // answer it
end
endtask
task run_phase(uvm_phase phase);
fork
serve(sport);
serve(tsport);
join
endtask
endclass
class tlm_channel_test extends uvm_test;
`uvm_component_utils(tlm_channel_test)
chan_requester req;
chan_responder rsp;
uvm_tlm_req_rsp_channel #(apb_item, apb_item) rr_ch;
uvm_tlm_transport_channel #(apb_item, apb_item) tr_ch;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
req = chan_requester::type_id::create("req", this);
rsp = chan_responder::type_id::create("rsp", this);
rr_ch = new("rr_ch", this);
tr_ch = new("tr_ch", this);
endfunction
function void connect_phase(uvm_phase phase);
req.mport.connect(rr_ch.master_export); // put REQ, get RSP
rsp.sport.connect(rr_ch.slave_export); // get REQ, put RSP
req.tport.connect(tr_ch.transport_export); // transport(req, rsp) in one call
rsp.tsport.connect(tr_ch.slave_export);
endfunction
endclass
// ---------------------------------------------------------------- connection counts and connect() errors
// Two put imps for the over-connected port to reach.
class put_sink extends uvm_component;
`uvm_component_utils(put_sink)
uvm_blocking_put_imp #(apb_item, put_sink) imp;
function new(string name, uvm_component parent);
super.new(name, parent);
imp = new("imp", this);
endfunction
task put(apb_item t);
endtask
endclass
// Five connectors, three of them wired wrongly for their counts.
class port_demo extends uvm_component;
`uvm_component_utils(port_demo)
uvm_blocking_put_port #(apb_item) lonely_port; // min 1, left unconnected
uvm_blocking_put_port #(apb_item) greedy_port; // max 1, reaches two imps
uvm_analysis_export #(apb_item) dangling_export; // min 1, reaches no imp
uvm_analysis_port #(apb_item) quiet_ap; // min 0, left unconnected
uvm_seq_item_pull_port #(apb_item) pull_port; // min 0, left unconnected
function new(string name, uvm_component parent);
super.new(name, parent);
lonely_port = new("lonely_port", this);
greedy_port = new("greedy_port", this);
dangling_export = new("dangling_export", this);
quiet_ap = new("quiet_ap", this);
pull_port = new("pull_port", this);
endfunction
endclass
class tlm_cardinality_test extends uvm_test;
`uvm_component_utils(tlm_cardinality_test)
port_demo demo;
put_sink s1, s2;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
demo = port_demo::type_id::create("demo", this);
s1 = put_sink::type_id::create("s1", this);
s2 = put_sink::type_id::create("s2", this);
endfunction
function void connect_phase(uvm_phase phase);
demo.greedy_port.connect(s1.imp);
demo.greedy_port.connect(s2.imp);
endfunction
endclass
// export.connect(port): the call starts from the wrong end.
class tlm_export_port_test extends uvm_test;
`uvm_component_utils(tlm_export_port_test)
uvm_analysis_port #(apb_item) prt;
uvm_analysis_export #(apb_item) exp;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
prt = new("prt", this);
exp = new("exp", this);
endfunction
function void connect_phase(uvm_phase phase);
exp.connect(prt); // BUG: should be prt.connect(exp)
endfunction
endclass
// ---------------------------------------------------------------- the DMA scoreboard on analysis FIFOs
class dma_fifo_scoreboard extends uvm_scoreboard;
`uvm_component_utils(dma_fifo_scoreboard)
uvm_tlm_analysis_fifo #(apb_item) apb_fifo;
uvm_tlm_analysis_fifo #(axis_item) in_fifo, out_fifo;
bit [31:0] key;
bit [31:0] expected[$];
int unsigned n_match, n_mismatch;
time compare_delay = 0; // 80 ns stands in for a slow reference model
int unsigned max_used; // peak occupancy of out_fifo
bit busy; // a compare is in progress
function new(string name, uvm_component parent);
super.new(name, parent);
apb_fifo = new("apb_fifo", this);
in_fifo = new("in_fifo", this);
out_fifo = new("out_fifo", this);
endfunction
task run_phase(uvm_phase phase);
fork
forever begin apb_item t; apb_fifo.get(t); if (t.write && !t.slverr && t.addr == KEY) key = t.data; end
forever begin axis_item t; in_fifo.get(t); expected.push_back(t.data ^ key); end
forever begin
axis_item t;
if (out_fifo.used() > max_used) max_used = out_fifo.used();
out_fifo.get(t);
busy = 1;
if (compare_delay != 0) #(compare_delay);
compare(t);
busy = 0;
end
join
endtask
// Same as dma_scoreboard::write_out: pop expected, compare, count.
virtual function void compare(axis_item t);
bit [31:0] exp;
if (expected.size() == 0) begin
`uvm_error("FSB_UNEXP", $sformatf("output word %08h with nothing expected", t.data))
n_mismatch++;
return;
end
exp = expected.pop_front();
if (t.data !== exp) begin
`uvm_error("FSB_MISMATCH", $sformatf("expected %08h, got %08h", exp, t.data))
n_mismatch++;
end else begin
n_match++;
end
endfunction
function bit idle();
return apb_fifo.is_empty() && in_fifo.is_empty() && out_fifo.is_empty() && !busy;
endfunction
function void check_phase(uvm_phase phase);
if (!apb_fifo.is_empty() || !in_fifo.is_empty() || !out_fifo.is_empty())
`uvm_error("FSB_UNREAD", $sformatf("items left unread: apb=%0d in=%0d out=%0d",
apb_fifo.used(), in_fifo.used(), out_fifo.used()))
if (expected.size() != 0)
`uvm_error("FSB_LEFTOVER", $sformatf("%0d expected word(s) never came out", expected.size()))
if (n_match == 0) `uvm_error("FSB_EMPTY", "no words were compared")
endfunction
function void report_phase(uvm_phase phase);
`uvm_info("FSB", $sformatf("%0d words matched, %0d mismatched", n_match, n_mismatch), UVM_LOW)
if (compare_delay != 0)
`uvm_info("FSB", $sformatf("out_fifo peaked at %0d waiting item(s)", max_used), UVM_LOW)
endfunction
endclass
// The slow scoreboard that holds the run phase open until its FIFOs are drained.
class dma_fifo_scoreboard_eot extends dma_fifo_scoreboard;
`uvm_component_utils(dma_fifo_scoreboard_eot)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void phase_ready_to_end(uvm_phase phase);
if (phase.get_imp() != uvm_run_phase::get() || idle()) return;
phase.raise_objection(this, "analysis fifos not drained");
fork begin
while (!idle()) #10ns;
phase.drop_objection(this, "analysis fifos drained");
end join_none
endfunction
endclass
class dma_fifo_env extends dma_env;
`uvm_component_utils(dma_fifo_env)
dma_fifo_scoreboard fsb;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
fsb = dma_fifo_scoreboard::type_id::create("fsb", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase); // ap -> sb imps, as before
apb.ap.connect(fsb.apb_fifo.analysis_export); // a second subscriber per port
in_agt.ap.connect(fsb.in_fifo.analysis_export);
out_agt.ap.connect(fsb.out_fifo.analysis_export);
endfunction
endclass
// Both scoreboards side by side on the smoke test, plus the connection reports.
class dma_fifo_sb_test extends dma_smoke_test;
`uvm_component_utils(dma_fifo_sb_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_fifo_env::get_type());
super.build_phase(phase);
endfunction
function void end_of_elaboration_phase(uvm_phase phase);
dma_fifo_env fenv;
$cast(fenv, env);
env.in_agt.ap.debug_connected_to(); // fan-out of the publisher
fenv.fsb.in_fifo.analysis_export.debug_provided_to(); // fan-in of one subscriber
endfunction
endclass
// The FIFO scoreboard with an 80 ns compare: the run ends with words still queued.
class dma_fifo_slow_test extends dma_smoke_test;
`uvm_component_utils(dma_fifo_slow_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_fifo_env::get_type());
super.build_phase(phase);
endfunction
function void connect_phase(uvm_phase phase);
dma_fifo_env fenv;
super.connect_phase(phase);
$cast(fenv, env);
fenv.fsb.compare_delay = 80ns;
endfunction
endclass
// The same slow compare, with the scoreboard holding the run phase until drained.
class dma_fifo_drain_test extends dma_fifo_slow_test;
`uvm_component_utils(dma_fifo_drain_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
dma_fifo_scoreboard::type_id::set_type_override(dma_fifo_scoreboard_eot::get_type());
super.build_phase(phase);
endfunction
endclass
// ---------------------------------------------------------------- TLM-2.0: generic payload over blocking sockets
class axi_attr_ext extends uvm_tlm_extension #(axi_attr_ext);
bit [3:0] id;
bit [2:0] prot; // prot[1] = non-secure
`uvm_object_utils(axi_attr_ext)
function new(string name = "axi_attr_ext");
super.new(name);
endfunction
function void do_copy(uvm_object rhs);
axi_attr_ext r;
super.do_copy(rhs);
$cast(r, rhs);
id = r.id; prot = r.prot;
endfunction
endclass
// A 256-byte memory model: an annotating target (it adds its latency to delay).
class gp_mem extends uvm_component;
`uvm_component_utils(gp_mem)
uvm_tlm_b_target_socket #(gp_mem) tsock; // IMP = gp_mem
byte unsigned mem[bit [63:0]];
function new(string name, uvm_component parent);
super.new(name, parent);
tsock = new("tsock", this);
endfunction
task b_transport(uvm_tlm_generic_payload gp, uvm_tlm_time delay);
byte unsigned data[], be[];
uvm_tlm_extension_base xb;
axi_attr_ext attr;
bit [63:0] a = gp.get_address();
int unsigned len = gp.get_data_length();
int unsigned bel = gp.get_byte_enable_length();
if (a + len > 64'h100) begin
gp.set_response_status(UVM_TLM_ADDRESS_ERROR_RESPONSE);
return; // nothing executed, no delay added
end
xb = gp.get_extension(axi_attr_ext::ID()); // null if the initiator attached none
if (xb != null && $cast(attr, xb) && attr.prot[1]) begin
gp.set_response_status(UVM_TLM_COMMAND_ERROR_RESPONSE); // non-secure access refused
return;
end
gp.get_data(data);
gp.get_byte_enable(be);
for (int i = 0; i < len; i++) begin
if (bel != 0 && be[i % bel] == 8'h00) continue; // disabled byte
if (gp.is_write()) mem[a + i] = data[i];
else data[i] = mem.exists(a + i) ? mem[a + i] : 8'h00;
end
if (gp.is_read()) gp.set_data(data);
delay.incr(10ns, 1ns); // annotate 10 ns of latency
gp.set_response_status(UVM_TLM_OK_RESPONSE);
endtask
endclass
// The initiator waits out the delay the target annotated.
class gp_init extends uvm_component;
`uvm_component_utils(gp_init)
uvm_tlm_b_initiator_socket #() isock; // T = uvm_tlm_generic_payload
byte unsigned buf_[]; // reused for every transfer
function new(string name, uvm_component parent);
super.new(name, parent);
isock = new("isock", this);
endfunction
task xfer(uvm_tlm_command_e cmd, bit [63:0] addr, byte unsigned data[],
byte unsigned be[] = '{}, axi_attr_ext attr = null,
output uvm_tlm_generic_payload gp_out);
uvm_tlm_generic_payload gp = uvm_tlm_generic_payload::type_id::create("gp");
uvm_tlm_time delay = new("delay"); // starts at 0
if (cmd == UVM_TLM_WRITE_COMMAND) buf_ = data;
gp.set_command(cmd);
gp.set_address(addr);
gp.set_data(buf_);
gp.set_data_length(buf_.size());
gp.set_byte_enable(be); // empty array: all bytes enabled
gp.set_byte_enable_length(be.size());
gp.set_streaming_width(buf_.size());
gp.set_response_status(UVM_TLM_INCOMPLETE_RESPONSE);
if (attr != null) void'(gp.set_extension(attr));
isock.b_transport(gp, delay);
#(delay.get_realtime(1ns)); // spend the annotated time once
gp.get_data(buf_);
`uvm_info("GP", {gp.convert2string(), " -> ", gp.get_response_string()}, UVM_LOW)
gp_out = gp;
endtask
task run_phase(uvm_phase phase);
uvm_tlm_generic_payload gp, cl;
uvm_tlm_extension_base xb;
axi_attr_ext attr, cattr;
phase.raise_objection(this);
xfer(UVM_TLM_WRITE_COMMAND, 64'h10, '{8'h11, 8'h22, 8'h33, 8'h44}, '{}, null, gp);
xfer(UVM_TLM_WRITE_COMMAND, 64'h10, '{8'haa, 8'hbb, 8'hcc, 8'hdd}, '{8'hff, 8'hff, 8'h00, 8'h00}, null, gp);
xfer(UVM_TLM_READ_COMMAND, 64'h10, '{}, '{}, null, gp);
`uvm_info("GP", $sformatf("read back %02h %02h %02h %02h", buf_[0], buf_[1], buf_[2], buf_[3]), UVM_LOW)
xfer(UVM_TLM_READ_COMMAND, 64'hfe, '{}, '{}, null, gp);
attr = axi_attr_ext::type_id::create("attr");
attr.id = 4'h3; attr.prot = 3'b010;
xfer(UVM_TLM_WRITE_COMMAND, 64'h20, '{8'haa, 8'hbb, 8'hcc, 8'hdd}, '{}, attr, gp);
$cast(cl, gp.clone());
xb = cl.get_extension(axi_attr_ext::ID());
void'($cast(cattr, xb));
`uvm_info("GP", $sformatf("clone has %0d extension(s), prot=%b, same object=%0d",
cl.get_num_extensions(), cattr.prot[1:0], cattr == attr), UVM_LOW)
phase.drop_objection(this);
endtask
endclass
class tlm2_gp_test extends uvm_test;
`uvm_component_utils(tlm2_gp_test)
gp_init ini;
gp_mem mem;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
ini = gp_init::type_id::create("ini", this);
mem = gp_mem::type_id::create("mem", this);
endfunction
function void connect_phase(uvm_phase phase);
ini.isock.connect(mem.tsock); // the initiator side calls connect()
endfunction
endclass
endpackage
module top;
import uvm_pkg::*;
import dma_pkg::*;
import tlm_demo_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);
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 rst_if)::set(null, "uvm_test_top*", "rst_vif", rst);
run_test("tlm_deaf_test");
end
endmodule