| name | spec-to-rtl |
| description | MANDATORY entry point when `design_one_shot_runner.step_rtl_gen` WAIVES with `fallback_skill='spec-to-rtl'`. This is invoked for every IC class with `rtl_gen=null` in `ic_class_registry.json` (currently: digital_arithmetic_primitive, digital_cmd_driven, bare_fpga, processor_cpu, unknown_protocol_class). The runner has already (1) ingested the prompt into L1-L27, (2) detected the IC class, (3) set the expected RTL path. This skill authors synthesizable RTL into the runner's expected path so the runner's downstream gates (chip_top auto-emit, rtl_hygiene_lint --fix, eda_lint, eda_synth, eco_loop, spec_conformance_check, full_stack_tb_gen) can fire on it. Triggered automatically by the runner's WAIVE message; also fires on phrases like 'AI invokes spec-to-rtl', 'runner WAIVED rtl_gen', 'spec-to-rtl handoff'. THIS IS THE RUNNER'S INTENDED PATH — NOT BYPASS. Bypass means authoring with MCP outside the runner's pipeline (what the 2026-05-28 wrong-shape RTLLM 37/50 did). |
spec-to-rtl — the runner-orchestrated AI authoring step
What this skill IS
The deterministic authoring path for IC classes WITHOUT a registered rtl_gen
generator. Spec → RTL fundamentally requires a language model (open-benchmark-
methodology skill § 1); this skill is how the runner delegates that step to
the AI inside its own pipeline, so the surrounding gates still fire.
Invocation contract
When design_one_shot_runner.step_rtl_gen WAIVES with the message:
IC class 'XXX' registered but rtl_gen=null. Recommended action: AI invokes
skill spec-to-rtl.
…the AI MUST:
-
Read the L docs the runner just emitted:
<project>/phase1/generated_docs/L1_*.json (product metadata, IC name, target clock)
<project>/phase1/generated_docs/L2_*.json (functional spec)
<project>/phase1/generated_docs/L3_*.json (external interface — ports)
<project>/phase1/generated_docs/L7_*.json (verification plan / truth-tables)
<project>/phase1/generated_docs/L9_*.json (constraints, top_module name, top_ports)
- For Path-A (NL prompt) projects, ALSO read the original prompt at
<project>/input/phase1_prompt.md or <project>/input/docs/*.md. The
prompt typically has clearer port-name and behavioral details than the
auto-extracted L docs.
— and CONSULT THE CAPTURED-LESSON DIGEST (MANDATORY) —
The runner's WAIVE step deterministically writes
<project>/phase2/stage1/lessons.md (and sets lessons_digest +
lessons_count in its handoff extras): a chip-AGNOSTIC digest of every
### Skill: genre/topology convention captured from prior recoveries.
BEFORE you author, OPEN it, keyword-match THIS design's genre against each
section's **When to apply** line, and APPLY every match. These are general
patterns (frequency/clock-divider dual-edge-OR LEVEL topology,
shifter logical-vs-rotate default, FIFO Gray-pointer, restoring-divider
remainder width, overlapping-sequence FSM re-seed, valid/ready handshake
inference, …), NOT per-problem answers — applying them is REQUIRED, not
optional. A runner-driven author that skipped this re-invented a
genre-determined topology from the prompt's loose wording and failed; this is
the SAME digest Shape-C blind authors already MUST read.
— and LIST THE OTHER ARTIFACTS THE SPEC MAKES MANDATORY (MANDATORY) —
The WAIVE handoff tells you two things: write the RTL here, and read
lessons.md. It says nothing about the OTHER artifacts the same spec makes
mandatory and that you are the only one who can produce. Those clauses are
already extractable from the SAME input docs you have just read, so run the
preflight BEFORE you author:
python3 plugins/vibe-ic/programs/spec_required_artifact_check.py \
<project> --preflight
It prints every still-OUTSTANDING spec-declared artifact with the input doc
and the clause that demands it, and always exits 0 — at handoff those
files are legitimately absent, so it can never block a correct run. Author
each one alongside the RTL. Skipping this does not make the requirement go
away: the same extraction runs as a BLOCKING assertion at final_audit, so
a missing declaration costs an entire Phase 2 (hygiene, lint, synth, DFT, a
multi-minute LEC) to discover a file that took seconds to write.
-
Respect the blind rule: read ONLY the L docs + original prompt. NEVER
read testbench.v, verified_*.v, hidden cocotb harness, or any reference
RTL the upstream benchmark ships. This is enforced by the open-benchmark-
methodology skill's absolute-blindness rule and applies inside this skill.
-
Determine the module name:
- If
L9_INTEGRATION_SPEC.json declares top_module (e.g. "chip_top")
AND that matches the description's stated module name, use that.
- Otherwise (common for RTLLM-class designs): use the exact name the
prompt/description states ("Module name: "). The hidden TB
instantiates by that name. The runner's chip_top auto-emit (v0.1.32+)
will wrap your module if L9.top_module differs.
-
Author synthesizable RTL at the runner's expected path:
<project>/phase2/stage1/rtl/<module_name>.v (or .sv)
- Verilog-2001 or SystemVerilog; synthesizable for yosys + gf180/sky130
- EXACT port list per L3 / L9: names, directions, widths (
[3:0] ⇒ 4 bits)
- Implement the behavior the description states. Algorithm choices the
description doesn't pin down are R3-permitted design freedom.
-
Apply known hygiene proactively (so the runner's gates don't have to
work around them):
- Combinational
always @(*) blocks: every branch assigns every output
OR a default covers them (no inferred latches).
- Reset-less registered outputs: add
initial <reg> = 0; in a SEPARATE
initial block (NOT inline on output reg q = 0;) so the runner's
rtl_hygiene_lint --fix doesn't have to repair PROCASSINIT.
case: include default; rewrite overlapping casez priority encoders
as if/else-if.
-
Self-verify with the HARNESS-EXACT toolchain BEFORE emit (MANDATORY —
ORGANIC #688). A host-only / RTL+TB-together self-check passes code the
scorer rejects, because the scorer (a) pins a specific tool version, (b)
compiles the RTL alone under the harness top flag -s <module> (full
codegen, not a -t null elaborate), and (c) runs a lint gate. Run the
deterministic gate program — it is the gate, not a suggestion:
python3 plugins/vibe-ic/programs/harness_exact_selfverify.py \
--rtl <project>/phase2/stage1/rtl/<module_name>.v \
--top <module_name> \
--tb <your_functional_tb.sv> \
--report harness_exact_selfverify.json
The program runs THREE gates and exits 0 only when every enforced gate
passes:
- Gate A (deterministic) —
iverilog -g2012 -o sim.vvp -s <top> rtl
standalone full codegen (catches ELAB-only + standalone-top fail
classes). This is the harness-exact flag, NOT a -t null elaborate.
- Gate B (deterministic) —
verilator --lint-only -Wall clean.
- Gate C (you author, the program RUNS) — a functional TB whose golden
vectors are the prompt's OWN worked examples / tables (+ random +
boundary). Extracting those examples is YOUR judgment; pass the TB via
--tb and the program compiles+runs it and parses the verdict. No --tb
→ gate C is reported skipped, never silently passed.
The program DISCLOSES any host/scorer tool-version skew and does NOT claim
to catch spec-INTERPRETATION mismatches (it cannot — that residual stays an
authoring judgment). When a tool is absent it discloses and skips (or, under
--require-tools, hard-refuses) — it never fakes a pass.
6b. Prompt→interface conformance pre-emit check (ORGANIC #695). Before
handing back, run the deterministic interface gate — it reads ONLY the
prompt + your RTL (BLIND; never the oracle/hidden TB) and flags the three
PROMPT-DERIVABLE interface misses the hidden cocotb harness gets you on:
python3 plugins/vibe-ic/programs/iface_conformance_v2.py \
--id <problem_id> \
--prompt <prompt.txt> \
--rtl <project>/phase2/stage1/rtl/<module_name>.v
It checks (1) MODULE-NAME-CASE — the RTL module name must match the
canonical id stem the harness uses as TOPLEVEL CASE-EXACTLY (-s findfasterclock won't find FindFasterClock); (2) MISSING-PORT — every
interface signal the prompt NAMES (table rows, backtick signal names with a
nearby direction, a given-code module header, wavedrom name entries) must
appear in your port list (AXI master must keep ar*/aw*, s_ready, etc.);
(3) PORT-DIRECTION — a port's direction must match the prompt's signal table
(don't declare output sram_valid when the harness drives it as an input).
ADVISORY by default (prompt extraction is heuristic — an internal signal
mentioned in prose is NOT a port and must not block); add --strict to make
any finding exit 1 once you've confirmed the named signals really are ports.
FIX every confirmed finding before emit — these are deterministic
elaboration/bind failures the scorer would hit, not authoring judgment.
6c. Spec-first coverage attribution pre-emit gate (ORGANIC #697). The
hidden scorer is built from the SAME spec you read — where "spec" is the
WHOLE input chain (prompt → fact graph → the L1-L27 the runner just emitted).
So your self-TB must cover every spec-derived requirement, or the hidden TB
will catch a bug yours never exercised. Run the deterministic gate — it reads
ONLY the chain + your RTL + your TB (BLIND; never the oracle):
python3 plugins/vibe-ic/programs/spec_coverage_check.py \
--prompt <prompt.txt> --ldocs <project>/generated_docs/ \
[--fact-graph <fact_graph.json>] \
--rtl <project>/phase2/stage1/rtl/<module_name>.v \
--tb <your_functional_tb.sv> --strict
It extracts a DETERMINISTIC checklist (ports/widths/directions, reset
value+polarity+sync/async, stated latency, every table row, every worked
example, every ENUMERATED SET + its outside-the-set/default boundary —
the most-missed #697 pattern — signed-ness, byte order, overflow, handshake)
from EVERY chain station and reports any item your TB leaves UNCOVERED
(--strict BLOCKs on a coverage gap). FIX the gap by adding the missing
directed stimulus/assertion (especially an OUTSIDE-the-set value for any
enumerated set). On a downstream FAIL, re-run with --failure "<behavior>":
a coverage-gap ⇒ enhance your TB; an extraction-gap ⇒ the program names
the route_to: station (ic-expert-agent / spec-to-rtl) that
dropped the requirement (community-backlog it); only a cited spec-absent is
a genuine floor. The structural extraction + routing is deterministic;
deciding whether a prose sentence is a distinct testable requirement (so
you can author its stimulus) is your LLM judgment.
-
Tell the orchestrator you're done. The caller will re-invoke
vibe_ic_one_shot_runner.py so the runner detects the RTL at the
expected path, skips step_rtl_gen, and continues with: chip_top
wrapper auto-emit (v0.1.32+), rtl_hygiene_lint --fix, eda_lint,
eda_synth, spec_conformance_check, eco_loop (up to 3 retries
on reference_tb FAIL), full_stack_tb_gen, final_audit.
What this skill IS NOT
- NOT a fully-deterministic program. Spec→Verilog needs an LLM; this
skill is the AI's role inside the runner pipeline, with all the
structural gates wrapping it.
- NOT a bypass of the runner. Bypass = authoring with MCP outside the
runner's pipeline (no phase1 L doc context, no chip_top auto-emit, no
hygiene
--fix, no eco_loop, no conformance, no audit). The wrong-shape
RTLLM 37/50 baseline was bypass. Shape B done correctly invokes THIS
skill.
- NOT a free pass to ignore the blind rule. The original benchmark's
testbench / golden RTL are STILL HIDDEN during this authoring step. Only
the host scorer (after all gates run) touches them.
Quality bar
A "good" spec-to-rtl emission means:
harness_exact_selfverify.py exits 0 (gate A standalone -s <top> codegen
- gate B verilator lint both pass — see step 6; this subsumes "compiles
standalone" and "lint clean" with the SCORER's exact flags, so the host-only
accept-set cannot diverge).
eda_synth gf180 emits ≥1 cell (not pure-passthrough; not optimised to nothing).
- The module name + port list match the description verbatim.
- No latches inferred (or, if a latch IS intended per the description,
declared
always_latch).
- Reset-less registered outputs have an
initial = 0 block.
If you cannot satisfy this from the description blindly (e.g. the description
genuinely under-specifies a parameter the TB will instantiate by name), emit
your best honest reading and let the runner's downstream gates report the
mismatch. Per the open-benchmark-methodology skill § 4 Cat B, that's
documented as benchmark under-specification, not a skill failure.
Honest history
This skill was filed at v0.1.31 as fix (A) of three suggestions for
ORGANIC-20260528-null-rtl-gen-classes-need-bridge. Before v0.1.32, the
runner's WAIVE message referenced spec-to-rtl skill but no skill file
existed — leading 5 disciplined RTLLM Shape B agents to interpret the
absence as "no path forward" and emit 0/50 in the first attempt. v0.1.32
ships this skill so the WAIVE → handoff is unambiguous + reproducible.
Error-flag behavior — classify recoverable vs fatal from L3/L5 (#468)
When you author RTL that raises an error flag on an undefined-access /
illegal-command / out-of-range path, decide from the L3/L5 protocol prose
(not from convenience) whether the FSM should recover or halt, and encode
that decision so the downstream fsm_error_invariant gate and /rtl-review can
audit it:
- recoverable — if L3 (transaction protocol) / L5 (error-handling spec) says the
block sets the error flag and continues serving the next transaction (returns to
IDLE/ready), implement exactly that: raise the flag, then transition back to the
serving state. Add a
// fsm_error: recoverable annotation at the error-assign site
so the reviewer can confirm without re-deriving the semantics.
- fatal — if L3/L5 binds the error to a halt/lockup state or says it
requires a reset (or explicit clear) to clear, implement the halt and do NOT add
the recoverable annotation; the FSM stays in the error state until reset.
FORBIDDEN: annotating a site // fsm_error: recoverable (or, in review, silencing
the gate) without the L3/L5 sentence(s) that establish the halt-vs-continue
behavior. The annotation is a claim about the spec and must be backed by spec text.
why_not_bucket_a: the gate program already does its half — it flags the
error-flag sites structurally. The recoverable-vs-fatal call is a semantic judgment
that lives in protocol prose (L3/L5), not in RTL structure; the identical
error <= 1'b1 line means "keep going" in one protocol and "lock until reset" in
another, so no deterministic rule over the RTL can decide it. This is the residual LLM
authoring judgment, cross-referenced with /rtl-review's matching classification
section.
Output timing — same-cycle (Moore-combinational) vs registered pulse (#560)
A recurring functional miss (CVDP FUNC_ALL family): a status / event / strobe
output is implemented as a registered pulse (out <= <event>; under a
clock), which makes it appear one cycle late, but the spec / testbench
expects the output to be visible in the same cycle as the event. cocotb
checks that sample the output on the same edge the event occurs then read 0
and FAIL, even though the logic is otherwise correct.
Decide the output's timing from the prose, then encode it:
-
same-cycle (Moore-combinational decode) — when the spec describes the
output and its triggering event in the same breath ("asserts done when
the count reaches N", "drives error on an invalid command", "valid is
high while in state S"), OR the testbench style is a same-edge check, derive
the output combinationally from state / inputs and do NOT register it:
// Moore decode — same-cycle, no register delay
assign done = (state == DONE);
always @(*) error = (cmd_valid && !cmd_legal);
This is the right default for FSM status outputs, single-cycle strobes whose
event is a combinational condition, and "output follows state" descriptions.
-
registered / next-cycle — ONLY when the prose explicitly says the output
is registered, appears one cycle after the event, is pipelined, or
must be glitch-free for an external interface. Then use the NBA form:
always @(posedge clk) out <= <event>; // intentional 1-cycle latency
Worked examples (round-5 CVDP recoveries): a vending-machine error+return
asserted the same cycle as the bad coin; an FSM output that follows the state
transition in the same cycle; a simple-SPI output that tracks the transition
when it happens. All three FAILed as registered pulses and PASSed once decoded
combinationally from state.
why_not_bucket_a: same-cycle vs registered is a reading of the spec prose
("when"/"on"/"while" vs "registered"/"one cycle later"/"pipelined") and of the
testbench's sampling convention. The identical event→output mapping is correct
as combinational in one problem and as registered in another; no regex over the
RTL or the prompt reliably separates the two, so this stays an LLM authoring
judgment.
Independent differential self-verification (N-version) — break the single-self-TB circularity (#700)
If you author BOTH the RTL and its self-testbench from ONE reading of the
spec, the self-verification is circular: a misread baked into that one
reading lands in BOTH surfaces, so the TB happily confirms the wrong behaviour.
Empirically this passed a real oversight (an hmac write-data live-read vs
latched-read: 3471 diffs once an independent check was added → fixed to 0).
Break the circularity with a SECOND, INDEPENDENT derivation and
cross-check it against the RTL every cycle with diff_verify_harness.py:
-
Derive a reference model INDEPENDENTLY — fresh reasoning, without
reusing your RTL derivation. Author a Python module exposing ref(seq)
(input-sequence → expected-output-sequence). It must be a behavioural
transfer function, not a copy of the RTL's structure.
-
EXPLICITLY enumerate every ambiguous quantity and PIN each via the spec's
worked examples before writing ref:
- latency (count the pipeline stages → the leading-prefix length, e.g.
[0,0]+seq[:-2] for an exactly-2-cycle delay);
- registered-vs-comb output;
- off-by-one (pulse offset / first-vs-last / wrap / inclusive-exclusive);
- bit/byte packing and encoding (MSB/LSB-first, sub-byte width).
Pin each to a concrete spec worked example so the reference is grounded, not
guessed.
-
Run the differential harness — it generates and runs a cycle-accurate
differential testbench (directed + random + boundary vectors), driving the
RTL and comparing every cycle to your independent ref:
python3 plugins/vibe-ic/programs/diff_verify_harness.py \
--rtl <your_rtl.sv> --ref <your_ref.py> --top <module> \
--vectors directed+random+boundary
AGREE (rc 0) = the two independent derivations match every cycle.
A first-mismatch line (rc 1, cycle/signal) = a designer-vs-reference
DIFF: one derivation noticed a clause the other missed.
-
Adjudicate every mismatch by RE-READING the spec — decide which
derivation is correct, fix the wrong one, re-run. Emit only after RTL and
the independent reference AGREE.
Honest SCOPE (this is a COMPLEMENT, not a silver bullet): it catches
OVERSIGHT misreads (one derivation noticed a clause the other missed). It
does NOT catch genuine ambiguity where the spec wording biases ALL
independent blind readings the same way (an exact-latency phrase both you
and the reference read identically-but-wrong), nor benchmark spec↔TB
contradictions — those are FLOOR (on the hardest CVDP ambiguity residual it
recovered 0/8 per #697). Its value is on FRESH runs preventing oversight bugs
before the scorer. It is the differential complement to the deterministic
#697 spec_coverage_check (force the self-TB to COVER each dimension) and
the #699 timing/encoding reading disciplines — not a replacement for either.
why_not_bucket_a: authoring the independent reference and adjudicating a
designer-vs-reference mismatch require reading and interpreting the spec; no
regex derives the reference. The DETERMINISTIC half — generating and running
the differential harness over directed/random/boundary vectors and reporting
per-cycle mismatches — IS the program (diff_verify_harness); this step records
the judgment residual (deriving the reference + adjudicating).
Latency/timing conformance — the PROGRAM measures, not your self-TB (#705)
When the spec states an EXACT latency — "output asserts N cycles after
the start event", "WIDTH+2-cycle delay", "1 cycle overhead when
transitioning IDLE→BUSY registering inputs" — DO NOT trust your self-TB's
measurement. Empirically, across four blind authoring strategies agents scored
0/8 on off-by-one latency failures: each improvised a counting convention
that happened to match its OWN (wrong) RTL, so the self-TB confirmed the wrong
behaviour. There is no independent yard-stick in a single-self-TB flow.
latency_conformance_check.py IS that yard-stick. It generates its OWN canonical
measurement testbench, counts the way a hidden scorer counts (pulse the event
HIGH for exactly one clock = one latch edge, then count posedges until the output
first asserts), resolves the spec literal against the module's real parameters,
and BLOCKS on any mismatch. Run it and fix the RTL until it prints
latency-conformance ok:
python3 plugins/vibe-ic/programs/latency_conformance_check.py \
--rtl <your_rtl.sv> --top <module> \
--event <start_port> --output <valid_port> --expect "<expr>"
--expect is the spec latency literal as arithmetic over the module's
parameters (WIDTH+2, N+1, 8); resolved against the #(...) defaults (or
a --param NAME=VAL override). It is evaluated by a tiny SAFE evaluator
(digits, param names, + - * // ( ) only — never eval).
LATENCY-MISMATCH: measured=<m> but spec <expr>=<e> (rc 1) = your RTL's real
latency is <m>, the spec demands <e> — an off-by-one. Fix the RTL (one
iteration / one register stage off) and re-run.
latency-conformance ok: measured=<m> == spec <expr> (rc 0) = the measured
latency matches the spec literal — emit.
- iverilog absent → a distinct
SKIP (rc 0), never a fabricated PASS; the
output never asserting → LATENCY-TIMEOUT (rc 1).
why_not_bucket_a: the canonical MEASUREMENT (build the TB, pulse the event,
count posedges to the output assertion) + the comparison against the resolved
literal IS the deterministic program. The LLM residual is reading the spec to
decide WHICH port is the event, WHICH is the output, and WHICH
expected expression the prose names (WIDTH+2 vs N+1 vs a constant). This is
the timing complement to #697 (coverage attribution — force the self-TB to
COVER each dimension) and #700 (independent differential verify — cross-check
a second derivation): #705 supplies the absolute latency yard-stick those two do
not.
"Read the simulation waveform" tables — the PROGRAM replays the published table (#716)
When the prompt embeds a literal simulation table — rows of
time [clk] <input...> [internal...] <output> under a "Read the simulation
waveforms to determine what the circuit does, then implement it" instruction
(the VerilogEval circuitN family) — the table IS a directed test vector you
must reproduce exactly. The trap that fails the hidden scorer is
MIS-COUNTING PIPELINE STAGES: the early rows where the output reads x are
the input-sampling NBA race (@(posedge) a<=val leaves the input x on the
first edge), NOT an extra register stage. An agent who reads that X-window as
a second stage authors a TWO-stage q1<=~a; q<=q1 pipeline when the spec is the
ONE-stage q<=~a; its self-TB still "passes" but it scores ~58/123 mismatches on
the hidden TB (Prob098_circuit7, the round-18 FAIL). The X-window is consumed by
the X-match convention — it does not license an added latency stage. Count
stages from the first DEFINED output transition, not from the first x.
waveform_table_conformance_check.py is the independent yard-stick. It parses the
table, replays it the way the scorer compares (X in the table matches anything;
X in the DUT only matches a table X), and BLOCKS on any mismatch:
python3 plugins/vibe-ic/programs/waveform_table_conformance_check.py \
--prompt <prompt.txt> --rtl <your_rtl.sv> --top <module>
WTC_PASS (rc 0) — the RTL reproduces the published table; emit.
WTC_FAIL mismatches=<n> + per-row WTC_MISMATCH t=.. expected=.. got=..
(rc 1) — your RTL diverges from the table (the stage-count / inversion /
function is wrong). Fix and re-run.
WTC_SKIP_<reason> / WTC_NO_TABLE (rc 0) — the prompt has no table, OR the
design is outside the proven-faithful envelope (a negedge / level-sensitive
transparent latch, a multi-bit/hex output column, a Moore FSM exposing
multiple observable outputs, or non-binary table values). The gate
refuses to block these — its replay timing is only proven faithful for
combinational truth-tables and single-bit single-clock posedge-registered
outputs, so for everything else it advises rather than blocks (NO false-block).
- iverilog absent →
WTC_SKIP_no_tools (rc 0), never a fabricated PASS.
why_not_bucket_a: parsing the table, building the directed replay TB, running
it under the scorer's X-match, and comparing every row IS the deterministic
program. The LLM residual is reading the prose to confirm it is a
"read-the-waveform" problem and, on a WTC_FAIL, deciding WHICH clause of the
RTL (stage count, polarity, function) the mismatch implicates. This is the
DIRECTED-VECTOR complement to #700 (a random differential cross-check, which
is circular here because a stage-count misread biases BOTH the RTL and a
hand-derived ref) and #705 (which needs a prose latency literal a
waveform-only prompt does not supply).
Behavioral-prose Moore FSM — extract the table, let the PROGRAM emit (2026-06-23)
A FSM whose states + transitions are stated in NARRATIVE PROSE (Lemmings "if bumped
on the left it walks right; if it falls for >20 cycles it splatters"; a PS/2 byte-
boundary search; a sliding-window counter; a multi-phase controller) is the case
where reading prose → structure genuinely needs a language model — no deterministic
parser extracts it (so spec_artifact_registry.generate() correctly SKIPs it). But
once the structure is a COMPLETE enumerated table, emitting correct RTL is a pure
formula. So DO NOT hand-author the always-blocks — split the work:
- You (AI) extract the COMPLETE canonical Moore-FSM table from the prose — every
state, every Moore output per state, the reset (state + sync/async + level), and
EVERY transition (one row per state × every input combination; UNROLL a counter
like "falls for >20 cycles" into explicit states — the internal encoding is FREE
because the TB observes only the Moore outputs). Inputs may be a 1-bit port or a
bus bit-select (
in[3]). The format is the docstring of
programs/moore_fsm_table_emit.py.
- The PROGRAM emits + gates:
python3 programs/moore_fsm_table_emit.py --prompt <prompt> --table <your.tbl> --top <TopModule>. It VALIDATES the table is complete
and matches the declared interface (rejects a hallucinated port, a missing input
combo, an unknown next-state, a missing output) and emits the RTL, or SKIPs
(exit 1). The emitted RTL is then your authored sample.
This is the §4.2 AI-step-gated-by-program pattern, not free-text authoring: the
program guarantees the RTL is a pure function of a table it proved complete, so a
mis-extraction becomes a SKIP, never a wrong-but-plausible machine. Proven
0-mismatch on the full Lemmings family (1–4, incl. the 47-state >20-cycle splatter
counter), PS/2 (in[3]), the 3-cycle window counter, and the multi-phase motor
controller. If your first table mismatches its self-TB, RE-READ THE PROMPT (never
the hidden reference) to fix the offending transition/output.
Compliance gate (mandatory)
After producing your output, save it to a file and run:
python3 plugins/vibe-ic/_shared/skill_compliance_check.py \
--requirements plugins/vibe-ic/skills/SKILL_NAME/compliance.yaml \
<your_output_file>
Exit 0 = PASS, exit 1 = FAIL with specific missing elements listed.
compliance.yaml in this skill directory enumerates every required
element of your output: section headers, handoff lines, summary blocks.
Your task is not complete until the audit returns PASS. Missing
elements are the single largest source of skill-execution non-determinism
across different agents.