Install
$ agentstack add skill-gokeshenzhen-gen-tb-skill-gen-tb-skill ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ● Shell / process execution Used
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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 _sanity_test, run _reg_access_test, and provide two usage surfaces:
- DV persona: UVM tests, sequences, agent, monitor, RAL hooks
- DE persona:
tb_api::write/read/expect_regtask-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 (_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 /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
with ` ∈ {discover, intake, normalize, scaffold, compilefix, sanity, handoff}. The script looks for /.gentbhooks/[.strict].{py,sh} and is a no-op when absent (zero cost in the common case). Non-strict hook failures are logged to work/genaudit/hooks/.log and do not block the pipeline; .strict. hooks abort the phase on non-zero exit. Hooks receive a JSON payload on stdin and GENTBPHASE / GENTBIPROOT / GENTBAUDIT_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 / 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 axifullsignature: true in rtldiscovery.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 busprotocol: generic`.
- Otherwise set
bus_protocol: unknownand 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:
_vip_path- desired reuse level:
import_onlyordrive_with_vip
Use:
bus_protocol: apb # or ahb, axi_lite
apb_vip_source: reuse_my_vip # or ahb_vip_source / axi_lite_vip_source
apb_vip_path: # or ahb_vip_path / axi_lite_vip_path
apb_vip_reuse_level: import_only # or ahb_vip_reuse_level / axi_lite_vip_reuse_level; default
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 /.gen_tb_hooks/ exists, run python3 scripts/hooks.py discover .
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, oraclk/aresetnfrequency, 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(canonicalmake all SV_CASE=).questa→script/makefile_questa
(make -f makefile_questa all SV_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=). 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_ all SV_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, oraxi_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.pyexits
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:
direction: slave | master
clock: { name: , freq_mhz: }
reset: { name: , polarity: low|high, cycles: }
addr: { port: , width: } | null # null = non-addressed
data: { write_port: , read_port: , width: }
handshake:
kind: req_ack | valid_ready | strobe | custom
req:
ack:
extra: [ ]
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 _reg_access_test are not generated and Phase 6 degrades to _responder_smoke_test.
All generated tests must be named __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 /.gen_tb_hooks/ exists, run python3 scripts/hooks.py intake .
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 /.gen_tb_hooks/ exists, run python3 scripts/hooks.py normalize .
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, notrtl/design.f - write lowercase
script/makefile - write
script/tb.ffor 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
_vip_source: generate_fresh, emittb/_agt_top/ - if
reuse_my_vip, emittb/external_vip.fand 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_apigenerated in all modes - generate
top/_tb_top.sv,tb/_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.pyemits the bus-agnostic skeleton it already
knows: top tree, script/design.f, lowercase script/makefile, script/tb.f, test directory, audit dirs, empty tb/_agt_top/ with placeholder files, and a stub tb/_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)
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: gokeshenzhen
- Source: gokeshenzhen/gen-tb-skill
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.