"updt"
This commit is contained in:
3
src/peakrdl_busdecoder/__init__.py
Normal file
3
src/peakrdl_busdecoder/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .exporter import BusDecoderExporter
|
||||
|
||||
__all__ = ["BusDecoderExporter"]
|
||||
120
src/peakrdl_busdecoder/__peakrdl__.py
Normal file
120
src/peakrdl_busdecoder/__peakrdl__.py
Normal file
@@ -0,0 +1,120 @@
|
||||
from typing import TYPE_CHECKING
|
||||
import functools
|
||||
|
||||
from peakrdl.plugins.exporter import ExporterSubcommandPlugin
|
||||
from peakrdl.config import schema
|
||||
from peakrdl.plugins.entry_points import get_entry_points
|
||||
|
||||
from .exporter import BusDecoderExporter
|
||||
from .cpuif import BaseCpuif, apb3, apb4
|
||||
from .udps import ALL_UDPS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import argparse
|
||||
from systemrdl.node import AddrmapNode
|
||||
|
||||
|
||||
class Exporter(ExporterSubcommandPlugin):
|
||||
short_desc = "Generate a SystemVerilog control/status register (CSR) block"
|
||||
|
||||
udp_definitions = ALL_UDPS
|
||||
|
||||
cfg_schema = {
|
||||
"cpuifs": {"*": schema.PythonObjectImport()},
|
||||
}
|
||||
|
||||
@functools.lru_cache()
|
||||
def get_cpuifs(self) -> dict[str, type[BaseCpuif]]:
|
||||
# All built-in CPUIFs
|
||||
cpuifs: dict[str, type[BaseCpuif]] = {
|
||||
# "passthrough": passthrough.PassthroughCpuif,
|
||||
"apb3": apb3.APB3Cpuif,
|
||||
"apb3-flat": apb3.APB3CpuifFlat,
|
||||
"apb4": apb4.APB4Cpuif,
|
||||
"apb4-flat": apb4.APB4CpuifFlat,
|
||||
# "axi4-lite": axi4lite.AXI4Lite_Cpuif,
|
||||
# "axi4-lite-flat": axi4lite.AXI4Lite_Cpuif_flattened,
|
||||
}
|
||||
|
||||
# Load any cpuifs specified via entry points
|
||||
for ep, _ in get_entry_points("peakrdl_busdecoder.cpuif"):
|
||||
name = ep.name
|
||||
cpuif = ep.load()
|
||||
if name in cpuifs:
|
||||
raise RuntimeError(
|
||||
f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it already exists"
|
||||
)
|
||||
if not issubclass(cpuif, BaseCpuif):
|
||||
raise RuntimeError(
|
||||
f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it not a BaseCpuif class"
|
||||
)
|
||||
cpuifs[name] = cpuif
|
||||
|
||||
# Load any CPUIFs via config import
|
||||
for name, cpuif in self.cfg["cpuifs"].items():
|
||||
if name in cpuifs:
|
||||
raise RuntimeError(
|
||||
f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it already exists"
|
||||
)
|
||||
if not issubclass(cpuif, BaseCpuif):
|
||||
raise RuntimeError(
|
||||
f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it not a BaseCpuif class"
|
||||
)
|
||||
cpuifs[name] = cpuif
|
||||
|
||||
return cpuifs
|
||||
|
||||
def add_exporter_arguments(self, arg_group: "argparse._ActionsContainer") -> None:
|
||||
cpuifs = self.get_cpuifs()
|
||||
|
||||
arg_group.add_argument(
|
||||
"--cpuif",
|
||||
choices=cpuifs.keys(),
|
||||
default="apb3",
|
||||
help="Select the CPU interface protocol to use [apb3]",
|
||||
)
|
||||
|
||||
arg_group.add_argument(
|
||||
"--module-name",
|
||||
metavar="NAME",
|
||||
default=None,
|
||||
help="Override the SystemVerilog module name",
|
||||
)
|
||||
|
||||
arg_group.add_argument(
|
||||
"--package-name",
|
||||
metavar="NAME",
|
||||
default=None,
|
||||
help="Override the SystemVerilog package name",
|
||||
)
|
||||
|
||||
arg_group.add_argument(
|
||||
"--addr-width",
|
||||
type=int,
|
||||
default=None,
|
||||
help="""Override the CPU interface's address width. By default,
|
||||
address width is sized to the contents of the busdecoder.
|
||||
""",
|
||||
)
|
||||
|
||||
arg_group.add_argument(
|
||||
"--unroll",
|
||||
action="store_true",
|
||||
help="""Unroll arrayed addressable nodes into separate instances in
|
||||
the CPU interface. By default, arrayed nodes are kept as arrays.
|
||||
""",
|
||||
)
|
||||
|
||||
def do_export(self, top_node: "AddrmapNode", options: "argparse.Namespace") -> None:
|
||||
cpuifs = self.get_cpuifs()
|
||||
|
||||
x = BusDecoderExporter()
|
||||
x.export(
|
||||
top_node,
|
||||
options.output,
|
||||
cpuif_cls=cpuifs[options.cpuif],
|
||||
module_name=options.module_name,
|
||||
package_name=options.package_name,
|
||||
address_width=options.addr_width,
|
||||
unroll=options.unroll,
|
||||
)
|
||||
162
src/peakrdl_busdecoder/addr_decode.py
Normal file
162
src/peakrdl_busdecoder/addr_decode.py
Normal file
@@ -0,0 +1,162 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from systemrdl.node import FieldNode, RegNode
|
||||
from systemrdl.walker import WalkerAction
|
||||
|
||||
from .utils import get_indexed_path
|
||||
from .forloop_generator import RDLForLoopGenerator
|
||||
from .sv_int import SVInt
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .exporter import BusDecoderExporter
|
||||
from systemrdl.node import AddrmapNode, AddressableNode
|
||||
|
||||
|
||||
class AddressDecode:
|
||||
def __init__(self, exp: "BusDecoderExporter"):
|
||||
self.exp = exp
|
||||
|
||||
@property
|
||||
def top_node(self) -> "AddrmapNode":
|
||||
return self.exp.ds.top_node
|
||||
|
||||
def get_implementation(self) -> str:
|
||||
gen = DecodeLogicGenerator(self)
|
||||
s = gen.get_content(self.top_node)
|
||||
assert s is not None
|
||||
return s
|
||||
|
||||
def get_access_strobe(
|
||||
self, node: RegNode | FieldNode, reduce_substrobes: bool = True
|
||||
) -> str:
|
||||
"""
|
||||
Returns the Verilog string that represents the register/field's access strobe.
|
||||
"""
|
||||
if isinstance(node, FieldNode):
|
||||
field = node
|
||||
path = get_indexed_path(self.top_node, node.parent)
|
||||
|
||||
regwidth = node.parent.get_property("regwidth")
|
||||
accesswidth = node.parent.get_property("accesswidth")
|
||||
if regwidth > accesswidth:
|
||||
# Is wide register.
|
||||
# Determine the substrobe(s) relevant to this field
|
||||
sidx_hi = field.msb // accesswidth
|
||||
sidx_lo = field.lsb // accesswidth
|
||||
if sidx_hi == sidx_lo:
|
||||
suffix = f"[{sidx_lo}]"
|
||||
else:
|
||||
suffix = f"[{sidx_hi}:{sidx_lo}]"
|
||||
path += suffix
|
||||
|
||||
if sidx_hi != sidx_lo and reduce_substrobes:
|
||||
return "|decoded_reg_strb." + path
|
||||
|
||||
else:
|
||||
path = get_indexed_path(self.top_node, node)
|
||||
|
||||
return "decoded_reg_strb." + path
|
||||
|
||||
def get_external_block_access_strobe(self, node: "AddressableNode") -> str:
|
||||
assert node.external
|
||||
assert not isinstance(node, RegNode)
|
||||
path = get_indexed_path(self.top_node, node)
|
||||
return "decoded_reg_strb." + path
|
||||
|
||||
|
||||
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: list[int] = []
|
||||
|
||||
def enter_AddressableComponent(
|
||||
self, node: "AddressableNode"
|
||||
) -> WalkerAction | None:
|
||||
super().enter_AddressableComponent(node)
|
||||
|
||||
if node.array_dimensions:
|
||||
assert node.array_stride is not None
|
||||
# Collect strides for each array dimension
|
||||
current_stride = node.array_stride
|
||||
strides: list[int] = []
|
||||
for dim in reversed(node.array_dimensions):
|
||||
strides.append(current_stride)
|
||||
current_stride *= dim
|
||||
strides.reverse()
|
||||
self._array_stride_stack.extend(strides)
|
||||
|
||||
if node.external and not isinstance(node, RegNode):
|
||||
# Is an external block
|
||||
addr_str = self._get_address_str(node)
|
||||
strb = self.addr_decode.get_external_block_access_strobe(node)
|
||||
rhs = f"cpuif_req_masked & (cpuif_addr >= {addr_str}) & (cpuif_addr <= {addr_str} + {SVInt(node.size - 1, self.addr_decode.exp.ds.addr_width)})"
|
||||
self.add_content(f"{strb} = {rhs};")
|
||||
self.add_content(f"is_external |= {rhs};")
|
||||
return WalkerAction.SkipDescendants
|
||||
|
||||
return WalkerAction.Continue
|
||||
|
||||
def _get_address_str(self, node: "AddressableNode", subword_offset: int = 0) -> str:
|
||||
expr_width = self.addr_decode.exp.ds.addr_width
|
||||
a = str(
|
||||
SVInt(
|
||||
node.raw_absolute_address
|
||||
- self.addr_decode.top_node.raw_absolute_address
|
||||
+ subword_offset,
|
||||
expr_width,
|
||||
)
|
||||
)
|
||||
for i, stride in enumerate(self._array_stride_stack):
|
||||
a += f" + ({expr_width})'(i{i}) * {SVInt(stride, expr_width)}"
|
||||
return a
|
||||
|
||||
def enter_Reg(self, node: RegNode) -> None:
|
||||
regwidth = node.get_property("regwidth")
|
||||
accesswidth = node.get_property("accesswidth")
|
||||
|
||||
if regwidth == accesswidth:
|
||||
rhs = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)})"
|
||||
s = f"{self.addr_decode.get_access_strobe(node)} = {rhs};"
|
||||
self.add_content(s)
|
||||
if node.external:
|
||||
readable = node.has_sw_readable
|
||||
writable = node.has_sw_writable
|
||||
if readable and writable:
|
||||
self.add_content(f"is_external |= {rhs};")
|
||||
elif readable and not writable:
|
||||
self.add_content(f"is_external |= {rhs} & !cpuif_req_is_wr;")
|
||||
elif not readable and writable:
|
||||
self.add_content(f"is_external |= {rhs} & cpuif_req_is_wr;")
|
||||
else:
|
||||
raise RuntimeError
|
||||
else:
|
||||
# Register is wide. Create a substrobe for each subword
|
||||
n_subwords = regwidth // accesswidth
|
||||
subword_stride = accesswidth // 8
|
||||
for i in range(n_subwords):
|
||||
rhs = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node, subword_offset=(i * subword_stride))})"
|
||||
s = f"{self.addr_decode.get_access_strobe(node)}[{i}] = {rhs};"
|
||||
self.add_content(s)
|
||||
if node.external:
|
||||
readable = node.has_sw_readable
|
||||
writable = node.has_sw_writable
|
||||
if readable and writable:
|
||||
self.add_content(f"is_external |= {rhs};")
|
||||
elif readable and not writable:
|
||||
self.add_content(f"is_external |= {rhs} & !cpuif_req_is_wr;")
|
||||
elif not readable and writable:
|
||||
self.add_content(f"is_external |= {rhs} & cpuif_req_is_wr;")
|
||||
else:
|
||||
raise RuntimeError
|
||||
|
||||
def exit_AddressableComponent(self, node: "AddressableNode") -> None:
|
||||
super().exit_AddressableComponent(node)
|
||||
|
||||
if not node.array_dimensions:
|
||||
return
|
||||
|
||||
for _ in node.array_dimensions:
|
||||
self._array_stride_stack.pop()
|
||||
3
src/peakrdl_busdecoder/cpuif/__init__.py
Normal file
3
src/peakrdl_busdecoder/cpuif/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .base_cpuif import BaseCpuif
|
||||
|
||||
__all__ = ["BaseCpuif"]
|
||||
4
src/peakrdl_busdecoder/cpuif/apb3/__init__.py
Normal file
4
src/peakrdl_busdecoder/cpuif/apb3/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .apb3_cpuif import APB3Cpuif
|
||||
from .apb3_cpuif_flat import APB3CpuifFlat
|
||||
|
||||
__all__ = ["APB3Cpuif", "APB3CpuifFlat"]
|
||||
79
src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py
Normal file
79
src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from systemrdl.node import AddressableNode
|
||||
from ..base_cpuif import BaseCpuif
|
||||
|
||||
|
||||
class APB3Cpuif(BaseCpuif):
|
||||
template_path = "apb3_tmpl.sv"
|
||||
is_interface = True
|
||||
|
||||
def _port_declaration(self, child: AddressableNode) -> str:
|
||||
base = f"apb3_intf.master m_apb_{child.inst_name}"
|
||||
if not child.is_array:
|
||||
return base
|
||||
if child.current_idx is not None:
|
||||
return f"{base}_{'_'.join(map(str, child.current_idx))} [N_{child.inst_name.upper()}S]"
|
||||
return f"{base} [N_{child.inst_name.upper()}S]"
|
||||
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
slave_ports: list[str] = ["apb3_intf.slave s_apb"]
|
||||
master_ports: list[str] = list(
|
||||
map(self._port_declaration, self.addressable_children)
|
||||
)
|
||||
|
||||
return ",\n".join(slave_ports + master_ports)
|
||||
|
||||
def signal(
|
||||
self,
|
||||
signal: str,
|
||||
node: AddressableNode | None = None,
|
||||
idx: str | int | None = None,
|
||||
) -> str:
|
||||
if node is None:
|
||||
# Node is none, so this is a slave signal
|
||||
return f"s_apb.{signal}"
|
||||
|
||||
# Master signal
|
||||
base = f"m_apb_{node.inst_name}"
|
||||
if not node.is_array:
|
||||
return f"{base}.{signal}"
|
||||
if node.current_idx is not None:
|
||||
# This is a specific instance of an array
|
||||
return f"{base}_{'_'.join(map(str, node.current_idx))}.{signal}"
|
||||
if idx is not None:
|
||||
return f"{base}[{idx}].{signal}"
|
||||
|
||||
raise ValueError("Must provide an index for arrayed interface signals")
|
||||
|
||||
def get_address_predicate(self, node: AddressableNode) -> str:
|
||||
"""
|
||||
Returns a SystemVerilog expression that evaluates to true when the
|
||||
address on the bus matches the address range of the given node.
|
||||
"""
|
||||
|
||||
addr_mask = (1 << self.addr_width) - 1
|
||||
addr = node.absolute_address & addr_mask
|
||||
size = node.size
|
||||
if size == 0:
|
||||
raise ValueError("Node size must be greater than 0")
|
||||
if (addr % size) != 0:
|
||||
raise ValueError("Node address must be aligned to its size")
|
||||
|
||||
# Calculate the address range of the node
|
||||
addr_start = addr
|
||||
addr_end = addr + size - 1
|
||||
if addr_end > addr_mask:
|
||||
raise ValueError("Node address range exceeds address width")
|
||||
|
||||
if addr_start == addr_end:
|
||||
return f"({self.signal('PADDR')} == 'h{addr_start:X})"
|
||||
|
||||
return f"({self.signal('PADDR')} >= 'h{addr_start:X} && {self.signal('PADDR')} <= 'h{addr_end:X})"
|
||||
|
||||
def get_address_decode_condition(self, node: AddressableNode) -> str:
|
||||
"""
|
||||
Returns a SystemVerilog expression that evaluates to true when the
|
||||
address on the bus matches the address range of the given node.
|
||||
"""
|
||||
addr_pred = self.get_address_predicate(node)
|
||||
return addr_pred
|
||||
62
src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py
Normal file
62
src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from systemrdl.node import AddressableNode
|
||||
from ..base_cpuif import BaseCpuif
|
||||
|
||||
|
||||
class APB3CpuifFlat(BaseCpuif):
|
||||
template_path = "apb3_tmpl.sv"
|
||||
is_interface = False
|
||||
|
||||
def _port_declaration(self, child: AddressableNode) -> list[str]:
|
||||
return [
|
||||
f"input logic {self.signal('PCLK', child)}",
|
||||
f"input logic {self.signal('PRESETn', child)}",
|
||||
f"input logic {self.signal('PSELx', child)}",
|
||||
f"input logic {self.signal('PENABLE', child)}",
|
||||
f"input logic {self.signal('PWRITE', child)}",
|
||||
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR', child)}",
|
||||
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA', child)}",
|
||||
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA', child)}",
|
||||
f"output logic {self.signal('PREADY', child)}",
|
||||
f"output logic {self.signal('PSLVERR', child)}",
|
||||
]
|
||||
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
slave_ports: list[str] = [
|
||||
f"input logic {self.signal('PCLK')}",
|
||||
f"input logic {self.signal('PRESETn')}",
|
||||
f"input logic {self.signal('PSELx')}",
|
||||
f"input logic {self.signal('PENABLE')}",
|
||||
f"input logic {self.signal('PWRITE')}",
|
||||
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR')}",
|
||||
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA')}",
|
||||
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA')}",
|
||||
f"output logic {self.signal('PREADY')}",
|
||||
f"output logic {self.signal('PSLVERR')}",
|
||||
]
|
||||
master_ports: list[str] = []
|
||||
for child in self.addressable_children:
|
||||
master_ports.extend(self._port_declaration(child))
|
||||
|
||||
return ",\n".join(slave_ports + master_ports)
|
||||
|
||||
def signal(
|
||||
self,
|
||||
signal: str,
|
||||
node: AddressableNode | None = None,
|
||||
idx: str | int | None = None,
|
||||
) -> str:
|
||||
if node is None:
|
||||
# Node is none, so this is a slave signal
|
||||
return f"s_apb_{signal}"
|
||||
|
||||
# Master signal
|
||||
base = f"m_apb_{node.inst_name}"
|
||||
if not node.is_array:
|
||||
return f"{base}_{signal}"
|
||||
if node.current_idx is not None:
|
||||
# This is a specific instance of an array
|
||||
return f"{base}_{signal}_{'_'.join(map(str, node.current_idx))}"
|
||||
if idx is not None:
|
||||
return f"{base}_{signal}[{idx}]"
|
||||
return f"{base}_{signal}[N_{node.inst_name.upper()}S]"
|
||||
115
src/peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv
Normal file
115
src/peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv
Normal file
@@ -0,0 +1,115 @@
|
||||
{%- if cpuif.is_interface -%}
|
||||
`ifndef SYNTHESIS
|
||||
initial begin
|
||||
assert_bad_addr_width: assert($bits({{cpuif.signal("PADDR")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH)
|
||||
else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("PADDR")}}), {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH);
|
||||
assert_bad_data_width: assert($bits({{cpuif.signal("PWDATA")}}) == {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH)
|
||||
else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("PWDATA")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH);
|
||||
end
|
||||
`endif
|
||||
{% endif -%}
|
||||
|
||||
//======================================================
|
||||
// APB Fanout
|
||||
//======================================================
|
||||
{%- for child in cpuif.addressable_children -%}
|
||||
{%- if child is array -%}
|
||||
for (genvar g_{{child.inst_name|lower}}_idx = 0; g_{{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; g_{{child.inst_name|lower}}_idx++) begin : g_passthrough_{{child.inst_name|lower}}
|
||||
assign {{self.signal("PCLK", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PCLK")}};
|
||||
assign {{self.signal("PRESETn", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PRESETn")}};
|
||||
assign {{self.signal("PENABLE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PENABLE")}};
|
||||
assign {{self.signal("PWRITE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWRITE")}};
|
||||
assign {{self.signal("PADDR", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing
|
||||
assign {{self.signal("PWDATA", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWDATA")}};
|
||||
end
|
||||
{%- else -%}
|
||||
assign {{self.signal("PCLK", child)}} = {{self.signal("PCLK")}};
|
||||
assign {{self.signal("PRESETn", child)}} = {{self.signal("PRESETn")}};
|
||||
assign {{self.signal("PENABLE", child)}} = {{self.signal("PENABLE")}};
|
||||
assign {{self.signal("PWRITE", child)}} = {{self.signal("PWRITE")}};
|
||||
assign {{self.signal("PADDR", child)}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing
|
||||
assign {{self.signal("PWDATA", child)}} = {{self.signal("PWDATA")}};
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
|
||||
//======================================================
|
||||
// Address Decode Logic
|
||||
//======================================================
|
||||
always_comb begin
|
||||
// Default all PSELx signals to 0
|
||||
{%- for child in cpuif.addressable_children -%}
|
||||
{%- if child is array -%}
|
||||
for (int {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
|
||||
{{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b0;
|
||||
end
|
||||
{%- else -%}
|
||||
{{self.signal("PSELx", child)}} = 1'b0;
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
|
||||
if ({{self.signal("PSELx")}}) begin
|
||||
{%- for child in cpuif.addressable_children -%}
|
||||
{%- if loop.first -%}
|
||||
if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||
{%- else -%}
|
||||
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||
{%- endif -%}
|
||||
// Address matched for {{child.inst_name}}
|
||||
{%- if child is array -%}
|
||||
for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
|
||||
{{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b1;
|
||||
end
|
||||
{%- else -%}
|
||||
{{self.signal("PSELx", child)}} = 1'b1;
|
||||
{%- endif -%}
|
||||
{%- if loop.last -%}
|
||||
end else begin
|
||||
// No address matched
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
end
|
||||
end else begin
|
||||
// PSELx is low, nothing to do
|
||||
end
|
||||
end
|
||||
|
||||
//======================================================
|
||||
// Read Data Mux
|
||||
//======================================================
|
||||
always_comb begin
|
||||
// Default read data to 0
|
||||
{{self.signal("PRDATA")}} = '0;
|
||||
{{self.signal("PREADY")}} = 1'b1;
|
||||
{{self.signal("PSLVERR")}} = 1'b0;
|
||||
|
||||
if ({{self.signal("PSELx")}} && !{{self.signal("PWRITE")}} && {{self.signal("PENABLE")}}) begin
|
||||
{%- for child in cpuif.addressable_children -%}
|
||||
{%- if loop.first -%}
|
||||
if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||
{%- else -%}
|
||||
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||
{%- endif -%}
|
||||
// Address matched for {{child.inst_name}}
|
||||
{%- if child is array -%}
|
||||
for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
|
||||
{{self.signal("PRDATA")}} = {{self.signal("PRDATA", child, f"{child.inst_name.lower()}_idx")}};
|
||||
{{self.signal("PREADY")}} = {{self.signal("PREADY", child, f"{child.inst_name.lower()}_idx")}};
|
||||
{{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child, f"{child.inst_name.lower()}_idx")}};
|
||||
end
|
||||
{%- else -%}
|
||||
{{self.signal("PRDATA")}} = {{self.signal("PRDATA", child)}};
|
||||
{{self.signal("PREADY")}} = {{self.signal("PREADY", child)}};
|
||||
{{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child)}};
|
||||
{%- endif -%}
|
||||
{%- if loop.last -%}
|
||||
end else begin
|
||||
// No address matched
|
||||
{{self.signal("PRDATA")}} = {'hdeadbeef}[{{ds.data_width - 1}}:0]; // Indicate error on no match
|
||||
{{self.signal("PSLVERR")}} = 1'b1; // Indicate error on no match
|
||||
end
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
end else begin
|
||||
// Not a read transfer, nothing to do
|
||||
end
|
||||
end
|
||||
4
src/peakrdl_busdecoder/cpuif/apb4/__init__.py
Normal file
4
src/peakrdl_busdecoder/cpuif/apb4/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .apb4_cpuif import APB4Cpuif
|
||||
from .apb4_cpuif_flat import APB4CpuifFlat
|
||||
|
||||
__all__ = ["APB4Cpuif", "APB4CpuifFlat"]
|
||||
81
src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif.py
Normal file
81
src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from systemrdl.node import AddressableNode
|
||||
from ..base_cpuif import BaseCpuif
|
||||
|
||||
|
||||
class APB4Cpuif(BaseCpuif):
|
||||
template_path = "apb4_tmpl.sv"
|
||||
is_interface = True
|
||||
|
||||
def _port_declaration(self, child: AddressableNode) -> str:
|
||||
base = f"apb4_intf.master m_apb_{child.inst_name}"
|
||||
if not child.is_array:
|
||||
return base
|
||||
if child.current_idx is not None:
|
||||
return f"{base}_{'_'.join(map(str, child.current_idx))} [N_{child.inst_name.upper()}S]"
|
||||
return f"{base} [N_{child.inst_name.upper()}S]"
|
||||
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
"""Returns the port declaration for the APB4 interface."""
|
||||
slave_ports: list[str] = ["apb4_intf.slave s_apb"]
|
||||
master_ports: list[str] = list(
|
||||
map(self._port_declaration, self.addressable_children)
|
||||
)
|
||||
|
||||
return ",\n".join(slave_ports + master_ports)
|
||||
|
||||
def signal(
|
||||
self,
|
||||
signal: str,
|
||||
node: AddressableNode | None = None,
|
||||
idx: str | int | None = None,
|
||||
) -> str:
|
||||
"""Returns the signal name for the given signal and node."""
|
||||
if node is None:
|
||||
# Node is none, so this is a slave signal
|
||||
return f"s_apb.{signal}"
|
||||
|
||||
# Master signal
|
||||
base = f"m_apb_{node.inst_name}"
|
||||
if not node.is_array:
|
||||
return f"{base}.{signal}"
|
||||
if node.current_idx is not None:
|
||||
# This is a specific instance of an array
|
||||
return f"{base}_{'_'.join(map(str, node.current_idx))}.{signal}"
|
||||
if idx is not None:
|
||||
return f"{base}[{idx}].{signal}"
|
||||
|
||||
raise ValueError("Must provide an index for arrayed interface signals")
|
||||
|
||||
def get_address_predicate(self, node: AddressableNode) -> str:
|
||||
"""
|
||||
Returns a SystemVerilog expression that evaluates to true when the
|
||||
address on the bus matches the address range of the given node.
|
||||
"""
|
||||
|
||||
addr_mask = (1 << self.addr_width) - 1
|
||||
addr = node.absolute_address & addr_mask
|
||||
size = node.size
|
||||
if size == 0:
|
||||
raise ValueError("Node size must be greater than 0")
|
||||
if (addr % size) != 0:
|
||||
raise ValueError("Node address must be aligned to its size")
|
||||
|
||||
# Calculate the address range of the node
|
||||
addr_start = addr
|
||||
addr_end = addr + size - 1
|
||||
if addr_end > addr_mask:
|
||||
raise ValueError("Node address range exceeds address width")
|
||||
|
||||
if addr_start == addr_end:
|
||||
return f"({self.signal('PADDR')} == 'h{addr_start:X})"
|
||||
|
||||
return f"({self.signal('PADDR')} >= 'h{addr_start:X} && {self.signal('PADDR')} <= 'h{addr_end:X})"
|
||||
|
||||
def get_address_decode_condition(self, node: AddressableNode) -> str:
|
||||
"""
|
||||
Returns a SystemVerilog expression that evaluates to true when the
|
||||
address on the bus matches the address range of the given node.
|
||||
"""
|
||||
addr_pred = self.get_address_predicate(node)
|
||||
return addr_pred
|
||||
66
src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif_flat.py
Normal file
66
src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif_flat.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from systemrdl.node import AddressableNode
|
||||
from ..base_cpuif import BaseCpuif
|
||||
|
||||
|
||||
class APB4CpuifFlat(BaseCpuif):
|
||||
template_path = "apb4_tmpl.sv"
|
||||
is_interface = False
|
||||
|
||||
def _port_declaration(self, child: AddressableNode) -> list[str]:
|
||||
return [
|
||||
f"input logic {self.signal('PCLK', child)}",
|
||||
f"input logic {self.signal('PRESETn', child)}",
|
||||
f"input logic {self.signal('PSELx', child)}",
|
||||
f"input logic {self.signal('PENABLE', child)}",
|
||||
f"input logic {self.signal('PWRITE', child)}",
|
||||
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR', child)}",
|
||||
f"input logic [2:0] {self.signal('PPROT', child)}",
|
||||
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA', child)}",
|
||||
f"input logic [{self.data_width // 8 - 1}:0] {self.signal('PSTRB', child)}",
|
||||
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA', child)}",
|
||||
f"output logic {self.signal('PREADY', child)}",
|
||||
f"output logic {self.signal('PSLVERR', child)}",
|
||||
]
|
||||
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
slave_ports: list[str] = [
|
||||
f"input logic {self.signal('PCLK')}",
|
||||
f"input logic {self.signal('PRESETn')}",
|
||||
f"input logic {self.signal('PSELx')}",
|
||||
f"input logic {self.signal('PENABLE')}",
|
||||
f"input logic {self.signal('PWRITE')}",
|
||||
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR')}",
|
||||
f"input logic [2:0] {self.signal('PPROT')}",
|
||||
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA')}",
|
||||
f"input logic [{self.data_width // 8 - 1}:0] {self.signal('PSTRB')}",
|
||||
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA')}",
|
||||
f"output logic {self.signal('PREADY')}",
|
||||
f"output logic {self.signal('PSLVERR')}",
|
||||
]
|
||||
master_ports: list[str] = []
|
||||
for child in self.addressable_children:
|
||||
master_ports.extend(self._port_declaration(child))
|
||||
|
||||
return ",\n".join(slave_ports + master_ports)
|
||||
|
||||
def signal(
|
||||
self,
|
||||
signal: str,
|
||||
node: AddressableNode | None = None,
|
||||
idx: str | int | None = None,
|
||||
) -> str:
|
||||
if node is None:
|
||||
# Node is none, so this is a slave signal
|
||||
return f"s_apb_{signal}"
|
||||
|
||||
# Master signal
|
||||
base = f"m_apb_{node.inst_name}"
|
||||
if not node.is_array:
|
||||
return f"{base}_{signal}"
|
||||
if node.current_idx is not None:
|
||||
# This is a specific instance of an array
|
||||
return f"{base}_{signal}_{'_'.join(map(str, node.current_idx))}"
|
||||
if idx is not None:
|
||||
return f"{base}_{signal}[{idx}]"
|
||||
return f"{base}_{signal}[N_{node.inst_name.upper()}S]"
|
||||
119
src/peakrdl_busdecoder/cpuif/apb4/apb4_tmpl.sv
Normal file
119
src/peakrdl_busdecoder/cpuif/apb4/apb4_tmpl.sv
Normal file
@@ -0,0 +1,119 @@
|
||||
{%- if cpuif.is_interface -%}
|
||||
`ifndef SYNTHESIS
|
||||
initial begin
|
||||
assert_bad_addr_width: assert($bits({{cpuif.signal("PADDR")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH)
|
||||
else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("PADDR")}}), {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH);
|
||||
assert_bad_data_width: assert($bits({{cpuif.signal("PWDATA")}}) == {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH)
|
||||
else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("PWDATA")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH);
|
||||
end
|
||||
`endif
|
||||
{% endif -%}
|
||||
|
||||
//======================================================
|
||||
// APB Fanout
|
||||
//======================================================
|
||||
{%- for child in cpuif.addressable_children -%}
|
||||
{%- if child is array -%}
|
||||
for (genvar g_{{child.inst_name|lower}}_idx = 0; g_{{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; g_{{child.inst_name|lower}}_idx++) begin : g_passthrough_{{child.inst_name|lower}}
|
||||
assign {{self.signal("PCLK", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PCLK")}};
|
||||
assign {{self.signal("PRESETn", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PRESETn")}};
|
||||
assign {{self.signal("PENABLE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PENABLE")}};
|
||||
assign {{self.signal("PWRITE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWRITE")}};
|
||||
assign {{self.signal("PADDR", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing
|
||||
assign {{self.signal("PPROT", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PPROT")}};
|
||||
assign {{self.signal("PWDATA", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWDATA")}};
|
||||
assign {{self.signal("PSTRB", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PSTRB")}};
|
||||
end
|
||||
{%- else -%}
|
||||
assign {{self.signal("PCLK", child)}} = {{self.signal("PCLK")}};
|
||||
assign {{self.signal("PRESETn", child)}} = {{self.signal("PRESETn")}};
|
||||
assign {{self.signal("PENABLE", child)}} = {{self.signal("PENABLE")}};
|
||||
assign {{self.signal("PWRITE", child)}} = {{self.signal("PWRITE")}};
|
||||
assign {{self.signal("PADDR", child)}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing
|
||||
assign {{self.signal("PPROT", child)}} = {{self.signal("PPROT")}};
|
||||
assign {{self.signal("PWDATA", child)}} = {{self.signal("PWDATA")}};
|
||||
assign {{self.signal("PSTRB", child)}} = {{self.signal("PSTRB")}};
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
|
||||
//======================================================
|
||||
// Address Decode Logic
|
||||
//======================================================
|
||||
always_comb begin
|
||||
// Default all PSELx signals to 0
|
||||
{%- for child in cpuif.addressable_children -%}
|
||||
{%- if child is array -%}
|
||||
for (int {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
|
||||
{{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b0;
|
||||
end
|
||||
{%- else -%}
|
||||
{{self.signal("PSELx", child)}} = 1'b0;
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
|
||||
if ({{self.signal("PSELx")}}) begin
|
||||
{%- for child in cpuif.addressable_children -%}
|
||||
{%- if loop.first -%}
|
||||
if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||
{%- else -%}
|
||||
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||
{%- endif -%}
|
||||
// Address matched for {{child.inst_name}}
|
||||
{%- if child is array -%}
|
||||
for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
|
||||
{{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b1;
|
||||
end
|
||||
{%- else -%}
|
||||
{{self.signal("PSELx", child)}} = 1'b1;
|
||||
{%- endif -%}
|
||||
{%- if loop.last -%}
|
||||
end else begin
|
||||
// No address matched
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
end
|
||||
end else begin
|
||||
// PSELx is low, nothing to do
|
||||
end
|
||||
end
|
||||
|
||||
//======================================================
|
||||
// Read Data Mux
|
||||
//======================================================
|
||||
always_comb begin
|
||||
// Default read data to 0
|
||||
{{self.signal("PRDATA")}} = '0;
|
||||
{{self.signal("PREADY")}} = 1'b1;
|
||||
{{self.signal("PSLVERR")}} = 1'b0;
|
||||
|
||||
if ({{self.signal("PSELx")}} && !{{self.signal("PWRITE")}} && {{self.signal("PENABLE")}}) begin
|
||||
{%- for child in cpuif.addressable_children -%}
|
||||
{%- if loop.first -%}
|
||||
if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||
{%- else -%}
|
||||
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||
{%- endif -%}
|
||||
// Address matched for {{child.inst_name}}
|
||||
{%- if child is array -%}
|
||||
for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
|
||||
{{self.signal("PRDATA")}} = {{self.signal("PRDATA", child, f"{child.inst_name.lower()}_idx")}};
|
||||
{{self.signal("PREADY")}} = {{self.signal("PREADY", child, f"{child.inst_name.lower()}_idx")}};
|
||||
{{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child, f"{child.inst_name.lower()}_idx")}};
|
||||
end
|
||||
{%- else -%}
|
||||
{{self.signal("PRDATA")}} = {{self.signal("PRDATA", child)}};
|
||||
{{self.signal("PREADY")}} = {{self.signal("PREADY", child)}};
|
||||
{{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child)}};
|
||||
{%- endif -%}
|
||||
{%- if loop.last -%}
|
||||
end else begin
|
||||
// No address matched
|
||||
{{self.signal("PRDATA")}} = {'hdeadbeef}[{{ds.data_width - 1}}:0]; // Indicate error on no match
|
||||
{{self.signal("PSLVERR")}} = 1'b1; // Indicate error on no match
|
||||
end
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
end else begin
|
||||
// Not a read transfer, nothing to do
|
||||
end
|
||||
end
|
||||
67
src/peakrdl_busdecoder/cpuif/axi4lite/__init__.py
Normal file
67
src/peakrdl_busdecoder/cpuif/axi4lite/__init__.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from ..base_cpuif import CpuifBase
|
||||
|
||||
|
||||
class AXI4Lite_Cpuif(CpuifBase):
|
||||
template_path = "axi4lite_tmpl.sv"
|
||||
is_interface = True
|
||||
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
return "axi4lite_intf.slave s_axil"
|
||||
|
||||
def signal(self, name: str) -> str:
|
||||
return "s_axil." + name.upper()
|
||||
|
||||
@property
|
||||
def busdecoder_latency(self) -> int:
|
||||
return max(self.exp.ds.min_read_latency, self.exp.ds.min_write_latency)
|
||||
|
||||
@property
|
||||
def max_outstanding(self) -> int:
|
||||
"""
|
||||
Best pipelined performance is when the max outstanding transactions
|
||||
is the design's latency + 2.
|
||||
Anything beyond that does not have any effect, aside from adding unnecessary
|
||||
logic and additional buffer-bloat latency.
|
||||
"""
|
||||
return self.busdecoder_latency + 2
|
||||
|
||||
@property
|
||||
def resp_buffer_size(self) -> int:
|
||||
"""
|
||||
Response buffer size must be greater or equal to max outstanding
|
||||
transactions to prevent response overrun.
|
||||
"""
|
||||
return self.max_outstanding
|
||||
|
||||
|
||||
class AXI4Lite_Cpuif_flattened(AXI4Lite_Cpuif):
|
||||
is_interface = False
|
||||
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
lines = [
|
||||
"output logic " + self.signal("awready"),
|
||||
"input wire " + self.signal("awvalid"),
|
||||
f"input wire [{self.addr_width - 1}:0] " + self.signal("awaddr"),
|
||||
"input wire [2:0] " + self.signal("awprot"),
|
||||
"output logic " + self.signal("wready"),
|
||||
"input wire " + self.signal("wvalid"),
|
||||
f"input wire [{self.data_width - 1}:0] " + self.signal("wdata"),
|
||||
f"input wire [{self.data_width_bytes - 1}:0]" + self.signal("wstrb"),
|
||||
"input wire " + self.signal("bready"),
|
||||
"output logic " + self.signal("bvalid"),
|
||||
"output logic [1:0] " + self.signal("bresp"),
|
||||
"output logic " + self.signal("arready"),
|
||||
"input wire " + self.signal("arvalid"),
|
||||
f"input wire [{self.addr_width - 1}:0] " + self.signal("araddr"),
|
||||
"input wire [2:0] " + self.signal("arprot"),
|
||||
"input wire " + self.signal("rready"),
|
||||
"output logic " + self.signal("rvalid"),
|
||||
f"output logic [{self.data_width - 1}:0] " + self.signal("rdata"),
|
||||
"output logic [1:0] " + self.signal("rresp"),
|
||||
]
|
||||
return ",\n".join(lines)
|
||||
|
||||
def signal(self, name: str) -> str:
|
||||
return "s_axil_" + name
|
||||
254
src/peakrdl_busdecoder/cpuif/axi4lite/axi4lite_tmpl.sv
Normal file
254
src/peakrdl_busdecoder/cpuif/axi4lite/axi4lite_tmpl.sv
Normal file
@@ -0,0 +1,254 @@
|
||||
{%- if cpuif.is_interface -%}
|
||||
`ifndef SYNTHESIS
|
||||
initial begin
|
||||
assert_bad_addr_width: assert($bits({{cpuif.signal("araddr")}}) >= {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH)
|
||||
else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("araddr")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH);
|
||||
assert_bad_data_width: assert($bits({{cpuif.signal("wdata")}}) == {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH)
|
||||
else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("wdata")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH);
|
||||
end
|
||||
`endif
|
||||
|
||||
{% endif -%}
|
||||
|
||||
// Max Outstanding Transactions: {{cpuif.max_outstanding}}
|
||||
logic [{{clog2(cpuif.max_outstanding+1)-1}}:0] axil_n_in_flight;
|
||||
logic axil_prev_was_rd;
|
||||
logic axil_arvalid;
|
||||
logic [{{cpuif.addr_width-1}}:0] axil_araddr;
|
||||
logic axil_ar_accept;
|
||||
logic axil_awvalid;
|
||||
logic [{{cpuif.addr_width-1}}:0] axil_awaddr;
|
||||
logic axil_wvalid;
|
||||
logic [{{cpuif.data_width-1}}:0] axil_wdata;
|
||||
logic [{{cpuif.data_width_bytes-1}}:0] axil_wstrb;
|
||||
logic axil_aw_accept;
|
||||
logic axil_resp_acked;
|
||||
|
||||
// Transaction request acceptance
|
||||
always_ff {{get_always_ff_event(cpuif.reset)}} begin
|
||||
if({{get_resetsignal(cpuif.reset)}}) begin
|
||||
axil_prev_was_rd <= '0;
|
||||
axil_arvalid <= '0;
|
||||
axil_araddr <= '0;
|
||||
axil_awvalid <= '0;
|
||||
axil_awaddr <= '0;
|
||||
axil_wvalid <= '0;
|
||||
axil_wdata <= '0;
|
||||
axil_wstrb <= '0;
|
||||
axil_n_in_flight <= '0;
|
||||
end else begin
|
||||
// AR* acceptance register
|
||||
if(axil_ar_accept) begin
|
||||
axil_prev_was_rd <= '1;
|
||||
axil_arvalid <= '0;
|
||||
end
|
||||
if({{cpuif.signal("arvalid")}} && {{cpuif.signal("arready")}}) begin
|
||||
axil_arvalid <= '1;
|
||||
axil_araddr <= {{cpuif.signal("araddr")}};
|
||||
end
|
||||
|
||||
// AW* & W* acceptance registers
|
||||
if(axil_aw_accept) begin
|
||||
axil_prev_was_rd <= '0;
|
||||
axil_awvalid <= '0;
|
||||
axil_wvalid <= '0;
|
||||
end
|
||||
if({{cpuif.signal("awvalid")}} && {{cpuif.signal("awready")}}) begin
|
||||
axil_awvalid <= '1;
|
||||
axil_awaddr <= {{cpuif.signal("awaddr")}};
|
||||
end
|
||||
if({{cpuif.signal("wvalid")}} && {{cpuif.signal("wready")}}) begin
|
||||
axil_wvalid <= '1;
|
||||
axil_wdata <= {{cpuif.signal("wdata")}};
|
||||
axil_wstrb <= {{cpuif.signal("wstrb")}};
|
||||
end
|
||||
|
||||
// Keep track of in-flight transactions
|
||||
if((axil_ar_accept || axil_aw_accept) && !axil_resp_acked) begin
|
||||
axil_n_in_flight <= axil_n_in_flight + 1'b1;
|
||||
end else if(!(axil_ar_accept || axil_aw_accept) && axil_resp_acked) begin
|
||||
axil_n_in_flight <= axil_n_in_flight - 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
{{cpuif.signal("arready")}} = (!axil_arvalid || axil_ar_accept);
|
||||
{{cpuif.signal("awready")}} = (!axil_awvalid || axil_aw_accept);
|
||||
{{cpuif.signal("wready")}} = (!axil_wvalid || axil_aw_accept);
|
||||
end
|
||||
|
||||
// Request dispatch
|
||||
always_comb begin
|
||||
cpuif_wr_data = axil_wdata;
|
||||
for(int i=0; i<{{cpuif.data_width_bytes}}; i++) begin
|
||||
cpuif_wr_biten[i*8 +: 8] = {8{axil_wstrb[i]}};
|
||||
end
|
||||
cpuif_req = '0;
|
||||
cpuif_req_is_wr = '0;
|
||||
cpuif_addr = '0;
|
||||
axil_ar_accept = '0;
|
||||
axil_aw_accept = '0;
|
||||
|
||||
if(axil_n_in_flight < {{clog2(cpuif.max_outstanding+1)}}'d{{cpuif.max_outstanding}}) begin
|
||||
// Can safely issue more transactions without overwhelming response buffer
|
||||
if(axil_arvalid && !axil_prev_was_rd) begin
|
||||
cpuif_req = '1;
|
||||
cpuif_req_is_wr = '0;
|
||||
{%- if cpuif.data_width_bytes == 1 %}
|
||||
cpuif_addr = axil_araddr;
|
||||
{%- else %}
|
||||
cpuif_addr = {axil_araddr[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
|
||||
{%- endif %}
|
||||
if(!cpuif_req_stall_rd) axil_ar_accept = '1;
|
||||
end else if(axil_awvalid && axil_wvalid) begin
|
||||
cpuif_req = '1;
|
||||
cpuif_req_is_wr = '1;
|
||||
{%- if cpuif.data_width_bytes == 1 %}
|
||||
cpuif_addr = axil_awaddr;
|
||||
{%- else %}
|
||||
cpuif_addr = {axil_awaddr[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
|
||||
{%- endif %}
|
||||
if(!cpuif_req_stall_wr) axil_aw_accept = '1;
|
||||
end else if(axil_arvalid) begin
|
||||
cpuif_req = '1;
|
||||
cpuif_req_is_wr = '0;
|
||||
{%- if cpuif.data_width_bytes == 1 %}
|
||||
cpuif_addr = axil_araddr;
|
||||
{%- else %}
|
||||
cpuif_addr = {axil_araddr[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
|
||||
{%- endif %}
|
||||
if(!cpuif_req_stall_rd) axil_ar_accept = '1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
// AXI4-Lite Response Logic
|
||||
{%- if cpuif.resp_buffer_size == 1 %}
|
||||
always_ff {{get_always_ff_event(cpuif.reset)}} begin
|
||||
if({{get_resetsignal(cpuif.reset)}}) begin
|
||||
{{cpuif.signal("rvalid")}} <= '0;
|
||||
{{cpuif.signal("rresp")}} <= '0;
|
||||
{{cpuif.signal("rdata")}} <= '0;
|
||||
{{cpuif.signal("bvalid")}} <= '0;
|
||||
{{cpuif.signal("bresp")}} <= '0;
|
||||
end else begin
|
||||
if({{cpuif.signal("rvalid")}} && {{cpuif.signal("rready")}}) begin
|
||||
{{cpuif.signal("rvalid")}} <= '0;
|
||||
end
|
||||
|
||||
if({{cpuif.signal("bvalid")}} && {{cpuif.signal("bready")}}) begin
|
||||
{{cpuif.signal("bvalid")}} <= '0;
|
||||
end
|
||||
|
||||
if(cpuif_rd_ack) begin
|
||||
{{cpuif.signal("rvalid")}} <= '1;
|
||||
{{cpuif.signal("rdata")}} <= cpuif_rd_data;
|
||||
if(cpuif_rd_err) {{cpuif.signal("rresp")}} <= 2'b10; // SLVERR
|
||||
else {{cpuif.signal("rresp")}} <= 2'b00; // OKAY
|
||||
end
|
||||
|
||||
if(cpuif_wr_ack) begin
|
||||
{{cpuif.signal("bvalid")}} <= '1;
|
||||
if(cpuif_wr_err) {{cpuif.signal("bresp")}} <= 2'b10; // SLVERR
|
||||
else {{cpuif.signal("bresp")}} <= 2'b00; // OKAY
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
axil_resp_acked = '0;
|
||||
if({{cpuif.signal("rvalid")}} && {{cpuif.signal("rready")}}) axil_resp_acked = '1;
|
||||
if({{cpuif.signal("bvalid")}} && {{cpuif.signal("bready")}}) axil_resp_acked = '1;
|
||||
end
|
||||
|
||||
{%- else %}
|
||||
struct {
|
||||
logic is_wr;
|
||||
logic err;
|
||||
logic [{{cpuif.data_width-1}}:0] rdata;
|
||||
} axil_resp_buffer[{{roundup_pow2(cpuif.resp_buffer_size)}}];
|
||||
{%- if not is_pow2(cpuif.resp_buffer_size) %}
|
||||
// axil_resp_buffer is intentionally padded to the next power of two despite
|
||||
// only requiring {{cpuif.resp_buffer_size}} entries.
|
||||
// This is to avoid quirks in some tools that cannot handle indexing into a non-power-of-2 array.
|
||||
// Unused entries are expected to be optimized away
|
||||
{% endif %}
|
||||
|
||||
logic [{{clog2(cpuif.resp_buffer_size)}}:0] axil_resp_wptr;
|
||||
logic [{{clog2(cpuif.resp_buffer_size)}}:0] axil_resp_rptr;
|
||||
|
||||
always_ff {{get_always_ff_event(cpuif.reset)}} begin
|
||||
if({{get_resetsignal(cpuif.reset)}}) begin
|
||||
for(int i=0; i<{{cpuif.resp_buffer_size}}; i++) begin
|
||||
axil_resp_buffer[i].is_wr <= '0;
|
||||
axil_resp_buffer[i].err <= '0;
|
||||
axil_resp_buffer[i].rdata <= '0;
|
||||
end
|
||||
axil_resp_wptr <= '0;
|
||||
axil_resp_rptr <= '0;
|
||||
end else begin
|
||||
// Store responses in buffer until AXI response channel accepts them
|
||||
if(cpuif_rd_ack || cpuif_wr_ack) begin
|
||||
if(cpuif_rd_ack) begin
|
||||
axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].is_wr <= '0;
|
||||
axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].err <= cpuif_rd_err;
|
||||
axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].rdata <= cpuif_rd_data;
|
||||
|
||||
end else if(cpuif_wr_ack) begin
|
||||
axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].is_wr <= '1;
|
||||
axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].err <= cpuif_wr_err;
|
||||
end
|
||||
{%- if is_pow2(cpuif.resp_buffer_size) %}
|
||||
axil_resp_wptr <= axil_resp_wptr + 1'b1;
|
||||
{%- else %}
|
||||
if(axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] == {{cpuif.resp_buffer_size-1}}) begin
|
||||
axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] <= '0;
|
||||
axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)}}] <= ~axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)}}];
|
||||
end else begin
|
||||
axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] <= axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] + 1'b1;
|
||||
end
|
||||
{%- endif %}
|
||||
end
|
||||
|
||||
// Advance read pointer when acknowledged
|
||||
if(axil_resp_acked) begin
|
||||
{%- if is_pow2(cpuif.resp_buffer_size) %}
|
||||
axil_resp_rptr <= axil_resp_rptr + 1'b1;
|
||||
{%- else %}
|
||||
if(axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] == {{cpuif.resp_buffer_size-1}}) begin
|
||||
axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] <= '0;
|
||||
axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)}}] <= ~axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)}}];
|
||||
end else begin
|
||||
axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] <= axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] + 1'b1;
|
||||
end
|
||||
{%- endif %}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
axil_resp_acked = '0;
|
||||
{{cpuif.signal("bvalid")}} = '0;
|
||||
{{cpuif.signal("rvalid")}} = '0;
|
||||
if(axil_resp_rptr != axil_resp_wptr) begin
|
||||
if(axil_resp_buffer[axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].is_wr) begin
|
||||
{{cpuif.signal("bvalid")}} = '1;
|
||||
if({{cpuif.signal("bready")}}) axil_resp_acked = '1;
|
||||
end else begin
|
||||
{{cpuif.signal("rvalid")}} = '1;
|
||||
if({{cpuif.signal("rready")}}) axil_resp_acked = '1;
|
||||
end
|
||||
end
|
||||
|
||||
{{cpuif.signal("rdata")}} = axil_resp_buffer[axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].rdata;
|
||||
if(axil_resp_buffer[axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].err) begin
|
||||
{{cpuif.signal("bresp")}} = 2'b10;
|
||||
{{cpuif.signal("rresp")}} = 2'b10;
|
||||
end else begin
|
||||
{{cpuif.signal("bresp")}} = 2'b00;
|
||||
{{cpuif.signal("rresp")}} = 2'b00;
|
||||
end
|
||||
end
|
||||
{%- endif %}
|
||||
92
src/peakrdl_busdecoder/cpuif/base_cpuif.py
Normal file
92
src/peakrdl_busdecoder/cpuif/base_cpuif.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from typing import TYPE_CHECKING
|
||||
import inspect
|
||||
import os
|
||||
|
||||
import jinja2 as jj
|
||||
from systemrdl.node import AddressableNode
|
||||
|
||||
from ..utils import clog2, is_pow2, roundup_pow2
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..exporter import BusDecoderExporter
|
||||
|
||||
|
||||
class BaseCpuif:
|
||||
# Path is relative to the location of the class that assigns this variable
|
||||
template_path = ""
|
||||
|
||||
def __init__(self, exp: "BusDecoderExporter") -> None:
|
||||
self.exp = exp
|
||||
self.reset = exp.ds.top_node.cpuif_reset
|
||||
self.unroll = exp.ds.cpuif_unroll
|
||||
|
||||
@property
|
||||
def addressable_children(self) -> list[AddressableNode]:
|
||||
return [
|
||||
child
|
||||
for child in self.exp.ds.top_node.children(unroll=self.unroll)
|
||||
if isinstance(child, AddressableNode)
|
||||
]
|
||||
|
||||
@property
|
||||
def addr_width(self) -> int:
|
||||
return self.exp.ds.addr_width
|
||||
|
||||
@property
|
||||
def data_width(self) -> int:
|
||||
return self.exp.ds.cpuif_data_width
|
||||
|
||||
@property
|
||||
def data_width_bytes(self) -> int:
|
||||
return self.data_width // 8
|
||||
|
||||
@property
|
||||
def port_declaration(self) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def parameters(self) -> list[str]:
|
||||
"""
|
||||
Optional list of additional parameters this CPU interface provides to
|
||||
the module's definition
|
||||
"""
|
||||
array_parameters = [
|
||||
f"parameter N_{child.inst_name.upper()}S = {child.n_elements}"
|
||||
for child in self.addressable_children
|
||||
if self.check_is_array(child)
|
||||
]
|
||||
return array_parameters
|
||||
|
||||
def _get_template_path_class_dir(self) -> str:
|
||||
"""
|
||||
Traverse up the MRO and find the first class that explicitly assigns
|
||||
template_path. Returns the directory that contains the class definition.
|
||||
"""
|
||||
for cls in inspect.getmro(self.__class__):
|
||||
if "template_path" in cls.__dict__:
|
||||
class_dir = os.path.dirname(inspect.getfile(cls))
|
||||
return class_dir
|
||||
raise RuntimeError
|
||||
|
||||
def check_is_array(self, node: AddressableNode) -> bool:
|
||||
return node.is_array and not self.unroll
|
||||
|
||||
def get_implementation(self) -> str:
|
||||
class_dir = self._get_template_path_class_dir()
|
||||
loader = jj.FileSystemLoader(class_dir)
|
||||
jj_env = jj.Environment(
|
||||
loader=loader,
|
||||
undefined=jj.StrictUndefined,
|
||||
)
|
||||
jj_env.tests["array"] = self.check_is_array # type: ignore
|
||||
jj_env.filters["clog2"] = clog2 # type: ignore
|
||||
jj_env.filters["is_pow2"] = is_pow2 # type: ignore
|
||||
jj_env.filters["roundup_pow2"] = roundup_pow2 # type: ignore
|
||||
|
||||
context = {
|
||||
"cpuif": self,
|
||||
"ds": self.exp.ds,
|
||||
}
|
||||
|
||||
template = jj_env.get_template(self.template_path)
|
||||
return template.render(context)
|
||||
42
src/peakrdl_busdecoder/dereferencer.py
Normal file
42
src/peakrdl_busdecoder/dereferencer.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from systemrdl.node import AddrmapNode, FieldNode, RegNode, AddressableNode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .exporter import BusDecoderExporter, DesignState
|
||||
from .addr_decode import AddressDecode
|
||||
|
||||
|
||||
class Dereferencer:
|
||||
"""
|
||||
This class provides an interface to convert conceptual SystemRDL references
|
||||
into Verilog identifiers
|
||||
"""
|
||||
|
||||
def __init__(self, exp: "BusDecoderExporter"):
|
||||
self.exp = exp
|
||||
|
||||
@property
|
||||
def address_decode(self) -> "AddressDecode":
|
||||
return self.exp.address_decode
|
||||
|
||||
@property
|
||||
def ds(self) -> "DesignState":
|
||||
return self.exp.ds
|
||||
|
||||
@property
|
||||
def top_node(self) -> AddrmapNode:
|
||||
return self.exp.ds.top_node
|
||||
|
||||
def get_access_strobe(
|
||||
self, obj: RegNode | FieldNode, reduce_substrobes: bool = True
|
||||
) -> str:
|
||||
"""
|
||||
Returns the Verilog string that represents the register's access strobe
|
||||
"""
|
||||
return self.address_decode.get_access_strobe(obj, reduce_substrobes)
|
||||
|
||||
def get_external_block_access_strobe(self, obj: "AddressableNode") -> str:
|
||||
"""
|
||||
Returns the Verilog string that represents the external block's access strobe
|
||||
"""
|
||||
return self.address_decode.get_external_block_access_strobe(obj)
|
||||
184
src/peakrdl_busdecoder/exporter.py
Normal file
184
src/peakrdl_busdecoder/exporter.py
Normal file
@@ -0,0 +1,184 @@
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import jinja2 as jj
|
||||
from systemrdl.node import AddrmapNode, RootNode
|
||||
from systemrdl.rdltypes.user_enum import UserEnum
|
||||
|
||||
from .addr_decode import AddressDecode
|
||||
from .dereferencer import Dereferencer
|
||||
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 BaseCpuif
|
||||
from .cpuif.apb4 import APB4Cpuif
|
||||
from .sv_int import SVInt
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class BusDecoderExporter:
|
||||
cpuif: BaseCpuif
|
||||
address_decode: AddressDecode
|
||||
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: 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_busdecoder.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.
|
||||
address_width: int
|
||||
Override the CPU interface's address width. By default, address width
|
||||
is sized to the contents of the busdecoder.
|
||||
unroll: bool
|
||||
Unroll arrayed addressable nodes into separate instances in the CPU
|
||||
interface. By default, arrayed nodes are kept as arrays.
|
||||
"""
|
||||
# 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: type[BaseCpuif] = kwargs.pop("cpuif_cls", None) or APB4Cpuif
|
||||
|
||||
# Check for stray kwargs
|
||||
if kwargs:
|
||||
raise TypeError(
|
||||
f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'"
|
||||
)
|
||||
|
||||
# Construct exporter components
|
||||
self.cpuif = cpuif_cls(self)
|
||||
self.address_decode = AddressDecode(self)
|
||||
self.dereferencer = Dereferencer(self)
|
||||
|
||||
# Validate that there are no unsupported constructs
|
||||
DesignValidator(self).do_validate()
|
||||
|
||||
# Build Jinja template context
|
||||
context = {
|
||||
"cpuif": self.cpuif,
|
||||
"address_decode": self.address_decode,
|
||||
"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()
|
||||
|
||||
|
||||
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: bool = kwargs.pop("reuse_hwif_typedefs", True)
|
||||
self.module_name: str = kwargs.pop("module_name", None) or kwf(
|
||||
self.top_node.inst_name
|
||||
)
|
||||
self.package_name: str = kwargs.pop("package_name", None) or (
|
||||
self.module_name + "_pkg"
|
||||
)
|
||||
user_addr_width: int | None = kwargs.pop("address_width", None)
|
||||
|
||||
self.cpuif_unroll: bool = kwargs.pop("cpuif_unroll", False)
|
||||
|
||||
# ------------------------
|
||||
# Info about the design
|
||||
# ------------------------
|
||||
self.cpuif_data_width = 0
|
||||
|
||||
# Track any referenced enums
|
||||
self.user_enums: 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.inst.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
|
||||
101
src/peakrdl_busdecoder/forloop_generator.py
Normal file
101
src/peakrdl_busdecoder/forloop_generator.py
Normal file
@@ -0,0 +1,101 @@
|
||||
from typing import TYPE_CHECKING
|
||||
import textwrap
|
||||
|
||||
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from systemrdl.node import AddressableNode, Node
|
||||
|
||||
|
||||
class Body:
|
||||
def __init__(self) -> None:
|
||||
self.children: list[str | Body] = []
|
||||
|
||||
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, "\t")
|
||||
+ "\nend"
|
||||
)
|
||||
|
||||
|
||||
class ForLoopGenerator:
|
||||
i_type = "int"
|
||||
loop_body_cls = LoopBody
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._loop_level = 0
|
||||
self._stack: list[Body] = []
|
||||
|
||||
@property
|
||||
def current_loop(self) -> Body:
|
||||
return self._stack[-1]
|
||||
|
||||
def push_loop(self, dim: int) -> None:
|
||||
i = f"i{self._loop_level}"
|
||||
b = self.loop_body_cls(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) -> None:
|
||||
assert not self._stack
|
||||
b = Body()
|
||||
self._stack.append(b)
|
||||
|
||||
def finish(self) -> str | None:
|
||||
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") -> str | None:
|
||||
self.start()
|
||||
walker = RDLWalker()
|
||||
walker.walk(node, self, skip_top=True)
|
||||
return self.finish()
|
||||
|
||||
def enter_AddressableComponent(
|
||||
self, node: "AddressableNode"
|
||||
) -> WalkerAction | None:
|
||||
if not node.array_dimensions:
|
||||
return None
|
||||
|
||||
for dim in node.array_dimensions:
|
||||
self.push_loop(dim)
|
||||
return None
|
||||
|
||||
def exit_AddressableComponent(self, node: "AddressableNode") -> WalkerAction | None:
|
||||
if not node.array_dimensions:
|
||||
return None
|
||||
|
||||
for _ in node.array_dimensions:
|
||||
self.pop_loop()
|
||||
return None
|
||||
52
src/peakrdl_busdecoder/identifier_filter.py
Normal file
52
src/peakrdl_busdecoder/identifier_filter.py
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
# All SystemVerilog 2017 keywords
|
||||
SV_KEYWORDS = {
|
||||
'accept_on', 'alias', 'always', 'always_comb', 'always_ff', 'always_latch',
|
||||
'and', 'assert', 'assign', 'assume', 'automatic', 'before', 'begin', 'bind',
|
||||
'bins', 'binsof', 'bit', 'break', 'buf', 'bufif0', 'bufif1', 'byte', 'case',
|
||||
'casex', 'casez', 'cell', 'chandle', 'checker', 'class', 'clocking', 'cmos',
|
||||
'config', 'const', 'constraint', 'context', 'continue', 'cover', 'covergroup',
|
||||
'coverpoint', 'cross', 'deassign', 'default', 'defparam', 'design', 'disable',
|
||||
'dist', 'do', 'edge', 'else', 'end', 'endcase', 'endchecker', 'endclass',
|
||||
'endclocking', 'endconfig', 'endfunction', 'endgenerate', 'endgroup',
|
||||
'endinterface', 'endmodule', 'endpackage', 'endprimitive', 'endprogram',
|
||||
'endproperty', 'endspecify', 'endsequence', 'endtable', 'endtask', 'enum',
|
||||
'event', 'eventually', 'expect', 'export', 'extends', 'extern', 'final',
|
||||
'first_match', 'for', 'force', 'foreach', 'forever', 'fork', 'forkjoin',
|
||||
'function', 'generate', 'genvar', 'global', 'highz0', 'highz1', 'if', 'iff',
|
||||
'ifnone', 'ignore_bins', 'illegal_bins', 'implements', 'implies', 'import',
|
||||
'incdir', 'include', 'initial', 'inout', 'input', 'inside', 'instance',
|
||||
'int', 'integer', 'interconnect', 'interface', 'intersect', 'join',
|
||||
'join_any', 'join_none', 'large', 'let', 'liblist', 'library', 'local',
|
||||
'localparam', 'logic', 'longint', 'macromodule', 'matches', 'medium',
|
||||
'modport', 'module', 'nand', 'negedge', 'nettype', 'new', 'nexttime', 'nmos',
|
||||
'nor', 'noshowcancelled', 'not', 'notif0', 'notif1', 'null', 'or', 'output',
|
||||
'package', 'packed', 'parameter', 'pmos', 'posedge', 'primitive', 'priority',
|
||||
'program', 'property', 'protected', 'pull0', 'pull1', 'pulldown', 'pullup',
|
||||
'pulsestyle_ondetect', 'pulsestyle_onevent', 'pure', 'rand', 'randc',
|
||||
'randcase', 'randsequence', 'rcmos', 'real', 'realtime', 'ref', 'reg',
|
||||
'reject_on', 'release', 'repeat', 'restrict', 'return', 'rnmos', 'rpmos',
|
||||
'rtran', 'rtranif0', 'rtranif1', 's_always', 's_eventually', 's_nexttime',
|
||||
's_until', 's_until_with', 'scalared', 'sequence', 'shortint', 'shortreal',
|
||||
'showcancelled', 'signed', 'small', 'soft', 'solve', 'specify', 'specparam',
|
||||
'static', 'string', 'strong', 'strong0', 'strong1', 'struct', 'super',
|
||||
'supply0', 'supply1', 'sync_accept_on', 'sync_reject_on', 'table', 'tagged',
|
||||
'task', 'this', 'throughout', 'time', 'timeprecision', 'timeunit', 'tran',
|
||||
'tranif0', 'tranif1', 'tri', 'tri0', 'tri1', 'triand', 'trior', 'trireg',
|
||||
'type', 'typedef', 'union', 'unique', 'unique0', 'unsigned', 'until',
|
||||
'until_with', 'untyped', 'use', 'uwire', 'var', 'vectored', 'virtual', 'void',
|
||||
'wait', 'wait_order', 'wand', 'weak', 'weak0', 'weak1', 'while', 'wildcard',
|
||||
'wire', 'with', 'within', 'wor', 'xnor', 'xor'
|
||||
}
|
||||
|
||||
|
||||
def kw_filter(s: str) -> str:
|
||||
"""
|
||||
Make all user identifiers 'safe' and ensure they do not collide with
|
||||
SystemVerilog keywords.
|
||||
|
||||
If an SV keyword is encountered, add an underscore suffix
|
||||
"""
|
||||
if s in SV_KEYWORDS:
|
||||
s += "_"
|
||||
return s
|
||||
103
src/peakrdl_busdecoder/module_tmpl.sv
Normal file
103
src/peakrdl_busdecoder/module_tmpl.sv
Normal file
@@ -0,0 +1,103 @@
|
||||
|
||||
//==========================================================
|
||||
// Module: {{ds.module_name}}
|
||||
// Description: CPU Interface Bus Decoder
|
||||
// Author: PeakRDL-busdecoder
|
||||
// License: GLPLv3
|
||||
// Date: {{current_date}}
|
||||
// Version: {{version}}
|
||||
// Links:
|
||||
// - https://github.com/SystemRDL/PeakRDL-busdecoder
|
||||
//==========================================================
|
||||
|
||||
|
||||
module {{ds.module_name}}
|
||||
{%- if cpuif.parameters %} #(
|
||||
{{-cpuif.parameters|join(",\n")|indent(8)}}
|
||||
) {%- endif %} (
|
||||
{{-cpuif.port_declaration|indent(8)}}
|
||||
);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// CPU Bus interface logic
|
||||
//--------------------------------------------------------------------------
|
||||
logic cpuif_req;
|
||||
logic [{{cpuif.addr_width-1}}:0] cpuif_wr_addr;
|
||||
logic [{{cpuif.addr_width-1}}:0] cpuif_rd_addr;
|
||||
|
||||
logic cpuif_wr_ack;
|
||||
logic cpuif_wr_err;
|
||||
logic [{{cpuif.data_width-1}}:0] cpuif_wr_data;
|
||||
logic [{{cpuif.data_width//8-1}}:0] cpuif_wr_byte_en;
|
||||
|
||||
logic cpuif_rd_ack;
|
||||
logic cpuif_rd_err;
|
||||
logic [{{cpuif.data_width-1}}:0] cpuif_rd_data;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Child instance signals
|
||||
//--------------------------------------------------------------------------
|
||||
logic [{{cpuif.addressable_children | length}}-1:0] cpuif_wr_sel;
|
||||
logic [{{cpuif.addressable_children | length}}-1:0] cpuif_rd_sel;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Slave <-> Internal CPUIF <-> Master
|
||||
//--------------------------------------------------------------------------
|
||||
{{-cpuif.get_implementation()|indent}}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Write Address Decoder
|
||||
//--------------------------------------------------------------------------
|
||||
always_comb begin
|
||||
// Default all write select signals to 0
|
||||
cpuif_wr_sel = '0;
|
||||
|
||||
if (cpuif_req && cpuif_wr_en) begin
|
||||
// A write request is pending
|
||||
{%- for child in cpuif.addressable_children -%}
|
||||
{%- if loop.first -%}
|
||||
if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||
{%- else -%}
|
||||
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||
{%- endif -%}
|
||||
// Address matched for {{child.inst_name}}
|
||||
cpuif_wr_sel[{{loop.index0}}] = 1'b1;
|
||||
{%- endfor -%}
|
||||
end else begin
|
||||
// No address match, all select signals remain 0
|
||||
cpuif_wr_err = 1'b1; // Indicate error on no match
|
||||
end
|
||||
end else begin
|
||||
// No write request, all select signals remain 0
|
||||
end
|
||||
end
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Read Address Decoder
|
||||
//--------------------------------------------------------------------------
|
||||
always_comb begin
|
||||
// Default all read select signals to 0
|
||||
cpuif_rd_sel = '0;
|
||||
|
||||
|
||||
if (cpuif_req && !cpuif_wr_en) begin
|
||||
// A read request is pending
|
||||
{%- for child in cpuif.addressable_children -%}
|
||||
{%- if loop.first -%}
|
||||
if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||
{%- else -%}
|
||||
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||
{%- endif -%}
|
||||
// Address matched for {{child.inst_name}}
|
||||
cpuif_rd_sel[{{loop.index0}}] = 1'b1;
|
||||
{%- endfor -%}
|
||||
end else begin
|
||||
// No address match, all select signals remain 0
|
||||
cpuif_rd_err = 1'b1; // Indicate error on no match
|
||||
end
|
||||
end else begin
|
||||
// No read request, all select signals remain 0
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
{# (eof newline anchor) #}
|
||||
10
src/peakrdl_busdecoder/package_tmpl.sv
Normal file
10
src/peakrdl_busdecoder/package_tmpl.sv
Normal file
@@ -0,0 +1,10 @@
|
||||
// Generated by PeakRDL-busdecoder - A free and open-source SystemVerilog generator
|
||||
// https://github.com/SystemRDL/PeakRDL-busdecoder
|
||||
|
||||
package {{ds.package_name}};
|
||||
|
||||
localparam {{ds.module_name.upper()}}_DATA_WIDTH = {{ds.cpuif_data_width}};
|
||||
localparam {{ds.module_name.upper()}}_MIN_ADDR_WIDTH = {{ds.addr_width}};
|
||||
localparam {{ds.module_name.upper()}}_SIZE = {{SVInt(ds.top_node.size)}};
|
||||
endpackage
|
||||
{# (eof newline anchor) #}
|
||||
47
src/peakrdl_busdecoder/scan_design.py
Normal file
47
src/peakrdl_busdecoder/scan_design.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
|
||||
from systemrdl.node import RegNode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from systemrdl.node import Node, AddressableNode, AddrmapNode
|
||||
from .exporter import DesignState
|
||||
|
||||
|
||||
class DesignScanner(RDLListener):
|
||||
"""
|
||||
Scans through the register model and validates that any unsupported features
|
||||
are not present.
|
||||
|
||||
Also collects any information that is required prior to the start of the export process.
|
||||
"""
|
||||
|
||||
def __init__(self, ds: "DesignState") -> None:
|
||||
self.ds = ds
|
||||
self.msg = self.top_node.env.msg
|
||||
|
||||
@property
|
||||
def top_node(self) -> "AddrmapNode":
|
||||
return self.ds.top_node
|
||||
|
||||
def do_scan(self) -> None:
|
||||
RDLWalker().walk(self.top_node, self)
|
||||
if self.msg.had_error:
|
||||
self.msg.fatal("Unable to export due to previous errors")
|
||||
|
||||
def enter_Component(self, node: "Node") -> WalkerAction | None:
|
||||
if node.external and (node != self.top_node):
|
||||
# Do not inspect external components. None of my business
|
||||
return WalkerAction.SkipDescendants
|
||||
|
||||
# Collect any signals that are referenced by a property
|
||||
for prop_name in node.list_properties():
|
||||
_ = node.get_property(prop_name)
|
||||
|
||||
return WalkerAction.Continue
|
||||
|
||||
def enter_AddressableComponent(self, node: "AddressableNode") -> None:
|
||||
if node.external and node != self.top_node:
|
||||
self.ds.has_external_addressable = True
|
||||
if not isinstance(node, RegNode):
|
||||
self.ds.has_external_block = True
|
||||
15
src/peakrdl_busdecoder/sv_int.py
Normal file
15
src/peakrdl_busdecoder/sv_int.py
Normal file
@@ -0,0 +1,15 @@
|
||||
class SVInt:
|
||||
def __init__(self, value: int, width: int | None = None) -> None:
|
||||
self.value = value
|
||||
self.width = width
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.width is not None:
|
||||
# Explicit width
|
||||
return f"{self.width}'h{self.value:x}"
|
||||
elif self.value.bit_length() > 32:
|
||||
# SV standard only enforces that unsized literals shall be at least 32-bits
|
||||
# To support larger literals, they need to be sized explicitly
|
||||
return f"{self.value.bit_length()}'h{self.value:x}"
|
||||
else:
|
||||
return f"'h{self.value:x}"
|
||||
18
src/peakrdl_busdecoder/udps/__init__.py
Normal file
18
src/peakrdl_busdecoder/udps/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from systemrdl.udp import UDPDefinition
|
||||
from .rw_buffering import BufferWrites, WBufferTrigger
|
||||
from .rw_buffering import BufferReads, RBufferTrigger
|
||||
from .extended_swacc import ReadSwacc, WriteSwacc
|
||||
from .fixedpoint import IntWidth, FracWidth
|
||||
from .signed import IsSigned
|
||||
|
||||
ALL_UDPS: list[type[UDPDefinition]] = [
|
||||
BufferWrites,
|
||||
WBufferTrigger,
|
||||
BufferReads,
|
||||
RBufferTrigger,
|
||||
ReadSwacc,
|
||||
WriteSwacc,
|
||||
IntWidth,
|
||||
FracWidth,
|
||||
IsSigned,
|
||||
]
|
||||
23
src/peakrdl_busdecoder/udps/extended_swacc.py
Normal file
23
src/peakrdl_busdecoder/udps/extended_swacc.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from systemrdl.udp import UDPDefinition
|
||||
from systemrdl.component import Field
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from systemrdl.node import Node
|
||||
|
||||
class ReadSwacc(UDPDefinition):
|
||||
name = "rd_swacc"
|
||||
valid_components = {Field}
|
||||
valid_type = bool
|
||||
|
||||
def get_unassigned_default(self, node: 'Node') -> Any:
|
||||
return False
|
||||
|
||||
class WriteSwacc(UDPDefinition):
|
||||
name = "wr_swacc"
|
||||
valid_components = {Field}
|
||||
valid_type = bool
|
||||
|
||||
def get_unassigned_default(self, node: 'Node') -> Any:
|
||||
return False
|
||||
73
src/peakrdl_busdecoder/udps/fixedpoint.py
Normal file
73
src/peakrdl_busdecoder/udps/fixedpoint.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from typing import Any
|
||||
|
||||
from systemrdl.component import Field
|
||||
from systemrdl.node import Node, FieldNode
|
||||
from systemrdl.udp import UDPDefinition
|
||||
|
||||
|
||||
class _FixedpointWidth(UDPDefinition):
|
||||
valid_components = {Field}
|
||||
valid_type = int
|
||||
|
||||
def validate(self, node: "Node", value: Any) -> None:
|
||||
assert isinstance(node, FieldNode)
|
||||
|
||||
intwidth = node.get_property("intwidth")
|
||||
fracwidth = node.get_property("fracwidth")
|
||||
assert intwidth is not None
|
||||
assert fracwidth is not None
|
||||
prop_ref = node.inst.property_src_ref.get(self.name)
|
||||
|
||||
# incompatible with "counter" fields
|
||||
if node.get_property("counter"):
|
||||
self.msg.error(
|
||||
"Fixed-point representations are not supported for counter fields.",
|
||||
prop_ref
|
||||
)
|
||||
|
||||
# incompatible with "encode" fields
|
||||
if node.get_property("encode") is not None:
|
||||
self.msg.error(
|
||||
"Fixed-point representations are not supported for fields encoded as an enum.",
|
||||
prop_ref
|
||||
)
|
||||
|
||||
# ensure node width = fracwidth + intwidth
|
||||
if intwidth + fracwidth != node.width:
|
||||
self.msg.error(
|
||||
f"Number of integer bits ({intwidth}) plus number of fractional bits ({fracwidth})"
|
||||
f" must be equal to the width of the component ({node.width}).",
|
||||
prop_ref
|
||||
)
|
||||
|
||||
|
||||
class IntWidth(_FixedpointWidth):
|
||||
name = "intwidth"
|
||||
|
||||
def get_unassigned_default(self, node: "Node") -> Any:
|
||||
"""
|
||||
If 'fracwidth' is defined, 'intwidth' is inferred from the node width.
|
||||
"""
|
||||
assert isinstance(node, FieldNode)
|
||||
fracwidth = node.get_property("fracwidth", default=None)
|
||||
if fracwidth is not None:
|
||||
return node.width - fracwidth
|
||||
else:
|
||||
# not a fixed-point number
|
||||
return None
|
||||
|
||||
|
||||
class FracWidth(_FixedpointWidth):
|
||||
name = "fracwidth"
|
||||
|
||||
def get_unassigned_default(self, node: "Node") -> Any:
|
||||
"""
|
||||
If 'intwidth' is defined, 'fracwidth' is inferred from the node width.
|
||||
"""
|
||||
assert isinstance(node, FieldNode)
|
||||
intwidth = node.get_property("intwidth", default=None)
|
||||
if intwidth is not None:
|
||||
return node.width - intwidth
|
||||
else:
|
||||
# not a fixed-point number
|
||||
return None
|
||||
130
src/peakrdl_busdecoder/udps/rw_buffering.py
Normal file
130
src/peakrdl_busdecoder/udps/rw_buffering.py
Normal file
@@ -0,0 +1,130 @@
|
||||
from typing import Any
|
||||
|
||||
from systemrdl.udp import UDPDefinition
|
||||
from systemrdl.component import Reg
|
||||
from systemrdl.rdltypes.references import RefType, PropertyReference
|
||||
from systemrdl.rdltypes import NoValue
|
||||
from systemrdl.node import Node, RegNode, VectorNode, SignalNode, FieldNode
|
||||
|
||||
|
||||
class xBufferTrigger(UDPDefinition):
|
||||
valid_components = {Reg}
|
||||
valid_type = RefType
|
||||
|
||||
def validate(self, node: Node, value: Any) -> None:
|
||||
# TODO: Reference shall not cross an internal/external boundary
|
||||
|
||||
if value is NoValue:
|
||||
self.msg.error(
|
||||
"Double-buffer trigger property is missing a value assignment",
|
||||
self.get_src_ref(node)
|
||||
)
|
||||
elif isinstance(value, VectorNode):
|
||||
# Trigger can reference a vector, but only if it is a single-bit
|
||||
if value.width != 1:
|
||||
self.msg.error(
|
||||
"%s '%s' references %s '%s' but its width is not 1"
|
||||
% (
|
||||
type(node.inst).__name__.lower(), node.inst_name,
|
||||
type(value.inst).__name__.lower(), value.inst_name
|
||||
),
|
||||
self.get_src_ref(node)
|
||||
)
|
||||
|
||||
if isinstance(value, SignalNode):
|
||||
if not value.get_property('activehigh') and not value.get_property('activelow'):
|
||||
self.msg.error(
|
||||
"Trigger was asigned a signal, but it does not specify whether it is activehigh/activelow",
|
||||
self.get_src_ref(node)
|
||||
)
|
||||
|
||||
elif isinstance(value, PropertyReference) and value.width is not None:
|
||||
# Trigger can reference a property, but only if it is a single-bit
|
||||
if value.width != 1:
|
||||
self.msg.error(
|
||||
"%s '%s' references property '%s->%s' but its width is not 1"
|
||||
% (
|
||||
type(node.inst).__name__.lower(), node.inst_name,
|
||||
value.node.inst_name, value.name,
|
||||
),
|
||||
self.get_src_ref(node)
|
||||
)
|
||||
elif isinstance(value, RegNode):
|
||||
# Trigger can reference a register, which implies access of the
|
||||
# 'correct half' of the register is the trigger.
|
||||
# For buffered writes, it is the upper-half.
|
||||
# For buffered reads, it is the lower-half.
|
||||
pass
|
||||
else:
|
||||
# All other reference types are invalid
|
||||
self.msg.error(
|
||||
"Reference to a %s component is incompatible with the '%s' property."
|
||||
% (type(node.inst).__name__.lower(), self.name),
|
||||
self.get_src_ref(node)
|
||||
)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
class BufferWrites(UDPDefinition):
|
||||
name = "buffer_writes"
|
||||
valid_components = {Reg}
|
||||
valid_type = bool
|
||||
|
||||
def validate(self, node: 'Node', value: Any) -> None:
|
||||
assert isinstance(node, RegNode)
|
||||
if value:
|
||||
if not node.has_sw_writable:
|
||||
self.msg.error(
|
||||
"'buffer_writes' is set to true, but this register does not contain any writable fields.",
|
||||
self.get_src_ref(node)
|
||||
)
|
||||
|
||||
def get_unassigned_default(self, node: 'Node') -> Any:
|
||||
return False
|
||||
|
||||
|
||||
class WBufferTrigger(xBufferTrigger):
|
||||
name = "wbuffer_trigger"
|
||||
|
||||
def get_unassigned_default(self, node: 'Node') -> Any:
|
||||
# If buffering is enabled, trigger is the register itself
|
||||
if node.get_property('buffer_writes'):
|
||||
return node
|
||||
return None
|
||||
|
||||
def validate(self, node: Node, value: Any) -> None:
|
||||
super().validate(node, value)
|
||||
|
||||
if isinstance(value, FieldNode):
|
||||
if value.parent == node:
|
||||
self.msg.error(
|
||||
"Trigger for a write-buffered register cannot be a field "
|
||||
"within the same register since the buffering makes it impossible to trigger."
|
||||
)
|
||||
|
||||
|
||||
class BufferReads(UDPDefinition):
|
||||
name = "buffer_reads"
|
||||
valid_components = {Reg}
|
||||
valid_type = bool
|
||||
|
||||
def validate(self, node: 'Node', value: Any) -> None:
|
||||
assert isinstance(node, RegNode)
|
||||
if value:
|
||||
if not node.has_sw_readable:
|
||||
self.msg.error(
|
||||
"'buffer_reads' is set to true, but this register does not contain any readable fields.",
|
||||
self.get_src_ref(node)
|
||||
)
|
||||
|
||||
def get_unassigned_default(self, node: 'Node') -> Any:
|
||||
return False
|
||||
|
||||
|
||||
class RBufferTrigger(xBufferTrigger):
|
||||
name = "rbuffer_trigger"
|
||||
|
||||
def get_unassigned_default(self, node: 'Node') -> Any:
|
||||
# If buffering is enabled, trigger is the register itself
|
||||
if node.get_property('buffer_reads'):
|
||||
return node
|
||||
return None
|
||||
33
src/peakrdl_busdecoder/udps/signed.py
Normal file
33
src/peakrdl_busdecoder/udps/signed.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from typing import Any
|
||||
|
||||
from systemrdl.component import Field
|
||||
from systemrdl.node import Node
|
||||
from systemrdl.udp import UDPDefinition
|
||||
|
||||
|
||||
class IsSigned(UDPDefinition):
|
||||
name = "is_signed"
|
||||
valid_components = {Field}
|
||||
valid_type = bool
|
||||
default_assignment = True
|
||||
|
||||
def validate(self, node: "Node", value: Any) -> None:
|
||||
# "counter" fields can not be signed
|
||||
if value and node.get_property("counter"):
|
||||
self.msg.error(
|
||||
"The property is_signed=true is not supported for counter fields.",
|
||||
node.inst.property_src_ref["is_signed"]
|
||||
)
|
||||
|
||||
# incompatible with "encode" fields
|
||||
if value and node.get_property("encode") is not None:
|
||||
self.msg.error(
|
||||
"The property is_signed=true is not supported for fields encoded as an enum.",
|
||||
node.inst.property_src_ref["is_signed"]
|
||||
)
|
||||
|
||||
def get_unassigned_default(self, node: "Node") -> Any:
|
||||
"""
|
||||
Unsigned by default if not specified.
|
||||
"""
|
||||
return False
|
||||
121
src/peakrdl_busdecoder/utils.py
Normal file
121
src/peakrdl_busdecoder/utils.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import re
|
||||
from typing import Match, overload
|
||||
|
||||
from systemrdl.rdltypes.references import PropertyReference
|
||||
from systemrdl.node import Node, AddrmapNode
|
||||
|
||||
from .identifier_filter import kw_filter as kwf
|
||||
from .sv_int import SVInt
|
||||
|
||||
|
||||
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 ReplaceUnknown:
|
||||
def __init__(self) -> None:
|
||||
self.i = 0
|
||||
|
||||
def __call__(self, match: Match[str]) -> str:
|
||||
s = f"i{self.i}"
|
||||
self.i += 1
|
||||
return s
|
||||
|
||||
path = re.sub(r"!", ReplaceUnknown(), path)
|
||||
|
||||
# Sanitize any SV keywords
|
||||
def kw_filter_repl(m: Match[str]) -> str:
|
||||
return kwf(m.group(0))
|
||||
|
||||
path = re.sub(r"\w+", kw_filter_repl, path)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def clog2(n: int) -> int:
|
||||
return (n - 1).bit_length()
|
||||
|
||||
|
||||
def is_pow2(x: int) -> bool:
|
||||
return (x > 0) and ((x & (x - 1)) == 0)
|
||||
|
||||
|
||||
def roundup_pow2(x: int) -> int:
|
||||
return 1 << (x - 1).bit_length()
|
||||
|
||||
|
||||
def ref_is_internal(top_node: AddrmapNode, ref: Node | PropertyReference) -> bool:
|
||||
"""
|
||||
Determine whether the reference is internal to the top node.
|
||||
|
||||
For the sake of this exporter, root signals are treated as internal.
|
||||
"""
|
||||
# current_node: Optional[Node]
|
||||
if isinstance(ref, Node):
|
||||
current_node = ref
|
||||
elif isinstance(ref, PropertyReference):
|
||||
current_node = ref.node
|
||||
else:
|
||||
raise RuntimeError
|
||||
|
||||
while current_node is not None:
|
||||
if current_node == top_node:
|
||||
# reached top node without finding any external components
|
||||
# is internal!
|
||||
return True
|
||||
|
||||
if current_node.external:
|
||||
# not internal!
|
||||
return False
|
||||
|
||||
current_node = current_node.parent
|
||||
|
||||
# A root signal was referenced, which dodged the top addrmap
|
||||
# This is considered internal for this exporter
|
||||
return True
|
||||
|
||||
|
||||
@overload
|
||||
def do_slice(value: SVInt, high: int, low: int) -> SVInt: ...
|
||||
@overload
|
||||
def do_slice(value: str, high: int, low: int) -> str: ...
|
||||
def do_slice(value: SVInt | str, high: int, low: int) -> SVInt | str:
|
||||
if isinstance(value, str):
|
||||
# If string, assume this is an identifier. Append bit-slice
|
||||
if high == low:
|
||||
return f"{value}[{low}]"
|
||||
else:
|
||||
return f"{value}[{high}:{low}]"
|
||||
else:
|
||||
# it is an SVInt literal. Slice it down
|
||||
mask = (1 << (high + 1)) - 1
|
||||
v = (value.value & mask) >> low
|
||||
|
||||
if value.width is not None:
|
||||
w = high - low + 1
|
||||
else:
|
||||
w = None
|
||||
|
||||
return SVInt(v, w)
|
||||
|
||||
|
||||
@overload
|
||||
def do_bitswap(value: SVInt) -> SVInt: ...
|
||||
@overload
|
||||
def do_bitswap(value: str) -> str: ...
|
||||
def do_bitswap(value: SVInt | str) -> SVInt | str:
|
||||
if isinstance(value, str):
|
||||
# If string, assume this is an identifier. Wrap in a streaming operator
|
||||
return "{<<{" + value + "}}"
|
||||
else:
|
||||
# it is an SVInt literal. bitswap it
|
||||
assert value.width is not None # width must be known!
|
||||
v = value.value
|
||||
vswap = 0
|
||||
for _ in range(value.width):
|
||||
vswap = (vswap << 1) + (v & 1)
|
||||
v >>= 1
|
||||
return SVInt(vswap, value.width)
|
||||
208
src/peakrdl_busdecoder/validate_design.py
Normal file
208
src/peakrdl_busdecoder/validate_design.py
Normal file
@@ -0,0 +1,208 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
|
||||
from systemrdl.rdltypes import PropertyReference
|
||||
from systemrdl.node import Node, RegNode, FieldNode, SignalNode, AddressableNode
|
||||
from systemrdl.node import RegfileNode, AddrmapNode
|
||||
|
||||
from .utils import roundup_pow2, is_pow2
|
||||
|
||||
from .utils import ref_is_internal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .exporter import BusDecoderExporter
|
||||
|
||||
|
||||
class DesignValidator(RDLListener):
|
||||
"""
|
||||
Performs additional rule-checks on the design that check for limitations
|
||||
imposed by this exporter.
|
||||
"""
|
||||
|
||||
def __init__(self, exp: "BusDecoderExporter") -> None:
|
||||
self.exp = exp
|
||||
self.ds = exp.ds
|
||||
self.msg = self.top_node.env.msg
|
||||
|
||||
self._contains_external_block_stack: list[bool] = []
|
||||
self.contains_external_block = False
|
||||
|
||||
@property
|
||||
def top_node(self) -> "AddrmapNode":
|
||||
return self.exp.ds.top_node
|
||||
|
||||
def do_validate(self) -> None:
|
||||
RDLWalker().walk(self.top_node, self)
|
||||
if self.msg.had_error:
|
||||
self.msg.fatal("Unable to export due to previous errors")
|
||||
|
||||
def enter_Component(self, node: "Node") -> WalkerAction | None:
|
||||
if node.external and (node != self.top_node):
|
||||
# Do not inspect external components. None of my business
|
||||
return WalkerAction.SkipDescendants
|
||||
|
||||
# Check if any property references reach across the internal/external boundary
|
||||
for prop_name in node.list_properties():
|
||||
value = node.get_property(prop_name)
|
||||
if isinstance(value, (PropertyReference, Node)):
|
||||
if not ref_is_internal(self.top_node, value):
|
||||
if isinstance(value, PropertyReference):
|
||||
src_ref = value.src_ref
|
||||
else:
|
||||
src_ref = node.inst.property_src_ref.get(
|
||||
prop_name, node.inst.inst_src_ref
|
||||
)
|
||||
self.msg.error(
|
||||
"Property is assigned a reference that points to a component not internal to the busdecoder being exported.",
|
||||
src_ref,
|
||||
)
|
||||
return None
|
||||
|
||||
def enter_Signal(self, node: "SignalNode") -> None:
|
||||
# If encountering a CPUIF reset that is nested within the register model,
|
||||
# warn that it will be ignored.
|
||||
# Only cpuif resets in the top-level node or above will be honored
|
||||
if node.get_property("cpuif_reset") and (node.parent != self.top_node):
|
||||
self.msg.warning(
|
||||
"Only cpuif_reset signals that are instantiated in the top-level "
|
||||
"addrmap or above will be honored. Any cpuif_reset signals nested "
|
||||
"within children of the addrmap being exported will be ignored.",
|
||||
node.inst.inst_src_ref,
|
||||
)
|
||||
|
||||
def enter_AddressableComponent(self, node: "AddressableNode") -> None:
|
||||
# All registers must be aligned to the internal data bus width
|
||||
alignment = self.exp.cpuif.data_width_bytes
|
||||
if (node.raw_address_offset % alignment) != 0:
|
||||
self.msg.error(
|
||||
"Unaligned registers are not supported. Address offset of "
|
||||
f"instance '{node.inst_name}' must be a multiple of {alignment}",
|
||||
node.inst.inst_src_ref,
|
||||
)
|
||||
if node.is_array and (node.array_stride % alignment) != 0: # type: ignore # is_array implies stride is not none
|
||||
self.msg.error(
|
||||
"Unaligned registers are not supported. Address stride of "
|
||||
f"instance array '{node.inst_name}' must be a multiple of {alignment}",
|
||||
node.inst.inst_src_ref,
|
||||
)
|
||||
|
||||
if not isinstance(node, RegNode):
|
||||
# Entering a block-like node
|
||||
if node == self.top_node:
|
||||
# Ignore top addrmap's external property when entering
|
||||
self._contains_external_block_stack.append(False)
|
||||
else:
|
||||
self._contains_external_block_stack.append(node.external)
|
||||
|
||||
def enter_Regfile(self, node: RegfileNode) -> None:
|
||||
self._check_sharedextbus(node)
|
||||
|
||||
def enter_Addrmap(self, node: AddrmapNode) -> None:
|
||||
self._check_sharedextbus(node)
|
||||
|
||||
def _check_sharedextbus(self, node: RegfileNode | AddrmapNode) -> None:
|
||||
if node.get_property("sharedextbus"):
|
||||
self.msg.error(
|
||||
"This exporter does not support enabling the 'sharedextbus' property yet.",
|
||||
node.inst.property_src_ref.get("sharedextbus", node.inst.inst_src_ref),
|
||||
)
|
||||
|
||||
def enter_Reg(self, node: "RegNode") -> None:
|
||||
# accesswidth of wide registers must be consistent within the register block
|
||||
accesswidth = node.get_property("accesswidth")
|
||||
regwidth = node.get_property("regwidth")
|
||||
|
||||
if accesswidth < regwidth:
|
||||
# register is 'wide'
|
||||
if accesswidth != self.exp.cpuif.data_width:
|
||||
self.msg.error(
|
||||
f"Multi-word registers that have an accesswidth ({accesswidth}) "
|
||||
"that are inconsistent with this busdecoder's CPU bus width "
|
||||
f"({self.exp.cpuif.data_width}) are not supported.",
|
||||
node.inst.inst_src_ref,
|
||||
)
|
||||
|
||||
def enter_Field(self, node: "FieldNode") -> None:
|
||||
parent_accesswidth = node.parent.get_property("accesswidth")
|
||||
parent_regwidth = node.parent.get_property("regwidth")
|
||||
if (parent_accesswidth < parent_regwidth) and (
|
||||
node.lsb // parent_accesswidth
|
||||
) != (node.msb // parent_accesswidth):
|
||||
# field spans multiple sub-words
|
||||
|
||||
if node.is_sw_writable and not node.parent.get_property("buffer_writes"):
|
||||
# ... and is writable without the protection of double-buffering
|
||||
# Enforce 10.6.1-f
|
||||
self.msg.error(
|
||||
f"Software-writable field '{node.inst_name}' shall not span"
|
||||
" multiple software-accessible subwords. Consider enabling"
|
||||
" write double-buffering.\n"
|
||||
"For more details, see: https://peakrdl-busdecoder.readthedocs.io/en/latest/udps/write_buffering.html",
|
||||
node.inst.inst_src_ref,
|
||||
)
|
||||
|
||||
if node.get_property("onread") is not None and not node.parent.get_property(
|
||||
"buffer_reads"
|
||||
):
|
||||
# ... is modified by an onread action without the atomicity of read buffering
|
||||
# Enforce 10.6.1-f
|
||||
self.msg.error(
|
||||
f"The field '{node.inst_name}' spans multiple software-accessible"
|
||||
" subwords and is modified on-read, making it impossible to"
|
||||
" access its value correctly. Consider enabling read"
|
||||
" double-buffering. \n"
|
||||
"For more details, see: https://peakrdl-busdecoder.readthedocs.io/en/latest/udps/read_buffering.html",
|
||||
node.inst.inst_src_ref,
|
||||
)
|
||||
|
||||
# Check for unsynthesizable reset
|
||||
reset = node.get_property("reset")
|
||||
if not (reset is None or isinstance(reset, int)):
|
||||
# Has reset that is not a constant value
|
||||
resetsignal = node.get_property("resetsignal")
|
||||
if resetsignal:
|
||||
is_async_reset = resetsignal.get_property("async")
|
||||
else:
|
||||
is_async_reset = self.ds.default_reset_async
|
||||
|
||||
if is_async_reset:
|
||||
self.msg.error(
|
||||
"A field that uses an asynchronous reset cannot use a dynamic reset value. This is not synthesizable.",
|
||||
node.inst.inst_src_ref,
|
||||
)
|
||||
|
||||
def exit_AddressableComponent(self, node: AddressableNode) -> None:
|
||||
if not isinstance(node, RegNode):
|
||||
# Exiting block-like node
|
||||
contains_external_block = self._contains_external_block_stack.pop()
|
||||
|
||||
if self._contains_external_block_stack:
|
||||
# Still in the design. Update stack
|
||||
self._contains_external_block_stack[-1] |= contains_external_block
|
||||
else:
|
||||
# Exiting top addrmap. Resolve final answer
|
||||
self.contains_external_block = contains_external_block
|
||||
|
||||
if contains_external_block:
|
||||
# Check that addressing follows strict alignment rules to allow
|
||||
# for simplified address bit-pruning
|
||||
if node.external:
|
||||
err_suffix = "is external"
|
||||
else:
|
||||
err_suffix = "contains an external addrmap/regfile/mem"
|
||||
|
||||
req_align = roundup_pow2(node.size)
|
||||
if (node.raw_address_offset % req_align) != 0:
|
||||
self.msg.error(
|
||||
f"Address offset +0x{node.raw_address_offset:x} of instance '{node.inst_name}' is not a power of 2 multiple of its size 0x{node.size:x}. "
|
||||
f"This is required by the busdecoder exporter if a component {err_suffix}.",
|
||||
node.inst.inst_src_ref,
|
||||
)
|
||||
if node.is_array:
|
||||
assert node.array_stride is not None
|
||||
if not is_pow2(node.array_stride):
|
||||
self.msg.error(
|
||||
f"Address stride of instance array '{node.inst_name}' is not a power of 2"
|
||||
f"This is required by the busdecoder exporter if a component {err_suffix}.",
|
||||
node.inst.inst_src_ref,
|
||||
)
|
||||
Reference in New Issue
Block a user