mirror of
https://github.com/riscv/riscv-opcodes.git
synced 2025-10-14 02:58:32 +08:00
Adding support to generate an svg from opcodes (#364)
* 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
This commit is contained in:
4
.github/workflows/python-app.yml
vendored
4
.github/workflows/python-app.yml
vendored
@@ -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
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ priv-instr-table.tex
|
||||
inst.rs
|
||||
inst.spinalhdl
|
||||
inst.sverilog
|
||||
inst.svg
|
||||
instr_dict.json
|
||||
|
||||
__pycache__/
|
||||
|
@@ -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]
|
||||
|
8
parse.py
8
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,
|
||||
)
|
||||
|
||||
|
||||
|
12
rv_colors.py
Normal file
12
rv_colors.py
Normal file
@@ -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",
|
||||
}
|
284
svg_utils.py
Normal file
284
svg_utils.py
Normal file
@@ -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)
|
Reference in New Issue
Block a user