aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Herber <christian.herber@oss.nxp.com>2025-08-08 22:42:05 +0200
committerGitHub <noreply@github.com>2025-08-08 13:42:05 -0700
commit767b74181f2ee325a2f6a2606620530d743553d2 (patch)
tree9eccf37ce5255dcb2ec35fda216db9a138a8dba1
parent1d4b38c868963b312d3289fbcf41bc11a69872b0 (diff)
downloadriscv-opcodes-master.zip
riscv-opcodes-master.tar.gz
riscv-opcodes-master.tar.bz2
Adding support to generate an svg from opcodes (#364)HEADmaster
* Adding support to generate an svg from opcodes * Attempt to fix linting issues * Adding matplotlib dependency to precommit hooks * Update linting changes * Added typed tuples * Fixing further linting issue (WiP) * Resolved all linting issue Matplotlib types cannot be resolved by the linter. Matplotlib calles are ignored for type checking * Adding matplotlib to dependencies and svg to output for coverage
-rw-r--r--.github/workflows/python-app.yml4
-rw-r--r--.gitignore1
-rw-r--r--.pre-commit-config.yaml2
-rwxr-xr-xparse.py8
-rw-r--r--rv_colors.py12
-rw-r--r--svg_utils.py284
6 files changed, 309 insertions, 2 deletions
diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml
index 0ba3ac0..eb1dbc1 100644
--- a/.github/workflows/python-app.yml
+++ b/.github/workflows/python-app.yml
@@ -30,13 +30,13 @@ jobs:
${{ runner.os }}-pre-commit-
- name: Install dependencies
- run: python3 -m pip install pre-commit coverage
+ run: python3 -m pip install pre-commit coverage matplotlib
- name: Run pre-commit
run: pre-commit run --all-files
- name: Generate
- run: coverage run ./parse.py -c -chisel -sverilog -rust -latex -spinalhdl -go "rv*" "unratified/rv*"
+ run: coverage run ./parse.py -c -chisel -sverilog -rust -latex -spinalhdl -svg -go "rv*" "unratified/rv*"
- name: Check C output
run: cat encoding.out.h | cpp
diff --git a/.gitignore b/.gitignore
index 1380615..3ce74bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ priv-instr-table.tex
inst.rs
inst.spinalhdl
inst.sverilog
+inst.svg
instr_dict.json
__pycache__/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index dc25bc1..9d27e66 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -25,8 +25,10 @@ repos:
rev: v3.3.1
hooks:
- id: pylint
+ additional_dependencies: [matplotlib]
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.383
hooks:
- id: pyright
+ additional_dependencies: [matplotlib]
diff --git a/parse.py b/parse.py
index 8aebf5c..29853af 100755
--- a/parse.py
+++ b/parse.py
@@ -13,6 +13,7 @@ from latex_utils import make_latex_table, make_priv_latex_table
from rust_utils import make_rust
from shared_utils import add_segmented_vls_insn, create_inst_dict
from sverilog_utils import make_sverilog
+from svg_utils import make_svg
LOG_FORMAT = "%(levelname)s:: %(message)s"
LOG_LEVEL = logging.INFO
@@ -31,6 +32,7 @@ def generate_extensions(
rust: bool,
go: bool,
latex: bool,
+ svg: bool,
):
instr_dict = create_inst_dict(extensions, include_pseudo)
instr_dict = dict(sorted(instr_dict.items()))
@@ -73,6 +75,10 @@ def generate_extensions(
make_priv_latex_table()
logging.info("priv-instr-table.tex generated successfully")
+ if svg:
+ make_svg(instr_dict)
+ logging.info("inst.svg generated successfully")
+
def main():
parser = argparse.ArgumentParser(description="Generate RISC-V constants headers")
@@ -92,6 +98,7 @@ def main():
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")
+ parser.add_argument("-svg", action="store_true", help="Generate .svg output")
parser.add_argument(
"extensions",
nargs="*",
@@ -112,6 +119,7 @@ def main():
args.rust,
args.go,
args.latex,
+ args.svg,
)
diff --git a/rv_colors.py b/rv_colors.py
new file mode 100644
index 0000000..76e53a5
--- /dev/null
+++ b/rv_colors.py
@@ -0,0 +1,12 @@
+palette = {
+ "Berkeley Blue": "#003262",
+ "California Gold": "#FDB515",
+ "Dark Blue": "#011e41",
+ "Teal": "#0a6b7c",
+ "Magenta": "#cb007b",
+ "Purple": "#60269e",
+ "Light Gold": "#fdda64",
+ "Light Teal": "#62cbc9",
+ "Pink": "#fe9bb1",
+ "Lavender": "#c2a6e1",
+}
diff --git a/svg_utils.py b/svg_utils.py
new file mode 100644
index 0000000..d0d4a2e
--- /dev/null
+++ b/svg_utils.py
@@ -0,0 +1,284 @@
+import logging
+import pprint
+from typing import Dict, List, NamedTuple
+
+from matplotlib import patches
+from matplotlib import pyplot as plt
+
+from rv_colors import palette
+from shared_utils import InstrDict, instr_dict_2_extensions
+
+pp = pprint.PrettyPrinter(indent=2)
+logging.basicConfig(level=logging.INFO, format="%(levelname)s:: %(message)s")
+
+
+class RectangleDimensions(NamedTuple):
+ x: float
+ y: float
+ w: float
+ h: float
+
+
+class InstrRectangle(NamedTuple):
+ dims: RectangleDimensions
+ extension: str
+ label: str
+
+
+InstrDimsDict = Dict[str, RectangleDimensions]
+
+
+def encoding_to_rect(encoding: str) -> RectangleDimensions:
+ """Convert a binary encoding string to rectangle dimensions."""
+
+ def calculate_size(free_bits: int, tick: float) -> float:
+ """Calculate size based on number of free bits and tick value."""
+ return 2**free_bits * tick
+
+ instr_length = len(encoding)
+ # starting position
+ x = 0
+ y = 0
+ x_tick = 1 / (2 ** (0.5 * instr_length))
+ y_tick = 1 / (2 ** (0.5 * instr_length))
+ x_free_bits = 0
+ y_free_bits = 0
+ even = encoding[0::2]
+ odd = encoding[1::2]
+ # Process bits from least significant to most significant
+ for i, bit in enumerate(encoding):
+ if bit == "1":
+ offset = 0.5 / (2 ** int(i / 2))
+ if i % 2 == 0:
+ y += offset
+ else:
+ x += offset
+ elif bit == "0":
+ pass
+ # position not adjusted on 0
+
+ x_free_bits = odd.count("-")
+ y_free_bits = even.count("-")
+ x_size = calculate_size(x_free_bits, x_tick)
+ y_size = calculate_size(y_free_bits, y_tick)
+
+ # If we came here, encoding can be visualized with a single rectangle
+ rectangle = RectangleDimensions(x=x, y=y, w=x_size, h=y_size)
+ return rectangle
+
+
+FIGSIZE = 128
+
+
+def plot_image(
+ instr_dict: InstrDict,
+ instr_dims_dict: InstrDimsDict,
+ extension_sizes: Dict[str, float],
+) -> None:
+ """Plot the instruction rectangles using matplotlib."""
+
+ def get_readable_font_color(bg_hex: str) -> str:
+ """Determine readable font color based on background color."""
+
+ def hex_to_rgb(hex_color: str) -> tuple[int, int, int]:
+ """Convert hex color string to RGB tuple."""
+ hex_color = hex_color.lstrip("#")
+ r = int(hex_color[0:2], 16)
+ g = int(hex_color[2:4], 16)
+ b = int(hex_color[4:6], 16)
+
+ return (r, g, b)
+
+ r, g, b = hex_to_rgb(bg_hex)
+ luminance = 0.299 * r + 0.587 * g + 0.114 * b
+ return "#000000" if luminance > 186 else "#FFFFFF"
+
+ def plot_with_matplotlib(
+ rectangles: list[InstrRectangle],
+ colors: list[str],
+ hatches: list[str],
+ extensions: list[str],
+ ) -> None:
+ """Plot rectangles with matplotlib using specified styles."""
+
+ _, ax = plt.subplots(figsize=(FIGSIZE, FIGSIZE), facecolor="none") # type: ignore
+ ax.set_facecolor("none") # type: ignore
+ linewidth = FIGSIZE / 100
+ for dims, ext, label in rectangles:
+ x, y, w, h = dims
+ ext_idx = extensions.index(ext)
+ color = colors[ext_idx]
+ hatch = hatches[ext_idx]
+ rect = patches.Rectangle(
+ (x, y),
+ w,
+ h,
+ linewidth=linewidth,
+ edgecolor="black",
+ facecolor=color,
+ hatch=hatch,
+ alpha=1.0,
+ )
+ ax.add_patch(rect)
+
+ if w >= h:
+ base_dim = w
+ rotation = 0
+ else:
+ base_dim = h
+ rotation = 90
+
+ # Scale font size based on base dimension and label length
+ n_chars = len(label)
+ font_size = (
+ base_dim / n_chars * 90 * FIGSIZE
+ ) # Adjust scaling factor as needed
+ if font_size > 1:
+ fontdict = {
+ "fontsize": font_size,
+ "color": get_readable_font_color(color),
+ "family": "DejaVu Sans Mono",
+ }
+ ax.text( # type: ignore
+ x + w / 2,
+ y + h / 2,
+ label,
+ ha="center",
+ va="center",
+ fontdict=fontdict,
+ rotation=rotation,
+ )
+
+ plt.axis("off") # type: ignore
+ plt.tight_layout() # type: ignore
+ plt.savefig("inst.svg", format="svg") # type: ignore
+ plt.show() # type: ignore
+
+ extensions: List[str] = sorted(
+ extension_sizes.keys(), key=lambda k: extension_sizes[k], reverse=True
+ )
+
+ rectangles: List[InstrRectangle] = []
+ for instr in instr_dict:
+ dims = instr_dims_dict[instr]
+ rectangles.append(
+ InstrRectangle(
+ dims=dims,
+ extension=instr_dict[instr]["extension"][0],
+ label=instr.replace("_", "."),
+ )
+ )
+
+ # sort rectangles so that small ones are in the foreground
+ # An overlap occurs e.g. for pseudo ops, and these should be on top of the encoding it reuses
+ rectangles = sorted(rectangles, key=lambda x: x.dims.w * x.dims.h, reverse=True)
+
+ colors, hatches = generate_styles(extensions)
+
+ plot_with_matplotlib(rectangles, colors, hatches, extensions)
+
+
+def generate_styles(extensions: list[str]) -> tuple[list[str], list[str]]:
+ """Generate color and hatch styles for extensions."""
+ n_colors = len(palette)
+ colors = [""] * len(extensions)
+ hatches = [""] * len(extensions)
+ hatch_options = ["", "/", "\\", "|", "-", "+", "x", ".", "*"]
+ color_options = list(palette.values())
+
+ for i in range(len(extensions)):
+ colors[i] = color_options[i % n_colors]
+ hatches[i] = hatch_options[int(i / n_colors)]
+
+ return colors, hatches
+
+
+def defragment_encodings(
+ encodings: list[str], length: int = 32, offset: int = 0
+) -> list[str]:
+ """Defragment a list of binary encodings by reordering bits."""
+ # determine bit position which has the most fixed bits
+ fixed_encodings = ["0", "1"]
+ fixed_bits = [0] * length
+ fixed_encoding_indeces: Dict[str, List[int]] = {
+ value: [] for value in fixed_encodings
+ }
+ for index, encoding in enumerate(encodings):
+ for position, value in enumerate(encoding):
+ if position > offset:
+ if value != "-":
+ fixed_bits[position] += 1
+
+ # find bit position with most fixed bits, starting with the LSB to favor the opcode field
+ max_fixed_bits = max(fixed_bits)
+ if max_fixed_bits == 0:
+ # fully defragemented
+ return encodings
+ max_fixed_position = len(fixed_bits) - 1 - fixed_bits[::-1].index(max_fixed_bits)
+
+ # move bit position with the most fixed bits to the front
+ for index, encoding in enumerate(encodings):
+ encodings[index] = (
+ encoding[0:offset]
+ + encoding[max_fixed_position]
+ + encoding[offset:max_fixed_position]
+ + encoding[max_fixed_position + 1 :]
+ )
+
+ if encoding[max_fixed_position] in fixed_encodings:
+ fixed_encoding_indeces[encoding[max_fixed_position]].append(index)
+ else:
+ # No more fixed bits in this encoding
+ pass
+
+ if offset < length:
+ # continue to defragement starting from the next offset
+ offset = offset + 1
+
+ # separate encodings
+ sep_encodings: Dict[str, List[str]] = {}
+ for fixed_encoding in fixed_encodings:
+ sep_encodings[fixed_encoding] = [
+ encodings[i] for i in fixed_encoding_indeces[fixed_encoding]
+ ]
+ sep_encodings[fixed_encoding] = defragment_encodings(
+ sep_encodings[fixed_encoding], length=length, offset=offset
+ )
+
+ # join encodings
+ for new_index, orig_index in enumerate(
+ fixed_encoding_indeces[fixed_encoding]
+ ):
+ encodings[orig_index] = sep_encodings[fixed_encoding][new_index]
+
+ return encodings
+
+
+def defragment_encoding_dict(instr_dict: InstrDict) -> InstrDict:
+ """Apply defragmentation to the encoding dictionary."""
+ encodings = [instr["encoding"] for instr in instr_dict.values()]
+ encodings_defragemented = defragment_encodings(encodings, length=32, offset=0)
+ for index, instr in enumerate(instr_dict):
+ instr_dict[instr]["encoding"] = encodings_defragemented[index]
+ return instr_dict
+
+
+def make_svg(instr_dict: InstrDict) -> None:
+ """Generate an SVG image from instruction encodings."""
+ extensions = instr_dict_2_extensions(instr_dict)
+ extension_size: Dict[str, float] = {}
+
+ instr_dict = defragment_encoding_dict(instr_dict)
+ instr_dims_dict: InstrDimsDict = {}
+
+ for ext in extensions:
+ extension_size[ext] = 0
+
+ for instr in instr_dict:
+ dims = encoding_to_rect(instr_dict[instr]["encoding"])
+
+ extension_size[instr_dict[instr]["extension"][0]] += dims.h * dims.w
+
+ instr_dims_dict[instr] = dims
+
+ plot_image(instr_dict, instr_dims_dict, extension_size)