Skip to content
VLSI Mentor

Python Part II · Module 17 · Thinking Like a VLSI Automation Engineer

Naming the Inputs and Outputs Before Coding

Two of the seven questions from the previous chapter deserve a chapter of their own, because they look like the easy ones and are where most of the expensive ambiguity hides. Everyone can say what their script takes and produces — “it reads the log and prints whether it passed” — and that sentence contains at least four unanswered questions, each of which will be answered eventually by whoever is debugging the script at the time. Answering them now costs ten minutes and a sentence. Answering them later costs a rewrite, because by then somebody has built something on top of the guess.

Intermediate12 min readPython for VLSIScript DesignInterfacesEngineering JudgmentVerification Flow

Part II · Module 17 · Chapter 17.6 · Naming the Inputs and Outputs Before Coding

1. The Sentence Everybody Writes

It reads the log and prints whether it passed.

That is the specification most flow scripts actually have, and it is not useless — it conveys the intent. It is just not yet a specification, because at least four people could implement it four different ways and all four would be defensible.

Which log? One, or all of them in a directory? Where does the path come from? What are the possible answers, exactly? And what does "prints" mean to a Makefile, which cannot read?

The goal of this chapter is a pair of sentences precise enough that you could hand them to a colleague, go on holiday, and recognise what they built.

2. The Target

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# in : a path to one simulation log
# out: "PASS" | "FAIL" | "NO_FINISH" + an exit status

Two lines. Read what they settle that the earlier sentence did not: exactly one log rather than a directory; a path supplied rather than discovered; three possible verdicts rather than two; and an exit status listed as a separate output rather than folded into "prints".

Everything below is how you get from the first sentence to these two.

3. Interrogating the Input

Take "it reads the log" and ask four questions.

How many? One log, a directory of logs, or a list of paths? These are three different scripts with three different output shapes, and choosing late means writing two of them. One log is usually the right answer early, because a script that handles one can be called in a loop by a script that handles many — the reverse is not true.

From where does the path come? Hard-coded, an argument, a config file, or discovered by searching? Hard-coding is fine for an afternoon and fatal for a tool. An argument is the normal answer and the one Module 22 formalises.

Who produces it, and when? This is the question people skip, and it has teeth. A simulation log is being written while the simulation runs. A parser that opens it at the wrong moment reads a file that is genuinely incomplete — not corrupt, just not finished yet — and gets the same evidence a crashed run would produce.

What does it guarantee? Encoding, line endings, whether a header is always present, whether the end marker is always last. Most of these are inherited from a tool you do not control, which means the honest answer to several of them is unknown, so the script must check — and that is Chapter 1.8.

The resulting sentence:

In: the filesystem path of one completed sim.log, supplied by the caller as an argument.

The word "completed" is carrying real weight. It says the script is not responsible for waiting, and therefore that something else must be — which is a decision now recorded rather than discovered during a race condition.

4. There Are Two Outputs

Now "prints whether it passed", which hides the more important half.

A flow script almost always has two outputs with different audiences:

AudienceFormatRead by
Output 1a persontext, in Englishan engineer at a terminal
Output 2a programan exit statusa Makefile, a CI job, an LSF wrapper

"Prints the result" specifies the first and silently omits the second, and that omission is why so many working scripts have to be rewritten the first time somebody tries to use them in a pipeline. A wrapper is not going to parse your sentence; it checks a number.

The two are independent. A script can print a beautiful summary and tell a Makefile nothing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import sys

LOG = ["UVM_ERROR drv.sv(88) bad handshake", "Simulation complete"]

errors = 0
for line in LOG:
    if "UVM_ERROR" in line:
        errors += 1

print(f"FAIL ({errors} errors)")
print("exiting with status:", 0)
sys.exit(0)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
FAIL (1 errors)
exiting with status: 0

The human sees FAIL. The shell sees success, because nothing set the status. Chapter 1.9 is entirely about that one line, and it is in this chapter only to establish that the exit status is an output that must be specified, not an implementation detail to be remembered later.

5. List the Whole Set, Not an Example

The second thing a precise output sentence must do is enumerate.

It prints "PASS" or "FAIL".

