Skip to content

UVM

UVM Architecture

The fixed UVM testbench hierarchy — test, env, agent, sequencer, driver, monitor, scoreboard, coverage — and how stimulus and observation flow through it.

Introduction to UVM · Module 2 · Page 2.5

The Engineering Problem

The origin story is complete — you know where UVM came from and that it is a ratified standard. Now the practical question: what does a UVM testbench actually look like? Because here is the fact that makes UVM learnable: every UVM environment, for every protocol, has the same skeleton. A test configures an environment; the environment holds one or more agents plus a scoreboard and coverage; each agent holds a sequencer, a driver, and a monitor. APB, AXI, a custom FIFO — the protocol changes, the skeleton does not.

That regularity is the entire payoff of the standard, and it is also the thing a newcomer must internalise first. If you dive into sequences, or the factory, or phasing, without the map of where each piece sits and what it is for, every mechanism feels disconnected. With the map, each later topic slots into a known slot. So before we zoom into any single component, this chapter draws the whole shape.

What is the fixed architecture of a UVM testbench — the role of each component, how they nest, and how stimulus and observation flow through them — so the rest of the course has a map to hang every detail on?

Motivation — why one fixed architecture for everything

UVM could have let every team invent its own structure. It deliberately does not, and the reasons are the reasons UVM exists at all:

  • The agent is a reusable unit. Package a protocol's sequencer, driver, and monitor into an agent, and that agent drops into any environment that needs that interface — block-level, SoC-level, this project, the next. Reuse has a shape, and the agent is it.
  • Separation of concerns enables reuse. Each component has exactly one job: the driver drives, the monitor observes, the scoreboard checks, coverage measures, the sequencer arbitrates stimulus. Because roles don't mix, any one component can be reused or replaced without dragging the others.
  • A standard shape is a shared language. An engineer who knows the skeleton can open any UVM environment and immediately find the driver, the scoreboard, the coverage. Onboarding, debugging, and integrating third-party VIP all rely on the structure being predictable.
  • It scales by nesting. A bigger system is just more agents in the environment (or environments inside environments). The same skeleton composes from one interface to a whole SoC without changing its rules.

The motivation, in one line: the fixed architecture is what turns "a testbench" into "a reusable, legible, composable testbench" — and learning it once pays off in every UVM environment you will ever read.

Mental Model

Hold two pictures of the same structure:

The hierarchy is an org chart; the dataflow is an assembly line. The org chart answers who contains and builds whom: the test owns the environment, the environment owns the agents and scoreboard, each agent owns its sequencer, driver, and monitor. The assembly line answers how a transaction travels: a sequence hands an item to the sequencer, which feeds the driver, which wiggles the DUT's pins; the monitor watches those pins, reassembles the transaction, and broadcasts it to the scoreboard and coverage. Same components, two views — containment and flow. You need both to read a UVM testbench: the org chart to know what exists, the assembly line to know what happens.

So whenever you meet a UVM concept later, locate it on both maps: which box in the hierarchy is this, and where is it on the stimulus-or-observation path? Every component answers both questions, and knowing the answers is what makes the architecture feel inevitable instead of arbitrary.

Visual Explanation — the hierarchy (the org chart)

The structural skeleton is a tree. A test sits at the top; under it, the environment; under that, the agent (containing sequencer, driver, monitor) alongside the scoreboard and coverage. This exact shape recurs in every UVM environment.

UVM testbench hierarchy — test contains env; env contains agent, scoreboard, coverage; agent contains sequencer, driver, monitorsqrsqruvm_sequenceruvm_sequencerdrvdrvuvm_driveruvm_drivermonmonuvm_monitoruvm_monitoragentagentuvm_agentuvm_agentsbsbuvm_scoreboarduvm_scoreboardcovcovuvm_subscriberuvm_subscriberenvenvuvm_envuvm_envtest_basetest_baseuvm_testuvm_test
Figure 1 — the canonical UVM testbench hierarchy. The test (uvm_test) builds and configures the environment (uvm_env); the environment builds one or more agents plus a scoreboard (uvm_scoreboard) and coverage (a uvm_subscriber). Each agent (uvm_agent) builds a sequencer, a driver, and a monitor. The protocol changes the contents of these classes; the shape of the tree stays the same.

Read the tree as roles. The test is the top-level intent — it picks the configuration and the sequences to run. The environment is the reusable container of everything needed to verify a block. The agent is the per-interface unit, and the three components inside it divide the interface's work: the sequencer arbitrates and routes stimulus, the driver turns transactions into pin activity, the monitor turns pin activity back into transactions. Beside the agent, the scoreboard judges correctness and coverage measures what was exercised. Every box has one job; the tree is just those jobs, nested.

