Skip to main content

Pure-Python RISC-V random instruction generator with built-in functional coverage, auto-regression, and CI-ready dashboards.

Project description

rvgen

Pure-Python RISC-V instruction generator with built-in functional coverage, auto-regression, and CI-ready dashboards.

Python 3.11+ Apache-2.0 332 tests Spike regression Verification guide

Verification GuideCoverage ReferenceArchitectureTestlist ReferenceSample HTML Report

Install: pipx install rvgen  or  pip install rvgen in a venv — see Install for details.


rvgen replaces the CHIPS Alliance's riscv-dv at the generator layer with a small, single-dependency (PyYAML) Python package. Every riscv-dv testlist YAML runs unchanged. On top of that parity, the project adds a first-class functional-coverage subsystem — covergroups, CGF-style goal files, coverage-directed auto-regression, per-test attribution, HTML dashboards, CI integration — that in the SV world you'd normally assemble from a UVM flow, riscv-isac, and a simulator licence.

Who this is for: verification engineers bringing up RISC-V cores who want the power of random instruction generation without SystemVerilog, UVM, or a simulator licence between them and their .S files.


Table of contents


See it in action

Generate, simulate, and collect coverage in one command:

python -m rvgen \
    --target rv32imc --test riscv_rand_instr_test \
    --steps gen,gcc_compile,iss_sim,cov --iss spike --iss_trace \
    --output out/ --start_seed 100 -i 1

Output (truncated):

Generated out/asm_test/riscv_rand_instr_test_0.S (seed=100, 3020 lines)
Compiling out/asm_test/riscv_rand_instr_test_0.S
Running spike: out/asm_test/riscv_rand_instr_test_0.o
2 tests passed ISS sim
Coverage DB updated: out/coverage.json
Coverage goals layered from: .../baseline.yaml
Coverage report: out/coverage_report.txt

out/coverage_report.txt:

covergroups: 35    unique bins hit: 2970    total samples: 299604    grade: 87/100

[opcode_cg]  unique_bins=82  total_hits=10284  43/45 goals met
    ADD                             248 / 5     ✓
    SUB                             120 / 5     ✓
    ...
  MISSING (2):
    ! FENCE                            0 / 2
    ! JALR                             0 / 5

The HTML dashboard (python -m rvgen.coverage.tools export out/coverage.json --html cov.html):

See a real rendered example at docs/examples/coverage-report.html (self-contained, no JS, ~5k lines).


Highlights

  • 486 instructions across RV32I/M/A/C/F/FC/D/DC, RV64 counterparts, Zba/Zbb/Zbc/Zbs, draft RV32B, ratified crypto (Zbkb/Zbkc/Zbkx/Zknd/Zkne/Zknh/Zksh/Zksed), and RVV 1.0 (184 vector opcodes).
  • 27 targets — rv32i through rv64gcv, plus bare rv32ui, 4 crypto variants, and 5 Zve* embedded-vector profiles including Google's Coral NPU (rv32imf_zve32x_zbb).
  • 16 directed-stream classes — corner-value init, JAL chain, JALR pairs, loops, LR/SC, AMO, plus an SV-faithful scalar load/store family with locality / hazard / multi-page variants.
  • 32 functional-coverage groups — opcode, format, category, group, operand registers, immediates, hazards (RAW/WAR/WAW), CSR access, FP rounding, vtype, memory alignment, category and opcode transitions, register crosses, plus runtime bins (branch direction, privilege mode, CSR values, bit-activity).
  • CGF-style YAML goals with layered overlays (baseline + per-target + per-test). 12 goal files shipped.
  • Coverage-directed auto-regression--cov_directed perturbs gen_opts per seed based on the currently-missing bin set. Baseline rv32imc goals close in 1 seed vs 8+ for blind seed-sweep.
  • CI-ready — GitHub Actions integration (GITHUB_OUTPUT + step summary), composite 0-100 coverage grade, standardized exit codes, golden-baseline regression gate, goals linter.
  • Coverage analysis CLImerge, diff, attribute, per-test, export (CSV + HTML), report, suggest-seeds, baseline-check, lint-goals.
  • Parallel regression runner (scripts/regression.py) — target × test × seed matrix execution with merged coverage + HTML dashboard.
  • One hard dependency: PyYAML. No constraint solver. No UVM. No simulator licence.
  • Pure Python generation is fast — 10k-instruction test in seconds; a typical matrix regression runs at ~8 seeds/sec on an 8-core laptop.

