Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rdl2ot/src/rdl2ot/opentitan.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def needs_we(field: dict) -> bool:
RC registers, which should use a read-enable signal (connected to their
prim_subreg's we port).
"""
return field["reggen_sw_access"] != "RC" and field["sw_writable"]
return field["opentitan"]["reggen_sw_access"] != "RC" and field["sw_writable"]


def is_homogeneous(reg: dict) -> bool:
Expand Down
192 changes: 142 additions & 50 deletions rdl2ot/src/rdl2ot/rtl_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
"""Export RDL to opentitan RTL."""

import json
from enum import Enum
from pathlib import Path

from jinja2 import Environment, FileSystemLoader
from systemrdl import node
from systemrdl.rdltypes import OnReadType
from systemrdl.rdltypes.user_struct import UserStruct

from rdl2ot import opentitan

Expand All @@ -30,22 +32,25 @@ def run(root_node: node.AddrmapNode, out_dir: Path, is_soc: bool = False) -> Non
factory = OtInterfaceBuilder()
data = factory.parse_soc(root_node) if is_soc else factory.parse_ip_block(root_node)

Path(out_dir / "rdl.json").write_text(json.dumps(data, indent=2), encoding="utf-8")
json_file = Path(out_dir / "rdl.json")
json_file.write_text(json.dumps(data, indent=2), encoding="utf-8")
print(f"Generated {json_file}.")

if not is_soc:
_export(data, out_dir)
return

for ip_block in data["devices"]:
_export(ip_block, out_dir)
if ip_block["type"] == "device":
_export(ip_block, out_dir)


def _export(ip_block: dict, out_dir: Path) -> None:
file_loader = FileSystemLoader(TEMPLATES_DIR)
env = Environment(loader=file_loader)
env.filters["camelcase"] = _camelcase

ip_name = ip_block["ip_name"].lower()
ip_name = ip_block["name"].lower()
reg_pkg_tpl = env.get_template("reg_pkg.sv.tpl")
stream = reg_pkg_tpl.render(ip_block)
path = out_dir / f"{ip_name}_reg_pkg.sv"
Expand All @@ -55,13 +60,40 @@ def _export(ip_block: dict, out_dir: Path) -> None:
reg_top_tpl = env.get_template("reg_top.sv.tpl")
for interface in ip_block["interfaces"]:
name = "_{}".format(interface["name"].lower()) if "name" in interface else ""
data_ = {"ip_name": ip_name, "interface": interface}
data_ = {"name": ip_name, "interface": interface}
stream = reg_top_tpl.render(data_).replace(" \n", "\n")
path = out_dir / f"{ip_name}{name}_reg_top.sv"
path.open("w").write(stream)
print(f"Generated {path}.")


class SigType(Enum):
"""Used to give a signal different purposes."""

NONE = "None"
PadInOut = "PadInOut"
PadInput = "PadInput"
PadOutput = "PadOutput"
Interrupt = "Interrupt"

def is_pad(self) -> bool:
"""Check whether a signal is a pad."""
return self in [SigType.PadInOut, SigType.PadInput, SigType.PadOutput]

def is_interrupt(self) -> bool:
"""Check whether a signal is a interrupt."""
return self in [SigType.Interrupt]


class IoCombine(Enum):
"""May be used when a signal is a pad."""

NONE = "None"
Mux = "Mux"
And = "And"
Or = "Or"


class OtInterfaceBuilder:
"""OpenTitan Interface Builder."""

Expand All @@ -73,11 +105,33 @@ class OtInterfaceBuilder:
any_shadowed_reg: bool = False
reg_index: int = 0

def get_signal(self, sig: node.SignalNode) -> dict:
"""Parse a signal and return a dict."""
obj = {}
obj["name"] = sig.inst_name
kind = sig.get_property("sigtype")
obj["type"] = kind.name
if SigType(kind.name).is_pad():
obj["width"] = sig.get_property("signalwidth")
if combine := sig.get_property("io_combine"):
obj["combine"] = combine.name
return obj

def parse_array(self, node_: node.AddressableNode) -> list:
"""Parse an array node and return a list of offsets."""
offsets = []
if node_.is_array:
offset = node_.raw_address_offset
for _idx in range(node_.array_dimensions[0]):
offsets.append(offset)
offset += node_.array_stride
else:
offsets.append(node_.address_offset)
return offsets

def get_field(self, field: node.FieldNode) -> dict:
"""Parse a field and return a dictionary."""
obj = {}
obj["name"] = field.inst_name
obj["type"] = "field"
obj = {"name": field.inst_name, "type": "field", "type_name": field.type_name}
obj["desc"] = field.get_property("desc", default="")
obj["parent_name"] = field.parent.inst_name
obj["lsb"] = field.lsb
Expand All @@ -104,29 +158,32 @@ def get_field(self, field: node.FieldNode) -> dict:
obj["encode"] = encode.type_name
obj["async"] = field.get_property("async", default=None)
obj["sync"] = field.get_property("sync", default=None)
obj["reggen_sw_access"] = opentitan.get_sw_access_enum(field)
obj["opentitan"] = {"reggen_sw_access": opentitan.get_sw_access_enum(field)}
return obj

def get_mem(self, mem: node.FieldNode) -> dict:
"""Parse a memory and return a dictionary representing a window."""
obj = {}
obj["name"] = mem.inst_name
obj["type"] = "mem"
obj["entries"] = mem.get_property("mementries")
obj["sw_writable"] = mem.is_sw_writable
obj["sw_readable"] = mem.is_sw_readable
obj["width"] = mem.get_property("memwidth")
obj["offset"] = mem.address_offset
obj["size"] = obj["width"] * obj["entries"] // 8
obj["integrity_bypass"] = mem.get_property("integrity_bypass", default=False)
if udps := self.get_udps(mem):
obj["udps"] = udps

self.all_async_clk &= bool(mem.get_property("async_clk", default=False))
self.num_windows += 1
return obj

def get_reg(self, reg: node.RegNode) -> dict:
"""Parse a register and return a dictionary."""
obj = {}
obj["name"] = reg.inst_name
obj["type"] = "reg"
obj = {"name": reg.inst_name, "type": "reg", "type_name": reg.type_name}
obj["desc"] = reg.get_property("desc", default="")
obj["width"] = reg.get_property("regwidth")
obj["hw_readable"] = reg.has_hw_readable
obj["hw_writable"] = reg.has_hw_writable
Expand All @@ -138,23 +195,16 @@ def get_reg(self, reg: node.RegNode) -> dict:
obj["shadowed"] = reg.get_property("shadowed", default=False)
obj["hwre"] = reg.get_property("hwre", default=False)

obj["offsets"] = []
if reg.is_array:
obj["is_multireg"] = True
self.num_regs += reg.array_dimensions[0]
offset = reg.raw_address_offset
for _idx in range(reg.array_dimensions[0]):
obj["offsets"].append(offset)
offset += reg.array_stride
else:
self.num_regs += 1
obj["offsets"].append(reg.address_offset)
obj["offsets"] = self.parse_array(reg)
array_size = len(obj["offsets"])
self.num_regs += array_size
obj["is_multireg"] = array_size > 1

obj["fields"] = []
sw_write_en = False
msb = 0
reset_val = 0
bitmask = 0
obj["fields"] = []
for f in reg.fields():
field = self.get_field(f)
obj["fields"].append(field)
Expand All @@ -164,24 +214,25 @@ def get_reg(self, reg: node.RegNode) -> dict:
reset_val |= field.get("reset", 0) << field["lsb"]

obj["msb"] = msb
obj["permit"] = opentitan.register_permit_mask(obj)
obj["sw_write_en"] = sw_write_en
obj["bitmask"] = bitmask
obj["reset"] = reset_val
obj["async"] = False
obj["needs_write_en"] = opentitan.needs_write_en(obj)
obj["needs_read_en"] = opentitan.needs_read_en(obj)
obj["needs_qe"] = opentitan.needs_qe(obj)
obj["needs_int_qe"] = opentitan.needs_int_qe(obj)
obj["fields_no_write_en"] = opentitan.fields_no_write_en(obj)
obj["is_multifields"] = len(obj["fields"]) > 1
obj["is_homogeneous"] = opentitan.is_homogeneous(obj)
obj["opentitan"] = {
"permit": opentitan.register_permit_mask(obj),
"needs_write_en": opentitan.needs_write_en(obj),
"needs_read_en": opentitan.needs_read_en(obj),
"needs_qe": opentitan.needs_qe(obj),
"needs_int_qe": opentitan.needs_int_qe(obj),
"fields_no_write_en": opentitan.fields_no_write_en(obj),
"is_homogeneous": opentitan.is_homogeneous(obj),
}

self.any_async_clk |= bool(obj["async_clk"])
self.all_async_clk &= bool(obj["async_clk"])
self.any_shadowed_reg |= bool(obj["shadowed"])

array_size = len(obj["offsets"])
if bool(obj["async_clk"]):
for index in range(array_size):
reg_name = reg.inst_name + (f"_{index}" if array_size > 1 else "")
Expand All @@ -196,6 +247,21 @@ def get_paramesters(self, obj: node.AddrmapNode | node.RegfileNode) -> [dict]:
for param in obj.inst.parameters
]

def get_udps(self, obj: node.AddrmapNode | node.RegfileNode) -> [dict]:
"""Parse the customs properties and return a list of dictionaries."""
udps = obj.list_properties(include_native=False)
if len(udps) < 1:
return None
res = {}
for name in udps:
udp = obj.get_property(name)
if isinstance(udp, list) and isinstance(udp[0], UserStruct):
res.update({name: [dict(item.members) for item in udp]})
else:
res.update({name: udp})

return res

def get_interface(self, addrmap: node.AddrmapNode, defalt_name: None | str = None) -> dict:
"""Parse an interface and return a dictionary."""
self.num_regs = 0
Expand All @@ -222,6 +288,9 @@ def get_interface(self, addrmap: node.AddrmapNode, defalt_name: None | str = Non
elif isinstance(child, node.MemNode):
child_obj = self.get_mem(child)
interface["windows"].append(child_obj)
elif isinstance(child, node.SignalNode):
# Ignore: it should have being parsed by `parse_ip_block`
continue
else:
print(f"WARNING: Unsupported type: {type(child)}, skiping...")
continue
Expand Down Expand Up @@ -257,35 +326,37 @@ def get_interface(self, addrmap: node.AddrmapNode, defalt_name: None | str = Non

def parse_ip_block(self, ip_block: node.AddrmapNode) -> dict:
"""Parse the ip_block node of an IP block and return a dictionary."""
obj = {}
params = self.get_paramesters(ip_block)
if params:
obj = {"name": ip_block.inst_name, "type": "device", "type_name": ip_block.type_name}
if params := self.get_paramesters(ip_block):
obj["parameters"] = params
obj["ip_name"] = ip_block.inst_name

obj["offsets"] = []
if ip_block.is_array:
offset = ip_block.raw_address_offset
for _idx in range(ip_block.array_dimensions[0]):
obj["offsets"].append(offset)
offset += ip_block.array_stride
else:
obj["offsets"].append(ip_block.address_offset)

if udps := self.get_udps(ip_block):
obj["udps"] = udps

obj["offsets"] = self.parse_array(ip_block)
obj["size"] = ip_block.array_stride if ip_block.is_array else ip_block.size

obj["interfaces"] = []
obj["alerts"] = []
obj["pads"] = []
obj["interrupts"] = []
for child in ip_block.children():
if isinstance(child, node.AddrmapNode):
child_obj = self.get_interface(child, DEFAULT_INTERFACE_NAME)
obj["interfaces"].append(child_obj)
obj["alerts"].extend(child_obj["alerts"])
elif isinstance(child, node.SignalNode):
signal = self.get_signal(child)
if SigType(signal["type"]).is_pad():
obj["pads"].append(signal)
elif SigType(signal["type"]).is_interrupt():
obj["interrupts"].append(signal)
else:
print(f"WARNING: Unsupported signal type: {signal}.")
elif isinstance(child, node.RegNode | node.MemNode | node.RegfileNode):
continue
else:
print(
f"""Error: Unsupported type: {type(child)}, top level only supports
addrmap and reg components."""
)
print(f"ERROR: Unsupported type: {type(child)} in Ip block {ip_block.inst_name}.")
raise TypeError

# If the ip_block contain imediate registers, use a default interface name
Expand All @@ -305,7 +376,28 @@ def parse_soc(self, root: node.AddrmapNode) -> dict:
print("Error: Top level must be an addrmap")
raise TypeError

obj = {"devices": []}
obj = {"name": root.inst_name, "devices": []}
for child in root.children():
obj["devices"].append(self.parse_ip_block(child))
if isinstance(child, node.AddrmapNode):
obj["devices"].append(self.parse_ip_block(child))
elif isinstance(child, node.MemNode):
obj["devices"].append(self.get_mem(child))
else:
print(
f"""Error: Unsupported type: {type(child)}, top level only supports
addrmap and mem components."""
)
raise TypeError

interrupts = []
for device in obj["devices"]:
if len(device.get("interrupts", [])) == 0:
continue
is_array = len(device["offsets"]) > 0
for idx, _ in enumerate(device["offsets"]):
suffix = f"_{idx}" if is_array else ""
interrupts.append(device["name"] + suffix)

if len(interrupts) > 0:
obj["interrupts"] = interrupts
return obj
Loading