TO Be coming

Showing posts with label UVM verification. Show all posts
Showing posts with label UVM verification. Show all posts

Oct 10, 2026

Verification Quiz - 6 ( UVM ScoreCard)

⬅ All Verification Quizzes

Ready for the Full Verification Challenge? ⚡

15 more topics, 150+ blitz questions, and the answers that actually get you hired. Your next UVM interview might depend on it.

🚀 Open the Main Quiz Hub Free • No sign-up • Built for UVM & SystemVerilog job seekers
📖 Read the Full UVM Scoreboard Tutorial Study the notes & code examples first, then take the quiz!
UVM SCOREBOARD •

15-Question Blitz

10 seconds per question. Read fast, answer faster!

UVM : UVM_ScoreBoard

UVM TUTORIAL SERIES

🎯 UVM Scoreboard Explained

Study Notes • Working Code • Animated Demo

🔍 What is a UVM Scoreboard?

The Scoreboard is the self-checking brain of a UVM testbench. It receives transactions from the monitor, compares DUT behavior against a golden reference (predicted) result, and declares PASS or FAIL.

📥
CollectReceives actual results from the Monitor via TLM analysis ports
🧠
PredictContains a reference model that computes expected results
⚖️
CompareChecks actual vs expected and logs MATCH / MISMATCH
🏁
VerdictDeclares overall pass/fail in report_phase
💡 Golden Rule: A scoreboard never drives stimulus — it only observes and checks. It is always a uvm_component, never a sequence item.

📡 Where does the Scoreboard sit?

Two data streams flow into the scoreboard — the expected results (from a predictor/reference model) and the actual results (from the monitor). Watch them converge:

🧠 Reference Modelexpected results
👀 Monitoractual results
🎯
SCOREBOARD
✓ PASS
✗ FAIL

🔌 How transactions reach the Scoreboard (TLM)

TLM ConstructDirectionUsed By
uvm_analysis_portBroadcasts (many subscribers)Monitor
uvm_analysis_impReceives — calls write()Scoreboard
uvm_analysis_exportPass-through for hierarchiesEnv / Subscribers
uvm_subscriberBase class with built-in impScoreboard / Coverage
⚡ Key point: write() in the scoreboard is a function (zero delay), called automatically every time the monitor publishes a transaction.

⚙️ Scoreboard Internals — Live Demo

Expected and actual transactions arrive, get paired up, and the comparator fires its verdict:

EXPECTED QUEUE (from Reference Model)
E1
E2
E3
⚖️ COMPARE
✓ MATCH / ✗ MISMATCH
ACTUAL STREAM (from Monitor)
A1
A2
A3

💻 Example 1: Self-Checking ALU Scoreboard

Step 1 — The Transaction

// alu_txn.sv
class alu_txn extends uvm_sequence_item;
  rand bit [7:0] a, b;
  rand bit [1:0] op;        // 00:ADD 01:SUB 10:AND 11:OR
       bit [7:0] result;    // captured from DUT

  `uvm_object_utils_begin(alu_txn)
    `uvm_field_int(a,      UVM_ALL_ON)
    `uvm_field_int(b,      UVM_ALL_ON)
    `uvm_field_int(op,     UVM_ALL_ON)
    `uvm_field_int(result, UVM_ALL_ON)
  `uvm_object_utils_end

  function new(string name = "alu_txn");
    super.new(name);
  endfunction
endclass

Step 2 — The Scoreboard (predictor + checker inside)

