Skip to content
VLSI Mentor

Linux & Bash · Part II · Module 27 · Invoking Simulators and EDA Tools from the Shell

Controlling Waveform Dumping

Dumping is the one simulation option that changes a run's cost without changing its result, and the cost is large enough to reshape a regression. This chapter measures it four ways on the same run and finds that the standard advice — limit the hierarchy depth — is the weaker of the two available levers by two orders of magnitude.

Intermediate20 min readLinuxPart IIWaveformsPerformanceDisk

Part II · Module 27 · Chapter 27.6 · Controlling Waveform Dumping

A waveform database is the only artifact a simulation produces that is bigger than its input, and the only option whose cost you pay on every run whether or not anybody ever opens the result. Chapter 27.5 got one test launched correctly; this chapter decides what it writes while it runs.

1. Dumping Is a Testbench Decision the Shell Can Make

The calls that produce a waveform live in the source:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$dumpfile("dump.vcd");      // where
$dumpvars(0, tb_arb);       // what: level 0 means "every level below here"

Hard-coded, that means every run dumps. The pattern that makes it a shell decision is to guard them with a plusarg and read the parameters the same way:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if ($test$plusargs("DUMP")) begin
  if (!$value$plusargs("DUMP_FILE=%s", dumpname)) dumpname = "dump.vcd";
  if (!$value$plusargs("DUMP_DEPTH=%d", depth))   depth = 0;
  $dumpfile(dumpname);
  $dumpvars(depth, tb_arb);
  if ($value$plusargs("DUMP_FROM=%d", dump_from)) begin
    $dumpoff;                  // armed but off; turned on at DUMP_FROM
    $display("TB  dumping %0s depth=%0d window from %0d", dumpname, depth, dump_from);
  end else begin
    $display("TB  dumping %0s depth=%0d (whole run)", dumpname, depth);
  end
end

With that in place, dumping is off unless asked for — and off means nothing is written at all:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ vvp build.vvp +TESTNAME=smoke +DUMP
VCD info: dumpfile dump.vcd opened for output.
TB  dumping dump.vcd depth=0 (whole run)
TB  TEST PASSED  test=smoke checks=3 errors=0
$ ls -l dump.vcd
-rw-r--r--  1 …  1249 … dump.vcd

$ rm -f dump.vcd
$ vvp build.vvp +TESTNAME=smoke
TB  test=smoke seed=1 cycles=40 verbosity=LOW
TB  TEST PASSED  test=smoke checks=3 errors=0
$ ls dump.vcd
ls: dump.vcd: No such file or directory

No file, not an empty one. That distinction matters downstream: a validator can treat "the dump is missing" as "dumping was off" rather than as "the run failed", and an empty file would be ambiguous between the two.

2. What It Costs, Measured Four Ways

Two levers on the same cost, and they are not the same size

A waveform dump's size is the number of signals multiplied by the value changes per signal. Limiting hierarchy depth reduces the signal count and removed nine per cent of the measured file. Limiting the time window reduces the value changes and removed ninety-nine point nine per cent, making the file nine hundred and twenty-seven times smaller.Dump sizesignals times value changesDepth limits thesignalsonly those below your cutRemoved 9 percent19.5 MB from 21.4 MBMeasured, not inheritedthree runs and a byte countA window limits thetimethe whole run, excludedRemoved 99.9 percent23 KB, and 927 times smallerfactor 1factor 212
A dump's size is roughly the number of signals multiplied by the number of value changes each one makes. Depth limiting cuts the first factor and can only remove the fraction of signals that live below your cut; windowing cuts the second and removes the fraction of the run you excluded. On the measured workload that was nine per cent against ninety-nine point nine, and the arithmetic — not the design — is why.

One workload — the random test, 200,000 cycles, seed 3 — run four times with the same stimulus and four dumping configurations. The design is an arbiter with four ports plus its testbench, so this is a small design; the proportions are discussed below.

configurationruntimeVCD size
no dumping567 ms0
depth 0 (all levels), whole run969 ms21,394,015
depth 1 (top level only), whole run837 ms19,463,864
depth 0, 200-cycle window600 ms23,080

Read the second row first, because it is the baseline cost of the default behaviour: 1.71× the runtime and 21 MB, for a design with four signals in it. Multiply by a regression of two thousand runs and the disk figure alone decides whether the regression can run.

Now the two ways of reducing it.

Limiting the depth saved 9% of the file. 19.46 MB against 21.39 MB. It also took 14% off the runtime overhead. This is the advice everybody gives — dump only the level you need — and on this design it is close to useless.

