"updt"
This commit is contained in:
22
.github/workflows/build.yml
vendored
22
.github/workflows/build.yml
vendored
@@ -19,35 +19,29 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version:
|
python-version:
|
||||||
- "3.7"
|
|
||||||
- "3.8"
|
|
||||||
- "3.9"
|
|
||||||
- "3.10"
|
- "3.10"
|
||||||
- "3.11"
|
- "3.11"
|
||||||
- "3.12"
|
- "3.12"
|
||||||
|
- "3.13"
|
||||||
|
- "3.14"
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- 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 }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install UV
|
||||||
|
uses: astral-sh/setup-uv@v6
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4
|
run: |
|
||||||
with:
|
uv venv -p ${{ matrix.python-version }} .venv
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install -r tests/requirements.txt
|
uv sync --group test
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "peakrdl-busdecoder"
|
name = "peakrdl-busdecoder"
|
||||||
dynamic = ["version"]
|
version = "0.1.0"
|
||||||
requires-python = ">=3.7"
|
requires-python = ">=3.10"
|
||||||
dependencies = ["systemrdl-compiler ~= 1.29", "Jinja2>=2.11"]
|
dependencies = [
|
||||||
|
"jinja2>=3.1.6",
|
||||||
|
"systemrdl-compiler~=1.30.1",
|
||||||
|
]
|
||||||
|
|
||||||
authors = [{ name = "Alex Mykyta" }]
|
authors = [{ name = "Alex Mykyta" }]
|
||||||
description = "Compile SystemRDL into a SystemVerilog control/status register (CSR) block"
|
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"
|
Changelog = "https://github.com/SystemRDL/PeakRDL-busdecoder/releases"
|
||||||
Documentation = "https://peakrdl-busdecoder.readthedocs.io/"
|
Documentation = "https://peakrdl-busdecoder.readthedocs.io/"
|
||||||
|
|
||||||
[tool.setuptools.dynamic]
|
[dependency-groups]
|
||||||
version = { attr = "peakrdl_busdecoder.__about__.__version__" }
|
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"]
|
[project.entry-points."peakrdl.exporters"]
|
||||||
busdecoder = "peakrdl_busdecoder.__peakrdl__:Exporter"
|
busdecoder = "peakrdl_busdecoder.__peakrdl__:Exporter"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
from .__about__ import __version__
|
|
||||||
|
|
||||||
from .exporter import BusDecoderExporter
|
from .exporter import BusDecoderExporter
|
||||||
|
|
||||||
|
__all__ = ["BusDecoderExporter"]
|
||||||
120
src/peakrdl_busdecoder/__peakrdl__.py
Normal file
120
src/peakrdl_busdecoder/__peakrdl__.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from peakrdl.plugins.exporter import ExporterSubcommandPlugin
|
||||||
|
from peakrdl.config import schema
|
||||||
|
from peakrdl.plugins.entry_points import get_entry_points
|
||||||
|
|
||||||
|
from .exporter import BusDecoderExporter
|
||||||
|
from .cpuif import BaseCpuif, apb3, apb4
|
||||||
|
from .udps import ALL_UDPS
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import argparse
|
||||||
|
from systemrdl.node import AddrmapNode
|
||||||
|
|
||||||
|
|
||||||
|
class Exporter(ExporterSubcommandPlugin):
|
||||||
|
short_desc = "Generate a SystemVerilog control/status register (CSR) block"
|
||||||
|
|
||||||
|
udp_definitions = ALL_UDPS
|
||||||
|
|
||||||
|
cfg_schema = {
|
||||||
|
"cpuifs": {"*": schema.PythonObjectImport()},
|
||||||
|
}
|
||||||
|
|
||||||
|
@functools.lru_cache()
|
||||||
|
def get_cpuifs(self) -> dict[str, type[BaseCpuif]]:
|
||||||
|
# All built-in CPUIFs
|
||||||
|
cpuifs: dict[str, type[BaseCpuif]] = {
|
||||||
|
# "passthrough": passthrough.PassthroughCpuif,
|
||||||
|
"apb3": apb3.APB3Cpuif,
|
||||||
|
"apb3-flat": apb3.APB3CpuifFlat,
|
||||||
|
"apb4": apb4.APB4Cpuif,
|
||||||
|
"apb4-flat": apb4.APB4CpuifFlat,
|
||||||
|
# "axi4-lite": axi4lite.AXI4Lite_Cpuif,
|
||||||
|
# "axi4-lite-flat": axi4lite.AXI4Lite_Cpuif_flattened,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load any cpuifs specified via entry points
|
||||||
|
for ep, _ in get_entry_points("peakrdl_busdecoder.cpuif"):
|
||||||
|
name = ep.name
|
||||||
|
cpuif = ep.load()
|
||||||
|
if name in cpuifs:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it already exists"
|
||||||
|
)
|
||||||
|
if not issubclass(cpuif, BaseCpuif):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it not a BaseCpuif class"
|
||||||
|
)
|
||||||
|
cpuifs[name] = cpuif
|
||||||
|
|
||||||
|
# Load any CPUIFs via config import
|
||||||
|
for name, cpuif in self.cfg["cpuifs"].items():
|
||||||
|
if name in cpuifs:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it already exists"
|
||||||
|
)
|
||||||
|
if not issubclass(cpuif, BaseCpuif):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it not a BaseCpuif class"
|
||||||
|
)
|
||||||
|
cpuifs[name] = cpuif
|
||||||
|
|
||||||
|
return cpuifs
|
||||||
|
|
||||||
|
def add_exporter_arguments(self, arg_group: "argparse._ActionsContainer") -> None:
|
||||||
|
cpuifs = self.get_cpuifs()
|
||||||
|
|
||||||
|
arg_group.add_argument(
|
||||||
|
"--cpuif",
|
||||||
|
choices=cpuifs.keys(),
|
||||||
|
default="apb3",
|
||||||
|
help="Select the CPU interface protocol to use [apb3]",
|
||||||
|
)
|
||||||
|
|
||||||
|
arg_group.add_argument(
|
||||||
|
"--module-name",
|
||||||
|
metavar="NAME",
|
||||||
|
default=None,
|
||||||
|
help="Override the SystemVerilog module name",
|
||||||
|
)
|
||||||
|
|
||||||
|
arg_group.add_argument(
|
||||||
|
"--package-name",
|
||||||
|
metavar="NAME",
|
||||||
|
default=None,
|
||||||
|
help="Override the SystemVerilog package name",
|
||||||
|
)
|
||||||
|
|
||||||
|
arg_group.add_argument(
|
||||||
|
"--addr-width",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="""Override the CPU interface's address width. By default,
|
||||||
|
address width is sized to the contents of the busdecoder.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
arg_group.add_argument(
|
||||||
|
"--unroll",
|
||||||
|
action="store_true",
|
||||||
|
help="""Unroll arrayed addressable nodes into separate instances in
|
||||||
|
the CPU interface. By default, arrayed nodes are kept as arrays.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def do_export(self, top_node: "AddrmapNode", options: "argparse.Namespace") -> None:
|
||||||
|
cpuifs = self.get_cpuifs()
|
||||||
|
|
||||||
|
x = BusDecoderExporter()
|
||||||
|
x.export(
|
||||||
|
top_node,
|
||||||
|
options.output,
|
||||||
|
cpuif_cls=cpuifs[options.cpuif],
|
||||||
|
module_name=options.module_name,
|
||||||
|
package_name=options.package_name,
|
||||||
|
address_width=options.addr_width,
|
||||||
|
unroll=options.unroll,
|
||||||
|
)
|
||||||
@@ -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.node import FieldNode, RegNode
|
||||||
from systemrdl.walker import WalkerAction
|
from systemrdl.walker import WalkerAction
|
||||||
|
|
||||||
from .utils import get_indexed_path
|
from .utils import get_indexed_path
|
||||||
from .struct_generator import RDLStructGenerator
|
|
||||||
from .forloop_generator import RDLForLoopGenerator
|
from .forloop_generator import RDLForLoopGenerator
|
||||||
from .identifier_filter import kw_filter as kwf
|
|
||||||
from .sv_int import SVInt
|
from .sv_int import SVInt
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .exporter import BusDecoderExporter
|
from .exporter import BusDecoderExporter
|
||||||
from systemrdl.node import AddrmapNode, AddressableNode
|
from systemrdl.node import AddrmapNode, AddressableNode
|
||||||
from systemrdl.node import RegfileNode, MemNode
|
|
||||||
|
|
||||||
|
|
||||||
class AddressDecode:
|
class AddressDecode:
|
||||||
@@ -23,12 +20,6 @@ class AddressDecode:
|
|||||||
def top_node(self) -> "AddrmapNode":
|
def top_node(self) -> "AddrmapNode":
|
||||||
return self.exp.ds.top_node
|
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:
|
def get_implementation(self) -> str:
|
||||||
gen = DecodeLogicGenerator(self)
|
gen = DecodeLogicGenerator(self)
|
||||||
s = gen.get_content(self.top_node)
|
s = gen.get_content(self.top_node)
|
||||||
@@ -36,7 +27,7 @@ class AddressDecode:
|
|||||||
return s
|
return s
|
||||||
|
|
||||||
def get_access_strobe(
|
def get_access_strobe(
|
||||||
self, node: Union[RegNode, FieldNode], reduce_substrobes: bool = True
|
self, node: RegNode | FieldNode, reduce_substrobes: bool = True
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Returns the Verilog string that represents the register/field's access strobe.
|
Returns the Verilog string that represents the register/field's access strobe.
|
||||||
@@ -73,77 +64,24 @@ class AddressDecode:
|
|||||||
return "decoded_reg_strb." + path
|
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):
|
class DecodeLogicGenerator(RDLForLoopGenerator):
|
||||||
def __init__(self, addr_decode: AddressDecode) -> None:
|
def __init__(self, addr_decode: AddressDecode) -> None:
|
||||||
self.addr_decode = addr_decode
|
self.addr_decode = addr_decode
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
# List of address strides for each dimension
|
# List of address strides for each dimension
|
||||||
self._array_stride_stack = [] # type: List[int]
|
self._array_stride_stack: list[int] = []
|
||||||
|
|
||||||
def enter_AddressableComponent(
|
def enter_AddressableComponent(
|
||||||
self, node: "AddressableNode"
|
self, node: "AddressableNode"
|
||||||
) -> Optional[WalkerAction]:
|
) -> WalkerAction | None:
|
||||||
super().enter_AddressableComponent(node)
|
super().enter_AddressableComponent(node)
|
||||||
|
|
||||||
if node.array_dimensions:
|
if node.array_dimensions:
|
||||||
assert node.array_stride is not None
|
assert node.array_stride is not None
|
||||||
# Collect strides for each array dimension
|
# Collect strides for each array dimension
|
||||||
current_stride = node.array_stride
|
current_stride = node.array_stride
|
||||||
strides = []
|
strides: list[int] = []
|
||||||
for dim in reversed(node.array_dimensions):
|
for dim in reversed(node.array_dimensions):
|
||||||
strides.append(current_stride)
|
strides.append(current_stride)
|
||||||
current_stride *= dim
|
current_stride *= dim
|
||||||
3
src/peakrdl_busdecoder/cpuif/__init__.py
Normal file
3
src/peakrdl_busdecoder/cpuif/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .base_cpuif import BaseCpuif
|
||||||
|
|
||||||
|
__all__ = ["BaseCpuif"]
|
||||||
4
src/peakrdl_busdecoder/cpuif/apb3/__init__.py
Normal file
4
src/peakrdl_busdecoder/cpuif/apb3/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from .apb3_cpuif import APB3Cpuif
|
||||||
|
from .apb3_cpuif_flat import APB3CpuifFlat
|
||||||
|
|
||||||
|
__all__ = ["APB3Cpuif", "APB3CpuifFlat"]
|
||||||
79
src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py
Normal file
79
src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
from systemrdl.node import AddressableNode
|
||||||
|
from ..base_cpuif import BaseCpuif
|
||||||
|
|
||||||
|
|
||||||
|
class APB3Cpuif(BaseCpuif):
|
||||||
|
template_path = "apb3_tmpl.sv"
|
||||||
|
is_interface = True
|
||||||
|
|
||||||
|
def _port_declaration(self, child: AddressableNode) -> str:
|
||||||
|
base = f"apb3_intf.master m_apb_{child.inst_name}"
|
||||||
|
if not child.is_array:
|
||||||
|
return base
|
||||||
|
if child.current_idx is not None:
|
||||||
|
return f"{base}_{'_'.join(map(str, child.current_idx))} [N_{child.inst_name.upper()}S]"
|
||||||
|
return f"{base} [N_{child.inst_name.upper()}S]"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port_declaration(self) -> str:
|
||||||
|
slave_ports: list[str] = ["apb3_intf.slave s_apb"]
|
||||||
|
master_ports: list[str] = list(
|
||||||
|
map(self._port_declaration, self.addressable_children)
|
||||||
|
)
|
||||||
|
|
||||||
|
return ",\n".join(slave_ports + master_ports)
|
||||||
|
|
||||||
|
def signal(
|
||||||
|
self,
|
||||||
|
signal: str,
|
||||||
|
node: AddressableNode | None = None,
|
||||||
|
idx: str | int | None = None,
|
||||||
|
) -> str:
|
||||||
|
if node is None:
|
||||||
|
# Node is none, so this is a slave signal
|
||||||
|
return f"s_apb.{signal}"
|
||||||
|
|
||||||
|
# Master signal
|
||||||
|
base = f"m_apb_{node.inst_name}"
|
||||||
|
if not node.is_array:
|
||||||
|
return f"{base}.{signal}"
|
||||||
|
if node.current_idx is not None:
|
||||||
|
# This is a specific instance of an array
|
||||||
|
return f"{base}_{'_'.join(map(str, node.current_idx))}.{signal}"
|
||||||
|
if idx is not None:
|
||||||
|
return f"{base}[{idx}].{signal}"
|
||||||
|
|
||||||
|
raise ValueError("Must provide an index for arrayed interface signals")
|
||||||
|
|
||||||
|
def get_address_predicate(self, node: AddressableNode) -> str:
|
||||||
|
"""
|
||||||
|
Returns a SystemVerilog expression that evaluates to true when the
|
||||||
|
address on the bus matches the address range of the given node.
|
||||||
|
"""
|
||||||
|
|
||||||
|
addr_mask = (1 << self.addr_width) - 1
|
||||||
|
addr = node.absolute_address & addr_mask
|
||||||
|
size = node.size
|
||||||
|
if size == 0:
|
||||||
|
raise ValueError("Node size must be greater than 0")
|
||||||
|
if (addr % size) != 0:
|
||||||
|
raise ValueError("Node address must be aligned to its size")
|
||||||
|
|
||||||
|
# Calculate the address range of the node
|
||||||
|
addr_start = addr
|
||||||
|
addr_end = addr + size - 1
|
||||||
|
if addr_end > addr_mask:
|
||||||
|
raise ValueError("Node address range exceeds address width")
|
||||||
|
|
||||||
|
if addr_start == addr_end:
|
||||||
|
return f"({self.signal('PADDR')} == 'h{addr_start:X})"
|
||||||
|
|
||||||
|
return f"({self.signal('PADDR')} >= 'h{addr_start:X} && {self.signal('PADDR')} <= 'h{addr_end:X})"
|
||||||
|
|
||||||
|
def get_address_decode_condition(self, node: AddressableNode) -> str:
|
||||||
|
"""
|
||||||
|
Returns a SystemVerilog expression that evaluates to true when the
|
||||||
|
address on the bus matches the address range of the given node.
|
||||||
|
"""
|
||||||
|
addr_pred = self.get_address_predicate(node)
|
||||||
|
return addr_pred
|
||||||
62
src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py
Normal file
62
src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
from systemrdl.node import AddressableNode
|
||||||
|
from ..base_cpuif import BaseCpuif
|
||||||
|
|
||||||
|
|
||||||
|
class APB3CpuifFlat(BaseCpuif):
|
||||||
|
template_path = "apb3_tmpl.sv"
|
||||||
|
is_interface = False
|
||||||
|
|
||||||
|
def _port_declaration(self, child: AddressableNode) -> list[str]:
|
||||||
|
return [
|
||||||
|
f"input logic {self.signal('PCLK', child)}",
|
||||||
|
f"input logic {self.signal('PRESETn', child)}",
|
||||||
|
f"input logic {self.signal('PSELx', child)}",
|
||||||
|
f"input logic {self.signal('PENABLE', child)}",
|
||||||
|
f"input logic {self.signal('PWRITE', child)}",
|
||||||
|
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR', child)}",
|
||||||
|
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA', child)}",
|
||||||
|
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA', child)}",
|
||||||
|
f"output logic {self.signal('PREADY', child)}",
|
||||||
|
f"output logic {self.signal('PSLVERR', child)}",
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port_declaration(self) -> str:
|
||||||
|
slave_ports: list[str] = [
|
||||||
|
f"input logic {self.signal('PCLK')}",
|
||||||
|
f"input logic {self.signal('PRESETn')}",
|
||||||
|
f"input logic {self.signal('PSELx')}",
|
||||||
|
f"input logic {self.signal('PENABLE')}",
|
||||||
|
f"input logic {self.signal('PWRITE')}",
|
||||||
|
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR')}",
|
||||||
|
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA')}",
|
||||||
|
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA')}",
|
||||||
|
f"output logic {self.signal('PREADY')}",
|
||||||
|
f"output logic {self.signal('PSLVERR')}",
|
||||||
|
]
|
||||||
|
master_ports: list[str] = []
|
||||||
|
for child in self.addressable_children:
|
||||||
|
master_ports.extend(self._port_declaration(child))
|
||||||
|
|
||||||
|
return ",\n".join(slave_ports + master_ports)
|
||||||
|
|
||||||
|
def signal(
|
||||||
|
self,
|
||||||
|
signal: str,
|
||||||
|
node: AddressableNode | None = None,
|
||||||
|
idx: str | int | None = None,
|
||||||
|
) -> str:
|
||||||
|
if node is None:
|
||||||
|
# Node is none, so this is a slave signal
|
||||||
|
return f"s_apb_{signal}"
|
||||||
|
|
||||||
|
# Master signal
|
||||||
|
base = f"m_apb_{node.inst_name}"
|
||||||
|
if not node.is_array:
|
||||||
|
return f"{base}_{signal}"
|
||||||
|
if node.current_idx is not None:
|
||||||
|
# This is a specific instance of an array
|
||||||
|
return f"{base}_{signal}_{'_'.join(map(str, node.current_idx))}"
|
||||||
|
if idx is not None:
|
||||||
|
return f"{base}_{signal}[{idx}]"
|
||||||
|
return f"{base}_{signal}[N_{node.inst_name.upper()}S]"
|
||||||
115
src/peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv
Normal file
115
src/peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
{%- if cpuif.is_interface -%}
|
||||||
|
`ifndef SYNTHESIS
|
||||||
|
initial begin
|
||||||
|
assert_bad_addr_width: assert($bits({{cpuif.signal("PADDR")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH)
|
||||||
|
else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("PADDR")}}), {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH);
|
||||||
|
assert_bad_data_width: assert($bits({{cpuif.signal("PWDATA")}}) == {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH)
|
||||||
|
else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("PWDATA")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH);
|
||||||
|
end
|
||||||
|
`endif
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
//======================================================
|
||||||
|
// APB Fanout
|
||||||
|
//======================================================
|
||||||
|
{%- for child in cpuif.addressable_children -%}
|
||||||
|
{%- if child is array -%}
|
||||||
|
for (genvar g_{{child.inst_name|lower}}_idx = 0; g_{{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; g_{{child.inst_name|lower}}_idx++) begin : g_passthrough_{{child.inst_name|lower}}
|
||||||
|
assign {{self.signal("PCLK", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PCLK")}};
|
||||||
|
assign {{self.signal("PRESETn", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PRESETn")}};
|
||||||
|
assign {{self.signal("PENABLE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PENABLE")}};
|
||||||
|
assign {{self.signal("PWRITE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWRITE")}};
|
||||||
|
assign {{self.signal("PADDR", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing
|
||||||
|
assign {{self.signal("PWDATA", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWDATA")}};
|
||||||
|
end
|
||||||
|
{%- else -%}
|
||||||
|
assign {{self.signal("PCLK", child)}} = {{self.signal("PCLK")}};
|
||||||
|
assign {{self.signal("PRESETn", child)}} = {{self.signal("PRESETn")}};
|
||||||
|
assign {{self.signal("PENABLE", child)}} = {{self.signal("PENABLE")}};
|
||||||
|
assign {{self.signal("PWRITE", child)}} = {{self.signal("PWRITE")}};
|
||||||
|
assign {{self.signal("PADDR", child)}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing
|
||||||
|
assign {{self.signal("PWDATA", child)}} = {{self.signal("PWDATA")}};
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
|
||||||
|
//======================================================
|
||||||
|
// Address Decode Logic
|
||||||
|
//======================================================
|
||||||
|
always_comb begin
|
||||||
|
// Default all PSELx signals to 0
|
||||||
|
{%- for child in cpuif.addressable_children -%}
|
||||||
|
{%- if child is array -%}
|
||||||
|
for (int {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
|
||||||
|
{{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b0;
|
||||||
|
end
|
||||||
|
{%- else -%}
|
||||||
|
{{self.signal("PSELx", child)}} = 1'b0;
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
|
||||||
|
if ({{self.signal("PSELx")}}) begin
|
||||||
|
{%- for child in cpuif.addressable_children -%}
|
||||||
|
{%- if loop.first -%}
|
||||||
|
if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||||
|
{%- else -%}
|
||||||
|
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||||
|
{%- endif -%}
|
||||||
|
// Address matched for {{child.inst_name}}
|
||||||
|
{%- if child is array -%}
|
||||||
|
for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
|
||||||
|
{{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b1;
|
||||||
|
end
|
||||||
|
{%- else -%}
|
||||||
|
{{self.signal("PSELx", child)}} = 1'b1;
|
||||||
|
{%- endif -%}
|
||||||
|
{%- if loop.last -%}
|
||||||
|
end else begin
|
||||||
|
// No address matched
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
// PSELx is low, nothing to do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
//======================================================
|
||||||
|
// Read Data Mux
|
||||||
|
//======================================================
|
||||||
|
always_comb begin
|
||||||
|
// Default read data to 0
|
||||||
|
{{self.signal("PRDATA")}} = '0;
|
||||||
|
{{self.signal("PREADY")}} = 1'b1;
|
||||||
|
{{self.signal("PSLVERR")}} = 1'b0;
|
||||||
|
|
||||||
|
if ({{self.signal("PSELx")}} && !{{self.signal("PWRITE")}} && {{self.signal("PENABLE")}}) begin
|
||||||
|
{%- for child in cpuif.addressable_children -%}
|
||||||
|
{%- if loop.first -%}
|
||||||
|
if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||||
|
{%- else -%}
|
||||||
|
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||||
|
{%- endif -%}
|
||||||
|
// Address matched for {{child.inst_name}}
|
||||||
|
{%- if child is array -%}
|
||||||
|
for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
|
||||||
|
{{self.signal("PRDATA")}} = {{self.signal("PRDATA", child, f"{child.inst_name.lower()}_idx")}};
|
||||||
|
{{self.signal("PREADY")}} = {{self.signal("PREADY", child, f"{child.inst_name.lower()}_idx")}};
|
||||||
|
{{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child, f"{child.inst_name.lower()}_idx")}};
|
||||||
|
end
|
||||||
|
{%- else -%}
|
||||||
|
{{self.signal("PRDATA")}} = {{self.signal("PRDATA", child)}};
|
||||||
|
{{self.signal("PREADY")}} = {{self.signal("PREADY", child)}};
|
||||||
|
{{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child)}};
|
||||||
|
{%- endif -%}
|
||||||
|
{%- if loop.last -%}
|
||||||
|
end else begin
|
||||||
|
// No address matched
|
||||||
|
{{self.signal("PRDATA")}} = {'hdeadbeef}[{{ds.data_width - 1}}:0]; // Indicate error on no match
|
||||||
|
{{self.signal("PSLVERR")}} = 1'b1; // Indicate error on no match
|
||||||
|
end
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
end else begin
|
||||||
|
// Not a read transfer, nothing to do
|
||||||
|
end
|
||||||
|
end
|
||||||
4
src/peakrdl_busdecoder/cpuif/apb4/__init__.py
Normal file
4
src/peakrdl_busdecoder/cpuif/apb4/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from .apb4_cpuif import APB4Cpuif
|
||||||
|
from .apb4_cpuif_flat import APB4CpuifFlat
|
||||||
|
|
||||||
|
__all__ = ["APB4Cpuif", "APB4CpuifFlat"]
|
||||||
81
src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif.py
Normal file
81
src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
from systemrdl.node import AddressableNode
|
||||||
|
from ..base_cpuif import BaseCpuif
|
||||||
|
|
||||||
|
|
||||||
|
class APB4Cpuif(BaseCpuif):
|
||||||
|
template_path = "apb4_tmpl.sv"
|
||||||
|
is_interface = True
|
||||||
|
|
||||||
|
def _port_declaration(self, child: AddressableNode) -> str:
|
||||||
|
base = f"apb4_intf.master m_apb_{child.inst_name}"
|
||||||
|
if not child.is_array:
|
||||||
|
return base
|
||||||
|
if child.current_idx is not None:
|
||||||
|
return f"{base}_{'_'.join(map(str, child.current_idx))} [N_{child.inst_name.upper()}S]"
|
||||||
|
return f"{base} [N_{child.inst_name.upper()}S]"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port_declaration(self) -> str:
|
||||||
|
"""Returns the port declaration for the APB4 interface."""
|
||||||
|
slave_ports: list[str] = ["apb4_intf.slave s_apb"]
|
||||||
|
master_ports: list[str] = list(
|
||||||
|
map(self._port_declaration, self.addressable_children)
|
||||||
|
)
|
||||||
|
|
||||||
|
return ",\n".join(slave_ports + master_ports)
|
||||||
|
|
||||||
|
def signal(
|
||||||
|
self,
|
||||||
|
signal: str,
|
||||||
|
node: AddressableNode | None = None,
|
||||||
|
idx: str | int | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Returns the signal name for the given signal and node."""
|
||||||
|
if node is None:
|
||||||
|
# Node is none, so this is a slave signal
|
||||||
|
return f"s_apb.{signal}"
|
||||||
|
|
||||||
|
# Master signal
|
||||||
|
base = f"m_apb_{node.inst_name}"
|
||||||
|
if not node.is_array:
|
||||||
|
return f"{base}.{signal}"
|
||||||
|
if node.current_idx is not None:
|
||||||
|
# This is a specific instance of an array
|
||||||
|
return f"{base}_{'_'.join(map(str, node.current_idx))}.{signal}"
|
||||||
|
if idx is not None:
|
||||||
|
return f"{base}[{idx}].{signal}"
|
||||||
|
|
||||||
|
raise ValueError("Must provide an index for arrayed interface signals")
|
||||||
|
|
||||||
|
def get_address_predicate(self, node: AddressableNode) -> str:
|
||||||
|
"""
|
||||||
|
Returns a SystemVerilog expression that evaluates to true when the
|
||||||
|
address on the bus matches the address range of the given node.
|
||||||
|
"""
|
||||||
|
|
||||||
|
addr_mask = (1 << self.addr_width) - 1
|
||||||
|
addr = node.absolute_address & addr_mask
|
||||||
|
size = node.size
|
||||||
|
if size == 0:
|
||||||
|
raise ValueError("Node size must be greater than 0")
|
||||||
|
if (addr % size) != 0:
|
||||||
|
raise ValueError("Node address must be aligned to its size")
|
||||||
|
|
||||||
|
# Calculate the address range of the node
|
||||||
|
addr_start = addr
|
||||||
|
addr_end = addr + size - 1
|
||||||
|
if addr_end > addr_mask:
|
||||||
|
raise ValueError("Node address range exceeds address width")
|
||||||
|
|
||||||
|
if addr_start == addr_end:
|
||||||
|
return f"({self.signal('PADDR')} == 'h{addr_start:X})"
|
||||||
|
|
||||||
|
return f"({self.signal('PADDR')} >= 'h{addr_start:X} && {self.signal('PADDR')} <= 'h{addr_end:X})"
|
||||||
|
|
||||||
|
def get_address_decode_condition(self, node: AddressableNode) -> str:
|
||||||
|
"""
|
||||||
|
Returns a SystemVerilog expression that evaluates to true when the
|
||||||
|
address on the bus matches the address range of the given node.
|
||||||
|
"""
|
||||||
|
addr_pred = self.get_address_predicate(node)
|
||||||
|
return addr_pred
|
||||||
66
src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif_flat.py
Normal file
66
src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif_flat.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
from systemrdl.node import AddressableNode
|
||||||
|
from ..base_cpuif import BaseCpuif
|
||||||
|
|
||||||
|
|
||||||
|
class APB4CpuifFlat(BaseCpuif):
|
||||||
|
template_path = "apb4_tmpl.sv"
|
||||||
|
is_interface = False
|
||||||
|
|
||||||
|
def _port_declaration(self, child: AddressableNode) -> list[str]:
|
||||||
|
return [
|
||||||
|
f"input logic {self.signal('PCLK', child)}",
|
||||||
|
f"input logic {self.signal('PRESETn', child)}",
|
||||||
|
f"input logic {self.signal('PSELx', child)}",
|
||||||
|
f"input logic {self.signal('PENABLE', child)}",
|
||||||
|
f"input logic {self.signal('PWRITE', child)}",
|
||||||
|
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR', child)}",
|
||||||
|
f"input logic [2:0] {self.signal('PPROT', child)}",
|
||||||
|
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA', child)}",
|
||||||
|
f"input logic [{self.data_width // 8 - 1}:0] {self.signal('PSTRB', child)}",
|
||||||
|
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA', child)}",
|
||||||
|
f"output logic {self.signal('PREADY', child)}",
|
||||||
|
f"output logic {self.signal('PSLVERR', child)}",
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port_declaration(self) -> str:
|
||||||
|
slave_ports: list[str] = [
|
||||||
|
f"input logic {self.signal('PCLK')}",
|
||||||
|
f"input logic {self.signal('PRESETn')}",
|
||||||
|
f"input logic {self.signal('PSELx')}",
|
||||||
|
f"input logic {self.signal('PENABLE')}",
|
||||||
|
f"input logic {self.signal('PWRITE')}",
|
||||||
|
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR')}",
|
||||||
|
f"input logic [2:0] {self.signal('PPROT')}",
|
||||||
|
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA')}",
|
||||||
|
f"input logic [{self.data_width // 8 - 1}:0] {self.signal('PSTRB')}",
|
||||||
|
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA')}",
|
||||||
|
f"output logic {self.signal('PREADY')}",
|
||||||
|
f"output logic {self.signal('PSLVERR')}",
|
||||||
|
]
|
||||||
|
master_ports: list[str] = []
|
||||||
|
for child in self.addressable_children:
|
||||||
|
master_ports.extend(self._port_declaration(child))
|
||||||
|
|
||||||
|
return ",\n".join(slave_ports + master_ports)
|
||||||
|
|
||||||
|
def signal(
|
||||||
|
self,
|
||||||
|
signal: str,
|
||||||
|
node: AddressableNode | None = None,
|
||||||
|
idx: str | int | None = None,
|
||||||
|
) -> str:
|
||||||
|
if node is None:
|
||||||
|
# Node is none, so this is a slave signal
|
||||||
|
return f"s_apb_{signal}"
|
||||||
|
|
||||||
|
# Master signal
|
||||||
|
base = f"m_apb_{node.inst_name}"
|
||||||
|
if not node.is_array:
|
||||||
|
return f"{base}_{signal}"
|
||||||
|
if node.current_idx is not None:
|
||||||
|
# This is a specific instance of an array
|
||||||
|
return f"{base}_{signal}_{'_'.join(map(str, node.current_idx))}"
|
||||||
|
if idx is not None:
|
||||||
|
return f"{base}_{signal}[{idx}]"
|
||||||
|
return f"{base}_{signal}[N_{node.inst_name.upper()}S]"
|
||||||
119
src/peakrdl_busdecoder/cpuif/apb4/apb4_tmpl.sv
Normal file
119
src/peakrdl_busdecoder/cpuif/apb4/apb4_tmpl.sv
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
{%- if cpuif.is_interface -%}
|
||||||
|
`ifndef SYNTHESIS
|
||||||
|
initial begin
|
||||||
|
assert_bad_addr_width: assert($bits({{cpuif.signal("PADDR")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH)
|
||||||
|
else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("PADDR")}}), {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH);
|
||||||
|
assert_bad_data_width: assert($bits({{cpuif.signal("PWDATA")}}) == {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH)
|
||||||
|
else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("PWDATA")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH);
|
||||||
|
end
|
||||||
|
`endif
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
//======================================================
|
||||||
|
// APB Fanout
|
||||||
|
//======================================================
|
||||||
|
{%- for child in cpuif.addressable_children -%}
|
||||||
|
{%- if child is array -%}
|
||||||
|
for (genvar g_{{child.inst_name|lower}}_idx = 0; g_{{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; g_{{child.inst_name|lower}}_idx++) begin : g_passthrough_{{child.inst_name|lower}}
|
||||||
|
assign {{self.signal("PCLK", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PCLK")}};
|
||||||
|
assign {{self.signal("PRESETn", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PRESETn")}};
|
||||||
|
assign {{self.signal("PENABLE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PENABLE")}};
|
||||||
|
assign {{self.signal("PWRITE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWRITE")}};
|
||||||
|
assign {{self.signal("PADDR", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing
|
||||||
|
assign {{self.signal("PPROT", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PPROT")}};
|
||||||
|
assign {{self.signal("PWDATA", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWDATA")}};
|
||||||
|
assign {{self.signal("PSTRB", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PSTRB")}};
|
||||||
|
end
|
||||||
|
{%- else -%}
|
||||||
|
assign {{self.signal("PCLK", child)}} = {{self.signal("PCLK")}};
|
||||||
|
assign {{self.signal("PRESETn", child)}} = {{self.signal("PRESETn")}};
|
||||||
|
assign {{self.signal("PENABLE", child)}} = {{self.signal("PENABLE")}};
|
||||||
|
assign {{self.signal("PWRITE", child)}} = {{self.signal("PWRITE")}};
|
||||||
|
assign {{self.signal("PADDR", child)}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing
|
||||||
|
assign {{self.signal("PPROT", child)}} = {{self.signal("PPROT")}};
|
||||||
|
assign {{self.signal("PWDATA", child)}} = {{self.signal("PWDATA")}};
|
||||||
|
assign {{self.signal("PSTRB", child)}} = {{self.signal("PSTRB")}};
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
|
||||||
|
//======================================================
|
||||||
|
// Address Decode Logic
|
||||||
|
//======================================================
|
||||||
|
always_comb begin
|
||||||
|
// Default all PSELx signals to 0
|
||||||
|
{%- for child in cpuif.addressable_children -%}
|
||||||
|
{%- if child is array -%}
|
||||||
|
for (int {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
|
||||||
|
{{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b0;
|
||||||
|
end
|
||||||
|
{%- else -%}
|
||||||
|
{{self.signal("PSELx", child)}} = 1'b0;
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
|
||||||
|
if ({{self.signal("PSELx")}}) begin
|
||||||
|
{%- for child in cpuif.addressable_children -%}
|
||||||
|
{%- if loop.first -%}
|
||||||
|
if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||||
|
{%- else -%}
|
||||||
|
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||||
|
{%- endif -%}
|
||||||
|
// Address matched for {{child.inst_name}}
|
||||||
|
{%- if child is array -%}
|
||||||
|
for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
|
||||||
|
{{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b1;
|
||||||
|
end
|
||||||
|
{%- else -%}
|
||||||
|
{{self.signal("PSELx", child)}} = 1'b1;
|
||||||
|
{%- endif -%}
|
||||||
|
{%- if loop.last -%}
|
||||||
|
end else begin
|
||||||
|
// No address matched
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
// PSELx is low, nothing to do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
//======================================================
|
||||||
|
// Read Data Mux
|
||||||
|
//======================================================
|
||||||
|
always_comb begin
|
||||||
|
// Default read data to 0
|
||||||
|
{{self.signal("PRDATA")}} = '0;
|
||||||
|
{{self.signal("PREADY")}} = 1'b1;
|
||||||
|
{{self.signal("PSLVERR")}} = 1'b0;
|
||||||
|
|
||||||
|
if ({{self.signal("PSELx")}} && !{{self.signal("PWRITE")}} && {{self.signal("PENABLE")}}) begin
|
||||||
|
{%- for child in cpuif.addressable_children -%}
|
||||||
|
{%- if loop.first -%}
|
||||||
|
if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||||
|
{%- else -%}
|
||||||
|
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||||
|
{%- endif -%}
|
||||||
|
// Address matched for {{child.inst_name}}
|
||||||
|
{%- if child is array -%}
|
||||||
|
for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
|
||||||
|
{{self.signal("PRDATA")}} = {{self.signal("PRDATA", child, f"{child.inst_name.lower()}_idx")}};
|
||||||
|
{{self.signal("PREADY")}} = {{self.signal("PREADY", child, f"{child.inst_name.lower()}_idx")}};
|
||||||
|
{{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child, f"{child.inst_name.lower()}_idx")}};
|
||||||
|
end
|
||||||
|
{%- else -%}
|
||||||
|
{{self.signal("PRDATA")}} = {{self.signal("PRDATA", child)}};
|
||||||
|
{{self.signal("PREADY")}} = {{self.signal("PREADY", child)}};
|
||||||
|
{{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child)}};
|
||||||
|
{%- endif -%}
|
||||||
|
{%- if loop.last -%}
|
||||||
|
end else begin
|
||||||
|
// No address matched
|
||||||
|
{{self.signal("PRDATA")}} = {'hdeadbeef}[{{ds.data_width - 1}}:0]; // Indicate error on no match
|
||||||
|
{{self.signal("PSLVERR")}} = 1'b1; // Indicate error on no match
|
||||||
|
end
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
end else begin
|
||||||
|
// Not a read transfer, nothing to do
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..base import CpuifBase
|
from ..base_cpuif import CpuifBase
|
||||||
|
|
||||||
|
|
||||||
class AXI4Lite_Cpuif(CpuifBase):
|
class AXI4Lite_Cpuif(CpuifBase):
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
from typing import TYPE_CHECKING, List
|
from typing import TYPE_CHECKING
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import jinja2 as jj
|
import jinja2 as jj
|
||||||
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
from ..utils import clog2, is_pow2, roundup_pow2
|
from ..utils import clog2, is_pow2, roundup_pow2
|
||||||
|
|
||||||
@@ -10,13 +11,22 @@ if TYPE_CHECKING:
|
|||||||
from ..exporter import BusDecoderExporter
|
from ..exporter import BusDecoderExporter
|
||||||
|
|
||||||
|
|
||||||
class CpuifBase:
|
class BaseCpuif:
|
||||||
# Path is relative to the location of the class that assigns this variable
|
# Path is relative to the location of the class that assigns this variable
|
||||||
template_path = ""
|
template_path = ""
|
||||||
|
|
||||||
def __init__(self, exp: "BusDecoderExporter"):
|
def __init__(self, exp: "BusDecoderExporter") -> None:
|
||||||
self.exp = exp
|
self.exp = exp
|
||||||
self.reset = exp.ds.top_node.cpuif_reset
|
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
|
@property
|
||||||
def addr_width(self) -> int:
|
def addr_width(self) -> int:
|
||||||
@@ -35,12 +45,17 @@ class CpuifBase:
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parameters(self) -> List[str]:
|
def parameters(self) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Optional list of additional parameters this CPU interface provides to
|
Optional list of additional parameters this CPU interface provides to
|
||||||
the module's definition
|
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:
|
def _get_template_path_class_dir(self) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -53,6 +68,9 @@ class CpuifBase:
|
|||||||
return class_dir
|
return class_dir
|
||||||
raise RuntimeError
|
raise RuntimeError
|
||||||
|
|
||||||
|
def check_is_array(self, node: AddressableNode) -> bool:
|
||||||
|
return node.is_array and not self.unroll
|
||||||
|
|
||||||
def get_implementation(self) -> str:
|
def get_implementation(self) -> str:
|
||||||
class_dir = self._get_template_path_class_dir()
|
class_dir = self._get_template_path_class_dir()
|
||||||
loader = jj.FileSystemLoader(class_dir)
|
loader = jj.FileSystemLoader(class_dir)
|
||||||
@@ -60,14 +78,13 @@ class CpuifBase:
|
|||||||
loader=loader,
|
loader=loader,
|
||||||
undefined=jj.StrictUndefined,
|
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 = {
|
context = {
|
||||||
"cpuif": self,
|
"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,
|
"ds": self.exp.ds,
|
||||||
}
|
}
|
||||||
|
|
||||||
42
src/peakrdl_busdecoder/dereferencer.py
Normal file
42
src/peakrdl_busdecoder/dereferencer.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from systemrdl.node import AddrmapNode, FieldNode, RegNode, AddressableNode
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .exporter import BusDecoderExporter, DesignState
|
||||||
|
from .addr_decode import AddressDecode
|
||||||
|
|
||||||
|
|
||||||
|
class Dereferencer:
|
||||||
|
"""
|
||||||
|
This class provides an interface to convert conceptual SystemRDL references
|
||||||
|
into Verilog identifiers
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, exp: "BusDecoderExporter"):
|
||||||
|
self.exp = exp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def address_decode(self) -> "AddressDecode":
|
||||||
|
return self.exp.address_decode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ds(self) -> "DesignState":
|
||||||
|
return self.exp.ds
|
||||||
|
|
||||||
|
@property
|
||||||
|
def top_node(self) -> AddrmapNode:
|
||||||
|
return self.exp.ds.top_node
|
||||||
|
|
||||||
|
def get_access_strobe(
|
||||||
|
self, obj: RegNode | FieldNode, reduce_substrobes: bool = True
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Returns the Verilog string that represents the register's access strobe
|
||||||
|
"""
|
||||||
|
return self.address_decode.get_access_strobe(obj, reduce_substrobes)
|
||||||
|
|
||||||
|
def get_external_block_access_strobe(self, obj: "AddressableNode") -> str:
|
||||||
|
"""
|
||||||
|
Returns the Verilog string that represents the external block's access strobe
|
||||||
|
"""
|
||||||
|
return self.address_decode.get_external_block_access_strobe(obj)
|
||||||
184
src/peakrdl_busdecoder/exporter.py
Normal file
184
src/peakrdl_busdecoder/exporter.py
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import os
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
|
import jinja2 as jj
|
||||||
|
from systemrdl.node import AddrmapNode, RootNode
|
||||||
|
from systemrdl.rdltypes.user_enum import UserEnum
|
||||||
|
|
||||||
|
from .addr_decode import AddressDecode
|
||||||
|
from .dereferencer import Dereferencer
|
||||||
|
from .identifier_filter import kw_filter as kwf
|
||||||
|
from .utils import clog2
|
||||||
|
from .scan_design import DesignScanner
|
||||||
|
from .validate_design import DesignValidator
|
||||||
|
from .cpuif import BaseCpuif
|
||||||
|
from .cpuif.apb4 import APB4Cpuif
|
||||||
|
from .sv_int import SVInt
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BusDecoderExporter:
|
||||||
|
cpuif: BaseCpuif
|
||||||
|
address_decode: AddressDecode
|
||||||
|
dereferencer: Dereferencer
|
||||||
|
ds: "DesignState"
|
||||||
|
|
||||||
|
def __init__(self, **kwargs: Any) -> None:
|
||||||
|
# Check for stray kwargs
|
||||||
|
if kwargs:
|
||||||
|
raise TypeError(
|
||||||
|
f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
loader = jj.ChoiceLoader(
|
||||||
|
[
|
||||||
|
jj.FileSystemLoader(os.path.dirname(__file__)),
|
||||||
|
jj.PrefixLoader(
|
||||||
|
{
|
||||||
|
"base": jj.FileSystemLoader(os.path.dirname(__file__)),
|
||||||
|
},
|
||||||
|
delimiter=":",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.jj_env = jj.Environment(
|
||||||
|
loader=loader,
|
||||||
|
undefined=jj.StrictUndefined,
|
||||||
|
)
|
||||||
|
|
||||||
|
def export(
|
||||||
|
self, node: RootNode | AddrmapNode, output_dir: str, **kwargs: Any
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
node: AddrmapNode
|
||||||
|
Top-level SystemRDL node to export.
|
||||||
|
output_dir: str
|
||||||
|
Path to the output directory where generated SystemVerilog will be written.
|
||||||
|
Output includes two files: a module definition and package definition.
|
||||||
|
cpuif_cls: :class:`peakrdl_busdecoder.cpuif.CpuifBase`
|
||||||
|
Specify the class type that implements the CPU interface of your choice.
|
||||||
|
Defaults to AMBA APB4.
|
||||||
|
module_name: str
|
||||||
|
Override the SystemVerilog module name. By default, the module name
|
||||||
|
is the top-level node's name.
|
||||||
|
package_name: str
|
||||||
|
Override the SystemVerilog package name. By default, the package name
|
||||||
|
is the top-level node's name with a "_pkg" suffix.
|
||||||
|
address_width: int
|
||||||
|
Override the CPU interface's address width. By default, address width
|
||||||
|
is sized to the contents of the busdecoder.
|
||||||
|
unroll: bool
|
||||||
|
Unroll arrayed addressable nodes into separate instances in the CPU
|
||||||
|
interface. By default, arrayed nodes are kept as arrays.
|
||||||
|
"""
|
||||||
|
# If it is the root node, skip to top addrmap
|
||||||
|
if isinstance(node, RootNode):
|
||||||
|
top_node = node.top
|
||||||
|
else:
|
||||||
|
top_node = node
|
||||||
|
|
||||||
|
self.ds = DesignState(top_node, kwargs)
|
||||||
|
|
||||||
|
cpuif_cls: type[BaseCpuif] = kwargs.pop("cpuif_cls", None) or APB4Cpuif
|
||||||
|
|
||||||
|
# Check for stray kwargs
|
||||||
|
if kwargs:
|
||||||
|
raise TypeError(
|
||||||
|
f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Construct exporter components
|
||||||
|
self.cpuif = cpuif_cls(self)
|
||||||
|
self.address_decode = AddressDecode(self)
|
||||||
|
self.dereferencer = Dereferencer(self)
|
||||||
|
|
||||||
|
# Validate that there are no unsupported constructs
|
||||||
|
DesignValidator(self).do_validate()
|
||||||
|
|
||||||
|
# Build Jinja template context
|
||||||
|
context = {
|
||||||
|
"cpuif": self.cpuif,
|
||||||
|
"address_decode": self.address_decode,
|
||||||
|
"ds": self.ds,
|
||||||
|
"kwf": kwf,
|
||||||
|
"SVInt": SVInt,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write out design
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
package_file_path = os.path.join(output_dir, self.ds.package_name + ".sv")
|
||||||
|
template = self.jj_env.get_template("package_tmpl.sv")
|
||||||
|
stream = template.stream(context)
|
||||||
|
stream.dump(package_file_path)
|
||||||
|
|
||||||
|
module_file_path = os.path.join(output_dir, self.ds.module_name + ".sv")
|
||||||
|
template = self.jj_env.get_template("module_tmpl.sv")
|
||||||
|
stream = template.stream(context)
|
||||||
|
stream.dump(module_file_path)
|
||||||
|
|
||||||
|
if hwif_report_file:
|
||||||
|
hwif_report_file.close()
|
||||||
|
|
||||||
|
|
||||||
|
class DesignState:
|
||||||
|
"""
|
||||||
|
Dumping ground for all sorts of variables that are relevant to a particular
|
||||||
|
design.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, top_node: AddrmapNode, kwargs: Any) -> None:
|
||||||
|
self.top_node = top_node
|
||||||
|
msg = top_node.env.msg
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# Extract compiler args
|
||||||
|
# ------------------------
|
||||||
|
self.reuse_hwif_typedefs: bool = kwargs.pop("reuse_hwif_typedefs", True)
|
||||||
|
self.module_name: str = kwargs.pop("module_name", None) or kwf(
|
||||||
|
self.top_node.inst_name
|
||||||
|
)
|
||||||
|
self.package_name: str = kwargs.pop("package_name", None) or (
|
||||||
|
self.module_name + "_pkg"
|
||||||
|
)
|
||||||
|
user_addr_width: int | None = kwargs.pop("address_width", None)
|
||||||
|
|
||||||
|
self.cpuif_unroll: bool = kwargs.pop("cpuif_unroll", False)
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# Info about the design
|
||||||
|
# ------------------------
|
||||||
|
self.cpuif_data_width = 0
|
||||||
|
|
||||||
|
# Track any referenced enums
|
||||||
|
self.user_enums: list[type[UserEnum]] = []
|
||||||
|
|
||||||
|
# Scan the design to fill in above variables
|
||||||
|
DesignScanner(self).do_scan()
|
||||||
|
|
||||||
|
if self.cpuif_data_width == 0:
|
||||||
|
# Scanner did not find any registers in the design being exported,
|
||||||
|
# so the width is not known.
|
||||||
|
# Assume 32-bits
|
||||||
|
msg.warning(
|
||||||
|
"Addrmap being exported only contains external components. Unable to infer the CPUIF bus width. Assuming 32-bits.",
|
||||||
|
self.top_node.inst.def_src_ref,
|
||||||
|
)
|
||||||
|
self.cpuif_data_width = 32
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# Min address width encloses the total size AND at least 1 useful address bit
|
||||||
|
self.addr_width = max(
|
||||||
|
clog2(self.top_node.size), clog2(self.cpuif_data_width // 8) + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_addr_width is not None:
|
||||||
|
if user_addr_width < self.addr_width:
|
||||||
|
msg.fatal(
|
||||||
|
f"User-specified address width shall be greater than or equal to {self.addr_width}."
|
||||||
|
)
|
||||||
|
self.addr_width = user_addr_width
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import TYPE_CHECKING, Optional, List, Union
|
from typing import TYPE_CHECKING
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
|
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
|
||||||
@@ -6,15 +6,16 @@ from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from systemrdl.node import AddressableNode, Node
|
from systemrdl.node import AddressableNode, Node
|
||||||
|
|
||||||
class Body:
|
|
||||||
|
|
||||||
|
class Body:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.children = [] # type: List[Union[str, Body]]
|
self.children: list[str | Body] = []
|
||||||
|
|
||||||
def __str__(self) -> str:
|
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
|
return s
|
||||||
|
|
||||||
|
|
||||||
class LoopBody(Body):
|
class LoopBody(Body):
|
||||||
def __init__(self, dim: int, iterator: str, i_type: str) -> None:
|
def __init__(self, dim: int, iterator: str, i_type: str) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -26,7 +27,7 @@ class LoopBody(Body):
|
|||||||
s = super().__str__()
|
s = super().__str__()
|
||||||
return (
|
return (
|
||||||
f"for({self.i_type} {self.iterator}=0; {self.iterator}<{self.dim}; {self.iterator}++) begin\n"
|
f"for({self.i_type} {self.iterator}=0; {self.iterator}<{self.dim}; {self.iterator}++) begin\n"
|
||||||
+ textwrap.indent(s, " ")
|
+ textwrap.indent(s, "\t")
|
||||||
+ "\nend"
|
+ "\nend"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ class ForLoopGenerator:
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._loop_level = 0
|
self._loop_level = 0
|
||||||
self._stack = [] # type: List[Body]
|
self._stack: list[Body] = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_loop(self) -> Body:
|
def current_loop(self) -> Body:
|
||||||
@@ -65,7 +66,7 @@ class ForLoopGenerator:
|
|||||||
b = Body()
|
b = Body()
|
||||||
self._stack.append(b)
|
self._stack.append(b)
|
||||||
|
|
||||||
def finish(self) -> Optional[str]:
|
def finish(self) -> str | None:
|
||||||
b = self._stack.pop()
|
b = self._stack.pop()
|
||||||
assert not self._stack
|
assert not self._stack
|
||||||
|
|
||||||
@@ -73,15 +74,17 @@ class ForLoopGenerator:
|
|||||||
return None
|
return None
|
||||||
return str(b)
|
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()
|
self.start()
|
||||||
walker = RDLWalker()
|
walker = RDLWalker()
|
||||||
walker.walk(node, self, skip_top=True)
|
walker.walk(node, self, skip_top=True)
|
||||||
return self.finish()
|
return self.finish()
|
||||||
|
|
||||||
def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
|
def enter_AddressableComponent(
|
||||||
|
self, node: "AddressableNode"
|
||||||
|
) -> WalkerAction | None:
|
||||||
if not node.array_dimensions:
|
if not node.array_dimensions:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -89,7 +92,7 @@ class RDLForLoopGenerator(ForLoopGenerator, RDLListener):
|
|||||||
self.push_loop(dim)
|
self.push_loop(dim)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def exit_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
|
def exit_AddressableComponent(self, node: "AddressableNode") -> WalkerAction | None:
|
||||||
if not node.array_dimensions:
|
if not node.array_dimensions:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
103
src/peakrdl_busdecoder/module_tmpl.sv
Normal file
103
src/peakrdl_busdecoder/module_tmpl.sv
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
|
||||||
|
//==========================================================
|
||||||
|
// Module: {{ds.module_name}}
|
||||||
|
// Description: CPU Interface Bus Decoder
|
||||||
|
// Author: PeakRDL-busdecoder
|
||||||
|
// License: GLPLv3
|
||||||
|
// Date: {{current_date}}
|
||||||
|
// Version: {{version}}
|
||||||
|
// Links:
|
||||||
|
// - https://github.com/SystemRDL/PeakRDL-busdecoder
|
||||||
|
//==========================================================
|
||||||
|
|
||||||
|
|
||||||
|
module {{ds.module_name}}
|
||||||
|
{%- if cpuif.parameters %} #(
|
||||||
|
{{-cpuif.parameters|join(",\n")|indent(8)}}
|
||||||
|
) {%- endif %} (
|
||||||
|
{{-cpuif.port_declaration|indent(8)}}
|
||||||
|
);
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// CPU Bus interface logic
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
logic cpuif_req;
|
||||||
|
logic [{{cpuif.addr_width-1}}:0] cpuif_wr_addr;
|
||||||
|
logic [{{cpuif.addr_width-1}}:0] cpuif_rd_addr;
|
||||||
|
|
||||||
|
logic cpuif_wr_ack;
|
||||||
|
logic cpuif_wr_err;
|
||||||
|
logic [{{cpuif.data_width-1}}:0] cpuif_wr_data;
|
||||||
|
logic [{{cpuif.data_width//8-1}}:0] cpuif_wr_byte_en;
|
||||||
|
|
||||||
|
logic cpuif_rd_ack;
|
||||||
|
logic cpuif_rd_err;
|
||||||
|
logic [{{cpuif.data_width-1}}:0] cpuif_rd_data;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Child instance signals
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
logic [{{cpuif.addressable_children | length}}-1:0] cpuif_wr_sel;
|
||||||
|
logic [{{cpuif.addressable_children | length}}-1:0] cpuif_rd_sel;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Slave <-> Internal CPUIF <-> Master
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
{{-cpuif.get_implementation()|indent}}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Write Address Decoder
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
always_comb begin
|
||||||
|
// Default all write select signals to 0
|
||||||
|
cpuif_wr_sel = '0;
|
||||||
|
|
||||||
|
if (cpuif_req && cpuif_wr_en) begin
|
||||||
|
// A write request is pending
|
||||||
|
{%- for child in cpuif.addressable_children -%}
|
||||||
|
{%- if loop.first -%}
|
||||||
|
if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||||
|
{%- else -%}
|
||||||
|
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||||
|
{%- endif -%}
|
||||||
|
// Address matched for {{child.inst_name}}
|
||||||
|
cpuif_wr_sel[{{loop.index0}}] = 1'b1;
|
||||||
|
{%- endfor -%}
|
||||||
|
end else begin
|
||||||
|
// No address match, all select signals remain 0
|
||||||
|
cpuif_wr_err = 1'b1; // Indicate error on no match
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
// No write request, all select signals remain 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Read Address Decoder
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
always_comb begin
|
||||||
|
// Default all read select signals to 0
|
||||||
|
cpuif_rd_sel = '0;
|
||||||
|
|
||||||
|
|
||||||
|
if (cpuif_req && !cpuif_wr_en) begin
|
||||||
|
// A read request is pending
|
||||||
|
{%- for child in cpuif.addressable_children -%}
|
||||||
|
{%- if loop.first -%}
|
||||||
|
if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||||
|
{%- else -%}
|
||||||
|
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
|
||||||
|
{%- endif -%}
|
||||||
|
// Address matched for {{child.inst_name}}
|
||||||
|
cpuif_rd_sel[{{loop.index0}}] = 1'b1;
|
||||||
|
{%- endfor -%}
|
||||||
|
end else begin
|
||||||
|
// No address match, all select signals remain 0
|
||||||
|
cpuif_rd_err = 1'b1; // Indicate error on no match
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
// No read request, all select signals remain 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
{# (eof newline anchor) #}
|
||||||
@@ -6,9 +6,5 @@ package {{ds.package_name}};
|
|||||||
localparam {{ds.module_name.upper()}}_DATA_WIDTH = {{ds.cpuif_data_width}};
|
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()}}_MIN_ADDR_WIDTH = {{ds.addr_width}};
|
||||||
localparam {{ds.module_name.upper()}}_SIZE = {{SVInt(ds.top_node.size)}};
|
localparam {{ds.module_name.upper()}}_SIZE = {{SVInt(ds.top_node.size)}};
|
||||||
|
|
||||||
{{-hwif.get_extra_package_params()|indent}}
|
|
||||||
|
|
||||||
{{-hwif.get_package_contents()|indent}}
|
|
||||||
endpackage
|
endpackage
|
||||||
{# (eof newline anchor) #}
|
{# (eof newline anchor) #}
|
||||||
47
src/peakrdl_busdecoder/scan_design.py
Normal file
47
src/peakrdl_busdecoder/scan_design.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
|
||||||
|
from systemrdl.node import RegNode
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from systemrdl.node import Node, AddressableNode, AddrmapNode
|
||||||
|
from .exporter import DesignState
|
||||||
|
|
||||||
|
|
||||||
|
class DesignScanner(RDLListener):
|
||||||
|
"""
|
||||||
|
Scans through the register model and validates that any unsupported features
|
||||||
|
are not present.
|
||||||
|
|
||||||
|
Also collects any information that is required prior to the start of the export process.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, ds: "DesignState") -> None:
|
||||||
|
self.ds = ds
|
||||||
|
self.msg = self.top_node.env.msg
|
||||||
|
|
||||||
|
@property
|
||||||
|
def top_node(self) -> "AddrmapNode":
|
||||||
|
return self.ds.top_node
|
||||||
|
|
||||||
|
def do_scan(self) -> None:
|
||||||
|
RDLWalker().walk(self.top_node, self)
|
||||||
|
if self.msg.had_error:
|
||||||
|
self.msg.fatal("Unable to export due to previous errors")
|
||||||
|
|
||||||
|
def enter_Component(self, node: "Node") -> WalkerAction | None:
|
||||||
|
if node.external and (node != self.top_node):
|
||||||
|
# Do not inspect external components. None of my business
|
||||||
|
return WalkerAction.SkipDescendants
|
||||||
|
|
||||||
|
# Collect any signals that are referenced by a property
|
||||||
|
for prop_name in node.list_properties():
|
||||||
|
_ = node.get_property(prop_name)
|
||||||
|
|
||||||
|
return WalkerAction.Continue
|
||||||
|
|
||||||
|
def enter_AddressableComponent(self, node: "AddressableNode") -> None:
|
||||||
|
if node.external and node != self.top_node:
|
||||||
|
self.ds.has_external_addressable = True
|
||||||
|
if not isinstance(node, RegNode):
|
||||||
|
self.ds.has_external_block = True
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
class SVInt:
|
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.value = value
|
||||||
self.width = width
|
self.width = width
|
||||||
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
|
from systemrdl.udp import UDPDefinition
|
||||||
from .rw_buffering import BufferWrites, WBufferTrigger
|
from .rw_buffering import BufferWrites, WBufferTrigger
|
||||||
from .rw_buffering import BufferReads, RBufferTrigger
|
from .rw_buffering import BufferReads, RBufferTrigger
|
||||||
from .extended_swacc import ReadSwacc, WriteSwacc
|
from .extended_swacc import ReadSwacc, WriteSwacc
|
||||||
from .fixedpoint import IntWidth, FracWidth
|
from .fixedpoint import IntWidth, FracWidth
|
||||||
from .signed import IsSigned
|
from .signed import IsSigned
|
||||||
|
|
||||||
ALL_UDPS = [
|
ALL_UDPS: list[type[UDPDefinition]] = [
|
||||||
BufferWrites,
|
BufferWrites,
|
||||||
WBufferTrigger,
|
WBufferTrigger,
|
||||||
BufferReads,
|
BufferReads,
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
from typing import Match, Union, Optional
|
from typing import Match, overload
|
||||||
|
|
||||||
from systemrdl.rdltypes.references import PropertyReference
|
from systemrdl.rdltypes.references import PropertyReference
|
||||||
from systemrdl.node import Node, AddrmapNode
|
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 .identifier_filter import kw_filter as kwf
|
||||||
from .sv_int import SVInt
|
from .sv_int import SVInt
|
||||||
|
|
||||||
|
|
||||||
def get_indexed_path(top_node: Node, target_node: Node) -> str:
|
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
|
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:
|
class ReplaceUnknown:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.i = 0
|
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
|
self.i += 1
|
||||||
return s
|
return s
|
||||||
path = re.sub(r'!', ReplaceUnknown(), path)
|
|
||||||
|
path = re.sub(r"!", ReplaceUnknown(), path)
|
||||||
|
|
||||||
# Sanitize any SV keywords
|
# Sanitize any SV keywords
|
||||||
def kw_filter_repl(m: Match) -> str:
|
def kw_filter_repl(m: Match[str]) -> str:
|
||||||
return kwf(m.group(0))
|
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
|
return path
|
||||||
|
|
||||||
|
|
||||||
def clog2(n: int) -> int:
|
def clog2(n: int) -> int:
|
||||||
return (n-1).bit_length()
|
return (n - 1).bit_length()
|
||||||
|
|
||||||
|
|
||||||
def is_pow2(x: int) -> bool:
|
def is_pow2(x: int) -> bool:
|
||||||
return (x > 0) and ((x & (x - 1)) == 0)
|
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.
|
Determine whether the reference is internal to the top node.
|
||||||
|
|
||||||
For the sake of this exporter, root signals are treated as internal.
|
For the sake of this exporter, root signals are treated as internal.
|
||||||
"""
|
"""
|
||||||
current_node: Optional[Node]
|
# current_node: Optional[Node]
|
||||||
if isinstance(ref, Node):
|
if isinstance(ref, Node):
|
||||||
current_node = ref
|
current_node = ref
|
||||||
elif isinstance(ref, PropertyReference):
|
elif isinstance(ref, PropertyReference):
|
||||||
@@ -70,7 +78,11 @@ def ref_is_internal(top_node: AddrmapNode, ref: Union[Node, PropertyReference])
|
|||||||
return True
|
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 isinstance(value, str):
|
||||||
# If string, assume this is an identifier. Append bit-slice
|
# If string, assume this is an identifier. Append bit-slice
|
||||||
if high == low:
|
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)
|
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 isinstance(value, str):
|
||||||
# If string, assume this is an identifier. Wrap in a streaming operator
|
# If string, assume this is an identifier. Wrap in a streaming operator
|
||||||
return "{<<{" + value + "}}"
|
return "{<<{" + value + "}}"
|
||||||
else:
|
else:
|
||||||
# it is an SVInt literal. bitswap it
|
# 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
|
v = value.value
|
||||||
vswap = 0
|
vswap = 0
|
||||||
for _ in range(value.width):
|
for _ in range(value.width):
|
||||||
@@ -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.walker import RDLListener, RDLWalker, WalkerAction
|
||||||
from systemrdl.rdltypes import PropertyReference
|
from systemrdl.rdltypes import PropertyReference
|
||||||
@@ -24,7 +24,7 @@ class DesignValidator(RDLListener):
|
|||||||
self.ds = exp.ds
|
self.ds = exp.ds
|
||||||
self.msg = self.top_node.env.msg
|
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
|
self.contains_external_block = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -36,7 +36,7 @@ class DesignValidator(RDLListener):
|
|||||||
if self.msg.had_error:
|
if self.msg.had_error:
|
||||||
self.msg.fatal("Unable to export due to previous errors")
|
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):
|
if node.external and (node != self.top_node):
|
||||||
# Do not inspect external components. None of my business
|
# Do not inspect external components. None of my business
|
||||||
return WalkerAction.SkipDescendants
|
return WalkerAction.SkipDescendants
|
||||||
@@ -100,7 +100,7 @@ class DesignValidator(RDLListener):
|
|||||||
def enter_Addrmap(self, node: AddrmapNode) -> None:
|
def enter_Addrmap(self, node: AddrmapNode) -> None:
|
||||||
self._check_sharedextbus(node)
|
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"):
|
if node.get_property("sharedextbus"):
|
||||||
self.msg.error(
|
self.msg.error(
|
||||||
"This exporter does not support enabling the 'sharedextbus' property yet.",
|
"This exporter does not support enabling the 'sharedextbus' property yet.",
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
version_info = (1, 1, 1)
|
|
||||||
__version__ = ".".join([str(n) for n in version_info])
|
|
||||||
@@ -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,
|
|
||||||
)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
from .base import CpuifBase
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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)
|
|
||||||
@@ -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;
|
|
||||||
@@ -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)"
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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 = ""
|
|
||||||
@@ -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))
|
|
||||||
@@ -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;",
|
|
||||||
]
|
|
||||||
@@ -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)
|
|
||||||
@@ -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;",
|
|
||||||
]
|
|
||||||
@@ -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}"
|
|
||||||
@@ -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;",
|
|
||||||
]
|
|
||||||
@@ -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})"
|
|
||||||
@@ -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;",
|
|
||||||
]
|
|
||||||
@@ -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 %}
|
|
||||||
@@ -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 %}
|
|
||||||
@@ -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 %}
|
|
||||||
@@ -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 %}
|
|
||||||
@@ -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
|
|
||||||
@@ -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;"
|
|
||||||
)
|
|
||||||
@@ -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) #}
|
|
||||||
@@ -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)};"
|
|
||||||
)
|
|
||||||
@@ -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"
|
|
||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
always_ff @(posedge clk) begin
|
|
||||||
if({{rbuf.get_trigger(node)}}) begin
|
|
||||||
{{get_assignments(node)|indent(8)}}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -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)
|
|
||||||
@@ -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
|
|
||||||
@@ -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 %}
|
|
||||||
@@ -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
|
|
||||||
),
|
|
||||||
)
|
|
||||||
@@ -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
|
|
||||||
@@ -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)
|
|
||||||
@@ -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))
|
|
||||||
@@ -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")
|
|
||||||
@@ -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
|
|
||||||
@@ -4,7 +4,7 @@ import inspect
|
|||||||
|
|
||||||
import jinja2 as jj
|
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
|
from ..sv_line_anchor import SVLineAnchor
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user