Skip to content

Commit 286b8a1

Browse files
authored
Merge pull request #5 from lowRISC/develop
Create library to export RDL files from AST
2 parents b7020e5 + 52820be commit 286b8a1

File tree

15 files changed

+930
-46
lines changed

15 files changed

+930
-46
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919

2020
- name: Install dependencies with uv
2121
run: |
22-
uv sync --extra ci
22+
uv sync --all-extras
2323
2424
- name: Linting
2525
run: |
@@ -28,3 +28,7 @@ jobs:
2828
- name: Test rdl2ot
2929
run: uv run pytest
3030
working-directory: rdl2ot
31+
32+
- name: Test rdlexporter
33+
run: uv run pytest
34+
working-directory: rdlexporter

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.10

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,12 @@ pytest
3030
cd rdl2ot
3131
python src/rdl2ot export-rtl tests/snapshots/lc_ctrl.rdl /tmp/
3232
```
33+
34+
## Rdl-exporter
35+
A library to generate SystemRDL files from the Hierarchical Register Model.
36+
37+
### How to run tests
38+
```sh
39+
cd rdl-exporter
40+
pytest
41+
```

pyproject.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,11 @@ py-modules = []
3232
[tool.ruff]
3333
target-version = "py310"
3434
line-length = 100
35-
# Don't lint vendored files which must be fixed upstream.
36-
extend-exclude = [ "*vendor*" ]
3735

3836
[tool.ruff.lint]
3937
preview = true
4038
explicit-preview-rules = true
4139
extend-select = ["E", "E303", "W391"]
4240

4341
[tool.uv.workspace]
44-
members = ["rdl2ot"]
42+
members = [ "rdl2ot", "rdlexporter"]

rdl2ot/pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
[project]
22
name = "rdl2ot"
3-
version = "0.0.0"
3+
version = "0.1.0"
44
description = "An extension of PeakRDL to generate Opentitan RTL."
55
requires-python = ">=3.10"
66
dependencies = [
77
]
8+
9+
[build-system]
10+
requires = ["hatchling"]
11+
build-backend = "hatchling.build"

rdl2ot/src/rdl2ot/opentitan.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
33
# SPDX-License-Identifier: Apache-2.0
44

5+
56
def register_permit_mask(msb: int, lsb: int) -> int:
67
"""
78
One bit presents one byte in the register, so in total 4 bits are used.

rdlexporter/pyproject.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[project]
2+
name = "rdlexporter"
3+
version = "0.1.0"
4+
description = "Library to export rdl files"
5+
requires-python = ">=3.10"
6+
dependencies = []
7+
8+
[build-system]
9+
requires = ["hatchling"]
10+
build-backend = "hatchling.build"
11+
12+
[tool.hatch.build.targets.wheel]
13+
packages = ["src/rdlexporter"]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright lowRISC contributors.
2+
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
from .exporter import RdlExporter
6+
7+
__all__ = [
8+
"RdlExporter",
9+
]
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# Copyright lowRISC contributors.
2+
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
from pathlib import Path
6+
from systemrdl import RDLCompiler
7+
from systemrdl.rdltypes.user_enum import UserEnumMeta
8+
from systemrdl.ast.literals import StringLiteral, BoolLiteral, IntLiteral, BuiltinEnumLiteral
9+
from systemrdl.ast.references import InstRef
10+
from systemrdl.component import Addrmap, Reg, Field, Mem
11+
from dataclasses import dataclass, field
12+
13+
14+
@dataclass
15+
class RdlExporter:
16+
rdlc: RDLCompiler
17+
stream: str = ""
18+
indent_pos = 0
19+
indent_width = 4
20+
indent_str = " "
21+
dynamic_assignment: dict[str, list[dict]] = field(default_factory=dict)
22+
ast_path: list[str] = field(default_factory=list)
23+
24+
def _raise_type_error(self, type_name):
25+
print(f"Error: Unsupported type: {type_name} at this level only supports")
26+
raise RuntimeError
27+
28+
def _indent(self) -> str:
29+
return f"{self.indent_str:>{self.indent_pos}}" if self.indent_pos else ""
30+
31+
def _is_nested(self) -> bool:
32+
return self.indent_pos > 0
33+
34+
def _emit_dynamic_assignment(self) -> None:
35+
# Nothing to be emited
36+
current_scope = self.ast_path[-1]
37+
if current_scope not in self.dynamic_assignment:
38+
return
39+
for scope in self.dynamic_assignment.pop(current_scope):
40+
left_expr = (
41+
".".join(scope["ast_path"]).removeprefix(".".join(self.ast_path)).lstrip(".")
42+
)
43+
left_expr = f"{left_expr} -> {scope['property']}"
44+
45+
right_expr = "".join([f"{elem[0]}." for elem in scope["ref"].ref_elements]).rstrip(".")
46+
expr = f"{left_expr} = {right_expr};\n"
47+
self.stream += self._indent() + expr
48+
49+
def _emit_property(self, properties: dict) -> None:
50+
for name, obj in properties.items():
51+
if isinstance(obj, UserEnumMeta):
52+
val = obj.type_name
53+
elif isinstance(obj, BuiltinEnumLiteral):
54+
val = obj.val.name
55+
elif isinstance(obj, StringLiteral):
56+
val = f'''"{obj.get_value()}"'''
57+
elif isinstance(obj, BoolLiteral):
58+
val = str(obj.get_value()).lower()
59+
elif isinstance(obj, IntLiteral):
60+
val = f"0x{obj.get_value():x}"
61+
elif isinstance(obj, InstRef):
62+
# This should be emited at a higher scope indicated by `ref_root._scope_name`.
63+
ref = obj.get_value()
64+
scope = ref.ref_root._scope_name
65+
self.dynamic_assignment.setdefault(scope, []).append(
66+
{
67+
"property": name,
68+
"ast_path": self.ast_path.copy(),
69+
"ref": ref,
70+
}
71+
)
72+
continue
73+
else:
74+
print(f"Warning: Type {type(obj)} not implemented.")
75+
76+
self.stream += self._indent() + f"{name} = {val};\n"
77+
78+
def _arrays(self, component: Reg) -> str:
79+
if not component.is_array:
80+
return ""
81+
82+
if len(component.array_dimensions) > 1:
83+
print("Error: Unsupported multidimentional arrays.")
84+
raise RuntimeError
85+
86+
array_str = f"[{component.array_dimensions[0].get_value()}]"
87+
return array_str
88+
89+
def _emit_parameters(self, parameters: list) -> None:
90+
if not len(parameters):
91+
return
92+
93+
self.stream += "#(\n"
94+
self.indent_pos += self.indent_width
95+
for index, param in enumerate(parameters):
96+
val = param.get_value()
97+
if isinstance(val, int) or param.param_type.is_integer:
98+
type_ = "longint"
99+
else:
100+
self._raise_type_error(type(param.param_type))
101+
102+
self.stream += self._indent() + f"{type_} {param.name} = {val}"
103+
last = index == (len(parameters) - 1)
104+
self.stream += ",\n" if not last else "\n"
105+
106+
self.indent_pos -= self.indent_width
107+
self.stream += ")"
108+
109+
def _emit_mem(self, mem: Mem) -> None:
110+
self.ast_path.append(mem.inst_name)
111+
external_str = "external " if mem.external else ""
112+
self.stream += self._indent() + external_str + "mem "
113+
self._emit_parameters(mem.parameters)
114+
self.stream += "{\n"
115+
self.indent_pos += self.indent_width
116+
self._emit_property(mem.properties)
117+
self.indent_pos -= self.indent_width
118+
self.stream += self._indent() + f"}} {mem.inst_name};\n"
119+
self.ast_path.pop()
120+
121+
def _emit_field(self, field: Field) -> None:
122+
self.ast_path.append(field.inst_name)
123+
self.stream += self._indent() + "field "
124+
self._emit_parameters(field.parameters)
125+
self.stream += "{\n"
126+
self.indent_pos += self.indent_width
127+
self._emit_property(field.properties)
128+
self.indent_pos -= self.indent_width
129+
msb, lsb = (field.msb.get_value(), field.lsb.get_value())
130+
self.stream += self._indent() + f"}} {field.inst_name}[{msb}:{lsb}];\n"
131+
self.ast_path.pop()
132+
133+
def _emit_register(self, register: Reg) -> None:
134+
self.ast_path.append(register.inst_name)
135+
external_str = "external " if register.external else ""
136+
self.stream += self._indent() + external_str + "reg "
137+
self._emit_parameters(register.parameters)
138+
self.stream += "{\n"
139+
self.indent_pos += self.indent_width
140+
self._emit_property(register.properties)
141+
for child in register.children:
142+
if isinstance(child, Field):
143+
self._emit_field(child)
144+
else:
145+
self._raise_type_error(type(child))
146+
self.indent_pos -= self.indent_width
147+
offset = register.addr_offset.get_value()
148+
self.stream += self._indent() + f"}} {register.inst_name}" + self._arrays(register)
149+
self.stream += f" @ 0x{offset:X};\n"
150+
self.ast_path.pop()
151+
152+
def _emit_addrmap(self, name: str, addrmap: Addrmap) -> None:
153+
self.ast_path.append(name)
154+
self.stream += self._indent() + "addrmap "
155+
self.stream += f"{name} " if not self._is_nested() else ""
156+
self._emit_parameters(addrmap.parameters)
157+
self.stream += "{\n"
158+
self.indent_pos += self.indent_width
159+
for child in addrmap.children:
160+
if isinstance(child, Reg):
161+
self._emit_register(child)
162+
elif isinstance(child, Addrmap):
163+
self._emit_addrmap(child.inst_name, child)
164+
elif isinstance(child, Mem):
165+
self._emit_mem(child)
166+
else:
167+
self._raise_type_error(type(child))
168+
self._emit_dynamic_assignment()
169+
self.stream += "\n"
170+
171+
self.indent_pos -= self.indent_width
172+
self.stream += self._indent() + "}"
173+
self.stream += f" {name};\n" if self._is_nested() else ";\n"
174+
self.ast_path.pop()
175+
176+
def export(self, outfile: Path) -> None:
177+
self.ast_path.append(str(self.rdlc.root.inst_name))
178+
for name, component in self.rdlc.root.comp_defs.items():
179+
if isinstance(component, Addrmap):
180+
self._emit_addrmap(name, component)
181+
else:
182+
self._raise_type_error(type(component))
183+
184+
self.stream.lstrip("\n ")
185+
with outfile.open("a") as f:
186+
f.write(self.stream)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright lowRISC contributors.
2+
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
# SPDX-License-Identifier: Apache-2.0

0 commit comments

Comments
 (0)