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
    • DPI-C: Calling C from SystemVerilog
    • bind: Attaching Verification to RTL
    • Macros, Defines & Compilation
    • Plusargs, Files & Runtime Configuration
    • Simulation Performance & TB Memory
      • Where Simulation Time Goes
      • Testbench Hotspots
      • TB Memory Discipline
      • Visibility Cost and Debug Strategy
    • RTL/TB Boundaries, Races & Timing
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Simulation Performance & TB Memory
  1. Topics
  2. SystemVerilog
  3. Advanced Language & Integration
  4. Simulation Performance & TB Memory
  5. Visibility Cost and Debug Strategy

Part 7 · Advanced & Integration · Intermediate

Visibility Cost and Debug Strategy

Scoped and windowed dumps, quiet-by-default logs, two profiles and the two-pass workflow.

On this page

    Dump less and see enough

    Recording every signal in every scope makes the simulator serialize every value change to disk. On an active design that commonly adds a large fraction of wall-clock time and produces wave files in the tens of gigabytes. Full dumps suit interactive debug of one failure, never a thousand-test regression.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // snippet
    // Full dump: maximum visibility, maximum cost
    initial begin
      $dumpfile("waves.vcd");
      $dumpvars(0, tb_top);            // depth 0 means every level below tb_top
    end
    
    // Scoped dump: only the block under debug
    initial begin
      $dumpfile("waves.vcd");
      $dumpvars(2, tb_top.dut.u_dma);  // 2 levels under the DMA only
    end
    
    // Windowed dump: only the time region of interest
    initial begin
      $dumpfile("waves.vcd");
      $dumpvars(1, tb_top.dut.u_dma);
      $dumpoff;                        // start with recording paused
      #1ms   $dumpon;                  // open the window
      #100us $dumpoff;                 // close it
    end
    • Scope: dump the suspect block, not the chip. Cost scales with signals recorded.

    • Depth: prefer depth 2 over depth 0 to skip leaf-cell noise.

    • Window: open recording around the failure time learned from a previous run.

    • Format: vendor binary formats are far smaller and faster than VCD.

    Logs that stay quiet until asked

    One display call is free. A million of them is formatting cost plus file I/O plus a log nobody can open. Verbosity must filter before formatting and switch at runtime through plusargs, with no recompile to go quiet.

    A logging macro needs a begin and end wrapper. Without it the bare if inside the macro steals the else of any outer if. The safe form below keeps the guard before the format.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    // snippet
    int unsigned verbosity = 100;   // default: regression-quiet
    // `define LOG_HIGH(msg) \
    //   begin \
    //     if (verbosity >= 300) $display msg; \
    //   end
    // Usage (guard runs before the format):
    // `LOG_HIGH(("txn id=%0d at %0t", id, $time))
    Diagram
    LOG COST AT SCALE: 2,000-test nightly regression
    
      per-test detail lines, 50K txns per test:
         2,000 tests x 50,000 lines x about 120 bytes = about 12 GB of logs
         plus formatting CPU plus filesystem pressure on the farm
    
      regression-quiet profile (reports plus summary only):
         2,000 tests x about 200 lines = about 50 MB
         Same checking. Same coverage. All signal, no noise.

    Two profiles and the two-pass workflow

    Everything in this topic converges into one operational idea: exactly two run profiles, a fast regression profile and a full debug profile, with every visibility feature switchable at runtime. One compiled testbench, two behaviors, no per-run tinkering.

    Feature

    Regression (fast)

    Debug (visible)

    Waves

    Off

    On, scoped and windowed

    Log verbosity

    Reports plus summary

    Full per-transaction detail

    Functional coverage

    On, it is why the run exists

    Off, not needed to debug

    Assertions

    On, non-negotiable

    On

    Debug pools and logs

    Off

    On

    Seed

    Random, logged

    Forced to the failing seed

    Switch

    Use for

    Why

    Plusargs at runtime

    Waves, verbosity, debug pools

    One binary serves both profiles

    Compile defines

    Heavyweight instrumentation

    Removes code whose cost survives a runtime guard

    Prefer plusargs by default and reserve defines for cost that survives a runtime guard, such as bound-in debug monitors or large static debug arrays. Coverage-off runs must never feed closure metrics.

    Diagram
    POST-FAIL RERUN-WITH-WAVES FLOW
    
      NIGHTLY REGRESSION (fast profile): 2,000 tests, no waves, quiet logs
         1,997 pass and 3 fail
      Harvest from each failure: test name, seed, plusargs, fail time T
      RERUN (debug profile, 3 runs only): same seed, waves on, raised verbosity,
         wave window opened around time T
      Debug with full visibility, fix, confirm in the fast profile,
      and return the test to the nightly pool.
    
      Cost: visibility tax on 3 runs instead of 2,000.
      Requirement: seeds logged, runs reproducible.
    Diagram
    regression:  ./simv +ntb_random_seed=$SEED                   (script picks and logs SEED)
      debug:       ./simv +ntb_random_seed=7731 +WAVES +VERBOSITY=300
    
      VCS spelling shown; each simulator has its own seed switch (Questa: -sv_seed).

    The workflow depends on one non-negotiable property: reproducibility. The same seed plus the same command line must produce the same behavior. Anything that breaks seed stability breaks the entire strategy.

    Common mistakes

    Common pitfall

    A full-hierarchy dump left in the regression script taxes every nightly run for waves nobody opens. Debug instrumentation behind a runtime flag can also still allocate gigabytes when disabled. Check both the flag and the allocation.

    Interview

    Likely question: “A test fails with no waves and quiet logs. What do you do.”

    • Harvest: test name, logged seed, plusargs and approximate fail time.

    • Rerun: the exact seed with scoped, windowed waves and raised verbosity.

    • Require: reproducibility. Unseeded randomness anywhere breaks the rerun.

    Check yourself The regression runs 2,000 tests with waves on and quiet logs. Only 3 fail. What changes, and what does it save?

    Run the regression blind and rerun only the 3 failures with waves. The visibility tax then applies to 3 runs instead of 2,000, with the same debug information.

    Check yourself Coverage is off in the debug rerun. Is that a sign-off risk?

    No, as long as closure metrics come from the regression-profile runs. Coverage-off reruns are for debug only and must never feed sign-off numbers.

    Testbench

    One testbench in the two-profile style: the dump and the verbosity are plusarg-switched, detail lines are guarded, and the run self-checks the count.

    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
    36
    37
    38
    39
    40
    timeunit 1ns; timeprecision 1ps;
    
    module tb_two_profile;
      logic clk = 0;
      always #5 clk = ~clk;
      logic [7:0] count = 0;
      always_ff @(posedge clk) count <= count + 8'd1;
      int unsigned verbosity = 100;   // regression-quiet default
      int unsigned seed = 1;
      int high_msgs = 0;
      int errors = 0;
    
      initial begin
        void'($value$plusargs("VERBOSITY=%d", verbosity));
        void'($value$plusargs("SEED=%d", seed));
        void'($urandom(seed));   // the logged seed seeds this thread's RNG
        $display("SEED=%0d VERBOSITY=%0d", seed, verbosity);
        if ($test$plusargs("WAVES")) begin
          $dumpfile("waves.vcd");
          $dumpvars(2, tb_two_profile);   // scoped even in debug
        end
      end
    
      initial begin
        repeat (50) @(posedge clk);
        #1;
        for (int k = 0; k < 200; k++) begin
          if (verbosity >= 300) begin
            $display("txn k=%0d count=%0h", k, count);
            high_msgs++;
          end
        end
        if (count !== 8'd50) errors++;
        // Either profile passes: detail lines appear only at verbosity 300 and up.
        if (high_msgs !== (verbosity >= 300 ? 200 : 0)) errors++;
        if (errors == 0) $display("PASS: 50 clocks counted and detail lines matched the verbosity profile");
        else $fatal(1, "FAIL: count=%0d high_msgs=%0d", count, high_msgs);
        $finish;
      end
    endmodule

    Related topics

    • Plusargs and runtime control — dump scope, verbosity and seeds

    Previous

    206 / 259

    Next: RTL/TB Boundaries, Races & Timing

    Up next

    Up nextRTL/TB Boundaries, Races & Timing

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Where Simulation Time Goes
    • Testbench Hotspots