diff --git a/.claude/skills/encoding-viz/SKILL.md b/.claude/skills/encoding-viz/SKILL.md new file mode 100644 index 000000000..9d7c6f6be --- /dev/null +++ b/.claude/skills/encoding-viz/SKILL.md @@ -0,0 +1,103 @@ +--- +name: encoding-viz +description: Generate or port the RISC-V encoding-space visualizer — an HTML map showing spec coverage (decoded vs not), hardware-backed custom instructions, toolchain-only dead definitions, and spec deviations. Use when asked to visualize/audit instruction encodings, check ISA-spec alignment, find reclaimable encoding space, or set up the tool in another hardware repo. +--- + +# Encoding-space visualizer + +`util/enc_viz/gen_encoding_viz.py` (stdlib-only Python 3, read-only) renders a +self-contained `encoding_map.html` answering four questions: + +1. Which **spec** instructions does the hardware decode (blue) vs not (gray)? +2. Which **custom** instructions are hardware-backed (one hue per extension)? +3. What is **in the toolchain but not decoded** (black + dedicated section) + — i.e. reclaimable encoding space? +4. Where does the implementation **deviate from the spec** (same-name + encoding mismatches; custom instructions on spec-claimed encodings)? + +## Using it in this repo + +```sh +cd util/enc_viz && python3 gen_encoding_viz.py # -> encoding_map.html +python3 gen_encoding_viz.py --crypto # + rv_zv* on the canvas +python3 gen_encoding_viz.py --decoder new_decoder.sv # extend decoder scan +``` + +- First run clones upstream `riscv/riscv-opcodes` into `.cache/` (needs network + once); later runs are offline. Pinned via `UPSTREAM_COMMIT`. +- The enabled custom extensions are parsed **live from the top-level Makefile + `OPCODES` variable** — after enabling/disabling an extension or running + `make update_opcodes`, just re-run the script. +- If the console warns that an RTL file references instructions no scanned + decoder handles, a new decoder file exists: re-run with `--decoder FILE.sv`. +- If it warns `riscv_instr.sv is stale`, run `make update_opcodes` first. + +Read `util/enc_viz/README.md` for the full option table and map legend. + +## Core semantics (preserve these when porting) + +- **Ground truth** = a machine-readable spec opcode file (upstream + `riscv-opcodes/extensions/rv_v` here), never a hand-copied list. +- **"Implemented" = decode check only**: the instruction name (as generated in + the instruction package, e.g. `VADD_VV`) appears as a whole-word token in an + explicit allowlist of decoder RTL files (`DECODER_FILES`). Do not scan all + RTL — functional units referencing an op would create false positives. + Whole-word matching against the candidate set + requiring the file to + reference the instruction package makes unqualified matches safe. +- **"In toolchain"** = has a localparam in the generated instruction package + (`riscv_instr.sv`), which is also cross-checked against the opcode files so + stale generation is flagged instead of silently trusted. +- **Local-spec drift**: the project's local copy of the spec opcode file is + diffed against upstream. Name-absent-upstream entries become a + "not in spec" pseudo-extension (they are usually custom instructions or + legacy leftovers hiding in the spec file); same-name-different-bits entries + go to the mismatch panel. +- **Grid placement**: an instruction occupies exactly the funct7 rows its + fixed bits allow (`(f7 & mask7) == match7`) — an op using funct7 bits as + immediate/operand fills its whole column, a funct6-only op fills 2 rows. + Never model partial-funct7 ops as "column claimed"; compute row + compatibility. +- **Conflicts**: pairwise fixed-bit overlap across different origins, split + into *active* (both decoded) vs *latent* (≥1 dead). Red is reserved for + genuine bit overlaps only. + +## Porting checklist (new hardware project) + +Copy `util/enc_viz/` (script + README), then adapt the constants at the top of +the script — everything project-specific is configuration, not logic: + +1. **Spec canvas**: `UPSTREAM_URL` / `UPSTREAM_COMMIT` / `SPEC_FILES` — which + upstream extension files are the ground truth for this project (e.g. + `rv_v`, or `rv_i` + `rv_m`, …). +2. **Local opcode files**: path in `main()` (`sw/toolchain/riscv-opcodes` + here) and `RVV_LOCAL_FILE` (the local spec copy to diff). How is the + enabled set declared? Adapt `parse_makefile_opcodes` (`_OPCODES_RE`) to the + project's build variable, keep `EXT_FILES_FALLBACK` as the manual fallback. +3. **Generated instruction package**: `sv_path` (this repo: + `hw/ip/snitch/src/riscv_instr.sv`) and, if the format differs, the + `_SV_RE` regex in `parse_riscv_instr_sv`. +4. **Decoders**: `DECODER_FILES` — find them empirically: scan all RTL for + whole-word instruction-name references and list the files that decode + (case/casez on the instruction), not the FUs that consume decoded ops. The + built-in "referenced outside decoders" warning will catch any you miss. +5. **Repo detection**: `find_repo()` walks up looking for a landmark path — + change the marker directory. +6. **Grids**: `CUSTOM_MAJORS`, `FUNCT3_CAT`/`CATEGORY_ORDER` (OP-V-specific), + and `MAJOR_NAMES` if the project overloads different major opcodes. Drop + the OP-V grid entirely for non-vector projects; the funct7×funct3 custom + grids and list sections are generic. +7. **Colors**: curate `EXT_COLORS` for the known extensions; unknown ones get + `EXTRA_HUES` automatically. + +## Verifying a port (or any substantial change) + +- Cross-check the printed counts against ground truth: spec instr count must + equal the upstream file's instruction count; `toolchain localparams` must + match the generated package; `0 missing / 0 drift` on the consistency check. +- Simulate an end-to-end change in a throwaway fake repo (temp dir with a + minimal Makefile, opcode files, instruction package, and a fake decoder + referencing one of two new instructions): the map must show one live, one + dead, and — without `--decoder` — warn about the unscanned decoder file. +- Sanity-check one known instruction per category in the HTML: a decoded spec + op (blue), a decoded custom op (its hue), a known-dead definition (black), + and any known deviation (mismatch panel). diff --git a/Makefile b/Makefile index b87b946f1..c4e2363fe 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,8 @@ include util/Makefrag BENDER_VERSION = 0.29.1 # Do not include minifloat opcodes, since they conflict with the RVV opcodes! -OPCODES := "opcodes-rvv opcodes-rv32b_CUSTOM opcodes-ipu_CUSTOM opcodes-frep_CUSTOM opcodes-dma_CUSTOM opcodes-ssr_CUSTOM opcodes-smallfloat opcodes-vfx_CUSTOM" +# enable hardware-implemented custom extensions +OPCODES := "opcodes-rvv opcodes-frep_CUSTOM opcodes-dma_CUSTOM opcodes-smallfloat opcodes-vfx_CUSTOM" # Default target diff --git a/hw/ip/snitch/src/riscv_instr.sv b/hw/ip/snitch/src/riscv_instr.sv index e0c6bd867..54e61a672 100644 --- a/hw/ip/snitch/src/riscv_instr.sv +++ b/hw/ip/snitch/src/riscv_instr.sv @@ -34,88 +34,6 @@ package riscv_instr; localparam logic [31:0] DMREP = 32'b000011100000?????000000000101011; localparam logic [31:0] FREP_O = 32'b?????????????????000????10001011; localparam logic [31:0] FREP_I = 32'b?????????????????000????00001011; - localparam logic [31:0] IMV_X_W = 32'b111000000000?????000?????1011011; - localparam logic [31:0] IMV_W_X = 32'b111100000000?????000?????1011011; - localparam logic [31:0] IADDI = 32'b?????????????????000?????1111011; - localparam logic [31:0] ISLLI = 32'b000000???????????001?????1111011; - localparam logic [31:0] ISLTI = 32'b?????????????????010?????1111011; - localparam logic [31:0] ISLTIU = 32'b?????????????????011?????1111011; - localparam logic [31:0] IXORI = 32'b?????????????????100?????1111011; - localparam logic [31:0] ISRLI = 32'b000000???????????101?????1111011; - localparam logic [31:0] ISRAI = 32'b010000???????????101?????1111011; - localparam logic [31:0] IORI = 32'b?????????????????110?????1111011; - localparam logic [31:0] IANDI = 32'b?????????????????111?????1111011; - localparam logic [31:0] IADD = 32'b0000000??????????000?????1011011; - localparam logic [31:0] ISUB = 32'b0100000??????????000?????1011011; - localparam logic [31:0] ISLL = 32'b0000000??????????001?????1011011; - localparam logic [31:0] ISLT = 32'b0000000??????????010?????1011011; - localparam logic [31:0] ISLTU = 32'b0000000??????????011?????1011011; - localparam logic [31:0] IXOR = 32'b0000000??????????100?????1011011; - localparam logic [31:0] ISRL = 32'b0000000??????????101?????1011011; - localparam logic [31:0] ISRA = 32'b0100000??????????101?????1011011; - localparam logic [31:0] IOR = 32'b0000000??????????110?????1011011; - localparam logic [31:0] IAND = 32'b0000000??????????111?????1011011; - localparam logic [31:0] IMADD = 32'b?????01??????????000?????1011011; - localparam logic [31:0] IMSUB = 32'b?????01??????????001?????1011011; - localparam logic [31:0] INMSUB = 32'b?????01??????????010?????1011011; - localparam logic [31:0] INMADD = 32'b?????01??????????011?????1011011; - localparam logic [31:0] IMUL = 32'b0000010??????????000?????1011011; - localparam logic [31:0] IMULH = 32'b0000010??????????001?????1011011; - localparam logic [31:0] IMULHSU = 32'b0000010??????????010?????1011011; - localparam logic [31:0] IMULHU = 32'b0000010??????????011?????1011011; - localparam logic [31:0] IANDN = 32'b0100000??????????111?????1011011; - localparam logic [31:0] IORN = 32'b0100000??????????110?????1011011; - localparam logic [31:0] IXNOR = 32'b0100000??????????100?????1011011; - localparam logic [31:0] ISLO = 32'b0010000??????????001?????1011011; - localparam logic [31:0] ISRO = 32'b0010000??????????101?????1011011; - localparam logic [31:0] IROL = 32'b0110000??????????001?????1011011; - localparam logic [31:0] IROR = 32'b0110000??????????101?????1011011; - localparam logic [31:0] ISBCLR = 32'b0100100??????????001?????1011011; - localparam logic [31:0] ISBSET = 32'b0010100??????????001?????1011011; - localparam logic [31:0] ISBINV = 32'b0110100??????????001?????1011011; - localparam logic [31:0] ISBEXT = 32'b0100100??????????101?????1011011; - localparam logic [31:0] IGORC = 32'b0010100??????????101?????1011011; - localparam logic [31:0] IGREV = 32'b0110100??????????101?????1011011; - localparam logic [31:0] ISLOI = 32'b001000???????????001?????1111011; - localparam logic [31:0] ISROI = 32'b001000???????????101?????1111011; - localparam logic [31:0] IRORI = 32'b011000???????????101?????1111011; - localparam logic [31:0] ISBCLRI = 32'b010010???????????001?????1111011; - localparam logic [31:0] ISBSETI = 32'b001010???????????001?????1111011; - localparam logic [31:0] ISBINVI = 32'b011010???????????001?????1111011; - localparam logic [31:0] ISBEXTI = 32'b010010???????????101?????1111011; - localparam logic [31:0] IGORCI = 32'b001010???????????101?????1111011; - localparam logic [31:0] IGREVI = 32'b011010???????????101?????1111011; - localparam logic [31:0] ICLZ = 32'b011000000000?????010?????1011011; - localparam logic [31:0] ICTZ = 32'b011000000001?????010?????1011011; - localparam logic [31:0] IPCNT = 32'b011000000010?????010?????1011011; - localparam logic [31:0] ISEXT_B = 32'b011000000100?????010?????1011011; - localparam logic [31:0] ISEXT_H = 32'b011000000101?????010?????1011011; - localparam logic [31:0] ICRC32_B = 32'b011000010000?????001?????1011011; - localparam logic [31:0] ICRC32_H = 32'b011000010001?????001?????1011011; - localparam logic [31:0] ICRC32_W = 32'b011000010010?????001?????1011011; - localparam logic [31:0] ICRC32C_B = 32'b011000011000?????001?????1011011; - localparam logic [31:0] ICRC32C_H = 32'b011000011001?????001?????1011011; - localparam logic [31:0] ICRC32C_W = 32'b011000011010?????001?????1011011; - localparam logic [31:0] ISH1ADD = 32'b0010000??????????010?????1011011; - localparam logic [31:0] ISH2ADD = 32'b0010000??????????100?????1011011; - localparam logic [31:0] ISH3ADD = 32'b0010000??????????110?????1011011; - localparam logic [31:0] ICLMUL = 32'b0000101??????????001?????1011011; - localparam logic [31:0] ICLMULR = 32'b0000101??????????010?????1011011; - localparam logic [31:0] ICLMULH = 32'b0000101??????????011?????1011011; - localparam logic [31:0] IMIN = 32'b0000101??????????100?????1011011; - localparam logic [31:0] IMAX = 32'b0000101??????????101?????1011011; - localparam logic [31:0] IMINU = 32'b0000101??????????110?????1011011; - localparam logic [31:0] IMAXU = 32'b0000101??????????111?????1011011; - localparam logic [31:0] ISHFL = 32'b0000100??????????001?????1011011; - localparam logic [31:0] IUNSHFL = 32'b0000100??????????101?????1011011; - localparam logic [31:0] IBEXT = 32'b0000100??????????110?????1011011; - localparam logic [31:0] IBDEP = 32'b0100100??????????110?????1011011; - localparam logic [31:0] IPACK = 32'b0000100??????????100?????1011011; - localparam logic [31:0] IPACKU = 32'b0100100??????????100?????1011011; - localparam logic [31:0] IPACKH = 32'b0000100??????????111?????1011011; - localparam logic [31:0] IBFP = 32'b0100100??????????111?????1011011; - localparam logic [31:0] ISHFLI = 32'b0000100??????????001?????1111011; - localparam logic [31:0] IUNSHFLI = 32'b0000100??????????101?????1111011; localparam logic [31:0] SLLI_RV32 = 32'b0000000??????????001?????0010011; localparam logic [31:0] SRLI_RV32 = 32'b0000000??????????101?????0010011; localparam logic [31:0] SRAI_RV32 = 32'b0100000??????????101?????0010011; @@ -150,64 +68,6 @@ package riscv_instr; localparam logic [31:0] AMOSWAP_W = 32'b00001????????????010?????0101111; localparam logic [31:0] LR_W = 32'b00010??00000?????010?????0101111; localparam logic [31:0] SC_W = 32'b00011????????????010?????0101111; - localparam logic [31:0] ANDN = 32'b0100000??????????111?????0110011; - localparam logic [31:0] ORN = 32'b0100000??????????110?????0110011; - localparam logic [31:0] XNOR = 32'b0100000??????????100?????0110011; - localparam logic [31:0] SLO = 32'b0010000??????????001?????0110011; - localparam logic [31:0] SRO = 32'b0010000??????????101?????0110011; - localparam logic [31:0] ROL = 32'b0110000??????????001?????0110011; - localparam logic [31:0] ROR = 32'b0110000??????????101?????0110011; - localparam logic [31:0] SBCLR = 32'b0100100??????????001?????0110011; - localparam logic [31:0] SBSET = 32'b0010100??????????001?????0110011; - localparam logic [31:0] SBINV = 32'b0110100??????????001?????0110011; - localparam logic [31:0] SBEXT = 32'b0100100??????????101?????0110011; - localparam logic [31:0] GORC = 32'b0010100??????????101?????0110011; - localparam logic [31:0] GREV = 32'b0110100??????????101?????0110011; - localparam logic [31:0] SLOI = 32'b001000???????????001?????0010011; - localparam logic [31:0] SROI = 32'b001000???????????101?????0010011; - localparam logic [31:0] RORI = 32'b011000???????????101?????0010011; - localparam logic [31:0] SBCLRI = 32'b010010???????????001?????0010011; - localparam logic [31:0] SBSETI = 32'b001010???????????001?????0010011; - localparam logic [31:0] SBINVI = 32'b011010???????????001?????0010011; - localparam logic [31:0] SBEXTI = 32'b010010???????????101?????0010011; - localparam logic [31:0] GORCI = 32'b001010???????????101?????0010011; - localparam logic [31:0] GREVI = 32'b011010???????????101?????0010011; - localparam logic [31:0] CMIX = 32'b?????11??????????001?????0110011; - localparam logic [31:0] CMOV = 32'b?????11??????????101?????0110011; - localparam logic [31:0] FSL = 32'b?????10??????????001?????0110011; - localparam logic [31:0] FSR = 32'b?????10??????????101?????0110011; - localparam logic [31:0] FSRI = 32'b?????1???????????101?????0010011; - localparam logic [31:0] CLZ = 32'b011000000000?????001?????0010011; - localparam logic [31:0] CTZ = 32'b011000000001?????001?????0010011; - localparam logic [31:0] PCNT = 32'b011000000010?????001?????0010011; - localparam logic [31:0] SEXT_B = 32'b011000000100?????001?????0010011; - localparam logic [31:0] SEXT_H = 32'b011000000101?????001?????0010011; - localparam logic [31:0] CRC32_B = 32'b011000010000?????001?????0010011; - localparam logic [31:0] CRC32_H = 32'b011000010001?????001?????0010011; - localparam logic [31:0] CRC32_W = 32'b011000010010?????001?????0010011; - localparam logic [31:0] CRC32C_B = 32'b011000011000?????001?????0010011; - localparam logic [31:0] CRC32C_H = 32'b011000011001?????001?????0010011; - localparam logic [31:0] CRC32C_W = 32'b011000011010?????001?????0010011; - localparam logic [31:0] SH1ADD = 32'b0010000??????????010?????0110011; - localparam logic [31:0] SH2ADD = 32'b0010000??????????100?????0110011; - localparam logic [31:0] SH3ADD = 32'b0010000??????????110?????0110011; - localparam logic [31:0] CLMUL = 32'b0000101??????????001?????0110011; - localparam logic [31:0] CLMULR = 32'b0000101??????????010?????0110011; - localparam logic [31:0] CLMULH = 32'b0000101??????????011?????0110011; - localparam logic [31:0] MIN = 32'b0000101??????????100?????0110011; - localparam logic [31:0] MAX = 32'b0000101??????????101?????0110011; - localparam logic [31:0] MINU = 32'b0000101??????????110?????0110011; - localparam logic [31:0] MAXU = 32'b0000101??????????111?????0110011; - localparam logic [31:0] SHFL = 32'b0000100??????????001?????0110011; - localparam logic [31:0] UNSHFL = 32'b0000100??????????101?????0110011; - localparam logic [31:0] BEXT = 32'b0000100??????????110?????0110011; - localparam logic [31:0] BDEP = 32'b0100100??????????110?????0110011; - localparam logic [31:0] PACK = 32'b0000100??????????100?????0110011; - localparam logic [31:0] PACKU = 32'b0100100??????????100?????0110011; - localparam logic [31:0] PACKH = 32'b0000100??????????111?????0110011; - localparam logic [31:0] BFP = 32'b0100100??????????111?????0110011; - localparam logic [31:0] SHFLI = 32'b0000100??????????001?????0010011; - localparam logic [31:0] UNSHFLI = 32'b0000100??????????101?????0010011; localparam logic [31:0] C_SRLI_RV32 = 32'b????????????????100000????????01; localparam logic [31:0] C_SRAI_RV32 = 32'b????????????????100001????????01; localparam logic [31:0] C_SLLI_RV32 = 32'b????????????????0000??????????10; @@ -1043,10 +903,6 @@ package riscv_instr; localparam logic [31:0] FCVT_AB_B = 32'b010001100011?????000?????1010011; localparam logic [31:0] FCVT_B_AB = 32'b010001100011?????000?????1010011; localparam logic [31:0] FCVT_AB_AB = 32'b010001100011?????000?????1010011; - localparam logic [31:0] SCFGRI = 32'b????????????00000001?????0101011; - localparam logic [31:0] SCFGWI = 32'b?????????????????010000000101011; - localparam logic [31:0] SCFGR = 32'b0000000?????00001001?????0101011; - localparam logic [31:0] SCFGW = 32'b0000000??????????010000010101011; localparam logic [31:0] ECALL = 32'b00000000000000000000000001110011; localparam logic [31:0] EBREAK = 32'b00000000000100000000000001110011; localparam logic [31:0] URET = 32'b00000000001000000000000001110011; diff --git a/util/enc_viz/.gitignore b/util/enc_viz/.gitignore new file mode 100644 index 000000000..ceddaa37f --- /dev/null +++ b/util/enc_viz/.gitignore @@ -0,0 +1 @@ +.cache/ diff --git a/util/enc_viz/README.md b/util/enc_viz/README.md new file mode 100644 index 000000000..250d7a8c8 --- /dev/null +++ b/util/enc_viz/README.md @@ -0,0 +1,167 @@ +# Encoding-space visualizer + +`gen_encoding_viz.py` builds a single, self-contained HTML map of how the +RISC-V Vector (RVV) encoding space — and the adjacent custom major opcodes — is +used by this hardware. It is **read-only**: it never modifies the repo. + +It answers four questions at a glance, checked against the RVV v1.0 ground +truth ([upstream `riscv-opcodes` `extensions/rv_v`](https://github.com/riscv/riscv-opcodes/blob/master/extensions/rv_v)): + +1. **Which RVV v1.0 spec instructions does the hardware implement?** + 🟦 blue = decoded, ⬜ gray = not. *Implemented = the instruction is decoded + by one of the decoders (snitch, spatz decoder, FPU sequencer, DMA + front-end) — a decode check only; the logic behind the decoder is not + verified.* +2. **Which custom instructions are hardware-backed?** One hue per custom + extension group (same decode check). +3. **What is in the toolchain but not in hardware?** Instructions with a + `riscv_instr.sv` localparam that no decoder references — shown ⬛ black in + the grids *and* listed exhaustively in a dedicated section (reclaimable + encoding space). +4. **Where does the implementation deviate from the spec?** A dedicated + panel lists (a) encoding mismatches — same instruction name, different + fixed bits vs the spec — and (b) custom instructions occupying + spec-claimed encodings. + +--- + +## Quick start + +```sh +cd util/enc_viz +python3 gen_encoding_viz.py +# opens: util/enc_viz/encoding_map.html (open it in any browser) +``` + +First run clones the upstream spec (~seconds, needs network); later runs are offline. + +Re-run it any time the RTL, the opcode files, or `riscv_instr.sv` change — the +map always reflects the current sources. + +--- + +## Requirements + +- **Python 3** (standard library only — no pip installs). +- **git** and **network access on the first run** only, to fetch the upstream + spec. It is cached under `util/enc_viz/.cache/` (git-ignored); subsequent runs + are fully offline. + +--- + +## What it reads + +| Input | Role | +|-------|------| +| upstream `riscv/riscv-opcodes` `extensions/rv_v` (auto-cloned, pinned commit) | the RVV v1.0 ground truth = the canvas (`--crypto` adds the `rv_zv*` exts) | +| `sw/toolchain/riscv-opcodes/opcodes-*` (the 7 custom entries in the Makefile `OPCODES` var) | our custom extensions | +| `sw/toolchain/riscv-opcodes/opcodes-rvv` (the 8th `OPCODES` entry) | diffed against the upstream canvas: entries absent upstream (e.g. `vlx*`, `vfwdotp`, legacy v0.9 leftovers) become the **`rvv (not in spec v1.0)`** pseudo-extension; same-name entries with different fixed bits are listed in the **spec-deviation panel** | +| `hw/ip/snitch/src/riscv_instr.sv` | the toolchain truth: "in toolchain" = has a localparam here (also cross-checked against the opcode files; a stale file triggers a warning) | +| the decoders under `hw/`: `snitch.sv`, `spatz_decoder.sv`, `spatz_fpu_sequencer.sv`, `axi_dma_tc_snitch_fe.sv` | what the hardware actually decodes | + +"Implemented" = the instruction name is referenced in one of the decoder +files, either qualified (`riscv_instr::VADD_VV`) or unqualified (`FREP_O`, +when the file does `import riscv_instr::*`). This is a decode check only. + +--- + +## Command-line options + +``` +python3 gen_encoding_viz.py [--repo DIR] [--out FILE] [--spec-commit SHA] [--crypto] +``` + +| Option | Default | Meaning | +|--------|---------|---------| +| `--repo DIR` | auto-detected | Spatz repo root (found by walking up from the script). | +| `--out FILE` | `encoding_map.html` next to the script | Where to write the HTML. | +| `--spec-commit SHA` | pinned commit | Which upstream `riscv-opcodes` commit to diff against. | +| `--crypto` | off | Also draw the ratified vector-crypto exts (`rv_zv*`) on the canvas (they claim OP-V slots too — e.g. Zvfbfwma's `vfwmaccbf16` sits on funct6 0x3b, which `vfwdotp` reuses). | +| `--decoder FILE.sv` | — | Treat an additional RTL file as a decoder (repeatable; extends the built-in list). | + +Examples: +```sh +# write somewhere else +python3 gen_encoding_viz.py --out /tmp/enc.html + +# run from anywhere, point at a specific checkout +python3 util/enc_viz/gen_encoding_viz.py --repo /path/to/spatz +``` + +--- + +## Reading the map + +**Colors** +| Color | Meaning | +|-------|---------| +| 🟦 blue | spec instruction the hardware decodes | +| ⬜ gray | spec instruction we do **not** implement | +| extension hue | custom instruction that **is** decoded | +| ⬛ black | in the toolchain (`riscv_instr.sv`) but decoded by no hardware (reclaimable) | +| blue/gray two-tone | one slot holding both implemented and unimplemented sub-encodings | +| 🟥 red hatch | a **genuine** encoding conflict (two instructions' fixed bits overlap) | +| empty | free encoding space | + +**Sections** +- **OP-V grid** — funct6 (rows) × category (columns) for major 0x57. A cell + labeled `name +N` holds several legal sub-encodings that differ by + `vm`/`vs1`/`vs2`/`rs1`; hover to see each one and its status. +- **Custom major grids** — CUSTOM-0/1/2/3 (0x0b/2b/5b/7b) as funct7 × funct3 + grids (collapsible; the summary line shows used/free slot counts). I-type + immediate ops claim a whole funct3 column and are shown in a banner; free + cells inside a claimed column are red-hatched. +- **Standard opcodes overloaded by custom ext** — rv32b (OP / OP-IMM), + smallfloat (OP-FP / FMA), vector-crypto (0x77), listed so nothing is dropped. +- **In toolchain but not decoded by hardware** — the exhaustive list of ⬛ + dead definitions, grouped per extension (collapsible), so reclaimable + encoding space is visible at a glance rather than scattered across grids. +- **Active vs latent conflicts** — *active* = both sides decoded in hardware + (real collision); *latent* = at least one side is a dead definition + (paper collision only). +- **Deviations from the RVV v1.0 spec** — (a) *encoding mismatches*: same + instruction name, different fixed bits between our `opcodes-rvv` and the + spec (these deviations end up in the generated decoder masks); (b) *custom + instructions on spec-claimed encodings*: a custom instruction's fixed bits + overlap an RVV spec instruction. +- **Extension status table** — per extension: defined / implemented / dead. + +Hover any cell or chip for the full 32-bit pattern, origin file, and the RTL +file(s) that decode it. + +--- + +## Sharing the result + +`encoding_map.html` is fully self-contained (all CSS/JS inlined, no external +requests), so you can open it locally, email it, or host it on any internal web +server / GitLab Pages — no dependencies required. + +--- + +## Adding a new custom extension + +The tool follows the build configuration automatically: + +1. Add your `opcodes-xxx_CUSTOM` file and list it in the top-level Makefile + `OPCODES` variable — the tool parses that variable each run and assigns the + new extension a distinct hue automatically. +2. Run `make update_opcodes` — if you forget, the tool warns that + `riscv_instr.sv` is stale. +3. If the new instructions are decoded by one of the known decoders + (`snitch.sv`, `spatz_decoder.sv`, `spatz_fpu_sequencer.sv`, + `axi_dma_tc_snitch_fe.sv`), nothing else to do. If your hardware adds a + **new decoder file**, the tool detects instructions referenced outside the + known decoders and prints/renders a warning — re-run with + `--decoder your_decoder.sv` (repeatable) to include it in the scan. + +## Notes & limitations + +- "Implemented" is a decode check by design: the name is referenced in one of + the decoder files. An instruction that is decoded but whose execution logic + is broken/absent still reads as implemented — verifying the logic is out of + scope for this tool. +- The standard-overloaded majors (OP / OP-IMM / OP-FP) are shown as lists, not + grids — gridding them would require drawing base-ISA occupancy too. +- The upstream spec commit is pinned for reproducibility; bump `--spec-commit` + (or edit `UPSTREAM_COMMIT` in the script) to track a newer spec. diff --git a/util/enc_viz/encoding_map.html b/util/enc_viz/encoding_map.html new file mode 100644 index 000000000..7fe4242c6 --- /dev/null +++ b/util/enc_viz/encoding_map.html @@ -0,0 +1,230 @@ + + +

