Skip to content

Commit c2f7142

Browse files
committed
[rdl2ot] Support SoC description in the root level
Now a SoC/Top described in RDL can be parsed and generate RTL for multiple IP blocks by adding the flag --soc in the CLI. Signed-off-by: Douglas Reis <[email protected]>
1 parent 618b4d2 commit c2f7142

File tree

5 files changed

+69
-31
lines changed

5 files changed

+69
-31
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ extend-select = ["W391", "E303"]
3737
allowed-confusables = [""]
3838
ignore = [
3939
"D203", "D213", "COM812", "ISC001", "FIX", "TD", "T201", "S101", "C901", "D401",
40-
"PLR0911", "PLR0915", "INP001", "RUF012", "EXE001", "S701"
40+
"PLR0911", "PLR0915", "INP001", "RUF012", "EXE001", "S701", "FBT001", "FBT002"
4141
]
4242

4343
[tool.uv.workspace]

rdl2ot/src/rdl2ot/cli.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,23 @@ def main() -> None:
2929
default="./result",
3030
type=click.Path(writable=True),
3131
)
32-
def export_rtl(input_file: str, out_dir: str) -> None:
32+
@click.option(
33+
"--soc",
34+
is_flag=True,
35+
)
36+
def export_rtl(input_file: str, out_dir: str, soc: bool = False) -> None:
3337
"""Export opentitan rtl.
3438
3539
INPUT_FILE: The input RDL
3640
OUT_DIR: The destination dir to generate the output
41+
SOC: Indicates that the input RDL is a SoC top
3742
3843
"""
39-
print("Compiling file: {input_file}...")
44+
print(f"Compiling file: {input_file}...")
4045
rdlc = RDLCompiler()
4146
rdlc.compile_file(input_file)
4247
root = rdlc.elaborate()
4348

44-
rtl_exporter.run(root.top, Path(out_dir))
49+
rtl_exporter.run(root.top, Path(out_dir), soc)
4550

4651
print("Successfully finished!\n")

rdl2ot/src/rdl2ot/rtl_exporter.py

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,38 @@ def _camelcase(value: str) -> str:
2222
return "".join(word.capitalize() for word in words)
2323

2424

25-
def run(root_node: node.AddrmapNode, out_dir: Path) -> None:
26-
"""Export RDL to opentitan RTL."""
25+
def run(root_node: node.AddrmapNode, out_dir: Path, is_soc: bool = False) -> None:
26+
"""Export RDL to opentitan RTL.
27+
28+
IS_SOC: True if the root node is a SoC with peripherals/devices.
29+
"""
2730
factory = OtInterfaceBuilder()
28-
data = factory.parse_root(root_node)
31+
data = factory.parse_soc(root_node) if is_soc else factory.parse_ip_block(root_node)
2932

3033
Path(out_dir / "rdl.json").write_text(json.dumps(data, indent=2), encoding="utf-8")
3134

35+
if not is_soc:
36+
_export(data, out_dir)
37+
return
38+
39+
for ip_block in data["devices"]:
40+
_export(ip_block, out_dir)
41+
42+
43+
def _export(ip_block: dict, out_dir: Path) -> None:
3244
file_loader = FileSystemLoader(TEMPLATES_DIR)
3345
env = Environment(loader=file_loader)
3446
env.filters["camelcase"] = _camelcase
3547

36-
ip_name = data["ip_name"]
48+
ip_name = ip_block["ip_name"].lower()
3749
reg_pkg_tpl = env.get_template("reg_pkg.sv.tpl")
38-
stream = reg_pkg_tpl.render(data)
50+
stream = reg_pkg_tpl.render(ip_block)
3951
path = out_dir / f"{ip_name}_reg_pkg.sv"
4052
path.open("w").write(stream)
4153
print(f"Generated {path}.")
4254

