01 · Reusable infrastructure
Dynamic Reconfiguration & Polymorphic Testbenches
Keep one stable verification environment while swapping protocol implementations, lane-width variants, software models, or emulator transactors behind a deliberate abstract contract.
A common agent contract owns intent and analysis data; factory-selected concrete implementations adapt protocol, lane width, software model, or emulation transport before build creates them.
override before factory create · concrete adapters preserve canonical transaction semantics
Why it matters
- One IP can support several protocols, such as a SerDes configured for PCIe, SATA, or USB.
- The same design can have structural variants, such as 4-lane and 16-lane configurations.
- A polymorphic testbench keeps tests and environment connections stable while changing Driver, Monitor, model, or transactor implementation.
What is difficult
- A factory override can replace a PCIe_Driver with a SATA_Driver, but their virtual interfaces may have different signals and protocol semantics.
- Every implementation must satisfy a stable base API while still reaching its protocol-specific interface.
- Factory overrides must be installed before the requested component is instantiated.
- Excessive runtime substitution can hide which class is executing and make the environment difficult for a new engineer to debug.
Failure signatures
- An override is installed after creation and has no effect.
- The environment calls new() directly and bypasses the factory.
- The selected driver cannot use the configured virtual-interface type.
- A protocol_t topology setting disagrees with the factory-selected implementation.
- A generic transaction cannot be translated into required protocol-specific signal behavior.
- The source visible in the environment differs from the runtime class and obscures debug ownership.
Two viable approaches—and their cost
UVM factory overrides
Use set_type_override_by_type to replace a base component with an extended implementation.
- Clean, standard UVM selection at the test level.
- Leaves the reusable environment unchanged.
- All replacements must share an assignment-compatible base class.
- Factory substitution does not solve signal-level interface differences.
Abstract or facade class
Give the environment an abstract-driver contract and provide the concrete implementation through factory/configuration policy.
- Decouples the environment from physical signaling.
- Can hide an SV driver, C++ DPI model, or Palladium emulator transactor behind one API.
- Needs a bridge from generic UVM transactions to protocol-specific operations.
- The shared API can become too broad if every protocol-specific exception leaks into it.
Interview answer, built from the mechanism
- For a multi-protocol IP, I define abstract base Drivers and Monitors that specify what the environment can request and observe. Derived classes implement how PCIe, SATA, USB, or an emulator transactor performs that work.
- The test installs a UVM factory type override before the environment creates the base component. A typed configuration object carries protocol_t and the topology or interface information needed by that implementation.
- This lets the same test scenario and environment connection logic run against different protocol and lane-width variants while keeping physical signaling behind a bridge or facade.
- I also print or record the resolved factory type and topology because runtime polymorphism must remain observable during debug.
Component responsibility contract
| Component | Responsibility | Required change |
|---|---|---|
| Base Driver | Interface definition | Create an abstract class with a pure virtual drive_bus() task. |
| Test | Selector | Install the factory type override before the target build-phase creation. |
| Config Object | Topology map | Add a protocol_t enum field that guides build and interface policy. |
Implementation patterns
// --- The Abstract Base ---
virtual class base_driver extends uvm_driver #(trans);
pure virtual task drive_bus(trans tr);
endclass
// --- Specialized Implementation ---
class pcie_driver extends base_driver;
`uvm_component_utils(pcie_driver)
virtual task drive_bus(trans tr);
/* PCIe Handshake */
endtask
endclass
// --- The Test (Swapping implementation) ---
class pcie_test extends base_test;
virtual function void build_phase(uvm_phase phase);
// Replace generic base with specific PCIe version
set_type_override_by_type(
base_driver::get_type(),
pcie_driver::get_type()
);
super.build_phase(phase);
endfunction
endclassThe environment requests base_driver. The test changes the factory resolution before super.build_phase() creates the environment, while pcie_driver supplies the protocol-specific handshake.
Stress recipe
- Run one unchanged test with the base request resolved to pcie_driver.
- Select a SATA or USB implementation through the same base API and confirm that environment connections do not change.
- Repeat across 4-lane and 16-lane topology settings and verify protocol_t matches the bound interface.
- Install an override after creation in a negative test and prove that the existing component does not change.
- Exercise a protocol-specific signal requirement to verify that the wrapper, bridge, or typed VIF reaches the selected agent.
- Perform a mid-test behavior change only through an explicit Mux or Proxy and check that ownership remains deterministic.
Follow-up questions
How do you handle different interfaces for different drivers?
Use a wrapper interface containing the required signal families, or pass the specific typed virtual interface through the factory-overridden agent. Keep protocol translation behind the facade rather than exposing it throughout the environment.
Can you override a component after build_phase?
No factory override can replace an instance that already exists. Install the override before creation. Use a Mux component or Proxy pattern when behavior truly must switch during a test.
What is the risk of excessive polymorphism?
The testbench becomes a black box: the class visible in the environment source is not the class executing at runtime, making ownership and debug harder to follow.
