JanusHDL

Determinism and races

Last updated: 

JanusHDL is deterministic: the same design, with the same inputs and options, gives the same output on every run and on every machine, Windows or Linux, x86-64 or ARM, with native code or without. A failure seen in CI reproduces on your desk.

Within that, one thing is left open by the VHDL standard: the order in which the processes of one simulation cycle run. This chapter explains what that order can and cannot change, what order JanusHDL uses, and how to use other orders to find testbenches that pass only by luck.

The simulation cycle in brief

A VHDL simulation is a sequence of cycles (LRM 14.7.5). In each cycle:

  1. Time advances to the next time anything is scheduled, or stays where it is: then the cycle is a delta cycle.
  2. Every signal with a transaction due is updated; a signal whose value changes has an event.
  3. Every process waiting on a signal with an event (and whose wait until condition is now true), and every process whose timeout expires, resumes.
  4. Each resumed process runs until it suspends again.
  5. Postponed processes run last, once time is about to advance.

Signal assignments made in step 4 take effect in step 2 of a later cycle, at the earliest the next delta cycle. So within one cycle every process sees the same signal values, and no process sees another's assignments of that cycle.

What the order can change

The standard fixes which processes run in a cycle, the values they read, how resolved signals resolve, and the order of the cycles. It does not fix the order in which the processes of one cycle run, at initialization or later: every simulator chooses its own.

For signal values, the order changes nothing. A design or testbench whose processes communicate only through signals gives the same waveforms and the same verdict under any order.

It can show through anything that is not a signal and that several processes touch in the same cycle:

  • shared variables and protected objects: scoreboards, queues, message boxes of verification libraries, a shared random generator; which process calls a method first;
  • files and the transcript: the order of report messages, and of the lines several processes write to one file, within one time step;
  • std.env.stop and finish, called by several processes in one cycle.

A testbench whose verdict depends on that order has a race: legal VHDL whose result the standard leaves to the simulator. Such races are not rare. A testbench with one can pass on one simulator and fail or hang on another, or change behaviour when an unrelated process is added. Logs can list the messages of one time step in a different order even when nothing is wrong.

JanusHDL's order

JanusHDL decides each process's place once, at elaboration, and runs the processes of every cycle in that order:

  • The processes that run the same code are neighbours: the instances of one process statement, in every instance of an entity or iteration of a generate, run one after another, in elaboration order.
  • The statements themselves are ordered by the names of their design unit (entity, then architecture) and, within a unit, by their place in the text. The order therefore depends only on the design's text, not on the order of the files or where they are.
  • A PSL directive runs before the other processes of its cycle, so that its 'EVENT and 'SIGNAL are those of that cycle (LRM 16.2.7); postponed processes run last.
  • A process keeps its place whatever woke it; one that wakes, finds its wait until condition false and waits again keeps its place too.

This order was chosen because it runs fast: processes that share code keep it in the processor's caches.

Running under another order

--order-seed N runs the processes in another fixed order: each process gets a pseudo-random place drawn from the seed N. The same seed gives the same order on every run; another seed, another order.

jhdl -f tb.f --top work.tb --order-seed 2

The same in other places:

Where How
Command line --order-seed N
Environment, for every jhdl run (also through the front ends and VUnit) JHDL_ORDER_SEED=N
Project file order-seed N (Projects)
Window Project settings → Simulation → Process order seed

JHDL_ORDER_SEED takes precedence over a project's order-seed, so a whole suite can run under another order without changing any file. Without a seed, JanusHDL uses its default order described above.

Finding races

Running a test suite under several orders is a race detector: a test whose verdict changes from one order to another has a race.

for seed in 2 3 4 5 6 7 8 9; do
  JHDL_ORDER_SEED=$seed jhdl -f tb.f --top work.tb --quiet > seed_$seed.log 2>&1 \
    && echo "seed $seed: passed" || echo "seed $seed: FAILED"
done
foreach ($seed in 2..9) {
  $env:JHDL_ORDER_SEED = $seed
  jhdl -f tb.f --top work.tb --quiet *> "seed_$seed.log"
  if ($LASTEXITCODE -eq 0) { "seed ${seed}: passed" } else { "seed ${seed}: FAILED" }
}
Remove-Item Env:JHDL_ORDER_SEED

When a seed fails:

  1. Run that seed with --wave and open the recording in jhdlw.
  2. Go to the time of the first difference, and open Signals → Show the delta cycles of the time step at the cursor. The Delta cycles panel lists, delta cycle by delta cycle, the processes that ran and the signals that changed (Delta cycles).
  3. Look for two processes in one delta cycle that both touch the same shared variable, protected object, file or std.env call. Comparing the panel for the default order and for the failing seed shows where the two runs part.

The cure is to make the order not matter: communicate through signals, which no process sees change within a cycle, or make one process wait a delta cycle (wait for 0 ns;) before it reads what another writes in the same cycle.

Comparing logs between seeds, expect messages of one time step to come in different orders; messages at different times, and the verdict, should not differ.

Delta cycles in output files

  • Messages carry their delta cycle: 100 ns (delta 2).
  • The waveform database keeps the delta cycles of every time step (unless recorded with --wave-deltas off): which signals changed in each, from which value to which, including changes a later delta cycle overwrote, and which processes ran. The waves draw each time step's last value; the Delta cycles panel shows the rest.
  • VCD files have no place for delta cycles: changes in several delta cycles of one time step collapse into the changes at that time.