// alu_scoreboard.sv
class alu_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(alu_scoreboard)

  // TLM imp — receives every transaction from the monitor
  uvm_analysis_imp #(alu_txn, alu_scoreboard) mon_imp;

  int match_count    = 0;
  int mismatch_count = 0;

  function new(string name, uvm_component parent);
    super.new(name, parent);
    mon_imp = new("mon_imp", this); // 'this' → imp calls our write()
  endfunction

  // ---------- Reference Model (Predictor) ----------
  function bit [7:0] predict(alu_txn t);
    case (t.op)
      2'b00 : return t.a + t.b;      // ADD
      2'b01 : return t.a - t.b;      // SUB
      2'b10 : return t.a & t.b;      // AND
      2'b11 : return t.a | t.b;      // OR
    endcase
  endfunction

  // ---------- Checker ----------
  // Called AUTOMATICALLY for every monitor transaction
  function void write(alu_txn t);
    bit [7:0] exp = predict(t);

    if (t.result === exp) begin
      match_count++;
      `uvm_info("SCOREBOARD",
        $sformatf("MATCH   : a=%0d b=%0d → %0d", t.a, t.b, t.result), UVM_LOW)
    end
    else begin
      mismatch_count++;
      `uvm_error("SCOREBOARD",
        $sformatf("MISMATCH: expected=%0d  got=%0d", exp, t.result))
    end
  endfunction

  // ---------- Final Verdict ----------
  function void report_phase(uvm_phase phase);
    `uvm_info("SCOREBOARD", $sformatf("Matches=%0d | Mismatches=%0d",
              match_count, mismatch_count), UVM_LOW)
    if (mismatch_count == 0)
      `uvm_info("SCOREBOARD", "*** TEST PASSED ***", UVM_LOW)
    else
      `uvm_error("SCOREBOARD", "*** TEST FAILED ***")
  endfunction
endclass

Step 3 — Connect it in the Environment

// alu_env.sv
class alu_env extends uvm_env;
  alu_agent      agent;
  alu_scoreboard sb;
  `uvm_component_utils(alu_env)

  function void build_phase(uvm_phase phase);
    agent = alu_agent::type_id::create("agent", this);
    sb    = alu_scoreboard::type_id::create("sb",    this);
  endfunction

  function void connect_phase(uvm_phase phase);
    agent.monitor.ap.connect(sb.mon_imp);  // ◀ THE magic line!
  endfunction
endclass

💻 Example 2: Queue-Based Scoreboard (2 data streams)

When expected and actual results arrive from different sources, use uvm_analysis_imp_decl to create two imps with separate write() methods, and store expected results in a queue:

// Declare imps with unique write method names
`uvm_analysis_imp_decl(_exp)
`uvm_analysis_imp_decl(_act)

class fifo_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(fifo_scoreboard)

  uvm_analysis_imp_exp #(mem_txn, fifo_scoreboard) expected_imp;
  uvm_analysis_imp_act #(mem_txn, fifo_scoreboard) actual_imp;

  mem_txn exp_q[$];              // golden queue

  function new(string name, uvm_component parent);
    super.new(name, parent);
    expected_imp = new("expected_imp", this);
    actual_imp   = new("actual_imp",   this);
  endfunction

  // From the reference model — store expected
  function void write_exp(mem_txn t);
    exp_q.push_back(t);
  endfunction

  // From the monitor — pop & compare (in-order)
  function void write_act(mem_txn t);
    if (exp_q.size() == 0) begin
      `uvm_error("SB", "Actual arrived with empty expected queue!")
      return;
    end
    mem_txn e = exp_q.pop_front();
    if (e.data === t.data)
      `uvm_info("SB", "MATCH ✓", UVM_HIGH)
    else
      `uvm_error("SB", $sformatf("MISMATCH exp=%0h got=%0h", e.data, t.data))
  endfunction
endclass
🔄 Out-of-order responses? Replace the queue with an associative array keyed by transaction ID, and delete the entry on match instead of popping.

Alternative: uvm_subscriber shortcut

class my_scoreboard extends uvm_subscriber #(alu_txn);
  `uvm_component_utils(my_scoreboard)
  // imp is built-in — just implement write():
  function void write(alu_txn t);
    // checking logic here
  endfunction
endclass

✅ Key Takeaways

✔ Scoreboard = predict + compare + verdict
✔ Uses analysis TLM connections (monitor.ap → sb.imp)
✔ write() is a function — no time-consuming code inside
✔ Use === (4-state compare) to catch X/Z values
✔ Use queues for in-order, associative arrays for out-of-order
✔ Final PASS/FAIL verdict goes in report_phase / check_phase

🎤 Interview Quick-Fire

Q: uvm_analysis_port vs uvm_analysis_imp?
A: Port broadcasts transactions (monitor side); imp receives them and invokes the attached component's write() (scoreboard side).
Q: Why must write() be a function, not a task?
A: Analysis traffic is instantaneous broadcast — it cannot block or consume simulation time.
Q: How do you handle responses that return out of order?
A: Key an associative array with a unique transaction ID; look up and delete on arrival.
Q: What happens if two scoreboards connect to one analysis port?
A: Both receive every transaction — analysis ports support multiple subscribers.
Q: Where should the predictor live?
A: Either inside the scoreboard, or as a separate component feeding an "expected" analysis stream — the second scales better for complex designs.

⭐ If this helped you, share it with your verification team! ⭐

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

Oct 7, 2026

UVM : UVM_Component

UVM Component Study

UVM Component Study

A Comprehensive Guide to the Building Blocks of UVM Verification

1. What is a UVM Component?

A UVM component is the fundamental building block of a UVM testbench. It represents a structural element of the verification environment that has:

  • A lifecycle (managed by phases).
  • Access to the configuration database.
  • The ability to interact with other components via factory overrides and communication interfaces.
