Run your own flows and scripts

Requires Odatix 4.0+

A flow is a way of running an eda tool: different options, different scripts, sometimes a different binary. Vivado ships a timing oriented flow and a power oriented one; Design Compiler ships a dc_shell flow and a dcnxt_shell one. Flows of the same tool are alternatives meant to be compared, so each one runs in its own work directory and tags the results it produces.

Adding a flow is the shortest path to running your own scripts under Odatix: you keep the tool, its metrics and its targets, and change only what runs.

The default flow

The commands declared directly in the unix / windows section of a tool.yml belong to the tool’s default flow — the one used when --flow is not given. Its name comes from the top-level default_flow key (default when absent); declaring it under flows: only attaches metadata to it.

tool.yml
default_flow: standard

unix:
  tool_test_command: [...]
  fmax_synthesis_command: [...]          # commands of the "standard" flow
  custom_freq_synthesis_command: [...]

flows:
  standard:                              # metadata for the default flow
    label: "Standard"
    description: "Timing oriented synthesis and implementation"

Declaring another flow

Every other flow is an entry of the flows section, with its own platform sections:

tool.yml
flows:
  power_opt:
    label: "Power optimized"
    description: "Automatic clock gating and power optimization pass"
    unix:
      custom_freq_synthesis_command:
        - vivado -mode tcl -notrace
        - -source $script_path/init_script.tcl
        - -source $script_path/flow_power_opt.tcl
        - -source $script_path/synth_script.tcl
        - -source $script_path/exit.tcl

A command is either a string or a list of strings; a list is joined with spaces, which is the readable way to write a long command line.

Flow keys

Key Type Description
label string Human-readable name, shown in the GUI. Defaults to the flow name.
description string One line, shown on the tool’s card in the GUI.
icon string Optional image replacing the default pictogram.
metrics_file string Metrics definition file specific to this flow. Defaults to the tool’s default_metrics_file.
unix / windows dict What this flow runs on that platform (see below).

What a platform section of a flow may declare

Key Description
tool_test_command Installation check. Inherited from the tool; only a flow running a different binary needs its own.
<job_type>_command The job type run in one shot.
<job_type>_steps The job type run as an ordered list of resumable steps.
<job_type>_session How the tool is opened for the job type, once for all the steps of a run.
constants YAML anchors, for readability only (see Anchors).

with <job_type> one of:

Job type key Run by Steps key Session key
fmax_synthesis_command odatix fmax fmax_synthesis_steps fmax_synthesis_session
custom_freq_synthesis_command odatix synth custom_freq_synthesis_steps custom_freq_synthesis_session
pnr_command odatix pnr pnr_steps pnr_session
analysis_command odatix analyze analysis_steps analysis_session

Inheritance: a flow changes only what it says

A flow starts from the default flow’s declaration for each job type and overrides only what it declares. For a given job type:

  • it declares nothing → it runs what the default flow runs;
  • it declares a <job_type>_command → it runs that, in one shot, even if the default flow is split into steps;
  • it declares <job_type>_steps → its steps are merged into the inherited ones by name;
  • it declares <job_type>_session → it is merged into the inherited session key by key, so a flow changing where the log goes, or adding a script every session must source, says that much and keeps the steps as they are.

This is why the Vivado power_opt flow above is four lines and not a copy of the whole tool: everything it does not mention still comes from standard.

A flow therefore supports every job type the tool supports, unless it explicitly replaces one. Asking for a flow that cannot run a job type is an error naming the flows that can — never a silent run of something else.

Splitting a flow into steps

Instead of one command, a job type can declare an ordered list of steps, so a run can stop at any of them and a later run picks up where it left off instead of starting over.

tool.yml
unix:
  custom_freq_synthesis_steps:
    - name: synthesis
      command: tclsh $script_path/step_synthesis.tcl $work_path
    - name: pnr
      default: true
      command: tclsh $script_path/step_pnr.tcl $work_path
    - name: bitstream
      command: tclsh $script_path/step_bitstream.tcl $work_path
Step key Description
name Identifier used by --until and --rerun-from, and recorded as the step meta key. Required.
command String or list, like any command. The step is then a process of its own.
args What the step adds to the job type’s session instead of running a command of its own.
default Optional. Marks the step a run stops at when it is not told where to. The last marked step wins; no marked step means the whole flow runs.

Being stepped is a property of what the tool runs, not a flow of its own: every flow of the tool inherits the split, and --until works the same whichever flow is picked.

Terminal
odatix synth -t vivado --until pnr        # implement, no bitstream yet
odatix synth -t vivado                    # only runs the bitstream step
odatix synth -t vivado --rerun-from pnr   # redo place & route onwards

Merging steps by name