This is the most common output specification in flow scripting and it is almost always incomplete, because a third case exists and has not been thought about yet. When it arrives — a truncated log, a missing file, an unparseable line — the script emits something that was never in the spec, and every consumer meets a value it has no branch for.

Compare:

Out: exactly one of PASS, FAIL, NO_FINISH; plus an exit status of 0 for PASS and 1 otherwise.

"Exactly one of" is doing the work. It is a closed set, and a reader can now write a complete if chain without guessing. It also forces the question is three enough? at a point where adding a fourth is free.

The effect on a consumer is immediate:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
VERDICTS = ["PASS", "FAIL", "NO_FINISH", "WHAT_IS_THIS"]


def summarise(verdict):
    """A consumer written against a CLOSED set of three."""
    if verdict == "PASS":
        return "  clean"
    if verdict == "FAIL":
        return "  design bug"
    if verdict == "NO_FINISH":
        return "  infrastructure problem"
    return f"  UNEXPECTED VERDICT {verdict!r} -- the producer broke its contract"


for v in VERDICTS:
    print(f"{v:<13}{summarise(v)}")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
PASS           clean
FAIL           design bug
NO_FINISH      infrastructure problem
WHAT_IS_THIS   UNEXPECTED VERDICT 'WHAT_IS_THIS' -- the producer broke its contract

The last branch exists only because the set was declared closed. A consumer written against "PASS or FAIL" would have had an else meaning "FAIL", and a NO_FINISH would have been silently filed as a design bug — which is the wrong team's afternoon.

6. The Specification Goes in the Docstring

Once both sentences exist, they have a home — the function's docstring, where the next reader will actually find them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
END_MARKER = "Simulation complete"


def classify_run(lines):
    """Decide whether one simulation run was clean.

    In : the lines of one completed sim.log.
    Out: exactly one of "PASS", "FAIL", "NO_FINISH".

    NO_FINISH means the end marker is absent, which indicates the
    simulator was interrupted rather than that the design misbehaved.
    """
    errors = 0
    finished = False
    for line in lines:
        if "UVM_ERROR" in line:
            errors += 1
        if END_MARKER in line:
            finished = True
    if not finished:
        return "NO_FINISH"
    return "PASS" if errors == 0 else "FAIL"


CASES = {
    "clean": ["UVM_INFO ok", "Simulation complete"],
    "failed": ["UVM_ERROR drv.sv(88)", "Simulation complete"],
    "cut": ["UVM_INFO ok"],
}

for name in sorted(CASES):
    print(f"{name:<7} -> {classify_run(CASES[name])}")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
clean   -> PASS
cut     -> NO_FINISH
failed  -> FAIL

Three lines of docstring that a reader meets before the code, stating what arrives, the closed set of what leaves, and the one piece of domain meaning that is not obvious from the name. The function body is then unsurprising, which is the goal.

Note what the docstring does not say: anything about files, paths or how the lines were obtained. classify_run takes lines, not a path — a separation Chapter 1.11 makes the case for properly, and which the input sentence made visible by distinguishing "a path" from "the contents".

7. The Ambiguity Is Free Now and Expensive Later

The canonical purpose's phrase is worth taking literally. Here is the same ambiguity resolved at three different times.

At the desk, before coding. You ask "one log or a directory?", decide one, and write it down. Cost: ten seconds.

During implementation. You are halfway through and realise the caller has a directory. You adapt, and the function now takes either a path or a list depending on what it is handed. Cost: an hour, plus a function with two shapes.

After release. Three scripts call yours, two pass a file and one passes a directory because its author guessed. The behaviour is now load-bearing in both forms and cannot be changed without breaking somebody. Cost: the ambiguity is permanent.

Nothing about the engineering got harder between those three points. The only thing that changed is how much was built on top of the unanswered question — which is the argument for answering it at the only moment when nothing is.

8. The Inputs You Did Not Know You Had

The input sentence covers what the script is handed. Most scripts also consume several things nobody thinks of as input, and every one of them is a way the same script produces different answers on two machines.

