simulation-plan · git:20260905.276ce02 · 2026-09-05 · sha256 4083e6b56fafa743

simulation-plan git:20260905.276ce02A

Immutable. This exact content is served forever at /api/v1/blob/4083e6b56fafa743.

---
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` / `children` /
`clocks` / `requirements` / `check_hints` / `top_io` all resolve to the specification stage root.

| Path | What it is |
|---|---|
| `<design>/design.md` | §1 behavior, §1.4 boundary, and §1.5 timing scenarios with their waveforms — the only home for the scenarios you author sequences from, so you read it |
| `<children>/<child>.md` | Per-child implementation constraints a testpoint may have to verify: register side effects, exceptions, concurrency, back-pressure, reset, state-machine boundaries |
| `<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>/` (per child, one file) | Per child, how simulation observes each requirement row it realizes — `check_id` is unique across all of them, and each 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; `children[]` is the roster the check hints are aggregated over |

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/review.md`, `plan-review/decisions.md` | The reviewer's findings, and the user's resolution of anything it called blocking |
| `result.json` | The status envelope |

Each sidecar's shape, and per field whether it is yours to author or script-injected, is
[`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.5 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 fill the script-injected fields, 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/review.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.

**Gate, human.** Path-handoff: present the `verification-plan.md` path and the
`plan-review/review.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.

Then the user approves, requests changes, or rejects:

- **approve**: if the user accepts a finding the reviewer called blocking, write their reason —
  **their words, not yours** — to `{workdir}/plan-review/decisions.md`, so the override travels
  with the review it overrode instead of living only in this session. Nothing downstream re-checks
  testpoint-vs-spec (sim conformance judges TB-vs-testpoint), so an accepted coverage gap is a
  terminal accept.
- **request changes**: revise incrementally, re-run the script gate, re-dispatch the reviewer,
  re-present.
- **reject**: finalize with `--status fail` and no `--fail-reason`, which records
  `fail_reason="user rejected plan"`.

### 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> \
  [--status fail] [--fail-reason "<one-line reason>"] [--fix-owner <rule>] \
  [--revision '<one-line revision narrative>']
```

You supply only the human-gate outcome, plus `--revision` on 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`.