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


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

From PyPI:

pip install rvgen

Or from source (for development):

git clone https://github.com/LogicX-Tatsu/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):

python -m rvgen \
    --target rv32imc --test riscv_arithmetic_basic_test \
    --testlist /path/to/riscv-dv/target/rv32imc/testlist.yaml \
    --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.


Quick start — with coverage (2 minutes)

End-to-end: generate → assemble → simulate → collect static + runtime coverage:

export RISCV_GCC=/path/to/riscv64-unknown-elf-gcc
export SPIKE_PATH=/path/to/spike

python -m rvgen \
    --target rv32imc --test riscv_rand_instr_test \
    --testlist /path/to/riscv-dv/target/rv32imc/testlist.yaml \
    --steps gen,gcc_compile,iss_sim,cov --iss spike --iss_trace \
    --output out/ --start_seed 100 -i 1

Open the report:

# text (terminal-friendly)
less out/coverage_report.txt

# or self-contained HTML
python -m rvgen.coverage.tools export out/coverage.json \
    --html out/coverage.html
xdg-open out/coverage.html

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.1.0.tar.gz (186.7 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.1.0-py3-none-any.whl (213.3 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for rvgen-0.1.0.tar.gz
Algorithm Hash digest
SHA256 39ab9103c940f4cce3faa4dfdaab67beaaf4a0390b007a27940ed138fbcad9ce
MD5 243479cdbea490c5dfac5f729ad37b58
BLAKE2b-256 fb2af266afaeedecd9503cbf10dc4729f0e6c05f5d6c17d7231d38da6c7a68ef

See more details on using hashes here.

File details

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

File metadata

  • Download URL: rvgen-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 213.3 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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3e887d74fa938e6834affded686d8bb18586d55fa73e837bdc02481ae2cb4700
MD5 7cfab0a9b1dda4fec727effb43a1d1b9
BLAKE2b-256 67c06f141b6e916aa22f604a3445cd3528b5c4320074944def8ef4fa13ce97f6

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