Drivers & Monitors
Implement UVM drivers that convert transactions to pin-level signals, and monitors that observe the interface.
Drivers & Monitors
Drivers and monitors are the bridge between the transaction-level UVM world and the pin-level RTL world.
The Driver
The driver takes sequence items from the sequencer and drives them onto the DUT's interface.
systemverilogclass apb_driver extends uvm_driver #(apb_txn); `uvm_component_utils(apb_driver) virtual apb_if vif; // Handle to the DUT interface function new(string name, uvm_component parent); super.new(name, parent); 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", "Virtual interface not found") endfunction task run_phase(uvm_phase phase); apb_txn req; // Initialize all signals to idle vif.PSEL <= 0; vif.PENABLE <= 0; vif.PWRITE <= 0; forever begin seq_item_port.get_next_item(req); drive_transfer(req); seq_item_port.item_done(); end endtask task drive_transfer(apb_txn txn); // ── SETUP Phase ── @(posedge vif.PCLK); vif.PSEL <= 1; vif.PADDR <= txn.addr; vif.PWRITE <= txn.write; if (txn.write) vif.PWDATA <= txn.data; // ── ACCESS Phase ── @(posedge vif.PCLK); vif.PENABLE <= 1; // Wait for slave ready do @(posedge vif.PCLK); while (!vif.PREADY); // Capture read data if (!txn.write) txn.data = vif.PRDATA; // Return to idle vif.PSEL <= 0; vif.PENABLE <= 0; endtask endclass
The Monitor
The monitor passively observes the interface. It never drives signals. It collects transactions and broadcasts them via an analysis port.
systemverilogclass apb_monitor extends uvm_monitor; `uvm_component_utils(apb_monitor) virtual apb_if vif; uvm_analysis_port #(apb_txn) ap; // Broadcast port function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); ap = new("ap", this); if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif)) `uvm_fatal("NOVIF", "Virtual interface not found") endfunction task run_phase(uvm_phase phase); apb_txn txn; forever begin txn = apb_txn::type_id::create("txn"); // Wait for a transaction to start @(posedge vif.PCLK); wait(vif.PSEL); // Capture address and direction txn.addr = vif.PADDR; txn.write = vif.PWRITE; if (vif.PWRITE) txn.data = vif.PWDATA; // Wait for completion @(posedge vif.PCLK); wait(vif.PENABLE && vif.PREADY); if (!vif.PWRITE) txn.data = vif.PRDATA; // Broadcast to all subscribers ap.write(txn); `uvm_info("MON", txn.convert2string(), UVM_HIGH) end endtask endclass
Driver vs Monitor — Comparison
| Aspect | Driver | Monitor |
|---|---|---|
| Direction | Drives signals to DUT | Reads signals from DUT |
| Active/Passive | Only in active agents | In all agents |
| Base class | uvm_driver | uvm_monitor |
| Communication | Gets items from sequencer | Broadcasts via analysis port |
| Signal handling | Assigns (drives) | Samples (reads only) |
The Virtual Interface
Both driver and monitor access DUT signals through a virtual interface:
systemveriloginterface apb_if(input logic PCLK); logic PSEL; logic PENABLE; logic PWRITE; logic [31:0] PADDR; logic [31:0] PWDATA; logic [31:0] PRDATA; logic PREADY; endinterface
It's set in the top module and retrieved via uvm_config_db:
systemverilog// Top module: module tb_top; logic clk; apb_if vif(clk); initial begin uvm_config_db#(virtual apb_if)::set( null, "uvm_test_top.env.agent*", "vif", vif ); run_test(); end endmodule
Best Practices
- Deassert signals between transactions in the driver — prevents carry-over.
- Monitors never drive signals — they are passive observers only.
- Use analysis ports in monitors — enables multiple subscribers.
- Always check for the virtual interface —
\uvm_fatalif not found. - Handle reset gracefully — both driver and monitor should reset cleanly.