simulation-plan · diff
git:20260914.6197c92 to git:20260914.68e2759
6 added, 7 removed. Audit A to A.
---
name: simulation-plan
description: Use when generating or evolving the verification plan, scaffold specification, testpoints, and power scenarios for a module; not for materializing UVM TB or running EDA tools.
---
# Verification Planning
Your sole responsibility: from `specification`, produce `verification-plan.md` — the human-readable
review anchor — plus its machine half, three sidecars split so each downstream consumer declares
only what it reads.
## Iron Rule
- Write only under `{workdir}`; never another module's artifacts.
- Do not read RTL source, do not invoke EDA tools. A plan written from the implementation verifies
the implementation against itself.
## Artifacts
`<skill>` is this skill's own base directory, named on the first line of this file.
Read `{workdir}/dispatch.json` for this round's inputs: its `inputs` table maps each upstream key
to a location, so `<key>/<subpath>` is how you address one. `design` / `manifest` /
`clocks` / `requirements` / `check_hints` / `top_io` all resolve to the specification stage root.
| Path | What it is |
|---|---|
| `<design>/design.md` | §1 behavior, §1.3 boundary, and §1.4's scenario table — the only home for the scenarios you author sequences from, so you read it. A waveform there is optional and not authoritative; the rows and the scenario entry govern |
| `<requirements>/requirements.json` | The engineer's requirements, one row each with the stage that judges it. The rows judged by simulation are what the testpoints exist to establish; the rows judged by simulation-plan are requirements on this plan itself: a stimulus distribution, a seed, a scope |
| `<intent>/` | The intent tree: the engineer's container — `brainstorm.md` plus whatever they delivered with it. Open a file here only when a requirements row points at it, and read it there rather than from any copy |
| `<check_hints>/check-hints.json` | How simulation observes each requirement row it judges — `check_id` is unique in it, and each hint names the rows it establishes |
| `<design>/clocks.json`, `<design>/top-io.json` | The clock and the DUT boundary `materialize-scaffold` derives from |
| `<manifest>/manifest.json` | `.module` is the Top field in plan §1 |
Everything below is produced under `{workdir}`.
| Path | What it is |
|---|---|
| `verification-plan.md` | The review anchor the human gate is held over (template: `references/verification-plan-template.md`) |
| `tb-scaffold.json` | What simulation builds the TB from: `agents` / `tests` / `testpoints[]` (each naming the checks it covers and the `seqs` that drive it) / `rm` / `scoreboard` / `skipped_checks[]` |
| `sequences.json` | The sequence roster — the one part both simulation and power-analysis read |
| `power-scenarios.json` | The power scenarios, read by power-analysis alone. Its own file so a scenario-only edit does not invalidate simulation's proof |
| `plan-review/findings.md` | The reviewer's findings. This round's record: it does not carry into the next round |
| `plan-review/decisions.md` | The user's resolution of anything it called blocking. It accumulates across rounds — carried forward and appended to, never rewritten |
| `result.json` | The status envelope |
Each sidecar's shape and field semantics are in
[`references/tb-scaffold.schema.json`](references/tb-scaffold.schema.json) /
[`sequences.schema.json`](references/sequences.schema.json) /
[`power-scenarios.schema.json`](references/power-scenarios.schema.json). Read them before authoring:
they carry the judgment no validator can express, and `simplan check-scaffold` enforces the rest.
## Workflow
Two phases, each closed by its own gate, then finalize.
### Which round is this
The kernel writes `scope` / `caused_by` / `reasons` into `dispatch.json` **only when they carry
something**, so their presence is what tells you. Either way you run both phases; the branch
decides how much of the plan you touch.
- **`caused_by` present — failures are waiting on this stage.** Each entry is one failing run's own `result.json`, and a round scheduled for some other reason carries them too: answer them in this round. Scope is the union of `dispatch.json`'s `scope` — the
module-relative inputs whose change invalidated the plan — and what the
`caused_by` records attribute; read each envelope once, and amend only the plan sections those
paths map to. It is a pointer, not a boundary: if the gap sits elsewhere, widen and record why in
`result.json`. `reasons`, when present, is a human's judgment on this repair and outranks your own
reading of the files.
- **`caused_by` absent — a first delivery.** Generate the plan and all three sidecars.
Your previous round, if any, is already in `{workdir}`: edit it in place, touching only what this
round requires. Rewriting a sidecar this round did not change still changes its bytes, and
`simulation` and `power-analysis` both declare those files as inputs, so a cosmetic rewrite costs a
full TB recompile and regression downstream for no change in content.
A `{workdir}` already holding part of a round means the session was compacted or interrupted: that
work is yours to continue or redo, and artifacts on disk are not a gate you already passed.
### Author the plan and its sidecars
Author the judgment fields into the three sidecars and write `verification-plan.md` per
[`references/verification-plan-template.md`](references/verification-plan-template.md). How the spec
fields map to the scaffold objects — agents from interface groups, sequences from §1.4 scenarios,
tests from the requirements, RM / scoreboard from the check hints — is
[`references/spec-input-contract.md`](references/spec-input-contract.md).
Every `check_hints[]` check_id must end up in some `testpoints[].covers[]` or in `skipped_checks[]`
with a reason; the gate below enforces that matrix. Power scenarios come from
[`references/power-scenarios-template.md`](references/power-scenarios-template.md) — load the table
before authoring, because no gate checks that a scenario came from the standard set. Every
`sequence_ref` must resolve to a `sequences[].name`; add the `sequences[]` entry first when a
scenario needs its own stimulus.
When amending, keep testpoint IDs / sequence names / `sequence_ref` stable: downstream coverage /
scaffold / SAIF caches key off them, so renumbering one silently breaks the cache. One amendment is
counter-intuitive enough to name: when triage attributes a coverage hole to plan over-spec — a
situation the RTL cannot legally reach — **narrow the testpoint's `intent`** rather than chasing the
hole, and delete the testpoint outright if the whole thing is unreachable, recording the over-spec
attribution in §5. Coverage does not fall; a hole that could never be hit was never coverage.
**Gate, script.** Run `materialize-scaffold` to check the agents' interface groups, then
`check-scaffold` to validate the sidecars' structure, semantics, and coverage matrix:
```bash
python3 <skill>/scripts/simplan/__main__.py materialize-scaffold --plan {workdir} --spec <design>
python3 <skill>/scripts/simplan/__main__.py check-scaffold --plan {workdir} --spec <design>
```
Both run on every pass. Each names the defect on stderr; `materialize-scaffold` also names the
upstream stage to re-run when what is missing is the spec's. Fix and re-run until clean, or finalize
a failure when the defect is not yours to fix.
### Review the plan
Dispatch ONE Level-1 reviewer per
[`references/plan-review-task-contract.md`](references/plan-review-task-contract.md), passing paths.
It writes its own `{workdir}/plan-review/findings.md`. After dispatching, send a brief status and end
the turn; reap before proceeding. A `STATUS: BLOCKED` reviewer is a crash, not a verdict: the review
did not happen, so finalize a failure with that as the reason.
**Handoff, not a gate.** Give the user the `verification-plan.md` path and the
- `plan-review/findings.md` path, echoing no body. You do not summarize the findings, rank them, or
- decide which ones matter: a review relayed through your summary is your judgment wearing the
- reviewer's name.
+ `plan-review/findings.md` path, echoing no body, so they can read the review themselves.
Nothing here asks the user for a verdict, and the round does not wait on one. Nothing downstream
re-checks testpoint-vs-spec — simulation's check-adequacy reviewer takes the plan as immutable and
judges TB-vs-testpoint — so this review is the last reader of that question, and a gap it names is
acted on below, never shipped under a word typed to keep the round moving.
- Act on what they say: revise incrementally and re-present, re-running the script gate and
- re-dispatching the reviewer; a finding they tell you to accept as-is goes to
- `{workdir}/plan-review/decisions.md` in **their words, not yours**, so the reasoning travels with
- the review it overrode. A round that cannot deliver at all closes below with `--fail-reason`.
+ Read the findings against the specification and repair defects in the plan. Revise incrementally,
+ re-run the script gate, and re-dispatch the reviewer. Ask the user when resolution requires a
+ decision about intent. Record any finding the user explicitly accepts as-is in
+ `{workdir}/plan-review/decisions.md` in their words. If a blocking defect remains unresolved,
+ close the round with `--fail-reason` and name its owner as described below.
### Finalize
Every run ends here, including one you could not carry to the human gate:
```bash
python3 <skill>/scripts/simplan/__main__.py finalize \
--workdir {workdir} --spec <design> \
[--fail-reason "<one-line reason>"] [--fix-owner <rule>] \
[--revision '<one-line revision narrative>']
```
The status is derived, so you assert nothing about the outcome: a reason names what stopped the
round and lands in the envelope's `stage_specific.fail_reason`, and its absence means the stage
delivered what it owes. `--revision` marks a scoped revision. On a failure name
the owner: `--fix-owner specification` when the spec does not say enough for any plan to cover the
gap, `--fix-owner simulation-plan` when the gap is yours but you have exhausted what you can do
from here, and omit the flag when you cannot tell.
finalize re-runs `check-scaffold` in-process on the pass path — it was clean at the script gate, so
a failure now means an artifact was edited afterwards, which is BLOCKED rather than a routable fail.
Exit 0 = `result.json` written, status pass or fail. A non-zero exit is a program exception:
BLOCKED, reason on stderr, never a `status=fail`.
## Return Contract
Control returns to the caller, which decides what runs next from `result.json`.
Your sole completion signal is `{workdir}/result.json` present with `status=pass`.