This commit is contained in:
Arnav Sacheti
2025-10-13 18:39:19 -07:00
parent b4f9eaff71
commit 35015d7051
79 changed files with 2401 additions and 5601 deletions

View File

@@ -19,35 +19,29 @@ jobs:
strategy:
matrix:
python-version:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
include:
- os: ubuntu-latest
# older versions need older OS
- python-version: "3.7"
os: ubuntu-22.04
- python-version: "3.8"
os: ubuntu-22.04
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Install UV
uses: astral-sh/setup-uv@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
run: |
uv venv -p ${{ matrix.python-version }} .venv
- name: Install dependencies
run: |
python -m pip install -r tests/requirements.txt
uv sync --group test
- name: Install
run: |

View File

@@ -4,9 +4,12 @@ build-backend = "setuptools.build_meta"
[project]
name = "peakrdl-busdecoder"
dynamic = ["version"]
requires-python = ">=3.7"
dependencies = ["systemrdl-compiler ~= 1.29", "Jinja2>=2.11"]
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"jinja2>=3.1.6",
"systemrdl-compiler~=1.30.1",
]
authors = [{ name = "Alex Mykyta" }]
description = "Compile SystemRDL into a SystemVerilog control/status register (CSR) block"
@@ -46,8 +49,21 @@ Tracker = "https://github.com/SystemRDL/PeakRDL-busdecoder/issues"
Changelog = "https://github.com/SystemRDL/PeakRDL-busdecoder/releases"
Documentation = "https://peakrdl-busdecoder.readthedocs.io/"
[tool.setuptools.dynamic]
version = { attr = "peakrdl_busdecoder.__about__.__version__" }
[dependency-groups]
docs = [
"pygments-systemrdl>=1.3.0",
"sphinx-book-theme>=1.1.4",
"sphinxcontrib-wavedrom>=3.0.4",
]
test = [
"parameterized>=0.9.0",
"pytest>=7.4.4",
"pytest-cov>=4.1.0",
"pytest-xdist>=3.5.0",
]
tools = [
"ruff>=0.14.0",
]
[project.entry-points."peakrdl.exporters"]
busdecoder = "peakrdl_busdecoder.__peakrdl__:Exporter"

View File

@@ -1,3 +1,3 @@
from .__about__ import __version__
from .exporter import BusDecoderExporter
__all__ = ["BusDecoderExporter"]

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

View File

@@ -1,18 +1,15 @@
from typing import TYPE_CHECKING, Union, List, Optional
from typing import TYPE_CHECKING
from systemrdl.node import FieldNode, RegNode
from systemrdl.walker import WalkerAction
from .utils import get_indexed_path
from .struct_generator import RDLStructGenerator
from .forloop_generator import RDLForLoopGenerator
from .identifier_filter import kw_filter as kwf
from .sv_int import SVInt
if TYPE_CHECKING:
from .exporter import BusDecoderExporter
from systemrdl.node import AddrmapNode, AddressableNode
from systemrdl.node import RegfileNode, MemNode
class AddressDecode:
@@ -23,12 +20,6 @@ class AddressDecode:
def top_node(self) -> "AddrmapNode":
return self.exp.ds.top_node
def get_strobe_struct(self) -> str:
struct_gen = DecodeStructGenerator()
s = struct_gen.get_struct(self.top_node, "decoded_reg_strb_t")
assert s is not None # guaranteed to have at least one reg
return s
def get_implementation(self) -> str:
gen = DecodeLogicGenerator(self)
s = gen.get_content(self.top_node)
@@ -36,7 +27,7 @@ class AddressDecode:
return s
def get_access_strobe(
self, node: Union[RegNode, FieldNode], reduce_substrobes: bool = True
self, node: RegNode | FieldNode, reduce_substrobes: bool = True
) -> str:
"""
Returns the Verilog string that represents the register/field's access strobe.
@@ -73,77 +64,24 @@ class AddressDecode:
return "decoded_reg_strb." + path
class DecodeStructGenerator(RDLStructGenerator):
def _enter_external_block(self, node: "AddressableNode") -> None:
self.add_member(
kwf(node.inst_name),
array_dimensions=node.array_dimensions,
)
def enter_Addrmap(self, node: "AddrmapNode") -> Optional[WalkerAction]:
assert node.external
self._enter_external_block(node)
return WalkerAction.SkipDescendants
def exit_Addrmap(self, node: "AddrmapNode") -> None:
assert node.external
def enter_Regfile(self, node: "RegfileNode") -> Optional[WalkerAction]:
if node.external:
self._enter_external_block(node)
return WalkerAction.SkipDescendants
super().enter_Regfile(node)
return WalkerAction.Continue
def exit_Regfile(self, node: "RegfileNode") -> None:
if node.external:
return
super().exit_Regfile(node)
def enter_Mem(self, node: "MemNode") -> Optional[WalkerAction]:
assert node.external
self._enter_external_block(node)
return WalkerAction.SkipDescendants
def exit_Mem(self, node: "MemNode") -> None:
assert node.external
def enter_Reg(self, node: "RegNode") -> None:
# if register is "wide", expand the strobe to be able to access the sub-words
n_subwords = node.get_property("regwidth") // node.get_property("accesswidth")
self.add_member(
kwf(node.inst_name),
width=n_subwords,
array_dimensions=node.array_dimensions,
)
# Stub out
def exit_Reg(self, node: "RegNode") -> None:
pass
def enter_Field(self, node: "FieldNode") -> None:
pass
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 = [] # type: List[int]
self._array_stride_stack: list[int] = []
def enter_AddressableComponent(
self, node: "AddressableNode"
) -> Optional[WalkerAction]:
) -> 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 = []
strides: list[int] = []
for dim in reversed(node.array_dimensions):
strides.append(current_stride)
current_stride *= dim

View File

@@ -0,0 +1,3 @@
from .base_cpuif import BaseCpuif
__all__ = ["BaseCpuif"]

View File

@@ -0,0 +1,4 @@
from .apb3_cpuif import APB3Cpuif
from .apb3_cpuif_flat import APB3CpuifFlat
__all__ = ["APB3Cpuif", "APB3CpuifFlat"]

View 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

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

View 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

View File

@@ -0,0 +1,4 @@
from .apb4_cpuif import APB4Cpuif
from .apb4_cpuif_flat import APB4CpuifFlat
__all__ = ["APB4Cpuif", "APB4CpuifFlat"]

View 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

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

View 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

View File

@@ -1,4 +1,4 @@
from ..base import CpuifBase
from ..base_cpuif import CpuifBase
class AXI4Lite_Cpuif(CpuifBase):

View File

@@ -1,8 +1,9 @@
from typing import TYPE_CHECKING, List
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
@@ -10,13 +11,22 @@ if TYPE_CHECKING:
from ..exporter import BusDecoderExporter
class CpuifBase:
class BaseCpuif:
# Path is relative to the location of the class that assigns this variable
template_path = ""
def __init__(self, exp: "BusDecoderExporter"):
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:
@@ -35,12 +45,17 @@ class CpuifBase:
raise NotImplementedError()
@property
def parameters(self) -> List[str]:
def parameters(self) -> list[str]:
"""
Optional list of additional parameters this CPU interface provides to
the module's definition
"""
return []
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:
"""
@@ -53,6 +68,9 @@ class CpuifBase:
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)
@@ -60,14 +78,13 @@ class CpuifBase:
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,
"get_always_ff_event": self.exp.dereferencer.get_always_ff_event,
"get_resetsignal": self.exp.dereferencer.get_resetsignal,
"clog2": clog2,
"is_pow2": is_pow2,
"roundup_pow2": roundup_pow2,
"ds": self.exp.ds,
}

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

View 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

View File

@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Optional, List, Union
from typing import TYPE_CHECKING
import textwrap
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
@@ -6,15 +6,16 @@ from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
if TYPE_CHECKING:
from systemrdl.node import AddressableNode, Node
class Body:
class Body:
def __init__(self) -> None:
self.children = [] # type: List[Union[str, Body]]
self.children: list[str | Body] = []
def __str__(self) -> str:
s = '\n'.join((str(x) for x in self.children))
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__()
@@ -26,7 +27,7 @@ class LoopBody(Body):
s = super().__str__()
return (
f"for({self.i_type} {self.iterator}=0; {self.iterator}<{self.dim}; {self.iterator}++) begin\n"
+ textwrap.indent(s, " ")
+ textwrap.indent(s, "\t")
+ "\nend"
)
@@ -37,7 +38,7 @@ class ForLoopGenerator:
def __init__(self) -> None:
self._loop_level = 0
self._stack = [] # type: List[Body]
self._stack: list[Body] = []
@property
def current_loop(self) -> Body:
@@ -65,7 +66,7 @@ class ForLoopGenerator:
b = Body()
self._stack.append(b)
def finish(self) -> Optional[str]:
def finish(self) -> str | None:
b = self._stack.pop()
assert not self._stack
@@ -73,15 +74,17 @@ class ForLoopGenerator:
return None
return str(b)
class RDLForLoopGenerator(ForLoopGenerator, RDLListener):
def get_content(self, node: 'Node') -> Optional[str]:
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') -> Optional[WalkerAction]:
def enter_AddressableComponent(
self, node: "AddressableNode"
) -> WalkerAction | None:
if not node.array_dimensions:
return None
@@ -89,7 +92,7 @@ class RDLForLoopGenerator(ForLoopGenerator, RDLListener):
self.push_loop(dim)
return None
def exit_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
def exit_AddressableComponent(self, node: "AddressableNode") -> WalkerAction | None:
if not node.array_dimensions:
return None

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

View File

