Skip to content

Commit a32ab6f

Browse files
authored
Merge pull request #3 from joecrop/access-types
Added control for which access type to use
2 parents a81004a + 8b74796 commit a32ab6f

31 files changed

Lines changed: 503 additions & 47 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [unreleased]
99

10+
### Added
11+
12+
- `access_mode` parameter to control whether software or hardware access permissions are used.
13+
- `read_only` parameter to force all registers/fields to be treated as read-only.
14+
1015
### Fixed
1116

1217
- Generate self-contained `use` statements that don't rely on the module structure outside the generated code. ([#9](https://github.com/darsor/PeakRDL-rust/issues/9))

crates/peakrdl-rust-build/src/lib.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ pub enum Endian {
2121
Little,
2222
}
2323

24+
/// Access mode
25+
#[derive(Clone, Copy, Debug, PartialEq)]
26+
pub enum AccessMode {
27+
/// Use software access permissions (sw property)
28+
Software,
29+
/// Use hardware access permissions (hw property)
30+
Hardware,
31+
}
32+
2433
/// Builder for configuring and running the PeakRDL-rust code generator.
2534
#[derive(Debug)]
2635
pub struct Generator {
@@ -54,6 +63,11 @@ pub struct Generator {
5463
/// By default uses the `littleendian` and `bigendian` addrmap properties,
5564
/// or little endian if not defined.
5665
word_endian: Option<Endian>,
66+
// Access mode for register/field access functions.
67+
access_mode: Option<AccessMode>,
68+
// Treat all registers and fields as read-only. Write-only registers and
69+
// fields are not exposed.
70+
read_only: bool,
5771
}
5872

5973
impl Default for Generator {
@@ -79,6 +93,8 @@ impl Generator {
7993
fmt: false,
8094
byte_endian: None,
8195
word_endian: None,
96+
access_mode: None,
97+
read_only: false,
8298
}
8399
}
84100

@@ -174,7 +190,20 @@ impl Generator {
174190
self
175191
}
176192

193+
/// Set the access mode for register/field access functions.
194+
pub fn access_mode(&mut self, mode: AccessMode) -> &mut Self {
195+
self.access_mode = Some(mode);
196+
self
197+
}
198+
199+
/// Set to `true` to treat all registers/fields as read-only.
200+
pub fn read_only(&mut self, read_only: bool) -> &mut Self {
201+
self.read_only = read_only;
202+
self
203+
}
204+
177205
#[allow(clippy::missing_errors_doc)]
206+
#[allow(clippy::too_many_lines)]
178207
/// Run the code generator.
179208
///
180209
/// This will:
@@ -282,7 +311,21 @@ impl Generator {
282311
Some(Endian::Little) => {
283312
cmd.args(["--word-endian", "little"]);
284313
}
285-
_ => (),
314+
None => (),
315+
}
316+
317+
match self.access_mode {
318+
Some(AccessMode::Hardware) => {
319+
cmd.args(["--access-mode", "hardware"]);
320+
}
321+
Some(AccessMode::Software) => {
322+
cmd.args(["--access-mode", "software"]);
323+
}
324+
None => (),
325+
}
326+
327+
if self.read_only {
328+
cmd.arg("--read-only");
286329
}
287330

288331
for file in &self.files {

docs/configuring.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ For example:
1616
fmt = true
1717
byte_endian = "big"
1818
word_endian = "little"
19+
access_mode = "software"
20+
read_only = false
1921
2022
2123
.. data:: force
@@ -49,3 +51,24 @@ For example:
4951
word endianness.
5052

5153
Default: addrmap endianness propery, or ``little`` if not defined
54+
55+
56+
.. data:: access_mode
57+
58+
Controls which access properties are used for register and field access
59+
determination.
60+
61+
Options:
62+
63+
- ``software`` - Use software read/write access properties (sw property)
64+
- ``hardware`` - Use hardware read/write access properties (hw property)
65+
66+
Default: ``software``
67+
68+
69+
.. data:: read_only
70+
71+
If true, treat all registers and fields as read-only. Write-only registers and
72+
fields are not exposed.
73+
74+
Default: ``false``

docs/examples/Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ $(EXAMPLE_HTML):
77
mkdir -p $(REPO_ROOT)/docs/rust_docs/examples
88
mkdir -p turboencabulator/tests
99
cp $(TEST_SRC)/turboencabulator.rs turboencabulator/tests/
10-
sed -i 's/peakrdl_rust_test/turboencabulator/g' turboencabulator/tests/turboencabulator.rs
1110
cd turboencabulator && cargo test
1211
cd turboencabulator && cargo doc
1312
cp -r turboencabulator/target/doc/. $(REPO_ROOT)/docs/rust_docs/examples/

src/peakrdl_rust/__peakrdl__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class Exporter(ExporterSubcommandPlugin):
2222
"no_fmt": schema.Boolean(),
2323
"byte_endian": schema.Choice(["big", "little"]),
2424
"word_endian": schema.Choice(["big", "little"]),
25+
"access_mode": schema.Choice(["software", "hardware"]),
26+
"read_only": schema.Boolean(),
2527
}
2628

2729
def add_exporter_arguments(self, arg_group: "argparse._ActionsContainer") -> None:
@@ -63,6 +65,26 @@ def add_exporter_arguments(self, arg_group: "argparse._ActionsContainer") -> Non
6365
""",
6466
)
6567

68+
arg_group.add_argument(
69+
"--access-mode",
70+
choices=["software", "hardware"],
71+
default="software",
72+
help="""
73+
Which RDL access attribute (hw or sw) to use for register/field
74+
access permissions.
75+
""",
76+
)
77+
78+
arg_group.add_argument(
79+
"--read-only",
80+
action="store_true",
81+
default=False,
82+
help="""
83+
Treat all registers and fields as read-only. Write-only registers and
84+
fields are not exposed.
85+
""",
86+
)
87+
6688
def do_export(self, top_node: "AddrmapNode", options: "argparse.Namespace") -> None:
6789
x = RustExporter()
6890
x.export(
@@ -72,4 +94,6 @@ def do_export(self, top_node: "AddrmapNode", options: "argparse.Namespace") -> N
7294
fmt=options.fmt,
7395
byte_endian=options.byte_endian,
7496
word_endian=options.word_endian,
97+
access_mode=options.access_mode,
98+
read_only=options.read_only,
7599
)

src/peakrdl_rust/component_context.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,18 @@ def __init__(
183183
top_nodes: list[AddrmapNode],
184184
byte_endian: Literal["Big", "Little"],
185185
word_endian: Literal["Big", "Little"],
186+
access_mode: str = "software",
187+
read_only: bool = False,
186188
) -> None:
187189
self.top_nodes = top_nodes
188190
self.byte_endian: Literal["Big", "Little"] = byte_endian
189191
self.word_endian: Literal["Big", "Little"] = word_endian
192+
self.access_mode = access_mode
193+
self.read_only = read_only
190194
self.top_component_modules: list[str] = []
191195
self.components: dict[Path, Component] = {}
192196
self.msg = top_nodes[0].env.msg
197+
self.access_mode = access_mode
193198

194199
def run(self) -> None:
195200
for node in self.top_nodes:
@@ -260,7 +265,7 @@ def enter_addrmap_or_regfile_or_memory(
260265
addr_offset = child.address_offset
261266

262267
if isinstance(child, RegNode):
263-
if not utils.reg_access(child):
268+
if not utils.reg_access(child, self.access_mode, self.read_only):
264269
continue
265270
registers.append(
266271
RegisterInst(
@@ -325,10 +330,10 @@ def enter_addrmap_or_regfile_or_memory(
325330
memories=memories,
326331
size=node.size,
327332
)
328-
else:
333+
else: # MemNode
329334
assert len(submaps) == 0
330335
assert len(memories) == 0
331-
if not (access := utils.field_access(node)):
336+
if not (access := utils.mem_access(node, self.access_mode, self.read_only)):
332337
return WalkerAction.Continue
333338
memwidth = node.get_property("memwidth")
334339
primitive_width = 2 ** int(math.ceil(math.log2(memwidth)))
@@ -366,12 +371,19 @@ def enter_Reg(self, node: RegNode) -> Optional[WalkerAction]:
366371
# already handled
367372
return WalkerAction.SkipDescendants
368373

369-
if not (access := utils.reg_access(node)):
374+
if not (reg_access := utils.reg_access(node, self.access_mode, self.read_only)):
370375
return WalkerAction.Continue
371376

372377
reg_reset_val = 0
373378
fields: list[FieldInst] = []
374379
for field in node.fields():
380+
if not (
381+
field_access := utils.field_access(
382+
field, self.access_mode, self.read_only
383+
)
384+
):
385+
continue
386+
375387
encoding = field.get_property("encode")
376388
if encoding is not None:
377389
encoding_name = (
@@ -429,7 +441,7 @@ def enter_Reg(self, node: RegNode) -> Optional[WalkerAction]:
429441
comment=utils.doc_comment(field),
430442
inst_name=snakecase(field.inst_name),
431443
type_name=pascalcase(field.inst_name),
432-
access=utils.field_access(field),
444+
access=field_access,
433445
primitive=primitive,
434446
encoding=encoding_name,
435447
exhaustive=exhaustive,
@@ -454,7 +466,7 @@ def enter_Reg(self, node: RegNode) -> Optional[WalkerAction]:
454466
type_name=utils.rust_type_name(node),
455467
regwidth=node.get_property("regwidth"),
456468
accesswidth=node.get_property("accesswidth"),
457-
access=access,
469+
access=reg_access,
458470
reset_val=reg_reset_val,
459471
fields=fields,
460472
has_sw_readable=node.has_sw_readable,

src/peakrdl_rust/design_state.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ def __init__(self, top_nodes: list[AddrmapNode], path: str, kwargs: Any) -> None
4747
self.byte_endian: Literal["Big", "Little"] = byte_endian.capitalize() # type: ignore
4848
self.word_endian: Literal["Big", "Little"] = word_endian.capitalize() # type: ignore
4949

50+
self.access_mode: str
51+
self.access_mode = kwargs.pop("access_mode", "software")
52+
if self.access_mode not in ("software", "hardware"):
53+
raise ValueError(
54+
f"Invalid access_mode '{self.access_mode}'. "
55+
"Must be one of: 'software', 'hardware'"
56+
)
57+
58+
self.read_only: bool
59+
self.read_only = kwargs.pop("read_only", False)
60+
5061
# ------------------------
5162
# Collect info for export
5263
# ------------------------
@@ -55,7 +66,11 @@ def __init__(self, top_nodes: list[AddrmapNode], path: str, kwargs: Any) -> None
5566
self.has_fixedpoint: bool = scanner.has_fixedpoint
5667

5768
component_context = ContextScanner(
58-
self.top_nodes, self.byte_endian, self.word_endian
69+
self.top_nodes,
70+
self.byte_endian,
71+
self.word_endian,
72+
self.access_mode,
73+
self.read_only,
5974
)
6075
component_context.run()
6176
self.top_component_modules: list[str] = component_context.top_component_modules

src/peakrdl_rust/exporter.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ def export(
3333
word_endian: Optional[Literal["big", "little"]]
3434
Ordering of `accesswidth`-sized words within a wide register. Overrides
3535
the `littleendian` and `bigendian` addrmap properties.
36+
access_mode: str
37+
Access mode for register/field access functions. One of:
38+
- `software` (default): Use software read/write access properties
39+
- `hardware`: Use hardware read/write access properties
40+
read_only: bool
41+
Treat all registers and fields as read-only. Write-only registers and
42+
fields are not exposed.
3643
"""
3744
# If it is the root node, skip to top addrmap
3845
if isinstance(node, RootNode):

src/peakrdl_rust/utils.py

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,27 +170,74 @@ def crate_enum_module_path(field: FieldNode, enum: type[UserEnum]) -> list[str]:
170170
return parent_modules + ["named_types", module_name]
171171

172172

173-
def reg_access(node: RegNode) -> Union[str, None]:
174-
if node.has_sw_readable:
175-
if node.has_sw_writable:
173+
def reg_access(
174+
node: RegNode,
175+
access_mode: str = "software",
176+
read_only: bool = False,
177+
) -> Union[str, None]:
178+
if access_mode == "hardware":
179+
readable = node.has_hw_readable
180+
writable = node.has_hw_writable and not read_only
181+
else: # software (default)
182+
readable = node.has_sw_readable
183+
writable = node.has_sw_writable and not read_only
184+
185+
if readable:
186+
if writable:
176187
return "RW"
177188
else:
178189
return "R"
179190
else:
180-
if node.has_sw_writable:
191+
if writable:
181192
return "W"
182193
else:
183194
return None
184195

185196

186-
def field_access(node: Union[FieldNode, MemNode]) -> Union[str, None]:
187-
if node.is_sw_readable:
188-
if node.is_sw_writable:
197+
def field_access(
198+
node: FieldNode,
199+
access_mode: str = "software",
200+
read_only: bool = False,
201+
) -> Union[str, None]:
202+
if access_mode == "hardware":
203+
readable = node.is_hw_readable
204+
writable = node.is_hw_writable and not read_only
205+
else: # software (default)
206+
readable = node.is_sw_readable
207+
writable = node.is_sw_writable and not read_only
208+
209+
if readable:
210+
if writable:
189211
return "RW"
190212
else:
191213
return "R"
192214
else:
193-
if node.is_sw_writable:
215+
if writable:
216+
return "W"
217+
else:
218+
return None
219+
220+
221+
def mem_access(
222+
node: MemNode,
223+
access_mode: str = "software",
224+
read_only: bool = False,
225+
) -> Union[str, None]:
226+
if access_mode == "hardware":
227+
# Assume RW since there's no hw access attribute for memories
228+
readable = True
229+
writable = not read_only
230+
else: # software (default)
231+
readable = node.is_sw_readable
232+
writable = node.is_sw_writable and not read_only
233+
234+
if readable:
235+
if writable:
236+
return "RW"
237+
else:
238+
return "R"
239+
else:
240+
if writable:
194241
return "W"
195242
else:
196243
return None

0 commit comments

Comments
 (0)