Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations18
  • UVM Phases & Simulation Lifecycle94
  • Factory & Configuration37
  • Transaction-Level Modeling (TLM)42
  • UVM Sequences & Stimulus44
  • UVM Agents & Protocol VIP71
  • Environment & Test Classes55
    • Environment Architecture
    • Environment Composition Hub: Scaling Reusable Env Topologies
    • Scoreboard + Coverage Hub: Integrated Checking Pipelines
    • Tests: Base Classes, Selection and Startup
      • Base Test Pattern: One Contract for Every Scenario
      • Scenario Inheritance: Specialize Intent, Not Infrastructure
      • Factory Overrides in Tests: Explicit, Local, Reviewable
      • Shared run_phase: One End-of-Test Policy for Every Test
      • +UVM_TESTNAME and Plusargs: Runtime Test Selection
      • Top Module and run_test Startup
      • Designing a Test Library
      • Test Library Organization: Scalable Structure and Naming
      • Test Base Debug: Symptom-First Hierarchy Triage
    • Config Distribution Hub: Test-to-Env Policy Propagation
    • Virtual Sequencer Hub: Env-Level Multi-Agent Coordination
    • Plusargs & Test Selection Hub: Runtime Control Without Recompile
    • End-of-Test Hub: Objections, Readiness, and Fail-Fast Shutdown
    • Top Module Hub: Startup Contracts from HDL to UVM
  • Checking & Coverage in UVM37
  • UVM Register Abstraction Layer (RAL)53
  • Advanced UVM in Practice44
  • Debug & Regression10
  • Coding Practice47
  • Senior Prep: Architecture, Leadership and Interviews92
  • Verification in Industry Practice10
Loading lesson

‹Tests: Base Classes, Selection and Startup
  1. Topics
  2. UVM Verification
  3. Environment & Test Classes
  4. Tests: Base Classes, Selection and Startup
  5. Factory Overrides in Tests: Explicit, Local, Reviewable

Part 7 · Environment & Tests · Intermediate

Factory Overrides in Tests: Explicit, Local, Reviewable

How the bench's tests swap components: a type override for the stream driver, the scoreboard and the env, an instance override aimed at env.out_agt only, which override wins, and when registering before super.build_phase is required rather than a habit.

