Files
PeakRDL-regblock/src/peakrdl_regblock/exporter.py

344 lines
14 KiB
Python

import os
from typing import TYPE_CHECKING, Union, Any, Type, Optional, Set, List
from collections import OrderedDict
import jinja2 as jj
from systemrdl.node import AddrmapNode, RootNode
from .addr_decode import AddressDecode
from .field_logic import FieldLogic
from .dereferencer import Dereferencer
from .readback import Readback
from .identifier_filter import kw_filter as kwf
from .utils import clog2
from .scan_design import DesignScanner
from .validate_design import DesignValidator
from .cpuif import CpuifBase
from .cpuif.apb4 import APB4_Cpuif
from .hwif import Hwif
from .write_buffering import WriteBuffering
from .read_buffering import ReadBuffering
from .external_acks import ExternalWriteAckGenerator, ExternalReadAckGenerator
from .parity import ParityErrorReduceGenerator
from .sv_int import SVInt
if TYPE_CHECKING:
from systemrdl.node import SignalNode
from systemrdl.rdltypes import UserEnum
class RegblockExporter:
hwif: Hwif
cpuif: CpuifBase
address_decode: AddressDecode
field_logic: FieldLogic
readback: Readback
write_buffering: WriteBuffering
read_buffering: ReadBuffering
dereferencer: Dereferencer
ds: 'DesignState'
def __init__(self, **kwargs: Any) -> None:
# Check for stray kwargs
if kwargs:
raise TypeError(f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'")
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: Union[RootNode, AddrmapNode], output_dir:str, **kwargs: Any) -> None:
"""
Parameters
----------
node: AddrmapNode
Top-level SystemRDL node to export.
output_dir: str
Path to the output directory where generated SystemVerilog will be written.
Output includes two files: a module definition and package definition.
cpuif_cls: :class:`peakrdl_regblock.cpuif.CpuifBase`
Specify the class type that implements the CPU interface of your choice.
Defaults to AMBA APB4.
module_name: str
Override the SystemVerilog module name. By default, the module name
is the top-level node's name.
package_name: str
Override the SystemVerilog package name. By default, the package name
is the top-level node's name with a "_pkg" suffix.
reuse_hwif_typedefs: bool
By default, the exporter will attempt to re-use hwif struct definitions for
nodes that are equivalent. This allows for better modularity and type reuse.
Struct type names are derived using the SystemRDL component's type
name and declared lexical scope path.
If this is not desireable, override this parameter to ``False`` and structs
will be generated more naively using their hierarchical paths.
retime_read_fanin: bool
Set this to ``True`` to enable additional read path retiming.
For large register blocks that operate at demanding clock rates, this
may be necessary in order to manage large readback fan-in.
The retiming flop stage is automatically placed in the most optimal point in the
readback path so that logic-levels and fanin are minimized.
Enabling this option will increase read transfer latency by 1 clock cycle.
retime_read_response: bool
Set this to ``True`` to enable an additional retiming flop stage between
the readback mux and the CPU interface response logic.
This option may be beneficial for some CPU interfaces that implement the
response logic fully combinationally. Enabling this stage can better
isolate timing paths in the register file from the rest of your system.
Enabling this when using CPU interfaces that already implement the
response path sequentially may not result in any meaningful timing improvement.
Enabling this option will increase read transfer latency by 1 clock cycle.
retime_external_reg: bool
Retime outputs to external ``reg`` components.
retime_external_regfile: bool
Retime outputs to external ``regfile`` components.
retime_external_mem: bool
Retime outputs to external ``mem`` components.
retime_external_addrmap: bool
Retime outputs to external ``addrmap`` components.
generate_hwif_report: bool
If set, generates a hwif report that can help designers understand
the contents of the ``hwif_in`` and ``hwif_out`` structures.
address_width: int
Override the CPU interface's address width. By default, address width
is sized to the contents of the regblock.
default_reset_activelow: bool
If overriden to True, default reset is active-low instead of active-high.
default_reset_async: bool
If overriden to True, default reset is asynchronous instead of synchronous.
err_if_bad_addr: bool
If overriden to True: If the address is decoded incorrectly, the CPUIF response
signal shows an error. For example: APB.PSLVERR = 1'b1, AXI4LITE.*RESP = 2'b10.
err_if_bad_rw: bool
If overriden to True: If an illegal access is performed to a read-only or write-only
register, the CPUIF response signal shows an error. For example: APB.PSLVERR = 1'b1,
AXI4LITE.*RESP = 2'b10.
"""
# If it is the root node, skip to top addrmap
if isinstance(node, RootNode):
top_node = node.top
else:
top_node = node
self.ds = DesignState(top_node, kwargs)
cpuif_cls = kwargs.pop("cpuif_cls", None) or APB4_Cpuif # type: Type[CpuifBase]
generate_hwif_report = kwargs.pop("generate_hwif_report", False) # type: bool
# Check for stray kwargs
if kwargs:
raise TypeError(f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'")
if generate_hwif_report:
path = os.path.join(output_dir, f"{self.ds.module_name}_hwif.rpt")
hwif_report_file = open(path, "w", encoding='utf-8') # pylint: disable=consider-using-with
else:
hwif_report_file = None
# Construct exporter components
self.cpuif = cpuif_cls(self)
self.hwif = Hwif(self, hwif_report_file=hwif_report_file)
self.readback = Readback(self)
self.address_decode = AddressDecode(self)
self.field_logic = FieldLogic(self)
self.write_buffering = WriteBuffering(self)
self.read_buffering = ReadBuffering(self)
self.dereferencer = Dereferencer(self)
ext_write_acks = ExternalWriteAckGenerator(self)
ext_read_acks = ExternalReadAckGenerator(self)
parity = ParityErrorReduceGenerator(self)
# Validate that there are no unsupported constructs
DesignValidator(self).do_validate()
# Compute readback implementation early.
# Readback has the capability to disable retiming if the fanin is tiny.
# This affects the rest of the design's implementation, and must be known
# before any other templates are rendered
readback_implementation = self.readback.get_implementation()
# Build Jinja template context
context = {
"cpuif": self.cpuif,
"hwif": self.hwif,
"module_has_parameters": self.module_has_parameters,
"get_module_parameter_list": self.get_module_parameter_list,
"get_module_port_list": self.get_module_port_list,
"write_buffering": self.write_buffering,
"read_buffering": self.read_buffering,
"get_resetsignal": self.dereferencer.get_resetsignal,
"default_resetsignal_name": self.dereferencer.default_resetsignal_name,
"address_decode": self.address_decode,
"field_logic": self.field_logic,
"readback_implementation": readback_implementation,
"ext_write_acks": ext_write_acks,
"ext_read_acks": ext_read_acks,
"parity": parity,
"get_always_ff_event": self.dereferencer.get_always_ff_event,
"ds": self.ds,
"kwf": kwf,
"SVInt" : SVInt,
}
# Write out design
os.makedirs(output_dir, exist_ok=True)
package_file_path = os.path.join(output_dir, self.ds.package_name + ".sv")
template = self.jj_env.get_template("package_tmpl.sv")
stream = template.stream(context)
stream.dump(package_file_path)
module_file_path = os.path.join(output_dir, self.ds.module_name + ".sv")
template = self.jj_env.get_template("module_tmpl.sv")
stream = template.stream(context)
stream.dump(module_file_path)
if hwif_report_file:
hwif_report_file.close()
def module_has_parameters(self) -> bool:
return bool(self.cpuif.parameters)
def get_module_parameter_list(self) -> str:
return ",\n".join(self.cpuif.parameters)
def get_module_port_list(self) -> str:
groups = []
# Main clock & reset
clkrst = [
"input wire clk",
f"input wire {self.dereferencer.default_resetsignal_name}"
]
groups.append(",\n".join(clkrst))
# Signals that were declared outside of the hierarchy of the addrmap
# being exported
out_of_hier_signals = []
for signal in self.ds.out_of_hier_signals.values():
if signal.width == 1:
out_of_hier_signals.append(f"input wire {kwf(signal.inst_name)}")
else:
out_of_hier_signals.append(f"input wire [{signal.width - 1}:0] {kwf(signal.inst_name)}")
if out_of_hier_signals:
groups.append(",\n".join(out_of_hier_signals))
# Parity check error output
if self.ds.has_paritycheck:
groups.append("output logic parity_error")
# CPU interface ports
groups.append(self.cpuif.port_declaration)
if self.hwif.has_input_struct or self.hwif.has_output_struct:
groups.append(self.hwif.port_declaration)
return ",\n\n".join(groups)
class DesignState:
"""
Dumping ground for all sorts of variables that are relevant to a particular
design.
"""
def __init__(self, top_node: AddrmapNode, kwargs: Any) -> None:
self.top_node = top_node
msg = top_node.env.msg
#------------------------
# Extract compiler args
#------------------------
self.reuse_hwif_typedefs = kwargs.pop("reuse_hwif_typedefs", True) # type: bool
self.module_name = kwargs.pop("module_name", None) or kwf(self.top_node.inst_name) # type: str
self.package_name = kwargs.pop("package_name", None) or (self.module_name + "_pkg") # type: str
user_addr_width = kwargs.pop("address_width", None) # type: Optional[int]
# Pipelining options
self.retime_read_fanin = kwargs.pop("retime_read_fanin", False) # type: bool
self.retime_read_response = kwargs.pop("retime_read_response", False) # type: bool
self.retime_external_reg = kwargs.pop("retime_external_reg", False) # type: bool
self.retime_external_regfile = kwargs.pop("retime_external_regfile", False) # type: bool
self.retime_external_mem = kwargs.pop("retime_external_mem", False) # type: bool
self.retime_external_addrmap = kwargs.pop("retime_external_addrmap", False) # type: bool
# Default reset type
self.default_reset_activelow = kwargs.pop("default_reset_activelow", False) # type: bool
self.default_reset_async = kwargs.pop("default_reset_async", False) # type: bool
# Generating a cpuif error
self.err_if_bad_addr = kwargs.pop("err_if_bad_addr", False) # type: bool
self.err_if_bad_rw = kwargs.pop("err_if_bad_rw", False) # type: bool
#------------------------
# Info about the design
#------------------------
self.cpuif_data_width = 0
# Collections of signals that were actually referenced by the design
self.in_hier_signal_paths = set() # type: Set[str]
self.out_of_hier_signals = OrderedDict() # type: OrderedDict[str, SignalNode]
self.has_writable_msb0_fields = False
self.has_buffered_write_regs = False
self.has_buffered_read_regs = False
self.has_external_block = False
self.has_external_addressable = False
self.has_paritycheck = False
# Track any referenced enums
self.user_enums = [] # type: List[Type[UserEnum]]
# Scan the design to fill in above variables
DesignScanner(self).do_scan()
if self.cpuif_data_width == 0:
# Scanner did not find any registers in the design being exported,
# so the width is not known.
# Assume 32-bits
msg.warning(
"Addrmap being exported only contains external components. Unable to infer the CPUIF bus width. Assuming 32-bits.",
self.top_node.def_src_ref
)
self.cpuif_data_width = 32
#------------------------
# Min address width encloses the total size AND at least 1 useful address bit
self.addr_width = max(clog2(self.top_node.size), clog2(self.cpuif_data_width//8) + 1)
if user_addr_width is not None:
if user_addr_width < self.addr_width:
msg.fatal(f"User-specified address width shall be greater than or equal to {self.addr_width}.")
self.addr_width = user_addr_width
@property
def min_read_latency(self) -> int:
n = 0
if self.retime_read_fanin:
n += 1
if self.retime_read_response:
n += 1
return n
@property
def min_write_latency(self) -> int:
n = 0
return n