Key Rule: Components are structural and control elements. They do not carry data. Data is carried by UVM Objects (e.g., transactions, sequences).

2. UVM Component Hierarchy

The UVM class library provides several specialized component classes. The basic hierarchy looks like this:

uvm_component (abstract base) ├── uvm_env │ ├── uvm_scoreboard │ ├── uvm_coverage_collector │ └── ... ├── uvm_agent │ ├── uvm_sequencer │ ├── uvm_driver │ ├── uvm_monitor │ └── uvm_scoreboard (optional) ├── uvm_subscriber └── uvm_test

Core Component Classes

Class Purpose
uvm_component Base class for all UVM components. Abstract; cannot be instantiated directly.
uvm_env Container for agents, scoreboards, and other components. Represents the verification environment.
uvm_agent Encapsulates a self-contained verification IP (driver, sequencer, monitor, optional scoreboard).
uvm_sequencer Receives sequences and passes them to the driver.
uvm_driver Drives the DUT interface based on items from the sequencer.
uvm_monitor Monitors the DUT interface and broadcasts observed transactions.
uvm_scoreboard Compares expected vs. actual results.
uvm_test Top-level component that configures the environment and initiates the test.

3. UVM Component Lifecycle (Phases)

UVM components follow a strict phase-based execution model. The main phases are:

Phase Description
build_phase Instantiate and configure sub-components. Configure the configuration database.
connect_phase Connect components (e.g., connect analyzers to scoreboard).
end_of_elaboration_phase Final setup after all components are built and connected.
start_of_simulation_phase Pre-run setup (e.g., reset signals).
run_phase Main simulation phase. Can be forked into sub-phases (pre_reset, reset, post_reset, main, shutdown, etc.).
extract_phase Post-simulation analysis (e.g., collect coverage).
check_phase Verify final DUT state or results.
report_phase Generate final reports (e.g., number of mismatches).
final_phase Clean up (e.g., close files).

Phase Execution Order

  1. Top-down for build_phase and connect_phase.
  2. Bottom-up for end_of_elaboration_phase, start_of_simulation_phase, and all subsequent phases.
Important: The run_phase is concurrent. All components enter run_phase simultaneously and run until the test ends.

4. Key Features of UVM Components

4.1 Name and Parent

Every component has a unique name in the hierarchy, set during instantiation:

my_driver = my_driver::type_id::create("my_driver", this);

The name is used for:

  • Logging (via uvm_info, uvm_error, etc.)
  • Configuration database lookup
  • Hierarchical queries

4.2 Factory

UVM components are created via the factory mechanism, which allows for runtime overrides:

// Override a component in the factory
uvm_config_db#(uvm_object_wrapper)::set(null, "*", "type_override", my_driver::get_type());

// Or use a factory override
my_driver = my_driver::type_id::create("my_driver", this); // Uses factory

Factory overrides are critical for:

  • Replacing a driver with a debug driver
  • Substituting a scoreboard with a different implementation
  • Enabling/disabling components via configuration

4.3 Configuration Database (uvm_config_db)

The uvm_config_db is a type-safe, hierarchical database for passing configuration data from parent to child components.

Common Uses:

  • Passing virtual interfaces
  • Passing parameters (e.g., number of ports, latency)
  • Enabling/disabling features

Example: Setting a Virtual Interface

// In the top-level test or env
virtual my_if vif;

function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    uvm_config_db#(virtual my_if)::set(this, "*", "vif", vif);
endfunction
// In the driver
virtual my_if vif;

function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
        `uvm_fatal(get_type_name(), "Failed to get virtual interface")
endfunction

Key Methods:

  • uvm_config_db#(T)::set(scope, inst_name, field_name, value)
  • uvm_config_db#(T)::get(scope, inst_name, field_name, value)
  • uvm_config_db#(T)::exists(scope, inst_name, field_name)

4.4 Reporting

Components use the UVM reporting mechanism to log messages:

`uvm_info(get_type_name(), "Simulation started", UVM_LOW)
`uvm_error(get_type_name(), "Invalid transaction")
`uvm_fatal(get_type_name(), "Critical error in driver")

4.5 Analysis Ports and Export

Components can communicate via analysis ports (broadcast) and analysis exports (subscribe).

// In monitor
uvm_analysis_port #(my_txn) ap;

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

task run_phase(uvm_phase phase);
    // ... monitor logic ...
    ap.write(txn); // Broadcast transaction
endtask
// In scoreboard
uvm_analysis_imp #(my_txn, my_scoreboard) imp;

function void new(string name, uvm_component parent);
    super.new(name, parent);
    imp = new("imp", this);
endfunction

