Skip to guide

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.

Watercolor of a verification workstation with waveform traces and a development board.
Trace the mechanism from failure signature through independent evidence, component ownership, stress, and recovery.

Mechanism and evidence

Guided verification lab

Choose the implementation before creating the instance.

Compare the factory’s selected policy with the component that has already been instantiated.

STEP THROUGH
STATE & OBSERVATIONPCIe driver / 1 of 4
FUTURE CREATION POLICYbase

↓ Applies at the factory boundary

FACTORY BOUNDARYnot yet

↓ Creates an instance

EXISTING INSTANCEnone

The base contract stays stable; create() chooses the implementation.

FUTURE CREATION POLICYbaseFACTORY BOUNDARYnot yetEXISTING INSTANCEnoneThe base contract stays stable; create() chooses the implementation.

Checkpoint 1 · Declare a stable driver contract.

BASE CONTRACT
base
FACTORY TYPE
base
CREATE
not yet
INSTANCE
none
Compare all 4 checkpoints
  1. 1 · Declare a stable driver contract.

    BASE CONTRACT
    base
    FACTORY TYPE
    base
    CREATE
    not yet
    INSTANCE
    none
  2. 2 · Select PCIe before creation.

    BASE CONTRACT
    base
    FACTORY TYPE
    PCIe
    CREATE
    not yet
    INSTANCE
    none
  3. 3 · Create the PCIe instance.

    BASE CONTRACT
    base
    FACTORY TYPE
    PCIe
    CREATE
    create()
    INSTANCE
    PCIe
  4. 4 · A later override leaves the instance intact.

    BASE CONTRACT
    base
    FACTORY TYPE
    other
    CREATE
    already done
    INSTANCE
    PCIe
1234BASE CONTRACTbasebasebasebaseFACTORY TYPEbasePCIePCIeotherCREATEnot yetnot yetcreate()already doneINSTANCEnonenonePCIePCIe
Inspect the contract

Declare a stable driver contract.

Factory
Base
Instance
None

The agent creates components through the base type. The implementation has not yet been selected.

Evidence to inspect

Inspect the factory creation path and base-driver interface.

Checkpoint 1 / 4
Inspect the checker Selected checkpoint pseudocode
Conceptual checker / selected checkpoint
base_driver driver;
// No instance exists until create().
Example contract & limitations

Example contract. The scenario expresses a shared base-driver contract; each protocol still needs a compatible virtual interface and protocol-specific checks. A factory override cannot repair an incompatible interface. Checkpoints show selected state changes, not equally spaced simulation cycles.

Read the complete walkthrough

PCIe driver

  1. Declare a stable driver contract.. The agent creates components through the base type. The implementation has not yet been selected. Evidence: Inspect the factory creation path and base-driver interface.
  2. Select PCIe before creation.. Install the PCIe override before the factory creates this component. The override changes future creation policy. Evidence: Check that the selected type and virtual interface belong to the same protocol.
  3. Create the PCIe instance.. The factory resolves the override at create(). The instance now has PCIe behavior. Evidence: Record the instantiated runtime type, not only the configured override.
  4. A later override leaves the instance intact.. Changing the factory afterward affects later create() calls. It does not replace or mutate an existing driver. Evidence: Compare the current instance type before and after the late override.

SATA driver

  1. Declare a stable driver contract.. The agent creates components through the base type. The implementation has not yet been selected. Evidence: Inspect the factory creation path and base-driver interface.
  2. Select SATA before creation.. Install the SATA override before the factory creates this component. The override changes future creation policy. Evidence: Check that the selected type and virtual interface belong to the same protocol.
  3. Create the SATA instance.. The factory resolves the override at create(). The instance now has SATA behavior. Evidence: Record the instantiated runtime type, not only the configured override.
  4. A later override leaves the instance intact.. Changing the factory afterward affects later create() calls. It does not replace or mutate an existing driver. Evidence: Compare the current instance type before and after the late override.

USB driver

  1. Declare a stable driver contract.. The agent creates components through the base type. The implementation has not yet been selected. Evidence: Inspect the factory creation path and base-driver interface.
  2. Select USB before creation.. Install the USB override before the factory creates this component. The override changes future creation policy. Evidence: Check that the selected type and virtual interface belong to the same protocol.
  3. Create the USB instance.. The factory resolves the override at create(). The instance now has USB behavior. Evidence: Record the instantiated runtime type, not only the configured override.
  4. A later override leaves the instance intact.. Changing the factory afterward affects later create() calls. It does not replace or mutate an existing driver. Evidence: Compare the current instance type before and after the late override.
Related implementation: Base contract and PCIe override
// --- 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.

PCIe driver, checkpoint 1: Declare a stable driver contract.. Inspect the contract.
Understand the failure
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

Stable environment, replaceable implementation

The test selects a concrete implementation while the environment depends only on the base contract and typed topology policy.

Reusable test
Expresses protocol-neutral scenarios and installs the desired override.
Factory resolver
Maps the requested base_driver type to the selected implementation before creation.
Abstract base API
Defines drive_bus(trans) as the stable environment contract.
Protocol bridge
Translates generic transactions into PCIe, SATA, USB, DPI, or emulator operations.
Concrete interface
Carries the selected protocol signals and 4-lane or 16-lane topology.
  1. Test policy -> factory override
  2. Environment create(base_driver) -> concrete resolved type
  3. Generic transaction -> abstract drive_bus()
  4. Bridge -> protocol-specific virtual interface
  5. protocol_t topology -> compatible implementation and lane map

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.
Compare approaches

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.
Explain it in an interview

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.
Assign responsibilities

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.
Build the checker

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 the design

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.

Engineering qualifications