A UVM environment is a hierarchy of components — test at the top, env below it containing agents, each agent wrapping a driver, sequencer, and monitor, with scoreboards and coverage tied in via analysis ports. Understanding how these pieces connect is the foundation of all UVM work.
TLM (Transaction Level Modeling) ports provide type-safe, direction-enforced connections between components. The main TLM channels in UVM:
| Port Type | Direction | Use Case |
|---|---|---|
uvm_analysis_port | Producer → 0..N consumers | Monitor broadcasts observed transactions |
uvm_analysis_imp | Consumer side | Scoreboard/coverage receives from analysis_port |
uvm_seq_item_pull_port | Driver → Sequencer | Driver pulls items from sequencer |
uvm_seq_item_pull_export | Sequencer side | Sequencer export connected to driver port |
uvm_tlm_analysis_fifo | Buffered FIFO | Decouples monitor write speed from scoreboard read speed |
class my_agent extends uvm_agent; `uvm_component_utils(my_agent) // Components — only created in active mode my_driver drv; my_sequencer sqr; my_monitor mon; // Analysis port — forwards monitor output to env uvm_analysis_port #(my_item) ap; function new(string name, uvm_component parent); super.new(name, parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); // Always create monitor (passive and active mode) mon = my_monitor::type_id::create("mon", this); // Only create driver+sequencer in ACTIVE mode if (get_is_active() == UVM_ACTIVE) begin drv = my_driver::type_id::create("drv", this); sqr = my_sequencer::type_id::create("sqr", this); end ap = new("ap", this); endfunction virtual function void connect_phase(uvm_phase phase); // Connect monitor ap to agent ap (agent re-exports it) mon.ap.connect(ap); // Connect driver pull port to sequencer export if (get_is_active() == UVM_ACTIVE) drv.seq_item_port.connect(sqr.seq_item_export); endfunction endclass
class my_env extends uvm_env; `uvm_component_utils(my_env) my_agent agent; my_scoreboard scb; my_coverage cov; function new(string name, uvm_component parent); super.new(name, parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); agent = my_agent::type_id::create("agent", this); scb = my_scoreboard::type_id::create("scb", this); cov = my_coverage::type_id::create("cov", this); endfunction virtual function void connect_phase(uvm_phase phase); // Monitor's analysis port feeds scoreboard AND coverage agent.ap.connect(scb.analysis_export); agent.ap.connect(cov.analysis_export); endfunction endclass
class base_test extends uvm_test; `uvm_component_utils(base_test) my_env env; function new(string name, uvm_component parent); super.new(name, parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); // Pass virtual interface to agent driver and monitor uvm_config_db #(virtual my_if)::set(this, "env.agent.*", "vif", vif); env = my_env::type_id::create("env", this); endfunction virtual task run_phase(uvm_phase phase); my_sequence seq; phase.raise_objection(this); seq = my_sequence::type_id::create("seq"); seq.start(env.agent.sqr); phase.drop_objection(this); endtask endclass
// Minimal driver skeleton class my_driver extends uvm_driver #(my_item); `uvm_component_utils(my_driver) virtual my_if vif; function new(string name, uvm_component parent); super.new(name, parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); if (!uvm_config_db #(virtual my_if)::get(this, "", "vif", vif)) `uvm_fatal("DRV", "No virtual interface found") endfunction virtual task run_phase(uvm_phase phase); my_item item; forever begin seq_item_port.get_next_item(item); drive_item(item); seq_item_port.item_done(); end endtask task drive_item(my_item item); @(vif.cb); vif.cb.valid <= 1; vif.cb.addr <= item.addr; @(vif.cb); vif.cb.valid <= 0; endtask endclass // Minimal monitor skeleton class my_monitor extends uvm_monitor; `uvm_component_utils(my_monitor) virtual my_if vif; uvm_analysis_port #(my_item) ap; function new(string name, uvm_component parent); super.new(name, parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); ap = new("ap", this); if (!uvm_config_db #(virtual my_if)::get(this, "", "vif", vif)) `uvm_fatal("MON", "No virtual interface found") endfunction virtual task run_phase(uvm_phase phase); my_item item; forever begin @(vif.cb); if (vif.cb.valid) begin item = my_item::type_id::create("item"); item.addr = vif.cb.addr; ap.write(item); // broadcast to all subscribers end end endtask endclass
// In scoreboard — implements uvm_analysis_imp class my_scoreboard extends uvm_scoreboard; `uvm_component_utils(my_scoreboard) // analysis_imp implements write() — called by analysis_port uvm_analysis_imp #(my_item, my_scoreboard) analysis_export; function new(string name, uvm_component parent); super.new(name, parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); analysis_export = new("analysis_export", this); endfunction // Called automatically each time monitor calls ap.write() virtual function void write(my_item item); `uvm_info("SCB", $sformatf("Received: addr=%0h", item.addr), UVM_HIGH) // compare against expected model here endfunction endclass // Using TLM FIFO to buffer between monitor and scoreboard // In env connect_phase: uvm_tlm_analysis_fifo #(my_item) fifo; // build: fifo = new("fifo", this); // connect: agent.ap.connect(fifo.analysis_export); // scb.get_port.connect(fifo.get_export);
// One class per file, file name matches class name: // my_item.sv → class my_item extends uvm_sequence_item // my_sequence.sv → class my_sequence extends uvm_sequence // my_sequencer.sv → typedef uvm_sequencer #(my_item) my_sequencer; // my_driver.sv → class my_driver extends uvm_driver // my_monitor.sv → class my_monitor extends uvm_monitor // my_agent.sv → class my_agent extends uvm_agent // my_scoreboard.sv → class my_scoreboard extends uvm_scoreboard // my_env.sv → class my_env extends uvm_env // my_test.sv → class my_test extends uvm_test // Package file — include all in order (items before users): package my_tb_pkg; `include "uvm_macros.svh" import uvm_pkg::*; `include "my_item.sv" `include "my_sequence.sv" `include "my_sequencer.sv" `include "my_driver.sv" `include "my_monitor.sv" `include "my_agent.sv" `include "my_scoreboard.sv" `include "my_env.sv" `include "my_test.sv" endpackage