Intermediate UVM

Agents, drivers and monitors, TLM ports, scoreboards, and functional coverage.

01 · The agent

Driver + monitor + sequencer, bundled

An agent is a UVM component that wraps everything needed to interact with one interface of the DUT: a driver, a monitor, and (if active) a sequencer.

axi_agent.svAGENT
class axi_agent extends uvm_agent;
  `uvm_component_utils(axi_agent)

  axi_driver    drv;
  axi_monitor   mon;
  axi_sequencer seqr;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    mon = axi_monitor::type_id::create("mon", this);
    if (get_is_active() == UVM_ACTIVE) begin
      drv  = axi_driver::type_id::create("drv", this);
      seqr = axi_sequencer::type_id::create("seqr", this);
    end
  endfunction

  function void connect_phase(uvm_phase phase);
    if (get_is_active() == UVM_ACTIVE)
      drv.seq_item_port.connect(seqr.seq_item_export);
  endfunction
endclass

Active vs passive

An active agent drives the interface. A passive agent only monitors. When you instantiate the same DUT's interface in two places (e.g., master and slave), one agent is active, the other passive — but both contribute to coverage.

02 · Driver and monitor

Where pins meet transactions

The driver consumes sequence items from the sequencer and drives the interface. The monitor samples the interface and publishes observed transactions via an analysis port.

driver.svDRIVER
class axi_driver extends uvm_driver#(axi_txn);
  `uvm_component_utils(axi_driver)
  virtual axi_lite_if vif;

  task run_phase(uvm_phase phase);
    forever begin
      seq_item_port.get_next_item(req);
      drive_txn(req);
      seq_item_port.item_done();
    end
  endtask

  task drive_txn(axi_txn t);
    @(vif.cb_m);
    vif.cb_m.awaddr  <= t.addr;
    vif.cb_m.awvalid <= 1;
    wait (vif.cb_m.awready);
    @(vif.cb_m);
    vif.cb_m.awvalid <= 0;
    // … W and B channels
  endtask
endclass
Monitor ≠ driver inverse
The monitor must work even when the DUT is the source of the transaction. Sample the interface passively via clocking blocks — never reach into the driver's state.
03 · TLM ports

Analysis ports and the broadcast pattern

The monitor talks to scoreboards, coverage collectors, and predictors through an analysis port. An analysis port supports many subscribers — just call write() once and every connected subscriber gets a copy.

monitor.svANALYSIS PORT
class axi_monitor extends uvm_monitor;
  `uvm_component_utils(axi_monitor)

  uvm_analysis_port#(axi_txn) ap;
  virtual axi_lite_if vif;

  function void build_phase(uvm_phase phase);
    ap = new("ap", this);
  endfunction

  task run_phase(uvm_phase phase);
    forever begin
      axi_txn t;
      collect_txn(t);
      ap.write(t);      // fan out to every subscriber
    end
  endtask
endclass

Receiving analysis writes

Subscribers receive via uvm_analysis_imp. When a component needs to receive from multiple analysis ports, use `uvm_analysis_imp_decl(_suffix) to create distinct write_suffix methods.

04 · Scoreboard

Self-checking at the transaction level

A scoreboard is the component that decides if the DUT is correct. It receives transactions from the monitor, runs them through a reference model, and compares against DUT responses.

scoreboard.svSCOREBOARD
`uvm_analysis_imp_decl(_input)
`uvm_analysis_imp_decl(_output)

class axi_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(axi_scoreboard)

  uvm_analysis_imp_input#(axi_txn, axi_scoreboard) in_imp;
  uvm_analysis_imp_output#(axi_txn, axi_scoreboard) out_imp;

  axi_txn expected[$];

  function void write_input(axi_txn t);
    expected.push_back(predict(t));
  endfunction

  function void write_output(axi_txn t);
    axi_txn exp = expected.pop_front();
    if (!exp.compare(t))
      `uvm_error("SB", $sformatf("mismatch exp=%s got=%s",
                                exp.sprint(), t.sprint()))
  endfunction
endclass
05 · Functional coverage in UVM

Subscriber pattern for coverage

A coverage collector is a uvm_subscriber that listens to an analysis port and samples covergroups. Keep it separate from the scoreboard — one is about what passed, the other is about what you exercised.

coverage.svSUBSCRIBER
class axi_coverage extends uvm_subscriber#(axi_txn);
  `uvm_component_utils(axi_coverage)

  axi_txn t;

  covergroup cg;
    option.per_instance = 1;
    addr_cp : coverpoint t.addr {
      bins low  = {[0:'hFFF]};
      bins high = {['h1000:$]};
    }
    resp_cp : coverpoint t.resp;
    cross_ar : cross addr_cp, resp_cp;
  endgroup

  function void write(axi_txn tr);
    t = tr;
    cg.sample();
  endfunction
endclass