Reorganize test dir to ensure test of installed pkg

This commit is contained in:
Alex Mykyta
2022-02-28 23:08:41 -08:00
parent a8bf3c5132
commit 54d783e1ab
138 changed files with 15 additions and 19 deletions

View File

@@ -0,0 +1 @@
__version__ = "0.1.0"

View File

@@ -0,0 +1,3 @@
from .__about__ import __version__
from .exporter import RegblockExporter

View File

@@ -0,0 +1,100 @@
from typing import TYPE_CHECKING, Union, List
from systemrdl.node import AddrmapNode, AddressableNode, RegNode, FieldNode
from .utils import get_indexed_path
from .struct_generator import RDLStructGenerator
from .forloop_generator import RDLForLoopGenerator
if TYPE_CHECKING:
from .exporter import RegblockExporter
class AddressDecode:
def __init__(self, exp:'RegblockExporter'):
self.exp = exp
@property
def top_node(self) -> AddrmapNode:
return self.exp.top_node
def get_strobe_struct(self) -> str:
struct_gen = DecodeStructGenerator()
s = struct_gen.get_struct(self.top_node, "decoded_reg_strb_t")
assert s is not None # guaranteed to have at least one reg
return s
def get_implementation(self) -> str:
gen = DecodeLogicGenerator(self)
s = gen.get_content(self.top_node)
assert s is not None
return s
def get_access_strobe(self, node: Union[RegNode, FieldNode]) -> str:
"""
Returns the Verilog string that represents the register/field's access strobe.
"""
if isinstance(node, FieldNode):
node = node.parent
path = get_indexed_path(self.top_node, node)
return "decoded_reg_strb." + path
class DecodeStructGenerator(RDLStructGenerator):
def enter_Reg(self, node: 'RegNode') -> None:
self.add_member(node.inst_name, array_dimensions=node.array_dimensions)
# Stub out
def exit_Reg(self, node: 'RegNode') -> None:
pass
def enter_Field(self, node: 'FieldNode') -> None:
pass
class DecodeLogicGenerator(RDLForLoopGenerator):
def __init__(self, addr_decode: AddressDecode) -> None:
self.addr_decode = addr_decode
super().__init__()
# List of address strides for each dimension
self._array_stride_stack = [] # type: List[List[int]]
def enter_AddressableComponent(self, node: 'AddressableNode') -> None:
super().enter_AddressableComponent(node)
if not node.is_array:
return
# Collect strides for each array dimension
current_stride = node.array_stride
strides = []
for dim in reversed(node.array_dimensions):
strides.append(current_stride)
current_stride *= dim
strides.reverse()
self._array_stride_stack.extend(strides)
def _get_address_str(self, node:AddressableNode) -> str:
a = f"'h{(node.raw_absolute_address - self.addr_decode.top_node.raw_absolute_address):x}"
for i, stride in enumerate(self._array_stride_stack):
a += f" + i{i}*'h{stride:x}"
return a
def enter_Reg(self, node: RegNode) -> None:
s = f"{self.addr_decode.get_access_strobe(node)} = cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)});"
self.add_content(s)
def exit_AddressableComponent(self, node: 'AddressableNode') -> None:
super().exit_AddressableComponent(node)
if not node.is_array:
return
for _ in node.array_dimensions:
self._array_stride_stack.pop()

View File

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

View File

@@ -0,0 +1,30 @@
from ..base import CpuifBase
class APB3_Cpuif(CpuifBase):
template_path = "apb3_tmpl.sv"
@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):
@property
def port_declaration(self) -> str:
lines = [
"input wire " + self.signal("psel"),
"input wire " + self.signal("penable"),
"input wire " + self.signal("pwrite"),
f"input wire [{self.addr_width-1}:0] " + self.signal("paddr"),
f"input wire [{self.data_width-1}:0] " + self.signal("pwdata"),
"output logic " + self.signal("pready"),
f"output logic [{self.data_width-1}:0] " + self.signal("prdata"),
"output logic " + self.signal("pslverr"),
]
return ",\n".join(lines)
def signal(self, name:str) -> str:
return "s_apb_" + name

View File

