Skip to content
VLSI Mentor

Makefile · Part II · Module 7 · EDA Tool Integration

VCS, Xcelium and Questa Integration

Everything so far ran on one free simulator. A real project runs Synopsys VCS on the farm, Cadence Xcelium for a second opinion, and Siemens Questa on somebody's desk. They take different options and have genuinely different shapes — two commands, one command, or a script. This chapter writes the recipe for each, and shows how to check that the command your Makefile built is the command you meant.

Intermediate26 min readMakefileGNU MakePart IIVCSXceliumQuesta

A Makefile that drives a simulator is responsible for exactly one thing: composing the right command and reacting to what comes back. It does not know how to elaborate a design or schedule events. So the useful question when integrating a tool is not "what does this tool do?" but "did my Makefile hand it the arguments I intended, in the right order, with the right quoting?"

That question has an answer you can measure, and this chapter measures it for all three mainstream simulators.

1. What the Three Tools Have in Common

Before the differences, the part that does not change. All three simulators:

  • read a filelist of sources, with -f (Chapter 5.2);
  • take the test name as a plusarg, +UVM_TESTNAME=<name> (Chapter 6.1);
  • write a log that the flow has to read to learn whether the test passed, because the tool's exit status will not tell you (Chapter 5.4 §5);
  • accept a seed, though this is where they stop agreeing.

That is most of a flow. The differences are about shape — how many commands, and what drives the run.

2. Synopsys VCS: Build an Image, Then Run It

VCS compiles the design into an executable, conventionally called simv, and you run that executable. Two commands, two stages, which is the shape Chapter 5.3 built the flow around.

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — the VCS shape
vcs-run:
	@mkdir -p $(RUNDIR)
	@$(VCS) -full64 -sverilog -timescale=1ns/1ps \
	        -f $(FILELIST) -o $(RUNDIR)/simv -l $(RUNDIR)/compile.log
	@$(RUNDIR)/simv +UVM_TESTNAME=$(TEST) +ntb_random_seed=$(SEED) | tee $(RUNDIR)/run.log

And here is what the Makefile actually handed the tool — captured, not transcribed:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — argv composed for vcs
-full64
-sverilog
-timescale=1ns/1ps
-f
filelists/all.f
-o
build/axi_smoke/1/simv
-l
build/axi_smoke/1/compile.log
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — argv composed for the image
+UVM_TESTNAME=axi_smoke
+ntb_random_seed=1

Nine arguments for the compile and two for the run, each on its own line so there is no ambiguity about where one ends. -timescale=1ns/1ps is a single argument because of the =; keep that in mind when comparing with Xcelium below.

Note which stage carries which value. The filelist and the output name go to the compile; the test name and seed go to the run. That split is the whole reason a different test costs no rebuild.

3. Cadence Xcelium: One Command Does Everything

xrun is a front end that compiles, elaborates and runs in a single invocation. Convenient interactively, and it changes the Makefile's shape: there is no separate image to depend on.

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — the Xcelium shape
xrun-run:
	@mkdir -p $(RUNDIR)
	@$(XRUN) -64bit -sv -timescale 1ns/1ps \
	         -f $(FILELIST) +UVM_TESTNAME=$(TEST) -svseed $(SEED) \
	         -l $(RUNDIR)/run.log | tee $(RUNDIR)/run.log
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — argv composed for xrun
-64bit
-sv
-timescale
1ns/1ps
-f
filelists/all.f
+UVM_TESTNAME=axi_smoke
-svseed
1
-l
build/axi_smoke/1/run.log

Two things to notice against §2. -timescale 1ns/1ps is two arguments here, where VCS took one — the vendors differ on = versus a space, and a Makefile that gets it wrong produces an error about a missing value rather than about the option. And the test name and seed sit in the same command as the filelist, because there is only one command.

4. Siemens Questa: A Script Drives the Run

Questa's batch mode is different again: vsim -c starts the simulator and a do-file of Tcl commands tells it what to do. Nothing runs until the script says run -all.

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — the Questa shape
questa-run: $(RUNDIR)/run.do
	@$(VSIM) -c -do $(RUNDIR)/run.do tb_top \
	         +UVM_TESTNAME=$(TEST) -sv_seed $(SEED) | tee $(RUNDIR)/run.log

