Authoring a Custom Flow#

The built-in flows cover the usual paths – lint, synthesis, RTL-to-GDSII, signoff. Sooner or later you want something they do not do: an extra check between two steps, a tool the standard flow does not run, or a pipeline assembled out of pieces of several flows.

A flow is just a graph of tasks, and building one is a handful of lines. This page walks one end to end; Building a Flowgraph is the API reference to keep open beside it.

When you actually need one#

Reach for a custom flow last, not first. Three cheaper things handle most cases:

You want to

Do this instead

Run only part of a standard flow

[option,from] / [option,to]Run only part of the flow

Try several settings for one step

the flow’s _np argument – Parallel Job Execution

Inject a Tcl script around a tool

add_prescript() / add_postscript()Using Commercial Tools

Change which tools run, or in what order

a custom flow

Nodes and edges#

Two calls build a graph. Flowgraph.node() names a step and gives it a task to run; Flowgraph.edge() says one must finish before another starts:

from siliconcompiler import Flowgraph

flow = Flowgraph("myflow")
flow.node("import", ImportFilesTask())
flow.node("synthesis", SynthesisTask())
flow.edge("import", "synthesis")

project.set_flow(flow)

Each node is a task – a tool driver class, not a tool name. That is what lets a flow be checked before anything runs: the task knows what it needs and what it produces.

Nodes with no edge between them carry no ordering constraint, so the scheduler may run them together. Whether it does depends on [option,scheduler,maxnodes], the resources available, and how wide the rest of the flow is – leaving an edge out permits concurrency rather than guaranteeing it.

A worked example#

examples/heartbeat builds a timing-signoff flow that no built-in flow provides: stage a parasitics file into a node’s inputs, then run timing against it.

# Stage the parasitics into the timing node's inputs; sc_timing.tcl reads
# the per-corner SPEF from the step inputs (there is no SPEF fileset type).
signoff_flow.node("stage", ImportFilesTask())
signoff_flow.node("signoff", TimingTask())
signoff_flow.edge("stage", "signoff")
signoff.set_flow(signoff_flow)

Two nodes, one edge, and it replaces the flow the target installed. Note what is not here: the target is still loaded, so the PDK, the libraries and the delay models all come from it. A custom flow changes what runs, not what it runs against.

The rest of that function is worth reading for how a custom flow fits into a larger build – it is the third job in a chain, taking a netlist from the implementation run and a VCD from a simulation run. See Multi-Job Flows for that pattern.

Configuring a task in the flow#

Once a task is in a flow, find_task() reaches it to set its options:

ImportFilesTask.find_task(signoff).add_import_file(spef)

This is the general shape for tuning any node: find the task on the project, then call its accessors. It works for built-in flows too – SynthesisTask.find_task(project).set_threads(4).

Reusing pieces#

Flows compose. Flowgraph.graph() splices an existing flow in as a subgraph, which is how the built-in flows are built out of each other – the ASIC flow is a floorplanning flow, a placement flow, a routing flow and more, joined together:

flow = Flowgraph("check_then_build")
flow.graph(LintFlow(), name="lint")
flow.graph(SynthesisFlow(), name="syn")

Prefer this to rebuilding a standard pipeline node by node – you inherit its updates for free.

Checking what you built#

A flowgraph is data, so look at it before you spend an hour running it:

flow.write_flowgraph("myflow.svg")

Warning

Give a new flow its own name. A target has usually already registered a flow under the default one, and constructing another with that name resolves back to its copy – so your changes silently do not appear. The failure is quiet: the run succeeds, just not the run you wrote.

Project.run() checks the graph for correctness before executing anything: unreachable nodes, missing inputs, and tasks whose requirements are not met all fail before a tool starts.

Writing the task itself#

If no existing task wraps the tool you need, that is the next layer down: Building a Tool covers writing a driver – the setup, the command line, and reading metrics back out of the tool’s reports. The four methods a task can implement are setup(), pre_process(), runtime_options() and post_process().

See also

Building a Flowgraph for the full API, Compilation Process for how a graph is executed, and flows for the built-in ones – worth reading as worked examples before writing your own.