decoder compiles
This commit is contained in:
@@ -65,13 +65,13 @@ class Exporter(ExporterSubcommandPlugin):
|
|||||||
|
|
||||||
return cpuifs
|
return cpuifs
|
||||||
|
|
||||||
def add_exporter_arguments(self, arg_group: "argparse._ActionsContainer") -> None:
|
def add_exporter_arguments(self, arg_group: "argparse.ArgumentParser") -> None: # type: ignore
|
||||||
cpuifs = self.get_cpuifs()
|
cpuifs = self.get_cpuifs()
|
||||||
|
|
||||||
arg_group.add_argument(
|
arg_group.add_argument(
|
||||||
"--cpuif",
|
"--cpuif",
|
||||||
choices=cpuifs.keys(),
|
choices=cpuifs.keys(),
|
||||||
default="apb3",
|
default="apb4",
|
||||||
help="Select the CPU interface protocol to use [apb3]",
|
help="Select the CPU interface protocol to use [apb3]",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
from .body import Body, SupportsStr
|
from .body import Body, SupportsStr
|
||||||
from .for_loop_body import ForLoopBody
|
|
||||||
from .while_loop_body import WhileLoopBody
|
|
||||||
from .if_body import IfBody
|
|
||||||
from .combinational_body import CombinationalBody
|
from .combinational_body import CombinationalBody
|
||||||
|
from .for_loop_body import ForLoopBody
|
||||||
|
from .if_body import IfBody
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Body",
|
"Body",
|
||||||
"SupportsStr",
|
|
||||||
"ForLoopBody",
|
|
||||||
"WhileLoopBody",
|
|
||||||
"IfBody",
|
|
||||||
"CombinationalBody",
|
"CombinationalBody",
|
||||||
|
"ForLoopBody",
|
||||||
|
"IfBody",
|
||||||
|
"SupportsStr",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -15,3 +15,6 @@ class Body:
|
|||||||
def __add__(self, other: SupportsStr) -> Self:
|
def __add__(self, other: SupportsStr) -> Self:
|
||||||
self.lines.append(other)
|
self.lines.append(other)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
return bool(self.lines)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from textwrap import indent
|
from textwrap import indent
|
||||||
|
|
||||||
from .body import Body
|
from .body import Body
|
||||||
|
|
||||||
|
|
||||||
class CombinationalBody(Body):
|
class CombinationalBody(Body):
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"""always_comb begin
|
return f"""always_comb begin
|
||||||
{indent(super().__str__(), "\t")}
|
{indent(super().__str__(), " ")}
|
||||||
end"""
|
end"""
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
from textwrap import indent
|
from textwrap import indent
|
||||||
|
|
||||||
from .body import Body
|
from .body import Body
|
||||||
|
|
||||||
|
|
||||||
class ForLoopBody(Body):
|
class ForLoopBody(Body):
|
||||||
def __init__(self, type: str, iterator: str, dim: int):
|
def __init__(self, type: str, iterator: str, dim: int) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._type = type
|
self._type = type
|
||||||
self._iterator = iterator
|
self._iterator = iterator
|
||||||
@@ -11,5 +12,5 @@ class ForLoopBody(Body):
|
|||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"""for ({self._type} {self._iterator} = 0; {self._iterator} < {self._dim}; {self._iterator}++) begin
|
return f"""for ({self._type} {self._iterator} = 0; {self._iterator} < {self._dim}; {self._iterator}++) begin
|
||||||
{indent(super().__str__(), "\t")}
|
{indent(super().__str__(), " ")}
|
||||||
end"""
|
end"""
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from textwrap import indent
|
||||||
from types import EllipsisType
|
from types import EllipsisType
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
|
||||||
from .body import Body, SupportsStr
|
from .body import Body, SupportsStr
|
||||||
|
|
||||||
|
|
||||||
class IfBody(Body):
|
class IfBody(Body):
|
||||||
def __init__(self, *, indent: int = 4) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
# (None means 'else')
|
# (None means 'else')
|
||||||
self._branches: list[tuple[SupportsStr | None, Body]] = []
|
self._branches: list[tuple[SupportsStr | None, Body]] = []
|
||||||
self._has_else = False
|
self._has_else = False
|
||||||
self._indent = " " * indent
|
|
||||||
|
|
||||||
# --- Item access: if/else-if via condition; else via Ellipsis/None ---
|
# --- Item access: if/else-if via condition; else via Ellipsis/None ---
|
||||||
def __getitem__(self, condition: SupportsStr | EllipsisType | None) -> Body:
|
def __getitem__(self, condition: SupportsStr | EllipsisType | None) -> Body:
|
||||||
@@ -63,7 +65,7 @@ class IfBody(Body):
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def cm(self, condition: SupportsStr | None) -> "IfBody._BranchCtx":
|
def cm(self, condition: SupportsStr | None) -> IfBody._BranchCtx:
|
||||||
"""Use with: with ifb.cm('cond') as b: ... ; use None for else."""
|
"""Use with: with ifb.cm('cond') as b: ... ; use None for else."""
|
||||||
return IfBody._BranchCtx(self, condition)
|
return IfBody._BranchCtx(self, condition)
|
||||||
|
|
||||||
@@ -79,6 +81,12 @@ class IfBody(Body):
|
|||||||
out.append("else begin")
|
out.append("else begin")
|
||||||
body_str = str(body)
|
body_str = str(body)
|
||||||
if body_str:
|
if body_str:
|
||||||
out.extend(self._indent + ln for ln in body_str.splitlines())
|
out.extend(indent(ln, " ") for ln in body_str.splitlines())
|
||||||
out.append("end")
|
out.append("end")
|
||||||
return "\n".join(out)
|
return "\n".join(out)
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self._branches)
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
return bool(self._branches)
|
||||||
|
|||||||
15
src/peakrdl_busdecoder/body/struct_body.py
Normal file
15
src/peakrdl_busdecoder/body/struct_body.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from textwrap import indent
|
||||||
|
|
||||||
|
from .body import Body
|
||||||
|
|
||||||
|
|
||||||
|
class StructBody(Body):
|
||||||
|
def __init__(self, name: str, packed: bool = True) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self._name = name
|
||||||
|
self._packed = packed
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"""typedef struct {"packed " if self._packed else ""} {{
|
||||||
|
{indent(super().__str__(), " ")}
|
||||||
|
}} {self._name};"""
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
from textwrap import indent
|
|
||||||
from .body import Body
|
|
||||||
|
|
||||||
|
|
||||||
class WhileLoopBody(Body):
|
|
||||||
def __init__(self, condition: str):
|
|
||||||
super().__init__()
|
|
||||||
self._condition = condition
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"""while ({self._condition}) begin
|
|
||||||
{indent(super().__str__(), "\t")}
|
|
||||||
end"""
|
|
||||||
@@ -42,37 +42,4 @@ class APB3Cpuif(BaseCpuif):
|
|||||||
if idx is not None:
|
if idx is not None:
|
||||||
return f"{base}[{idx}].{signal}"
|
return f"{base}[{idx}].{signal}"
|
||||||
|
|
||||||
raise ValueError("Must provide an index for arrayed interface signals")
|
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
|
|
||||||
@@ -16,14 +16,14 @@ assign cpuif_rd_en = !{{cpuif.signal("PWRITE")}};
|
|||||||
|
|
||||||
{%- for child in cpuif.addressable_children -%}
|
{%- for child in cpuif.addressable_children -%}
|
||||||
{%- if child is array -%}
|
{%- if child is array -%}
|
||||||
for (genvar g_idx = 0; g_idx < N_{{child.inst_name|upper}}S; g_idx++) begin : g_passthrough_{{child.inst_name|lower}}
|
for (genvar gi = 0; gi < N_{{child.inst_name|upper}}S; gi++) begin : g_passthrough_{{child.inst_name|lower}}
|
||||||
assign {{cpuif.signal("PCLK", child, "g_idx")}} = {{cpuif.signal("PCLK")}};
|
assign {{cpuif.signal("PCLK", child, "gi")}} = {{cpuif.signal("PCLK")}};
|
||||||
assign {{cpuif.signal("PRESETn", child, "g_idx")}} = {{cpuif.signal("PRESETn")}};
|
assign {{cpuif.signal("PRESETn", child, "gi")}} = {{cpuif.signal("PRESETn")}};
|
||||||
assign {{cpuif.signal("PSELx", child, "g_idx")}} = (cpuif_wr_req[{{loop.index}}] || cpuif_rd_req[{{loop.index}}]) ? 1'b1 : 1'b0;
|
assign {{cpuif.signal("PSELx", child, "gi")}} = (cpuif_wr_req[{{loop.index}}] || cpuif_rd_req[{{loop.index}}]) ? 1'b1 : 1'b0;
|
||||||
assign {{cpuif.signal("PENABLE", child, "g_idx")}} = {{cpuif.signal("PENABLE")}};
|
assign {{cpuif.signal("PENABLE", child, "gi")}} = {{cpuif.signal("PENABLE")}};
|
||||||
assign {{cpuif.signal("PWRITE", child, "g_idx")}} = (cpuif_wr_req[{{loop.index}}]) ? 1'b1 : 1'b0;
|
assign {{cpuif.signal("PWRITE", child, "gi")}} = (cpuif_wr_req[{{loop.index}}]) ? 1'b1 : 1'b0;
|
||||||
assign {{cpuif.signal("PADDR", child, "g_idx")}} = cpuif_wr_addr{{child|address_slice}};
|
assign {{cpuif.signal("PADDR", child, "gi")}} = {{child|address_slice(cpuif_addr="cpuif_wr_addr")}};
|
||||||
assign {{cpuif.signal("PWDATA", child, "g_idx")}} = cpuif_wr_data;
|
assign {{cpuif.signal("PWDATA", child, "gi")}} = cpuif_wr_data;
|
||||||
assign cpuif_rd_ack[loop.index] = {{cpuif.signal("PREADY", child)}};
|
assign cpuif_rd_ack[loop.index] = {{cpuif.signal("PREADY", child)}};
|
||||||
assign cpuif_rd_data[loop.index] = {{cpuif.signal("PRDATA", child)}};
|
assign cpuif_rd_data[loop.index] = {{cpuif.signal("PRDATA", child)}};
|
||||||
assign cpuif_rd_err[loop.index] = {{cpuif.signal("PSLVERR", child)}};
|
assign cpuif_rd_err[loop.index] = {{cpuif.signal("PSLVERR", child)}};
|
||||||
@@ -31,10 +31,10 @@ end
|
|||||||
{%- else -%}
|
{%- else -%}
|
||||||
assign {{cpuif.signal("PCLK", child)}} = {{cpuif.signal("PCLK")}};
|
assign {{cpuif.signal("PCLK", child)}} = {{cpuif.signal("PCLK")}};
|
||||||
assign {{cpuif.signal("PRESETn", child)}} = {{cpuif.signal("PRESETn")}};
|
assign {{cpuif.signal("PRESETn", child)}} = {{cpuif.signal("PRESETn")}};
|
||||||
assign {{cpuif.signal("PSELx", child)}} = (cpuif_wr_sel[{{loop.index0}}] || cpuif_rd_sel[{{loop.index0}}]) ? 1'b1 : 1'b0;
|
assign {{cpuif.signal("PSELx", child)}} = (cpuif_wr_sel[{{loop.index0}}] || cpuif_rd_sel[{{loop.index0}}]) ? 1'b1 : 1'b0;
|
||||||
assign {{cpuif.signal("PENABLE", child)}} = {{cpuif.signal("PENABLE")}};
|
assign {{cpuif.signal("PENABLE", child)}} = {{cpuif.signal("PENABLE")}};
|
||||||
assign {{cpuif.signal("PWRITE", child)}} = (cpuif_wr_req[{{loop.index}}]) ? 1'b1 : 1'b0;
|
assign {{cpuif.signal("PWRITE", child)}} = (cpuif_wr_req[{{loop.index}}]) ? 1'b1 : 1'b0;
|
||||||
assign {{cpuif.signal("PADDR", child)}} = cpuif_wr_addr{{child|address_slice}};
|
assign {{cpuif.signal("PADDR", child)}} = {{child|address_slice(cpuif_addr="cpuif_wr_addr")}};
|
||||||
assign {{cpuif.signal("PWDATA", child)}} = cpuif_wr_data;
|
assign {{cpuif.signal("PWDATA", child)}} = cpuif_wr_data;
|
||||||
assign cpuif_rd_ack[loop.index] = {{cpuif.signal("PREADY", child)}};
|
assign cpuif_rd_ack[loop.index] = {{cpuif.signal("PREADY", child)}};
|
||||||
assign cpuif_rd_data[loop.index] = {{cpuif.signal("PRDATA", child)}};
|
assign cpuif_rd_data[loop.index] = {{cpuif.signal("PRDATA", child)}};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from systemrdl.node import AddressableNode
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
from ..base_cpuif import BaseCpuif
|
from ..base_cpuif import BaseCpuif
|
||||||
|
|
||||||
|
|
||||||
@@ -44,36 +45,3 @@ class APB4Cpuif(BaseCpuif):
|
|||||||
return f"{base}[{idx}].{signal}"
|
return f"{base}[{idx}].{signal}"
|
||||||
|
|
||||||
raise ValueError("Must provide an index for arrayed interface signals")
|
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
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from systemrdl.node import AddressableNode
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
from ..base_cpuif import BaseCpuif
|
from ..base_cpuif import BaseCpuif
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{%- if cpuif.is_interface -%}
|
{%- if cpuif.is_interface %}
|
||||||
`ifndef SYNTHESIS
|
`ifndef SYNTHESIS
|
||||||
initial begin
|
initial begin
|
||||||
assert_bad_addr_width: assert($bits({{cpuif.signal("PADDR")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH)
|
assert_bad_addr_width: assert($bits({{cpuif.signal("PADDR")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH)
|
||||||
@@ -7,55 +7,76 @@
|
|||||||
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);
|
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
|
end
|
||||||
`endif
|
`endif
|
||||||
{% endif -%}
|
{%- endif %}
|
||||||
|
|
||||||
assign cpuif_req = {{cpuif.signal("PSELx")}};
|
assign cpuif_req = {{cpuif.signal("PSELx")}};
|
||||||
assign cpuif_wr_en = {{cpuif.signal("PWRITE")}};
|
assign cpuif_wr_en = {{cpuif.signal("PWRITE")}};
|
||||||
assign cpuif_wr_data = {{cpuif.signal("PWDATA")}};
|
assign cpuif_wr_data = {{cpuif.signal("PWDATA")}};
|
||||||
assign cpuif_rd_en = !{{cpuif.signal("PWRITE")}};
|
assign cpuif_rd_en = !{{cpuif.signal("PWRITE")}};
|
||||||
|
|
||||||
{%- for child in cpuif.addressable_children -%}
|
//--------------------------------------------------------------------------
|
||||||
{%- if child is array -%}
|
// Fanout CPU Bus interface signals
|
||||||
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("PSELx", child, f"g_{child.inst_name.lower()}_idx")}} = (cpuif_wr_sel[{{loop.index}}] || cpuif_rd_sel[{{loop.index}}]) ? 1'b1 : 1'b0;
|
|
||||||
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")}} = (cpuif_wr_sel[{{loop.index}}]) ? 1'b1 : 1'b0;
|
|
||||||
assign {{self.signal("PADDR", child, f"g_{child.inst_name.lower()}_idx")}} = {{cpuif.get_address_slice(cpuif_wr_addr, child)}};
|
|
||||||
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")}} = cpuif_wr_data;
|
|
||||||
assign {{self.signal("PSTRB", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PSTRB")}};
|
|
||||||
assign cpuif_rd_ack[loop.index] = {{cpuif.signal("PREADY", child)}};
|
|
||||||
assign cpuif_rd_data[loop.index] = {{cpuif.signal("PRDATA", child)}};
|
|
||||||
assign cpuif_rd_err[loop.index] = {{cpuif.signal("PSLVERR", child)}};
|
|
||||||
end
|
|
||||||
{%- else -%}
|
|
||||||
assign {{self.signal("PCLK", child)}} = {{self.signal("PCLK")}};
|
|
||||||
assign {{self.signal("PRESETn", child)}} = {{self.signal("PRESETn")}};
|
|
||||||
assign {{self.signal("PSELx", child)}} = (cpuif_wr_sel[{{loop.index0}}] || cpuif_rd_sel[{{loop.index0}}]) ? 1'b1 : 1'b0;
|
|
||||||
assign {{self.signal("PENABLE", child)}} = {{self.signal("PENABLE")}};
|
|
||||||
assign {{self.signal("PWRITE", child)}} = cpuif_wr_sel[{{loop.index}}] ? 1'b1 : 1'b0;
|
|
||||||
assign {{self.signal("PADDR", child)}} = {{cpuif.get_address_slice(cpuif_wr_addr, child)}};
|
|
||||||
assign {{self.signal("PPROT", child)}} = {{self.signal("PPROT")}};
|
|
||||||
assign {{self.signal("PWDATA", child)}} = cpuif_wr_data;
|
|
||||||
assign {{self.signal("PSTRB", child)}} = {{self.signal("PSTRB")}};
|
|
||||||
assign cpuif_rd_ack[loop.index] = {{cpuif.signal("PREADY", child)}};
|
|
||||||
assign cpuif_rd_data[loop.index] = {{cpuif.signal("PRDATA", child)}};
|
|
||||||
assign cpuif_rd_err[loop.index] = {{cpuif.signal("PSLVERR", child)}};
|
|
||||||
{%- endif -%}
|
|
||||||
{%- endfor -%}
|
|
||||||
|
|
||||||
|
// {%- for child in cpuif.addressable_children -%}
|
||||||
|
// {%- if child is array %}
|
||||||
|
// for (genvar gi = 0; gi < N_{{child.inst_name|upper}}S; gi++) begin : g_passthrough_{{child.inst_name|lower}}
|
||||||
|
// assign {{cpuif.signal("PCLK", child, "gi")}} = {{cpuif.signal("PCLK")}};
|
||||||
|
// assign {{cpuif.signal("PRESETn", child, "gi")}} = {{cpuif.signal("PRESETn")}};
|
||||||
|
// assign {{cpuif.signal("PSELx", child, "gi")}} = (cpuif_wr_sel[{{loop.index}}] || cpuif_rd_sel[{{loop.index}}]) ? 1'b1 : 1'b0;
|
||||||
|
// assign {{cpuif.signal("PENABLE", child, "gi")}} = {{cpuif.signal("PENABLE")}};
|
||||||
|
// assign {{cpuif.signal("PWRITE", child, "gi")}} = (cpuif_wr_sel[{{loop.index}}]) ? 1'b1 : 1'b0;
|
||||||
|
// assign {{cpuif.signal("PADDR", child, "gi")}} = {{child|address_slice(cpuif_addr="cpuif_wr_addr")}};
|
||||||
|
// assign {{cpuif.signal("PPROT", child, "gi")}} = {{cpuif.signal("PPROT")}};
|
||||||
|
// assign {{cpuif.signal("PWDATA", child, "gi")}} = cpuif_wr_data;
|
||||||
|
// assign {{cpuif.signal("PSTRB", child, "gi")}} = {{cpuif.signal("PSTRB")}};
|
||||||
|
// assign cpuif_rd_ack[{{loop.index}}] = {{cpuif.signal("PREADY", child, "gi")}};
|
||||||
|
// assign cpuif_rd_data[{{loop.index}}] = {{cpuif.signal("PRDATA", child, "gi")}};
|
||||||
|
// assign cpuif_rd_err[{{loop.index}}] = {{cpuif.signal("PSLVERR", child, "gi")}};
|
||||||
|
// end
|
||||||
|
// {%- else %}
|
||||||
|
// assign {{cpuif.signal("PCLK", child)}} = {{cpuif.signal("PCLK")}};
|
||||||
|
// assign {{cpuif.signal("PRESETn", child)}} = {{cpuif.signal("PRESETn")}};
|
||||||
|
// assign {{cpuif.signal("PSELx", child)}} = (cpuif_wr_sel[{{loop.index}}] || cpuif_rd_sel[{{loop.index}}]) ? 1'b1 : 1'b0;
|
||||||
|
// assign {{cpuif.signal("PENABLE", child)}} = {{cpuif.signal("PENABLE")}};
|
||||||
|
// assign {{cpuif.signal("PWRITE", child)}} = cpuif_wr_sel[{{loop.index}}] ? 1'b1 : 1'b0;
|
||||||
|
// assign {{cpuif.signal("PADDR", child)}} = {{child|address_slice(cpuif_addr="cpuif_wr_addr")}};
|
||||||
|
// assign {{cpuif.signal("PPROT", child)}} = {{cpuif.signal("PPROT")}};
|
||||||
|
// assign {{cpuif.signal("PWDATA", child)}} = cpuif_wr_data;
|
||||||
|
// assign {{cpuif.signal("PSTRB", child)}} = {{cpuif.signal("PSTRB")}};
|
||||||
|
// assign cpuif_rd_ack[{{loop.index}}] = {{cpuif.signal("PREADY", child)}};
|
||||||
|
// assign cpuif_rd_data[{{loop.index}}] = {{cpuif.signal("PRDATA", child)}};
|
||||||
|
// assign cpuif_rd_err[{{loop.index}}] = {{cpuif.signal("PSLVERR", child)}};
|
||||||
|
// {%- endif -%}
|
||||||
|
// {%- endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Fanin CPU Bus interface signals
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
always_comb begin
|
always_comb begin
|
||||||
{{cpuif.signal("PREADY")}} = 1'b0;
|
{{cpuif.signal("PREADY")}} = 1'b0;
|
||||||
{{cpuif.signal("PRDATA")}} = '0;
|
{{cpuif.signal("PRDATA")}} = '0;
|
||||||
{{cpuif.signal("PSLVERR")}} = 1'b0;
|
{{cpuif.signal("PSLVERR")}} = 1'b0;
|
||||||
|
|
||||||
for(int i = 0; i < {{cpuif.addressable_children | length}}; i++) begin
|
|
||||||
if (cpuif_rd_sel[i]) begin
|
{%- for child in cpuif.addressable_children -%}
|
||||||
{{cpuif.signal("PREADY")}} = cpuif_rd_ack[i];
|
{%- if loop.first %}
|
||||||
{{cpuif.signal("PRDATA")}} = cpuif_rd_data[i];
|
if (cpuif_rd_sel.{{child|get_path}}) begin
|
||||||
{{cpuif.signal("PSLVERR")}} = cpuif_rd_err[i];
|
{%- else %}
|
||||||
end
|
end else if (cpuif_rd_sel.{{child|get_path}}) begin
|
||||||
|
{%- endif %}
|
||||||
|
{{cpuif.signal("PREADY")}} = cpuif_rd_ack[{{loop.index}}];
|
||||||
|
{{cpuif.signal("PRDATA")}} = cpuif_rd_data[{{loop.index}}];
|
||||||
|
{{cpuif.signal("PSLVERR")}} = cpuif_rd_err[{{loop.index}}];
|
||||||
|
{%- endfor %}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
// for(int i = 0; i < {{cpuif.addressable_children | length}}; i++) begin
|
||||||
|
// if (cpuif_rd_sel[i]) begin
|
||||||
|
// {{cpuif.signal("PREADY")}} = cpuif_rd_ack[i];
|
||||||
|
// {{cpuif.signal("PRDATA")}} = cpuif_rd_data[i];
|
||||||
|
// {{cpuif.signal("PSLVERR")}} = cpuif_rd_err[i];
|
||||||
|
// end
|
||||||
|
// end
|
||||||
end
|
end
|
||||||
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
|
|||||||
import jinja2 as jj
|
import jinja2 as jj
|
||||||
from systemrdl.node import AddressableNode
|
from systemrdl.node import AddressableNode
|
||||||
|
|
||||||
from ..utils import clog2, is_pow2, roundup_pow2
|
from ..utils import clog2, get_indexed_path, is_pow2, roundup_pow2
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..exporter import BusDecoderExporter
|
from ..exporter import BusDecoderExporter
|
||||||
@@ -83,6 +83,7 @@ class BaseCpuif:
|
|||||||
jj_env.filters["is_pow2"] = is_pow2 # type: ignore
|
jj_env.filters["is_pow2"] = is_pow2 # type: ignore
|
||||||
jj_env.filters["roundup_pow2"] = roundup_pow2 # type: ignore
|
jj_env.filters["roundup_pow2"] = roundup_pow2 # type: ignore
|
||||||
jj_env.filters["address_slice"] = self.get_address_slice # type: ignore
|
jj_env.filters["address_slice"] = self.get_address_slice # type: ignore
|
||||||
|
jj_env.filters["get_path"] = lambda x: get_indexed_path(self.exp.ds.top_node, x, "i") # type: ignore
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"cpuif": self,
|
"cpuif": self,
|
||||||
@@ -92,33 +93,13 @@ class BaseCpuif:
|
|||||||
template = jj_env.get_template(self.template_path)
|
template = jj_env.get_template(self.template_path)
|
||||||
return template.render(context)
|
return template.render(context)
|
||||||
|
|
||||||
def get_address_slice(self, node: AddressableNode) -> str:
|
def get_address_slice(self, node: AddressableNode, cpuif_addr: str = "cpuif_addr") -> str:
|
||||||
"""
|
addr = node.raw_absolute_address - self.exp.ds.top_node.raw_absolute_address
|
||||||
Returns a SystemVerilog expression that extracts the address bits
|
|
||||||
relevant to the given node.
|
|
||||||
"""
|
|
||||||
addr_mask = (1 << self.addr_width) - 1
|
|
||||||
addr = node.absolute_address & addr_mask
|
|
||||||
size = node.size
|
size = node.size
|
||||||
if size == 0:
|
|
||||||
raise ValueError(f"Node size '{size:#X}' must be greater than 0")
|
|
||||||
if (addr % size) != 0:
|
|
||||||
raise ValueError(f"Node address '{addr:#X}' must be aligned to its size '{size:#X}'")
|
|
||||||
|
|
||||||
# Calculate the address range of the node
|
addr_msb = clog2(addr + size) - 1
|
||||||
addr_start = addr
|
addr_lsb = clog2(addr)
|
||||||
addr_end = addr + size - 1
|
|
||||||
if addr_end > addr_mask:
|
|
||||||
raise ValueError("Node address range exceeds address width")
|
|
||||||
|
|
||||||
# Calculate the number of bits needed to represent the size
|
if addr_msb == addr_lsb:
|
||||||
size_bits = size.bit_length() - 1
|
return f"{cpuif_addr}[{addr_lsb}]"
|
||||||
if size_bits < 0:
|
return f"{cpuif_addr}[{addr_msb}:{addr_lsb}]"
|
||||||
size_bits = 0
|
|
||||||
|
|
||||||
if size_bits >= self.addr_width:
|
|
||||||
# Node covers entire address space, so return full address
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# Extract the relevant bits from PADDR
|
|
||||||
return f"[{self.addr_width - 1}:{size_bits}]"
|
|
||||||
|
|||||||
@@ -1,72 +1,92 @@
|
|||||||
from collections import deque
|
from collections import deque
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
from systemrdl.node import AddressableNode
|
from systemrdl.node import AddressableNode
|
||||||
from systemrdl.walker import RDLListener, RDLSimpleWalker, WalkerAction
|
from systemrdl.walker import RDLListener, RDLSimpleWalker, WalkerAction
|
||||||
|
|
||||||
from .body import Body, ForLoopBody, IfBody
|
from .body import Body, ForLoopBody, IfBody
|
||||||
|
from .design_state import DesignState
|
||||||
from .sv_int import SVInt
|
from .sv_int import SVInt
|
||||||
|
from .utils import get_indexed_path
|
||||||
|
|
||||||
|
|
||||||
|
class DecodeLogicFlavor(Enum):
|
||||||
|
READ = "rd"
|
||||||
|
WRITE = "wr"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cpuif_address(self) -> str:
|
||||||
|
return f"cpuif_{self.value}_addr"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cpuif_select(self) -> str:
|
||||||
|
return f"cpuif_{self.value}_sel"
|
||||||
|
|
||||||
|
|
||||||
class AddressDecode:
|
class AddressDecode:
|
||||||
def __init__(self, node: AddressableNode, addr_width: int) -> None:
|
def __init__(self, flavor: DecodeLogicFlavor, ds: DesignState) -> None:
|
||||||
self._node = node
|
self._flavor = flavor
|
||||||
self._addr_width = addr_width
|
self._ds = ds
|
||||||
|
|
||||||
def walk(self) -> str:
|
def walk(self) -> str:
|
||||||
walker = RDLSimpleWalker()
|
walker = RDLSimpleWalker()
|
||||||
dlg = DecodeLogicGenerator(self)
|
dlg = DecodeLogicGenerator(self._flavor, self._ds)
|
||||||
walker.walk(self._node, dlg, skip_top=True)
|
walker.walk(self._ds.top_node, dlg, skip_top=True)
|
||||||
return str(dlg)
|
return str(dlg)
|
||||||
|
|
||||||
@property
|
|
||||||
def node(self) -> AddressableNode:
|
|
||||||
return self._node
|
|
||||||
|
|
||||||
@property
|
|
||||||
def addr_width(self) -> int:
|
|
||||||
return self._addr_width
|
|
||||||
|
|
||||||
|
|
||||||
class DecodeLogicGenerator(RDLListener):
|
class DecodeLogicGenerator(RDLListener):
|
||||||
cpuif_addr_signal = "addr"
|
|
||||||
cpuif_sel_prefix = "cpuif_"
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
address_decoder: AddressDecode,
|
flavor: DecodeLogicFlavor,
|
||||||
max_depth: int = 1,
|
ds: DesignState,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._address_decoder = address_decoder
|
self._ds = ds
|
||||||
self._depth = 0
|
self._flavor = flavor
|
||||||
self._max_depth = max_depth
|
|
||||||
|
|
||||||
self._stack: list[Body] = [IfBody()]
|
self._decode_stack: deque[Body] = deque() # Tracks decoder body
|
||||||
self._conditions: deque[str] = deque()
|
self._cond_stack: deque[str] = deque() # Tracks conditions nested for loops
|
||||||
self._select_signal = [f"{self.cpuif_sel_prefix}{address_decoder.node.inst_name}"]
|
self._array_stride_stack: deque[int] = deque() # Tracks nested array strids
|
||||||
|
|
||||||
# Stack to keep track of array strides for nested arrayed components
|
# Initial Stack Conditions
|
||||||
self._array_stride_stack: list[int] = []
|
self._decode_stack.append(IfBody())
|
||||||
|
|
||||||
def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
|
def cpuif_addr_predicate(self, node: AddressableNode) -> list[str]:
|
||||||
# Generate address bounds
|
# Generate address bounds
|
||||||
addr_width = self._address_decoder.addr_width
|
addr_width = self._ds.addr_width
|
||||||
l_bound = SVInt(
|
l_bound = SVInt(
|
||||||
node.raw_absolute_address - self._address_decoder.node.raw_absolute_address,
|
node.raw_address_offset,
|
||||||
addr_width,
|
addr_width,
|
||||||
)
|
)
|
||||||
u_bound = l_bound + SVInt(node.total_size, addr_width)
|
u_bound = l_bound + SVInt(node.total_size, addr_width)
|
||||||
|
|
||||||
# Handle arrayed components
|
# Handle arrayed components
|
||||||
l_bound_str = str(l_bound)
|
l_bound_comp = [str(l_bound)]
|
||||||
u_bound_str = str(u_bound)
|
u_bound_comp = [str(u_bound)]
|
||||||
for i, stride in enumerate(self._array_stride_stack):
|
for i, stride in enumerate(self._array_stride_stack):
|
||||||
l_bound_str += f" + (({addr_width})'(i{i}) * {SVInt(stride, addr_width)})"
|
l_bound_comp.append(f"({addr_width}'(i{i})*{SVInt(stride, addr_width)})")
|
||||||
u_bound_str += f" + (({addr_width})'(i{i}) * {SVInt(stride, addr_width)})"
|
u_bound_comp.append(f"({addr_width}'(i{i})*{SVInt(stride, addr_width)})")
|
||||||
|
|
||||||
# Generate condition string
|
# Generate Conditions
|
||||||
condition = (
|
return [
|
||||||
f"({self.cpuif_addr_signal} >= ({l_bound_str})) && ({self.cpuif_addr_signal} < ({u_bound_str}))"
|
f"{self._flavor.cpuif_address} >= ({'+'.join(l_bound_comp)})",
|
||||||
)
|
f"{self._flavor.cpuif_address} < ({'+'.join(u_bound_comp)})",
|
||||||
|
]
|
||||||
|
|
||||||
|
def cpuif_prot_predicate(self, node: AddressableNode) -> list[str]:
|
||||||
|
if self._flavor == DecodeLogicFlavor.READ:
|
||||||
|
# Can we have PROT on read? (axi full?)
|
||||||
|
return []
|
||||||
|
|
||||||
|
# TODO: Implement
|
||||||
|
return []
|
||||||
|
|
||||||
|
def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
|
||||||
|
conditions: list[str] = []
|
||||||
|
conditions.extend(self.cpuif_addr_predicate(node))
|
||||||
|
conditions.extend(self.cpuif_prot_predicate(node))
|
||||||
|
|
||||||
|
condition = " && ".join(f"({c})" for c in conditions)
|
||||||
|
|
||||||
if node.array_dimensions:
|
if node.array_dimensions:
|
||||||
assert node.array_stride is not None, "Array stride should be defined for arrayed components"
|
assert node.array_stride is not None, "Array stride should be defined for arrayed components"
|
||||||
@@ -80,26 +100,25 @@ class DecodeLogicGenerator(RDLListener):
|
|||||||
self._array_stride_stack.extend(strides)
|
self._array_stride_stack.extend(strides)
|
||||||
|
|
||||||
# Generate condition string and manage stack
|
# Generate condition string and manage stack
|
||||||
signal = node.inst_name
|
if isinstance(self._decode_stack[-1], IfBody) and node.array_dimensions:
|
||||||
if isinstance(self._stack[-1], IfBody) and node.array_dimensions:
|
|
||||||
# arrayed component with new if-body
|
# arrayed component with new if-body
|
||||||
self._conditions.append(condition)
|
self._cond_stack.append(condition)
|
||||||
for dim in node.array_dimensions:
|
for i, dim in enumerate(
|
||||||
|
node.array_dimensions,
|
||||||
|
start=len(self._array_stride_stack) - len(node.array_dimensions),
|
||||||
|
):
|
||||||
fb = ForLoopBody(
|
fb = ForLoopBody(
|
||||||
"int",
|
"int",
|
||||||
f"i{self._depth}",
|
f"i{i}",
|
||||||
dim,
|
dim,
|
||||||
)
|
)
|
||||||
self._stack.append(fb)
|
self._decode_stack.append(fb)
|
||||||
signal += f"[i{self._depth}]"
|
|
||||||
self._depth += 1
|
|
||||||
|
|
||||||
self._stack.append(IfBody())
|
self._decode_stack.append(IfBody())
|
||||||
elif isinstance(self._stack[-1], IfBody):
|
elif isinstance(self._decode_stack[-1], IfBody):
|
||||||
# non-arrayed component with if-body
|
# non-arrayed component with if-body
|
||||||
with self._stack[-1].cm(condition) as b:
|
with self._decode_stack[-1].cm(condition) as b:
|
||||||
b += f"{'.'.join([*self._select_signal, signal])} = 1'b1;"
|
b += f"{self._flavor.cpuif_select}.{get_indexed_path(self._ds.top_node, node)} = 1'b1;"
|
||||||
self._select_signal.append(signal)
|
|
||||||
|
|
||||||
# if node.external:
|
# if node.external:
|
||||||
# return WalkerAction.SkipDescendants
|
# return WalkerAction.SkipDescendants
|
||||||
@@ -107,26 +126,34 @@ class DecodeLogicGenerator(RDLListener):
|
|||||||
return WalkerAction.Continue
|
return WalkerAction.Continue
|
||||||
|
|
||||||
def exit_AddressableComponent(self, node: AddressableNode) -> None:
|
def exit_AddressableComponent(self, node: AddressableNode) -> None:
|
||||||
self._select_signal.pop()
|
|
||||||
|
|
||||||
if not node.array_dimensions:
|
if not node.array_dimensions:
|
||||||
return
|
return
|
||||||
|
|
||||||
ifb = self._stack.pop()
|
ifb = self._decode_stack.pop()
|
||||||
self._stack[-1] += ifb
|
if ifb:
|
||||||
|
self._decode_stack[-1] += ifb
|
||||||
|
else:
|
||||||
|
self._decode_stack[-1] += (
|
||||||
|
f"{self._flavor.cpuif_select}.{get_indexed_path(self._ds.top_node, node)} = 1'b1;"
|
||||||
|
)
|
||||||
|
|
||||||
for _ in node.array_dimensions:
|
for _ in node.array_dimensions:
|
||||||
self._depth -= 1
|
b = self._decode_stack.pop()
|
||||||
|
if not b:
|
||||||
|
continue
|
||||||
|
|
||||||
b = self._stack.pop()
|
if isinstance(self._decode_stack[-1], IfBody):
|
||||||
if b.lines:
|
with self._decode_stack[-1].cm(self._cond_stack.pop()) as parent_b:
|
||||||
if isinstance(self._stack[-1], IfBody):
|
parent_b += b
|
||||||
with self._stack[-1].cm(self._conditions.pop()) as parent_b:
|
else:
|
||||||
parent_b += b
|
self._decode_stack[-1] += b
|
||||||
else:
|
|
||||||
self._stack[-1] += b
|
|
||||||
|
|
||||||
self._array_stride_stack.pop()
|
self._array_stride_stack.pop()
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return str(self._stack[-1])
|
body = self._decode_stack[-1]
|
||||||
|
if isinstance(body, IfBody):
|
||||||
|
with body.cm(...) as b:
|
||||||
|
b += f"{self._flavor.cpuif_select}.bad_addr = 1'b1;"
|
||||||
|
|
||||||
|
return str(body)
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from systemrdl.node import RegNode
|
from systemrdl.node import AddressableNode, AddrmapNode, Node, RegNode
|
||||||
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
|
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from systemrdl.node import AddressableNode, AddrmapNode, Node
|
from .design_state import DesignState
|
||||||
|
|
||||||
from .exporter import DesignState
|
|
||||||
|
|
||||||
|
|
||||||
class DesignScanner(RDLListener):
|
class DesignScanner(RDLListener):
|
||||||
@@ -22,7 +20,7 @@ class DesignScanner(RDLListener):
|
|||||||
self.msg = self.top_node.env.msg
|
self.msg = self.top_node.env.msg
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def top_node(self) -> "AddrmapNode":
|
def top_node(self) -> AddrmapNode:
|
||||||
return self.ds.top_node
|
return self.ds.top_node
|
||||||
|
|
||||||
def do_scan(self) -> None:
|
def do_scan(self) -> None:
|
||||||
@@ -30,7 +28,7 @@ class DesignScanner(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") -> WalkerAction | None:
|
def enter_Component(self, node: Node) -> WalkerAction:
|
||||||
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
|
||||||
@@ -41,7 +39,7 @@ class DesignScanner(RDLListener):
|
|||||||
|
|
||||||
return WalkerAction.Continue
|
return WalkerAction.Continue
|
||||||
|
|
||||||
def enter_AddressableComponent(self, node: "AddressableNode") -> None:
|
def enter_AddressableComponent(self, node: AddressableNode) -> None:
|
||||||
if node.external and node != self.top_node:
|
if node.external and node != self.top_node:
|
||||||
self.ds.has_external_addressable = True
|
self.ds.has_external_addressable = True
|
||||||
if not isinstance(node, RegNode):
|
if not isinstance(node, RegNode):
|
||||||
72
src/peakrdl_busdecoder/design_state.py
Normal file
72
src/peakrdl_busdecoder/design_state.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from systemrdl.node import AddrmapNode
|
||||||
|
from systemrdl.rdltypes.user_enum import UserEnum
|
||||||
|
|
||||||
|
from .design_scanner import DesignScanner
|
||||||
|
from .identifier_filter import kw_filter as kwf
|
||||||
|
from .utils import clog2
|
||||||
|
|
||||||
|
|
||||||
|
class DesignStateKwargs(TypedDict, total=False):
|
||||||
|
reuse_hwif_typedefs: bool
|
||||||
|
module_name: str
|
||||||
|
package_name: str
|
||||||
|
address_width: int
|
||||||
|
cpuif_unroll: bool
|
||||||
|
|
||||||
|
|
||||||
|
class DesignState:
|
||||||
|
"""
|
||||||
|
Dumping ground for all sorts of variables that are relevant to a particular
|
||||||
|
design.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, top_node: AddrmapNode, kwargs: DesignStateKwargs) -> 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 f"{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]] = []
|
||||||
|
|
||||||
|
self.has_external_addressable = False
|
||||||
|
self.has_external_block = False
|
||||||
|
|
||||||
|
# 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 None:
|
||||||
|
return
|
||||||
|
|
||||||
|
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,53 +1,63 @@
|
|||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from importlib.metadata import version
|
from importlib.metadata import version
|
||||||
from typing import TYPE_CHECKING, Any
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, TypedDict
|
||||||
|
|
||||||
import jinja2 as jj
|
import jinja2 as jj
|
||||||
from systemrdl.node import AddrmapNode, RootNode
|
from systemrdl.node import AddrmapNode, RootNode
|
||||||
from systemrdl.rdltypes.user_enum import UserEnum
|
from typing_extensions import Unpack
|
||||||
|
|
||||||
from .cpuif import BaseCpuif
|
from .cpuif import BaseCpuif
|
||||||
from .cpuif.apb4 import APB4Cpuif
|
from .cpuif.apb4 import APB4Cpuif
|
||||||
from .decoder import AddressDecode
|
from .decoder import AddressDecode, DecodeLogicFlavor
|
||||||
|
from .design_state import DesignState
|
||||||
from .identifier_filter import kw_filter as kwf
|
from .identifier_filter import kw_filter as kwf
|
||||||
from .scan_design import DesignScanner
|
|
||||||
from .sv_int import SVInt
|
from .sv_int import SVInt
|
||||||
from .utils import clog2
|
|
||||||
from .validate_design import DesignValidator
|
from .validate_design import DesignValidator
|
||||||
|
|
||||||
|
|
||||||
|
class ExporterKwargs(TypedDict, total=False):
|
||||||
|
cpuif_cls: type[BaseCpuif]
|
||||||
|
module_name: str
|
||||||
|
package_name: str
|
||||||
|
address_width: int
|
||||||
|
cpuif_unroll: bool
|
||||||
|
reuse_hwif_typedefs: bool
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BusDecoderExporter:
|
class BusDecoderExporter:
|
||||||
cpuif: BaseCpuif
|
cpuif: BaseCpuif
|
||||||
address_decode: AddressDecode
|
address_decode: type[AddressDecode]
|
||||||
ds: "DesignState"
|
ds: DesignState
|
||||||
|
|
||||||
def __init__(self, **kwargs: Any) -> None:
|
def __init__(self, **kwargs: Unpack[ExporterKwargs]) -> None:
|
||||||
# Check for stray kwargs
|
# Check for stray kwargs
|
||||||
if kwargs:
|
if kwargs:
|
||||||
raise TypeError(f"got an unexpected keyword argument '{next(iter(kwargs.keys()))}'")
|
raise TypeError(f"got an unexpected keyword argument '{next(iter(kwargs.keys()))}'")
|
||||||
|
|
||||||
loader = jj.ChoiceLoader(
|
fs_loader = jj.FileSystemLoader(Path(__file__).parent)
|
||||||
|
c_loader = jj.ChoiceLoader(
|
||||||
[
|
[
|
||||||
jj.FileSystemLoader(os.path.dirname(__file__)),
|
fs_loader,
|
||||||
jj.PrefixLoader(
|
jj.PrefixLoader(
|
||||||
{
|
{"base": fs_loader},
|
||||||
"base": jj.FileSystemLoader(os.path.dirname(__file__)),
|
|
||||||
},
|
|
||||||
delimiter=":",
|
delimiter=":",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.jj_env = jj.Environment(
|
self.jj_env = jj.Environment(
|
||||||
loader=loader,
|
loader=c_loader,
|
||||||
undefined=jj.StrictUndefined,
|
undefined=jj.StrictUndefined,
|
||||||
)
|
)
|
||||||
|
self.jj_env.filters["kwf"] = kwf
|
||||||
|
|
||||||
def export(self, node: RootNode | AddrmapNode, output_dir: str, **kwargs: Any) -> None:
|
def export(self, node: RootNode | AddrmapNode, output_dir: str, **kwargs: Unpack[ExporterKwargs]) -> None:
|
||||||
"""
|
"""
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@@ -68,7 +78,7 @@ class BusDecoderExporter:
|
|||||||
address_width: int
|
address_width: int
|
||||||
Override the CPU interface's address width. By default, address width
|
Override the CPU interface's address width. By default, address width
|
||||||
is sized to the contents of the busdecoder.
|
is sized to the contents of the busdecoder.
|
||||||
unroll: bool
|
cpuif_unroll: bool
|
||||||
Unroll arrayed addressable nodes into separate instances in the CPU
|
Unroll arrayed addressable nodes into separate instances in the CPU
|
||||||
interface. By default, arrayed nodes are kept as arrays.
|
interface. By default, arrayed nodes are kept as arrays.
|
||||||
"""
|
"""
|
||||||
@@ -88,7 +98,7 @@ class BusDecoderExporter:
|
|||||||
|
|
||||||
# Construct exporter components
|
# Construct exporter components
|
||||||
self.cpuif = cpuif_cls(self)
|
self.cpuif = cpuif_cls(self)
|
||||||
self.address_decode = AddressDecode(top_node, self.ds.addr_width)
|
self.address_decode = AddressDecode
|
||||||
|
|
||||||
# Validate that there are no unsupported constructs
|
# Validate that there are no unsupported constructs
|
||||||
DesignValidator(self).do_validate()
|
DesignValidator(self).do_validate()
|
||||||
@@ -99,8 +109,8 @@ class BusDecoderExporter:
|
|||||||
"version": version("peakrdl-busdecoder"),
|
"version": version("peakrdl-busdecoder"),
|
||||||
"cpuif": self.cpuif,
|
"cpuif": self.cpuif,
|
||||||
"address_decode": self.address_decode,
|
"address_decode": self.address_decode,
|
||||||
|
"DecodeLogicFlavor": DecodeLogicFlavor,
|
||||||
"ds": self.ds,
|
"ds": self.ds,
|
||||||
"kwf": kwf,
|
|
||||||
"SVInt": SVInt,
|
"SVInt": SVInt,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,59 +125,3 @@ class BusDecoderExporter:
|
|||||||
template = self.jj_env.get_template("module_tmpl.sv")
|
template = self.jj_env.get_template("module_tmpl.sv")
|
||||||
stream = template.stream(context)
|
stream = template.stream(context)
|
||||||
stream.dump(module_file_path)
|
stream.dump(module_file_path)
|
||||||
|
|
||||||
|
|
||||||
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]] = []
|
|
||||||
|
|
||||||
self.has_external_addressable = False
|
|
||||||
self.has_external_block = False
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|||||||
@@ -39,8 +39,11 @@ module {{ds.module_name}}
|
|||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Child instance signals
|
// Child instance signals
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
logic [{{cpuif.addressable_children | length}}-1:0] cpuif_wr_sel;
|
typedef struct packed {
|
||||||
logic [{{cpuif.addressable_children | length}}-1:0] cpuif_rd_sel;
|
|
||||||
|
} cpuif_sel_t;
|
||||||
|
cpuif_sel_t cpuif_wr_sel;
|
||||||
|
cpuif_sel_t cpuif_rd_sel;
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Slave <-> Internal CPUIF <-> Master
|
// Slave <-> Internal CPUIF <-> Master
|
||||||
@@ -56,19 +59,7 @@ module {{ds.module_name}}
|
|||||||
|
|
||||||
if (cpuif_req && cpuif_wr_en) begin
|
if (cpuif_req && cpuif_wr_en) begin
|
||||||
// A write request is pending
|
// A write request is pending
|
||||||
{%- for child in cpuif.addressable_children -%}
|
{{address_decode(DecodeLogicFlavor.WRITE, ds).walk()|indent(12)}}
|
||||||
{%- if loop.first -%}
|
|
||||||
if {{child|address_decode}} begin
|
|
||||||
{%- else -%}
|
|
||||||
end else if {{child|address_decode}} begin
|
|
||||||
{%- endif -%}
|
|
||||||
// Address matched for {{child.inst_name}}
|
|
||||||
cpuif_wr_sel[{{loop.index}}] = 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
|
end else begin
|
||||||
// No write request, all select signals remain 0
|
// No write request, all select signals remain 0
|
||||||
end
|
end
|
||||||
@@ -83,19 +74,7 @@ module {{ds.module_name}}
|
|||||||
|
|
||||||
if (cpuif_req && cpuif_rd_en) begin
|
if (cpuif_req && cpuif_rd_en) begin
|
||||||
// A read request is pending
|
// A read request is pending
|
||||||
{%- for child in cpuif.addressable_children -%}
|
{{address_decode(DecodeLogicFlavor.READ, ds).walk()|indent(12)}}
|
||||||
{%- if loop.first -%}
|
|
||||||
if {{child|address_decode}} begin
|
|
||||||
{%- else -%}
|
|
||||||
end else if {{child|address_decode}} begin
|
|
||||||
{%- endif -%}
|
|
||||||
// Address matched for {{child.inst_name}}
|
|
||||||
cpuif_rd_sel[{{loop.index}}] = 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
|
end else begin
|
||||||
// No read request, all select signals remain 0
|
// No read request, all select signals remain 0
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import re
|
import re
|
||||||
from re import Match
|
from re import Match
|
||||||
from typing import overload
|
|
||||||
|
|
||||||
from systemrdl.node import AddrmapNode, Node
|
from systemrdl.node import AddrmapNode, Node
|
||||||
from systemrdl.rdltypes.references import PropertyReference
|
from systemrdl.rdltypes.references import PropertyReference
|
||||||
|
|
||||||
from .identifier_filter import kw_filter as kwf
|
from .identifier_filter import kw_filter as kwf
|
||||||
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, indexer: str = "i") -> str:
|
||||||
"""
|
"""
|
||||||
TODO: Add words about indexing and why i'm doing this. Copy from logbook
|
Get the relative path from top_node to target_node, replacing any unknown
|
||||||
|
array indexes with incrementing iterators (i0, i1, ...).
|
||||||
"""
|
"""
|
||||||
path = target_node.get_rel_path(top_node, empty_array_suffix="[!]")
|
path = target_node.get_rel_path(top_node, empty_array_suffix="[!]")
|
||||||
|
|
||||||
@@ -21,7 +20,7 @@ def get_indexed_path(top_node: Node, target_node: Node) -> str:
|
|||||||
self.i = 0
|
self.i = 0
|
||||||
|
|
||||||
def __call__(self, match: Match[str]) -> str:
|
def __call__(self, match: Match[str]) -> str:
|
||||||
s = f"i{self.i}"
|
s = f"{indexer}{self.i}"
|
||||||
self.i += 1
|
self.i += 1
|
||||||
return s
|
return s
|
||||||
|
|
||||||
@@ -54,13 +53,10 @@ def ref_is_internal(top_node: AddrmapNode, ref: Node | PropertyReference) -> boo
|
|||||||
|
|
||||||
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]
|
if isinstance(ref, PropertyReference):
|
||||||
if isinstance(ref, Node):
|
|
||||||
current_node = ref
|
|
||||||
elif isinstance(ref, PropertyReference):
|
|
||||||
current_node = ref.node
|
current_node = ref.node
|
||||||
else:
|
else:
|
||||||
raise RuntimeError
|
current_node = ref
|
||||||
|
|
||||||
while current_node is not None:
|
while current_node is not None:
|
||||||
if current_node == top_node:
|
if current_node == top_node:
|
||||||
@@ -77,46 +73,3 @@ def ref_is_internal(top_node: AddrmapNode, ref: Node | PropertyReference) -> boo
|
|||||||
# A root signal was referenced, which dodged the top addrmap
|
# A root signal was referenced, which dodged the top addrmap
|
||||||
# This is considered internal for this exporter
|
# This is considered internal for this exporter
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def do_slice(value: SVInt, high: int, low: int) -> SVInt: ...
|
|
||||||
@overload
|
|
||||||
def do_slice(value: str, high: int, low: int) -> str: ...
|
|
||||||
def do_slice(value: SVInt | str, high: int, low: int) -> SVInt | str:
|
|
||||||
if isinstance(value, str):
|
|
||||||
# If string, assume this is an identifier. Append bit-slice
|
|
||||||
if high == low:
|
|
||||||
return f"{value}[{low}]"
|
|
||||||
else:
|
|
||||||
return f"{value}[{high}:{low}]"
|
|
||||||
else:
|
|
||||||
# it is an SVInt literal. Slice it down
|
|
||||||
mask = (1 << (high + 1)) - 1
|
|
||||||
v = (value.value & mask) >> low
|
|
||||||
|
|
||||||
if value.width is not None:
|
|
||||||
w = high - low + 1
|
|
||||||
else:
|
|
||||||
w = None
|
|
||||||
|
|
||||||
return SVInt(v, w)
|
|
||||||
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def do_bitswap(value: SVInt) -> SVInt: ...
|
|
||||||
@overload
|
|
||||||
def do_bitswap(value: str) -> str: ...
|
|
||||||
def do_bitswap(value: SVInt | str) -> SVInt | str:
|
|
||||||
if isinstance(value, str):
|
|
||||||
# If string, assume this is an identifier. Wrap in a streaming operator
|
|
||||||
return "{<<{" + value + "}}"
|
|
||||||
else:
|
|
||||||
# it is an SVInt literal. bitswap it
|
|
||||||
assert value.width is not None # width must be known!
|
|
||||||
v = value.value
|
|
||||||
vswap = 0
|
|
||||||
for _ in range(value.width):
|
|
||||||
vswap = (vswap << 1) + (v & 1)
|
|
||||||
v >>= 1
|
|
||||||
return SVInt(vswap, value.width)
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from systemrdl.node import AddressableNode, AddrmapNode, FieldNode, Node, RegfileNode, RegNode, SignalNode
|
from systemrdl.node import AddressableNode, AddrmapNode, FieldNode, Node, RegfileNode, RegNode, SignalNode
|
||||||
from systemrdl.rdltypes import PropertyReference
|
from systemrdl.rdltypes.references import PropertyReference
|
||||||
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
|
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
|
||||||
|
|
||||||
from .utils import is_pow2, ref_is_internal, roundup_pow2
|
from .utils import is_pow2, ref_is_internal, roundup_pow2
|
||||||
@@ -148,21 +148,6 @@ class DesignValidator(RDLListener):
|
|||||||
node.inst.inst_src_ref,
|
node.inst.inst_src_ref,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check for unsynthesizable reset
|
|
||||||
reset = node.get_property("reset")
|
|
||||||
if not (reset is None or isinstance(reset, int)):
|
|
||||||
# Has reset that is not a constant value
|
|
||||||
resetsignal = node.get_property("resetsignal")
|
|
||||||
if resetsignal:
|
|
||||||
is_async_reset = resetsignal.get_property("async")
|
|
||||||
else:
|
|
||||||
is_async_reset = self.ds.default_reset_async
|
|
||||||
|
|
||||||
if is_async_reset:
|
|
||||||
self.msg.error(
|
|
||||||
"A field that uses an asynchronous reset cannot use a dynamic reset value. This is not synthesizable.",
|
|
||||||
node.inst.inst_src_ref,
|
|
||||||
)
|
|
||||||
|
|
||||||
def exit_AddressableComponent(self, node: AddressableNode) -> None:
|
def exit_AddressableComponent(self, node: AddressableNode) -> None:
|
||||||
if not isinstance(node, RegNode):
|
if not isinstance(node, RegNode):
|
||||||
|
|||||||
Reference in New Issue
Block a user