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
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:
2
src/peakrdl_python_regmap/__about__.py
Normal file
2
src/peakrdl_python_regmap/__about__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
version_info = (0, 0, 1)
|
||||
__version__ = ".".join([str(n) for n in version_info])
|
||||
0
src/peakrdl_python_regmap/__init__.py
Normal file
0
src/peakrdl_python_regmap/__init__.py
Normal file
25
src/peakrdl_python_regmap/__peakrdl__.py
Normal file
25
src/peakrdl_python_regmap/__peakrdl__.py
Normal 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
|
||||
)
|
||||
96
src/peakrdl_python_regmap/design_scanner.py
Normal file
96
src/peakrdl_python_regmap/design_scanner.py
Normal 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
|
||||
30
src/peakrdl_python_regmap/design_state.py
Normal file
30
src/peakrdl_python_regmap/design_state.py
Normal 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
|
||||
#------------------------
|
||||
41
src/peakrdl_python_regmap/exporter.py
Normal file
41
src/peakrdl_python_regmap/exporter.py
Normal 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)
|
||||
52
src/peakrdl_python_regmap/generator.py
Normal file
52
src/peakrdl_python_regmap/generator.py
Normal 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
|
||||
32
src/peakrdl_python_regmap/identifier_filter.py
Normal file
32
src/peakrdl_python_regmap/identifier_filter.py
Normal 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
|
||||
0
src/peakrdl_python_regmap/py.typed
Normal file
0
src/peakrdl_python_regmap/py.typed
Normal file
2
src/peakrdl_python_regmap/templates/regmap.py
Normal file
2
src/peakrdl_python_regmap/templates/regmap.py
Normal file
@@ -0,0 +1,2 @@
|
||||
class AddrNode():
|
||||
addr: int
|
||||
37
src/peakrdl_python_regmap/utils.py
Normal file
37
src/peakrdl_python_regmap/utils.py
Normal 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()
|
||||
Reference in New Issue
Block a user