basic framework
This commit is contained in:
1
peakrdl/regblock/__about__.py
Normal file
1
peakrdl/regblock/__about__.py
Normal file
@@ -0,0 +1 @@
|
||||
__version__ = "0.1.0"
|
||||
3
peakrdl/regblock/__init__.py
Normal file
3
peakrdl/regblock/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .__about__ import __version__
|
||||
|
||||
from .exporter import RegblockExporter
|
||||
123
peakrdl/regblock/addr_decode.py
Normal file
123
peakrdl/regblock/addr_decode.py
Normal file
@@ -0,0 +1,123 @@
|
||||
import re
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from systemrdl.node import Node, AddressableNode, RegNode
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .exporter import RegblockExporter
|
||||
|
||||
class AddressDecode:
|
||||
def __init__(self, exporter:'RegblockExporter', top_node:AddressableNode):
|
||||
self.exporter = exporter
|
||||
self.top_node = top_node
|
||||
|
||||
self._indent_level = 0
|
||||
|
||||
# List of address strides for each dimension
|
||||
self._array_stride_stack = []
|
||||
|
||||
def get_strobe_struct(self) -> str:
|
||||
lines = []
|
||||
self._do_struct(lines, self.top_node, is_top=True)
|
||||
return "\n".join(lines)
|
||||
|
||||
def get_implementation(self) -> str:
|
||||
lines = []
|
||||
self._do_address_decode_node(lines, self.top_node)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Struct generation functions
|
||||
#---------------------------------------------------------------------------
|
||||
@property
|
||||
def _indent(self) -> str:
|
||||
return " " * self._indent_level
|
||||
|
||||
def _get_node_array_suffix(self, node:AddressableNode) -> str:
|
||||
if node.is_array:
|
||||
return "".join([f'[{dim}]' for dim in node.array_dimensions])
|
||||
return ""
|
||||
|
||||
def _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 {{")
|
||||
|
||||
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
|
||||
|
||||
if is_top:
|
||||
lines.append(f"{self._indent}}} access_strb_t;")
|
||||
else:
|
||||
lines.append(f"{self._indent}}} {node.inst_name}{self._get_node_array_suffix(node)};")
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Access strobe generation functions
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def _push_array_dims(self, lines:List[str], node:AddressableNode):
|
||||
if not node.is_array:
|
||||
return
|
||||
|
||||
# Collect strides for each array dimension
|
||||
current_stride = node.array_stride
|
||||
strides = []
|
||||
for dim in reversed(node.array_dimensions):
|
||||
strides.append(current_stride)
|
||||
current_stride *= dim
|
||||
strides.reverse()
|
||||
|
||||
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):
|
||||
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 _get_strobe_str(self, node:AddressableNode) -> str:
|
||||
path = node.get_rel_path(self.top_node, array_suffix="[!]", empty_array_suffix="[!]")
|
||||
|
||||
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)
|
||||
strb = "access_strb." + path
|
||||
return strb
|
||||
|
||||
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_strobe_str(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
peakrdl/regblock/cpuif/__init__.py
Normal file
1
peakrdl/regblock/cpuif/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .base import CpuifBase
|
||||
33
peakrdl/regblock/cpuif/apb4/__init__.py
Normal file
33
peakrdl/regblock/cpuif/apb4/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from ..base import CpuifBase
|
||||
|
||||
class APB4_Cpuif(CpuifBase):
|
||||
template_path = "cpuif/apb4/apb4_tmpl.sv"
|
||||
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
return "apb4_intf.slave s_apb"
|
||||
|
||||
def signal(self, name:str) -> str:
|
||||
return "s_apb." + name
|
||||
|
||||
|
||||
class APB4_Cpuif_flattened(APB4_Cpuif):
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
# TODO: Reference data/addr width from verilog parameter perhaps?
|
||||
lines = [
|
||||
"input wire %s" % self.signal("psel"),
|
||||
"input wire %s" % self.signal("penable"),
|
||||
"input wire %s" % self.signal("pwrite"),
|
||||
"input wire %s" % self.signal("pprot"),
|
||||
"input wire [%d-1:0] %s" % (self.addr_width, self.signal("paddr")),
|
||||
"input wire [%d-1:0] %s" % (self.data_width, self.signal("pwdata")),
|
||||
"input wire [%d-1:0] %s" % (self.data_width / 8, self.signal("pstrb")),
|
||||
"output logic %s" % self.signal("pready"),
|
||||
"output logic [%d-1:0] %s" % (self.data_width, self.signal("prdata")),
|
||||
"output logic %s" % self.signal("pslverr"),
|
||||
]
|
||||
return ",\n".join(lines)
|
||||
|
||||
def signal(self, name:str) -> str:
|
||||
return "s_apb_" + name
|
||||
40
peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv
Normal file
40
peakrdl/regblock/cpuif/apb4/apb4_tmpl.sv
Normal file
@@ -0,0 +1,40 @@
|
||||
{% extends "cpuif/base_tmpl.sv" %}
|
||||
{%- import "utils_tmpl.sv" as utils with context %}
|
||||
|
||||
{% block body %}
|
||||
// Request
|
||||
logic is_active;
|
||||
{%- call utils.AlwaysFF(cpuif_reset) %}
|
||||
if({{cpuif_reset.activehigh_identifier}}) begin
|
||||
is_active <= '0;
|
||||
cpuif_req <= '0;
|
||||
cpuif_req_is_wr <= '0;
|
||||
cpuif_addr <= '0;
|
||||
cpuif_wr_data <= '0;
|
||||
cpuif_wr_bitstrb <= '0;
|
||||
end else begin
|
||||
if(~is_active) begin
|
||||
if({{cpuif.signal("psel")}}) begin
|
||||
is_active <= '1;
|
||||
cpuif_req <= '1;
|
||||
cpuif_req_is_wr <= {{cpuif.signal("pwrite")}};
|
||||
cpuif_addr <= {{cpuif.signal("paddr")}}[ADDR_WIDTH-1:0];
|
||||
cpuif_wr_data <= {{cpuif.signal("pwdata")}};
|
||||
for(int i=0; i<DATA_WIDTH/8; i++) begin
|
||||
cpuif_wr_bitstrb[i*8 +: 8] <= {{"{8{"}}{{cpuif.signal("pstrb")}}[i]{{"}}"}};
|
||||
end
|
||||
end
|
||||
end else begin
|
||||
cpuif_req <= '0;
|
||||
if(cpuif_rd_ack || cpuif_wr_ack) begin
|
||||
is_active <= '0;
|
||||
end
|
||||
end
|
||||
end
|
||||
{%- endcall %}
|
||||
|
||||
// Response
|
||||
assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack;
|
||||
assign {{cpuif.signal("prdata")}} = cpuif_rd_data;
|
||||
assign {{cpuif.signal("pslverr")}} = cpuif_rd_err | cpuif_wr_err;
|
||||
{%- endblock body%}
|
||||
31
peakrdl/regblock/cpuif/base.py
Normal file
31
peakrdl/regblock/cpuif/base.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import jinja2
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..exporter import RegblockExporter
|
||||
from ..signals import SignalBase
|
||||
|
||||
class CpuifBase:
|
||||
template_path = "cpuif/base_tmpl.sv"
|
||||
|
||||
def __init__(self, exporter:'RegblockExporter', cpuif_reset:'SignalBase', data_width:int=32, addr_width:int=32):
|
||||
self.exporter = exporter
|
||||
self.cpuif_reset = cpuif_reset
|
||||
self.data_width = data_width
|
||||
self.addr_width = addr_width
|
||||
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_implementation(self) -> str:
|
||||
context = {
|
||||
"cpuif": self,
|
||||
"cpuif_reset": self.cpuif_reset,
|
||||
"data_width": self.data_width,
|
||||
"addr_width": self.addr_width,
|
||||
}
|
||||
|
||||
template = self.exporter.jj_env.get_template(self.template_path)
|
||||
return template.render(context)
|
||||
6
peakrdl/regblock/cpuif/base_tmpl.sv
Normal file
6
peakrdl/regblock/cpuif/base_tmpl.sv
Normal file
@@ -0,0 +1,6 @@
|
||||
begin
|
||||
{%- filter indent %}
|
||||
{%- block body %}
|
||||
{%- endblock %}
|
||||
{%- endfilter %}
|
||||
end
|
||||
77
peakrdl/regblock/dereferencer.py
Normal file
77
peakrdl/regblock/dereferencer.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from typing import TYPE_CHECKING, Union
|
||||
from systemrdl.node import Node, FieldNode, SignalNode, RegNode
|
||||
from systemrdl.rdltypes import PropertyReference
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .exporter import RegblockExporter
|
||||
from .hwif.base import HwifBase
|
||||
from .field_logic import FieldLogic
|
||||
|
||||
class Dereferencer:
|
||||
"""
|
||||
This class provides an interface to convert conceptual SystemRDL references
|
||||
into Verilog identifiers
|
||||
"""
|
||||
def __init__(self, exporter:'RegblockExporter', hwif:'HwifBase', field_logic: "FieldLogic", top_node:Node):
|
||||
self.exporter = exporter
|
||||
self.hwif = hwif
|
||||
self.field_logic = field_logic
|
||||
self.top_node = top_node
|
||||
|
||||
def get_value(self, obj: Union[int, FieldNode, SignalNode, PropertyReference]) -> str:
|
||||
"""
|
||||
Returns the Verilog string that represents the value associated with the object.
|
||||
|
||||
If given a simple scalar value, then the corresponding Verilog literal is returned.
|
||||
|
||||
If obj references a structural systemrdl object, then the corresponding Verilog
|
||||
expression is returned that represents its value.
|
||||
"""
|
||||
if isinstance(obj, int):
|
||||
# Is a simple scalar value
|
||||
return f"'h{obj:x}"
|
||||
|
||||
elif isinstance(obj, FieldNode):
|
||||
if obj.implements_storage:
|
||||
return self.field_logic.get_storage_identifier(obj)
|
||||
|
||||
if self.hwif.has_value_input(obj):
|
||||
return self.hwif.get_input_identifier(obj)
|
||||
|
||||
# Field does not have a storage element, nor does it have a HW input
|
||||
# must be a constant value as defined by its reset value
|
||||
reset_value = obj.get_property('reset')
|
||||
if reset_value is not None:
|
||||
return f"'h{reset_value:x}"
|
||||
else:
|
||||
# No reset value defined!
|
||||
# Fall back to a value of 0
|
||||
return "'h0"
|
||||
|
||||
elif isinstance(obj, SignalNode):
|
||||
# Signals are always inputs from the hwif
|
||||
return self.hwif.get_input_identifier(obj)
|
||||
|
||||
elif isinstance(obj, PropertyReference):
|
||||
# TODO: Table G1 describes other possible ref targets
|
||||
|
||||
# Value reduction properties
|
||||
val = self.get_value(obj.node)
|
||||
if obj.name == "anded":
|
||||
return f"&({val})"
|
||||
elif obj.name == "ored":
|
||||
return f"|({val})"
|
||||
elif obj.name == "xored":
|
||||
return f"^({val})"
|
||||
else:
|
||||
raise RuntimeError
|
||||
|
||||
else:
|
||||
raise RuntimeError
|
||||
|
||||
def get_access_strobe(self, reg: RegNode) -> str:
|
||||
"""
|
||||
Returns the Verilog string that represents the register's access strobe
|
||||
"""
|
||||
# TODO: Implement me
|
||||
raise NotImplementedError
|
||||
106
peakrdl/regblock/exporter.py
Normal file
106
peakrdl/regblock/exporter.py
Normal file
@@ -0,0 +1,106 @@
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import jinja2 as jj
|
||||
from systemrdl.node import Node, 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 .cpuif.apb4 import APB4_Cpuif
|
||||
from .hwif.struct import StructHwif
|
||||
|
||||
class RegblockExporter:
|
||||
def __init__(self, **kwargs):
|
||||
user_template_dir = kwargs.pop("user_template_dir", None)
|
||||
|
||||
# Check for stray kwargs
|
||||
if kwargs:
|
||||
raise TypeError("got an unexpected keyword argument '%s'" % list(kwargs.keys())[0])
|
||||
|
||||
if user_template_dir:
|
||||
loader = jj.ChoiceLoader([
|
||||
jj.FileSystemLoader(user_template_dir),
|
||||
jj.FileSystemLoader(os.path.dirname(__file__)),
|
||||
jj.PrefixLoader({
|
||||
'user': jj.FileSystemLoader(user_template_dir),
|
||||
'base': jj.FileSystemLoader(os.path.dirname(__file__)),
|
||||
}, delimiter=":")
|
||||
])
|
||||
else:
|
||||
loader = jj.ChoiceLoader([
|
||||
jj.FileSystemLoader(os.path.dirname(__file__)),
|
||||
jj.PrefixLoader({
|
||||
'base': jj.FileSystemLoader(os.path.dirname(__file__)),
|
||||
}, delimiter=":")
|
||||
])
|
||||
|
||||
self.jj_env = jj.Environment(
|
||||
loader=loader,
|
||||
undefined=jj.StrictUndefined,
|
||||
)
|
||||
|
||||
|
||||
def export(self, node:Node, output_path:str, **kwargs):
|
||||
# If it is the root node, skip to top addrmap
|
||||
if isinstance(node, RootNode):
|
||||
node = node.top
|
||||
|
||||
cpuif_cls = kwargs.pop("cpuif_cls", APB4_Cpuif)
|
||||
hwif_cls = kwargs.pop("hwif_cls", StructHwif)
|
||||
module_name = kwargs.pop("module_name", node.inst_name)
|
||||
package_name = kwargs.pop("package_name", module_name + "_pkg")
|
||||
|
||||
# Check for stray kwargs
|
||||
if kwargs:
|
||||
raise TypeError("got an unexpected keyword argument '%s'" % list(kwargs.keys())[0])
|
||||
|
||||
|
||||
|
||||
# TODO: Scan design...
|
||||
|
||||
# TODO: derive this from somewhere
|
||||
cpuif_reset = InferredSignal("rst")
|
||||
reset_signals = [cpuif_reset]
|
||||
|
||||
cpuif = cpuif_cls(
|
||||
self,
|
||||
cpuif_reset=cpuif_reset, # TODO:
|
||||
data_width=32, # TODO:
|
||||
addr_width=32 # TODO:
|
||||
)
|
||||
|
||||
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, hwif, field_logic, node)
|
||||
|
||||
# Build Jinja template context
|
||||
context = {
|
||||
"module_name": module_name,
|
||||
"data_width": 32, # TODO:
|
||||
"addr_width": 32, # TODO:
|
||||
"reset_signals": reset_signals,
|
||||
"cpuif_reset": cpuif_reset,
|
||||
"user_signals": [], # TODO:
|
||||
"interrupts": [], # TODO:
|
||||
"cpuif": cpuif,
|
||||
"hwif": hwif,
|
||||
"address_decode": address_decode,
|
||||
"field_logic": field_logic,
|
||||
"readback_mux": readback_mux,
|
||||
}
|
||||
|
||||
# Write out design
|
||||
template = self.jj_env.get_template("module_tmpl.sv")
|
||||
stream = template.stream(context)
|
||||
stream.dump(output_path)
|
||||
92
peakrdl/regblock/field_logic/__init__.py
Normal file
92
peakrdl/regblock/field_logic/__init__.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
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):
|
||||
self.exporter = exporter
|
||||
self.top_node = top_node
|
||||
|
||||
self._indent_level = 0
|
||||
|
||||
def get_storage_struct(self) -> str:
|
||||
lines = []
|
||||
self._do_struct(lines, self.top_node, is_top=True)
|
||||
|
||||
# Only declare the storage struct if it exists
|
||||
if lines:
|
||||
lines.append(f"{self._indent}field_storage_t field_storage;")
|
||||
return "\n".join(lines)
|
||||
|
||||
def get_implementation(self) -> str:
|
||||
return "TODO:"
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Field utility functions
|
||||
#---------------------------------------------------------------------------
|
||||
def get_storage_identifier(self, obj: FieldNode):
|
||||
assert obj.implements_storage
|
||||
|
||||
return "TODO: implement get_storage_identifier()"
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 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)};")
|
||||
1
peakrdl/regblock/hwif/__init__.py
Normal file
1
peakrdl/regblock/hwif/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .base import HwifBase
|
||||
82
peakrdl/regblock/hwif/base.py
Normal file
82
peakrdl/regblock/hwif/base.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from typing import TYPE_CHECKING, Union
|
||||
from systemrdl.node import Node, SignalNode, FieldNode
|
||||
from systemrdl.rdltypes import AccessType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..exporter import RegblockExporter
|
||||
|
||||
class HwifBase:
|
||||
"""
|
||||
Defines how the hardware input/output signals are generated:
|
||||
- Field outputs
|
||||
- Field inputs
|
||||
- Signal inputs (except those that are promoted to the top)
|
||||
"""
|
||||
|
||||
def __init__(self, exporter:'RegblockExporter', top_node:'Node', package_name:str):
|
||||
self.exporter = exporter
|
||||
self.top_node = top_node
|
||||
self.package_name = package_name
|
||||
|
||||
|
||||
def get_package_declaration(self) -> str:
|
||||
"""
|
||||
If this hwif requires a package, generate the string
|
||||
"""
|
||||
return ""
|
||||
|
||||
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
"""
|
||||
Returns the declaration string for all I/O ports in the hwif group
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# hwif utility functions
|
||||
#---------------------------------------------------------------------------
|
||||
def has_value_input(self, obj: Union[FieldNode, SignalNode]) -> bool:
|
||||
"""
|
||||
Returns True if the object infers an input wire in the hwif
|
||||
"""
|
||||
if isinstance(obj, FieldNode):
|
||||
return obj.get_property("hw") in {AccessType.rw, AccessType.w}
|
||||
elif isinstance(obj, SignalNode):
|
||||
raise NotImplementedError # TODO:
|
||||
else:
|
||||
raise RuntimeError
|
||||
|
||||
|
||||
def has_value_output(self, obj: FieldNode) -> bool:
|
||||
"""
|
||||
Returns True if the object infers an output wire in the hwif
|
||||
"""
|
||||
return obj.get_property("hw") in {AccessType.rw, AccessType.r}
|
||||
|
||||
|
||||
def get_input_identifier(self, obj) -> str:
|
||||
"""
|
||||
Returns the identifier string that best represents the input object.
|
||||
|
||||
if obj is:
|
||||
Field: the fields input value port
|
||||
Signal: signal input value
|
||||
TODO: finish this
|
||||
|
||||
raises an exception if obj is invalid
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def get_output_identifier(self, obj) -> str:
|
||||
"""
|
||||
Returns the identifier string that best represents the output object.
|
||||
|
||||
if obj is:
|
||||
Field: the fields output value port
|
||||
TODO: finish this
|
||||
|
||||
raises an exception if obj is invalid
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
182
peakrdl/regblock/hwif/struct.py
Normal file
182
peakrdl/regblock/hwif/struct.py
Normal file
@@ -0,0 +1,182 @@
|
||||
from typing import Union, List, TYPE_CHECKING
|
||||
|
||||
from systemrdl.node import Node, AddressableNode, FieldNode, SignalNode
|
||||
|
||||
from .base import HwifBase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..exporter import RegblockExporter
|
||||
|
||||
class StructHwif(HwifBase):
|
||||
|
||||
def __init__(self, exporter:'RegblockExporter', top_node:Node, package_name:str):
|
||||
super().__init__(exporter, top_node, package_name)
|
||||
|
||||
self.has_input_struct = None
|
||||
self.has_output_struct = None
|
||||
self._indent_level = 0
|
||||
|
||||
|
||||
def get_package_declaration(self) -> str:
|
||||
lines = []
|
||||
|
||||
lines.append(f"package {self.package_name};")
|
||||
self._indent_level += 1
|
||||
self.has_input_struct = self._do_struct_addressable(lines, self.top_node, is_input=True)
|
||||
self.has_output_struct = self._do_struct_addressable(lines, self.top_node, is_input=False)
|
||||
self._indent_level -= 1
|
||||
lines.append("")
|
||||
lines.append(f"endpackage")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
"""
|
||||
Returns the declaration string for all I/O ports in the hwif group
|
||||
"""
|
||||
|
||||
# Assume get_package_declaration() is always called prior to this
|
||||
assert self.has_input_struct is not None
|
||||
assert self.has_output_struct is not None
|
||||
|
||||
lines = []
|
||||
if self.has_input_struct:
|
||||
lines.append(f"input {self.package_name}::{self._get_struct_name(self.top_node, is_input=True)} hwif_in")
|
||||
if self.has_output_struct:
|
||||
lines.append(f"output {self.package_name}::{self._get_struct_name(self.top_node, is_input=False)} hwif_out")
|
||||
|
||||
return ",\n".join(lines)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Struct generation functions
|
||||
#---------------------------------------------------------------------------
|
||||
@property
|
||||
def _indent(self) -> str:
|
||||
return " " * self._indent_level
|
||||
|
||||
def _get_node_array_suffix(self, node:AddressableNode) -> str:
|
||||
if node.is_array:
|
||||
return "".join([f'[{dim}]' for dim in node.array_dimensions])
|
||||
return ""
|
||||
|
||||
def _get_struct_name(self, node:Node, is_input:bool = True) -> str:
|
||||
base = node.get_rel_path(
|
||||
self.top_node.parent,
|
||||
hier_separator="__",
|
||||
array_suffix="x",
|
||||
empty_array_suffix="x"
|
||||
)
|
||||
if is_input:
|
||||
return f'{base}_in_t'
|
||||
return f'{base}__out_t'
|
||||
|
||||
def _do_struct_addressable(self, lines:list, node:AddressableNode, is_input:bool = True) -> bool:
|
||||
|
||||
struct_children = []
|
||||
|
||||
# Generate structs for children first
|
||||
for child in node.children():
|
||||
if isinstance(child, AddressableNode):
|
||||
if self._do_struct_addressable(lines, child, is_input):
|
||||
struct_children.append(child)
|
||||
elif isinstance(child, FieldNode):
|
||||
if self._do_struct_field(lines, child, is_input):
|
||||
struct_children.append(child)
|
||||
elif is_input and isinstance(child, SignalNode):
|
||||
# No child struct needed here
|
||||
# TODO: Skip if this is a top-level child
|
||||
struct_children.append(child)
|
||||
|
||||
# Generate this addressable node's struct
|
||||
if struct_children:
|
||||
lines.append("")
|
||||
lines.append(f"{self._indent}// {node.get_rel_path(self.top_node.parent)}")
|
||||
lines.append(f"{self._indent}typedef struct {{")
|
||||
self._indent_level += 1
|
||||
for child in struct_children:
|
||||
if isinstance(child, AddressableNode):
|
||||
lines.append(f"{self._indent}{self._get_struct_name(child, is_input)} {child.inst_name}{self._get_node_array_suffix(child)};")
|
||||
elif isinstance(child, FieldNode):
|
||||
lines.append(f"{self._indent}{self._get_struct_name(child, is_input)} {child.inst_name};")
|
||||
elif isinstance(child, SignalNode):
|
||||
if child.width == 1:
|
||||
lines.append(f"{self._indent}logic {child.inst_name};")
|
||||
else:
|
||||
lines.append(f"{self._indent}logic [{child.msb}:{child.lsb}] {child.inst_name};")
|
||||
|
||||
self._indent_level -= 1
|
||||
lines.append(f"{self._indent}}} {self._get_struct_name(node, is_input)};")
|
||||
|
||||
return bool(struct_children)
|
||||
|
||||
def _do_struct_field(self, lines:list, node:FieldNode, is_input:bool = True) -> bool:
|
||||
contents = []
|
||||
|
||||
if is_input:
|
||||
contents = self._get_struct_input_field_contents(node)
|
||||
else:
|
||||
contents = self._get_struct_output_field_contents(node)
|
||||
|
||||
if contents:
|
||||
lines.append("")
|
||||
lines.append(f"{self._indent}// {node.get_rel_path(self.top_node.parent)}")
|
||||
lines.append(f"{self._indent}typedef struct {{")
|
||||
self._indent_level += 1
|
||||
for member in contents:
|
||||
lines.append(self._indent + member)
|
||||
self._indent_level -= 1
|
||||
lines.append(f"{self._indent}}} {self._get_struct_name(node, is_input)};")
|
||||
|
||||
return bool(contents)
|
||||
|
||||
def _get_struct_input_field_contents(self, node:FieldNode) -> List[str]:
|
||||
contents = []
|
||||
|
||||
# Provide input to field's value if it is writable by hw
|
||||
if self.has_value_input(node):
|
||||
if node.width == 1:
|
||||
contents.append("logic value;")
|
||||
else:
|
||||
contents.append(f"logic [{node.width-1}:0] value;")
|
||||
|
||||
# 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
|
||||
"""
|
||||
|
||||
return contents
|
||||
|
||||
def _get_struct_output_field_contents(self, node:FieldNode) -> List[str]:
|
||||
contents = []
|
||||
|
||||
# Expose field's value if it is readable by hw
|
||||
if self.has_value_output(node):
|
||||
if node.width == 1:
|
||||
contents.append("logic value;")
|
||||
else:
|
||||
contents.append(f"logic [{node.width-1}:0] value;")
|
||||
|
||||
# TODO:
|
||||
"""
|
||||
bitwise reductions
|
||||
if anded, ored, xored == True, output a signal
|
||||
swmod/swacc
|
||||
event strobes
|
||||
Are there was_written/was_read strobes too?
|
||||
"""
|
||||
|
||||
return contents
|
||||
90
peakrdl/regblock/module_tmpl.sv
Normal file
90
peakrdl/regblock/module_tmpl.sv
Normal file
@@ -0,0 +1,90 @@
|
||||
{%- import "utils_tmpl.sv" as utils with context -%}
|
||||
|
||||
{{hwif.get_package_declaration()}}
|
||||
|
||||
module {{module_name}} #(
|
||||
// TODO: pipeline parameters
|
||||
)(
|
||||
input wire clk,
|
||||
{%- for signal in reset_signals %}
|
||||
{{signal.port_declaration}},
|
||||
{% endfor %}
|
||||
|
||||
{%- for signal in user_signals %}
|
||||
{{signal.port_declaration}},
|
||||
{% endfor %}
|
||||
|
||||
{%- for interrupt in interrupts %}
|
||||
{{interrupt.port_declaration}},
|
||||
{% endfor %}
|
||||
|
||||
{{cpuif.port_declaration|indent(8)}},
|
||||
|
||||
{{hwif.port_declaration|indent(8)}}
|
||||
);
|
||||
|
||||
localparam ADDR_WIDTH = {{addr_width}};
|
||||
localparam DATA_WIDTH = {{data_width}};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// CPU Bus interface logic
|
||||
//--------------------------------------------------------------------------
|
||||
logic cpuif_req;
|
||||
logic cpuif_req_is_wr;
|
||||
logic [ADDR_WIDTH-1:0] cpuif_addr;
|
||||
logic [DATA_WIDTH-1:0] cpuif_wr_data;
|
||||
logic [DATA_WIDTH-1:0] cpuif_wr_bitstrb;
|
||||
|
||||
logic cpuif_rd_ack;
|
||||
logic [DATA_WIDTH-1:0] cpuif_rd_data;
|
||||
logic cpuif_rd_err;
|
||||
|
||||
logic cpuif_wr_ack;
|
||||
logic cpuif_wr_err;
|
||||
|
||||
{{cpuif.get_implementation()|indent}}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Address Decode
|
||||
//--------------------------------------------------------------------------
|
||||
{{address_decode.get_strobe_struct()|indent}}
|
||||
access_strb_t access_strb;
|
||||
|
||||
always_comb begin
|
||||
{{address_decode.get_implementation()|indent(8)}}
|
||||
end
|
||||
|
||||
// Writes are always posted with no error response
|
||||
assign cpuif_wr_ack = cpuif_req & cpuif_req_is_wr;
|
||||
assign cpuif_wr_err = '0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Field logic
|
||||
//--------------------------------------------------------------------------
|
||||
{{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}}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Readback mux
|
||||
//--------------------------------------------------------------------------
|
||||
logic readback_err;
|
||||
logic readback_done;
|
||||
logic [DATA_WIDTH-1:0] readback_data;
|
||||
|
||||
{{readback_mux.get_implementation()|indent}}
|
||||
|
||||
{%- call utils.AlwaysFF(cpuif_reset) %}
|
||||
if({{cpuif_reset.activehigh_identifier}}) begin
|
||||
cpuif_rd_ack <= '0;
|
||||
cpuif_rd_data <= '0;
|
||||
cpuif_rd_err <= '0;
|
||||
end else begin
|
||||
cpuif_rd_ack <= readback_done;
|
||||
cpuif_rd_data <= readback_data;
|
||||
cpuif_rd_err <= readback_err;
|
||||
end
|
||||
{%- endcall %}
|
||||
|
||||
endmodule
|
||||
29
peakrdl/regblock/readback_mux.py
Normal file
29
peakrdl/regblock/readback_mux.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import re
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from systemrdl.node import Node, AddressableNode, RegNode
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .exporter import RegblockExporter
|
||||
|
||||
class ReadbackMux:
|
||||
def __init__(self, exporter:'RegblockExporter', top_node:AddressableNode):
|
||||
self.exporter = exporter
|
||||
self.top_node = top_node
|
||||
|
||||
self._indent_level = 0
|
||||
|
||||
|
||||
def get_implementation(self) -> str:
|
||||
# TODO: Count the number of readable registers
|
||||
# TODO: Emit the declaration for the readback array
|
||||
# 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
|
||||
14
peakrdl/regblock/scan_design.py
Normal file
14
peakrdl/regblock/scan_design.py
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
"""
|
||||
- Signal References
|
||||
Collect any references to signals that lie outside of the hierarchy
|
||||
These will be added as top-level signals
|
||||
- top-level interrupts
|
||||
|
||||
|
||||
Validate:
|
||||
- Error if a property references a non-signal component, or property reference from
|
||||
outside the export hierarchy
|
||||
- No Mem components allowed
|
||||
- Uniform regwidth, accesswidth, etc.
|
||||
"""
|
||||
90
peakrdl/regblock/signals.py
Normal file
90
peakrdl/regblock/signals.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from systemrdl import SignalNode
|
||||
|
||||
class SignalBase:
|
||||
|
||||
@property
|
||||
def is_async(self) -> bool:
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def is_activehigh(self) -> bool:
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def activehigh_identifier(self) -> str:
|
||||
"""
|
||||
Normalizes the identifier reference to be active-high logic
|
||||
"""
|
||||
if not self.is_activehigh:
|
||||
return "~%s" % self.identifier
|
||||
return self.identifier
|
||||
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
"""
|
||||
Returns the port delcaration text for this signal.
|
||||
In the context of this exporter, all signal objects happen to be inputs.
|
||||
"""
|
||||
if self.width > 1:
|
||||
return "input wire [%d:0] %s" % (self.width - 1, self.identifier)
|
||||
return "input wire %s" % self.identifier
|
||||
|
||||
|
||||
class RDLSignal(SignalBase):
|
||||
"""
|
||||
Wrapper around a SystemRDL signal object
|
||||
"""
|
||||
def __init__(self, rdl_signal:'SignalNode'):
|
||||
self.rdl_signal = rdl_signal
|
||||
|
||||
@property
|
||||
def is_async(self) -> bool:
|
||||
return self.rdl_signal.get_property("async")
|
||||
|
||||
@property
|
||||
def is_activehigh(self) -> bool:
|
||||
return self.rdl_signal.get_property("activehigh")
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return self.rdl_signal.width
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
# TODO: uniquify this somehow
|
||||
# TODO: Deal with different hierarchies
|
||||
return "TODO_%s" % self.rdl_signal.inst_name
|
||||
|
||||
|
||||
class InferredSignal(SignalBase):
|
||||
def __init__(self, identifier:str, width:int=1, is_async:bool=False):
|
||||
self._identifier = identifier
|
||||
self._width = width
|
||||
self._is_async = is_async
|
||||
|
||||
@property
|
||||
def is_async(self) -> bool:
|
||||
return self._is_async
|
||||
|
||||
@property
|
||||
def is_activehigh(self) -> bool:
|
||||
return True
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return self._width
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
return self._identifier
|
||||
16
peakrdl/regblock/utils_tmpl.sv
Normal file
16
peakrdl/regblock/utils_tmpl.sv
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
/*
|
||||
* 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 %}
|
||||
Reference in New Issue
Block a user