# Vf Rtl

> Use this skill to start or resume the VeriFlow RTL hardware design pipeline (architect to synth). Trigger this when the user asks to "run the RTL flow", "design hardware", or "start the pipeline". Pass the project directory path as the argument.

- Skill: `majiayu000/vf-rtl-2` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add majiayu000/vf-rtl-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/vf-rtl-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/majiayu000/vf-rtl-2

---


# 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:

```bash
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:
>
> 1. Launch Claude Code with `--permission-mode acceptEdits` (or rely on
>    `~/.claude/settings.json` setting `skipDangerousModePermissionPrompt: true`).
> 2. Or, extend `.claude/settings.json` allow-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.

```bash
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/*.md` files

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_golden`
- `Stage 2: codegen`
- `Stage 3: verify_fix`
- `Stage 4: lint_synth`

---

## Stage Pattern (ALL stages follow this)

Every stage MUST execute these 3 steps in order:

**Pre-stage:**
```bash
[ -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:**
```bash
[ -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 as `SPEC_TEMPLATE`
- `${CLAUDE_SKILL_DIR}/templates/golden_model_template.py` → passed as `GOLDEN_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/*.md` already 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:

1. Read `workspace/docs/spec.json` and `workspace/docs/golden_model.py` to 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.

```bash
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):
```bash
$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):
```bash
$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.

```bash
$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.json
  - `TIMING_TABLE`: main session builds a cycle-accurate timing table from
    spec.json `cycle_timing` and `timing_contract`, showing:
    - Registered outputs (use `output wire` + `reg` + `assign`)
    - Combinational outputs (use `output wire` + `assign` directly)
    - Pipeline stages and latency
  - `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: <classification>"). 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}/templates` path
  - **DRIVE_PHASE_CYCLES**: Read from `spec.json timing_convention.golden_to_rtl_offset_cycles`. If not set, fall back to `max(pipeline_delay_cycles)` from timing_contract.
  - **CRITICAL**: The Verilog testbench MUST respect input hold time derived from spec.json `module_connectivity` timing_contract. Data inputs MUST remain stable for at least `DRIVE_PHASE_CYCLES + 1` cycles after the valid pulse.

After ALL return, verify outputs exist:
```bash
ls "$PROJECT_DIR/workspace/rtl/"*.v "$PROJECT_DIR/workspace/tb/"*.v "$PROJECT_DIR/workspace/tb/"*.py 2>/dev/null
```
```bash
$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):**
1. cocotb runs FIRST when `cocotb-config` is 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.
2. The pure-Verilog testbench runs SECOND as a fallback / cross-check.
   When cocotb is unavailable, it is the only path.

Pre-stage:
```bash
[ -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.

```bash
[ -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`.

```bash
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

```bash
[ -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

```bash
$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

1. **Run timing diagnostic + expected trace** (BEFORE manual analysis):
```bash
[ -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):
```bash
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:
```bash
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.

2. **Read** `logs/timing_diagnostic.json` — this contains the classification
   (B_late/B_early/A/D) and `fix_suggestion` with precise instructions.
   **Follow the fix_suggestion directly** — you do NOT need to understand NBA timing.

3. **If no diagnosis** (tool returns "No FIRST DIVERGENCE found"):
   **Read** `${CLAUDE_SKILL_DIR}/error_recovery.md` — follow the full procedure
   **Collect data**: read `logs/sim.log`, run vcd2table diff, classify bug type
   **5-point root cause analysis** → write to `logs/stage_journal.md`

4. **Fix RTL** using Edit tool

5. **Record this failure signature** before re-running (lets the loop detector
   see repeats):
```bash
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`):
```bash
$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.

6. **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:
```bash
$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
```

7. **Re-run simulation** (go back to "Run simulation" above)

8. **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 codegen` and 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.

```bash
$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: `_n` suffix for active-low, `_i`/`_o` for direction
- **Verilog-2005 only** — NO SystemVerilog
- Interface Lock: port names, handshake protocols, and module hierarchy are frozen after Stage 1

