Skip to content

UVM

Case Study — A Complete SoC Environment

Compose the APB, AXI, and UART agents and the register layer into a system-level UVM environment — reusing each agent active or passive per integration level, coordinating them with a virtual sequencer and virtual sequences, checking end to end with a system scoreboard, and the bus-contention bug that an agent left active at SoC level causes.

Industry Case Studies · Module 30 · Page 30.5

The Engineering Problem

You built an APB agent, an AXI agent, a UART agent, and a register layer — each a self-contained piece of reusable verification IP. This final case study is the payoff: compose them into a system-level environment for an SoC where those interfaces all coexist. The system env does not rebuild anything; it instantiates the agents you already have, reuses each active or passive depending on who drives that bus at the system level, coordinates them with a virtual sequencer and virtual sequences, and checks end to end with a system scoreboard.

Three system-level mechanisms tie the agents together. Active/passive reuse: at the SoC, the real on-chip masters drive most buses, so the testbench agents on those buses are configured passive (observe only) — driving them would collide with the RTL. A virtual sequencer holds handles to each agent's real sequencer, and a virtual sequence runs on it to coordinate multi-agent scenarios from one place — configure the DUT over APB, then drive UART traffic, while observing AXI. A system scoreboard subscribes to the agents' monitors and checks behavior across interfaces. The one mistake that turns a working block-level agent into a system-level disaster: leaving an agent active on a bus the RTL now drives, causing bus contention.

How do you compose existing APB, AXI, and UART agents into an SoC environment — reusing each active or passive per integration level, coordinating them with a virtual sequencer and virtual sequences, and checking end to end — so the system env is an assembly of reusable IP rather than a rebuild?

Motivation — why composition is the payoff of building IP

The whole point of building agents instead of directed tests is this moment — system-level reuse:

  • The system env reuses, it does not rebuild. Every block-level agent drops into the SoC env unchanged, reconfigured active or passive. If the agents were not built for reuse — hardcoded paths, baked-in checks — this composition would be impossible, which is why the earlier discipline mattered.
  • Active/passive is what makes reuse real. At block level an agent drives; at SoC level the real RTL drives and the agent observes. One configuration field flips the role, and getting it wrong causes bus contention — the system-level failure that proves the active/passive design earns its keep.
  • Virtual sequences coordinate what single sequences cannot. A system scenario spans interfaces — configure over APB, traffic over AXI, side-band over UART — and a virtual sequence on a virtual sequencer is how you orchestrate that from one place, the system-level stimulus mechanism.
  • End-to-end checking lives at the system level. Block scoreboards check their interface; the interesting SoC bugs are in the interactions, so a system scoreboard checks behavior across interfaces — where the real integration bugs hide.

The motivation, in one line: the SoC environment is the payoff of building reusable agents — it composes them by reconfiguration into a coordinated, end-to-end-checked system, and the active/passive, virtual-sequence, and system-scoreboard mechanisms are what make that composition work.

Mental Model

Hold the SoC environment as a conductor leading an orchestra of musicians you already trained, most of them now listening rather than playing:

You have spent the season training individual musicians, each excellent at their instrument — that was building the agents. The concert is the SoC, and conducting it is a different job from training a player. You do not re-teach anyone their instrument; you assemble them and coordinate them. The first thing a conductor decides is who plays and who rests in each passage. In the system performance, the real soloists — the actual on-chip masters — carry most of the melody, so your trained musicians on those parts do not play over them; they rest and listen, ready to follow, contributing by their presence and attention rather than their sound. A musician who keeps playing their full part while the soloist is also playing the same line does not add richness — they clash, two instruments fighting over one line, and the result is noise that drowns both. So the conductor is meticulous about who is active and who is passive in each moment. Then the conductor coordinates: from the podium, a single gesture cues the strings to begin, then the brass to enter, then the percussion — one point of control orchestrating independent sections into a coherent whole, which no individual player could do from their seat. And the conductor listens to the whole, not just one section, hearing whether the ensemble is together, because the performance is judged as a whole, not instrument by instrument. The art is not playing every instrument — it is deciding who plays and who rests, cueing them in coordination, and judging the result end to end. You trained the musicians individually — that was building the agents. The concert is the SoC, and conducting is a different job. You do not re-teach instruments; you assemble and coordinate. First you decide who plays and who rests: the real soloists (on-chip masters) carry the melody, so your musicians on those parts rest and listen rather than play over them — a player who keeps playing the soloist's line clashes, two instruments fighting over one line, producing noise. So you are meticulous about active versus passive. Then you coordinate from the podium — one gesture cues strings, then brass, then percussionone point of control orchestrating independent sections. And you listen to the whole, judging the ensemble end to end.

