A candidate is asked to snapshot every transaction a monitor sees, so a later check can compare against it. They write snap = mon.clone();, meaning to copy the monitor's last item. The interviewer asks what that line returns. The answer is null, with an ILLCLN error in the log, because a monitor is not data and cannot be cloned. One line, and the interviewer knows whether you have the most basic split in UVM straight. What is that split, and what else follows from it?
Every UVM class sits on one side of a single fork. Components (uvm_component and everything below it) are the furniture of the testbench: built once at the start, each with a fixed place in a tree, taking part in every phase, and staying until the simulation ends. Objects (uvm_object and the classes that are not components) are what moves through the room: transactions, sequences and configuration bundles, created and discarded by the thousand, with no place in the tree and no phases.
Almost every rule you will meet follows from which side a class is on. Components get two-argument constructors, phase methods and a full path that config_db and instance overrides key on. Objects get one-argument constructors, are randomized and copied, and are reclaimed by garbage collection when nothing refers to them any more.
Two placements surprise people. A sequence is an object: it descends from uvm_sequence_item, so it has no phases and no tree slot, even though it runs for a long time. A register model (uvm_reg_block) is an object too, which is why the env builds it with create but never sees it in the topology print.
Here are an object and a component from the bench, side by side. The item's constructor takes only a name, with a default. The monitor's takes a name and a parent, with no defaults, because a component cannot exist without a place in the tree.
// snippet: from the course DMA bench
class apb_item extends uvm_sequence_item;
rand bit [7:0] addr;
rand bit write;
rand bit [31:0] data; // write data
bit [31:0] rdata; // filled by the driver on reads
bit slverr; // filled by the driver
constraint word_aligned_c { addr[1:0] == 2'b00; }
`uvm_object_utils(apb_item)
function new(string name = "apb_item");
super.new(name);
endfunction
function string convert2string();
return write ? $sformatf("WR addr=%02h data=%08h%s", addr, data, slverr ? " SLVERR" : "")
: $sformatf("RD addr=%02h rdata=%08h%s", addr, rdata, slverr ? " SLVERR" : "");
endfunction
function void do_copy(uvm_object rhs);
apb_item r;
super.do_copy(rhs);
$cast(r, rhs);
addr = r.addr; write = r.write; data = r.data; rdata = r.rdata; slverr = r.slverr;
endfunction
endclass// snippet: from the course DMA bench
class apb_monitor extends uvm_monitor;
`uvm_component_utils(apb_monitor)
virtual apb_if vif;
uvm_analysis_port #(apb_item) ap;
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 apb_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "apb_if not set for this monitor")
endfunction
// A transfer completes on the edge where PSEL, PENABLE and PREADY are all 1.
task run_phase(uvm_phase phase);
forever begin
@(vif.mon_cb);
if (vif.mon_cb.psel && vif.mon_cb.penable && vif.mon_cb.pready) begin
apb_item it = apb_item::type_id::create("it"); // a fresh object per transfer
it.addr = vif.mon_cb.paddr;
it.write = vif.mon_cb.pwrite;
it.data = vif.mon_cb.pwdata;
it.rdata = vif.mon_cb.prdata;
it.slverr = vif.mon_cb.pslverr;
`uvm_info("APB_MON", it.convert2string(), UVM_HIGH)
ap.write(it);
end
end
endtask
endclassThe item registers with uvm_object_utils, carries random fields, and is created fresh for every transfer. Its do_copy and convert2string are the subject of the next lesson.
The monitor registers with uvm_component_utils, overrides build_phase and run_phase, and lives for the whole simulation. Its full path, uvm_test_top.env.apb.mon, is what the top module's config_db path matches.
Inside the monitor, apb_item::type_id::create("it") creates an object with one argument. Objects can be given a parent context for instance overrides, but they never get a tree slot.
Knob bundles are objects. The bench's dma_job_cfg describes one DMA job; a test randomizes it or fetches one from config_db, and nothing about it needs phases.
// snippet: from the course DMA bench
// One DMA job as a randomizable object. A test can randomize it, or put one in
// config_db so a test further down the hierarchy picks it up.
class dma_job_cfg extends uvm_object;
rand bit [31:0] key;
rand int unsigned len;
rand bit irq_en;
constraint len_c { len inside {[1:16]}; }
constraint irq_c { soft irq_en == 1; }
`uvm_object_utils(dma_job_cfg)
function new(string name = "dma_job_cfg");
super.new(name);
endfunction
function string convert2string();
return $sformatf("key=%08h len=%0d irq_en=%0b", key, len, irq_en);
endfunction
endclassYou try to | What happens | Why |
|---|---|---|
|
| A copy would need a second place in the tree with the same name |
Call |
| Components are built through |
Create a component after build_phase has ended |
| The tree is frozen; connect and run phases assume it is complete |
Give an object a phase method | It compiles and is never called | Only components are visited by the phase scheduler |
In the topology the base test prints (shown in What UVM standardizes), every row is a component or a port: env, agents, drivers, monitors, sequencers, the scoreboard. No apb_item, no axis_pkt_seq, no dma_job_cfg appears, even though thousands of items are created during the run. That absence is the fork made visible. The run's own log also tells you when the objects existed: the [JOB] lines from dma_rand_test each print a fresh dma_job_cfg, created, randomized and dropped inside one loop iteration.
UVM_INFO dma_tb.sv(731) @ 0: uvm_test_top [JOB] key=90fdd8fd len=10 irq_en=1
UVM_INFO dma_tb.sv(731) @ 645000: uvm_test_top [JOB] key=0e564b57 len=1 irq_en=1
UVM_INFO dma_tb.sv(731) @ 885000: uvm_test_top [JOB] key=5a1793f4 len=3 irq_en=1The values are random and differ by simulator and seed. The reporter is uvm_test_top, a component: objects that need to print usually do it through the component that owns them, or through uvm_info with the global reporter.
This bug shows up in nearly every team's first UVM bench, because it is the fork in disguise. A monitor author decides that creating a new item per transfer is wasteful, creates one item in the constructor, and refills its fields on every transfer before calling ap.write(it). The scoreboard pushes each received handle onto a queue. Every compare then fails in the same strange way: all the queued "expected" entries hold the values of the latest transfer.
The root cause is treating an object as if it were furniture. There was only ever one object, and the queue held many references to it. The fix is what the bench's monitors do, a fresh create for every transfer, or a clone before storing. The deeper rule is that a component may live forever, but the objects it hands out must be safe to keep: once you broadcast an object, assume someone stores it.
uvm_reg_block) a component or an object, and what follows from the answer?An object. It has no phases and no tree slot, so it is built with create and then explicitly build()-ed and locked by the env, and it does not appear in the topology print. Components that use it (the predictor, sequences) get a handle to it, usually through the env or config_db.
No. A component cannot be cloned (ILLCLN), and a copy would need its own place in the tree anyway. The test registers a factory override before the env builds, so the env creates the derived scoreboard in its place, exactly as dma_drain_test does with dma_scoreboard_eot.
Yes, if they store the same handle: an analysis port passes a reference, not a copy, so both scoreboards hold the same object. A subscriber that intends to modify what it receives should clone it first. The monitor's job is to send a fresh object per transaction; it cannot protect subscribers from each other.
Next: if objects are copied, compared and printed constantly, how should those methods be written? Objects that copy, compare and print compares field macros with hand-written methods.