Plan: complete ADR 0012 CDC jitter injection
Tracks the deferred half of ADR 0012. Issue: #92.
Where it stands
Implemented: the run-parameters file + per-domain seeded PRNG
(src/sim/run_params.rs), jitter_ps per ClockConfig, the uniform
per-domain draw, and a jitter displacement applied to the timing-VCD
event timestamp (cosim_metal.rs, inside the --output-vcd block
only). So today jitter perturbs the waveform timeline but nothing
else — it does not reach the setup/hold checker, model-driven clocks,
or coincident-edge ordering.
The goal of this plan is to make jitter actually stress CDC paths, then extend it to model-driven clocks and tidy the loose ends, so ADR 0012's present-tense design fully matches the code.
Phase 1 — Jitter reaches the timing checker (the core value)
Right now jitter_displacement only adjusts the VCD base_timestamp
(cosim_metal.rs:~3928-3948) and is computed inside the timing-VCD
emission block, so it has no effect without --output-vcd and never
influences violations.
- Hoist the per-tick per-domain displacement draw out of the VCD block
so it is available whenever
jitter_active, independent of--output-vcd. - Apply each domain's displacement to the arrival offsets that
setup/hold checking consumes (the
arrival_statesection), not just the VCD base timestamp — so a jittered edge can move a margin across the setup/hold boundary and surface in--timing-report. - True per-domain perturbation (ADR §4): keep a displacement per
firing domain this tick rather than the current single global value
(the loop overwrites
jitter_displacementwith the last domain's draw). Coincident edges from domains A and B then move independently, exercising both orderings over a seed sweep.
Verify: a small two-domain design with a deliberately marginal CDC path; assert that a seed sweep produces both "no violation" and "violation" outcomes, and that a fixed seed reproduces exactly.
Phase 2 — Model-driven clock jitter (ADR §3)
Model-driven clocks (JtagReplayModel, SPI SCK, …) bypass the scheduler
and currently get no jitter.
- Add
--cdc-model-jitter-ps <N>(and/or per-modeljitter_psin config) → a budget + seeded stream viaRunParams::domain_seed(model_name). - After a model fires its edge, displace the timing-model arrival for that transition (not the functional edge — the DFF still samples on the same tick), mirroring the Phase 1 arrival-offset path.
Verify: extend tests/jtag_minimal (model-driven TCK) with a model
jitter budget; confirm reproducibility and that TCK→sys_clk CDC margins
vary by seed.
Phase 3 — Hygiene / correctness guards
gcd_ps / 2constraint (ADR §2): at startup, error (or clamp with a loud warning) if anyjitter_ps > scheduler.gcd_ps / 2, since larger values would reorder edges across GCD ticks.- Always persist the seed (ADR §1): when neither
--run-paramsnor--output-vcdis given,RunParams::generate()currently does not write the file. Persist to a default path unconditionally so every run is replayable. - master_seed in the VCD header (ADR §1/§5): emit the master seed as
a VCD header comment in
vcd_io.rs, so the seed is recoverable from an output artifact, not just the INFO log.
Phase 4 — CI CDC stress sweep (ADR Consequences)
Once jitter feeds violations (Phase 1), add a lightweight CI step: run
the marginal-CDC design across a few sequential seeds, upload each run's
run_params.json as an artifact, fail if an unexpected violation
appears. Gives every PR a cheap CDC regression.
Out of scope (separate ADRs / plans)
- X-injection on CDC paths (needs MC.1 island partitioner — ADR 0012 "Deferred").
- Non-uniform jitter distributions (Gaussian period jitter, etc.) — the seed+budget interface is distribution-agnostic, add later.
- Frequency sweep / DFS.