Skip to content

Generate decoder from ISA descriptor#725

Open
yc199911 wants to merge 10 commits into
sysprog21:masterfrom
yc199911:issue-103-decoder-gen
Open

Generate decoder from ISA descriptor#725
yc199911 wants to merge 10 commits into
sysprog21:masterfrom
yc199911:issue-103-decoder-gen

Conversation

@yc199911

@yc199911 yc199911 commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Replace the hand-written instruction decoder in src/decode.c with one
generated from src/instructions.in via scripts/gen-decoder.py,
closing #103.

Maintenance Cost Reduction (Quantified)

Code that requires human maintenance

Source master this PR delta
src/decode.c (hand-written / generated) 2052 1838 (generated, not maintained by hand) n/a
src/instructions.in (declarative ISA descriptor) 239 +239
scripts/gen-decoder.py (one-time tooling) 1019 +1019
Net hand-maintained decoder logic 2052 239 −88%

The 1019 lines in gen-decoder.py are a one-time investment. Adding a
new instruction or extension touches only instructions.in, never
gen-decoder.py. The fair comparison for ongoing maintenance is
2052 → 239 lines.

Cost of adding a new extension (decoder side only)

Measured against two recent extension-addition commits on master:

Commit Extension decode.c decode.h
29dabe4 Zbs +45 +11
c10b895 Zbb +95 +21

With the generator, adding the same extensions touches only
instructions.in:

  • Zbs: 9 lines (1 @extension tag + 8 instructions)
  • Zbb: 20 lines (1 @extension tag + 19 instructions)

That is a ~5–6× reduction in decoder-side diff size, and the diff
is now declarative bit-pattern data lifted directly from the
riscv-opcodes repo, instead
of hand-written if/switch chains that must be reasoned about
case-by-case.

Bug class eliminated by construction

git log on decode.c shows 36 commits, of which a recurring class
is decode-table mistakes:

  • 82205cb Fix FCVT illegal instruction handling
  • a00aeea Fix rounding mode selection
  • 52fe009 Fix illegal instruction handling for SLLI, SRLI, SRAI
  • ebf2362 Fix decode annotation error

These are mismatches between the spec and hand-written bit-pattern
matching. Generating decode.c from a single declarative source
removes this entire class — there is no second hand-maintained copy
of the bit patterns to drift out of sync with the spec.

Workflow change for future maintainers

Before (hand-written decoder): adding one instruction requires
edits in 5+ places — decode.h enum, decode.c switch case (often
nested inside the right op_xxx handler), the right #if RV32_HAS()
guard, plus execution semantics. Easy to miss the guard or get the
funct3/funct7 nesting wrong.

After (this PR): adding one instruction is one line in
instructions.in (copy from riscv-opcodes), plus execution semantics
in emulate.c. make regenerates decode.c automatically. Adding a
whole new extension is the same plus one @extension tag.

docs/decoder-generator.md documents the descriptor format and the
extension-addition workflow.

Verification

  • All 12 ENABLE_*=0 extension-disable builds pass under both
    defconfig and jit_defconfig (matches the CI matrix in
    .github/workflows/main.yml).
  • make check (puzzle, fcalc, pi) passes.
  • make tests (cache, map, path) passes.
  • RISCV_DEVICE=IMACFZicsrZifencei: all architecture tests pass.

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarks

Details
Benchmark suite Current: 8b775f4 Previous: f281b95 Ratio
Dhrystone 1764.333 DMIPS 1572.333 DMIPS 0.89
CoreMark 1158.742 iterations/sec 1109.822 iterations/sec 0.96

This comment was automatically generated by workflow using github-action-benchmark.

@yc199911 yc199911 requested a review from jserv April 20, 2026 15:28
@yc199911 yc199911 force-pushed the issue-103-decoder-gen branch from 8ef6e5d to 17ba12d Compare April 28, 2026 06:51
Comment thread docs/decoder-generator.md Outdated
Comment thread docs/decoder-generator.md
Comment on lines +20 to +32
The generated decoder has two responsibilities for each instruction:

1. **Identify** the instruction from its bit pattern (decision tree).
2. **Extract** the operands (registers and immediates) into `rv_insn_t`.

For 32-bit instructions, identification uses nested switch statements on
the opcode, funct3, and funct7 fields. Operand extraction is handled by
type decoders (`decode_itype`, `decode_rtype`, etc.) inferred from the
operand names.

