Variables

Requires Odatix 3.4+

A variable is a named set of values declared at the root of a _settings.yml, under variables. Odatix expands the cross-product of all variables and produces one run per combination.

The declaration is always the same. What changes is what the values are used for:

Where variables are declared What Odatix does with them Guide
An architecture or a parameter domain with generate_configurations: Yes Writes one parameter file per combination, from template, named by name. Configuration generation
A workflow Substitutes ${var} in task commands — a virtual parameter domain, no folder required. Virtual parameter domains
An architecture with generate_rtl: Yes Substitutes ${var} in generate_command, running the RTL generation once per value. Virtual parameter domains

One page, one syntax: a range behaves identically whether it ends up in a parameter file, on a simulation command line, or in a Chisel generation command.

Declaring a variable

_settings.yml
variables:
  <name>:
    type: <type>       # mandatory
    settings:          # mandatory for every type except "bool"
      ...
    unit: <string>     # optional — annotates the value in run names
    format: <printf>   # optional — how the value is rendered
    group: <label>     # optional — pair this variable with another

Referencing a variable is done with $name or ${name}. The two forms are equivalent; use the braced form whenever the name is followed by a character that could be read as part of it (${var}bits).

Tip

Longer names are substituted first, so $WIDTH and $WIDTH_OUT can coexist without corrupting each other.

Note

variables used to be declared inside generate_configurations_settings, next to template and name. Since variables are not specific to configuration generation, they now live at the root of the settings file. Existing files keep working: Odatix still reads the former location when the root key is absent, and moves the block to the root the next time it writes the file (a save from the GUI or from the Python API, for instance).

Type summary

type Adds a dimension Mandatory settings
bool yes (none)
range yes from, to (step optional)
power_of_two yes from_2^ + to_2^, or from + to
list yes list
multiples yes from, to, base
union yes sources
disjunctive_union yes sources
intersection yes sources
difference yes sources (exactly two)
function no op
conversion no source, from, to
format no source (format optional)

All generators (bool excepted) additionally accept whitelist and blacklist.

Value generators

These types produce values of their own. Each one is a dimension: it multiplies the number of generated runs.

bool

_settings.yml
variables:
  enable_pipeline:
    type: bool

Generates {0, 1}. The only type that needs no settings.

range

_settings.yml
variables:
  var:
    type: range
    settings:
      from: 10
      to: 100
      step: 10      # optional, defaults to 1

Generates {10, 20, 30, …, 100}. Bounds are inclusive. step must not be 0.

power_of_two

_settings.yml
variables:
  var:
    type: power_of_two
    settings:
      from_2^: 5
      to_2^: 10
Generates {32, 64, 128, 256, 512, 1024}.

Both spellings give the same list. Use from_2^ when the design’s parameter is the exponent, from when it is the size. With the from / to form, bounds must be strictly positive, and a lower bound that is not itself a power of two is rounded up so no generated value falls below it.

list

_settings.yml
variables:
  var:
    type: list
    settings:
      list: [100, 225, 412, 803]

The fallback for anything that is not a regular progression. Values need not be numeric — list: [balanced, aggressive] is valid.

multiples

_settings.yml
variables:
  var:
    type: multiples
    settings:
      base: 8
      from: 8
      to: 64

Generates every multiple of base within [from:to] — {8, 16, 24, …, 64}. Equivalent to a range with a matching step, but expressed in terms of the constraint that actually matters (byte alignment, bus width, lane count).

Filtering with whitelist / blacklist

Every generator above accepts two optional settings, applied after generation:

_settings.yml
variables:
  var:
    type: range
    settings:
      from: 1
      to: 10
      blacklist: [3, 7]     # keep everything except these
  other:
    type: range
    settings:
      from: 1
      to: 100
      whitelist: [8, 16, 32]  # keep only these

Use them to punch a hole in an otherwise regular sweep (a value known to break the design for example) without breaking it into several variables.

Tip

For more complex filtering, use operations between variables instead of whitelist / blacklist.

Set operations between variables

Four types take a sources list of other variables and combine their value sets. The result is a dimension like any generator; the sources themselves generate no runs of their own.

