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

View File

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

View File

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

View File

@@ -0,0 +1,79 @@
from systemrdl.node import AddressableNode
from ..base_cpuif import BaseCpuif
class APB3Cpuif(BaseCpuif):
template_path = "apb3_tmpl.sv"
is_interface = True
def _port_declaration(self, child: AddressableNode) -> str:
base = f"apb3_intf.master m_apb_{child.inst_name}"
if not child.is_array:
return base
if child.current_idx is not None:
return f"{base}_{'_'.join(map(str, child.current_idx))} [N_{child.inst_name.upper()}S]"
return f"{base} [N_{child.inst_name.upper()}S]"
@property
def port_declaration(self) -> str:
slave_ports: list[str] = ["apb3_intf.slave s_apb"]
master_ports: list[str] = list(
map(self._port_declaration, self.addressable_children)
)
return ",\n".join(slave_ports + master_ports)
def signal(
self,
signal: str,
node: AddressableNode | None = None,
idx: str | int | None = None,
) -> str:
if node is None:
# Node is none, so this is a slave signal
return f"s_apb.{signal}"
# Master signal
base = f"m_apb_{node.inst_name}"
if not node.is_array:
return f"{base}.{signal}"
if node.current_idx is not None:
# This is a specific instance of an array
return f"{base}_{'_'.join(map(str, node.current_idx))}.{signal}"
if idx is not None:
return f"{base}[{idx}].{signal}"
raise ValueError("Must provide an index for arrayed interface signals")
def get_address_predicate(self, node: AddressableNode) -> str:
"""
Returns a SystemVerilog expression that evaluates to true when the
address on the bus matches the address range of the given node.
"""
addr_mask = (1 << self.addr_width) - 1
addr = node.absolute_address & addr_mask
size = node.size
if size == 0:
raise ValueError("Node size must be greater than 0")
if (addr % size) != 0:
raise ValueError("Node address must be aligned to its size")
# Calculate the address range of the node
addr_start = addr
addr_end = addr + size - 1
if addr_end > addr_mask:
raise ValueError("Node address range exceeds address width")
if addr_start == addr_end:
return f"({self.signal('PADDR')} == 'h{addr_start:X})"
return f"({self.signal('PADDR')} >= 'h{addr_start:X} && {self.signal('PADDR')} <= 'h{addr_end:X})"
def get_address_decode_condition(self, node: AddressableNode) -> str:
"""
Returns a SystemVerilog expression that evaluates to true when the
address on the bus matches the address range of the given node.
"""
addr_pred = self.get_address_predicate(node)
return addr_pred

View File

@@ -0,0 +1,62 @@
from systemrdl.node import AddressableNode
from ..base_cpuif import BaseCpuif
class APB3CpuifFlat(BaseCpuif):
template_path = "apb3_tmpl.sv"
is_interface = False
def _port_declaration(self, child: AddressableNode) -> list[str]:
return [
f"input logic {self.signal('PCLK', child)}",
f"input logic {self.signal('PRESETn', child)}",
f"input logic {self.signal('PSELx', child)}",
f"input logic {self.signal('PENABLE', child)}",
f"input logic {self.signal('PWRITE', child)}",
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR', child)}",
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA', child)}",
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA', child)}",
f"output logic {self.signal('PREADY', child)}",
f"output logic {self.signal('PSLVERR', child)}",
]
@property
def port_declaration(self) -> str:
slave_ports: list[str] = [
f"input logic {self.signal('PCLK')}",
f"input logic {self.signal('PRESETn')}",
f"input logic {self.signal('PSELx')}",
f"input logic {self.signal('PENABLE')}",
f"input logic {self.signal('PWRITE')}",
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR')}",
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA')}",
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA')}",
f"output logic {self.signal('PREADY')}",
f"output logic {self.signal('PSLVERR')}",
]
master_ports: list[str] = []
for child in self.addressable_children:
master_ports.extend(self._port_declaration(child))
return ",\n".join(slave_ports + master_ports)
def signal(
self,
signal: str,
node: AddressableNode | None = None,
idx: str | int | None = None,
) -> str:
if node is None:
# Node is none, so this is a slave signal
return f"s_apb_{signal}"
# Master signal
base = f"m_apb_{node.inst_name}"
if not node.is_array:
return f"{base}_{signal}"
if node.current_idx is not None:
# This is a specific instance of an array
return f"{base}_{signal}_{'_'.join(map(str, node.current_idx))}"
if idx is not None:
return f"{base}_{signal}[{idx}]"
return f"{base}_{signal}[N_{node.inst_name.upper()}S]"