RVV & custom encoding-space map — Spatz

+
+Ground truth: upstream riscv/riscv-opcodes rv_v (RVV v1.0) @ c6edca7d8c
+Implemented = decoded in snitch.sv, spatz_decoder.sv, spatz_fpu_sequencer.sv, axi_dma_tc_snitch_fe.sv (decode check only; the logic behind the decoder is not verified)
+Generated read-only — no repo files modified. +
+
This map answers four questions:
+
    +
  1. Which RVV v1.0 spec instructions does the hardware decode (blue) — and which not (gray)?
  2. +
  3. Which custom instructions are hardware-backed (one hue per extension)?
  4. +
  5. Which instructions are in the toolchain (riscv_instr.sv) but not decoded (black — see the dedicated section)?
  6. +
  7. Where does the implementation deviate from the spec (mismatch / overlap panels)?
  8. +
+ + + + + + + + +
spec — implemented: RVV v1.0 instruction decoded by the hardware
spec — not implemented: RVV v1.0 instruction with no decoder reference
spec — partially implemented: slot holds several sub-encodings, only some decoded (hover for the list)
custom — implemented, one hue per extension:
frep_CUSTOMdma_CUSTOMsmallfloatvfx_CUSTOMrvv (not in spec v1.0)
custom — in toolchain, not decoded: has a riscv_instr.sv localparam but no decoder reference (reclaimable)
conflict: two instructions' fixed bits genuinely overlap
free: unallocated encoding space
+
+spec instrs: 375 (261 impl / 114 not) +custom-ext instrs: 139 (66 live / 73 dead) +active conflicts: 0 +latent (paper) conflicts: 0 +spec deviations: 8 mismatches, 0 custom-on-spec overlaps +OP-V free slots: 176/448 +
+

