python-library-ralf-model 0.1.1__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/PKG-INFO +3 -1
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/README.md +2 -0
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/pyproject.toml +1 -1
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/ralf_model/__init__.py +2 -1
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/ralf_model/emit.py +43 -3
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/ralf_model/nodes.py +37 -1
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/ralf_model/parse.py +155 -15
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/tests/test_ralf_model.py +115 -0
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/.gitignore +0 -0
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/example/__main__.py +0 -0
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/example.bat +0 -0
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/ralf_model/abc.py +0 -0
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/ralf_model/errors.py +0 -0
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/ralf_model/io.py +0 -0
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/ralf_model/source_expand.py +0 -0
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/test.bat +0 -0
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/tests/__init__.py +0 -0
- {python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/tests/test_source_include.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-library-ralf-model
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Requires-Python: >=3.10
|
|
5
5
|
Requires-Dist: pydantic<3,>=2.0
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -41,6 +41,8 @@ doc = load_ralf_file(
|
|
|
41
41
|
## 能力范围
|
|
42
42
|
|
|
43
43
|
- **block**:定义 ``block 名 { ... }``;简单映射 ``block 名 @地址;``;赋值与可选路径、地址 ``block 左名 = 右名``、``block 左名 = 右名 (hdl路径)``、``... @地址``,可与 ``{ ... }`` 组合。
|
|
44
|
+
- **register**:``register 名 [ (hdl路径) ] [ @字节偏移 ] { ... }`` 或 ``register 名;`` 前向引用。
|
|
45
|
+
- **field**:``field 名 [(hdl路径)] [ @位偏移 ] { ... }``(括号路径可紧贴字段名,如 ``field f(hdl.f)``)。
|
|
44
46
|
- `field` 花括号内按**源顺序**保留各条语句(含 `enum { ... };` 等),便于往返。
|
|
45
47
|
- `@` 后的偏移在写出时统一为 Verilog 风格十六进制字面量(如 `'h5`);`bytes` 等为十进制。
|
|
46
48
|
|
|
@@ -34,6 +34,8 @@ doc = load_ralf_file(
|
|
|
34
34
|
## 能力范围
|
|
35
35
|
|
|
36
36
|
- **block**:定义 ``block 名 { ... }``;简单映射 ``block 名 @地址;``;赋值与可选路径、地址 ``block 左名 = 右名``、``block 左名 = 右名 (hdl路径)``、``... @地址``,可与 ``{ ... }`` 组合。
|
|
37
|
+
- **register**:``register 名 [ (hdl路径) ] [ @字节偏移 ] { ... }`` 或 ``register 名;`` 前向引用。
|
|
38
|
+
- **field**:``field 名 [(hdl路径)] [ @位偏移 ] { ... }``(括号路径可紧贴字段名,如 ``field f(hdl.f)``)。
|
|
37
39
|
- `field` 花括号内按**源顺序**保留各条语句(含 `enum { ... };` 等),便于往返。
|
|
38
40
|
- `@` 后的偏移在写出时统一为 Verilog 风格十六进制字面量(如 `'h5`);`bytes` 等为十进制。
|
|
39
41
|
|
|
@@ -4,7 +4,7 @@ from ralf_model.abc import AbstractRalfBlock, AbstractRalfField, AbstractRalfReg
|
|
|
4
4
|
from ralf_model.emit import dump_ralf
|
|
5
5
|
from ralf_model.errors import RalfError, RalfParseError, RalfSourceError
|
|
6
6
|
from ralf_model.io import dump_ralf_file, dumps_ralf, load_ralf_file, loads_ralf
|
|
7
|
-
from ralf_model.nodes import BlockNode, FieldNode, RalfDocument, RegisterNode
|
|
7
|
+
from ralf_model.nodes import BlockNode, FieldNode, RalfDocument, RegisterNode, SystemNode
|
|
8
8
|
from ralf_model.parse import normalize_ralf_whitespace, parse_ralf
|
|
9
9
|
from ralf_model.source_expand import expand_ralf_sources, resolve_source_path
|
|
10
10
|
|
|
@@ -16,6 +16,7 @@ __all__ = [
|
|
|
16
16
|
"FieldNode",
|
|
17
17
|
"RalfDocument",
|
|
18
18
|
"RegisterNode",
|
|
19
|
+
"SystemNode",
|
|
19
20
|
"RalfError",
|
|
20
21
|
"RalfParseError",
|
|
21
22
|
"RalfSourceError",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from ralf_model.nodes import BlockNode, FieldNode, RalfDocument, RegisterNode
|
|
3
|
+
from ralf_model.nodes import BlockNode, FieldNode, RalfDocument, RegisterNode, SystemNode
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def _fmt_at_int(v: int) -> str:
|
|
@@ -12,6 +12,8 @@ def _fmt_at_int(v: int) -> str:
|
|
|
12
12
|
|
|
13
13
|
def _emit_field(f: FieldNode, indent: str) -> list[str]:
|
|
14
14
|
head = f"{indent}field {f.name}"
|
|
15
|
+
if f.paren_path is not None:
|
|
16
|
+
head += f"({f.paren_path})"
|
|
15
17
|
if f.offset_bits is not None:
|
|
16
18
|
head += f" @{_fmt_at_int(f.offset_bits)}"
|
|
17
19
|
head += " {"
|
|
@@ -26,6 +28,8 @@ def _emit_field(f: FieldNode, indent: str) -> list[str]:
|
|
|
26
28
|
|
|
27
29
|
def _emit_register(r: RegisterNode, indent: str) -> list[str]:
|
|
28
30
|
head = f"{indent}register {r.name}"
|
|
31
|
+
if r.paren_path is not None:
|
|
32
|
+
head += f" ({r.paren_path})"
|
|
29
33
|
if r.offset_bytes is not None:
|
|
30
34
|
head += f" @{_fmt_at_int(r.offset_bytes)}"
|
|
31
35
|
if r.declaration_only:
|
|
@@ -54,6 +58,35 @@ def _emit_block_open_line(b: BlockNode, indent: str) -> str:
|
|
|
54
58
|
return line
|
|
55
59
|
|
|
56
60
|
|
|
61
|
+
def _emit_system_open_line(s: SystemNode, indent: str) -> str:
|
|
62
|
+
line = f"{indent}system {s.name}"
|
|
63
|
+
if s.rhs_head is not None:
|
|
64
|
+
line += f" = {s.rhs_head}"
|
|
65
|
+
if s.rhs_paren_path is not None:
|
|
66
|
+
line += f" ({s.rhs_paren_path})"
|
|
67
|
+
if s.base_address is not None:
|
|
68
|
+
line += f" @{_fmt_at_int(s.base_address)}"
|
|
69
|
+
elif s.base_address is not None:
|
|
70
|
+
line += f" @{_fmt_at_int(s.base_address)}"
|
|
71
|
+
return line
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _emit_system(s: SystemNode, indent: str) -> list[str]:
|
|
75
|
+
head_line = _emit_system_open_line(s, indent)
|
|
76
|
+
if not s.has_body:
|
|
77
|
+
return [f"{head_line};"]
|
|
78
|
+
lines = [head_line + " {"]
|
|
79
|
+
inner = indent + " "
|
|
80
|
+
if s.bytes_width is not None:
|
|
81
|
+
lines.append(f"{inner}bytes {s.bytes_width};")
|
|
82
|
+
for sub in s.systems:
|
|
83
|
+
lines.extend(_emit_system(sub, inner))
|
|
84
|
+
for sub in s.blocks:
|
|
85
|
+
lines.extend(_emit_block(sub, inner))
|
|
86
|
+
lines.append(indent + "}")
|
|
87
|
+
return lines
|
|
88
|
+
|
|
89
|
+
|
|
57
90
|
def _emit_block(b: BlockNode, indent: str) -> list[str]:
|
|
58
91
|
head_line = _emit_block_open_line(b, indent)
|
|
59
92
|
if not b.has_body:
|
|
@@ -73,8 +106,15 @@ def _emit_block(b: BlockNode, indent: str) -> list[str]:
|
|
|
73
106
|
def dump_ralf(doc: RalfDocument) -> str:
|
|
74
107
|
"""将 `RalfDocument` 序列化为 RALF 源文本(规范化排版)。"""
|
|
75
108
|
out: list[str] = []
|
|
76
|
-
|
|
77
|
-
|
|
109
|
+
first = True
|
|
110
|
+
for s in doc.systems:
|
|
111
|
+
if not first:
|
|
112
|
+
out.append("")
|
|
113
|
+
first = False
|
|
114
|
+
out.extend(_emit_system(s, ""))
|
|
115
|
+
for b in doc.blocks:
|
|
116
|
+
if not first:
|
|
78
117
|
out.append("")
|
|
118
|
+
first = False
|
|
79
119
|
out.extend(_emit_block(b, ""))
|
|
80
120
|
return "\n".join(out) + ("\n" if out else "")
|
|
@@ -9,6 +9,10 @@ class FieldNode(BaseModel):
|
|
|
9
9
|
model_config = ConfigDict(extra="forbid")
|
|
10
10
|
|
|
11
11
|
name: str
|
|
12
|
+
paren_path: str | None = Field(
|
|
13
|
+
default=None,
|
|
14
|
+
description="紧跟在 field 名后的圆括号路径内容(不含括号),如 ``field f(hdl.sig)``",
|
|
15
|
+
)
|
|
12
16
|
offset_bits: int | None = Field(default=None, description="field 内 `@` 后的位偏移")
|
|
13
17
|
inner_statements: list[str] = Field(
|
|
14
18
|
default_factory=list,
|
|
@@ -22,6 +26,10 @@ class RegisterNode(BaseModel):
|
|
|
22
26
|
model_config = ConfigDict(extra="forbid")
|
|
23
27
|
|
|
24
28
|
name: str
|
|
29
|
+
paren_path: str | None = Field(
|
|
30
|
+
default=None,
|
|
31
|
+
description="紧跟在 register 名后的圆括号路径内容(不含括号),如 ``register r (dut.reg)``",
|
|
32
|
+
)
|
|
25
33
|
offset_bytes: int | None = Field(default=None, description="register 后的 `@` 字节偏移")
|
|
26
34
|
bytes_width: int | None = None
|
|
27
35
|
fields: list[FieldNode] = Field(default_factory=list)
|
|
@@ -61,9 +69,37 @@ class BlockNode(BaseModel):
|
|
|
61
69
|
blocks: list[BlockNode] = Field(default_factory=list)
|
|
62
70
|
|
|
63
71
|
|
|
72
|
+
class SystemNode(BaseModel):
|
|
73
|
+
"""层次化 system;体内可嵌套 system 与 block,不含 register。"""
|
|
74
|
+
|
|
75
|
+
model_config = ConfigDict(extra="forbid")
|
|
76
|
+
|
|
77
|
+
name: str
|
|
78
|
+
rhs_head: str | None = Field(
|
|
79
|
+
default=None,
|
|
80
|
+
description="`=` 右侧起始的层级名(含可能的 `[..]` 后缀)",
|
|
81
|
+
)
|
|
82
|
+
rhs_paren_path: str | None = Field(
|
|
83
|
+
default=None,
|
|
84
|
+
description="紧跟在 rhs_head 后的圆括号路径内容(不含括号)",
|
|
85
|
+
)
|
|
86
|
+
base_address: int | None = Field(
|
|
87
|
+
default=None,
|
|
88
|
+
description="`@` 后的地址;可出现于简单 `system 名 @addr` 或 `=` 形式末尾",
|
|
89
|
+
)
|
|
90
|
+
has_body: bool = Field(
|
|
91
|
+
default=True,
|
|
92
|
+
description="是否带有 `{ ... }`;仅分号结尾的声明为 False",
|
|
93
|
+
)
|
|
94
|
+
bytes_width: int | None = None
|
|
95
|
+
systems: list[SystemNode] = Field(default_factory=list)
|
|
96
|
+
blocks: list[BlockNode] = Field(default_factory=list)
|
|
97
|
+
|
|
98
|
+
|
|
64
99
|
class RalfDocument(BaseModel):
|
|
65
|
-
"""顶层 RALF
|
|
100
|
+
"""顶层 RALF 文件内容(顶层为若干 `system`;亦兼容顶层 `block`)。"""
|
|
66
101
|
|
|
67
102
|
model_config = ConfigDict(extra="forbid")
|
|
68
103
|
|
|
104
|
+
systems: list[SystemNode] = Field(default_factory=list)
|
|
69
105
|
blocks: list[BlockNode] = Field(default_factory=list)
|
|
@@ -5,7 +5,7 @@ from collections.abc import Sequence
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from ralf_model.errors import RalfParseError
|
|
8
|
-
from ralf_model.nodes import BlockNode, FieldNode, RalfDocument, RegisterNode
|
|
8
|
+
from ralf_model.nodes import BlockNode, FieldNode, RalfDocument, RegisterNode, SystemNode
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class _Parser:
|
|
@@ -39,11 +39,22 @@ class _Parser:
|
|
|
39
39
|
j = self._i + offset
|
|
40
40
|
return self._s[j] if j < self._n else None
|
|
41
41
|
|
|
42
|
-
def
|
|
42
|
+
def _error_location(self) -> tuple[Path | None, int]:
|
|
43
43
|
path = self._path
|
|
44
|
+
line = self.line
|
|
44
45
|
if self._line_sources and 0 < self.line <= len(self._line_sources):
|
|
45
46
|
path = self._line_sources[self.line - 1]
|
|
46
|
-
|
|
47
|
+
local = 1
|
|
48
|
+
idx = self.line - 2
|
|
49
|
+
while idx >= 0 and self._line_sources[idx] == path:
|
|
50
|
+
local += 1
|
|
51
|
+
idx -= 1
|
|
52
|
+
line = local
|
|
53
|
+
return path, line
|
|
54
|
+
|
|
55
|
+
def _error(self, msg: str) -> None:
|
|
56
|
+
path, line = self._error_location()
|
|
57
|
+
raise RalfParseError(msg, line=line, col=self.col, path=path)
|
|
47
58
|
|
|
48
59
|
def skip_ws_and_comments(self) -> None:
|
|
49
60
|
while self._i < self._n:
|
|
@@ -168,16 +179,19 @@ class _Parser:
|
|
|
168
179
|
self._error("期望 ;")
|
|
169
180
|
|
|
170
181
|
def parse_document(self) -> RalfDocument:
|
|
182
|
+
systems: list[SystemNode] = []
|
|
171
183
|
blocks: list[BlockNode] = []
|
|
172
184
|
self.skip_ws_and_comments()
|
|
173
185
|
while self._i < self._n:
|
|
174
186
|
kw = self._peek_keyword()
|
|
175
|
-
if kw == "
|
|
187
|
+
if kw == "system":
|
|
188
|
+
systems.append(self.parse_system())
|
|
189
|
+
elif kw == "block":
|
|
176
190
|
blocks.append(self.parse_block())
|
|
177
191
|
else:
|
|
178
|
-
self._error(f"顶层期望 block,得到 {kw!r}")
|
|
192
|
+
self._error(f"顶层期望 system 或 block,得到 {kw!r}")
|
|
179
193
|
self.skip_ws_and_comments()
|
|
180
|
-
return RalfDocument(blocks=blocks)
|
|
194
|
+
return RalfDocument(systems=systems, blocks=blocks)
|
|
181
195
|
|
|
182
196
|
def _peek_keyword(self) -> str:
|
|
183
197
|
self.skip_ws_and_comments()
|
|
@@ -261,8 +275,8 @@ class _Parser:
|
|
|
261
275
|
self.skip_ws_and_comments()
|
|
262
276
|
return head, paren_inner, addr
|
|
263
277
|
|
|
264
|
-
def
|
|
265
|
-
self.expect_keyword("
|
|
278
|
+
def parse_system(self) -> SystemNode:
|
|
279
|
+
self.expect_keyword("system")
|
|
266
280
|
name = self.read_hierarchical_block_name()
|
|
267
281
|
self.skip_ws_and_comments()
|
|
268
282
|
|
|
@@ -270,14 +284,128 @@ class _Parser:
|
|
|
270
284
|
self._advance()
|
|
271
285
|
addr = self.parse_integer_value()
|
|
272
286
|
self.skip_ws_and_comments()
|
|
273
|
-
self.
|
|
274
|
-
|
|
287
|
+
if self._peek() == ";":
|
|
288
|
+
self._advance()
|
|
289
|
+
return SystemNode(
|
|
290
|
+
name=name,
|
|
291
|
+
base_address=addr,
|
|
292
|
+
has_body=False,
|
|
293
|
+
systems=[],
|
|
294
|
+
blocks=[],
|
|
295
|
+
)
|
|
296
|
+
if self._peek() == "{":
|
|
297
|
+
self._advance()
|
|
298
|
+
bw, subs_sys, subs_blk = self._parse_system_body()
|
|
299
|
+
self.expect_char("}")
|
|
300
|
+
return SystemNode(
|
|
301
|
+
name=name,
|
|
302
|
+
base_address=addr,
|
|
303
|
+
has_body=True,
|
|
304
|
+
bytes_width=bw,
|
|
305
|
+
systems=subs_sys,
|
|
306
|
+
blocks=subs_blk,
|
|
307
|
+
)
|
|
308
|
+
self._error("system @地址 之后应为 ; 或 {")
|
|
309
|
+
|
|
310
|
+
if self._peek() == "=":
|
|
311
|
+
self._advance()
|
|
312
|
+
self.skip_ws_and_comments()
|
|
313
|
+
rhs_head, rhs_path, addr_rhs = self.parse_block_rhs_after_equals()
|
|
314
|
+
self.skip_ws_and_comments()
|
|
315
|
+
if self._peek() == ";":
|
|
316
|
+
self._advance()
|
|
317
|
+
return SystemNode(
|
|
318
|
+
name=name,
|
|
319
|
+
rhs_head=rhs_head,
|
|
320
|
+
rhs_paren_path=rhs_path,
|
|
321
|
+
base_address=addr_rhs,
|
|
322
|
+
has_body=False,
|
|
323
|
+
systems=[],
|
|
324
|
+
blocks=[],
|
|
325
|
+
)
|
|
326
|
+
if self._peek() == "{":
|
|
327
|
+
self._advance()
|
|
328
|
+
bw, subs_sys, subs_blk = self._parse_system_body()
|
|
329
|
+
self.expect_char("}")
|
|
330
|
+
return SystemNode(
|
|
331
|
+
name=name,
|
|
332
|
+
rhs_head=rhs_head,
|
|
333
|
+
rhs_paren_path=rhs_path,
|
|
334
|
+
base_address=addr_rhs,
|
|
335
|
+
has_body=True,
|
|
336
|
+
bytes_width=bw,
|
|
337
|
+
systems=subs_sys,
|
|
338
|
+
blocks=subs_blk,
|
|
339
|
+
)
|
|
340
|
+
self._error("system ... = ... 之后应为 ; 或 {")
|
|
341
|
+
|
|
342
|
+
if self._peek() == "{":
|
|
343
|
+
self._advance()
|
|
344
|
+
bw, subs_sys, subs_blk = self._parse_system_body()
|
|
345
|
+
self.expect_char("}")
|
|
346
|
+
return SystemNode(
|
|
275
347
|
name=name,
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
blocks=
|
|
348
|
+
has_body=True,
|
|
349
|
+
bytes_width=bw,
|
|
350
|
+
systems=subs_sys,
|
|
351
|
+
blocks=subs_blk,
|
|
280
352
|
)
|
|
353
|
+
self._error("system 名称之后应为 @、= 或 {")
|
|
354
|
+
|
|
355
|
+
def _parse_system_body(
|
|
356
|
+
self,
|
|
357
|
+
) -> tuple[int | None, list[SystemNode], list[BlockNode]]:
|
|
358
|
+
bw: int | None = None
|
|
359
|
+
subs_sys: list[SystemNode] = []
|
|
360
|
+
subs_blk: list[BlockNode] = []
|
|
361
|
+
while True:
|
|
362
|
+
self.skip_ws_and_comments()
|
|
363
|
+
if self._peek() == "}":
|
|
364
|
+
break
|
|
365
|
+
kw = self._peek_keyword()
|
|
366
|
+
if kw == "bytes":
|
|
367
|
+
self.expect_keyword("bytes")
|
|
368
|
+
bw = self.parse_integer_value()
|
|
369
|
+
self.expect_char(";")
|
|
370
|
+
elif kw == "system":
|
|
371
|
+
subs_sys.append(self.parse_system())
|
|
372
|
+
elif kw == "block":
|
|
373
|
+
subs_blk.append(self.parse_block())
|
|
374
|
+
else:
|
|
375
|
+
self._error(f"system 内出现未识别的内容 {kw!r}")
|
|
376
|
+
return bw, subs_sys, subs_blk
|
|
377
|
+
|
|
378
|
+
def parse_block(self) -> BlockNode:
|
|
379
|
+
self.expect_keyword("block")
|
|
380
|
+
name = self.read_hierarchical_block_name()
|
|
381
|
+
self.skip_ws_and_comments()
|
|
382
|
+
|
|
383
|
+
if self._peek() == "@":
|
|
384
|
+
self._advance()
|
|
385
|
+
addr = self.parse_integer_value()
|
|
386
|
+
self.skip_ws_and_comments()
|
|
387
|
+
if self._peek() == ";":
|
|
388
|
+
self._advance()
|
|
389
|
+
return BlockNode(
|
|
390
|
+
name=name,
|
|
391
|
+
base_address=addr,
|
|
392
|
+
has_body=False,
|
|
393
|
+
registers=[],
|
|
394
|
+
blocks=[],
|
|
395
|
+
)
|
|
396
|
+
if self._peek() == "{":
|
|
397
|
+
self._advance()
|
|
398
|
+
bw, regs, subs = self._parse_block_body()
|
|
399
|
+
self.expect_char("}")
|
|
400
|
+
return BlockNode(
|
|
401
|
+
name=name,
|
|
402
|
+
base_address=addr,
|
|
403
|
+
has_body=True,
|
|
404
|
+
bytes_width=bw,
|
|
405
|
+
registers=regs,
|
|
406
|
+
blocks=subs,
|
|
407
|
+
)
|
|
408
|
+
self._error("block @地址 之后应为 ; 或 {")
|
|
281
409
|
|
|
282
410
|
if self._peek() == "=":
|
|
283
411
|
self._advance()
|
|
@@ -349,6 +477,10 @@ class _Parser:
|
|
|
349
477
|
self.expect_keyword("register")
|
|
350
478
|
name = self.read_ident()
|
|
351
479
|
self.skip_ws_and_comments()
|
|
480
|
+
paren_path: str | None = None
|
|
481
|
+
if self._peek() == "(":
|
|
482
|
+
paren_path = self.read_round_paren_inner()
|
|
483
|
+
self.skip_ws_and_comments()
|
|
352
484
|
offset: int | None = None
|
|
353
485
|
if self._peek() == "@":
|
|
354
486
|
self._advance()
|
|
@@ -358,6 +490,7 @@ class _Parser:
|
|
|
358
490
|
self._advance()
|
|
359
491
|
return RegisterNode(
|
|
360
492
|
name=name,
|
|
493
|
+
paren_path=paren_path,
|
|
361
494
|
offset_bytes=offset,
|
|
362
495
|
declaration_only=True,
|
|
363
496
|
)
|
|
@@ -367,6 +500,7 @@ class _Parser:
|
|
|
367
500
|
self.expect_char("}")
|
|
368
501
|
return RegisterNode(
|
|
369
502
|
name=name,
|
|
503
|
+
paren_path=paren_path,
|
|
370
504
|
offset_bytes=offset,
|
|
371
505
|
bytes_width=rbw,
|
|
372
506
|
fields=fields,
|
|
@@ -396,6 +530,10 @@ class _Parser:
|
|
|
396
530
|
self.expect_keyword("field")
|
|
397
531
|
name = self.read_ident()
|
|
398
532
|
self.skip_ws_and_comments()
|
|
533
|
+
paren_path: str | None = None
|
|
534
|
+
if self._peek() == "(":
|
|
535
|
+
paren_path = self.read_round_paren_inner()
|
|
536
|
+
self.skip_ws_and_comments()
|
|
399
537
|
off_bits: int | None = None
|
|
400
538
|
if self._peek() == "@":
|
|
401
539
|
self._advance()
|
|
@@ -403,7 +541,9 @@ class _Parser:
|
|
|
403
541
|
self.expect_char("{")
|
|
404
542
|
fn = self._parse_field_body()
|
|
405
543
|
self.expect_char("}")
|
|
406
|
-
return fn.model_copy(
|
|
544
|
+
return fn.model_copy(
|
|
545
|
+
update={"name": name, "paren_path": paren_path, "offset_bits": off_bits}
|
|
546
|
+
)
|
|
407
547
|
|
|
408
548
|
def _parse_field_body(self) -> FieldNode:
|
|
409
549
|
inner: list[str] = []
|
{python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/tests/test_ralf_model.py
RENAMED
|
@@ -148,6 +148,69 @@ block top {
|
|
|
148
148
|
out = dump_ralf(doc)
|
|
149
149
|
self.assertIn("register R0;", out)
|
|
150
150
|
|
|
151
|
+
def test_register_paren_path_at_offset(self) -> None:
|
|
152
|
+
src = """
|
|
153
|
+
block top {
|
|
154
|
+
bytes 4;
|
|
155
|
+
register CTRL (dut.ctrl_reg) @'h0 {
|
|
156
|
+
bytes 4;
|
|
157
|
+
field ena(dut.ctrl_reg.en) @1 {
|
|
158
|
+
bits 1;
|
|
159
|
+
access rw;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
"""
|
|
164
|
+
doc = parse_ralf(src)
|
|
165
|
+
reg = doc.blocks[0].registers[0]
|
|
166
|
+
self.assertEqual(reg.name, "CTRL")
|
|
167
|
+
self.assertEqual(reg.paren_path, "dut.ctrl_reg")
|
|
168
|
+
self.assertEqual(reg.offset_bytes, 0)
|
|
169
|
+
fld = reg.fields[0]
|
|
170
|
+
self.assertEqual(fld.name, "ena")
|
|
171
|
+
self.assertEqual(fld.paren_path, "dut.ctrl_reg.en")
|
|
172
|
+
self.assertEqual(fld.offset_bits, 1)
|
|
173
|
+
out = dump_ralf(doc)
|
|
174
|
+
self.assertIn("register CTRL (dut.ctrl_reg) @'h0", out)
|
|
175
|
+
self.assertIn("field ena(dut.ctrl_reg.en) @'h1", out)
|
|
176
|
+
doc2 = parse_ralf(out)
|
|
177
|
+
self.assertEqual(doc.model_dump(), doc2.model_dump())
|
|
178
|
+
|
|
179
|
+
def test_register_paren_path_forward_decl(self) -> None:
|
|
180
|
+
src = """
|
|
181
|
+
block top {
|
|
182
|
+
register R0 (hdl.r0) @'h10;
|
|
183
|
+
}
|
|
184
|
+
"""
|
|
185
|
+
doc = parse_ralf(src)
|
|
186
|
+
r = doc.blocks[0].registers[0]
|
|
187
|
+
self.assertTrue(r.declaration_only)
|
|
188
|
+
self.assertEqual(r.paren_path, "hdl.r0")
|
|
189
|
+
self.assertEqual(r.offset_bytes, 0x10)
|
|
190
|
+
out = dump_ralf(doc)
|
|
191
|
+
self.assertIn("register R0 (hdl.r0) @'h10;", out)
|
|
192
|
+
doc2 = parse_ralf(out)
|
|
193
|
+
self.assertEqual(doc.model_dump(), doc2.model_dump())
|
|
194
|
+
|
|
195
|
+
def test_field_paren_path_no_space(self) -> None:
|
|
196
|
+
src = """
|
|
197
|
+
block top {
|
|
198
|
+
register r {
|
|
199
|
+
bytes 4;
|
|
200
|
+
field sig(hdl.sig) @0 {
|
|
201
|
+
bits 4;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
"""
|
|
206
|
+
doc = parse_ralf(src)
|
|
207
|
+
fld = doc.blocks[0].registers[0].fields[0]
|
|
208
|
+
self.assertEqual(fld.paren_path, "hdl.sig")
|
|
209
|
+
out = dump_ralf(doc)
|
|
210
|
+
self.assertIn("field sig(hdl.sig)", out)
|
|
211
|
+
doc2 = parse_ralf(out)
|
|
212
|
+
self.assertEqual(doc.model_dump(), doc2.model_dump())
|
|
213
|
+
|
|
151
214
|
def test_block_ref_rhs_with_parentheses(self) -> None:
|
|
152
215
|
src = """
|
|
153
216
|
block top {
|
|
@@ -178,6 +241,41 @@ block a {
|
|
|
178
241
|
self.assertNotIn("//", n)
|
|
179
242
|
|
|
180
243
|
|
|
244
|
+
class SystemParseTests(unittest.TestCase):
|
|
245
|
+
def test_top_system_nests_system_and_block(self) -> None:
|
|
246
|
+
src = """
|
|
247
|
+
system chip {
|
|
248
|
+
system cpu @0 {
|
|
249
|
+
block core {
|
|
250
|
+
register r @0 {
|
|
251
|
+
bytes 4;
|
|
252
|
+
field f {}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
block peri @'h1000 {
|
|
257
|
+
register s @0 {
|
|
258
|
+
bytes 1;
|
|
259
|
+
field g {}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
"""
|
|
264
|
+
doc = parse_ralf(src)
|
|
265
|
+
self.assertEqual(len(doc.systems), 1)
|
|
266
|
+
chip = doc.systems[0]
|
|
267
|
+
self.assertEqual(chip.name, "chip")
|
|
268
|
+
self.assertEqual(len(chip.systems), 1)
|
|
269
|
+
self.assertEqual(chip.systems[0].name, "cpu")
|
|
270
|
+
self.assertEqual(chip.systems[0].base_address, 0)
|
|
271
|
+
self.assertEqual(chip.blocks[0].name, "peri")
|
|
272
|
+
self.assertEqual(chip.blocks[0].base_address, 0x1000)
|
|
273
|
+
core = chip.systems[0].blocks[0]
|
|
274
|
+
self.assertEqual(core.registers[0].name, "r")
|
|
275
|
+
doc2 = parse_ralf(dump_ralf(doc))
|
|
276
|
+
self.assertEqual(doc.model_dump(), doc2.model_dump())
|
|
277
|
+
|
|
278
|
+
|
|
181
279
|
class ParseErrorTests(unittest.TestCase):
|
|
182
280
|
def test_parse_error_includes_file_path(self) -> None:
|
|
183
281
|
with tempfile.TemporaryDirectory() as td:
|
|
@@ -200,6 +298,23 @@ class ParseErrorTests(unittest.TestCase):
|
|
|
200
298
|
load_ralf_file(top)
|
|
201
299
|
self.assertIn(str(broken), str(ctx.exception))
|
|
202
300
|
self.assertNotIn(str(top.resolve()), str(ctx.exception))
|
|
301
|
+
self.assertEqual(ctx.exception.line, 2)
|
|
302
|
+
|
|
303
|
+
def test_parse_error_line_in_included_file_after_filler(self) -> None:
|
|
304
|
+
with tempfile.TemporaryDirectory() as td:
|
|
305
|
+
root = Path(td)
|
|
306
|
+
inc = root / "inc"
|
|
307
|
+
inc.mkdir()
|
|
308
|
+
(inc / "broken.ralf").write_text(
|
|
309
|
+
"// line1\n// line2\nblock y {\n ???\n}\n",
|
|
310
|
+
encoding="utf-8",
|
|
311
|
+
)
|
|
312
|
+
top = root / "top.ralf"
|
|
313
|
+
filler = "\n".join(["// filler"] * 50)
|
|
314
|
+
top.write_text(filler + '\nsource "inc/broken.ralf"\n', encoding="utf-8")
|
|
315
|
+
with self.assertRaises(RalfParseError) as ctx:
|
|
316
|
+
load_ralf_file(top)
|
|
317
|
+
self.assertEqual(ctx.exception.line, 4)
|
|
203
318
|
|
|
204
319
|
|
|
205
320
|
if __name__ == "__main__":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/ralf_model/source_expand.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_library_ralf_model-0.1.1 → python_library_ralf_model-0.1.3}/tests/test_source_include.py
RENAMED
|
File without changes
|