type Result
union All values from every source.
disjunctive_union Values in exactly one source (symmetric difference).
intersection Values present in all sources.
difference Values in the first source but not in the second. Exactly two sources are required, and order matters.
_settings.yml — intersection
variables:
  mult_3:
    type: multiples
    settings: { base: 3, from: 1, to: 50 }
  mult_4:
    type: multiples
    settings: { base: 4, from: 1, to: 50 }
  inter_var:
    type: intersection
    settings:
      sources: [mult_3, mult_4]

inter_var → the common multiples of 3 and 4 in [1:50] = {12, 24, 36, 48}.

Derived variables

These types compute a value from the variables already resolved for a combination. They do not add a dimension: they ride along with the values they depend on, so they never multiply the number of runs.

function — compute an expression

_settings.yml
variables:
  var:
    type: multiples
    settings: { from: 0, to: 56, base: 8 }
  var_func:
    type: function
    settings:
      op: ${var}+7

var → {0, 8, …, 56}, var_func → {7, 15, …, 63}.

The expression is evaluated in a sandbox containing only the current variable values and the Python math module. ^ is accepted as the power operator (op: 2^$var), and the usual arithmetic and comparison operators apply — op: math.ceil(math.log2($depth)) is valid.

Tip

function is what to reach for whenever two parameters of a design are not independent: write the relationship once instead of maintaining two lists that must stay in step.

conversion — change number base

_settings.yml
variables:
  mask_hex:
    type: conversion
    settings:
      source: ${mask}
      from: dec
      to: hex

from and to each accept bin, dec and hex. The result is a string without prefix (ff, not 0xff), ready to be pasted into a source template or a command line.

format — build a string

_settings.yml
variables:
  formatted_bits:
    type: format
    settings:
      source: "${bits}"
      format: "%02d"

source is a template substituted with the current values, format an optional printf-style format applied to the result. The classic use is zero-padding so that generated names sort correctly — 04bits, 06bits, 08bits, 10bits instead of 4bits, 6bits, 8bits, 10bits (see the Sine ROM example).

Optional per-variable keys

format — render a value

Any variable — not only type: format ones — accepts a top-level format key holding a printf-style format string applied wherever the variable is substituted:

_settings.yml
variables:
  ratio:
    type: list
    format: "%.2f"
    settings:
      list: [0.5, 1, 2]

If the value cannot be formatted with the given string, Odatix warns and falls back to the plain string value.

unit — annotate the value in run names

_settings.yml
variables:
  max_speed:
    type: list
    unit: kmh
    settings:
      list: [35, 45, 55]

The unit is appended to the value in the generated domain name — max_speed/35kmh, max_speed/45kmh — reproducing by declaration what a folder-based domain would encode in its file names. It applies where variables become parameter domains, i.e. workflows and generated-RTL architectures.

group — pair variables instead of crossing them

By default, every dimension is cross-combined with the others. Variables sharing the same non-empty group label are instead zipped: matched position by position, so a couple of parameters stays paired.

_settings.yml
variables:
  width:
    type: list
    group: shape
    settings:
      list: [8, 16, 32]
  depth:
    type: list
    group: shape
    settings:
      list: [64, 128, 256]

Produces three runs — 8/64, 16/128, 32/256 — not nine. If grouped variables have different value counts, pairing is truncated to the shortest one and Odatix warns.

Where variables expand into runs

The declaration is shared; the effect depends on the context.

With generate_configurations: Yes, odatix generate writes one parameter file per combination. template and name are mandatory here.

architectures/ALU/_settings.yml
generate_configurations: Yes
generate_configurations_settings:
  template: "parameter BITS = $var;"
  name: "config_${var}"

variables:
  var:
    type: power_of_two
    settings: { from: 8, to: 64 }

Full guide: Configuration generation.

Note

A ${name} that matches a file-based parameter domain is substituted with the content of its selected parameter file, and a name matching neither a variable nor a domain is left untouched — so environment variables such as $HOME still reach the shell.

Note

In an architecture, variables only expand into runs when generate_command actually references them. An architecture whose variables exist to generate configurations (generate_configurations: Yes) keeps that sole meaning.

See also