Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/regress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,31 @@ jobs:
run: ./bin/build_container
- name: Generate extension PDF
run: ./do gen:profile[MockProfileRelease]
regress-gen-go:
runs-on: ubuntu-latest
env:
SINGULARITY: 1
steps:
- name: Clone Github Repo Action
uses: actions/checkout@v4
- 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 Go code
run: ./do gen:go
Comment on lines +195 to +221

Check warning

Code scanning / CodeQL

Workflow does not contain permissions

Actions Job or Workflow does not set permissions
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,8 @@ namespace :test do
Rake::Task["#{$root}/gen/certificate_doc/pdf/MockCertificateModel.pdf"].invoke
Rake::Task["#{$root}/gen/profile_doc/pdf/MockProfileRelease.pdf"].invoke

Rake::Task["gen:go"].invoke

puts
puts "Regression test PASSED"
end
Expand Down
170 changes: 170 additions & 0 deletions backends/generators/Go/go_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#!/usr/bin/env python3
import os
import sys
import argparse
import logging

# Add parent directory to path to find generator.py
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from generator import load_instructions, load_csrs, parse_match, signed

logging.basicConfig(level=logging.INFO, format="%(levelname)s:: %(message)s")


def make_go(instr_dict, csrs, output_file="inst.go"):
"""
Generate a Go source file with the instruction encodings followed by
a map of CSR names and addresses.
"""
args = " ".join(sys.argv)
prelude = f"// Code generated by {args}; DO NOT EDIT.\n"
prelude += """package riscv

import "cmd/internal/obj"

type inst struct {
opcode uint32
funct3 uint32
rs1 uint32
rs2 uint32
csr int64
funct7 uint32
}

func encode(a obj.As) *inst {
switch a {
"""

instr_str = ""
# Process instructions in sorted order (by name)
for name, info in sorted(instr_dict.items(), key=lambda x: x[0].upper()):
match_str = info["match"]
enc_match = parse_match(match_str)
opcode = (enc_match >> 0) & ((1 << 7) - 1)
funct3 = (enc_match >> 12) & ((1 << 3) - 1)
rs1 = (enc_match >> 15) & ((1 << 5) - 1)
rs2 = (enc_match >> 20) & ((1 << 5) - 1)
csr_val = (enc_match >> 20) & ((1 << 12) - 1)
funct7 = (enc_match >> 25) & ((1 << 7) - 1)
# Create the instruction case name. For example, "bclri" becomes "ABCLRI"
instr_case = f"A{name.upper().replace('.','')}"
instr_str += f""" case {instr_case}:
return &inst{{ {hex(opcode)}, {hex(funct3)}, {hex(rs1)}, {hex(rs2)}, {signed(csr_val,12)}, {hex(funct7)} }}
"""
instructions_end = """ }
return nil
}
"""

# Build the CSR map block - now matching the second script's format
csrs_map_str = "var csrs = map[uint16]string {\n"
# Convert the dictionary to a list of tuples and sort by address
csr_items = [(int(addr), name.upper()) for addr, name in csrs.items()]
for addr, name in sorted(csr_items, key=lambda x: x[0]):
csrs_map_str += f'{hex(addr)} : "{name}",\n'
csrs_map_str += "}\n"

go_code = prelude + instr_str + instructions_end + "\n" + csrs_map_str

with open(output_file, "w", encoding="utf-8") as f:
f.write(go_code)
logging.info(
f"Generated {output_file} with {len(instr_dict)} instructions and {len(csrs)} CSRs"
)


def parse_args():
parser = argparse.ArgumentParser(
description="Generate Go code for RISC-V instructions and CSRs filtered by extensions"
)
parser.add_argument(
"--inst-dir",
default="../../../arch/inst/",
help="Directory containing instruction YAML files",
)
parser.add_argument(
"--csr-dir",
default="../../../arch/csr/",
help="Directory containing CSR YAML files",
)
parser.add_argument("--output", default="inst.go", help="Output Go file name")
parser.add_argument(
"--extensions",
default="A,D,F,I,M,Q,Zba,Zbb,Zbs,S,System,V,Zicsr,Smpmp,Sm,H,U,Zicntr,Zihpm,Smhpm",
help="Comma-separated list of enabled extensions. Default includes standard extensions.",
)
parser.add_argument(
"--arch",
default="RV64",
choices=["RV32", "RV64", "BOTH"],
help="Target architecture (RV32, RV64, or BOTH). Default is RV64.",
)
parser.add_argument(
"--verbose", "-v", action="store_true", help="Enable verbose logging"
)
parser.add_argument(
"--include-all",
"-a",
action="store_true",
help="Include all instructions, ignoring extension filtering",
)
return parser.parse_args()


def main():
args = parse_args()

if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)

# Check if we should include all instructions
include_all = args.include_all or not args.extensions

# Parse enabled extensions
if include_all:
enabled_extensions = []
logging.info(
"Including all instructions and CSRs (extension filtering disabled)"
)
else:
# Get extensions from the command line
enabled_extensions = [
ext.strip() for ext in args.extensions.split(",") if ext.strip()
]
logging.info(f"Enabled extensions: {', '.join(enabled_extensions)}")

# Log target architecture
logging.info(f"Target architecture: {args.arch}")

# Check if the directories exist
if not os.path.isdir(args.inst_dir):
logging.error(f"Instruction directory not found: {args.inst_dir}")
sys.exit(1)
if not os.path.isdir(args.csr_dir):
logging.warning(f"CSR directory not found: {args.csr_dir}")

# Load instructions filtered by extensions or all instructions
instr_dict = load_instructions(
args.inst_dir, enabled_extensions, include_all, args.arch
)
if not instr_dict:
logging.error("No instructions found or all were filtered out.")
logging.error(
"Try using --verbose to see more details about the filtering process."
)
sys.exit(1)
logging.info(f"Loaded {len(instr_dict)} instructions")

# Load CSRs filtered by extensions or all CSRs
csrs = load_csrs(args.csr_dir, enabled_extensions, include_all, args.arch)
if not csrs:
logging.warning("No CSRs found or all were filtered out.")
else:
logging.info(f"Loaded {len(csrs)} CSRs")

# Generate the Go code
make_go(instr_dict, csrs, args.output)


if __name__ == "__main__":
main()
Loading