Skip to content

Commit cbcb87e

Browse files
AliAlaa88AFOliveirajordancarlin
authored
feat: add SystemVerilog backend generator (#1090)
This pull request adds support for generating a SystemVerilog package containing RISC-V instruction and CSR definitions, and enhances the instruction encoding logic to support a new schema format. The changes include introducing a new generator script, updating the encoding extraction logic to handle new formats, and integrating the SystemVerilog generation into the build process. **SystemVerilog Generation:** - Added a new script `sverilog_generator.py` that generates a SystemVerilog package (`riscv_decode_package.svh`) with instruction and CSR encodings, including formatting and argument parsing for flexible generation. - Integrated a new Rake task (`gen:sverilog`) to invoke the SystemVerilog generator, with options for configuration and output directory. - Created the `gen/sverilog` output directory as part of the build process. **Instruction Encoding Enhancements:** - Added `build_match_from_format` function to extract encoding match strings from the new `format` field in instruction definitions, supporting opcodes with specific bit fields. - Updated instruction loading logic to use the new `build_match_from_format` when the legacy `encoding` field is missing, improving compatibility with new schema definitions. - Improved handling of RV32/RV64 instruction encodings to ensure both variants are processed and named correctly. **CSR Handling:** - Simplified CSR naming logic by always using the uppercase name without `.RV32` suffix, regardless of architecture. --------- Signed-off-by: Afonso Oliveira <[email protected]> Signed-off-by: Alieldin Alaa <[email protected]> Co-authored-by: Afonso Oliveira <[email protected]> Co-authored-by: Jordan Carlin <[email protected]>
1 parent e52bf81 commit cbcb87e

File tree

5 files changed

+408
-156
lines changed

5 files changed

+408
-156
lines changed

.github/workflows/regress.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,17 @@ jobs:
190190
uses: ./.github/actions/singularity-setup
191191
- name: Generate c_header code
192192
run: ./do gen:c_header
193+
regress-gen-sverilog:
194+
runs-on: ubuntu-latest
195+
env:
196+
SINGULARITY: 1
197+
steps:
198+
- name: Clone Github Repo Action
199+
uses: actions/checkout@v4
200+
- name: singularity setup
201+
uses: ./.github/actions/singularity-setup
202+
- name: Generate sverilog_header code
203+
run: ./do gen:sverilog
193204
regress-cpp-unit:
194205
runs-on: ubuntu-latest
195206
env:

backends/generators/c_header/generate_encoding.py

Lines changed: 1 addition & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import logging
99
import argparse
1010
import yaml
11-
import json
1211

1312
# Add parent directory to path to import generator.py
1413
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -18,6 +17,7 @@
1817
from generator import (
1918
load_instructions,
2019
load_csrs,
20+
load_exception_codes,
2121
parse_match,
2222
parse_extension_requirements,
2323
)
@@ -30,125 +30,6 @@ def calculate_mask(match_str):
3030
return int("".join("0" if c == "-" else "1" for c in match_str), 2)
3131

3232

33-
def load_exception_codes(
34-
ext_dir, enabled_extensions=None, include_all=False, resolved_codes_file=None
35-
):
36-
"""Load exception codes from extension YAML files or pre-resolved JSON file."""
37-
exception_codes = []
38-
found_extensions = 0
39-
found_files = 0
40-
41-
if enabled_extensions is None:
42-
enabled_extensions = []
43-
44-
# If we have a resolved codes file, use it instead of processing YAML files
45-
if resolved_codes_file and os.path.exists(resolved_codes_file):
46-
try:
47-
with open(resolved_codes_file, encoding="utf-8") as f:
48-
resolved_codes = json.load(f)
49-
50-
for code in resolved_codes:
51-
num = code.get("num")
52-
name = code.get("name")
53-
if num is not None and name is not None:
54-
sanitized_name = (
55-
name.lower()
56-
.replace(" ", "_")
57-
.replace("/", "_")
58-
.replace("-", "_")
59-
)
60-
exception_codes.append((num, sanitized_name))
61-
62-
logging.info(
63-
f"Loaded {len(exception_codes)} pre-resolved exception codes from {resolved_codes_file}"
64-
)
65-
66-
# Sort by exception code number and deduplicate
67-
seen_nums = set()
68-
unique_codes = []
69-
for num, name in sorted(exception_codes, key=lambda x: x[0]):
70-
if num not in seen_nums:
71-
seen_nums.add(num)
72-
unique_codes.append((num, name))
73-
74-
return unique_codes
75-
76-
except Exception as e:
77-
logging.error(
78-
f"Error loading resolved codes file {resolved_codes_file}: {e}"
79-
)
80-
# Fall back to processing YAML files
81-
82-
for dirpath, _, filenames in os.walk(ext_dir):
83-
for fname in filenames:
84-
if not fname.endswith(".yaml"):
85-
continue
86-
87-
found_files += 1
88-
path = os.path.join(dirpath, fname)
89-
90-
try:
91-
with open(path, encoding="utf-8") as f:
92-
data = yaml.safe_load(f)
93-
94-
if not isinstance(data, dict) or data.get("kind") != "extension":
95-
continue
96-
97-
found_extensions += 1
98-
ext_name = data.get("name", "unnamed")
99-
100-
# Skip extension filtering if include_all is True
101-
if not include_all:
102-
# Filter by extension requirements
103-
definedBy = data.get("definedBy")
104-
if definedBy:
105-
meets_req = parse_extension_requirements(definedBy)
106-
if not meets_req(enabled_extensions):
107-
continue
108-
109-
# Check if excluded
110-
excludedBy = data.get("excludedBy")
111-
if excludedBy:
112-
exclusion_check = parse_extension_requirements(excludedBy)
113-
if exclusion_check(enabled_extensions):
114-
continue
115-
116-
# Get exception codes
117-
for code in data.get("exception_codes", []):
118-
num = code.get("num")
119-
name = code.get("name")
120-
121-
if num is not None and name is not None:
122-
sanitized_name = (
123-
name.lower()
124-
.replace(" ", "_")
125-
.replace("/", "_")
126-
.replace("-", "_")
127-
)
128-
exception_codes.append((num, sanitized_name))
129-
130-
except Exception as e:
131-
logging.error(f"Error processing file {path}: {e}")
132-
133-
if found_extensions > 0:
134-
logging.info(
135-
f"Found {found_extensions} extension definitions in {found_files} files"
136-
)
137-
logging.info(f"Added {len(exception_codes)} exception codes to the output")
138-
else:
139-
logging.warning(f"No extension definitions found in {ext_dir}")
140-
141-
# Sort by exception code number and deduplicate
142-
seen_nums = set()
143-
unique_codes = []
144-
for num, name in sorted(exception_codes, key=lambda x: x[0]):
145-
if num not in seen_nums:
146-
seen_nums.add(num)
147-
unique_codes.append((num, name))
148-
149-
return unique_codes
150-
151-
15233
def extract_instruction_fields(instructions):
15334
"""Extract field names and their positions from instruction definitions."""
15435
field_dict = {}

backends/generators/generator.py

Lines changed: 124 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import yaml
44
import logging
55
import pprint
6+
import json
67

78
pp = pprint.PrettyPrinter(indent=2)
89
logging.basicConfig(level=logging.INFO, format="%(levelname)s:: %(message)s")
@@ -326,14 +327,15 @@ def load_instructions(
326327

327328
# Process RV64 encoding
328329
rv64_match = rv64_encoding.get("match")
330+
rv32_match = rv32_encoding.get("match")
331+
329332
if rv64_match:
330333
instr_dict[name] = {
331334
"match": rv64_match
332335
} # RV64 gets the default name
333336

334-
# Process RV32 encoding with a _rv32 suffix
335-
rv32_match = rv32_encoding.get("match")
336-
if rv32_match:
337+
if rv32_match and rv32_match != rv64_match:
338+
# Process RV32 encoding with a _rv32 suffix
337339
instr_dict[f"{name}_rv32"] = {"match": rv32_match}
338340

339341
continue # Skip the rest of the loop as we've already added the encodings
@@ -491,11 +493,7 @@ def load_csrs(csr_root, enabled_extensions, include_all=False, target_arch="RV64
491493
else:
492494
addr_int = int(addr_to_use, 0)
493495

494-
# For BOTH architecture, add suffix to RV32-specific CSRs
495-
if target_arch == "BOTH" and base == 32:
496-
csrs[addr_int] = f"{name.upper()}.RV32"
497-
else:
498-
csrs[addr_int] = name.upper()
496+
csrs[addr_int] = name.upper()
499497
except Exception as e:
500498
logging.error(f"Error parsing address {addr_to_use} in {path}: {e}")
501499
address_errors += 1
@@ -518,6 +516,124 @@ def load_csrs(csr_root, enabled_extensions, include_all=False, target_arch="RV64
518516
return csrs
519517

520518

519+
def load_exception_codes(
520+
ext_dir, enabled_extensions=None, include_all=False, resolved_codes_file=None
521+
):
522+
"""Load exception codes from extension YAML files or pre-resolved JSON file."""
523+
exception_codes = []
524+
found_extensions = 0
525+
found_files = 0
526+
527+
if enabled_extensions is None:
528+
enabled_extensions = []
529+
# If we have a resolved codes file, use it instead of processing YAML files
530+
if resolved_codes_file and os.path.exists(resolved_codes_file):
531+
try:
532+
with open(resolved_codes_file, encoding="utf-8") as f:
533+
resolved_codes = json.load(f)
534+
535+
for code in resolved_codes:
536+
num = code.get("num")
537+
name = code.get("name")
538+
if num is not None and name is not None:
539+
sanitized_name = (
540+
name.lower()
541+
.replace(" ", "_")
542+
.replace("/", "_")
543+
.replace("-", "_")
544+
)
545+
exception_codes.append((num, sanitized_name))
546+
547+
logging.info(
548+
f"Loaded {len(exception_codes)} pre-resolved exception codes from {resolved_codes_file}"
549+
)
550+
551+
# Sort by exception code number and deduplicate
552+
seen_nums = set()
553+
unique_codes = []
554+
for num, name in sorted(exception_codes, key=lambda x: x[0]):
555+
if num not in seen_nums:
556+
seen_nums.add(num)
557+
unique_codes.append((num, name))
558+
559+
return unique_codes
560+
561+
except Exception as e:
562+
logging.error(
563+
f"Error loading resolved codes file {resolved_codes_file}: {e}"
564+
)
565+
# Fall back to processing YAML files
566+
567+
for dirpath, _, filenames in os.walk(ext_dir):
568+
for fname in filenames:
569+
if not fname.endswith(".yaml"):
570+
continue
571+
572+
found_files += 1
573+
path = os.path.join(dirpath, fname)
574+
575+
try:
576+
with open(path, encoding="utf-8") as f:
577+
data = yaml.safe_load(f)
578+
579+
if not isinstance(data, dict) or data.get("kind") != "extension":
580+
continue
581+
582+
found_extensions += 1
583+
ext_name = data.get("name", "unnamed")
584+
585+
# Skip extension filtering if include_all is True
586+
if not include_all:
587+
# Filter by extension requirements
588+
definedBy = data.get("definedBy")
589+
if definedBy:
590+
meets_req = parse_extension_requirements(definedBy)
591+
if not meets_req(enabled_extensions):
592+
continue
593+
594+
# Check if excluded
595+
excludedBy = data.get("excludedBy")
596+
if excludedBy:
597+
exclusion_check = parse_extension_requirements(excludedBy)
598+
if exclusion_check(enabled_extensions):
599+
continue
600+
601+
# Get exception codes
602+
for code in data.get("exception_codes", []):
603+
num = code.get("num")
604+
name = code.get("name")
605+
606+
if num is not None and name is not None:
607+
sanitized_name = (
608+
name.lower()
609+
.replace(" ", "_")
610+
.replace("/", "_")
611+
.replace("-", "_")
612+
)
613+
exception_codes.append((num, sanitized_name))
614+
615+
except Exception as e:
616+
logging.error(f"Error processing file {path}: {e}")
617+
618+
if found_extensions > 0:
619+
logging.info(
620+
f"Found {found_extensions} extension definitions in {found_files} files"
621+
)
622+
logging.info(f"Added {len(exception_codes)} exception codes to the output")
623+
else:
624+
logging.warning(f"No extension definitions found in {ext_dir}")
625+
626+
# Sort by exception code number and deduplicate
627+
seen_nums = set()
628+
unique_codes = []
629+
for num, name in sorted(exception_codes, key=lambda x: x[0]):
630+
if num not in seen_nums:
631+
seen_nums.add(num)
632+
unique_codes.append((num, name))
633+
634+
return unique_codes
635+
636+
521637
def parse_match(match_str):
522638
"""
523639
Convert the bit pattern string to an integer.

0 commit comments

Comments
 (0)