diff --git a/.github/workflows/regress.yml b/.github/workflows/regress.yml index 83f06dc2f..e9ee24869 100644 --- a/.github/workflows/regress.yml +++ b/.github/workflows/regress.yml @@ -185,3 +185,42 @@ jobs: run: ./bin/build_container - name: Generate extension PDF run: ./do gen:profile[MockProfileRelease] + regress-gen-opcode: + runs-on: ubuntu-latest + env: + SINGULARITY: 1 + steps: + - name: Clone Github Repo Action + uses: actions/checkout@v4 + - name: Cache riscv-opcodes submodule + id: cache-opcodes + uses: actions/cache@v4 + with: + path: ext/riscv-opcodes + key: ${{ runner.os }}-submodule-riscv-opcodes-${{ hashFiles('.gitmodules') }} + - if: steps.cache-opcodes.outputs.cache-hit != 'true' + name: Checkout riscv-opcodes submodule + run: | + git submodule init ext/riscv-opcodes + git submodule update ext/riscv-opcodes + - name: Setup apptainer + uses: eWaterCycle/setup-apptainer@v2.0.0 + - name: Get container from cache + id: cache-sif + uses: actions/cache@v4 + with: + path: .singularity/image.sif + key: ${{ hashFiles('container.def', 'bin/.container-tag') }} + - name: Get gems and node files from cache + id: cache-bundle-npm + uses: actions/cache@v4 + with: + path: | + .home/.gems + node_modules + key: ${{ hashFiles('Gemfile.lock') }}-${{ hashFiles('package-lock.json') }} + - if: ${{ steps.cache-sif.outputs.cache-hit != 'true' }} + name: Build container + run: ./bin/build_container + - name: Generate opcode outputs + run: ./do gen:opcode_outputs diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml old mode 100644 new mode 100755 diff --git a/Rakefile b/Rakefile index 40b2c220e..831899dea 100644 --- a/Rakefile +++ b/Rakefile @@ -10,6 +10,7 @@ require "yard" require "minitest/test_task" require_relative $root / "lib" / "architecture" +$opcode_outputs = $root / "gen" / "opcodes_outputs" directory "#{$root}/.stamps" @@ -39,6 +40,28 @@ file "#{$root}/.stamps/dev_gems" => ["#{$root}/.stamps"] do |t| FileUtils.touch t.name end +namespace :gen do + desc "Generate opcode outputs, optionally specify YAML_DIR=path/to/yaml" + task :opcode_outputs do + yaml_dir = ENV['YAML_DIR'] || "#{$root}/arch/inst" + mkdir_p $opcode_outputs + sh "#{$root}/.home/.venv/bin/python3 #{$root}/backends/opcodes_maker/yaml_to_json.py #{yaml_dir} #{$opcode_outputs}" + sh "#{$root}/.home/.venv/bin/python3 #{$root}/backends/opcodes_maker/generator.py #{$opcode_outputs}/instr_dict.json -c -chisel -spinalhdl -sverilog -rust -go -latex" + + # Move generated files to output dir + Dir.chdir("#{$root}") do + mv "encoding.out.h", "#{$opcode_outputs}/", force: true + mv "inst.chisel", "#{$opcode_outputs}/", force: true + mv "inst.spinalhdl", "#{$opcode_outputs}/", force: true + mv "inst.sverilog", "#{$opcode_outputs}/", force: true + mv "inst.rs", "#{$opcode_outputs}/", force: true + mv "inst.go", "#{$opcode_outputs}/", force: true + mv "instr-table.tex", "#{$opcode_outputs}/", force: true + mv "priv-instr-table.tex", "#{$opcode_outputs}/", force: true + end + end + end + namespace :gen do desc "Generate documentation for the ruby tooling" task tool_doc: "#{$root}/.stamps/dev_gems" do @@ -319,6 +342,8 @@ namespace :test do Rake::Task["gen:html"].invoke("generic_rv64") + Rake::Task["gen:opcode_outputs"].invoke + Rake::Task["#{$root}/gen/certificate_doc/pdf/MockCertificateModel.pdf"].invoke Rake::Task["#{$root}/gen/profile_doc/pdf/MockProfileRelease.pdf"].invoke diff --git a/backends/opcodes_maker/Makefile b/backends/opcodes_maker/Makefile new file mode 100755 index 000000000..c6cdca745 --- /dev/null +++ b/backends/opcodes_maker/Makefile @@ -0,0 +1,46 @@ +# Directories +YAML_DIR ?= ../../arch/inst +OUTPUT_DIR := output + +# Python scripts +YAML_TO_JSON := yaml_to_json.py +GENERATOR := generator.py + +# Generated files +INSTR_DICT := $(OUTPUT_DIR)/instr_dict.json +C_OUT := $(OUTPUT_DIR)/encoding.out.h +CHISEL_OUT := $(OUTPUT_DIR)/inst.chisel +SPINALHDL_OUT := $(OUTPUT_DIR)/inst.spinalhdl +SVERILOG_OUT := $(OUTPUT_DIR)/inst.sverilog +RUST_OUT := $(OUTPUT_DIR)/inst.rs +GO_OUT := $(OUTPUT_DIR)/inst.go +LATEX_OUT := $(OUTPUT_DIR)/instr-table.tex +LATEX_PRIV_OUT := $(OUTPUT_DIR)/priv-instr-table.tex + +# Check for required files +REQUIRED_FILES := $(YAML_TO_JSON) $(GENERATOR) +$(foreach file,$(REQUIRED_FILES),\ + $(if $(wildcard $(file)),,$(error Required file $(file) not found))) + +# Default target +all: generate + +# Create output directory +$(OUTPUT_DIR): + mkdir -p $(OUTPUT_DIR) + +# Convert YAML to JSON +$(INSTR_DICT): $(YAML_TO_JSON) | $(OUTPUT_DIR) + python3 $(YAML_TO_JSON) $(YAML_DIR) $(OUTPUT_DIR) + +# Generate all outputs +generate: $(INSTR_DICT) + python3 $(GENERATOR) $(INSTR_DICT) -c -chisel -spinalhdl -sverilog -rust -go -latex + mv encoding.out.h inst.chisel inst.spinalhdl inst.sverilog inst.rs inst.go \ + instr-table.tex priv-instr-table.tex $(OUTPUT_DIR)/ 2>/dev/null || true + +# Clean generated files +clean: + rm -rf $(OUTPUT_DIR) + +.PHONY: all generate clean diff --git a/backends/opcodes_maker/README.md b/backends/opcodes_maker/README.md new file mode 100644 index 000000000..84d5735ae --- /dev/null +++ b/backends/opcodes_maker/README.md @@ -0,0 +1,89 @@ +# RISC-V Instruction Format Generator + +This tool converts RISC-V instruction YAML definitions into various output formats including C headers, Chisel, Rust, Go, and LaTeX documentation. + +## Prerequisites + +- Python 3 +- YAML Python package (`pip install pyyaml`) +- Make + +## Directory Structure + +``` +. +├── yaml_to_json.py # Converts YAML instruction definitions to JSON +├── generator.py # Generates various output formats from JSON +├── output/ # Generated files directory +└── Makefile # Build system configuration +``` + +## Input/Output Format + +### Input +- YAML files containing RISC-V instruction definitions +- Default input directory: `../../arch/inst` +- Can be customized using `YAML_DIR` variable + +### Output +All outputs are generated in the `output` directory: +- `encoding.out.h` - C header definitions +- `inst.chisel` - Chisel implementation +- `inst.spinalhdl` - SpinalHDL implementation +- `inst.sverilog` - SystemVerilog implementation +- `inst.rs` - Rust implementation +- `inst.go` - Go implementation +- `instr-table.tex` - LaTeX instruction table +- `priv-instr-table.tex` - LaTeX privileged instruction table +- `instr_dict.json` - Intermediate JSON representation +- `processed_instr_dict.json` - Final processed JSON + +## Usage + +### Basic Usage +```bash +make # Use default YAML directory +make YAML_DIR=/custom/path # Use custom YAML directory +make clean # Remove all generated files +make help # Show help message +``` + +### Pipeline Steps +1. YAML to JSON conversion (`yaml_to_json.py`) + - Reads YAML instruction definitions + - Creates intermediate JSON representation + +2. Output Generation (`generator.py`) + - Takes JSON input + - Generates all output formats + - Places results in output directory + +### Customization +- Input directory can be changed: + ```bash + make YAML_DIR=/path/to/yaml/files + ``` +- Default paths in Makefile: + ```makefile + YAML_DIR ?= ../../arch/inst + OPCODES_DIR := ../riscv-opcodes + OUTPUT_DIR := output + ``` + +## Error Handling +- Checks for required Python scripts before execution +- Verifies input directory exists +- Creates output directory if missing +- Shows helpful error messages for missing files/directories + +## Cleaning Up +```bash +make clean # Removes all generated files and output directory +``` + +## Dependencies +- Requires access to RISC-V opcodes repository (expected at `../riscv-opcodes`) +- Python scripts use standard libraries plus PyYAML + +## Note +Make sure your input YAML files follow the expected RISC-V instruction definition format. For format details, refer to the RISC-V specification or example YAML files in the arch/inst directory. diff --git a/backends/opcodes_maker/fieldo.js b/backends/opcodes_maker/fieldo.js new file mode 100644 index 000000000..d7d661548 --- /dev/null +++ b/backends/opcodes_maker/fieldo.js @@ -0,0 +1,153 @@ +'use strict'; + +const fieldo = { + fd: {msb: 11, lsb: 7, kind: 'fr', prio: 10, dst: true, count: 0}, + fs1: {msb: 19, lsb: 15, kind: 'fr', prio: 20, src: true, count: 0}, + fs2: {msb: 24, lsb: 20, kind: 'fr', prio: 30, src: true, count: 0}, + fs3: {msb: 31, lsb: 27, kind: 'fr', prio: 40, src: true, count: 0}, + + rd: {msb: 11, lsb: 7, kind: 'xr', prio: 10, dst: true, count: 753}, + rs1: {msb: 19, lsb: 15, kind: 'xr', prio: 20, src: true, count: 1007}, + rs2: {msb: 24, lsb: 20, kind: 'xr', prio: 30, src: true, count: 501}, + rs3: {msb: 31, lsb: 27, kind: 'xr', prio: 40, src: true, count: 26}, + + rm: {msb: 14, lsb: 12, kind: 'rm', count: 80}, + shamtq: {msb: 26, lsb: 20, bits: [6, 5, 4, 3, 2, 1, 0], count: 6}, + shamtd: {msb: 25, lsb: 20, bits: [5, 4, 3, 2, 1, 0], count: 17}, + shamtw: {msb: 24, lsb: 20, bits: [4, 3, 2, 1, 0], count: 29}, + shamtw4: {msb: 23, lsb: 20, bits: [3, 2, 1, 0], count: 2}, + + imm12: {msb: 31, lsb: 20, bits: [11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], kind: 'sext', count: 23}, + + imm12lo: {msb: 11, lsb: 7, bits: [4, 3, 2, 1, 0], count: 9}, + imm12hi: {msb: 31, lsb: 25, bits: [11, 10, 9, 8, 7, 6, 5], count: 12}, + + imm20: {msb: 31, lsb: 12, bits: [31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12], count: 2}, + jimm20: {msb: 31, lsb: 12, bits: [20, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 11, 19, 18, 17, 16, 15, 14, 13, 12], count: 1}, + + // vector + vd: {msb: 11, lsb: 7, kind: 'vr', prio: 10, dst: true, count: 412}, + vs1: {msb: 19, lsb: 15, kind: 'vr', prio: 40, src: true, count: 128}, + vs2: {msb: 24, lsb: 20, kind: 'vr', prio: 30, src: true, count: 380}, + vs3: {msb: 11, lsb: 7, kind: 'vr', prio: 20, src: true, dst: true, count: 38}, + vm: {msb: 25, lsb: 25, count: 395}, + nf: {msb: 31, lsb: 29, count: 72}, + wd: {msb: 26, lsb: 26, count: 36}, + + simm5: {msb: 19, lsb: 15, bits: [4, 3, 2, 1, 0], kind: 'sext', count: 30}, + zimm5: {msb: 19, lsb: 15}, + + fd_p: {msb: 4, lsb: 2, kind: 'frc', prio: 10, dst: true, count: 10}, + fs2_p: {msb: 4, lsb: 2, kind: 'frc', prio: 30, src: true, count: 15}, + c_fs2: {msb: 6, lsb: 2, kind: 'fr', prio: 30, src: true, count: 6}, + + rs1_p: {msb: 9, lsb: 7, kind: 'xrc', prio: 20, src: true, count: 19}, + rs2_p: {msb: 4, lsb: 2, kind: 'xrc', prio: 30, src: true, count: 15}, + rd_p: {msb: 4, lsb: 2, kind: 'xrc', prio: 10, dst: true, count: 10}, + rd_rs1_n0: {msb: 11, lsb: 7, kind: 'xr', prio: 10, dst: true, src: true, count: 3}, + rd_rs1_p: {msb: 9, lsb: 7, kind: 'xrc', prio: 10, dst: true, src: true, count: 18}, + rd_rs1: {msb: 11, lsb: 7, kind: 'xr', prio: 10, dst: true, count: 3}, + rd_n2: {msb: 11, lsb: 7, kind: 'xr', prio: 10, dst: true, count: 1}, + rd_n0: {msb: 11, lsb: 7, kind: 'xr', prio: 10, dst: true, count: 3}, + rs1_n0: {msb: 11, lsb: 7, kind: 'xr', prio: 20, src: true, count: 1}, + c_rs2_n0: {msb: 6, lsb: 2, kind: 'xr', prio: 30, src: true, count: 2}, + c_rs1_n0: {msb: 11, lsb: 7, kind: 'xr', prio: 20, src: true, count: 1}, + c_rs2: {msb: 6, lsb: 2, kind: 'xr', prio: 30, src: true, count: 6}, + c_sreg1: {msb: 9, lsb: 7, kind: 'xrc', prio: 20, src: true, count: 2}, + c_sreg2: {msb: 4, lsb: 2, kind: 'xrc', prio: 30, src: true, count: 2}, + + aq: {msb: 26, lsb: 26, count: 22}, + rl: {msb: 25, lsb: 25, count: 22}, + + // Compact Immediate Literals + c_nzuimm10: {msb: 12, lsb: 5, bits: [5, 4, 9, 8, 7, 6, 2, 3], count: 1}, + + c_uimm7lo: {msb: 6, lsb: 5, bits: [2, 6], count: 4}, + c_uimm7hi: {msb: 12, lsb: 10, bits: [5, 4, 3], count: 4}, + + c_nzimm6lo: {msb: 6, lsb: 2, bits: [4, 3, 2, 1, 0], count: 2}, + c_nzimm6hi: {msb: 12, lsb: 12, bits: [5], count: 2}, + + c_imm6lo: {msb: 6, lsb: 2, bits: [4, 3, 2, 1, 0], count: 4}, + c_imm6hi: {msb: 12, lsb: 12, bits: [5], kind: 'sext', count: 4}, + + c_nzimm10lo: {msb: 6, lsb: 2, bits: [4, 6, 8, 7, 5], count: 1}, + c_nzimm10hi: {msb: 12, lsb: 12, bits: [9], count: 1}, + + c_nzimm18lo: {msb: 6, lsb: 2, bits: [16, 15, 14, 13, 12], count: 1}, + c_nzimm18hi: {msb: 12, lsb: 12, bits: [17], count: 1}, + + c_imm12: {msb: 12, lsb: 2, bits: [11, 4, 9, 8, 10, 6, 7, 3, 2, 1, 5], count: 2}, + + c_bimm9lo: {msb: 6, lsb: 2, bits: [7, 6, 2, 1, 5], count: 2}, + c_bimm9hi: {msb: 12, lsb: 10, bits: [8, 4, 3], count: 2}, + + c_uimm8splo: {msb: 6, lsb: 2, bits: [4, 3, 2, 7, 6], count: 2}, + c_uimm8sphi: {msb: 12, lsb: 12, bits: [5], count: 2}, + + c_uimm8sp_s: {msb: 12, lsb: 7, bits: [5, 4, 3, 2, 7, 6], count: 2}, + + c_nzuimm5: {msb: 6, lsb: 2, bits: [4, 3, 2, 1, 0], count: 2}, + + c_nzuimm6lo: {msb: 6, lsb: 2, bits: [4, 3, 2, 1, 0], count: 4}, + c_nzuimm6hi: {msb: 12, lsb: 12, bits: [5], count: 3}, + + c_uimm8lo: {msb: 6, lsb: 5, bits: [7, 6], count: 6}, + c_uimm8hi: {msb: 12, lsb: 10, bits: [5, 4, 3], count: 6}, + + c_uimm9splo: {msb: 6, lsb: 2, bits: [4, 5, 8, 7, 6], count: 3}, + c_uimm9sphi: {msb: 12, lsb: 12, bits: [5], count: 3}, + + c_uimm9sp_s: {msb: 12, lsb: 7, bits: [5, 4, 3, 8, 7, 6], count: 3}, + + c_uimm2: {msb: 6, lsb: 5, count: 2}, + c_uimm1: {msb: 5, lsb: 5, count: 3}, + c_spimm: {msb: 3, lsb: 2, count: 4}, + c_uimm9lo: {msb: 6, lsb: 5, count: 2}, + c_uimm9hi: {msb: 12, lsb: 10, count: 2}, + c_uimm10splo: {msb: 6, lsb: 2, count: 1}, + c_uimm10sphi: {msb: 12, lsb: 12, count: 1}, + c_uimm10sp_s: {msb: 12, lsb: 7, count: 1}, + c_index: {msb: 9, lsb: 2, count: 1}, + c_rlist: {msb: 7, lsb: 4, count: 4}, + + bs: {msb: 31, lsb: 30, count: 6}, // byte select for RV32K AES + rnum: {msb: 23, lsb: 20, count: 1}, + + bimm12hi: {msb: 31, lsb: 25, bits: [12, 10, 9, 8, 7, 6, 5], count: 6}, + bimm12lo: {msb: 11, lsb: 7, bits: [4, 3, 2, 1, 11], count: 6}, + + fm: {msb: 31, lsb: 28, kind: 'fm', count: 1}, + pred: {msb: 27, lsb: 24, kind: 'pred', count: 1}, + succ: {msb: 23, lsb: 20, kind: 'succ', count: 1}, + + csr: {msb: 31, lsb: 20, kind: 'csr', count: 6}, + + zimm: {msb: 19, lsb: 15, count: 6}, + zimm10: {msb: 29, lsb: 20, kind: 'vtypei', count: 1}, + zimm11: {msb: 30, lsb: 20, kind: 'vtypei', count: 1}, + + zimm6hi: {msb: 26, lsb: 26}, + zimm6lo: {msb: 19, lsb: 15}, + + // rv32_zpn + imm2: {msb: 21, lsb: 20, count: 1}, + // rv_zpn + imm3: {msb: 22, lsb: 20, count: 9}, + imm4: {msb: 23, lsb: 20, count: 8}, + imm5: {msb: 24, lsb: 20, count: 11}, + imm6: {msb: 25, lsb: 20, count: 1}, + + mop_r_t_30: {msb: 30, lsb: 30}, + mop_r_t_27_26: {msb: 27, lsb: 26}, + mop_r_t_21_20: {msb: 21, lsb: 20}, + mop_rr_t_30: {msb: 30, lsb: 30}, + mop_rr_t_27_26: {msb: 27, lsb: 26}, + + c_mop_t: {msb: 10, lsb: 8}, + +}; + +module.exports = fieldo; + +/* eslint camelcase: 0 */ diff --git a/backends/opcodes_maker/generator.py b/backends/opcodes_maker/generator.py new file mode 100755 index 000000000..e61c51638 --- /dev/null +++ b/backends/opcodes_maker/generator.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 + +import argparse +import json +import logging +import pprint +import os +import sys +import shutil +from contextlib import contextmanager +from pathlib import Path +from typing import Dict, List, Any + +# Add riscv-opcodes directory to Python path +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +RISCV_OPCODES_DIR = os.path.join(SCRIPT_DIR, "..", "..", "ext", "riscv-opcodes") +sys.path.insert(0, RISCV_OPCODES_DIR) + + +@contextmanager +def working_directory(path): + """Context manager for changing the current working directory""" + prev_cwd = os.getcwd() + os.chdir(path) + try: + yield prev_cwd + finally: + os.chdir(prev_cwd) + + +# Change to riscv-opcodes directory when importing to ensure relative paths work +with working_directory(RISCV_OPCODES_DIR): + from c_utils import make_c + from chisel_utils import make_chisel + from constants import emitted_pseudo_ops + from go_utils import make_go + from latex_utils import make_latex_table, make_priv_latex_table + from rust_utils import make_rust + from sverilog_utils import make_sverilog + +LOG_FORMAT = "%(levelname)s:: %(message)s" +LOG_LEVEL = logging.INFO + +pretty_printer = pprint.PrettyPrinter(indent=2) +logging.basicConfig(level=LOG_LEVEL, format=LOG_FORMAT) + + +def load_instruction_dict(json_path: str) -> Dict[str, Any]: + """ + Load instruction dictionary from a JSON file. + """ + try: + with open(json_path, encoding="utf-8") as f: + return json.load(f) + except FileNotFoundError: + logging.error(f"Input JSON file not found: {json_path}") + raise + except json.JSONDecodeError: + logging.error(f"Invalid JSON format in file: {json_path}") + raise + + +def move_file(src: str, dest_dir: str): + """ + Move a file to the destination directory if it exists. + """ + if os.path.exists(src): + dest = os.path.join(dest_dir, os.path.basename(src)) + shutil.move(src, dest) + + +def generate_outputs( + instr_dict: Dict[str, Any], + include_pseudo: bool, + c: bool, + chisel: bool, + spinalhdl: bool, + sverilog: bool, + rust: bool, + go: bool, + latex: bool, +): + """ + Generate output files based on the instruction dictionary. + """ + # Sort the dictionary for consistent output + instr_dict = dict(sorted(instr_dict.items())) + + # Save the processed dictionary in current directory + with open("processed_instr_dict.json", "w", encoding="utf-8") as outfile: + json.dump(instr_dict, outfile, indent=2) + + # Generate files in riscv-opcodes directory and move them to current directory + with working_directory(RISCV_OPCODES_DIR) as orig_dir: + if c: + # For C output, filter pseudo-ops if needed + if not include_pseudo: + c_dict = { + k: v for k, v in instr_dict.items() if k not in emitted_pseudo_ops + } + else: + c_dict = instr_dict + make_c(c_dict) + move_file("encoding.out.h", orig_dir) + logging.info("encoding.out.h generated successfully") + + if chisel: + make_chisel(instr_dict) + move_file("inst.chisel", orig_dir) + logging.info("inst.chisel generated successfully") + + if spinalhdl: + make_chisel(instr_dict, True) + move_file("inst.spinalhdl", orig_dir) + logging.info("inst.spinalhdl generated successfully") + + if sverilog: + make_sverilog(instr_dict) + move_file("inst.sverilog", orig_dir) + logging.info("inst.sverilog generated successfully") + + if rust: + make_rust(instr_dict) + move_file("inst.rs", orig_dir) + logging.info("inst.rs generated successfully") + + if go: + make_go(instr_dict) + move_file("inst.go", orig_dir) + logging.info("inst.go generated successfully") + + if latex: + make_latex_table() + make_priv_latex_table() + move_file("instr-table.tex", orig_dir) + move_file("priv-instr-table.tex", orig_dir) + logging.info("LaTeX files generated successfully") + + +def main(): + parser = argparse.ArgumentParser( + description="Generate RISC-V constants from JSON input" + ) + parser.add_argument( + "input_json", help="Path to JSON file containing instruction definitions" + ) + parser.add_argument( + "-pseudo", action="store_true", help="Include pseudo-instructions" + ) + parser.add_argument("-c", action="store_true", help="Generate output for C") + parser.add_argument( + "-chisel", action="store_true", help="Generate output for Chisel" + ) + parser.add_argument( + "-spinalhdl", action="store_true", help="Generate output for SpinalHDL" + ) + parser.add_argument( + "-sverilog", action="store_true", help="Generate output for SystemVerilog" + ) + parser.add_argument("-rust", action="store_true", help="Generate output for Rust") + parser.add_argument("-go", action="store_true", help="Generate output for Go") + parser.add_argument("-latex", action="store_true", help="Generate output for Latex") + + args = parser.parse_args() + + # Load instruction dictionary from JSON + instr_dict = load_instruction_dict(args.input_json) + + print(f"Loaded instruction dictionary from: {args.input_json}") + + # Generate outputs based on the loaded dictionary + generate_outputs( + instr_dict, + args.pseudo, + args.c, + args.chisel, + args.spinalhdl, + args.sverilog, + args.rust, + args.go, + args.latex, + ) + + +if __name__ == "__main__": + main() diff --git a/backends/opcodes_maker/sorter.py b/backends/opcodes_maker/sorter.py new file mode 100644 index 000000000..b2b07deac --- /dev/null +++ b/backends/opcodes_maker/sorter.py @@ -0,0 +1,30 @@ +import json + + +def sort_instr_json(dir_name, outname): + with open(dir_name) as file: + data = json.load(file) + + sorted_data = {} + for key in sorted(data): + entry = data[key] + if "variable_fields" in entry: + entry["variable_fields"] = sorted(entry["variable_fields"]) + if "extension" in entry: + entry["extension"] = sorted(entry["extension"]) + + # Add the processed entry to the sorted data + sorted_data[key] = entry + + with open(outname, "w") as file: + json.dump(sorted_data, file, indent=4) + + print(json.dumps(sorted_data, indent=4)) + + +def main(): + sort_instr_json("instr_dict.json", "udb_sorted_data.json") + sort_instr_json("instr_dict.json", "opcodes_sorted_data.json") + + +main() diff --git a/backends/opcodes_maker/yaml_to_json.py b/backends/opcodes_maker/yaml_to_json.py new file mode 100755 index 000000000..ae7a280b2 --- /dev/null +++ b/backends/opcodes_maker/yaml_to_json.py @@ -0,0 +1,337 @@ +import re +from typing import List, Dict, Union, Any +import argparse +import os +import sys +import yaml +import json +from typing import List, Dict, Union +import subprocess + + +def load_fieldo() -> dict: + """ + Load the fieldo mapping from the JavaScript file (fieldo.js) by invoking Node.js. + + """ + this_dir = os.path.dirname(os.path.abspath(__file__)) + # The command runs Node.js in the current directory and prints the JSON representation. + cmd = ["node", "-e", 'console.log(JSON.stringify(require("./fieldo.js")));'] + output = subprocess.check_output(cmd, cwd=this_dir) + return json.loads(output) + + +# Set of register names that need transformation. +reg_names = {"qs1", "qs2", "qd", "fs1", "fs2", "fd"} +fieldo = load_fieldo() + + +def range_size(range_str: str) -> int: + """Compute the bit width from a range string like '31-20'.""" + try: + end, start = map(int, range_str.split("-")) + return abs(end - start) + 1 + except Exception: + return 0 + + +def lookup_immediate_by_range( + var_base: str, high: int, low: int, instr_name: str +) -> Union[str, None]: + """ + Look up a canonical field name from the fieldo mapping based on the bit range. + + - If var_base is "imm", then we consider any field whose name contains "imm" + (but not starting with "c_" and not "csr"). + - If var_base starts with "c_", then only keys starting with "c_" are considered. + - Otherwise, we require that the key starts with var_base (for "simm", "jimm", etc.). + + If multiple candidates are found and var_base is "imm", we prefer "zimm" if present. + Otherwise, instr_name is used as a hint. + """ + candidates = [] + for key, field in fieldo.items(): + if field.get("msb") == high and field.get("lsb") == low: + if var_base == "imm": + if "imm" in key and not key.startswith("c_") and key != "csr": + candidates.append(key) + elif var_base.startswith("c_"): + if key.startswith("c_"): + candidates.append(key) + else: + if key.startswith(var_base): + candidates.append(key) + if candidates: + if len(candidates) == 1: + return candidates[0] + else: + if var_base == "imm" and "zimm" in candidates: + return "zimm" + lower_instr = instr_name.lower() + for cand in candidates: + if lower_instr.startswith("j") and cand.startswith("jimm"): + return cand + if lower_instr.startswith("b") and cand.startswith("bimm"): + return cand + return candidates[0] + return None + + +def canonical_immediate_names( + var_name: str, location: str, instr_name: str +) -> List[str]: + """ + Given a YAML immediate variable (its base name and location), return a list of canonical + field names strictly from the fieldo mapping. + + - For non-composite locations (e.g. "31-20"), the range is parsed and a lookup is performed. + - For composite locations (detected by "|" in the location), we assume a branch-immediate split: + the high part uses the range (31, 25) and the low part (11, 7), with an appropriate prefix. + - If no candidate is found in fieldo, an empty list is returned. + """ + if "|" in location: + parts = location.split("|") + if len(parts) == 4: + prefix = "bimm" if instr_name.lower().startswith("b") else "imm" + hi_candidate = lookup_immediate_by_range(prefix, 31, 25, instr_name) + lo_candidate = lookup_immediate_by_range(prefix, 11, 7, instr_name) + if hi_candidate is None or lo_candidate is None: + print( + f"Warning: composite immediate candidate not found in fieldo for {var_name} with location {location}" + ) + return [] + return [hi_candidate, lo_candidate] + else: + # For other composite formats, attempt a basic lookup using the first two numbers. + nums = list(map(int, re.findall(r"\d+", location))) + if len(nums) >= 2: + high, low = nums[0], nums[1] + candidate = lookup_immediate_by_range(var_name, high, low, instr_name) + if candidate: + return [candidate] + print( + f"Warning: composite immediate candidate not found in fieldo for {var_name} with location {location}" + ) + return [] + else: + try: + high, low = map(int, location.split("-")) + except Exception: + print(f"Warning: invalid immediate location {location} for {var_name}") + return [] + candidate = lookup_immediate_by_range(var_name, high, low, instr_name) + if candidate: + return [candidate] + else: + print( + f"Warning: No fieldo canonical name for {var_name} with range {location}" + ) + return [] + + +def GetVariables(vars: List[Dict[str, str]], instr_name: str = "") -> List[str]: + """ + Process the YAML variable definitions and return a list of variable names as expected by the generator. + + - For registers (names in reg_names), the first character is replaced with "r". + - For "shamt", the field is renamed to "shamtw" if its width is 5 or "shamtd" if 6. + - For immediates (base names "imm", "simm", "zimm", "jimm", or those starting with "c_"), + the canonical names are determined strictly by looking them up in fieldo. + Only names found in fieldo are used. + + The variables are processed in reverse order. + """ + result = [] + for var in reversed(vars): + var_name = var["name"] + location = var.get("location", "") + if var_name in reg_names: + result.append("r" + var_name[1:]) + elif var_name == "shamt": + size = range_size(location) + if size == 5: + result.append("shamtw") + elif size == 6: + result.append("shamtd") + else: + result.append(var_name) + elif var_name in ("imm", "simm", "zimm", "jimm") or var_name.startswith("c_"): + canon_names = canonical_immediate_names(var_name, location, instr_name) + if canon_names: + result.extend(canon_names) + else: + print( + f"Warning: Skipping immediate field {var_name} with location {location} since no fieldo mapping was found." + ) + else: + result.append(var_name) + return result + + +def BitStringToHex(bit_str: str) -> str: + new_bit_str = "" + for bit in bit_str: + if bit == "-": + new_bit_str += "0" + else: + new_bit_str += bit + return hex(int(new_bit_str, 2)) + + +def GetMask(bit_str: str) -> str: + mask_str = "" + for bit in bit_str: + if bit == "-": + mask_str += "0" + else: + mask_str += "1" + return hex(int(mask_str, 2)) + + +def process_extension(ext: Union[str, dict]) -> List[str]: + """Process an extension definition into a list of strings.""" + if isinstance(ext, str): + return [ext.lower()] + elif isinstance(ext, dict): + result = [] + for item in ext.values(): + if isinstance(item, list): + result.extend( + [ + x.lower() if isinstance(x, str) else x["name"].lower() + for x in item + ] + ) + elif isinstance(item, (str, dict)): + if isinstance(item, str): + result.append(item.lower()) + else: + result.append(item["name"].lower()) + return result + return [] + + +def GetExtension(ext: Union[str, dict, list], base: str) -> List[str]: + """Get a list of extensions with proper prefix.""" + prefix = f"rv{base}_" + final_extensions = [] + + if isinstance(ext, (str, dict)): + extensions = process_extension(ext) + final_extensions.extend(prefix + x for x in extensions) + elif isinstance(ext, list): + for item in ext: + extensions = process_extension(item) + final_extensions.extend(prefix + x for x in extensions) + + # Remove duplicates while preserving order + seen = set() + return [x for x in final_extensions if not (x in seen or seen.add(x))] + + +def GetEncodings(enc: str) -> str: + n = len(enc) + if n < 32: + return "-" * (32 - n) + enc + return enc + + +def convert(file_dir: str, json_out: Dict[str, Any]) -> None: + try: + with open(file_dir) as file: + data = yaml.safe_load(file) + instr_name = data["name"].replace(".", "_") + + print(instr_name) + encodings = data["encoding"] + + # USE RV_64 + rv64_flag = False + if "RV64" in encodings: + encodings = encodings["RV64"] + rv64_flag = True + enc_match = GetEncodings(encodings["match"]) + + var_names = [] + if "variables" in encodings: + var_names = GetVariables(encodings["variables"]) + + extension = [] + prefix = "" + if rv64_flag: + prefix = "64" + try: + if "base" in data: + extension = GetExtension(data["definedBy"], data["base"]) + else: + extension = GetExtension(data["definedBy"], prefix) + except Exception as e: + print( + f"Warning: Error processing extensions for {instr_name}: {str(e)}" + ) + extension = [] + + match_hex = BitStringToHex(enc_match) + match_mask = GetMask(enc_match) + + json_out[instr_name] = { + "encoding": enc_match, + "variable_fields": var_names, + "extension": extension, + "match": match_hex, + "mask": match_mask, + } + except Exception as e: + print(f"Error processing file {file_dir}: {str(e)}") + raise + + +def read_yaml_insts(path: str) -> List[str]: + yaml_files = [] + for root, _, files in os.walk(path): + for file in files: + if file.endswith(".yaml") or file.endswith(".yml"): + yaml_files.append(os.path.join(root, file)) + return yaml_files + + +def main(): + parser = argparse.ArgumentParser( + description="Convert YAML instruction files to JSON" + ) + parser.add_argument("input_dir", help="Directory containing YAML instruction files") + parser.add_argument("output_dir", help="Output directory for generated files") + + args = parser.parse_args() + + # Ensure input directory exists + if not os.path.isdir(args.input_dir): + parser.error(f"Input directory does not exist: {args.input_dir}") + + insts = read_yaml_insts(args.input_dir) + if not insts: + parser.error(f"No YAML files found in {args.input_dir}") + + inst_dict = {} + output_file = os.path.join(args.output_dir, "instr_dict.json") + + try: + for inst_dir in insts: + try: + convert(inst_dir, inst_dict) + except Exception as e: + print(f"Warning: Failed to process {inst_dir}: {str(e)}") + continue + + with open(output_file, "w") as outfile: + json.dump(inst_dict, outfile, indent=4) + + print(f"Successfully processed {len(insts)} YAML files") + print(f"Output written to: {output_file}") + except Exception as e: + print(f"Error: Failed to process YAML files: {str(e)}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/ext/riscv-opcodes b/ext/riscv-opcodes index 5ce8977a5..9f70bcd37 160000 --- a/ext/riscv-opcodes +++ b/ext/riscv-opcodes @@ -1 +1 @@ -Subproject commit 5ce8977a5961a6bbfc1638e6676e60489665d882 +Subproject commit 9f70bcd37db27fddd34b4aa0524b8f19d31e9bb8