$(BUILD)/%/run.do:
	@mkdir -p $(@D)
	@printf '%s\n' \
	  'onerror {quit -code 1}' \
	  'run -all' \
	  'quit -code 0' > $@
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — argv composed for vsim
-c
-do
build/axi_smoke/1/run.do
tb_top
+UVM_TESTNAME=axi_smoke
-sv_seed
1
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — the generated do-file
onerror {quit -code 1}
run -all
quit -code 0

The do-file is a generated file with its own rule, which is the right instinct: it depends on nothing but the Makefile, it lands in the run directory, and make clean removes it. Writing it by hand into the repository would be a file that silently disagrees with the flow around it.

onerror {quit -code 1} matters more than it looks. Without it, an error inside the do-file leaves vsim sitting there, and in batch mode that is a job that hangs until the farm kills it. The two lines that bracket run -all are what make the run safe to leave unattended.

5. What Actually Differs

With all three argv captures side by side, the comparison is short:

ConcernVCSXcelium (xrun)Questa
Commands per run2 (vcs, then ./simv)11 batch, driven by a script
Source list-f <file>-f <file>-f <file> (at vlog)
SystemVerilog-sverilog-sv-sv (at vlog)
Timescale-timescale=1ns/1ps (one arg)-timescale 1ns/1ps (two args)set in vlog
Test name+UVM_TESTNAME= on the image+UVM_TESTNAME= on xrun+UVM_TESTNAME= on vsim
Seed+ntb_random_seed=<n> (a plusarg)-svseed <n> (an option)-sv_seed <n> (an option)
Log-l <file>-l <file>transcript or redirect
Run controlimplicitimplicitexplicit run -all

The seed row is the only genuine divergence: a plusarg on one tool, a dash-option on the other two, and even those two spell it differently (-svseed against -sv_seed). Everything else is the same idea wearing different clothes.

That matters for Chapter 7.2, because it tells you how much of the flow needs to be per-tool. The answer is: very little — which is why one Makefile can serve all three.

Four rows, each flowing left to right from a command shape to what it means for the Makefile. Row one, VCS runs two commands, giving a compiled image file that Make can depend on. Row two, Xcelium xrun runs one command, so there is no image artifact for Make to track. Row three, Questa runs vsim with a do-file, so a generated script controls the run. Row four, the seed option, is the only genuine divergence: a plusarg for VCS but a dash option for the other two.VCS: two commandsvcs, then ./simvAn image to trackcompile once, run manyxrun: one commandcompile and run togetherNo artifactevery run recompilesvsim -c -doa script drives itGenerated do-filerun -all, then quitThe seed optionthe real differencePlusarg vs option+ntb vs -svseed12
Figure 1 — the three simulator shapes. The first column is what a person types; the second is what the tool does with it. Only the last row differs in substance: everything above it is the same flow with different option spellings, which is why one Makefile can drive all three.

6. Name Every Tool in a Variable

Every recipe above says $(VCS) and $(XRUN) rather than vcs and xrun:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — tools are variables
VCS  ?= vcs
XRUN ?= xrun
VSIM ?= vsim

The obvious reason is that it lets a caller point at a specific build: make vcs-run VCS=/tools/vcs/2023.12/bin/vcs. The better reason is a trap worth seeing in full, because the alternative looks like it works.

The tempting alternative is to put the tool directory on PATH inside the Makefile:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
export PATH := $(CURDIR)/tools/mock:$(PATH)

Measured, in a single Makefile with all three recipes: xrun and vsim worked and vcs did not.

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
make: vcs: No such file or directory
make: *** [vcs-run] Error 1

Nothing is wrong with the export. From inside a recipe, the shell genuinely sees the new directory first:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
PATH as the recipe shell sees it:
/.../tools/mock
/opt/homebrew/bin
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — which, from a recipe
/.../tools/mock/vcs
/.../tools/mock/xrun

The difference is that Make does not always use a shell. When a recipe line contains no shell metacharacters, Make skips the shell and executes the program directly as an optimisation — and that direct execution resolves the program name using Make's own PATH, not the PATH Make exports to its children.

The xrun and vsim recipes happened to end with | tee $(RUNDIR)/run.log. A pipe is a shell metacharacter, so those lines got a shell, and the shell found the mock. The vcs compile line had no metacharacters at all, so it did not.

Three measurements confirm the mechanism:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
--- a: single command, no shell metacharacters ---
make: vcs: No such file or directory
--- b: same command plus a ';' (forces a shell) ---
MOCK vcs: compiling from 'filelists/all.f' -> /tmp/x2
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — the same Makefile, started with the directory on make's own PATH
MOCK vcs: compiling from 'filelists/all.f' -> /tmp/x1

