Python Part II · Module 17 · Thinking Like a VLSI Automation Engineer
Reading Someone Else's Script
The script you will spend most of your time with is one you did not write. It has no docstring, its author has changed project, and it has been running nightly for two years, which means something depends on every strange thing in it. The instinct is to start at line one and read down, and it is the wrong instinct — it takes forty minutes and still leaves you unable to answer the question you actually have, which is what this thing does when the input is not what it expects. This chapter is the model from the rest of the module run backwards, as a procedure with an order and a time budget.
Intermediate12 min readPython for VLSICode ReadingMaintainabilityEngineering JudgmentVerification Flow
Part II · Module 17 · Chapter 17.12 · Reading Someone Else's Script
1. The Engineering Problem
An engineer leaves and you inherit check_regression.py. It is two hundred lines, it has no docstring, and it runs every night at two in the morning. This morning it reported something odd and somebody has asked you whether it can be trusted.
You do not need to understand the script. You need to answer four questions: what it takes, what it produces, what rule it applies, and what it does when the input is wrong. Reading it front to back answers those slowly and incidentally, if at all, because the answers are scattered across two hundred lines and three of them are usually in the last twenty.
The procedure below answers all four in under ten minutes, and it works by not reading most of the file.
2. The Four Anchors, in Order
# find in order: the argv/config read, the main loop,
# the verdict, and the exit callFour things, each greppable, in an order chosen so you never read a line twice:
- The input read —
sys.argv,open(, a config load, an environment variable. Answers what goes in. - The main loop — the
forthat walks the real data. Answers what it extracts. - The verdict — where a
returnor an assignment produces PASS, FAIL or a status string. Answers what rule it applies. - The exit —
sys.exit, or its absence. Answers what the outside world learns.
The order is not arbitrary. Anchor 1 tells you what the variables hold, which makes anchor 2 readable without tracing. Anchor 3 is usually near anchor 4, so finding one locates the other. Working in any other order means re-reading.
3. Working the Procedure
Here is an inherited script in the style these are actually written in — no docstring, a hard-coded fallback, and the author long gone:
import sys
LOGDIR = "/proj/nightly/latest"
THRESHOLD = 0
def gather(paths):
results = {}
for p in paths:
try:
with open(p) as f:
text = f.read()
except:
continue
n = 0
for line in text.splitlines():
if "UVM_ERROR" in line:
n += 1
results[p] = n
return results
def report(results):
bad = []
for p in sorted(results):
if results[p] > THRESHOLD:
bad.append(p)
print(f"{p}: {results[p]} errors")
return bad
if __name__ == "__main__":
paths = sys.argv[1:] or [LOGDIR + "/sim.log"]
res = gather(paths)
bad = report(res)
print(f"{len(bad)} of {len(res)} failed")
sys.exit(0)0 of 0 failedThe script ran with no arguments, found no file at the hard-coded path, and printed 0 of 0 failed. Hold that result; the procedure explains it.
Anchor 1 — the input read. sys.argv[1:] or [LOGDIR + "/sim.log"]. So: a list of paths from the command line, falling back to a hard-coded path when none are given. That fallback is the first finding — the script has a default input baked into it, pointing at one specific project directory.
Anchor 2 — the main loop. for p in paths inside gather, opening each and counting UVM_ERROR. So the extracted fact is an error count per file. Note what is not extracted: no end marker, no runtime, no completion check.
Anchor 3 — the verdict. In report: if results[p] > THRESHOLD. The rule is more than zero errors means bad, with the threshold as a module constant. That is the entire decision, and it is one line.
Anchor 4 — the exit. sys.exit(0), unconditionally. Whatever it found, it reports success to its caller.
Four anchors, maybe thirty lines read, and you can now answer the question you were asked.
4. The Most Important Finding Is an Absence
Three of the four anchors are things you find. The fourth question — what it does when the input is wrong — is usually answered by something you fail to find, and that requires knowing what to look for.
Three greps prove the absences in the script above:
No existence check. Nothing calls .exists() or os.path.isfile. The script discovers a missing file by trying to open it.
A bare except: continue. In gather, any failure to open a file is silently skipped. A missing log, a permissions error, a directory where a file should be — all vanish, and the file simply does not appear in results.
An unconditional sys.exit(0). The verdict never reaches the exit status.
Together those three produce the behaviour observed in Section 3, and it is worth stating precisely what it means:
RESULTS = {} # gather() skipped everything it could not open
bad = []
print(f"{len(bad)} of {len(RESULTS)} failed")
print("exit status: 0")
print()
print("what a nightly wrapper concludes:", "success" if 0 == 0 else "failure")
print("what actually happened :", "no logs were read at all")0 of 0 failed
exit status: 0
what a nightly wrapper concludes: success
what actually happened : no logs were read at allA night on which every log is missing is indistinguishable from a clean night. The script prints 0 of 0 failed, exits zero, and the dashboard is green. This is Chapter 14.9's silent handler and Chapter 1.9's missing exit status, in the same twenty-line function, found by three greps rather than by reading.
5. You Are Deliberately Not Reading the Script
Worth making explicit, because the procedure can feel like cheating.
Thirty lines of a two-hundred-line file were read. gather's counting loop was skimmed to learn what it counts, not verified. report's formatting was ignored entirely. Whether the sorting is right, whether the f-strings are tidy, whether the names are good — none of it was examined.
That is correct for this task. Reading for the four anchors is a different activity from reviewing the code, and conflating them is why inherited scripts feel unapproachable. You are building a specification from evidence, and a specification does not require understanding every line any more than knowing what a tool does requires reading its source.
The body becomes worth reading when one of the four answers is surprising, and then you read only the part that produced the surprise. Here, the surprise is 0 of 0 failed, and it leads to exactly one function.
6. The Deliverable
The output of the ten minutes is Chapter 1.5's five-part model, filled in from the code rather than written before it:
# problem : how many of last night's runs had UVM_ERRORs?
# input : paths from argv, defaulting to /proj/nightly/latest/sim.log
# processing : count UVM_ERROR lines per file; unreadable files are SKIPPED
# decision : a file is bad if count > THRESHOLD (0)
# output : per-file counts and an "N of M failed" line
# exit : ALWAYS 0 -- the verdict never reaches the caller
#
# findings:
# - a missing log is silently skipped (bare except in gather)
# - therefore "0 of 0 failed" is indistinguishable from a clean night
# - no completion check: a truncated log counts as clean
# - exit status is hard-coded 0, so no wrapper can detect failureSix lines of model and four findings. That block is the thing to hand back to whoever asked, and it is also the docstring the file never had — so paste it at the top of the script, where it saves the next person the same ten minutes.
The findings are ordered by severity, which is the judgement the procedure does not make for you. Here the exit status is the one to fix first: it is one line, and until it changes no amount of improvement inside the script is visible to the nightly flow.
7. When Ten Minutes Is Not Enough
The honest limit.
The procedure assumes a script with one job. A six-hundred-line file that launches simulations, parses results, writes a report and emails it has four sets of anchors, and the right move is to find the top-level main first and treat each stage as a separate ten-minute pass.
It also assumes the anchors exist. A script with no main, no functions and three hundred lines at module level has no loop to find because it has nine. For those, fall back to the one question that always works: what does it exit with, and on which paths? Finding every sys.exit and working backwards from each is slower but bounded, and it answers the question that matters most.
And it assumes you are answering a question rather than taking ownership. Inheriting a script permanently is a different job, and the first thing to do with the ten minutes' output is use it as the specification for the tests that do not exist yet — which is Module 33, and which this module's whole arc has been pointing at.
8. What to Do With the Findings
The ten minutes produces a document, and a document changes nothing. What happens next is a judgement the procedure does not make for you, and getting it wrong in either direction is common.
Do not immediately fix everything. The script has been running for two years, which means every strange thing in it is load-bearing until proven otherwise. The hard-coded fallback path in Section 3 looks like an obvious defect; it may also be the only reason a particular nightly job works, because somebody removed its argument three years ago and nobody noticed. Changing it without checking is how a reading exercise becomes an incident.
Separate the findings into three kinds, because they want different responses.
Actively dangerous — the unconditional sys.exit(0) is the example. It means no caller can detect failure, which means the script's output is currently trusted on no evidence. This is worth fixing before anything else, it is a one-line change, and it is also the one most likely to reveal problems rather than cause them: the night after you fix it, the nightly may well go red for the first time, and that is the fix working.
Latent — the bare except: continue, the missing completion check. Real, not currently on fire, and worth a ticket rather than an afternoon. These are also the findings most likely to be fixed properly by whoever next has a reason to touch the function.
Observations — the hard-coded path, the threshold as a module constant, the absence of a docstring. Worth recording and not worth acting on alone.
Write the findings where the next person will meet them. The six-line model from Section 7 pasted in as the docstring is worth more than the same text in a ticket, because the next person to open the file is not going to search the tracker first. A ticket is for the work; the docstring is for the knowledge.
And say what you did not check. The procedure deliberately skipped most of the file. If somebody asks whether the script is trustworthy, the honest answer names its boundary: I checked what it takes, what it decides and what it reports; I did not verify that the counting is correct. That sentence is the difference between a useful review and an endorsement you cannot support — and the counting is exactly the kind of thing Module 33's tests exist to settle properly.
9. Exercises
Reasoning, with a real script.
Exercise 1 — Four anchors, ten minutes
Take an inherited script from your flow and find the four anchors in order. Time yourself and note which was hardest to locate.
Exercise 2 — Prove the absences
Run the three greps from Section 4 against the same script: existence checks, bare excepts, and the exit calls. Write down what you did not find.
Exercise 3 — Predict the bad night
From the anchors alone, predict what the script does when its input is missing. Then arrange that input and check.
Exercise 4 — Fill in the model
Produce the six-line model plus findings for your script. Paste it in as the docstring.
Exercise 5 — Rank the findings
Order your findings by severity and justify the top one. If the exit status is hard-coded, it is almost certainly first.
Exercise 6 — The no-function case
Find a script with no functions at all and apply the fallback from Section 7: locate every exit and work backwards. Say what that told you that reading forward would not have.
10. Related Tutorials
The scripts you are most likely to inherit:
- Automating GLS Triage — the shape and the typical defects of a triage script written in a hurry.
- GLS in Regression — the nightly context that makes a hard-coded exit status expensive.
What the findings usually touch:
- UVM Report Server — the format an inherited parser is matching against, and which may have moved under it.
- Coverage Closure and Signoff — downstream consumers of numbers produced by scripts nobody has read in two years.
11. Summary
- You do not need to understand an inherited script — you need four answers: input, processing, decision, exit.
- The four anchors are greppable: the argv or config read, the main loop, the verdict, the exit call.
- The order matters — it is chosen so you never read a line twice.
- Reading for anchors is not reviewing the code, and conflating them is why inherited scripts feel unapproachable.
- Thirty lines of two hundred is the right amount to read for this question.
- The most important finding is usually an absence: no existence check, a bare
except, an unconditional exit. - When you cannot find the handling, it does not exist — write that down rather than continuing to search.
- A bare
except: continueplussys.exit(0)makes a night with no logs identical to a clean night. - The deliverable is the five-part model filled in from the code, plus findings ordered by severity — and it becomes the docstring the file never had.
- A hard-coded exit status is almost always the first thing to fix, because until it changes nothing else is visible to the flow.
- Do not fix everything immediately — in a two-year-old script every strange thing is load-bearing until proven otherwise.
- Sort findings into dangerous, latent and observations, which want a fix, a ticket and a note respectively.
- Fixing the exit status may turn the nightly red for the first time, and that is the fix working.
- Put the model in the docstring, not just a ticket, and state plainly what you did not check.
That closes this module. You can decide whether a task deserves a script, specify what it takes and produces, enumerate how it fails, make the verdict visible to automation, keep the logic reachable, recognise which stage a script is at, and reconstruct all of that from somebody else's two hundred lines. The rest of the curriculum is the language you will write it in.
Where this fits
Part of the Python curriculum.