So building the SoC env is conducting: the agents are the trained musicians, reused; active/passive is who plays and who rests (and the contention bug is two playing the same line); the virtual sequencer and virtual sequence are the podium and the conductor's gestures coordinating the sections; and the system scoreboard is listening to the whole ensemble. The disaster you must avoid is the musician left playing over the soloist — an agent left active on a bus the RTL drives.

The SoC Environment Hierarchy

The system env composes the register env (APB), the AXI agent, and the UART agent, plus a virtual sequencer and a system scoreboard — agents reused, configured by integration level.

SoC environment hierarchy: soc_test contains soc_env, which contains reg_env, AXI agent (passive), UART agent, a virtual sequencer, and a system scoreboardregsregsreg_env (APB + RAL)reg_env (APB + RAL)axiaxiaxi_agent (passive)axi_agent (passive)uartuartuart_agentuart_agentvseqrvseqrsoc_virtual_sequencersoc_virtual_sequencersbsbsoc_scoreboardsoc_scoreboardsoc_envsoc_envuvm_envuvm_envsoc_testsoc_testuvm_testuvm_test
Figure 1 — the complete SoC environment. The soc_test builds a soc_env that instantiates the reg_env (the APB agent plus the register model from the previous case study), the AXI agent (passive — the real master drives AXI at SoC level), the UART agent, a virtual sequencer holding handles to each agent's real sequencer, and a system scoreboard subscribing to the agents' monitors. Nothing here is rebuilt; the agents are the same reusable IP, reconfigured active or passive and composed under one env, coordinated by the virtual sequencer and checked end to end by the system scoreboard.

The hierarchy is a composition of finished parts. The reg_env brings the APB agent and the register model from the register-verification case study. The AXI agent is reused passive because the real on-chip master drives AXI at the system level. The UART agent may be active (the testbench provides the serial peer). The virtual sequencer is a coordination component — it drives no pins; it holds handles to the agents' real sequencers. The system scoreboard subscribes to the agents' monitors for end-to-end checks. None of the agents changed; they are configured and wired together, which is exactly what reusable IP is for.

Composition, Configuration, and the Virtual Sequencer

The env builds the sub-components, sets each agent active or passive through nested config, builds a virtual sequencer, and wires the sequencer handles and scoreboard in connect_phase.

