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
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
    • Sequence Items and the Driver Handshake
      • Item vs Driver Boundary: What Belongs Where
      • Constraints and randomize() with: Legal Space, Defaults and Corners
      • Derived Items: Narrower Stimulus Without Editing the Agent
      • Field Macros and Item Ownership: Who May Change an Item
      • The Driver Handshake: start_item, finish_item, get_next_item, item_done
      • Stalled Sequences: Hang Triage
    • Sequence Mechanics: Lifecycle, Sequencer, Responses and Control
    • Virtual Sequences and Coordination
  • 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

‹Sequence Items and the Driver Handshake
  1. Topics
  2. UVM Verification
  3. UVM Sequences & Stimulus
  4. Sequence Items and the Driver Handshake
  5. Derived Items: Narrower Stimulus Without Editing the Agent

Part 5 · Sequences · Intermediate

Derived Items: Narrower Stimulus Without Editing the Agent

Extending apb_item into register-map and error-injection items: added and replaced constraints, the registration rule, post_randomize, why sequences stay typed on the base item, and how the factory swaps the type.

On this page

    The DMA answers any access outside its five registers with PSLVERR. You need two new kinds of stimulus: register traffic that only ever hits real registers, and an error test that only ever hits the holes. The APB agent is shared with three other teams and you are not allowed to change it. Its sequences, its driver and its monitor all speak apb_item.

    This lesson shows how to get both kinds of traffic by extending the item, why every extension needs its own registration line, and why the sequences that send these items should still be typed on the base class.

    The idea: a derived item is a stricter order slip

    A derived item is a subclass of the agent's item. It inherits every field and every constraint, and adds more. Constraints in a subclass are combined with the parent's (all must hold), so a subclass can only ever narrow the legal space. To widen or change a rule, the subclass redeclares a constraint block with the same name, which replaces the parent's block of that name.

    Think of a printed order form with an extra stamp: "vegetarian menu only". The kitchen (the driver) reads the same form it always has. It does not need to know about the stamp; it just never sees a steak. That is the property that makes derived items so cheap: the sequencer and driver are parameterized on apb_item, and a derived object is an apb_item, so it flows through them unchanged.

    The picture

    Class tree with apb_item and two derived items, and the path from a sequence through the factory to the unchanged sequencer and driver.
    The sequence asks the factory for an apb_item; an override hands back a derived object. The sequencer and driver never change.

    Build it: two derived items for the DMA

    Narrowing to the register map

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // snippet: in a test package that imports dma_pkg
    class apb_reg_item extends apb_item;
      `uvm_object_utils(apb_reg_item)          // its own registration: see "What breaks"
      constraint in_map_c { addr inside {CTRL, STATUS, LEN, KEY, COUNT}; }
    
      function new(string name = "apb_reg_item");
        super.new(name);
      endfunction
    endclass

    in_map_c is added to the inherited word_aligned_c; both hold. The register names come from the dma_pkg localparams, so the item and the scoreboard share one register map.

    Aiming at the holes, and predicting the answer

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet
    class apb_unmapped_item extends apb_item;
      `uvm_object_utils(apb_unmapped_item)
      bit expect_slverr;                        // derived, not random
      constraint hole_c { !(addr inside {CTRL, STATUS, LEN, KEY, COUNT}); }
    
      function new(string name = "apb_unmapped_item");
        super.new(name);
      endfunction
    
      function void post_randomize();
        super.post_randomize();
        expect_slverr = 1;                      // every legal value of this item is a hole
      endfunction
    endclass

    post_randomize runs after a successful solve and is the right place for fields that follow from the random ones. Here the expectation is trivial, but the pattern is the same for parity, CRC or a length field computed from a payload.

    Breaking a base rule on purpose

    An item that must produce misaligned addresses cannot add a constraint, because word_aligned_c would still forbid them. It redeclares the block by name instead:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // snippet
    class apb_misaligned_item extends apb_item;
      `uvm_object_utils(apb_misaligned_item)
      constraint word_aligned_c { addr[1:0] != 2'b00; }   // same name: replaces the parent's block
    
      function new(string name = "apb_misaligned_item");
        super.new(name);
      endfunction
    endclass

    Use this sparingly and name the class for what it does. A reader who sees word_aligned_c in the base class should not have to hunt through subclasses to learn that it sometimes means the opposite.

    Keep the sequences typed on the base item

    The sequences that send these items stay uvm_sequence #(apb_item). A sequence declared as uvm_sequence #(apb_unmapped_item) would not match apb_sequencer, which is uvm_sequencer #(apb_item): the sequence's typed handle to its sequencer and its response type would both be wrong. Instead, the sequence creates an apb_item through the factory, and the factory decides which class to build. Two ways to choose:

    • Create the derived type directly in a sequence written for it: apb_item it = apb_unmapped_item::type_id::create("it");. Simple and explicit, and it changes only that sequence.

    • Override from the test, so existing sequences send the new item without any edit: apb_item::type_id::set_type_override(apb_reg_item::get_type());. To aim the override at one sequence or one agent, create items with a context path and use an instance override; Overriding items and sequences teaches exactly that, so it is not repeated here.

    Watch it run: what an unmapped access looks like

    A one-access scratch test added to the bench points an apb_rw_seq at a hole (0x14) and checks slverr after finish_item, as a sequence built on apb_unmapped_item would. The DMA answers with PSLVERR, the driver copies it into the item, and the monitor reports it (Verilator, UVM_HIGH):

    Diagram
    UVM_INFO dma_tb.sv(151) @ 75000: uvm_test_top.env.apb.mon [APB_MON] RD addr=14 rdata=00000000 SLVERR

    convert2string appends SLVERR when the bit is set, so the error is visible in the log without a waveform. A scoreboard that models the register map would treat it as expected for apb_unmapped_item and as an error for any other item.

    What breaks: the error test that injected nothing

    A team wrote apb_unmapped_item by copying apb_item, deleting the fields, and adding hole_c. They forgot the uvm_object_utils(apb_unmapped_item) line. Everything compiled. The error test overrode apb_item with apb_unmapped_item and passed on the first run, with no PSLVERR anywhere in the log. That should have been the clue: an error test with no errors.

    A class without its own registration macro inherits its parent's type_id, so apb_unmapped_item::get_type() returned the proxy for apb_item. The test had registered an override of apb_item with apb_item. The factory built plain base items, hole_c never existed in any object, and every access hit a real register. It is not a compile error, which is what makes it expensive. The fix is one line, and the habit that prevents it is to make every error test check that its error happened at least once, for example by counting slverr responses in check_phase.

    Interview

    "You need a test that sends only illegal addresses through a VIP you cannot modify. How?"

    A weak answer: "Add an illegal flag to the VIP's item and an if in its driver." That modifies the VIP, which the question ruled out, and spreads one test's needs across every user.

    A strong answer: "Extend the VIP's item, register the subclass with its own uvm_object_utils and constructor, and add or replace constraints there: a new block to narrow, a same-named block to replace a base rule. The sequences stay typed on the base item, so the sequencer and driver are untouched. Then either create the derived type in a dedicated sequence or apply a factory override from the test, scoped with an instance path if only one agent should see it. And the test checks that the error actually occurred."

    Follow-ups: What happens if the subclass has no registration macro? (it inherits the parent's type_id, so create() builds the parent silently). Why not parameterize the sequence on the derived item? (it would no longer match the agent's sequencer type, and its response type would be wrong).

    Check yourself apb_reg_item adds in_map_c. A test randomizes it with with { addr == 8'h14; }. What happens?

    0x14 is aligned, so word_aligned_c is satisfied, but it is not in the register map, so in_map_c is violated. The constraints of the parent, the child and the in-line block must all hold together, so randomize() returns 0 and the bench's check fires a fatal.

    Check yourself You want random gaps between APB accesses. A colleague adds rand int unsigned gap_clks to a derived apb_gap_item and overrides apb_item with it. The items carry random gaps, but the bus shows none. Why?

    A knob is only intent; the stock apb_driver knows nothing about gap_clks, so it ignores it. A knob only works when the driver implements it: either the agent's owners add it to the base item and driver, or you pair the derived item with a derived driver (installed by a factory override, as the bench does with axis_truncating_driver in the next lesson) that $casts and reads it.

    Check yourself Why does the override in the error test have to be registered before the item is created, but the sequence itself needs no change?

    The factory decides the class at the moment type_id::create() runs. If the override is registered in the test's build_phase, every later apb_item::type_id::create() call, inside any sequence, returns the derived class. The sequence's code is unchanged because it only ever asked for an apb_item.

    Next: once items are flowing, several pieces of code hold handles to the same object. Field macros and item ownership sets out who may write an item at each step, and why the truncating driver clones before it changes anything.

    Related topics

    • Overriding items and sequences

    • Registration macros

    • Error injection

    Previous

    75 / 280

    Next: Field Macros and Item Ownership: Who May Change an Item

    Up next

    Up nextField Macros and Item Ownership: Who May Change an Item

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Item vs Driver Boundary: What Belongs Where
    • The Driver Handshake: start_item, finish_item, get_next_item, item_done
    • Stalled Sequences: Hang Triage