4355
reg_top_tpl = env.get_template("reg_top.sv.tpl")
44-
for interface in data["interfaces"]:
56+
for interface in ip_block["interfaces"]:
4557
name = "_{}".format(interface["name"].lower()) if "name" in interface else ""
4658
data_ = {"ip_name": ip_name, "interface": interface}
4759
stream = reg_top_tpl.render(data_).replace(" \n", "\n")
@@ -193,14 +205,10 @@ def get_interface(self, addrmap: node.AddrmapNode, defalt_name: None | str = Non
193205
self.any_shadowed_reg = False
194206
self.async_registers.clear()
195207

196-
if addrmap.is_array:
197-
print(f"WARNING: Unsupported array type: {type(addrmap)}, skiping...")
198-
199208
interface = {}
200209
if defalt_name:
201210
interface["name"] = addrmap.inst_name or defalt_name
202211

203-
interface["offset"] = addrmap.address_offset
204212
interface["regs"] = []
205213
interface["windows"] = []
206214
for child in addrmap.children():
@@ -247,25 +255,26 @@ def get_interface(self, addrmap: node.AddrmapNode, defalt_name: None | str = Non
247255
]
248256
return interface
249257

250-
def parse_root(self, root: node.AddrmapNode) -> dict:
251-
"""Parse the root node and return a dictionary representing a window."""
252-
if root.is_array:
253-
print("Error: Unsupported array type on the top")
254-
raise RuntimeError
255-
if not isinstance(root, node.AddrmapNode):
256-
print("Error: Top level must be an addrmap")
257-
raise TypeError
258-
258+
def parse_ip_block(self, ip_block: node.AddrmapNode) -> dict:
259+
"""Parse the ip_block node of an IP block and return a dictionary."""
259260
obj = {}
260-
params = self.get_paramesters(root)
261+
params = self.get_paramesters(ip_block)
261262
if params:
262263
obj["parameters"] = params
263-
obj["ip_name"] = root.inst_name
264-
obj["offset"] = root.address_offset
264+
obj["ip_name"] = ip_block.inst_name
265+
266+
obj["offsets"] = []
267+
if ip_block.is_array:
268+
offset = ip_block.raw_address_offset
269+
for _idx in range(ip_block.array_dimensions[0]):
270+
obj["offsets"].append(offset)
271+
offset += ip_block.array_stride
272+
else:
273+
obj["offsets"].append(ip_block.address_offset)
265274

266275
obj["interfaces"] = []
267276
obj["alerts"] = []
268-
for child in root.children():
277+
for child in ip_block.children():
269278
if isinstance(child, node.AddrmapNode):
270279
child_obj = self.get_interface(child, DEFAULT_INTERFACE_NAME)
271280
obj["interfaces"].append(child_obj)
@@ -279,10 +288,24 @@ def parse_root(self, root: node.AddrmapNode) -> dict:
279288
)
280289
raise TypeError
281290

282-
# If the root contain imediate registers, use a default interface name
283-
if len(root.registers()) > 0:
284-
interface = self.get_interface(root)
291+
# If the ip_block contain imediate registers, use a default interface name
292+
if len(ip_block.registers()) > 0:
293+
interface = self.get_interface(ip_block)
285294
obj["interfaces"].append(interface)
286295
obj["alerts"].extend(interface["alerts"])
287296

288297
return obj
298+
299+
def parse_soc(self, root: node.AddrmapNode) -> dict:
300+
"""Parse the SoC root node and return a dictionary."""
301+
if root.is_array:
302+
print("Error: Unsupported array type on the top")
303+
raise RuntimeError
304+
if not isinstance(root, node.AddrmapNode):
305+
print("Error: Top level must be an addrmap")
306+
raise TypeError
307+
308+
obj = {"devices": []}
309+
for child in root.children():
310+
obj["devices"].append(self.parse_ip_block(child))
311+
return obj
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
`include "uart.rdl"
2+
`include "lc_ctrl.rdl"
3+
4+
addrmap soc_strawberry {
5+
uart UART[3];
6+
lc_ctrl LC_CTRL;
7+
};

rdl2ot/tests/test_rdl2ot.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ def _run_cli_tool(input_file_path: Path, output_dir_path: Path) -> subprocess.Co
2222
str(input_file_path),
2323
str(output_dir_path),
2424
]
25+
if "soc" in input_file_path.name:
26+
command.append("--soc")
27+
2528
return subprocess.run(command, capture_output=True, text=True, check=False) # noqa: S603
2629

2730

28-
test_ips = ["lc_ctrl", "uart"]
31+
test_ips = ["lc_ctrl", "uart", "soc_strawberry"]
2932

3033

3134
@pytest.mark.parametrize("ip_block", test_ips)

0 commit comments

Comments
 (0)