basic framework

This commit is contained in:
Alex Mykyta
2021-06-01 21:51:24 -07:00
parent 292aec1c6e
commit 0d5b663f98
40 changed files with 1920 additions and 0 deletions

View File

@@ -0,0 +1 @@
__version__ = "0.1.0"

View File

@@ -0,0 +1,3 @@
from .__about__ import __version__
from .exporter import RegblockExporter

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

View File

@@ -0,0 +1 @@
from .base import CpuifBase

View 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

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

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

View File

@@ -0,0 +1,6 @@
begin
{%- filter indent %}
{%- block body %}
{%- endblock %}
{%- endfilter %}
end

View 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

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

View 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)};")

View File

@@ -0,0 +1 @@
from .base import HwifBase

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

View 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

View 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

View 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

View 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.
"""

View 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

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