soc_config (nested) and soc_virtual_sequencer (handles to real sequencers)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class soc_config extends uvm_object;
  apb_config  apb_cfg;
  axi_config  axi_cfg;
  uart_config uart_cfg;
  `uvm_object_utils(soc_config)
  function new(string name = "soc_config"); super.new(name); endfunction
endclass
 
class soc_virtual_sequencer extends uvm_sequencer;
  `uvm_component_utils(soc_virtual_sequencer)
  apb_sequencer  apb_seqr;        // handles to the agents' REAL sequencers
  uart_sequencer uart_seqr;       // (no axi handle: AXI is passive here)
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
endclass
soc_env — compose the agents, set active/passive, wire coordination + scoreboard
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class soc_env extends uvm_env;
  `uvm_component_utils(soc_env)
  reg_env               regs;        // APB agent + register model (reused)
  axi_agent             axi;         // reused, configured passive
  uart_agent            uart;        // reused
  soc_virtual_sequencer vseqr;
  soc_scoreboard        sb;
  soc_config            cfg;
  // ... new() ...
 
  function void build_phase(uvm_phase phase);
    if (!uvm_config_db#(soc_config)::get(this, "", "cfg", cfg))
      `uvm_fatal("NOCFG", "soc_config not provided")
 
    // INTEGRATION-LEVEL CHOICE: the real master drives AXI at SoC level → observe only
    cfg.axi_cfg.is_active = UVM_PASSIVE;
    uvm_config_db#(axi_config)::set(this,  "axi",      "cfg", cfg.axi_cfg);
    uvm_config_db#(uart_config)::set(this, "uart",     "cfg", cfg.uart_cfg);
    uvm_config_db#(apb_config)::set(this,  "regs.agent","cfg", cfg.apb_cfg);
 
    regs  = reg_env::type_id::create("regs", this);
    axi   = axi_agent::type_id::create("axi", this);
    uart  = uart_agent::type_id::create("uart", this);
    vseqr = soc_virtual_sequencer::type_id::create("vseqr", this);
    sb    = soc_scoreboard::type_id::create("sb", this);
  endfunction
 
  function void connect_phase(uvm_phase phase);
    // hand the virtual sequencer the agents' real sequencers (active agents only)
    vseqr.apb_seqr  = regs.agent.seqr;
    vseqr.uart_seqr = uart.seqr;
    // end-to-end checking: agent monitors → system scoreboard
    axi.mon.ap.connect(sb.axi_in);
    uart.mon.ap.connect(sb.uart_in);
    regs.agent.mon.ap.connect(sb.apb_in);
  endfunction
endclass

The build_phase is composition plus the integration-level choice: it sets cfg.axi_cfg.is_active = UVM_PASSIVE because the real master drives AXI here, then distributes each sub-config down the hierarchy and creates the reused agents and the coordination components — nested config mirroring the component tree. The connect_phase does the two system-level wirings: it hands the virtual sequencer the handles to each active agent's real sequencer (no AXI handle, since AXI is passive and has no driver), and connects each agent's monitor to the system scoreboard for end-to-end checking. Note the AXI monitor still connects to the scoreboard even though the AXI agent is passive — observation is exactly what a passive agent is for.

The Virtual Sequence — coordinating the agents

A virtual sequence runs on the virtual sequencer and orchestrates a multi-agent scenario by starting sub-sequences on the agents' real sequencers, bracketed by one objection.

A virtual sequence on the virtual sequencer starting sub-sequences on the APB and UART agents' real sequencers, each of which feeds its driver onto its bussequencesequencerdriverDUTstart_item(req)get_next_item(req)drive vif signalsresponseitem_done()finish_item()
Figure 2 — the virtual sequence coordinating multiple agents. The virtual sequence runs on the soc_virtual_sequencer, which holds handles (via p_sequencer) to the APB and UART agents' real sequencers. The virtual sequence's body starts a register-config sub-sequence on the APB sequencer, waits for it, then starts a UART traffic sub-sequence on the UART sequencer — orchestrating the whole system scenario from one place. One objection, raised by the test or the virtual sequence, brackets the entire coordinated scenario so the run phase stays alive for all of it.
soc_smoke_vseq — orchestrate config-over-APB then traffic-over-UART
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class soc_smoke_vseq extends uvm_sequence #(uvm_sequence_item);
  `uvm_object_utils(soc_smoke_vseq)
  `uvm_declare_p_sequencer(soc_virtual_sequencer)   // typed access to the handles
  function new(string name = "soc_smoke_vseq"); super.new(name); endfunction
 
  task body();
    reg_smoke_seq cfg_seq = reg_smoke_seq::type_id::create("cfg_seq");
    uart_tx_seq   tx_seq  = uart_tx_seq::type_id::create("tx_seq");
 
    // 1. configure the DUT through its registers, on the APB sequencer
    cfg_seq.regmodel = p_sequencer.regmodel;
    cfg_seq.start(p_sequencer.apb_seqr);
 
    // 2. then drive UART traffic, on the UART sequencer
    assert(tx_seq.randomize());
    tx_seq.start(p_sequencer.uart_seqr);
  endtask
endclass
 
class soc_test extends uvm_test;
  `uvm_component_utils(soc_test)
  soc_env env;
  // ... build_phase: set soc_config, create env ...
  task run_phase(uvm_phase phase);
    soc_smoke_vseq vseq = soc_smoke_vseq::type_id::create("vseq");
    phase.raise_objection(this);          // one objection brackets the whole scenario
    vseq.start(env.vseqr);                // run the virtual sequence on the virtual sequencer
    phase.drop_objection(this);
  endtask
endclass

The virtual sequence is the conductor's score. `uvm_declare_p_sequencer gives it typed access to the virtual sequencer's handles via p_sequencer. Its body coordinates across agents: first it starts the register-config sub-sequence on the APB sequencer and waits for it (configure the DUT first), then it starts the UART traffic sub-sequence on the UART sequencer — a multi-interface scenario orchestrated from one place. The test wraps the whole thing in one objection so the run phase stays alive for the entire coordinated scenario, and runs the virtual sequence on env.vseqr. The virtual sequence drives no pins itself; it conducts the real sequencers.

Waveform Perspective — what an agent left active does to the bus

Active/passive is not cosmetic. If the AXI agent is left active while the real master also drives AXI, both drive the same signals and the bus goes to X — the contention this section's DebugLab is about.

Bus contention — the AXI agent left active drives the same bus as the real master

5 cycles
Bus contention — the AXI agent left active drives the same bus as the real masterreal master drives the bus cleanly; active agent silent — looks finereal master drives the…still clean — the bug is latent while the agent happens not to drivestill clean — the bug …active agent also drives → two drivers contend → bus = X, propagates everywhereactive agent also driv…clkrtl_drvagent_drvbusA0A1XXXXXXt0t1t2t3t4
Figure 3 — what happens when an agent is left active at SoC level. While the real on-chip master drives the AXI bus cleanly (cycles 1–2), a leftover active testbench agent stays silent. When the active agent also begins driving (cycle 3 onward), two drivers fight over the same wires: the bus resolves to X (unknown) wherever they disagree, and that X propagates into the DUT and every downstream check. The fix is to configure the agent passive so only the real master drives — the testbench agent observes.

The waveform shows why the active/passive choice is load-bearing. While the leftover active agent happens not to drive (cycles 1–2), the bus is clean and the bug is latent — exactly why it survives a casual smoke test. The moment the active agent drives (cycle 3), two sources fight over the same wires and the bus resolves to X, which propagates into the DUT and corrupts every downstream check. The cure is the one configuration field: set the agent passive so only the real master drives and the agent observes. This is the system-level consequence of the active/passive design introduced all the way back in the APB agent.

DebugLab — the agent left active at SoC level

X everywhere and intermittent failures — an agent drove a bus the real master owns

Symptom

The block-level tests all passed, but at SoC level the environment failed with X values propagating through the AXI datapath and into the scoreboard — intermittently. Some runs passed, some failed, and the failures showed X on the AXI bus signals that no block-level test had ever shown. The AXI agent's code was unchanged from the block level where it worked perfectly.

Root cause

The AXI agent was reused at SoC level but left UVM_ACTIVE, so its driver drove the AXI bus that the real on-chip master also drives — two drivers contending on the same wires:

the agent that should have been passive
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
block level:  axi_cfg.is_active = UVM_ACTIVE;   // testbench drives AXI — correct there
SoC level:    axi_cfg.is_active = UVM_ACTIVE;   // ✗ real master ALSO drives AXI now
effect:       two drivers (RTL master + testbench agent's driver) on the same bus signals
result:       wherever they disagree, the bus resolves to X → propagates into DUT + scoreboard
intermittent: only when the active agent actually drives (depends on stimulus/timing)
fix:          axi_cfg.is_active = UVM_PASSIVE;  // SoC: real master drives, agent observes ✓

At block level the testbench is the AXI master, so the agent is correctly active. At SoC level the real RTL master drives AXI, so an active testbench agent is a second master on the same bus. When both drive, the signals contend and resolve to X wherever the two disagree, and that X spreads through the DUT and into the checks. The intermittency is the giveaway: the contention only manifests on cycles the active agent actually drives, which depends on the stimulus, so some runs look fine. The agent code is blameless — the configuration for the integration level was wrong.

Diagnosis

The tell is X on a bus at SoC level that was clean at block level, often intermittent. Localize it at the agent's active/passive configuration:

  1. Check is_active for every agent on a bus the RTL drives. Any agent on a bus with a real on-chip master must be UVM_PASSIVE. An active one is a contending second master.
  2. Trace the X to a contended signal. X that appears only at SoC level and originates on a bus (not from uninitialized DUT state) points at two drivers; identify which agent shares that bus with the RTL.
  3. Correlate failures with when the agent drives. Intermittent X that coincides with the testbench agent's driving activity confirms contention rather than a DUT bug.
Prevention

Make the integration-level role explicit and checked:

  1. Configure agents passive on any bus the RTL drives. At SoC level, set is_active = UVM_PASSIVE for every agent whose bus has a real master; only agents on testbench-driven interfaces stay active.
  2. Drive active/passive from the integration-level config, not the agent default. The env's config sets each agent's role for that integration, so the same agent is active at block level and passive at SoC by configuration alone.
  3. Add bus-contention assertions or a zero-driver check. An assertion that at most one driver is active on a shared bus catches a mis-set agent immediately, instead of as intermittent downstream X.

The one-sentence lesson: at SoC level the real masters drive the buses, so reuse each testbench agent passive on any bus the RTL drives — an agent left active becomes a second master, contends with the RTL, and resolves the bus to X that propagates everywhere, intermittently.

Common Mistakes

  • Leaving an agent active on an RTL-driven bus. Two masters contend and the bus goes X — the DebugLab. Configure agents passive on any bus a real master drives.
  • Rebuilding instead of reusing. The system env should instantiate and reconfigure the existing agents, not re-author them. Re-authoring duplicates and drifts.
  • Wiring a virtual-sequencer handle to a passive agent. A passive agent has no sequencer; only active agents' sequencers go into the virtual sequencer. Wiring a passive one is a null handle.
  • Coordinating without a virtual sequence. Multi-agent scenarios need a virtual sequence on a virtual sequencer; starting independent sequences with no coordination loses the ordering the scenario needs.
  • Per-sub-sequence objections instead of one. The objection should bracket the whole coordinated scenario at the virtual-sequence or test level, not be managed independently by each sub-sequence.
  • Flat config instead of nested. System configuration should be a nested config tree mirroring the env; scattering individual sets does not scale and invites scope mismatches.

Senior Design Review Notes

Interview Insights

You instantiate the existing block-level agents inside a system env, configure each active or passive depending on who drives its bus at the SoC level, wrap them under one env with nested configuration, coordinate them with a virtual sequencer and virtual sequences, and check across them with a system scoreboard — reusing, not rebuilding. The system env creates the same APB, AXI, UART agents and the register env you built at block level; nothing is re-authored. The key configuration decision is active versus passive: at the SoC, the real on-chip masters drive most buses, so the testbench agents on those buses are set passive, observing and feeding coverage and checking without driving — driving would collide with the RTL. Agents on interfaces the testbench still drives stay active. Configuration flows through a nested config tree: a top soc_config containing each agent's config, distributed down the hierarchy, so the structure mirrors the components. Coordination is through a virtual sequencer that holds handles to the active agents' real sequencers, and virtual sequences that run on it and start sub-sequences on those sequencers to orchestrate multi-interface scenarios from one place. Checking moves up: block scoreboards may remain, but a system scoreboard subscribes to the agents' monitors and checks end-to-end behavior across interfaces, where the integration bugs are. So the system env is an assembly: reuse the agents, set their roles by integration level, coordinate with virtual sequences, check end to end. The understanding to convey is reuse-by-reconfiguration plus virtual-sequence coordination plus system-level checking, which is the payoff of having built each agent as reusable IP.

Agents are passive at SoC level on any bus the real on-chip master drives because the testbench should observe those buses, not drive them, and if you leave such an agent active, its driver contends with the real master on the same wires and the bus resolves to X, which propagates everywhere. At block level the testbench is the master of an interface, so the agent is active and generates stimulus. At the SoC, the real RTL blocks drive each other over those same interfaces, so the testbench's job on them shifts from driving to observing — collecting coverage and feeding the scoreboard. An agent configured passive builds only its monitor and does not drive, which is exactly right. If you mistakenly leave it active, it builds its driver and drives the bus that the real master is also driving, so there are two drivers on the same signals; wherever they disagree, the resolved value is X, the unknown that propagates into the DUT and corrupts every downstream check. The bug is often intermittent, because the contention only shows when the active agent actually drives, which depends on the stimulus, so some runs pass and some fail with X — a classic SoC-integration symptom. The fix is the one configuration field: set the agent passive for that integration level, which the env's config does. This is the system-level reason active/passive is designed into the agent from the start. The understanding to convey is that passive observes a real-master bus while active would contend and produce propagating X, intermittently — which shows you understand active/passive as the mechanism that makes agent reuse across integration levels actually work.

A virtual sequencer is a sequencer that drives no interface but holds handles to the real sequencers of the agents in the environment, and a virtual sequence runs on it and coordinates the agents by starting sub-sequences on those real sequencers, orchestrating a multi-interface scenario from one place. In a system env with several agents, you often need a scenario that spans interfaces — configure the DUT over one bus, then drive traffic on another, perhaps handle a side channel on a third — and that coordination has to live somewhere central. The virtual sequencer is that central point: it is a uvm_sequencer subclass that does not connect to a driver but carries handles to the agents' actual sequencers, wired up in the env's connect_phase. A virtual sequence runs on the virtual sequencer and, through a p_sequencer reference declared with uvm_declare_p_sequencer, reaches those handles; its body starts sub-sequences on the specific agents' sequencers — a register-config sequence on the APB sequencer, then a UART traffic sequence on the UART sequencer — in the order the scenario needs. So the virtual sequence is the conductor: it does not drive pins itself; it conducts the real sequencers. Objections are typically managed once around the whole virtual sequence, at the test or virtual-sequence level, so the run phase stays alive for the coordinated scenario as a whole rather than each sub-sequence managing its own. Only active agents' sequencers go into the virtual sequencer, since passive agents have no sequencer. The understanding to convey is the virtual sequencer as the handle-holding coordination point and the virtual sequence as the conductor that starts sub-sequences on the real sequencers, which is how multi-agent system scenarios are orchestrated.

Block-level checking verifies one interface in isolation — a block scoreboard checks that block's transactions — while system-level end-to-end checking verifies behavior across interfaces, because the interesting SoC bugs are in the interactions between blocks, not within one. At block level, an agent's scoreboard receives that interface's monitored transactions and checks them against an expected model for that block: did the APB register write take, did the AXI read return the right data. That is necessary but not sufficient at the SoC, where the question becomes whether the blocks together do the right thing: a value written over APB to configure a block should change how that block responds to AXI traffic, and a UART byte should produce the expected downstream effect. A system scoreboard subscribes to multiple agents' monitors — APB, AXI, UART — and checks these cross-interface relationships, often with a system-level reference model that predicts the end-to-end behavior from the stimulus across all interfaces. So the difference is scope: block checking is within-interface correctness, system checking is across-interface, end-to-end behavior. Both can coexist — block scoreboards still catch local issues — but the system scoreboard is what catches integration bugs, like a misrouted transaction, a configuration that did not propagate, or an interaction the blocks get wrong together. The understanding to convey is within-interface versus across-interface checking, and that the SoC bugs live in the interactions, so a system scoreboard checking end-to-end across the agents' monitors is what finds them — which shows you understand why checking moves up at the system level.

An agent is composable into an SoC env when it is fully configuration-driven, active/passive capable, factory-built, and free of environment-specific assumptions, so it can be instantiated and reconfigured for the system without editing — and what blocks it is anything hardcoded: a fixed interface path, a baked-in active/passive choice, checks wired into the monitor, or construction with new instead of the factory. The active/passive capability is essential, because the same agent must drive at block level and observe at SoC level; an agent that cannot be passive cannot be reused on an RTL-driven bus. Configuration-driven means everything variable — the interface, options, role — comes from a config object, so the system env supplies a new config rather than touching the agent. Factory construction means the system test can override the agent's item or components if needed. Freedom from environmental assumptions means the agent does not reach up the hierarchy by absolute path, assume a particular parent, or hardcode a config-db scope, so it works wherever it is placed. A monitor kept protocol-pure — observing and broadcasting, not checking — is reusable passive, whereas a monitor with block-specific checks baked in drags assumptions into the SoC. What blocks reuse, concretely, is the inverse of each: hardcoded paths, fixed is_active, new instead of create, checks in the monitor, assumptions about the surrounding env. This is why the discipline at block level — building each agent as reusable IP — is what makes the system composition possible at all; an agent built as a one-off cannot be composed. The understanding to convey is the properties that make an agent composable and the hardcoding that blocks it, and that SoC composition is the test of whether the agents were really built for reuse — which ties the whole case-study arc together.

Exercises

  1. Set the roles. For an SoC where the RTL drives AXI and APB but the testbench drives UART, state which agents are active and which passive, and what goes wrong if AXI is left active.
  2. Wire the virtual sequencer. Write the connect_phase lines that hand the virtual sequencer the active agents' sequencers, and explain why no AXI handle is wired.
  3. Coordinate a scenario. Outline a virtual sequence that configures a block over APB registers, then drives AXI traffic and observes a UART side-band, with one objection.
  4. Diagnose the X. Given intermittent X on the AXI bus at SoC level that never appeared at block level, give your ordered diagnosis and the configuration fix.
  5. Justify reuse. Explain what property of the agents lets the SoC env reuse them unchanged, and name two kinds of hardcoding that would have made composition impossible.

Summary

  • A complete SoC environment composes the agents you already built — APB/register env, AXI agent, UART agent — by instantiation and reconfiguration, not by rebuilding.
  • Active/passive per integration level is load-bearing: at the SoC the real masters drive the buses, so agents on those buses are passive (observe only); leaving one active contends with the RTL and resolves the bus to X that propagates — intermittently — the DebugLab.
  • A virtual sequencer holds handles to the active agents' real sequencers, and a virtual sequence ( `uvm_declare_p_sequencer) runs on it to coordinate multi-interface scenarios from one place, bracketed by one objection.
  • A system scoreboard subscribes to the agents' monitors and checks end-to-end behavior across interfaces — where the integration bugs live, distinct from block-level within-interface checks.
  • Nested config mirrors the env hierarchy, and the composition only works because each agent was built reusable — config-driven, active/passive, factory-built, assumption-free.
  • The durable rule of thumb: build the SoC environment by reusing each agent active or passive per integration level, coordinating them with a virtual sequencer and virtual sequences and checking end to end with a system scoreboard — because the system env is the payoff of building reusable IP, and the one rule that keeps it from self-destructing is that only one master drives each bus, so every agent on an RTL-driven bus is passive.

Next — Protocol Verification Projects: with a complete SoC environment assembled from reusable agents, the final case study steps back to the project level — how these pieces become a verification plan, a regression, and a coverage-closure effort on a real protocol-verification project, turning the methodology you have built into a shippable verification campaign.