@@ -6,9 +6,5 @@ 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)}};
{{-hwif.get_extra_package_params()|indent}}
{{-hwif.get_package_contents()|indent}}
endpackage
{# (eof newline anchor) #}

View 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

View File

@@ -1,7 +1,5 @@
from typing import Optional
class SVInt:
def __init__(self, value: int, width: Optional[int] = None) -> None:
def __init__(self, value: int, width: int | None = None) -> None:
self.value = value
self.width = width

View File

@@ -1,10 +1,11 @@
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 = [
ALL_UDPS: list[type[UDPDefinition]] = [
BufferWrites,
WBufferTrigger,
BufferReads,

View File

@@ -1,5 +1,5 @@
import re
from typing import Match, Union, Optional
from typing import Match, overload
from systemrdl.rdltypes.references import PropertyReference
from systemrdl.node import Node, AddrmapNode
@@ -7,6 +7,7 @@ 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
@@ -17,35 +18,42 @@ def get_indexed_path(top_node: Node, target_node: Node) -> str:
class ReplaceUnknown:
def __init__(self) -> None:
self.i = 0
def __call__(self, match: Match) -> str:
s = f'i{self.i}'
def __call__(self, match: Match[str]) -> str:
s = f"i{self.i}"
self.i += 1
return s
path = re.sub(r'!', ReplaceUnknown(), path)
path = re.sub(r"!", ReplaceUnknown(), path)
# Sanitize any SV keywords
def kw_filter_repl(m: Match) -> str:
def kw_filter_repl(m: Match[str]) -> str:
return kwf(m.group(0))
path = re.sub(r'\w+', kw_filter_repl, path)
path = re.sub(r"\w+", kw_filter_repl, path)
return path
def clog2(n: int) -> int:
return (n-1).bit_length()
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: Union[Node, PropertyReference]) -> bool:
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]
# current_node: Optional[Node]
if isinstance(ref, Node):
current_node = ref
elif isinstance(ref, PropertyReference):
@@ -70,7 +78,11 @@ def ref_is_internal(top_node: AddrmapNode, ref: Union[Node, PropertyReference])
return True
def do_slice(value: Union[SVInt, str], high: int, low: int) -> Union[SVInt, str]:
@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:
@@ -89,13 +101,18 @@ def do_slice(value: Union[SVInt, str], high: int, low: int) -> Union[SVInt, str]
return SVInt(v, w)
def do_bitswap(value: Union[SVInt, str]) -> Union[SVInt, str]:
@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!
assert value.width is not None # width must be known!
v = value.value
vswap = 0
for _ in range(value.width):

View File

@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Optional, List, Union
from typing import TYPE_CHECKING
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
from systemrdl.rdltypes import PropertyReference
@@ -24,7 +24,7 @@ class DesignValidator(RDLListener):
self.ds = exp.ds
self.msg = self.top_node.env.msg
self._contains_external_block_stack = [] # type: List[bool]
self._contains_external_block_stack: list[bool] = []
self.contains_external_block = False
@property
@@ -36,7 +36,7 @@ class DesignValidator(RDLListener):
if self.msg.had_error:
self.msg.fatal("Unable to export due to previous errors")
def enter_Component(self, node: "Node") -> Optional[WalkerAction]:
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
@@ -100,7 +100,7 @@ class DesignValidator(RDLListener):
def enter_Addrmap(self, node: AddrmapNode) -> None:
self._check_sharedextbus(node)
def _check_sharedextbus(self, node: Union[RegfileNode, AddrmapNode]) -> None:
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.",

View File

@@ -1,2 +0,0 @@
version_info = (1, 1, 1)
__version__ = ".".join([str(n) for n in version_info])

View File

@@ -1,213 +0,0 @@
from typing import TYPE_CHECKING, Dict, Type
import functools
import sys
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 CpuifBase, apb3, apb4, axi4lite, passthrough, avalon
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()},
"default_reset": schema.Choice(["rst", "rst_n", "arst", "arst_n"]),
}
@functools.lru_cache()
def get_cpuifs(self) -> Dict[str, Type[CpuifBase]]:
# All built-in CPUIFs
cpuifs = {
"passthrough": passthrough.PassthroughCpuif,
"apb3": apb3.APB3_Cpuif,
"apb3-flat": apb3.APB3_Cpuif_flattened,
"apb4": apb4.APB4_Cpuif,
"apb4-flat": apb4.APB4_Cpuif_flattened,
"axi4-lite": axi4lite.AXI4Lite_Cpuif,
"axi4-lite-flat": axi4lite.AXI4Lite_Cpuif_flattened,
"avalon-mm": avalon.Avalon_Cpuif,
"avalon-mm-flat": avalon.Avalon_Cpuif_flattened,
}
# Load any cpuifs specified via entry points
for ep, dist 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, CpuifBase):
raise RuntimeError(
f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it not a CpuifBase 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, CpuifBase):
raise RuntimeError(
f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it not a CpuifBase 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(
"--type-style",
dest="type_style",
choices=["lexical", "hier"],
default="lexical",
help="""Choose how HWIF struct type names are generated.
The 'lexical' style will use RDL lexical scope & type names where
possible and attempt to re-use equivalent type definitions.
The 'hier' style uses component's hierarchy as the struct type name. [lexical]
""",
)
arg_group.add_argument(
"--hwif-report",
action="store_true",
default=False,
help="Generate a HWIF report file",
)
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(
"--rt-read-fanin",
action="store_true",
default=False,
help="Enable additional read path retiming. Good for register blocks with large readback fan-in",
)
arg_group.add_argument(
"--rt-read-response",
action="store_true",
default=False,
help="Enable additional retiming stage between readback fan-in and cpu interface",
)
arg_group.add_argument(
"--rt-external",
help="Retime outputs to external components. Specify a comma-separated list of: reg,regfile,mem,addrmap,all",
)
arg_group.add_argument(
"--default-reset",
choices=["rst", "rst_n", "arst", "arst_n"],
default=None,
help="""Choose the default style of reset signal if not explicitly
specified by the SystemRDL design. If unspecified, the default reset
is active-high and synchronous [rst]""",
)
def do_export(self, top_node: "AddrmapNode", options: "argparse.Namespace") -> None:
cpuifs = self.get_cpuifs()
retime_external_reg = False
retime_external_regfile = False
retime_external_mem = False
retime_external_addrmap = False
if options.rt_external:
for key in options.rt_external.split(","):
key = key.strip().lower()
if key == "reg":
retime_external_reg = True
elif key == "regfile":
retime_external_regfile = True
elif key == "mem":
retime_external_mem = True
elif key == "addrmap":
retime_external_addrmap = True
elif key == "all":
retime_external_reg = True
retime_external_regfile = True
retime_external_mem = True
retime_external_addrmap = True
else:
print(
"error: invalid option for --rt-external: '%s'" % key,
file=sys.stderr,
)
# Get default reset. Favor command-line over cfg. Fall back to 'rst'
default_rst = options.default_reset or self.cfg["default_reset"] or "rst"
if default_rst == "rst":
default_reset_activelow = False
default_reset_async = False
elif default_rst == "rst_n":
default_reset_activelow = True
default_reset_async = False
elif default_rst == "arst":
default_reset_activelow = False
default_reset_async = True
elif default_rst == "arst_n":
default_reset_activelow = True
default_reset_async = True
else:
raise RuntimeError
x = BusDecoderExporter()
x.export(
top_node,
options.output,
cpuif_cls=cpuifs[options.cpuif],
module_name=options.module_name,
package_name=options.package_name,
reuse_hwif_typedefs=(options.type_style == "lexical"),
retime_read_fanin=options.rt_read_fanin,
retime_read_response=options.rt_read_response,
retime_external_reg=retime_external_reg,
retime_external_regfile=retime_external_regfile,
retime_external_mem=retime_external_mem,
retime_external_addrmap=retime_external_addrmap,
generate_hwif_report=options.hwif_report,
address_width=options.addr_width,
default_reset_activelow=default_reset_activelow,
default_reset_async=default_reset_async,
)

View File

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

View File

@@ -1,33 +0,0 @@
from ..base import CpuifBase
class APB3_Cpuif(CpuifBase):
template_path = "apb3_tmpl.sv"
is_interface = True
@property
def port_declaration(self) -> str:
return "apb3_intf.slave s_apb"
def signal(self, name:str) -> str:
return "s_apb." + name.upper()
class APB3_Cpuif_flattened(APB3_Cpuif):
is_interface = False
@property
def port_declaration(self) -> str:
lines = [
"input wire " + self.signal("psel"),
"input wire " + self.signal("penable"),
"input wire " + self.signal("pwrite"),
f"input wire [{self.addr_width-1}:0] " + self.signal("paddr"),
f"input wire [{self.data_width-1}:0] " + self.signal("pwdata"),
"output logic " + self.signal("pready"),
f"output logic [{self.data_width-1}:0] " + self.signal("prdata"),
"output logic " + self.signal("pslverr"),
]
return ",\n".join(lines)
def signal(self, name:str) -> str:
return "s_apb_" + name

View File

@@ -1,48 +0,0 @@
{%- 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 -%}
// Request
logic is_active;
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
is_active <= '0;
cpuif_req <= '0;
cpuif_req_is_wr <= '0;
cpuif_addr <= '0;
cpuif_wr_data <= '0;
end else begin
if(~is_active) begin
if({{cpuif.signal("psel")}}) begin
is_active <= '1;
cpuif_req <= '1;
cpuif_req_is_wr <= {{cpuif.signal("pwrite")}};
{%- if cpuif.data_width_bytes == 1 %}
cpuif_addr <= {{cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:0];
{%- else %}
cpuif_addr <= { {{-cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
{%- endif %}
cpuif_wr_data <= {{cpuif.signal("pwdata")}};
end
end else begin
cpuif_req <= '0;
if(cpuif_rd_ack || cpuif_wr_ack) begin
is_active <= '0;
end
end
end
end
assign cpuif_wr_biten = '1;
// Response
assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack;
assign {{cpuif.signal("prdata")}} = cpuif_rd_data;
assign {{cpuif.signal("pslverr")}} = cpuif_rd_err | cpuif_wr_err;

View File

@@ -1,35 +0,0 @@
from ..base import CpuifBase
class APB4_Cpuif(CpuifBase):
template_path = "apb4_tmpl.sv"
is_interface = True
@property
def port_declaration(self) -> str:
return "apb4_intf.slave s_apb"
def signal(self, name:str) -> str:
return "s_apb." + name.upper()
class APB4_Cpuif_flattened(APB4_Cpuif):
is_interface = False
@property
def port_declaration(self) -> str:
lines = [
"input wire " + self.signal("psel"),
"input wire " + self.signal("penable"),
"input wire " + self.signal("pwrite"),
"input wire [2:0] " + self.signal("pprot"),
f"input wire [{self.addr_width-1}:0] " + self.signal("paddr"),
f"input wire [{self.data_width-1}:0] " + self.signal("pwdata"),
f"input wire [{self.data_width_bytes-1}:0] " + self.signal("pstrb"),
"output logic " + self.signal("pready"),
f"output logic [{self.data_width-1}:0] " + self.signal("prdata"),
"output logic " + self.signal("pslverr"),
]
return ",\n".join(lines)
def signal(self, name:str) -> str:
return "s_apb_" + name

View File

@@ -1,51 +0,0 @@
{%- 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 -%}
// Request
logic is_active;
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
is_active <= '0;
cpuif_req <= '0;
cpuif_req_is_wr <= '0;
cpuif_addr <= '0;
cpuif_wr_data <= '0;
cpuif_wr_biten <= '0;
end else begin
if(~is_active) begin
if({{cpuif.signal("psel")}}) begin
is_active <= '1;
cpuif_req <= '1;
cpuif_req_is_wr <= {{cpuif.signal("pwrite")}};
{%- if cpuif.data_width_bytes == 1 %}
cpuif_addr <= {{cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:0];
{%- else %}
cpuif_addr <= { {{-cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
{%- endif %}
cpuif_wr_data <= {{cpuif.signal("pwdata")}};
for(int i=0; i<{{cpuif.data_width_bytes}}; i++) begin
cpuif_wr_biten[i*8 +: 8] <= {8{ {{-cpuif.signal("pstrb")}}[i]}};
end
end
end else begin
cpuif_req <= '0;
if(cpuif_rd_ack || cpuif_wr_ack) begin
is_active <= '0;
end
end
end
end
// Response
assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack;
assign {{cpuif.signal("prdata")}} = cpuif_rd_data;
assign {{cpuif.signal("pslverr")}} = cpuif_rd_err | cpuif_wr_err;

View File

@@ -1,40 +0,0 @@
from ..base import CpuifBase
from ...utils import clog2
class Avalon_Cpuif(CpuifBase):
template_path = "avalon_tmpl.sv"
is_interface = True
@property
def port_declaration(self) -> str:
return "avalon_mm_intf.agent avalon"
def signal(self, name:str) -> str:
return "avalon." + name
@property
def word_addr_width(self) -> int:
# Avalon agents use word addressing, therefore address width is reduced
return self.addr_width - clog2(self.data_width_bytes)
class Avalon_Cpuif_flattened(Avalon_Cpuif):
is_interface = False
@property
def port_declaration(self) -> str:
lines = [
"input wire " + self.signal("read"),
"input wire " + self.signal("write"),
"output logic " + self.signal("waitrequest"),
f"input wire [{self.word_addr_width-1}:0] " + self.signal("address"),
f"input wire [{self.data_width-1}:0] " + self.signal("writedata"),
f"input wire [{self.data_width_bytes-1}:0] " + self.signal("byteenable"),
"output logic " + self.signal("readdatavalid"),
"output logic " + self.signal("writeresponsevalid"),
f"output logic [{self.data_width-1}:0] " + self.signal("readdata"),
"output logic [1:0] " + self.signal("response"),
]
return ",\n".join(lines)
def signal(self, name:str) -> str:
return "avalon_" + name

View File

@@ -1,41 +0,0 @@
{%- if cpuif.is_interface -%}
`ifndef SYNTHESIS
initial begin
assert_bad_addr_width: assert($bits({{cpuif.signal("address")}}) >= {{cpuif.word_addr_width}})
else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("address")}}), {{cpuif.word_addr_width}});
assert_bad_data_width: assert($bits({{cpuif.signal("writedata")}}) == {{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("writedata")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH);
end
`endif
{% endif -%}
// Request
always_comb begin
cpuif_req = {{cpuif.signal("read")}} | {{cpuif.signal("write")}};
cpuif_req_is_wr = {{cpuif.signal("write")}};
{%- if cpuif.data_width_bytes == 1 %}
cpuif_addr = {{cpuif.signal("address")}};
{%- else %}
cpuif_addr = { {{-cpuif.signal("address")}}, {{clog2(cpuif.data_width_bytes)}}'b0};
{%- endif %}
cpuif_wr_data = {{cpuif.signal("writedata")}};
for(int i=0; i<{{cpuif.data_width_bytes}}; i++) begin
cpuif_wr_biten[i*8 +: 8] = {8{ {{-cpuif.signal("byteenable")}}[i]}};
end
{{cpuif.signal("waitrequest")}} = (cpuif_req_stall_rd & {{cpuif.signal("read")}}) | (cpuif_req_stall_wr & {{cpuif.signal("write")}});
end
// Response
always_comb begin
{{cpuif.signal("readdatavalid")}} = cpuif_rd_ack;
{{cpuif.signal("writeresponsevalid")}} = cpuif_wr_ack;
{{cpuif.signal("readdata")}} = cpuif_rd_data;
if(cpuif_rd_err || cpuif_wr_err) begin
// SLVERR
{{cpuif.signal("response")}} = 2'b10;
end else begin
// OK
{{cpuif.signal("response")}} = 2'b00;
end
end

View File

@@ -1,22 +0,0 @@
from ..base import CpuifBase
class PassthroughCpuif(CpuifBase):
template_path = "passthrough_tmpl.sv"
@property
def port_declaration(self) -> str:
lines = [
"input wire s_cpuif_req",
"input wire s_cpuif_req_is_wr",
f"input wire [{self.addr_width-1}:0] s_cpuif_addr",
f"input wire [{self.data_width-1}:0] s_cpuif_wr_data",
f"input wire [{self.data_width-1}:0] s_cpuif_wr_biten",
"output wire s_cpuif_req_stall_wr",
"output wire s_cpuif_req_stall_rd",
"output wire s_cpuif_rd_ack",
"output wire s_cpuif_rd_err",
f"output wire [{self.data_width-1}:0] s_cpuif_rd_data",
"output wire s_cpuif_wr_ack",
"output wire s_cpuif_wr_err",
]
return ",\n".join(lines)

View File

@@ -1,12 +0,0 @@
assign cpuif_req = s_cpuif_req;
assign cpuif_req_is_wr = s_cpuif_req_is_wr;
assign cpuif_addr = s_cpuif_addr;
assign cpuif_wr_data = s_cpuif_wr_data;
assign cpuif_wr_biten = s_cpuif_wr_biten;
assign s_cpuif_req_stall_wr = cpuif_req_stall_wr;
assign s_cpuif_req_stall_rd = cpuif_req_stall_rd;
assign s_cpuif_rd_ack = cpuif_rd_ack;
assign s_cpuif_rd_err = cpuif_rd_err;
assign s_cpuif_rd_data = cpuif_rd_data;
assign s_cpuif_wr_ack = cpuif_wr_ack;
assign s_cpuif_wr_err = cpuif_wr_err;

View File

@@ -1,271 +0,0 @@
from typing import TYPE_CHECKING, Union, Optional
from systemrdl.node import AddrmapNode, FieldNode, SignalNode, RegNode, AddressableNode
from systemrdl.rdltypes import PropertyReference
from .sv_int import SVInt
if TYPE_CHECKING:
from .exporter import BusDecoderExporter, DesignState
from .hwif import Hwif
from .field_logic import FieldLogic
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 hwif(self) -> "Hwif":
return self.exp.hwif
@property
def address_decode(self) -> "AddressDecode":
return self.exp.address_decode
@property
def field_logic(self) -> "FieldLogic":
return self.exp.field_logic
@property
def ds(self) -> "DesignState":
return self.exp.ds
@property
def top_node(self) -> AddrmapNode:
return self.exp.ds.top_node
def get_value(
self,
obj: Union[int, FieldNode, SignalNode, PropertyReference],
width: Optional[int] = None,
) -> Union[SVInt, str]:
"""
Returns the Verilog string that represents the readable value associated
with the object.
If given a simple scalar value, then the corresponding Verilog literal is returned.
If obj references a structural systemrdl object, then the corresponding Verilog
expression is returned that represents its value.
The optional width argument can be provided to hint at the expression's desired bitwidth.
"""
if isinstance(obj, int):
# Is a simple scalar value
return SVInt(obj, width)
if isinstance(obj, FieldNode):
if obj.implements_storage:
return self.field_logic.get_storage_identifier(obj)
if self.hwif.has_value_input(obj):
return self.hwif.get_input_identifier(obj, width)
# Field does not have a storage element, nor does it have a HW input
# must be a constant value as defined by its reset value
reset_value = obj.get_property("reset")
if reset_value is not None:
return self.get_value(reset_value, obj.width)
else:
# No reset value defined!
obj.env.msg.warning(
f"Field '{obj.inst_name}' is a constant but does not have a known value (missing reset). Assigning it a value of X.",
obj.inst.inst_src_ref,
)
return "'X"
if isinstance(obj, SignalNode):
# Signals are always inputs from the hwif
return self.hwif.get_input_identifier(obj, width)
if isinstance(obj, PropertyReference):
if isinstance(obj.node, FieldNode):
return self.get_field_propref_value(obj.node, obj.name, width)
elif isinstance(obj.node, RegNode):
return self.get_reg_propref_value(obj.node, obj.name)
else:
raise RuntimeError
raise RuntimeError(f"Unhandled reference to: {obj}")
def get_field_propref_value(
self,
field: FieldNode,
prop_name: str,
width: Optional[int] = None,
) -> Union[SVInt, str]:
# Value reduction properties.
# Wrap with the appropriate Verilog reduction operator
if prop_name == "anded":
val = self.get_value(field)
return f"&({val})"
elif prop_name == "ored":
val = self.get_value(field)
return f"|({val})"
elif prop_name == "xored":
val = self.get_value(field)
return f"^({val})"
# references that directly access a property value
if prop_name in {
"decrvalue",
"enable",
"haltenable",
"haltmask",
"hwenable",
"hwmask",
"incrvalue",
"mask",
"reset",
"resetsignal",
}:
return self.get_value(field.get_property(prop_name), width)
# Field Next
if prop_name == "next":
prop_value = field.get_property(prop_name)
if prop_value is None:
# unset by the user, points to the implied internal signal
return self.field_logic.get_field_combo_identifier(field, "next")
else:
return self.get_value(prop_value, width)
# References to another component value, or an implied input
if prop_name in {"hwclr", "hwset"}:
prop_value = field.get_property(prop_name)
if prop_value is True:
# Points to inferred hwif input
return self.hwif.get_implied_prop_input_identifier(field, prop_name)
elif prop_value is False:
# This should never happen, as this is checked by the compiler's validator
raise RuntimeError
else:
return self.get_value(prop_value)
# References to another component value, or an implied input
# May have a complementary partner property
complementary_pairs = {
"we": "wel",
"wel": "we",
"swwe": "swwel",
"swwel": "swwe",
}
if prop_name in complementary_pairs:
prop_value = field.get_property(prop_name)
if prop_value is True:
# Points to inferred hwif input
return self.hwif.get_implied_prop_input_identifier(field, prop_name)
elif prop_value is False:
# Try complementary property
prop_value = field.get_property(complementary_pairs[prop_name])
if prop_value is True:
# Points to inferred hwif input
return f"!({self.hwif.get_implied_prop_input_identifier(field, complementary_pairs[prop_name])})"
elif prop_value is False:
# This should never happen, as this is checked by the compiler's validator
raise RuntimeError
else:
return f"!({self.get_value(prop_value)})"
else:
return self.get_value(prop_value, width)
if prop_name == "swacc":
return self.field_logic.get_swacc_identifier(field)
if prop_name == "swmod":
return self.field_logic.get_swmod_identifier(field)
# translate aliases
aliases = {
"saturate": "incrsaturate",
"threshold": "incrthreshold",
}
prop_name = aliases.get(prop_name, prop_name)
# Counter properties
if prop_name == "incr":
return self.field_logic.get_counter_incr_strobe(field)
if prop_name == "decr":
return self.field_logic.get_counter_decr_strobe(field)
if prop_name in {
"decrsaturate",
"decrthreshold",
"incrsaturate",
"incrthreshold",
"overflow",
"underflow",
}:
return self.field_logic.get_field_combo_identifier(field, prop_name)
raise RuntimeError(f"Unhandled reference to: {field}->{prop_name}")
def get_reg_propref_value(self, reg: RegNode, prop_name: str) -> str:
if prop_name in {"halt", "intr"}:
return self.hwif.get_implied_prop_output_identifier(reg, prop_name)
raise NotImplementedError
def get_access_strobe(
self, obj: Union[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)
@property
def default_resetsignal_name(self) -> str:
s = "rst"
if self.ds.default_reset_async:
s = f"a{s}"
if self.ds.default_reset_activelow:
s = f"{s}_n"
return s
def get_resetsignal(self, obj: Optional[SignalNode] = None) -> str:
"""
Returns a normalized active-high reset signal
"""
if isinstance(obj, SignalNode):
s = self.get_value(obj)
if obj.get_property("activehigh"):
return str(s)
else:
return f"~{s}"
# No explicit reset signal specified. Fall back to default reset signal
s = self.default_resetsignal_name
if self.ds.default_reset_activelow:
s = f"~{s}"
return s
def get_always_ff_event(self, resetsignal: Optional[SignalNode] = None) -> str:
if resetsignal is None:
# No explicit reset signal specified. Fall back to default reset signal
if self.ds.default_reset_async:
if self.ds.default_reset_activelow:
return f"@(posedge clk or negedge {self.default_resetsignal_name})"
else:
return f"@(posedge clk or posedge {self.default_resetsignal_name})"
else:
return "@(posedge clk)"
elif resetsignal.get_property("async") and resetsignal.get_property(
"activehigh"
):
return f"@(posedge clk or posedge {self.get_value(resetsignal)})"
elif resetsignal.get_property("async") and not resetsignal.get_property(
"activehigh"
):
return f"@(posedge clk or negedge {self.get_value(resetsignal)})"
return "@(posedge clk)"

View File

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

View File

@@ -1,58 +0,0 @@
from typing import TYPE_CHECKING
from systemrdl.walker import WalkerAction
from systemrdl.node import RegNode
from .forloop_generator import RDLForLoopGenerator
if TYPE_CHECKING:
from .exporter import BusDecoderExporter
from systemrdl.node import AddressableNode
class ExternalWriteAckGenerator(RDLForLoopGenerator):
def __init__(self, exp: "BusDecoderExporter") -> None:
super().__init__()
self.exp = exp
def get_implementation(self) -> str:
content = self.get_content(self.exp.ds.top_node)
if content is None:
return ""
return content
def enter_AddressableComponent(self, node: "AddressableNode") -> WalkerAction:
super().enter_AddressableComponent(node)
if node.external:
if not isinstance(node, RegNode) or node.has_sw_writable:
self.add_content(
f"wr_ack |= {self.exp.hwif.get_external_wr_ack(node)};"
)
return WalkerAction.SkipDescendants
return WalkerAction.Continue
class ExternalReadAckGenerator(RDLForLoopGenerator):
def __init__(self, exp: "BusDecoderExporter") -> None:
super().__init__()
self.exp = exp
def get_implementation(self) -> str:
content = self.get_content(self.exp.ds.top_node)
if content is None:
return ""
return content
def enter_AddressableComponent(self, node: "AddressableNode") -> WalkerAction:
super().enter_AddressableComponent(node)
if node.external:
if not isinstance(node, RegNode) or node.has_sw_readable:
self.add_content(
f"rd_ack |= {self.exp.hwif.get_external_rd_ack(node)};"
)
return WalkerAction.SkipDescendants
return WalkerAction.Continue

View File

@@ -1,576 +0,0 @@
from typing import TYPE_CHECKING, Union
from systemrdl.rdltypes import PrecedenceType, InterruptType
from .bases import AssignmentPrecedence, NextStateConditional
from . import sw_onread
from . import sw_onwrite
from . import sw_singlepulse
from . import hw_write
from . import hw_set_clr
from . import hw_interrupts
from . import hw_interrupts_with_write
from ..utils import get_indexed_path
from ..sv_int import SVInt
from .generators import (
CombinationalStructGenerator,
FieldStorageStructGenerator,
FieldLogicGenerator,
)
if TYPE_CHECKING:
from typing import Dict, List
from systemrdl.node import AddrmapNode, FieldNode
from ..exporter import BusDecoderExporter, DesignState
class FieldLogic:
def __init__(self, exp: "BusDecoderExporter"):
self.exp = exp
self._hw_conditionals = {} # type: Dict[int, List[NextStateConditional]]
self._sw_conditionals = {} # type: Dict[int, List[NextStateConditional]]
self.init_conditionals()
@property
def ds(self) -> "DesignState":
return self.exp.ds
@property
def top_node(self) -> "AddrmapNode":
return self.exp.ds.top_node
def get_storage_struct(self) -> str:
struct_gen = FieldStorageStructGenerator(self)
s = struct_gen.get_struct(self.top_node, "field_storage_t")
# Only declare the storage struct if it exists
if s is None:
return ""
return s + "\nfield_storage_t field_storage;"
def get_combo_struct(self) -> str:
struct_gen = CombinationalStructGenerator(self)
s = struct_gen.get_struct(self.top_node, "field_combo_t")
# Only declare the storage struct if it exists
if s is None:
return ""
return s + "\nfield_combo_t field_combo;"
def get_implementation(self) -> str:
gen = FieldLogicGenerator(self)
s = gen.get_content(self.top_node)
if s is None:
return ""
return s
# ---------------------------------------------------------------------------
# Field utility functions
# ---------------------------------------------------------------------------
def get_storage_identifier(self, field: "FieldNode") -> str:
"""
Returns the Verilog string that represents the storage register element
for the referenced field
"""
assert field.implements_storage
path = get_indexed_path(self.top_node, field)
return f"field_storage.{path}.value"
def get_next_q_identifier(self, field: "FieldNode") -> str:
"""
Returns the Verilog string that represents the storage register element
for the delayed 'next' input value
"""
assert field.implements_storage
path = get_indexed_path(self.top_node, field)
return f"field_storage.{path}.next_q"
def get_field_combo_identifier(self, field: "FieldNode", name: str) -> str:
"""
Returns a Verilog string that represents a field's internal combinational
signal.
"""
assert field.implements_storage
path = get_indexed_path(self.top_node, field)
return f"field_combo.{path}.{name}"
def get_counter_incr_strobe(self, field: "FieldNode") -> str:
"""
Return the Verilog string that represents the field's incr strobe signal.
"""
prop_value = field.get_property("incr")
if prop_value:
return str(self.exp.dereferencer.get_value(prop_value))
# unset by the user, points to the implied input signal
return self.exp.hwif.get_implied_prop_input_identifier(field, "incr")
def get_counter_incrvalue(self, field: "FieldNode") -> Union[SVInt, str]:
"""
Return the string that represents the field's increment value
"""
incrvalue = field.get_property("incrvalue")
if incrvalue is not None:
return self.exp.dereferencer.get_value(incrvalue, field.width)
if field.get_property("incrwidth"):
return self.exp.hwif.get_implied_prop_input_identifier(field, "incrvalue")
return "1'b1"
def get_counter_incrsaturate_value(self, field: "FieldNode") -> Union[SVInt, str]:
prop_value = field.get_property("incrsaturate")
if prop_value is True:
return self.exp.dereferencer.get_value(2**field.width - 1, field.width)
return self.exp.dereferencer.get_value(prop_value, field.width)
def counter_incrsaturates(self, field: "FieldNode") -> bool:
"""
Returns True if the counter saturates
"""
return field.get_property("incrsaturate") is not False
def get_counter_incrthreshold_value(self, field: "FieldNode") -> Union[SVInt, str]:
prop_value = field.get_property("incrthreshold")
if isinstance(prop_value, bool):
# No explicit value set. use max
return self.exp.dereferencer.get_value(2**field.width - 1, field.width)
return self.exp.dereferencer.get_value(prop_value, field.width)
def get_counter_decr_strobe(self, field: "FieldNode") -> str:
"""
Return the Verilog string that represents the field's incr strobe signal.
"""
prop_value = field.get_property("decr")
if prop_value:
return str(self.exp.dereferencer.get_value(prop_value))
# unset by the user, points to the implied input signal
return self.exp.hwif.get_implied_prop_input_identifier(field, "decr")
def get_counter_decrvalue(self, field: "FieldNode") -> Union[SVInt, str]:
"""
Return the string that represents the field's decrement value
"""
decrvalue = field.get_property("decrvalue")
if decrvalue is not None:
return self.exp.dereferencer.get_value(decrvalue, field.width)
if field.get_property("decrwidth"):
return self.exp.hwif.get_implied_prop_input_identifier(field, "decrvalue")
return "1'b1"
def get_counter_decrsaturate_value(self, field: "FieldNode") -> Union[SVInt, str]:
prop_value = field.get_property("decrsaturate")
if prop_value is True:
return f"{field.width}'d0"
return self.exp.dereferencer.get_value(prop_value, field.width)
def counter_decrsaturates(self, field: "FieldNode") -> bool:
"""
Returns True if the counter saturates
"""
return field.get_property("decrsaturate") is not False
def get_counter_decrthreshold_value(self, field: "FieldNode") -> Union[SVInt, str]:
prop_value = field.get_property("decrthreshold")
if isinstance(prop_value, bool):
# No explicit value set. use min
return f"{field.width}'d0"
return self.exp.dereferencer.get_value(prop_value, field.width)
def get_swacc_identifier(self, field: "FieldNode") -> str:
"""
Asserted when field is software accessed (read or write)
"""
buffer_reads = field.parent.get_property("buffer_reads")
buffer_writes = field.parent.get_property("buffer_writes")
if buffer_reads and buffer_writes:
rstrb = self.exp.read_buffering.get_trigger(field.parent)
wstrb = self.exp.write_buffering.get_write_strobe(field)
return f"{rstrb} || {wstrb}"
elif buffer_reads and not buffer_writes:
strb = self.exp.dereferencer.get_access_strobe(field)
rstrb = self.exp.read_buffering.get_trigger(field.parent)
return f"{rstrb} || ({strb} && decoded_req_is_wr)"
elif not buffer_reads and buffer_writes:
strb = self.exp.dereferencer.get_access_strobe(field)
wstrb = self.exp.write_buffering.get_write_strobe(field)
return f"{wstrb} || ({strb} && !decoded_req_is_wr)"
else:
strb = self.exp.dereferencer.get_access_strobe(field)
return strb
def get_rd_swacc_identifier(self, field: "FieldNode") -> str:
"""
Asserted when field is software accessed (read)
"""
buffer_reads = field.parent.get_property("buffer_reads")
if buffer_reads:
rstrb = self.exp.read_buffering.get_trigger(field.parent)
return rstrb
else:
strb = self.exp.dereferencer.get_access_strobe(field)
return f"{strb} && !decoded_req_is_wr"
def get_wr_swacc_identifier(self, field: "FieldNode") -> str:
"""
Asserted when field is software accessed (write)
"""
buffer_writes = field.parent.get_property("buffer_writes")
if buffer_writes:
wstrb = self.exp.write_buffering.get_write_strobe(field)
return wstrb
else:
strb = self.exp.dereferencer.get_access_strobe(field)
return f"{strb} && decoded_req_is_wr"
def get_swmod_identifier(self, field: "FieldNode") -> str:
"""
Asserted when field is modified by software (written or read with a
set or clear side effect).
"""
w_modifiable = field.is_sw_writable
r_modifiable = field.get_property("onread") is not None
buffer_writes = field.parent.get_property("buffer_writes")
buffer_reads = field.parent.get_property("buffer_reads")
accesswidth = field.parent.get_property("accesswidth")
astrb = self.exp.dereferencer.get_access_strobe(field)
conditions = []
if r_modifiable:
if buffer_reads:
rstrb = self.exp.read_buffering.get_trigger(field.parent)
else:
rstrb = f"{astrb} && !decoded_req_is_wr"
conditions.append(rstrb)
if w_modifiable:
if buffer_writes:
wstrb = self.exp.write_buffering.get_write_strobe(field)
else:
wstrb = f"{astrb} && decoded_req_is_wr"
# Due to 10.6.1-f, it is impossible for a field that is sw-writable to
# be split across subwords.
# Therefore it is ok to get the subword idx from only one of the bit offsets
# in order to compute the biten range
sidx = field.low // accesswidth
biten = self.get_wr_biten(field, sidx)
wstrb += f" && |({biten})"
conditions.append(wstrb)
if not conditions:
# Not sw modifiable
return "1'b0"
else:
return " || ".join(conditions)
def get_parity_identifier(self, field: "FieldNode") -> str:
"""
Returns the identifier for the stored 'golden' parity value of the field
"""
path = get_indexed_path(self.top_node, field)
return f"field_storage.{path}.parity"
def get_parity_error_identifier(self, field: "FieldNode") -> str:
"""
Returns the identifier for whether the field currently has a parity error
"""
path = get_indexed_path(self.top_node, field)
return f"field_combo.{path}.parity_error"
def has_next_q(self, field: "FieldNode") -> bool:
"""
Some fields require a delayed version of their 'next' input signal in
order to do edge-detection.
Returns True if this is the case.
"""
if field.get_property("intr type") in {
InterruptType.posedge,
InterruptType.negedge,
InterruptType.bothedge,
}:
return True
return False
def get_wbus_bitslice(self, field: "FieldNode", subword_idx: int = 0) -> str:
"""
Get the bitslice range string of the internal cpuif's data/biten bus
that corresponds to this field
"""
if field.parent.get_property("buffer_writes"):
# register is buffered.
# write buffer is the full width of the register. no need to deal with subwords
high = field.high
low = field.low
if field.msb < field.lsb:
# slice is for an msb0 field.
# mirror it
regwidth = field.parent.get_property("regwidth")
low = regwidth - 1 - low
high = regwidth - 1 - high
low, high = high, low
else:
# Regular non-buffered register
# For normal fields this ends up passing-through the field's low/high
# values unchanged.
# For fields within a wide register (accesswidth < regwidth), low/high
# may be shifted down and clamped depending on which sub-word is being accessed
accesswidth = field.parent.get_property("accesswidth")
# Shift based on subword
high = field.high - (subword_idx * accesswidth)
low = field.low - (subword_idx * accesswidth)
# clamp to accesswidth
high = max(min(high, accesswidth), 0)
low = max(min(low, accesswidth), 0)
if field.msb < field.lsb:
# slice is for an msb0 field.
# mirror it
bus_width = self.exp.cpuif.data_width
low = bus_width - 1 - low
high = bus_width - 1 - high
low, high = high, low
return f"[{high}:{low}]"
def get_wr_biten(self, field: "FieldNode", subword_idx: int = 0) -> str:
"""
Get the bit-enable slice that corresponds to this field
"""
if field.parent.get_property("buffer_writes"):
# Is buffered. Use value from write buffer
# No need to check msb0 ordering. Bus is pre-swapped, and bitslice
# accounts for it
bslice = self.get_wbus_bitslice(field)
wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field)
return wbuf_prefix + ".biten" + bslice
else:
# Regular non-buffered register
bslice = self.get_wbus_bitslice(field, subword_idx)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = "decoded_wr_biten_bswap" + bslice
else:
value = "decoded_wr_biten" + bslice
return value
def get_wr_data(self, field: "FieldNode", subword_idx: int = 0) -> str:
"""
Get the write data slice that corresponds to this field
"""
if field.parent.get_property("buffer_writes"):
# Is buffered. Use value from write buffer
# No need to check msb0 ordering. Bus is pre-swapped, and bitslice
# accounts for it
bslice = self.get_wbus_bitslice(field)
wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field)
return wbuf_prefix + ".data" + bslice
else:
# Regular non-buffered register
bslice = self.get_wbus_bitslice(field, subword_idx)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = "decoded_wr_data_bswap" + bslice
else:
value = "decoded_wr_data" + bslice
return value
# ---------------------------------------------------------------------------
# Field Logic Conditionals
# ---------------------------------------------------------------------------
def add_hw_conditional(
self, conditional: NextStateConditional, precedence: AssignmentPrecedence
) -> None:
"""
Register a NextStateConditional action for hardware-triggered field updates.
Categorizing conditionals correctly by hw/sw ensures that the RDL precedence
property can be reliably honored.
The ``precedence`` argument determines the conditional assignment's priority over
other assignments of differing precedence.
If multiple conditionals of the same precedence are registered, they are
searched sequentially and only the first to match the given field is used.
"""
if precedence not in self._hw_conditionals:
self._hw_conditionals[precedence] = []
self._hw_conditionals[precedence].append(conditional)
def add_sw_conditional(
self, conditional: NextStateConditional, precedence: AssignmentPrecedence
) -> None:
"""
Register a NextStateConditional action for software-triggered field updates.
Categorizing conditionals correctly by hw/sw ensures that the RDL precedence
property can be reliably honored.
The ``precedence`` argument determines the conditional assignment's priority over
other assignments of differing precedence.
If multiple conditionals of the same precedence are registered, they are
searched sequentially and only the first to match the given field is used.
"""
if precedence not in self._sw_conditionals:
self._sw_conditionals[precedence] = []
self._sw_conditionals[precedence].append(conditional)
def init_conditionals(self) -> None:
"""
Initialize all possible conditionals here.
Remember: The order in which conditionals are added matters within the
same assignment precedence.
"""
self.add_sw_conditional(
sw_onread.ClearOnRead(self.exp), AssignmentPrecedence.SW_ONREAD
)
self.add_sw_conditional(
sw_onread.SetOnRead(self.exp), AssignmentPrecedence.SW_ONREAD
)
self.add_sw_conditional(
sw_onwrite.Write(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteSet(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteClear(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteZeroToggle(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteZeroClear(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteZeroSet(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteOneToggle(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteOneClear(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_onwrite.WriteOneSet(self.exp), AssignmentPrecedence.SW_ONWRITE
)
self.add_sw_conditional(
sw_singlepulse.Singlepulse(self.exp), AssignmentPrecedence.SW_SINGLEPULSE
)
self.add_hw_conditional(
hw_interrupts_with_write.PosedgeStickybitWE(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.PosedgeStickybitWEL(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.NegedgeStickybitWE(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.NegedgeStickybitWEL(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.BothedgeStickybitWE(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.BothedgeStickybitWEL(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.StickyWE(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_interrupts_with_write.StickyWEL(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_interrupts_with_write.StickybitWE(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts_with_write.StickybitWEL(self.exp),
AssignmentPrecedence.HW_WRITE,
)
self.add_hw_conditional(
hw_interrupts.PosedgeStickybit(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_interrupts.NegedgeStickybit(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_interrupts.BothedgeStickybit(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_interrupts.Sticky(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_interrupts.Stickybit(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_write.WEWrite(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_write.WELWrite(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_write.AlwaysWrite(self.exp), AssignmentPrecedence.HW_WRITE
)
self.add_hw_conditional(
hw_set_clr.HWClear(self.exp), AssignmentPrecedence.HWCLR
)
self.add_hw_conditional(hw_set_clr.HWSet(self.exp), AssignmentPrecedence.HWSET)
def _get_X_conditionals(
self, conditionals: "Dict[int, List[NextStateConditional]]", field: "FieldNode"
) -> "List[NextStateConditional]":
result = []
precedences = sorted(conditionals.keys(), reverse=True)
for precedence in precedences:
for conditional in conditionals[precedence]:
if conditional.is_match(field):
result.append(conditional)
break
return result
def get_conditionals(self, field: "FieldNode") -> "List[NextStateConditional]":
"""
Get a list of NextStateConditional objects that apply to the given field.
The returned list is sorted in priority order - the conditional with highest
precedence is first in the list.
"""
sw_precedence = field.get_property("precedence") == PrecedenceType.sw
result = []
if sw_precedence:
result.extend(self._get_X_conditionals(self._sw_conditionals, field))
result.extend(self._get_X_conditionals(self._hw_conditionals, field))
if not sw_precedence:
result.extend(self._get_X_conditionals(self._sw_conditionals, field))
return result

View File

@@ -1,114 +0,0 @@
from typing import TYPE_CHECKING, List
import enum
from ..utils import get_indexed_path
if TYPE_CHECKING:
from systemrdl.node import FieldNode
from ..exporter import BusDecoderExporter
class AssignmentPrecedence(enum.IntEnum):
"""
Enumeration of standard assignment precedence groups.
Each value represents the precedence of a single conditional assignment
category that determines a field's next state.
Higher value denotes higher precedence
Important: If inserting custom intermediate assignment rules, do not rely on the absolute
value of the enumeration. Insert your rules relative to an existing precedence:
FieldBuilder.add_hw_conditional(MyConditional, HW_WE + 1)
"""
# Software access assignment groups
SW_ONREAD = 5000
SW_ONWRITE = 4000
SW_SINGLEPULSE = 3000
# Hardware access assignment groups
HW_WRITE = 3000
HWSET = 2000
HWCLR = 1000
COUNTER_INCR_DECR = 0
class SVLogic:
"""
Represents a SystemVerilog logic signal
"""
def __init__(self, name: str, width: int, default_assignment: str) -> None:
self.name = name
self.width = width
self.default_assignment = default_assignment
def __eq__(self, o: object) -> bool:
if not isinstance(o, SVLogic):
return False
return (
o.name == self.name
and o.width == self.width
and o.default_assignment == self.default_assignment
)
class NextStateConditional:
"""
Describes a single conditional action that determines the next state of a field
Provides information to generate the following content:
if(<conditional>) begin
<assignments>
end
"""
# Optional comment to emit next to the conditional
comment = ""
def __init__(self, exp: "BusDecoderExporter"):
self.exp = exp
def is_match(self, field: "FieldNode") -> bool:
"""
Returns True if this conditional is relevant to the field. If so,
it instructs the FieldBuilder that Verilog for this conditional shall
be emitted
"""
raise NotImplementedError
def get_field_path(self, field: "FieldNode") -> str:
return get_indexed_path(self.exp.ds.top_node, field)
def get_predicate(self, field: "FieldNode") -> str:
"""
Returns the rendered conditional text
"""
raise NotImplementedError
def get_assignments(self, field: "FieldNode") -> List[str]:
"""
Returns a list of rendered assignment strings
This will basically always be two:
<field>.next = <next value>
<field>.load_next = '1;
"""
raise NotImplementedError
def get_extra_combo_signals(self, field: "FieldNode") -> List[SVLogic]:
"""
Return any additional combinational signals that this conditional
will assign if present.
"""
return []
class NextStateUnconditional(NextStateConditional):
"""
Use this class if predicate can never evaluate to false.
This will be generated as an 'else' clause, or a direct assignment
"""
# Explanation text for use in error message about conflicts
unconditional_explanation = ""

View File

@@ -1,393 +0,0 @@
from typing import TYPE_CHECKING, List, Optional
from collections import OrderedDict
from systemrdl.walker import WalkerAction
from systemrdl.node import RegNode, RegfileNode, MemNode, AddrmapNode
from ..struct_generator import RDLStructGenerator
from ..forloop_generator import RDLForLoopGenerator
from ..utils import get_indexed_path, clog2
from ..identifier_filter import kw_filter as kwf
from .bases import NextStateUnconditional
if TYPE_CHECKING:
from . import FieldLogic
from systemrdl.node import FieldNode, AddressableNode
from .bases import SVLogic
class CombinationalStructGenerator(RDLStructGenerator):
def __init__(self, field_logic: 'FieldLogic'):
super().__init__()
self.field_logic = field_logic
def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
super().enter_AddressableComponent(node)
if node.external:
return WalkerAction.SkipDescendants
return WalkerAction.Continue
def enter_Field(self, node: 'FieldNode') -> None:
# If a field doesn't implement storage, it is not relevant here
if not node.implements_storage:
return
# collect any extra combo signals that this field requires
extra_combo_signals = OrderedDict() # type: OrderedDict[str, SVLogic]
for conditional in self.field_logic.get_conditionals(node):
for signal in conditional.get_extra_combo_signals(node):
if signal.name in extra_combo_signals:
# Assert that subsequent declarations of the same signal
# are identical
assert signal == extra_combo_signals[signal.name]
else:
extra_combo_signals[signal.name] = signal
self.push_struct(kwf(node.inst_name))
self.add_member("next", node.width)
self.add_member("load_next")
for signal in extra_combo_signals.values():
self.add_member(signal.name, signal.width)
if node.is_up_counter:
self.add_up_counter_members(node)
if node.is_down_counter:
self.add_down_counter_members(node)
if node.get_property('paritycheck'):
self.add_member("parity_error")
self.pop_struct()
def add_up_counter_members(self, node: 'FieldNode') -> None:
self.add_member('incrthreshold')
if self.field_logic.counter_incrsaturates(node):
self.add_member('incrsaturate')
else:
self.add_member('overflow')
def add_down_counter_members(self, node: 'FieldNode') -> None:
self.add_member('decrthreshold')
if self.field_logic.counter_decrsaturates(node):
self.add_member('decrsaturate')
else:
self.add_member('underflow')
class FieldStorageStructGenerator(RDLStructGenerator):
def __init__(self, field_logic: 'FieldLogic') -> None:
super().__init__()
self.field_logic = field_logic
def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
super().enter_AddressableComponent(node)
if node.external:
return WalkerAction.SkipDescendants
return WalkerAction.Continue
def enter_Field(self, node: 'FieldNode') -> None:
self.push_struct(kwf(node.inst_name))
if node.implements_storage:
self.add_member("value", node.width)
if node.get_property('paritycheck'):
self.add_member("parity")
if self.field_logic.has_next_q(node):
self.add_member("next_q", node.width)
self.pop_struct()
class FieldLogicGenerator(RDLForLoopGenerator):
i_type = "genvar"
def __init__(self, field_logic: 'FieldLogic') -> None:
super().__init__()
self.field_logic = field_logic
self.exp = field_logic.exp
self.ds = self.exp.ds
self.field_storage_template = self.exp.jj_env.get_template(
"field_logic/templates/field_storage.sv"
)
self.external_reg_template = self.exp.jj_env.get_template(
"field_logic/templates/external_reg.sv"
)
self.external_block_template = self.exp.jj_env.get_template(
"field_logic/templates/external_block.sv"
)
self.intr_fields = [] # type: List[FieldNode]
self.halt_fields = [] # type: List[FieldNode]
self.msg = self.ds.top_node.env.msg
def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
super().enter_AddressableComponent(node)
if node.external and not isinstance(node, RegNode):
# Is an external block
self.assign_external_block_outputs(node)
# Do not recurse
return WalkerAction.SkipDescendants
return WalkerAction.Continue
def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
self.intr_fields = []
self.halt_fields = []
if node.external:
self.assign_external_reg_outputs(node)
# Do not recurse to fields
return WalkerAction.SkipDescendants
return WalkerAction.Continue
def enter_Field(self, node: 'FieldNode') -> None:
if node.implements_storage:
self.generate_field_storage(node)
self.assign_field_outputs(node)
if node.get_property('intr'):
self.intr_fields.append(node)
if node.get_property('haltenable') or node.get_property('haltmask'):
self.halt_fields.append(node)
def exit_Reg(self, node: 'RegNode') -> None:
# Assign register's intr output
if self.intr_fields:
strs = []
for field in self.intr_fields:
enable = field.get_property('enable')
mask = field.get_property('mask')
F = self.exp.dereferencer.get_value(field)
if enable:
E = self.exp.dereferencer.get_value(enable)
s = f"|({F} & {E})"
elif mask:
M = self.exp.dereferencer.get_value(mask)
s = f"|({F} & ~{M})"
else:
s = f"|{F}"
strs.append(s)
self.add_content(
f"assign {self.exp.hwif.get_implied_prop_output_identifier(node, 'intr')} ="
)
self.add_content(
" "
+ "\n || ".join(strs)
+ ";"
)
# Assign register's halt output
if self.halt_fields:
strs = []
for field in self.halt_fields:
enable = field.get_property('haltenable')
mask = field.get_property('haltmask')
F = self.exp.dereferencer.get_value(field)
if enable:
E = self.exp.dereferencer.get_value(enable)
s = f"|({F} & {E})"
elif mask:
M = self.exp.dereferencer.get_value(mask)
s = f"|({F} & ~{M})"
else:
s = f"|{F}"
strs.append(s)
self.add_content(
f"assign {self.exp.hwif.get_implied_prop_output_identifier(node, 'halt')} ="
)
self.add_content(
" "
+ "\n || ".join(strs)
+ ";"
)
def generate_field_storage(self, node: 'FieldNode') -> None:
conditionals = self.field_logic.get_conditionals(node)
extra_combo_signals = OrderedDict()
unconditional: Optional[NextStateUnconditional] = None
new_conditionals = []
for conditional in conditionals:
for signal in conditional.get_extra_combo_signals(node):
extra_combo_signals[signal.name] = signal
if isinstance(conditional, NextStateUnconditional):
if unconditional is not None:
# Too inconvenient to validate this early. Easier to validate here in-place generically
self.msg.fatal(
"Field has multiple conflicting properties that unconditionally set its state:\n"
f" * {conditional.unconditional_explanation}\n"
f" * {unconditional.unconditional_explanation}",
node.inst.inst_src_ref
)
unconditional = conditional
else:
new_conditionals.append(conditional)
conditionals = new_conditionals
resetsignal = node.get_property('resetsignal')
reset_value = node.get_property('reset')
if reset_value is not None:
reset_value_str = self.exp.dereferencer.get_value(reset_value, node.width)
else:
# 5.9.1-g: If no reset value given, the field is not reset, even if it has a resetsignal.
reset_value_str = None
resetsignal = None
context = {
'node': node,
'reset': reset_value_str,
'field_logic': self.field_logic,
'extra_combo_signals': extra_combo_signals,
'conditionals': conditionals,
'unconditional': unconditional,
'resetsignal': resetsignal,
'get_always_ff_event': self.exp.dereferencer.get_always_ff_event,
'get_value': self.exp.dereferencer.get_value,
'get_resetsignal': self.exp.dereferencer.get_resetsignal,
'get_input_identifier': self.exp.hwif.get_input_identifier,
'ds': self.ds,
}
self.add_content(self.field_storage_template.render(context))
def assign_field_outputs(self, node: 'FieldNode') -> None:
# Field value output
if self.exp.hwif.has_value_output(node):
output_identifier = self.exp.hwif.get_output_identifier(node)
value = self.exp.dereferencer.get_value(node)
self.add_content(
f"assign {output_identifier} = {value};"
)
# Inferred logical reduction outputs
if node.get_property('anded'):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "anded")
value = self.exp.dereferencer.get_field_propref_value(node, "anded")
self.add_content(
f"assign {output_identifier} = {value};"
)
if node.get_property('ored'):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "ored")
value = self.exp.dereferencer.get_field_propref_value(node, "ored")
self.add_content(
f"assign {output_identifier} = {value};"
)
if node.get_property('xored'):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "xored")
value = self.exp.dereferencer.get_field_propref_value(node, "xored")
self.add_content(
f"assign {output_identifier} = {value};"
)
# Software access strobes
if node.get_property('swmod'):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "swmod")
value = self.field_logic.get_swmod_identifier(node)
self.add_content(
f"assign {output_identifier} = {value};"
)
if node.get_property('swacc'):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "swacc")
value = self.field_logic.get_swacc_identifier(node)
self.add_content(
f"assign {output_identifier} = {value};"
)
if node.get_property('rd_swacc'):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "rd_swacc")
value = self.field_logic.get_rd_swacc_identifier(node)
self.add_content(
f"assign {output_identifier} = {value};"
)
if node.get_property('wr_swacc'):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "wr_swacc")
value = self.field_logic.get_wr_swacc_identifier(node)
self.add_content(
f"assign {output_identifier} = {value};"
)
# Counter thresholds
if node.get_property('incrthreshold') is not False: # (explicitly not False. Not 0)
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "incrthreshold")
value = self.field_logic.get_field_combo_identifier(node, 'incrthreshold')
self.add_content(
f"assign {output_identifier} = {value};"
)
if node.get_property('decrthreshold') is not False: # (explicitly not False. Not 0)
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "decrthreshold")
value = self.field_logic.get_field_combo_identifier(node, 'decrthreshold')
self.add_content(
f"assign {output_identifier} = {value};"
)
# Counter events
if node.get_property('overflow'):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "overflow")
value = self.field_logic.get_field_combo_identifier(node, 'overflow')
self.add_content(
f"assign {output_identifier} = {value};"
)
if node.get_property('underflow'):
output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "underflow")
value = self.field_logic.get_field_combo_identifier(node, 'underflow')
self.add_content(
f"assign {output_identifier} = {value};"
)
def assign_external_reg_outputs(self, node: 'RegNode') -> None:
prefix = "hwif_out." + get_indexed_path(self.exp.ds.top_node, node)
strb = self.exp.dereferencer.get_access_strobe(node)
width = min(self.exp.cpuif.data_width, node.get_property('regwidth'))
if width != self.exp.cpuif.data_width:
bslice = f"[{width - 1}:0]"
else:
bslice = ""
context = {
"has_sw_writable": node.has_sw_writable,
"has_sw_readable": node.has_sw_readable,
"prefix": prefix,
"strb": strb,
"bslice": bslice,
"retime": self.ds.retime_external_reg,
'get_always_ff_event': self.exp.dereferencer.get_always_ff_event,
"get_resetsignal": self.exp.dereferencer.get_resetsignal,
"resetsignal": self.exp.ds.top_node.cpuif_reset,
}
self.add_content(self.external_reg_template.render(context))
def assign_external_block_outputs(self, node: 'AddressableNode') -> None:
prefix = "hwif_out." + get_indexed_path(self.exp.ds.top_node, node)
strb = self.exp.dereferencer.get_external_block_access_strobe(node)
addr_width = clog2(node.size)
retime = False
if isinstance(node, RegfileNode):
retime = self.ds.retime_external_regfile
elif isinstance(node, MemNode):
retime = self.ds.retime_external_mem
elif isinstance(node, AddrmapNode):
retime = self.ds.retime_external_addrmap
context = {
"prefix": prefix,
"strb": strb,
"addr_width": addr_width,
"retime": retime,
'get_always_ff_event': self.exp.dereferencer.get_always_ff_event,
"get_resetsignal": self.exp.dereferencer.get_resetsignal,
"resetsignal": self.exp.ds.top_node.cpuif_reset,
}
self.add_content(self.external_block_template.render(context))

View File

@@ -1,162 +0,0 @@
from typing import TYPE_CHECKING, List
from systemrdl.rdltypes import InterruptType
from .bases import NextStateConditional
if TYPE_CHECKING:
from systemrdl.node import FieldNode
class Sticky(NextStateConditional):
"""
Normal multi-bit sticky
"""
comment = "multi-bit sticky"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and field.get_property('sticky')
)
def get_predicate(self, field: 'FieldNode') -> str:
I = self.exp.hwif.get_input_identifier(field)
R = self.exp.field_logic.get_storage_identifier(field)
return f"({R} == '0) && ({I} != '0)"
def get_assignments(self, field: 'FieldNode') -> List[str]:
I = self.exp.hwif.get_input_identifier(field)
return [
f"next_c = {I};",
"load_next_c = '1;",
]
class Stickybit(NextStateConditional):
"""
Normal stickybit
"""
comment = "stickybit"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and field.get_property('stickybit')
and field.get_property('intr type') in {None, InterruptType.level}
)
def get_predicate(self, field: 'FieldNode') -> str:
F = self.exp.hwif.get_input_identifier(field)
if field.width == 1:
return str(F)
else:
return f"{F} != '0"
def get_assignments(self, field: 'FieldNode') -> List[str]:
if field.width == 1:
return [
"next_c = '1;",
"load_next_c = '1;",
]
else:
I = self.exp.hwif.get_input_identifier(field)
R = self.exp.field_logic.get_storage_identifier(field)
return [
f"next_c = {R} | {I};",
"load_next_c = '1;",
]
class PosedgeStickybit(NextStateConditional):
"""
Positive edge stickybit
"""
comment = "posedge stickybit"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and field.get_property('stickybit')
and field.get_property('intr type') == InterruptType.posedge
)
def get_predicate(self, field: 'FieldNode') -> str:
I = self.exp.hwif.get_input_identifier(field)
Iq = self.exp.field_logic.get_next_q_identifier(field)
return f"(~{Iq} & {I}) != '0"
def get_assignments(self, field: 'FieldNode') -> List[str]:
if field.width == 1:
return [
"next_c = '1;",
"load_next_c = '1;",
]
else:
I = self.exp.hwif.get_input_identifier(field)
Iq = self.exp.field_logic.get_next_q_identifier(field)
R = self.exp.field_logic.get_storage_identifier(field)
return [
f"next_c = {R} | (~{Iq} & {I});",
"load_next_c = '1;",
]
class NegedgeStickybit(NextStateConditional):
"""
Negative edge stickybit
"""
comment = "negedge stickybit"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and field.get_property('stickybit')
and field.get_property('intr type') == InterruptType.negedge
)
def get_predicate(self, field: 'FieldNode') -> str:
I = self.exp.hwif.get_input_identifier(field)
Iq = self.exp.field_logic.get_next_q_identifier(field)
return f"({Iq} & ~{I}) != '0"
def get_assignments(self, field: 'FieldNode') -> List[str]:
if field.width == 1:
return [
"next_c = '1;",
"load_next_c = '1;",
]
else:
I = self.exp.hwif.get_input_identifier(field)
Iq = self.exp.field_logic.get_next_q_identifier(field)
R = self.exp.field_logic.get_storage_identifier(field)
return [
f"next_c = {R} | ({Iq} & ~{I});",
"load_next_c = '1;",
]
class BothedgeStickybit(NextStateConditional):
"""
edge-sensitive stickybit
"""
comment = "bothedge stickybit"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and field.get_property('stickybit')
and field.get_property('intr type') == InterruptType.bothedge
)
def get_predicate(self, field: 'FieldNode') -> str:
I = self.exp.hwif.get_input_identifier(field)
Iq = self.exp.field_logic.get_next_q_identifier(field)
return f"{Iq} != {I}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
if field.width == 1:
return [
"next_c = '1;",
"load_next_c = '1;",
]
else:
I = self.exp.hwif.get_input_identifier(field)
Iq = self.exp.field_logic.get_next_q_identifier(field)
R = self.exp.field_logic.get_storage_identifier(field)
return [
f"next_c = {R} | ({Iq} ^ {I});",
"load_next_c = '1;",
]

View File

@@ -1,187 +0,0 @@
from typing import List, TYPE_CHECKING
from .hw_interrupts import (
Sticky, Stickybit,
PosedgeStickybit, NegedgeStickybit, BothedgeStickybit
)
from .hw_write import WEWrite, WELWrite
if TYPE_CHECKING:
from systemrdl.node import FieldNode
class StickyWE(Sticky, WEWrite):
"""
Normal multi-bit sticky with write enable
"""
comment = "multi-bit sticky with WE"
def is_match(self, field: 'FieldNode') -> bool:
return (
Sticky.is_match(self, field)
and WEWrite.is_match(self, field)
)
def get_predicate(self, field: 'FieldNode') -> str:
BASE = Sticky.get_predicate(self, field)
WE = WEWrite.get_predicate(self, field)
return f"{BASE} && {WE}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
return Sticky.get_assignments(self, field)
class StickyWEL(Sticky, WELWrite):
"""
Normal multi-bit sticky with write enable low
"""
comment = "multi-bit sticky with WEL"
def is_match(self, field: 'FieldNode') -> bool:
return (
Sticky.is_match(self, field)
and WELWrite.is_match(self, field)
)
def get_predicate(self, field: 'FieldNode') -> str:
BASE = Sticky.get_predicate(self, field)
WEL = WELWrite.get_predicate(self, field)
return f"{BASE} && {WEL}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
return Sticky.get_assignments(self, field)
class StickybitWE(Stickybit, WEWrite):
"""
Normal stickybiti with write enable
"""
comment = "stickybit with WE"
def is_match(self, field: 'FieldNode') -> bool:
return (
Stickybit.is_match(self, field)
and WEWrite.is_match(self, field)
)
def get_predicate(self, field: 'FieldNode') -> str:
BASE = Stickybit.get_predicate(self, field)
WE = WEWrite.get_predicate(self, field)
return f"{BASE} && {WE}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
return Stickybit.get_assignments(self, field)
class StickybitWEL(Stickybit, WELWrite):
"""
Normal stickybiti with write enable low
"""
comment = "stickybit with WEL"
def is_match(self, field: 'FieldNode') -> bool:
return Stickybit.is_match(self, field) \
and WELWrite.is_match(self, field)
def get_predicate(self, field: 'FieldNode') -> str:
BASE = Stickybit.get_predicate(self, field)
WEL = WELWrite.get_predicate(self, field)
return f"{BASE} && {WEL}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
return Stickybit.get_assignments(self, field)
class PosedgeStickybitWE(PosedgeStickybit, WEWrite):
"""
Positive edge stickybit with write enable
"""
comment = "posedge stickybit with WE"
def is_match(self, field: 'FieldNode') -> bool:
return PosedgeStickybit.is_match(self, field) \
and WEWrite.is_match(self, field)
def get_predicate(self, field: 'FieldNode') -> str:
BASE = PosedgeStickybit.get_predicate(self, field)
WE = WEWrite.get_predicate(self, field)
return f"{BASE} && {WE}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
return PosedgeStickybit.get_assignments(self, field)
class PosedgeStickybitWEL(PosedgeStickybit, WELWrite):
"""
Positive edge stickybit with write enable low
"""
comment = "posedge stickybit with WEL"
def is_match(self, field: 'FieldNode') -> bool:
return PosedgeStickybit.is_match(self, field) \
and WELWrite.is_match(self, field)
def get_predicate(self, field: 'FieldNode') -> str:
BASE = PosedgeStickybit.get_predicate(self, field)
WEL = WELWrite.get_predicate(self, field)
return f"{BASE} && {WEL}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
return PosedgeStickybit.get_assignments(self, field)
class NegedgeStickybitWE(NegedgeStickybit, WEWrite):
"""
Negative edge stickybit with write enable
"""
comment = "negedge stickybit with WE"
def is_match(self, field: 'FieldNode') -> bool:
return NegedgeStickybit.is_match(self, field) \
and WEWrite.is_match(self, field)
def get_predicate(self, field: 'FieldNode') -> str:
BASE = NegedgeStickybit.get_predicate(self, field)
WE = WEWrite.get_predicate(self, field)
return f"{BASE} && {WE}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
return NegedgeStickybit.get_assignments(self, field)
class NegedgeStickybitWEL(NegedgeStickybit, WELWrite):
"""
Negative edge stickybit with write enable low
"""
comment = "negedge stickybit with WEL"
def is_match(self, field: 'FieldNode') -> bool:
return NegedgeStickybit.is_match(self, field) \
and WELWrite.is_match(self, field)
def get_predicate(self, field: 'FieldNode') -> str:
BASE = NegedgeStickybit.get_predicate(self, field)
WEL = WELWrite.get_predicate(self, field)
return f"{BASE} && {WEL}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
return NegedgeStickybit.get_assignments(self, field)
class BothedgeStickybitWE(BothedgeStickybit, WEWrite):
"""
edge-sensitive stickybit with write enable
"""
comment = "bothedge stickybit with WE"
def is_match(self, field: 'FieldNode') -> bool:
return BothedgeStickybit.is_match(self, field) \
and WEWrite.is_match(self, field)
def get_predicate(self, field: 'FieldNode') -> str:
BASE = BothedgeStickybit.get_predicate(self, field)
WE = WEWrite.get_predicate(self, field)
return f"{BASE} && {WE}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
return BothedgeStickybit.get_assignments(self, field)
class BothedgeStickybitWEL(BothedgeStickybit, WELWrite):
"""
edge-sensitive stickybit with write enable low
"""
comment = "bothedge stickybit with WEL"
def is_match(self, field: 'FieldNode') -> bool:
return BothedgeStickybit.is_match(self, field) \
and WELWrite.is_match(self, field)
def get_predicate(self, field: 'FieldNode') -> str:
BASE = BothedgeStickybit.get_predicate(self, field)
WEL = WELWrite.get_predicate(self, field)
return f"{BASE} && {WEL}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
return BothedgeStickybit.get_assignments(self, field)

View File

@@ -1,72 +0,0 @@
from typing import TYPE_CHECKING, List
from .bases import NextStateConditional
if TYPE_CHECKING:
from systemrdl.node import FieldNode
class HWSet(NextStateConditional):
comment = "HW Set"
def is_match(self, field: 'FieldNode') -> bool:
return bool(field.get_property('hwset'))
def get_predicate(self, field: 'FieldNode') -> str:
prop = field.get_property('hwset')
if isinstance(prop, bool):
identifier = self.exp.hwif.get_implied_prop_input_identifier(field, "hwset")
else:
# signal or field
identifier = str(self.exp.dereferencer.get_value(prop))
return identifier
def get_assignments(self, field: 'FieldNode') -> List[str]:
hwmask = field.get_property('hwmask')
hwenable = field.get_property('hwenable')
R = self.exp.field_logic.get_storage_identifier(field)
if hwmask is not None:
M = self.exp.dereferencer.get_value(hwmask)
next_val = f"{R} | ~{M}"
elif hwenable is not None:
E = self.exp.dereferencer.get_value(hwenable)
next_val = f"{R} | {E}"
else:
next_val = "'1"
return [
f"next_c = {next_val};",
"load_next_c = '1;",
]
class HWClear(NextStateConditional):
comment = "HW Clear"
def is_match(self, field: 'FieldNode') -> bool:
return bool(field.get_property('hwclr'))
def get_predicate(self, field: 'FieldNode') -> str:
prop = field.get_property('hwclr')
if isinstance(prop, bool):
identifier = self.exp.hwif.get_implied_prop_input_identifier(field, "hwclr")
else:
# signal or field
identifier = str(self.exp.dereferencer.get_value(prop))
return identifier
def get_assignments(self, field: 'FieldNode') -> List[str]:
hwmask = field.get_property('hwmask')
hwenable = field.get_property('hwenable')
R = self.exp.field_logic.get_storage_identifier(field)
if hwmask is not None:
M = self.exp.dereferencer.get_value(hwmask)
next_val = f"{R} & {M}"
elif hwenable is not None:
E = self.exp.dereferencer.get_value(hwenable)
next_val = f"{R} & ~{E}"
else:
next_val = "'0"
return [
f"next_c = {next_val};",
"load_next_c = '1;",
]

View File

@@ -1,95 +0,0 @@
from typing import TYPE_CHECKING, List
from .bases import NextStateConditional, NextStateUnconditional
if TYPE_CHECKING:
from systemrdl.node import FieldNode
class AlwaysWrite(NextStateUnconditional):
"""
hw writable, without any qualifying we/wel
"""
comment = "HW Write"
unconditional_explanation = "A hardware-writable field without a write-enable (we/wel) will always update the field value"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and not field.get_property('we')
and not field.get_property('wel')
)
def get_assignments(self, field: 'FieldNode') -> List[str]:
hwmask = field.get_property('hwmask')
hwenable = field.get_property('hwenable')
I = str(self.exp.hwif.get_input_identifier(field))
R = self.exp.field_logic.get_storage_identifier(field)
if hwmask is not None:
M = self.exp.dereferencer.get_value(hwmask)
next_val = f"{I} & ~{M} | {R} & {M}"
elif hwenable is not None:
E = self.exp.dereferencer.get_value(hwenable)
next_val = f"{I} & {E} | {R} & ~{E}"
else:
next_val = I
return [
f"next_c = {next_val};",
"load_next_c = '1;",
]
class _QualifiedWrite(NextStateConditional):
def get_assignments(self, field: 'FieldNode') -> List[str]:
hwmask = field.get_property('hwmask')
hwenable = field.get_property('hwenable')
I = str(self.exp.hwif.get_input_identifier(field))
R = self.exp.field_logic.get_storage_identifier(field)
if hwmask is not None:
M = self.exp.dereferencer.get_value(hwmask)
next_val = f"{I} & ~{M} | {R} & {M}"
elif hwenable is not None:
E = self.exp.dereferencer.get_value(hwenable)
next_val = f"{I} & {E} | {R} & ~{E}"
else:
next_val = I
return [
f"next_c = {next_val};",
"load_next_c = '1;",
]
class WEWrite(_QualifiedWrite):
comment = "HW Write - we"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and bool(field.get_property('we'))
)
def get_predicate(self, field: 'FieldNode') -> str:
prop = field.get_property('we')
if isinstance(prop, bool):
identifier = self.exp.hwif.get_implied_prop_input_identifier(field, "we")
else:
# signal or field
identifier = str(self.exp.dereferencer.get_value(prop))
return identifier
class WELWrite(_QualifiedWrite):
comment = "HW Write - wel"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and bool(field.get_property('wel'))
)
def get_predicate(self, field: 'FieldNode') -> str:
prop = field.get_property('wel')
if isinstance(prop, bool):
identifier = self.exp.hwif.get_implied_prop_input_identifier(field, "wel")
else:
# signal or field
identifier = str(self.exp.dereferencer.get_value(prop))
return f"!{identifier}"

View File

@@ -1,45 +0,0 @@
from typing import TYPE_CHECKING, List
from systemrdl.rdltypes import OnReadType
from .bases import NextStateConditional
if TYPE_CHECKING:
from systemrdl.node import FieldNode
class _OnRead(NextStateConditional):
onreadtype = None # type: OnReadType
def is_match(self, field: 'FieldNode') -> bool:
return field.get_property('onread') == self.onreadtype
def get_predicate(self, field: 'FieldNode') -> str:
if field.parent.get_property('buffer_reads'):
# Is buffered read. Use alternate strobe
rstrb = self.exp.read_buffering.get_trigger(field.parent)
return rstrb
else:
# is regular register
strb = self.exp.dereferencer.get_access_strobe(field)
return f"{strb} && !decoded_req_is_wr"
class ClearOnRead(_OnRead):
comment = "SW clear on read"
onreadtype = OnReadType.rclr
def get_assignments(self, field: 'FieldNode') -> List[str]:
return [
"next_c = '0;",
"load_next_c = '1;",
]
class SetOnRead(_OnRead):
comment = "SW set on read"
onreadtype = OnReadType.rset
def get_assignments(self, field: 'FieldNode') -> List[str]:
return [
"next_c = '1;",
"load_next_c = '1;",
]

View File

@@ -1,129 +0,0 @@
from typing import TYPE_CHECKING, List, Optional
from systemrdl.rdltypes import OnWriteType
from .bases import NextStateConditional
if TYPE_CHECKING:
from systemrdl.node import FieldNode
# TODO: implement sw=w1 "write once" fields
class _OnWrite(NextStateConditional):
onwritetype: Optional[OnWriteType] = None
def is_match(self, field: 'FieldNode') -> bool:
return field.is_sw_writable and field.get_property('onwrite') == self.onwritetype
def get_predicate(self, field: 'FieldNode') -> str:
if field.parent.get_property('buffer_writes'):
# Is buffered write. Use alternate strobe
wstrb = self.exp.write_buffering.get_write_strobe(field)
if field.get_property('swwe') or field.get_property('swwel'):
# dereferencer will wrap swwel complement if necessary
qualifier = self.exp.dereferencer.get_field_propref_value(field, 'swwe')
return f"{wstrb} && {qualifier}"
return wstrb
else:
# is regular register
strb = self.exp.dereferencer.get_access_strobe(field)
if field.get_property('swwe') or field.get_property('swwel'):
# dereferencer will wrap swwel complement if necessary
qualifier = self.exp.dereferencer.get_field_propref_value(field, 'swwe')
return f"{strb} && decoded_req_is_wr && {qualifier}"
return f"{strb} && decoded_req_is_wr"
def get_assignments(self, field: 'FieldNode') -> List[str]:
accesswidth = field.parent.get_property("accesswidth")
# Due to 10.6.1-f, it is impossible for a field with an onwrite action to
# be split across subwords.
# Therefore it is ok to get the subword idx from only one of the bit offsets
sidx = field.low // accesswidth
# field does not get split between subwords
R = self.exp.field_logic.get_storage_identifier(field)
D = self.exp.field_logic.get_wr_data(field, sidx)
S = self.exp.field_logic.get_wr_biten(field, sidx)
lines = [
f"next_c = {self.get_onwrite_rhs(R, D, S)};",
"load_next_c = '1;",
]
return lines
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
raise NotImplementedError
#-------------------------------------------------------------------------------
class WriteOneSet(_OnWrite):
comment = "SW write 1 set"
onwritetype = OnWriteType.woset
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"{reg} | ({data} & {strb})"
class WriteOneClear(_OnWrite):
comment = "SW write 1 clear"
onwritetype = OnWriteType.woclr
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"{reg} & ~({data} & {strb})"
class WriteOneToggle(_OnWrite):
comment = "SW write 1 toggle"
onwritetype = OnWriteType.wot
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"{reg} ^ ({data} & {strb})"
class WriteZeroSet(_OnWrite):
comment = "SW write 0 set"
onwritetype = OnWriteType.wzs
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"{reg} | (~{data} & {strb})"
class WriteZeroClear(_OnWrite):
comment = "SW write 0 clear"
onwritetype = OnWriteType.wzc
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"{reg} & ({data} | ~{strb})"
class WriteZeroToggle(_OnWrite):
comment = "SW write 0 toggle"
onwritetype = OnWriteType.wzt
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"{reg} ^ (~{data} & {strb})"
class WriteClear(_OnWrite):
comment = "SW write clear"
onwritetype = OnWriteType.wclr
def get_assignments(self, field: 'FieldNode') -> List[str]:
return [
"next_c = '0;",
"load_next_c = '1;",
]
class WriteSet(_OnWrite):
comment = "SW write set"
onwritetype = OnWriteType.wset
def get_assignments(self, field: 'FieldNode') -> List[str]:
return [
"next_c = '1;",
"load_next_c = '1;",
]
class Write(_OnWrite):
comment = "SW write"
onwritetype = None
def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
return f"({reg} & ~{strb}) | ({data} & {strb})"

View File

@@ -1,19 +0,0 @@
from typing import TYPE_CHECKING, List
from .bases import NextStateUnconditional
if TYPE_CHECKING:
from systemrdl.node import FieldNode
class Singlepulse(NextStateUnconditional):
comment = "singlepulse clears back to 0"
unconditional_explanation = "The 'singlepulse' property unconditionally clears a field when not written"
def is_match(self, field: 'FieldNode') -> bool:
return field.get_property('singlepulse')
def get_assignments(self, field: 'FieldNode') -> List[str]:
return [
"next_c = '0;",
"load_next_c = '1;",
]

View File

@@ -1,48 +0,0 @@
{% macro up_counter(field) -%}
if({{field_logic.get_counter_incr_strobe(node)}}) begin // increment
{%- if field_logic.counter_incrsaturates(node) %}
if((({{node.width+1}})'(next_c) + {{field_logic.get_counter_incrvalue(node)}}) > {{field_logic.get_counter_incrsaturate_value(node)}}) begin // up-counter saturated
next_c = {{field_logic.get_counter_incrsaturate_value(node)}};
end else begin
next_c = next_c + {{field_logic.get_counter_incrvalue(node)}};
end
{%- else %}
{{field_logic.get_field_combo_identifier(node, "overflow")}} = ((({{node.width+1}})'(next_c) + {{field_logic.get_counter_incrvalue(node)}}) > {{get_value(2**node.width - 1, node.width)}});
next_c = next_c + {{field_logic.get_counter_incrvalue(node)}};
{%- endif %}
load_next_c = '1;
{%- if not field_logic.counter_incrsaturates(node) %}
end else begin
{{field_logic.get_field_combo_identifier(node, "overflow")}} = '0;
{%- endif %}
end
{{field_logic.get_field_combo_identifier(node, "incrthreshold")}} = ({{field_logic.get_storage_identifier(node)}} >= {{field_logic.get_counter_incrthreshold_value(node)}});
{%- if field_logic.counter_incrsaturates(node) %}
{{field_logic.get_field_combo_identifier(node, "incrsaturate")}} = ({{field_logic.get_storage_identifier(node)}} >= {{field_logic.get_counter_incrsaturate_value(node)}});
{%- endif %}
{%- endmacro %}
{% macro down_counter(field) -%}
if({{field_logic.get_counter_decr_strobe(node)}}) begin // decrement
{%- if field_logic.counter_decrsaturates(node) %}
if(({{node.width+1}})'(next_c) < ({{field_logic.get_counter_decrvalue(node)}} + {{field_logic.get_counter_decrsaturate_value(node)}})) begin // down-counter saturated
next_c = {{field_logic.get_counter_decrsaturate_value(node)}};
end else begin
next_c = next_c - {{field_logic.get_counter_decrvalue(node)}};
end
{%- else %}
{{field_logic.get_field_combo_identifier(node, "underflow")}} = (next_c < ({{field_logic.get_counter_decrvalue(node)}}));
next_c = next_c - {{field_logic.get_counter_decrvalue(node)}};
{%- endif %}
load_next_c = '1;
{%- if not field_logic.counter_decrsaturates(node) %}
end else begin
{{field_logic.get_field_combo_identifier(node, "underflow")}} = '0;
{%- endif %}
end
{{field_logic.get_field_combo_identifier(node, "decrthreshold")}} = ({{field_logic.get_storage_identifier(node)}} <= {{field_logic.get_counter_decrthreshold_value(node)}});
{%- if field_logic.counter_decrsaturates(node) %}
{{field_logic.get_field_combo_identifier(node, "decrsaturate")}} = ({{field_logic.get_storage_identifier(node)}} <= {{field_logic.get_counter_decrsaturate_value(node)}});
{%- endif %}
{%- endmacro %}

View File

@@ -1,31 +0,0 @@
{% if retime -%}
always_ff {{get_always_ff_event(resetsignal)}} begin
if({{get_resetsignal(resetsignal)}}) begin
{{prefix}}.req <= '0;
{{prefix}}.addr <= '0;
{{prefix}}.req_is_wr <= '0;
{{prefix}}.wr_data <= '0;
{{prefix}}.wr_biten <= '0;
end else begin
{{prefix}}.req <= {{strb}};
{{prefix}}.addr <= decoded_addr[{{addr_width-1}}:0];
{{prefix}}.req_is_wr <= decoded_req_is_wr;
{{prefix}}.wr_data <= decoded_wr_data;
{{prefix}}.wr_biten <= decoded_wr_biten;
end
end
{%- else -%}
assign {{prefix}}.req = {{strb}};
assign {{prefix}}.addr = decoded_addr[{{addr_width-1}}:0];
assign {{prefix}}.req_is_wr = decoded_req_is_wr;
assign {{prefix}}.wr_data = decoded_wr_data;
assign {{prefix}}.wr_biten = decoded_wr_biten;
{%- endif %}

View File

@@ -1,46 +0,0 @@
{% if retime -%}
always_ff {{get_always_ff_event(resetsignal)}} begin
if({{get_resetsignal(resetsignal)}}) begin
{{prefix}}.req <= '0;
{{prefix}}.req_is_wr <= '0;
{%- if has_sw_writable %}
{{prefix}}.wr_data <= '0;
{{prefix}}.wr_biten <= '0;
{%- endif %}
end else begin
{%- if has_sw_readable and has_sw_writable %}
{{prefix}}.req <= {{strb}};
{%- elif has_sw_readable and not has_sw_writable %}
{{prefix}}.req <= !decoded_req_is_wr ? {{strb}} : '0;
{%- elif not has_sw_readable and has_sw_writable %}
{{prefix}}.req <= decoded_req_is_wr ? {{strb}} : '0;
{%- endif %}
{{prefix}}.req_is_wr <= decoded_req_is_wr;
{%- if has_sw_writable %}
{{prefix}}.wr_data <= decoded_wr_data{{bslice}};
{{prefix}}.wr_biten <= decoded_wr_biten{{bslice}};
{%- endif %}
end
end
{%- else -%}
{%- if has_sw_readable and has_sw_writable %}
assign {{prefix}}.req = {{strb}};
{%- elif has_sw_readable and not has_sw_writable %}
assign {{prefix}}.req = !decoded_req_is_wr ? {{strb}} : '0;
{%- elif not has_sw_readable and has_sw_writable %}
assign {{prefix}}.req = decoded_req_is_wr ? {{strb}} : '0;
{%- endif %}
assign {{prefix}}.req_is_wr = decoded_req_is_wr;
{%- if has_sw_writable %}
assign {{prefix}}.wr_data = decoded_wr_data{{bslice}};
assign {{prefix}}.wr_biten = decoded_wr_biten{{bslice}};
{%- endif %}
{%- endif %}

View File

@@ -1,86 +0,0 @@
{%- import 'field_logic/templates/counter_macros.sv' as counter_macros with context -%}
// Field: {{node.get_path()}}
always_comb begin
automatic logic [{{node.width-1}}:0] next_c;
automatic logic load_next_c;
next_c = {{field_logic.get_storage_identifier(node)}};
load_next_c = '0;
{%- for signal in extra_combo_signals %}
{{field_logic.get_field_combo_identifier(node, signal.name)}} = {{signal.default_assignment}};
{%- endfor %}
{% for conditional in conditionals %}
{%- if not loop.first %} else {% endif %}if({{conditional.get_predicate(node)}}) begin // {{conditional.comment}}
{%- for assignment in conditional.get_assignments(node) %}
{{assignment|indent}}
{%- endfor %}
end
{%- endfor %}
{%- if unconditional %}
{%- if conditionals %} else begin // {{unconditional.comment}}
{%- for assignment in unconditional.get_assignments(node) %}
{{assignment|indent}}
{%- endfor %}
end
{%- else %}
// {{unconditional.comment}}
{%- for assignment in unconditional.get_assignments(node) %}
{{assignment|indent}}
{%- endfor %}
{%- endif %}
{%- endif %}
{%- if node.is_up_counter %}
{{counter_macros.up_counter(node)}}
{%- endif %}
{%- if node.is_down_counter %}
{{counter_macros.down_counter(node)}}
{%- endif %}
{{field_logic.get_field_combo_identifier(node, "next")}} = next_c;
{{field_logic.get_field_combo_identifier(node, "load_next")}} = load_next_c;
{%- if node.get_property('paritycheck') %}
{{field_logic.get_parity_error_identifier(node)}} = ({{field_logic.get_parity_identifier(node)}} != ^{{field_logic.get_storage_identifier(node)}});
{%- endif %}
end
{%- if reset is not none %}
always_ff {{get_always_ff_event(resetsignal)}} begin
if({{get_resetsignal(resetsignal)}}) begin
{{field_logic.get_storage_identifier(node)}} <= {{reset}};
{%- if node.get_property('paritycheck') %}
{{field_logic.get_parity_identifier(node)}} <= ^{{reset}};
{%- endif %}
{%- if field_logic.has_next_q(node) %}
{{field_logic.get_next_q_identifier(node)}} <= {{reset}};
{%- endif %}
end else begin
if({{field_logic.get_field_combo_identifier(node, "load_next")}}) begin
{{field_logic.get_storage_identifier(node)}} <= {{field_logic.get_field_combo_identifier(node, "next")}};
{%- if node.get_property('paritycheck') %}
{{field_logic.get_parity_identifier(node)}} <= ^{{field_logic.get_field_combo_identifier(node, "next")}};
{%- endif %}
end
{%- if field_logic.has_next_q(node) %}
{{field_logic.get_next_q_identifier(node)}} <= {{get_input_identifier(node)}};
{%- endif %}
end
end
{%- else %}
always_ff @(posedge clk) begin
if({{field_logic.get_field_combo_identifier(node, "load_next")}}) begin
{{field_logic.get_storage_identifier(node)}} <= {{field_logic.get_field_combo_identifier(node, "next")}};
{%- if node.get_property('paritycheck') %}
{{field_logic.get_parity_identifier(node)}} <= ^{{field_logic.get_field_combo_identifier(node, "next")}};
{%- endif %}
end
{%- if field_logic.has_next_q(node) %}
{{field_logic.get_next_q_identifier(node)}} <= {{get_input_identifier(node)}};
{%- endif %}
end
{%- endif %}

View File

@@ -1,253 +0,0 @@
from typing import TYPE_CHECKING, Union, Optional, TextIO
from systemrdl.node import AddrmapNode, SignalNode, FieldNode, RegNode, AddressableNode
from systemrdl.rdltypes import PropertyReference
from ..utils import get_indexed_path
from ..identifier_filter import kw_filter as kwf
from ..sv_int import SVInt
from .generators import InputStructGenerator_Hier, OutputStructGenerator_Hier
from .generators import InputStructGenerator_TypeScope, OutputStructGenerator_TypeScope
from .generators import EnumGenerator
if TYPE_CHECKING:
from ..exporter import BusDecoderExporter, DesignState
class Hwif:
"""
Defines how the hardware input/output signals are generated:
- Field outputs
- Field inputs
- Signal inputs (except those that are promoted to the top)
"""
def __init__(self, exp: "BusDecoderExporter", hwif_report_file: Optional[TextIO]):
self.exp = exp
self.has_input_struct = False
self.has_output_struct = False
self.hwif_report_file = hwif_report_file
if not self.ds.reuse_hwif_typedefs:
self._gen_in_cls = InputStructGenerator_Hier
self._gen_out_cls = OutputStructGenerator_Hier
else:
self._gen_in_cls = InputStructGenerator_TypeScope
self._gen_out_cls = OutputStructGenerator_TypeScope
@property
def ds(self) -> "DesignState":
return self.exp.ds
@property
def top_node(self) -> AddrmapNode:
return self.exp.ds.top_node
def get_extra_package_params(self) -> str:
lines = [""]
for param in self.top_node.inst.parameters:
value = param.get_value()
if isinstance(value, int):
lines.append(f"localparam {param.name} = {SVInt(value)};")
elif isinstance(value, str):
lines.append(f"localparam {param.name} = {value};")
return "\n".join(lines)
def get_package_contents(self) -> str:
"""
If this hwif requires a package, generate the string
"""
lines = [""]
gen_in = self._gen_in_cls(self)
structs_in = gen_in.get_struct(
self.top_node, f"{self.top_node.inst_name}__in_t"
)
if structs_in is not None:
self.has_input_struct = True
lines.append(structs_in)
else:
self.has_input_struct = False
gen_out = self._gen_out_cls(self)
structs_out = gen_out.get_struct(
self.top_node, f"{self.top_node.inst_name}__out_t"
)
if structs_out is not None:
self.has_output_struct = True
lines.append(structs_out)
else:
self.has_output_struct = False
gen_enum = EnumGenerator()
enums = gen_enum.get_enums(self.ds.user_enums)
if enums is not None:
lines.append(enums)
return "\n\n".join(lines)
@property
def port_declaration(self) -> str:
"""
Returns the declaration string for all I/O ports in the hwif group
"""
# Assume get_package_declaration() is always called prior to this
assert self.has_input_struct is not None
assert self.has_output_struct is not None
lines = []
if self.has_input_struct:
type_name = f"{self.top_node.inst_name}__in_t"
lines.append(f"input {self.ds.package_name}::{type_name} hwif_in")
if self.has_output_struct:
type_name = f"{self.top_node.inst_name}__out_t"
lines.append(f"output {self.ds.package_name}::{type_name} hwif_out")
return ",\n".join(lines)
# ---------------------------------------------------------------------------
# hwif utility functions
# ---------------------------------------------------------------------------
def has_value_input(self, obj: Union[FieldNode, SignalNode]) -> bool:
"""
Returns True if the object infers an input wire in the hwif
"""
if isinstance(obj, FieldNode):
return obj.is_hw_writable
elif isinstance(obj, SignalNode):
# Signals are implicitly always inputs
return True
else:
raise RuntimeError
def has_value_output(self, obj: FieldNode) -> bool:
"""
Returns True if the object infers an output wire in the hwif
"""
return obj.is_hw_readable
def get_input_identifier(
self,
obj: Union[FieldNode, SignalNode, PropertyReference],
width: Optional[int] = None,
) -> Union[SVInt, str]:
"""
Returns the identifier string that best represents the input object.
if obj is:
Field: the fields hw input value port
Signal: signal input value
Prop reference:
could be an implied hwclr/hwset/swwe/swwel/we/wel input
raises an exception if obj is invalid
"""
if isinstance(obj, FieldNode):
next_value = obj.get_property("next")
if next_value is not None:
# 'next' property replaces the inferred input signal
return self.exp.dereferencer.get_value(next_value, width)
# Otherwise, use inferred
path = get_indexed_path(self.top_node, obj)
return "hwif_in." + path + ".next"
elif isinstance(obj, SignalNode):
if obj.get_path() in self.ds.out_of_hier_signals:
return kwf(obj.inst_name)
path = get_indexed_path(self.top_node, obj)
return "hwif_in." + path
elif isinstance(obj, PropertyReference):
assert isinstance(obj.node, FieldNode)
return self.get_implied_prop_input_identifier(obj.node, obj.name)
raise RuntimeError(f"Unhandled reference to: {obj}")
def get_external_rd_data(self, node: AddressableNode) -> str:
"""
Returns the identifier string for an external component's rd_data signal
"""
path = get_indexed_path(self.top_node, node)
return "hwif_in." + path + ".rd_data"
def get_external_rd_ack(self, node: AddressableNode) -> str:
"""
Returns the identifier string for an external component's rd_ack signal
"""
path = get_indexed_path(self.top_node, node)
return "hwif_in." + path + ".rd_ack"
def get_external_wr_ack(self, node: AddressableNode) -> str:
"""
Returns the identifier string for an external component's wr_ack signal
"""
path = get_indexed_path(self.top_node, node)
return "hwif_in." + path + ".wr_ack"
def get_implied_prop_input_identifier(self, field: FieldNode, prop: str) -> str:
assert prop in {
"hwclr",
"hwset",
"swwe",
"swwel",
"we",
"wel",
"incr",
"decr",
"incrvalue",
"decrvalue",
}
path = get_indexed_path(self.top_node, field)
return "hwif_in." + path + "." + prop
def get_output_identifier(self, obj: Union[FieldNode, PropertyReference]) -> str:
"""
Returns the identifier string that best represents the output object.
if obj is:
Field: the fields hw output value port
Property ref: this is also part of the struct
raises an exception if obj is invalid
"""
if isinstance(obj, FieldNode):
path = get_indexed_path(self.top_node, obj)
return "hwif_out." + path + ".value"
elif isinstance(obj, PropertyReference):
# TODO: this might be dead code.
# not sure when anything would call this function with a prop ref
# when dereferencer's get_value is more useful here
assert obj.node.get_property(obj.name)
assert isinstance(obj.node, (RegNode, FieldNode))
return self.get_implied_prop_output_identifier(obj.node, obj.name)
raise RuntimeError(f"Unhandled reference to: {obj}")
def get_implied_prop_output_identifier(
self, node: Union[FieldNode, RegNode], prop: str
) -> str:
if isinstance(node, FieldNode):
assert prop in {
"anded",
"ored",
"xored",
"swmod",
"swacc",
"incrthreshold",
"decrthreshold",
"overflow",
"underflow",
"rd_swacc",
"wr_swacc",
}
elif isinstance(node, RegNode):
assert prop in {
"intr",
"halt",
}
path = get_indexed_path(self.top_node, node)
return "hwif_out." + path + "." + prop

View File

@@ -1,385 +0,0 @@
from typing import TYPE_CHECKING, Optional, List, Type
from systemrdl.node import FieldNode, RegNode, AddrmapNode, MemNode
from systemrdl.walker import WalkerAction
from ..struct_generator import RDLFlatStructGenerator
from ..identifier_filter import kw_filter as kwf
from ..sv_int import SVInt
from ..utils import clog2
if TYPE_CHECKING:
from systemrdl.node import Node, SignalNode, AddressableNode, RegfileNode
from . import Hwif
from systemrdl.rdltypes import UserEnum
class HWIFStructGenerator(RDLFlatStructGenerator):
def __init__(self, hwif: 'Hwif', hwif_name: str) -> None:
super().__init__()
self.hwif = hwif
self.top_node = hwif.top_node
self.hwif_report_stack = [hwif_name]
def push_struct(self, type_name: str, inst_name: str, array_dimensions: Optional[List[int]] = None, packed: bool = False) -> None: # type: ignore
super().push_struct(type_name, inst_name, array_dimensions, packed)
if array_dimensions:
array_suffix = "".join([f"[0:{dim-1}]" for dim in array_dimensions])
segment = inst_name + array_suffix
else:
segment = inst_name
self.hwif_report_stack.append(segment)
def pop_struct(self) -> None:
super().pop_struct()
self.hwif_report_stack.pop()
def add_member(self, name: str, width: int = 1, *, lsb: int = 0, signed: bool = False) -> None: # type: ignore # pylint: disable=arguments-differ
super().add_member(name, width, lsb=lsb, signed=signed)
if width > 1 or lsb != 0:
suffix = f"[{lsb+width-1}:{lsb}]"
else:
suffix = ""
path = ".".join(self.hwif_report_stack)
if self.hwif.hwif_report_file:
self.hwif.hwif_report_file.write(f"{path}.{name}{suffix}\n")
#-------------------------------------------------------------------------------
class InputStructGenerator_Hier(HWIFStructGenerator):
def __init__(self, hwif: 'Hwif') -> None:
super().__init__(hwif, "hwif_in")
def get_typdef_name(self, node:'Node', suffix: str = "") -> str:
base = node.get_rel_path(
self.top_node.parent,
hier_separator="__",
array_suffix="x",
empty_array_suffix="x"
)
return f'{base}{suffix}__in_t'
def enter_Signal(self, node: 'SignalNode') -> None:
# only emit the signal if design scanner detected it is actually being used
path = node.get_path()
if path in self.hwif.ds.in_hier_signal_paths:
self.add_member(kwf(node.inst_name), node.width)
def _add_external_block_members(self, node: 'AddressableNode') -> None:
self.add_member("rd_ack")
self.add_member("rd_data", self.hwif.ds.cpuif_data_width)
self.add_member("wr_ack")
def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
super().enter_Addrmap(node)
assert node.external
self._add_external_block_members(node)
return WalkerAction.SkipDescendants
def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
super().enter_Regfile(node)
if node.external:
self._add_external_block_members(node)
return WalkerAction.SkipDescendants
return WalkerAction.Continue
def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
super().enter_Mem(node)
assert node.external
self._add_external_block_members(node)
return WalkerAction.SkipDescendants
def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
super().enter_Reg(node)
if node.external:
width = min(self.hwif.ds.cpuif_data_width, node.get_property('regwidth'))
n_subwords = node.get_property("regwidth") // node.get_property("accesswidth")
if node.has_sw_readable:
self.add_member("rd_ack")
self.add_external_reg_rd_data(node, width, n_subwords)
if node.has_sw_writable:
self.add_member("wr_ack")
return WalkerAction.SkipDescendants
return WalkerAction.Continue
def add_external_reg_rd_data(self, node: 'RegNode', width: int, n_subwords: int) -> None:
if n_subwords == 1:
# External reg is 1 sub-word. Add a packed struct to represent it
type_name = self.get_typdef_name(node, "__fields")
self.push_struct(type_name, "rd_data", packed=True)
current_bit = width - 1
for field in reversed(list(node.fields())):
if not field.is_sw_readable:
continue
if field.high < current_bit:
# Add padding
self.add_member(
f"_reserved_{current_bit}_{field.high + 1}",
current_bit - field.high
)
self.add_member(
kwf(field.inst_name),
field.width
)
current_bit = field.low - 1
# Add end padding if needed
if current_bit != -1:
self.add_member(
f"_reserved_{current_bit}_0",
current_bit + 1
)
self.pop_struct()
else:
# Multiple sub-words. Cannot generate a struct
self.add_member("rd_data", width)
def enter_Field(self, node: 'FieldNode') -> None:
type_name = self.get_typdef_name(node)
self.push_struct(type_name, kwf(node.inst_name))
# Provide input to field's next value if it is writable by hw, and it
# was not overridden by the 'next' property
if node.is_hw_writable and node.get_property('next') is None:
# Get the field's LSB index (can be nonzero for fixed-point values)
fracwidth = node.get_property("fracwidth")
lsb = 0 if fracwidth is None else -fracwidth
# get the signedness of the field
signed = node.get_property("is_signed")
self.add_member("next", node.width, lsb=lsb, signed=signed)
# Generate implied inputs
for prop_name in ["we", "wel", "swwe", "swwel", "hwclr", "hwset"]:
# if property is boolean and true, implies a corresponding input signal on the hwif
if node.get_property(prop_name) is True:
self.add_member(prop_name)
# Generate any implied counter inputs
if node.is_up_counter:
if not node.get_property('incr'):
# User did not provide their own incr component reference.
# Imply an input
self.add_member('incr')
width = node.get_property('incrwidth')
if width:
# Implies a corresponding incrvalue input
self.add_member('incrvalue', width)
if node.is_down_counter:
if not node.get_property('decr'):
# User did not provide their own decr component reference.
# Imply an input
self.add_member('decr')
width = node.get_property('decrwidth')
if width:
# Implies a corresponding decrvalue input
self.add_member('decrvalue', width)
def exit_Field(self, node: 'FieldNode') -> None:
self.pop_struct()
class OutputStructGenerator_Hier(HWIFStructGenerator):
def __init__(self, hwif: 'Hwif') -> None:
super().__init__(hwif, "hwif_out")
def get_typdef_name(self, node:'Node', suffix: str = "") -> str:
base = node.get_rel_path(
self.top_node.parent,
hier_separator="__",
array_suffix="x",
empty_array_suffix="x"
)
return f'{base}{suffix}__out_t'
def _add_external_block_members(self, node: 'AddressableNode') -> None:
self.add_member("req")
self.add_member("addr", clog2(node.size))
self.add_member("req_is_wr")
self.add_member("wr_data", self.hwif.ds.cpuif_data_width)
self.add_member("wr_biten", self.hwif.ds.cpuif_data_width)
def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
super().enter_Addrmap(node)
assert node.external
self._add_external_block_members(node)
return WalkerAction.SkipDescendants
def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
super().enter_Regfile(node)
if node.external:
self._add_external_block_members(node)
return WalkerAction.SkipDescendants
return WalkerAction.Continue
def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
super().enter_Mem(node)
assert node.external
self._add_external_block_members(node)
return WalkerAction.SkipDescendants
def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
super().enter_Reg(node)
if node.external:
width = min(self.hwif.ds.cpuif_data_width, node.get_property('regwidth'))
n_subwords = node.get_property("regwidth") // node.get_property("accesswidth")
self.add_member("req", n_subwords)
self.add_member("req_is_wr")
if node.has_sw_writable:
self.add_external_reg_wr_data("wr_data", node, width, n_subwords)
self.add_external_reg_wr_data("wr_biten", node, width, n_subwords)
return WalkerAction.SkipDescendants
return WalkerAction.Continue
def add_external_reg_wr_data(self, name: str, node: 'RegNode', width: int, n_subwords: int) -> None:
if n_subwords == 1:
# External reg is 1 sub-word. Add a packed struct to represent it
type_name = self.get_typdef_name(node, "__fields")
self.push_struct(type_name, name, packed=True)
current_bit = width - 1
for field in reversed(list(node.fields())):
if not field.is_sw_writable:
continue
if field.high < current_bit:
# Add padding
self.add_member(
f"_reserved_{current_bit}_{field.high + 1}",
current_bit - field.high
)
self.add_member(
kwf(field.inst_name),
field.width
)
current_bit = field.low - 1
# Add end padding if needed
if current_bit != -1:
self.add_member(
f"_reserved_{current_bit}_0",
current_bit + 1
)
self.pop_struct()
else:
# Multiple sub-words. Cannot generate a struct
self.add_member(name, width)
def enter_Field(self, node: 'FieldNode') -> None:
type_name = self.get_typdef_name(node)
self.push_struct(type_name, kwf(node.inst_name))
# Expose field's value if it is readable by hw
if node.is_hw_readable:
# Get the node's LSB index (can be nonzero for fixed-point values)
fracwidth = node.get_property("fracwidth")
lsb = 0 if fracwidth is None else -fracwidth
# get the signedness of the field
signed = node.get_property("is_signed")
self.add_member("value", node.width, lsb=lsb, signed=signed)
# Generate output bit signals enabled via property
for prop_name in ["anded", "ored", "xored", "swmod", "swacc", "overflow", "underflow", "rd_swacc", "wr_swacc"]:
if node.get_property(prop_name):
self.add_member(prop_name)
if node.get_property('incrthreshold') is not False: # (explicitly not False. Not 0)
self.add_member('incrthreshold')
if node.get_property('decrthreshold') is not False: # (explicitly not False. Not 0)
self.add_member('decrthreshold')
def exit_Field(self, node: 'FieldNode') -> None:
self.pop_struct()
def exit_Reg(self, node: 'RegNode') -> None:
if node.is_interrupt_reg:
self.add_member('intr')
if node.is_halt_reg:
self.add_member('halt')
super().exit_Reg(node)
#-------------------------------------------------------------------------------
class InputStructGenerator_TypeScope(InputStructGenerator_Hier):
def get_typdef_name(self, node:'Node', suffix: str = "") -> str:
scope_path = node.get_global_type_name("__")
if scope_path is None:
# Unable to determine a reusable type name. Fall back to hierarchical path
# Add prefix to prevent collision when mixing namespace methods
scope_path = "xtern__" + super().get_typdef_name(node)
if node.external:
# Node generates alternate external signals
extra_suffix = "__external"
else:
extra_suffix = ""
return f'{scope_path}{extra_suffix}{suffix}__in_t'
class OutputStructGenerator_TypeScope(OutputStructGenerator_Hier):
def get_typdef_name(self, node:'Node', suffix: str = "") -> str:
scope_path = node.get_global_type_name("__")
if scope_path is None:
# Unable to determine a reusable type name. Fall back to hierarchical path
# Add prefix to prevent collision when mixing namespace methods
scope_path = "xtern__" + super().get_typdef_name(node)
if node.external:
# Node generates alternate external signals
extra_suffix = "__external"
else:
extra_suffix = ""
return f'{scope_path}{extra_suffix}{suffix}__out_t'
#-------------------------------------------------------------------------------
class EnumGenerator:
"""
Generator for user-defined enum definitions
"""
def get_enums(self, user_enums: List[Type['UserEnum']]) -> Optional[str]:
if not user_enums:
return None
lines = []
for user_enum in user_enums:
lines.append(self._enum_typedef(user_enum))
return '\n\n'.join(lines)
@staticmethod
def _get_prefix(user_enum: Type['UserEnum']) -> str:
scope = user_enum.get_scope_path("__")
if scope:
return f"{scope}__{user_enum.type_name}"
else:
return user_enum.type_name
def _enum_typedef(self, user_enum: Type['UserEnum']) -> str:
prefix = self._get_prefix(user_enum)
lines = []
max_value = 1
for enum_member in user_enum:
lines.append(f" {prefix}__{enum_member.name} = {SVInt(enum_member.value)}")
max_value = max(max_value, enum_member.value)
if max_value.bit_length() == 1:
datatype = "logic"
else:
datatype = f"logic [{max_value.bit_length() - 1}:0]"
return (
f"typedef enum {datatype} {{\n"
+ ",\n".join(lines)
+ f"\n}} {prefix}_e;"
)

View File

@@ -1,293 +0,0 @@
// Generated by PeakRDL-busdecoder - A free and open-source SystemVerilog generator
// https://github.com/SystemRDL/PeakRDL-busdecoder
module {{ds.module_name}}
{%- if cpuif.parameters %} #(
{{",\n ".join(cpuif.parameters)}}
) {%- endif %} (
input wire clk,
input wire {{default_resetsignal_name}},
{%- for signal in ds.out_of_hier_signals.values() %}
{%- if signal.width == 1 %}
input wire {{kwf(signal.inst_name)}},
{%- else %}
input wire [{{signal.width-1}}:0] {{kwf(signal.inst_name)}},
{%- endif %}
{%- endfor %}
{%- if ds.has_paritycheck %}
output logic parity_error,
{%- endif %}
{{cpuif.port_declaration|indent(8)}}
{%- if hwif.has_input_struct or hwif.has_output_struct %},{% endif %}
{{hwif.port_declaration|indent(8)}}
);
//--------------------------------------------------------------------------
// CPU Bus interface logic
//--------------------------------------------------------------------------
logic cpuif_req;
logic cpuif_req_is_wr;
logic [{{cpuif.addr_width-1}}:0] cpuif_addr;
logic [{{cpuif.data_width-1}}:0] cpuif_wr_data;
logic [{{cpuif.data_width-1}}:0] cpuif_wr_biten;
logic cpuif_req_stall_wr;
logic cpuif_req_stall_rd;
logic cpuif_rd_ack;
logic cpuif_rd_err;
logic [{{cpuif.data_width-1}}:0] cpuif_rd_data;
logic cpuif_wr_ack;
logic cpuif_wr_err;
{{cpuif.get_implementation()|indent}}
logic cpuif_req_masked;
{%- if ds.has_external_addressable %}
logic external_req;
logic external_pending;
logic external_wr_ack;
logic external_rd_ack;
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
external_pending <= '0;
end else begin
if(external_req & ~external_wr_ack & ~external_rd_ack) external_pending <= '1;
else if(external_wr_ack | external_rd_ack) external_pending <= '0;
`ifndef SYNTHESIS
assert_bad_ext_wr_ack: assert(!external_wr_ack || (external_pending | external_req))
else $error("An external wr_ack strobe was asserted when no external request was active");
assert_bad_ext_rd_ack: assert(!external_rd_ack || (external_pending | external_req))
else $error("An external rd_ack strobe was asserted when no external request was active");
`endif
end
end
{%- endif %}
{% if ds.min_read_latency == ds.min_write_latency %}
// Read & write latencies are balanced. Stalls not required
{%- if ds.has_external_addressable %}
// except if external
assign cpuif_req_stall_rd = external_pending;
assign cpuif_req_stall_wr = external_pending;
{%- else %}
assign cpuif_req_stall_rd = '0;
assign cpuif_req_stall_wr = '0;
{%- endif %}
{%- elif ds.min_read_latency > ds.min_write_latency %}
// Read latency > write latency. May need to delay next write that follows a read
logic [{{ds.min_read_latency - ds.min_write_latency - 1}}:0] cpuif_req_stall_sr;
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
cpuif_req_stall_sr <= '0;
end else if(cpuif_req && !cpuif_req_is_wr) begin
cpuif_req_stall_sr <= '1;
end else begin
cpuif_req_stall_sr <= (cpuif_req_stall_sr >> 'd1);
end
end
{%- if ds.has_external_addressable %}
assign cpuif_req_stall_rd = external_pending;
assign cpuif_req_stall_wr = cpuif_req_stall_sr[0] | external_pending;
{%- else %}
assign cpuif_req_stall_rd = '0;
assign cpuif_req_stall_wr = cpuif_req_stall_sr[0];
{%- endif %}
{%- else %}
// Write latency > read latency. May need to delay next read that follows a write
logic [{{ds.min_write_latency - ds.min_read_latency - 1}}:0] cpuif_req_stall_sr;
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
cpuif_req_stall_sr <= '0;
end else if(cpuif_req && cpuif_req_is_wr) begin
cpuif_req_stall_sr <= '1;
end else begin
cpuif_req_stall_sr <= (cpuif_req_stall_sr >> 'd1);
end
end
{%- if ds.has_external_addressable %}
assign cpuif_req_stall_rd = cpuif_req_stall_sr[0] | external_pending;
assign cpuif_req_stall_wr = external_pending;
{%- else %}
assign cpuif_req_stall_rd = cpuif_req_stall_sr[0];
assign cpuif_req_stall_wr = '0;
{%- endif %}
{%- endif %}
assign cpuif_req_masked = cpuif_req
& !(!cpuif_req_is_wr & cpuif_req_stall_rd)
& !(cpuif_req_is_wr & cpuif_req_stall_wr);
//--------------------------------------------------------------------------
// Address Decode
//--------------------------------------------------------------------------
{{address_decode.get_strobe_struct()|indent}}
decoded_reg_strb_t decoded_reg_strb;
{%- if ds.has_external_addressable %}
logic decoded_strb_is_external;
{% endif %}
{%- if ds.has_external_block %}
logic [{{cpuif.addr_width-1}}:0] decoded_addr;
{% endif %}
logic decoded_req;
logic decoded_req_is_wr;
logic [{{cpuif.data_width-1}}:0] decoded_wr_data;
logic [{{cpuif.data_width-1}}:0] decoded_wr_biten;
always_comb begin
{%- if ds.has_external_addressable %}
automatic logic is_external;
is_external = '0;
{%- endif %}
{{address_decode.get_implementation()|indent(8)}}
{%- if ds.has_external_addressable %}
decoded_strb_is_external = is_external;
external_req = is_external;
{%- endif %}
end
// Pass down signals to next stage
{%- if ds.has_external_block %}
assign decoded_addr = cpuif_addr;
{% endif %}
assign decoded_req = cpuif_req_masked;
assign decoded_req_is_wr = cpuif_req_is_wr;
assign decoded_wr_data = cpuif_wr_data;
assign decoded_wr_biten = cpuif_wr_biten;
{% if ds.has_writable_msb0_fields %}
// bitswap for use by fields with msb0 ordering
logic [{{cpuif.data_width-1}}:0] decoded_wr_data_bswap;
logic [{{cpuif.data_width-1}}:0] decoded_wr_biten_bswap;
assign decoded_wr_data_bswap = {<<{decoded_wr_data}};
assign decoded_wr_biten_bswap = {<<{decoded_wr_biten}};
{%- endif %}
{%- if ds.has_buffered_write_regs %}
//--------------------------------------------------------------------------
// Write double-buffers
//--------------------------------------------------------------------------
{{write_buffering.get_storage_struct()|indent}}
{{write_buffering.get_implementation()|indent}}
{%- endif %}
//--------------------------------------------------------------------------
// Field logic
//--------------------------------------------------------------------------
{{field_logic.get_combo_struct()|indent}}
{{field_logic.get_storage_struct()|indent}}
{{field_logic.get_implementation()|indent}}
{%- if ds.has_paritycheck %}
//--------------------------------------------------------------------------
// Parity Error
//--------------------------------------------------------------------------
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
parity_error <= '0;
end else begin
automatic logic err;
err = '0;
{{parity.get_implementation()|indent(12)}}
parity_error <= err;
end
end
{%- endif %}
{%- if ds.has_buffered_read_regs %}
//--------------------------------------------------------------------------
// Read double-buffers
//--------------------------------------------------------------------------
{{read_buffering.get_storage_struct()|indent}}
{{read_buffering.get_implementation()|indent}}
{%- endif %}
//--------------------------------------------------------------------------
// Write response
//--------------------------------------------------------------------------
{%- if ds.has_external_addressable %}
always_comb begin
automatic logic wr_ack;
wr_ack = '0;
{{ext_write_acks.get_implementation()|indent(8)}}
external_wr_ack = wr_ack;
end
assign cpuif_wr_ack = external_wr_ack | (decoded_req & decoded_req_is_wr & ~decoded_strb_is_external);
{%- else %}
assign cpuif_wr_ack = decoded_req & decoded_req_is_wr;
{%- endif %}
// Writes are always granted with no error response
assign cpuif_wr_err = '0;
//--------------------------------------------------------------------------
// Readback
//--------------------------------------------------------------------------
{%- if ds.has_external_addressable %}
logic readback_external_rd_ack_c;
always_comb begin
automatic logic rd_ack;
rd_ack = '0;
{{ext_read_acks.get_implementation()|indent(8)}}
readback_external_rd_ack_c = rd_ack;
end
logic readback_external_rd_ack;
{%- if ds.retime_read_fanin %}
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
readback_external_rd_ack <= '0;
end else begin
readback_external_rd_ack <= readback_external_rd_ack_c;
end
end
{%- else %}
assign readback_external_rd_ack = readback_external_rd_ack_c;
{%- endif %}
{%- endif %}
logic readback_err;
logic readback_done;
logic [{{cpuif.data_width-1}}:0] readback_data;
{{readback_implementation|indent}}
{% if ds.retime_read_response %}
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
cpuif_rd_ack <= '0;
cpuif_rd_data <= '0;
cpuif_rd_err <= '0;
{%- if ds.has_external_addressable %}
external_rd_ack <= '0;
{%- endif %}
end else begin
{%- if ds.has_external_addressable %}
external_rd_ack <= readback_external_rd_ack;
cpuif_rd_ack <= readback_done | readback_external_rd_ack;
{%- else %}
cpuif_rd_ack <= readback_done;
{%- endif %}
cpuif_rd_data <= readback_data;
cpuif_rd_err <= readback_err;
end
end
{% else %}
{%- if ds.has_external_addressable %}
assign external_rd_ack = readback_external_rd_ack;
assign cpuif_rd_ack = readback_done | readback_external_rd_ack;
{%- else %}
assign cpuif_rd_ack = readback_done;
{%- endif %}
assign cpuif_rd_data = readback_data;
assign cpuif_rd_err = readback_err;
{%- endif %}
endmodule
{# (eof newline anchor) #}

View File

@@ -1,34 +0,0 @@
from typing import TYPE_CHECKING
from systemrdl.walker import WalkerAction
from .forloop_generator import RDLForLoopGenerator
if TYPE_CHECKING:
from .exporter import BusDecoderExporter
from systemrdl.node import FieldNode, AddressableNode
class ParityErrorReduceGenerator(RDLForLoopGenerator):
def __init__(self, exp: "BusDecoderExporter") -> None:
super().__init__()
self.exp = exp
def get_implementation(self) -> str:
content = self.get_content(self.exp.ds.top_node)
if content is None:
return ""
return content
def enter_AddressableComponent(self, node: "AddressableNode") -> WalkerAction:
super().enter_AddressableComponent(node)
if node.external:
return WalkerAction.SkipDescendants
return WalkerAction.Continue
def enter_Field(self, node: "FieldNode") -> None:
if node.get_property("paritycheck") and node.implements_storage:
self.add_content(
f"err |= {self.exp.field_logic.get_parity_error_identifier(node)};"
)

View File

@@ -1,61 +0,0 @@
from typing import TYPE_CHECKING, Union
from systemrdl.node import AddrmapNode, RegNode, SignalNode
from .storage_generator import RBufStorageStructGenerator
from .implementation_generator import RBufLogicGenerator
from ..utils import get_indexed_path
from ..sv_int import SVInt
if TYPE_CHECKING:
from ..exporter import BusDecoderExporter
class ReadBuffering:
def __init__(self, exp: "BusDecoderExporter"):
self.exp = exp
@property
def top_node(self) -> "AddrmapNode":
return self.exp.ds.top_node
def get_storage_struct(self) -> str:
struct_gen = RBufStorageStructGenerator()
s = struct_gen.get_struct(self.top_node, "rbuf_storage_t")
assert s is not None
return s + "\nrbuf_storage_t rbuf_storage;"
def get_implementation(self) -> str:
gen = RBufLogicGenerator(self)
s = gen.get_content(self.top_node)
assert s is not None
return s
def get_trigger(self, node: RegNode) -> str:
trigger = node.get_property("rbuffer_trigger")
if isinstance(trigger, RegNode):
# Trigger is a register.
# trigger when lowermost address of the register is written
regwidth = trigger.get_property("regwidth")
accesswidth = trigger.get_property("accesswidth")
strb_prefix = self.exp.dereferencer.get_access_strobe(
trigger, reduce_substrobes=False
)
if accesswidth < regwidth:
return f"{strb_prefix}[0] && !decoded_req_is_wr"
else:
return f"{strb_prefix} && !decoded_req_is_wr"
elif isinstance(trigger, SignalNode):
s = self.exp.dereferencer.get_value(trigger)
if trigger.get_property("activehigh"):
return str(s)
else:
return f"~{s}"
else:
# Trigger is a field or propref bit
return str(self.exp.dereferencer.get_value(trigger))
def get_rbuf_data(self, node: RegNode) -> str:
return "rbuf_storage." + get_indexed_path(self.top_node, node) + ".data"

View File

@@ -1,59 +0,0 @@
from typing import TYPE_CHECKING
from systemrdl.component import Reg
from systemrdl.node import RegNode
from ..forloop_generator import RDLForLoopGenerator
if TYPE_CHECKING:
from . import ReadBuffering
class RBufLogicGenerator(RDLForLoopGenerator):
i_type = "genvar"
def __init__(self, rbuf: 'ReadBuffering') -> None:
super().__init__()
self.rbuf = rbuf
self.exp = rbuf.exp
self.template = self.exp.jj_env.get_template(
"read_buffering/template.sv"
)
def enter_Reg(self, node: RegNode) -> None:
super().enter_Reg(node)
assert isinstance(node.inst, Reg)
if not node.get_property('buffer_reads'):
return
context = {
'node': node,
'rbuf': self.rbuf,
'get_assignments': self.get_assignments,
}
self.add_content(self.template.render(context))
def get_assignments(self, node: RegNode) -> str:
data = self.rbuf.get_rbuf_data(node)
bidx = 0
s = []
for field in node.fields():
if bidx < field.low:
# zero padding before field
s.append(f"{data}[{field.low-1}:{bidx}] <= '0;")
value = self.exp.dereferencer.get_value(field)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{{value}}}}}"
s.append(f"{data}[{field.high}:{field.low}] <= {value};")
bidx = field.high + 1
regwidth = node.get_property('regwidth')
if bidx < regwidth:
# zero padding after last field
s.append(f"{data}[{regwidth-1}:{bidx}] <= '0;")
return "\n".join(s)

View File

@@ -1,18 +0,0 @@
from systemrdl.node import FieldNode, RegNode
from ..struct_generator import RDLStructGenerator
class RBufStorageStructGenerator(RDLStructGenerator):
def enter_Field(self, node: FieldNode) -> None:
# suppress parent class's field behavior
pass
def enter_Reg(self, node: RegNode) -> None:
super().enter_Reg(node)
if not node.get_property('buffer_reads'):
return
regwidth = node.get_property('regwidth')
self.add_member("data", regwidth)

View File

@@ -1,5 +0,0 @@
always_ff @(posedge clk) begin
if({{rbuf.get_trigger(node)}}) begin
{{get_assignments(node)|indent(8)}}
end
end

View File

@@ -1,71 +0,0 @@
from typing import TYPE_CHECKING
import math
from .generators import ReadbackAssignmentGenerator
if TYPE_CHECKING:
from ..exporter import BusDecoderExporter, DesignState
from systemrdl.node import AddrmapNode
class Readback:
def __init__(self, exp: "BusDecoderExporter"):
self.exp = exp
@property
def ds(self) -> "DesignState":
return self.exp.ds
@property
def top_node(self) -> "AddrmapNode":
return self.exp.ds.top_node
def get_implementation(self) -> str:
gen = ReadbackAssignmentGenerator(self.exp)
array_assignments = gen.get_content(self.top_node)
array_size = gen.current_offset
# Enabling the fanin stage doesnt make sense if readback fanin is
# small. This also avoids pesky corner cases
if array_size < 4:
self.ds.retime_read_fanin = False
context = {
"array_assignments": array_assignments,
"array_size": array_size,
"get_always_ff_event": self.exp.dereferencer.get_always_ff_event,
"get_resetsignal": self.exp.dereferencer.get_resetsignal,
"cpuif": self.exp.cpuif,
"ds": self.ds,
}
if self.ds.retime_read_fanin:
# If adding a fanin pipeline stage, goal is to try to
# split the fanin path in the middle so that fanin into the stage
# and the following are roughly balanced.
fanin_target = math.sqrt(array_size)
# Size of fanin group to consume per fanin element
fanin_stride = math.floor(fanin_target)
# Number of array elements to reduce to.
# Round up to an extra element in case there is some residual
fanin_array_size = math.ceil(array_size / fanin_stride)
# leftovers are handled in an extra array element
fanin_residual_stride = array_size % fanin_stride
if fanin_residual_stride != 0:
# If there is a partial fanin element, reduce the number of
# loops performed in the bulk fanin stage
fanin_loop_iter = fanin_array_size - 1
else:
fanin_loop_iter = fanin_array_size
context["fanin_stride"] = fanin_stride
context["fanin_array_size"] = fanin_array_size
context["fanin_residual_stride"] = fanin_residual_stride
context["fanin_loop_iter"] = fanin_loop_iter
template = self.exp.jj_env.get_template("readback/templates/readback.sv")
return template.render(context)

View File

@@ -1,449 +0,0 @@
from typing import TYPE_CHECKING, List
from systemrdl.node import RegNode, AddressableNode
from systemrdl.walker import WalkerAction
from ..forloop_generator import RDLForLoopGenerator, LoopBody
from ..utils import do_bitswap, do_slice
if TYPE_CHECKING:
from ..exporter import BusDecoderExporter
class ReadbackLoopBody(LoopBody):
def __init__(self, dim: int, iterator: str, i_type: str) -> None:
super().__init__(dim, iterator, i_type)
self.n_regs = 0
def __str__(self) -> str:
# replace $i#sz token when stringifying
s = super().__str__()
token = f"${self.iterator}sz"
s = s.replace(token, str(self.n_regs))
return s
class ReadbackAssignmentGenerator(RDLForLoopGenerator):
i_type = "genvar"
loop_body_cls = ReadbackLoopBody
def __init__(self, exp: "BusDecoderExporter") -> None:
super().__init__()
self.exp = exp
# The readback array collects all possible readback values into a flat
# array. The array width is equal to the CPUIF bus width. Each entry in
# the array represents an aligned read access.
self.current_offset = 0
self.start_offset_stack = [] # type: List[int]
self.dim_stack = [] # type: List[int]
@property
def current_offset_str(self) -> str:
"""
Derive a string that represents the current offset being assigned.
This consists of:
- The current integer offset
- multiplied index of any enclosing loop
The integer offset from "current_offset" is static and is monotonically
incremented as more register assignments are processed.
The component of the offset from loops is added by multiplying the current
loop index by the loop size.
Since the loop's size is not known at this time, it is emitted as a
placeholder token like: $i0sz, $i1sz, $i2sz, etc
These tokens can be replaced once the loop body has been completed and the
size of its contents is known.
"""
offset_parts = []
for i in range(self._loop_level):
offset_parts.append(f"i{i} * $i{i}sz")
offset_parts.append(str(self.current_offset))
return " + ".join(offset_parts)
def push_loop(self, dim: int) -> None:
super().push_loop(dim)
self.start_offset_stack.append(self.current_offset)
self.dim_stack.append(dim)
def pop_loop(self) -> None:
start_offset = self.start_offset_stack.pop()
dim = self.dim_stack.pop()
# Number of registers enclosed in this loop
n_regs = self.current_offset - start_offset
self.current_loop.n_regs = n_regs # type: ignore
super().pop_loop()
# Advance current scope's offset to account for loop's contents
self.current_offset = start_offset + n_regs * dim
def enter_AddressableComponent(self, node: "AddressableNode") -> WalkerAction:
super().enter_AddressableComponent(node)
if node.external and not isinstance(node, RegNode):
# External block
strb = self.exp.hwif.get_external_rd_ack(node)
data = self.exp.hwif.get_external_rd_data(node)
self.add_content(
f"assign readback_array[{self.current_offset_str}] = {strb} ? {data} : '0;"
)
self.current_offset += 1
return WalkerAction.SkipDescendants
return WalkerAction.Continue
def enter_Reg(self, node: RegNode) -> WalkerAction:
if not node.has_sw_readable:
return WalkerAction.SkipDescendants
if node.external:
self.process_external_reg(node)
return WalkerAction.SkipDescendants
accesswidth = node.get_property("accesswidth")
regwidth = node.get_property("regwidth")
rbuf = node.get_property("buffer_reads")
if rbuf:
trigger = node.get_property("rbuffer_trigger")
is_own_trigger = isinstance(trigger, RegNode) and trigger == node
if is_own_trigger:
if accesswidth < regwidth:
self.process_buffered_reg_with_bypass(node, regwidth, accesswidth)
else:
# bypass cancels out. Behaves like a normal reg
self.process_reg(node)
else:
self.process_buffered_reg(node, regwidth, accesswidth)
elif accesswidth < regwidth:
self.process_wide_reg(node, accesswidth)
else:
self.process_reg(node)
return WalkerAction.SkipDescendants
def process_external_reg(self, node: RegNode) -> None:
strb = self.exp.hwif.get_external_rd_ack(node)
data = self.exp.hwif.get_external_rd_data(node)
regwidth = node.get_property("regwidth")
if regwidth < self.exp.cpuif.data_width:
self.add_content(
f"assign readback_array[{self.current_offset_str}][{self.exp.cpuif.data_width - 1}:{regwidth}] = '0;"
)
self.add_content(
f"assign readback_array[{self.current_offset_str}][{regwidth - 1}:0] = {strb} ? {data} : '0;"
)
else:
self.add_content(
f"assign readback_array[{self.current_offset_str}] = {strb} ? {data} : '0;"
)
self.current_offset += 1
def process_reg(self, node: RegNode) -> None:
current_bit = 0
rd_strb = (
f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)"
)
# Fields are sorted by ascending low bit
for field in node.fields():
if not field.is_sw_readable:
continue
# insert reserved assignment before this field if needed
if field.low != current_bit:
self.add_content(
f"assign readback_array[{self.current_offset_str}][{field.low - 1}:{current_bit}] = '0;"
)
value = self.exp.dereferencer.get_value(field)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = do_bitswap(value)
self.add_content(
f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;"
)
current_bit = field.high + 1
# Insert final reserved assignment if needed
bus_width = self.exp.cpuif.data_width
if current_bit < bus_width:
self.add_content(
f"assign readback_array[{self.current_offset_str}][{bus_width - 1}:{current_bit}] = '0;"
)
self.current_offset += 1
def process_buffered_reg(
self, node: RegNode, regwidth: int, accesswidth: int
) -> None:
rbuf = self.exp.read_buffering.get_rbuf_data(node)
if accesswidth < regwidth:
# Is wide reg
n_subwords = regwidth // accesswidth
astrb = self.exp.dereferencer.get_access_strobe(
node, reduce_substrobes=False
)
for i in range(n_subwords):
rd_strb = f"({astrb}[{i}] && !decoded_req_is_wr)"
bslice = f"[{(i + 1) * accesswidth - 1}:{i * accesswidth}]"
self.add_content(
f"assign readback_array[{self.current_offset_str}] = {rd_strb} ? {rbuf}{bslice} : '0;"
)
self.current_offset += 1
else:
# Is regular reg
rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)"
self.add_content(
f"assign readback_array[{self.current_offset_str}][{regwidth - 1}:0] = {rd_strb} ? {rbuf} : '0;"
)
bus_width = self.exp.cpuif.data_width
if regwidth < bus_width:
self.add_content(
f"assign readback_array[{self.current_offset_str}][{bus_width - 1}:{regwidth}] = '0;"
)
self.current_offset += 1
def process_buffered_reg_with_bypass(
self, node: RegNode, regwidth: int, accesswidth: int
) -> None:
"""
Special case for a buffered register when the register is its own trigger.
First sub-word shall bypass the read buffer and assign directly.
Subsequent subwords assign from the buffer.
Caller guarantees this is a wide reg
"""
astrb = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False)
# Generate assignments for first sub-word
bidx = 0
rd_strb = f"({astrb}[0] && !decoded_req_is_wr)"
for field in node.fields():
if not field.is_sw_readable:
continue
if field.low >= accesswidth:
# field is not in this subword.
break
if bidx < field.low:
# insert padding before
self.add_content(
f"assign readback_array[{self.current_offset_str}][{field.low - 1}:{bidx}] = '0;"
)
if field.high >= accesswidth:
# field gets truncated
r_low = field.low
r_high = accesswidth - 1
f_low = 0
f_high = accesswidth - 1 - field.low
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
# Mirror the low/high indexes
f_low = field.width - 1 - f_low
f_high = field.width - 1 - f_high
f_low, f_high = f_high, f_low
value = do_bitswap(
do_slice(self.exp.dereferencer.get_value(field), f_high, f_low)
)
else:
value = do_slice(
self.exp.dereferencer.get_value(field), f_high, f_low
)
self.add_content(
f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;"
)
bidx = accesswidth
else:
# field fits in subword
value = self.exp.dereferencer.get_value(field)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = do_bitswap(value)
self.add_content(
f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;"
)
bidx = field.high + 1
# pad up remainder of subword
if bidx < accesswidth:
self.add_content(
f"assign readback_array[{self.current_offset_str}][{accesswidth - 1}:{bidx}] = '0;"
)
self.current_offset += 1
# Assign remainder of subwords from read buffer
n_subwords = regwidth // accesswidth
rbuf = self.exp.read_buffering.get_rbuf_data(node)
for i in range(1, n_subwords):
rd_strb = f"({astrb}[{i}] && !decoded_req_is_wr)"
bslice = f"[{(i + 1) * accesswidth - 1}:{i * accesswidth}]"
self.add_content(
f"assign readback_array[{self.current_offset_str}] = {rd_strb} ? {rbuf}{bslice} : '0;"
)
self.current_offset += 1
def process_wide_reg(self, node: RegNode, accesswidth: int) -> None:
bus_width = self.exp.cpuif.data_width
subword_idx = 0
current_bit = 0 # Bit-offset within the wide register
access_strb = self.exp.dereferencer.get_access_strobe(
node, reduce_substrobes=False
)
# Fields are sorted by ascending low bit
for field in node.fields():
if not field.is_sw_readable:
continue
# insert zero assignment before this field if needed
if field.low >= accesswidth * (subword_idx + 1):
# field does not start in this subword
if current_bit > accesswidth * subword_idx:
# current subword had content. Assign remainder
low = current_bit % accesswidth
high = bus_width - 1
self.add_content(
f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;"
)
self.current_offset += 1
# Advance to subword that contains the start of the field
subword_idx = field.low // accesswidth
current_bit = accesswidth * subword_idx
if current_bit != field.low:
# assign zero up to start of this field
low = current_bit % accesswidth
high = (field.low % accesswidth) - 1
self.add_content(
f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;"
)
current_bit = field.low
# Assign field
# loop until the entire field's assignments have been generated
field_pos = field.low
while current_bit <= field.high:
# Assign the field
rd_strb = f"({access_strb}[{subword_idx}] && !decoded_req_is_wr)"
if (field_pos == field.low) and (
field.high < accesswidth * (subword_idx + 1)
):
# entire field fits into this subword
low = field.low - accesswidth * subword_idx
high = field.high - accesswidth * subword_idx
value = self.exp.dereferencer.get_value(field)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = do_bitswap(value)
self.add_content(
f"assign readback_array[{self.current_offset_str}][{high}:{low}] = {rd_strb} ? {value} : '0;"
)
current_bit = field.high + 1
if current_bit == accesswidth * (subword_idx + 1):
# Field ends at the subword boundary
subword_idx += 1
self.current_offset += 1
elif field.high >= accesswidth * (subword_idx + 1):
# only a subset of the field can fit into this subword
# high end gets truncated
# assignment slice
r_low = field_pos - accesswidth * subword_idx
r_high = accesswidth - 1
# field slice
f_low = field_pos - field.low
f_high = accesswidth * (subword_idx + 1) - 1 - field.low
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
# Mirror the low/high indexes
f_low = field.width - 1 - f_low
f_high = field.width - 1 - f_high
f_low, f_high = f_high, f_low
value = do_bitswap(
do_slice(
self.exp.dereferencer.get_value(field), f_high, f_low
)
)
else:
value = do_slice(
self.exp.dereferencer.get_value(field), f_high, f_low
)
self.add_content(
f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;"
)
# advance to the next subword
subword_idx += 1
current_bit = accesswidth * subword_idx
field_pos = current_bit
self.current_offset += 1
else:
# only a subset of the field can fit into this subword
# finish field
# assignment slice
r_low = field_pos - accesswidth * subword_idx
r_high = field.high - accesswidth * subword_idx
# field slice
f_low = field_pos - field.low
f_high = field.high - field.low
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
# Mirror the low/high indexes
f_low = field.width - 1 - f_low
f_high = field.width - 1 - f_high
f_low, f_high = f_high, f_low
value = do_bitswap(
do_slice(
self.exp.dereferencer.get_value(field), f_high, f_low
)
)
else:
value = do_slice(
self.exp.dereferencer.get_value(field), f_high, f_low
)
self.add_content(
f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;"
)
current_bit = field.high + 1
if current_bit == accesswidth * (subword_idx + 1):
# Field ends at the subword boundary
subword_idx += 1
self.current_offset += 1
# insert zero assignment after the last field if needed
if current_bit > accesswidth * subword_idx:
# current subword had content. Assign remainder
low = current_bit % accesswidth
high = bus_width - 1
self.add_content(
f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;"
)
self.current_offset += 1

View File

@@ -1,79 +0,0 @@
{% if array_assignments is not none %}
// Assign readback values to a flattened array
logic [{{cpuif.data_width-1}}:0] readback_array[{{array_size}}];
{{array_assignments}}
{%- if ds.retime_read_fanin %}
// fanin stage
logic [{{cpuif.data_width-1}}:0] readback_array_c[{{fanin_array_size}}];
for(genvar g=0; g<{{fanin_loop_iter}}; g++) begin
always_comb begin
automatic logic [{{cpuif.data_width-1}}:0] readback_data_var;
readback_data_var = '0;
for(int i=g*{{fanin_stride}}; i<((g+1)*{{fanin_stride}}); i++) readback_data_var |= readback_array[i];
readback_array_c[g] = readback_data_var;
end
end
{%- if fanin_residual_stride == 1 %}
assign readback_array_c[{{fanin_array_size-1}}] = readback_array[{{array_size-1}}];
{%- elif fanin_residual_stride > 1 %}
always_comb begin
automatic logic [{{cpuif.data_width-1}}:0] readback_data_var;
readback_data_var = '0;
for(int i={{(fanin_array_size-1) * fanin_stride}}; i<{{array_size}}; i++) readback_data_var |= readback_array[i];
readback_array_c[{{fanin_array_size-1}}] = readback_data_var;
end
{%- endif %}
logic [{{cpuif.data_width-1}}:0] readback_array_r[{{fanin_array_size}}];
logic readback_done_r;
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
for(int i=0; i<{{fanin_array_size}}; i++) readback_array_r[i] <= '0;
readback_done_r <= '0;
end else begin
readback_array_r <= readback_array_c;
{%- if ds.has_external_addressable %}
readback_done_r <= decoded_req & ~decoded_req_is_wr & ~decoded_strb_is_external;
{%- else %}
readback_done_r <= decoded_req & ~decoded_req_is_wr;
{%- endif %}
end
end
// Reduce the array
always_comb begin
automatic logic [{{cpuif.data_width-1}}:0] readback_data_var;
readback_done = readback_done_r;
readback_err = '0;
readback_data_var = '0;
for(int i=0; i<{{fanin_array_size}}; i++) readback_data_var |= readback_array_r[i];
readback_data = readback_data_var;
end
{%- else %}
// Reduce the array
always_comb begin
automatic logic [{{cpuif.data_width-1}}:0] readback_data_var;
{%- if ds.has_external_addressable %}
readback_done = decoded_req & ~decoded_req_is_wr & ~decoded_strb_is_external;
{%- else %}
readback_done = decoded_req & ~decoded_req_is_wr;
{%- endif %}
readback_err = '0;
readback_data_var = '0;
for(int i=0; i<{{array_size}}; i++) readback_data_var |= readback_array[i];
readback_data = readback_data_var;
end
{%- endif %}
{%- else %}
assign readback_done = decoded_req & ~decoded_req_is_wr;
assign readback_data = '0;
assign readback_err = '0;
{% endif %}

View File

@@ -1,126 +0,0 @@
from typing import TYPE_CHECKING, Optional
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
from systemrdl.node import SignalNode, RegNode
if TYPE_CHECKING:
from systemrdl.node import Node, FieldNode, 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 _get_out_of_hier_field_reset(self) -> None:
current_node: Optional[Node]
current_node = self.top_node.parent
while current_node is not None:
for signal in current_node.signals():
if signal.get_property("field_reset"):
path = signal.get_path()
self.ds.out_of_hier_signals[path] = signal
return
current_node = current_node.parent
def do_scan(self) -> None:
# Collect cpuif reset, if any.
cpuif_reset = self.top_node.cpuif_reset
if cpuif_reset is not None:
path = cpuif_reset.get_path()
rel_path = cpuif_reset.get_rel_path(self.top_node)
if rel_path.startswith("^"):
self.ds.out_of_hier_signals[path] = cpuif_reset
else:
self.ds.in_hier_signal_paths.add(path)
# collect out-of-hier field_reset, if any
self._get_out_of_hier_field_reset()
# Ensure addrmap is not a bridge. This concept does not make sense for
# terminal components.
if self.top_node.get_property("bridge"):
self.msg.error(
"BusDecoder generator does not support exporting bridge address maps",
self.top_node.inst.property_src_ref.get(
"bridge", self.top_node.inst.inst_src_ref
),
)
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") -> Optional[WalkerAction]:
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():
value = node.get_property(prop_name)
if isinstance(value, SignalNode):
path = value.get_path()
rel_path = value.get_rel_path(self.top_node)
if rel_path.startswith("^"):
self.ds.out_of_hier_signals[path] = value
else:
self.ds.in_hier_signal_paths.add(path)
if prop_name == "encode":
if value not in self.ds.user_enums:
self.ds.user_enums.append(value)
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
def enter_Reg(self, node: "RegNode") -> None:
# The CPUIF's bus width is sized according to the largest accesswidth in the design
accesswidth = node.get_property("accesswidth")
self.ds.cpuif_data_width = max(self.ds.cpuif_data_width, accesswidth)
self.ds.has_buffered_write_regs = self.ds.has_buffered_write_regs or bool(
node.get_property("buffer_writes")
)
self.ds.has_buffered_read_regs = self.ds.has_buffered_read_regs or bool(
node.get_property("buffer_reads")
)
def enter_Signal(self, node: "SignalNode") -> None:
if node.get_property("field_reset"):
path = node.get_path()
self.ds.in_hier_signal_paths.add(path)
def enter_Field(self, node: "FieldNode") -> None:
if node.is_sw_writable and (node.msb < node.lsb):
self.ds.has_writable_msb0_fields = True
if node.get_property("paritycheck") and node.implements_storage:
self.ds.has_paritycheck = True
if node.get_property("reset") is None:
self.msg.warning(
f"Field '{node.inst_name}' includes parity check logic, but "
"its reset value was not defined. Will result in an undefined "
"value on the module's 'parity_error' output.",
self.top_node.inst.property_src_ref.get(
"paritycheck", self.top_node.inst.inst_src_ref
),
)

View File

@@ -1,292 +0,0 @@
from typing import TYPE_CHECKING, Optional, List
import textwrap
from collections import OrderedDict
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
from .identifier_filter import kw_filter as kwf
if TYPE_CHECKING:
from typing import Union
from systemrdl.node import AddrmapNode, RegfileNode, RegNode, FieldNode, Node, MemNode
class _StructBase:
def __init__(self) -> None:
self.children = [] # type: List[Union[str, _StructBase]]
def __str__(self) -> str:
s = '\n'.join((str(x) for x in self.children))
return textwrap.indent(s, " ")
class _AnonymousStruct(_StructBase):
def __init__(self, inst_name: str, array_dimensions: Optional[List[int]] = None):
super().__init__()
self.inst_name = inst_name
self.array_dimensions = array_dimensions
def __str__(self) -> str:
if self.array_dimensions:
suffix = "[" + "][".join((str(n) for n in self.array_dimensions)) + "]"
else:
suffix = ""
return (
"struct {\n"
+ super().__str__()
+ f"\n}} {self.inst_name}{suffix};"
)
class _TypedefStruct(_StructBase):
def __init__(self, type_name: str, inst_name: Optional[str] = None, array_dimensions: Optional[List[int]] = None, packed: bool = False):
super().__init__()
self.type_name = type_name
self.inst_name = inst_name
self.array_dimensions = array_dimensions
self.packed = packed
def __str__(self) -> str:
if self.packed:
return (
"typedef struct packed {\n"
+ super().__str__()
+ f"\n}} {self.type_name};"
)
else:
return (
"typedef struct {\n"
+ super().__str__()
+ f"\n}} {self.type_name};"
)
@property
def instantiation(self) -> str:
if self.array_dimensions:
suffix = "[" + "][".join((str(n) for n in self.array_dimensions)) + "]"
else:
suffix = ""
return f"{self.type_name} {self.inst_name}{suffix};"
#-------------------------------------------------------------------------------
class StructGenerator:
def __init__(self) -> None:
self._struct_stack = [] # type: List[_StructBase]
@property
def current_struct(self) -> _StructBase:
return self._struct_stack[-1]
def push_struct(self, inst_name: str, array_dimensions: Optional[List[int]] = None) -> None:
s = _AnonymousStruct(inst_name, array_dimensions)
self._struct_stack.append(s)
def add_member(
self,
name: str,
width: int = 1,
array_dimensions: Optional[List[int]] = None,
*,
lsb: int = 0,
signed: bool = False,
) -> None:
if array_dimensions:
suffix = "[" + "][".join((str(n) for n in array_dimensions)) + "]"
else:
suffix = ""
if signed:
sign = "signed "
else:
# the default 'logic' type is unsigned per SV LRM 6.11.3
sign = ""
if width == 1 and lsb == 0:
m = f"logic {sign}{name}{suffix};"
else:
m = f"logic {sign}[{lsb+width-1}:{lsb}] {name}{suffix};"
self.current_struct.children.append(m)
def pop_struct(self) -> None:
s = self._struct_stack.pop()
if s.children:
# struct is not empty. Attach it to the parent
self.current_struct.children.append(s)
def start(self, type_name: str) -> None:
assert not self._struct_stack
s = _TypedefStruct(type_name)
self._struct_stack.append(s)
def finish(self) -> Optional[str]:
s = self._struct_stack.pop()
assert not self._struct_stack
if not s.children:
return None
return str(s)
class RDLStructGenerator(StructGenerator, RDLListener):
"""
Struct generator that naively translates an RDL node tree into a single
struct typedef containing nested anonymous structs
This can be extended to add more intelligent behavior
"""
def get_struct(self, node: 'Node', type_name: str) -> Optional[str]:
self.start(type_name)
walker = RDLWalker()
walker.walk(node, self, skip_top=True)
return self.finish()
def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
self.push_struct(kwf(node.inst_name), node.array_dimensions)
return WalkerAction.Continue
def exit_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
self.pop_struct()
return WalkerAction.Continue
def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
self.push_struct(kwf(node.inst_name), node.array_dimensions)
return WalkerAction.Continue
def exit_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
self.pop_struct()
return WalkerAction.Continue
def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
self.push_struct(kwf(node.inst_name), node.array_dimensions)
return WalkerAction.Continue
def exit_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
self.pop_struct()
return WalkerAction.Continue
def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
self.push_struct(kwf(node.inst_name), node.array_dimensions)
return WalkerAction.Continue
def exit_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
self.pop_struct()
return WalkerAction.Continue
def enter_Field(self, node: 'FieldNode') -> Optional[WalkerAction]:
self.add_member(kwf(node.inst_name), node.width)
return WalkerAction.Continue
#-------------------------------------------------------------------------------
class FlatStructGenerator(StructGenerator):
def __init__(self) -> None:
super().__init__()
self.typedefs = OrderedDict() # type: OrderedDict[str, _TypedefStruct]
def push_struct(self, type_name: str, inst_name: str, array_dimensions: Optional[List[int]] = None, packed = False) -> None: # type: ignore # pylint: disable=arguments-renamed
s = _TypedefStruct(type_name, inst_name, array_dimensions, packed)
self._struct_stack.append(s)
def pop_struct(self) -> None:
s = self._struct_stack.pop()
assert isinstance(s, _TypedefStruct)
if s.children:
# struct is not empty. Attach it to the parent
self.current_struct.children.append(s.instantiation)
# Add to collection of struct definitions
if s.type_name not in self.typedefs:
self.typedefs[s.type_name] = s
def finish(self) -> Optional[str]:
s = self._struct_stack.pop()
assert isinstance(s, _TypedefStruct)
assert not self._struct_stack
# no children, no struct.
if not s.children:
return None
# Add to collection of struct definitions
if s.type_name not in self.typedefs:
self.typedefs[s.type_name] = s
all_structs = [str(s) for s in self.typedefs.values()]
return "\n\n".join(all_structs)
class RDLFlatStructGenerator(FlatStructGenerator, RDLListener):
"""
Struct generator that naively translates an RDL node tree into a flat list
of typedefs
This can be extended to add more intelligent behavior
"""
def get_typdef_name(self, node:'Node') -> str:
raise NotImplementedError
def get_struct(self, node: 'Node', type_name: str) -> Optional[str]:
self.start(type_name)
walker = RDLWalker()
walker.walk(node, self, skip_top=True)
return self.finish()
def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
type_name = self.get_typdef_name(node)
self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions)
return WalkerAction.Continue
def exit_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
self.pop_struct()
return WalkerAction.Continue
def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
type_name = self.get_typdef_name(node)
self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions)
return WalkerAction.Continue
def exit_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
self.pop_struct()
return WalkerAction.Continue
def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
type_name = self.get_typdef_name(node)
self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions)
return WalkerAction.Continue
def exit_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
self.pop_struct()
return WalkerAction.Continue
def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
type_name = self.get_typdef_name(node)
self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions)
return WalkerAction.Continue
def exit_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
self.pop_struct()
return WalkerAction.Continue
def enter_Field(self, node: 'FieldNode') -> Optional[WalkerAction]:
self.add_member(kwf(node.inst_name), node.width)
return WalkerAction.Continue

View File

@@ -1,81 +0,0 @@
from typing import TYPE_CHECKING, Union
from systemrdl.node import AddrmapNode, RegNode, FieldNode, SignalNode
from .storage_generator import WBufStorageStructGenerator
from .implementation_generator import WBufLogicGenerator
from ..utils import get_indexed_path
from ..sv_int import SVInt
if TYPE_CHECKING:
from ..exporter import BusDecoderExporter
class WriteBuffering:
def __init__(self, exp: "BusDecoderExporter"):
self.exp = exp
@property
def top_node(self) -> "AddrmapNode":
return self.exp.ds.top_node
def get_storage_struct(self) -> str:
struct_gen = WBufStorageStructGenerator(self)
s = struct_gen.get_struct(self.top_node, "wbuf_storage_t")
assert s is not None
return s + "\nwbuf_storage_t wbuf_storage;"
def get_implementation(self) -> str:
gen = WBufLogicGenerator(self)
s = gen.get_content(self.top_node)
assert s is not None
return s
def get_wbuf_prefix(self, node: Union[RegNode, FieldNode]) -> str:
if isinstance(node, FieldNode):
node = node.parent
wbuf_prefix = "wbuf_storage." + get_indexed_path(self.top_node, node)
return wbuf_prefix
def get_write_strobe(self, node: Union[RegNode, FieldNode]) -> str:
prefix = self.get_wbuf_prefix(node)
return f"{prefix}.pending && {self.get_trigger(node)}"
def get_raw_trigger(self, node: "RegNode") -> Union[SVInt, str]:
trigger = node.get_property("wbuffer_trigger")
if isinstance(trigger, RegNode):
# Trigger is a register.
# trigger when uppermost address of the register is written
regwidth = trigger.get_property("regwidth")
accesswidth = trigger.get_property("accesswidth")
strb_prefix = self.exp.dereferencer.get_access_strobe(
trigger, reduce_substrobes=False
)
if accesswidth < regwidth:
n_subwords = regwidth // accesswidth
return f"{strb_prefix}[{n_subwords - 1}] && decoded_req_is_wr"
else:
return f"{strb_prefix} && decoded_req_is_wr"
elif isinstance(trigger, SignalNode):
s = self.exp.dereferencer.get_value(trigger)
if trigger.get_property("activehigh"):
return s
else:
return f"~{s}"
else:
# Trigger is a field or propref bit
return self.exp.dereferencer.get_value(trigger)
def get_trigger(self, node: Union[RegNode, FieldNode]) -> Union[SVInt, str]:
if isinstance(node, FieldNode):
node = node.parent
trigger = node.get_property("wbuffer_trigger")
if isinstance(trigger, RegNode) and trigger == node:
# register is its own trigger
# use the delayed trigger signal
return self.get_wbuf_prefix(node) + ".trigger_q"
else:
return self.get_raw_trigger(node)

View File

@@ -1,59 +0,0 @@
from typing import TYPE_CHECKING
from collections import namedtuple
from systemrdl.component import Reg
from systemrdl.node import RegNode
from ..forloop_generator import RDLForLoopGenerator
if TYPE_CHECKING:
from . import WriteBuffering
class WBufLogicGenerator(RDLForLoopGenerator):
i_type = "genvar"
def __init__(self, wbuf: 'WriteBuffering') -> None:
super().__init__()
self.wbuf = wbuf
self.exp = wbuf.exp
self.template = self.exp.jj_env.get_template(
"write_buffering/template.sv"
)
def enter_Reg(self, node: 'RegNode') -> None:
super().enter_Reg(node)
assert isinstance(node.inst, Reg)
if not node.get_property('buffer_writes'):
return
regwidth = node.get_property('regwidth')
accesswidth = node.get_property('accesswidth')
strb_prefix = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False)
Segment = namedtuple("Segment", ["strobe", "bslice"])
segments = []
if accesswidth < regwidth:
n_subwords = regwidth // accesswidth
for i in range(n_subwords):
strobe = strb_prefix + f"[{i}]"
if node.inst.is_msb0_order:
bslice = f"[{regwidth - (accesswidth * i) - 1}: {regwidth - (accesswidth * (i+1))}]"
else:
bslice = f"[{(accesswidth * (i + 1)) - 1}:{accesswidth * i}]"
segments.append(Segment(strobe, bslice))
else:
segments.append(Segment(strb_prefix, ""))
trigger = node.get_property('wbuffer_trigger')
is_own_trigger = (isinstance(trigger, RegNode) and trigger == node)
context = {
'wbuf': self.wbuf,
'wbuf_prefix': self.wbuf.get_wbuf_prefix(node),
'segments': segments,
'node': node,
'cpuif': self.exp.cpuif,
'get_resetsignal': self.exp.dereferencer.get_resetsignal,
'get_always_ff_event': self.exp.dereferencer.get_always_ff_event,
'is_own_trigger': is_own_trigger,
}
self.add_content(self.template.render(context))

View File

@@ -1,32 +0,0 @@
from typing import TYPE_CHECKING
from systemrdl.node import FieldNode, RegNode
from ..struct_generator import RDLStructGenerator
if TYPE_CHECKING:
from . import WriteBuffering
class WBufStorageStructGenerator(RDLStructGenerator):
def __init__(self, wbuf: 'WriteBuffering') -> None:
super().__init__()
self.wbuf = wbuf
def enter_Field(self, node: FieldNode) -> None:
# suppress parent class's field behavior
pass
def enter_Reg(self, node: RegNode) -> None:
super().enter_Reg(node)
if not node.get_property('buffer_writes'):
return
regwidth = node.get_property('regwidth')
self.add_member("data", regwidth)
self.add_member("biten", regwidth)
self.add_member("pending")
trigger = node.get_property('wbuffer_trigger')
if isinstance(trigger, RegNode) and trigger == node:
self.add_member("trigger_q")

View File

@@ -1,31 +0,0 @@
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
{{wbuf_prefix}}.pending <= '0;
{{wbuf_prefix}}.data <= '0;
{{wbuf_prefix}}.biten <= '0;
{%- if is_own_trigger %}
{{wbuf_prefix}}.trigger_q <= '0;
{%- endif %}
end else begin
if({{wbuf.get_trigger(node)}}) begin
{{wbuf_prefix}}.pending <= '0;
{{wbuf_prefix}}.data <= '0;
{{wbuf_prefix}}.biten <= '0;
end
{%- for segment in segments %}
if({{segment.strobe}} && decoded_req_is_wr) begin
{{wbuf_prefix}}.pending <= '1;
{%- if node.inst.is_msb0_order %}
{{wbuf_prefix}}.data{{segment.bslice}} <= ({{wbuf_prefix}}.data{{segment.bslice}} & ~decoded_wr_biten_bswap) | (decoded_wr_data_bswap & decoded_wr_biten_bswap);
{{wbuf_prefix}}.biten{{segment.bslice}} <= {{wbuf_prefix}}.biten{{segment.bslice}} | decoded_wr_biten_bswap;
{%- else %}
{{wbuf_prefix}}.data{{segment.bslice}} <= ({{wbuf_prefix}}.data{{segment.bslice}} & ~decoded_wr_biten) | (decoded_wr_data & decoded_wr_biten);
{{wbuf_prefix}}.biten{{segment.bslice}} <= {{wbuf_prefix}}.biten{{segment.bslice}} | decoded_wr_biten;
{%- endif %}
end
{%- endfor %}
{%- if is_own_trigger %}
{{wbuf_prefix}}.trigger_q <= {{wbuf.get_raw_trigger(node)}};
{%- endif %}
end
end

View File

@@ -4,7 +4,7 @@ import inspect
import jinja2 as jj
from peakrdl_busdecoder.cpuif.base import CpuifBase
from peakrdl_busdecoder.cpuif.base_cpuif import CpuifBase
from ..sv_line_anchor import SVLineAnchor

1255
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff