Oct 8, 2026

UVM : UVM_Monitor

UVM Monitor Study

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.
Golden Rule: A Monitor must never affect the DUT. It should be "invisible" to the DUT. If your monitor changes the simulation timing or DUT state, it is incorrectly implemented.

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:

  1. Monitor creates a Transaction Object (my_txn).
  2. Monitor calls analysis_port.write(txn).
  3. 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
  • Create the Analysis Port.
  • Retrieve the Virtual Interface from uvm_config_db.
connect_phase Usually empty, unless connecting internal sub-ports. External connections are made in the Agent/Env.
run_phase
  • Enter a continuous loop (or forever block).
  • Wait for transaction start signals (e.g., wait(vif.valid)).
  • Sample data.
  • Construct and configure the Transaction Object.
  • Call ap.write(txn).
Best Practice: Use 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 forever loop.
  • 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.

UVM Monitor Study Guide © 2023

SiteMap