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 Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
    • UVM Factory: Registration, Overrides and Debug
      • The Factory: Swapping Classes Without Editing the Env
      • Registration Macros: What uvm_component_utils Writes for You
      • The type_id::create Pattern
      • Override Resolution: Who Wins
      • Override Timing, Safety and Debug
      • Factory Advanced: Parameterized, Abstract and By-Name
      • Command-Line Overrides
      • Overriding Items and Sequences
    • config_db and Resources
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹UVM Factory: Registration, Overrides and Debug
  1. Topics
  2. UVM Verification
  3. Factory & Configuration
  4. UVM Factory: Registration, Overrides and Debug
  5. The Factory: Swapping Classes Without Editing the Env
On this page

    Part 3/Factory & ConfigurationIntermediate

    The Factory: Swapping Classes Without Editing the Env

    What the UVM factory stores, what happens at every create call, and how dma_truncate_test replaces the stream driver with one line.

    The DMA spec says: if a packet ends early (TLAST arrives before LEN words), stop, set STATUS.ERR and raise the interrupt. You need a test for it. The stream driver in the bench always sends complete packets, and it is shared by every other test. You could add an if (truncate) knob to the driver, copy the agent, or fork the env. Each option touches code that forty passing tests depend on.

    The DMA bench does it with one line in one test, dma_truncate_test, and no other file changes. This lesson explains the machinery behind that line: what the factory is, what it stores, and what happens every time a component is created.

    The idea: ask for a role, not a person

    Think of a staffing agency. The env never hires a specific driver; it phones the agency and asks for "a stream driver for in_agt". Normally the agency sends the standard one. But a test can leave a note at the agency beforehand: "this week, whenever anyone asks for a stream driver, send the truncating one". The env makes exactly the same call, and gets a different worker who can do the same job plus one extra trick.

    In UVM terms, the factory is a single, global registry of class proxies (small objects that know how to construct one class). Every registered class puts its proxy in the registry when the simulation starts. A test records overrides: substitution rules of the form "when someone asks for type A, build type B instead". And every type_id::create call goes through the registry, which applies the overrides at that moment. Three things meet at each create: registration, policy and the request.

    The substitute must be able to do the job: it has to be the requested class or a class derived from it, because the caller stores the result in a handle of the requested type. A truncating driver is an axis_driver, so it fits wherever an axis_driver handle is waiting.

    The picture

    Flow: two class proxies register with the factory; the test adds an override; the agent's create request goes to the factory, which returns a truncating driver.
    Registration fills the factory at time 0. The test adds a rule during build. The agent's create call is resolved against both, and the agent receives the derived driver in its axis_driver handle.

    Notice what is not in the picture: the agent never names axis_truncating_driver. The env and the agent were written months before the truncating driver existed, and they did not need to change.

    Build it: the three pieces in the bench

    Registration: one macro per class

    Every class in the bench carries a registration macro. ` uvm_component_utils(axis_truncating_driver) `` creates the proxy and registers it under both its type and its name string. What the macro generates is the subject of Registration macros; here, all that matters is that the class is now known to the factory.

    The request: the agent asks for an axis_driver

    The agent builds its driver through the factory. It asks for the base type and stores the result in a base-type handle:

    design.sv
    1
    2
    3
    4
    5
    6
    // snippet: from axis_agent::build_phase in the bench
    mon = axis_monitor::type_id::create("mon", this);
    if (get_is_active() == UVM_ACTIVE) begin
      sqr = axis_sequencer::type_id::create("sqr", this);
      drv = axis_driver::type_id::create("drv", this);   // the factory decides the class
    end

    The policy: the test records an override

    Now the test. dma_truncate_test adds one rule in its build_phase, then lets the base test build the env as usual. The run phase programs an 8-word job and expects the DMA to report ERR.

    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
    // snippet: from the course DMA bench
    // Factory override: the stream driver cuts each packet short, so the DMA must
    // stop early and report ERR. No sequence or env file changes.
    class dma_truncate_test extends dma_base_test;
      `uvm_component_utils(dma_truncate_test)
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        axis_driver::type_id::set_type_override(axis_truncating_driver::get_type());
        super.build_phase(phase);                // the env is created after the override
      endfunction
    
      task run_phase(uvm_phase phase);
        dma_job_cfg job = dma_job_cfg::type_id::create("job");
        bit [31:0] status;
        phase.raise_objection(this);
        if (!job.randomize() with { len == 8; }) `uvm_fatal("RAND", "dma_job_cfg randomize failed")
        run_job(job, 8, status);
        if (status[2] !== 1'b1) `uvm_error("STATUS", $sformatf("ERR not set after a short packet: STATUS=%08h", status))
        else `uvm_info("TRUNC", "DMA reported ERR for the short packet, as expected", UVM_LOW)
        #100ns;
        phase.drop_objection(this);
      endtask
    endclass

    set_type_override is a type override: it applies to every axis_driver created anywhere in the testbench. In this bench that means exactly one object, because only in_agt is active; out_agt is passive and builds no driver at all, and the APB driver is a different type. When a substitution must hit one instance and not another, you use an instance override instead; the rules for which one wins live in Override resolution.

    The derived driver itself is small. It reuses everything from axis_driver and changes only one virtual hook, pre_drive, which the base driver calls for every beat. The full listing and why it clones before changing an item are in Override timing, safety and debug.

    Watch it run

    Run the bench with +UVM_TESTNAME=dma_truncate_test. The relevant lines (Verilator; the random key differs by simulator and seed, times are in ps):

    Diagram
    +UVM_TESTNAME=dma_truncate_test
    UVM_INFO dma_tb.sv(520) @ 275000: uvm_test_top.env.in_agt.drv [TRUNC] TLAST forced on beat 3
    UVM_INFO dma_tb.sv(761) @ 385000: uvm_test_top [TRUNC] DMA reported ERR for the short packet, as expected
    UVM_INFO dma_tb.sv(411) @ 485000: uvm_test_top.env.sb [SB] 4 words matched, 0 mismatched
    UVM_ERROR :    0
    • The first line comes from code that exists only in axis_truncating_driver, reported from the path uvm_test_top.env.in_agt.drv. The agent's drv handle really holds the derived object.

    • 4 words matched: beats 0 to 3 went through, and the scoreboard checked each against word ^ key. The override changed the stimulus; the checking stayed honest.

    • The topology printed at the start of the run (from print_topology in the base test) shows drv with type axis_truncating_driver. That table is the quickest proof an override took effect.

    What breaks: the override that was set but never used

    A team adds a truncation test exactly like this one, and it fails with UVM_ERROR ... [STATUS] ERR not set after a short packet. The TRUNC message never appears. Someone prints the factory and the override is right there in the table. The test author is sure the factory is broken.

    The cause is in an agent written by someone else, long ago: drv = new("drv", this);. Calling new on a component is perfectly legal SystemVerilog, so nothing complained. But new builds exactly the class named in the handle declaration. It never asks the factory, so no override can ever apply to that object. The fix is one line, drv = axis_driver::type_id::create("drv", this);, and a review rule: every component and every object that a test might want to replace is created through type_id::create. The type_id::create pattern shows the full set of create calls.

    An override that "does nothing" has three usual causes: the object was built with new, the override was registered after the object was created, or the override really applied but the new code never runs (a non-virtual method). The last one is a real bench story, told in Override timing, safety and debug.

    In IEEE 1800.2: class registration is completed by uvm_init, which run_test calls, instead of purely by static initialization as in UVM 1.2. Registration still finishes before any phase runs, so tests that set overrides in build_phase behave the same in both.

    "What is the UVM factory, and why not just call new?"

    A weak answer: "The factory creates objects. You use type_id::create instead of new because that's the UVM way."

    A strong answer: "It's a global registry of class proxies plus override tables. Each class registers through its utils macro. Every type_id::create call is resolved at that moment: the factory checks instance overrides for the full path, then type overrides, and builds the winner. That lets a test substitute a derived class, say a truncating stream driver, into an env it doesn't own, with no edits. The substitute must derive from the requested type, the override must be in place before the create runs, and anything built with new is invisible to it."

    Follow-ups to expect: What decides between a type and an instance override? (instance first; see the resolution lesson) and If the derived class is built but its new code never runs, what do you check? (virtual dispatch on the methods it overrides).

    Check yourself After dma_truncate_test sets its override, how many axis_truncating_driver objects exist in the bench, and why not more?

    One, at uvm_test_top.env.in_agt.drv. The type override applies to every axis_driver created through the factory, but out_agt is passive (the env sets is_active to UVM_PASSIVE for it), so it builds no driver. The APB agent's driver is an apb_driver, an unrelated type, so the override does not touch it.

    Check yourself A colleague writes axis_driver::type_id::set_type_override(apb_driver::get_type()) by mistake. The code compiles. What happens at run time?

    The factory looks up the override and constructs an apb_driver, but axis_driver::type_id::create must return an axis_driver handle, and the cast inside the registry fails. UVM reports a fatal error from the factory saying it could not return an object of the requested type. The rule behind it: an override must be the requested class or derived from it.

    Check yourself How would you prove from the log alone that the override applied, before any stimulus runs?

    Read the topology table printed at end of elaboration. The drv row under in_agt shows type axis_truncating_driver instead of axis_driver. You can also call uvm_factory::get().print() in the test to list the registered overrides, but the topology shows what was actually built, which is the stronger evidence.

    Next: the macro did a lot of quiet work here. Registration macros opens it up, including the one-word mistake that turns into a baffling compile error.

    Related topics

    • Registration macros

    • Override resolution: who wins

    • Your first UVM testbench (the DMA bench)

    • The factory pattern from scratch

    Previous

    45 / 280

    Next: Registration Macros: What uvm_component_utils Writes for You

    Up next

    Up nextRegistration Macros: What uvm_component_utils Writes for You

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • The type_id::create Pattern
    • Override Resolution: Who Wins
    • Override Timing, Safety and Debug