For 16-bit compressed (RVC) instructions, identification follows the same
switch-based decision tree. Operand extraction is per-instruction because
each RVC format has a unique bit layout for immediates.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have to classify RISC-V extensions in advance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restructured the extension table by RISC-V classification: base ISA (I), standard unprivileged extensions (M/A/F/C), and Z-extensions (Zicsr, Zifencei, Zba/Zbb/Zbc/Zbs).

@yc199911 yc199911 force-pushed the issue-103-decoder-gen branch from 42ae982 to 530a06e Compare May 16, 2026 05:53
@jserv jserv added this to the release-2026.2 milestone May 17, 2026
@yc199911

Copy link
Copy Markdown
Contributor Author

Hi @jserv, I noticed a conflict in src/decode.c after #733 (RVV) was merged. Since this PR auto-generates decode.c from instructions.in, should I:
(a) Add RVV instructions to instructions.in in this PR, or
(b) Keep this PR scoped to the original 161 instructions and add RVV in a follow-up PR?
Would appreciate your guidance.

@jserv

jserv commented May 17, 2026

Copy link
Copy Markdown
Contributor

should I: (a) Add RVV instructions to instructions.in in this PR

The RVV decoder should be generated from extension definitions, which is precisely what this pull request aims to achieve.

Replace the 2052-line hand-written src/decode.c with a generator-driven
flow. The decoder is now produced by scripts/gen-decoder.py from a
declarative ISA descriptor src/instructions.in, and decode.c becomes
a generated artifact that is no longer maintained by hand.

The motivation is to reduce ongoing maintenance cost. On master, the
hand-written decoder receives commits both for adding extensions and
for fixing bit-pattern mistakes:

- 82205cb Fix FCVT illegal instruction handling
- a00aeea Fix rounding mode selection
- 52fe009 Fix illegal instruction handling for SLLI, SRLI, SRAI
- ebf2362 Fix decode annotation error

Such bugs arise because the bit patterns are duplicated in C code
that must stay in sync with the spec. Driving decode.c from a single
declarative source eliminates this entire class of bug by
construction.

Quantified maintenance cost reduction:

- Hand-maintained decoder logic shrinks from 2052 lines (decode.c on
  master) to 239 lines (instructions.in), a reduction of 88 percent.
  scripts/gen-decoder.py adds 1019 lines of one-time tooling that is
  not touched when adding instructions.

- Adding a new extension shrinks roughly five to six fold. On master,
  commit c10b895 added Zbb with +95 lines in decode.c and +21 in
  decode.h. The same change via the generator is about 20 declarative
  lines in instructions.in, copied directly from riscv-opcodes.
  Commit 29dabe4 added Zbs with +45 in decode.c and +11 in decode.h;
  the generator equivalent is 9 lines.

Future maintenance workflow:

- Adding one instruction is one line in instructions.in plus the
  execution semantics in emulate.c. Running make regenerates
  decode.c automatically. No edits to decode.c, decode.h, or
  gen-decoder.py are needed.

- Adding a new extension adds one @extension tag in instructions.in
  followed by the instruction lines.

The descriptor format and extension-addition workflow are documented
in docs/decoder-generator.md.

Close sysprog21#103
@yc199911 yc199911 force-pushed the issue-103-decoder-gen branch 2 times, most recently from 6680ff8 to 650012a Compare May 18, 2026 16:57
@yc199911

Copy link
Copy Markdown
Contributor Author

should I: (a) Add RVV instructions to instructions.in in this PR

The RVV decoder should be generated from extension definitions, which is precisely what this pull request aims to achieve.

While migrating I noticed two encoding-level
bugs inherited verbatim from decode_v.c:

  1. Reserved vfrdiv encoding (funct6=0x21, funct3=1) is decoded
    as vfrdiv.vf instead of trapping as illegal-instruction
    only defines it at funct3=5).
  2. vwmaccsu.vx mislabeled as vwmaccus.vx at funct6=0x3f,
    funct3=6 — both executors exist with distinct semantics, so the
    mislabel silently swaps signed/unsigned operand handling.

Both fixes are minimal — Bug 1 is a one-line delete in
instructions.in, Bug 2 is changing the mnemonic on one line.
Would you prefer them folded into this PR, or tracked in a
follow-up issue?

@jserv

jserv commented May 20, 2026

Copy link
Copy Markdown
Contributor

