UVM Monitor

The UVM monitor is a passive component in a UVM testbench. Its primary responsibility is to observe activity on the interface connected to the Design Under Verification (DUV) and convert it into transaction-level data (sequence items). Unlike the driver, the monitor does not actively drive any signals on the interface. It simply observes and collects data.

Diagram of UVM Monitor

+-----------------------+
|   UVM Environment     |
|                       |
|  +------------------+ |
|  |       Agent      | |
|  | +--------------+ | |
|  | |    Driver    | | |
|  | +--------------+ | |
|  | +--------------+ | |
|  | |    Monitor   | | |
|  | +--------------+ | |
|  | +--------------+ | |
|  | |  Sequencer   | | |
|  | +--------------+ | |
|  +------------------+ |
|                       |
|  +------------------+ |
|  |   Analysis Port  | |
|  +------------------+ |
+-----------------------+
          |
          v
+----------------------+
|        DUT           |
+----------------------+

 

Why Use a Dedicated Monitor?

  • Separation of Concerns: Decouples observation logic from stimulus generation (handled by the driver and sequence). This makes the testbench more modular and maintainable.
  • Reusability: Monitors can be reused across different tests and even different projects, as long as the interface remains the same.
  • Protocol Checking: Monitors can perform protocol checks to ensure that the DUV is behaving correctly.
  • Data Collection for Coverage and Analysis: Monitors collect transaction data that can be used for functional coverage analysis, debugging, and performance analysis.

Key Concepts of UVM Monitors

  1. Monitor Class (uvm_monitor): A base class for creating monitor components.
  2. Transaction Collection: Monitors convert signal activities into transactions.
  3. Passive Observation: Monitors do not influence the DUT; they only observe.
  4. Data Reporting: Monitors report collected transactions to analysis ports connected to other components.

Structure of a UVM Monitor:

A UVM monitor is a class that extends uvm_monitor. It typically contains the following key elements:

  1. `uvm_component_utilsĀ  Macro: This macro is essential for registering the monitor with the UVM factory and enabling other UVM features.
  2. Interface Handle: A handle to the interface connected to the DUV.
  3. Analysis Ports: One or more uvm_analysis_port instances used to broadcast collected transactions to other components (e.g., scoreboards, coverage collectors).
  4. new() Constructor: The constructor is used to create an instance of the monitor.
  5. build_phase(): The build_phase is used to get the virtual interface from the configuration database.
  6. run_phase(): This phase is the main execution phase of the monitor. It’s where the monitor observes the interface and collects data.
  7. collect_transaction() (User-Defined): A task (or function) that captures the relevant data from the interface and creates a transaction (sequence item).

Diagram 1: Monitor in the Testbench Architecture

     DUV ---->| Monitor |---->| Scoreboard/ |---->| Coverage |
              +---------+     | Coverage   |     | Collector|
                              | Collector  |     +----------+
                              +------------+

Example Code:

`include "uvm_macros.svh"

class my_transaction extends uvm_sequence_item; // (From previous examples)
    rand bit [7:0] address;
    rand bit [31:0] data;
    rand bit write_enable;
    `uvm_object_utils(my_transaction)
    // ... (rest of the transaction class)
endclass

class my_monitor extends uvm_monitor;
    `uvm_component_utils(my_monitor)

    virtual my_interface vif; // Interface handle
    uvm_analysis_port#(my_transaction) item_collected_port;

    function new(string name = "my_monitor", uvm_component parent = null);
        super.new(name, parent);
        item_collected_port = new("item_collected_port", this);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if(!uvm_config_db#(virtual my_interface)::get(this, "", "vif", vif))
            `uvm_fatal("CFG", "Failed to get vif from config_db");
    endfunction

    task run_phase(uvm_phase phase);
        forever begin
            @(posedge vif.clk); // Example clocking
            if (vif.valid) begin
                my_transaction trans = my_transaction::type_id::create("trans");
                collect_transaction(trans);
                item_collected_port.write(trans); // Broadcast the transaction
            end
        end
    endtask

    task collect_transaction(my_transaction trans);
        trans.address = vif.address;
        trans.data = vif.data;
        trans.write_enable = vif.write_enable;
    endtask
endclass

Diagram 2: Monitor Class Structure

+-----------------+
| my_monitor      |
+-----------------+
| `uvm_component_|
|  utils(my_mon)  |
| vif             |
| item_collected_|
|  port           |
| new()           |
| build_phase()   |
| run_phase()     |
| collect_trans()|
+-----------------+

Explanation:

  1. The monitor declares an uvm_analysis_port to broadcast collected transactions.
  2. In the run_phase, the monitor continuously observes the interface, triggered by a clock edge (in this example).
  3. When a valid transaction is detected on the interface, the collect_transaction() task is called to capture the relevant data.
  4. The collected transaction is then broadcast to other components using item_collected_port.write().

Defining a UVM Monitor

1. Monitor Class

The monitor class is derived from uvm_monitor. It implements the functionality to sample the interface signals and convert them into transactions.

class my_monitor extends uvm_monitor;
  `uvm_component_utils(my_monitor)

  virtual my_interface vif;
  uvm_analysis_port#(my_transaction) ap;

  function new(string name = "my_monitor", uvm_component parent = null);
    super.new(name, parent);
    ap = new("ap", this);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(virtual my_interface)::get(this, "", "vif", vif))
      `uvm_fatal("NOVIF", "Virtual interface not defined! Simulation aborted.")
  endfunction

  task run_phase(uvm_phase phase);
    my_transaction tx;
    forever begin
      @(posedge vif.clk);
      tx = my_transaction::type_id::create("tx");
      // Sample interface signals and assign to transaction fields
      tx.addr = vif.addr;
      tx.data = vif.data;
      tx.write_enable = vif.we;
      ap.write(tx); // Send transaction to analysis port
    end
  endtask
endclass

Connecting the Monitor

2. Connecting the Monitor in the Environment

Monitors are typically instantiated and connected within an agent, which is part of the environment.

class my_agent extends uvm_agent;
  `uvm_component_utils(my_agent)

  my_monitor mon;
  my_driver drv;
  my_sequencer seqr;

  function new(string name = "my_agent", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    mon = my_monitor::type_id::create("mon", this);
    drv = my_driver::type_id::create("drv", this);
    seqr = my_sequencer::type_id::create("seqr", this);
  endfunction
endclass

Benefits of UVM Monitors

  • Passive Observation: Monitors observe and report on signal activities without influencing the DUT.
  • Data Collection: They convert raw signal activities into structured transactions.
  • Reusability: Monitors can be reused across different testbenches and projects.
  • Modularity: Clear separation of concerns, making the testbench modular and easier to manage.

Key Takeaways:

  • The monitor observes interface activity and converts it into transactions.
  • It uses uvm_analysis_port to broadcast collected transactions.
  • The collect_transaction() task implements the data capture logic.
  • Monitors promote modularity, reusability, and maintainability.
  • Monitors are passive components and do not drive any signals on the interface.