Why rvgen?

rvgen riscv-dv (SV/UVM) force-riscv riscv-isac
Language Python 3.11+ SystemVerilog + UVM + Python glue C++ + Python Python
Simulator licence — (open-source spike) VCS / Questa typically required for SV
Install complexity pip install -e . Full EDA install + UVM libs Build from source, C++ pip install riscv_isac
Time to 10k-instr test seconds minutes (pygen: ~12 min) seconds n/a (post-hoc tool)
Instruction generation
RVV 1.0 ✓ (recent)
Zve* embedded profiles ✓ (5 targets)
Functional coverage built-in (32 groups, CGF goals) separate SV covergroup file + sim coverage engine (C++) primary (CGF-native)
Coverage-directed regression ✓ (--cov_directed) — (blind only)
Goals YAML layering + linter partial
HTML coverage dashboard ✓ (built-in) UCIS + vendor tool partial
CI integration (GITHUB_OUTPUT + grade)
New-test creation edit YAML edit YAML + possibly SV class edit XML n/a

The summary: if you already have a commercial SV flow, riscv-dv is still the richest framework. If you don't — or if you're running a CI workflow where "pip install + run" matters — rvgen gives you random instruction generation, the same testlist format, and a complete coverage workflow in one open-source package.


Install

rvgen is a command-line tool, so the recommended install is pipx — it puts rvgen on your PATH while keeping its dependencies in an isolated venv:

# One-time pipx setup (Debian/Ubuntu):
sudo apt install pipx && pipx ensurepath
# (open a new terminal so the PATH update takes effect)

pipx install rvgen
rvgen --help

Plain pip works too — on conda, macOS, Windows, or any system without PEP 668 restrictions:

pip install rvgen

On modern Debian/Ubuntu the system pip is locked down by PEP 668. If you hit error: externally-managed-environment, either use pipx (above) or install into a venv:

python3 -m venv ~/rvgen-env
~/rvgen-env/bin/pip install rvgen
~/rvgen-env/bin/rvgen --help

For development (clone + editable install with test extras):

git clone https://github.com/TatsuProject/rvgen.git
cd rvgen
pip install -e ".[test]"

Runtime dependencies (external tools):

Tool Used for Env var
riscv64-unknown-elf-gcc Assemble .S → ELF $RISCV_GCC
riscv64-unknown-elf-objcopy ELF → raw binary resolved next to GCC
spike (ISA simulator) Execute ELF + emit trace $SPIKE_PATH
spike-vector For RVV / Zve* targets $SPIKE_PATH

Toolchain setup guides: SiFive freedom-tools / riscv-gnu-toolchain / spike.


Quick start — 30 seconds

Just generate an assembly file (skip GCC and spike). No external riscv-dv checkout needed — rvgen ships a baseline testlist inside the wheel.

pip install rvgen   # or: pipx install rvgen

rvgen --target rv32imc --test riscv_arithmetic_basic_test \
      --steps gen --output out/ --start_seed 100 -i 1

Output: out/asm_test/riscv_arithmetic_basic_test_0.S. Inspect it, assemble it with your own toolchain, run it where you want.

Want custom plusargs / per-target tests? Drop a YAML at <user_dir>/testlists/<target>.yaml (set $RVGEN_USER_DIR) or pass --testlist <your.yaml>. Otherwise the packaged baseline is used automatically.


Quick start — with coverage (2 minutes)

End-to-end: generate → assemble → simulate → collect static + runtime coverage. The two env vars below point at your toolchain — substitute real paths (e.g. /opt/riscv/bin/riscv64-unknown-elf-gcc, /usr/local/bin/spike). Skip them if the binaries are already on $PATH.