function void write(my_txn t);
    // Handle received transaction
endfunction

In connect_phase:

function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    agent.monitor.ap.connect(scoreboard.imp);
endfunction

5. Common Component Patterns

5.1 Agent

An agent encapsulates a DUT interface and typically contains:

  • Driver: Drives the interface.
  • Sequencer: Interfaces with sequences.
  • Monitor: Monitors the interface.
  • Scoreboard (optional): If the agent is responsible for checking its own IP.
class my_agent extends uvm_agent;
    my_driver     drv;
    my_sequencer  sqr;
    my_monitor    mon;

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if (is_active == UVM_ACTIVE) begin
            drv = my_driver::type_id::create("drv", this);
            sqr = my_sequencer::type_id::create("sqr", this);
        end
        mon = my_monitor::type_id::create("mon", this);
    endfunction

    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        if (is_active == UVM_ACTIVE)
            sqr.seq_item_export.connect(drv.seq_item_fifo);
    endfunction
endclass

5.2 Environment

The environment is the top-level container that holds agents, scoreboards, and coverage collectors.

class my_env extends uvm_env;
    my_agent     agent;
    my_scoreboard scoreboard;
    my_coverage coverage;

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        agent = my_agent::type_id::create("agent", this);
        scoreboard = my_scoreboard::type_id::create("scoreboard", this);
        coverage = my_coverage::type_id::create("coverage", this);
    endfunction

    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        agent.mon.ap.connect(scoreboard.imp);
        agent.mon.ap.connect(coverage.imp);
    endfunction
endclass

5.3 Test

The test is the entry point of the simulation. It creates the environment and starts the simulation.

class my_test extends uvm_test;
    my_env env;

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        env = my_env::type_id::create("env", this);
    endfunction

    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        // Additional top-level connections
    endfunction
endclass

6. Best Practices

  1. Use type_id::create for instantiation:
    my_driver = my_driver::type_id::create("my_driver", this);
    Never use new for UVM components.
  2. Keep components lightweight:
    • Components should handle control and configuration.
    • Data should be passed via objects (transactions).
  3. Use uvm_config_db for configuration:
    • Avoid global variables or direct member access for configuration.
    • Use unique field names.
  4. Phase discipline:
    • Do heavy work in run_phase, not build_phase.
    • Use fork-join in run_phase for parallel tasks.
  5. Logging:
    • Use uvm_info, uvm_warning, uvm_error, uvm_fatal appropriately.
    • Use UVM_HIGH for verbose debugging, UVM_LOW for critical info.
  6. Factory Overrides:
    • Design components to be replaceable.
    • Use factory overrides for debugging or alternate implementations.
  7. Analysis Ports:
    • Use analysis ports for broadcast communication (monitor → scoreboard/coverage).
    • Use TLM sockets for more complex communication (e.g., FIFO, interface).
  8. Avoid Cyclic Dependencies:
    • Ensure the component hierarchy is a tree, not a graph.
    • Use analysis ports/exports to decouple components.

7. Debugging Components

7.1 UVM Phase Trace

To see which phase is executing:

`uvm_info("PHASE", $sformatf("In %s phase", phase.get_name()), UVM_MEDIUM)

7.2 Configuration Database Debug

To check what is in the config DB:

uvm_config_db#(virtual my_if)::exists(this, "", "vif");

7.3 Component Hierarchy

To print the component hierarchy:

`uvm_info("HIERARCHY", "Printing component hierarchy", UVM_LOW)
uvm_top.print_topology();

7.4 Sequence/Driver Debug

  • Use sqr.get_seq_item() and sqr.item_done() to step through sequences.
  • Use drv.req_port.try_next_item() to inspect driver state.

8. Common Pitfalls

Pitfall Solution
Forgetting to call super.build_phase() Always call super in all phase methods.
Incorrect type_id::create name Use a unique, descriptive name.
Configuration not set before build_phase Set config in the parent's build_phase before creating children.
Mixing up uvm_object and uvm_component Components are structural; objects are data.
Hardcoding configurations Use uvm_config_db and factory overrides.
Ignoring is_active in agents Check is_active before creating driver/sequencer.

9. Summary

  • UVM Components are the structural backbone of the verification environment.
  • They follow a strict phase-based lifecycle.
  • They communicate via the configuration database, analysis ports, and TLM.
  • They are created via the factory to enable flexibility and overrides.
  • Agents, Environments, and Tests are the most common component types.
  • Best practices include using type_id::create, proper configuration, and clean phase discipline.

Understanding UVM components thoroughly is essential for building scalable, maintainable, and reusable verification environments.

UVM Component Study Guide © 2023

SiteMap