Skip to content

Commit 0c29f9a

Browse files
DanielKellerMamykyta3
authored andcommitted
feat: Add support for OBI protocol (#158)
Signed-off-by: Daniel Keller <daniel.kellermartinez@csem.ch>
1 parent b097062 commit 0c29f9a

File tree

3 files changed

+150
-1
lines changed

3 files changed

+150
-1
lines changed

src/peakrdl_regblock/__peakrdl__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from peakrdl.plugins.entry_points import get_entry_points
88

99
from .exporter import RegblockExporter
10-
from .cpuif import CpuifBase, apb3, apb4, axi4lite, passthrough, avalon
10+
from .cpuif import CpuifBase, apb3, apb4, axi4lite, passthrough, avalon, obi
1111
from .udps import ALL_UDPS
1212

1313
if TYPE_CHECKING:
@@ -38,6 +38,8 @@ def get_cpuifs(self) -> Dict[str, Type[CpuifBase]]:
3838
"axi4-lite-flat": axi4lite.AXI4Lite_Cpuif_flattened,
3939
"avalon-mm": avalon.Avalon_Cpuif,
4040
"avalon-mm-flat": avalon.Avalon_Cpuif_flattened,
41+
"obi": obi.OBI_Cpuif,
42+
"obi-flat": obi.OBI_Cpuif_flattened,
4143
}
4244

4345
# Load any cpuifs specified via entry points
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from ..base import CpuifBase
2+
3+
class OBI_Cpuif(CpuifBase):
4+
template_path = "obi_tmpl.sv"
5+
is_interface = True
6+
7+
@property
8+
def port_declaration(self) -> str:
9+
return "obi_intf.subordinate obi"
10+
11+
def signal(self, name: str) -> str:
12+
return "obi." + name
13+
14+
@property
15+
def regblock_latency(self) -> int:
16+
return max(self.exp.ds.min_read_latency, self.exp.ds.min_write_latency)
17+
18+
@property
19+
def max_outstanding(self) -> int:
20+
"""
21+
OBI supports multiple outstanding transactions.
22+
Best performance when max outstanding is design latency + 1.
23+
"""
24+
return self.regblock_latency + 1
25+
26+
27+
class OBI_Cpuif_flattened(OBI_Cpuif):
28+
is_interface = False
29+
30+
@property
31+
def port_declaration(self) -> str:
32+
lines = [
33+
# OBI Request Channel (A)
34+
"input wire " + self.signal("req"),
35+
f"input wire [{self.addr_width-1}:0] " + self.signal("addr"),
36+
"input wire " + self.signal("we"),
37+
f"input wire [{self.data_width//8-1}:0] " + self.signal("be"),
38+
f"input wire [{self.data_width-1}:0] " + self.signal("wdata"),
39+
f"input wire [{self.id_width-1}:0] " + self.signal("aid"),
40+
41+
# OBI Response Channel (R)
42+
"output logic " + self.signal("gnt"),
43+
"output logic " + self.signal("rvalid"),
44+
f"output logic [{self.data_width-1}:0] " + self.signal("rdata"),
45+
f"output logic [{self.id_width-1}:0] " + self.signal("rid"),
46+
"output logic " + self.signal("err"),
47+
"input wire " + self.signal("rready"),
48+
]
49+
return ",\n".join(lines)
50+
51+
def signal(self, name: str) -> str:
52+
return "obi_" + name
53+
54+
@property
55+
def id_width(self) -> int:
56+
return 1 # Default ID width
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
{%- if cpuif.is_interface -%}
2+
`ifndef SYNTHESIS
3+
initial begin
4+
assert_bad_addr_width: assert($bits({{cpuif.signal("addr")}}) >= {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH)
5+
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);
6+
assert_bad_data_width: assert($bits({{cpuif.signal("wdata")}}) == {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH)
7+
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);
8+
end
9+
`endif
10+
{% endif -%}
11+
12+
// OBI Interface Implementation
13+
// This register block acts as an OBI subordinate
14+
15+
localparam int unsigned DATA_WIDTH = {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH;
16+
localparam int unsigned BYTES = DATA_WIDTH/8;
17+
18+
// State & holding regs
19+
logic is_active; // A request is being served (not yet fully responded)
20+
logic gnt_q; // one-cycle grant for A-channel
21+
logic rsp_pending; // response ready but not yet accepted by manager
22+
logic [DATA_WIDTH-1:0] rsp_rdata_q;
23+
logic rsp_err_q;
24+
logic [$bits({{cpuif.signal("aid")}})-1:0] rid_q;
25+
26+
// Latch AID on accept to echo back the response
27+
always_ff {{get_always_ff_event(cpuif.reset)}} begin
28+
if ({{get_resetsignal(cpuif.reset)}}) begin
29+
is_active <= 1'b0;
30+
gnt_q <= 1'b0;
31+
rsp_pending <= 1'b0;
32+
rsp_rdata_q <= '0;
33+
rsp_err_q <= 1'b0;
34+
rid_q <= '0;
35+
36+
cpuif_req <= '0;
37+
cpuif_req_is_wr <= '0;
38+
cpuif_addr <= '0;
39+
cpuif_wr_data <= '0;
40+
cpuif_wr_biten <= '0;
41+
end else begin
42+
// defaults
43+
cpuif_req <= 1'b0;
44+
gnt_q <= {{cpuif.signal("req")}} & ~is_active;
45+
46+
// Accept new request when idle
47+
if (~is_active) begin
48+
if ({{cpuif.signal("req")}}) begin
49+
is_active <= 1'b1;
50+
cpuif_req <= 1'b1;
51+
cpuif_req_is_wr <= {{cpuif.signal("we")}};
52+
cpuif_addr <= {{cpuif.signal("addr")}};
53+
cpuif_wr_data <= {{cpuif.signal("wdata")}};
54+
rid_q <= {{cpuif.signal("aid")}};
55+
for (int i = 0; i < BYTES; i++) begin
56+
cpuif_wr_biten[i*8 +: 8] <= {8{ {{cpuif.signal("be")}}[i] }};
57+
end
58+
end
59+
end
60+
61+
// Capture response
62+
if (is_active && (cpuif_rd_ack || cpuif_wr_ack)) begin
63+
rsp_pending <= 1'b1;
64+
rsp_rdata_q <= cpuif_rd_data;
65+
rsp_err_q <= cpuif_rd_err | cpuif_wr_err;
66+
// NOTE: Keep 'is_active' asserted until the external R handshake completes
67+
end
68+
69+
// Complete external R-channel handshake only if manager ready
70+
if (rsp_pending && {{cpuif.signal("rvalid")}} && {{cpuif.signal("rready")}}) begin
71+
rsp_pending <= 1'b0;
72+
is_active <= 1'b0; // free to accept the next request
73+
end
74+
end
75+
end
76+
77+
// R-channel outputs (held stable while rsp_pending=1)
78+
assign {{cpuif.signal("rvalid")}} = rsp_pending;
79+
assign {{cpuif.signal("rdata")}} = rsp_rdata_q;
80+
assign {{cpuif.signal("err")}} = rsp_err_q;
81+
assign {{cpuif.signal("rid")}} = rid_q;
82+
83+
// A-channel grant (registered one-cycle pulse when we accept a request)
84+
assign {{cpuif.signal("gnt")}} = gnt_q;
85+
86+
// If OBI config RReady is disabled, tie it high in the top-level/TB.
87+
// `ifndef SYNTHESIS
88+
// initial begin
89+
// if (0) $display("RReady supported; tie high if unused.");
90+
// end
91+
// `endif

0 commit comments

Comments
 (0)