Skip to guide

Reusable infrastructure

Dynamic Reconfiguration & Polymorphic Testbenches

01 of 2 · Scalable Verification Infrastructure

Keep one stable verification environment while swapping protocol implementations, lane-width variants, software models, or emulator transactors behind a deliberate abstract contract.

Trace the mechanism from failure signature through independent evidence, component ownership, stress, and recovery.

Native mechanism diagram2 implementation tradeoffs3 interview follow-ups
One architecture, many systemsOne reusable contract across 1 cases.
  1. 01INTENTKeep scenarios independent of one physical interface
  2. 02BINDResolve topology, protocol, and implementation policy
  3. 03OBSERVEAggregate identity-preserving evidence
  4. 04REUSERun across simulation, acceleration, and new systems

Polymorphic TB

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.

Mechanism · failure · evidence · recovery
Stable contract, swappable implementationReuse comes from an honest abstraction boundary.

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.

Stable environment contractvirtual driver API + canonical transactionconfigure before create()
AXI agent64-bit RTL interfaceCHI agentflit-based interfaceDPI modelshared C implementationEmulation proxyaccelerated transactor
Invariantanalysis schemaVarianttransport + timingGuardrailcompatible interface type

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.

Strengths
  • Clean, standard UVM selection at the test level.
  • Leaves the reusable environment unchanged.
Costs
  • 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.

Strengths
  • Decouples the environment from physical signaling.
  • Can hide an SV driver, C++ DPI model, or Palladium emulator transactor behind one API.
Costs
  • 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

  1. 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.
  2. 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.
  3. 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.
  4. I also print or record the resolved factory type and topology because runtime polymorphism must remain observable during debug.

Component responsibility contract

Component responsibilities and required verification changes for Dynamic Reconfiguration & Polymorphic Testbenches
ComponentResponsibilityRequired change
Base DriverInterface definitionCreate an abstract class with a pure virtual drive_bus() task.
TestSelectorInstall the factory type override before the target build-phase creation.
Config ObjectTopology mapAdd a protocol_t enum field that guides build and interface policy.

Implementation patterns

Base contract and PCIe overridesystemverilog
// --- 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
endclass

The 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

  1. Run one unchanged test with the base request resolved to pcie_driver.
  2. Select a SATA or USB implementation through the same base API and confirm that environment connections do not change.
  3. Repeat across 4-lane and 16-lane topology settings and verify protocol_t matches the bound interface.
  4. Install an override after creation in a negative test and prove that the existing component does not change.
  5. Exercise a protocol-specific signal requirement to verify that the wrapper, bridge, or typed VIF reaches the selected agent.
  6. 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.