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
    • Testbench Anatomy & Layering
    • Stimulus: Generators, Transactions & Drivers
      • Transaction Design for Stimulus
      • Layered Constraint Architecture
      • Shaping Stimulus with Knobs and Policies
      • Protocol Legality and Error Injection
      • Randomizing Scenarios, Not Just Items
      • Generator Patterns
      • Driver Design
      • Mixing Directed and Random Stimulus
    • Monitors, Scoreboards & Reference Models
    • Self-Checking Tests & Regression
    • From Hand-Built TB to UVM
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Stimulus: Generators, Transactions & Drivers
  1. Topics
  2. SystemVerilog
  3. Testbench Architecture in SystemVerilog
  4. Stimulus: Generators, Transactions & Drivers
  5. Transaction Design for Stimulus

Part 6 · Testbench Architecture · Intermediate

Transaction Design for Stimulus

Field taxonomy for transactions: solver controls, derived responses, debug metadata.

On this page

    Three kinds of fields

    A transaction sorts its fields into three buckets. Solver controls are rand, responses are filled during execution, and metadata is never randomized and never driven.

    Bucket

    Fields

    Who writes it

    Rand controls

    kind, addr, wdata, gap

    The solver, under constraints

    Derived responses

    rdata, resp_err

    Driver or monitor during execution

    Metadata

    id, id_tag, timestamps, source

    Constructor and components, never rand

    The solver only ever sees the top bucket. Mixing buckets wastes solver effort and confuses field ownership.

    A disciplined transaction class

    The next listing is not run in this course's checks. It is a class with no top module.

    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
    // not-run: class only, no top module
    typedef enum {READ, WRITE} kind_e;
    
    class bus_txn;
      rand kind_e       kind;
      rand bit [15:0]   addr;
      rand bit [31:0]   wdata;
      rand int unsigned gap;
      constraint c_gap { gap inside {[0:8]}; gap dist {0 :/ 6, [1:8] :/ 4}; }
      constraint c_align { addr[1:0] == 2'b00; }
      bit [31:0] rdata;
      bit        resp_err;
      bit [3:0]  id_tag;
      static int unsigned next_id;
      int unsigned id;
      time t_created, t_driven;
      string source = "gen";
      function new();
        id = next_id++;
        t_created = $time;
      endfunction
      function string convert2string();
        return $sformatf("[%0d %s@%0t] %s addr=%h wdata=%h rdata=%h gap=%0d",
                         id, source, t_driven, kind.name(), addr,
                         wdata, rdata, gap);
      endfunction
      virtual function bus_txn clone();   // derived types override it
        bus_txn c = new();
        c.kind = kind; c.addr = addr; c.wdata = wdata; c.gap = gap;
        c.source = source;
        return c;
      endfunction
    endclass

    The gap field is stimulus: spacing is randomized with back-to-back weighted heavily. The id_tag field is driver scratch space for pipelined issue, not protocol state.

    • A clone is a new identity with the same stimulus: fresh id, copied fields, copied source for lineage.

    • clone() is virtual. A derived transaction that adds fields overrides it, so a copy keeps the derived type and its fields.

    • Every component logs through convert2string, so one id greps across generator, driver, monitor, and scoreboard.

    • Response fields stay non-rand: randomizing them burns solver effort and hides ownership.

    Common mistakes

    Common pitfall

    Re-randomizing one transaction object in a loop without cloning corrupts in-flight handles downstream. Mailboxes pass handles, so put a clone, never the blueprint itself.

    Interview

    Likely question: what goes in a transaction?

    The senior answer is the taxonomy: rand controls, derived responses, debug metadata. Constraints shape the top bucket; tests tighten through derived classes.

    Check yourself A clone carries a different id than its source. Does that break id traceability?

    No. The clone is a new object with copied stimulus and copied source, so lineage survives. Each live object still names exactly one transaction at one time.

    Check yourself Why is gap a rand field instead of a driver-side delay?

    Spacing is stimulus: back-to-back traffic finds pipeline bugs. Weighting gap in the solver shapes timing intent that the driver then honors.

    Testbench

    An id tracker plus a testbench that proves one id per valid cycle, in order.

    txn_id_tracker.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
    36
    37
    38
    39
    module txn_id_tracker (
      input  wire        clk,
      input  wire        valid,
      output reg  [31:0] id_out
    );
      timeunit 1ns; timeprecision 1ps;
      reg [31:0] next_id = 0;
      always @(posedge clk) begin
        if (valid) begin
          id_out  <= next_id;
          next_id <= next_id + 1;
        end
      end
    endmodule
    
    module tb_txn_ids;
      timeunit 1ns; timeprecision 1ps;
      localparam int N_TXN = 8;
      reg clk = 0;
      always #5 clk = ~clk;
      reg valid = 0;
      wire [31:0] id_out;
      txn_id_tracker dut (.*);
      integer i;
      integer problems;
      initial begin
        problems = 0;
        @(posedge clk); #1;
        valid = 1;
        for (i = 0; i < N_TXN; i = i + 1) begin
          @(posedge clk); #1;
          if (id_out !== i) problems = problems + 1;
        end
        valid = 0;
        if (problems == 0) $display("PASS: 8 ids issued in order, one per valid cycle");
        else $fatal(1, "FAIL: found %0d unexpected ids", problems);
        $finish;
      end
    endmodule

    Related topics

    • Layered Constraint Architecture

    • Generator Patterns

    Previous

    167 / 259

    Next: Layered Constraint Architecture

    Up next

    Up nextLayered Constraint Architecture

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Shaping Stimulus with Knobs and Policies
    • Protocol Legality and Error Injection
    • Randomizing Scenarios, Not Just Items