From f473dfb9e7bf132edac2dadc08bd9b4f56e568e0 Mon Sep 17 00:00:00 2001 From: Alex Mykyta Date: Fri, 16 Jul 2021 18:05:57 -0700 Subject: [PATCH] fill in more hwif utility functions for dereferencer --- doc/logbooks/000-Main-Logbook | 15 +- doc/logbooks/Validation Needed | 36 +++- doc/logbooks/template-layers/4-fields | 114 ++++++++++++ peakrdl/regblock/addr_decode.py | 133 ++++++------- peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv | 5 +- peakrdl/regblock/cpuif/base.py | 3 +- peakrdl/regblock/dereferencer.py | 29 ++- peakrdl/regblock/exporter.py | 47 +++-- peakrdl/regblock/field_logic/__init__.py | 138 ++++++-------- peakrdl/regblock/field_logic/bases.py | 95 ++++++++++ peakrdl/regblock/field_logic/field_builder.py | 176 ++++++++++++++++++ peakrdl/regblock/field_logic/sw_onread.py | 38 ++++ peakrdl/regblock/field_logic/sw_onwrite.py | 107 +++++++++++ .../field_logic/templates/field_storage.sv | 25 +++ peakrdl/regblock/forloop_generator.py | 96 ++++++++++ peakrdl/regblock/hwif.py | 98 ++++++---- peakrdl/regblock/module_tmpl.sv | 19 +- peakrdl/regblock/readback_mux.py | 16 +- peakrdl/regblock/signals.py | 5 +- peakrdl/regblock/struct_generator.py | 139 ++++++++++++++ peakrdl/regblock/utils.py | 30 +++ peakrdl/regblock/utils_tmpl.sv | 16 -- setup.py | 6 +- x.rdl | 4 +- 24 files changed, 1105 insertions(+), 285 deletions(-) create mode 100644 peakrdl/regblock/field_logic/bases.py create mode 100644 peakrdl/regblock/field_logic/field_builder.py create mode 100644 peakrdl/regblock/field_logic/sw_onread.py create mode 100644 peakrdl/regblock/field_logic/sw_onwrite.py create mode 100644 peakrdl/regblock/field_logic/templates/field_storage.sv create mode 100644 peakrdl/regblock/forloop_generator.py create mode 100644 peakrdl/regblock/struct_generator.py create mode 100644 peakrdl/regblock/utils.py delete mode 100644 peakrdl/regblock/utils_tmpl.sv diff --git a/doc/logbooks/000-Main-Logbook b/doc/logbooks/000-Main-Logbook index c87b2b8..b789b10 100644 --- a/doc/logbooks/000-Main-Logbook +++ b/doc/logbooks/000-Main-Logbook @@ -32,14 +32,17 @@ Basically, i'd define a ton of helper functions that return the signal identifie Dev Todo list ================================================================================ -- Lots to do in HWIF layer - - reorg base.py+struct.py. - - I have no desire to do flattened I/O. No need to split off a base class +- tidy up stuff + - merge FieldBuilder and FieldLogic classes. It makes no sense for these to be separate + - propagate the exporter class EVERYWHERE + shorten it to simply "self.exp" +- Build out a few more NextStateConditional implementations + - hw we +- readback mux +- HWIF layer - User Signals Generate these in the io struct? I forget what I decided - - get_input/output_identifier() functions -- Field logic - Basically need all aspects of the dereferencer to be done first +- dereferencer has some remaining todos that depend on field logic diff --git a/doc/logbooks/Validation Needed b/doc/logbooks/Validation Needed index ce9310e..076771e 100644 --- a/doc/logbooks/Validation Needed +++ b/doc/logbooks/Validation Needed @@ -47,7 +47,11 @@ X multiple cpuif_reset in the same hierarchy there can only be one signal declared with cpuif_reset in a given hierarchy -! incrwidth/incrvalue & decrvalue/decrwidth +X Mutually-exclusive property checking + --> Yes. compiler now auto-clears mutex partners on assign, so it is + implicitly handled + +X incrwidth/incrvalue & decrvalue/decrwidth these pairs are mutually exclusive. Make sure they are not both set after elaboration Compiler checks for mutex within the same scope, but @@ -55,18 +59,34 @@ X multiple cpuif_reset in the same hierarchy ... or, make these properties clear each-other on assignment -? If a node ispresent=true, and any of it's properties are a reference, - then thse references' its ispresent shall also be true - Pretty sure this is an explicit clause in the spec. - Make sure it is actually implemented - - -? Illegal property references: +X Illegal property references: - reference any of the counter property references to something that isn't a counter + decrsaturate / incrsaturate / saturate + overflow / underflow - reference hwclr or hwset, but the owner node has them set to False means that the actual inferred signal doesnt exist! - reference swwe/swwel or we/wel, but the owner node has them, AND their complement set to False means that the actual inferred signal doesnt exist! + - only valid to reference if owner has this prop set + enable/mask + haltenable/haltmask + hwenable + hwmask + decr/incr, decr../incrthreshold/..value + - others references that may not always make sense: + intr/halt - target must contain interrupt/halt fields + next + is this ever illegal? + +X If a node ispresent=true, and any of it's properties are a reference, + then those references' ispresent shall also be true + This is an explicit clause in the spec: 5.3.1-i + + + + + + ================================================================================ diff --git a/doc/logbooks/template-layers/4-fields b/doc/logbooks/template-layers/4-fields index 4480ac8..6fb4a5c 100644 --- a/doc/logbooks/template-layers/4-fields +++ b/doc/logbooks/template-layers/4-fields @@ -1,12 +1,24 @@ -------------------------------------------------------------------------------- Field storage / next value layer -------------------------------------------------------------------------------- + Where all the magic happens!! Any field that implements storage is defined here. Bigass struct that only contains storage elements Each field consists of: + - Entries in the storage element struct + - if implements storage - field value + - user extensible values? + - Entries in the combo struct + - if implements storage: + - Field's "next" value + - load-enable strobe + - If counter + various event strobes (overflow/overflow). + These are convenient to generate alongside the field next state logic + - user extensible values? - an always_comb block: - generates the "next value" combinational signal - May generate other intermediate strobes? @@ -47,3 +59,105 @@ Implementation Makes sense to use a listener class Be sure to skip alias registers + +-------------------------------------------------------------------------------- + +NextStateConditional Class + Decribes a single conditional action that determines the next state of a field + Provides information to generate the following content: + if() begin + + end + + - is_match(self, field: FieldNode) -> bool: + Returns True if this conditional is relevant to the field. If so, + it instructs the FieldBuider that code for this conditional shall be emitted + TODO: better name than "is_match"? More like "is this relevant" + + - get_conditional(self, field: FieldNode) -> str: + Returns the rendered conditional text + + - get_assignments(self, field: FieldNode) -> List[str]: + Returns a list of rendered assignment strings + This will basically always be two: + .next = + .load_next = '1; + + - get_extra_combo_signals(self, field: FieldNode) -> List[TBD]: + Some conditionals will need to set some extra signals (eg. counter underflow/overflow strobes) + Compiler needs to know to: + - declare these inthe combo struct + - initialize them in the beginning of always_comb + + Return something that denotes the following information: (namedtuple?) + - signal name: str + - width: int + - default value assignment: str + + Multiple NextStateConditional can declare the same extra combo signal + as long as their definitions agree + --> Assert this + + +FieldBuilder Class + Describes how to build fields + + Contains NextStateConditional definitions + Nested inside the class namespace, define all the NextStateConditional classes + that apply + User can override definitions or add own to extend behavior + + NextStateConditional objects are stored in a dictionary as follows: + _conditionals { + assignment_precedence: [ + conditional_option_3, + conditional_option_2, + conditional_option_1, + ] + } + + add_conditional(self, conditional, assignment_precedence): + Inserts the NextStateConditional into the given assignment precedence bin + The last one added to a precedence bin is first in that bin's search order + + init_conditionals(self) -> None: + Called from __init__. + loads all possible conditionals into self.conditionals list + This function is to provide a hook for the user to add their own. + + Do not do fancy class intospection. Load them explicitly by name like so: + self.add_conditional(MyNextState(), AssignmentPrecedence.SW_ACCESS) + + If user wants to extend this class, they can pile onto the bins of conditionals freely! + +-------------------------------------------------------------------------------- +Misc +-------------------------------------------------------------------------------- +What about complex behaviors like a read-clear counter? + if({{software read}}) + next = 0 + elif({{increment}}) + next = prev + 1 + + --> Implement this by stacking multiple NextStateConditional in the same assignment precedence. + In this case, there would be a special action on software read that would be specific to read-clear counters + this would get inserted ahead of the search order. + + +Precedence & Search order + There are two layers of priority I need to keep track of: + - Assignment Precedence + RTL precedence of the assignment conditional + - Search order (sp?) + Within an assignment precedence, order in which the NextStateConditional classes are + searched for a match + + For assignment precedence, it makes sense to use an integer enumeration for this + since there really aren't too many precedence levels that apply here. + Space out the integer enumerations so that user can reliably insert their own actions, ie: + my_precedence = AssignmentPrecedence.SW_ACCESS + 1 + + For search order, provide a user API to load a NextStateConditional into + a precedence 'bin'. Pushing into a bin always inserts into the front of the search order + This makes sense since user overrides will always want to be highest priority - and + rule themselves out before falling back to builtin behavior diff --git a/peakrdl/regblock/addr_decode.py b/peakrdl/regblock/addr_decode.py index d7993d8..777f3bc 100644 --- a/peakrdl/regblock/addr_decode.py +++ b/peakrdl/regblock/addr_decode.py @@ -1,31 +1,34 @@ import re from typing import TYPE_CHECKING, List, Union -from systemrdl.node import Node, AddressableNode, RegNode, FieldNode +from systemrdl.node import AddrmapNode, AddressableNode, RegNode, FieldNode +from .utils import get_indexed_path +from .struct_generator import RDLStructGenerator +from .forloop_generator import RDLForLoopGenerator if TYPE_CHECKING: from .exporter import RegblockExporter class AddressDecode: - def __init__(self, exporter:'RegblockExporter', top_node:AddressableNode): + def __init__(self, exporter:'RegblockExporter'): self.exporter = exporter - self.top_node = top_node - self._indent_level = 0 - - # List of address strides for each dimension - self._array_stride_stack = [] + @property + def top_node(self) -> AddrmapNode: + return self.exporter.top_node def get_strobe_struct(self) -> str: - lines = [] - self._do_struct(lines, self.top_node, is_top=True) - return "\n".join(lines) + struct_gen = DecodeStructGenerator() + s = struct_gen.get_struct(self.top_node, "decoded_reg_strb_t") + assert s is not None # guaranteed to have at least one reg + return s def get_implementation(self) -> str: - lines = [] - self._do_address_decode_node(lines, self.top_node) - return "\n".join(lines) + gen = DecodeLogicGenerator(self) + s = gen.get_content(self.top_node) + assert s is not None + return s def get_access_strobe(self, node: Union[RegNode, FieldNode]) -> str: """ @@ -34,56 +37,35 @@ class AddressDecode: if isinstance(node, FieldNode): node = node.parent - path = node.get_rel_path(self.top_node, empty_array_suffix="[!]") - - # replace unknown indexes with incrementing iterators i0, i1, ... - class repl: - def __init__(self): - self.i = 0 - def __call__(self, match): - s = f'i{self.i}' - self.i += 1 - return s - path = re.sub(r'!', repl(), path) - + path = get_indexed_path(self.top_node, node) return "decoded_reg_strb." + path - #--------------------------------------------------------------------------- - # 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 "" +class DecodeStructGenerator(RDLStructGenerator): - def _do_struct(self, lines:List[str], node:AddressableNode, is_top:bool = False) -> None: - if is_top: - lines.append(f"{self._indent}typedef struct {{") - else: - lines.append(f"{self._indent}struct {{") + def enter_Reg(self, node: 'RegNode') -> None: + self.add_member(node.inst_name, array_dimensions=node.array_dimensions) - self._indent_level += 1 - for child in node.children(): - if isinstance(child, RegNode): - lines.append(f"{self._indent}logic {child.inst_name}{self._get_node_array_suffix(child)};") - elif isinstance(child, AddressableNode): - self._do_struct(lines, child) - self._indent_level -= 1 + # Stub out + def exit_Reg(self, node: 'RegNode') -> None: + pass + def enter_Field(self, node: 'FieldNode') -> None: + pass - if is_top: - lines.append(f"{self._indent}}} decoded_reg_strb_t;") - else: - lines.append(f"{self._indent}}} {node.inst_name}{self._get_node_array_suffix(node)};") - #--------------------------------------------------------------------------- - # Access strobe generation functions - #--------------------------------------------------------------------------- +class DecodeLogicGenerator(RDLForLoopGenerator): + + def __init__(self, addr_decode: AddressDecode) -> None: + self.addr_decode = addr_decode + super().__init__() + + # List of address strides for each dimension + self._array_stride_stack = [] + + + def enter_AddressableComponent(self, node: 'AddressableNode') -> None: + super().enter_AddressableComponent(node) - def _push_array_dims(self, lines:List[str], node:AddressableNode): if not node.is_array: return @@ -94,35 +76,26 @@ class AddressDecode: strides.append(current_stride) current_stride *= dim strides.reverse() + self._array_stride_stack.extend(strides) - for dim, stride in zip(node.array_dimensions, strides): - iterator = "i%d" % len(self._array_stride_stack) - self._array_stride_stack.append(stride) - lines.append(f"{self._indent}for(int {iterator}=0; {iterator}<{dim}; {iterator}++) begin") - self._indent_level += 1 - def _pop_array_dims(self, lines:List[str], node:AddressableNode): + def _get_address_str(self, node:AddressableNode) -> str: + a = "'h%x" % (node.raw_absolute_address - self.addr_decode.top_node.raw_absolute_address) + for i, stride in enumerate(self._array_stride_stack): + a += f" + i{i}*'h{stride:x}" + return a + + + def enter_Reg(self, node: RegNode) -> None: + s = f"{self.addr_decode.get_access_strobe(node)} = cpuif_req & (cpuif_addr == {self._get_address_str(node)});" + self.add_content(s) + + + def exit_AddressableComponent(self, node: 'AddressableNode') -> None: + super().exit_AddressableComponent(node) + if not node.is_array: return for _ in node.array_dimensions: self._array_stride_stack.pop() - self._indent_level -= 1 - lines.append(f"{self._indent}end") - - def _get_address_str(self, node:AddressableNode) -> str: - a = "'h%x" % (node.raw_absolute_address - self.top_node.raw_absolute_address) - for i, stride in enumerate(self._array_stride_stack): - a += f" + i{i}*'h{stride:x}" - return a - - def _do_address_decode_node(self, lines:List[str], node:AddressableNode) -> None: - for child in node.children(): - if isinstance(child, RegNode): - self._push_array_dims(lines, child) - lines.append(f"{self._indent}{self.get_access_strobe(child)} = cpuif_req & (cpuif_addr == {self._get_address_str(child)});") - self._pop_array_dims(lines, child) - elif isinstance(child, AddressableNode): - self._push_array_dims(lines, child) - self._do_address_decode_node(lines, child) - self._pop_array_dims(lines, child) diff --git a/peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv b/peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv index edc4137..3c073f0 100644 --- a/peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv +++ b/peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv @@ -1,10 +1,9 @@ {% extends "cpuif/base_tmpl.sv" %} -{%- import "utils_tmpl.sv" as utils with context %} {% block body %} // Request logic is_active; -{%- call utils.AlwaysFF(cpuif_reset) %} +always_ff {{get_always_ff_event(cpuif_reset)}} begin if({{cpuif_reset.activehigh_identifier}}) begin is_active <= '0; cpuif_req <= '0; @@ -31,7 +30,7 @@ logic is_active; end end end -{%- endcall %} +end // Response assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack; diff --git a/peakrdl/regblock/cpuif/base.py b/peakrdl/regblock/cpuif/base.py index 594c02c..7100f09 100644 --- a/peakrdl/regblock/cpuif/base.py +++ b/peakrdl/regblock/cpuif/base.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING -import jinja2 +from ..utils import get_always_ff_event if TYPE_CHECKING: from ..exporter import RegblockExporter @@ -25,6 +25,7 @@ class CpuifBase: "cpuif_reset": self.cpuif_reset, "data_width": self.data_width, "addr_width": self.addr_width, + "get_always_ff_event": get_always_ff_event, } template = self.exporter.jj_env.get_template(self.template_path) diff --git a/peakrdl/regblock/dereferencer.py b/peakrdl/regblock/dereferencer.py index b7a682d..a9035f9 100644 --- a/peakrdl/regblock/dereferencer.py +++ b/peakrdl/regblock/dereferencer.py @@ -1,5 +1,5 @@ from typing import TYPE_CHECKING, Union -from systemrdl.node import Node, FieldNode, SignalNode, RegNode +from systemrdl.node import AddrmapNode, FieldNode, SignalNode, RegNode from systemrdl.rdltypes import PropertyReference if TYPE_CHECKING: @@ -13,12 +13,24 @@ class Dereferencer: This class provides an interface to convert conceptual SystemRDL references into Verilog identifiers """ - def __init__(self, exporter:'RegblockExporter', top_node:Node, hwif:'Hwif', address_decode: 'AddressDecode', field_logic: 'FieldLogic'): + def __init__(self, exporter:'RegblockExporter'): self.exporter = exporter - self.hwif = hwif - self.address_decode = address_decode - self.field_logic = field_logic - self.top_node = top_node + + @property + def hwif(self) -> 'Hwif': + return self.exporter.hwif + + @property + def address_decode(self) -> 'AddressDecode': + return self.exporter.address_decode + + @property + def field_logic(self) -> 'FieldLogic': + return self.exporter.field_logic + + @property + def top_node(self) -> AddrmapNode: + return self.exporter.top_node def get_value(self, obj: Union[int, FieldNode, SignalNode, PropertyReference]) -> str: """ @@ -85,14 +97,14 @@ class Dereferencer: prop_value = obj.node.get_property(obj.name) if prop_value is None: # unset by the user, points to the implied internal signal - raise NotImplementedError # TODO: Implement this + return self.field_logic.get_counter_control_identifier(obj) else: return self.get_value(prop_value) elif obj.name == "next": prop_value = obj.node.get_property(obj.name) if prop_value is None: # unset by the user, points to the implied internal signal - raise NotImplementedError # TODO: Implement this + return self.field_logic.get_field_next_identifier(obj.node) else: return self.get_value(prop_value) @@ -127,7 +139,6 @@ class Dereferencer: if prop_value is True: # Points to inferred hwif input return f"!({self.hwif.get_input_identifier(obj)})" - raise NotImplementedError # TODO: Implement this elif prop_value is False: # This should never happen, as this is checked by the compiler's validator raise RuntimeError diff --git a/peakrdl/regblock/exporter.py b/peakrdl/regblock/exporter.py index 7bb73ba..20703e6 100644 --- a/peakrdl/regblock/exporter.py +++ b/peakrdl/regblock/exporter.py @@ -1,17 +1,18 @@ import os -from typing import TYPE_CHECKING +from typing import Union import jinja2 as jj -from systemrdl.node import Node, RootNode +from systemrdl.node import AddrmapNode, RootNode from .addr_decode import AddressDecode from .field_logic import FieldLogic from .dereferencer import Dereferencer from .readback_mux import ReadbackMux -from .signals import InferredSignal +from .signals import InferredSignal, SignalBase from .cpuif.apb4 import APB4_Cpuif from .hwif import Hwif +from .utils import get_always_ff_event class RegblockExporter: def __init__(self, **kwargs): @@ -21,6 +22,16 @@ class RegblockExporter: if kwargs: raise TypeError("got an unexpected keyword argument '%s'" % list(kwargs.keys())[0]) + + self.top_node = None # type: AddrmapNode + self.hwif = None # type: Hwif + self.address_decode = AddressDecode(self) + self.field_logic = FieldLogic(self) + self.readback_mux = ReadbackMux(self) + self.dereferencer = Dereferencer(self) + self.default_resetsignal = InferredSignal("rst") + + if user_template_dir: loader = jj.ChoiceLoader([ jj.FileSystemLoader(user_template_dir), @@ -44,14 +55,17 @@ class RegblockExporter: ) - def export(self, node:Node, output_path:str, **kwargs): + def export(self, node: Union[RootNode, AddrmapNode], output_path:str, **kwargs): # If it is the root node, skip to top addrmap if isinstance(node, RootNode): - node = node.top + self.top_node = node.top + else: + self.top_node = node + cpuif_cls = kwargs.pop("cpuif_cls", APB4_Cpuif) hwif_cls = kwargs.pop("hwif_cls", Hwif) - module_name = kwargs.pop("module_name", node.inst_name) + module_name = kwargs.pop("module_name", self.top_node.inst_name) package_name = kwargs.pop("package_name", module_name + "_pkg") # Check for stray kwargs @@ -63,8 +77,8 @@ class RegblockExporter: # TODO: Scan design... # TODO: derive this from somewhere - cpuif_reset = InferredSignal("rst") - reset_signals = [cpuif_reset] + cpuif_reset = self.default_resetsignal + reset_signals = set([cpuif_reset, self.default_resetsignal]) cpuif = cpuif_cls( self, @@ -73,17 +87,11 @@ class RegblockExporter: addr_width=32 # TODO: ) - hwif = hwif_cls( + self.hwif = hwif_cls( self, - top_node=node, package_name=package_name, ) - address_decode = AddressDecode(self, node) - field_logic = FieldLogic(self, node) - readback_mux = ReadbackMux(self, node) - dereferencer = Dereferencer(self, node, hwif, address_decode, field_logic) - # Build Jinja template context context = { "module_name": module_name, @@ -94,10 +102,11 @@ class RegblockExporter: "user_signals": [], # TODO: "interrupts": [], # TODO: "cpuif": cpuif, - "hwif": hwif, - "address_decode": address_decode, - "field_logic": field_logic, - "readback_mux": readback_mux, + "hwif": self.hwif, + "address_decode": self.address_decode, + "field_logic": self.field_logic, + "readback_mux": self.readback_mux, + "get_always_ff_event": get_always_ff_event, } # Write out design diff --git a/peakrdl/regblock/field_logic/__init__.py b/peakrdl/regblock/field_logic/__init__.py index 2f9bc3a..3bc9450 100644 --- a/peakrdl/regblock/field_logic/__init__.py +++ b/peakrdl/regblock/field_logic/__init__.py @@ -1,105 +1,77 @@ -import re -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING + +from systemrdl.node import AddrmapNode, FieldNode +from systemrdl.rdltypes import PropertyReference + +from ..utils import get_indexed_path +from .field_builder import FieldBuilder, FieldStorageStructGenerator +from .field_builder import CombinationalStructGenerator, FieldLogicGenerator -from systemrdl.node import Node, AddressableNode, RegNode, FieldNode if TYPE_CHECKING: from ..exporter import RegblockExporter class FieldLogic: - def __init__(self, exporter:'RegblockExporter', top_node:Node): + def __init__(self, exporter:'RegblockExporter'): self.exporter = exporter - self.top_node = top_node + self.field_builder = FieldBuilder(exporter) - self._indent_level = 0 + @property + def top_node(self) -> AddrmapNode: + return self.exporter.top_node def get_storage_struct(self) -> str: - lines = [] - self._do_struct(lines, self.top_node, is_top=True) + struct_gen = FieldStorageStructGenerator() + s = struct_gen.get_struct(self.top_node, "field_storage_t") # Only declare the storage struct if it exists - if lines: - lines.append(f"{self._indent}field_storage_t field_storage;") - return "\n".join(lines) + if s is None: + return "" + + return s + "\nfield_storage_t field_storage;" + + def get_combo_struct(self) -> str: + struct_gen = CombinationalStructGenerator(self.field_builder) + s = struct_gen.get_struct(self.top_node, "field_combo_t") + + # Only declare the storage struct if it exists + if s is None: + return "" + + return s + "\nfield_combo_t field_combo;" def get_implementation(self) -> str: - return "TODO:" + gen = FieldLogicGenerator(self.field_builder) + s = gen.get_content(self.top_node) + if s is None: + return "" + return s #--------------------------------------------------------------------------- # Field utility functions #--------------------------------------------------------------------------- - def get_storage_identifier(self, node: FieldNode): + def get_storage_identifier(self, node: FieldNode) -> str: + """ + Returns the Verilog string that represents the storage register element + for the referenced field + """ assert node.implements_storage - - path = node.get_rel_path(self.top_node, empty_array_suffix="[!]") - - # replace unknown indexes with incrementing iterators i0, i1, ... - class repl: - def __init__(self): - self.i = 0 - def __call__(self, match): - s = f'i{self.i}' - self.i += 1 - return s - path = re.sub(r'!', repl(), path) - + path = get_indexed_path(self.top_node, node) return "field_storage." + path + def get_field_next_identifier(self, node: FieldNode) -> str: + """ + Returns a Verilog string that represents the field's next-state. + This is specifically for use in Field->next property references. + """ + # TODO: Implement this + raise NotImplementedError - #--------------------------------------------------------------------------- - # 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 _do_struct(self, lines:List[str], node:AddressableNode, is_top:bool = False) -> bool: - # Collect struct members first - contents = [] - self._indent_level += 1 - for child in node.children(): - if isinstance(child, RegNode): - self._do_reg_struct(contents, child) - elif isinstance(child, AddressableNode): - self._do_struct(contents, child) - self._indent_level -= 1 - - # If struct is not empty, emit a struct! - if contents: - if is_top: - lines.append(f"{self._indent}typedef struct {{") - else: - lines.append(f"{self._indent}struct {{") - - lines.extend(contents) - - if is_top: - lines.append(f"{self._indent}}} field_storage_t;") - else: - lines.append(f"{self._indent}}} {node.inst_name}{self._get_node_array_suffix(node)};") - - - def _do_reg_struct(self, lines:List[str], node:RegNode) -> None: - - fields = [] - for field in node.fields(): - if field.implements_storage: - fields.append(field) - - if not fields: - return - - lines.append(f"{self._indent}struct {{") - self._indent_level += 1 - for field in fields: - if field.width == 1: - lines.append(f"{self._indent}logic {field.inst_name};") - else: - lines.append(f"{self._indent}logic [{field.width-1}:0] {field.inst_name};") - self._indent_level -= 1 - lines.append(f"{self._indent}}} {node.inst_name}{self._get_node_array_suffix(node)};") + def get_counter_control_identifier(self, prop_ref: PropertyReference) -> str: + """ + Return the Veriog string that represents the field's inferred incr/decr strobe signal. + prop_ref will be either an incr or decr property reference, and it is already known that + the incr/decr properties are not explicitly set by the user and are therefore inferred. + """ + # TODO: Implement this + raise NotImplementedError diff --git a/peakrdl/regblock/field_logic/bases.py b/peakrdl/regblock/field_logic/bases.py new file mode 100644 index 0000000..7da9f46 --- /dev/null +++ b/peakrdl/regblock/field_logic/bases.py @@ -0,0 +1,95 @@ +from typing import TYPE_CHECKING, List +import enum + +from ..utils import get_indexed_path + +if TYPE_CHECKING: + from systemrdl.node import FieldNode + + from ..exporter import RegblockExporter + +class AssignmentPrecedence(enum.IntEnum): + """ + Enumeration of standard assignment precedence groups. + Each value represents the precedence of a single conditional assignment + category that determines a field's next state. + + Higher value denotes higher precedence + + Important: If inserting custom intermediate assignment rules, do not rely on the absolute + value of the enumeration. Insert your rules relative to an existing precedence: + FieldBuilder.add_hw_conditional(MyConditional, HW_WE + 1) + """ + + # Software access assignment groups + SW_ONREAD = 5000 + SW_ONWRITE = 4000 + + # Hardware access assignment groups + HW_WE = 3000 + HWSET = 2000 + HWCLR = 1000 + COUNTER_INCR_DECR = 0 + + + + +class SVLogic: + """ + Represents a SystemVerilog logic signal + """ + def __init__(self, name: str, width: int, default_assignment: str) -> None: + self.name = name + self.width = width + self.default_assignment = default_assignment + + def __eq__(self, o: object) -> bool: + if not isinstance(o, SVLogic): + return False + + return ( + o.name == self.name + and o.width == self.width + and o.default_assignment == self.default_assignment + ) + + +class NextStateConditional: + """ + Decribes a single conditional action that determines the next state of a field + Provides information to generate the following content: + if() begin + + end + """ + def __init__(self, exporter:'RegblockExporter'): + self.exporter = exporter + + def is_match(self, field: 'FieldNode') -> bool: + """ + Returns True if this conditional is relevant to the field. If so, + it instructs the FieldBuider that Verilog for this conditional shall + be emitted + """ + raise NotImplementedError + + def get_field_path(self, field:'FieldNode') -> str: + return get_indexed_path(self.exporter.top_node, field) + + def get_conditional(self, field: 'FieldNode') -> str: + """ + Returns the rendered conditional text + """ + raise NotImplementedError + + + def get_assignments(self, field: 'FieldNode') -> List[str]: + """ + Returns a list of rendered assignment strings + This will basically always be two: + .next = + .load_next = '1; + """ + + def get_extra_combo_signals(self, field: 'FieldNode') -> List[SVLogic]: + return [] diff --git a/peakrdl/regblock/field_logic/field_builder.py b/peakrdl/regblock/field_logic/field_builder.py new file mode 100644 index 0000000..a919e6f --- /dev/null +++ b/peakrdl/regblock/field_logic/field_builder.py @@ -0,0 +1,176 @@ +from typing import TYPE_CHECKING +from collections import OrderedDict + +from systemrdl.rdltypes import PrecedenceType + +from .bases import AssignmentPrecedence, NextStateConditional +from . import sw_onread +from . import sw_onwrite + +from ..struct_generator import RDLStructGenerator +from ..forloop_generator import RDLForLoopGenerator +from ..utils import get_indexed_path, get_always_ff_event +from ..signals import RDLSignal + + +if TYPE_CHECKING: + from typing import Dict, List + + from systemrdl.node import FieldNode, AddrmapNode + + from ..exporter import RegblockExporter + + +class FieldBuilder: + + def __init__(self, exporter:'RegblockExporter'): + self.exporter = exporter + + self._hw_conditionals = {} # type: Dict[int, List[NextStateConditional]] + self._sw_conditionals = {} # type: Dict[int, List[NextStateConditional]] + + self.init_conditionals() + + @property + def top_node(self) -> 'AddrmapNode': + return self.exporter.top_node + + def add_hw_conditional(self, conditional: NextStateConditional, precedence: AssignmentPrecedence) -> None: + # TODO: Add docstring! + if precedence not in self._hw_conditionals: + self._hw_conditionals[precedence] = [] + self._hw_conditionals[precedence].append(conditional) + + + def add_sw_conditional(self, conditional: NextStateConditional, precedence: AssignmentPrecedence) -> None: + # TODO: Add docstring! + if precedence not in self._sw_conditionals: + self._sw_conditionals[precedence] = [] + self._sw_conditionals[precedence].append(conditional) + + + def init_conditionals(self) -> None: + # TODO: Add docstring! + + # TODO: Add all the other things + self.add_sw_conditional(sw_onread.ClearOnRead(self.exporter), AssignmentPrecedence.SW_ONREAD) + self.add_sw_conditional(sw_onread.SetOnRead(self.exporter), AssignmentPrecedence.SW_ONREAD) + + self.add_hw_conditional(sw_onwrite.WriteOneSet(self.exporter), AssignmentPrecedence.SW_ONWRITE) + self.add_hw_conditional(sw_onwrite.WriteOneClear(self.exporter), AssignmentPrecedence.SW_ONWRITE) + self.add_hw_conditional(sw_onwrite.WriteOneToggle(self.exporter), AssignmentPrecedence.SW_ONWRITE) + self.add_hw_conditional(sw_onwrite.WriteZeroSet(self.exporter), AssignmentPrecedence.SW_ONWRITE) + self.add_hw_conditional(sw_onwrite.WriteZeroClear(self.exporter), AssignmentPrecedence.SW_ONWRITE) + self.add_hw_conditional(sw_onwrite.WriteZeroToggle(self.exporter), AssignmentPrecedence.SW_ONWRITE) + self.add_hw_conditional(sw_onwrite.WriteClear(self.exporter), AssignmentPrecedence.SW_ONWRITE) + self.add_hw_conditional(sw_onwrite.WriteSet(self.exporter), AssignmentPrecedence.SW_ONWRITE) + self.add_hw_conditional(sw_onwrite.Write(self.exporter), AssignmentPrecedence.SW_ONWRITE) + + + + def _get_X_conditionals(self, conditionals: 'Dict[int, List[NextStateConditional]]', field: 'FieldNode') -> 'List[NextStateConditional]': + result = [] + precedences = sorted(conditionals.keys(), reverse=True) + for precedence in precedences: + for conditional in conditionals[precedence]: + if conditional.is_match(field): + result.append(conditional) + return result + + + def get_conditionals(self, field: 'FieldNode') -> 'List[NextStateConditional]': + # TODO: Add docstring! - list of NextStateConditional. Highest precedence comes first + sw_precedence = (field.get_property('precedence') == PrecedenceType.sw) + result = [] + + if sw_precedence: + result.extend(self._get_X_conditionals(self._sw_conditionals, field)) + + result.extend(self._get_X_conditionals(self._hw_conditionals, field)) + + if not sw_precedence: + result.extend(self._get_X_conditionals(self._sw_conditionals, field)) + + return result + + +class CombinationalStructGenerator(RDLStructGenerator): + + def __init__(self, field_builder: FieldBuilder): + super().__init__() + self.field_builder = field_builder + + + def enter_Field(self, node: 'FieldNode') -> None: + # If a field doesn't implement storage, it is not relevant here + if not node.implements_storage: + return + + # collect any extra combo signals that this field requires + extra_combo_signals = OrderedDict() + for conditional in self.field_builder.get_conditionals(node): + for signal in conditional.get_extra_combo_signals(node): + if signal.name in extra_combo_signals: + # Assert that subsequent declarations of the same signal + # are identical + assert signal == extra_combo_signals[signal.name] + else: + extra_combo_signals[signal.name] = signal + + self.push_struct(node.inst_name) + self.add_member("next", node.width) + self.add_member("load_next") + for signal in extra_combo_signals.values(): + self.add_member(signal.name, signal.width) + self.pop_struct() + + +class FieldStorageStructGenerator(RDLStructGenerator): + + def enter_Field(self, node: 'FieldNode') -> None: + if node.implements_storage: + self.add_member(node.inst_name, node.width) + + +class FieldLogicGenerator(RDLForLoopGenerator): + i_type = "genvar" + def __init__(self, field_builder: FieldBuilder): + super().__init__() + self.field_builder = field_builder + self.template = self.field_builder.exporter.jj_env.get_template( + "field_logic/templates/field_storage.sv" + ) + + + def enter_Field(self, node: 'FieldNode') -> None: + # If a field doesn't implement storage, it is not relevant here + if not node.implements_storage: + return + + conditionals = self.field_builder.get_conditionals(node) + extra_combo_signals = OrderedDict() + for conditional in conditionals: + for signal in conditional.get_extra_combo_signals(node): + extra_combo_signals[signal.name] = signal + + reset_value = node.get_property("reset") or 0 + + sig = node.get_property("resetsignal") + if sig is not None: + resetsignal = RDLSignal(sig) + else: + resetsignal = self.field_builder.exporter.default_resetsignal + + context = { + 'node': node, + 'reset': self.field_builder.exporter.dereferencer.get_value(reset_value), + 'field_path': get_indexed_path(self.field_builder.top_node, node), + 'extra_combo_signals': extra_combo_signals, + 'conditionals': conditionals, + 'resetsignal': resetsignal, + 'get_always_ff_event': get_always_ff_event, + 'has_value_output': self.field_builder.exporter.hwif.has_value_output, + 'get_output_identifier': self.field_builder.exporter.hwif.get_output_identifier, + + } + self.add_content(self.template.render(context)) diff --git a/peakrdl/regblock/field_logic/sw_onread.py b/peakrdl/regblock/field_logic/sw_onread.py new file mode 100644 index 0000000..f4309c5 --- /dev/null +++ b/peakrdl/regblock/field_logic/sw_onread.py @@ -0,0 +1,38 @@ +from typing import TYPE_CHECKING, List + +from systemrdl.rdltypes import OnReadType + +from .bases import NextStateConditional + +if TYPE_CHECKING: + from systemrdl.node import FieldNode + +class _OnRead(NextStateConditional): + onreadtype = None + def is_match(self, field: 'FieldNode') -> bool: + return field.get_property("onread") == self.onreadtype + + def get_conditional(self, field: 'FieldNode') -> str: + strb = self.exporter.dereferencer.get_access_strobe(field) + return f"decoded_req && !decoded_req_is_wr && {strb}" + +class ClearOnRead(_OnRead): + onreadtype = OnReadType.rclr + + def get_assignments(self, field: 'FieldNode') -> List[str]: + field_path = self.get_field_path(field) + return [ + f"field_combo.{field_path}.next = '0;", + f"field_combo.{field_path}.load_next = '1;", + ] + + +class SetOnRead(_OnRead): + onreadtype = OnReadType.rset + + def get_assignments(self, field: 'FieldNode') -> List[str]: + field_path = self.get_field_path(field) + return [ + f"field_combo.{field_path}.next = '1;", + f"field_combo.{field_path}.load_next = '1;", + ] diff --git a/peakrdl/regblock/field_logic/sw_onwrite.py b/peakrdl/regblock/field_logic/sw_onwrite.py new file mode 100644 index 0000000..182b067 --- /dev/null +++ b/peakrdl/regblock/field_logic/sw_onwrite.py @@ -0,0 +1,107 @@ +from typing import TYPE_CHECKING, List + +from systemrdl.rdltypes import OnWriteType + +from .bases import NextStateConditional + +if TYPE_CHECKING: + from systemrdl.node import FieldNode + +class _OnWrite(NextStateConditional): + onwritetype = None + def is_match(self, field: 'FieldNode') -> bool: + return field.get_property("onwrite") == self.onwritetype + + def get_conditional(self, field: 'FieldNode') -> str: + strb = self.exporter.dereferencer.get_access_strobe(field) + return f"decoded_req && decoded_req_is_wr && {strb}" + +class WriteOneSet(_OnWrite): + onwritetype = OnWriteType.woset + + def get_assignments(self, field: 'FieldNode') -> List[str]: + field_path = self.get_field_path(field) + return [ + f"field_combo.{field_path}.next = field_storage.{field_path} | decoded_wr_data;", + f"field_combo.{field_path}.load_next = '1;", + ] + +class WriteOneClear(_OnWrite): + onwritetype = OnWriteType.woclr + + def get_assignments(self, field: 'FieldNode') -> List[str]: + field_path = self.get_field_path(field) + return [ + f"field_combo.{field_path}.next = field_storage.{field_path} & ~decoded_wr_data;", + f"field_combo.{field_path}.load_next = '1;", + ] + +class WriteOneToggle(_OnWrite): + onwritetype = OnWriteType.wot + + def get_assignments(self, field: 'FieldNode') -> List[str]: + field_path = self.get_field_path(field) + return [ + f"field_combo.{field_path}.next = field_storage.{field_path} ^ decoded_wr_data;", + f"field_combo.{field_path}.load_next = '1;", + ] + +class WriteZeroSet(_OnWrite): + onwritetype = OnWriteType.wzs + + def get_assignments(self, field: 'FieldNode') -> List[str]: + field_path = self.get_field_path(field) + return [ + f"field_combo.{field_path}.next = field_storage.{field_path} | ~decoded_wr_data;", + f"field_combo.{field_path}.load_next = '1;", + ] + +class WriteZeroClear(_OnWrite): + onwritetype = OnWriteType.wzc + + def get_assignments(self, field: 'FieldNode') -> List[str]: + field_path = self.get_field_path(field) + return [ + f"field_combo.{field_path}.next = field_storage.{field_path} & decoded_wr_data;", + f"field_combo.{field_path}.load_next = '1;", + ] + +class WriteZeroToggle(_OnWrite): + onwritetype = OnWriteType.wzt + + def get_assignments(self, field: 'FieldNode') -> List[str]: + field_path = self.get_field_path(field) + return [ + f"field_combo.{field_path}.next = field_storage.{field_path} ^ ~decoded_wr_data;", + f"field_combo.{field_path}.load_next = '1;", + ] + +class WriteClear(_OnWrite): + onwritetype = OnWriteType.wclr + + def get_assignments(self, field: 'FieldNode') -> List[str]: + field_path = self.get_field_path(field) + return [ + f"field_combo.{field_path}.next = '0;", + f"field_combo.{field_path}.load_next = '1;", + ] + +class WriteSet(_OnWrite): + onwritetype = OnWriteType.wset + + def get_assignments(self, field: 'FieldNode') -> List[str]: + field_path = self.get_field_path(field) + return [ + f"field_combo.{field_path}.next = '1;", + f"field_combo.{field_path}.load_next = '1;", + ] + +class Write(_OnWrite): + onwritetype = None + + def get_assignments(self, field: 'FieldNode') -> List[str]: + field_path = self.get_field_path(field) + return [ + f"field_combo.{field_path}.next = decoded_wr_data;", + f"field_combo.{field_path}.load_next = '1;", + ] diff --git a/peakrdl/regblock/field_logic/templates/field_storage.sv b/peakrdl/regblock/field_logic/templates/field_storage.sv new file mode 100644 index 0000000..ee55130 --- /dev/null +++ b/peakrdl/regblock/field_logic/templates/field_storage.sv @@ -0,0 +1,25 @@ +// Field: {{node.get_path()}} +always_comb begin + field_combo.{{field_path}}.next = '0; + field_combo.{{field_path}}.load_next = '0; + {%- for signal in extra_combo_signals %} + field_combo.{{field_path}}.{{signal.name}} = {{signal.default_assignment}}; + {%- endfor %} + {%- for conditional in conditionals %} + {% if not loop.first %}end else {% endif %}if({{conditional.get_conditional(node)}}) begin + {%- for assignment in conditional.get_assignments(node) %} + {{assignment|indent}} + {%- endfor %} + end + {%- endfor %} +end +always_ff {{get_always_ff_event(resetsignal)}} begin + if({{resetsignal.activehigh_identifier}}) begin + field_storage.{{field_path}} <= {{reset}}; + end else if(field_combo.{{field_path}}.load_next) begin + field_storage.{{field_path}} <= field_combo.{{field_path}}.next; + end +end +{% if has_value_output(node) -%} + assign {{get_output_identifier(node)}} = field_storage.{{field_path}}; +{%- endif -%} diff --git a/peakrdl/regblock/forloop_generator.py b/peakrdl/regblock/forloop_generator.py new file mode 100644 index 0000000..de3f91a --- /dev/null +++ b/peakrdl/regblock/forloop_generator.py @@ -0,0 +1,96 @@ +from typing import TYPE_CHECKING, Optional, List +import textwrap + +from systemrdl.walker import RDLListener, RDLWalker + +if TYPE_CHECKING: + from systemrdl.node import AddressableNode, Node + +class Body: + + def __init__(self) -> None: + self.children = [] + + def __str__(self) -> str: + s = '\n'.join((str(x) for x in self.children)) + return s + +class LoopBody(Body): + def __init__(self, dim: int, iterator: str, i_type: str) -> None: + super().__init__() + self.dim = dim + self.iterator = iterator + self.i_type = i_type + + def __str__(self) -> str: + s = super().__str__() + return ( + f"for({self.i_type} {self.iterator}=0; {self.iterator}<{self.dim}; {self.iterator}++) begin\n" + + textwrap.indent(s, " ") + + "\nend" + ) + + + +class ForLoopGenerator: + i_type = "int" + + def __init__(self) -> None: + self._loop_level = 0 + self._stack = [] + + @property + def current_loop(self) -> Body: + return self._stack[-1] + + def push_loop(self, dim: int) -> None: + i = f"i{self._loop_level}" + b = LoopBody(dim, i, self.i_type) + self._stack.append(b) + self._loop_level += 1 + + def add_content(self, s: str) -> None: + self.current_loop.children.append(s) + + def pop_loop(self) -> None: + b = self._stack.pop() + + if b.children: + # Loop body is not empty. Attach it to the parent + self.current_loop.children.append(b) + self._loop_level -= 1 + + def start(self): + assert not self._stack + b = Body() + self._stack.append(b) + + def finish(self) -> Optional[str]: + b = self._stack.pop() + assert not self._stack + + if not b.children: + return None + return str(b) + +class RDLForLoopGenerator(ForLoopGenerator, RDLListener): + + def get_content(self, node: 'Node') -> Optional[str]: + self.start() + walker = RDLWalker() + walker.walk(node, self, skip_top=True) + return self.finish() + + def enter_AddressableComponent(self, node: 'AddressableNode') -> None: + if not node.is_array: + return + + for dim in node.array_dimensions: + self.push_loop(dim) + + def exit_AddressableComponent(self, node: 'AddressableNode') -> None: + if not node.is_array: + return + + for _ in node.array_dimensions: + self.pop_loop() diff --git a/peakrdl/regblock/hwif.py b/peakrdl/regblock/hwif.py index a23c609..cb75f64 100644 --- a/peakrdl/regblock/hwif.py +++ b/peakrdl/regblock/hwif.py @@ -1,7 +1,10 @@ from typing import TYPE_CHECKING, Union, List -from systemrdl.node import Node, SignalNode, FieldNode, AddressableNode + +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 @@ -13,15 +16,18 @@ class Hwif: - Signal inputs (except those that are promoted to the top) """ - def __init__(self, exporter: 'RegblockExporter', top_node: Node, package_name: str): + def __init__(self, exporter: 'RegblockExporter', package_name: str): self.exporter = exporter - self.top_node = top_node 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.exporter.top_node + def get_package_declaration(self) -> str: """ @@ -151,19 +157,35 @@ class Hwif: 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.get_property("counter"): + if not node.get_property("incr"): + # User did not provide their own incr component reference. + # Imply an input + contents.append("logic incr;") + 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("incrwidth") + if width: + # Implies a corresponding incrvalue input + contents.append(f"logic [{width-1}:0] incrvalue;") + + width = node.get_property("decrwidth") + if width: + # Implies a corresponding decrvalue input + contents.append(f"logic [{width-1}:0] decrvalue;") + # TODO: """ - we/wel - if either is boolean, and true - not part of external hwif if reference - mutually exclusive - hwclr/hwset - if either is boolean, and true - not part of external hwif if reference - incr/decr - if counter=true, generate BOTH - incrvalue/decrvalue - if either incrwidth/decrwidth are set signals! any signal instances instantiated in the scope """ @@ -180,14 +202,11 @@ class Hwif: else: contents.append(f"logic [{node.width-1}:0] value;") - # TODO: - """ - bitwise reductions - if anded, ored, xored == True, output a signal - swmod/swacc - event strobes - Are there was_written/was_read strobes too? - """ + # Generate output bit signals enabled via property + for prop_name in ["anded", "ored", "xored", "swmod", "swacc"]: + if node.get_property(prop_name): + contents.append(f"logic {prop_name};") + # TODO: Are there was_written/was_read strobes too? return contents @@ -211,7 +230,6 @@ class Hwif: """ Returns True if the object infers an output wire in the hwif """ - # TODO: Extend this for signals and prop references? return obj.is_hw_readable @@ -220,28 +238,44 @@ class Hwif: Returns the identifier string that best represents the input object. if obj is: - Field: the fields input value port + Field: the fields hw input value port Signal: signal input value Prop reference: could be an implied hwclr/hwset/swwe/swwel/we/wel input - Raise a runtime error if an illegal prop ref is requested, or if - the prop ref is not actually implied, but explicitly ref a component - TODO: finish this raises an exception if obj is invalid """ - raise NotImplementedError() + 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): + assert obj.name in {'hwclr', 'hwset', 'swwe', 'swwel', 'we', 'wel'} + path = get_indexed_path(self.top_node, obj.node) + return "hwif_in." + path + "." + obj.name + + raise RuntimeError("Unhandled reference to: %s", obj) - def get_output_identifier(self, obj: FieldNode) -> str: + 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 output value port + Field: the fields hw output value port Property ref: this is also part of the struct - TODO: finish this raises an exception if obj is invalid """ - raise NotImplementedError() + if isinstance(obj, FieldNode): + path = get_indexed_path(self.top_node, obj) + return "hwif_out." + path + ".value" + elif isinstance(obj, PropertyReference): + assert obj.name in {"anded", "ored", "xored", "swmod", "swacc"} + assert obj.node.get_property(obj.name) + path = get_indexed_path(self.top_node, obj.node) + return "hwif_out." + path + "." + obj.name + + raise RuntimeError("Unhandled reference to: %s", obj) diff --git a/peakrdl/regblock/module_tmpl.sv b/peakrdl/regblock/module_tmpl.sv index 787be92..f8a038a 100644 --- a/peakrdl/regblock/module_tmpl.sv +++ b/peakrdl/regblock/module_tmpl.sv @@ -1,22 +1,19 @@ -{%- import "utils_tmpl.sv" as utils with context -%} {{hwif.get_package_declaration()}} -module {{module_name}} #( - // TODO: pipeline parameters - )( +module {{module_name}} ( input wire clk, {%- for signal in reset_signals %} {{signal.port_declaration}}, - {% endfor %} + {%- endfor %} {%- for signal in user_signals %} {{signal.port_declaration}}, - {% endfor %} + {%- endfor %} {%- for interrupt in interrupts %} {{interrupt.port_declaration}}, - {% endfor %} + {%- endfor %} {{cpuif.port_declaration|indent(8)}}, @@ -71,10 +68,12 @@ module {{module_name}} #( //-------------------------------------------------------------------------- // Field logic //-------------------------------------------------------------------------- + {{field_logic.get_combo_struct()|indent}} + {{field_logic.get_storage_struct()|indent}} - // TODO: Field next-state logic, and output port signal assignment (aka output mapping layer) {{field_logic.get_implementation()|indent}} + // TODO: output port signal assignment (aka output mapping layer) //-------------------------------------------------------------------------- // Readback mux @@ -85,7 +84,7 @@ module {{module_name}} #( {{readback_mux.get_implementation()|indent}} - {%- call utils.AlwaysFF(cpuif_reset) %} + always_ff {{get_always_ff_event(cpuif_reset)}} begin if({{cpuif_reset.activehigh_identifier}}) begin cpuif_rd_ack <= '0; cpuif_rd_data <= '0; @@ -95,6 +94,6 @@ module {{module_name}} #( cpuif_rd_data <= readback_data; cpuif_rd_err <= readback_err; end - {%- endcall %} + end endmodule diff --git a/peakrdl/regblock/readback_mux.py b/peakrdl/regblock/readback_mux.py index 3734a5a..89273b6 100644 --- a/peakrdl/regblock/readback_mux.py +++ b/peakrdl/regblock/readback_mux.py @@ -1,19 +1,19 @@ import re from typing import TYPE_CHECKING, List -from systemrdl.node import Node, AddressableNode, RegNode +from systemrdl.node import AddrmapNode if TYPE_CHECKING: from .exporter import RegblockExporter class ReadbackMux: - def __init__(self, exporter:'RegblockExporter', top_node:AddressableNode): + def __init__(self, exporter:'RegblockExporter'): self.exporter = exporter - self.top_node = top_node - - self._indent_level = 0 + @property + def top_node(self) -> AddrmapNode: + return self.exporter.top_node def get_implementation(self) -> str: # TODO: Count the number of readable registers @@ -21,9 +21,3 @@ class ReadbackMux: # TODO: Always comb block to assign & mask all elements # TODO: Separate always_comb block to OR reduce down return "//TODO" - - - #--------------------------------------------------------------------------- - @property - def _indent(self) -> str: - return " " * self._indent_level diff --git a/peakrdl/regblock/signals.py b/peakrdl/regblock/signals.py index cb7a3b5..7706074 100644 --- a/peakrdl/regblock/signals.py +++ b/peakrdl/regblock/signals.py @@ -68,10 +68,11 @@ class RDLSignal(SignalBase): class InferredSignal(SignalBase): - def __init__(self, identifier:str, width:int=1, is_async:bool=False): + def __init__(self, identifier:str, width:int=1, is_async:bool=False, is_activehigh=True): self._identifier = identifier self._width = width self._is_async = is_async + self._is_activehigh = is_activehigh @property def is_async(self) -> bool: @@ -79,7 +80,7 @@ class InferredSignal(SignalBase): @property def is_activehigh(self) -> bool: - return True + return self._is_activehigh @property def width(self) -> int: diff --git a/peakrdl/regblock/struct_generator.py b/peakrdl/regblock/struct_generator.py new file mode 100644 index 0000000..e0d85c3 --- /dev/null +++ b/peakrdl/regblock/struct_generator.py @@ -0,0 +1,139 @@ +from typing import TYPE_CHECKING, Optional, List +import textwrap + +from systemrdl.walker import RDLListener, RDLWalker + +if TYPE_CHECKING: + from typing import Union + + from systemrdl.node import AddrmapNode, RegfileNode, RegNode, FieldNode, Node + + +class _StructBase: + def __init__(self): + self.children = [] # type: Union[str, _StructBase] + + def __str__(self) -> str: + s = '\n'.join((str(x) for x in self.children)) + return textwrap.indent(s, " ") + + +class _AnonymousStruct(_StructBase): + def __init__(self, inst_name: str, array_dimensions: Optional[List[int]] = None): + super().__init__() + self.inst_name = inst_name + self.array_dimensions = array_dimensions + + def __str__(self) -> str: + if self.array_dimensions: + suffix = "[" + "][".join((str(n) for n in self.array_dimensions)) + "]" + else: + suffix = "" + + return ( + "struct {\n" + + super().__str__() + + f"\n}} {self.inst_name}{suffix};" + ) + + +class _TypedefStruct(_StructBase): + def __init__(self, type_name: str): + super().__init__() + self.type_name = type_name + + def __str__(self) -> str: + return ( + "typedef struct {\n" + + super().__str__() + + f"\n}} {self.type_name};" + ) + + +class StructGenerator: + + def __init__(self): + self._struct_stack = [] + + @property + def current_struct(self) -> _StructBase: + return self._struct_stack[-1] + + + def push_struct(self, inst_name: str, array_dimensions: Optional[List[int]] = None) -> None: + s = _AnonymousStruct(inst_name, array_dimensions) + self._struct_stack.append(s) + + + def add_member(self, name: str, width: int = 1, array_dimensions: Optional[List[int]] = None) -> None: + if array_dimensions: + suffix = "[" + "][".join((str(n) for n in array_dimensions)) + "]" + else: + suffix = "" + + if width == 1: + m = f"logic {name}{suffix};" + else: + m = f"logic [{width-1}:0] {name}{suffix};" + self.current_struct.children.append(m) + + + 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) + + + def start(self, type_name: str): + assert not self._struct_stack + s = _TypedefStruct(type_name) + self._struct_stack.append(s) + + def finish(self) -> Optional[str]: + s = self._struct_stack.pop() + assert not self._struct_stack + + if not s.children: + return None + return str(s) + + +class RDLStructGenerator(StructGenerator, RDLListener): + """ + Struct generator that naively translates an RDL node tree into a single + struct typedef containing nested anonymous structs + + This can be extended to add more intelligent behavior + """ + + 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: + self.push_struct(node.inst_name, node.array_dimensions) + + def exit_Addrmap(self, node: 'AddrmapNode') -> None: + self.pop_struct() + + def enter_Regfile(self, node: 'RegfileNode') -> None: + self.push_struct(node.inst_name, node.array_dimensions) + + def exit_Regfile(self, node: 'RegfileNode') -> None: + self.pop_struct() + + def enter_Reg(self, node: 'RegNode') -> None: + self.push_struct(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/peakrdl/regblock/utils.py b/peakrdl/regblock/utils.py new file mode 100644 index 0000000..93a7f7d --- /dev/null +++ b/peakrdl/regblock/utils.py @@ -0,0 +1,30 @@ +import re +from typing import TYPE_CHECKING +import textwrap + +if TYPE_CHECKING: + from systemrdl.node import Node + from .signals import SignalBase + +def get_indexed_path(top_node: 'Node', target_node: 'Node') -> str: + """ + TODO: Add words about indexing and why i'm doing this. Copy from logbook + """ + path = target_node.get_rel_path(top_node, empty_array_suffix="[!]") + # replace unknown indexes with incrementing iterators i0, i1, ... + class repl: + def __init__(self): + self.i = 0 + def __call__(self, match): + s = f'i{self.i}' + self.i += 1 + return s + return re.sub(r'!', repl(), path) + + +def get_always_ff_event(resetsignal: 'SignalBase') -> str: + if resetsignal.is_async and resetsignal.is_activehigh: + return f"@(posedge clk or posedge {resetsignal.identifier})" + elif resetsignal.is_async and not resetsignal.is_activehigh: + return f"@(posedge clk or negedge {resetsignal.identifier})" + return "@(posedge clk)" diff --git a/peakrdl/regblock/utils_tmpl.sv b/peakrdl/regblock/utils_tmpl.sv deleted file mode 100644 index 3f55930..0000000 --- a/peakrdl/regblock/utils_tmpl.sv +++ /dev/null @@ -1,16 +0,0 @@ - -/* - * Creates an always_ff begin/end block with the appropriate edge sensitivity - * list depending on the resetsignal used - */ -{% macro AlwaysFF(resetsignal) %} -{%- if resetsignal.is_async and resetsignal.is_activehigh %} -always_ff @(posedge clk or posedge {{resetsignal.identifier}}) begin -{%- elif resetsignal.is_async and not resetsignal.is_activehigh %} -always_ff @(posedge clk or negedge {{resetsignal.identifier}}) begin -{%- else %} -always_ff @(posedge clk) begin -{%- endif %} -{{- caller() }} -end -{%- endmacro %} diff --git a/setup.py b/setup.py index ea3bd6c..8ad327e 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,11 @@ import os import setuptools -with open("README.md", "r") as fh: +with open("README.md", "r", encoding='utf-8') as fh: long_description = fh.read() -with open(os.path.join("peakrdl/regblock", "__about__.py")) as f: +with open(os.path.join("peakrdl/regblock", "__about__.py"), encoding='utf-8') as f: v_dict = {} exec(f.read(), v_dict) version = v_dict['__version__'] @@ -22,7 +22,7 @@ setuptools.setup( packages=['peakrdl.regblock'], include_package_data=True, install_requires=[ - "systemrdl-compiler>=1.13.2", + "systemrdl-compiler>=1.21.0", "Jinja2>=2.11", ], classifiers=( diff --git a/x.rdl b/x.rdl index 82cb305..a26fb6f 100644 --- a/x.rdl +++ b/x.rdl @@ -9,8 +9,8 @@ addrmap top { regfile { reg { field { - hw=r; sw=rw; - } abc[16:2] = 0; + hw=w; sw=r; + } abc[16:2] = 20; field { hw=r; sw=rw; } def[4] = 0;