# Optional: only needed if the binaries aren't already on $PATH.
export RISCV_GCC=$(which riscv64-unknown-elf-gcc)
export SPIKE_PATH=$(which spike)

rvgen --target rv32imc --test riscv_rand_instr_test \
      --steps gen,gcc_compile,iss_sim,cov --iss spike --iss_trace \
      --output out/ --start_seed 100 -i 1

Open the report. The cov step writes three artifacts:

File Description
out/coverage_dashboard.html SOTA Codecov-style sunburst dashboard — per-subsystem scorecard, drill-into-covergroup, missing-bins ranking, light/dark themes. The headline view.
out/coverage_report.txt Terminal-friendly text report.
out/coverage.json Raw coverage DB — feeds merge / diff / dashboard / scorecard tools.
xdg-open out/coverage_dashboard.html   # the main view
less out/coverage_report.txt           # text fallback

See docs/verification-guide.md for the complete tutorial.


The pipeline

pipeline

Every stage is optional via --steps. Coverage accumulates across runs when you re-use the same coverage.json path (--cov_db).


Functional coverage at a glance

coverage-model

32 covergroups sampled from two sources:

  • Static (at generation): opcode / format / category / group / rs1 / rs2 / rd / imm_sign / imm_range / hazard / csr / csr_access / fp_rm / vreg / vtype / mem_align / load_store_width / load_store_offset / category_transition / opcode_transition / rs1==rs2 / rs1==rd / directed_stream + 2 crosses.
  • Runtime (from spike -l --log-commits): branch_direction + branch×mnemonic / exception / privilege_mode / pc_reach / csr_value / rs_val_corner / bit_activity + opcode_cg.*__dyn.

Goals are CGF-style YAML. Layered overlays, auto-resolved from goals/<target>.yaml, linted for typos.

Complete reference: docs/coverage.md.


Auto-regression

--auto_regress loops seeds until goals are met or a plateau is detected:

auto-regress

python -m rvgen \
    --target rv32imc --test riscv_rand_instr_test \
    --auto_regress --cov_directed --max_seeds 16 \
    --output out/regress/

Coverage-directed (--cov_directed) mode inspects the currently-missing bin set and perturbs gen_opts per seed — dropping +no_fence=1 if FENCE is missing, injecting riscv_load_store_rand_instr_stream if LB/LH aren't hit, etc. Result: baseline rv32imc goals close in 1 seed vs 8+ for blind sweep.

Bookkeeping: convergence.json (per-bin first-hit seed), cov_timeline.json (time-series), ASCII sparkline in the log, rotating per-seed .S snapshots in asm_test/seed_archive/.


Supported ISA and targets

Instructions (486 total):

Group Count Notes
RV32I / RV64I 62 base + W-width
RV32M / RV64M 13 mul / div
RV32A / RV64A 22 LR/SC + AMO
RV32F / RV64F / RV32D / RV64D 60 + FCVT + FMV
RV32FC / RV32DC 8 compressed FP load/store
RV32C / RV64C 35 base compressed
Zba / Zbb / Zbc / Zbs 30 ratified bit-manip
Zbkb / Zbkc / Zbkx 6 crypto bit-manip
Zknd / Zkne / Zknh 19 AES + SHA
Zksh / Zksed 4 SM3 / SM4
RV32B (draft) 40 for historical compatibility
RVV 1.0 184 integer + FP + widening/narrowing + mask + reductions + loads/stores + AMO

Targets (27):

rv32i        rv32im        rv32ic        rv32ia       rv32iac      rv32imac
rv32imc      rv32if        rv32imafdc    rv32imcb     rv32imc_sv32 rv32ui
rv32imc_zkn  rv32imc_zkn_zks  rv32imc_zve32x  rv32imfc_zve32f
rv64imc      rv64imcb      rv64imc_zkn   rv64imafdc   rv64gc       rv64gcv
rv64imc_zve64x  rv64imafdc_zve64d
coralnpu     ml            multi_harts