The working directory. A relative path is resolved from wherever the process was started, not from where the script lives. A script that works when run from its own folder and fails from a job scheduler has not changed; the directory it was asked from did. This is the most common of the hidden inputs and the one that produces the most confusing bug reports, because the person reporting it is genuinely running the same command.

Environment variables. PROJECT_ROOT, PATH, the licence server address, and whatever the site's setup scripts export. A script reading one of these has an input that does not appear in its argument list, is different for every user, and is invisible in the command line somebody pastes into a ticket.

Configuration files. A config read from a fixed location is an input. One read from a location that itself depends on an environment variable is two.

The clock. Anything that uses today's date — picking "the latest run directory", naming an output, deciding whether something is stale — produces a different answer tomorrow. That is sometimes exactly right and should still be written down, because it means the script cannot be re-run later to reproduce an old result.

The tool versions on the path. A parser's real input is not "a log" but "a log produced by whichever simulator version this machine has", and an upgrade changes the input without changing anything you wrote.

The practical move is not to eliminate these — several are unavoidable and some are desirable. It is to list them in the input sentence so they are visible:

In: the path of one completed sim.log, supplied as an argument; resolved relative to the working directory; $PROJECT_ROOT must be set.

That sentence is longer and it is honest. Someone debugging at 3am can read it and check three things rather than guessing.

A useful test for finding the hidden ones: could this script give a different answer on a colleague's machine with the same arguments? Every yes is an input you have not written down. The strictest version of the discipline — pass everything explicitly, read nothing from the environment — is a reasonable goal for anything a team depends on and overkill for a personal helper, which is the proportionality rule from Chapter 1.4 applied one more time.

9. Exercises

Reasoning, with one short program.

Exercise 1 — Two sentences

For a script you are about to write, write the input sentence and the output sentence. Each must fit on one line and name a closed set where applicable.

Exercise 2 — Interrogate an input

Take "it reads the log" and answer all four questions from Section 3 for your own flow. Note which answer you did not know.

Exercise 3 — Find the second output

Take a script your team already runs and determine what exit status it produces on failure. If the answer is 0, describe what a Makefile wrapping it would conclude.

Exercise 4 — Close the set

Find a script whose output is specified as "PASS or FAIL" and determine what it actually prints when the input is missing. Add that value to the specification.

Exercise 5 — The unexpected verdict

Write a consumer with an explicit branch for an unrecognised verdict, as in Section 5. Say what you would have it do in production: warn, or stop.

Exercise 6 — Docstring as specification

Write the three-line docstring for a function you already have. If you cannot state the output as a closed set, that is the finding.

Where the input's guarantees come from:

  • UVM Report Server — the component that decides what an error line looks like, and therefore what your input format actually is.
  • Verbosity Control — a switch that changes the content of your input without changing its name.

What consumes the output:

11. Summary

  • "It reads the log and prints whether it passed" is an intent, not a specification — it leaves at least four questions open.
  • Interrogate the input: how many, from where, produced by whom and when, and what it guarantees.
  • "One log, supplied as an argument" beats "the logs", because a script handling one can be called in a loop; the reverse is not true.
  • "Completed" in an input sentence is a real decision — it says waiting is somebody else's job.
  • There are two outputs, not one: text for a person and an exit status for a program.
  • "Prints the result" silently omits the second, which is why working scripts get rewritten the first time they meet a Makefile.
  • Enumerate the output as a closed set — "exactly one of" — rather than giving an example.
  • A consumer written against an open set files unknown verdicts under the wrong category, which is the wrong team's afternoon.
  • The two sentences belong in the docstring, where the next reader meets them before the code.
  • Most scripts have hidden inputs: the working directory, environment variables, config files, the clock and the tool versions on the path.
  • The test is whether a colleague with the same arguments could get a different answer — every yes is an unwritten input.
  • List the hidden inputs in the input sentence rather than eliminating them; a longer honest sentence beats a short misleading one.
  • The ambiguity is free to fix now, costs an hour during implementation, and becomes permanent after release — nothing about the engineering changed, only how much was built on the guess.

The next chapter takes the hardest of the seven questions on its own: what the script should do when the input is wrong.

Where this fits

Part of the Python curriculum.