Skip to content

Commit cfbf86c

Browse files
committed
feat: add SystemVerilog backend generator
Implements a new backend generator for SystemVerilog output, matching the exact format used by riscv-opcodes/inst.sverilog. This provides direct compatibility with hardware designs using the riscv-opcodes SystemVerilog package format. Features: - Generates SystemVerilog package with instruction and CSR definitions - Outputs 32-bit instruction patterns with proper bit encoding - Handles compressed (16-bit) instructions correctly - Supports all standard RISC-V extensions - Integrated with the ./do build system as gen:sverilog task The generator produces output identical to riscv-opcodes format: - Instructions as 'localparam [31:0] NAME = 32'bpattern' - CSRs as 'localparam logic [11:0] CSR_NAME = 12'haddr' - Proper alignment and formatting for readability Tested against riscv-opcodes/inst.sverilog to ensure format compatibility. Signed-off-by: Afonso Oliveira <afonsoo@synopsys.com>
1 parent aab7d0f commit cfbf86c

File tree

2 files changed

+204
-0
lines changed

2 files changed

+204
-0
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import os
5+
import sys
6+
import logging
7+
from pathlib import Path
8+
9+
# Add parent directory to path for imports
10+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
11+
12+
from generator import load_instructions, load_csrs
13+
14+
15+
def format_instruction_name(name):
16+
"""Format instruction name for SystemVerilog (uppercase with underscores)."""
17+
# Handle compressed instructions
18+
if name.startswith("c."):
19+
name = "C_" + name[2:]
20+
# Replace dots with underscores and convert to uppercase
21+
return name.replace(".", "_").upper()
22+
23+
24+
def format_csr_name(name):
25+
"""Format CSR name for SystemVerilog (uppercase with underscores)."""
26+
return "CSR_" + name.replace(".", "_").upper()
27+
28+
29+
def match_to_sverilog_bits(match_str, is_compressed=False):
30+
"""Convert a match string to SystemVerilog bit pattern."""
31+
if not match_str:
32+
return "32'b" + "?" * 32
33+
34+
# For compressed instructions (16-bit), we need to handle them differently
35+
# The riscv-opcodes format puts the 16-bit pattern in the lower 16 bits
36+
# with the upper 16 bits as wildcards
37+
if is_compressed or len(match_str) == 16:
38+
# Pad with wildcards on the left for 16-bit instructions
39+
match_str = "?" * 16 + match_str
40+
elif len(match_str) < 32:
41+
# For other cases, pad on the right
42+
match_str = match_str + "-" * (32 - len(match_str))
43+
44+
# Convert to SystemVerilog format (0, 1, or ?)
45+
result = []
46+
for bit in match_str:
47+
if bit == "0":
48+
result.append("0")
49+
elif bit == "1":
50+
result.append("1")
51+
else: # '-' or any other character
52+
result.append("?")
53+
54+
return "32'b" + "".join(result)
55+
56+
57+
def generate_sverilog(instructions, csrs, output_file):
58+
"""Generate SystemVerilog package file."""
59+
with open(output_file, "w") as f:
60+
# Write header
61+
f.write("\n/* Automatically generated by parse_opcodes */\n")
62+
f.write("package riscv_instr;\n")
63+
64+
# Find the maximum name length for alignment
65+
max_instr_len = max((len(format_instruction_name(name)) for name in instructions.keys()), default=0)
66+
max_csr_len = max((len(format_csr_name(csrs[addr])) for addr in csrs.keys()), default=0)
67+
max_len = max(max_instr_len, max_csr_len)
68+
69+
# Write instruction parameters
70+
for name in sorted(instructions.keys()):
71+
encoding = instructions[name]
72+
sv_name = format_instruction_name(name)
73+
# Pad the name for alignment
74+
padded_name = sv_name.ljust(max_len)
75+
76+
# Get the match pattern
77+
if isinstance(encoding, dict) and "match" in encoding:
78+
match = encoding["match"]
79+
else:
80+
# If no match field, use all wildcards
81+
match = "-" * 32
82+
83+
# Check if this is a compressed instruction
84+
is_compressed = name.startswith("c.")
85+
sv_bits = match_to_sverilog_bits(match, is_compressed)
86+
f.write(f" localparam [31:0] {padded_name} = {sv_bits};\n")
87+
88+
# Write CSR parameters
89+
# CSRs are returned as {address: name} by load_csrs
90+
for addr in sorted(csrs.keys()):
91+
csr_name = csrs[addr]
92+
sv_name = format_csr_name(csr_name)
93+
# Pad the name for alignment
94+
padded_name = sv_name.ljust(max_len)
95+
96+
# Format CSR address as 12-bit hex
97+
f.write(f" localparam logic [11:0] {padded_name} = 12'h{addr:03x};\n")
98+
99+
# Write footer
100+
f.write("\nendpackage\n")
101+
102+
103+
def parse_args():
104+
parser = argparse.ArgumentParser(
105+
description="Generate SystemVerilog package from RISC-V instruction definitions"
106+
)
107+
parser.add_argument(
108+
"--inst-dir",
109+
default="../../../gen/resolved_spec/_/inst/",
110+
help="Directory containing instruction YAML files",
111+
)
112+
parser.add_argument(
113+
"--csr-dir",
114+
default="../../../gen/resolved_spec/_/csr/",
115+
help="Directory containing CSR YAML files",
116+
)
117+
parser.add_argument(
118+
"--output",
119+
default="inst.sverilog",
120+
help="Output SystemVerilog file name"
121+
)
122+
parser.add_argument(
123+
"--extensions",
124+
default="A,D,F,I,M,Q,Zba,Zbb,Zbs,S,System,V,Zicsr,Smpmp,Sm,H,U,Zicntr,Zihpm,Smhpm",
125+
help="Comma-separated list of enabled extensions. Default includes standard extensions.",
126+
)
127+
parser.add_argument(
128+
"--arch",
129+
default="RV64",
130+
choices=["RV32", "RV64", "BOTH"],
131+
help="Target architecture (RV32, RV64, or BOTH). Default is RV64.",
132+
)
133+
parser.add_argument(
134+
"--verbose", "-v", action="store_true", help="Enable verbose logging"
135+
)
136+
parser.add_argument(
137+
"--include-all",
138+
action="store_true",
139+
help="Include all instructions and CSRs regardless of extensions",
140+
)
141+
return parser.parse_args()
142+
143+
144+
def main():
145+
args = parse_args()
146+
147+
# Set up logging
148+
log_level = logging.DEBUG if args.verbose else logging.INFO
149+
logging.basicConfig(level=log_level, format="%(levelname)s:: %(message)s")
150+
151+
# Parse extensions
152+
if args.include_all:
153+
enabled_extensions = []
154+
logging.info("Including all instructions and CSRs (ignoring extension filter)")
155+
else:
156+
enabled_extensions = [ext.strip() for ext in args.extensions.split(",")]
157+
logging.info(f"Enabled extensions: {', '.join(enabled_extensions)}")
158+
159+
logging.info(f"Target architecture: {args.arch}")
160+
161+
# Load instructions
162+
instructions = load_instructions(
163+
args.inst_dir, enabled_extensions, args.include_all, args.arch
164+
)
165+
logging.info(f"Loaded {len(instructions)} instructions")
166+
167+
# Load CSRs
168+
csrs = load_csrs(args.csr_dir, enabled_extensions, args.include_all, args.arch)
169+
logging.info(f"Loaded {len(csrs)} CSRs")
170+
171+
# Generate the SystemVerilog file
172+
generate_sverilog(instructions, csrs, args.output)
173+
logging.info(
174+
f"Generated {args.output} with {len(instructions)} instructions and {len(csrs)} CSRs"
175+
)
176+
177+
178+
if __name__ == "__main__":
179+
main()

