From aa9a21046dc394d4f55fd97b1c502c788aa7b18c Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Mon, 22 Sep 2025 16:54:27 +0200 Subject: [PATCH] feat: Add support for OBI protocol (#158) Signed-off-by: Daniel Keller --- src/peakrdl_regblock/__peakrdl__.py | 4 +- src/peakrdl_regblock/cpuif/obi/__init__.py | 56 +++++++++++++ src/peakrdl_regblock/cpuif/obi/obi_tmpl.sv | 91 ++++++++++++++++++++++ 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 src/peakrdl_regblock/cpuif/obi/__init__.py create mode 100644 src/peakrdl_regblock/cpuif/obi/obi_tmpl.sv diff --git a/src/peakrdl_regblock/__peakrdl__.py b/src/peakrdl_regblock/__peakrdl__.py index c67bdb5..21ac379 100644 --- a/src/peakrdl_regblock/__peakrdl__.py +++ b/src/peakrdl_regblock/__peakrdl__.py @@ -7,7 +7,7 @@ from peakrdl.config import schema from peakrdl.plugins.entry_points import get_entry_points 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 if TYPE_CHECKING: @@ -38,6 +38,8 @@ class Exporter(ExporterSubcommandPlugin): "axi4-lite-flat": axi4lite.AXI4Lite_Cpuif_flattened, "avalon-mm": avalon.Avalon_Cpuif, "avalon-mm-flat": avalon.Avalon_Cpuif_flattened, + "obi": obi.OBI_Cpuif, + "obi-flat": obi.OBI_Cpuif_flattened, } # Load any cpuifs specified via entry points diff --git a/src/peakrdl_regblock/cpuif/obi/__init__.py b/src/peakrdl_regblock/cpuif/obi/__init__.py new file mode 100644 index 0000000..0beb9d5 --- /dev/null +++ b/src/peakrdl_regblock/cpuif/obi/__init__.py @@ -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 diff --git a/src/peakrdl_regblock/cpuif/obi/obi_tmpl.sv b/src/peakrdl_regblock/cpuif/obi/obi_tmpl.sv new file mode 100644 index 0000000..efb6951 --- /dev/null +++ b/src/peakrdl_regblock/cpuif/obi/obi_tmpl.sv @@ -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