diff --git a/peakrdl/regblock/exporter.py b/peakrdl/regblock/exporter.py index 9a4f191..623af34 100644 --- a/peakrdl/regblock/exporter.py +++ b/peakrdl/regblock/exporter.py @@ -70,6 +70,7 @@ class RegblockExporter: cpuif_cls = kwargs.pop("cpuif_cls", APB3_Cpuif) module_name = kwargs.pop("module_name", self.top_node.inst_name) package_name = kwargs.pop("package_name", module_name + "_pkg") + reuse_hwif_typedefs = kwargs.pop("reuse_hwif_typedefs", True) # Pipelining options retime_read_fanin = kwargs.pop("retime_read_fanin", False) @@ -107,6 +108,7 @@ class RegblockExporter: self.hwif = Hwif( self, package_name=package_name, + reuse_typedefs=reuse_hwif_typedefs ) self.readback = Readback( diff --git a/peakrdl/regblock/hwif.py b/peakrdl/regblock/hwif.py deleted file mode 100644 index 017b68b..0000000 --- a/peakrdl/regblock/hwif.py +++ /dev/null @@ -1,299 +0,0 @@ -from typing import TYPE_CHECKING, Union, List - -from systemrdl.node import AddrmapNode, Node, SignalNode, FieldNode, AddressableNode -from systemrdl.rdltypes import PropertyReference - -from .utils import get_indexed_path - -if TYPE_CHECKING: - from .exporter import RegblockExporter - -class Hwif: - """ - Defines how the hardware input/output signals are generated: - - Field outputs - - Field inputs - - Signal inputs (except those that are promoted to the top) - """ - - def __init__(self, exp: 'RegblockExporter', package_name: str): - self.exp = exp - self.package_name = package_name - - self.has_input_struct = None - self.has_output_struct = None - self._indent_level = 0 - - @property - def top_node(self) -> AddrmapNode: - return self.exp.top_node - - - def get_package_contents(self) -> str: - """ - If this hwif requires a package, generate the string - """ - lines = [] - - self.has_input_struct = self._do_struct_addressable(lines, self.top_node, is_input=True) - self.has_output_struct = self._do_struct_addressable(lines, self.top_node, is_input=False) - - return "\n".join(lines) - - - @property - def port_declaration(self) -> str: - """ - Returns the declaration string for all I/O ports in the hwif group - """ - - # Assume get_package_declaration() is always called prior to this - assert self.has_input_struct is not None - assert self.has_output_struct is not None - - lines = [] - if self.has_input_struct: - lines.append(f"input {self.package_name}::{self._get_struct_name(self.top_node, is_input=True)} hwif_in") - if self.has_output_struct: - lines.append(f"output {self.package_name}::{self._get_struct_name(self.top_node, is_input=False)} hwif_out") - - return ",\n".join(lines) - - - #--------------------------------------------------------------------------- - # Struct generation functions - #--------------------------------------------------------------------------- - @property - def _indent(self) -> str: - return " " * self._indent_level - - def _get_node_array_suffix(self, node:AddressableNode) -> str: - if node.is_array: - return "".join([f'[{dim}]' for dim in node.array_dimensions]) - return "" - - def _get_struct_name(self, node:Node, is_input:bool = True) -> str: - base = node.get_rel_path( - self.top_node.parent, - hier_separator="__", - array_suffix="x", - empty_array_suffix="x" - ) - if is_input: - return f'{base}__in_t' - return f'{base}__out_t' - - def _do_struct_addressable(self, lines:list, node:AddressableNode, is_input:bool = True) -> bool: - - struct_children = [] - - # Generate structs for children first - for child in node.children(): - if isinstance(child, AddressableNode): - if self._do_struct_addressable(lines, child, is_input): - struct_children.append(child) - elif isinstance(child, FieldNode): - if self._do_struct_field(lines, child, is_input): - struct_children.append(child) - elif is_input and isinstance(child, SignalNode): - # No child struct needed here - # TODO: Skip if this is a top-level child - struct_children.append(child) - - # Generate this addressable node's struct - if struct_children: - lines.append("") - lines.append(f"{self._indent}// {node.get_rel_path(self.top_node.parent)}") - lines.append(f"{self._indent}typedef struct {{") - self._indent_level += 1 - for child in struct_children: - if isinstance(child, AddressableNode): - lines.append(f"{self._indent}{self._get_struct_name(child, is_input)} {child.inst_name}{self._get_node_array_suffix(child)};") - elif isinstance(child, FieldNode): - lines.append(f"{self._indent}{self._get_struct_name(child, is_input)} {child.inst_name};") - elif isinstance(child, SignalNode): - if child.width == 1: - lines.append(f"{self._indent}logic {child.inst_name};") - else: - lines.append(f"{self._indent}logic [{child.msb}:{child.lsb}] {child.inst_name};") - - self._indent_level -= 1 - lines.append(f"{self._indent}}} {self._get_struct_name(node, is_input)};") - - return bool(struct_children) - - def _do_struct_field(self, lines:list, node:FieldNode, is_input:bool = True) -> bool: - contents = [] - - if is_input: - contents = self._get_struct_input_field_contents(node) - else: - contents = self._get_struct_output_field_contents(node) - - if contents: - lines.append("") - lines.append(f"{self._indent}// {node.get_rel_path(self.top_node.parent)}") - lines.append(f"{self._indent}typedef struct {{") - self._indent_level += 1 - for member in contents: - lines.append(self._indent + member) - self._indent_level -= 1 - lines.append(f"{self._indent}}} {self._get_struct_name(node, is_input)};") - - return bool(contents) - - def _get_struct_input_field_contents(self, node:FieldNode) -> List[str]: - contents = [] - - # Provide input to field's value if it is writable by hw - if self.has_value_input(node): - if node.width == 1: - contents.append("logic value;") - else: - contents.append(f"logic [{node.width-1}:0] value;") - - # Generate implied inputs - for prop_name in ["we", "wel", "swwe", "swwel", "hwclr", "hwset"]: - # if property is boolean and true, implies a corresponding input signal on the hwif - if node.get_property(prop_name) is True: - contents.append(f"logic {prop_name};") - - # Generate any implied counter inputs - if node.is_up_counter: - if not node.get_property('incr'): - # User did not provide their own incr component reference. - # Imply an input - contents.append("logic incr;") - - width = node.get_property('incrwidth') - if width: - # Implies a corresponding incrvalue input - contents.append(f"logic [{width-1}:0] incrvalue;") - - if node.is_down_counter: - if not node.get_property('decr'): - # User did not provide their own decr component reference. - # Imply an input - contents.append("logic decr;") - - width = node.get_property('decrwidth') - if width: - # Implies a corresponding decrvalue input - contents.append(f"logic [{width-1}:0] decrvalue;") - - # TODO: - """ - signals! - any signal instances instantiated in the scope - """ - - return contents - - def _get_struct_output_field_contents(self, node:FieldNode) -> List[str]: - contents = [] - - # Expose field's value if it is readable by hw - if self.has_value_output(node): - if node.width == 1: - contents.append("logic value;") - else: - contents.append(f"logic [{node.width-1}:0] value;") - - # Generate output bit signals enabled via property - for prop_name in ["anded", "ored", "xored", "swmod", "swacc", "overflow", "underflow"]: - if node.get_property(prop_name): - contents.append(f"logic {prop_name};") - - if node.get_property('incrthreshold') is not False: # (explicitly not False. Not 0) - contents.append("logic incrthreshold;") - if node.get_property('decrthreshold') is not False: # (explicitly not False. Not 0) - contents.append("logic decrthreshold;") - - return contents - - #--------------------------------------------------------------------------- - # hwif utility functions - #--------------------------------------------------------------------------- - def has_value_input(self, obj: Union[FieldNode, SignalNode]) -> bool: - """ - Returns True if the object infers an input wire in the hwif - """ - if isinstance(obj, FieldNode): - return obj.is_hw_writable - elif isinstance(obj, SignalNode): - # Signals are implicitly always inputs - return True - else: - raise RuntimeError - - - def has_value_output(self, obj: FieldNode) -> bool: - """ - Returns True if the object infers an output wire in the hwif - """ - return obj.is_hw_readable - - - def get_input_identifier(self, obj: Union[FieldNode, SignalNode, PropertyReference]) -> str: - """ - Returns the identifier string that best represents the input object. - - if obj is: - Field: the fields hw input value port - Signal: signal input value - Prop reference: - could be an implied hwclr/hwset/swwe/swwel/we/wel input - - raises an exception if obj is invalid - """ - if isinstance(obj, FieldNode): - path = get_indexed_path(self.top_node, obj) - return "hwif_in." + path + ".value" - elif isinstance(obj, SignalNode): - # TODO: Implement this - raise NotImplementedError() - elif isinstance(obj, PropertyReference): - return self.get_implied_prop_input_identifier(obj.node, obj.name) - - raise RuntimeError("Unhandled reference to: %s", obj) - - - def get_implied_prop_input_identifier(self, field: FieldNode, prop: str) -> str: - assert prop in { - 'hwclr', 'hwset', 'swwe', 'swwel', 'we', 'wel', - 'incr', 'decr', 'incrvalue', 'decrvalue' - } - path = get_indexed_path(self.top_node, field) - return "hwif_in." + path + "." + prop - - - def get_output_identifier(self, obj: Union[FieldNode, PropertyReference]) -> str: - """ - Returns the identifier string that best represents the output object. - - if obj is: - Field: the fields hw output value port - Property ref: this is also part of the struct - - raises an exception if obj is invalid - """ - if isinstance(obj, FieldNode): - path = get_indexed_path(self.top_node, obj) - return "hwif_out." + path + ".value" - elif isinstance(obj, PropertyReference): - # TODO: this might be dead code. - # not sure when anything would call this function with a prop ref - # when dereferencer's get_value is more useful here - assert obj.node.get_property(obj.name) - return self.get_implied_prop_output_identifier(obj.node, obj.name) - - raise RuntimeError("Unhandled reference to: %s", obj) - - - def get_implied_prop_output_identifier(self, field: FieldNode, prop: str) -> str: - assert prop in { - "anded", "ored", "xored", "swmod", "swacc", - "incrthreshold", "decrthreshold", "overflow", "underflow" - } - path = get_indexed_path(self.top_node, field) - return "hwif_out." + path + "." + prop diff --git a/peakrdl/regblock/hwif/__init__.py b/peakrdl/regblock/hwif/__init__.py new file mode 100644 index 0000000..4ce5b22 --- /dev/null +++ b/peakrdl/regblock/hwif/__init__.py @@ -0,0 +1,178 @@ +from typing import TYPE_CHECKING, Union, List + +from systemrdl.node import AddrmapNode, Node, SignalNode, FieldNode, AddressableNode +from systemrdl.rdltypes import PropertyReference + +from ..utils import get_indexed_path + +from .generators import InputStructGenerator_Hier, OutputStructGenerator_Hier +from .generators import InputStructGenerator_TypeScope, OutputStructGenerator_TypeScope + +if TYPE_CHECKING: + from ..exporter import RegblockExporter + +class Hwif: + """ + Defines how the hardware input/output signals are generated: + - Field outputs + - Field inputs + - Signal inputs (except those that are promoted to the top) + """ + + def __init__(self, exp: 'RegblockExporter', package_name: str, reuse_typedefs: bool): + self.exp = exp + self.package_name = package_name + + self.has_input_struct = None + self.has_output_struct = None + self._indent_level = 0 + + if reuse_typedefs: + self._gen_in_cls = InputStructGenerator_TypeScope + self._gen_out_cls = OutputStructGenerator_TypeScope + else: + self._gen_in_cls = InputStructGenerator_Hier + self._gen_out_cls = OutputStructGenerator_Hier + + @property + def top_node(self) -> AddrmapNode: + return self.exp.top_node + + + def get_package_contents(self) -> str: + """ + If this hwif requires a package, generate the string + """ + lines = [] + + gen_in = self._gen_in_cls(self.top_node) + structs_in = gen_in.get_struct( + self.top_node, + f"{self.top_node.inst_name}__in_t" + ) + if structs_in is not None: + self.has_input_struct = True + lines.append(structs_in) + else: + self.has_input_struct = False + + gen_out = self._gen_out_cls(self.top_node) + structs_out = gen_out.get_struct( + self.top_node, + f"{self.top_node.inst_name}__out_t" + ) + if structs_out is not None: + self.has_output_struct = True + lines.append(structs_out) + else: + self.has_output_struct = False + + return "\n\n".join(lines) + + + @property + def port_declaration(self) -> str: + """ + Returns the declaration string for all I/O ports in the hwif group + """ + + # Assume get_package_declaration() is always called prior to this + assert self.has_input_struct is not None + assert self.has_output_struct is not None + + lines = [] + if self.has_input_struct: + type_name = f"{self.top_node.inst_name}__in_t" + lines.append(f"input {self.package_name}::{type_name} hwif_in") + if self.has_output_struct: + type_name = f"{self.top_node.inst_name}__out_t" + lines.append(f"output {self.package_name}::{type_name} hwif_out") + + return ",\n".join(lines) + + #--------------------------------------------------------------------------- + # hwif utility functions + #--------------------------------------------------------------------------- + def has_value_input(self, obj: Union[FieldNode, SignalNode]) -> bool: + """ + Returns True if the object infers an input wire in the hwif + """ + if isinstance(obj, FieldNode): + return obj.is_hw_writable + elif isinstance(obj, SignalNode): + # Signals are implicitly always inputs + return True + else: + raise RuntimeError + + + def has_value_output(self, obj: FieldNode) -> bool: + """ + Returns True if the object infers an output wire in the hwif + """ + return obj.is_hw_readable + + + def get_input_identifier(self, obj: Union[FieldNode, SignalNode, PropertyReference]) -> str: + """ + Returns the identifier string that best represents the input object. + + if obj is: + Field: the fields hw input value port + Signal: signal input value + Prop reference: + could be an implied hwclr/hwset/swwe/swwel/we/wel input + + raises an exception if obj is invalid + """ + if isinstance(obj, FieldNode): + path = get_indexed_path(self.top_node, obj) + return "hwif_in." + path + ".value" + elif isinstance(obj, SignalNode): + # TODO: Implement this + raise NotImplementedError() + elif isinstance(obj, PropertyReference): + return self.get_implied_prop_input_identifier(obj.node, obj.name) + + raise RuntimeError("Unhandled reference to: %s", obj) + + + def get_implied_prop_input_identifier(self, field: FieldNode, prop: str) -> str: + assert prop in { + 'hwclr', 'hwset', 'swwe', 'swwel', 'we', 'wel', + 'incr', 'decr', 'incrvalue', 'decrvalue' + } + path = get_indexed_path(self.top_node, field) + return "hwif_in." + path + "." + prop + + + def get_output_identifier(self, obj: Union[FieldNode, PropertyReference]) -> str: + """ + Returns the identifier string that best represents the output object. + + if obj is: + Field: the fields hw output value port + Property ref: this is also part of the struct + + raises an exception if obj is invalid + """ + if isinstance(obj, FieldNode): + path = get_indexed_path(self.top_node, obj) + return "hwif_out." + path + ".value" + elif isinstance(obj, PropertyReference): + # TODO: this might be dead code. + # not sure when anything would call this function with a prop ref + # when dereferencer's get_value is more useful here + assert obj.node.get_property(obj.name) + return self.get_implied_prop_output_identifier(obj.node, obj.name) + + raise RuntimeError("Unhandled reference to: %s", obj) + + + def get_implied_prop_output_identifier(self, field: FieldNode, prop: str) -> str: + assert prop in { + "anded", "ored", "xored", "swmod", "swacc", + "incrthreshold", "decrthreshold", "overflow", "underflow" + } + path = get_indexed_path(self.top_node, field) + return "hwif_out." + path + "." + prop diff --git a/peakrdl/regblock/hwif/generators.py b/peakrdl/regblock/hwif/generators.py new file mode 100644 index 0000000..05e6f0d --- /dev/null +++ b/peakrdl/regblock/hwif/generators.py @@ -0,0 +1,119 @@ +from typing import TYPE_CHECKING +from ..struct_generator import RDLFlatStructGenerator + +if TYPE_CHECKING: + from systemrdl.node import Node, SignalNode, FieldNode + +class InputStructGenerator_Hier(RDLFlatStructGenerator): + def __init__(self, top_node: 'Node'): + super().__init__() + self.top_node = top_node + + def get_typdef_name(self, node:'Node') -> str: + base = node.get_rel_path( + self.top_node.parent, + hier_separator="__", + array_suffix="x", + empty_array_suffix="x" + ) + return f'{base}__in_t' + + def enter_Signal(self, node: 'SignalNode') -> None: + self.add_member(node.inst_name, node.width) + + def enter_Field(self, node: 'FieldNode') -> None: + type_name = self.get_typdef_name(node) + self.push_struct(type_name, node.inst_name) + + # Provide input to field's value if it is writable by hw + if node.is_hw_writable: + self.add_member("value", node.width) + + # Generate implied inputs + for prop_name in ["we", "wel", "swwe", "swwel", "hwclr", "hwset"]: + # if property is boolean and true, implies a corresponding input signal on the hwif + if node.get_property(prop_name) is True: + self.add_member(prop_name) + + # Generate any implied counter inputs + if node.is_up_counter: + if not node.get_property('incr'): + # User did not provide their own incr component reference. + # Imply an input + self.add_member('incr') + + width = node.get_property('incrwidth') + if width: + # Implies a corresponding incrvalue input + self.add_member('incrvalue', width) + + if node.is_down_counter: + if not node.get_property('decr'): + # User did not provide their own decr component reference. + # Imply an input + self.add_member('decr') + + width = node.get_property('decrwidth') + if width: + # Implies a corresponding decrvalue input + self.add_member('decrvalue', width) + + def exit_Field(self, node: 'FieldNode') -> None: + self.pop_struct() + + +class OutputStructGenerator_Hier(RDLFlatStructGenerator): + def __init__(self, top_node: 'Node'): + super().__init__() + self.top_node = top_node + + def get_typdef_name(self, node:'Node') -> str: + base = node.get_rel_path( + self.top_node.parent, + hier_separator="__", + array_suffix="x", + empty_array_suffix="x" + ) + return f'{base}__out_t' + + def enter_Field(self, node: 'FieldNode') -> None: + type_name = self.get_typdef_name(node) + self.push_struct(type_name, node.inst_name) + + # Expose field's value if it is readable by hw + if node.is_hw_readable: + self.add_member("value", node.width) + + # Generate output bit signals enabled via property + for prop_name in ["anded", "ored", "xored", "swmod", "swacc", "overflow", "underflow"]: + if node.get_property(prop_name): + self.add_member(prop_name) + + if node.get_property('incrthreshold') is not False: # (explicitly not False. Not 0) + self.add_member('incrthreshold') + if node.get_property('decrthreshold') is not False: # (explicitly not False. Not 0) + self.add_member('decrthreshold') + + def exit_Field(self, node: 'FieldNode') -> None: + self.pop_struct() + +#------------------------------------------------------------------------------- +class InputStructGenerator_TypeScope(InputStructGenerator_Hier): + def get_typdef_name(self, node:'Node') -> str: + scope_path = node.inst.get_scope_path("__") + if scope_path is None: + # Unable to determine a reusable type name. Fall back to hierarchical path + # Add prefix to prevent collision when mixing namespace methods + scope_path = "xtern__" + super().get_typdef_name(node) + + return f'{scope_path}__{node.type_name}__in_t' + +class OutputStructGenerator_TypeScope(OutputStructGenerator_Hier): + def get_typdef_name(self, node:'Node') -> str: + scope_path = node.inst.get_scope_path("__") + if scope_path is None: + # Unable to determine a reusable type name. Fall back to hierarchical path + # Add prefix to prevent collision when mixing namespace methods + scope_path = "xtern__" + super().get_typdef_name(node) + + return f'{scope_path}__{node.type_name}__out_t' diff --git a/peakrdl/regblock/struct_generator.py b/peakrdl/regblock/struct_generator.py index e0d85c3..7f0ba26 100644 --- a/peakrdl/regblock/struct_generator.py +++ b/peakrdl/regblock/struct_generator.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING, Optional, List import textwrap +from collections import OrderedDict from systemrdl.walker import RDLListener, RDLWalker @@ -38,9 +39,11 @@ class _AnonymousStruct(_StructBase): class _TypedefStruct(_StructBase): - def __init__(self, type_name: str): + def __init__(self, type_name: str, inst_name: Optional[str] = None, array_dimensions: Optional[List[int]] = None): super().__init__() self.type_name = type_name + self.inst_name = inst_name + self.array_dimensions = array_dimensions def __str__(self) -> str: return ( @@ -49,6 +52,16 @@ class _TypedefStruct(_StructBase): + f"\n}} {self.type_name};" ) + @property + def instantiation(self) -> str: + if self.array_dimensions: + suffix = "[" + "][".join((str(n) for n in self.array_dimensions)) + "]" + else: + suffix = "" + + return f"{self.type_name} {self.inst_name}{suffix};" + +#------------------------------------------------------------------------------- class StructGenerator: @@ -137,3 +150,86 @@ class RDLStructGenerator(StructGenerator, RDLListener): def enter_Field(self, node: 'FieldNode') -> None: self.add_member(node.inst_name, node.width) + +#------------------------------------------------------------------------------- + +class FlatStructGenerator(StructGenerator): + + def __init__(self): + super().__init__() + self.typedefs = OrderedDict() + + def push_struct(self, type_name: str, inst_name: str, array_dimensions: Optional[List[int]] = None) -> None: + s = _TypedefStruct(type_name, inst_name, array_dimensions) + self._struct_stack.append(s) + + def pop_struct(self) -> None: + s = self._struct_stack.pop() + + if s.children: + # struct is not empty. Attach it to the parent + self.current_struct.children.append(s.instantiation) + + # Add to collection of struct definitions + if s.type_name not in self.typedefs: + self.typedefs[s.type_name] = s + + def finish(self) -> Optional[str]: + s = self._struct_stack.pop() + assert not self._struct_stack + + # no children, no struct. + if not s.children: + return None + + # Add to collection of struct definitions + if s.type_name not in self.typedefs: + self.typedefs[s.type_name] = s + + all_structs = [str(s) for s in self.typedefs.values()] + + return "\n\n".join(all_structs) + + +class RDLFlatStructGenerator(FlatStructGenerator, RDLListener): + """ + Struct generator that naively translates an RDL node tree into a flat list + of typedefs + + This can be extended to add more intelligent behavior + """ + + def get_typdef_name(self, node:'Node') -> str: + raise NotImplementedError + + def get_struct(self, node: 'Node', type_name: str) -> Optional[str]: + self.start(type_name) + + walker = RDLWalker() + walker.walk(node, self, skip_top=True) + + return self.finish() + + def enter_Addrmap(self, node: 'AddrmapNode') -> None: + type_name = self.get_typdef_name(node) + self.push_struct(type_name, node.inst_name, node.array_dimensions) + + def exit_Addrmap(self, node: 'AddrmapNode') -> None: + self.pop_struct() + + def enter_Regfile(self, node: 'RegfileNode') -> None: + type_name = self.get_typdef_name(node) + self.push_struct(type_name, node.inst_name, node.array_dimensions) + + def exit_Regfile(self, node: 'RegfileNode') -> None: + self.pop_struct() + + def enter_Reg(self, node: 'RegNode') -> None: + type_name = self.get_typdef_name(node) + self.push_struct(type_name, node.inst_name, node.array_dimensions) + + def exit_Reg(self, node: 'RegNode') -> None: + self.pop_struct() + + def enter_Field(self, node: 'FieldNode') -> None: + self.add_member(node.inst_name, node.width) diff --git a/test/lib/test_params.py b/test/lib/test_params.py index 627e546..8720d7d 100644 --- a/test/lib/test_params.py +++ b/test/lib/test_params.py @@ -15,9 +15,10 @@ def get_permutations(spec): return param_list #------------------------------------------------------------------------------- -# TODO: this wont scale well. Create groups of permutatuions. not necessary to permute everything all the time. +# TODO: this wont scale well. Create groups of permutations. not necessary to permute everything all the time. TEST_PARAMS = get_permutations({ "cpuif": all_cpuif, "retime_read_fanin": [True, False], "retime_read_response": [True, False], + "reuse_hwif_typedefs": [True, False], })