AgentStack
SKILL verified Apache-2.0 Self-run

Vtestgen

skill-phamcuong21478-rtl-skills-vtestgen · by phamcuong21478

Scaffold a Makefile-driven, CPU-bus-controlled testbench project for an IP and generate a comprehensive set of self-checking testcases, validate each with make, and write a testcase list

No reviews yet
0 installs
3 views
0.0% view→install

Install

$ agentstack add skill-phamcuong21478-rtl-skills-vtestgen

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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 No
  • 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.

Are you the author of Vtestgen? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Verilog Testcase Generator

Overview

This skill builds the IP-level test environment in two phases:

  • Phase A — Scaffold the bench project. Parse _top, then generate a

reusable, Makefile-driven simulation project under tb//: build system, clock/reset generators, a DUT-instance core, CPU-bus BFM tasks, and one CPU-controllable stimulus driver per data interface.

  • Phase B — Plan and generate the suite. Plan testcases across six mandatory

categories, write each as a thin tb_*.sv that drives the bench only through the CPU bus, validate each with make, and write tc_list.md.

vtestrun later executes the suite through the same Makefile.

The input is an IP name. The outputs are:

tb//Makefile              top dispatcher (auto-discovers tb_*.sv)
tb//script/library.mk     per-testcase xvlog/xelab/xsim recipe
tb//script/rtl.f          generated RTL hierarchy filelist
tb//script/skip.list      names of skipped testcases, excluded by the Makefile
tb//clock.vh              parameterized clock + sync-reset generators
tb//dut.vh                reg-map, DUT signals/instance, sti instances, CPU BFM tasks + sequences
tb//sti_.sv        one CPU-controllable stimulus driver per data interface
tb//sco_.sv        one CPU-controllable scoreboard per checkable stream output (optional)
tb//tb_.sv          self-checking testcases (CPU-bus only)
tb//tc_list.md            testcase index with status
tb//sim//             disposable build dirs

Template files for every artifact live in templates/ next to this SKILL.md. Treat them as the canonical shape; fill the {{...}} placeholders from the parsed IP. Testbench code is SystemVerilog (.sv) — permitted under tb/ (see .claude/skills/shared/CodingStyle.md).

Central design rule — the CPU bus is the single control plane. Every testcase drives the whole bench using only cpu_write / cpu_read / cpu_read_check (and the higher-level sequences built on them in dut.vh). The DUT is configured over the CPU bus; traffic generators live in separate sti_*.sv modules that are themselves CPU-bus slaves, so the test script configures and starts stimulus with cpu_write too.

Prerequisites

| Tool | Purpose | Check | |-------|---------------------|-------------------| | xvlog | compile / lint | xvlog --version | | xelab | elaborate | xelab --version | | xsim | simulate | xsim --version | | make | build orchestration | make --version |

If a required tool is missing, report it and stop — do not fake validation.


Phase A — Scaffold the Bench Project

Step A1 - Understand the IP and parse the top

Read before generating anything:

  1. ./doc/.md — full IP behavior, architecture, interface protocols, register map.
  2. ./rtl/_top.v — exact top-level port list, signal names, and widths.

Parse the top module header and classify every port:

| Class | How to detect | Bench treatment | |--------------------------|---------------|-----------------| | clock | name matches clk* / *_clk | generate in clock.vh, period param T_ | | reset | name matches rst* / *_rst* / *_n | generate sync reset in clock.vh | | CPU bus | *cpu*, or *_we/*_oe/*_addr/*_din/*_dout, or an APB/AXI-lite-ish group | wire to cpu_* and the BFM tasks; build the register map | | data-plane in | remaining inputs (valid/data/sof/eof/...) grouped by prefix | driven by an sti_*.sv module | | data-plane out / status | remaining outputs | status outputs: checked via CPU-readable status registers. A stream output (continuous valid/data beats) gets a sco_*.sv scoreboard (Step A6) for per-beat checking |

Also extract parameters (name + default, e.g. DATA_W, ID_BIT, RS_LV).

If the IP has no CPU bus, still generate the CPU bus in the bench as the control plane for the sti_* drivers, and connect the DUT's data ports directly to the sti outputs / to observation logic. Note this to the user.

Present a short parse summary (params, classified ports, detected clocks, detected data interfaces → planned sti_* drivers, detected stream outputs → planned sco_* scoreboards) before generating files. Adjust on feedback.

Step A2 - Create the skeleton, copy the build system, generate rtl.f

Create folders tb// and tb//script/ (and sim/ is created on demand by the Makefile). Never delete existing files; if Makefile / script/library.mk already exist, diff against the template and only offer to update, do not overwrite silently.

Copy verbatim:

  • templates/Makefiletb//Makefile
  • templates/library.mktb//script/library.mk

Generate tb//script/rtl.f from templates/rtl.f: walk the instantiation hierarchy of _top following .claude/skills/shared/HierarchyFilelist.md, and list every required file as RTL_FILES += $(RTL_FOLDER)/... or $(LIB_FOLDER)/..., top module first. Both Makefiles include this fragment, so it is the single source of the compiled filelist (no wildcard globbing).

Step A3 - Generate tb//clock.vh

Expand templates/clock.vh: one always toggle per detected clock (period T_), one synchronous reset per clock domain using RS_LV (assert = RS_LV, deassert after 10*T_). One clock is the "system" clock ({{SYS_CLK}}, used by watchdog/run-length); the CPU bus uses {{CPU_CLK}} (may be the same clock).

Step A4 - Generate tb//dut.vh

Expand templates/dut.vh:

  • {{REGISTER_MAP}}localparam REG_ = 20'h..; for every CPU register.

Take real addresses from doc/.md or the RTL decode; if none exists, emit a clearly-marked TODO block with example registers.

  • {{DUT_SIGNALS}} — declare every DUT port with exact widths/names: bench-driven

inputs and the CPU bus as reg/handled by tasks, DUT outputs as wire, data-plane inputs as wire (driven by sti modules).

  • {{DUT_PARAM_MAP}} / {{DUT_PORT_MAP}}.NAME(value) lines; connect the CPU

port group to cpu_*, data-plane ports to the matching sti_* outputs, clocks/ resets to the generated names. The DUT read-data output connects to a dut_cpu_dout wire so it can be muxed.

  • {{STI_INSTANCES}} — one instance per sti_*.sv (Step A5), each given a unique

STI_ADDR on a 0x100-word window (STI_BASE, STI_BASE+'h100, ...), sharing the CPU bus. Bind RS_LV (and DATA_W) to the DUT's values so the driver resets at the DUT's polarity — a driver left at the default RS_LV=0 while the DUT is RS_LV=1 is held in reset for the whole run.

  • {{SCO_INSTANCES}} — one instance per sco_*.sv (Step A6), each given a unique

SCO_ADDR on a 0x100-word window (SCO_BASE, SCO_BASE+'h100, ...), sharing the CPU bus and tapping a DUT output. Bind RS_LV (and DATA_W) to the DUT's values, same as the sti drivers. Leave empty (and drop SCO_BASE) if no scoreboard is generated.

  • Each slave owns a 256-word window, so its registers — including any

width-dependent payload/capture that spans NW = DATA_W/32 words — never reach the next slave's base (good for DATA_W up to 8192). Keep every slave base 0x100-aligned and any word-addressed payload within its window.

  • {{STI_DOUT_OR}} / {{SCO_DOUT_OR}} — OR of each sti / sco instance's *_dout

for the read mux. Substitute 32'h0 for either term when that group is empty.

  • {{CPU_CLK}} / {{CPU_CLK_UPPER}} — the CPU-domain clock name used by the BFM tasks.

The three BFM tasks (cpu_write, cpu_read, cpu_read_check) are already in the template; keep them, only fix the clock name. cpu_read_check prints [FINISH] FAIL + $finish on mismatch — this is the failure path of the contract.

Each access is held for a parameterized number of CPU clocks — CPU_WR_LAT for writes, CPU_RD_LAT for reads (declared by the testcase, default 1). Set these higher when the DUT's CPU slave needs more than one cycle per access; read data is sampled at the end of the CPU_RD_LAT window while cpu_oe is still asserted. For a slave with a variable-latency ready/ack handshake, poll that signal in the task instead of using a fixed latency.

Put reusable high-level sequences in dut.vh, not in the testcases. Any multi-step CPU sequence more than one testcase will repeat — load a key/config block, trigger+poll a "done" bit, load a payload word-by-word, fire a beat and wait for capture, read a payload back — belongs here as a task built on the BFM tasks (e.g. load_key, cfg_run, sti_load, sti_read, sti_beat). Make them width-generic by looping 0..NW-1 over the word-addressed sti_* registers. This keeps every tb_*.sv to pure intent and lets a testcase at a different DW reuse the exact same sequences. The template already ships a commented arm_stream / check_stream pair (the driver↔scoreboard runtime-sync handshake from Step A5): uncomment and keep it when the bench has an sti_*/sco_* pair, drop the sco half otherwise.

