Linux & Bash · Part II · Module 25 · EDA Environment and Tool Access
What a Tool Needs Before It Will Start
An EDA tool that will not launch is almost never broken. It is missing one of five things, and each of them produces a different message from a different source. This chapter turns that into a checklist you can run — measured against a real tool, built for the purpose, that fails in all five ways on demand.
Intermediate21 min readLinuxPart IIEDAEnvironmentDiagnosis
Part II · Module 25 · Chapter 25.1 · What a Tool Needs Before It Will Start
"The tool does not work" is almost always "the tool did not start", and those are different problems. A tool that does not start has not read your design, so nothing about your design can be the cause.
1. No Commercial Tool Is Installed Here, So One Was Built
The honest position first:
$ for t in vcs xrun simv vsim lmstat; do command -v $t > /dev/null || echo "$t absent"; done
vcs absent
xrun absent
simv absent
vsim absent
lmstat absentSo rather than invent transcripts, this module uses a tool written for it: a small C program called simtool, linked against a real shared library, which checks a licence file, looks for a feature line in it, and verifies that its temporary directory is writable — exiting with a different status for each failure. It is not a simulator, and it does not need to be: the subject of this module is everything that happens before a simulator reads a single line of Verilog, and that part is faithfully reproduced.
Where a statement needs a commercial tool, it is attributed rather than shown — Chapter 22.1 §7's rule.
2. The Five Preconditions
Five things a tool needs, and who complains when each is missing
| # | Precondition | Who reports the failure | Typical status |
|---|---|---|---|
| 1 | An executable on PATH | the shell | 127 |
| 2 | Its runtime libraries | the dynamic loader | 134 here |
| 3 | A licence it can read | the tool | tool-specific |
| 4 | Writable temp and home areas | the tool, or a crash | tool-specific |
| 5 | Project variables it requires | the tool, or wrong results | tool-specific |
The first two are resolved before the tool's own code runs, which is why their messages do not look like tool errors and why searching a vendor's documentation for them finds nothing.
3. It Works When Everything Is Present
The baseline, which you need before any of the failures mean anything:
$ printf 'FEATURE simtool 2024.0901 permanent\n' > licenses/simtool.lic
$ SIMTOOL_LICENSE_FILE=$PWD/licenses/simtool.lic ./tools/simtool-2024.09/bin/simtool chip_top
simtool 2024.09 starting (simlib 1.4.2)
simlib: elaborating chip_top
$ echo $?
0Two lines of output and status 0. Note that the second line comes from the shared library, not from the tool — which will matter in §5.
4. The Three Failures the Tool Reports Itself
No licence variable at all:
$ ./tools/simtool-2024.09/bin/simtool chip_top
simtool: FATAL: SIMTOOL_LICENSE_FILE is not set
$ echo $?
3The variable is set but the file is not there:
$ SIMTOOL_LICENSE_FILE=/nope.lic ./tools/simtool-2024.09/bin/simtool chip_top
simtool: FATAL: cannot read licence file: /nope.lic
$ echo $?
4The file exists and does not grant this tool:
$ printf 'FEATURE othertool 1.0 permanent\n' > licenses/wrong.lic
$ SIMTOOL_LICENSE_FILE=$PWD/licenses/wrong.lic ./tools/simtool-2024.09/bin/simtool chip_top
simtool: FATAL: no simtool feature in /home/engineer/eda/licenses/wrong.lic
$ echo $?
5Three distinct statuses — 3, 4, 5 — for three distinct causes, and each message names the thing to fix. That is what a well-behaved tool does, and it is worth noticing because it is the standard against which you should judge the real ones. Chapter 21.4 argued for exactly this; here it is from the caller's side.
And a fourth, which is the one people least expect:
$ mkdir -p /tmp/ro25 && chmod 500 /tmp/ro25
$ SIMTOOL_LICENSE_FILE=$LIC TMPDIR=/tmp/ro25 ./tools/simtool-2024.09/bin/simtool chip_top
simtool: FATAL: TMPDIR is not writable: /tmp/ro25
$ echo $?
6A tool needs somewhere to write. Not just its output directory — a scratch area for intermediate files, which it takes from TMPDIR and which on a compute node may be a local disk that is full or a path that does not exist on that machine. Chapter 22.4 is where that lives; the symptom here is a tool that refuses to start for a reason unrelated to anything you did.
5. The Failure That Is Not the Tool's
Move the shared library away and the tool never runs at all:
$ mv tools/simtool-2024.09/lib/libsim.dylib /tmp/
$ SIMTOOL_LICENSE_FILE=$LIC ./tools/simtool-2024.09/bin/simtool chip_top
dyld[20842]: Library not loaded: @rpath/libsim.dylib
Referenced from: <…> /home/engineer/eda/tools/simtool-2024.09/bin/simtool
Reason: tried: '/home/engineer/eda/tools/simtool-2024.09/lib/libsim.dylib' (no such file), …
$ echo $?
134Four things in that output are different in kind from §4's:
The prefix is dyld[20842], not simtool. The dynamic loader is speaking, and the tool's own code has not executed a single instruction. No amount of tool configuration will change this message.
The status is 134, which is 128 + 6 — a process killed by signal 6, SIGABRT. Chapter 7.6 established the 128 + N rule; this is it appearing where you would not expect a signal at all.
It lists every path it tried, which is the most useful loader diagnostic there is and the reason to read the whole message rather than the first line.
And the licence was present and irrelevant. Precondition 2 fails before precondition 3 is examined, which is why the diagnosis order in §2 matters: checking the licence first would have told you nothing.
Chapter 25.3 is this failure in full, including why the variable that fixes it behaves differently on macOS than on Linux.
6. The Checklist as a Script
#!/usr/bin/env bash
# toolcheck — why will this tool not start?
# usage: toolcheck <tool> [licence-variable]
# exit: 0 all five preconditions look satisfied · 1 a problem was found · 2 usage
set -o nounset
[ $# -ge 1 ] || { printf 'usage: toolcheck <tool> [licence-variable]\n' >&2; exit 2; }
tool=$1
licvar=${2:-}
bad=0
note() { printf '%-4s %s\n' "$1" "$2"; }
# 1. is it on PATH, and which one did we get?
path=$(command -v -- "$tool" 2>/dev/null) || {
note FAIL "$tool is not on PATH"
printf ' PATH entries:\n'; printf '%s\n' "$PATH" | tr ':' '\n' | sed 's/^/ /'
exit 1; }
note ok "found: $path"
case $path in
/*) : ;;
*) note WARN "command -v returned a bare name — $tool is a function or builtin, not a program" ;;
esac
# a second one further along PATH is worth knowing about
others=$(command which -a -- "$tool" 2>/dev/null | sed '1d') || others=
[ -z "$others" ] || { note WARN "other copies on PATH:"; printf '%s\n' "$others" | sed 's/^/ /'; }
# 2. are its runtime libraries resolvable?
if command -v otool > /dev/null 2>&1; then
missing=$(otool -L -- "$path" 2>/dev/null | sed '1d' | awk '{print $1}' |
while IFS= read -r l; do
case $l in (@*) continue ;; esac # rpath-relative: resolved at run time
[ -e "$l" ] || printf '%s\n' "$l"
done)
elif command -v ldd > /dev/null 2>&1; then
missing=$(ldd -- "$path" 2>/dev/null | awk '/not found/ {print $1}')
else
missing=''; note WARN "neither otool nor ldd is available; skipping the library check"
fi
if [ -n "${missing:-}" ]; then
note FAIL "unresolved libraries:"; printf '%s\n' "$missing" | sed 's/^/ /'; bad=1
else
note ok "runtime libraries resolve"
fi
# 3. does the no-licence path work?
"$path" -version > /dev/null 2>&1
vrc=$?
if [ "$vrc" -eq 0 ]; then
note ok "-version runs, so the executable and its libraries are fine"
elif [ "$vrc" -ge 128 ]; then
note FAIL "-version died on signal $(( vrc - 128 )) (status $vrc) — run it directly and read the loader's message"
bad=1
else
note WARN "-version exited $vrc; the tool may use a different flag"
fi
# 4. the licence variable, if one was named
if [ -n "$licvar" ]; then
eval "licval=\${$licvar:-}"
if [ -z "${licval:-}" ]; then
note FAIL "$licvar is not set"; bad=1
else
note ok "$licvar=$licval"
IFS=: read -r -a parts <<< "$licval"
for p in "${parts[@]}"; do
case $p in
*@*) note ok " $p looks like port@host (not checked from here)" ;;
*) [ -r "$p" ] && note ok " $p is readable" || { note FAIL " $p is not readable"; bad=1; } ;;
esac
done
fi
fi
# 5. writable scratch and home
for d in "${TMPDIR:-/tmp}" "$HOME"; do
[ -w "$d" ] && note ok "writable: $d" || { note FAIL "not writable: $d"; bad=1; }
done
[ "$bad" -eq 0 ]$ export PATH=$PWD/tools/simtool-2024.09/bin:$PATH
$ SIMTOOL_LICENSE_FILE=$PWD/licenses/simtool.lic toolcheck simtool SIMTOOL_LICENSE_FILE
ok found: /home/engineer/eda/tools/simtool-2024.09/bin/simtool
ok runtime libraries resolve
ok -version runs, so the executable and its libraries are fine
ok SIMTOOL_LICENSE_FILE=/home/engineer/eda/licenses/simtool.lic
ok /home/engineer/eda/licenses/simtool.lic is readable
ok writable: /var/folders/…/T/
ok writable: /home/engineer
$ echo $?
0Eight decisions, each with a reason you can check:
command -v -- "$tool" and then a test for a leading /, because a bare name means a shell function or builtin rather than a program — Chapter 22.6 §2's discriminator, and a real cause of "the tool behaves differently in a script".
command which -a rather than bare which, because which may itself be a shell function or alias; command bypasses that. The sed '1d' drops the copy we already reported.
@*) continue when walking otool -L output, because an @rpath/@loader_path entry is resolved at run time against the executable's search paths and testing it as a filesystem path would report every such library as missing. Getting this wrong would make the check cry wolf on every correctly built tool.
Both otool and ldd branches, with a WARN if neither exists. ldd is absent on macOS and otool on Linux, so a script that assumes either is wrong half the time — Chapter 21.6 §1's feature test.
-version is tried and a failure only warns. The flag is not standard, which Chapter 25.7 measures across four tools — so treating its absence as an error would reject working installations.
eval "licval=\${$licvar:-}" to read a variable whose name is in a variable. This is the one place in the course where eval is the right answer in Bash 3.2, because ${!var} indirect expansion exists but ${!var:-} with a default does not compose cleanly; the value is a variable name the user supplied, and it is worth noting that an untrusted name here would be a shell-injection hole.
A -version that dies on a signal is a FAIL, not a WARN, per the callout above — and it is the only part of the script that catches a missing rpath library.
And a port@host licence entry is reported, not tested. Checking it needs a network query and a vendor tool, neither of which this script can assume — Chapter 25.4 is where that is taken apart. Saying "not checked from here" is better than a check that silently passes.
Practice
- Run
command -vfor a tool you use. Is the answer a path or a bare name? - Run
which -afor it. Is there more than one? - Run the tool's
-version(or-ID, or--version). Does it work with no licence set? - Unset your licence variable in a subshell and run a real command. What is the message and the status?
- Point the licence variable at a file that does not exist. Does the message distinguish that from the variable being unset?
- Find your tool's
TMPDIRrequirement. SetTMPDIRto a read-only directory and see what happens. - Run
otool -L(macOS) orldd(Linux) on a tool's executable. How many libraries does it need? - Identify which of those entries are
@rpath-relative and explain why a file test on them would be wrong. - Write
toolcheckand run it against a tool that works. Then break one precondition at a time and confirm each is reported. - Point it at a shell function with the same name as a tool and confirm the
WARN. - Give it a licence variable holding
port@hostand check the message. - For each of the five preconditions, write down who reports the failure on your system.
Check Your Understanding
- Why is "the tool does not work" a different problem from "the tool did not start"?
- What are the five preconditions, in checking order?
- Which two fail before the tool's own code runs, and how can you tell?
- What did statuses 3, 4, 5 and 6 distinguish?
- Why is
-versionthe first thing to run? - What does status 134 mean, and who produced the message?
- Why does the library check skip entries beginning with
@? - Why
command which -arather thanwhich -a? - Why is a
port@hostlicence entry reported rather than tested? - Why does a failing
-versiononly warn?
7. Remember
- "Did not start" is not "does not work." A tool that did not start never read your design.
- Five preconditions: executable on
PATH, runtime libraries, licence, writable temp and home, project variables. - The first two fail before the tool's code runs, so their messages come from the shell and the loader and are absent from vendor documentation.
- Measured: statuses 3, 4, 5 and 6 for an unset licence variable, an unreadable file, a missing feature, and an unwritable
TMPDIR. -versionneeds no licence — measured — so it is the first command to run and splits the diagnosis in half.- Status 134 is
128 + 6,SIGABRT, and the message isdyld's. It lists every path it tried; read all of it. - A tool needs somewhere to write.
TMPDIRon a compute node is not the one on your workstation. command -vreturning a bare name means a function or builtin, not a program.- Use
command which -a, sincewhichitself may be shadowed. - Skip
@rpathentries when testing libraries as files, or the check cries wolf on every working tool. lddis Linux,otool -Lis macOS. Feature-test for both.- Report what you did not check — a
port@hostlicence needs a network query.
8. What Is Next
Chapter 25.2 takes precondition one seriously: putting a specific tool version on PATH, and proving which executable a command actually resolved to — measured with two versions of the same tool installed side by side.
Where this fits
Part of the Linux & Bash curriculum.
