fill in more hwif utility functions for dereferencer
This commit is contained in:
@@ -32,14 +32,17 @@ Basically, i'd define a ton of helper functions that return the signal identifie
|
|||||||
Dev Todo list
|
Dev Todo list
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
- Lots to do in HWIF layer
|
- tidy up stuff
|
||||||
- reorg base.py+struct.py.
|
- merge FieldBuilder and FieldLogic classes. It makes no sense for these to be separate
|
||||||
- I have no desire to do flattened I/O. No need to split off a base class
|
- 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
|
- User Signals
|
||||||
Generate these in the io struct? I forget what I decided
|
Generate these in the io struct? I forget what I decided
|
||||||
- get_input/output_identifier() functions
|
|
||||||
|
|
||||||
- Field logic
|
- dereferencer has some remaining todos that depend on field logic
|
||||||
Basically need all aspects of the dereferencer to be done first
|
|
||||||
|
|||||||
@@ -47,7 +47,11 @@ X multiple cpuif_reset in the same hierarchy
|
|||||||
there can only be one signal declared with cpuif_reset
|
there can only be one signal declared with cpuif_reset
|
||||||
in a given hierarchy
|
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.
|
these pairs are mutually exclusive.
|
||||||
Make sure they are not both set after elaboration
|
Make sure they are not both set after elaboration
|
||||||
Compiler checks for mutex within the same scope, but
|
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
|
... or, make these properties clear each-other on assignment
|
||||||
|
|
||||||
? If a node ispresent=true, and any of it's properties are a reference,
|
X Illegal property references:
|
||||||
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:
|
|
||||||
- reference any of the counter property references to something that isn't a counter
|
- 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
|
- reference hwclr or hwset, but the owner node has them set to False
|
||||||
means that the actual inferred signal doesnt exist!
|
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
|
- 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!
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
Field storage / next value layer
|
Field storage / next value layer
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
Where all the magic happens!!
|
Where all the magic happens!!
|
||||||
|
|
||||||
Any field that implements storage is defined here.
|
Any field that implements storage is defined here.
|
||||||
Bigass struct that only contains storage elements
|
Bigass struct that only contains storage elements
|
||||||
|
|
||||||
Each field consists of:
|
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:
|
- an always_comb block:
|
||||||
- generates the "next value" combinational signal
|
- generates the "next value" combinational signal
|
||||||
- May generate other intermediate strobes?
|
- May generate other intermediate strobes?
|
||||||
@@ -47,3 +59,105 @@ Implementation
|
|||||||
Makes sense to use a listener class
|
Makes sense to use a listener class
|
||||||
|
|
||||||
Be sure to skip alias registers
|
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
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
import re
|
import re
|
||||||
from typing import TYPE_CHECKING, List, Union
|
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:
|
if TYPE_CHECKING:
|
||||||
from .exporter import RegblockExporter
|
from .exporter import RegblockExporter
|
||||||
|
|
||||||
class AddressDecode:
|
class AddressDecode:
|
||||||
def __init__(self, exporter:'RegblockExporter', top_node:AddressableNode):
|
def __init__(self, exporter:'RegblockExporter'):
|
||||||
self.exporter = exporter
|
self.exporter = exporter
|
||||||
self.top_node = top_node
|
|
||||||
|
|
||||||
self._indent_level = 0
|
@property
|
||||||
|
def top_node(self) -> AddrmapNode:
|
||||||
# List of address strides for each dimension
|
return self.exporter.top_node
|
||||||
self._array_stride_stack = []
|
|
||||||
|
|
||||||
def get_strobe_struct(self) -> str:
|
def get_strobe_struct(self) -> str:
|
||||||
lines = []
|
struct_gen = DecodeStructGenerator()
|
||||||
self._do_struct(lines, self.top_node, is_top=True)
|
s = struct_gen.get_struct(self.top_node, "decoded_reg_strb_t")
|
||||||
return "\n".join(lines)
|
assert s is not None # guaranteed to have at least one reg
|
||||||
|
return s
|
||||||
|
|
||||||
def get_implementation(self) -> str:
|
def get_implementation(self) -> str:
|
||||||
lines = []
|
gen = DecodeLogicGenerator(self)
|
||||||
self._do_address_decode_node(lines, self.top_node)
|
s = gen.get_content(self.top_node)
|
||||||
return "\n".join(lines)
|
assert s is not None
|
||||||
|
return s
|
||||||
|
|
||||||
def get_access_strobe(self, node: Union[RegNode, FieldNode]) -> str:
|
def get_access_strobe(self, node: Union[RegNode, FieldNode]) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -34,56 +37,35 @@ class AddressDecode:
|
|||||||
if isinstance(node, FieldNode):
|
if isinstance(node, FieldNode):
|
||||||
node = node.parent
|
node = node.parent
|
||||||
|
|
||||||
path = node.get_rel_path(self.top_node, empty_array_suffix="[!]")
|
path = get_indexed_path(self.top_node, node)
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
return "decoded_reg_strb." + path
|
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:
|
class DecodeStructGenerator(RDLStructGenerator):
|
||||||
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) -> None:
|
def enter_Reg(self, node: 'RegNode') -> None:
|
||||||
if is_top:
|
self.add_member(node.inst_name, array_dimensions=node.array_dimensions)
|
||||||
lines.append(f"{self._indent}typedef struct {{")
|
|
||||||
else:
|
|
||||||
lines.append(f"{self._indent}struct {{")
|
|
||||||
|
|
||||||
self._indent_level += 1
|
# Stub out
|
||||||
for child in node.children():
|
def exit_Reg(self, node: 'RegNode') -> None:
|
||||||
if isinstance(child, RegNode):
|
pass
|
||||||
lines.append(f"{self._indent}logic {child.inst_name}{self._get_node_array_suffix(child)};")
|
def enter_Field(self, node: 'FieldNode') -> None:
|
||||||
elif isinstance(child, AddressableNode):
|
pass
|
||||||
self._do_struct(lines, child)
|
|
||||||
self._indent_level -= 1
|
|
||||||
|
|
||||||
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)};")
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
class DecodeLogicGenerator(RDLForLoopGenerator):
|
||||||
# Access strobe generation functions
|
|
||||||
#---------------------------------------------------------------------------
|
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:
|
if not node.is_array:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -94,35 +76,26 @@ class AddressDecode:
|
|||||||
strides.append(current_stride)
|
strides.append(current_stride)
|
||||||
current_stride *= dim
|
current_stride *= dim
|
||||||
strides.reverse()
|
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:
|
if not node.is_array:
|
||||||
return
|
return
|
||||||
|
|
||||||
for _ in node.array_dimensions:
|
for _ in node.array_dimensions:
|
||||||
self._array_stride_stack.pop()
|
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)
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
{% extends "cpuif/base_tmpl.sv" %}
|
{% extends "cpuif/base_tmpl.sv" %}
|
||||||
{%- import "utils_tmpl.sv" as utils with context %}
|
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
// Request
|
// Request
|
||||||
logic is_active;
|
logic is_active;
|
||||||
{%- call utils.AlwaysFF(cpuif_reset) %}
|
always_ff {{get_always_ff_event(cpuif_reset)}} begin
|
||||||
if({{cpuif_reset.activehigh_identifier}}) begin
|
if({{cpuif_reset.activehigh_identifier}}) begin
|
||||||
is_active <= '0;
|
is_active <= '0;
|
||||||
cpuif_req <= '0;
|
cpuif_req <= '0;
|
||||||
@@ -31,7 +30,7 @@ logic is_active;
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
{%- endcall %}
|
end
|
||||||
|
|
||||||
// Response
|
// Response
|
||||||
assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack;
|
assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import jinja2
|
from ..utils import get_always_ff_event
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..exporter import RegblockExporter
|
from ..exporter import RegblockExporter
|
||||||
@@ -25,6 +25,7 @@ class CpuifBase:
|
|||||||
"cpuif_reset": self.cpuif_reset,
|
"cpuif_reset": self.cpuif_reset,
|
||||||
"data_width": self.data_width,
|
"data_width": self.data_width,
|
||||||
"addr_width": self.addr_width,
|
"addr_width": self.addr_width,
|
||||||
|
"get_always_ff_event": get_always_ff_event,
|
||||||
}
|
}
|
||||||
|
|
||||||
template = self.exporter.jj_env.get_template(self.template_path)
|
template = self.exporter.jj_env.get_template(self.template_path)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from typing import TYPE_CHECKING, Union
|
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
|
from systemrdl.rdltypes import PropertyReference
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -13,12 +13,24 @@ class Dereferencer:
|
|||||||
This class provides an interface to convert conceptual SystemRDL references
|
This class provides an interface to convert conceptual SystemRDL references
|
||||||
into Verilog identifiers
|
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.exporter = exporter
|
||||||
self.hwif = hwif
|
|
||||||
self.address_decode = address_decode
|
@property
|
||||||
self.field_logic = field_logic
|
def hwif(self) -> 'Hwif':
|
||||||
self.top_node = top_node
|
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:
|
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)
|
prop_value = obj.node.get_property(obj.name)
|
||||||
if prop_value is None:
|
if prop_value is None:
|
||||||
# unset by the user, points to the implied internal signal
|
# 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:
|
else:
|
||||||
return self.get_value(prop_value)
|
return self.get_value(prop_value)
|
||||||
elif obj.name == "next":
|
elif obj.name == "next":
|
||||||
prop_value = obj.node.get_property(obj.name)
|
prop_value = obj.node.get_property(obj.name)
|
||||||
if prop_value is None:
|
if prop_value is None:
|
||||||
# unset by the user, points to the implied internal signal
|
# 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:
|
else:
|
||||||
return self.get_value(prop_value)
|
return self.get_value(prop_value)
|
||||||
|
|
||||||
@@ -127,7 +139,6 @@ class Dereferencer:
|
|||||||
if prop_value is True:
|
if prop_value is True:
|
||||||
# Points to inferred hwif input
|
# Points to inferred hwif input
|
||||||
return f"!({self.hwif.get_input_identifier(obj)})"
|
return f"!({self.hwif.get_input_identifier(obj)})"
|
||||||
raise NotImplementedError # TODO: Implement this
|
|
||||||
elif prop_value is False:
|
elif prop_value is False:
|
||||||
# This should never happen, as this is checked by the compiler's validator
|
# This should never happen, as this is checked by the compiler's validator
|
||||||
raise RuntimeError
|
raise RuntimeError
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import os
|
import os
|
||||||
from typing import TYPE_CHECKING
|
from typing import Union
|
||||||
|
|
||||||
import jinja2 as jj
|
import jinja2 as jj
|
||||||
from systemrdl.node import Node, RootNode
|
from systemrdl.node import AddrmapNode, RootNode
|
||||||
|
|
||||||
from .addr_decode import AddressDecode
|
from .addr_decode import AddressDecode
|
||||||
from .field_logic import FieldLogic
|
from .field_logic import FieldLogic
|
||||||
from .dereferencer import Dereferencer
|
from .dereferencer import Dereferencer
|
||||||
from .readback_mux import ReadbackMux
|
from .readback_mux import ReadbackMux
|
||||||
from .signals import InferredSignal
|
from .signals import InferredSignal, SignalBase
|
||||||
|
|
||||||
from .cpuif.apb4 import APB4_Cpuif
|
from .cpuif.apb4 import APB4_Cpuif
|
||||||
from .hwif import Hwif
|
from .hwif import Hwif
|
||||||
|
from .utils import get_always_ff_event
|
||||||
|
|
||||||
class RegblockExporter:
|
class RegblockExporter:
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@@ -21,6 +22,16 @@ class RegblockExporter:
|
|||||||
if kwargs:
|
if kwargs:
|
||||||
raise TypeError("got an unexpected keyword argument '%s'" % list(kwargs.keys())[0])
|
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:
|
if user_template_dir:
|
||||||
loader = jj.ChoiceLoader([
|
loader = jj.ChoiceLoader([
|
||||||
jj.FileSystemLoader(user_template_dir),
|
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 it is the root node, skip to top addrmap
|
||||||
if isinstance(node, RootNode):
|
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)
|
cpuif_cls = kwargs.pop("cpuif_cls", APB4_Cpuif)
|
||||||
hwif_cls = kwargs.pop("hwif_cls", Hwif)
|
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")
|
package_name = kwargs.pop("package_name", module_name + "_pkg")
|
||||||
|
|
||||||
# Check for stray kwargs
|
# Check for stray kwargs
|
||||||
@@ -63,8 +77,8 @@ class RegblockExporter:
|
|||||||
# TODO: Scan design...
|
# TODO: Scan design...
|
||||||
|
|
||||||
# TODO: derive this from somewhere
|
# TODO: derive this from somewhere
|
||||||
cpuif_reset = InferredSignal("rst")
|
cpuif_reset = self.default_resetsignal
|
||||||
reset_signals = [cpuif_reset]
|
reset_signals = set([cpuif_reset, self.default_resetsignal])
|
||||||
|
|
||||||
cpuif = cpuif_cls(
|
cpuif = cpuif_cls(
|
||||||
self,
|
self,
|
||||||
@@ -73,17 +87,11 @@ class RegblockExporter:
|
|||||||
addr_width=32 # TODO:
|
addr_width=32 # TODO:
|
||||||
)
|
)
|
||||||
|
|
||||||
hwif = hwif_cls(
|
self.hwif = hwif_cls(
|
||||||
self,
|
self,
|
||||||
top_node=node,
|
|
||||||
package_name=package_name,
|
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
|
# Build Jinja template context
|
||||||
context = {
|
context = {
|
||||||
"module_name": module_name,
|
"module_name": module_name,
|
||||||
@@ -94,10 +102,11 @@ class RegblockExporter:
|
|||||||
"user_signals": [], # TODO:
|
"user_signals": [], # TODO:
|
||||||
"interrupts": [], # TODO:
|
"interrupts": [], # TODO:
|
||||||
"cpuif": cpuif,
|
"cpuif": cpuif,
|
||||||
"hwif": hwif,
|
"hwif": self.hwif,
|
||||||
"address_decode": address_decode,
|
"address_decode": self.address_decode,
|
||||||
"field_logic": field_logic,
|
"field_logic": self.field_logic,
|
||||||
"readback_mux": readback_mux,
|
"readback_mux": self.readback_mux,
|
||||||
|
"get_always_ff_event": get_always_ff_event,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Write out design
|
# Write out design
|
||||||
|
|||||||
@@ -1,105 +1,77 @@
|
|||||||
import re
|
from typing import TYPE_CHECKING
|
||||||
from typing import TYPE_CHECKING, List
|
|
||||||
|
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:
|
if TYPE_CHECKING:
|
||||||
from ..exporter import RegblockExporter
|
from ..exporter import RegblockExporter
|
||||||
|
|
||||||
class FieldLogic:
|
class FieldLogic:
|
||||||
def __init__(self, exporter:'RegblockExporter', top_node:Node):
|
def __init__(self, exporter:'RegblockExporter'):
|
||||||
self.exporter = exporter
|
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:
|
def get_storage_struct(self) -> str:
|
||||||
lines = []
|
struct_gen = FieldStorageStructGenerator()
|
||||||
self._do_struct(lines, self.top_node, is_top=True)
|
s = struct_gen.get_struct(self.top_node, "field_storage_t")
|
||||||
|
|
||||||
# Only declare the storage struct if it exists
|
# Only declare the storage struct if it exists
|
||||||
if lines:
|
if s is None:
|
||||||
lines.append(f"{self._indent}field_storage_t field_storage;")
|
return ""
|
||||||
return "\n".join(lines)
|
|
||||||
|
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:
|
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
|
# 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
|
assert node.implements_storage
|
||||||
|
path = get_indexed_path(self.top_node, node)
|
||||||
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)
|
|
||||||
|
|
||||||
return "field_storage." + path
|
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
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
def get_counter_control_identifier(self, prop_ref: PropertyReference) -> str:
|
||||||
# Struct generation functions
|
"""
|
||||||
#---------------------------------------------------------------------------
|
Return the Veriog string that represents the field's inferred incr/decr strobe signal.
|
||||||
@property
|
prop_ref will be either an incr or decr property reference, and it is already known that
|
||||||
def _indent(self) -> str:
|
the incr/decr properties are not explicitly set by the user and are therefore inferred.
|
||||||
return " " * self._indent_level
|
"""
|
||||||
|
# TODO: Implement this
|
||||||
def _get_node_array_suffix(self, node:AddressableNode) -> str:
|
raise NotImplementedError
|
||||||
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)};")
|
|
||||||
|
|||||||
95
peakrdl/regblock/field_logic/bases.py
Normal file
95
peakrdl/regblock/field_logic/bases.py
Normal 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 []
|
||||||
176
peakrdl/regblock/field_logic/field_builder.py
Normal file
176
peakrdl/regblock/field_logic/field_builder.py
Normal 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))
|
||||||
38
peakrdl/regblock/field_logic/sw_onread.py
Normal file
38
peakrdl/regblock/field_logic/sw_onread.py
Normal 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;",
|
||||||
|
]
|
||||||
107
peakrdl/regblock/field_logic/sw_onwrite.py
Normal file
107
peakrdl/regblock/field_logic/sw_onwrite.py
Normal 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;",
|
||||||
|
]
|
||||||
25
peakrdl/regblock/field_logic/templates/field_storage.sv
Normal file
25
peakrdl/regblock/field_logic/templates/field_storage.sv
Normal 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 -%}
|
||||||
96
peakrdl/regblock/forloop_generator.py
Normal file
96
peakrdl/regblock/forloop_generator.py
Normal 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()
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
from typing import TYPE_CHECKING, Union, List
|
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 systemrdl.rdltypes import PropertyReference
|
||||||
|
|
||||||
|
from .utils import get_indexed_path
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .exporter import RegblockExporter
|
from .exporter import RegblockExporter
|
||||||
|
|
||||||
@@ -13,15 +16,18 @@ class Hwif:
|
|||||||
- Signal inputs (except those that are promoted to the top)
|
- 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.exporter = exporter
|
||||||
self.top_node = top_node
|
|
||||||
self.package_name = package_name
|
self.package_name = package_name
|
||||||
|
|
||||||
self.has_input_struct = None
|
self.has_input_struct = None
|
||||||
self.has_output_struct = None
|
self.has_output_struct = None
|
||||||
self._indent_level = 0
|
self._indent_level = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def top_node(self) -> AddrmapNode:
|
||||||
|
return self.exporter.top_node
|
||||||
|
|
||||||
|
|
||||||
def get_package_declaration(self) -> str:
|
def get_package_declaration(self) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -151,19 +157,35 @@ class Hwif:
|
|||||||
else:
|
else:
|
||||||
contents.append(f"logic [{node.width-1}:0] value;")
|
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:
|
# 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!
|
signals!
|
||||||
any signal instances instantiated in the scope
|
any signal instances instantiated in the scope
|
||||||
"""
|
"""
|
||||||
@@ -180,14 +202,11 @@ class Hwif:
|
|||||||
else:
|
else:
|
||||||
contents.append(f"logic [{node.width-1}:0] value;")
|
contents.append(f"logic [{node.width-1}:0] value;")
|
||||||
|
|
||||||
# TODO:
|
# Generate output bit signals enabled via property
|
||||||
"""
|
for prop_name in ["anded", "ored", "xored", "swmod", "swacc"]:
|
||||||
bitwise reductions
|
if node.get_property(prop_name):
|
||||||
if anded, ored, xored == True, output a signal
|
contents.append(f"logic {prop_name};")
|
||||||
swmod/swacc
|
# TODO: Are there was_written/was_read strobes too?
|
||||||
event strobes
|
|
||||||
Are there was_written/was_read strobes too?
|
|
||||||
"""
|
|
||||||
|
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
@@ -211,7 +230,6 @@ class Hwif:
|
|||||||
"""
|
"""
|
||||||
Returns True if the object infers an output wire in the 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
|
return obj.is_hw_readable
|
||||||
|
|
||||||
|
|
||||||
@@ -220,28 +238,44 @@ class Hwif:
|
|||||||
Returns the identifier string that best represents the input object.
|
Returns the identifier string that best represents the input object.
|
||||||
|
|
||||||
if obj is:
|
if obj is:
|
||||||
Field: the fields input value port
|
Field: the fields hw input value port
|
||||||
Signal: signal input value
|
Signal: signal input value
|
||||||
Prop reference:
|
Prop reference:
|
||||||
could be an implied hwclr/hwset/swwe/swwel/we/wel input
|
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
|
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()
|
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.
|
Returns the identifier string that best represents the output object.
|
||||||
|
|
||||||
if obj is:
|
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
|
Property ref: this is also part of the struct
|
||||||
TODO: finish this
|
|
||||||
|
|
||||||
raises an exception if obj is invalid
|
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)
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
{%- import "utils_tmpl.sv" as utils with context -%}
|
|
||||||
|
|
||||||
{{hwif.get_package_declaration()}}
|
{{hwif.get_package_declaration()}}
|
||||||
|
|
||||||
module {{module_name}} #(
|
module {{module_name}} (
|
||||||
// TODO: pipeline parameters
|
|
||||||
)(
|
|
||||||
input wire clk,
|
input wire clk,
|
||||||
{%- for signal in reset_signals %}
|
{%- for signal in reset_signals %}
|
||||||
{{signal.port_declaration}},
|
{{signal.port_declaration}},
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
|
|
||||||
{%- for signal in user_signals %}
|
{%- for signal in user_signals %}
|
||||||
{{signal.port_declaration}},
|
{{signal.port_declaration}},
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
|
|
||||||
{%- for interrupt in interrupts %}
|
{%- for interrupt in interrupts %}
|
||||||
{{interrupt.port_declaration}},
|
{{interrupt.port_declaration}},
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
|
|
||||||
{{cpuif.port_declaration|indent(8)}},
|
{{cpuif.port_declaration|indent(8)}},
|
||||||
|
|
||||||
@@ -71,10 +68,12 @@ module {{module_name}} #(
|
|||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Field logic
|
// Field logic
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
{{field_logic.get_combo_struct()|indent}}
|
||||||
|
|
||||||
{{field_logic.get_storage_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}}
|
{{field_logic.get_implementation()|indent}}
|
||||||
|
// TODO: output port signal assignment (aka output mapping layer)
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Readback mux
|
// Readback mux
|
||||||
@@ -85,7 +84,7 @@ module {{module_name}} #(
|
|||||||
|
|
||||||
{{readback_mux.get_implementation()|indent}}
|
{{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
|
if({{cpuif_reset.activehigh_identifier}}) begin
|
||||||
cpuif_rd_ack <= '0;
|
cpuif_rd_ack <= '0;
|
||||||
cpuif_rd_data <= '0;
|
cpuif_rd_data <= '0;
|
||||||
@@ -95,6 +94,6 @@ module {{module_name}} #(
|
|||||||
cpuif_rd_data <= readback_data;
|
cpuif_rd_data <= readback_data;
|
||||||
cpuif_rd_err <= readback_err;
|
cpuif_rd_err <= readback_err;
|
||||||
end
|
end
|
||||||
{%- endcall %}
|
end
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import re
|
import re
|
||||||
from typing import TYPE_CHECKING, List
|
from typing import TYPE_CHECKING, List
|
||||||
|
|
||||||
from systemrdl.node import Node, AddressableNode, RegNode
|
from systemrdl.node import AddrmapNode
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .exporter import RegblockExporter
|
from .exporter import RegblockExporter
|
||||||
|
|
||||||
class ReadbackMux:
|
class ReadbackMux:
|
||||||
def __init__(self, exporter:'RegblockExporter', top_node:AddressableNode):
|
def __init__(self, exporter:'RegblockExporter'):
|
||||||
self.exporter = exporter
|
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:
|
def get_implementation(self) -> str:
|
||||||
# TODO: Count the number of readable registers
|
# TODO: Count the number of readable registers
|
||||||
@@ -21,9 +21,3 @@ class ReadbackMux:
|
|||||||
# TODO: Always comb block to assign & mask all elements
|
# TODO: Always comb block to assign & mask all elements
|
||||||
# TODO: Separate always_comb block to OR reduce down
|
# TODO: Separate always_comb block to OR reduce down
|
||||||
return "//TODO"
|
return "//TODO"
|
||||||
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
|
||||||
@property
|
|
||||||
def _indent(self) -> str:
|
|
||||||
return " " * self._indent_level
|
|
||||||
|
|||||||
@@ -68,10 +68,11 @@ class RDLSignal(SignalBase):
|
|||||||
|
|
||||||
|
|
||||||
class InferredSignal(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._identifier = identifier
|
||||||
self._width = width
|
self._width = width
|
||||||
self._is_async = is_async
|
self._is_async = is_async
|
||||||
|
self._is_activehigh = is_activehigh
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_async(self) -> bool:
|
def is_async(self) -> bool:
|
||||||
@@ -79,7 +80,7 @@ class InferredSignal(SignalBase):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_activehigh(self) -> bool:
|
def is_activehigh(self) -> bool:
|
||||||
return True
|
return self._is_activehigh
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self) -> int:
|
def width(self) -> int:
|
||||||
|
|||||||
139
peakrdl/regblock/struct_generator.py
Normal file
139
peakrdl/regblock/struct_generator.py
Normal 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
30
peakrdl/regblock/utils.py
Normal 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)"
|
||||||
@@ -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 %}
|
|
||||||
6
setup.py
6
setup.py
@@ -1,11 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
import setuptools
|
import setuptools
|
||||||
|
|
||||||
with open("README.md", "r") as fh:
|
with open("README.md", "r", encoding='utf-8') as fh:
|
||||||
long_description = fh.read()
|
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 = {}
|
v_dict = {}
|
||||||
exec(f.read(), v_dict)
|
exec(f.read(), v_dict)
|
||||||
version = v_dict['__version__']
|
version = v_dict['__version__']
|
||||||
@@ -22,7 +22,7 @@ setuptools.setup(
|
|||||||
packages=['peakrdl.regblock'],
|
packages=['peakrdl.regblock'],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"systemrdl-compiler>=1.13.2",
|
"systemrdl-compiler>=1.21.0",
|
||||||
"Jinja2>=2.11",
|
"Jinja2>=2.11",
|
||||||
],
|
],
|
||||||
classifiers=(
|
classifiers=(
|
||||||
|
|||||||
Reference in New Issue
Block a user