Targets are declarative — adding a new one for a custom SoC is a single YAML file in the user area (no framework edit required). Built-in targets also live on TargetCfg, now split across rvgen/targets/core_setting.py (dataclass), presets.py (CSR/interrupt/exception tuples), and builtin.py (the 27 definitions). Each target carries its own CLINT memory map so generated timer-IRQ code targets the right addresses on any SoC.


Writing your own test

Entirely YAML. No Python needed.

# my_tests.yaml
- test: my_hazard_heavy_test
  description: "Force hazards via tight reg pool + directed hazard streams."
  iterations: 4
  gen_test: riscv_instr_base_test
  gen_opts: >
    +instr_cnt=8000
    +num_of_sub_program=3
    +directed_instr_0=riscv_hazard_instr_stream,6
    +directed_instr_1=riscv_load_store_hazard_instr_stream,6
    +no_csr_instr=0
  rtl_test: core_base_test

Run:

python -m rvgen --target rv32imc --test my_hazard_heavy_test \
    --testlist my_tests.yaml --steps gen,gcc_compile,iss_sim,cov --iss spike \
    --output out/ -i 4 --start_seed 100

Full plusarg reference and the directed-stream catalogue: docs/testlist.md.


Writing your own directed stream

If gen_opts isn't enough, a new stream is ~20 lines of Python. See docs/examples/custom-stream.py for a complete annotated template.

@dataclass
class MyBurstStream(DirectedInstrStream):
    def build(self) -> None:
        for _ in range(10):
            instr = get_instr(RiscvInstrName.ADD)
            instr.rs1 = instr.rd = RiscvReg.T0  # in-place accumulator
            instr.rs2 = self.rng.choice([r for r in RiscvReg
                                         if r not in self.cfg.reserved_regs])
            instr.post_randomize()
            self.instr_list.append(instr)

register_stream("my_burst_stream", MyBurstStream)

Reference it from any testlist:

gen_opts: >
  +directed_instr_0=my_burst_stream,5

Validation

All green at the tip of main:

  • 332 unit tests (python -m pytest tests/ -q).
  • 51/51 scalar end-to-end on spike (17 tests × 3 seeds across rv32imc / rv32imafdc / rv32imcb / rv64imc / rv64imcb).
  • 18/18 vector end-to-end on spike-vector (6 tests × 3 seeds on rv64gcv).
  • 5/5 Zve*-profile end-to-end (coralnpu / rv32imc_zve32x / rv32imfc_zve32f / rv64imc_zve64x / rv64imafdc_zve64d).
  • 21/21 instruction-by-instruction trace matches against the chipforge-mcu RV32IMC+Zkn RTL (7 tests × 3 seeds, via scripts/mcu_validate.sh).
  • 1 integration-regression test pinning the fixed-seed rv32imc run against a known coverage floor.

Reproduce the scalar sweep:

for t in rv32imc:riscv_arithmetic_basic_test rv32imc:riscv_rand_instr_test \
         rv32imc:riscv_jump_stress_test rv32imc:riscv_loop_test \
         rv32imc:riscv_amo_test rv32imc:riscv_rand_jump_test \
         rv32imc:riscv_no_fence_test rv32imc:riscv_mmu_stress_test \
         rv32imc:riscv_unaligned_load_store_test \
         rv32imafdc:riscv_floating_point_arithmetic_test \
         rv32imcb:riscv_b_ext_test rv32imcb:riscv_zbb_zbt_test \
         rv64imc:riscv_arithmetic_basic_test rv64imc:riscv_rand_instr_test \
         rv64imc:riscv_loop_test rv64imc:riscv_jump_stress_test \
         rv64imcb:riscv_b_ext_test; do
  target=${t%%:*}; test=${t##*:}
  for s in 100 200 300; do
    python -m rvgen --target $target --test $test \
        --steps gen,gcc_compile,iss_sim --iss spike \
        --output /tmp/reg_${target}_${test}_${s} --start_seed $s -i 1 2>&1 \
      | grep -qE "tests passed ISS sim" \
      && echo "PASS $target/$test/$s" || echo "FAIL $target/$test/$s"
  done
done

Expected: 51 PASS.


Project layout

rvgen/            # main package
├── cli.py                       # entry point: python -m rvgen
├── auto_regress.py              # --auto_regress loop + convergence tracking
├── config.py                    # Config dataclass, plusarg parsing
├── targets/__init__.py          # 27 TargetCfg entries
├── testlist.py                  # YAML loader (riscv-dv schema compatible)
├── seeding.py                   # SeedGen: fixed/start/rerun/random
├── isa/                         # per-extension instruction modules
│   ├── base.py                   # Instr base class
│   ├── rv32i.py, rv32m.py ...    # scalar registrations
│   ├── bitmanip.py, crypto.py    # Zb* / Zk* registrations
│   ├── rv32v.py                  # RVV 1.0 registrations
│   ├── floating_point.py         # FP base class
│   ├── vector.py                 # VectorInstr base class + factory
│   ├── factory.py                # INSTR_REGISTRY + define_instr()
│   └── filtering.py              # create_instr_list + get_rand_instr
├── stream.py, sequence.py        # instr-stream + sequence machinery
├── asm_program_gen.py            # top-level .S composer
├── streams/                      # directed streams
│   ├── base.py, directed.py, loop.py, amo_streams.py, load_store.py
├── privileged/                   # boot CSR + trap handlers
├── sections/                     # data pages, signature, stack
├── gcc.py, iss.py                # external-tool wrappers (GCC + spike)
├── coverage/                     # functional-coverage subsystem
│   ├── collectors.py              # 32 covergroups + sample_*
│   ├── runtime.py                 # spike-trace parser
│   ├── cgf.py                     # goals YAML loader
│   ├── directed.py                # auto-regress perturbation table
│   ├── report.py                  # text report + composite grade
│   ├── tools.py                   # merge/diff/attribute/export CLI
│   └── goals/*.yaml               # 12 shipped goal overlays
└── vector_config.py              # VectorConfig + Vtype + legal_eew

docs/                            # deep documentation
├── verification-guide.md         # 9-section tutorial
├── coverage.md                   # complete coverage reference
├── architecture.md               # module / data flow
├── testlist.md                   # gen_opts + stream reference
├── releasing.md                  # PyPI release checklist
├── images/                       # SVG diagrams
└── examples/                     # coverage HTML, annotated goals, custom stream

scripts/
├── regression.py                 # parallel matrix runner
└── mcu_validate.sh               # chipforge-mcu trace-compare driver

tests/unit/                      # 332 unit tests

Community


License

Apache 2.0 — same permissive licence as riscv-dv. Free to use commercially, modify, redistribute.


Citation

If you use rvgen in academic work, see CITATION.cff for the canonical citation.


Acknowledgements

  • Structurally inspired by riscv-dv — the SystemVerilog reference we ported.
  • CGF goals format from riscv-isac.
  • Spike, the RISC-V ISA simulator we validate against.

This project is not affiliated with the RISC-V Foundation, Google, or the chipsalliance organisation.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rvgen-0.2.0.tar.gz (309.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rvgen-0.2.0-py3-none-any.whl (345.5 kB view details)

Uploaded Python 3

File details

Details for the file rvgen-0.2.0.tar.gz.

File metadata

  • Download URL: rvgen-0.2.0.tar.gz
  • Upload date:
  • Size: 309.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for rvgen-0.2.0.tar.gz
Algorithm Hash digest
SHA256 e9738b2ffda9b57b0c61e586fc928dd0b52e1dee91d2ecf88e56a7e7489910cd
MD5 30f3e7ab8409a96c465efa6c0a9a660c
BLAKE2b-256 fb3360ea01e994fa798088a92c1d2a4f6da5c6fa194efa7d64b99d73a9881404

See more details on using hashes here.

File details

Details for the file rvgen-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: rvgen-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 345.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.4

File hashes

Hashes for rvgen-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e1d6ce465f30e79f0ff2e662db17b74d22506609421cc09b2c12655e89a2433b
MD5 30f0422f385f8bbbab373b1494857399
BLAKE2b-256 30325e5fbfc6b4fc56f9d1dca5b2ff58143742f7e2b117b40ba9ad0bb239632f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page