A flow redefining <job_type>_steps does not replace the list: steps are merged by name, the inherited order is kept, a step of the same name is replaced where it already was, and steps the default flow does not have are appended.

tool.yml
flows:
  power_opt:
    unix:
      custom_freq_synthesis_steps:
        - name: synthesis            # "pnr" and "bitstream" are inherited
          command: ...

That is the honest model of what actually differs: a power optimization pass changes how the design is synthesized, and a checkpoint carries the result into a place & route that has no reason to change.

Note

An fmax search is a special case: it reruns the whole flow at every frequency it probes, so its steps are not a design carried forward but searches of increasing depth — converge on post-synthesis timing (fast, optimistic), then on post-route timing (what the design really reaches), then implement. A flow overriding one of them usually has to override all of them, since nothing is handed over between them. See Steps of an fmax search.

Running every step in one session

Steps declaring a whole command each open and close the tool once per step. For Vivado that is a minute of startup thrown away at every step boundary, and a design written to disk and read back for nothing.

Declare instead how the tool is opened — once per run — and let the steps declare only what they add to it:

tool.yml
unix:
  custom_freq_synthesis_session:
    command:
      - export LC_ALL=C; unset LANGUAGE;
      - vivado -mode tcl -notrace
      - -log $log_path/$first_step.log
    end:
      - -source $script_path/exit.tcl

  custom_freq_synthesis_steps:
    - name: synthesis
      args: [-source $script_path/step_synthesis.tcl]
    - name: pnr
      default: true
      args: [-source $script_path/step_pnr.tcl]
    - name: bitstream
      args: [-source $script_path/step_bitstream.tcl]

The steps of a run that share a session are run by a single process: Vivado opens, sources what each step adds, and exits. --until and --rerun-from are unaffected — the session covers exactly the steps the run has left to do, so resuming at pnr opens one session on pnr and bitstream alone.

Two details make this work in practice:

  • Name the log after the run, not the step. $first_step and $last_step expand to the steps the process covers, so -log $log_path/$first_step.log gives one log per run instead of a shared file a resuming run overwrites.
  • Record each step as it completes. Odatix records the steps of a process once it exits, so a session dying halfway would lose what it had already finished. Call odatix_step_done <name> (from _common/settings.tcl) at the end of each step script.

Steps declaring their own command keep a process each, and can be mixed with session steps in the same list — which is what the built-in Vivado fmax steps do, each being a whole search of its own.

Handing state over between steps

Odatix decides which steps to run; carrying the design from one to the next is the scripts’ job. Tools do it with their own checkpoint mechanism — write_checkpoint / open_checkpoint for Vivado, write -format ddc for Design Compiler, write_db for Genus.

Inside one session the design is still in memory, so a step should skip reading back what the previous one wrote. Odatix’s Vivado steps track what the process holds: odatix_open_checkpoint returns immediately when it is already the right design, and the checkpoints are written anyway, since a later run resuming in a fresh process has nothing else to start from.

The steps a job directory has completed are recorded in log/steps.yml, and the last one reached is exported as the step meta key. Metrics are exported whichever step a run ended on, so every step producing meaningful numbers must write the reports they are read from.

Variables available in commands

Commands are expanded before being run:

Variable Value
$work_path The job’s work directory (absolute).
$script_path <work_path>/scripts, where the tool’s scripts have been copied.
$log_path <work_path>/log.
$tool_path The tool’s own directory (the workspace one when it exists).
$eda_tools_path The built-in tools directory.
$odatix_path The Odatix installation directory.
$tool_install_path The tool_install_path of the target file.
$clock_signal, $top_level_module, $lib_name Of the design being run.
$source_work_path, $source_tool Place & route jobs only: the synthesis job this one continues.
$first_step, $last_step, $steps Stepped job types only: the steps the process about to run covers.

Adding a flow to a built-in tool

Create a tool.yml under odatix_userconfig/tools/<builtin name>/ holding nothing but your flow. It is merged over the built-in definition, so the tool keeps its commands, its metrics and its log formatting:

odatix_userconfig/tools/vivado/tool.yml
flows:
  retiming:
    label: "Retiming"
    description: "Timing oriented synthesis with global retiming enabled"
    unix:
      custom_freq_synthesis_steps:
        - name: synthesis
          command:
            - export LC_ALL=C; unset LANGUAGE;
            - vivado -mode tcl -notrace
            - -log $log_path/synthesis.log
            - -source $script_path/init_script.tcl
            - -source $script_path/flow_retiming.tcl
            - -source $script_path/analyze_script.tcl
            - -source $script_path/step_synthesis.tcl
            - -source $script_path/exit.tcl

Scripts of your own go in odatix_userconfig/tools/vivado/tcl/: both directories are copied into every job directory, yours last, so flow_retiming.tcl sits next to Odatix’s own scripts and can source them.

