UVM
UVM Reuse Checklist
The condensed sign-off list for whether a UVM agent or environment is truly reusable — configuration-driven behavior, active/passive capability, factory construction, freedom from environmental assumptions, monitor purity, and clean packaging — the difference between IP that composes and a one-off that does not.
UVM Design Review Checklist · Module 31 · Page 31.2
The architecture checklist confirmed an environment is built correctly. This one confirms it can be reused — that an agent drops into a new environment or integration level unchanged, the whole point of building agents instead of directed tests. Reuse is a design discipline, not a feature, and it is easy to lose one item at a time: a hardcoded path here, a check baked into a monitor there, and an agent that worked at block level cannot be composed at the SoC. This checklist is the pass that catches those losses — configuration-driven behavior, active/passive capability, factory construction, no environmental assumptions, monitor purity, and clean packaging — before an agent is declared reusable IP.
1. Why a Reuse Checklist: Reuse Is Easy to Lose
You have learned why reuse matters — the same agent active at block level and passive at SoC level, composed into systems without rewriting. A reuse checklist ensures each agent actually has the properties reuse requires, because reuse degrades silently: an agent keeps working in its own environment while quietly accumulating assumptions that make it un-composable elsewhere. The hardcoded interface path, the fixed is_active, the check wired into the monitor, the new instead of create — each works locally and fails only when someone tries to reuse the agent at a new integration level. The checklist catches these reuse-killing assumptions at design time, when the cost is a small edit, rather than at SoC integration, when the cost is a rebuild.
2. Configuration-Driven Behavior
The first category is whether everything that varies comes from configuration — the items that keep an agent adaptable without edits.
- Every variable aspect comes from a config object, not a hardcoded constant. The virtual interface, options like data width or address map, timing parameters, and counts must be configurable; a hardcoded value ties the agent to one context.
- The config object is delivered as one unit, not many individual sets. A bundled, possibly nested config is cohesive and composes hierarchically, which a scatter of individual
config_dbsets does not. - No magic numbers in the agent body. Counts, sizes, and addresses come from config, so the same agent serves a different block by supplying a different config.
- The config is read in
build_phaseand used to decide what to build. The agent configures itself from the config it gets, including how many sub-components or which mode — driven, not fixed.
3. Active / Passive Capability
The second category is whether the agent can both drive and observe — the property that makes reuse across integration levels real.
- The agent builds the monitor unconditionally and the sequencer and driver only when
UVM_ACTIVE(get_is_active()). The monitor is always wanted; the stimulus half is built only when the agent drives. - The driver-to-sequencer connection is guarded by the same
is_activecheck. A passive agent has no driver or sequencer, so an unguarded connect is a null-handle crash. is_activeis set by configuration, not fixed in the agent. The integration-level config chooses the role, so the same agent is active at block level and passive at SoC level by configuration alone.- A passive instance drives nothing. Confirm a passive agent only observes — never asserts a bus signal — so it can sit on an RTL-driven bus without contention.
4. Factory Construction
The third category is whether the agent and its item are overridable — the property that lets a test specialize it without edits.
- Every component is created with
type_id::create, nevernew. Factory creation is what lets a test override the driver, monitor, or any component for a specific scenario; anewsilently breaks that. - The transaction item is created via the factory too. So a test can substitute an error-injecting or extended item without touching the agent or its sequences.
- Every class is registered with the utils macro. Registration gives the
type_idthe factory needs to create and override; without it overrides cannot target the type. - Parameterized components register and override by the exact specialization. A parameterized class is a distinct type per parameter value, so registration and any override must name the specialization, or the override silently no-ops.
5. No Environmental Assumptions
The fourth category is whether the agent is free of dependencies on its surroundings — the items that let it be placed anywhere.
- No absolute hierarchical paths. The agent does not reach up or across the tree by absolute path; it uses relative or wildcarded
config_dbscoping so it works wherever it is instantiated. - No assumption about its parent or position. The agent does not assume a particular parent type or a fixed depth in the hierarchy; it functions identically at block and SoC level.
- No reach into sibling components. The agent communicates through its config, its analysis port, and its sequencer — not by directly referencing other components, which would couple it to one environment.
- No hardcoded
config_dbfield names tied to one environment's naming. The field-name contract is the agent's own, documented, and stable, so a new environment supplies config the agent expects.
6. Monitor Purity and Packaging
The fifth and sixth categories are keeping the monitor reusable and shipping the agent as a unit.
- The monitor observes and broadcasts only — no protocol checks baked in. A monitor with block-specific checks drags assumptions into every reuse; keep checking in the scoreboard so a passive SoC-level monitor stays pure and reusable.
- The monitor reconstructs from the pins, not from the driver's item. Independence is what lets the monitor work in a passive agent that has no driver, and what makes its observations trustworthy.
- The agent ships as a self-contained package — the agent, its config, its transaction item, and its sequence library — with no dependencies on the surrounding environment.
- The configuration contract is documented. The consuming team learns what config fields exist and what they mean, sets the config, and uses the agent — without reaching inside it.
- A sequence library ships with the agent. Consumers get the stimulus vocabulary (base, atomic, and compound sequences), not just the plumbing, so reuse includes how to drive it.
7. Common Misconceptions
8. Sign-off Insight
9. Interview Questions
A reusable agent is one whose behavior is entirely configuration-driven, that can run active or passive, that constructs through the factory, that makes no assumptions about its environment, and whose monitor is pure — and you check it by asking whether the same agent could drop into a new environment by supplying only a new config. Configuration-driven means every variable aspect — the virtual interface, options, counts, the role — comes from a config object rather than a hardcoded constant, so a new context is a new config, not an edit. Active/passive capability means the agent builds the monitor always and the sequencer and driver only when active, guarded on is_active, so it drives at block level and observes at SoC level by one field; an agent that cannot be passive cannot be reused on an RTL-driven bus. Factory construction means every component and the item are created via type_id::create and registered, so a test can override them without touching the agent. No environmental assumptions means no absolute paths, no assumed parent, no reach into siblings — relative scoping so it works wherever placed. Monitor purity means the monitor observes and broadcasts without baked-in checks, so it stays reusable passive. To check, you go category by category, but the unifying test is the thought experiment: could this agent be instantiated in a different environment, given only a different config object, with no source change? Every place the answer is no — a hardcoded path, a fixed is_active, a check in the monitor, a new instead of create — is a reuse-killing assumption. The understanding to convey is the properties of reusable IP and the drop-in-with-only-a-config test, which is how you distinguish an agent built for reuse from a one-off that happens to work.
Because reuse across integration levels requires the same agent to drive at one level and only observe at another, and active/passive is exactly that capability — at block level the testbench is the master so the agent is active and generates stimulus, while at SoC level the real on-chip master drives the bus so the agent must be passive and only observe, or it would contend with the RTL. Without active/passive, an agent is locked to one role: an always-active agent cannot sit on a bus the real master drives, because two drivers would fight and the bus would resolve to X; an always-passive agent cannot generate stimulus at block level. So an agent that cannot be passive simply cannot be reused at the SoC, which is the most important reuse target, since the whole point of building block-level agents is to compose them into the system. The mechanism is one configuration field: the agent builds the monitor unconditionally and the sequencer and driver only when is_active is UVM_ACTIVE, and guards the driver-sequencer connection on the same check, so flipping the field flips the role with no code change. The checklist confirms both the conditional build and the guarded connect, and that is_active comes from config rather than being fixed. This is why active/passive is designed into the agent from the start rather than added later — it is the property that makes the same agent serve block and system verification. The understanding to convey is that active/passive enables the drive-here-observe-there reuse across integration levels, and that its absence makes SoC reuse impossible, which is why it is the core reuse property rather than one feature among many.
The reuse-killers are hardcoded interface or config paths, a fixed active/passive role, checks baked into the monitor, magic numbers in the agent body, construction with new instead of the factory, and any reach into a specific parent or sibling — and they are hard to catch because each one works perfectly in the agent's own environment and fails only when someone tries to reuse it elsewhere. A hardcoded config-db path ties the agent to one hierarchy location, so it works where it was written and breaks when instantiated at a different depth or under a different parent. A fixed is_active locks the role, so the agent cannot go passive at SoC level. A check baked into the monitor carries block-specific assumptions into every reuse, so a passive SoC instance drags along expectations that no longer hold. A new instead of create silently breaks overrides. Magic numbers tie behavior to one block's parameters. Each of these is invisible in the original environment because the original environment is exactly the one the assumption was made for — the path is right, the role is right, the check is right, there. The failure only appears at reuse, at a new integration level or a new block, which is often months later and a different engineer, when the cost to fix is a rewrite rather than an edit. That delayed, displaced failure is why a checklist is the right tool: it catches the assumption at design time, with the thought-experiment test of whether the agent could be dropped in with only a new config, rather than waiting for integration to expose it. The understanding to convey is the catalog of reuse-killers and that their danger is local-success-with-remote-failure, which is precisely what makes a design-time checklist valuable.
The monitor must stay pure — observing and broadcasting only, with no protocol checks baked in — because a monitor with checking embedded carries block-specific assumptions that break when the agent is reused passive at a different integration level, whereas a pure monitor that just reconstructs transactions and writes them to its analysis port is reusable anywhere. At block level you might be tempted to put checks directly in the monitor, since it sees every transaction. But those checks encode what is correct for this block in this context, and when the same agent is reused passive at the SoC, the context differs — the surrounding behavior, the expected results, the configuration — so the baked-in checks may no longer hold and the monitor is no longer a clean observer. Keeping the monitor pure and putting checking in the scoreboard separates concerns: the monitor's job is to observe reality and report it, identically in every reuse, and the scoreboard's job is to check, which can be reconfigured or replaced per integration level without touching the monitor. This is also what lets the monitor work in a passive agent that has no driver and no notion of intended behavior — it just watches and reports. Independence reinforces this: the monitor reconstructs from the pins rather than the driver's item, so it observes what actually happened, which is both reusable and what makes the check meaningful. The understanding to convey is that a pure, checking-free monitor is reusable across contexts while an embedded-check monitor is not, and that separating observation from checking is what preserves reuse and lets the checking strategy evolve per integration level.
You package the agent as a self-contained unit — the agent class, its config object, its transaction item, and its sequence library — inside one package, with a documented configuration contract and no dependencies on the surrounding environment, so the consuming team instantiates it, sets a config, and uses it without reaching inside. The package boundary matters: everything the agent needs is inside it, and everything it exposes is through well-defined points — the config object you set, the analysis port you subscribe to, the sequencer you start sequences on. The configuration contract is the interface the consuming team programs against: they learn what fields the config has and what they mean, set the config in their environment, and the agent configures itself; they never edit the agent. The transaction item lives in the package so consumers write sequences and connect scoreboards against the same type. The sequence library ships with the agent so consumers get the stimulus vocabulary — base, atomic, and compound sequences — not just the plumbing. Crucially the agent makes no assumptions about who instantiates it: no absolute paths, no assumed parent, relative or wildcarded scoping, so it works wherever placed. The contrast is a loose set of files with environment-specific assumptions, which forces the consuming team to untangle dependencies and adapt the code — that is not reuse. The understanding to convey is that reusable IP is a product with an interface — a package boundary, a documented config contract, a shipped item and sequence library, and zero environmental assumptions — so another team drops it in and uses it, which is the actual meaning of reusable verification IP.
10. Summary
The UVM reuse checklist confirms an agent has the properties reuse requires, because reuse degrades silently — an agent keeps working in its own environment while accumulating assumptions that make it un-composable elsewhere. It is organized into six categories. Configuration-driven behavior: every variable aspect from a config object, no magic numbers. Active/passive capability: monitor always, driver/sequencer only when active, role from config — the core reuse property, since an agent that cannot be passive cannot sit on an RTL-driven bus. Factory construction: every component and item via type_id::create, registered, so a test can override. No environmental assumptions: relative scoping, no absolute paths, no assumed parent or sibling reach. Monitor purity: observe and broadcast only, reconstruct from pins, check in the scoreboard. Packaging: a package boundary, a documented config contract, and a shipped sequence library.
The disciplines: run it before declaring an agent IP and again before SoC integration (where un-composable assumptions surface); check the highest-impact items explicitly (active/passive and hardcoded paths are the most common reuse-killers); and apply the drop-in test — could this agent be reused by supplying only a new config? Every failed item is a reuse-killing assumption to remove now, while it is a small edit rather than an integration-time rewrite. Reuse is a design discipline, not a feature, and the checklist is how that discipline is verified rather than assumed.
11. What Comes Next
You can now sign off a testbench's structure and its reusability; next, signing off what it has actually exercised:
Next — Coverage Checklist: the architecture and reuse checklists confirm the environment is built and composable; the coverage checklist confirms it measures the right things — a coverage model that maps to the verification plan, meaningful bins and crosses that do not explode, a sampling strategy, and the closure discipline that turns running tests into knowing what was verified.