RTL / Simulation Perspective — the skeleton in code

The hierarchy is built in code by each component constructing its children in build_phase and wiring them in connect_phase. The skeleton below is the whole architecture in miniature — env builds the agent and scoreboard; the agent builds its sequencer, driver, and monitor; the connections realise the two flows.

the UVM testbench skeleton — each component builds and connects its children
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class my_agent extends uvm_agent;
  `uvm_component_utils(my_agent)
  my_sequencer sqr;  my_driver drv;  my_monitor mon;
 
  function void build_phase(uvm_phase phase);
    mon = my_monitor::type_id::create("mon", this);       // monitor: always built (passive part)
    if (get_is_active() == UVM_ACTIVE) begin               // driver+sequencer: only when driving
      sqr = my_sequencer::type_id::create("sqr", this);
      drv = my_driver   ::type_id::create("drv", this);
    end
  endfunction
 
  function void connect_phase(uvm_phase phase);
    if (get_is_active() == UVM_ACTIVE)
      drv.seq_item_port.connect(sqr.seq_item_export);      // driver pulls items from the sequencer
  endfunction
endclass
 
class my_env extends uvm_env;
  `uvm_component_utils(my_env)
  my_agent agent;  my_scoreboard sb;
 
  function void build_phase(uvm_phase phase);
    agent = my_agent     ::type_id::create("agent", this); // env builds the agent...
    sb    = my_scoreboard::type_id::create("sb", this);    // ...and the scoreboard
  endfunction
 
  function void connect_phase(uvm_phase phase);
    agent.mon.ap.connect(sb.analysis_export);              // monitor broadcasts observed txns to the scoreboard
  endfunction
endclass

Three architectural facts are visible here. First, construction is hierarchical: each component creates its own children, so the tree builds itself top-down. Second, the active/passive distinction is real and structural — a passive agent builds only the monitor (it observes but does not drive), which is exactly what you want for an interface you watch but don't control. Third, the two flows are just two connections: drv.seq_item_port.connect(sqr...) realises the stimulus path, and mon.ap.connect(sb...) realises the observation path. You'll learn each construct in depth later; for now, see that the architecture is this build-and-connect skeleton.

Verification Perspective — the agent, and the two flows

The agent is the heart of the architecture because it is the unit of reuse — the bundle that makes a protocol interface portable. Look inside it and at how it connects outward, and the stimulus and observation paths become concrete.

UVM dataflow — driver drives DUT, monitor samples DUT, reference model and scoreboard check, coverage measuresexpectedactualTestbench TopDriverdrives DUT inputsDUTdesign under testMonitorsamples DUT outputsReference ModelScoreboardexpected vs actualCoverage12
Figure 2 — the dataflow view (the assembly line). Stimulus flows top-down: a sequence produces transactions on the sequencer, the driver pulls them and drives the DUT's pins. Observation flows bottom-up: the monitor samples the pins, reconstructs each transaction, and broadcasts it through an analysis port to the scoreboard (which checks it against a reference) and to coverage (which records it). The agent bundles the sequencer, driver, and monitor for one interface; an active agent has all three, a passive agent has only the monitor.

The decisive concept is active versus passive. An active agent contains the sequencer and driver (it generates and drives stimulus) plus the monitor; a passive agent contains only the monitor (it observes but never drives). This single switch is what lets the same agent be reused in opposite roles: drive an interface at block level (active), then merely watch that same interface at SoC level where another block drives it (passive). The monitor is always present because observation is always needed; the driver and sequencer are optional because driving is contextual. That asymmetry — always observe, sometimes drive — is the architecture encoding the reality of reuse.

Runtime / Execution Flow — a transaction's journey through the architecture

Put the two views together and watch one transaction travel the full path: born in a sequence, driven onto the pins, observed by the monitor, judged by the scoreboard, recorded by coverage. This single journey is what the entire architecture exists to support.

A transaction's journey: sequence to sequencer to driver to DUT, then monitor to scoreboard and coveragetransactionseq_itempinspinsobserved txnsampleSequencecreates the transactionSequencerarbitrates / routesDrivertransaction → pinsDUTMonitorpins → transactionScoreboardcheck vs referenceCoveragerecord what was hit12
Figure 3 — one transaction's end-to-end journey, mapping the dataflow onto the components. A sequence creates a transaction and sends it to the sequencer; the sequencer hands it to the driver; the driver converts it to pin-level activity on the DUT. The monitor samples those pins, reconstructs the transaction, and broadcasts it to the scoreboard (checked against the reference) and coverage (recorded). Stimulus path in brand colour, observation path following — the same loop runs for every transaction.

