UVM Monitor Study
In-Depth Analysis of the UVM Monitor Component & Practical Example
1. What is a UVM Monitor?
The UVM Monitor is a critical component in the verification environment responsible for observing the DUT (Device Under Test) interface. Unlike the Driver, the Monitor does not drive signals; it only samples them.
Key Responsibilities:
- Observation: Continuously sampling the physical interface signals (bus transactions, protocols).
- Decoding: Converting raw hardware signals (e.g.,
valid,ready,data) into high-level UVM Transactions (class objects). - Broadcasting: Sending the decoded transactions to other components (Scoreboard, Coverage) via Analysis Ports.
2. Monitor Architecture & Communication
2.1 Internal Structure
A typical UVM Monitor class inherits from uvm_monitor and contains:
- Virtual Interface: Access to the actual DUT pins/signals.
- Analysis Port: A
uvm_analysis_port#(T)to broadcast transactions. - Sampling Logic: Code (usually in
run_phase) that detects transaction boundaries and constructs objects.
2.2 Connection Pattern (TLM Analysis)
The Monitor uses a Broadcast/Subscribe pattern:
- Monitor creates a Transaction Object (
my_txn). - Monitor calls
analysis_port.write(txn). - All connected subscribers (Scoreboard, Coverage) receive a copy of the transaction.
Why use Analysis Ports?
- Decoupling: Monitor doesn't need to know who is listening.
- Scalability: Can add new scoreboards or coverage models without changing the monitor.
- Non-blocking: The write operation is non-blocking, ensuring the monitor continues sampling without waiting.
3. Monitor Lifecycle & Phases
The Monitor interacts with the UVM phase machine as follows:
| Phase | Monitor Action |
|---|---|
build_phase |
|
connect_phase |
Usually empty, unless connecting internal sub-ports. External connections are made in the Agent/Env. |
run_phase |
|
fork...join_none inside run_phase if you need to handle multiple independent protocol lanes or if you want to sample in the background while doing other checks.
4. Practical Example: Simple Bus Monitor
This example demonstrates a monitor for a simple 32-bit bus with valid, ready, addr, and data signals.
4.1 Interface Definition
// bus_if.sv
interface bus_if(input logic clk, input logic rst_n);
logic valid;
logic ready;
logic [31:0] addr;
logic [31:0] data;
modport dut (
output valid, addr, data,
input ready,
input clk,
input rst_n
);
modport mon (
input valid, ready, addr, data,
input clk,
input rst_n
);
endinterface
4.2 Transaction Class
// bus_txn.sv
class bus_txn extends uvm_sequence_item;
rand bit [31:0] addr;
rand bit [31:0] data;
int time_stamp;
`uvm_object_utils_begin(bus_txn)
`uvm_field_int(addr, UVM_ALL_ON | UVM_HEX)
`uvm_field_int(data, UVM_ALL_ON | UVM_HEX)
`uvm_field_int(time_stamp, UVM_ALL_ON)
`uvm_object_utils_end
function new(string name = "bus_txn");
super.new(name);
endfunction
function void convert2string(output string s);
$sformat(s, "Time: %0t | Addr: %h | Data: %h", time_stamp, addr, data);
endfunction
endclass
4.3 The UVM Monitor Class
// bus_monitor.sv
class bus_monitor extends uvm_monitor;
// 1. Virtual Interface
virtual bus_if.mon vif;
// 2. Analysis Port for broadcasting transactions
uvm_analysis_port#(bus_txn) ap;
// 3. Factory Macro
`uvm_component_utils(bus_monitor)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
// --- Build Phase ---
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Create Analysis Port
ap = new("ap", this);
// Get Virtual Interface from Config DB
if (!uvm_config_db#(virtual bus_if.mon)::get(this, "", "vif", vif)) begin
`uvm_fatal(get_type_name(), "Failed to get bus virtual interface")
end
endfunction
// --- Run Phase ---
task run_phase(uvm_phase phase);
super.run_phase(phase);
// Wait for reset to be de-asserted
wait(!vif.rst_n);
@(posedge vif.clk);
wait(vif.rst_n);
@(posedge vif.clk);
`uvm_info(get_type_name(), "Monitor started", UVM_MEDIUM)
// Continuous Monitoring Loop
forever begin
wait_txn_start();
bus_txn t;
t = bus_txn::type_id::create("mon_txn");
// 1. Decode/Extract Data
sample_data(t);
// 2. Set Timestamp
t.time_stamp = $time;
// 3. Log (Optional, for debugging)
`uvm_info(get_type_name(), $sformatf("Captured: %s", t.convert2string()), UVM_HIGH)
// 4. Broadcast via Analysis Port
ap.write(t);
end
endtask
// Helper Task: Wait for a valid transaction start
task wait_txn_start();
// Wait for valid to be high
@(posedge vif.valid);
// Wait for clock edge to stabilize
@(posedge vif.clk);
endtask
// Helper Task: Sample the data
task sample_data(bus_txn t);
// In a real scenario, you might check 'ready' here
// or wait until 'ready' is also high if back-to-back is not allowed.
// Here we assume sampling on 'valid' high.
t.addr = vif.addr;
t.data = vif.data;
endtask
endclass
5. Integrating the Monitor into an Agent
The Monitor is typically instantiated inside a uvm_agent. Below is how the agent sets up the interface and connects the monitor's analysis port to the environment.
// bus_agent.sv
class bus_agent extends uvm_agent;
bus_monitor mon;
// ... driver/sequencer if active ...
`uvm_component_utils(bus_agent)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Create Monitor (always active, regardless of is_active)
mon = bus_monitor::type_id::create("mon", this);
// Set Virtual Interface to Monitor
// The 'this' scope ensures only this agent's monitor gets it
uvm_config_db#(virtual bus_if.mon)::set(this, "mon", "vif", vif);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// Connection of ap is usually done in the ENV or SCOREBOARD
// because the agent doesn't know who the subscribers are.
endfunction
endclass
In the Environment (bus_env.sv):
class bus_env extends uvm_env;
bus_agent agent;
bus_scoreboard scoreboard;
// ... coverage ...
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// Connect Monitor's Analysis Port to Scoreboard's Export
agent.mon.ap.connect(scoreboard.analysis_export);
endfunction
endclass
6. Advanced Monitor Topics
6.1 Handling Back-to-Back Transactions
If the protocol allows consecutive transactions without a gap, you must ensure the monitor does not miss the start of the next one.
- Always re-check the start condition in the
foreverloop. - Do not add unnecessary delays after
ap.write().
6.2 Error Checking in Monitor
Monitors can also check for protocol violations.
// Inside run_phase or sample_data
if (vif.valid && !vif.ready && vif.misaligned_addr) begin
`uvm_error(get_type_name(), "Protocol Violation: Misaligned address while Valid=1")
end
6.3 Passive vs. Active Monitors
In UVM, all monitors are technically "passive" in terms of driving signals. However, you can have:
- Standard Monitor: Observes only the interface.
- Protocol Checker: Observes interface AND validates protocol rules (often done inside the monitor).
7. Common Pitfalls & Best Practices
| Pitfall | Solution |
|---|---|
Blocking in run_phase due to bad waits |
Ensure wait conditions are met eventually. Use timeouts if necessary for robustness. |
Forgetting to call ap.write() |
Always broadcast the transaction. If ap is null, the monitor will crash. |
| Directly driving signals in Monitor | Never assign values to DUT interface signals in a monitor. It breaks simulation integrity. |
| Incorrect virtual interface type | Ensure the modport direction in the interface matches what the monitor expects (usually input for all signals). |
| Sampling on wrong clock edge | Align sampling with the DUT's sampling clock (usually posedge). |
8. Summary
- The UVM Monitor observes the DUT and converts signals into objects.
- It uses a Virtual Interface to access the bus and an Analysis Port to broadcast data.
- It operates primarily in the run_phase with a continuous sampling loop.
- It must be passive (non-interfering) to ensure simulation accuracy.
- Proper integration requires setting the VIF in the Agent and connecting the Analysis Port in the Environment.