344 lines
14 KiB
Python
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
|