Extension status — which shipped extensions are live in this hardware

+ + + + + + +
extensiondefinedimplementeddead (reclaimable)
frep_CUSTOM220
dma_CUSTOM880
smallfloat38335
vfx_CUSTOM17170
rvv (not in spec v1.0)74668
+
“dead” = defined in the Makefile OPCODES build (present in riscv_instr.sv) but referenced by no decoder — encoding space reserved on paper, free to reclaim.
+

In toolchain but not decoded by hardware — 73 instructions have a riscv_instr.sv localparam but appear in no decoder; their encoding space is reclaimable

+
smallfloat — 5 dead definitions +
+FCVT_B_B +FCVT_L_H +FCVT_LU_H +FCVT_H_L +FCVT_H_LU +
+
rvv (not in spec v1.0) — 68 dead definitions +
+VAMOADDEI8_V +VAMOADDEI16_V +VAMOADDEI32_V +VAMOADDEI64_V +VAMOSWAPEI8_V +VAMOSWAPEI16_V +VAMOSWAPEI32_V +VAMOSWAPEI64_V +VSE128_V +VSE256_V +VSE512_V +VSE1024_V +VLE128FF_V +VLE256FF_V +VLE512FF_V +VLE1024FF_V +VLUXEI128_V +VSUXEI128_V +VLUXEI256_V +VSUXEI256_V +VLUXEI512_V +VSUXEI512_V +VLUXEI1024_V +VSUXEI1024_V +VLSE128_V +VSSE128_V +VLSE256_V +VSSE256_V +VLSE512_V +VSSE512_V +VLSE1024_V +VSSE1024_V +VLOXEI128_V +VSOXEI128_V +VLOXEI256_V +VSOXEI256_V +VLOXEI512_V +VSOXEI512_V +VLOXEI1024_V +VSOXEI1024_V +VAMOXOREI8_V +VAMOXOREI16_V +VAMOXOREI32_V +VAMOXOREI64_V +VAMOOREI8_V +VAMOOREI16_V +VAMOOREI32_V +VAMOOREI64_V +VAMOANDEI8_V +VAMOANDEI16_V +VAMOANDEI32_V +VAMOANDEI64_V +VAMOMINEI8_V +VAMOMINEI16_V +VAMOMINEI32_V +VAMOMINEI64_V +VAMOMAXEI8_V +VAMOMAXEI16_V +VAMOMAXEI32_V +VAMOMAXEI64_V +VAMOMINUEI8_V +VAMOMINUEI16_V +VAMOMINUEI32_V +VAMOMINUEI64_V +VAMOMAXUEI8_V +VAMOMAXUEI16_V +VAMOMAXUEI32_V +VAMOMAXUEI64_V +
+

Active encoding conflicts — both sides live in hardware

+
None. No two hardware-implemented instructions collide. 🎉
+

⚠ Deviations from the RVV v1.0 spec — where our implementation and the spec disagree about an encoding

+
Encoding mismatches — same instruction name, different fixed bits between local opcodes-rvv and the spec; the generated decoder masks deviate from the spec:
+
+
VMANDN_MM (decoded in HW)
  local: 0 11000?? ????? ????0 10 ????? 1010111
  spec : 0 110001? ????? ????0 10 ????? 1010111
+
VMAND_MM (decoded in HW)
  local: 0 11001?? ????? ????0 10 ????? 1010111
  spec : 0 110011? ????? ????0 10 ????? 1010111
+
VMOR_MM (decoded in HW)
  local: 0 11010?? ????? ????0 10 ????? 1010111
  spec : 0 110101? ????? ????0 10 ????? 1010111
+
VMXOR_MM (decoded in HW)
  local: 0 11011?? ????? ????0 10 ????? 1010111
  spec : 0 110111? ????? ????0 10 ????? 1010111
+
VMORN_MM (decoded in HW)
  local: 0 11100?? ????? ????0 10 ????? 1010111
  spec : 0 111001? ????? ????0 10 ????? 1010111
+
VMNAND_MM (decoded in HW)
  local: 0 11101?? ????? ????0 10 ????? 1010111
  spec : 0 111011? ????? ????0 10 ????? 1010111
+
VMNOR_MM (decoded in HW)
  local: 0 11110?? ????? ????0 10 ????? 1010111
  spec : 0 111101? ????? ????0 10 ????? 1010111
+
VMXNOR_MM (decoded in HW)
  local: 0 11111?? ????? ????0 10 ????? 1010111
  spec : 0 111111? ????? ????0 10 ????? 1010111
+
+

OP-V space (major 0x57) — funct6 × category

+
Rows = funct6 (bits 31..26). Columns = category (funct3). Empty = reusable slot. A “name +N” label means 15 slot(s) hold several legal sub-encodings distinguished by vm/vs1/vs2/rs1 (e.g. vfmerge.vfm & vfmv.v.f) — hover to see each and its status. Red is reserved for genuine bit-overlaps.
+
funct6OPIVVOPIVXOPIVIOPMVVOPMVXOPFVVOPFVF
000000
0x00
VADD_VVVADD_VXVADD_VIVREDSUM_VSVFADD_VVVFADD_VF
000001
0x01
VREDAND_VSVFREDUSUM_VS
000010
0x02
VSUB_VVVSUB_VXVREDOR_VSVFSUB_VVVFSUB_VF
000011
0x03
VRSUB_VXVRSUB_VIVREDXOR_VSVFREDOSUM_VS
000100
0x04
VMINU_VVVMINU_VXVREDMINU_VSVFMIN_VVVFMIN_VF
000101
0x05
VMIN_VVVMIN_VXVREDMIN_VSVFREDMIN_VS
000110
0x06
VMAXU_VVVMAXU_VXVREDMAXU_VSVFMAX_VVVFMAX_VF
000111
0x07
VMAX_VVVMAX_VXVREDMAX_VSVFREDMAX_VS
001000
0x08
VAADDU_VVVAADDU_VXVFSGNJ_VVVFSGNJ_VF
001001
0x09
VAND_VVVAND_VXVAND_VIVAADD_VVVAADD_VXVFSGNJN_VVVFSGNJN_VF
001010
0x0a
VOR_VVVOR_VXVOR_VIVASUBU_VVVASUBU_VXVFSGNJX_VVVFSGNJX_VF
001011
0x0b
VXOR_VVVXOR_VXVXOR_VIVASUB_VVVASUB_VX
001100
0x0c
VRGATHER_VVVRGATHER_VXVRGATHER_VI
001101
0x0d
001110
0x0e
VRGATHEREI16_VVVSLIDEUP_VXVSLIDEUP_VIVSLIDE1UP_VXVFSLIDE1UP_VF
001111
0x0f
VSLIDEDOWN_VXVSLIDEDOWN_VIVSLIDE1DOWN_VXVFSLIDE1DOWN_VF
010000
0x10
VADC_VVMVADC_VXMVADC_VIMVMV_X_S +2VMV_S_XVFMV_F_SVFMV_S_F
010001
0x11
VMADC_VVM +1VMADC_VXM +1VMADC_VIM +1
010010
0x12
VSBC_VVMVSBC_VXMVZEXT_VF8 +5VFCVT_XU_F_V +20
010011
0x13
VMSBC_VVM +1VMSBC_VXM +1VFSQRT_V +3
010100
0x14
VMSBF_M +4
010101
0x15
010110
0x16
010111
0x17
VMERGE_VVM +1VMERGE_VXM +1VMERGE_VIM +1VCOMPRESS_VMVFMERGE_VFM +1
011000
0x18
VMSEQ_VVVMSEQ_VXVMSEQ_VIVMANDN_MMVMFEQ_VVVMFEQ_VF
011001
0x19
VMSNE_VVVMSNE_VXVMSNE_VIVMAND_MMVMFLE_VVVMFLE_VF
011010
0x1a
VMSLTU_VVVMSLTU_VXVMOR_MM
011011
0x1b
VMSLT_VVVMSLT_VXVMXOR_MMVMFLT_VVVMFLT_VF
011100
0x1c
VMSLEU_VVVMSLEU_VXVMSLEU_VIVMORN_MMVMFNE_VVVMFNE_VF
011101
0x1d
VMSLE_VVVMSLE_VXVMSLE_VIVMNAND_MMVMFGT_VF
011110
0x1e
VMSGTU_VXVMSGTU_VIVMNOR_MM
011111
0x1f
VMSGT_VXVMSGT_VIVMXNOR_MMVMFGE_VF
100000
0x20
VSADDU_VVVSADDU_VXVSADDU_VIVDIVU_VVVDIVU_VXVFDIV_VVVFDIV_VF
100001
0x21
VSADD_VVVSADD_VXVSADD_VIVDIV_VVVDIV_VXVFRDIV_VF
100010
0x22
VSSUBU_VVVSSUBU_VXVREMU_VVVREMU_VX
100011
0x23
VSSUB_VVVSSUB_VXVREM_VVVREM_VX
100100
0x24
VMULHU_VVVMULHU_VXVFMUL_VVVFMUL_VF
100101
0x25
VSLL_VVVSLL_VXVSLL_VIVMUL_VVVMUL_VXVFXMACC_VF
100110
0x26
VMULHSU_VVVMULHSU_VXVFXMUL_VF
100111
0x27
VSMUL_VVVSMUL_VXVMV1R_V +3VMULH_VVVMULH_VXVFRSUB_VF
101000
0x28
VSRL_VVVSRL_VXVSRL_VIVFMADD_VVVFMADD_VF
101001
0x29
VSRA_VVVSRA_VXVSRA_VIVMADD_VVVMADD_VXVFNMADD_VVVFNMADD_VF
101010
0x2a
VSSRL_VVVSSRL_VXVSSRL_VIVFMSUB_VVVFMSUB_VF
101011
0x2b
VSSRA_VVVSSRA_VXVSSRA_VIVNMSUB_VVVNMSUB_VXVFNMSUB_VVVFNMSUB_VF
101100
0x2c
VNSRL_WVVNSRL_WXVNSRL_WIVFMACC_VVVFMACC_VF
101101
0x2d
VNSRA_WVVNSRA_WXVNSRA_WIVMACC_VVVMACC_VXVFNMACC_VVVFNMACC_VF
101110
0x2e
VNCLIPU_WVVNCLIPU_WXVNCLIPU_WIVFMSAC_VVVFMSAC_VF
101111
0x2f
VNCLIP_WVVNCLIP_WXVNCLIP_WIVNMSAC_VVVNMSAC_VXVFNMSAC_VVVFNMSAC_VF
110000
0x30
VWREDSUMU_VSVWADDU_VVVWADDU_VXVFWADD_VVVFWADD_VF
110001
0x31
VWREDSUM_VSVWADD_VVVWADD_VXVFWREDUSUM_VS
110010
0x32
VWSUBU_VVVWSUBU_VXVFWSUB_VVVFWSUB_VF
110011
0x33
VWSUB_VVVWSUB_VXVFWREDOSUM_VS
110100
0x34
VWADDU_WVVWADDU_WXVFWADD_WVVFWADD_WF
110101
0x35
VWADD_WVVWADD_WX
110110
0x36
VWSUBU_WVVWSUBU_WXVFWSUB_WVVFWSUB_WF
110111
0x37
VWSUB_WVVWSUB_WX
111000
0x38
VWMULU_VVVWMULU_VXVFWMUL_VVVFWMUL_VF
111001
0x39
111010
0x3a
VWMULSU_VVVWMULSU_VX
111011
0x3b
VWMUL_VVVWMUL_VXVFWDOTP_VVVFWDOTP_VF
111100
0x3c
VWMACCU_VVVWMACCU_VXVFWMACC_VVVFWMACC_VF
111101
0x3d
VWMACC_VVVWMACC_VXVFWNMACC_VVVFWNMACC_VF
111110
0x3e
VWMACCUS_VXVFWMSAC_VVVFWMSAC_VF
111111
0x3f
VWMACCSU_VVVWMACCSU_VXVFWNMSAC_VVVFWNMSAC_VF
+