View File

@@ -0,0 +1,115 @@
{%- if cpuif.is_interface -%}
`ifndef SYNTHESIS
initial begin
assert_bad_addr_width: assert($bits({{cpuif.signal("PADDR")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH)
else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("PADDR")}}), {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH);
assert_bad_data_width: assert($bits({{cpuif.signal("PWDATA")}}) == {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH)
else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("PWDATA")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH);
end
`endif
{% endif -%}
//======================================================
// APB Fanout
//======================================================
{%- for child in cpuif.addressable_children -%}
{%- if child is array -%}
for (genvar g_{{child.inst_name|lower}}_idx = 0; g_{{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; g_{{child.inst_name|lower}}_idx++) begin : g_passthrough_{{child.inst_name|lower}}
assign {{self.signal("PCLK", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PCLK")}};
assign {{self.signal("PRESETn", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PRESETn")}};
assign {{self.signal("PENABLE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PENABLE")}};
assign {{self.signal("PWRITE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWRITE")}};
assign {{self.signal("PADDR", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing
assign {{self.signal("PWDATA", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWDATA")}};
end
{%- else -%}
assign {{self.signal("PCLK", child)}} = {{self.signal("PCLK")}};
assign {{self.signal("PRESETn", child)}} = {{self.signal("PRESETn")}};
assign {{self.signal("PENABLE", child)}} = {{self.signal("PENABLE")}};
assign {{self.signal("PWRITE", child)}} = {{self.signal("PWRITE")}};
assign {{self.signal("PADDR", child)}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing
assign {{self.signal("PWDATA", child)}} = {{self.signal("PWDATA")}};
{%- endif -%}
{%- endfor -%}
//======================================================
// Address Decode Logic
//======================================================
always_comb begin
// Default all PSELx signals to 0
{%- for child in cpuif.addressable_children -%}
{%- if child is array -%}
for (int {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
{{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b0;
end
{%- else -%}
{{self.signal("PSELx", child)}} = 1'b0;
{%- endif -%}
{%- endfor -%}
if ({{self.signal("PSELx")}}) begin
{%- for child in cpuif.addressable_children -%}
{%- if loop.first -%}
if ({{cpuif.get_address_decode_condition(child)}}) begin
{%- else -%}
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
{%- endif -%}
// Address matched for {{child.inst_name}}
{%- if child is array -%}
for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
{{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b1;
end
{%- else -%}
{{self.signal("PSELx", child)}} = 1'b1;
{%- endif -%}
{%- if loop.last -%}
end else begin
// No address matched
{%- endif -%}
{%- endfor -%}
end
end else begin
// PSELx is low, nothing to do
end
end
//======================================================
// Read Data Mux
//======================================================
always_comb begin
// Default read data to 0
{{self.signal("PRDATA")}} = '0;
{{self.signal("PREADY")}} = 1'b1;
{{self.signal("PSLVERR")}} = 1'b0;
if ({{self.signal("PSELx")}} && !{{self.signal("PWRITE")}} && {{self.signal("PENABLE")}}) begin
{%- for child in cpuif.addressable_children -%}
{%- if loop.first -%}
if ({{cpuif.get_address_decode_condition(child)}}) begin
{%- else -%}
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
{%- endif -%}
// Address matched for {{child.inst_name}}
{%- if child is array -%}
for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
{{self.signal("PRDATA")}} = {{self.signal("PRDATA", child, f"{child.inst_name.lower()}_idx")}};
{{self.signal("PREADY")}} = {{self.signal("PREADY", child, f"{child.inst_name.lower()}_idx")}};
{{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child, f"{child.inst_name.lower()}_idx")}};
end
{%- else -%}
{{self.signal("PRDATA")}} = {{self.signal("PRDATA", child)}};
{{self.signal("PREADY")}} = {{self.signal("PREADY", child)}};
{{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child)}};
{%- endif -%}
{%- if loop.last -%}
end else begin
// No address matched
{{self.signal("PRDATA")}} = {'hdeadbeef}[{{ds.data_width - 1}}:0]; // Indicate error on no match
{{self.signal("PSLVERR")}} = 1'b1; // Indicate error on no match
end
{%- endif -%}
{%- endfor -%}
end else begin
// Not a read transfer, nothing to do
end
end

View File

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

View File

@@ -0,0 +1,81 @@
from systemrdl.node import AddressableNode
from ..base_cpuif import BaseCpuif
class APB4Cpuif(BaseCpuif):
template_path = "apb4_tmpl.sv"
is_interface = True
def _port_declaration(self, child: AddressableNode) -> str:
base = f"apb4_intf.master m_apb_{child.inst_name}"
if not child.is_array:
return base
if child.current_idx is not None:
return f"{base}_{'_'.join(map(str, child.current_idx))} [N_{child.inst_name.upper()}S]"
return f"{base} [N_{child.inst_name.upper()}S]"
@property
def port_declaration(self) -> str:
"""Returns the port declaration for the APB4 interface."""
slave_ports: list[str] = ["apb4_intf.slave s_apb"]
master_ports: list[str] = list(
map(self._port_declaration, self.addressable_children)
)
return ",\n".join(slave_ports + master_ports)
def signal(
self,
signal: str,
node: AddressableNode | None = None,
idx: str | int | None = None,
) -> str:
"""Returns the signal name for the given signal and node."""
if node is None:
# Node is none, so this is a slave signal
return f"s_apb.{signal}"
# Master signal
base = f"m_apb_{node.inst_name}"
if not node.is_array:
return f"{base}.{signal}"
if node.current_idx is not None:
# This is a specific instance of an array
return f"{base}_{'_'.join(map(str, node.current_idx))}.{signal}"
if idx is not None:
return f"{base}[{idx}].{signal}"
raise ValueError("Must provide an index for arrayed interface signals")
def get_address_predicate(self, node: AddressableNode) -> str:
"""
Returns a SystemVerilog expression that evaluates to true when the
address on the bus matches the address range of the given node.
"""
addr_mask = (1 << self.addr_width) - 1
addr = node.absolute_address & addr_mask
size = node.size
if size == 0:
raise ValueError("Node size must be greater than 0")
if (addr % size) != 0:
raise ValueError("Node address must be aligned to its size")
# Calculate the address range of the node
addr_start = addr
addr_end = addr + size - 1
if addr_end > addr_mask:
raise ValueError("Node address range exceeds address width")
if addr_start == addr_end:
return f"({self.signal('PADDR')} == 'h{addr_start:X})"
return f"({self.signal('PADDR')} >= 'h{addr_start:X} && {self.signal('PADDR')} <= 'h{addr_end:X})"
def get_address_decode_condition(self, node: AddressableNode) -> str:
"""
Returns a SystemVerilog expression that evaluates to true when the
address on the bus matches the address range of the given node.
"""
addr_pred = self.get_address_predicate(node)
return addr_pred

View File

@@ -0,0 +1,66 @@
from systemrdl.node import AddressableNode
from ..base_cpuif import BaseCpuif
class APB4CpuifFlat(BaseCpuif):
template_path = "apb4_tmpl.sv"
is_interface = False
def _port_declaration(self, child: AddressableNode) -> list[str]:
return [
f"input logic {self.signal('PCLK', child)}",
f"input logic {self.signal('PRESETn', child)}",
f"input logic {self.signal('PSELx', child)}",
f"input logic {self.signal('PENABLE', child)}",
f"input logic {self.signal('PWRITE', child)}",
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR', child)}",
f"input logic [2:0] {self.signal('PPROT', child)}",
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA', child)}",
f"input logic [{self.data_width // 8 - 1}:0] {self.signal('PSTRB', child)}",
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA', child)}",
f"output logic {self.signal('PREADY', child)}",
f"output logic {self.signal('PSLVERR', child)}",
]
@property
def port_declaration(self) -> str:
slave_ports: list[str] = [
f"input logic {self.signal('PCLK')}",
f"input logic {self.signal('PRESETn')}",
f"input logic {self.signal('PSELx')}",
f"input logic {self.signal('PENABLE')}",
f"input logic {self.signal('PWRITE')}",
f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR')}",
f"input logic [2:0] {self.signal('PPROT')}",
f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA')}",
f"input logic [{self.data_width // 8 - 1}:0] {self.signal('PSTRB')}",
f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA')}",
f"output logic {self.signal('PREADY')}",
f"output logic {self.signal('PSLVERR')}",
]
master_ports: list[str] = []
for child in self.addressable_children:
master_ports.extend(self._port_declaration(child))
return ",\n".join(slave_ports + master_ports)
def signal(
self,
signal: str,
node: AddressableNode | None = None,
idx: str | int | None = None,
) -> str:
if node is None:
# Node is none, so this is a slave signal
return f"s_apb_{signal}"
# Master signal
base = f"m_apb_{node.inst_name}"
if not node.is_array:
return f"{base}_{signal}"
if node.current_idx is not None:
# This is a specific instance of an array
return f"{base}_{signal}_{'_'.join(map(str, node.current_idx))}"
if idx is not None:
return f"{base}_{signal}[{idx}]"
return f"{base}_{signal}[N_{node.inst_name.upper()}S]"

View File

@@ -0,0 +1,119 @@
{%- if cpuif.is_interface -%}
`ifndef SYNTHESIS
initial begin
assert_bad_addr_width: assert($bits({{cpuif.signal("PADDR")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH)
else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("PADDR")}}), {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH);
assert_bad_data_width: assert($bits({{cpuif.signal("PWDATA")}}) == {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH)
else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("PWDATA")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH);
end
`endif
{% endif -%}
//======================================================
// APB Fanout
//======================================================
{%- for child in cpuif.addressable_children -%}
{%- if child is array -%}
for (genvar g_{{child.inst_name|lower}}_idx = 0; g_{{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; g_{{child.inst_name|lower}}_idx++) begin : g_passthrough_{{child.inst_name|lower}}
assign {{self.signal("PCLK", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PCLK")}};
assign {{self.signal("PRESETn", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PRESETn")}};
assign {{self.signal("PENABLE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PENABLE")}};
assign {{self.signal("PWRITE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWRITE")}};
assign {{self.signal("PADDR", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing
assign {{self.signal("PPROT", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PPROT")}};
assign {{self.signal("PWDATA", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWDATA")}};
assign {{self.signal("PSTRB", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PSTRB")}};
end
{%- else -%}
assign {{self.signal("PCLK", child)}} = {{self.signal("PCLK")}};
assign {{self.signal("PRESETn", child)}} = {{self.signal("PRESETn")}};
assign {{self.signal("PENABLE", child)}} = {{self.signal("PENABLE")}};
assign {{self.signal("PWRITE", child)}} = {{self.signal("PWRITE")}};
assign {{self.signal("PADDR", child)}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing
assign {{self.signal("PPROT", child)}} = {{self.signal("PPROT")}};
assign {{self.signal("PWDATA", child)}} = {{self.signal("PWDATA")}};
assign {{self.signal("PSTRB", child)}} = {{self.signal("PSTRB")}};
{%- endif -%}
{%- endfor -%}
//======================================================
// Address Decode Logic
//======================================================
always_comb begin
// Default all PSELx signals to 0
{%- for child in cpuif.addressable_children -%}
{%- if child is array -%}
for (int {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
{{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b0;
end
{%- else -%}
{{self.signal("PSELx", child)}} = 1'b0;
{%- endif -%}
{%- endfor -%}
if ({{self.signal("PSELx")}}) begin
{%- for child in cpuif.addressable_children -%}
{%- if loop.first -%}
if ({{cpuif.get_address_decode_condition(child)}}) begin
{%- else -%}
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
{%- endif -%}
// Address matched for {{child.inst_name}}
{%- if child is array -%}
for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
{{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b1;
end
{%- else -%}
{{self.signal("PSELx", child)}} = 1'b1;
{%- endif -%}
{%- if loop.last -%}
end else begin
// No address matched
{%- endif -%}
{%- endfor -%}
end
end else begin
// PSELx is low, nothing to do
end
end
//======================================================
// Read Data Mux
//======================================================
always_comb begin
// Default read data to 0
{{self.signal("PRDATA")}} = '0;
{{self.signal("PREADY")}} = 1'b1;
{{self.signal("PSLVERR")}} = 1'b0;
if ({{self.signal("PSELx")}} && !{{self.signal("PWRITE")}} && {{self.signal("PENABLE")}}) begin
{%- for child in cpuif.addressable_children -%}
{%- if loop.first -%}
if ({{cpuif.get_address_decode_condition(child)}}) begin
{%- else -%}
end else if ({{cpuif.get_address_decode_condition(child)}}) begin
{%- endif -%}
// Address matched for {{child.inst_name}}
{%- if child is array -%}
for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin
{{self.signal("PRDATA")}} = {{self.signal("PRDATA", child, f"{child.inst_name.lower()}_idx")}};
{{self.signal("PREADY")}} = {{self.signal("PREADY", child, f"{child.inst_name.lower()}_idx")}};
{{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child, f"{child.inst_name.lower()}_idx")}};
end
{%- else -%}
{{self.signal("PRDATA")}} = {{self.signal("PRDATA", child)}};
{{self.signal("PREADY")}} = {{self.signal("PREADY", child)}};
{{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child)}};
{%- endif -%}
{%- if loop.last -%}
end else begin
// No address matched
{{self.signal("PRDATA")}} = {'hdeadbeef}[{{ds.data_width - 1}}:0]; // Indicate error on no match
{{self.signal("PSLVERR")}} = 1'b1; // Indicate error on no match
end
{%- endif -%}
{%- endfor -%}
end else begin
// Not a read transfer, nothing to do
end
end

View File

@@ -0,0 +1,67 @@
from ..base_cpuif import CpuifBase
class AXI4Lite_Cpuif(CpuifBase):
template_path = "axi4lite_tmpl.sv"
is_interface = True
@property
def port_declaration(self) -> str:
return "axi4lite_intf.slave s_axil"
def signal(self, name: str) -> str:
return "s_axil." + name.upper()
@property
def busdecoder_latency(self) -> int:
return max(self.exp.ds.min_read_latency, self.exp.ds.min_write_latency)
@property
def max_outstanding(self) -> int:
"""
Best pipelined performance is when the max outstanding transactions
is the design's latency + 2.
Anything beyond that does not have any effect, aside from adding unnecessary
logic and additional buffer-bloat latency.
"""
return self.busdecoder_latency + 2
@property
def resp_buffer_size(self) -> int:
"""
Response buffer size must be greater or equal to max outstanding
transactions to prevent response overrun.
"""
return self.max_outstanding
class AXI4Lite_Cpuif_flattened(AXI4Lite_Cpuif):
is_interface = False
@property
def port_declaration(self) -> str:
lines = [
"output logic " + self.signal("awready"),
"input wire " + self.signal("awvalid"),
f"input wire [{self.addr_width - 1}:0] " + self.signal("awaddr"),
"input wire [2:0] " + self.signal("awprot"),
"output logic " + self.signal("wready"),
"input wire " + self.signal("wvalid"),
f"input wire [{self.data_width - 1}:0] " + self.signal("wdata"),
f"input wire [{self.data_width_bytes - 1}:0]" + self.signal("wstrb"),
"input wire " + self.signal("bready"),
"output logic " + self.signal("bvalid"),
"output logic [1:0] " + self.signal("bresp"),
"output logic " + self.signal("arready"),
"input wire " + self.signal("arvalid"),
f"input wire [{self.addr_width - 1}:0] " + self.signal("araddr"),
"input wire [2:0] " + self.signal("arprot"),
"input wire " + self.signal("rready"),
"output logic " + self.signal("rvalid"),
f"output logic [{self.data_width - 1}:0] " + self.signal("rdata"),
"output logic [1:0] " + self.signal("rresp"),
]
return ",\n".join(lines)
def signal(self, name: str) -> str:
return "s_axil_" + name

View File

@@ -0,0 +1,254 @@
{%- if cpuif.is_interface -%}
`ifndef SYNTHESIS
initial begin
assert_bad_addr_width: assert($bits({{cpuif.signal("araddr")}}) >= {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH)
else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("araddr")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH);
assert_bad_data_width: assert($bits({{cpuif.signal("wdata")}}) == {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH)
else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("wdata")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH);
end
`endif
{% endif -%}
// Max Outstanding Transactions: {{cpuif.max_outstanding}}
logic [{{clog2(cpuif.max_outstanding+1)-1}}:0] axil_n_in_flight;
logic axil_prev_was_rd;
logic axil_arvalid;
logic [{{cpuif.addr_width-1}}:0] axil_araddr;
logic axil_ar_accept;
logic axil_awvalid;
logic [{{cpuif.addr_width-1}}:0] axil_awaddr;
logic axil_wvalid;
logic [{{cpuif.data_width-1}}:0] axil_wdata;
logic [{{cpuif.data_width_bytes-1}}:0] axil_wstrb;
logic axil_aw_accept;
logic axil_resp_acked;
// Transaction request acceptance
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
axil_prev_was_rd <= '0;
axil_arvalid <= '0;
axil_araddr <= '0;
axil_awvalid <= '0;
axil_awaddr <= '0;
axil_wvalid <= '0;
axil_wdata <= '0;
axil_wstrb <= '0;
axil_n_in_flight <= '0;
end else begin
// AR* acceptance register
if(axil_ar_accept) begin
axil_prev_was_rd <= '1;
axil_arvalid <= '0;
end
if({{cpuif.signal("arvalid")}} && {{cpuif.signal("arready")}}) begin
axil_arvalid <= '1;
axil_araddr <= {{cpuif.signal("araddr")}};
end
// AW* & W* acceptance registers
if(axil_aw_accept) begin
axil_prev_was_rd <= '0;
axil_awvalid <= '0;
axil_wvalid <= '0;
end
if({{cpuif.signal("awvalid")}} && {{cpuif.signal("awready")}}) begin
axil_awvalid <= '1;
axil_awaddr <= {{cpuif.signal("awaddr")}};
end
if({{cpuif.signal("wvalid")}} && {{cpuif.signal("wready")}}) begin
axil_wvalid <= '1;
axil_wdata <= {{cpuif.signal("wdata")}};
axil_wstrb <= {{cpuif.signal("wstrb")}};
end
// Keep track of in-flight transactions
if((axil_ar_accept || axil_aw_accept) && !axil_resp_acked) begin
axil_n_in_flight <= axil_n_in_flight + 1'b1;
end else if(!(axil_ar_accept || axil_aw_accept) && axil_resp_acked) begin
axil_n_in_flight <= axil_n_in_flight - 1'b1;
end
end
end
always_comb begin
{{cpuif.signal("arready")}} = (!axil_arvalid || axil_ar_accept);
{{cpuif.signal("awready")}} = (!axil_awvalid || axil_aw_accept);
{{cpuif.signal("wready")}} = (!axil_wvalid || axil_aw_accept);
end
// Request dispatch
always_comb begin
cpuif_wr_data = axil_wdata;
for(int i=0; i<{{cpuif.data_width_bytes}}; i++) begin
cpuif_wr_biten[i*8 +: 8] = {8{axil_wstrb[i]}};
end
cpuif_req = '0;
cpuif_req_is_wr = '0;
cpuif_addr = '0;
axil_ar_accept = '0;
axil_aw_accept = '0;
if(axil_n_in_flight < {{clog2(cpuif.max_outstanding+1)}}'d{{cpuif.max_outstanding}}) begin
// Can safely issue more transactions without overwhelming response buffer
if(axil_arvalid && !axil_prev_was_rd) begin
cpuif_req = '1;
cpuif_req_is_wr = '0;
{%- if cpuif.data_width_bytes == 1 %}
cpuif_addr = axil_araddr;
{%- else %}
cpuif_addr = {axil_araddr[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
{%- endif %}
if(!cpuif_req_stall_rd) axil_ar_accept = '1;
end else if(axil_awvalid && axil_wvalid) begin
cpuif_req = '1;
cpuif_req_is_wr = '1;
{%- if cpuif.data_width_bytes == 1 %}
cpuif_addr = axil_awaddr;
{%- else %}
cpuif_addr = {axil_awaddr[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
{%- endif %}
if(!cpuif_req_stall_wr) axil_aw_accept = '1;
end else if(axil_arvalid) begin
cpuif_req = '1;
cpuif_req_is_wr = '0;
{%- if cpuif.data_width_bytes == 1 %}
cpuif_addr = axil_araddr;
{%- else %}
cpuif_addr = {axil_araddr[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
{%- endif %}
if(!cpuif_req_stall_rd) axil_ar_accept = '1;
end
end
end
// AXI4-Lite Response Logic
{%- if cpuif.resp_buffer_size == 1 %}
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
{{cpuif.signal("rvalid")}} <= '0;
{{cpuif.signal("rresp")}} <= '0;
{{cpuif.signal("rdata")}} <= '0;
{{cpuif.signal("bvalid")}} <= '0;
{{cpuif.signal("bresp")}} <= '0;
end else begin
if({{cpuif.signal("rvalid")}} && {{cpuif.signal("rready")}}) begin
{{cpuif.signal("rvalid")}} <= '0;
end
if({{cpuif.signal("bvalid")}} && {{cpuif.signal("bready")}}) begin
{{cpuif.signal("bvalid")}} <= '0;
end
if(cpuif_rd_ack) begin
{{cpuif.signal("rvalid")}} <= '1;
{{cpuif.signal("rdata")}} <= cpuif_rd_data;
if(cpuif_rd_err) {{cpuif.signal("rresp")}} <= 2'b10; // SLVERR
else {{cpuif.signal("rresp")}} <= 2'b00; // OKAY
end
if(cpuif_wr_ack) begin
{{cpuif.signal("bvalid")}} <= '1;
if(cpuif_wr_err) {{cpuif.signal("bresp")}} <= 2'b10; // SLVERR
else {{cpuif.signal("bresp")}} <= 2'b00; // OKAY
end
end
end
always_comb begin
axil_resp_acked = '0;
if({{cpuif.signal("rvalid")}} && {{cpuif.signal("rready")}}) axil_resp_acked = '1;
if({{cpuif.signal("bvalid")}} && {{cpuif.signal("bready")}}) axil_resp_acked = '1;
end
{%- else %}
struct {
logic is_wr;
logic err;
logic [{{cpuif.data_width-1}}:0] rdata;
} axil_resp_buffer[{{roundup_pow2(cpuif.resp_buffer_size)}}];
{%- if not is_pow2(cpuif.resp_buffer_size) %}
// axil_resp_buffer is intentionally padded to the next power of two despite
// only requiring {{cpuif.resp_buffer_size}} entries.
// This is to avoid quirks in some tools that cannot handle indexing into a non-power-of-2 array.
// Unused entries are expected to be optimized away
{% endif %}
logic [{{clog2(cpuif.resp_buffer_size)}}:0] axil_resp_wptr;
logic [{{clog2(cpuif.resp_buffer_size)}}:0] axil_resp_rptr;
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
for(int i=0; i<{{cpuif.resp_buffer_size}}; i++) begin
axil_resp_buffer[i].is_wr <= '0;
axil_resp_buffer[i].err <= '0;
axil_resp_buffer[i].rdata <= '0;
end
axil_resp_wptr <= '0;
axil_resp_rptr <= '0;
end else begin
// Store responses in buffer until AXI response channel accepts them
if(cpuif_rd_ack || cpuif_wr_ack) begin
if(cpuif_rd_ack) begin
axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].is_wr <= '0;
axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].err <= cpuif_rd_err;
axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].rdata <= cpuif_rd_data;
end else if(cpuif_wr_ack) begin
axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].is_wr <= '1;
axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].err <= cpuif_wr_err;
end
{%- if is_pow2(cpuif.resp_buffer_size) %}
axil_resp_wptr <= axil_resp_wptr + 1'b1;
{%- else %}
if(axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] == {{cpuif.resp_buffer_size-1}}) begin
axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] <= '0;
axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)}}] <= ~axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)}}];
end else begin
axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] <= axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] + 1'b1;
end
{%- endif %}
end
// Advance read pointer when acknowledged
if(axil_resp_acked) begin
{%- if is_pow2(cpuif.resp_buffer_size) %}
axil_resp_rptr <= axil_resp_rptr + 1'b1;
{%- else %}
if(axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] == {{cpuif.resp_buffer_size-1}}) begin
axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] <= '0;
axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)}}] <= ~axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)}}];
end else begin
axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] <= axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] + 1'b1;
end
{%- endif %}
end
end
end
always_comb begin
axil_resp_acked = '0;
{{cpuif.signal("bvalid")}} = '0;
{{cpuif.signal("rvalid")}} = '0;
if(axil_resp_rptr != axil_resp_wptr) begin
if(axil_resp_buffer[axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].is_wr) begin
{{cpuif.signal("bvalid")}} = '1;
if({{cpuif.signal("bready")}}) axil_resp_acked = '1;
end else begin
{{cpuif.signal("rvalid")}} = '1;
if({{cpuif.signal("rready")}}) axil_resp_acked = '1;
end
end
{{cpuif.signal("rdata")}} = axil_resp_buffer[axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].rdata;
if(axil_resp_buffer[axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].err) begin
{{cpuif.signal("bresp")}} = 2'b10;
{{cpuif.signal("rresp")}} = 2'b10;
end else begin
{{cpuif.signal("bresp")}} = 2'b00;
{{cpuif.signal("rresp")}} = 2'b00;
end
end
{%- endif %}

View File

@@ -0,0 +1,92 @@
from typing import TYPE_CHECKING
import inspect
import os
import jinja2 as jj
from systemrdl.node import AddressableNode
from ..utils import clog2, is_pow2, roundup_pow2
if TYPE_CHECKING:
from ..exporter import BusDecoderExporter
class BaseCpuif:
# Path is relative to the location of the class that assigns this variable
template_path = ""
def __init__(self, exp: "BusDecoderExporter") -> None:
self.exp = exp
self.reset = exp.ds.top_node.cpuif_reset
self.unroll = exp.ds.cpuif_unroll
@property
def addressable_children(self) -> list[AddressableNode]:
return [
child
for child in self.exp.ds.top_node.children(unroll=self.unroll)
if isinstance(child, AddressableNode)
]
@property
def addr_width(self) -> int:
return self.exp.ds.addr_width
@property
def data_width(self) -> int:
return self.exp.ds.cpuif_data_width
@property
def data_width_bytes(self) -> int:
return self.data_width // 8
@property
def port_declaration(self) -> str:
raise NotImplementedError()
@property
def parameters(self) -> list[str]:
"""
Optional list of additional parameters this CPU interface provides to
the module's definition
"""
array_parameters = [
f"parameter N_{child.inst_name.upper()}S = {child.n_elements}"
for child in self.addressable_children
if self.check_is_array(child)
]
return array_parameters
def _get_template_path_class_dir(self) -> str:
"""
Traverse up the MRO and find the first class that explicitly assigns
template_path. Returns the directory that contains the class definition.
"""
for cls in inspect.getmro(self.__class__):
if "template_path" in cls.__dict__:
class_dir = os.path.dirname(inspect.getfile(cls))
return class_dir
raise RuntimeError
def check_is_array(self, node: AddressableNode) -> bool:
return node.is_array and not self.unroll
def get_implementation(self) -> str:
class_dir = self._get_template_path_class_dir()
loader = jj.FileSystemLoader(class_dir)
jj_env = jj.Environment(
loader=loader,
undefined=jj.StrictUndefined,
)
jj_env.tests["array"] = self.check_is_array # type: ignore
jj_env.filters["clog2"] = clog2 # type: ignore
jj_env.filters["is_pow2"] = is_pow2 # type: ignore
jj_env.filters["roundup_pow2"] = roundup_pow2 # type: ignore
context = {
"cpuif": self,
"ds": self.exp.ds,
}
template = jj_env.get_template(self.template_path)
return template.render(context)