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 Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
    • Reusable Testbench Architecture
      • Policy Classes
      • Abstract BFM
      • Parameterized Interfaces
      • Interface Classes
      • Layered Stimulus
    • Checking Out-of-Order Traffic
    • Assertion Depth
    • Coverage Modelling for Real Features
    • Capstone: Out-of-Order Memory Controller
    • Principal Judgment
Loading lesson

‹Reusable Testbench Architecture
  1. Topics
  2. SystemVerilog
  3. Principal-Level Verification
  4. Reusable Testbench Architecture
  5. Policy Classes

Part 9 · Principal-Level Verification · Advanced

Policy Classes

Constraint policies as objects or layers instead of a subclass for every combination of rules, with a runnable bench and the tool limits that shape the choice.

On this page

    The problem: one subclass per combination

    Tests need to restrict a transaction in many independent ways: keep the address inside a window, cap the length, force word alignment, avoid a reserved region. The classic answer is a subclass per restriction, which is fine until tests need them in combination.

    Approach

    Classes for 4 independent rules

    Adding a fifth rule

    One subclass per combination

    Up to 16 (2^4)

    Doubles the count

    Linear inheritance chain

    4, but only one fixed order of rules

    Every chain that wants it must be edited

    Inline randomize() with

    0, but the rule is retyped in every test

    Copy and paste into each test

    Policy classes

    4, combined freely per test

    One new class, nothing else changes

    A policy is a constraint packaged so that it can be applied to an item it was not written inside. Tests pick the policies they want and the solver honours all of them together. Inline with constraints cannot be stored and reused, and constraint_mode() only switches blocks that already exist in the class.

    Runtime policies: a list of constraint objects

    The form usually called a policy class was presented at DVCon 2015 (John Dickol, "SystemVerilog Constraint Layering via Reusable Randomization Policy Classes"). The item holds a rand queue of policy handles. Each policy holds a handle back to the item, and its constraints refer to the item's fields through that handle. Because the queue is rand, the solver includes every policy object and its constraints in the same solve as the item.

    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
    34
    35
    // not-run: Verilator 5.048 does not honour a constraint that reaches the item through a back-reference (item.addr) or that is declared in a subclass of a rand member's declared type; Icarus 13 has no constraint solver
    typedef class packet;
    
    virtual class policy_base #(type ITEM = int);
      protected ITEM item;
      function void set_item(ITEM item); this.item = item; endfunction
    endclass
    
    class addr_window extends policy_base #(packet);
      bit [15:0] lo, hi;
      function new(bit [15:0] lo, bit [15:0] hi); this.lo = lo; this.hi = hi; endfunction
      constraint c_win { item.addr inside {[lo:hi]}; }
    endclass
    
    class max_len extends policy_base #(packet);
      bit [7:0] lim;
      function new(bit [7:0] lim); this.lim = lim; endfunction
      constraint c_len { item.len <= lim; }
    endclass
    
    class packet;
      rand bit [15:0] addr;
      rand bit [7:0]  len;
      rand policy_base #(packet) pcy[$];      // rand: policies join the solve
      constraint c_legal { len > 0; }
      function void pre_randomize();
        foreach (pcy[i]) pcy[i].set_item(this); // bind before the solver runs
      endfunction
    endclass
    
    // In a test:
    //   addr_window w = new(16'h1000, 16'h10FF);
    //   max_len     m = new(8);
    //   p.pcy = '{w, m};
    //   void'(p.randomize());

    Three details carry the pattern. The queue must be rand, or the policy constraints are not part of the solve. The back-reference is set in pre_randomize(), which the LRM calls before solving, so one policy object can be shared by many items. The queue holds base-class handles, and the solver must apply the constraints of each object's actual class.

    That last point is where the free tools stop. In our runs Verilator 5.048 ignored constraints declared in a subclass when the rand member's declared type was the base class, and when a policy constrained item.addr through the back-reference, randomize() still returned 1 but left the item's fields unrandomized, violating every constraint. That silent success is why an independent check of the results matters. The code above is legal SystemVerilog and is how teams use the pattern on commercial simulators; it cannot be run here.

    Compile-time policies: layers that extend their base

    The same reuse is available with a parameterized class that extends its own type parameter, often called a mixin. Each policy is written once as a layer, and a typedef stacks the layers a test needs. The object's class then contains every chosen constraint, so no back-reference and no polymorphic rand member is involved. Verilator runs this form.

    Runtime policy list

    Compile-time layers

    Choose policies

    At run time, per object

    At compile time, per typedef

    Policy values

    Fields of the policy object

    Fields of the layer, set at run time

    Switch one off

    Remove it from the list

    constraint_mode(0) on that layer's block

    Needs from the tool

    Polymorphic rand members, back-references

    Ordinary inheritance

    Runs on Verilator 5.048

    No

    Yes

    The bench below composes three layers onto a plain packet. The driver-side handle is a packet, so nothing downstream knows which policies are active. A separate function restates every rule procedurally and checks each of 400 results, so a missing policy shows up as a violation rather than as a quiet change in the distribution.

    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
    class packet;
      rand bit [15:0] addr;
      rand bit [7:0]  len;
      constraint c_legal { len > 0; }
    endclass
    
    // Each policy is written once, as a layer that can sit on top of any base
    // that has the fields it constrains. Limits are runtime fields, not parameters.
    class addr_window #(type BASE = packet) extends BASE;
      bit [15:0] win_lo = 16'h0000, win_hi = 16'hFFFF;
      constraint c_win { addr inside {[win_lo:win_hi]}; }
    endclass
    
    class max_len #(type BASE = packet) extends BASE;
      bit [7:0] len_max = 8'd255;
      constraint c_maxlen { len <= len_max; }
    endclass
    
    class word_aligned #(type BASE = packet) extends BASE;
      constraint c_align { addr[1:0] == 2'b00; }
    endclass
    
    // One line composes three policies. No dma_aligned_short_packet class.
    typedef word_aligned #(addr_window #(max_len #(packet))) dma_packet;
    

    The second phase switches the alignment policy off with constraint_mode(0) and requires that unaligned addresses then appear. That proves the knob works and that the check is not passing because the solver happens to favour aligned values.

    Mutant run. Dropping the word_aligned layer from the typedef (and the constraint_mode call that names it) makes the bench fail with 165 alignment violations in the first phase. The independent check, not the constraint, is what catches it.

    Common pitfall

    Two layers that use the same constraint block name do not combine: the outer layer's block overrides the inner one, exactly as a derived class constraint overrides a base constraint of the same name. In a quick Verilator check, stacking two layers that both named their block c_pol left only the outer rule active. Give every policy layer a unique block name, for example c_<policy>.

    Choosing between them

    • Many tests, few rule sets: compile-time layers. Each test picks a typedef, and constraint_mode() handles per-phase changes.

    • Rules chosen from configuration or a sequence library at run time: runtime policy lists, on a simulator that supports them.

    • Either way: keep each rule in exactly one class, give it a procedural twin for checking, and keep the item free of test-specific constraints.

    In UVM the runtime form usually sits inside a sequence item, and the sequence pushes policies before start_item(); the class structure is the same.

    Interview

    Likely question: “You have eight independent address and length rules. How do you avoid a class explosion?” Answer: package each rule as a policy object with its own constraint, hold them in a rand queue on the item, bind the back-reference in pre_randomize, and let each test push the policies it needs. Mention the compile-time mixin alternative and the name-collision trap.

    Check yourself A policy queue is declared without the rand keyword. What happens to the policies' constraints?

    They are not part of the solve. randomize() only includes member objects declared rand, so the item is randomized as if no policies were attached and the test silently loses its rules.

    Check yourself Why does the bench check results with a separate function instead of trusting randomize() returning 1?

    A return of 1 only says the solver found a solution for the constraints it saw. If a policy is missing, dropped or overridden, the solver still succeeds. Only an independent restatement of the rules notices that the result breaks a rule the test asked for.

    Related topics

    • Next: Abstract BFM

    • Shaping Stimulus with Knobs and Policies (Part 7)

    • Constraint Override via Inheritance

    • constraint_mode(): Toggling Blocks

    Previous

    232 / 259

    Next: Abstract BFM

    Up next

    Up nextAbstract BFM

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Parameterized Interfaces
    • Interface Classes
    • Layered Stimulus