Custom major opcodes — funct7 × funct3 grids

+
CUSTOM-0/1/2/3 (0x0b/2b/5b/7b). Rows = funct7 (bits 31..25), cols = funct3. An op that uses funct7 bits as immediate/operand (e.g. frep, *.vrf) fills every funct7 row its encoding covers — up to a whole column. Click a header to expand/collapse.
+
CUSTOM-0 (0x0b) — 5 instrs [frep_CUSTOM, vfx_CUSTOM] · 5 live · 193 funct7×funct3 slots used · 831 free
ops using funct7 bits as immediate/operand — each fills every funct7 row its encoding covers:
f3=0: FREP_I ×128f3=0: FREP_O ×128f3=2: VFXMACC_VRF ×32f3=2: VFXMUL_VRF ×32
funct7f3=0f3=1f3=2f3=3f3=4f3=5f3=6f3=7
0000000
0x00
FREP_O +1VFXMACC_VRF
0000001
0x01
FREP_O +1VFXMUL_VRF
0000010
0x02
FREP_O +1
0000011
0x03
FREP_O +1VVENTCLR
0000100
0x04
FREP_O +1VFXMACC_VRF
0000101
0x05
FREP_O +1VFXMUL_VRF
0000110
0x06
FREP_O +1
0000111
0x07
FREP_O +1
0001000
0x08
FREP_O +1VFXMACC_VRF
0001001
0x09
FREP_O +1VFXMUL_VRF
0001010
0x0a
FREP_O +1
0001011
0x0b
FREP_O +1
0001100
0x0c
FREP_O +1VFXMACC_VRF
0001101
0x0d
FREP_O +1VFXMUL_VRF
0001110
0x0e
FREP_O +1
0001111
0x0f
FREP_O +1
0010000
0x10
FREP_O +1VFXMACC_VRF
0010001
0x11
FREP_O +1VFXMUL_VRF
0010010
0x12
FREP_O +1
0010011
0x13
FREP_O +1
0010100
0x14
FREP_O +1VFXMACC_VRF
0010101
0x15
FREP_O +1VFXMUL_VRF
0010110
0x16
FREP_O +1
0010111
0x17
FREP_O +1
0011000
0x18
FREP_O +1VFXMACC_VRF
0011001
0x19
FREP_O +1VFXMUL_VRF
0011010
0x1a
FREP_O +1
0011011
0x1b
FREP_O +1
0011100
0x1c
FREP_O +1VFXMACC_VRF
0011101
0x1d
FREP_O +1VFXMUL_VRF
0011110
0x1e
FREP_O +1
0011111
0x1f
FREP_O +1
0100000
0x20
FREP_O +1VFXMACC_VRF
0100001
0x21
FREP_O +1VFXMUL_VRF
0100010
0x22
FREP_O +1
0100011
0x23
FREP_O +1
0100100
0x24
FREP_O +1VFXMACC_VRF
0100101
0x25
FREP_O +1VFXMUL_VRF
0100110
0x26
FREP_O +1
0100111
0x27
FREP_O +1
0101000
0x28
FREP_O +1VFXMACC_VRF
0101001
0x29
FREP_O +1VFXMUL_VRF
0101010
0x2a
FREP_O +1
0101011
0x2b
FREP_O +1
0101100
0x2c
FREP_O +1VFXMACC_VRF
0101101
0x2d
FREP_O +1VFXMUL_VRF
0101110
0x2e
FREP_O +1
0101111
0x2f
FREP_O +1
0110000
0x30
FREP_O +1VFXMACC_VRF
0110001
0x31
FREP_O +1VFXMUL_VRF
0110010
0x32
FREP_O +1
0110011
0x33
FREP_O +1
0110100
0x34
FREP_O +1VFXMACC_VRF
0110101
0x35
FREP_O +1VFXMUL_VRF
0110110
0x36
FREP_O +1
0110111
0x37
FREP_O +1
0111000
0x38
FREP_O +1VFXMACC_VRF
0111001
0x39
FREP_O +1VFXMUL_VRF
0111010
0x3a
FREP_O +1
0111011
0x3b
FREP_O +1
0111100
0x3c
FREP_O +1VFXMACC_VRF
0111101
0x3d
FREP_O +1VFXMUL_VRF
0111110
0x3e
FREP_O +1
0111111
0x3f
FREP_O +1
1000000
0x40
FREP_O +1VFXMACC_VRF
1000001
0x41
FREP_O +1VFXMUL_VRF
1000010
0x42
FREP_O +1
1000011
0x43
FREP_O +1
1000100
0x44
FREP_O +1VFXMACC_VRF
1000101
0x45
FREP_O +1VFXMUL_VRF
1000110
0x46
FREP_O +1
1000111
0x47
FREP_O +1
1001000
0x48
FREP_O +1VFXMACC_VRF
1001001
0x49
FREP_O +1VFXMUL_VRF
1001010
0x4a
FREP_O +1
1001011
0x4b
FREP_O +1
1001100
0x4c
FREP_O +1VFXMACC_VRF
1001101
0x4d
FREP_O +1VFXMUL_VRF
1001110
0x4e
FREP_O +1
1001111
0x4f
FREP_O +1
1010000
0x50
FREP_O +1VFXMACC_VRF
1010001
0x51
FREP_O +1VFXMUL_VRF
1010010
0x52
FREP_O +1
1010011
0x53
FREP_O +1
1010100
0x54
FREP_O +1VFXMACC_VRF
1010101
0x55
FREP_O +1VFXMUL_VRF
1010110
0x56
FREP_O +1
1010111
0x57
FREP_O +1
1011000
0x58
FREP_O +1VFXMACC_VRF
1011001
0x59
FREP_O +1VFXMUL_VRF
1011010
0x5a
FREP_O +1
1011011
0x5b
FREP_O +1
1011100
0x5c
FREP_O +1VFXMACC_VRF
1011101
0x5d
FREP_O +1VFXMUL_VRF
1011110
0x5e
FREP_O +1
1011111
0x5f
FREP_O +1
1100000
0x60
FREP_O +1VFXMACC_VRF
1100001
0x61
FREP_O +1VFXMUL_VRF
1100010
0x62
FREP_O +1
1100011
0x63
FREP_O +1
1100100
0x64
FREP_O +1VFXMACC_VRF
1100101
0x65
FREP_O +1VFXMUL_VRF
1100110
0x66
FREP_O +1
1100111
0x67
FREP_O +1
1101000
0x68
FREP_O +1VFXMACC_VRF
1101001
0x69
FREP_O +1VFXMUL_VRF
1101010
0x6a
FREP_O +1
1101011
0x6b
FREP_O +1
1101100
0x6c
FREP_O +1VFXMACC_VRF
1101101
0x6d
FREP_O +1VFXMUL_VRF
1101110
0x6e
FREP_O +1
1101111
0x6f
FREP_O +1
1110000
0x70
FREP_O +1VFXMACC_VRF
1110001
0x71
FREP_O +1VFXMUL_VRF
1110010
0x72
FREP_O +1
1110011
0x73
FREP_O +1
1110100
0x74
FREP_O +1VFXMACC_VRF
1110101
0x75
FREP_O +1VFXMUL_VRF
1110110
0x76
FREP_O +1
1110111
0x77
FREP_O +1
1111000
0x78
FREP_O +1VFXMACC_VRF
1111001
0x79
FREP_O +1VFXMUL_VRF
1111010
0x7a
FREP_O +1
1111011
0x7b
FREP_O +1
1111100
0x7c
FREP_O +1VFXMACC_VRF
1111101
0x7d
FREP_O +1VFXMUL_VRF
1111110
0x7e
FREP_O +1
1111111
0x7f
FREP_O +1
+
CUSTOM-1 (0x2b) — 8 instrs [dma_CUSTOM] · 8 live · 8 funct7×funct3 slots used · 1016 free
funct7f3=0f3=1f3=2f3=3f3=4f3=5f3=6f3=7
0000000
0x00
DMSRC
0000001
0x01
DMDST
0000010
0x02
DMCPYI
0000011
0x03
DMCPY
0000100
0x04
DMSTATI
0000101
0x05
DMSTAT
0000110
0x06
DMSTR
0000111
0x07
DMREP
0001000
0x08
0001001
0x09
0001010
0x0a
0001011
0x0b
0001100
0x0c
0001101
0x0d
0001110
0x0e
0001111
0x0f
0010000
0x10
0010001
0x11
0010010
0x12
0010011
0x13
0010100
0x14
0010101
0x15
0010110
0x16
0010111
0x17
0011000
0x18
0011001
0x19
0011010
0x1a
0011011
0x1b
0011100
0x1c
0011101
0x1d
0011110
0x1e
0011111
0x1f
0100000
0x20
0100001
0x21
0100010
0x22
0100011
0x23
0100100
0x24
0100101
0x25
0100110
0x26
0100111
0x27
0101000
0x28
0101001
0x29
0101010
0x2a
0101011
0x2b
0101100
0x2c
0101101
0x2d
0101110
0x2e
0101111
0x2f
0110000
0x30
0110001
0x31
0110010
0x32
0110011
0x33
0110100
0x34
0110101
0x35
0110110
0x36
0110111
0x37
0111000
0x38
0111001
0x39
0111010
0x3a
0111011
0x3b
0111100
0x3c
0111101
0x3d
0111110
0x3e
0111111
0x3f
1000000
0x40
1000001
0x41
1000010
0x42
1000011
0x43
1000100
0x44
1000101
0x45
1000110
0x46
1000111
0x47
1001000
0x48
1001001
0x49
1001010
0x4a
1001011
0x4b
1001100
0x4c
1001101
0x4d
1001110
0x4e
1001111
0x4f
1010000
0x50
1010001
0x51
1010010
0x52
1010011
0x53
1010100
0x54
1010101
0x55
1010110
0x56
1010111
0x57
1011000
0x58
1011001
0x59
1011010
0x5a
1011011
0x5b
1011100
0x5c
1011101
0x5d
1011110
0x5e
1011111
0x5f
1100000
0x60
1100001
0x61
1100010
0x62
1100011
0x63
1100100
0x64
1100101
0x65
1100110
0x66
1100111
0x67
1101000
0x68
1101001
0x69
1101010
0x6a
1101011
0x6b
1101100
0x6c
1101101
0x6d
1101110
0x6e
1101111
0x6f
1110000
0x70
1110001
0x71
1110010
0x72
1110011
0x73
1110100
0x74
1110101
0x75
1110110
0x76
1110111
0x77
1111000
0x78
1111001
0x79
1111010
0x7a
1111011
0x7b
1111100
0x7c
1111101
0x7d
1111110
0x7e
1111111
0x7f
+
CUSTOM-3 (0x7b) — 12 instrs [vfx_CUSTOM] · 12 live · 20 funct7×funct3 slots used · 1004 free
ops using funct7 bits as immediate/operand — each fills every funct7 row its encoding covers:
f3=0: P_VLE8_V_RRPOST ×2f3=0: P_VLX8_V_RRPOST ×2f3=5: P_VLE16_V_RRPOST ×2f3=5: P_VLX16_V_RRPOST ×2f3=6: P_VLE32_V_RRPOST ×2f3=6: P_VLX32_V_RRPOST ×2f3=7: P_VLE64_V_RRPOST ×2f3=7: P_VLX64_V_RRPOST ×2
funct7f3=0f3=1f3=2f3=3f3=4f3=5f3=6f3=7
0000000
0x00
0000001
0x01
0000010
0x02
0000011
0x03
0000100
0x04
0000101
0x05
0000110
0x06
0000111
0x07
0001000
0x08
0001001
0x09
0001010
0x0a
0001011
0x0b
0001100
0x0c
0001101
0x0d
0001110
0x0e
0001111
0x0f
0010000
0x10
0010001
0x11
0010010
0x12
0010011
0x13
0010100
0x14
0010101
0x15
0010110
0x16
0010111
0x17
0011000
0x18
0011001
0x19
0011010
0x1a
0011011
0x1b
0011100
0x1c
0011101
0x1d
0011110
0x1e
0011111
0x1f
0100000
0x20
0100001
0x21
0100010
0x22
0100011
0x23
0100100
0x24
0100101
0x25
0100110
0x26
0100111
0x27
0101000
0x28
0101001
0x29
0101010
0x2a
0101011
0x2b
0101100
0x2c
0101101
0x2d
0101110
0x2e
0101111
0x2f
0110000
0x30
0110001
0x31
0110010
0x32
0110011
0x33
0110100
0x34
0110101
0x35
0110110
0x36
0110111
0x37
0111000
0x38
0111001
0x39
0111010
0x3a
0111011
0x3b
0111100
0x3c
0111101
0x3d
0111110
0x3e
0111111
0x3f
1000000
0x40
1000001
0x41
1000010
0x42
1000011
0x43
1000100
0x44
1000101
0x45
1000110
0x46
1000111
0x47
1001000
0x48
1001001
0x49
1001010
0x4a
1001011
0x4b
1001100
0x4c
1001101
0x4d
1001110
0x4e
1001111
0x4f
1010000
0x50
P_FLB_RRPOSTP_FLH_RRPOSTP_FLW_RRPOSTP_FLD_RRPOST
1010001
0x51
1010010
0x52
1010011
0x53
1010100
0x54
1010101
0x55
1010110
0x56
1010111
0x57
1011000
0x58
1011001
0x59
1011010
0x5a
1011011
0x5b
1011100
0x5c
1011101
0x5d
1011110
0x5e
1011111
0x5f
1100000
0x60
P_VLE8_V_RRPOSTP_VLE16_V_RRPOSTP_VLE32_V_RRPOSTP_VLE64_V_RRPOST
1100001
0x61
P_VLE8_V_RRPOSTP_VLE16_V_RRPOSTP_VLE32_V_RRPOSTP_VLE64_V_RRPOST
1100010
0x62
P_VLX8_V_RRPOSTP_VLX16_V_RRPOSTP_VLX32_V_RRPOSTP_VLX64_V_RRPOST
1100011
0x63
P_VLX8_V_RRPOSTP_VLX16_V_RRPOSTP_VLX32_V_RRPOSTP_VLX64_V_RRPOST
1100100
0x64
1100101
0x65
1100110
0x66
1100111
0x67
1101000
0x68
1101001
0x69
1101010
0x6a
1101011
0x6b
1101100
0x6c
1101101
0x6d
1101110
0x6e
1101111
0x6f
1110000
0x70
1110001
0x71
1110010
0x72
1110011
0x73
1110100
0x74
1110101
0x75
1110110
0x76
1110111
0x77
1111000
0x78
1111001
0x79
1111010
0x7a
1111011
0x7b
1111100
0x7c
1111101
0x7d
1111110
0x7e
1111111
0x7f
+

