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

@@ -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
"""
raise NotImplementedError()
if isinstance(obj, FieldNode):
path = get_indexed_path(self.top_node, obj)
return "hwif_in." + path + ".value"
elif isinstance(obj, SignalNode):
# TODO: Implement this
raise NotImplementedError()
elif isinstance(obj, PropertyReference):
assert obj.name in {'hwclr', 'hwset', 'swwe', 'swwel', 'we', 'wel'}
path = get_indexed_path(self.top_node, obj.node)
return "hwif_in." + path + "." + obj.name
raise RuntimeError("Unhandled reference to: %s", obj)
def get_output_identifier(self, obj: FieldNode) -> str:
def get_output_identifier(self, obj: Union[FieldNode, PropertyReference]) -> str:
"""
Returns the identifier string that best represents the output object.
if obj is:
Field: the fields output value port
Field: the fields hw output value port
Property ref: this is also part of the struct
TODO: finish this
raises an exception if obj is invalid
"""
raise NotImplementedError()
if isinstance(obj, FieldNode):
path = get_indexed_path(self.top_node, obj)
return "hwif_out." + path + ".value"
elif isinstance(obj, PropertyReference):
assert obj.name in {"anded", "ored", "xored", "swmod", "swacc"}
assert obj.node.get_property(obj.name)
path = get_indexed_path(self.top_node, obj.node)
return "hwif_out." + path + "." + obj.name
raise RuntimeError("Unhandled reference to: %s", obj)

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