Adding a ; fixed it. Putting the directory on Make's own PATH before launching it fixed it. Exporting PATH from inside the Makefile did not.

7. Verifying the Command You Composed

The mocks in this chapter do one thing that is worth stealing even when you have the real tools: they write their own argv to a file.

Azvya Education Pvt. Ltd.VLSI Mentor
tools/mock/vcs — the whole idea, in two lines
printf '%s\n' "$@" > "$MOCK_ARGV_DIR/vcs.argv"

One argument per line, so whitespace and quoting are unambiguous. That gives you a way to answer the question this chapter opened with — did my Makefile build the command I meant? — without a license, without a farm slot, and without waiting for elaboration.

You do not need a mock to get the same information from a real flow. Three cheaper options, in order of effort:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# 1. Print the recipe without running it (Chapter 4.4 section 2)
make -n vcs-run

# 2. Let Make echo the command as it runs, by dropping the @
#    (or run `make vcs-run` with the @ removed from that line)

# 3. Record what the tool was actually given, with a one-line wrapper
#    placed in a variable, not on PATH:
#      make vcs-run VCS='./wrap vcs'

A wrap script is four lines and pays for itself the first time a flag goes missing:

Azvya Education Pvt. Ltd.VLSI Mentor
wrap — record the argv, then run the real tool
#!/bin/bash
tool="$1"; shift
printf '%s\n' "$@" >> "${ARGV_LOG:-argv.log}"
exec "$tool" "$@"

8. The Parts That Do Not Change

It is worth being explicit about how much of Modules 5 and 6 survives contact with a real simulator, because it is almost all of it.

The filelist is the same file, read with -f by all three tools (Chapter 5.2).

The run directory is the same design: build/<test>/<seed>/, one per run, nothing shared (Chapter 5.3).

The verdict still comes from reading the log, not from the exit status. This is not an Icarus quirk — all three commercial simulators exit 0 on a run where the testbench reported UVM_ERROR, for the same reason: the tool was asked to execute a testbench and it did (Chapter 5.4 §5).

The stamp, not the log, is the target, so a failed run keeps its evidence and cannot be mistaken for up to date (Chapter 5.4 §6).

The regression — test list, cross product, one pattern rule, per-run results, a report that decides the exit status — is entirely tool-independent (Chapter 6.3, Chapter 6.4).

What is left to make per-tool is a handful of lines: the command name, the option spellings, and whether there are one or two of them. That short list is exactly what Chapter 7.2 puts behind a single variable.

9. Common Mistakes

Putting a tool directory on PATH from inside the Makefile. Measured: it worked for two tools and failed for the third in the same Makefile, because Make executes metacharacter-free recipe lines without a shell and resolves them with its own PATH (§6).

Hardcoding the tool name. Then you cannot point at a specific version, and you cannot substitute a wrapper to see what the flow is doing (§6).

Assuming the option spellings match across vendors. -timescale=1ns/1ps is one argument for VCS and -timescale 1ns/1ps is two for xrun; the seed is a plusarg for one tool and an option for the others (§5).

Treating xrun's one-step mode as a free simplification. There is no image artifact, so every run recompiles unless you split the stages (§3).

Writing the Questa do-file by hand into the repository. Generate it, so it cannot drift from the flow that uses it (§4).

Omitting onerror {quit -code 1} from a do-file. A batch run that hits an error then waits, and the farm kills it much later (§4).

Trusting the simulator's exit status. All three exit 0 on a test that reported errors; read the log (§8).

Debugging a command you have not looked at. make -n, or a wrapper that records argv, answers "what did the tool actually get?" in seconds (§7).

10. Check Your Understanding

Question 1. A Makefile exports PATH with a tool directory prepended. Two of its three tool recipes work and the third reports "No such file or directory". Both working recipes end in | tee some.log. What is happening?

Question 2. Why does the VCS flow give Make something to depend on that the one-step xrun flow does not, and why does that matter for a regression?

Question 3. A flow is ported from VCS to Xcelium and the seed stops having any effect, with no error from the tool. What is the most likely cause?

Question 4. You suspect the Makefile is dropping an option. Give two ways to find out that do not require running the simulator.

11. Exercise

