RTL Pipeline Orchestrator
This skill IS the plan — execute each stage immediately using Read/Write/Bash/Agent tools. Do NOT plan before executing.
Project directory path: $ARGUMENTS
If $ARGUMENTS is empty, ask the user for it.
Variable: ${CLAUDE_SKILL_DIR} is set by Claude Code to the skill's installed directory.
Step 0: Initialization
Run the initialization script:
PY_INIT="${PYTHON_EXE:-python}"
cd "$ARGUMENTS" && "$PY_INIT" "${CLAUDE_SKILL_DIR}/init.py" "$ARGUMENTS"
[ -f "$ARGUMENTS/.veriflow/eda_env.sh" ] && source "$ARGUMENTS/.veriflow/eda_env.sh"
Read the output to determine: new project or resuming. If resuming, skip stages in stages_completed.
Permission Check (sub-agent tools)
Sub-agents cannot interact with the user — any permission prompt will hang the pipeline.
Check that the following tools are pre-approved in the project's .claude/settings.json:
Heads-up — the allow-list below is the minimum. Sub-agents also run shell commands like
source,cd,iverilog,vvp,yosys,mkdir,ls,xargs. Those are NOT auto-added here because the right scope is environment- dependent. Two ways to keep the pipeline from hanging on a permission prompt:
- Launch Claude Code with
--permission-mode acceptEdits(or rely on~/.claude/settings.jsonsettingskipDangerousModePermissionPrompt: true).- Or, extend
.claude/settings.jsonallow-list with the patterns above (e.g."Bash(source*)","Bash(iverilog*)","Bash(yosys*)").If Stage 2/3/4 hangs silently, the cause is almost always one of these unlisted commands waiting on an invisible permission dialog.
SETTINGS=".claude/settings.json"
if [ ! -f "$SETTINGS" ]; then
echo '{"permissions":{"allow":[]}}' > "$SETTINGS"
fi
python3 -c "
import json
s = json.load(open('$SETTINGS'))
allow = s.setdefault('permissions', {}).setdefault('allow', [])
# Tools that sub-agents need (must not trigger permission dialog):
# - WebSearch: main session only (sub-agents do not have it)
# - Bash(python*): all agents run python for hook validation
# - Bash(test*): agents run 'test -f ...' for file existence checks
# - Write: agents write output files (spec.json, golden_model.py, etc.)
needed = [
'Bash(python*)',
'Bash(python3*)',
'Bash(test*)',
]
added = []
for tool in needed:
if not any(tool in rule for rule in allow):
allow.append(tool)
added.append(tool)
if added:
json.dump(s, open('$SETTINGS','w'), indent=2)
print(f'[PERM] Added to allowlist: {added}')
else:
print('[PERM] All sub-agent tools already allowed')
"
Step 0b: Requirements Clarification
Read ALL input files in parallel (single message with multiple Read calls):
$ARGUMENTS/requirement.md(required)$ARGUMENTS/constraints.md(optional)$ARGUMENTS/design_intent.md(optional)$ARGUMENTS/context/*.mdfiles
Check each category below. If input files already answer it → note "confirmed" and skip. Only ask about what's missing or ambiguous. Use AskUserQuestion with up to 4 questions per call.
A. Functional clarity: module functionality, interface protocol, data format, FSM behavior, clock domain crossings B. Constraint clarity: clock frequency, target platform, area/power budget, reset strategy, IO standards C. Design intent: architecture style, module partitioning, interface preferences, IP reuse, key decisions D. Algorithm & protocol: algorithm reference, pseudocode, key formulas, test vectors E. Timing completeness: cycle-level behavior, latency, throughput, interface timing, reset recovery, backpressure F. Domain knowledge: design domain, standard reference, prerequisite concepts, test vectors G. Information completeness: implicit assumptions, missing scenarios
After resolved, write $ARGUMENTS/.veriflow/clarifications.md with all answers.
Step 0c: Create task list
Create one task per pipeline stage (skip if resuming and already completed):
Stage 1: spec_goldenStage 2: codegenStage 3: verify_fixStage 4: lint_synth
Stage Pattern (ALL stages follow this)
Every stage MUST execute these 3 steps in order:
Pre-stage:
[ -f "$PROJECT_DIR/.veriflow/eda_env.sh" ] && source "$PROJECT_DIR/.veriflow/eda_env.sh" && $PYTHON_EXE "${CLAUDE_SKILL_DIR}/state.py" "$PROJECT_DIR" "<STAGE>" --start
Execute: dispatch agents (Stages 1/2/4) or run inline (Stage 3)
Post-stage:
[ -f "$PROJECT_DIR/.veriflow/eda_env.sh" ] && source "$PROJECT_DIR/.veriflow/eda_env.sh" && $PYTHON_EXE "${CLAUDE_SKILL_DIR}/state.py" "$PROJECT_DIR" "<STAGE>" --hook="<HOOK_CMD>" --journal-outputs="<FILES>" --journal-notes="<NOTES>"
Then: TaskUpdate mark the stage task as completed.
Stage 1: spec_golden
Pre-stage: Web Research + Template Pre-read
Read all input files first (run in main session, parallel Read calls):
${CLAUDE_SKILL_DIR}/templates/spec_template.json→ passed asSPEC_TEMPLATE${CLAUDE_SKILL_DIR}/templates/golden_model_template.py→ passed asGOLDEN_TEMPLATE$PROJECT_DIR/requirement.md$PROJECT_DIR/constraints.md(if exists)$PROJECT_DIR/design_intent.md(if exists)$PROJECT_DIR/context/*.md(if exists)$PROJECT_DIR/.veriflow/clarifications.md
Web Research (run in main session, results passed inline to sub-agents):
After reading all input files above, judge whether WebSearch is needed:
- If
requirement.md+context/*.mdalready contain: algorithm specification, test vectors, pin/protocol definitions, and enough detail to build spec.json and golden_model.py → skip WebSearch. Write a note to$PROJECT_DIR/.veriflow/web_research.md:# Web research skipped — input files provide sufficient detail Algorithm: <name>, source: <which files had the info> - Otherwise, extract the algorithm/design name from
requirement.md, then use WebSearch:"<algorithm_name> specification test vectors"— for spec.json constraints"<algorithm_name> Verilog RTL reference"— for coder patterns Store results in$PROJECT_DIR/.veriflow/web_research.md
Agent Dispatch: Single spec-golden agent
Run vf-spec-golden (single Agent call):
- vf-spec-golden (subagent_type: general-purpose)
- Prompt includes: PROJECT_DIR, CLARIFICATIONS path, SPEC_TEMPLATE content, GOLDEN_TEMPLATE content, WEB_RESEARCH content, all input file contents inline
- The agent generates both spec.json and golden_model.py in one pass, with timing alignment done internally (it writes spec.json first, then uses its cycle_timing to align golden_model.py trace cycles).
After it returns:
- Read
workspace/docs/spec.jsonandworkspace/docs/golden_model.pyto verify.
Golden model self-check (pre-codegen gate — run FIRST to catch syntax errors):
Before proceeding to Stage 2, verify that golden_model.py passes its own test vectors. This catches algorithmic and syntax bugs before RTL is generated.
cd "$PROJECT_DIR"
if [ -f workspace/docs/golden_model.py ]; then
$PYTHON_EXE workspace/docs/golden_model.py 2>&1 | tee logs/golden_selfcheck.log
if grep -q "FAIL" logs/golden_selfcheck.log; then
echo "[GOLDEN] Self-check FAILED — fix golden_model.py before proceeding."
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/state.py" "$PROJECT_DIR" "spec_golden" --fail
echo "[GOLDEN] Stage 1 marked failed; Stage 2 will not run. Main session: fix golden_model.py and re-run Stage 1."
exit 1
fi
fi
Timing contract check (pre-verification, before codegen):
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/timing_contract_checker.py" \
--spec workspace/docs/spec.json \
--golden workspace/docs/golden_model.py \
--output logs/timing_check.json
If this reports errors, auto-fix first — the checker can correct most common timing contract mistakes (registered→combinational delays, missing handshake fields, latency mismatches):
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/timing_contract_checker.py" \
--fix \
--spec workspace/docs/spec.json \
--golden workspace/docs/golden_model.py \
--output logs/timing_check.json
Re-run the checker after --fix to confirm all errors are resolved. Only if errors remain after auto-fix, review logs/timing_check.json and manually fix spec.json or golden_model.py. Errors indicate timing contradictions that will cause RTL bugs.
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/state.py" "$PROJECT_DIR" "spec_golden" --hook="test -f workspace/docs/spec.json && test -f workspace/docs/golden_model.py" --journal-outputs="workspace/docs/spec.json, workspace/docs/golden_model.py" --journal-notes="spec.json interface + golden_model.py behavior"
TaskUpdate complete.
Stage 2: codegen
Read spec.json, golden_model.py, and coding_style.md (parallel Read calls) to include inline in prompts.
Single-path dispatch: All modules go through AI Assembly (vf-coder).
AI Assembly
- One vf-coder per module in spec.json (subagent_type: general-purpose)
- Prompt includes: MODULE_NAME, OUTPUT_FILE path
GOLDEN_MODEL: the relevant Python functions from golden_model.py that describe this module's behavior. The orchestrator extracts these by matching the module name against function names/classes in golden_model.py. Include the full Python implementation — vf-coder translates it into Verilog.MODULE_SPEC: this module's ports/parameters/timing_contract from spec.jsonTIMING_TABLE: main session builds a cycle-accurate timing table from spec.jsoncycle_timingandtiming_contract, showing:- Registered outputs (use
output wire+reg+assign) - Combinational outputs (use
output wire+assigndirectly) - Pipeline stages and latency
- Registered outputs (use
WEB_RESEARCH: content from.veriflow/web_research.md— only if the file has substantive content (more than just "skipped" or "unavailable"). If minimal, omit this field entirely to save prompt tokens.PREV_FAILURE(only on Stage 3 retry — see Stage 3 step 5b): a 5-line summary of the last simulation failure ("cycle N, signal X, expected=A, actual=B; bug class: "). When present, instruct vf-coder to address this specific divergence first before any other rewriting.- Condensed coding_style.md content
- For top modules: include submodule port definitions from spec.json
TB Generation + All Agent Dispatch
Dispatch ALL agents in parallel (single message):
- One vf-coder agent per module
- One vf-tb-gen (subagent_type: general-purpose)
- Prompt includes: PROJECT_DIR, DESIGN_NAME, spec.json content, golden_model.py content, COCOTB_AVAILABLE flag,
${CLAUDE_SKILL_DIR}/templatespath - DRIVE_PHASE_CYCLES: Read from
spec.json timing_convention.golden_to_rtl_offset_cycles. If not set, fall back tomax(pipeline_delay_cycles)from timing_contract. - CRITICAL: The Verilog testbench MUST respect input hold time derived from spec.json
module_connectivitytiming_contract. Data inputs MUST remain stable for at leastDRIVE_PHASE_CYCLES + 1cycles after the valid pulse.
- Prompt includes: PROJECT_DIR, DESIGN_NAME, spec.json content, golden_model.py content, COCOTB_AVAILABLE flag,
After ALL return, verify outputs exist:
ls "$PROJECT_DIR/workspace/rtl/"*.v "$PROJECT_DIR/workspace/tb/"*.v "$PROJECT_DIR/workspace/tb/"*.py 2>/dev/null
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/state.py" "$PROJECT_DIR" "codegen" --hook="ls workspace/rtl/*.v >/dev/null 2>&1 && (test -f workspace/tb/test_*.py || test -f workspace/tb/tb_*.v)" --journal-outputs="workspace/rtl/*.v, workspace/tb/test_*.py, workspace/tb/tb_*.v" --journal-notes="RTL and testbench generated in parallel"
TaskUpdate complete.
Stage 3: verify_fix (inline — runs in main session)
IMPORTANT: This stage runs inline because error recovery needs main session context for Edit tool.
Verification order (project policy — do NOT reverse):
- cocotb runs FIRST when
cocotb-configis on PATH — it is the primary verification path. The per-cycle VPI comparison and FIRST DIVERGENCE report are the main diagnostic signal for error recovery. - The pure-Verilog testbench runs SECOND as a fallback / cross-check. When cocotb is unavailable, it is the only path.
Pre-stage:
[ -f "$PROJECT_DIR/.veriflow/eda_env.sh" ] && source "$PROJECT_DIR/.veriflow/eda_env.sh" && $PYTHON_EXE "${CLAUDE_SKILL_DIR}/state.py" "$PROJECT_DIR" "verify_fix" --start
Golden Model Self-Check (BEFORE simulation)
If golden_model.py exists, verify it passes its own test vectors first. This catches golden model bugs BEFORE wasting time on RTL debugging.
[ -f "$PROJECT_DIR/.veriflow/eda_env.sh" ] && source "$PROJECT_DIR/.veriflow/eda_env.sh"
cd "$PROJECT_DIR"
if [ -f workspace/docs/golden_model.py ]; then
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/iverilog_runner.py" \
--golden-check workspace/docs/golden_model.py 2>&1 | tee logs/golden_selfcheck.log
# ${PIPESTATUS[0]} reads the runner's exit, not tee's. Bash-only.
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
echo "[GOLDEN] Self-check FAILED — the reference model has bugs."
echo "[GOLDEN] Fix golden_model.py FIRST. The problem is NOT in the RTL."
# Do NOT consume retry budget — this is a golden model issue.
# Mark stage failed AND abort so the main session is forced to fix
# golden_model.py before any RTL debugging.
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/state.py" "$PROJECT_DIR" "verify_fix" --fail
echo "[GOLDEN] verify_fix marked failed. Main session: fix golden_model.py, then re-run /vf-rtl."
exit 1
fi
fi
Cocotb per-cycle verification (if cocotb available)
Before running Verilog simulation, run cocotb with per-cycle internal signal comparison. This is the PRIMARY debugging tool — it finds the FIRST divergence point automatically, instead of only checking final outputs.
Uses cocotb_runner.py (no Makefile required) — it handles build, test,
VCD capture, and JSON result output via cocotb_tools.runner.Icarus.
if command -v cocotb-config &>/dev/null; then
TOP_MODULE=$($PYTHON_EXE -c "
import json
for m in json.load(open('workspace/docs/spec.json')).get('modules', []):
if m.get('module_type') == 'top': print(m['module_name']); break
")
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/cocotb_runner.py" \
--rtl-dir workspace/rtl \
--tb-dir workspace/tb \
--module $TOP_MODULE \
--build-dir workspace/sim_cocotb \
--results-file logs/cocotb_results.xml \
--verbose 2>&1 | tee logs/cocotb.log
# cocotb_runner.py exits 0=pass, 1=fail, 2=env error
# JSON summary is on stdout (last line), details in cocotb.log
if grep -q "FIRST DIVERGENCE" logs/cocotb.log; then
echo "[COCOTB] Internal signal mismatch found — see cocotb.log for details"
# Extract first divergence info — this is the PRIMARY diagnostic
# for error_recovery.md. Do NOT guess the root cause.
fi
fi
If cocotb is not available, proceed with Verilog simulation as before.
Run simulation
[ -f "$PROJECT_DIR/.veriflow/eda_env.sh" ] && source "$PROJECT_DIR/.veriflow/eda_env.sh"
cd "$PROJECT_DIR"
TOP_MODULE=$($PYTHON_EXE -c "
import json
for m in json.load(open('workspace/docs/spec.json')).get('modules', []):
if m.get('module_type') == 'top': print(m['module_name']); break
")
VERILOG_TB=$(ls workspace/tb/tb_*.v 2>/dev/null | head -1)
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/iverilog_runner.py" \
--module $TOP_MODULE --rtl-dir workspace/rtl --tb-file "$VERILOG_TB" \
--build-dir workspace/sim --verbose 2>&1 | tee logs/sim.log
If PASS
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/state.py" "$PROJECT_DIR" "verify_fix" --hook="grep -q 'ALL TESTS PASSED' logs/sim.log" --journal-outputs="logs/sim.log" --journal-notes="Simulation passed"
TaskUpdate complete. Go to Stage 4.
If FAIL
- Run timing diagnostic + expected trace (BEFORE manual analysis):
[ -f "$PROJECT_DIR/.veriflow/eda_env.sh" ] && source "$PROJECT_DIR/.veriflow/eda_env.sh"
LOG_FILE=$(test -f logs/cocotb.log && echo logs/cocotb.log || echo logs/sim.log)
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/timing_diagnostic.py" \
--log "$LOG_FILE" \
--golden workspace/docs/golden_model.py \
--spec workspace/docs/spec.json \
--output logs/timing_diagnostic.json
Then, generate the expected per-cycle register trace from golden_model.py.
This complements the bug-class output of timing_diagnostic.py with
concrete expected[cycle][reg] values to compare against the VCD-derived
actual[cycle][reg] table. Cycle count is derived from spec.json
(max pipeline_delay_cycles + 4, fallback 16):
cd "$PROJECT_DIR"
EXPECTED_TRACE_CYCLES=$($PYTHON_EXE -c "
import json
try:
spec = json.load(open('workspace/docs/spec.json'))
explicit = spec.get('constraints', {}).get('verification', {}).get('trace_cycles')
if isinstance(explicit, int):
print(explicit)
else:
delays = []
for m in spec.get('modules', []):
d = m.get('timing_contract', {}).get('pipeline_delay_cycles')
if isinstance(d, (int, float)):
delays.append(int(d))
print(max(delays) + 4 if delays else 16)
except Exception:
print(16)
")
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/expected_trace_gen.py" \
--golden workspace/docs/golden_model.py \
--cycles "$EXPECTED_TRACE_CYCLES" \
--output logs/expected_trace_golden.md \
2>&1 | tee logs/expected_trace_gen.log
Post-check: verify expected trace was generated:
if [ ! -s "$PROJECT_DIR/logs/expected_trace_golden.md" ]; then
echo "[WARN] Expected trace not generated — error recovery will lack per-cycle reference"
fi
Read logs/expected_trace_golden.md along with the simulation divergence
report — expected[cycle][reg] vs actual[cycle][reg] is the fastest way
to localise the wrong NBA assignment in the RTL.
Read
logs/timing_diagnostic.json— this contains the classification (B_late/B_early/A/D) andfix_suggestionwith precise instructions. Follow the fix_suggestion directly — you do NOT need to understand NBA timing.If no diagnosis (tool returns "No FIRST DIVERGENCE found"): Read
${CLAUDE_SKILL_DIR}/error_recovery.md— follow the full procedure Collect data: readlogs/sim.log, run vcd2table diff, classify bug type 5-point root cause analysis → write tologs/stage_journal.mdFix RTL using Edit tool
Record this failure signature before re-running (lets the loop detector see repeats):
SIG=$($PYTHON_EXE -c "
import json, pathlib
p = pathlib.Path('logs/timing_diagnostic.json')
if p.exists():
d = json.loads(p.read_text())
div = d.get('divergence', {})
print(f\"cycle:{div.get('cycle','?')}:signal:{div.get('signal','?')}\")
else:
print('no-diagnostic')
")
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/state.py" "$PROJECT_DIR" "verify_fix" \
--fail --error-sig="$SIG"
5b. Build the failure-summary file for the next vf-coder retry (D2: keep
the divergence one Read away — main session always re-reads it before any
inline fix; if step 6 falls back to codegen, it MUST inline this file into
the new vf-coder prompt under PREV_FAILURE):
$PYTHON_EXE - <<'PY' 2>&1 | tee logs/prev_failure_summary.md
import json, pathlib
diag = pathlib.Path("logs/timing_diagnostic.json")
expected = pathlib.Path("logs/expected_trace_golden.md")
print("# Previous attempt failure summary")
print()
if not diag.exists():
print("(no logs/timing_diagnostic.json — `timing_diagnostic.py` did not produce a report)")
else:
d = json.loads(diag.read_text())
div = d.get("divergence", {}) or {}
print(f"- **First divergence cycle**: {div.get('cycle', '?')}")
print(f"- **Signal**: `{div.get('signal', '?')}`")
print(f"- **Expected**: `{div.get('expected', '?')}`")
print(f"- **Actual**: `{div.get('actual', '?')}`")
print(f"- **Bug class**: {d.get('bug_class', '?')} ({d.get('confidence', '?')})")
fix = d.get("fix_suggestion") or {}
if fix:
print()
print("## Suggested fix direction")
for k, v in fix.items():
print(f"- **{k}**: {v}")
if expected.exists():
print()
print("## Expected trace (first 8 cycles, from golden_model.py)")
lines = expected.read_text().splitlines()
# Skip the heading, keep up to 10 lines of the table
body = [ln for ln in lines if ln.strip() and not ln.startswith("##")][:10]
print("\n".join(body))
PY
If logs/prev_failure_summary.md is non-empty, it MUST be passed as the
PREV_FAILURE field on any subsequent vf-coder invocation.
- Check whether we are looping on the same bug BEFORE consuming another retry slot. If the same divergence signature has fired 2+ times, fixing RTL is not converging — rollback to codegen instead of burning more attempts:
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/state.py" "$PROJECT_DIR" "verify_fix" \
--check-loop="$SIG"
LOOP_STATUS=$?
if [ "$LOOP_STATUS" -eq 2 ]; then
echo "[STAGE3] Detected fix-loop on '$SIG' — rolling back to codegen."
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/state.py" "$PROJECT_DIR" --reset codegen
# Main session: re-dispatch Stage 2 (codegen) and pass the contents of
# logs/prev_failure_summary.md inline as PREV_FAILURE for every vf-coder
# invocation — without this, the agent will repeat the same bug.
fi
Re-run simulation (go back to "Run simulation" above)
Retry budget: 3 attempts total
- 1st fail: fix RTL, retry
- 2nd fail:
state.py --reset codegen, restart from Stage 2 - 3rd fail: STOP, notify user
- At ANY attempt: if step 6's loop detector returns 2, jump straight to
state.py --reset codegenand re-run Stage 2 without waiting for the 3rd attempt.
Stage 4: lint_synth
Dispatch 2 parallel agents (single message):
- vf-linter (subagent_type: general-purpose) — include PROJECT_DIR, EDA_ENV path, PYTHON_EXE, SKILL_DIR
- vf-synthesizer (subagent_type: general-purpose) — include PROJECT_DIR, SPEC path, EDA_ENV path, PYTHON_EXE, SKILL_DIR
After BOTH return:
If lint failed → fix syntax errors in main session, re-run lint only. If synth failed → check report, fix if needed.
$PYTHON_EXE "${CLAUDE_SKILL_DIR}/state.py" "$PROJECT_DIR" "lint_synth" --hook="test -f logs/lint.log && test -f workspace/synth/synth_report.txt" --journal-outputs="logs/lint.log, workspace/synth/synth_report.txt" --journal-notes="Lint and synthesis complete"
TaskUpdate complete. Pipeline done.
Design Rules Summary
See ${CLAUDE_SKILL_DIR}/design_rules.md for full rules.
- Synchronous active-high reset named
rst - Port naming:
_nsuffix for active-low,_i/_ofor direction - Verilog-2005 only — NO SystemVerilog
- Interface Lock: port names, handshake protocols, and module hierarchy are frozen after Stage 1