Step A5 - Generate tb//sti_.sv per data interface

For each data-plane input group from Step A1, expand templates/sti_template.sv:

  • Name it sti_.sv, module sti_.
  • Keep the CPU-slave wrapper (write-decode of ENA/RATE/MODE/SEED/CLR, read-decode

into cpu_dout, address self-decode from STI_ADDR) unchanged — this is what makes the driver controllable from the test script via cpu_write only.

  • The GENERATOR BODY ships MODE-selectable (chosen at run time via the

MODE register, no recompile): MODE_LFSR (pseudo-random, replicated to fill DATA_W), MODE_COUNT (byte-stream counter-up), and MODE_FILE (replay DATA_W-wide words from PATTERN_FILE via $readmemh). Keep the modes that fit this interface, add ones it needs (packet burst, AXI master, sof/eof framing, ...), and match the DUT's handshake (valid/ready) and data width. A per-project "specific pattern" belongs in a hex file driven by MODE_FILE, not hardcoded in the body.

  • Runtime sync with the mirroring sco_*. The driver and scoreboard are two

independent generators; the CLR register arms each one back to the start of its sequence (reseed from SEED, counters to 0) without a full DUT reset. The testcase syncs them by: write the same MODE/SEED to both → pulse CLR on both → enable the sco → enable the driver. This holds across DUT latency and back-pressure because the sco advances per accepted output beat, but only when the DUT carries the stream 1:1 in order (FIFO/CDC/passthrough). For a DUT that drops/reorders/transforms beats, the mirror is invalid — use a reference-model sco_* (Step A6). Put the arm-and-enable handshake in a dut.vh sequence so every testcase reuses it.

  • Wire the handshake ready on both sides. The driver consumes the DUT

input ready via its out_ready port — it holds each beat until accepted and advances only then, so it stays in lock-step with the sco under back-pressure; tie out_ready to 1'b1 in dut.vh if the input has no ready. The DUT output ready is only tapped by the sco — something else must drive it: tie it high for a plain run, or add a small ready/throttle driver (an sti_*-style CPU slave) for back-pressure testcases.

  • Expose every output the DUT data port needs; expose status counters and any

captured output as readable registers so testcases can cpu_read_check them.

  • Make the driver width-generic — never hardcode the data width. Parameterise

