Create project
All checks were successful
build / test (3.10) (push) Successful in 6s
build / test (3.11) (push) Successful in 6s
build / test (3.12) (push) Successful in 6s
build / test (3.13) (push) Successful in 6s
build / test (3.9) (push) Successful in 6s
build / lint (push) Successful in 7s
build / mypy (push) Successful in 8s
build / test (3.10) (release) Successful in 6s
build / test (3.11) (release) Successful in 6s
build / test (3.12) (release) Successful in 6s
build / test (3.13) (release) Successful in 6s
build / test (3.9) (release) Successful in 6s
build / lint (release) Successful in 8s
build / mypy (release) Successful in 8s
build / Build distributions (push) Successful in 7s
build / Build distributions (release) Successful in 8s
build / deploy (push) Has been skipped
build / deploy (release) Successful in 6s

This commit is contained in:
Byron Lathi
2025-11-23 13:20:23 -08:00
commit b43de9206b
34 changed files with 1788 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
version_info = (0, 0, 1)
__version__ = ".".join([str(n) for n in version_info])

View File

View File

@@ -0,0 +1,25 @@
from typing import TYPE_CHECKING
from peakrdl.plugins.exporter import ExporterSubcommandPlugin
from .exporter import PythonRegmapExporter
if TYPE_CHECKING:
import argparse
from systemrdl.node import AddrmapNode
class Exporter(ExporterSubcommandPlugin):
short_desc = "Generate a Python regmap definition of an address space"
cfg_schema = {
}
def do_export(self, top_node: 'AddrmapNode', options: 'argparse.Namespace') -> None:
x = PythonRegmapExporter()
x.export(
top_node,
path=options.output
)

View File

@@ -0,0 +1,96 @@
from typing import Optional, List
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
from systemrdl.node import AddrmapNode, RegNode, AddressableNode
from .design_state import DesignState
class DesignScanner(RDLListener):
def __init__(self, ds: DesignState) -> None:
self.ds = ds
self.msg = ds.top_node.env.msg
self.prev_reg_stack: List[Optional[RegNode]]
self.prev_reg_stack = []
@property
def top_node(self) -> AddrmapNode:
return self.ds.top_node
def run(self) -> None:
RDLWalker().walk(self.top_node, self)
if self.msg.had_error:
self.msg.fatal(
"Unable to export due to previous errors"
)
def enter_AddressableComponent(self, node: AddressableNode) -> Optional[WalkerAction]:
if not isinstance(node, RegNode):
self.prev_reg_stack.append(None)
return WalkerAction.Continue
def exit_AddressableComponent(self, node: AddressableNode) -> Optional[WalkerAction]:
if not isinstance(node, RegNode):
self.prev_reg_stack.pop()
return WalkerAction.Continue
def enter_Reg(self, node: RegNode) -> Optional[WalkerAction]:
# Collect information about overlapping fields, if any.
overlapping_fields = []
fields = list(node.fields())
reg_bitmask = 0
for i, field in enumerate(fields):
field_bitmask = ((1 << field.width) - 1) << field.low
if field_bitmask & reg_bitmask:
# this field overlaps with a prior one
# Determine which one
for prior_field in fields[0:i]:
if prior_field.high >= field.low:
if prior_field.inst_name not in overlapping_fields:
overlapping_fields.append(prior_field.inst_name)
if field.inst_name not in overlapping_fields:
overlapping_fields.append(field.inst_name)
reg_bitmask |= field_bitmask
if overlapping_fields:
# Save infor about this register for later.
self.ds.overlapping_fields[node.get_path()] = overlapping_fields
# Check previous adjacent register for overlap
prev_reg = self.prev_reg_stack[-1]
if prev_reg and ((prev_reg.raw_address_offset + prev_reg.total_size) > node.raw_address_offset):
# registers overlap!
# Registers shall be co-located.
# This restriction guarantees that overlaps can only happen in pairs,
# and avoids the more complex overlap scenarios that involve multiple registers.
if (
prev_reg.raw_address_offset != node.raw_address_offset # Same offset
or prev_reg.size != node.size # Same size
or prev_reg.total_size != node.total_size # Same array footprint
):
self.msg.error(
"C header export currently only supports registers that are co-located. "
f"See registers: '{prev_reg.inst_name}' and '{node.inst_name}.'",
node.inst_src_ref
)
# Save information about register overlap pair
self.ds.overlapping_reg_pairs[prev_reg.get_path()] = node.inst_name
# Check for sparse register arrays
if node.is_array and node.array_stride > node.size: # type: ignore # is_array implies array_stride is not none
self.msg.error(
"C header export does not support sparse arrays of registers. "
f"See register: '{node.inst_name}.'",
node.inst_src_ref
)
return WalkerAction.SkipDescendants
def exit_Reg(self, node: RegNode) -> None:
self.prev_reg_stack[-1] = node

View File

@@ -0,0 +1,30 @@
from typing import Any, Dict, List
import os
import jinja2 as jj
from systemrdl.node import AddrmapNode
class DesignState:
def __init__(self, top_node: AddrmapNode, kwargs: Any) -> None:
loader = jj.FileSystemLoader(os.path.join(os.path.dirname(__file__), "templates"))
self.jj_env = jj.Environment(
loader=loader,
undefined=jj.StrictUndefined
)
self.top_node = top_node
#------------------------
# Info about the design
#------------------------
# Each reg that has overlapping fields generates an entry:
# reg_path : list of field names involved in overlap
self.overlapping_fields: Dict[str, List[str]] = {}
# Pairs of overlapping registers
# first_reg_path : partner_register_name
self.overlapping_reg_pairs: Dict[str, str] = {}
#------------------------
# Extract compiler args
#------------------------

