Tcl · Module 16
Tcl, Bash and Python: Which Does What
This is not a question of taste. Only one of the three can talk to the design database, only one is good at starting processes, and only one is pleasant for arithmetic over a pile of reports.
Foundation18 min readtclbashpythonedapart-iiautomation
Part II · Module 16 · Chapter 16.3 · Tcl, Bash and Python: Which Does What
You will meet this argument in every team, usually phrased as which language is better. That framing has no answer, because the three are not competing for the same job.
The useful framing is three prepositions:
Tcl runs inside the tool. Bash runs around it. Python runs over the data it produced.
Each describes a different relationship to the tool process, and that relationship — not preference — decides which you use.
1. The division
The arrows are the real sequence: Bash starts the tool, the tool runs your Tcl, the Tcl leaves files behind, and Python reads them afterwards.
2. The example
Three blocks, each timed, one of which fails. Each language does its own part, and all three of the scripts below really ran.
The only thing stubbed is the tool command itself — report_timing_stub reads a file instead of analysing a design, because no EDA tool exists here. Everything around it is real.
3. Tcl, inside
# inside.tcl — the shape of a script that runs INSIDE the tool.
proc report_timing_stub {block} {
# a real tool would compute this from the design database
set fp [open "runs/$block/timing.rpt" r]
set d [dict create]
while {[gets $fp line] >= 0} {
if {[llength $line] == 2} { lassign $line k v ; dict set d $k $v }
}
close $fp
return $d
}
set block [lindex $argv 0]
set d [report_timing_stub $block]
puts " $block: worst_slack [dict get $d worst_slack], violations [dict get $d violations]"
if {[dict get $d violations] > 0} { exit 1 }
exit 0 cpu_top: worst_slack 0.142, violations 0
[exit status 0]
fifo_ctrl: worst_slack -0.067, violations 14
[exit status 1]
alu_core: worst_slack 0.031, violations 0
[exit status 0]Why this part must be Tcl: it is the only one that can be inside the tool. The design database lives in the tool's process, and the only way to ask it anything is through the interpreter the tool embedded. Bash cannot reach it. Python cannot reach it. No amount of preference changes that.
Note what the script ends with: an exit status, from Chapter 15.4. That is the interface to the next layer.
4. Bash, around
#!/bin/bash
# around.sh — the shape of a script that runs AROUND the tool.
# It never looks inside a report; it launches runs and reads exit statuses.
failed=0
for block in cpu_top fifo_ctrl alu_core; do
if tclsh inside.tcl "$block" > "runs/$block/run.log" 2>&1; then
echo " $block: PASS"
else
echo " $block: FAIL (see runs/$block/run.log)"
failed=$((failed + 1))
fi
done
echo " $failed block(s) failed"
[ "$failed" -eq 0 ] || exit 1 cpu_top: PASS
fifo_ctrl: FAIL (see runs/fifo_ctrl/run.log)
alu_core: PASS
1 block(s) failed
[exit status 1]Why this part is Bash: starting processes, redirecting output to log files, and branching on exit statuses is what a shell is for. The whole loop is five lines because every one of those operations is a primitive.
Notice it never opens timing.rpt. It does not know what a slack is. It knows tclsh inside.tcl cpu_top either succeeded or did not — which is exactly the contract the Tcl script's exit established, and precisely why that status had to be honest.
5. Python, over
#!/usr/bin/env python3
"""Runs OVER the data the tool produced. It never talks to the tool."""
import pathlib, statistics
rows = []
for rpt in sorted(pathlib.Path("runs").glob("*/timing.rpt")):
d = dict(line.split() for line in rpt.read_text().split("\n") if len(line.split()) == 2)
rows.append((d["block"], float(d["worst_slack"]), int(d["endpoints"]), int(d["violations"])))
print(f" {'block':<12}{'slack':>9}{'endpoints':>11}{'viol':>6}")
for b, s, e, v in rows:
print(f" {b:<12}{s:>9.3f}{e:>11,}{v:>6}")
slacks = [s for _, s, _, _ in rows]
print(f" worst {min(slacks):+.3f} mean {statistics.mean(slacks):+.3f} "
f"total endpoints {sum(e for _,_,e,_ in rows):,} total violations {sum(v for _,_,_,v in rows)}") block slack endpoints viol
alu_core 0.031 7,733 0
cpu_top 0.142 18,422 0
fifo_ctrl -0.067 2,910 14
worst -0.067 mean +0.035 total endpoints 29,065 total violations 14Why this part is Python: by the time you want a mean, a sort by severity, a comparison against last week and a thousand-separated total, you want a language with a standard library for exactly that. statistics.mean, the :, thousands separator and the glob are all one-liners.
The same work in Tcl is possible and unpleasant — and it has no reason to be in Tcl, because the tool has already exited. The data is on disk. Nothing about it requires the design database.
6. How to decide
Three questions, in order. The first one that applies decides it:
- Does it need the design database? Only Tcl can reach it. Anything that queries cells, ports, timing or physical data is Tcl, inside the tool.
- Does it start, sequence or supervise processes? That is the shell's job — Bash, Make, or a scheduler.
- Is it computation over data already on disk? Python, after the tool has exited.
| Job | Language | Why |
|---|---|---|
| Query the netlist | Tcl | only it is inside the tool |
| Apply constraints | Tcl | the commands exist only there |
| Decide whether to continue mid-flow | Tcl | needs the tool's state |
| Launch forty runs | Bash / Make | process management |
| Retry a failed job | Bash / scheduler | reads exit statuses |
| Move, archive, clean up files | Bash | file primitives |
| Statistics across runs | Python | standard library |
| Compare two weeks of results | Python | data structures |
| Charts, spreadsheets, dashboards | Python | libraries exist |
7. Where the argument comes from
The disagreement is usually about a real frustration pointed at the wrong target.
Someone who dislikes Tcl is often describing a four-thousand-line flow script that does string processing, report parsing and statistics in the hardest possible language for those things. They are right that it is painful. The fix is not a different language inside the tool — that is not on offer. The fix is to move the parts that do not need the tool out of it.
Someone who wants to do everything in Python usually has not hit the wall yet: the design database is not reachable from outside the tool process. You can call the tool from Python, but what runs inside it is still Tcl.
You do not choose. You use all three, and the skill is knowing which part of the problem belongs where.
8. A practical rule for your own scripts
When a Tcl flow script starts to grow, ask of each section: does this line need the tool to be running?
- Querying the design, applying a constraint, reading a timing path — yes.
- Formatting a table, computing an average, deciding which of fifty runs was worst — no.
Write the first kind in Tcl. Have it emit a small, machine-readable file — one name value pair per line is enough, as in this chapter's example. Then let the tool exit, and do the second kind outside.
That one habit keeps Tcl scripts short, keeps tool licences free, and makes the analysis testable without a tool licence at all.
9. Exercises
Exercise 1. Give the one-line division of labour in three prepositions.
Exercise 2. Why can the design query not be done in Python?
Exercise 3. The Bash script never opens timing.rpt. What does it use instead, and why is that better?
Exercise 4. Name two reasons not to compute statistics inside the tool.
Exercise 5. Which language launches forty runs and retries the failures?
Exercise 6. A colleague says Tcl is a bad language, citing a 4,000-line flow script. What is actually wrong?
Exercise 7. What two things form the interface between the three layers?
Where this fits
Part of the Tcl curriculum.
