Skip to content

AMBA APB · Module 15

The UVM APB Agent

The standard reusable UVM APB agent — how it packages a sequencer, driver, and monitor (plus a sequence library and config) into one configurable unit instantiated once per interface. Active mode builds all three and drives stimulus through the get_next_item / item_done handshake; passive mode builds only the monitor. The agent's analysis port feeds the scoreboard and coverage.

The monitor, the driver, and the sequencer are not three loose components you wire by hand on every project — the UVM agent packages them into one reusable, configurable unit that is the standard atom of APB verification. The single idea to carry: the APB agent is a uvm_agent container that, in ACTIVE mode, builds a sequencer, a driver, and a monitor and drives stimulus onto the bus through the sequencer/driver get_next_item/item_done handshake; in PASSIVE mode, builds only the monitor and merely observes — and either way exposes one analysis port that broadcasts every reconstructed transfer to the rest of the environment. You already know the monitor as a standalone observer; here it becomes a sub-component. You already know APB legality; here you learn the component that drives legal APB and the component that arbitrates what to drive. Get the agent right — its build-time construction gated on is_active, its seq_item_port/seq_item_export connection, its exposed analysis port — and you have a single configurable block you instantiate once per bus and reuse across every project. Get it wrong — a passive agent that still builds a driver, a driver that never calls item_done — and the environment either drives a bus it should only watch, or hangs forever.

1. Problem statement

The problem is packaging stimulus generation and observation for one APB interface into a single, reusable, configurable component — so that any environment can instantiate one agent per bus, switch it between driving and observing with a single config knob, and consume its transactions through one analysis port — without re-wiring a sequencer, driver, and monitor by hand each time.

A verification environment for an APB peripheral needs three capabilities at the interface: something to decide what stimulus to apply (sequences), something to translate that stimulus into pin wiggles in protocol order (the driver), and something to witness what actually happened and report it as transactions (the monitor). Building those as three independently-instantiated components in every testbench is repetitive, error-prone, and unreusable. The agent solves this with three hard requirements:

  • It must be a single container with a clean external contract. The rest of the environment should see one component per interface, configure it, connect to its one analysis port, and run sequences on its one sequencer — not reach inside to wire sub-components. The agent's internal topology is its own business; its external surface is an analysis port out and a sequencer to target.
  • It must be configurable between active and passive at build time. The same agent class must be able to drive an interface in one testbench (block-level: stimulate the DUT) and only observe an identical interface in another (integration-level: watch a real master driving the DUT). That choice is is_activeUVM_ACTIVE builds sequencer+driver+monitor, UVM_PASSIVE builds the monitor alone — and it must be set from outside via uvm_config_db, decided at build_phase, never hard-coded in the components.
  • It must drive legal APB through a disciplined sequencer/driver handshake. When active, the driver pulls one transaction at a time from the sequencer with get_next_item, drives it through SETUP then ACCESS, waits for PREADY to know the slave is done, captures PRDATA/PSLVERR, and returns the result with item_done — exactly one item_done per get_next_item, or the sequencer/driver pipeline stalls.

So the job is not "know APB" or "know the monitor" — you do. It is to package the interface's stimulus and observation into one configurable unit with a disciplined internal handshake and a single clean external seam.

2. Why previous knowledge is insufficient

You have the pieces but not the package. Chapter 15.3 gave you the monitor as a standalone passive observer; 15.1 gave you the rules the driver must obey when it drives. Neither tells you how the components are assembled, configured, and connected into the reusable unit the environment actually instantiates:

  • The monitor is a sub-component here, not the whole story. In 15.3 the monitor was studied alone — its sampling discipline, its two-edge capture, its analysis port. In the agent it is one of up to three sub-components, built unconditionally in both modes, and its analysis port is re-exported as the agent's own port. This chapter does not re-derive the monitor's internals; it shows where the monitor sits inside the container and how the agent surfaces it. For everything about how the monitor samples, refer back to 15.3.
  • You know legal APB, but not the component that generates it. 15.1 told you SETUP→ACCESS, address stability, PREADY completion, PSLVERR at completion. The driver is the component whose run_phase produces exactly that pin sequence, and it does so by pulling transactions from a sequencer through get_next_item/item_done. That handshake — and the sequencer that arbitrates competing sequences onto the driver — is entirely new: it is the mechanism that turns a transaction-level sequence into a protocol-legal pin trace. The driver's legality is policed by the assertions of 15.2; here you build the driver that must pass them.
  • The analysis port had no consumers in 15.3; now the agent is the producer for the env. The monitor broadcasts; the agent re-exposes that broadcast as its own port, and that port is what the scoreboard (15.4) and the functional-coverage collector (15.5) subscribe to. The agent is the seam between the interface and the rest of the environment — the unit the env top connects to. Understanding that fan-out is what makes the agent a system component rather than a bag of parts.