Limiting the time window saved 99.89% of the file. 23 KB against 21.39 MB: 927 times smaller. And the runtime overhead fell from 402 ms to 33 ms, which is 8% of what whole-run dumping cost.

3. Windowing in Practice

A window is only useful if you know where to put it, and there are three ways to know.

You already have the failure time. This is the common case and the best one: a run failed, the log says at what time, and the rerun dumps a window around it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# first run: no dumping at all, so it is cheap
$ ( cd "$rundir" && exec vvp sim.vvp +TESTNAME=smoke +STRICT ) > "$rundir/run.log" 2>&1
$ cat "$rundir/run.log"
TB  test=smoke seed=1 cycles=40 verbosity=LOW
TB  ERROR t=45 gnt=10 expected 01
TB  TEST FAILED  test=smoke checks=3 errors=1

# it failed. The log says when, so the rerun knows where to put the window.
$ t=$(sed -n 's/^TB  ERROR t=\([0-9]*\).*/\1/p' "$rundir/run.log" | head -1)
$ echo "first error at $t"
first error at 45

Two details in that transcript. The time is extracted from the testbench's first ERROR line — head -1, because the originating failure is the one worth looking at and the rest are usually consequences, which is Chapter 24.6's rule and Module 29's subject. And the failing run exited 0: the only reason the flow knows to rerun at all is the log.

The testbench arms the window itself around whatever it is about to do, which is the right answer for a known-interesting phase:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// in the testbench: dump only across the phase under suspicion
if (dump_from >= 0 && i == dump_from)       begin $dumpon;  end
if (dump_from >= 0 && i == dump_from + 200) begin $dumpoff; end

The testbench dumps on its own failure, which needs the dump armed from the start and is therefore the expensive option — but it catches a failure you cannot predict. Note the asymmetry that makes this awkward: a waveform is interesting in the cycles before a failure, and by the time the testbench knows about the failure those cycles have passed. The usual compromise is a rolling window, which not every tool supports, or the two-run approach above.

The two-run approach is the one to default to, and it is worth naming why: the first run is cheap because it does not dump, and the second run is cheap because it dumps a window. A flow that dumps everything on every run pays the full cost on the 99% of runs that pass, in order to avoid a rerun on the 1% that fail.

4. The Switch Belongs in the Wrapper

Three plusargs, one wrapper flag:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#!/bin/bash
# the dumping part of a run wrapper
set -u
rargs=(+TESTNAME="$test" +SEED="$seed" +STRICT)
case "${dump:-off}" in
  (off)    : ;;                                        # the default
  (window) rargs+=(+DUMP +DUMP_FILE=dump.vcd +DUMP_DEPTH=0 +DUMP_FROM="${dump_from:?window needs a start cycle}") ;;
  (full)   rargs+=(+DUMP +DUMP_FILE=dump.vcd +DUMP_DEPTH="${dump_depth:-0}") ;;
  (*)      echo "unknown dump mode '$dump' (off|window|full)" >&2; exit 2 ;;
esac

Four things that are deliberate there.

off is the default, so the expensive behaviour has to be asked for. A flow whose default is full will have its disk filled by somebody who did not know there was a switch.

window requires its start cycle via ${dump_from:?…}, which fails at the point of use rather than silently dumping the whole run. A window with no start is full wearing a cheaper name, and that is exactly the kind of quiet substitution that makes a disk-usage mystery.

An unknown mode is an error, not a fall-through to off — the lesson from Chapter 27.4, where a silently-defaulted value ran the wrong test.

The file name is relative. dump.vcd, not $rundir/dump.vcd, because the run already happens inside $rundir — Chapter 27.3's arrangement. An absolute path works too. A path whose directory does not exist does something worse than failing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ vvp build.vvp +TESTNAME=smoke +DUMP +DUMP_FILE=/tmp/nodir/d.vcd
TB  test=smoke seed=1 cycles=40 verbosity=LOW
VCD Error: tb/tb_arb.v:42: Unable to open /tmp/nodir/d.vcd for output.
$ echo $?
1

The simulation aborted at the $dumpfile call and never reached a verdict — no TEST PASSED, no TEST FAILED, nothing. Compare that with the failing run in the next section, which prints TEST FAILED and exits 0. So the two statuses are the wrong way round from what you would guess: a dumping configuration error exits 1 and a design failure exits 0.

That is the NO-VERDICT state from Chapter 27.3 arriving for a reason that has nothing to do with the design, and it is why the wrapper should create the directory before the tool needs it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# the run directory exists before the tool is asked to write into it
mkdir -p "$rundir" || exit 2

