Skip to content
VLSI Mentor

Tcl · Module 25

The do File

People treat a do file as a list of commands to paste. It is a program, and the difference shows the moment you need the same setup to serve two situations.

Intermediate19 min readtcledapart-iisimulationdo-files

Part II · Module 25 · Chapter 25.2 · The do File

A do file is what the Questa/ModelSim family calls a script you hand the simulator, usually with -do run.do or by typing do run.do at its prompt.

Most people meet one as a block of commands to copy. That reading costs you everything interesting about it.

1. What a do file actually is

In the Questa family, vsim is a Tcl interpreter (Chapter 25.1 §2). It loads your design and then presents a prompt where the simulator's commands and Tcl's own commands sit in one namespace.

do run.do is, to a first approximation, source run.do.

As in Chapter 25.1, no real vsim exists on the machine these chapters were verified on, and Icarus — which is here and runs 25.1 and 25.4 for real — has no Tcl shell at all. So the shell below is a small teaching mock. What it cannot fake is the part that matters: source, proc, foreach and if are Tcl's, not the mock's, so the semantics demonstrated here are the real ones.

2. A do file that is a program

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# run.do — a do file. Every line here is a Tcl command.
vlib work
vlog -sv rtl/fifo.v tb/fifo_tb.v
vsim work.fifo_tb

# A do file is Tcl, so these are all available:
set TOP      fifo_tb
set DEPTH    4
set INTERVAL 50

# a procedure, defined and used inside the do file
proc wave_bus {group path width} {
    add wave -group $group -radix hex $path
    for {set i 0} {$i < $width} {incr i} {
        add wave -group $group $path\[$i\]
    }
}

# a loop over the design's own structure
foreach sig {clk rst_n push pop full empty} {
    add wave -group control /$TOP/$sig
}
wave_bus data /$TOP/din 2
add wave -group status -radix unsigned /$TOP/count

# a conditional, on a variable the caller can override
if {[info exists ::DEEP] && $::DEEP} {
    add wave -group internals /$TOP/dut/wptr /$TOP/dut/rptr
}

# and ordinary Tcl arithmetic driving the run
for {set t 0} {$t < 4} {incr t} {
    run ${INTERVAL}ns
}

Sourcing it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== sourcing the do file ===
  vlib: library 'work' created
  vlog: 2 file(s) compiled, options: -sv
  vsim: loaded work.fifo_tb
  run 50ns: now at 50 ns
  run 50ns: now at 100 ns
  run 50ns: now at 150 ns
  run 50ns: now at 200 ns
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== what the do file built ===
    group 'control'
      /fifo_tb/clk
      /fifo_tb/rst_n
      /fifo_tb/push
      /fifo_tb/pop
      /fifo_tb/full
      /fifo_tb/empty
    group 'data'
      /fifo_tb/din                 radix hex
      /fifo_tb/din[0]
      /fifo_tb/din[1]
    group 'status'
      /fifo_tb/count               radix unsigned
    10 signal(s) in the wave window
    simulation time: 200 ns

Four Tcl mechanisms are doing real work there.

$TOP means the module path is written once. Re-targeting the whole do file at a different testbench is one assignment, not a search-and-replace across forty add wave lines — the single most common reason a do file rots.

wave_bus is a procedure defined in the do file and called from it. Module 10 applies in full: it takes arguments, it can have defaults, and it can be moved to a shared file and sourced.

