| name | gen-tb |
| description | Generate a complete UVM testbench scaffold for a single IP from its spec documents (docx/pdf/md) and register table (xlsx/csv/md/IP-XACT). Use whenever the user asks to "generate a UVM tb", "build a verification environment", "scaffold a testbench", "create a UVC", "set up DV for this IP", or anything equivalent in Chinese ("帮我生成验证环境", "搭一个 tb", "做 UVM 环境", "做这个 IP 的验证"). Also trigger when the user points at a directory containing an IP spec and asks Claude to "verify it" / "test it" without naming UVM explicitly. Produces a runnable UVM tb (APB / AHB-Lite / AXI4-Lite as first-class buses; other single-channel buses via a generic fallback) with sanity + register-access tests passing, plus a DE-friendly `tb_api::` task BFM. |
gen-tb — UVM Testbench Scaffold Generator
Purpose
Generate a directory-aligned UVM testbench for one IP from:
Built-in first-class buses: APB slave, AHB-Lite (slave or master),
AXI4-Lite (slave or master). Other single-channel request/response
buses (I2C, SPI, Wishbone, OBI, custom register buses, …) are handled
by a generic fallback that reuses the same layout, sim flow, and
tb_api surface — see §Scope below.
Inputs:
- behavioral spec:
*.docx, *.pdf, *.md
- register table:
*.xlsx, *.csv, markdown table, IP-XACT XML
- RTL in place, an external RTL path, or a generated stub
The result must compile, run <ip>_sanity_test, run
<ip>_reg_access_test, and provide two usage surfaces:
- DV persona: UVM tests, sequences, agent, monitor, RAL hooks
- DE persona:
tb_api::write/read/expect_reg task-style BFM
Scope
| Dimension | Built-in (high-quality scaffold) | Generic fallback | Planned |
|---|
| Bus | APB slave, AHB-Lite (slave or master), AXI4-Lite (slave or master) | any single-channel request/response bus the scaffold sub-agent can infer from spec + the three built-in exemplars (e.g. I2C, SPI, Wishbone, OBI, custom register buses) | AXI4 full |
| Simulator | VCS, Questa (vlog/vsim — static-only, see note), xrun (Cadence Xcelium) | VCS, Questa, xrun | — |
| Ref model | none, SV, C-DPI | none, SV, C-DPI | Python-DPI |
| Spec input | docx, pdf, md | docx, pdf, md | — |
| Reg input | xlsx, csv, md, IP-XACT | xlsx, csv, md, IP-XACT (optional — set register_semantics: no for non-register buses) | — |
Generic fallback is a best-effort path: directory layout and sim flow
stay authoritative, but the bus-shaped pieces (<bus>_if.sv, agent,
sequence library, tb_api body) are inferred per-IP by a constrained
scaffold sub-agent that pattern-matches on the three built-in
references. Tell the user up-front that scaffold correctness is lower
and the chance of an unresolved.md delivery is higher than in
built-in mode. Load references/generic_bus.md only when entering
this path.
AXI4 full (bursts/IDs/outstanding) is out of scope in both modes — it
must never be routed to generic. If the DUT has AXI ports, follow the
AXI4-full detector in Phase 1 and the mandatory question in Phase 2.
For DUTs that expose AXI signals but use only single-beat transfers,
generate the AXI4-Lite environment with a monitor-side assertion that
AWLEN == 0 && ARLEN == 0, so any later burst use fails loud at sim
time rather than relying on user review.
Do not use this skill for debugging an existing UVM environment, formal
verification, gate-level sim, DFT, or a multi-master SoC subsystem.
Pipeline
Each phase writes audit artifacts under <ip>/work/_gen_audit/.
Do not collapse phases; resumability and auditability are part of the
deliverable.
- Discover: identify IP, spec, regs, RTL, optional external VIP
- Intake: ask only unresolved questions
- Normalize: produce
registers.yaml, behavior.md, parse_report.md
- Scaffold: write the generated tree
- Compile-fix: run compile and use a constrained sub-agent if needed
- Sanity: run mandatory tests and runtime-fix loop if needed
- Hand-off: write
CLAUDE.md, unresolved.md, and concise summary
Pipeline Hooks (Optional)
After each phase finishes writing its audit artifacts, call:
python3 scripts/hooks.py <phase> <ip_root>
with <phase> ∈ {discover, intake, normalize, scaffold, compile_fix, sanity, handoff}. The script looks for
<ip_root>/.gen_tb_hooks/<phase>[.strict].{py,sh} and is a no-op when
absent (zero cost in the common case). Non-strict hook failures are
logged to work/_gen_audit/hooks/<phase>.log and do not block the
pipeline; .strict. hooks abort the phase on non-zero exit. Hooks
receive a JSON payload on stdin and GEN_TB_PHASE / GEN_TB_IP_ROOT /
GEN_TB_AUDIT_DIR in env. Do not invoke hooks before audit artifacts
for the phase are on disk.
Phase 1: Discover
IP Selection
Pick the IP root in this order:
- Explicit IP name/path from the user
- Current directory if its basename looks like an IP name
- The only child directory containing spec files
- Otherwise ask the user to choose
Show the selected IP name and path once so the user can correct it.
Spec And Registers
Search spec/, doc/, docs/, then IP root. Classify:
- register table:
*reg*table*.xlsx/csv, *regs*.xlsx/csv, IP-XACT XML
- behavior spec: large
*.pdf, *.docx, *spec*.md
- markdown tables that look like regs: confirm if ambiguous
Do not ingest README, license, unrelated datasheets, draft/old/bak files.
RTL
Prefer an existing filelist. Otherwise scan rtl/, src/, design/,
hdl/, and <ip_name>/ for RTL, excluding tb/test/sim/bench paths.
If no RTL exists, generate a stub only when the user confirms.
Infer top module using AST tooling if available; regex inference is
medium confidence. If top is ambiguous, ask. Record exact APB / AHB /
AXI4-Lite signal names and widths in rtl_discovery.yaml; never
normalize case. For AHB-Lite and AXI4-Lite, also record which side of
the bus the DUT sits on (bus_direction: slave|master) — slave is the
default and means the TB drives a master BFM; master means the TB
provides a memory-backed slave responder.
For buses that fall through to generic mode, rtl_discovery.yaml
does not record bus_direction. The direction is collected in
Phase 2 into bus_handshake.yaml.direction and that is the single
source of truth for generic mode.
For details, load references/rtl_discovery.md and
references/rtl_stub.md.
Bus Classification
After signal recording, classify the bus:
- If the top's port set matches the APB / AHB-Lite / AXI4-Lite
signature, set
bus_protocol accordingly. Unchanged path.
- AXI4-full detector. If any of
awlen | arlen | awburst | arburst | awid | arid is present with width > 0, set
axi_full_signature: true in rtl_discovery.yaml. The Phase 2
mandatory question below decides whether to refuse or degrade
to AXI4-Lite. An AXI signal set must never be routed to
bus_protocol: generic.
- Otherwise set
bus_protocol: unknown and emit a
candidate_signals: block (the request/response-looking port
group) into rtl_discovery.yaml. Do not guess a name — the
bus name is collected in Phase 2.
Existing Bus VIP
If the user says they already have an APB, AHB, or AXI4-Lite VIP, ask
for only:
<bus>_vip_path
- desired reuse level:
import_only or drive_with_vip
Use:
bus_protocol: apb
apb_vip_source: reuse_my_vip
apb_vip_path: <path>
apb_vip_reuse_level: import_only
import_only means scaffold imports the VIP into the compile tree and
keeps built-in tests on tb_api. drive_with_vip means Phase 5 must
also generate/adapt glue so the user's VIP can drive a minimal bus
read/write smoke sequence — not implemented in v1; scaffold.py
refuses drive_with_vip and the skill must default to import_only
or hand-author the VIP-driven smoke test post-hoc. Do not guess
third-party VIP APIs in Phase 4. For import_only, scaffold may skip
vendor *_test_pkg.sv files that only serve the VIP's standalone
harness.
Load references/apb_external_vip.md, references/ahb_external_vip.md,
or references/axi_lite_external_vip.md before implementing or fixing
external VIP reuse.
At phase end, if <ip>/.gen_tb_hooks/ exists, run
python3 scripts/hooks.py discover <ip_root>.
Phase 2: Intake
Ask unresolved items in small batches. Questions to ask only when not
already known.
Preset bundles are allowed (e.g. "Default config" / "Add C-DPI" /
"VCS + xrun"), but every preset menu must satisfy:
- The currently supported simulators (
vcs, questa, xrun) must
all appear at least once across the offered presets, or be reachable
via an explicit "Customize simulators" / "Type something" path that
lists them. Never present a simulator-related preset that names only
a subset (e.g. listing "VCS / xrun" while omitting Questa) — it
misleads the user into thinking the missing one is unsupported.
- The same rule applies to any other dimension with >1 first-class
value (bus protocol, refmodel language). If a preset hides a
first-class option, the menu must include a visible "Customize"
escape that surfaces the full set.
- The AXI4-full mandatory question (see below) is never allowed
inside a preset bundle — it must be its own AskUserQuestion turn.
Fields to ask about when not already pinned by Phase 1:
- RTL state: found, external path, generated stub, none
- bus protocol: APB, AHB, AXI4-Lite, or
generic (when Phase 1 set
bus_protocol: unknown)
- bus direction (AHB-Lite, AXI4-Lite, generic): DUT is slave (default) or master
- bus VIP source: generate fresh, reuse existing VIP
- external VIP reuse level: import only, drive with VIP
- reference model language: none, SV, C-DPI (Python-DPI schema exists but is
not yet emitted — see
references/refm_dpi.md)
- clock/reset:
pclk/presetn, hclk/hresetn, or aclk/aresetn frequency, polarity, reset cycles
- UVM version: default 1.2 unless user specifies otherwise
- simulator toolchain (multi-select):
vcs, questa, xrun.
Default [vcs] when unanswered. Record in intake.yaml as
simulators: [vcs] / [vcs, questa] / [vcs, xrun] /
[vcs, questa, xrun] / etc. At least one must be selected.
Per-sim emission:
vcs → script/makefile (canonical make all SV_CASE=<case>).
questa → script/makefile_questa
(make -f makefile_questa all SV_CASE=<case>). Static-only —
gen-tb has no Questa install to validate against; tell the user to
verify locally before trusting it in CI.
xrun → script/makefile_xrun
(make -f makefile_xrun all SV_CASE=<case>).
When vcs is not selected, script/makefile becomes a thin shim
that includes whichever sim-specific makefile is present, so the
canonical make all SV_CASE=... still works. scripts/compile_and_sanity.py
drives VCS by default; for questa-only or xrun-only configurations,
run the mandatory tests with make -f makefile_<sim> all SV_CASE=<case>
manually. Recommended user-facing wording:
"Which simulators should I generate makefiles for? VCS / Questa /
xrun (multi-select; default VCS)."
- tcsh/csh setup helper: default no. If the user uses tcsh/csh,
record
emit_setup_csh: true in intake.yaml; scaffold still emits
script/setup.sh and additionally emits script/setup.csh. Do not
infer this from $SHELL; generated trees may be used by other users
or CI shells.
- address width:
paddr_width, haddr_width, or axi_addr_width, default 12
- endianness for multi-word arrays
- coverage: enabled or skipped
- required extra smoke tests
AXI4-full Mandatory Question
When rtl_discovery.yaml has axi_full_signature: true, ask exactly:
"Does this DUT use bursts (>1 beat), IDs, or outstanding?" This
question is mandatory and cannot be skipped. Record the answer in
intake.yaml as axi4_full_features: none (No) or
axi4_full_features: present (Yes) — scaffold.py refuses to run
without it.
- Yes (
axi4_full_features: present) → hard refuse. Write
unresolved.md pointing to planned AXI4-full work and exit. Do not
enter Phase 3.
- No (
axi4_full_features: none) → route to the built-in AXI4-Lite
path. Phase 4 must emit a monitor-side assertion that
AWLEN == 0 && ARLEN == 0, so any later burst use fails loud at
sim time.
- Unanswered / skipped → treat as Yes (refuse).
scaffold.py exits
FATAL when axi_full_signature: true and the intake key is absent.
Generic Bus Intake Sub-Batch
When bus_protocol resolves to generic, collect the structured
handshake description into work/_gen_audit/bus_handshake.yaml:
bus_name: <short token, used in filenames>
direction: slave | master
clock: { name: <port>, freq_mhz: <n> }
reset: { name: <port>, polarity: low|high, cycles: <n> }
addr: { port: <port>, width: <n> } | null
data: { write_port: <port>, read_port: <port>, width: <n> }
handshake:
kind: req_ack | valid_ready | strobe | custom
req: <port>
ack: <port>
extra: [ <other control ports to model> ]
burst: none | simple_incr
register_semantics: yes | no
notes: |
Free-form. Primary handshake hint to the scaffold sub-agent.
Confirm register_semantics explicitly. no means RAL and
<ip>_reg_access_test are not generated and Phase 6 degrades to
<ip>_responder_smoke_test.
All generated tests must be named <ip>_<purpose>_test.
Every question turn must include an abort/restart option. Save partial
answers to work/_gen_audit/intake.yaml and bus_handshake.yaml, and
resume from them if present.
At phase end, if <ip>/.gen_tb_hooks/ exists, run
python3 scripts/hooks.py intake <ip_root>.
Phase 3: Normalize
Create:
work/_gen_audit/spec_normalized/registers.yaml
work/_gen_audit/spec_normalized/behavior.md
work/_gen_audit/spec_normalized/parse_report.md
Never proceed to Phase 4 without valid registers.yaml, except
when bus_handshake.yaml.register_semantics == no in generic mode —
that case has no register table by design, and registers.yaml is
optional. behavior.md and parse_report.md are still required in
all cases. Missing behavior parsers may degrade to warnings, but a
missing/unparseable register table is blocking outside the
register_semantics: no exception.
Field-level reset values win over register-level reset values. Emit
warnings for unparseable offsets, widths, bit ranges, PDF tables,
ambiguous aliases, and contradictions. The user must be told to read
parse_report.md.
Load references/spec_parsing.md and
references/registers_yaml_schema.md for schemas and parser rules.
At phase end, if <ip>/.gen_tb_hooks/ exists, run
python3 scripts/hooks.py normalize <ip_root>.
Phase 4: Scaffold
Use scripts/scaffold.py with the audit inputs. The generated layout is
fixed; load references/directory_layout.md for the authoritative tree.
Key scaffold rules:
- write
script/design.f, not rtl/design.f
- write lowercase
script/makefile
- write
script/tb.f for tb-side sources
- always write bash-compatible
script/setup.sh; when
emit_setup_csh: true, also write script/setup.csh
- never copy or modify user RTL
- if
<bus>_vip_source: generate_fresh, emit tb/<bus>_agt_top/
- if
reuse_my_vip, emit tb/external_vip.f and skip fresh agent files
- external VIP reuse is supported only for built-in buses; generic
mode always generates fresh (no
generic_vip_source: reuse_my_vip)
- keep
tb_api generated in all modes
- generate
top/<ip>_tb_top.sv, tb/<bus>_if.sv, tests, audit
- RAL is generated when the configuration has register semantics: i.e.
built-in slave direction, or generic slave direction with
register_semantics: yes. Master direction in either mode skips
RAL; generic register_semantics: no skips RAL.
For APB generation, load references/apb.md. For AHB generation, load
references/ahb.md. For AXI4-Lite generation, load
references/axi_lite.md. For external VIP reuse, load
references/apb_external_vip.md, references/ahb_external_vip.md, or
references/axi_lite_external_vip.md.
For top wiring, load
references/top_sv.md. For Makefile, load
references/makefile_contract.md. For tb_api, load
references/tb_api.md. For DPI, load references/refm_dpi.md.
Generic Bus Branch
When bus_protocol == generic:
-
scripts/scaffold.py emits the bus-agnostic skeleton it already
knows: top tree, script/design.f, lowercase script/makefile,
script/tb.f, test directory, audit dirs, empty
tb/<bus>_agt_top/ with placeholder files, and a stub
tb/<bus>_if.sv containing only the ports from
bus_handshake.yaml. RAL is generated iff
register_semantics: yes.
-
Invoke the scaffold sub-agent defined in
references/sub_agent_generic_scaffold.md. Inputs:
references/generic_bus.md (contract)
references/apb.md, ahb.md, axi_lite.md (exemplars)
references/directory_layout.md, top_sv.md, tb_api.md
bus_handshake.yaml, rtl_discovery.yaml, and
registers.yaml (only if register_semantics: yes)
- the placeholder files from step 1
-
The sub-agent fills in <bus>_if.sv, driver, monitor, sequencer,
sequence library, tb_api body, and top wiring. Hard
constraints from §"Hard Constraints" still apply: writes confined
to tb/ top/ test/ script/; no user RTL/VIP/spec/regs edits.
-
Persist:
work/_gen_audit/generic_bus_scaffold_prompt.md — prompt
plus the sub-agent's assumption list,
work/_gen_audit/generic_bus_scaffold_diff.patch — unified
diff of the sub-agent's writes against the placeholder
skeleton.
These are the audit artifacts a future maintainer reads when
deciding whether to promote this bus to a first-class reference.
At phase end, if <ip>/.gen_tb_hooks/ exists, run
python3 scripts/hooks.py scaffold <ip_root>.
Phase 5: Compile-Fix
Run:
make comp
If compile fails, save the log under
work/_gen_audit/compile_fix_attempts/attempt_N.log and use a
constrained sub-agent. The sub-agent may edit only:
tb/
top/
test/
script/
work/_gen_audit/ (audit notes only — never intake.yaml,
rtl_discovery.yaml, bus_handshake.yaml, or
spec_normalized/registers.yaml)
It may not edit user RTL, user VIP source, specs, or register tables.
For reuse_my_vip:
import_only: fix filelist/include/order issues needed to compile
drive_with_vip: additionally create generated glue, interface
bridge, config_db wiring, and one minimal read/write smoke sequence
Stop after 5 compile attempts in built-in mode, 8 in generic mode.
Write unresolved.md with the last error, likely cause, and next
action. Do not fake success.
In generic mode, additionally load references/generic_bus.md and the
three exemplar files (apb.md, ahb.md, axi_lite.md) so the
compile-fix sub-agent can pattern-match against built-in buses. If
attempt N's log is dominated by structural errors in a bus-agent file
(not just typos), the sub-agent may regenerate the whole file
instead of patching. Record this in
compile_fix_attempts/attempt_N.note.md; it still counts as one
attempt. The sub-agent may also revise the interface clocking block
when a sampling-skew error is the diagnosed root cause. tb_api
signatures must not change.
Load references/sub_agent_compile_fix.md for the prompt template and
constraints.
At phase end, if <ip>/.gen_tb_hooks/ exists, run
python3 scripts/hooks.py compile_fix <ip_root>.
Phase 6: Sanity
Run mandatory tests:
make all SV_CASE=<ip>_sanity_test
make all SV_CASE=<ip>_reg_access_test
Sanity must include a positive bus check: read a register with known
non-zero reset when possible; otherwise read register 0 and prove the
DUT responds (pready / hready / rvalid) before timeout. For
AHB-Lite or AXI4-Lite with bus_direction: master, the mandatory test
is instead <ip>_responder_smoke_test: the slave responder must
observe at least one valid write or read handshake from the DUT within
a timeout. RAL and <ip>_reg_access_test are not generated for the
master direction.
In generic mode the mandatory test set depends on
bus_handshake.yaml.register_semantics:
yes → <ip>_sanity_test + <ip>_reg_access_test (RAL-backed
read of a known reset value if any field has non-zero reset; else
read offset 0 and prove handshake completes).
no → <ip>_responder_smoke_test only (observe ≥1 valid
handshake under timeout). RAL is not generated.
For generic mode with direction: master, the
<ip>_responder_smoke_test rule applies regardless of
register_semantics.
If a mandatory runtime test fails, re-enter Phase 5 with a runtime-fix
sub-agent for up to 3 attempts in built-in mode, 5 in generic mode.
Save results to work/_gen_audit/sanity_result.json — run
python3 scripts/compile_and_sanity.py --ip-root <ip> --sanity, which
compiles, runs every test in test/sv_list, and writes that file.
For algorithmic IPs with a reference model, also run one end-to-end
smoke test that proves scoreboard/refmodel integration.
For drive_with_vip, also run the generated external-VIP read/write
smoke test.
At phase end, if <ip>/.gen_tb_hooks/ exists, run
python3 scripts/hooks.py sanity <ip_root>.
Phase 7: Hand-Off
Write:
<ip>/CLAUDE.md
<ip>/work/_gen_audit/unresolved.md
- concise chat summary with exact next command
The summary should state generated IP, tests passed, compile-fix attempt
count, unresolved count, and (in generic mode) bus_protocol: generic
plus which exemplars were used and which mandatory test set
(reg_access vs responder_smoke) was applied. Generated CLAUDE.md
in generic mode must additionally carry the manual review checklist
from references/generic_bus.md.
The summary's exact next command depends on intake.yaml: simulators:
cd <ip>/script && source setup.sh && make all SV_CASE=<ip>_sanity_test
cd <ip>/script && source setup.sh && make -f makefile_questa all SV_CASE=<ip>_sanity_test
cd <ip>/script && source setup.sh && make -f makefile_xrun all SV_CASE=<ip>_sanity_test
If intake.yaml has emit_setup_csh: true, also show the tcsh/csh
variant:
cd <ip>/script
source setup.csh
make all SV_CASE=<ip>_sanity_test
Load references/generated_claude_md.md for CLAUDE.md.
At phase end, if <ip>/.gen_tb_hooks/ exists, run
python3 scripts/hooks.py handoff <ip_root>. Run this last so a
notification/dashboard hook sees the final tree.
Hard Constraints
- Never copy user RTL.
- Never edit user RTL.
- Never edit user VIP source during generation or compile-fix.
- Never write through a symlink that resolves outside the IP root.
- Never silently overwrite without
--force or backup policy from the
implementation.
- Never invent registers; RAL is 1:1 from
registers.yaml.
- Treat TraceWeave MCP,
eda-environment, and skill-creator as
optional, not hard dependencies.
- If using VCS fails due to license/environment, source the user's shell
setup if instructed and rerun outside sandbox when needed.
References
Load only the relevant file for the current phase.
| File | Use |
|---|
references/directory_layout.md | generated tree and ownership |
references/makefile_contract.md | makefile API |
references/rtl_discovery.md | RTL discovery schema |
references/top_sv.md | top/interface wiring |
references/apb.md | generated APB agent |
references/apb_external_vip.md | existing APB VIP reuse |
references/ahb.md | generated AHB-Lite agent |
references/ahb_external_vip.md | existing AHB VIP reuse |
references/axi_lite.md | generated AXI4-Lite agent (slave + master directions) |
references/axi_lite_external_vip.md | existing AXI4-Lite VIP reuse |
references/spec_parsing.md | parser rules |
references/registers_yaml_schema.md | normalized registers |
references/ral_gen.md | RAL generation |
references/refm_dpi.md | C/Python DPI |
references/tb_api.md | DE task BFM |
references/rtl_stub.md | generated RTL stub |
references/sub_agent_compile_fix.md | compile-fix sub-agent |
references/sub_agent_generic_scaffold.md | generic-mode scaffold sub-agent |
references/generic_bus.md | generic-bus scaffold contract and review checklist |
references/generated_claude_md.md | generated CLAUDE.md |
Evaluation
This skill ships with a three-layer eval harness under evals/. See
evals/README.md for the operator-facing walkthrough; the contract
below is what the skill promises.
-
Mechanical assertions (scripts/run_evals.py run) —
compile/sim must exit clean, log substrings must hit, generated
files must exist. Deterministic, no LLM. Always run after a change
to the skill or scaffolder. Per-eval artifacts land in
evals/iteration-N/<eval-name>/ (outputs/, transcript.md,
assertions_result.json).
-
Per-run quality Grader (run_evals.py run --grade) —
claude -p subprocess that reads evals/agents/grader.md and
judges 8 quality dimensions assertions can't catch (scoreboard
value, RAL fidelity, tb_api:: BFM usability, hardcoding risk,
unresolved.md honesty, …). Writes grading.json per eval with
strong/ok/weak/broken verdicts and severity counts. Always run
when iterating on the skill.
-
Optional Comparator + Analyzer (run_evals.py compare A B --analyze) — blind A/B between two iterations or candidate
variants. Most iterations don't need this — mechanical assertions
plus the Grader catch regressions. Use Comparator when you want a
stronger statement that "vN is genuinely better than vN-1" (e.g.
before promoting a major rewrite, or to pick between two candidate
skill variants). Analyzer runs after Comparator picks a non-tie
winner and writes <eval>.analysis.md with root-cause hypotheses
and concrete suggested edits to this SKILL.md or references/.
The three agent contracts live in evals/agents/{grader,comparator, analyzer}.md. They are written so the same contracts can be invoked
by the harness (claude -p) or by a conversational Claude via the
Task tool — pick whichever fits the iteration loop.
Generic-mode sub-agent gate. Evals tagged
requires_generic_sub_agent: true invoke the scaffold sub-agent
(references/sub_agent_generic_scaffold.md) between scaffold and
compile, via a constrained claude -p subprocess. This is off by
default — it costs Claude API tokens per run and is non-deterministic.
Enable with --with-generic-sub-agent (or env var
GENTB_EVAL_GENERIC_SUB_AGENT=1) when iterating on the generic-bus
scaffold contract or before promoting a generic bus to first-class.
If the claude CLI is unavailable, the eval is reported as SKIP
rather than failed.
Done Checklist
Items marked (conditional) depend on the configuration; only the
applicable ones must hold.
<ip>/.prj_top exists
- symlink guard clean
make comp exits 0
- no dual-driver or structural/procedural driver warnings
- the mandatory test set for this configuration all pass:
- built-in slave direction →
<ip>_sanity_test (positive bus
assertion) + <ip>_reg_access_test
- built-in master direction →
<ip>_responder_smoke_test
- generic +
register_semantics: yes (slave) → <ip>_sanity_test
- generic +
register_semantics: no, or generic master →
<ip>_responder_smoke_test
- required extra smoke tests pass
parse_report.md, scaffold_audit.json, sanity_result.json, and
unresolved.md exist
- (conditional, generic mode)
bus_handshake.yaml,
generic_bus_scaffold_prompt.md, and
generic_bus_scaffold_diff.patch exist
- generated
CLAUDE.md exists (generic mode: includes the manual
review checklist from references/generic_bus.md)
- user receives concise summary and exact next command