5. Knowing What It Cost After the Fact

Two numbers belong in the run's record: the dump's size, and the runtime. Neither is interesting on one run and both are interesting across a hundred.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# after the run, beside the log
{ printf 'dump_bytes %s\n' "$( [ -f "$rundir/dump.vcd" ] && wc -c < "$rundir/dump.vcd" | tr -d ' ' || echo 0 )"
  printf 'dump_mode  %s\n' "${dump:-off}"
} >> "$rundir/config.txt"

And then the question a flow can actually answer: which runs are paying for dumping that nobody reads?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# every run directory, its dump size, largest first
$ for d in runs/*/; do
>   s=$( [ -f "$d/dump.vcd" ] && wc -c < "$d/dump.vcd" | tr -d ' ' || echo 0 )
>   printf '%12s  %s\n' "$s" "$(basename "$d")"
> done | sort -rn | head -5

The sort -rn is deliberate, and the reason is more delicate than it first looks. sort without -n compares as text:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ cat sizes.txt
9000000   big
100       small
21394015  huge
23080     window
$ sort -rn sizes.txt | head -2      # correct
21394015  huge
9000000   big
$ sort -r  sizes.txt | head -2      # text order
9000000   big
23080     window

Text order puts 9000000 above 21394015, because 9 sorts after 2. That is Chapter 13.3's trap.

That is the sort of small, previously-established rule that quietly decides whether a disk-usage report is true — and the sort that is worth re-measuring rather than remembering.

Module 28 takes this further and makes it a cleanup policy; here the point is narrower. If the size is not recorded, the cost of dumping is invisible until a filesystem fills up, and at that moment the information you need — which runs are large, and whether anybody opened them — no longer exists.

Practice

  1. Guard a $dumpfile and $dumpvars pair with $test$plusargs and confirm that without the plusarg no file is created, rather than an empty one.
  2. Run a long randomised test with no dumping and record the runtime.
  3. Run it again with whole-run dumping at depth 0, and record both runtime and file size. Compute the ratio.
  4. Run it with depth 1 and compute what fraction of the file the depth limit removed.
  5. Run it with a 200-cycle window and compute the same fraction.
  6. Compare the two fractions and decide which lever matters on your design.
  7. Explain the difference in terms of signals and value changes, and predict what would change on a large DUT under a small testbench.
  8. Build the two-run flow: a cheap non-dumping run, then extract the failure time from the log and rerun with a window there.
  9. Write the wrapper's dump-mode case with off as the default, and prove that window without a start cycle fails rather than dumping everything.
  10. Give the dump an absolute path into a directory that does not exist and observe when the failure happens relative to elaboration.
  11. Record the dump size per run and produce a largest-first listing. Then remove the -n from the sort and see which run it claims is largest.

Check Your Understanding

  1. Why does guarding dumping with a plusarg produce no file rather than an empty one, and why is that useful?
  2. On the measured workload, what did whole-run dumping cost in runtime and disk?
  3. How much of the file did limiting the depth remove?
  4. How much did a 200-cycle window remove?
  5. Express a dump's size as a product of two factors, and say which lever acts on which.
  6. Why did depth limiting barely help on this design, and when would it help more?
  7. Why is the two-run approach cheaper than always dumping, given that it sometimes runs twice?
  8. Why must a window's start cycle be required rather than defaulted?
  9. Why is the dump file name given relative to the run directory?
  10. A largest-first listing uses printf '%12s' and omits -n. Why does it still give the right answer, and when does it stop?

6. Remember

  • Guard dumping with a plusarg so it is off by default, and note that off means no file, not an empty one.
  • Whole-run dumping cost 1.71× the runtime and 21 MB on a four-signal design.
  • Depth limiting removed 9% of the file. A time window removed 99.89% — 927× smaller.
  • Size is roughly signals × value changes: depth acts on the first factor, windowing on the second, and the second is usually the larger lever.
  • Measure your own ratio. Three runs and a wc -c; do not inherit a proportion measured on somebody else's hierarchy.
  • Default to two runs: a cheap run with no dumping, then a windowed rerun at the failure time from the log.
  • off is the default, an unknown mode is an error, and a window without a start cycle must fail rather than quietly become a full dump.
  • Record the dump size with the run, and sort numerically when you report it — a fixed-width printf makes text order look correct until one value outgrows the field.

7. What Is Next

The database exists and is as small as it can be. Chapter 27.7 opens it — and confronts the question the previous chapters have been circling: given twenty run directories, which database belongs to the failure, and is it even complete?

Where this fits

Part of the Linux & Bash curriculum.