$path\[$i\] needs the brackets escaped, because [ starts command substitution (Chapter 2.5). This is the single most common do-file quoting bug: a bus index written unescaped makes Tcl try to run 0 as a command. The error — invalid command name "0" — points at the index, not at the add wave, which is Chapter 14.8's territory.

run ${INTERVAL}ns uses braces to end the variable name before the literal ns. Without them, $INTERVALns is a different variable name and Tcl reports it as unset rather than guessing.

3. The proof that it is really a script

Set one variable before sourcing the same file, change nothing else:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== the same do file with DEEP set, nothing else changed ===
  vlib: library 'work' created
  vlog: 2 file(s) compiled, options: -sv
  vsim: loaded work.fifo_tb
  run 50ns: now at 50 ns
  run 50ns: now at 100 ns
  run 50ns: now at 150 ns
  run 50ns: now at 200 ns
    12 signal(s) in the wave window

10 signals, then 12, from one file. A configuration format cannot do that. A script can, and this is the argument for treating a do file as one: the alternatives people reach for instead are two near-identical do files that drift apart, or a commented-out block somebody has to remember to uncomment.

The same one-script-with-options argument appeared in Chapter 21.7 §5 and Chapter 23.3 §7. It recurs because the failure mode it prevents — two copies of a script diverging silently — is the most expensive kind of flow debt.

4. Errors behave like Tcl errors

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== a missing source is refused, not ignored ===
    1 / cannot read source 'rtl/does_not_exist.v'

=== and a command before the design is loaded ===
    1 / run: no design loaded

Both are ordinary Tcl errors, catchable with catch and carrying an errorCode, so a do file can decide what to do about them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if {[catch {vlog -sv {*}$rtl_files} msg]} {
    puts stderr "compile failed: $msg"
    quit -code 2
}

5. A do file is not a shell script

This is where people writing their first do file go wrong, and the errors are confusing because they are Tcl errors about shell syntax.

You might writeWhat happensWhy
run -all > log.txt> and log.txt become arguments to runTcl does no redirection; > is just a word
vlog rtl/*.vthe literal string rtl/*.v is passedTcl does no globbing — use glob
set files $(ls rtl)$(ls is a variable nameTcl uses [...], not $(...)
vsim top && run -all&& becomes an argumentTcl has no command chaining operators
export FOO=barinvalid command name "export"environment goes through the env array

The Tcl equivalents are all Part I:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set rtl_files [glob -nocomplain -directory rtl *.v]      ;# Chapter 12.12
if {[llength $rtl_files] == 0} { error "no RTL found" }  ;# never glob silently
vlog -sv {*}$rtl_files                                   ;# Chapter 6.13

set fh [open logs/run.log w]                             ;# Chapter 12.4
puts $fh [report_waves]
close $fh

set env(SIM_MODE) batch                                  ;# Chapter 15.5

{*}$rtl_files rather than $rtl_files because the file list must arrive as separate arguments, not as one argument that happens to contain spaces — the distinction Chapter 6.13 exists for, and the one that produces cannot read source 'a.v b.v c.v' when forgotten.

And the glob-then-check pattern is deliberate: glob -nocomplain returning nothing is not an error, so a typo'd directory compiles zero files and elaboration fails much later with a confusing message. Chapter 21.2 made the same argument for synthesis; it is the same mistake.

6. Setting the exit status from a do file

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
quit -code 0     ;# the test passed
quit -code 1     ;# the test failed
quit -code 2     ;# the setup failed before the test ran

quit -code N is how a do file tells whatever launched it what happened. It is the mechanism that makes Chapter 25.4 possible at all, and the reason that chapter is about the testbench and the wrapper rather than about the simulator: in batch mode the do file is the only thing in a position to turn a verdict into a status.

Three conventions worth adopting, consistent with Chapter 15.4:

  • 0 only when the test genuinely passed. Not "the simulation finished".
  • Distinct non-zero codes for distinct failures. A setup failure and a test failure want different handling by the caller.
  • Never quit -code $errors. Only the low 8 bits of an exit status survive, so 256 errors reports success — the exit 256 trap from Chapter 15.4, and it applies here identically.

7. Exercises

Exercise 1. What is do run.do approximately equivalent to, and what follows from that?

Exercise 2. Why is $TOP worth using instead of writing the module path in every line?

Exercise 3. Why must $path\[$i\] escape its brackets, and what error appears if you forget?

Exercise 4. Why ${INTERVAL}ns rather than $INTERVALns?

Exercise 5. The same do file produced 10 signals and then 12. What does that prove?

Exercise 6. What state is the session in after an uncaught error half-way through a do file?

Exercise 7. What does run -all > log.txt do in a do file?

Exercise 8. Why {*}$rtl_files rather than $rtl_files?

Exercise 9. Why is quit -code $errors wrong?

Where this fits

Part of the Tcl curriculum.