Vector load/store (major 0x07 / 0x27)

+
Unit-stride, strided, indexed, segment, whole-register, mask, fault-only-first.
+
VLE8_VVSE8_VFLHFSHVLE16_VVSE16_VVLE32_VVSE32_VVLE64_VVSE64_VVLE8FF_VVLE16FF_VVLE32FF_VVLE64FF_VVL1RE8_VVS1R_VVL1RE16_VVL1RE32_VVL1RE64_VVLM_VVSM_VVLUXEI8_VVSUXEI8_VVLUXEI16_VVSUXEI16_VVLUXEI32_VVSUXEI32_VVLUXEI64_VVSUXEI64_VVLSE8_VVSSE8_VVLSE16_VVSSE16_VVLSE32_VVSSE32_VVLSE64_VVSSE64_VVLOXEI8_VVSOXEI8_VVLOXEI16_VVSOXEI16_VVLOXEI32_VVSOXEI32_VVLOXEI64_VVSOXEI64_VVLX8_VVSE128_VVLX16_VVSE256_VVLX32_VVSE512_VVLX64_VVSE1024_VVLE128FF_VVLE256FF_VVLE512FF_VVLE1024FF_VVLUXEI128_VVSUXEI128_VVLUXEI256_VVSUXEI256_VVLUXEI512_VVSUXEI512_VVLUXEI1024_VVSUXEI1024_VVLSE128_VVSSE128_VVLSE256_VVSSE256_VVLSE512_VVSSE512_VVLSE1024_VVSSE1024_VVLOXEI128_VVSOXEI128_VVLOXEI256_VVSOXEI256_VVLOXEI512_VVSOXEI512_VVLOXEI1024_VVSOXEI1024_VVL2RE8_VVS2R_VVL2RE16_VVL2RE32_VVL2RE64_VVL4RE8_VVS4R_VVL4RE16_VVL4RE32_VVL4RE64_VVL8RE8_VVS8R_VVL8RE16_VVL8RE32_VVL8RE64_V
+

Vector config (vset*)

+
OP-V funct3=0x7; encoded via bits 31/30 rather than a plain funct6.
+
VSETVLIVSETVLVSETIVLI
+

AMO (0x2f)

+
custom ext(s) rvv (not in spec v1.0) overloading the standard AMO encoding
+
VAMOADDEI8_VVAMOADDEI16_VVAMOADDEI32_VVAMOADDEI64_VVAMOSWAPEI8_VVAMOSWAPEI16_VVAMOSWAPEI32_VVAMOSWAPEI64_VVAMOXOREI8_VVAMOXOREI16_VVAMOXOREI32_VVAMOXOREI64_VVAMOOREI8_VVAMOOREI16_VVAMOOREI32_VVAMOOREI64_VVAMOANDEI8_VVAMOANDEI16_VVAMOANDEI32_VVAMOANDEI64_VVAMOMINEI8_VVAMOMINEI16_VVAMOMINEI32_VVAMOMINEI64_VVAMOMAXEI8_VVAMOMAXEI16_VVAMOMAXEI32_VVAMOMAXEI64_VVAMOMINUEI8_VVAMOMINUEI16_VVAMOMINUEI32_VVAMOMINUEI64_VVAMOMAXUEI8_VVAMOMAXUEI16_VVAMOMAXUEI32_VVAMOMAXUEI64_V
+

MADD (0x43)

+
custom ext(s) smallfloat overloading the standard MADD encoding
+
FMADD_H
+

MSUB (0x47)

+
custom ext(s) smallfloat overloading the standard MSUB encoding
+
FMSUB_H
+

NMSUB (0x4b)

+
custom ext(s) smallfloat overloading the standard NMSUB encoding
+
FNMSUB_H
+

NMADD (0x4f)

+
custom ext(s) smallfloat overloading the standard NMADD encoding
+
FNMADD_H
+

OP-FP (0x53)

