Skip to content

Commit e9b07aa

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 e9b07aa

File tree

4 files changed

+72
-30
lines changed

4 files changed

+72
-30
lines changed

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: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,41 @@ 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 represents an soc with address maps representing peripherals/devices.
29+
"""
2730
factory = OtInterfaceBuilder()
28-
data = factory.parse_root(root_node)
31+
if is_soc:
32+
data = factory.parse_soc(root_node)
33+
else:
34+
data = factory.parse_ip_block(root_node)
2935

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

38+
if not is_soc:
39+
_export(data, out_dir)
40+
return
41+
42+
for ip_block in data["devices"]:
43+
_export(ip_block, out_dir)
44+
45+
46+
def _export(ip_block: dict, out_dir: Path) -> None:
3247
file_loader = FileSystemLoader(TEMPLATES_DIR)
3348
env = Environment(loader=file_loader)
3449
env.filters["camelcase"] = _camelcase
3550

36-
ip_name = data["ip_name"]
51+
ip_name = ip_block["ip_name"].lower()
3752
reg_pkg_tpl = env.get_template("reg_pkg.sv.tpl")
38-
stream = reg_pkg_tpl.render(data)
53+
stream = reg_pkg_tpl.render(ip_block)
3954
path = out_dir / f"{ip_name}_reg_pkg.sv"
4055
path.open("w").write(stream)
4156
print(f"Generated {path}.")
4257

4358
reg_top_tpl = env.get_template("reg_top.sv.tpl")
44-
for interface in data["interfaces"]:
59+
for interface in ip_block["interfaces"]:
4560
name = "_{}".format(interface["name"].lower()) if "name" in interface else ""
4661
data_ = {"ip_name": ip_name, "interface": interface}
4762
stream = reg_top_tpl.render(data_).replace(" \n", "\n")
@@ -193,14 +208,10 @@ def get_interface(self, addrmap: node.AddrmapNode, defalt_name: None | str = Non
193208
self.any_shadowed_reg = False
194209
self.async_registers.clear()
195210

196-
if addrmap.is_array:
197-
print(f"WARNING: Unsupported array type: {type(addrmap)}, skiping...")
198-
199211
interface = {}
200212
if defalt_name:
201213
interface["name"] = addrmap.inst_name or defalt_name
202214

203-
interface["offset"] = addrmap.address_offset
204215
interface["regs"] = []
205216
interface["windows"] = []
206217
for child in addrmap.children():
@@ -247,25 +258,26 @@ def get_interface(self, addrmap: node.AddrmapNode, defalt_name: None | str = Non
247258
]
248259
return interface
249260

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-
261+
def parse_ip_block(self, ip_block: node.AddrmapNode) -> dict:
262+
"""Parse the ip_block node of an IP block and return a dictionary."""
259263
obj = {}
260-
params = self.get_paramesters(root)
264+
params = self.get_paramesters(ip_block)
261265
if params:
262266
obj["parameters"] = params
263-
obj["ip_name"] = root.inst_name
264-
obj["offset"] = root.address_offset
267+
obj["ip_name"] = ip_block.inst_name
268+
269+
obj["offsets"] = []
270+
if ip_block.is_array:
271+
offset = ip_block.raw_address_offset
272+
for _idx in range(ip_block.array_dimensions[0]):
273+
obj["offsets"].append(offset)
274+
offset += ip_block.array_stride
275+
else:
276+
obj["offsets"].append(ip_block.address_offset)
265277

266278
obj["interfaces"] = []
267279
obj["alerts"] = []
268-
for child in root.children():
280+
for child in ip_block.children():
269281
if isinstance(child, node.AddrmapNode):
270282
child_obj = self.get_interface(child, DEFAULT_INTERFACE_NAME)
271283
obj["interfaces"].append(child_obj)
@@ -279,10 +291,25 @@ def parse_root(self, root: node.AddrmapNode) -> dict:
279291
)
280292
raise TypeError
281293

282-
# If the root contain imediate registers, use a default interface name
283-
if len(root.registers()) > 0:
284-
interface = self.get_interface(root)
294+
# If the ip_block contain imediate registers, use a default interface name
295+
if len(ip_block.registers()) > 0:
296+
interface = self.get_interface(ip_block)
285297
obj["interfaces"].append(interface)
286298
obj["alerts"].extend(interface["alerts"])
287299

288300
return obj
301+
302+
def parse_soc(self, root: node.AddrmapNode) -> dict:
303+
"""Parse the SoC root node and return a dictionary."""
304+
if root.is_array:
305+
print("Error: Unsupported array type on the top")
306+
raise RuntimeError
307+
if not isinstance(root, node.AddrmapNode):
308+
print("Error: Top level must be an addrmap")
309+
raise TypeError
310+
311+
obj = {"devices": []}
312+
for child in root.children():
313+
obj["devices"].append(self.parse_ip_block(child))
314+
return obj
315+
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)