View File

@@ -0,0 +1,41 @@
from typing import Any, Union
from systemrdl.node import RootNode, AddrmapNode
from .design_state import DesignState
from .design_scanner import DesignScanner
from .generator import Generator
# from .testcase_generator import TestcaseGenerator
class PythonRegmapExporter:
def export(self, node: Union[RootNode, AddrmapNode], path: str, **kwargs: Any) -> None:
"""
Parameters
----------
node: AddrmapNode
Top-level SystemRDL node to export.
path: str
Output header file path
"""
# If it is the root node, skip to top addrmap
if isinstance(node, RootNode):
top_node = node.top
else:
top_node = node
ds = DesignState(top_node, kwargs)
# Check for stray kwargs
if kwargs:
raise TypeError(f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'")
# Validate and collect info for export
DesignScanner(ds).run()
top_nodes = []
top_nodes.append(top_node)
# Write output
Generator(ds).run(path, top_nodes)
# if ds.testcase:
# TestcaseGenerator(ds).run(path, top_nodes)

View File

@@ -0,0 +1,52 @@
from typing import TextIO, Set, List, Union, Dict, Any
from systemrdl.walker import RDLListener, RDLWalker
from systemrdl.node import AddrmapNode, AddressableNode, MemNode, RegfileNode
from .design_state import DesignState
class Generator(RDLListener):
root_node: Union[AddrmapNode, MemNode, RegfileNode]
f: TextIO
def __init__(self, ds: DesignState) -> None:
self.ds = ds
self.defined_namespace: Set[str]
self.defined_namespace = set()
self.indent_level = 0
def run(self, path: str, top_nodes: List[AddrmapNode]) -> None:
with open(path, "w", encoding='utf-8') as f:
self.f = f
context: Dict[str, Any] = {}
# Stream header via jinja
template = self.ds.jj_env.get_template("regmap.py")
template.stream(context).dump(f) # type: ignore # jinja incorrectly typed
f.write("\n")
for node in top_nodes:
self.root_node = node
RDLWalker().walk(node, self)
def enter_AddressableComponent(self, node: AddressableNode) -> None:
self.f.write(f"{' '*self.indent_level*4}class {node.inst_name}Class(AddrNode):\n")
self.indent_level+=1
def exit_AddressableComponent(self, node: AddressableNode) -> None:
self.f.write(f"{' '*self.indent_level*4}def __init__(self, addr: int = 0):\n")
self.f.write(f"{' '*self.indent_level*4} self.addr = addr\n")
for child in node.children():
if isinstance(child, AddressableNode):
if child.is_array:
assert child.array_dimensions is not None
if len(child.array_dimensions) > 1:
raise NotImplementedError("Multidimensional arrays not supported")
self.f.write(f"{' '*self.indent_level*4} self.{child.inst_name} = [self.{child.inst_name}Class({child.raw_address_offset} + {child.size}*i for i in range({child.n_elements}))]\n")
else:
self.f.write(f"{' '*self.indent_level*4} self.{child.inst_name} = self.{child.inst_name}Class({child.address_offset})\n")
self.indent_level-=1

View File

@@ -0,0 +1,32 @@
C_KEYWORDS = {
# Base
"auto", "break", "case", "char", "const", "continue", "default", "do",
"double", "else", "enum", "extern", "float", "for", "goto", "if", "int",
"long", "register", "return", "short", "signed", "sizeof", "static",
"struct", "switch", "typedef", "union", "unsigned", "void", "volatile",
"while",
# C99
"inline", "restrict", "_Bool", "_Complex", "_Imaginary",
# C11
"_Alignas", "_Alignof", "_Atomic", "_Generic", "_Noreturn",
"_Static_assert", "_Thread_local",
# C23
"alignas", "alignof", "bool", "constexpr", "false", "nullptr",
"static_assert", "thread_local", "true", "typeof", "typeof_unqual",
"_BitInt", "_Decimal128", "_Decimal32", "_Decimal64",
}
def kw_filter(s: str) -> str:
"""
Make all user identifiers 'safe' and ensure they do not collide with
C keywords.
If a C keyword is encountered, add an underscore suffix
"""
if s in C_KEYWORDS:
s += "_"
return s

View File

View File

@@ -0,0 +1,2 @@
class AddrNode():
addr: int

View File

@@ -0,0 +1,37 @@
from typing import Union
from systemrdl.node import AddressableNode, AddrmapNode, Node, MemNode, RegfileNode
from .design_state import DesignState
def get_node_prefix(ds: DesignState, root_node: Union[AddrmapNode, MemNode, RegfileNode], node: AddressableNode) -> str:
prefix = node.get_rel_path(
root_node.parent,
hier_separator="__",
array_suffix="x",
empty_array_suffix="x"
)
return prefix
def get_struct_name(ds: DesignState, root_node: Union[AddrmapNode, MemNode, RegfileNode], node: AddressableNode) -> str:
if node.is_array and node.array_stride > node.size: # type: ignore # is_array implies array_stride is not none
# Stride is larger than size of actual element.
# Struct will be padded up, and therefore needs a unique name
pad_suffix = f"__stride{node.array_stride:x}"
else:
pad_suffix = ""
return get_node_prefix(ds, root_node, node) + pad_suffix + "_t"
def get_friendly_name(ds: DesignState, root_node: Union[AddrmapNode, MemNode, RegfileNode], node: Node) -> str:
"""
Returns a useful string that helps identify the typedef in
a comment
"""
friendly_name = node.get_rel_path(root_node.parent)
return node.component_type_name + " - " + friendly_name
def roundup_pow2(x: int) -> int:
return 1<<(x-1).bit_length()