While migrating I noticed two encoding-level bugs inherited verbatim from decode_v.c:

  1. Reserved vfrdiv encoding (funct6=0x21, funct3=1) is decoded
    as vfrdiv.vf instead of trapping as illegal-instruction
    only defines it at funct3=5).
  2. vwmaccsu.vx mislabeled as vwmaccus.vx at funct6=0x3f,
    funct3=6 — both executors exist with distinct semantics, so the
    mislabel silently swaps signed/unsigned operand handling.
    Both fixes are minimal — Bug 1 is a one-line delete in instructions.in, Bug 2 is changing the mnemonic on one line. Would you prefer them folded into this PR, or tracked in a follow-up issue?

Submit new pull request(s) to resolve the current RVV decoder.

yc199911 added 9 commits May 20, 2026 23:37
- cflwsp: rd=0 addresses f0 (valid float register), not x0.
  Remove from _RVC_RD0_RESERVED to fix FCZicsr arch-test failure.
- decode_funct3: wrap in #if RV32_HAS(EXT_F) to fix unused-function
  error when building with ENABLE_EXT_F=0.
- docs: remove history paragraph, classify extensions by RISC-V
  category (base ISA / standard / Z-extensions).
Add RVV operand classes (vd, vs1, vs2, vs3, vm, nf, veew, simm5,
zimm5/10/11) and EXT_V / EXT_FV opcode groups to gen-decoder.py.
Required by subsequent commits that register V-config and
V-load/store instructions in src/instructions.in.

Mixed-group decode (LOAD-FP / STORE-FP) falls back to itype /
stype where the dominant fields are scalar; vector-specific
operands are emitted per-leaf instead of through a non-existent
decode_vtype helper.
Register vsetvli, vsetivli, and vsetvl in src/instructions.in
so the auto-generated decoder dispatches the three V-config
encodings under OP-V (opcode 0x15, funct3=7).
Enumerate every EEW x NF x MOP variant in src/instructions.in
for unit-stride, fault-only-first, strided, indexed-unordered,
indexed-ordered, whole-register, and mask loads/stores. Each
variant becomes its own decode leaf, mirroring the official
riscv-opcodes/rv_v listing rather than the runtime EEW-offset
trick used by the previous hand-written decoder.
Add OP-V (opcode 0x15) integer/mask encodings to instructions.in:
OPIVV / OPIVX / OPIVI (funct3=0/4/3) and OPMVV / OPMVX (funct3=2/6),
covering funct6 0x00-0x1f and 0x20-0x3f.

Handle three special dispatch patterns required by the encoding:
- VWXUNARY0 (funct6=0x10, funct3=2): sub-dispatch on bits[19:15]
- VMUNARY0  (funct6=0x14, funct3=2): same pattern
- vmv.v.v / vmerge.vvm (funct6=0x17): disambiguated by vm bit[25]
Add OPFVV (funct3=1) and OPFVF (funct3=5) encodings: vfadd/vfsub/
vfmul/vfdiv, fused multiply-add family, reductions, sign-injection,
slides, merge/move, and widening forms.

The reserved funct6=0x21, funct3=1 encoding is intentionally
decoded as vfrdiv.vf to preserve decode_v.c behavior; spec-correct
illegal-instruction trapping will be addressed in a follow-up.
Remove src/decode_v.c (2286 lines); the auto-generated decoder in
src/decode.c now covers all RVV v1.0 encodings previously handled
by the hand-written decoder.

Update docs/decoder-generator.md with EXT_V entries:
- RVV operand reference (vd/vs1/vs2/vs3/vm/veew/zimm5/...)
- OP-V funct6 / funct3 class conventions and constraint ordering
- Special dispatch patterns (VWXUNARY0, VMUNARY0, vmv/vmerge)
- Worked example for adding a new RVV instruction

Two encoding bugs from decode_v.c are preserved verbatim for
behavioral equivalence; a follow-up issue will track them.
The initial OPFVV/OPFVF generator covered funct6 < 0x18 and >= 0x20,
missing the 0x18-0x1f range that holds floating-point comparison
instructions producing mask results.

Add 10 missing entries:
- vmfeq.vv/.vf  funct6=0x18
- vmfle.vv/.vf  funct6=0x19
- vmflt.vv/.vf  funct6=0x1b
- vmfne.vv/.vf  funct6=0x1c
- vmfgt.vf      funct6=0x1d (OPFVF only)
- vmfge.vf      funct6=0x1f (OPFVF only)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants