223 lines
6.8 KiB
Python
223 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
|
import argparse
|
|
import json
|
|
from abc import abstractmethod
|
|
from pathlib import Path
|
|
from typing import Union
|
|
|
|
from jsonschema import ValidationError, validate
|
|
|
|
INSTRUCTION_SCHEMA = {
|
|
"type": "object",
|
|
"properties": {
|
|
"instructions": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"decoding": {"type": "string"},
|
|
},
|
|
"required": ["name", "decoding"],
|
|
},
|
|
}
|
|
},
|
|
"required": ["instructions"],
|
|
}
|
|
|
|
|
|
class EncodingPart:
|
|
pos: int = 0
|
|
size: int = 0
|
|
|
|
@abstractmethod
|
|
def coredsl(self) -> str:
|
|
pass
|
|
|
|
|
|
class Field(EncodingPart):
|
|
def get_bounds(self, s: str) -> tuple[int, int]:
|
|
if ":" in s:
|
|
end, start = s[s.index("[") :].strip("[").rstrip("]").split(":")
|
|
else:
|
|
start = end = int(s[s.index("[") :].strip("[").rstrip("]"))
|
|
return int(start), int(end)
|
|
|
|
def __init__(self, s: str) -> None:
|
|
assert "[" in s and "]" in s
|
|
assert s.count("[") == 1 and s.count("]")
|
|
self.start, self.end = self.get_bounds(s)
|
|
self.size = self.end - self.start + 1
|
|
self.name = s[: s.index("[")]
|
|
|
|
def __str__(self) -> str:
|
|
shift = " << " if self.pos >= self.start else ">>"
|
|
shamt = str(self.pos)
|
|
value_str = f"(\\{self.name} & 0b{self.size * '1'})"
|
|
if self.start:
|
|
value_str = f"(\\{self.name} & 0b{self.size * '1'}{self.start * '0'})"
|
|
shamt = str(abs(self.pos - self.start))
|
|
return value_str + shift + shamt if self.pos - self.start else value_str
|
|
|
|
def coredsl(self) -> str:
|
|
middle = (
|
|
f"[{self.end}:{self.start}]"
|
|
if self.size > 1
|
|
else f"[{self.end}:{self.end}]"
|
|
)
|
|
return self.name + middle
|
|
|
|
def __repr__(self) -> str:
|
|
return "Field: " + self.coredsl()
|
|
|
|
|
|
class Literal(EncodingPart):
|
|
def __init__(self, s: str) -> None:
|
|
assert len(s.replace("1", "").replace("0", "")) == 0
|
|
self.value = int(s, 2)
|
|
self.size = len(s)
|
|
|
|
def __str__(self) -> str:
|
|
shamt = str(self.pos)
|
|
value_str = format(self.value, f"#0{self.size + 2}b")
|
|
return value_str + " << " + shamt if self.pos else value_str
|
|
|
|
def coredsl(self) -> str:
|
|
return format(self.value, f"#0{self.size + 2}b")
|
|
|
|
def __repr__(self) -> str:
|
|
return "Literal: " + self.coredsl()
|
|
|
|
|
|
class Encoding:
|
|
name: str
|
|
parts: tuple[EncodingPart, ...]
|
|
|
|
def __init__(self, d: dict) -> None:
|
|
s = d["decoding"]
|
|
parts = s.split("|")
|
|
idx = 0
|
|
buf: list[EncodingPart] = []
|
|
# reverse to determine position easier
|
|
for part in reversed(parts):
|
|
typed = Field(part) if "[" in part else Literal(part)
|
|
typed.pos = idx
|
|
idx += typed.size
|
|
buf.append(typed)
|
|
self.parts = tuple(reversed(buf))
|
|
self.name = d["name"].upper()
|
|
|
|
def _get_masked_enc(self) -> str:
|
|
masked_enc = "".join(
|
|
[
|
|
bin(elem.value)[2:].zfill(elem.size)
|
|
if isinstance(elem, Literal)
|
|
else elem.size * "x"
|
|
for elem in self.parts
|
|
]
|
|
)
|
|
return masked_enc
|
|
|
|
def _collapse_literals(self):
|
|
new_parts: list[EncodingPart] = []
|
|
for part in self.parts:
|
|
if (
|
|
isinstance(part, Literal)
|
|
and new_parts
|
|
and isinstance(new_parts[-1], Literal)
|
|
):
|
|
new_value = (new_parts[-1].value << part.size) + part.value
|
|
new_size = new_parts[-1].size + part.size
|
|
combined_val = format(new_value, f"#0{new_size + 2}b")
|
|
collapsed = Literal(combined_val[2:])
|
|
new_parts[-1] = collapsed
|
|
else:
|
|
new_parts.append(part)
|
|
self.parts = tuple(new_parts)
|
|
|
|
def valid_size(self, target_size: int = -1):
|
|
total_size = sum([part.size for part in self.parts])
|
|
if target_size == -1:
|
|
enc_str = self._get_masked_enc()
|
|
if enc_str[-2:] != "11":
|
|
target_size = 16
|
|
elif enc_str[-4:-2] == "111":
|
|
# ILEN >32
|
|
target_size = -1
|
|
else:
|
|
target_size = 32
|
|
return total_size == target_size
|
|
|
|
def create_macro(self) -> str:
|
|
def riscv_sort_key(name: str) -> tuple[int, Union[int, str]]:
|
|
if name == "rd":
|
|
return (0, 0)
|
|
elif name.startswith("rs") and name[2:].isdigit():
|
|
return (1, int(name[2:]))
|
|
else:
|
|
return (2, name)
|
|
|
|
field_names = [elem.name for elem in self.parts if isinstance(elem, Field)]
|
|
unique_field_names = list(dict.fromkeys(field_names))
|
|
fields_str = ", ".join(sorted(unique_field_names, key=riscv_sort_key))
|
|
header = f".macro {self.name}{',' if len(unique_field_names) > 0 else ''} {fields_str}"
|
|
indent = " "
|
|
|
|
comment = "# Encoding parts: " + " ".join(
|
|
[elem.coredsl() for elem in self.parts]
|
|
)
|
|
self._collapse_literals()
|
|
strs = [str(elem) for elem in self.parts]
|
|
content = ".word " + " | ".join(strs)
|
|
|
|
tail = ".endm"
|
|
return "\n".join([header, indent + comment, indent + content, tail])
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate assembler macros from CoreDSL2JSON output."
|
|
)
|
|
parser.add_argument(
|
|
"path", type=Path, help="Path to the JSON file generated by CoreDSL2JSON."
|
|
)
|
|
parser.add_argument(
|
|
"--name",
|
|
type=str,
|
|
help="Name of the instruction to generate the macro for (optional).",
|
|
)
|
|
parser.add_argument(
|
|
"--size",
|
|
type=int,
|
|
default=-1,
|
|
help="Instruction size in bits. If not set, checks lowest bits of the instruction and determines size according to default RISC-V specification.",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def load_and_validate(path: Path):
|
|
data = json.loads(path.read_text())
|
|
try:
|
|
validate(instance=data, schema=INSTRUCTION_SCHEMA)
|
|
except ValidationError as e:
|
|
raise ValueError(f"Invalid JSON format: {e.message}") from e
|
|
return data
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
data = load_and_validate(args.path)
|
|
for instruction in data["instructions"]:
|
|
if args.name and args.name.upper() != instruction["name"]:
|
|
continue
|
|
enc = Encoding(instruction)
|
|
if not enc.valid_size(args.size):
|
|
print(f"Invalid size for {enc.name}")
|
|
continue
|
|
print(enc.create_macro())
|
|
print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|