On this page

    dma_backpressure_test needs a reactive slave on the DMA's output, an agent that drives TREADY. The output agent and the input agent are both axis_agent. A type override, the tool you used for the truncating driver, would turn both into slaves, and the input side would stop sending data. How do you change one instance of a class and leave its twin alone, without editing dma_env?

    The idea: the test hands the factory a substitution list

    Every component on the bench is created with type_id::create, which asks the factory, a registry of classes, for an object of the requested type. Before answering, the factory checks its list of overrides: a type override says "whenever anyone asks for type A, build B instead"; an instance override says "build B instead of A only for the component at this path". The test fills in that list in build_phase, and the env, which never changes, simply gets different objects.

    Think of a kitchen before service. The chef pins a substitution list to the pass: "all fries become sweet-potato fries tonight" (a type override), and "table 7's burger has no bun" (an instance override). The cooks follow the same recipes as always; the list changes what comes out. The list has to be up before the first order is cooked, which is the timing rule in this lesson.

    The picture: one request, two answers

    Flowchart: dma_env creates in_agt and out_agt as axis_agent; for in_agt no instance or type override matches, so it is axis_agent; for out_agt the instance override matches, so it becomes axis_sink_agent.
    In dma_backpressure_test the env makes the same request twice. The factory checks instance overrides first, by full path, then type overrides. Only out_agt matches the instance override.

    Build it: the overrides on the bench

    An instance override aimed at one path

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // snippet: from the course DMA bench
    // The output port gets a reactive slave with random backpressure: an instance
    // override swaps the agent at env.out_agt only; env.in_agt keeps axis_agent.
    class dma_backpressure_test extends dma_base_test;
      `uvm_component_utils(dma_backpressure_test)
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        axis_agent::type_id::set_inst_override(axis_sink_agent::get_type(), "env.out_agt", this);
        super.build_phase(phase);
      endfunction
    
      task run_phase(uvm_phase phase);
        axis_sink_agent sink;
        axis_ready_seq  rsp = axis_ready_seq::type_id::create("rsp");
        dma_job_cfg     job = dma_job_cfg::type_id::create("job");
        bit [31:0] status;
        phase.raise_objection(this);
        if (!$cast(sink, env.out_agt)) `uvm_fatal("CAST", "env.out_agt is not an axis_sink_agent")
        fork
          rsp.start(sink.sink_sqr);              // the responder runs for the whole test
        join_none
        if (!job.randomize() with { len == 12; }) `uvm_fatal("RAND", "dma_job_cfg randomize failed")
        run_job(job, 12, status);
        if (status[2:1] !== 2'b01) `uvm_error("STATUS", $sformatf("expected DONE only, STATUS=%08h", status))
        #200ns;
        `uvm_info("BP", $sformatf("the slave stalled the DMA for %0d clocks", sink.sink_drv.stall_clocks), UVM_LOW)
        phase.drop_objection(this);
      endtask
    endclass
    • set_inst_override(axis_sink_agent::get_type(), "env.out_agt", this): the third argument is the context. The path is taken relative to it, so the full path is uvm_test_top.env.out_agt. Leave this out and the string is treated as an absolute path, which matches nothing, silently.

    • In run_phase the test casts env.out_agt to axis_sink_agent to reach sink_sqr. The env's handle is still of type axis_agent; the object behind it is the derived type. A failed cast here is the cheapest proof that the override missed.

    • The responder sequence runs in a fork ... join_none for the whole test, answering every beat. Reactive (slave) sequences explains it.

    Type overrides: one line each

    Test

    Override

    Effect

    dma_truncate_test

    axis_driver → axis_truncating_driver

    Every stream driver forces TLAST early. Only in_agt has a driver, so only it changes.

    dma_drain_test

    dma_scoreboard → dma_scoreboard_eot

    The scoreboard holds the run phase open until every owed word is out.

    dma_reset_test

    both drivers → _ra versions; scoreboard → dma_scoreboard_ra

    Three components become reset-aware at once.

    dma_irq_agent_test

    dma_env → dma_env_irq

    The env itself is replaced by one that adds an interrupt agent.

    The truncate row shows a quiet strength of type overrides: "every axis_driver" turned out to mean one driver, because the passive output agent builds none. If a later change made out_agt active, the override would hit it too. That is the moment to switch to an instance override.

    Which override wins

    • Instance overrides are checked before type overrides. A matching instance override wins for that path; everything else falls through to the type overrides.

    • Type overrides replace. A second type override for the same requested type replaces the first (the replace argument defaults to 1), so the last one registered wins.

    • Instance overrides do not replace. They are kept in registration order, and the first one that matches wins. Register specific paths before wildcard paths.

    • A derived class needs its own registration macro. Without it, the class inherits its parent's type_id, and asking for it builds the parent, with no error.

    To see what the factory holds, call uvm_factory::get().print() at end of elaboration. There is no +UVM_FACTORY_TRACE plusarg. For a one-off run, +uvm_set_type_override and +uvm_set_inst_override do the same job from the command line; Command-line overrides has their exact forms.

    Before super.build_phase: when it is required

    Every override test on the bench registers its override and then calls super.build_phase. The rule behind that habit is narrower than it looks. An override must be registered before the create call it should affect. The base test's build_phase creates exactly one thing, the env. Everything inside the env is created later, in the env's own build_phase, which runs after the test's build_phase has returned, because build runs top-down.

    • For dma_irq_agent_test, which replaces the env itself, the order is required. Registered after super.build_phase, the env is already a plain dma_env.

    • For the driver, scoreboard and agent overrides, the order is a habit: they would still work if registered after super.build_phase, inside the test's build_phase. Keep the habit anyway: it makes every override test look the same, and it stays correct if someone later overrides the env too.

    • Registering in connect_phase or later is always too late: every component already exists.

    Watch it run

    The topology of dma_backpressure_test, port rows trimmed, shows exactly one swapped agent:

    Diagram
    uvm_test_top               dma_backpressure_test   -     @180
        env                      dma_env                 -     @250
          apb                    apb_agent               -     @264
          in_agt                 axis_agent              -     @272
            drv                  axis_driver             -     @644
            mon                  axis_monitor            -     @504
            sqr                  uvm_sequencer           -     @521
          out_agt                axis_sink_agent         -     @280
            mon                  axis_monitor            -     @682
            sink_drv             axis_sink_driver        -     @822
            sink_sqr             uvm_sequencer           -     @699
          sb                     dma_scoreboard          -     @288
    ...
    UVM_INFO dma_tb.sv(1246) @ 1005000: uvm_test_top [BP] the slave stalled the DMA for 28 clocks
    UVM_INFO dma_tb.sv(411) @ 1005000: uvm_test_top.env.sb [SB] 12 words matched, 0 mismatched

    out_agt is an axis_sink_agent with a monitor, a sink driver and a sink sequencer, and no ordinary driver: the env still marks out_agt passive, so the inherited axis_agent build skips the master side, while axis_sink_agent always builds its slave side. in_agt is untouched. The stall count changes with the seed; the 12 matched words must not.

    What breaks: the scoreboard override that never ran

    This one happened while building the bench. dma_drain_test overrode the scoreboard with dma_scoreboard_eot, which extends write_apb and write_out to count owed words. The topology printed dma_scoreboard_eot, so the override had clearly hit. Yet the new code never ran: the owed-word count stayed at zero and the test ended early.

    The cause was not the factory. The analysis imps that uvm_analysis_imp_decl creates hold a handle of the base scoreboard type and call write_out through it. In the first version of dma_scoreboard, write_out was not virtual, so the call bound to the base version at compile time. The fix was one keyword on each write_* method of the base class. The lesson for interviews: a factory override replaces the object, but whether your new method runs still depends on virtual dispatch.

    Interview

    "Type override versus instance override: when do you use each, and which wins if both match?"

    A weak answer: "Type changes all instances, instance changes one; the last one set wins." The last part is wrong for instance overrides.

    A strong answer: "A type override swaps every create of that type, which is right when the change is global, like a protocol-violating driver everywhere. An instance override takes a path, relative to the context argument, and is right when a class has twins, like our input and output stream agents. The factory checks instance overrides first, in registration order, first match wins, so specific paths go before wildcards. Type overrides replace by default, so the last one wins. I register them in the test's build_phase before the target is created; for an env override that means before super.build_phase. Then I check the topology print, and remember that an overridden method only runs if it is virtual."

    Follow-ups: Your instance override has no effect and no error. First two things to check? (the context argument and the full path in the topology print). Can you override the test class itself? (yes, if the override is registered before run_test creates it; the RNTST line then says "via factory override").

    Check yourself A test registers two instance overrides on axis_agent: first "env.*" to agent_a, then "env.out_agt" to agent_b, both with context this. What is out_agt?

    agent_a. Instance overrides are searched in registration order and the first match wins; env.* matches uvm_test_top.env.out_agt and was registered first. To get agent_b there, register the specific path first.

    Check yourself Someone moves the override in dma_irq_agent_test to after super.build_phase(phase). What happens at run time?

    The env is created by the base test as a plain dma_env, so there is no interrupt agent and nothing ever triggers the dma_irq event. The test raises its objection and waits in irq_ev.wait_trigger() forever. With no timeout set, the run burns its slot until the default 9200 s limit; the topology print, showing env as dma_env, is the clue.

    Check yourself The same move in dma_drain_test (scoreboard override after super.build_phase) changes nothing. Why the difference?

    The scoreboard is created inside the env's build_phase, which runs after the test's build_phase returns. The override is registered by then, so the factory still applies it. Only objects created by the test's own build_phase, here the env, are affected by the order of those two lines.

    Next: every test on the bench ends itself, in its own run_phase, and six of them end with a fixed delay. Shared run_phase asks whether that policy belongs in one place.

    Related topics

    • Create-time resolution

    • Command-line overrides

    • Overriding items and sequences

    • Shared run_phase

    Previous

    326 / 658

    Next: Dual-Stream Checking: Expected vs Observed Correlation

    Up next

    Up nextDual-Stream Checking: Expected vs Observed Correlation

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Base Test Pattern: One Contract for Every Scenario
    • Scenario Inheritance: Specialize Intent, Not Infrastructure
    • Shared run_phase: One End-of-Test Policy for Every Test