feat: Add support for OBI protocol (#158)

Signed-off-by: Daniel Keller <daniel.kellermartinez@csem.ch>
This commit is contained in:
Daniel Keller
2025-09-22 16:54:27 +02:00
committed by Alex Mykyta
parent 18cf2aabc7
commit aa9a21046d
3 changed files with 150 additions and 1 deletions

View File

@@ -7,7 +7,7 @@ from peakrdl.config import schema
from peakrdl.plugins.entry_points import get_entry_points from peakrdl.plugins.entry_points import get_entry_points
from .exporter import RegblockExporter from .exporter import RegblockExporter
from .cpuif import CpuifBase, apb3, apb4, axi4lite, passthrough, avalon from .cpuif import CpuifBase, apb3, apb4, axi4lite, passthrough, avalon, obi
from .udps import ALL_UDPS from .udps import ALL_UDPS
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -38,6 +38,8 @@ class Exporter(ExporterSubcommandPlugin):
"axi4-lite-flat": axi4lite.AXI4Lite_Cpuif_flattened, "axi4-lite-flat": axi4lite.AXI4Lite_Cpuif_flattened,
"avalon-mm": avalon.Avalon_Cpuif, "avalon-mm": avalon.Avalon_Cpuif,
"avalon-mm-flat": avalon.Avalon_Cpuif_flattened, "avalon-mm-flat": avalon.Avalon_Cpuif_flattened,
"obi": obi.OBI_Cpuif,
"obi-flat": obi.OBI_Cpuif_flattened,
} }
# Load any cpuifs specified via entry points # Load any cpuifs specified via entry points

View File

@@ -0,0 +1,56 @@
from ..base import CpuifBase
class OBI_Cpuif(CpuifBase):
template_path = "obi_tmpl.sv"
is_interface = True
@property
def port_declaration(self) -> str:
return "obi_intf.subordinate obi"
def signal(self, name: str) -> str:
return "obi." + name
@property
def regblock_latency(self) -> int:
return max(self.exp.ds.min_read_latency, self.exp.ds.min_write_latency)
@property
def max_outstanding(self) -> int:
"""
OBI supports multiple outstanding transactions.
Best performance when max outstanding is design latency + 1.
"""
return self.regblock_latency + 1
class OBI_Cpuif_flattened(OBI_Cpuif):
is_interface = False
@property
def port_declaration(self) -> str:
lines = [
# OBI Request Channel (A)
"input wire " + self.signal("req"),
f"input wire [{self.addr_width-1}:0] " + self.signal("addr"),
"input wire " + self.signal("we"),
f"input wire [{self.data_width//8-1}:0] " + self.signal("be"),
f"input wire [{self.data_width-1}:0] " + self.signal("wdata"),
f"input wire [{self.id_width-1}:0] " + self.signal("aid"),
# OBI Response Channel (R)
"output logic " + self.signal("gnt"),
"output logic " + self.signal("rvalid"),
f"output logic [{self.data_width-1}:0] " + self.signal("rdata"),
f"output logic [{self.id_width-1}:0] " + self.signal("rid"),
"output logic " + self.signal("err"),
"input wire " + self.signal("rready"),
]
return ",\n".join(lines)
def signal(self, name: str) -> str:
return "obi_" + name
@property
def id_width(self) -> int:
return 1 # Default ID width

View File

@@ -0,0 +1,91 @@
{%- if cpuif.is_interface -%}
`ifndef SYNTHESIS
initial begin
assert_bad_addr_width: assert($bits({{cpuif.signal("addr")}}) >= {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH)
else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("addr")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH);
assert_bad_data_width: assert($bits({{cpuif.signal("wdata")}}) == {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH)
else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("wdata")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH);
end
`endif
{% endif -%}
// OBI Interface Implementation
// This register block acts as an OBI subordinate
localparam int unsigned DATA_WIDTH = {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH;
localparam int unsigned BYTES = DATA_WIDTH/8;
// State & holding regs
logic is_active; // A request is being served (not yet fully responded)
logic gnt_q; // one-cycle grant for A-channel
logic rsp_pending; // response ready but not yet accepted by manager
logic [DATA_WIDTH-1:0] rsp_rdata_q;
logic rsp_err_q;
logic [$bits({{cpuif.signal("aid")}})-1:0] rid_q;
// Latch AID on accept to echo back the response
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if ({{get_resetsignal(cpuif.reset)}}) begin
is_active <= 1'b0;
gnt_q <= 1'b0;
rsp_pending <= 1'b0;
rsp_rdata_q <= '0;
rsp_err_q <= 1'b0;
rid_q <= '0;
cpuif_req <= '0;
cpuif_req_is_wr <= '0;
cpuif_addr <= '0;
cpuif_wr_data <= '0;
cpuif_wr_biten <= '0;
end else begin
// defaults
cpuif_req <= 1'b0;
gnt_q <= {{cpuif.signal("req")}} & ~is_active;
// Accept new request when idle
if (~is_active) begin
if ({{cpuif.signal("req")}}) begin
is_active <= 1'b1;
cpuif_req <= 1'b1;
cpuif_req_is_wr <= {{cpuif.signal("we")}};
cpuif_addr <= {{cpuif.signal("addr")}};
cpuif_wr_data <= {{cpuif.signal("wdata")}};
rid_q <= {{cpuif.signal("aid")}};
for (int i = 0; i < BYTES; i++) begin
cpuif_wr_biten[i*8 +: 8] <= {8{ {{cpuif.signal("be")}}[i] }};
end
end
end
// Capture response
if (is_active && (cpuif_rd_ack || cpuif_wr_ack)) begin
rsp_pending <= 1'b1;
rsp_rdata_q <= cpuif_rd_data;
rsp_err_q <= cpuif_rd_err | cpuif_wr_err;
// NOTE: Keep 'is_active' asserted until the external R handshake completes
end
// Complete external R-channel handshake only if manager ready
if (rsp_pending && {{cpuif.signal("rvalid")}} && {{cpuif.signal("rready")}}) begin
rsp_pending <= 1'b0;
is_active <= 1'b0; // free to accept the next request
end
end
end
// R-channel outputs (held stable while rsp_pending=1)
assign {{cpuif.signal("rvalid")}} = rsp_pending;
assign {{cpuif.signal("rdata")}} = rsp_rdata_q;
assign {{cpuif.signal("err")}} = rsp_err_q;
assign {{cpuif.signal("rid")}} = rid_q;
// A-channel grant (registered one-cycle pulse when we accept a request)
assign {{cpuif.signal("gnt")}} = gnt_q;
// If OBI config RReady is disabled, tie it high in the top-level/TB.
// `ifndef SYNTHESIS
// initial begin
// if (0) $display("RReady supported; tie high if unused.");
// end
// `endif