fill in more hwif utility functions for dereferencer

This commit is contained in:
Alex Mykyta
2021-07-16 18:05:57 -07:00
parent e3a49a65fb
commit f473dfb9e7
24 changed files with 1105 additions and 285 deletions

View File

@@ -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

View File

@@ -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
================================================================================

View File

@@ -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(<conditional>) begin
<assignments>
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:
<field>.next = <next value>
<field>.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

View File

@@ -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)

View File

@@ -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;

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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(<conditional>) begin
<assignments>
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:
<field>.next = <next value>
<field>.load_next = '1;
"""
def get_extra_combo_signals(self, field: 'FieldNode') -> List[SVLogic]:
return []

View File

@@ -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))

View File

@@ -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;",
]

View File

@@ -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;",
]

View File

@@ -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 -%}

View File

@@ -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()

View File

@@ -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
"""
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)

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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)

30
peakrdl/regblock/utils.py Normal file
View File

@@ -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)"

View File

@@ -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 %}

View File

@@ -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=(

4
x.rdl
View File

@@ -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;