Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/configuring.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ For example:
crate_version = "0.1.0"
force = true
no_fmt = false
access_mode = "software"


.. data:: crate_name
Expand Down Expand Up @@ -46,3 +47,17 @@ For example:
``cargo fmt``.

Default: ``false``


.. data:: access_mode

Controls which access properties are used for register and field access
determination.

Options:

- ``software`` - Use software read/write access properties (sw property)
- ``hardware`` - Use hardware read/write access properties (hw property)
- ``read_only`` - Force all registers and fields to be read-only

Default: ``software``
9 changes: 5 additions & 4 deletions src/peakrdl_rust/component_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,9 @@ class Enum(Component):


class ContextScanner(RDLListener):
def __init__(self, top_nodes: list[AddrmapNode]) -> None:
def __init__(self, top_nodes: list[AddrmapNode], access_mode: str = "software") -> None:
self.top_nodes = top_nodes
self.access_mode = access_mode
self.top_component_modules: list[str] = []
self.components: dict[Path, Component] = {}
self.msg = top_nodes[0].env.msg
Expand Down Expand Up @@ -250,7 +251,7 @@ def enter_addrmap_or_regfile_or_memory(
addr_offset = child.address_offset

if isinstance(child, RegNode):
if not (access := utils.reg_access(child)):
if not (access := utils.reg_access(child, self.access_mode)):
continue
registers.append(
RegisterInst(
Expand Down Expand Up @@ -319,7 +320,7 @@ def enter_addrmap_or_regfile_or_memory(
else:
assert len(submaps) == 0
assert len(memories) == 0
if not (access := utils.field_access(node)):
if not (access := utils.field_access(node, self.access_mode)):
return WalkerAction.Continue
memwidth = node.get_property("memwidth")
primitive_width = 2 ** int(math.ceil(math.log2(memwidth)))
Expand Down Expand Up @@ -415,7 +416,7 @@ def enter_Reg(self, node: RegNode) -> Optional[WalkerAction]:
comment=utils.doc_comment(field),
inst_name=snakecase(field.inst_name),
type_name=pascalcase(field.inst_name),
access=utils.field_access(field),
access=utils.field_access(field, self.access_mode),
primitive=primitive,
encoding=encoding_name,
exhaustive=exhaustive,
Expand Down
10 changes: 9 additions & 1 deletion src/peakrdl_rust/design_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,22 @@ def __init__(self, top_nodes: list[AddrmapNode], path: str, kwargs: Any) -> None
self.no_fmt: bool
self.no_fmt = kwargs.pop("no_fmt", False)

self.access_mode: str
self.access_mode = kwargs.pop("access_mode", "software")
if self.access_mode not in ("software", "hardware", "read_only"):
raise ValueError(
f"Invalid access_mode '{self.access_mode}'. "
"Must be one of: 'software', 'hardware', 'read_only'"
)

# ------------------------
# Collect info for export
# ------------------------
scanner = DesignScanner(self.top_nodes)
scanner.run()
self.has_fixedpoint: bool = scanner.has_fixedpoint

component_context = ContextScanner(self.top_nodes)
component_context = ContextScanner(self.top_nodes, self.access_mode)
component_context.run()
self.top_component_modules: list[str] = component_context.top_component_modules
self.components: dict[Path, Component] = component_context.components
5 changes: 5 additions & 0 deletions src/peakrdl_rust/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ def export(
Semantic version number for the generated crate.
no_fmt: bool
Don't attempt to format the generated rust code using `cargo fmt`.
access_mode: str
Access mode for register/field access functions. One of:
- 'software' (default): Returns software read/write access properties
- 'hardware': Returns hardware read/write access properties
- 'read_only': Returns read-only for all access types
"""
# If it is the root node, skip to top addrmap
if isinstance(node, RootNode):
Expand Down
36 changes: 28 additions & 8 deletions src/peakrdl_rust/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,27 +151,47 @@ def crate_enum_module_path(field: FieldNode, enum: type[UserEnum]) -> list[str]:
return parent_modules + ["named_types", module_name]


def reg_access(node: RegNode) -> Union[str, None]:
if node.has_sw_readable:
if node.has_sw_writable:
def reg_access(node: RegNode, access_mode: str = "software") -> Union[str, None]:
if access_mode == "read_only":
return "R"

if access_mode == "hardware":
readable = node.has_hw_readable
writable = node.has_hw_writable
else: # software (default)
readable = node.has_sw_readable
writable = node.has_sw_writable

if readable:
if writable:
return "RW"
else:
return "R"
else:
if node.has_sw_writable:
if writable:
return "W"
else:
return None


def field_access(node: Union[FieldNode, MemNode]) -> Union[str, None]:
if node.is_sw_readable:
if node.is_sw_writable:
def field_access(node: Union[FieldNode, MemNode], access_mode: str = "software") -> Union[str, None]:
if access_mode == "read_only":
return "R"

if access_mode == "hardware":
readable = node.is_hw_readable
writable = node.is_hw_writable
else: # software (default)
readable = node.is_sw_readable
writable = node.is_sw_writable

if readable:
if writable:
return "RW"
else:
return "R"
else:
if node.is_sw_writable:
if writable:
return "W"
else:
return None
Expand Down
46 changes: 46 additions & 0 deletions tests/rdl_src/access_modes.rdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

addrmap access_modes_test {
name = "Access Modes Test";
desc = "Test different access mode configurations for software and hardware";

reg {
name = "Software RW, Hardware R";
field {
sw=rw;
hw=r;
} sw_rw_hw_r[7:0] = 0x00;

field {
sw=r;
hw=rw;
} sw_r_hw_rw[15:8] = 0x00;

field {
sw=rw;
hw=rw;
} sw_rw_hw_rw[23:16] = 0x00;

field {
sw=w;
hw=r;
} sw_w_hw_r[31:24] = 0x00;
} test_reg @ 0x0;

reg {
name = "Hardware Access Only";
field {
sw=r;
hw=w;
} sw_r_hw_w[7:0] = 0x55;

field {
sw=w;
hw=r;
} sw_w_hw_r[15:8] = 0xAA;

field {
sw=r;
hw=r;
} sw_r_hw_r[23:16] = 0xFF;
} hw_reg @ 0x4;
};
176 changes: 176 additions & 0 deletions tests/test_access_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import shutil
from pathlib import Path

import pytest
from systemrdl.compiler import RDLCompiler

from peakrdl_rust.exporter import RustExporter
from peakrdl_rust.udps import ALL_UDPS


def test_access_mode_software() -> None:
"""Test exporter with software access mode (default)."""
rdl_file = Path(__file__).parent / "rdl_src" / "access_modes.rdl"
crate_dir = Path(__file__).parent / "output" / "access_modes_software"

if crate_dir.exists():
shutil.rmtree(crate_dir)
crate_dir.mkdir(exist_ok=True, parents=True)

rdlc = RDLCompiler()

# Load the UDPs
for udp in ALL_UDPS:
rdlc.register_udp(udp)
udp_file = Path(__file__).parent / "../src/peakrdl_rust/udps/udps.rdl"
rdlc.compile_file(str(udp_file))

rdlc.compile_file(str(rdl_file))
root = rdlc.elaborate(top_def_name="access_modes_test")

exporter = RustExporter()
exporter.export(
root.top,
path=str(crate_dir.parent),
crate_name="access_modes_software",
force=True,
access_mode="software", # Explicitly set software mode
)

# Verify the crate was created
assert crate_dir.exists()
assert (crate_dir / "Cargo.toml").exists()
assert (crate_dir / "src" / "lib.rs").exists()


def test_access_mode_hardware() -> None:
"""Test exporter with hardware access mode."""
rdl_file = Path(__file__).parent / "rdl_src" / "access_modes.rdl"
crate_dir = Path(__file__).parent / "output" / "access_modes_hardware"

if crate_dir.exists():
shutil.rmtree(crate_dir)
crate_dir.mkdir(exist_ok=True, parents=True)

rdlc = RDLCompiler()

# Load the UDPs
for udp in ALL_UDPS:
rdlc.register_udp(udp)
udp_file = Path(__file__).parent / "../src/peakrdl_rust/udps/udps.rdl"
rdlc.compile_file(str(udp_file))

rdlc.compile_file(str(rdl_file))
root = rdlc.elaborate(top_def_name="access_modes_test")

exporter = RustExporter()
exporter.export(
root.top,
path=str(crate_dir.parent),
crate_name="access_modes_hardware",
force=True,
access_mode="hardware",
)

# Verify the crate was created
assert crate_dir.exists()
assert (crate_dir / "Cargo.toml").exists()
assert (crate_dir / "src" / "lib.rs").exists()


def test_access_mode_read_only() -> None:
"""Test exporter with read_only access mode."""
rdl_file = Path(__file__).parent / "rdl_src" / "access_modes.rdl"
crate_dir = Path(__file__).parent / "output" / "access_modes_read_only"

if crate_dir.exists():
shutil.rmtree(crate_dir)
crate_dir.mkdir(exist_ok=True, parents=True)

rdlc = RDLCompiler()

# Load the UDPs
for udp in ALL_UDPS:
rdlc.register_udp(udp)
udp_file = Path(__file__).parent / "../src/peakrdl_rust/udps/udps.rdl"
rdlc.compile_file(str(udp_file))

rdlc.compile_file(str(rdl_file))
root = rdlc.elaborate(top_def_name="access_modes_test")

exporter = RustExporter()
exporter.export(
root.top,
path=str(crate_dir.parent),
crate_name="access_modes_read_only",
force=True,
access_mode="read_only",
)

# Verify the crate was created
assert crate_dir.exists()
assert (crate_dir / "Cargo.toml").exists()
assert (crate_dir / "src" / "lib.rs").exists()


def test_access_mode_default() -> None:
"""Test that default behavior is software mode."""
rdl_file = Path(__file__).parent / "rdl_src" / "access_modes.rdl"
crate_dir = Path(__file__).parent / "output" / "access_modes_default"

if crate_dir.exists():
shutil.rmtree(crate_dir)
crate_dir.mkdir(exist_ok=True, parents=True)

rdlc = RDLCompiler()

# Load the UDPs
for udp in ALL_UDPS:
rdlc.register_udp(udp)
udp_file = Path(__file__).parent / "../src/peakrdl_rust/udps/udps.rdl"
rdlc.compile_file(str(udp_file))

rdlc.compile_file(str(rdl_file))
root = rdlc.elaborate(top_def_name="access_modes_test")

exporter = RustExporter()
exporter.export(
root.top,
path=str(crate_dir.parent),
crate_name="access_modes_default",
force=True,
# No access_mode specified, should default to 'software'
)

# Verify the crate was created
assert crate_dir.exists()
assert (crate_dir / "Cargo.toml").exists()
assert (crate_dir / "src" / "lib.rs").exists()


def test_access_mode_invalid() -> None:
"""Test that invalid access mode raises ValueError."""
rdl_file = Path(__file__).parent / "rdl_src" / "access_modes.rdl"
crate_dir = Path(__file__).parent / "output" / "access_modes_invalid"

rdlc = RDLCompiler()

# Load the UDPs
for udp in ALL_UDPS:
rdlc.register_udp(udp)
udp_file = Path(__file__).parent / "../src/peakrdl_rust/udps/udps.rdl"
rdlc.compile_file(str(udp_file))

rdlc.compile_file(str(rdl_file))
root = rdlc.elaborate(top_def_name="access_modes_test")

exporter = RustExporter()

with pytest.raises(ValueError, match="Invalid access_mode"):
exporter.export(
root.top,
path=str(crate_dir.parent),
crate_name="access_modes_invalid",
force=True,
access_mode="invalid_mode", # This should raise ValueError
)
Loading