Warning

Redefining a built-in flow, the unix / windows section of a built-in tool, or its default_flow has no effect: those keys are dropped with a warning. Give your flow a new name, or duplicate the tool to own all of it.

Work directories and results

Each flow runs in work/<job type>/<tool>@<flow>/…; the tool’s default flow keeps the bare <tool> directory, so work directories produced before flows existed keep resolving.

All the flows of a tool export into that tool’s single results file, told apart by the flow meta key — which is what lets Odatix Explorer plot them against each other. The flow is also written into the job directory (flow.txt), so a full re-export (odatix res_synth) keeps the flow of results produced earlier.

flow and step are both dimensions. A result record is identified by the run it comes from — its architecture, its configuration, its target, its tool, its flow — and by the step of that flow it holds. Running the same configuration under two flows leaves two records side by side, which is what makes the flows comparable; running it through three steps leaves one record per step, which is what makes the steps comparable.

One record per step

A run split into steps measures the same design several times, each time more accurately: post-synthesis estimates first, post-route numbers after. Odatix exports one record per step, so those measurements are values of one metric at different steps rather than differently named metrics.

A metric is therefore declared once, and reads the report of the step being exported: $step in a file name resolves to the report directory that step wrote.

tools/vivado/metrics.yml
metrics:
  LUT_count:
    type: regex
    settings:
      file: report/$step/utilization.rep
      pattern: "\\| (Slice|CLB) LUTs \\s*\\|\\s*([0-9]+).*"
      group_id: 2
    format: "%.0f"

A job stopped at synthesis has one record, step: synthesis; resuming it to place & route adds a second one, step: pnr, next to the first instead of replacing it. Re-running it from an earlier step drops the records that re-run invalidated.

For this to work the tool’s scripts have to keep a copy of the reports per step, since each step overwrites the report files of the one before. Vivado’s steps do it through odatix_write_reports <step> (step_common.tcl), which writes the usual report/utilization.rep and a snapshot under report/<step>/. A step that wrote no such report simply has no value for the metrics reading it — that is not a missing file, and not an error.

A metric that belongs to one step alone still says so with step:, and is left out of every other record:

tools/vivado/metrics.yml
metrics:
  Bitstream_size:
    type: regex
    step: bitstream
    settings:
      file: report/bitstream/size.rep
      pattern: "size: ([0-9]+)"
      group_id: 1
    format: "%.0f"

The metrics tied to no step at all — Fmax, the parameter domains — are repeated in every record of the job, so selecting one step always gives a complete row.

Which step you get by default

Each record also carries _step_index (the position of the step in the flow) and _last_step (true on the furthest step the job reached). Odatix Explorer orders the Step dimension by the former, and turns the latter into a Step scope filter which starts on last alone: you see the finished results by default, and tick intermediate to compare a step against the next.

Which step is the last one belongs to the job, not to the step name: a job stopped at pnr and one that went on to bitstream each have their own, and both are shown by default.

Derived metrics follow the same rule — a source is read from the last step of its job unless the metric says otherwise:

odatix_userconfig/derived_metrics.yml
derived_metrics:
  # What place & route did to the post-synthesis estimate
  LUT_count_after_synthesis:
    metric: LUT_count
    from: synthesis
    step: synthesis
  LUT_pnr_delta:
    type: operation
    op: LUT_count - LUT_count_after_synthesis
Note

A flow name becomes part of a directory name, so it cannot contain @ (the separator), /, \, or be . / ... A flow declared with such a name is ignored.

Anchors

The built-in tool.yml files use a constants: list of YAML anchors to avoid repeating options across flows and steps:

tool.yml
unix:
  constants:
    - &vivado_unix vivado
    - &vivado_opt_unix -mode tcl -notrace

  fmax_synthesis_command:
    - *vivado_unix
    - *vivado_opt_unix

constants is not read by Odatix — it exists only so the anchors have somewhere to be defined. Anchors are resolved when the file is loaded; the graphical editor re-emits a flat, anchor-free tool.yml on save.

From the GUI

EDA Tools → Settings opens the Tool Editor, whose Flows section mirrors everything above: one card per flow, foldable, with its label, description, and, per platform and per job type, a three-way choice:

Choice tool.yml equivalent
Inherited (on the default flow: Not supported) declares nothing
Command <job_type>_command
Steps <job_type>_session (how the tool is opened, plus what runs on opening and on closing) followed by <job_type>_steps, each with its name, what it runs and a Default chip

A step’s text field holds its command when the job type opens no session, and what it adds to the session (args) when it does — steps added to a job type that declares a session join it.

One flow is marked as the default. Built-in flows appear locked; Add a flow creates yours, and the duplicate button on a flow is the fastest way to start from one that already works.

See also