+
custom ext(s) smallfloat overloading the standard OP-FP encoding
+
FADD_HFSUB_HFMUL_HFDIV_HFSGNJ_HFSGNJN_HFSGNJX_HFMIN_HFMAX_HFCVT_S_HFCVT_D_HFCVT_H_SFCVT_H_DFCVT_H_HFCVT_H_BFCVT_B_HFCVT_B_BFSQRT_HFLE_HFLT_HFEQ_HFCVT_W_HFCVT_WU_HFCVT_L_HFCVT_LU_HFCVT_H_WFCVT_H_WUFCVT_H_LFCVT_H_LUFMV_X_HFCLASS_HFMV_H_X
+ +
+ + \ No newline at end of file diff --git a/util/enc_viz/gen_encoding_viz.py b/util/enc_viz/gen_encoding_viz.py new file mode 100644 index 000000000..7cdf903ac --- /dev/null +++ b/util/enc_viz/gen_encoding_viz.py @@ -0,0 +1,1182 @@ +#!/usr/bin/env python3 +# Copyright 2026 ETH Zurich. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# RVV / custom encoding-space visualizer. +# +# Builds a single self-contained HTML page showing how the RISC-V Vector +# encoding space (and the adjacent custom major opcodes) is occupied by: +# * the official RVV spec (upstream riscv/riscv-opcodes) +# * the custom extensions we ship (local sw/toolchain/riscv-opcodes/opcodes-*) +# * local additions inside opcodes-rvv itself (entries absent upstream, e.g. +# vlx*, vfwdotp, legacy v0.9 leftovers) -> shown as their own pseudo-ext; +# same-name entries whose fixed bits differ from upstream are listed in an +# encoding-mismatch panel +# * what our hardware actually decodes (riscv_instr:: refs in the primary +# decoders: spatz_decoder.sv + snitch.sv) +# +# Color code (see LEGEND): +# blue -> spec instruction that our primary decoders implement +# gray -> spec instruction we do NOT implement +# -> a custom extension instruction (one hue per extension) +# empty -> free encoding space +# red -> conflict: >1 source fixes overlapping bits compatibly +# +# Usage: +# python3 gen_encoding_viz.py # auto-locates repo, writes encoding_map.html +# python3 gen_encoding_viz.py --help +# +# The script does not modify the repo; it only reads. The upstream spec repo is +# cached under util/enc_viz/.cache (git-ignored) on first run. + +import argparse +import os +import re +import subprocess +import sys +from collections import defaultdict, OrderedDict + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +# Pin upstream riscv-opcodes for reproducibility (override with --spec-commit). +UPSTREAM_URL = "https://github.com/riscv/riscv-opcodes.git" +UPSTREAM_COMMIT = "c6edca7d8c3f92694963a0a0baeb511930fb2af4" + +# Spec canvas: the RVV v1.0 ground truth = extensions/rv_v in upstream +# riscv/riscv-opcodes. Pass --crypto to additionally draw the ratified +# vector-crypto extensions (rv_zv*): they claim OP-V funct6 slots too (e.g. +# Zvfbfwma's vfwmaccbf16 sits on funct6 0x3b, which our vfwdotp reuses). +SPEC_FILES = ["rv_v"] # extended with rv_zv* only when --crypto is given + +# Custom / shipped extensions: read live from the top-level Makefile OPCODES +# variable, so the map always reflects the currently-enabled build. This list +# is only the fallback if the Makefile can't be parsed. opcodes-rvv is the +# spec copy and handled separately. +EXT_FILES_FALLBACK = [ + "opcodes-frep_CUSTOM", + "opcodes-dma_CUSTOM", + "opcodes-smallfloat", + "opcodes-vfx_CUSTOM", +] + +_OPCODES_RE = re.compile(r'^\s*OPCODES\s*:?=\s*"([^"]+)"', re.M) + + +def parse_makefile_opcodes(repo): + """Return the opcode files enabled in the top-level Makefile OPCODES var + (minus opcodes-rvv), or None if the variable can't be found.""" + try: + with open(os.path.join(repo, "Makefile")) as fh: + m = _OPCODES_RE.search(fh.read()) + except OSError: + return None + if not m: + return None + return [f for f in m.group(1).split() if f != RVV_LOCAL_FILE] + +# The local copy of the RVV spec fed to the riscv_instr.sv generator. It is +# diffed against the upstream canvas: entries whose name is absent upstream are +# local additions riding in the RVV space (e.g. vlx*, vfwdotp, legacy v0.9 +# leftovers) and are rendered as a pseudo-extension of their own; same-name +# entries whose fixed bits differ are reported in a mismatch panel. +RVV_LOCAL_FILE = "opcodes-rvv" +ORIGIN_RVV_LOCAL = "opcodes-rvv (not in spec v1.0)" + +# One distinct, accessible hue per extension (color-blind-aware categorical set). +EXT_COLORS = OrderedDict([ + ("opcodes-rv32b_CUSTOM", "#e6820a"), # orange + ("opcodes-ipu_CUSTOM", "#12897d"), # teal + ("opcodes-frep_CUSTOM", "#b5179e"), # magenta + ("opcodes-dma_CUSTOM", "#7048e8"), # violet + ("opcodes-ssr_CUSTOM", "#c9184a"), # crimson + ("opcodes-smallfloat", "#5c7f00"), # olive + ("opcodes-vfx_CUSTOM", "#0b7285"), # deep cyan + (ORIGIN_RVV_LOCAL, "#8a5a2b"), # brown +]) + +# Reserve hues, assigned deterministically (Makefile order) to extension +# files that have no curated entry above — so a newly added opcodes-* file +# gets a distinct color with zero script changes. +EXTRA_HUES = ["#9c36b5", "#2f9e44", "#e8590c", "#1971c2", + "#a61e4d", "#5f3dc4", "#087f5b", "#d9480f"] + +COLOR_IMPL = "#1f6fd6" # blue : spec, handled by HW +COLOR_UNIMPL = "#b7bdc6" # gray : spec, NOT handled by HW +COLOR_DEAD = "#101317" # black : custom, defined in build but NOT handled by HW +COLOR_CONFLICT = "#e03131" # red border/hatch +COLOR_FREE = "#f5f6f8" # empty slot + +# "Implemented in hardware" = the instruction name is referenced +# (riscv_instr::NAME) in one of the DECODER files below. This is a *decode* +# check only — it deliberately does not verify the logical implementation +# behind the decoder. Different instruction groups decode in different +# front-ends: RVV + custom vector in snitch/spatz_decoder, small-float offload +# in the FPU sequencer, DMA in the AXI DMA front-end. An instruction that no +# decoder references is not implemented -> reclaimable encoding space. +RTL_ROOT = "hw" +DECODER_FILES = [ + "snitch.sv", # scalar core: offload + custom scalar decode + "spatz_decoder.sv", # vector unit decoder + "spatz_fpu_sequencer.sv", # FP offload sequencer + "axi_dma_tc_snitch_fe.sv", # DMA front-end decoder +] + +# OP-V funct3 -> category column. +FUNCT3_CAT = { + 0b000: "OPIVV", 0b100: "OPIVX", 0b011: "OPIVI", + 0b010: "OPMVV", 0b110: "OPMVX", + 0b001: "OPFVV", 0b101: "OPFVF", + 0b111: "OPCFG", # vset* +} +CATEGORY_ORDER = ["OPIVV", "OPIVX", "OPIVI", "OPMVV", "OPMVX", "OPFVV", "OPFVF"] + +OPV = 0x57 +LOADFP = 0x07 +STOREFP = 0x27 +AMO = 0x2f +# Human-readable names for standard major opcodes that custom extensions overload. +MAJOR_NAMES = { + 0x03: "LOAD", 0x07: "LOAD-FP", 0x0f: "MISC-MEM", 0x13: "OP-IMM", + 0x17: "AUIPC", 0x1b: "OP-IMM-32", 0x23: "STORE", 0x27: "STORE-FP", + 0x2f: "AMO", 0x33: "OP", 0x37: "LUI", 0x3b: "OP-32", + 0x43: "MADD", 0x47: "MSUB", 0x4b: "NMSUB", 0x4f: "NMADD", + 0x53: "OP-FP", 0x63: "BRANCH", 0x67: "JALR", 0x6f: "JAL", + 0x73: "SYSTEM", 0x77: "OP-P / vector-crypto", +} +CUSTOM_MAJORS = OrderedDict([ + (0x0b, "CUSTOM-0"), (0x2b, "CUSTOM-1"), + (0x5b, "CUSTOM-2"), (0x7b, "CUSTOM-3"), +]) + + +# --------------------------------------------------------------------------- +# Encoding-line parser +# --------------------------------------------------------------------------- + +class Enc: + """A single decoded instruction: fixed-bit mask + match value (32-bit).""" + __slots__ = ("name", "raw", "mask", "match", "origin", "kind") + + def __init__(self, name, mask, match, origin, kind, raw=""): + self.name = name # normalized: VADD_VV + self.mask = mask # 1 where the bit is fixed + self.match = match # value on the fixed bits + self.origin = origin # 'spec:rv_v' or 'opcodes-vfx_CUSTOM' etc. + self.kind = kind # 'spec' | 'ext' + self.raw = raw + + def field(self, hi, lo): + """Return integer value of bits [hi:lo] if all fixed, else None.""" + width = hi - lo + 1 + m = ((1 << width) - 1) << lo + if (self.mask & m) != m: + return None + return (self.match & m) >> lo + + @property + def opcode(self): + return self.field(6, 0) + + +def normalize_name(name): + """vadd.vv -> VADD_VV (matches riscv-opcodes' SystemVerilog generator).""" + return name.strip().upper().replace(".", "_") + + +_RANGE_RE = re.compile(r"^(\d+)\.\.(\d+)=(.+)$") +_BIT_RE = re.compile(r"^(\d+)=(.+)$") + + +def _int(v): + return int(v, 16) if v.lower().startswith("0x") else int(v, 0) + + +def parse_opcode_line(line): + """Parse a `name ` opcode-file line into (name, mask, match). + + Only fixed bit-ranges/bits contribute to mask/match; bare argument names + (vd, rs1, vm, nf, ...) stay variable. Returns None for non-instruction + lines ($import, $pseudo, comments, blanks). + """ + line = line.split("#", 1)[0].strip() + if not line or line.startswith("$") or line.startswith("@"): + return None + toks = line.split() + if len(toks) < 2: + return None + name = toks[0] + mask = 0 + match = 0 + saw_fixed = False + for tok in toks[1:]: + m = _RANGE_RE.match(tok) + if m: + hi, lo, val = int(m.group(1)), int(m.group(2)), _int(m.group(3)) + width = hi - lo + 1 + fm = ((1 << width) - 1) << lo + mask |= fm + match = (match & ~fm) | ((val << lo) & fm) + saw_fixed = True + continue + m = _BIT_RE.match(tok) + if m: + b, val = int(m.group(1)), _int(m.group(2)) + mask |= (1 << b) + match = (match & ~(1 << b)) | ((val & 1) << b) + saw_fixed = True + continue + # bare arg name (variable) or arg=val (rare; not present in our files) + if "=" in tok: + # unknown named-field assignment -> ignore (leave variable) + pass + if not saw_fixed: + return None + return normalize_name(name), mask, match + + +def parse_opcode_file(path, origin, kind): + out = [] + with open(path) as fh: + for line in fh: + r = parse_opcode_line(line) + if r: + nm, mask, match = r + out.append(Enc(nm, mask, match, origin, kind, raw=line.strip())) + return out + + +# riscv_instr.sv line: localparam logic [31:0] VADD_VV = 32'b000000???...1010111; +_SV_RE = re.compile(r"^\s*localparam\s+logic\s+\[31:0\]\s+(\w+)\s*=\s*32'b([01?]{32})\s*;") + + +def parse_riscv_instr_sv(path): + """Return {NAME: (mask, match)} from the generated SystemVerilog.""" + out = {} + with open(path) as fh: + for line in fh: + m = _SV_RE.match(line) + if not m: + continue + name, bits = m.group(1), m.group(2) + mask = match = 0 + for i, ch in enumerate(bits): # bits[0] is MSB (bit 31) + bitpos = 31 - i + if ch == "?": + continue + mask |= (1 << bitpos) + if ch == "1": + match |= (1 << bitpos) + out[name] = (mask, match) + return out + + +# The generated package that *defines* the encodings — never counts as a "use". +DEFINITION_FILE = "riscv_instr.sv" +_TOKEN_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*") + + +def parse_handled(repo, candidates, rtl_root=RTL_ROOT): + """Return (handled, where) for the instruction names in `candidates`. + + An instruction is "implemented" if its name appears in one of the + DECODER_FILES either qualified (`riscv_instr::NAME`) or unqualified + (`NAME`, when the file does `import riscv_instr::*`). This is a decode + check only — the logic behind the decoder is not verified. We match + whole-word tokens against the known candidate set, which makes unqualified + matching safe (no partial/substring hits), and we only scan files that + reference the riscv_instr package at all, so an unrelated signal can't + accidentally mark an instruction as live. + """ + handled = set() + where = defaultdict(set) + outside = defaultdict(set) # candidate refs in files NOT in DECODER_FILES + root = os.path.join(repo, rtl_root) + for dirpath, _, filenames in os.walk(root): + for fn in filenames: + if not fn.endswith((".sv", ".svh")) or fn == DEFINITION_FILE: + continue + is_decoder = fn in DECODER_FILES + p = os.path.join(dirpath, fn) + try: + with open(p, errors="ignore") as fh: + txt = fh.read() + except OSError: + continue + if "riscv_instr" not in txt: # file doesn't use the ISA pkg + continue + for t in set(_TOKEN_RE.findall(txt)): + if t in candidates: + if is_decoder: + handled.add(t) + where[t].add(fn) + else: + outside[fn].add(t) + return handled, {k: sorted(v) for k, v in where.items()}, dict(outside) + + +# --------------------------------------------------------------------------- +# Conflict detection +# --------------------------------------------------------------------------- + +def encodings_overlap(a, b): + """True if some 32-bit word matches both a and b (compatible fixed bits).""" + common = a.mask & b.mask + return (a.match & common) == (b.match & common) + + +def find_conflicts(encs): + """Pairwise overlaps between encodings from *different* origins. + + Bucketed by major opcode first to keep it tractable. + """ + by_major = defaultdict(list) + for e in encs: + op = e.opcode + by_major[op].append(e) + conflicts = [] + for op, group in by_major.items(): + n = len(group) + for i in range(n): + for j in range(i + 1, n): + a, b = group[i], group[j] + if a.origin == b.origin: + continue + if encodings_overlap(a, b): + conflicts.append((a, b)) + return conflicts + + +# --------------------------------------------------------------------------- +# Repo / spec discovery +# --------------------------------------------------------------------------- + +def find_repo(start): + d = os.path.abspath(start) + while d != "/": + if os.path.isdir(os.path.join(d, "hw", "ip", "snitch", "src")): + return d + d = os.path.dirname(d) + raise SystemExit("Could not locate spatz repo root (looked for hw/ip/snitch/src)") + + +def ensure_upstream(cache_dir, commit): + repo = os.path.join(cache_dir, "riscv-opcodes-upstream") + if not os.path.isdir(os.path.join(repo, "extensions")): + os.makedirs(cache_dir, exist_ok=True) + print(f" cloning upstream riscv-opcodes into {repo} ...") + subprocess.check_call(["git", "clone", "--quiet", UPSTREAM_URL, repo]) + try: + subprocess.check_call(["git", "-C", repo, "checkout", "--quiet", commit]) + except subprocess.CalledProcessError: + print(" WARN: could not checkout pinned commit; using current HEAD", + file=sys.stderr) + return repo + + +# --------------------------------------------------------------------------- +# Classification +# --------------------------------------------------------------------------- + +def classify(kind, name, handled): + """Return the visual state for an instruction. + + spec + handled -> 'impl' (blue) + spec + unhandled -> 'unimpl' (gray) + ext + handled -> 'impl' (extension color) + ext + unhandled -> 'dead' (black: in build, not decoded/executed) + """ + is_handled = name in handled + if kind == "spec": + return "impl" if is_handled else "unimpl" + return "impl" if is_handled else "dead" + + +# --------------------------------------------------------------------------- +# HTML rendering +# --------------------------------------------------------------------------- + +def esc(s): + return (s.replace("&", "&").replace("<", "<").replace(">", ">")) + + +def bits_str(mask, match): + out = [] + for b in range(31, -1, -1): + if not (mask >> b) & 1: + out.append("?") + else: + out.append("1" if (match >> b) & 1 else "0") + s = "".join(out) + return s[0:1] + " " + s[1:8] + " " + s[8:13] + " " + s[13:18] + \ + " " + s[18:20] + " " + s[20:25] + " " + s[25:32] + + +def cell_color(entry): + if entry is None: + return COLOR_FREE + if entry["conflict"]: + return None # rendered with hatch + if entry["kind"] == "spec": + return COLOR_IMPL if entry["state"] == "impl" else COLOR_UNIMPL + # custom extension + if entry["state"] == "dead": + return COLOR_DEAD + return EXT_COLORS.get(entry["origin"], "#888") + + +def render_html(ctx): + P = [] + a = P.append + a("") + + a(f"

{esc(ctx['title'])}

") + canvas = "rv_v (RVV v1.0)" + (" + rv_zv* crypto" if ctx["crypto"] else "") + spec_url = (f"https://github.com/riscv/riscv-opcodes/blob/" + f"{ctx['spec_commit']}/extensions/rv_v") + a("
") + a(f"Ground truth: upstream riscv/riscv-opcodes " + f"{canvas} " + f"@ {ctx['spec_commit'][:10]}
") + a(f"Implemented = decoded in {', '.join(DECODER_FILES)} " + f"(decode check only; the logic behind the decoder is not verified)
") + a("Generated read-only — no repo files modified.") + a("
") + if ctx["missed"]: + a("
") + a("⚠ Possible unscanned decoder: the following RTL files reference " + "instructions that no scanned decoder handles — if one is a new " + "decoder, re-run with --decoder <file>:
") + for fn, names in sorted(ctx["missed"].items()): + only = sorted(names) + a(f"{esc(fn)}: {esc(', '.join(only[:8]))}" + f"{' …' if len(only) > 8 else ''}
") + a("
") + a("
This map answers four questions:
") + a("
    ") + a(f"
  1. Which RVV v1.0 spec instructions does the hardware decode " + f"(blue) — and which not (gray)?
  2. ") + a("
  3. Which custom instructions are hardware-backed (one hue per extension)?
  4. ") + a("
  5. Which instructions are in the toolchain (riscv_instr.sv) but not decoded " + "(black — see the dedicated section)?
  6. ") + a("
  7. Where does the implementation deviate from the spec " + "(mismatch / overlap panels)?
  8. ") + a("