This journey is the architecture in motion, and it maps cleanly onto Module 1's methodology loop: the sequence/sequencer/driver path is stimulus (constrained-random breadth and directed corners), the scoreboard is checking (the spec-derived golden), and coverage is measurement. UVM's architecture is precisely the standardised, reusable embodiment of that loop — each Module 1 concern given a fixed home in the hierarchy. When you learned the flow, you learned what these boxes are for; now you know which box is which.

Waveform Perspective — the architecture meets the pins

The two flows are abstract until they touch the DUT. On the waveform, the driver's stimulus becomes pin activity, and the monitor's observation captures it back — the top and bottom of the assembly line, made concrete on the same signals.

One transaction through the architecture — driver drives the pins, monitor captures them

10 cycles
One transaction through the architecture — driver drives the pins, monitor captures themStimulus path: sequence → sequencer → driver applies the transaction to the pinsStimulus path: sequenc…Observation path: monitor captures the completed transfer and broadcasts itObservation path: moni…monitor → analysis port → scoreboard + coverage; the loop repeats per transactionmonitor → analysis por…clkvalidreadydata00C1C200D0D100000000drv_applymon_capturet0t1t2t3t4t5t6t7t8t9
Figure 4 — the stimulus path lands on the pins: at cycle 1 the driver (fed by sequence → sequencer) applies a transaction (valid + data). The observation path reads them back: at cycle 2 the monitor has captured the completed transfer and broadcasts it to the scoreboard and coverage. The same drive-then-capture pattern repeats for the next transaction at cycles 4–5. The pins are where the driver's output and the monitor's input meet — the architecture's two flows touching the DUT.

The drv_apply and mon_capture pulses are the architecture's two flows touching the same wires: the driver writes the pins (stimulus, top-down) and the monitor reads them (observation, bottom-up). Notice they are independent — the monitor would capture this traffic whether the driver came from this agent (active) or from some other block entirely (passive). That independence is the architecture working as designed: observation is decoupled from stimulus, which is exactly why a monitor reused passively still sees, checks, and measures everything, even when something else is doing the driving.

DebugLab — the monitor that did the scoreboard's job

A monitor with checking inside it — reusable nowhere, entangled everywhere

Symptom

A block-level agent worked perfectly. At SoC integration, the team needed that interface as a passive agent — observe it, but let another block drive it. The moment they instantiated the agent passively, the environment broke: the agent dragged in checking logic that expected the block-level reference model and configuration, which didn't exist at the SoC. A monitor that should have been trivially reusable as a passive observer couldn't be reused at all.

Root cause

A separation-of-concerns violation — the architecture's cardinal rule. Someone had put scoreboard logic inside the monitor: the monitor not only observed transactions, it also compared them against a reference model and flagged mismatches. That fused two roles that the architecture deliberately keeps apart:

roles the architecture separates — and what was fused
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
monitor's job:     observe pins → reconstruct txn → BROADCAST via analysis port (no judgement)
scoreboard's job:  receive txns → CHECK against a reference → report pass/fail
what was built:    monitor did BOTH → observing was welded to checking
consequence:       passive reuse impossible — the "observer" demanded a reference model
                   and config that only the active, block-level env provided

Because observation and checking were welded together, the monitor could not be used in any context that didn't also supply the checking machinery — which is to say, it could not be reused at all.

Diagnosis

The tell is a component that can't be reused in a new context because it carries jobs that aren't its own. Diagnose architecture violations by asking what each component needs versus what its role requires:

  1. Check each component against its single role. A monitor should need only the interface and an analysis port — nothing about reference models or expected values. If it references checking machinery, it's doing the scoreboard's job.
  2. Try to reuse it in the opposite context. A correctly-built monitor reuses passively with zero changes. If passive instantiation pulls in dependencies, observation has been entangled with something else.
  3. Follow the analysis port. Observation should leave the monitor through an analysis port and be judged elsewhere. Checking that happens inside the monitor is the smell.
Prevention