The transcripts below are real captures from the mock tools described in the opening callout. If you have a real simulator, do the same exercise with the wrap script from §7 and compare its argv against these.

Step 1. Run the VCS shape and look at what the Makefile composed:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make vcs-run VCS=./tools/mock/vcs
cat build/axi_smoke/1/vcs.argv
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
MOCK vcs: compiling from 'filelists/all.f' -> build/axi_smoke/1/simv
MOCK simv: UVM_TESTNAME=axi_smoke seed=1
UVM_ERROR :  0
-full64
-sverilog
-timescale=1ns/1ps
-f
filelists/all.f
-o
build/axi_smoke/1/simv
-l
build/axi_smoke/1/compile.log

Step 2. Run the Xcelium shape and compare the timescale option:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make xrun-run XRUN=./tools/mock/xrun
sed -n '3,4p' build/axi_smoke/1/xrun.argv
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
-timescale
1ns/1ps

Two lines, so two arguments — against VCS's single -timescale=1ns/1ps.

Step 3. Run the Questa shape and read the generated do-file:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make questa-run VSIM=./tools/mock/vsim
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
MOCK vsim: running do-file 'build/axi_smoke/1/run.do' test=axi_smoke seed=1
MOCK vsim: do-file contents:
    | onerror {quit -code 1}
    | run -all
    | quit -code 0
UVM_ERROR :  0

Step 4. Reproduce the PATH trap. Replace the three $(TOOL) variables with bare tool names, add export PATH := $(CURDIR)/tools/mock:$(PATH) at the top, and run all three targets:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
make: vcs: No such file or directory
make: *** [vcs-run] Error 1

xrun-run and questa-run will both succeed. Now add a ; to the end of the vcs compile line and run it again — it works. Predict why before you look at §6, then confirm by running make -n vcs-run and asking yourself which of those lines needs a shell.

Step 5. Put the variables back, and check that a wrapper can stand in for the tool:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
printf '#!/bin/bash\ntool="$1"; shift\nprintf "%%s\\n" "$@" >> argv.log\nexec "$tool" "$@"\n' > wrap
chmod +x wrap
make xrun-run XRUN='./wrap ./tools/mock/xrun'
wc -l argv.log
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
      11 argv.log

Eleven lines — the same eleven arguments §3 captured, now recorded by a wrapper instead of by the tool itself. The argv lands in argv.log and the flow behaves exactly as before. That is the technique to keep: it works with a real tool, needs no license, and answers a different question from any log.

12. Remember

  • A Makefile's job with a tool is to compose the right command and react to what comes back — so "what argv did it build?" is the question worth being able to answer.
  • All three simulators agree on most of a flow: a filelist with -f, the test name as +UVM_TESTNAME=, and a log you must read because the exit status will not tell you.
  • The shapes differ. VCS is two commands and leaves an image Make can depend on; xrun is one command and leaves no artifact; Questa is one batch command driven by a generated do-file.
  • The seed is the only real divergence: +ntb_random_seed=<n> for VCS, -svseed <n> for Xcelium, -sv_seed <n> for Questa. Carried over unchanged it fails silently.
  • Option spelling differs in ways that bite: -timescale=1ns/1ps is one argument, -timescale 1ns/1ps is two.
  • Generate the Questa do-file, and start it with onerror {quit -code 1} so a batch error cannot hang the job.
  • Name every tool in a variable. It lets you pin a version and substitute a wrapper.
  • Never put a tool directory on PATH from inside a Makefile. Measured: the same export worked for two tools and failed for a third, because Make runs metacharacter-free recipe lines without a shell and resolves them with its own PATH.
  • That bug moves. Adding or removing an unrelated pipe makes it appear or vanish, in a different tool each time.
  • Record argv when a flow misbehaves. make -n, an un-silenced recipe line, or a four-line wrapper — all cheaper than reading a log for an option that was never passed.
  • Almost nothing from Modules 5 and 6 is tool-specific. The filelist, run directories, stamp-not-log rule, regression and report all carry over unchanged.

Three tools, three recipes, and a short list of genuine differences. Writing them as three separate target sets — as this chapter did, for clarity — is not how you would ship it: the moment a second simulator is in regular use, three parallel sets of recipes start to drift, and the one nobody runs daily is the one that breaks. Chapter 7.2 collapses them into one flow chosen by a single variable, with the per-tool differences in one file each.

Where this fits

Part of the Makefile curriculum.