diff --git a/.claude/skills/extract-instructions-from-subsection/SKILL.md b/.claude/skills/extract-instructions-from-subsection/SKILL.md new file mode 100644 index 0000000000..fb3ceb439d --- /dev/null +++ b/.claude/skills/extract-instructions-from-subsection/SKILL.md @@ -0,0 +1,115 @@ +--- +name: extract-instructions-from-subsection +description: Extract RISC-V instruction names from a named subsection of an AsciiDoc file and write them to /tmp/.yaml. +argument-hint: +allowed-tools: Read, Bash, Write +--- + +Copyright (c) 2026 Qualcomm Technologies, Inc. and/or its subsidiaries. +SPDX-License-Identifier: BSD-3-Clause + +Extract all RISC-V instruction names mentioned in the specified subsection of the given AsciiDoc file, then write them to `/tmp/.yaml`, where `` is argument 1 lowercased with spaces replaced by hyphens (e.g., `"Multiplication Operations"` → `/tmp/multiplication-operations.yaml`). + +## Arguments + +$ARGUMENTS + +- **Argument 1**: The subsection title to search for (e.g., `"Multiplication Operations"` or `"Integer Register-Immediate Instructions"`). +- **Argument 2**: Path to the AsciiDoc file (e.g., `ext/riscv-isa-manual/src/m-st-ext.adoc`). + +If either argument is missing, ask the user to provide it. + +## Steps + +### 1. Read the AsciiDoc file + +Read the full content of the AsciiDoc file given as argument 2. + +### 2. Locate the subsection + +Find the subsection whose title matches argument 1. AsciiDoc section headings use `=` prefixes: +- `== Title` — level 1 (chapter) +- `=== Title` — level 2 +- `==== Title` — level 3 +- `===== Title` — level 4 + +Match the subsection title case-insensitively. The subsection's content starts on the line after the heading and ends just before the next heading of equal or higher level (i.e., same or fewer `=` characters). + +### 3. Identify NOTE blocks to skip + +Before scanning for instructions, mark all NOTE blocks in the subsection so they can be excluded. AsciiDoc NOTE blocks appear in two forms: + +- **Delimited block**: starts with `[NOTE]` followed by `====` on the next line, and ends at the closing `====`. +- **Inline note**: a single line starting with `NOTE:` (no delimiter). + +Any instruction name that appears **only** inside NOTE blocks — and nowhere else in the subsection — must be excluded from the output. If an instruction appears both inside and outside a NOTE block, include it. + +### 4. Extract instruction names + +Scan the non-NOTE text of the subsection for RISC-V instruction names. Instruction names appear as **uppercase tokens** in the prose. Use the following rules to identify them: + +**Patterns that indicate an instruction name:** +- All-uppercase words of 2–10 characters that consist only of letters, digits, dots, and brackets (e.g., `ADD`, `ADDI`, `MULHSU`, `FENCE.TSO`, `LR.W`, `SC.D`, `C.ADD`) +- Uppercase tokens inside backticks: `` `ADD` ``, `` `JALR` `` +- Uppercase tokens in AsciiDoc index entries: `(((MUL, MULH)))` — extract each comma-separated token +- Uppercase tokens in AsciiDoc comment lines like `//.Integer register-register` — skip these (they are labels, not instructions) + +**Exclude pseudoinstructions:** +The prose explicitly signals pseudoinstructions with the phrase "assembler pseudoinstruction" or "pseudoinstruction" adjacent to the name, e.g.: +- `assembler pseudoinstruction SNEZ _rd, rs_` +- `assembler pseudoinstruction MV _rd, rs1_` +- `assembler pseudoinstruction SEQZ _rd, rs_` +- `assembler pseudoinstruction NOT _rd, rs_` +- `assembler pseudoinstruction J` +- `assembler pseudoinstruction RET` +- `assembler pseudoinstruction JR` + +Any token introduced by "pseudoinstruction" (with or without "assembler") must be excluded, even if it appears elsewhere in the subsection outside a pseudoinstruction context. Collect all pseudoinstruction names first, then exclude them from the final list. + +**The following tokens should never be considered instruction names:** +`XLEN`, `RV32`, `RV64`, `RV32I`, `RV64I`, `RV128I`, `ISA`, `ABI`, `PC`, `CSR`, `IALIGN`, `BTB`, `RAS`, `FPGA`, `MIPS`, `RISC`, `RISCV`, `RISC-V`, `L`, `M`, `S`, `B`, `J`, `R`, `I`, `U` + +Single-letter tokens (`R`, `I`, `S`, `B`, `U`, `J`) are format names, not instructions — exclude them. + +Tokens that are register names (`x0`–`x31`, `rd`, `rs1`, `rs2`) — exclude them. + +### 5. Deduplicate and normalize + +- Convert all extracted names to **lowercase** (matching the RISC-V Unified Database YAML file naming convention, e.g., `ADD` → `add`, `MULHSU` → `mulhsu`, `FENCE.TSO` → `fence.tso`, `LR.W` → `lr.w`) +- Remove duplicates +- Sort alphabetically + +### 6. Write the output + +Derive the output filename from argument 1: lowercase it and replace spaces with hyphens (e.g., `"Multiplication Operations"` → `multiplication-operations`). Write `/tmp/.yaml` with the following format: + +```yaml +instructions: + - add + - addi + - mul + - mulh +``` + +Use the Write tool to create the file. + +### 7. Report + +Print a summary: +- The subsection title found +- The number of instructions extracted +- The path written (e.g., `/tmp/multiplication-operations.yaml`) +- The list of instructions + +## Example + +For subsection `"Multiplication Operations"` in `ext/riscv-isa-manual/src/m-st-ext.adoc`, the output file is `/tmp/multiplication-operations.yaml`: + +```yaml +instructions: + - mul + - mulh + - mulhsu + - mulhu + - mulw +``` diff --git a/.claude/skills/model-instruction-from-spec/SKILL.md b/.claude/skills/model-instruction-from-spec/SKILL.md new file mode 100644 index 0000000000..8a9993261d --- /dev/null +++ b/.claude/skills/model-instruction-from-spec/SKILL.md @@ -0,0 +1,191 @@ +--- +name: model-instruction-from-spec +description: Generate an IDL operation() model for a RISC-V instruction from its YAML spec file. +argument-hint: +allowed-tools: Read, Glob, Grep, Write, Edit +--- + +Copyright (c) 2026 Qualcomm Technologies, Inc. and/or its subsidiaries. +SPDX-License-Identifier: BSD-3-Clause + +Given a RISC-V instruction name, locate its YAML spec file under `spec/std/isa/inst/`, read the instruction's description and encoding, then generate a correct IDL `operation()` body using the IDL language defined by the Treetop grammar in `tools/ruby-gems/idlc/lib/idlc/idl.treetop`. + +Write the generated IDL code into the `operation()` key of the spec YAML file **only if that key is currently empty**. If `operation()` already has content, write the new IDL code to `/tmp/$ARGUMENT[0].yaml` instead. + +## Arguments + +$ARGUMENTS + +- **Argument 1**: The instruction name (e.g., `vmv.v.i`, `mul`, `jalr`). The name must match the `name:` field in the YAML spec file. + +If the argument is missing, ask the user to provide it. + +## Steps + +### 1. Locate the instruction YAML file + +Search for the instruction's YAML spec file under `spec/std/isa/inst/`. The file is named `.yaml` and may be in any subdirectory. Use the Glob tool with the pattern `spec/std/isa/inst/**/.yaml`. + +If no file is found, report the error and stop. + +### 2. Read the instruction spec + +Read the full YAML file. Pay close attention to: + +- **`description`**: Natural-language description of what the instruction does. +- **`encoding`**: The bit-field layout, including `match` pattern and `variables` (operand names and bit positions). +- **`assembly`**: The assembly syntax (operand names used in the instruction). +- **`definedBy`**: The extension(s) that define this instruction. +- **`access`**: Privilege levels at which the instruction is accessible. +- **`operation()`**: The current value — check whether it is empty (just `|` with no following content before the next key) or already populated. + +### 3. Study IDL patterns from reference files + +Before writing IDL, read the following reference files to understand IDL syntax and conventions: + +- `spec/std/isa/inst/M/mul.yaml` — simple arithmetic with extension check +- `spec/std/isa/inst/M/div.yaml` — arithmetic with special-case handling +- `spec/std/isa/inst/I/jalr.yaml` — control flow with register read/write +- `spec/std/isa/inst/V/vmv.v.i.yaml` — vector instruction with loop and CSR access + +Key IDL language rules (from `tools/ruby-gems/idlc/lib/idlc/idl.treetop`): + +**Types:** +- `XReg` — XLEN-wide integer register value +- `Bits` — N-bit value +- `U32`, `U64` — unsigned integers +- `Boolean` — boolean value +- Custom struct/enum types (e.g., `VectorState`, `VmaOrderType`) + +**Register access:** +- `X[reg_name]` — read/write integer register (e.g., `X[xs1]`, `X[xd]`) +- `f[reg_name]` — read/write floating-point register (e.g., `f[fs1]`, `f[fs2]`, `f[fd]`) +- `CSR[csr_name].FIELD` — read/write CSR field (e.g., `CSR[misa].M`, `CSR[vstart].VALUE`) +- `v[vd]` — vector register access + +**Bit operations:** +- Bit slice: `expr[msb:lsb]` (e.g., `src1[MXLEN-1:0]`) +- Concatenation: `{a, b, c}` (Verilog-style) +- Replication: `{N{expr}}` (e.g., `{MXLEN{1'b1}}`) +- Sign cast: `$signed(expr)` +- Widening multiply: `` `* `` operator + +**Integer literals:** +- Verilog-style: `32'h0`, `1'b1`, `MXLEN'1` +- C-style: `0`, `0xFF`, `0b1010` + +**Control flow:** +- `if (cond) { ... } else if (cond) { ... } else { ... }` +- `for (Type i = start; i < end; i++) { ... }` + +**Exception raising:** +- `raise(ExceptionCode::IllegalInstruction, mode(), $encoding);` +- `raise(ExceptionCode::VirtualInstruction, mode(), $encoding);` + +**Extension check:** +- `implemented?(ExtensionName::M)` — check if extension is implemented + +**Special variables:** +- `$pc` — program counter +- `$encoding` — current instruction encoding +- `MXLEN` — machine XLEN +- `VLEN` — vector register length + +**Comments:** Use `#` for line comments. + +### 4. Generate the IDL operation() body + +Using the instruction's description and encoding variables, write the IDL `operation()` body. Follow these guidelines: + +1. **Extension check first**: If the instruction is defined by an optional extension (not base I), add a check at the top: + ``` + if (implemented?(ExtensionName::X) && (CSR[misa].X == 1'b0)) { + raise (ExceptionCode::IllegalInstruction, mode(), $encoding); + } + ``` + +2. **Privilege checks**: If `access` restricts the instruction to certain modes, raise `IllegalInstruction` for unauthorized modes. + +3. **Read operands**: Declare local variables for register operands before using them: + ``` + XReg src1 = X[xs1]; + XReg src2 = X[xs2]; + ``` + +4. **Implement the behavior**: Translate the description into IDL statements. Use bit operations, arithmetic, and control flow as needed. + +5. **Write results**: Assign to destination registers last: + ``` + X[xd] = result; + ``` + +6. **Vector instructions**: For vector instructions, iterate over elements using `CSR[vstart].VALUE` to `CSR[vl].VALUE`, access vector state via `vector_state()`, and reset `CSR[vstart].VALUE = 0` at the end. + +7. **Comments**: Add `#` comments to explain non-obvious logic, special cases, and edge conditions. + +The IDL body must be syntactically valid per the grammar in `tools/ruby-gems/idlc/lib/idlc/idl.treetop`. Do not use constructs not present in the grammar (e.g., no `switch`, no `while`, no `return` in operation bodies). + +### 5. Determine where to write the output + +Check the `operation()` key in the YAML file: + +- **Empty** (the key exists but has no content — just `operation(): |` followed immediately by the next key or end of file): write the generated IDL directly into the YAML file using the Edit tool, replacing the empty `operation(): |` line with `operation(): |` followed by the indented IDL body (2-space indent per line). + +- **Already populated** (the key has existing IDL content): do **not** modify the spec file. Instead, write the generated IDL to `/tmp/.yaml` with the format: + + ```yaml + operation(): | + + ``` + +### 6. Write the output + +**If writing to the spec YAML file:** + +Use the Edit tool to replace the empty `operation(): |` block. The replacement must preserve the YAML structure — the IDL lines must be indented with 2 spaces, and the next top-level key must remain at column 0. + +Example — replacing an empty operation(): +``` +operation(): | + # IDL code here + XReg src = X[xs1]; + X[xd] = src + 1; + +``` +(Leave a blank line before the next key to match YAML block scalar conventions.) + +**If writing to /tmp:** + +Use the Write tool to create `/tmp/.yaml` with: +```yaml +operation(): | + # IDL code here + XReg src = X[xs1]; + X[xd] = src + 1; +``` + +### 7. Report + +Print a summary: +- The instruction name +- The YAML file found +- Whether the IDL was written to the spec file or to `/tmp/.yaml` +- The generated IDL code + +## Example + +For instruction `mul`: + +The file `spec/std/isa/inst/M/mul.yaml` already has a populated `operation()` key, so the output goes to `/tmp/mul.yaml`: + +```yaml +operation(): | + if (implemented?(ExtensionName::M) && (CSR[misa].M == 1'b0)) { + raise (ExceptionCode::IllegalInstruction, mode(), $encoding); + } + + XReg src1 = X[xs1]; + XReg src2 = X[xs2]; + + X[xd] = (src1 * src2)[MXLEN-1:0]; +``` diff --git a/ext/docs-resources b/ext/docs-resources index 906963708e..1d5bcd2e27 160000 --- a/ext/docs-resources +++ b/ext/docs-resources @@ -1 +1 @@ -Subproject commit 906963708e2d480baa2a09883d02b39fa8b711b1 +Subproject commit 1d5bcd2e2707697c16dd5ed04b368c04deef28ac diff --git a/ext/rbi-central b/ext/rbi-central index d468cc92ef..dcd9266d90 160000 --- a/ext/rbi-central +++ b/ext/rbi-central @@ -1 +1 @@ -Subproject commit d468cc92ef013e02fc60449ccfef7c7e27e71821 +Subproject commit dcd9266d90d04059cb75f7d65a1c4a2b44c1d4bb diff --git a/ext/riscv-isa-manual b/ext/riscv-isa-manual index 7fc198f13a..5202d9c5d7 160000 --- a/ext/riscv-isa-manual +++ b/ext/riscv-isa-manual @@ -1 +1 @@ -Subproject commit 7fc198f13ad89e9608e9404be1c7a8119c14c13b +Subproject commit 5202d9c5d75d7da7502f5ae3bbe38290d30fc43f diff --git a/ext/riscv-opcodes b/ext/riscv-opcodes index a2766fdfaf..767b74181f 160000 --- a/ext/riscv-opcodes +++ b/ext/riscv-opcodes @@ -1 +1 @@ -Subproject commit a2766fdfaf275336c6ec8452e8b4972c45d501a8 +Subproject commit 767b74181f2ee325a2f6a2606620530d743553d2 diff --git a/ext/riscv-tests b/ext/riscv-tests index f443f44860..355f1fb9b8 160000 --- a/ext/riscv-tests +++ b/ext/riscv-tests @@ -1 +1 @@ -Subproject commit f443f4486085132552c9b43527fb0be5efa3cc0c +Subproject commit 355f1fb9b8a7142bee718e4123fea0aa8f49c319 diff --git a/sorbet/rbi/annotations/activesupport.rbi b/sorbet/rbi/annotations/activesupport.rbi index d6779e3f30..bde37b43de 100644 --- a/sorbet/rbi/annotations/activesupport.rbi +++ b/sorbet/rbi/annotations/activesupport.rbi @@ -88,10 +88,6 @@ class Hash sig { returns(T::Boolean) } def extractable_options?; end - - # @version >= 6.1.0 - sig { returns(T.self_type) } - def compact_blank; end end class Array @@ -198,26 +194,6 @@ class DateTime end module Enumerable - sig { type_parameters(:Block).params(block: T.proc.params(arg0: Elem).returns(T.type_parameter(:Block))).returns(T::Hash[T.type_parameter(:Block), Elem]) } - sig { returns(T::Enumerable[T.untyped]) } - def index_by(&block); end - - sig { type_parameters(:Block).params(block: T.proc.params(arg0: Elem).returns(T.type_parameter(:Block))).returns(T::Hash[Elem, T.type_parameter(:Block)]) } - sig { returns(T::Enumerable[T.untyped]) } - sig { type_parameters(:Default).params(default: T.type_parameter(:Default)).returns(T::Hash[Elem, T.type_parameter(:Default)]) } - def index_with(default = nil, &block); end - - sig { params(block: T.proc.params(arg0: Elem).returns(BasicObject)).returns(T::Boolean) } - sig { returns(T::Boolean) } - def many?(&block); end - - sig { params(object: BasicObject).returns(T::Boolean) } - def exclude?(object); end - - # @version >= 6.1.0 - sig { returns(T::Array[Elem]) } - def compact_blank; end - # @version >= 7.0.0 sig { returns(Elem) } def sole; end @@ -489,7 +465,4 @@ end module ActiveSupport::Testing::Assertions sig { type_parameters(:Block).params(block: T.proc.returns(T.type_parameter(:Block))).returns(T.type_parameter(:Block)) } def assert_nothing_raised(&block); end - - sig { type_parameters(:TResult).params(expression: T.any(Proc, Kernel), message: Kernel, from: T.anything, to: T.anything, block: T.proc.returns(T.type_parameter(:TResult))).returns(T.type_parameter(:TResult)) } - def assert_changes(expression, message = T.unsafe(nil), from: T.unsafe(nil), to: T.unsafe(nil), &block); end end diff --git a/sorbet/rbi/annotations/minitest.rbi b/sorbet/rbi/annotations/minitest.rbi index 6404733181..64a89286ee 100644 --- a/sorbet/rbi/annotations/minitest.rbi +++ b/sorbet/rbi/annotations/minitest.rbi @@ -60,6 +60,9 @@ module Minitest::Assertions sig { params(exp: T.anything, act: T.anything, msg: T.anything).returns(TrueClass) } def assert_same(exp, act, msg = nil); end + sig { params(send_ary: T::Array[T.anything], m: T.anything).returns(T::Boolean) } + def assert_send(send_ary, m = nil); end + sig { params(block: T.proc.void).returns(T::Boolean) } def assert_silent(&block); end diff --git a/spec/std/isa/inst/V/vmv.v.v.yaml b/spec/std/isa/inst/V/vmv.v.v.yaml index 4b0a3d052f..eb20acabfd 100644 --- a/spec/std/isa/inst/V/vmv.v.v.yaml +++ b/spec/std/isa/inst/V/vmv.v.v.yaml @@ -27,3 +27,40 @@ access: vu: always data_independent_timing: false operation(): | + # Get current vector state + VectorState state = vector_state(); + VectorLmulType lmul_type = state.lmul_type; + + XReg vlen = VLEN; + XReg vlmax; + if (lmul_type == VectorLmulType::Multiply) { + vlmax = (vlen << state.log2_lmul) >> state.log2_sew; + } else { + # (lmul_type == VectorLmulType::Divide) + vlmax = (vlen >> state.log2_lmul) >> state.log2_sew; + } + + # Iterate through elements from vstart to vl, copying vs1[i] -> vd[i] + for (U32 i = CSR[vstart].VALUE; i < CSR[vl].VALUE; i++) { + U32 start_bit_pos = i * state.sew; + U32 end_bit_pos = start_bit_pos + state.sew - 1; + + # Read element i from source register vs1 + XReg src_elem = v[vs1][end_bit_pos:start_bit_pos]; + + # Write element i to destination register vd, + # guarding against invalid bit-slice ranges at vector register boundaries + if (start_bit_pos == 0) { + if (end_bit_pos == VLEN - 1) { + v[vd] = src_elem; + } else { + v[vd] = {v[vd][VLEN-1:end_bit_pos + 1], src_elem}; + } + } else if (end_bit_pos == VLEN - 1) { + v[vd] = {src_elem, v[vd][start_bit_pos-1:0]}; + } else { + v[vd] = {v[vd][VLEN-1:end_bit_pos + 1], src_elem, v[vd][start_bit_pos-1:0]}; + } + } + + CSR[vstart].VALUE = 0; diff --git a/spec/std/isa/inst/V/vmv.v.x.yaml b/spec/std/isa/inst/V/vmv.v.x.yaml index 4c673d4fc0..a8347b0b65 100644 --- a/spec/std/isa/inst/V/vmv.v.x.yaml +++ b/spec/std/isa/inst/V/vmv.v.x.yaml @@ -27,3 +27,43 @@ access: vu: always data_independent_timing: false operation(): | + # Get current vector state + VectorState state = vector_state(); + VectorLmulType lmul_type = state.lmul_type; + + XReg vlen = VLEN; + XReg vlmax; + if (lmul_type == VectorLmulType::Multiply) { + vlmax = (vlen << state.log2_lmul) >> state.log2_sew; + } else { + # (lmul_type == VectorLmulType::Divide) + vlmax = (vlen >> state.log2_lmul) >> state.log2_sew; + } + + # Read scalar register xs1 and truncate/zero-extend to SEW bits + XReg scalar_val = X[xs1]; + + # Iterate through elements from vstart to vl, broadcasting scalar -> vd[i] + for (U32 i = CSR[vstart].VALUE; i < CSR[vl].VALUE; i++) { + U32 start_bit_pos = i * state.sew; + U32 end_bit_pos = start_bit_pos + state.sew - 1; + + # Truncate scalar to SEW bits (lower SEW bits of xs1) + XReg elem_val = scalar_val[state.sew-1:0]; + + # Write element i to destination register vd, + # guarding against invalid bit-slice ranges at vector register boundaries + if (start_bit_pos == 0) { + if (end_bit_pos == VLEN - 1) { + v[vd] = elem_val; + } else { + v[vd] = {v[vd][VLEN-1:end_bit_pos + 1], elem_val}; + } + } else if (end_bit_pos == VLEN - 1) { + v[vd] = {elem_val, v[vd][start_bit_pos-1:0]}; + } else { + v[vd] = {v[vd][VLEN-1:end_bit_pos + 1], elem_val, v[vd][start_bit_pos-1:0]}; + } + } + + CSR[vstart].VALUE = 0;