The architecture's separation of roles is the source of its reuse — respect it:

  1. One component, one role. Monitor observes and broadcasts; scoreboard checks; coverage measures; driver drives; sequencer arbitrates. Never merge two roles into one component, however convenient it seems.
  2. Make the monitor judgement-free. It reconstructs transactions and sends them out an analysis port — full stop. All checking lives downstream in the scoreboard, which can be present or absent independently of the monitor.
  3. Design for passive reuse from day one. Assume every interface you drive at block level will be observed-only somewhere else. A monitor that can't be reused passively is a monitor doing too much.

The one-sentence lesson: the architecture's separation of concerns is not bureaucracy — it is the mechanism of reuse, and a component that does another's job (a monitor that checks) becomes reusable nowhere.

Common Mistakes

  • Merging roles into one component. Putting checking in the monitor, or driving in the scoreboard, fuses jobs the architecture separates and destroys reuse. One component, one role — observation, checking, measurement, and driving stay in their own boxes.
  • Forgetting the active/passive distinction. An agent that can only be active can't be reused to merely observe an interface another block drives. Build the monitor unconditionally and the driver/sequencer only when active.
  • Building components in the constructor instead of build_phase. The hierarchy is constructed top-down in build_phase (via the factory's create); doing it elsewhere breaks phasing and the factory's ability to override types. (You'll see why in later chapters — for now, build children in build_phase.)
  • Flattening the hierarchy. Skipping the env/agent structure ("just a driver and a monitor in a module") forfeits the reuse and composability the hierarchy exists to provide, and it won't scale to SoC.
  • Confusing the two views. The hierarchy (containment) and the dataflow (connectivity) are different questions. A component's parent in the tree is not necessarily who it sends data to — the monitor lives in the agent but broadcasts to the scoreboard in the env.

Senior Design Review Notes

Interview Insights

A UVM testbench is a fixed hierarchy. At the top is the test (uvm_test), which selects the configuration and the sequences to run. It builds an environment (uvm_env), the reusable container for everything needed to verify a block. The environment contains one or more agents (uvm_agent) plus a scoreboard (uvm_scoreboard) and coverage (a uvm_subscriber). Each agent bundles the components for one interface: a sequencer (arbitrates and routes stimulus), a driver (turns transactions into pin activity), and a monitor (turns pin activity back into transactions). Functionally there are two flows: stimulus top-down (sequence → sequencer → driver → DUT) and observation bottom-up (DUT → monitor → analysis port → scoreboard and coverage). The protocol changes the contents of these classes; the skeleton stays the same for every environment, which is what makes UVM testbenches reusable and legible.

Exercises

  1. Label the tree. From memory, draw the canonical UVM hierarchy and write each component's single-sentence role: test, env, agent, sequencer, driver, monitor, scoreboard, coverage.
  2. Active or passive? For each, choose active or passive and justify: (a) the AXI master interface your block drives at block level; (b) that same AXI interface at SoC level where another IP is the master; (c) a status-bus you only ever observe; (d) an interface your testbench must both stimulate and check.
  3. Two views. For the monitor, state (a) its parent in the hierarchy and (b) which component(s) it sends data to — and explain why those are different answers and what that teaches about containment vs dataflow.
  4. Spot the violation. A teammate's monitor instantiates a reference model and raises errors on mismatches. Name the architectural rule broken, the reuse scenario it will break, and the refactor that fixes it.

Summary

  • A UVM testbench is a fixed hierarchy: testenvironmentagent(s) (each = sequencer + driver + monitor) + scoreboard + coverage. The protocol changes the contents; the skeleton is identical in every environment.
  • There are two flows over that structure: stimulus top-down (sequence → sequencer → driver → DUT) and observation bottom-up (DUT → monitor → analysis port → scoreboard + coverage). Read the hierarchy as an org chart (containment) and the dataflow as an assembly line (connectivity).
  • The agent is the unit of reuse, and the active/passive switch is what makes it portable: active agents drive (sequencer + driver + monitor); passive agents only observe (monitor). The monitor is always present because observation is always needed.
  • Separation of concerns is the mechanism of reuse: one role per component (driver drives, monitor observes-and-broadcasts, scoreboard checks, coverage measures, sequencer arbitrates). Merging roles — a monitor that checks — destroys reuse, as the DebugLab showed.
  • The durable rule of thumb: every UVM environment is the same skeleton with two flows — locate each new concept by its box in the hierarchy and its place on the stimulus-or-observation path, and the whole architecture stays legible.

Next — The UVM Mental Model: you have the structural map. The next chapter turns it into a mental model — the handful of recurring ideas (build-then-connect, transactions over signals, configure-from-the-top, observe-and-broadcast) that let you reason about any UVM environment without memorising it.