") + + # legend — ordered color-code table + a("") + a(f"" + "") + a(f"" + "") + a(f"" + "") + ext_sws = "".join( + f"{esc(ext.replace('opcodes-', ''))}" + for ext in ctx["ext_files"] + [ORIGIN_RVV_LOCAL]) + a("" + f"") + a(f"" + "") + a("" + "") + a(f"" + "") + a("
spec — implemented: RVV v1.0 instruction decoded by the hardware
spec — not implemented: RVV v1.0 instruction with no decoder reference
spec — partially implemented: slot holds several sub-encodings, " + "only some decoded (hover for the list)
custom — implemented, one hue per extension:
{ext_sws}
custom — in toolchain, not decoded: has a riscv_instr.sv localparam " + "but no decoder reference (reclaimable)
conflict: two instructions' fixed bits genuinely overlap
free: unallocated encoding space
") + + # stats + s = ctx["stats"] + a("
") + a(f"spec instrs: {s['spec_total']} " + f"({s['spec_impl']} impl / " + f"{s['spec_unimpl']} not)") + a(f"custom-ext instrs: {s['ext_total']} " + f"({s['ext_live']} live / {s['ext_dead']} dead)") + a(f"active conflicts: {s['conflicts']}") + a(f"latent (paper) conflicts: {s['latent']}") + a(f"spec deviations: {s['rvv_mismatch']} mismatches, " + f"{s['spec_overlap']} custom-on-spec overlaps") + a(f"OP-V free slots: {s['opv_free']}/448") + a("
") + + # per-extension breakdown + a("

Extension status — which shipped extensions are live in this hardware

") + a("") + for b in ctx["ext_breakdown"]: + cls = " class='alldead'" if b["live"] == 0 and b["total"] else "" + dead_txt = (f"{b['dead']}" + if b["dead"] else "0") + a(f"" + f"") + a("
extensiondefinedimplementeddead (reclaimable)
{esc(b['name'])}{b['total']}{b['live']}{dead_txt}
") + a("
“dead” = defined in the Makefile OPCODES build (present in riscv_instr.sv) " + "but referenced by no decoder — encoding space reserved on paper, free to reclaim.
") + + # toolchain staleness (opcode files vs generated riscv_instr.sv) + if ctx["tc_missing"] or ctx["tc_enc_diff"]: + a("
⚠ riscv_instr.sv is STALE vs the " + "opcode files — run make update_opcodes. Missing: " + f"{esc(', '.join(ctx['tc_missing']) or '—')} · encoding drift: " + f"{esc(', '.join(ctx['tc_enc_diff']) or '—')}
") + + # point 3: in toolchain, not decoded — the full list, per extension + n_dead = sum(len(d) for _, _, d in ctx["dead_groups"]) + a(f"

In toolchain but not decoded by hardware " + f"— " + f"{n_dead} instructions have a riscv_instr.sv localparam but appear in no " + f"decoder; their encoding space is reclaimable

") + if not ctx["dead_groups"]: + a("
None — every toolchain-defined custom instruction is decoded. 🎉
") + for origin, col, dead in ctx["dead_groups"]: + a(f"
" + f"{esc(origin.replace('opcodes-', ''))} — {len(dead)} dead definitions") + a("
") + for entry in sorted(dead, key=lambda e: e["match"] & 0xffffffff): + a(f"{esc(entry['name'])}") + a("
") + + # conflicts panels + a("

Active encoding conflicts — both sides live in hardware

") + if ctx["conflicts"]: + a("
") + for a1, b1 in ctx["conflicts"]: + a(f"
{esc(a1.name)} [{esc(a1.origin)}]" + f" ⨯ {esc(b1.name)} [{esc(b1.origin)}]
" + f"  {bits_str(a1.mask,a1.match)}
  {bits_str(b1.mask,b1.match)}
") + a("
") + else: + a("
None. No two hardware-implemented instructions collide. 🎉
") + + if ctx["latent"]: + a("
Latent conflicts (" + str(len(ctx["latent"])) + + ") — collide only in the opcode files; ≥1 side is a dead definition") + a("
") + for a1, b1 in ctx["latent"]: + a(f"
{esc(a1.name)} [{esc(a1.origin)}]" + f" ⨯ {esc(b1.name)} [{esc(b1.origin)}]
") + a("
") + + # point 4: deviations from the RVV spec + a("

⚠ Deviations from the RVV v1.0 spec " + "— where our " + "implementation and the spec disagree about an encoding

") + if not ctx["rvv_mismatch"] and not ctx["spec_overlap"]: + a("
None. The implemented encodings agree with the spec. 🎉
") + + if ctx["rvv_mismatch"]: + a("
Encoding mismatches — same instruction name, " + "different fixed bits between local opcodes-rvv and the spec; the " + "generated decoder masks deviate from the spec:
") + a("
") + for loc1, up1 in ctx["rvv_mismatch"]: + live = (" (decoded in HW)" + if loc1.name in ctx["handled"] else "") + a(f"
" + f"{esc(loc1.name)}{live}
" + f"  local: {bits_str(loc1.mask, loc1.match)}
" + f"  spec : {bits_str(up1.mask, up1.match)}
") + a("
") + + if ctx["spec_overlap"]: + a("
Custom instructions on spec-claimed encodings — " + "a custom instruction's fixed bits overlap an RVV spec instruction:
") + a("
") + for a1, b1, active in ctx["spec_overlap"]: + sp, cu = (a1, b1) if a1.kind == "spec" else (b1, a1) + tag = ("(both decoded — active collision)" + if active else "(latent)") + a(f"
" + f"{esc(cu.name)} [{esc(cu.origin)}]" + f" occupies {esc(sp.name)} [{esc(sp.origin)}] {tag}
" + f"  custom: {bits_str(cu.mask, cu.match)}
" + f"  spec : {bits_str(sp.mask, sp.match)}
") + a("
") + + # OP-V grid + a("

OP-V space (major 0x57) — funct6 × category

") + a(f"
Rows = funct6 (bits 31..26). Columns = category (funct3). " + f"Empty = reusable slot. A “name +N” label means {s['opv_multi']} slot(s) " + "hold several legal sub-encodings distinguished by vm/vs1/vs2/rs1 (e.g. vfmerge.vfm & " + "vfmv.v.f) — hover to see each and its status. Red is reserved for genuine bit-overlaps.
") + a(render_opv_grid(ctx)) + + # custom major-opcode grids (funct7 × funct3) + a("

Custom major opcodes — funct7 × funct3 grids

") + a("
CUSTOM-0/1/2/3 (0x0b/2b/5b/7b). Rows = funct7 " + "(bits 31..25), cols = funct3. An op that uses funct7 bits as " + "immediate/operand (e.g. frep, *.vrf) fills every funct7 row its " + "encoding covers — up to a whole column. " + "Click a header to expand/collapse.
") + for op, name, ents in ctx["custom_grids"]: + a(render_custom_grid(op, name, ents)) + + # per-section extra tables + for sec in ctx["sections"]: + a(f"

{esc(sec['title'])}