backends/generators/tasks.rake

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require 'tempfile'
66

77
directory "#{$root}/gen/go"
88
directory "#{$root}/gen/c_header"
9+
directory "#{$root}/gen/sverilog"
910

1011
namespace :gen do
1112
desc <<~DESC
@@ -87,4 +88,28 @@ namespace :gen do
8788
resolved_codes_file.unlink
8889
end
8990
end
91+
92+
desc <<~DESC
93+
Generate SystemVerilog package from RISC-V instruction and CSR definitions
94+
95+
Options:
96+
* CONFIG - Configuration name (defaults to "_")
97+
* OUTPUT_DIR - Output directory for generated SystemVerilog code (defaults to "#{$root}/gen/sverilog")
98+
DESC
99+
task sverilog: "#{$root}/gen/sverilog" do
100+
config_name = ENV["CONFIG"] || "_"
101+
output_dir = ENV["OUTPUT_DIR"] || "#{$root}/gen/sverilog/"
102+
103+
# Ensure the output directory exists
104+
FileUtils.mkdir_p output_dir
105+
106+
# Get the arch paths based on the config
107+
resolver = Udb::Resolver.new
108+
cfg_arch = resolver.cfg_arch_for(config_name)
109+
inst_dir = cfg_arch.path / "inst"
110+
csr_dir = cfg_arch.path / "csr"
111+
112+
# Run the SystemVerilog generator script using the same Python environment
113+
sh "#{$root}/.home/.venv/bin/python3 #{$root}/backends/generators/sverilog/sverilog_generator.py --inst-dir=#{inst_dir} --csr-dir=#{csr_dir} --output=#{output_dir}inst.sverilog"
114+
end
90115
end

0 commit comments

Comments
 (0)