@@ -0,0 +1,35 @@
// 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 == 8 %}
cpuif_addr <= {{cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:0];
{%- else %}
cpuif_addr <= { {{-cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width//8)}}], {{clog2(cpuif.data_width//8)}}'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
// Response
assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack;
assign {{cpuif.signal("prdata")}} = cpuif_rd_data;
assign {{cpuif.signal("pslverr")}} = cpuif_rd_err | cpuif_wr_err;

View File

@@ -0,0 +1,71 @@
from ..base import CpuifBase
class AXI4Lite_Cpuif(CpuifBase):
template_path = "axi4lite_tmpl.sv"
@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 data_width_bytes(self) -> int:
return self.data_width // 8
@property
def regblock_latency(self) -> int:
return max(self.exp.min_read_latency, self.exp.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.regblock_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):
@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//8-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,217 @@
// 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 axil_aw_accept;
logic axil_resp_acked;
// Transaction request accpetance
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_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")}};
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;
cpuif_req = '0;
cpuif_req_is_wr = '0;
cpuif_addr = '0;
axil_ar_accept = '0;
axil_aw_accept = '0;
if(axil_n_in_flight < '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;
cpuif_addr = axil_araddr;
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;
cpuif_addr = axil_awaddr;
if(!cpuif_req_stall_wr) axil_aw_accept = '1;
end else if(axil_arvalid) begin
cpuif_req = '1;
cpuif_req_is_wr = '0;
cpuif_addr = axil_araddr;
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[{{cpuif.resp_buffer_size}}];
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,58 @@
from typing import TYPE_CHECKING, Optional
import inspect
import os
import jinja2 as jj
from ..utils import get_always_ff_event, clog2, is_pow2
if TYPE_CHECKING:
from ..exporter import RegblockExporter
from systemrdl import SignalNode
class CpuifBase:
# Path is relative to the location of the class that assigns this variable
template_path = ""
def __init__(self, exp:'RegblockExporter', cpuif_reset:Optional['SignalNode'], data_width:int=32, addr_width:int=32):
self.exp = exp
self.reset = cpuif_reset
self.data_width = data_width
self.addr_width = addr_width
@property
def port_declaration(self) -> str:
raise NotImplementedError()
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 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,
)
context = {
"cpuif": self,
"get_always_ff_event": lambda resetsignal : get_always_ff_event(self.exp.dereferencer, resetsignal),
"get_resetsignal": self.exp.dereferencer.get_resetsignal,
"clog2": clog2,
"is_pow2": is_pow2,
}
template = jj_env.get_template(self.template_path)
return template.render(context)

View File

@@ -0,0 +1,21 @@
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",
"output wire s_cpuif_req_stall_wr",
"output wire s_cpuif_req_stall_rd",
"output wire s_cpuif_rd_ack",
"output wire s_cpuif_rd_err",
f"output wire [{self.data_width-1}:0] s_cpuif_rd_data",
"output wire s_cpuif_wr_ack",
"output wire s_cpuif_wr_err",
]
return ",\n".join(lines)

View File

@@ -0,0 +1,11 @@
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 s_cpuif_req_stall_wr = cpuif_req_stall_wr;
assign s_cpuif_req_stall_rd = cpuif_req_stall_rd;
assign s_cpuif_rd_ack = cpuif_rd_ack;
assign s_cpuif_rd_err = cpuif_rd_err;
assign s_cpuif_rd_data = cpuif_rd_data;
assign s_cpuif_wr_ack = cpuif_wr_ack;
assign s_cpuif_wr_err = cpuif_wr_err;

View File

@@ -0,0 +1,216 @@
from typing import TYPE_CHECKING, Union, Optional
from systemrdl.node import AddrmapNode, FieldNode, SignalNode, RegNode
from systemrdl.rdltypes import PropertyReference
if TYPE_CHECKING:
from .exporter import RegblockExporter
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:'RegblockExporter'):
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 top_node(self) -> AddrmapNode:
return self.exp.top_node
def get_value(self, obj: Union[int, FieldNode, SignalNode, PropertyReference]) -> 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.
"""
if isinstance(obj, int):
# Is a simple scalar value
return f"'h{obj:x}"
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)
# 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)
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)
if isinstance(obj, PropertyReference):
if isinstance(obj.node, FieldNode):
return self.get_field_propref_value(obj.node, obj.name)
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) -> 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))
# 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)
# 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)
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]) -> str:
"""
Returns the Verilog string that represents the register's access strobe
"""
return self.address_decode.get_access_strobe(obj)
def get_resetsignal(self, obj: Optional[SignalNode]) -> str:
"""
Returns a normalized active-high reset signal
"""
if isinstance(obj, SignalNode):
s = self.get_value(obj)
if obj.get_property('activehigh'):
return s
else:
return f"~{s}"
# default reset signal
return "rst"

View File

@@ -0,0 +1,172 @@
import os
from typing import Union, Any, Type
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 .cpuif import CpuifBase
from .cpuif.apb3 import APB3_Cpuif
from .hwif import Hwif
from .utils import get_always_ff_event
from .scan_design import DesignScanner
class RegblockExporter:
def __init__(self, **kwargs: Any) -> None:
# Check for stray kwargs
if kwargs:
raise TypeError(f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'")
self.top_node = None # type: AddrmapNode
self.hwif = None # type: Hwif
self.cpuif = None # type: CpuifBase
self.address_decode = AddressDecode(self)
self.field_logic = FieldLogic(self)
self.readback = None # type: Readback
self.dereferencer = Dereferencer(self)
self.min_read_latency = 0
self.min_write_latency = 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.regblock.cpuif.CpuifBase`
Specify the class type that implements the CPU interface of your choice.
Defaults to AMBA APB3.
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.
"""
# If it is the root node, skip to top addrmap
if isinstance(node, RootNode):
self.top_node = node.top
else:
self.top_node = node
cpuif_cls = kwargs.pop("cpuif_cls", APB3_Cpuif) # type: Type[CpuifBase]
module_name = kwargs.pop("module_name", self.top_node.inst_name) # type: str
package_name = kwargs.pop("package_name", module_name + "_pkg") # type: str
reuse_hwif_typedefs = kwargs.pop("reuse_hwif_typedefs", True) # type: bool
# Pipelining options
retime_read_fanin = kwargs.pop("retime_read_fanin", False) # type: bool
retime_read_response = kwargs.pop("retime_read_response", True) # type: bool
# Check for stray kwargs
if kwargs:
raise TypeError(f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'")
self.min_read_latency = 0
self.min_write_latency = 0
if retime_read_fanin:
self.min_read_latency += 1
if retime_read_response:
self.min_read_latency += 1
# Scan the design for any unsupported features
# Also collect pre-export information
scanner = DesignScanner(self)
scanner.do_scan()
self.cpuif = cpuif_cls(
self,
cpuif_reset=self.top_node.cpuif_reset,
data_width=scanner.cpuif_data_width,
addr_width=self.top_node.size.bit_length()
)
self.hwif = Hwif(
self,
package_name=package_name,
in_hier_signal_paths=scanner.in_hier_signal_paths,
out_of_hier_signals=scanner.out_of_hier_signals,
reuse_typedefs=reuse_hwif_typedefs,
)
self.readback = Readback(
self,
retime_read_fanin
)
# Build Jinja template context
context = {
"module_name": module_name,
"user_out_of_hier_signals": scanner.out_of_hier_signals.values(),
"cpuif": self.cpuif,
"hwif": self.hwif,
"get_resetsignal": self.dereferencer.get_resetsignal,
"address_decode": self.address_decode,
"field_logic": self.field_logic,
"readback": self.readback,
"get_always_ff_event": lambda resetsignal : get_always_ff_event(self.dereferencer, resetsignal),
"retime_read_response": retime_read_response,
"min_read_latency": self.min_read_latency,
"min_write_latency": self.min_write_latency,
}
# Write out design
package_file_path = os.path.join(output_dir, 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, module_name + ".sv")
template = self.jj_env.get_template("module_tmpl.sv")
stream = template.stream(context)
stream.dump(module_file_path)

View File

@@ -0,0 +1,331 @@
from typing import TYPE_CHECKING
from systemrdl.rdltypes import PropertyReference, PrecedenceType, InterruptType
from systemrdl.node import Node
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 ..utils import get_indexed_path
from .generators import CombinationalStructGenerator, FieldStorageStructGenerator, FieldLogicGenerator
if TYPE_CHECKING:
from typing import Dict, List
from systemrdl.node import AddrmapNode, FieldNode
from ..exporter import RegblockExporter
class FieldLogic:
def __init__(self, exp:'RegblockExporter'):
self.exp = exp
self._hw_conditionals = {} # type: Dict[int, List[NextStateConditional]]
self._sw_conditionals = {} # type: Dict[int, List[NextStateConditional]]
self.init_conditionals()
@property
def top_node(self) -> 'AddrmapNode':
return self.exp.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 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') -> 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)
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') -> str:
prop_value = field.get_property('incrsaturate')
if prop_value is True:
return self.exp.dereferencer.get_value(2**field.width - 1)
return self.exp.dereferencer.get_value(prop_value)
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') -> 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)
return self.exp.dereferencer.get_value(prop_value)
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 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') -> 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)
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') -> str:
prop_value = field.get_property('decrsaturate')
if prop_value is True:
return "'d0"
return self.exp.dereferencer.get_value(prop_value)
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') -> str:
prop_value = field.get_property('decrthreshold')
if isinstance(prop_value, bool):
# No explicit value set. use min
return "'d0"
return self.exp.dereferencer.get_value(prop_value)
def get_swacc_identifier(self, field: 'FieldNode') -> str:
"""
Asserted when field is software accessed (read)
"""
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)
strb = self.exp.dereferencer.get_access_strobe(field)
if w_modifiable and not r_modifiable:
# assert swmod only on sw write
return f"{strb} && decoded_req_is_wr"
if w_modifiable and r_modifiable:
# assert swmod on all sw actions
return strb
if not w_modifiable and r_modifiable:
# assert swmod only on sw read
return f"{strb} && !decoded_req_is_wr"
# Not sw modifiable
return "1'b0"
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
#---------------------------------------------------------------------------
# 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.
"""
# TODO: Add all the other things
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.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.PosedgeNonsticky(self.exp), AssignmentPrecedence.HW_WRITE)
self.add_hw_conditional(hw_interrupts.NegedgeNonsticky(self.exp), AssignmentPrecedence.HW_WRITE)
self.add_hw_conditional(hw_interrupts.BothedgeNonsticky(self.exp), AssignmentPrecedence.HW_WRITE)
self.add_hw_conditional(hw_interrupts.Sticky(self.exp), AssignmentPrecedence.HW_WRITE)
self.add_hw_conditional(hw_interrupts.Stickybit(self.exp), AssignmentPrecedence.HW_WRITE)
self.add_hw_conditional(hw_write.WEWrite(self.exp), AssignmentPrecedence.HW_WRITE)
self.add_hw_conditional(hw_write.WELWrite(self.exp), AssignmentPrecedence.HW_WRITE)
self.add_hw_conditional(hw_write.AlwaysWrite(self.exp), AssignmentPrecedence.HW_WRITE)
self.add_hw_conditional(hw_set_clr.HWClear(self.exp), AssignmentPrecedence.HWCLR)
self.add_hw_conditional(hw_set_clr.HWSet(self.exp), AssignmentPrecedence.HWSET)
def _get_X_conditionals(self, conditionals: 'Dict[int, List[NextStateConditional]]', field: 'FieldNode') -> 'List[NextStateConditional]':
result = []
precedences = sorted(conditionals.keys(), reverse=True)
for precedence in precedences:
for conditional in conditionals[precedence]:
if conditional.is_match(field):
result.append(conditional)
break
return result
def get_conditionals(self, field: 'FieldNode') -> 'List[NextStateConditional]':
"""
Get a list of NextStateConditional objects that apply to the given field.
The returned list is sorted in priority order - the conditional with highest
precedence is first in the list.
"""
sw_precedence = (field.get_property('precedence') == PrecedenceType.sw)
result = []
if sw_precedence:
result.extend(self._get_X_conditionals(self._sw_conditionals, field))
result.extend(self._get_X_conditionals(self._hw_conditionals, field))
if not sw_precedence:
result.extend(self._get_X_conditionals(self._sw_conditionals, field))
return result

View File

@@ -0,0 +1,102 @@
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 RegblockExporter
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:
"""
Decribes a single conditional action that determines the next state of a field
Provides information to generate the following content:
if(<conditional>) begin
<assignments>
end
"""
comment = ""
def __init__(self, exp:'RegblockExporter'):
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 FieldBuider that Verilog for this conditional shall
be emitted
"""
raise NotImplementedError
def get_field_path(self, field:'FieldNode') -> str:
return get_indexed_path(self.exp.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;
"""
def get_extra_combo_signals(self, field: 'FieldNode') -> List[SVLogic]:
"""
Return any additional combinational signals that this conditional
will assign if present.
"""
return []

View File

@@ -0,0 +1,264 @@
from typing import TYPE_CHECKING, List
from collections import OrderedDict
from ..struct_generator import RDLStructGenerator
from ..forloop_generator import RDLForLoopGenerator
from ..utils import get_always_ff_event
if TYPE_CHECKING:
from . import FieldLogic
from systemrdl.node import FieldNode, RegNode
from .bases import SVLogic
class CombinationalStructGenerator(RDLStructGenerator):
def __init__(self, field_logic: 'FieldLogic'):
super().__init__()
self.field_logic = field_logic
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(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)
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_Field(self, node: 'FieldNode') -> None:
self.push_struct(node.inst_name)
if node.implements_storage:
self.add_member("value", node.width)
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.field_storage_template = self.field_logic.exp.jj_env.get_template(
"field_logic/templates/field_storage.sv"
)
self.intr_fields = [] # type: List[FieldNode]
self.halt_fields = [] # type: List[FieldNode]
def enter_Reg(self, node: 'RegNode') -> None:
self.intr_fields = []
self.halt_fields = []
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()
for conditional in conditionals:
for signal in conditional.get_extra_combo_signals(node):
extra_combo_signals[signal.name] = signal
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)
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,
'resetsignal': resetsignal,
'get_always_ff_event': lambda resetsignal : get_always_ff_event(self.exp.dereferencer, resetsignal),
'get_value': self.exp.dereferencer.get_value,
'get_resetsignal': self.exp.dereferencer.get_resetsignal,
'get_input_identifier': self.exp.hwif.get_input_identifier,
}
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};"
)
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('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};"
)
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};"
)

View File

@@ -0,0 +1,202 @@
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')
)
def get_predicate(self, field: 'FieldNode') -> str:
return self.exp.hwif.get_input_identifier(field)
def get_assignments(self, field: 'FieldNode') -> List[str]:
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}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
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}"
def get_assignments(self, field: 'FieldNode') -> List[str]:
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]:
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 PosedgeNonsticky(NextStateConditional):
"""
Positive edge non-stickybit
"""
comment = "posedge nonsticky"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and not field.get_property('stickybit')
and field.get_property('intr type') == InterruptType.posedge
)
def get_predicate(self, field: 'FieldNode') -> str:
return "1"
def get_assignments(self, field: 'FieldNode') -> List[str]:
I = self.exp.hwif.get_input_identifier(field)
Iq = self.exp.field_logic.get_next_q_identifier(field)
return [
f"next_c = ~{Iq} & {I};",
"load_next_c = '1;",
]
class NegedgeNonsticky(NextStateConditional):
"""
Negative edge non-stickybit
"""
comment = "negedge nonsticky"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and not field.get_property('stickybit')
and field.get_property('intr type') == InterruptType.negedge
)
def get_predicate(self, field: 'FieldNode') -> str:
return "1"
def get_assignments(self, field: 'FieldNode') -> List[str]:
I = self.exp.hwif.get_input_identifier(field)
Iq = self.exp.field_logic.get_next_q_identifier(field)
return [
f"next_c = {Iq} & ~{I};",
"load_next_c = '1;",
]
class BothedgeNonsticky(NextStateConditional):
"""
edge-sensitive non-stickybit
"""
comment = "bothedge nonsticky"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and not field.get_property('stickybit')
and field.get_property('intr type') == InterruptType.bothedge
)
def get_predicate(self, field: 'FieldNode') -> str:
return "1"
def get_assignments(self, field: 'FieldNode') -> List[str]:
I = self.exp.hwif.get_input_identifier(field)
Iq = self.exp.field_logic.get_next_q_identifier(field)
return [
f"next_c = {Iq} ^ {I};",
"load_next_c = '1;",
]

View File

@@ -0,0 +1,72 @@
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 = 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 = self.exp.dereferencer.get_value(prop)
return identifier
def get_assignments(self, field: 'FieldNode') -> List[str]:
hwmask = field.get_property('hwmask')
hwenable = field.get_property('hwenable')
R = self.exp.field_logic.get_storage_identifier(field)
if hwmask is not None:
M = self.exp.dereferencer.get_value(hwmask)
next_val = f"{R} & {M}"
elif hwenable is not None:
E = self.exp.dereferencer.get_value(hwenable)
next_val = f"{R} & ~{E}"
else:
next_val = "'0"
return [
f"next_c = {next_val};",
"load_next_c = '1;",
]

View File

@@ -0,0 +1,76 @@
from typing import TYPE_CHECKING, List
from .bases import NextStateConditional
if TYPE_CHECKING:
from systemrdl.node import FieldNode
class AlwaysWrite(NextStateConditional):
"""
hw writable, without any qualifying we/wel
"""
comment = "HW Write"
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_predicate(self, field: 'FieldNode') -> str:
# TODO: make exporter promote this to an "else"?
return "1"
def get_assignments(self, field: 'FieldNode') -> List[str]:
hwmask = field.get_property('hwmask')
hwenable = field.get_property('hwenable')
I = 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(AlwaysWrite):
comment = "HW Write - we"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and 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 = self.exp.dereferencer.get_value(prop)
return identifier
class WELWrite(AlwaysWrite):
comment = "HW Write - wel"
def is_match(self, field: 'FieldNode') -> bool:
return (
field.is_hw_writable
and 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 = self.exp.dereferencer.get_value(prop)
return f"!{identifier}"

View File

@@ -0,0 +1,39 @@
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
def is_match(self, field: 'FieldNode') -> bool:
return field.get_property('onread') == self.onreadtype
def get_predicate(self, field: 'FieldNode') -> str:
strb = self.exp.dereferencer.get_access_strobe(field)
return f"{strb} && !decoded_req_is_wr"
class ClearOnRead(_OnRead):
comment = "SW clear on read"
onreadtype = OnReadType.rclr
def get_assignments(self, field: 'FieldNode') -> List[str]:
return [
"next_c = '0;",
"load_next_c = '1;",
]
class SetOnRead(_OnRead):
comment = "SW set on read"
onreadtype = OnReadType.rset
def get_assignments(self, field: 'FieldNode') -> List[str]:
return [
"next_c = '1;",
"load_next_c = '1;",
]

View File

@@ -0,0 +1,130 @@
from typing import TYPE_CHECKING, List
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 = 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:
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 _wr_data(self, field: 'FieldNode') -> str:
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{decoded_wr_data[{field.high}:{field.low}]}}}}"
else:
value = f"decoded_wr_data[{field.high}:{field.low}]"
return value
class WriteOneSet(_OnWrite):
comment = "SW write 1 set"
onwritetype = OnWriteType.woset
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
return [
f"next_c = {R} | {self._wr_data(field)};",
"load_next_c = '1;",
]
class WriteOneClear(_OnWrite):
comment = "SW write 1 clear"
onwritetype = OnWriteType.woclr
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
return [
f"next_c = {R} & ~{self._wr_data(field)};",
"load_next_c = '1;",
]
class WriteOneToggle(_OnWrite):
comment = "SW write 1 toggle"
onwritetype = OnWriteType.wot
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
return [
f"next_c = {R} ^ {self._wr_data(field)};",
"load_next_c = '1;",
]
class WriteZeroSet(_OnWrite):
comment = "SW write 0 set"
onwritetype = OnWriteType.wzs
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
return [
f"next_c = {R} | ~{self._wr_data(field)};",
"load_next_c = '1;",
]
class WriteZeroClear(_OnWrite):
comment = "SW write 0 clear"
onwritetype = OnWriteType.wzc
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
return [
f"next_c = {R} & {self._wr_data(field)};",
"load_next_c = '1;",
]
class WriteZeroToggle(_OnWrite):
comment = "SW write 0 toggle"
onwritetype = OnWriteType.wzt
def get_assignments(self, field: 'FieldNode') -> List[str]:
R = self.exp.field_logic.get_storage_identifier(field)
return [
f"next_c = {R} ^ ~{self._wr_data(field)};",
"load_next_c = '1;",
]
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_assignments(self, field: 'FieldNode') -> List[str]:
return [
f"next_c = {self._wr_data(field)};",
"load_next_c = '1;",
]

View File

@@ -0,0 +1,22 @@
from typing import TYPE_CHECKING, List
from .bases import NextStateConditional
if TYPE_CHECKING:
from systemrdl.node import FieldNode
class Singlepulse(NextStateConditional):
comment = "singlepulse clears back to 0"
def is_match(self, field: 'FieldNode') -> bool:
return field.get_property('singlepulse')
def get_predicate(self, field: 'FieldNode') -> str:
# TODO: make exporter promote this to an "else"?
# Be mindful of sw/hw precedence. this would have to come last regardless
return "1"
def get_assignments(self, field: 'FieldNode') -> List[str]:
return [
"next_c = '0;",
"load_next_c = '1;",
]

View File

@@ -0,0 +1,56 @@
{% 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)}});
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)}});
if(next_c > {{field_logic.get_counter_incrsaturate_value(node)}}) begin
next_c = {{field_logic.get_counter_incrsaturate_value(node)}};
load_next_c = '1;
end
{%- 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)}});
if(next_c < {{field_logic.get_counter_decrsaturate_value(node)}}) begin
next_c = {{field_logic.get_counter_decrsaturate_value(node)}};
load_next_c = '1;
end
{%- endif %}
{%- endmacro %}

View File

@@ -0,0 +1,35 @@
{%- 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 = {{field_logic.get_storage_identifier(node)}};
automatic logic 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 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;
end
always_ff {{get_always_ff_event(resetsignal)}} begin
{% if reset is not none -%}
if({{get_resetsignal(resetsignal)}}) begin
{{field_logic.get_storage_identifier(node)}} <= {{reset}};
end else {% endif %}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")}};
end
{%- if field_logic.has_next_q(node) %}
{{field_logic.get_next_q_identifier(node)}} <= {{get_input_identifier(node)}};
{%- endif %}
end

View File

@@ -0,0 +1,96 @@
from typing import TYPE_CHECKING, Optional, List, Union
import textwrap
from systemrdl.walker import RDLListener, RDLWalker
if TYPE_CHECKING:
from systemrdl.node import AddressableNode, Node
class Body:
def __init__(self) -> None:
self.children = [] # type: List[Union[str, Body]]
def __str__(self) -> str:
s = '\n'.join((str(x) for x in self.children))
return s
class LoopBody(Body):
def __init__(self, dim: int, iterator: str, i_type: str) -> None:
super().__init__()
self.dim = dim
self.iterator = iterator
self.i_type = i_type
def __str__(self) -> str:
s = super().__str__()
return (
f"for({self.i_type} {self.iterator}=0; {self.iterator}<{self.dim}; {self.iterator}++) begin\n"
+ textwrap.indent(s, " ")
+ "\nend"
)
class ForLoopGenerator:
i_type = "int"
loop_body_cls = LoopBody
def __init__(self) -> None:
self._loop_level = 0
self._stack = [] # type: List[Body]
@property
def current_loop(self) -> Body:
return self._stack[-1]
def push_loop(self, dim: int) -> None:
i = f"i{self._loop_level}"
b = self.loop_body_cls(dim, i, self.i_type)
self._stack.append(b)
self._loop_level += 1
def add_content(self, s: str) -> None:
self.current_loop.children.append(s)
def pop_loop(self) -> None:
b = self._stack.pop()
if b.children:
# Loop body is not empty. Attach it to the parent
self.current_loop.children.append(b)
self._loop_level -= 1
def start(self) -> None:
assert not self._stack
b = Body()
self._stack.append(b)
def finish(self) -> Optional[str]:
b = self._stack.pop()
assert not self._stack
if not b.children:
return None
return str(b)
class RDLForLoopGenerator(ForLoopGenerator, RDLListener):
def get_content(self, node: 'Node') -> Optional[str]:
self.start()
walker = RDLWalker()
walker.walk(node, self, skip_top=True)
return self.finish()
def enter_AddressableComponent(self, node: 'AddressableNode') -> None:
if not node.is_array:
return
for dim in node.array_dimensions:
self.push_loop(dim)
def exit_AddressableComponent(self, node: 'AddressableNode') -> None:
if not node.is_array:
return
for _ in node.array_dimensions:
self.pop_loop()

View File

@@ -0,0 +1,196 @@
from typing import TYPE_CHECKING, Union, List, Set, Dict
from systemrdl.node import AddrmapNode, Node, SignalNode, FieldNode, AddressableNode, RegNode
from systemrdl.rdltypes import PropertyReference
from ..utils import get_indexed_path
from .generators import InputStructGenerator_Hier, OutputStructGenerator_Hier
from .generators import InputStructGenerator_TypeScope, OutputStructGenerator_TypeScope
if TYPE_CHECKING:
from ..exporter import RegblockExporter
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: 'RegblockExporter', package_name: str,
in_hier_signal_paths: Set[str], out_of_hier_signals: Dict[str, SignalNode],
reuse_typedefs: bool
):
self.exp = exp
self.package_name = package_name
self.has_input_struct = False
self.has_output_struct = False
self.in_hier_signal_paths = in_hier_signal_paths
self.out_of_hier_signals = out_of_hier_signals
if reuse_typedefs:
self._gen_in_cls = InputStructGenerator_TypeScope
self._gen_out_cls = OutputStructGenerator_TypeScope
else:
self._gen_in_cls = InputStructGenerator_Hier
self._gen_out_cls = OutputStructGenerator_Hier
@property
def top_node(self) -> AddrmapNode:
return self.exp.top_node
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
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.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.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]) -> 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)
# 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.out_of_hier_signals:
return obj.inst_name
path = get_indexed_path(self.top_node, obj)
return "hwif_in." + path
elif isinstance(obj, PropertyReference):
return self.get_implied_prop_input_identifier(obj.node, obj.name)
raise RuntimeError(f"Unhandled reference to: {obj}")
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)
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",
}
elif isinstance(node, RegNode):
assert prop in {
"intr", "halt",
}
path = get_indexed_path(self.top_node, node)
return "hwif_out." + path + "." + prop

View File

@@ -0,0 +1,168 @@
from typing import TYPE_CHECKING
from systemrdl.node import FieldNode
from ..struct_generator import RDLFlatStructGenerator
if TYPE_CHECKING:
from systemrdl.node import Node, SignalNode, RegNode
from . import Hwif
class InputStructGenerator_Hier(RDLFlatStructGenerator):
def __init__(self, hwif: 'Hwif') -> None:
super().__init__()
self.hwif = hwif
self.top_node = hwif.top_node
def get_typdef_name(self, node:'Node') -> str:
base = node.get_rel_path(
self.top_node.parent,
hier_separator="__",
array_suffix="x",
empty_array_suffix="x"
)
return f'{base}__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.in_hier_signal_paths:
self.add_member(node.inst_name, node.width)
def enter_Field(self, node: 'FieldNode') -> None:
type_name = self.get_typdef_name(node)
self.push_struct(type_name, 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:
self.add_member("next", node.width)
# 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(RDLFlatStructGenerator):
def __init__(self, top_node: 'Node'):
super().__init__()
self.top_node = top_node
def get_typdef_name(self, node:'Node') -> str:
base = node.get_rel_path(
self.top_node.parent,
hier_separator="__",
array_suffix="x",
empty_array_suffix="x"
)
return f'{base}__out_t'
def enter_Field(self, node: 'FieldNode') -> None:
type_name = self.get_typdef_name(node)
self.push_struct(type_name, node.inst_name)
# Expose field's value if it is readable by hw
if node.is_hw_readable:
self.add_member("value", node.width)
# Generate output bit signals enabled via property
for prop_name in ["anded", "ored", "xored", "swmod", "swacc", "overflow", "underflow"]:
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') -> str:
scope_path = node.inst.get_scope_path("__")
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 isinstance(node, FieldNode):
extra_suffix = get_field_type_name_suffix(node)
else:
extra_suffix = ""
return f'{scope_path}__{node.type_name}{extra_suffix}__in_t'
class OutputStructGenerator_TypeScope(OutputStructGenerator_Hier):
def get_typdef_name(self, node:'Node') -> str:
scope_path = node.inst.get_scope_path("__")
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 isinstance(node, FieldNode):
extra_suffix = get_field_type_name_suffix(node)
else:
extra_suffix = ""
return f'{scope_path}__{node.type_name}{extra_suffix}__out_t'
def get_field_type_name_suffix(field: FieldNode) -> str:
"""
Fields may reuse the same type, but end up instantiating different widths
Uniquify the type name further if the field width was overridden when instantiating
"""
if field.inst.original_def is None:
return ""
if field.inst.original_def.type_name is None:
# is an anonymous definition. No extra suffix needed
return ""
if "fieldwidth" in field.list_properties():
# fieldwidth was explicitly set. This type name is already sufficiently distinct
return ""
if field.width == 1:
# field width is the default. Skip suffix
return ""
return f"_w{field.width}"

View File

@@ -0,0 +1,134 @@
// TODO: Add a banner
module {{module_name}} (
input wire clk,
input wire rst,
{%- for signal in user_out_of_hier_signals %}
{%- if signal.width == 1 %}
input wire {{signal.inst_name}},
{%- else %}
input wire [{{signal.width-1}}:0] {{signal.inst_name}},
{%- endif %}
{%- endfor %}
{{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_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 min_read_latency == min_write_latency %}
// Read & write latencies are balanced. Stalls not required
assign cpuif_req_stall_rd = '0;
assign cpuif_req_stall_wr = '0;
assign cpuif_req_masked = cpuif_req;
{%- elif min_read_latency > min_write_latency %}
// Read latency > write latency. May need to delay next write that follows a read
logic [{{min_read_latency - 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
assign cpuif_req_stall_rd = '0;
assign cpuif_req_stall_wr = cpuif_req_stall_sr[0];
assign cpuif_req_masked = cpuif_req & !(cpuif_req_is_wr & cpuif_req_stall_wr);
{%- else %}
// Write latency > read latency. May need to delay next read that follows a write
logic [{{min_write_latency - 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
assign cpuif_req_stall_rd = cpuif_req_stall_sr[0];
assign cpuif_req_stall_wr = '0;
assign cpuif_req_masked = cpuif_req & !(!cpuif_req_is_wr & cpuif_req_stall_rd);
{%- endif %}
//--------------------------------------------------------------------------
// Address Decode
//--------------------------------------------------------------------------
{{address_decode.get_strobe_struct()|indent}}
decoded_reg_strb_t decoded_reg_strb;
logic decoded_req;
logic decoded_req_is_wr;
logic [{{cpuif.data_width-1}}:0] decoded_wr_data;
always_comb begin
{{address_decode.get_implementation()|indent(8)}}
end
// Pass down signals to next stage
assign decoded_req = cpuif_req_masked;
assign decoded_req_is_wr = cpuif_req_is_wr;
assign decoded_wr_data = cpuif_wr_data;
// Writes are always granted with no error response
assign cpuif_wr_ack = decoded_req & decoded_req_is_wr;
assign cpuif_wr_err = '0;
//--------------------------------------------------------------------------
// Field logic
//--------------------------------------------------------------------------
{{field_logic.get_combo_struct()|indent}}
{{field_logic.get_storage_struct()|indent}}
{{field_logic.get_implementation()|indent}}
//--------------------------------------------------------------------------
// Readback
//--------------------------------------------------------------------------
logic readback_err;
logic readback_done;
logic [{{cpuif.data_width-1}}:0] readback_data;
{{readback.get_implementation()|indent}}
{% if 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;
end else begin
cpuif_rd_ack <= readback_done;
cpuif_rd_data <= readback_data;
cpuif_rd_err <= readback_err;
end
end
{% else %}
assign cpuif_rd_ack = readback_done;
assign cpuif_rd_data = readback_data;
assign cpuif_rd_err = readback_err;
{% endif %}
endmodule

View File

@@ -0,0 +1,4 @@
// TODO: Add a banner
package {{hwif.package_name}};
{{hwif.get_package_contents()|indent}}
endpackage

View File

@@ -0,0 +1,69 @@
from typing import TYPE_CHECKING
import math
from .generators import ReadbackAssignmentGenerator
from ..utils import get_always_ff_event
if TYPE_CHECKING:
from ..exporter import RegblockExporter
from systemrdl.node import AddrmapNode
class Readback:
def __init__(self, exp:'RegblockExporter', do_fanin_stage: bool):
self.exp = exp
self.do_fanin_stage = do_fanin_stage
@property
def top_node(self) -> 'AddrmapNode':
return self.exp.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.do_fanin_stage = False
context = {
"array_assignments" : array_assignments,
"array_size" : array_size,
"get_always_ff_event": lambda resetsignal : get_always_ff_event(self.exp.dereferencer, resetsignal),
"cpuif": self.exp.cpuif,
"do_fanin_stage": self.do_fanin_stage,
}
if self.do_fanin_stage:
# If adding a fanin pipeline stage, goal is to try to
# split the fanin path in the middle so that fanin into the stage
# and the following are roughly balanced.
fanin_target = math.sqrt(array_size)
# Size of fanin group to consume per fanin element
fanin_stride = math.floor(fanin_target)
# Number of array elements to reduce to.
# Round up to an extra element in case there is some residual
fanin_array_size = math.ceil(array_size / fanin_stride)
# leftovers are handled in an extra array element
fanin_residual_stride = array_size % fanin_stride
if fanin_residual_stride != 0:
# If there is a partial fanin element, reduce the number of
# loops performed in the bulk fanin stage
fanin_loop_iter = fanin_array_size - 1
else:
fanin_loop_iter = fanin_array_size
context['fanin_stride'] = fanin_stride
context['fanin_array_size'] = fanin_array_size
context['fanin_residual_stride'] = fanin_residual_stride
context['fanin_loop_iter'] = fanin_loop_iter
template = self.exp.jj_env.get_template(
"readback/templates/readback.sv"
)
return template.render(context)

View File

@@ -0,0 +1,107 @@
from typing import TYPE_CHECKING, List
from ..forloop_generator import RDLForLoopGenerator, LoopBody
if TYPE_CHECKING:
from ..exporter import RegblockExporter
from systemrdl.node import RegNode
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:'RegblockExporter') -> 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 enter_Reg(self, node: 'RegNode') -> None:
# TODO: account for smaller regs that are not aligned to the bus width
# - offset the field bit slice as appropriate
# - do not always increment the current offset
if node.has_sw_readable:
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 field.is_sw_readable:
# insert reserved assignment before if needed
if field.low != current_bit:
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.low-1}:{current_bit}] = '0;")
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}"
else:
value = self.exp.dereferencer.get_value(field)
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 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

View File

@@ -0,0 +1,68 @@
{% 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 do_fanin_stage %}
// 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 @(posedge clk) begin
if(rst) 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;
readback_done_r <= decoded_req & ~decoded_req_is_wr;
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;
readback_done = decoded_req & ~decoded_req_is_wr;
readback_err = '0;
readback_data_var = '0;
for(int i=0; i<{{array_size}}; i++) readback_data_var |= readback_array[i];
readback_data = readback_data_var;
end
{%- endif %}
{%- else %}
assign readback_done = decoded_req & ~decoded_req_is_wr;
assign readback_data = '0;
assign readback_err = '0;
{% endif %}

View File

@@ -0,0 +1,146 @@
from typing import TYPE_CHECKING, Set, List
from collections import OrderedDict
from systemrdl.walker import RDLListener, RDLWalker
from systemrdl.node import SignalNode, AddressableNode
if TYPE_CHECKING:
from systemrdl.node import Node, RegNode, FieldNode
from .exporter import RegblockExporter
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, exp:'RegblockExporter') -> None:
self.exp = exp
self.cpuif_data_width = 0
self.msg = exp.top_node.env.msg
# Keep track of max regwidth encountered in a given block
self.max_regwidth_stack = [] # type: List[int]
# 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]
def _get_out_of_hier_field_reset(self) -> None:
current_node = self.exp.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.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.exp.top_node.cpuif_reset
if cpuif_reset is not None:
path = cpuif_reset.get_path()
rel_path = cpuif_reset.get_rel_path(self.exp.top_node)
if rel_path.startswith("^"):
self.out_of_hier_signals[path] = cpuif_reset
else:
self.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.exp.top_node.get_property('bridge'):
self.msg.error(
"Regblock generator does not support exporting bridge address maps",
self.exp.top_node.inst.property_src_ref.get('bridge', self.exp.top_node.inst.inst_src_ref)
)
RDLWalker().walk(self.exp.top_node, self)
if self.msg.had_error:
self.msg.fatal(
"Unable to export due to previous errors"
)
raise ValueError
def enter_Reg(self, node: 'RegNode') -> None:
regwidth = node.get_property('regwidth')
self.max_regwidth_stack[-1] = max(self.max_regwidth_stack[-1], regwidth)
# The CPUIF's bus width is sized according to the largest register in the design
# TODO: make this user-overridable once more flexible regwidth/accesswidths are supported
self.cpuif_data_width = max(self.cpuif_data_width, regwidth)
# TODO: remove this limitation eventually
if regwidth != self.cpuif_data_width:
self.msg.error(
"register blocks with non-uniform regwidths are not supported yet",
node.inst.property_src_ref.get('regwidth', node.inst.inst_src_ref)
)
# TODO: remove this limitation eventually
if regwidth != node.get_property('accesswidth'):
self.msg.error(
"Registers that have an accesswidth different from the register width are not supported yet",
node.inst.property_src_ref.get('accesswidth', node.inst.inst_src_ref)
)
def enter_AddressableComponent(self, node: AddressableNode) -> None:
self.max_regwidth_stack.append(0)
def exit_AddressableComponent(self, node: AddressableNode) -> None:
max_block_regwidth = self.max_regwidth_stack.pop()
if self.max_regwidth_stack:
self.max_regwidth_stack[-1] = max(self.max_regwidth_stack[-1], max_block_regwidth)
alignment = int(max_block_regwidth / 8)
if (node.raw_address_offset % alignment) != 0:
self.msg.error(
f"Unaligned registers are not supported. Address offset of instance '{node.inst_name}' must be a multiple of {alignment}",
node.inst.inst_src_ref
)
if node.is_array and (node.array_stride % alignment) != 0:
self.msg.error(
f"Unaligned registers are not supported. Address stride of instance array '{node.inst_name}' must be a multiple of {alignment}",
node.inst.inst_src_ref
)
def enter_Component(self, node: 'Node') -> None:
if node.external and (node != self.exp.top_node):
self.msg.error(
"Exporter does not support external components",
node.inst.inst_src_ref
)
def enter_Signal(self, node: 'SignalNode') -> None:
# If encountering a CPUIF reset that is nested within the register model,
# warn that it will be ignored.
# Only cpuif resets in the top-level node or above will be honored
if node.get_property('cpuif_reset') and (node.parent != self.exp.top_node):
self.msg.warning(
"Only cpuif_reset signals that are instantiated in the top-level "
+ "addrmap or above will be honored. Any cpuif_reset signals nested "
+ "within children of the addrmap being exported will be ignored.",
node.inst.inst_src_ref
)
if node.get_property('field_reset'):
path = node.get_path()
self.in_hier_signal_paths.add(path)
def enter_Field(self, node: 'FieldNode') -> None:
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.exp.top_node)
if rel_path.startswith("^"):
self.out_of_hier_signals[path] = value
else:
self.in_hier_signal_paths.add(path)

View File

@@ -0,0 +1,237 @@
from typing import TYPE_CHECKING, Optional, List
import textwrap
from collections import OrderedDict
from systemrdl.walker import RDLListener, RDLWalker
if TYPE_CHECKING:
from typing import Union
from systemrdl.node import AddrmapNode, RegfileNode, RegNode, FieldNode, Node
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):
super().__init__()
self.type_name = type_name
self.inst_name = inst_name
self.array_dimensions = array_dimensions
def __str__(self) -> str:
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) -> None:
if array_dimensions:
suffix = "[" + "][".join((str(n) for n in array_dimensions)) + "]"
else:
suffix = ""
if width == 1:
m = f"logic {name}{suffix};"
else:
m = f"logic [{width-1}:0] {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') -> None:
self.push_struct(node.inst_name, node.array_dimensions)
def exit_Addrmap(self, node: 'AddrmapNode') -> None:
self.pop_struct()
def enter_Regfile(self, node: 'RegfileNode') -> None:
self.push_struct(node.inst_name, node.array_dimensions)
def exit_Regfile(self, node: 'RegfileNode') -> None:
self.pop_struct()
def enter_Reg(self, node: 'RegNode') -> None:
self.push_struct(node.inst_name, node.array_dimensions)
def exit_Reg(self, node: 'RegNode') -> None:
self.pop_struct()
def enter_Field(self, node: 'FieldNode') -> None:
self.add_member(node.inst_name, node.width)
#-------------------------------------------------------------------------------
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) -> None: # type: ignore # pylint: disable=arguments-differ
s = _TypedefStruct(type_name, inst_name, array_dimensions)
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') -> None:
type_name = self.get_typdef_name(node)
self.push_struct(type_name, node.inst_name, node.array_dimensions)
def exit_Addrmap(self, node: 'AddrmapNode') -> None:
self.pop_struct()
def enter_Regfile(self, node: 'RegfileNode') -> None:
type_name = self.get_typdef_name(node)
self.push_struct(type_name, node.inst_name, node.array_dimensions)
def exit_Regfile(self, node: 'RegfileNode') -> None:
self.pop_struct()
def enter_Reg(self, node: 'RegNode') -> None:
type_name = self.get_typdef_name(node)
self.push_struct(type_name, node.inst_name, node.array_dimensions)
def exit_Reg(self, node: 'RegNode') -> None:
self.pop_struct()
def enter_Field(self, node: 'FieldNode') -> None:
self.add_member(node.inst_name, node.width)

View File

@@ -0,0 +1,38 @@
import re
from typing import TYPE_CHECKING, Match
if TYPE_CHECKING:
from systemrdl.node import Node, SignalNode
from typing import Optional
from .dereferencer import Dereferencer
def get_indexed_path(top_node: 'Node', target_node: 'Node') -> str:
"""
TODO: Add words about indexing and why i'm doing this. Copy from logbook
"""
path = target_node.get_rel_path(top_node, empty_array_suffix="[!]")
# replace unknown indexes with incrementing iterators i0, i1, ...
class repl:
def __init__(self) -> None:
self.i = 0
def __call__(self, match: Match) -> str:
s = f'i{self.i}'
self.i += 1
return s
return re.sub(r'!', repl(), path)
def get_always_ff_event(dereferencer: 'Dereferencer', resetsignal: 'Optional[SignalNode]') -> str:
if resetsignal is None:
return "@(posedge clk)"
if resetsignal.get_property('async') and resetsignal.get_property('activehigh'):
return f"@(posedge clk or posedge {dereferencer.get_value(resetsignal)})"
elif resetsignal.get_property('async') and not resetsignal.get_property('activehigh'):
return f"@(posedge clk or negedge {dereferencer.get_value(resetsignal)})"
return "@(posedge clk)"
def clog2(n: int) -> int:
return (n-1).bit_length()
def is_pow2(x: int) -> bool:
return (x > 0) and ((x & (x - 1)) == 0)