by the DUT's data width (DW, plus UW/EW sidebands), derive everything from it (localparam NW = (DW+31)/32;, NB = DW/8;). Every payload/capture register and out_data must be [DW-1:0]; the shipped modes already size off DW. A driver hardcoded to one width silently works at the default DW then fails the first multi-block (DW=N*128) testcase.

  • If a testcase must push an exact payload over the CPU bus (rather than via

MODE_FILE), expose it as word-addressed registers — the CPU is 32-bit — at a base offset (PT word i at STI_PT+i, i = 0..NW-1) decoded with a range check + variable indexed part-select (pt[(cpu_addr-STI_PT)*32 +: 32]), exactly the same way sco_template.sv word-addresses its capture payload. Such a payload occupies NW CPU words, so the slave's address stride must clear it (see Step A4).

Additional sti_*.sv files can be added later; they are picked up automatically by the sti_*.sv glob in the Makefiles.

Step A6 - Generate tb//sco_.sv per checkable stream output (optional)

Generate a scoreboard only for data-plane outputs that produce a continuous stream (valid/data beats), where per-beat checking adds value over reading a final status register. Skip it for IPs whose only outputs are status registers — those are checked directly with cpu_read_check. If no scoreboard is generated, also drop the SCO_INSTANCES/SCO_DOUT_OR/SCO_BASE hooks from dut.vh.

For each such output, expand templates/sco_template.sv:

  • Name it sco_.sv, module sco_.
  • Keep the CPU-slave wrapper unchanged (write-decode of ENA/MODE/SEED/CLR,

read-decode of ERRCNT/CHKCNT + word-addressed first-mismatch capture, address self-decode from SCO_ADDR). This is what lets the test script configure and read the scoreboard via the CPU bus only.

  • The scoreboard taps the DUT output (in_valid/in_ready/in_data) — it must

never drive the DUT. Wire the tap in dut.vh's {{SCO_INSTANCES}}.

  • Replace only the COMPARE BODY to match the interface handshake. Default: the

scoreboard regenerates the expected stream itself from MODE/SEED/ PATTERN_FILE — the same MODE_LFSR/MODE_COUNT/MODE_FILE sources as sti_*.sv — so no expected payload is pushed over the CPU bus. The testcase just sets the scoreboard's MODE/SEED to match the driver feeding the DUT. Alternatives: build a reference model that recomputes expected from observed DUT inputs (use only when the transform is non-trivial); or, for irregular expected data, add a word-addressed EXP register written by the testcase.

  • On a mismatch the scoreboard prints [ERROR] ... then [FINISH] FAIL + $finish

(self-fail, consistent with cpu_read_check). ERRCNT is still exposed so a testcase may instead defer the verdict with cpu_read_check(SCO_ERRCNT, 32'd0).

  • Width-generic like the sti drivers — parameterise by DATA_W, derive NW/NB

from it, word-address the capture payload, never hardcode the width.

Additional sco_*.sv files are picked up automatically by the sco_*.sv glob in the Makefiles.


Phase B — Plan and Generate the Suite

Step B1 - Plan the test suite

List all planned testcases across the six mandatory categories:

| Category | Coverage target | |----------------|-----------------| | Reset | DUT reaches defined idle state after reset; all outputs at documented reset values | | Basic | One test per documented feature or operating mode | | Edge | Boundary data values (0, all-ones, max-minus-1), empty/full conditions, single-cycle bursts | | Back-pressure | Valid/ready handshakes held off; pipeline stalls and drains correctly under sustained back-pressure | | Error inject | Out-of-spec stimulus, concurrent events, overflow/underflow conditions | | Stress | Back-to-back transactions with no idle cycles; maximum throughput for at least 1000 clock cycles |

Present the full plan as a table before writing any code. Adjust on feedback.

Step B2 - Write each testcase

For each planned testcase, create tb//tb__.sv by expanding templates/tb_basic.sv. File name lowercase, underscores, no spaces, tb_ prefix (so the Makefile auto-discovers it).

Each testcase must:

  • declare the clock-period params (T_*), RS_LV (set to the DUT's own RS_LV

default parsed from _top,

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.