From fd659081e03b791567d72d8d96efb64c4dcc81f9 Mon Sep 17 00:00:00 2001 From: Luca Rufer Date: Wed, 1 Jul 2026 14:47:37 +0200 Subject: [PATCH] [Atop Filter] Add an obi_atop_filter module to filter-out atomic requests --- Bender.yml | 2 + Makefile | 2 +- src/obi_atop_filter.sv | 365 +++++++++++++++++++++++++++++++++ src/test/tb_obi_atop_filter.sv | 318 ++++++++++++++++++++++++++++ 4 files changed, 686 insertions(+), 1 deletion(-) create mode 100644 src/obi_atop_filter.sv create mode 100644 src/test/tb_obi_atop_filter.sv diff --git a/Bender.yml b/Bender.yml index f6220e8..d55cb4e 100644 --- a/Bender.yml +++ b/Bender.yml @@ -24,6 +24,7 @@ sources: - src/obi_to_apb.sv # Level 3 - src/obi_atop_resolver.sv + - src/obi_atop_filter.sv - src/obi_cut.sv - src/obi_demux.sv - src/obi_err_sbr.sv @@ -40,3 +41,4 @@ sources: - src/test/tb_obi_xbar.sv - src/test/atop_golden_mem_pkg.sv - src/test/tb_obi_atop_resolver.sv + - src/test/tb_obi_atop_filter.sv diff --git a/Makefile b/Makefile index f065071..6f9af56 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ BENDER ?= bender VSIM ?= vsim -AVAILABLE_TESTBENCHES = tb_obi_xbar tb_obi_atop_resolver +AVAILABLE_TESTBENCHES = tb_obi_xbar tb_obi_atop_resolver tb_obi_atop_filter scripts/compile.tcl: mkdir -p scripts diff --git a/src/obi_atop_filter.sv b/src/obi_atop_filter.sv new file mode 100644 index 0000000..c0b6571 --- /dev/null +++ b/src/obi_atop_filter.sv @@ -0,0 +1,365 @@ +// Copyright 2026 Mosaic SoC Ltd. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "obi/typedef.svh" +`include "common_cells/assertions.svh" + +/// Filter atomic operations (ATOPs) in a protocol-compliant manner. +/// +/// This module filters atomic operations (ATOPs), i.e., write transactions that have a non-zero +/// `a.atop` value, from its `sbr` to its `mgr` port. This module guarantees that: +/// +/// 1) `a.atop` is always `obi_pkg::ATOPNONE` on the `mgr` port; +/// +/// 2) Transactions with non-zero `a.atop` on the `sbr` port are handled in conformance with the +/// OBI standard by replying to such transactions with `{err, exokay} = 2’b10`. +/// +/// ## Intended usage +/// This module is intended to be placed between managers that may issue ATOPs and subordinates that +/// do not support ATOPs. That way, this module ensures that the OBI protocol remains in a defined +/// state on systems with mixed ATOP capabilities. +/// +/// This module does not support OBI integrity. +module obi_atop_filter + import obi_pkg::*; +#( + /// The configuration of the subordinate ports (input ports). + parameter obi_pkg::obi_cfg_t SbrPortObiCfg = obi_pkg::ObiDefaultConfig, + /// The configuration of the manager port (output port). + parameter obi_pkg::obi_cfg_t MgrPortObiCfg = SbrPortObiCfg, + /// The request struct for the subordinate port (input ports). + parameter type sbr_port_obi_req_t = logic, + /// The response struct for the subordinate port (input ports). + parameter type sbr_port_obi_rsp_t = logic, + /// The request struct for the manager port (output port). + parameter type mgr_port_obi_req_t = sbr_port_obi_req_t, + /// The response struct for the manager ports (output ports). + parameter type mgr_port_obi_rsp_t = sbr_port_obi_rsp_t, + /// The optional field structs. + parameter type mgr_port_obi_a_optional_t = logic, + parameter type sbr_port_obi_r_optional_t = logic, + /// Maximum number of transactions in-flight + parameter int unsigned MaxTrans = 32'd2 +) ( + input logic clk_i, + input logic rst_ni, + input logic testmode_i, + + input sbr_port_obi_req_t sbr_port_req_i, + output sbr_port_obi_rsp_t sbr_port_rsp_o, + + output mgr_port_obi_req_t mgr_port_req_o, + input mgr_port_obi_rsp_t mgr_port_rsp_i +); + + localparam int unsigned TransCounterBitWidth = (MaxTrans == 1) ? 2 : $clog2(MaxTrans+1); + + mgr_port_obi_a_optional_t a_optional; + if (MgrPortObiCfg.OptionalCfg.AUserWidth) begin : gen_auser + if (SbrPortObiCfg.OptionalCfg.AUserWidth) begin : gen_auser_assign + always_comb begin + a_optional.auser = '0; + a_optional.auser = sbr_port_req_i.a.a_optional.auser; + end + end else begin : gen_no_auser + assign a_optional.auser = '0; + end + end + if (MgrPortObiCfg.OptionalCfg.WUserWidth) begin : gen_wuser + if (SbrPortObiCfg.OptionalCfg.WUserWidth) begin : gen_wuser_assign + always_comb begin + a_optional.wuser = '0; + a_optional.wuser = sbr_port_req_i.a.a_optional.wuser; + end + end else begin : gen_no_wuser + assign a_optional.wuser = '0; + end + end + if (MgrPortObiCfg.OptionalCfg.UseAtop) begin : gen_atop + // Atop not forwarded from Sbr port. + assign a_optional.atop = obi_pkg::DefaultAtop; + end + if (MgrPortObiCfg.OptionalCfg.UseProt) begin : gen_prot + if (SbrPortObiCfg.OptionalCfg.UseProt) begin : gen_prot_assign + assign a_optional.prot = sbr_port_req_i.a.a_optional.prot; + end else begin : gen_no_prot + assign a_optional.prot = obi_pkg::DefaultProt; + end + end + if (MgrPortObiCfg.OptionalCfg.UseMemtype) begin : gen_memtype + if (SbrPortObiCfg.OptionalCfg.UseMemtype) begin : gen_memtype_assign + assign a_optional.memtype = sbr_port_req_i.a.a_optional.memtype; + end else begin : gen_no_memtype + assign a_optional.memtype = obi_pkg::DefaultMemtype; + end + end + if (MgrPortObiCfg.OptionalCfg.MidWidth) begin : gen_mid + if (SbrPortObiCfg.OptionalCfg.MidWidth) begin : gen_mid_assign + always_comb begin + a_optional.mid = '0; + a_optional.mid = sbr_port_req_i.a.a_optional.mid; + end + end else begin : gen_no_mid + assign a_optional.mid = '0; + end + end + if (MgrPortObiCfg.OptionalCfg.UseDbg) begin : gen_dbg + if (SbrPortObiCfg.OptionalCfg.UseDbg) begin : gen_dbg_assign + assign a_optional.dbg = sbr_port_req_i.a.a_optional.dbg; + end else begin : gen_no_dbg + assign a_optional.dbg = '0; + end + end + if (MgrPortObiCfg.OptionalCfg.AChkWidth) begin : gen_achk + if (SbrPortObiCfg.OptionalCfg.AChkWidth) begin : gen_achk_assign + assign a_optional.achk = sbr_port_req_i.a.a_optional.achk; + end else begin : gen_no_achk + assign a_optional.achk = '0; + end + end + + if (!MgrPortObiCfg.OptionalCfg.AUserWidth && + !MgrPortObiCfg.OptionalCfg.WUserWidth && + !MgrPortObiCfg.OptionalCfg.UseAtop && + !MgrPortObiCfg.OptionalCfg.UseProt && + !MgrPortObiCfg.OptionalCfg.UseMemtype && + !MgrPortObiCfg.OptionalCfg.MidWidth && + !MgrPortObiCfg.OptionalCfg.UseDbg && + !MgrPortObiCfg.OptionalCfg.AChkWidth) begin : gen_no_optional + assign a_optional = '0; + end + + sbr_port_obi_r_optional_t r_optional; + + if (SbrPortObiCfg.OptionalCfg.RUserWidth) begin : gen_ruser + if (MgrPortObiCfg.OptionalCfg.RUserWidth) begin : gen_ruser_assign + assign r_optional.ruser = mgr_port_rsp_i.r.r_optional.ruser; + end else begin : gen_no_ruser + assign r_optional.ruser = '0; + end + end + + assign r_optional.exokay = '0; + + logic [TransCounterBitWidth-1:0] trans_counter_d, trans_counter_q; + logic atop_req_pending_d, atop_req_pending_q; + logic atop_detected_d, atop_detected_q; + logic [SbrPortObiCfg.IdWidth-1:0] err_id_d, err_id_q; + + logic mgr_port_req_rready, sbr_port_req_rready; + logic is_atop; + logic inject_atop_err_resp; + + assign is_atop = sbr_port_req_i.a.a_optional.atop[5]; + + assign inject_atop_err_resp = atop_detected_q & (trans_counter_q == 0); + + always_comb begin : proc_req_rsp + // State + atop_req_pending_d = 1'b0; + + // Request feed-through + mgr_port_req_o.a.addr = sbr_port_req_i.a.addr; + mgr_port_req_o.a.we = sbr_port_req_i.a.we; + mgr_port_req_o.a.be = sbr_port_req_i.a.be; + mgr_port_req_o.a.wdata = sbr_port_req_i.a.wdata; + mgr_port_req_o.a.aid = sbr_port_req_i.a.aid; + mgr_port_req_o.a.a_optional = a_optional; + + // By default, forward the signals request handshake signals + mgr_port_req_o.req = sbr_port_req_i.req; + sbr_port_rsp_o.gnt = mgr_port_rsp_i.gnt; + + // Request Handshake signals + if (atop_detected_q | (trans_counter_q == MaxTrans)) begin + // Atomic OP detected or Counter full. Don't grant any new requests + mgr_port_req_o.req = 1'b0; + sbr_port_rsp_o.gnt = 1'b0; + end else begin + // Prevent forwarding atop requests + if (sbr_port_req_i.req & is_atop) begin + mgr_port_req_o.req = 1'b0; + end + + if (SbrPortObiCfg.CombGnt) begin + // If CombGnt is allowed, atop requests can be granted right away because they are handled + // locally. + if (sbr_port_req_i.req & is_atop) begin + sbr_port_rsp_o.gnt = 1'b1; + end + end else begin + // If CombGnt is not allowed, atop requests cannot be granted by this module right away, as + // this would create an illegal dependency. + // If the request is not granted in the same cycle, set a flag and grant it in the next + // cycle. + if (sbr_port_req_i.req & is_atop & !sbr_port_rsp_o.gnt & !atop_req_pending_q) begin + atop_req_pending_d = 1'b1; + end + if (atop_req_pending_q) begin + sbr_port_rsp_o.gnt = 1'b1; + end + end + end + + // Response + sbr_port_rsp_o.r.rdata = mgr_port_rsp_i.r.rdata; + sbr_port_rsp_o.r.rid = mgr_port_rsp_i.r.rid; + sbr_port_rsp_o.r.err = mgr_port_rsp_i.r.err; + sbr_port_rsp_o.r.r_optional = r_optional; + + mgr_port_req_rready = sbr_port_req_rready; + sbr_port_rsp_o.rvalid = mgr_port_rsp_i.rvalid; + + // Inject atop error response + if (inject_atop_err_resp) begin + sbr_port_rsp_o.r.rdata = '0; + sbr_port_rsp_o.r.rid = err_id_q; + sbr_port_rsp_o.r.err = 1'b1; + sbr_port_rsp_o.r.r_optional = '0; + + sbr_port_rsp_o.rvalid = 1'b1; + mgr_port_req_rready = 1'b0; + end + end + + always_comb begin : proc_atop_state + atop_detected_d = atop_detected_q; + err_id_d = err_id_q; + + // Save the request id on an atop request + if (sbr_port_req_i.req & sbr_port_rsp_o.gnt & is_atop & !atop_detected_q) begin + atop_detected_d = 1'b1; + err_id_d = sbr_port_req_i.a.aid; + end + + // Clear atop detected when the injected error response is accepted + if (sbr_port_rsp_o.rvalid & sbr_port_req_rready & inject_atop_err_resp) begin + atop_detected_d = 1'b0; + end + end + + always_comb begin : proc_counter + // Counter for outstanding transactions to be completed in-order + trans_counter_d = trans_counter_q; + if (mgr_port_rsp_i.rvalid & mgr_port_req_rready) begin + trans_counter_d--; + end + if (mgr_port_req_o.req & mgr_port_rsp_i.gnt) begin + trans_counter_d++; + end + end + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + trans_counter_q <= '0; + atop_req_pending_q <= '0; + atop_detected_q <= '0; + err_id_q <= '0; + end else begin + trans_counter_q <= trans_counter_d; + atop_req_pending_q <= atop_req_pending_d; + atop_detected_q <= atop_detected_d; + err_id_q <= err_id_d; + end + end + + if (MgrPortObiCfg.UseRReady) begin : gen_mgr_rready + assign mgr_port_req_o.rready = mgr_port_req_rready; + end + + if (SbrPortObiCfg.UseRReady) begin : gen_sbr_rready + assign sbr_port_req_rready = sbr_port_req_i.rready; + end else begin + assign sbr_port_req_rready = 1'b1; + end + +`ifndef OBI_ASSERTS_OFF + `ASSERT_INIT(sbr_atop_enabled, SbrPortObiCfg.OptionalCfg.UseAtop, + "obi_atop_filter: SbrPortObiCfg requires UseAtop.") + `ASSERT_INIT(integrity_unsupported, !SbrPortObiCfg.Integrity && !MgrPortObiCfg.Integrity, + "obi_atop_filter: Integrity not supported") + `ASSERT_INIT(max_trans_nonzero, MaxTrans >= 1, + "obi_atop_filter: MaxTrans must be >= 1") + `ASSERT_INIT(equal_addr_width, $bits(sbr_port_req_i.a.addr) == $bits(mgr_port_req_o.a.addr), + "obi_atop_filter: Address width mismatch between sbr and mgr ports!") + `ASSERT_INIT(equal_data_width, $bits(sbr_port_req_i.a.wdata) == $bits(mgr_port_req_o.a.wdata), + "obi_atop_filter: Data width mismatch between sbr and mgr ports!") + `ASSERT_INIT(equal_id_width, $bits(sbr_port_req_i.a.aid) == $bits(mgr_port_req_o.a.aid), + "obi_atop_filter: ID width mismatch between sbr and mgr ports") + if (MgrPortObiCfg.OptionalCfg.UseAtop) begin : gen_assert_mgr_atop_none + `ASSERT(mgr_atop_is_none, + mgr_port_req_o.req |-> (mgr_port_req_o.a.a_optional.atop == obi_pkg::ATOPNONE), + clk_i, !rst_ni, "obi_atop_filter: manager port atop is not ATOPNONE!") + `ASSERT(mgr_exokay_is_zero, + mgr_port_rsp_i.rvalid |-> !mgr_port_rsp_i.r.r_optional.exokay, + clk_i, !rst_ni, "obi_atop_filter: manager port exokay is not zero!") + end + `ASSERT(sbr_exokay_is_zero, + sbr_port_rsp_o.rvalid |-> !sbr_port_rsp_o.r.r_optional.exokay, + clk_i, !rst_ni, "obi_atop_filter: sbr port exokay is not zero!") + `ASSERT(counter_no_underflow, + (trans_counter_q == '0) |-> !(mgr_port_rsp_i.rvalid & mgr_port_req_rready), + clk_i, !rst_ni, "obi_atop_filter: transaction counter underflow!") + `ASSERT(counter_no_overflow, + (trans_counter_q == MaxTrans) |-> + !(mgr_port_req_o.req & mgr_port_rsp_i.gnt) | (mgr_port_rsp_i.rvalid & mgr_port_req_rready), + clk_i, !rst_ni, "obi_atop_filter: transaction counter overflow!") +`endif + +endmodule + +module obi_atop_filter_intf + import obi_pkg::*; +#( + /// The configuration of the subordinate ports (input ports). + parameter obi_pkg::obi_cfg_t SbrPortObiCfg = obi_pkg::ObiDefaultConfig, + /// The configuration of the manager port (output port). + parameter obi_pkg::obi_cfg_t MgrPortObiCfg = SbrPortObiCfg, + /// Maximum number of transactions in-flight + parameter int unsigned MaxTrans = 32'd2 +) ( + input logic clk_i, + input logic rst_ni, + input logic testmode_i, + + OBI_BUS.Subordinate sbr_port, + OBI_BUS.Manager mgr_port +); + + `OBI_TYPEDEF_ALL(sbr_port_obi, SbrPortObiCfg) + `OBI_TYPEDEF_ALL(mgr_port_obi, MgrPortObiCfg) + + sbr_port_obi_req_t sbr_port_req; + sbr_port_obi_rsp_t sbr_port_rsp; + + mgr_port_obi_req_t mgr_port_req; + mgr_port_obi_rsp_t mgr_port_rsp; + + `OBI_ASSIGN_TO_REQ(sbr_port_req, sbr_port, SbrPortObiCfg) + `OBI_ASSIGN_FROM_RSP(sbr_port, sbr_port_rsp, SbrPortObiCfg) + + `OBI_ASSIGN_FROM_REQ(mgr_port, mgr_port_req, MgrPortObiCfg) + `OBI_ASSIGN_TO_RSP(mgr_port_rsp, mgr_port, MgrPortObiCfg) + + obi_atop_filter #( + .SbrPortObiCfg (SbrPortObiCfg), + .MgrPortObiCfg (MgrPortObiCfg), + .sbr_port_obi_req_t (sbr_port_obi_req_t), + .sbr_port_obi_rsp_t (sbr_port_obi_rsp_t), + .mgr_port_obi_req_t (mgr_port_obi_req_t), + .mgr_port_obi_rsp_t (mgr_port_obi_rsp_t), + .mgr_port_obi_a_optional_t(mgr_port_obi_a_optional_t), + .sbr_port_obi_r_optional_t(sbr_port_obi_r_optional_t), + .MaxTrans (MaxTrans) + ) i_obi_atop_filter ( + .clk_i, + .rst_ni, + .testmode_i, + .sbr_port_req_i(sbr_port_req), + .sbr_port_rsp_o(sbr_port_rsp), + .mgr_port_req_o(mgr_port_req), + .mgr_port_rsp_i(mgr_port_rsp) + ); + +endmodule diff --git a/src/test/tb_obi_atop_filter.sv b/src/test/tb_obi_atop_filter.sv new file mode 100644 index 0000000..036c385 --- /dev/null +++ b/src/test/tb_obi_atop_filter.sv @@ -0,0 +1,318 @@ +// Copyright 2026 Mosaic SoC Ltd. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +`include "obi/typedef.svh" +`include "obi/assign.svh" + +module tb_obi_atop_filter; + import obi_pkg::*; + + localparam int unsigned MaxTimeout = 10000; + + localparam int unsigned AddrWidth = 32; + localparam int unsigned DataWidth = 32; + localparam int unsigned IdWidth = 3; + localparam int unsigned AUserWidth = 4; + localparam int unsigned WUserWidth = 2; + localparam int unsigned RUserWidth = 3; + localparam int unsigned MaxTrans = 4; + + localparam time CyclTime = 10ns; + localparam time ApplTime = 2ns; + localparam time TestTime = 8ns; + + localparam int unsigned NumTests = 1000; + + localparam obi_pkg::obi_cfg_t SbrConfig = '{ + UseRReady: 1'b1, + CombGnt: 1'b0, + AddrWidth: AddrWidth, + DataWidth: DataWidth, + IdWidth: IdWidth, + Integrity: 1'b0, + BeFull: 1'b1, + OptionalCfg: '{ + UseAtop: 1'b1, + UseMemtype: 1'b1, + UseProt: 1'b1, + UseDbg: 1'b1, + AUserWidth: AUserWidth, + WUserWidth: WUserWidth, + RUserWidth: RUserWidth, + MidWidth: 1, + AChkWidth: 1, + RChkWidth: 1 + } + }; + + // Manager Port: Inherit from Subordinate port + localparam obi_pkg::obi_cfg_t MgrConfig = SbrConfig; + + `OBI_TYPEDEF_ALL(sbr, SbrConfig) + `OBI_TYPEDEF_ALL(mgr, MgrConfig) + + typedef obi_test::obi_rand_manager #( + .ObiCfg ( SbrConfig ), + .obi_a_optional_t ( sbr_a_optional_t ), + .obi_r_optional_t ( sbr_r_optional_t ), + .TA ( ApplTime ), + .TT ( TestTime ), + .MinAddr ( 32'h0000_0000 ), + .MaxAddr ( 32'h0001_3000 ), + .AMinWaitCycles ( 0 ), + .AMaxWaitCycles ( 5 ), + .RMinWaitCycles ( 0 ), + .RMaxWaitCycles ( 10 ) + ) rand_manager_t; + + typedef obi_test::obi_rand_subordinate #( + .ObiCfg ( MgrConfig ), + .obi_a_optional_t ( mgr_a_optional_t ), + .obi_r_optional_t ( mgr_r_optional_t ), + .RandResp ( 1'b1 ), + .TA ( ApplTime ), + .TT ( TestTime ), + .AMinWaitCycles ( 0 ), + .AMaxWaitCycles ( 5 ), + .RMinWaitCycles ( 0 ), + .RMaxWaitCycles ( 10 ) + ) rand_subordinate_t; + + logic clk, rst_n; + logic end_of_sim; + int unsigned num_errors = 0; + int unsigned num_mgr_req_hs = 0; + int unsigned num_mgr_rsp_hs = 0; + int unsigned num_sbr_req_hs = 0; + int unsigned num_sbr_rsp_hs = 0; + + mgr_a_chan_t mgr_req_queue[$]; + mgr_a_chan_t non_atop_req_queue[$]; + mgr_r_chan_t sbr_rsp_queue[$]; + + OBI_BUS_DV #( + .OBI_CFG ( SbrConfig ), + .obi_a_optional_t ( sbr_a_optional_t ), + .obi_r_optional_t ( sbr_r_optional_t ) + ) sbr_bus_dv ( + .clk_i ( clk ), + .rst_ni ( rst_n ) + ); + OBI_BUS #( + .OBI_CFG ( SbrConfig ), + .obi_a_optional_t ( sbr_a_optional_t ), + .obi_r_optional_t ( sbr_r_optional_t ) + ) sbr_bus (); + + OBI_BUS #( + .OBI_CFG ( MgrConfig ), + .obi_a_optional_t ( mgr_a_optional_t ), + .obi_r_optional_t ( mgr_r_optional_t ) + ) mgr_bus (); + OBI_BUS_DV #( + .OBI_CFG ( MgrConfig ), + .obi_a_optional_t ( mgr_a_optional_t ), + .obi_r_optional_t ( mgr_r_optional_t ) + ) mgr_bus_dv ( + .clk_i ( clk ), + .rst_ni ( rst_n ) + ); + + rand_manager_t obi_rand_manager; + rand_subordinate_t obi_rand_subordinate; + + initial begin + obi_rand_manager = new(sbr_bus_dv, "MGR"); + end_of_sim <= 1'b0; + obi_rand_manager.reset(); + end + + initial begin + obi_rand_subordinate = new(mgr_bus_dv, "SUB"); + obi_rand_subordinate.reset(); + obi_rand_subordinate.run(); + end + + `OBI_ASSIGN(sbr_bus, sbr_bus_dv, SbrConfig, SbrConfig) + `OBI_ASSIGN(mgr_bus_dv, mgr_bus, MgrConfig, MgrConfig) + + clk_rst_gen #( + .ClkPeriod ( CyclTime ), + .RstClkCycles ( 5 ) + ) i_clk_gen ( + .clk_o ( clk ), + .rst_no ( rst_n ) + ); + + obi_atop_filter_intf #( + .SbrPortObiCfg ( SbrConfig ), + .MgrPortObiCfg ( MgrConfig ), + .MaxTrans ( MaxTrans ) + ) i_atop_filter ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .testmode_i ( '0 ), + .sbr_port ( sbr_bus ), + .mgr_port ( mgr_bus ) + ); + + /*==================================================================== + = Main = + ====================================================================*/ + + initial begin + wait (rst_n); + @(posedge clk); + + obi_rand_manager.run(NumTests); + + end_of_sim <= 1'b1; + end + + initial begin + wait (rst_n); + forever begin + @(posedge clk); + if (mgr_bus.req & mgr_bus.gnt) num_mgr_req_hs++; + if (mgr_bus.rvalid & mgr_bus.rready) num_mgr_rsp_hs++; + if (sbr_bus.req & sbr_bus.gnt) num_sbr_req_hs++; + if (sbr_bus.rvalid & sbr_bus.rready) num_sbr_rsp_hs++; + end + end + + /*==================================================================== + = Checker = + ====================================================================*/ + + initial begin + wait (rst_n); + forever begin + @(posedge clk); + + // sbr_bus req hs: capture all requests from the upstream manager + if (sbr_bus.req && sbr_bus.gnt) begin : push_mgr_req + automatic mgr_a_chan_t req; + req.addr = sbr_bus.addr; + req.we = sbr_bus.we; + req.be = sbr_bus.be; + req.wdata = sbr_bus.wdata; + req.aid = sbr_bus.aid; + req.a_optional = sbr_bus.a_optional; + mgr_req_queue.push_back(req); + if (req.a_optional.atop == '0) begin + non_atop_req_queue.push_back(req); + end + end + + // mgr_bus rsp: push downstream subordinate response for later sbr_bus check + if (mgr_bus.rvalid && mgr_bus.rready) begin : push_sbr_rsp + automatic mgr_r_chan_t rsp; + rsp.rdata = mgr_bus.rdata; + rsp.rid = mgr_bus.rid; + rsp.err = mgr_bus.err; + rsp.r_optional = mgr_bus.r_optional; + sbr_rsp_queue.push_back(rsp); + end + + // mgr_bus req hs: verify filter correctly forwards non-ATOP request + if (mgr_bus.req && mgr_bus.gnt) begin : check_fwd_req + automatic mgr_a_chan_t ref_req; + if (non_atop_req_queue.size() == 0) begin + $error("[t=%0t] mgr_bus req handshake but non_atop_req_queue is empty!", $time); + num_errors++; + end else begin + ref_req = non_atop_req_queue.pop_front(); + if (!(ref_req.addr ==? mgr_bus.addr ) || + !(ref_req.we ==? mgr_bus.we ) || + !(ref_req.be ==? mgr_bus.be ) || + !(ref_req.wdata ==? mgr_bus.wdata ) || + !(ref_req.aid ==? mgr_bus.aid ) || + !(ref_req.a_optional ==? mgr_bus.a_optional)) begin + $error("[t=%0t] Forwarded request mismatch on mgr_bus!", $time); + num_errors++; + end + end + end + + // sbr_bus rsp: verify response sent back to the upstream manager + if (sbr_bus.rvalid & sbr_bus.rready) begin : check_mgr_rsp + if (mgr_req_queue.size() == 0) begin + $error("[t=%0t] mgr_req_queue empty on sbr_bus rsp!", $time); + num_errors++; + end else begin + automatic mgr_a_chan_t req; + req = mgr_req_queue.pop_front(); + if (req.aid !== sbr_bus.rid) begin + $error("[t=%0t] ID mismatch on rsp: aid=%0d rid=%0d", + $time, req.aid, sbr_bus.rid); + num_errors++; + end + if (req.a_optional.atop != '0) begin + // ATOP was blocked; filter generated error response + if (sbr_bus.err !== 1'b1) begin + $error("[t=%0t] Expected err=1 on ATOP error rsp!", $time); + num_errors++; + end + end else begin + // Non-ATOP forwarded path: compare against captured downstream response + automatic mgr_r_chan_t fwd_rsp; + if (sbr_rsp_queue.size() == 0) begin + $error("[t=%0t] sbr_rsp_queue empty on non-atomic rsp!", $time); + num_errors++; + end else begin + fwd_rsp = sbr_rsp_queue.pop_front(); + if (!(fwd_rsp.rdata ==? sbr_bus.rdata ) || + !(fwd_rsp.rid ==? sbr_bus.rid ) || + !(fwd_rsp.err ==? sbr_bus.err ) || + !(fwd_rsp.r_optional ==? sbr_bus.r_optional)) begin + $error("[t=%0t] Forwarded response mismatch on sbr_bus!", $time); + num_errors++; + end + end + end + end + end + end + end + + /*==================================================================== + = Timeout = + ====================================================================*/ + + initial begin + automatic int unsigned timeout = 0; + automatic logic [1:0] handshake = 2'b00; + + @(posedge clk); + wait (rst_n); + + fork + while (timeout < MaxTimeout) begin + handshake = {mgr_bus.req, mgr_bus.gnt}; + @(posedge clk); + if (handshake != {mgr_bus.req, mgr_bus.gnt}) begin + timeout = 0; + end else begin + timeout += 1; + end + end + wait (end_of_sim); + join_any + + if (end_of_sim && num_errors == 0) begin + $display("SUCCESS"); + end else if (end_of_sim) begin + if (num_errors > 0) begin + $fatal(1, "Encountered %d errors.", num_errors); + end else begin + $display("All tests passed."); + end + end else begin + $fatal(1, "TIMEOUT"); + end + + $stop; + end + +endmodule