Simulation tests¶
Tests are part of the sim Flow: this page covers declaring
them in .booley/tests.toml. Test Runs, Test Variants and seeds
covers choosing them when you run sim.
tests.toml says what to verify on each sim Target: the Target's Tests,
how the testbench is told which Test to run, the Test Variants of a Test,
and how a Test Run is seeded.
[test_lists] # optional shared, reusable lists
smoke = ["reset", "basic"]
[sim] # keyed by .core Target name
tests = ["reset", "basic", "stress"]
select = "+test_id={index}" # one option token; {index} (0-based) or {name}
skip = ["stress"] # known-hangs to exclude from a full run
[sim_fast]
test_list = "smoke" # reference a shared list instead
[cpu_core] # a CPU core selecting firmware per test
tests = ["hello.elf", "coremark.elf"]
select = "--meminit=ram,{name}" # getopt argument, not a plusarg
Section keys. A section key is a bare Target name (sim) or a qualified
selector (vlnv-suffix#name, for example ["acme:uart:rx#sim"]; quote it
because of the colons). The qualifier matches the way --target does: any
trailing segments of vendor:library:name, and a version is ignored. A Target
uses the qualified key that names its core and otherwise the bare key with its
name, whatever the order of the keys in the file. A section comes from one key
only: a Target never reads another core's qualified section. Two keys naming
one Target make the Simulation Flow refuse the run and booley doctor fail,
naming both keys; a key that names no Target also fails booley doctor.
A Target section holds only these keys. Any other key fails to load, and
booley doctor names it:
| Key | Meaning |
|---|---|
tests |
The Target's Tests, in order. |
test_list |
The name of a shared [test_lists] entry to use as tests. A section sets tests or test_list, never both. |
skip |
Tests that a run of the whole Target leaves out. |
select |
The argument template that tells the testbench which Test to run. |
env |
Environment variables for the Target's build and runs. |
variants |
The Test Variants of the Target's Tests. |
seed |
The seed argument template. |
default_seed |
The Target's Default Seed. |
Test names. A Test name is not empty, is at most 512 UTF-8 bytes, is not
default, does not start with -, and contains no whitespace, control
character, +, @, <, >, quote or backslash. The same rule applies to Test Variant names. + and @
are reserved because a Test Run is written test+variant@seed. A valid name
can still contain characters that a shell interprets, such as ;, |, &,
$, (, ) or *: quote such a name on the command line. A name appears
at most once in a Target's tests list and at most once in its skip list.
select is exactly one option token (no embedded whitespace). Two
forms: a plusarg (+…), consumed by the testbench's $value$plusargs
runtime; or a getopt argument (-… / --…), forwarded verbatim to the sim
binary's own main: the form a CPU core needs when it selects boot software
with e.g. --meminit=ram,<elf>, which no plusarg can express. The template
must contain {index} (0-based) or {name}, and these are the only
substitutions. It cannot start with ++ or +-, which a simulator reads as a
different option. A Target without select uses +test_id={index}.
skip drops known-hanging / known-failing tests from a plain
booley flow sim --target <target>
run so each doesn't burn the full per-test wall-clock budget. Naming a skipped
test explicitly with repeatable exact --test <name> still runs it (an explicit override), and an
all-skip target fails preflight rather than passing with zero tests or silently
running configured-skipped work. For a
different explicit suite, use repeated --test options or --tests-file; there
is no per-invocation --skip option.
Test Variants (variants)¶
A Test Variant is one Test with a fixed list of extra run-time arguments, extra environment variables, or both. Declare a Variant for every configuration of a Test that you want to run: a longer iteration count, a fault-injection mode, a verbose log. Booley has no option that passes free-form arguments to a run, so that every run is a configuration the Project declared.
[sim]
select = "+test_id={name}"
tests = ["reset", "basic", "stress"]
[sim.variants.stress]
default = "short"
short = ["+iters=1000"]
soak = ["+iters=100000"]
fault = { args = ["+err_rate=5"], env = { FAULT_MODE = "1" } }
- The table
[<target>.variants.<test>]belongs to one Test, which must be in the Target'stests. - A Variant is a list of arguments, or a table with
args,env, or both. The list may be empty:plain = []is a Variant that adds nothing. defaultis required and names one of the declared Variants. The bare Test name means that Variant:--test stressand a run of the whole Target both runstress+short. Every other Variant runs only when it is named, as in--test stress+soak.- Each argument is one token that starts with
+or-and contains no whitespace. Likeselect, it cannot start with++or+-. - Variant arguments reach the simulator command after the
selectargument. They are run-time inputs only: two Variants of a Test share one simulator build.
A Variant cannot change what the Target or Booley already decides:
- An argument cannot set the option of the
selecttemplate, the option of theseedtemplate, or+verilator+seed+. - An argument cannot set a plusarg that the
.coreTarget declares as a parameter. The Target owns its parameter values. envcannot set a reserved name, nor a variable that the same Target'senvtable sets.- On a Cocotb Target a Variant carries
envonly. Arguments are refused there.
booley doctor reports each of these as a tests.toml failure that names the
Target, the Test and the Variant.
Seeds (seed, default_seed)¶
A Test Run is one Test Variant simulated with one seed. Its name is
test+variant@seed, or test@seed for a Test without Variants: for example
stress+soak@42 or reset@1. Reports, run directories and Goals use this
name.
Booley passes the seed of every Test Run itself and never relies on a simulator's own default seed. How the seed reaches the simulation depends on the Target:
| Target | What Booley passes |
|---|---|
| Verilator, HDL testbench | +verilator+seed+<seed>, and the rendered seed template when the Target declares one |
| Icarus, HDL testbench | the rendered seed template; without a template the Target is unseeded |
| Cocotb Target | cocotb's seed variable: COCOTB_RANDOM_SEED on cocotb 2.x, RANDOM_SEED on cocotb 1.x |
[sim]
tests = ["reset", "basic", "stress"]
seed = "+seed={seed}" # optional: the testbench reads its own seed argument
default_seed = 7 # optional: this Target's Default Seed
seedis an argument template for a testbench that reads its seed from an argument of its own. It is exactly one option token that starts with+or-, contains the plain field{seed}exactly once and no other field, and cannot start with++or+-. It cannot set the same option asselect, and it cannot be+verilator+seed+. Booley renders the seed as a decimal number. A Cocotb Target cannot declareseed.default_seedis the seed of a Test Run that names none. It is an integer from 1 to 2147483647. Without it the Default Seed is1.- An unseeded Target cannot apply a seed. Its Test Runs are written
without
@seed, and Booley refuses--seed,test@seed,test@xNand adefault_seedkey for it. To seed an Icarus Target, declare aseedtemplate that the testbench reads.
On the simulator command the arguments appear in this order: the Target's
plusarg parameters, the select argument, the Variant's arguments, then the
seed arguments. A Target plusarg that sets the same option as select or the
seed template is left out, so that the testbench sees one value.
booley doctor prints one line per sim Target that says how its Test Runs are
seeded and with which Default Seed, or that they are unseeded and why. See
Seeds and Test Variants on the command line
for --test and --seed.
Per-Target environment (env)¶
Plenty of upstream testbenches are parameterized by an environment variable —
a cocotb module that branches on os.getenv("FLAVOR"), an SV testbench whose
C++ main reads a config var. Declare it per Target and Booley exports it into
the simulator process:
[sim_vanilla]
tests = ["run_test_001"]
env = { FLAVOR = "vanilla" } # NAME = "value", strings only
[sim_small]
tests = ["run_test_001"]
env = { FLAVOR = "small", NOC_DEBUG = "1" }
Per Target because that is where the variance lives: the same testbench module
run under two RTL flavours is two Targets, each with its own value. The exports
happen in the shell that owns the build and the run, inside the Sandbox — no testbench edit needed. Pre-run
commands see the same
variables (so a flavour-aware firmware build works), but they can't provide
them: their own exports die with their shell. The test filter selects tests
rather than configuring them. Values must be quoted
strings, and names must be
shell-exportable identifiers ([A-Za-z_][A-Za-z0-9_]*). --dry-run previews
the exact export lines.
An env table, whether a Target's or a Test Variant's, cannot set a name
that Booley or the simulator integration owns. These names fail to load:
MODULE, TESTCASE, TOPLEVEL, TOPLEVEL_LANG, RANDOM_SEED,
LIBPYTHON_LOC, PYTHONPATH, LD_LIBRARY_PATH, and every name that starts
with BOOLEY_, COCOTB_, PYGPI_ or GPI_.
Booley also sets PYTHONPYCACHEPREFIX and pytest's cache_dir itself,
overriding Target values so Python artifacts stay under the Project runtime
directory instead of the source checkout. Pytest cache features remain enabled.
Design fields never appear in booley.toml, and neither do the source-category
directory listings: RTL vs testbench source dirs are derived from the .core
filesets (the tags:[tb] partition marks the TB sources). What lives
in booley.toml is the per-Flow execution knobs (enabled) plus a few
unrelated flags — never the source-dir listing itself. One such flag is
[sources.testbench].preflight_checks (bool, default true): set it false
to skip the check that a Target's testbench files already exist on disk, which
is what you want when the TB is authored during the run. (The broader
[project].preflight_checks toggles all such file-existence checks — scope
files and TB alike; the per-section flag narrows the relaxation to just the TB.)
Source: docs/user/TESTS.md on GitHub