") + if sec.get("note"): + a(f"
{esc(sec['note'])}
") + a(render_list_section(sec)) + + # tooltip + theme JS + a(""" +
+ + """) + return "\n" + "\n".join(P) + + +def tip_for(entry): + if entry is None: + return "free encoding slot" + lines = [f"{esc(entry['name'])}", + f"{esc(entry['origin'])}", + f"{bits_str(entry['mask'], entry['match'])}"] + if entry["state"] == "impl": + w = entry.get("where") or [] + lines.append("decoded by" + (": " + esc(", ".join(w)) if w else " HW")) + elif entry["kind"] == "spec": + lines.append("spec — NOT implemented (no decoder reference)") + else: + lines.append("custom — in toolchain (riscv_instr.sv), NOT decoded (reclaimable)") + if entry["conflict"]: + lines.append("⚠ CONFLICT: " + + esc(", ".join(entry["conflict_with"])) + "") + return "
".join(lines) + + +def cell_visual(entries, slot=None): + """Aggregate a list of entries sharing one encoding slot into + (css_class, inline_style, label, tooltip).""" + n = len(entries) + label = entries[0]["name"] + (f" +{n - 1}" if n > 1 else "") + + # genuine (active) overlap takes precedence -> red + if any(e["conflict"] for e in entries): + return "cell conflict", "", label, multi_tip(entries, slot) + + specs = [e for e in entries if e["kind"] == "spec"] + exts = [e for e in entries if e["kind"] == "ext"] + cls, style = "cell", "" + if exts: + live = [e for e in exts if e["state"] == "impl"] + if live: + style = f"background:{EXT_COLORS.get(live[0]['origin'], '#888')}" + else: + cls += " dead" + style = f"background:{COLOR_DEAD}" + else: + impl = sum(1 for e in specs if e["state"] == "impl") + if impl == len(specs): + style = f"background:{COLOR_IMPL}" + elif impl == 0: + style = f"background:{COLOR_UNIMPL}" + else: # partially implemented slot -> two-tone (not red) + style = (f"background:linear-gradient(135deg,{COLOR_IMPL} 0 50%," + f"{COLOR_UNIMPL} 50% 100%)") + return cls, style, label, multi_tip(entries, slot) + + +def multi_tip(entries, slot=None): + if len(entries) == 1: + return (esc(slot) + "
" if slot else "") + tip_for(entries[0]) + head = [f"{len(entries)} sub-encodings" + + (f" in {esc(slot)}" if slot else "") + " (differ by other fields):"] + for e in entries: + if e["state"] == "impl": + glyph = "✓ impl" + elif e["kind"] == "spec": + glyph = "✗ not impl" + else: + glyph = "✗ dead" + head.append(f" {esc(e['name'])} [{esc(e['origin'].replace('opcodes-',''))}] — {glyph}") + return "
".join(head) + + +def render_opv_grid(ctx): + grid = ctx["opv_grid"] # {(funct6,cat): [entry, ...]} + P = ["", ""] + for cat in CATEGORY_ORDER: + P.append(f"") + P.append("") + for f6 in range(64): + P.append(f"") + for cat in CATEGORY_ORDER: + entries = grid.get((f6, cat)) + if not entries: + P.append("") + continue + cls, style, label, tip = cell_visual(entries, f"funct6=0x{f6:02x} {cat}") + P.append(f"") + P.append("") + P.append("
funct6{cat}
{f6:06b}
0x{f6:02x}
" + "" + f"{esc(label)}
") + return "".join(P) + + +def build_custom_grid(entries): + """Slot custom-major entries into a funct7(31..25) × funct3(14..12) grid. + + Every entry occupies exactly the funct7 rows its fixed bits allow: a fully + fixed funct7 -> 1 row; funct6-only -> 2 rows; an op using bits 31..25 as + immediate/operand (frep, *.vrf) -> every compatible row, up to the whole + column. Returns (cells, spans, other) where: + cells[(funct7, funct3)] = [entry, ...] + spans = [(entry, funct3, nrows), ...] (nrows > 1) + other = [entry, ...] (funct3 not fixed -> can't place) + """ + cells = defaultdict(list) + spans = [] + other = [] + for ent in entries: + m, v = ent["mask"], ent["match"] + if (m >> 12) & 0x7 != 0x7: + other.append(ent) + continue + f3 = (v >> 12) & 0x7 + m7 = (m >> 25) & 0x7f # fixed funct7 bits + v7 = (v >> 25) & 0x7f + rows = [f7 for f7 in range(128) if (f7 & m7) == v7] + for f7 in rows: + cells[(f7, f3)].append(ent) + if len(rows) > 1: + spans.append((ent, f3, len(rows))) + return cells, spans, other + + +def render_custom_grid(op, name, entries): + cells, spans, other = build_custom_grid(entries) + used = len(cells) + free = 128 * 8 - used + live = sum(1 for e in entries if e["state"] == "impl") + exts = sorted({e["origin"].replace("opcodes-", "") for e in entries}) + + P = [] + P.append(f"
{name} " + f"(0x{op:02x}) — {len(entries)} instrs " + f"[{esc(', '.join(exts))}] · {live} live · " + f"{used} funct7×funct3 slots used · {free} free") + if other: + P.append("
note: " + str(len(other)) + + " instr(s) here don't fix funct3 and can't be gridded — " + + esc(", ".join(e["name"] for e in other)) + "
") + # multi-row spans: ops using (some of) funct7 as immediate/operand bits + if spans: + P.append("
ops using funct7 bits as immediate/operand " + "— each fills every funct7 row its encoding covers:
" + "
") + for ent, f3, nrows in sorted(spans, key=lambda s: (s[1], s[0]["name"])): + cls, style, label, tip = cell_visual( + [ent], f"funct3={f3}, spans {nrows} funct7 rows") + chip = "chip" + (" dead" if "dead" in cls else "") + P.append(f"" + f"f3={f3}: {esc(ent['name'])} ×{nrows}") + P.append("
") + # funct7 × funct3 grid + P.append("
") + P.append("") + for f3 in range(8): + P.append(f"") + P.append("") + for f7 in range(128): + P.append(f"") + for f3 in range(8): + ents = cells.get((f7, f3)) + if not ents: + P.append("") + continue + cls, style, label, tip = cell_visual( + ents, f"funct7=0x{f7:02x} funct3={f3}") + P.append(f"") + P.append("") + P.append("
funct7f3={f3}
{f7:07b}
0x{f7:02x}
" + "" + f"{esc(label)}
") + return "".join(P) + + +def render_list_section(sec): + """Render a classified chip list for non-OP-V spaces (LS / config / custom).""" + P = ["
"] + for entry in sorted(sec["entries"], key=lambda e: (e["match"] & 0xffffffff)): + col = cell_color(entry) + chip_cls = "chip" + if entry["conflict"]: + style = "background:repeating-linear-gradient(45deg,#e03131,#e03131 3px,#ff8787 3px,#ff8787 6px)" + else: + style = f"background:{col}" + if entry["state"] == "dead": + chip_cls += " dead" + P.append(f"{esc(entry['name'])}") + if not sec["entries"]: + P.append("(none)") + P.append("
") + return "".join(P) + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def make_entry(e, handled, where): + state = classify(e.kind, e.name, handled) + return { + "name": e.name, "origin": e.origin, "kind": e.kind, + "mask": e.mask, "match": e.match, + "state": state, + "where": where.get(e.name, []), + "conflict": False, "conflict_with": [], + } + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--repo", help="spatz repo root (auto-detected by default)") + ap.add_argument("--out", default=None, help="output HTML path") + ap.add_argument("--spec-commit", default=UPSTREAM_COMMIT) + ap.add_argument("--crypto", action="store_true", + help="also draw the ratified vector-crypto exts (rv_zv*) on " + "the spec canvas (default: base rv_v v1.0 only)") + ap.add_argument("--decoder", action="append", default=[], metavar="FILE.sv", + help="treat an additional RTL file as a decoder " + "(repeatable; extends the built-in list)") + args = ap.parse_args() + DECODER_FILES.extend(f for f in args.decoder if f not in DECODER_FILES) + + here = os.path.dirname(os.path.abspath(__file__)) + repo = args.repo or find_repo(here) + out = args.out or os.path.join(here, "encoding_map.html") + local_opc = os.path.join(repo, "sw", "toolchain", "riscv-opcodes") + sv_path = os.path.join(repo, "hw", "ip", "snitch", "src", "riscv_instr.sv") + + print("Encoding-space visualizer") + print(f" repo: {repo}") + + # 1. spec canvas (ground truth: rv_v v1.0; rv_zv* only on request) + up = ensure_upstream(os.path.join(here, ".cache"), args.spec_commit) + spec_files = list(SPEC_FILES) + if args.crypto: + extdir = os.path.join(up, "extensions") + spec_files += sorted(f for f in os.listdir(extdir) + if f.startswith("rv_zv")) + spec = [] + for f in spec_files: + p = os.path.join(up, "extensions", f) + if os.path.isfile(p): + spec += parse_opcode_file(p, f"spec:{f}", "spec") + print(f" spec instrs parsed: {len(spec)} from {spec_files}") + + # 2. custom extensions (local) — the set currently enabled in the + # top-level Makefile OPCODES variable. + ext_files = parse_makefile_opcodes(repo) + if ext_files is None: + print(" WARN: could not parse OPCODES from Makefile; using fallback list", + file=sys.stderr) + ext_files = list(EXT_FILES_FALLBACK) + print(f" enabled custom exts (Makefile OPCODES): " + f"{', '.join(f.replace('opcodes-', '') for f in ext_files)}") + # deterministic hue for any ext file without a curated color + extra_hues = iter(EXTRA_HUES) + for f in ext_files: + if f not in EXT_COLORS: + EXT_COLORS[f] = next(extra_hues, "#888") + exts = [] + for f in ext_files: + p = os.path.join(local_opc, f) + if os.path.isfile(p): + exts += parse_opcode_file(p, f, "ext") + else: + print(f" WARN: missing local ext file {f}", file=sys.stderr) + print(f" custom-ext instrs parsed: {len(exts)}") + + # 2b. local opcodes-rvv vs upstream spec canvas. + # * name absent upstream -> local addition riding in the RVV space + # (vlx*, vfwdotp, legacy v0.9 leftovers) -> pseudo-extension, so its + # occupied encoding space is drawn instead of reading as free. + # * same name, different fixed bits -> encoding mismatch panel. + rvv_mismatch = [] + rvv_path = os.path.join(local_opc, RVV_LOCAL_FILE) + if os.path.isfile(rvv_path): + up_by_name = {e.name: e for e in spec} + n_local_only = 0 + for e in parse_opcode_file(rvv_path, ORIGIN_RVV_LOCAL, "ext"): + u = up_by_name.get(e.name) + if u is None: + exts.append(e) + n_local_only += 1 + elif (u.mask, u.match) != (e.mask, e.match): + rvv_mismatch.append((e, u)) + print(f" local {RVV_LOCAL_FILE}: {n_local_only} instrs not in spec v1.0, " + f"{len(rvv_mismatch)} encoding mismatches vs upstream spec") + else: + print(f" WARN: missing local {RVV_LOCAL_FILE}", file=sys.stderr) + + # 3. toolchain truth: riscv_instr.sv (generated from the OPCODES files). + # "in toolchain" = the name has a localparam there. Also verify the + # opcode files and the generated file have not drifted apart. + sv_encs = parse_riscv_instr_sv(sv_path) + tc_missing = sorted(e.name for e in exts if e.name not in sv_encs) + tc_enc_diff = sorted(e.name for e in exts + if e.name in sv_encs + and sv_encs[e.name] != (e.mask, e.match)) + print(f" toolchain (riscv_instr.sv): {len(sv_encs)} localparams; " + f"opcode-file entries missing there: {len(tc_missing)}, " + f"encoding drift: {len(tc_enc_diff)}") + if tc_missing or tc_enc_diff: + print(" WARN: riscv_instr.sv is stale vs the opcode files — " + "run `make update_opcodes`", file=sys.stderr) + + # 4. implemented-in-hardware set: name referenced in one of the DECODER + # files (decode check only). Candidate names = everything we classify. + # `outside` = candidate refs in non-decoder RTL — usually FUs consuming + # an already-decoded op, but a NEW decoder file would show up here too. + candidates = {e.name for e in spec} | {e.name for e in exts} + handled, where, outside = parse_handled(repo, candidates) + print(f" implemented (decoded in {', '.join(DECODER_FILES)}): " + f"{len(handled & candidates)}") + missed = {fn: names for fn, names in outside.items() + if names - handled} # names decoded NOWHERE but referenced here + if missed: + for fn, names in sorted(missed.items()): + only = sorted(names - handled) + print(f" WARN: {fn} references {len(only)} instr(s) no decoder " + f"handles (new decoder? add --decoder {fn}): " + f"{', '.join(only[:6])}{' …' if len(only) > 6 else ''}", + file=sys.stderr) + + # 5. conflicts (across all sources), split into active vs latent. + # active = both sides are actually decoded by HW (real silicon collision) + # latent = at least one side is defined-but-dead (paper collision only) + all_encs = spec + exts + raw_conflicts = find_conflicts(all_encs) + conflicts, latent = [], [] + for a1, b1 in raw_conflicts: + if a1.name in handled and b1.name in handled: + conflicts.append((a1, b1)) + else: + latent.append((a1, b1)) + + # custom instructions sitting on encodings the RVV spec has claimed — + # these are spec deviations, surfaced in their own panel. + spec_overlap = [(a1, b1, a1.name in handled and b1.name in handled) + for a1, b1 in raw_conflicts + if {a1.kind, b1.kind} == {"spec", "ext"}] + + # dedup spec by name (aliases): keep first + seen = set() + spec_u = [] + for e in spec: + if e.name in seen: + continue + seen.add(e.name) + spec_u.append(e) + + # build entries and mark conflicts (only active conflicts colour a cell red) + entries = {} + for e in spec_u + exts: + entries[id(e)] = make_entry(e, handled, where) + for a1, b1 in conflicts: + for x, y in ((a1, b1), (b1, a1)): + ent = entries.get(id(x)) + if ent: + ent["conflict"] = True + ent["conflict_with"].append(f"{y.name}[{y.origin}]") + + # 5. place into OP-V grid + custom grids + list sections (drop NOTHING) + opv_grid = {} # (funct6, category) -> [entry, ...] + ls_entries, cfg_entries = [], [] + custom_sections = OrderedDict((op, []) for op in CUSTOM_MAJORS) + other_by_major = defaultdict(list) # standard opcodes overloaded by exts + + for e in spec_u + exts: + ent = entries[id(e)] + op = e.opcode + if op == OPV: + f3 = e.field(14, 12) + f6 = e.field(31, 26) + cat = FUNCT3_CAT.get(f3) if f3 is not None else None + if cat == "OPCFG" or f6 is None or cat is None: + cfg_entries.append(ent) + else: + # A (funct6, category) cell can legitimately hold several + # sub-encodings distinguished by vm / vs1 / vs2 / rs1 (e.g. + # vfmerge.vfm vs vfmv.v.f, or the whole VFCVT family). Keep all; + # red is reserved for GENUINE bit-overlaps (see active conflicts). + opv_grid.setdefault((f6, cat), []).append(ent) + elif op in (LOADFP, STOREFP): + ls_entries.append(ent) + elif op in CUSTOM_MAJORS: + custom_sections[op].append(ent) + else: + # everything else (AMO, and standard opcodes overloaded by custom + # extensions such as rv32b in OP/OP-IMM and smallfloat in OP-FP/FMA) + other_by_major[op].append(ent) + + # custom-major grids (funct7 × funct3) + custom_grids = [(op, CUSTOM_MAJORS[op], custom_sections[op]) + for op in CUSTOM_MAJORS if custom_sections[op]] + + sections = [] + sections.append({"title": "Vector load/store (major 0x07 / 0x27)", + "note": "Unit-stride, strided, indexed, segment, whole-register, mask, fault-only-first.", + "entries": ls_entries}) + sections.append({"title": "Vector config (vset*)", + "note": "OP-V funct3=0x7; encoded via bits 31/30 rather than a plain funct6.", + "entries": cfg_entries}) + # standard opcodes overloaded by custom extensions (previously dropped) + for op in sorted(other_by_major): + ents = other_by_major[op] + exts_here = sorted({e["origin"].replace("opcodes-", "") + for e in ents if e["kind"] == "ext"}) + label = MAJOR_NAMES.get(op, f"major 0x{op:02x}") + note = ("custom ext(s) " + ", ".join(exts_here) + + " overloading the standard " + label + " encoding") if exts_here \ + else "spec instructions in " + label + sections.append({"title": f"{label} (0x{op:02x})", + "note": note, "entries": ents}) + + dropped = sum(len(v) for v in other_by_major.values()) + print(f" routed to standard/other majors (not dropped): {dropped}") + + # per-extension live/dead breakdown (incl. the not-in-spec rvv pseudo-ext) + ext_breakdown = [] + for f in ext_files + [ORIGIN_RVV_LOCAL]: + names = [e.name for e in exts if e.origin == f] + live = sum(1 for n in names if n in handled) + ext_breakdown.append({ + "name": f.replace("opcodes-", ""), "origin": f, + "total": len(names), "live": live, "dead": len(names) - live, + "color": EXT_COLORS.get(f, "#888"), + }) + + # "in toolchain, not in hardware": every custom-side instruction that has + # a riscv_instr.sv localparam but no decoder reference, grouped by origin. + dead_groups = [] + for f in ext_files + [ORIGIN_RVV_LOCAL]: + dead = [entries[id(e)] for e in exts + if e.origin == f and e.name not in handled + and e.name in sv_encs] + if dead: + dead_groups.append((f, EXT_COLORS.get(f, "#888"), dead)) + + stats = { + "spec_total": len(spec_u), + "spec_impl": sum(1 for e in spec_u if e.name in handled), + "spec_unimpl": sum(1 for e in spec_u if e.name not in handled), + "ext_total": len(exts), + "ext_live": sum(1 for e in exts if e.name in handled), + "ext_dead": sum(1 for e in exts if e.name not in handled), + "conflicts": len(conflicts), + "latent": len(latent), + "rvv_mismatch": len(rvv_mismatch), + "spec_overlap": len(spec_overlap), + "opv_free": 448 - len(opv_grid), + "opv_multi": sum(1 for v in opv_grid.values() if len(v) > 1), + } + + ctx = { + "title": "RVV & custom encoding-space map — Spatz", + "spec_commit": args.spec_commit, + "crypto": args.crypto, + "ext_files": ext_files, + "missed": {fn: names - handled for fn, names in missed.items()}, + "opv_grid": opv_grid, + "custom_grids": custom_grids, + "sections": sections, + "conflicts": conflicts, + "latent": latent, + "rvv_mismatch": rvv_mismatch, + "spec_overlap": spec_overlap, + "dead_groups": dead_groups, + "tc_missing": tc_missing, + "tc_enc_diff": tc_enc_diff, + "handled": handled, + "ext_breakdown": ext_breakdown, + "stats": stats, + } + html = render_html(ctx) + with open(out, "w") as fh: + fh.write(html) + print(f"\n wrote {out}") + print(f" spec {stats['spec_total']} (impl {stats['spec_impl']}, " + f"unimpl {stats['spec_unimpl']}), ext {stats['ext_total']} " + f"(live {stats['ext_live']}, dead {stats['ext_dead']}), " + f"active-conflicts {stats['conflicts']}, latent {stats['latent']}, " + f"OP-V free {stats['opv_free']}/448") + + +if __name__ == "__main__": + main()