So the model to add is the configurable container: the uvm_agent that constructs sequencer/driver/monitor (or just the monitor) at build time, wires the sequencer to the driver internally, exposes one analysis port outward, and is the reusable unit the environment instantiates once per APB bus.

3. Mental model

The model: the agent is a self-contained workstation for one bus. Picture a single bench bolted to one APB interface. On it sit up to three workers. The sequencer is a dispatcher holding a queue of work orders (transactions) and deciding which order goes next when several sequences compete. The driver is the hands — it takes one order from the dispatcher, performs it on the bus in the protocol-correct sequence (SETUP, then ACCESS, then wait for the slave's "done"), reports the result back, and asks for the next. The monitor is the silent observer at the same bench, watching the pins and writing a transcript of everything that happens — and it is there whether or not the hands are. A single switch on the bench — is_active — decides whether the workstation is staffed to drive (dispatcher + hands + observer) or staffed only to watch (observer alone). Either way, the bench has exactly one outgoing chute — the analysis port — down which every transcript slides to whoever is listening.

Three refinements make it precise and interview-ready:

  • The sequencer arbitrates; the sequence decides; the driver executes. A common confusion is that "the sequencer generates stimulus." It does not — a sequence (a uvm_sequence) generates transactions and sends them to the sequencer, which arbitrates between sequences and hands one transaction at a time to the driver. The driver never knows which sequence produced the item; it just executes whatever the sequencer gives it. Three distinct roles: sequence = author, sequencer = dispatcher, driver = executor.
  • The driver/sequencer handshake is a strict request/response loop. The driver's run_phase is forever: get_next_item(req) → drive req on the pins → wait for PREADY → write back results → item_done. get_next_item blocks until a transaction is available and holds the sequencer's arbitration until item_done is called. Exactly one item_done must answer each get_next_item; miss it and the sequencer never releases, so the driver hangs on its next get_next_item and the whole agent stalls. (get_next_item/item_done is the blocking-arbitration pair; try_next_item/item_done is its non-blocking sibling for idle-driving drivers.)
  • is_active is a build-time decision read from config, not a runtime branch in the driver. At build_phase, the agent reads is_active from uvm_config_db (or its field) and constructs the sequencer and driver only when UVM_ACTIVE. A passive agent does not build them at all — there is no dormant driver waiting to be enabled. The monitor is constructed unconditionally. This is why "a passive agent still needs a driver" is wrong: in passive mode the driver object never exists.
A block diagram of the UVM APB agent: a sequence library feeds the sequencer, which hands items via seq_item_port to seq_item_export to the driver, which drives PSEL, PENABLE, PADDR, PWRITE, PWDATA onto the DUT and samples PREADY, PRDATA, PSLVERR; the monitor reads the same pins and broadcasts reconstructed apb_seq_item objects out of the agent's single analysis port to the scoreboard and coverage collector, with an ACTIVE versus PASSIVE split showing that active builds sequencer, driver, and monitor while passive builds only the monitor, the choice set by is_active from uvm_config_db at build time.
Figure 1 — the UVM APB agent as a configurable container for one interface. On the left, a sequence library feeds transactions to the sequencer, which arbitrates and hands one item at a time to the driver through the seq_item_port to seq_item_export connection; the driver wiggles PSEL, PENABLE, PADDR, PWRITE, PWDATA onto the DUT pins and samples PREADY, PRDATA, and PSLVERR back. The monitor taps the same pins read-only and reconstructs each completed transfer into an apb_seq_item, broadcasting it out of the agent's single analysis port to the scoreboard (15.4) and the coverage collector (15.5). A vertical split contrasts ACTIVE mode (sequencer, driver, and monitor all built, agent drives stimulus) against PASSIVE mode (only the monitor built, agent observes a bus it does not drive), with is_active set from uvm_config_db at build time. The figure stresses that the monitor is present in both modes, the analysis port is the one external seam, and one configurable agent serves one bus.

4. Real SoC implementation

In a real environment the agent is a uvm_agent subclass that constructs its sub-components in build_phase gated on is_active, wires the sequencer to the driver in connect_phase, and re-exports the monitor's analysis port as its own. The driver's run_phase is the protocol-legal request/response loop. Idiomatic and commented:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// apb_agent.sv — the reusable, configurable APB agent.
// ACTIVE: builds sequencer + driver + monitor and drives stimulus.
// PASSIVE: builds only the monitor and observes.
class apb_agent extends uvm_agent;
  `uvm_component_utils(apb_agent)
 
  // is_active is inherited from uvm_agent and defaults to UVM_ACTIVE.
  // It is set from outside via uvm_config_db at build time (see env below).
  apb_sequencer sequencer;   // arbitrates sequences onto the driver (active only)
  apb_driver    driver;      // drives pins, runs the SETUP/ACCESS handshake (active only)
  apb_monitor   monitor;     // passive observer — ALWAYS built, both modes
 
  // The agent re-exports the monitor's analysis port as its own external seam.
  uvm_analysis_port #(apb_seq_item) ap;
 
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
 
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    ap = new("ap", this);
    // Monitor is built unconditionally — observation happens in both modes.
    monitor = apb_monitor::type_id::create("monitor", this);
    // Sequencer + driver are built ONLY when active. In passive mode they
    // do not exist at all — there is no dormant driver to enable later.
    if (get_is_active() == UVM_ACTIVE) begin
      sequencer = apb_sequencer::type_id::create("sequencer", this);
      driver    = apb_driver   ::type_id::create("driver",    this);
    end
  endfunction
 
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    // Expose the monitor's broadcast as the agent's analysis port (both modes).
    monitor.ap.connect(this.ap);
    // Wire the sequencer to the driver ONLY when active: the driver's
    // seq_item_port pulls items from the sequencer's seq_item_export.
    if (get_is_active() == UVM_ACTIVE)
      driver.seq_item_port.connect(sequencer.seq_item_export);
  endfunction
endclass
 
 
// apb_driver.sv — turns transactions into protocol-legal APB pin activity.
class apb_driver extends uvm_driver #(apb_seq_item);
  `uvm_component_utils(apb_driver)
  virtual apb_if vif;   // from config_db
 
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
 
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
      `uvm_fatal(get_type_name(), "no virtual interface set for driver")
  endfunction
 
  task run_phase(uvm_phase phase);
    reset_pins();                              // park the bus idle out of reset
    forever begin
      // ---- 1. REQUEST: block until the sequencer hands us one transaction ----
      seq_item_port.get_next_item(req);        // holds arbitration until item_done
 
      // ---- 2. SETUP phase: assert PSEL, drive address/control, PENABLE low ----
      @(vif.drv_cb);
      vif.drv_cb.psel    <= 1'b1;
      vif.drv_cb.penable <= 1'b0;              // SETUP: PENABLE deasserted
      vif.drv_cb.paddr   <= req.addr;
      vif.drv_cb.pwrite  <= req.is_write;
      if (req.is_write) begin
        vif.drv_cb.pwdata <= req.data;
        vif.drv_cb.pstrb  <= req.strb;
      end
 
      // ---- 3. ACCESS phase: assert PENABLE, then HOLD until PREADY is high ----
      @(vif.drv_cb);
      vif.drv_cb.penable <= 1'b1;              // ACCESS begins
      // Sample PREADY through the clocking block; wait out any wait states.
      // Skipping this wait would race a wait-stated slave (see beat 7).
      do @(vif.drv_cb); while (!vif.drv_cb.pready);
 
      // ---- 4. COMPLETION: capture results at the completing edge ----
      if (!req.is_write) req.data = vif.drv_cb.prdata;   // read data valid only now
      req.slverr = vif.drv_cb.pslverr;                   // PSLVERR valid only now
 
      // ---- 5. De-assert and return to idle ----
      @(vif.drv_cb);
      vif.drv_cb.psel    <= 1'b0;
      vif.drv_cb.penable <= 1'b0;
 
      // ---- 6. RESPONSE: exactly ONE item_done per get_next_item ----
      seq_item_port.item_done();               // release arbitration; rsp fields filled in req
    end
  endtask
endclass

Two facts make this the right shape. First, construction is gated on is_active and the monitor is unconditionalget_is_active() == UVM_ACTIVE guards the sequencer and driver create calls and the seq_item_port/seq_item_export connect, while monitor is built and its port re-exported in every mode. That single guard is what makes one class serve both a stimulus-driving block testbench and an observe-only integration testbench. Second, the driver's loop maps one-to-one onto APB phases and is bracketed by get_next_item/item_done — SETUP drives address/control with PENABLE low, ACCESS asserts PENABLE and then waits on PREADY (the do…while absorbs wait states so the driver never races a slow slave), completion captures PRDATA/PSLVERR, and the single item_done answers the single get_next_item so the sequencer releases arbitration for the next transaction. The legality of those pin moves is what the assertions of 15.2 police; the agent's job is to produce them.

5. Engineering tradeoffs

The agent's design is a set of decisions about what gets built, who drives, and what each sub-component connects to. The first table is the active-versus-passive contract — the single most consequential configuration choice:

AspectACTIVE agent (UVM_ACTIVE)PASSIVE agent (UVM_PASSIVE)
Sequencer built?YesNo (object never created)
Driver built?YesNo (object never created)
Monitor built?YesYes (always)
Drives the bus?Yes — runs sequencesNo — observes only
Sequencer↔driver wired?Yes (seq_item_portseq_item_export)N/A (no driver)
Analysis port exposed?YesYes
Typical useBlock-level: stimulate the DUT slaveIntegration: watch a real master drive the DUT

The second table is the sub-component contract — each part's role and what it connects to:

Sub-componentRoleBuilt inConnects to
SequencerArbitrates competing sequences; hands one item at a time to the driverActive onlyDriver's seq_item_port (via seq_item_export); sequences run on it
DriverPulls items via get_next_item; drives SETUP/ACCESS; waits PREADY; returns via item_doneActive onlySequencer's seq_item_export; the DUT pins (drives)
MonitorPassively reconstructs each transfer into an apb_seq_item (see 15.3)AlwaysThe DUT pins (reads); its ap → agent's ap
Agent apSingle external analysis-port seam re-exporting the monitor's broadcastAlwaysScoreboard (15.4) and coverage (15.5)

The throughline: the monitor and the analysis port are mode-independent and always present; the sequencer and driver are the part that appears only in active mode. The most consequential single decision is making is_active a build-time config knob rather than a hard-coded choice — that is what lets one agent class be reused as a driver in a block testbench and an observer in an integration testbench. The second is keeping the analysis port the only external seam, so the environment connects to the agent, never to its internals.

6. Common RTL mistakes

7. Debugging scenario

The signature agent failure is a driver/sequencer handshake imbalance: a get_next_item without a matching item_done (or the driver completing a transfer before sampling PREADY), so the sequencer never releases arbitration and the simulation hangs — or worse, transactions silently race a wait-stated slave and post wrong data.

  • Observed symptom: the test starts, the first one or two transactions drive correctly, then the simulation hangs — no further bus activity, the sequence never advances past its second start_item/finish_item, and the run eventually times out with the driver blocked. No assertion fires; nothing reports an error. In a variant, the test does not hang but the scoreboard reports read-data mismatches that correlate with wait-stated transfers.
  • Waveform clue: in the hang case, the bus shows exactly one completed transfer and then PSEL stuck high (or parked) with no new SETUP — the driver is blocked inside get_next_item waiting for an item the sequencer will not release because the previous one was never acknowledged. In the data-mismatch variant, the waveform shows the driver de-asserting PENABLE on the first ACCESS cycle even though PREADY was low — it completed the transfer during a wait state, sampling PRDATA one edge before the slave drove it.
  • Root cause: the driver's run_phase either returned to the top of the forever loop without calling item_done (so get_next_item on the next iteration blocks forever — the sequencer is still holding arbitration for the un-acknowledged item), or it drove the ACCESS phase and immediately captured/completed without the do…while PREADY wait, racing a slave that inserts wait states. Both are handshake-discipline bugs: one breaks the request/response count, the other breaks the request/response timing.
  • Correct RTL (corrected driver loop): bracket every transfer with exactly one get_next_item/item_done, and never complete ACCESS until PREADY is sampled high:
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
task run_phase(uvm_phase phase);
  forever begin
    seq_item_port.get_next_item(req);          // exactly one get per transfer
    drive_setup(req);                          // SETUP: PSEL=1, PENABLE=0, addr/ctrl
    @(vif.drv_cb); vif.drv_cb.penable <= 1'b1; // ACCESS begins
    do @(vif.drv_cb); while (!vif.drv_cb.pready); // HOLD until slave is ready
    if (!req.is_write) req.data = vif.drv_cb.prdata; // capture only at completion
    req.slverr = vif.drv_cb.pslverr;
    idle_bus();
    seq_item_port.item_done();                 // exactly one done per get — release
  end
endtask
  • Verification assertion: beyond fixing the loop, add a handshake-balance check and a protocol check. A simulation-level assertion (or a UVM uvm_objection/heartbeat) that flags if get_next_item count diverges from item_done count catches the count bug; and the APB ACCESS-completion assertion from 15.2PENABLE must remain high until PREADY is sampled high — catches the timing bug at the pins, turning a silent data race into a loud failure.
  • Debug habit: when an agent hangs, suspect the handshake before the bus — count get_next_item against item_done and look for the driver blocked on a get. When data is wrong on wait-stated transfers, suspect the PREADY wait — confirm the driver holds ACCESS until PREADY is high before capturing. A hang is almost always a missing/double item_done; a wait-state-correlated mismatch is almost always a missing PREADY wait. Both live in the driver loop, not the DUT.
A comparison of the driver/sequencer handshake: the top red case shows get_next_item without a matching item_done, so the sequencer never releases arbitration and the next get_next_item hangs, plus a path where the driver completes ACCESS without waiting for PREADY and captures stale data; the bottom green case shows exactly one get_next_item and one item_done per transfer with a do-while PREADY wait before capturing PRDATA and PSLVERR, so transactions flow and data is captured at completion.
Figure 2 — the driver/sequencer handshake done wrong versus right. Top (red, the bug): the driver calls get_next_item, drives the transfer, but returns to the top of its forever loop WITHOUT item_done — so the sequencer keeps holding arbitration for the un-acknowledged item and the next get_next_item blocks forever; the bus parks after one transfer and the simulation hangs with no error. A second red path shows the driver completing ACCESS on the first cycle without waiting for PREADY, racing a wait-stated slave and capturing stale PRDATA. Bottom (green, correct): each transfer is bracketed by exactly one get_next_item and one item_done, and ACCESS is held with a do-while loop until PREADY is sampled high before PRDATA and PSLVERR are captured — so the sequencer releases arbitration, the next item flows, and read data is captured only at the completing edge. The figure teaches that a hang is a handshake-count bug (missing item_done) and a wait-state data mismatch is a handshake-timing bug (missing PREADY wait).

8. Verification perspective

The agent is the reusable unit the whole environment is built from, so its reuse story — the same class serving as a stimulus driver in one testbench and a silent observer in another — is itself a verification deliverable, and the analysis-port fan-out is the seam that makes that reuse pay off.

  • Passive reuse is the agent's highest-value property at integration. A block-level testbench instantiates the APB agent active to drive the DUT slave directly. When that block is integrated into a subsystem where a real master (another DUT, or a different agent) drives the same bus, you reuse the identical agent class — just configured passive via uvm_config_db — to observe the real traffic, reconstruct transactions, and feed the same scoreboard and coverage you used at block level. Nothing about the monitor changes; only the build-time is_active flips. This is why is_active is config-driven, not hard-coded: it is the switch that lets one verified agent move from block to integration without a rewrite.
  • The analysis port fans out to scoreboard and coverage independently. The agent exposes one ap; the env connects it to both the scoreboard (15.4) and the coverage collector (15.5) — each subscribes its own export, and each receives an independent copy of every reconstructed transfer. The agent neither knows nor cares how many subscribers exist; adding a logger or a second coverage model is a connect-time edit in the env, not a change to the agent. This decoupling is what keeps the agent a stable, reusable component while the env around it evolves.
  • uvm_config_db drives the whole topology, including active/passive and the virtual interface. Two things are configured into the agent at build: the virtual interface handle (so the driver and monitor know which physical bus to sample/drive) and is_active. Both come through uvm_config_db::set from the env or test, keyed to the agent's path. Getting the config wiring right is a verification concern in itself — a mis-set vif gives a uvm_fatal (loud, easy), but a mis-set is_active (passive when you meant active) gives an agent that silently never drives (quiet, dangerous), so the test must assert that an active agent actually produced traffic.
  • A virtual sequencer coordinates multiple agents when one bus is not enough. When a testbench has several APB agents (e.g. one per interface) or mixes APB with another protocol, a virtual sequencer holds handles to each agent's sequencer and runs a virtual sequence that orchestrates stimulus across them — e.g. "configure peripheral A, then issue a transfer on bus B." The individual agents stay reusable and protocol-local; the cross-bus intent lives in the virtual sequence, not inside any agent. This keeps each agent a clean, single-interface unit while still allowing coordinated multi-agent scenarios.

The point: you exploit the agent by reusing it passive at integration, fanning its one analysis port out to scoreboard and coverage, driving its topology (active/passive + vif) from uvm_config_db, and coordinating multiple agents with a virtual sequencer — because the agent's whole value is being the one configurable, reusable atom of APB verification that every testbench instantiates and connects to identically.

9. Interview discussion

"Walk me through a UVM agent for APB" is a critical verification question, and the answer that signals real experience moves from container to active/passive to the driver handshake.

Lead with the container: the agent is a uvm_agent that packages a sequencer, a driver, and a monitor for one interface, built in build_phase and wired in connect_phase, exposing one analysis port outward. Then the configuration axis: is_active (read from uvm_config_db at build) decides the topology — UVM_ACTIVE constructs sequencer + driver + monitor and drives stimulus; UVM_PASSIVE constructs only the monitor and observes — and the monitor is built unconditionally in both. Make the handshake explicit, because it is where depth shows: the driver's run_phase is forever: get_next_item(req) → drive SETUP then ACCESS → wait on PREADY → capture PRDATA/PSLVERR → item_done, with exactly one item_done per get_next_item or the sequencer never releases arbitration and the agent hangs. Then separate the three roles cleanly: the sequence authors transactions, the sequencer arbitrates between competing sequences, the driver executes — the sequencer does not generate stimulus. The depth flourishes that distinguish a strong answer: the connect-phase wiringdriver.seq_item_port.connect(sequencer.seq_item_export) for stimulus and monitor.ap.connect(agent.ap) to re-export the broadcast; passive reuse — the same agent class drives a DUT at block level and observes a real master at integration with only is_active flipped; and the killer closer: "a hang is almost always a missing or doubled item_done, and a wait-state-correlated data mismatch is almost always a missing PREADY wait in the driver — both are handshake-discipline bugs in the agent, not the DUT." That last line shows you have actually debugged a live agent, not just memorised its diagram.

10. Practice

  1. Gate the build. Write the build_phase that constructs the monitor unconditionally and the sequencer/driver only when get_is_active() == UVM_ACTIVE, and the connect_phase that wires them. State what is absent in passive mode.
  2. Trace the handshake. For one write transfer with two wait states, list each step of the driver loop from get_next_item to item_done, naming which signal moves on which edge and where the PREADY wait sits.
  3. Active vs passive. Given a block testbench that drives the DUT and an integration testbench that watches a real master, say how each instantiates the same agent class and which sub-components exist in each.
  4. Config the topology. Write the uvm_config_db::set calls from the env that give one agent its virtual interface and make it active, and a second agent its interface and make it passive. Explain why a mis-set is_active is more dangerous than a mis-set vif.
  5. Sequencer vs sequence. Explain, with one sentence each, the distinct roles of the sequence, the sequencer, and the driver — and why "the sequencer generates stimulus" is wrong.

11. Q&A

12. Key takeaways

  • The APB agent is a uvm_agent container that packages a sequencer, a driver, and a monitor for one interface — the reusable, configurable atom of APB verification, instantiated once per bus and connected to the env through one analysis port.
  • is_active decides the topology at build time: UVM_ACTIVE builds sequencer + driver + monitor and drives stimulus; UVM_PASSIVE builds only the monitor and observes — the monitor is always built, the driver and sequencer only in active mode, and the choice comes from uvm_config_db, never hard-coded.
  • The driver/sequencer handshake is a strict request/response loopget_next_item → drive SETUP/ACCESS → wait PREADY → item_done, with exactly one item_done per get_next_item; miss it and the agent hangs, skip the PREADY wait and it races a wait-stated slave.
  • Three distinct roles: the sequence authors transactions, the sequencer arbitrates between competing sequences, the driver executes — the sequencer does not generate stimulus, and the monitor (see 15.3) is a passive sub-component independent of the driver.
  • The agent re-exports the monitor's analysis port as its single external seammonitor.ap.connect(agent.ap) — and the env fans that one port out to the scoreboard (15.4) and coverage (15.5), each an independent subscriber.
  • Passive reuse is the payoff: the same agent class drives a DUT at block level and observes a real master at integration with only is_active flipped — so its block-level scoreboard and coverage carry into the subsystem unchanged, while a virtual sequencer coordinates multiple agents when one bus is not enough.