From 9dc32ab9cc9b23bd21601b9ae7cbbf65a0414e7c Mon Sep 17 00:00:00 2001 From: maksyuki Date: Sun, 2 Aug 2026 19:18:45 +0800 Subject: [PATCH] feat: implement XPI output-enable isolation test and add related testbench --- rtl/ip/rib/storage/xpi_core.sv | 124 +++++++++++++++++++++++---------- tests/rtl/xpi_io_tb.sv | 111 +++++++++++++++++++++++++++++ tests/test_xpi_io.py | 78 +++++++++++++++++++++ 3 files changed, 276 insertions(+), 37 deletions(-) create mode 100644 tests/rtl/xpi_io_tb.sv create mode 100644 tests/test_xpi_io.py diff --git a/rtl/ip/rib/storage/xpi_core.sv b/rtl/ip/rib/storage/xpi_core.sv index 2ac9f2bf..a0b6ebaa 100644 --- a/rtl/ip/rib/storage/xpi_core.sv +++ b/rtl/ip/rib/storage/xpi_core.sv @@ -77,6 +77,7 @@ module xpi_core ( logic s_xfer_condi, s_xfer_sta_trg, s_xfer_end_trg; logic s_dma_xfer_start, s_dma_xfer_trg; logic [7:0] s_dma_xfer_datlen; + logic [3:0] s_xpi_io_oe, s_xpi_io_do; assign xpi.sck_o = s_sclk; @@ -85,6 +86,8 @@ module xpi_core ( xpi.nss_o[nss_i] = s_nss_q; end assign xpi.irq_o = '0; + assign xpi.io_oe_o = s_xpi_io_oe; + assign xpi.io_do_o = s_xpi_io_do; assign tx_data_req_o = s_tx_data_req_q; assign rx_data_req_o = s_rx_data_req_q; @@ -122,6 +125,82 @@ module xpi_core ( ); + // Keep pad drive independent from the receive-data path. This preserves the + // bidirectional pad readback model without creating an input-to-output loop. + always_comb begin + s_xpi_io_oe = '0; + s_xpi_io_oe[0] = 1'b1; + s_xpi_io_do = '0; + unique case (s_fsm_q) + FSM_CMD: begin + unique case (cmdtyp_i) + `XPI_TYPE_SNGL: begin + s_xpi_io_oe[0] = 1'b1; + s_xpi_io_do[0] = s_xfer_data_q[31]; + end + `XPI_TYPE_DUAL: begin + s_xpi_io_oe[1:0] = 2'b11; + s_xpi_io_do[1:0] = s_xfer_data_q[31:30]; + end + `XPI_TYPE_QUAD: begin + s_xpi_io_oe[3:0] = 4'b1111; + s_xpi_io_do[3:0] = s_xfer_data_q[31:28]; + end + default: begin + s_xpi_io_oe[0] = 1'b1; + s_xpi_io_do[0] = s_xfer_data_q[31]; + end + endcase + end + FSM_ADDR: begin + unique case (adrtyp_i) + `XPI_TYPE_SNGL: begin + s_xpi_io_oe[0] = 1'b1; + s_xpi_io_do[0] = s_xfer_data_q[31]; + end + `XPI_TYPE_DUAL: begin + s_xpi_io_oe[1:0] = 2'b11; + s_xpi_io_do[1:0] = s_xfer_data_q[31:30]; + end + `XPI_TYPE_QUAD: begin + s_xpi_io_oe[3:0] = 4'b1111; + s_xpi_io_do[3:0] = s_xfer_data_q[31:28]; + end + default: begin + s_xpi_io_oe[0] = 1'b1; + s_xpi_io_do[0] = s_xfer_data_q[31]; + end + endcase + end + FSM_DUM, FSM_RXDATA: begin + s_xpi_io_oe = '0; + end + FSM_TXDATA: begin + unique case (dattyp_i) + `XPI_TYPE_SNGL: begin + s_xpi_io_oe[0] = 1'b1; + s_xpi_io_do[0] = s_xfer_data_q[31]; + end + `XPI_TYPE_DUAL: begin + s_xpi_io_oe[1:0] = 2'b11; + s_xpi_io_do[1:0] = s_xfer_data_q[31:30]; + end + `XPI_TYPE_QUAD: begin + s_xpi_io_oe[3:0] = 4'b1111; + s_xpi_io_do[3:0] = s_xfer_data_q[31:28]; + end + default: begin + s_xpi_io_oe[0] = 1'b1; + s_xpi_io_do[0] = s_xfer_data_q[31]; + end + endcase + end + default: begin + end + endcase + end + + always_comb begin s_fsm_d = s_fsm_q; s_nss_d = s_nss_q; @@ -134,10 +213,6 @@ module xpi_core ( // system rx_data_o = '0; done_o = 1'b0; - // xpi if - xpi.io_oe_o = '0; - xpi.io_oe_o[0] = 1'b1; - xpi.io_do_o = '0; unique case (s_fsm_q) FSM_IDLE: begin if (s_xfer_condi) begin @@ -176,26 +251,18 @@ module xpi_core ( `XPI_TYPE_SNGL: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd1; s_xfer_data_d = {s_xfer_data_q[30:0], 1'd0}; - xpi.io_oe_o[0] = 1'b1; - xpi.io_do_o[0] = s_xfer_data_q[31]; end `XPI_TYPE_DUAL: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd2; s_xfer_data_d = {s_xfer_data_q[29:0], 2'd0}; - xpi.io_oe_o[1:0] = 2'b11; - xpi.io_do_o[1:0] = s_xfer_data_q[31:30]; end `XPI_TYPE_QUAD: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd4; s_xfer_data_d = {s_xfer_data_q[27:0], 4'd0}; - xpi.io_oe_o[3:0] = 4'b1111; - xpi.io_do_o[3:0] = s_xfer_data_q[31:28]; end default: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd1; s_xfer_data_d = {s_xfer_data_q[30:0], 1'd0}; - xpi.io_oe_o[0] = 1'b1; - xpi.io_do_o[0] = s_xfer_data_q[31]; end endcase @@ -231,26 +298,18 @@ module xpi_core ( `XPI_TYPE_SNGL: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd1; s_xfer_data_d = {s_xfer_data_q[30:0], 1'd0}; - xpi.io_oe_o[0] = 1'b1; - xpi.io_do_o[0] = s_xfer_data_q[31]; end `XPI_TYPE_DUAL: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd2; s_xfer_data_d = {s_xfer_data_q[29:0], 2'd0}; - xpi.io_oe_o[1:0] = 2'b11; - xpi.io_do_o[1:0] = s_xfer_data_q[31:30]; end `XPI_TYPE_QUAD: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd4; s_xfer_data_d = {s_xfer_data_q[27:0], 4'd0}; - xpi.io_oe_o[3:0] = 4'b1111; - xpi.io_do_o[3:0] = s_xfer_data_q[31:28]; end default: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd1; s_xfer_data_d = {s_xfer_data_q[30:0], 1'd0}; - xpi.io_oe_o[0] = 1'b1; - xpi.io_do_o[0] = s_xfer_data_q[31]; end endcase @@ -278,7 +337,6 @@ module xpi_core ( end end FSM_DUM: begin - xpi.io_oe_o = '0; s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd1; if (s_xfer_bit_cnt_q == 8'd1) begin @@ -303,26 +361,18 @@ module xpi_core ( `XPI_TYPE_SNGL: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd1; s_xfer_data_d = {s_xfer_data_q[30:0], 1'd0}; - xpi.io_oe_o[0] = 1'b1; - xpi.io_do_o[0] = s_xfer_data_q[31]; end `XPI_TYPE_DUAL: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd2; s_xfer_data_d = {s_xfer_data_q[29:0], 2'd0}; - xpi.io_oe_o[1:0] = 2'b11; - xpi.io_do_o[1:0] = s_xfer_data_q[31:30]; end `XPI_TYPE_QUAD: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd4; s_xfer_data_d = {s_xfer_data_q[27:0], 4'd0}; - xpi.io_oe_o[3:0] = 4'b1111; - xpi.io_do_o[3:0] = s_xfer_data_q[31:28]; end default: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd1; s_xfer_data_d = {s_xfer_data_q[30:0], 1'd0}; - xpi.io_oe_o[0] = 1'b1; - xpi.io_do_o[0] = s_xfer_data_q[31]; end endcase @@ -349,22 +399,18 @@ module xpi_core ( `XPI_TYPE_SNGL: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd1; s_xfer_data_d = {s_xfer_data_q[30:0], xpi.io_di_i[1]}; - xpi.io_oe_o = '0; end `XPI_TYPE_DUAL: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd2; s_xfer_data_d = {s_xfer_data_q[29:0], xpi.io_di_i[1:0]}; - xpi.io_oe_o = '0; end `XPI_TYPE_QUAD: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd4; s_xfer_data_d = {s_xfer_data_q[27:0], xpi.io_di_i[3:0]}; - xpi.io_oe_o = '0; end default: begin s_xfer_bit_cnt_d = s_xfer_bit_cnt_q - 8'd1; s_xfer_data_d = {s_xfer_data_q[30:0], xpi.io_di_i[1]}; - xpi.io_oe_o = '0; end endcase @@ -448,10 +494,6 @@ module xpi_core ( // system rx_data_o = '0; done_o = 1'b0; - // xpi if - xpi.io_oe_o = '0; - xpi.io_oe_o[0] = 1'b1; - xpi.io_do_o = '0; end endcase end @@ -524,4 +566,12 @@ module xpi_core ( s_rx_data_req_q ); +`ifndef SV_ASSRT_DISABLE +`ifndef SYNTHESIS + a_xpi_read_pad_released : + assert property (@(posedge clk_i) disable iff (!rst_n_i) + (s_fsm_q == FSM_DUM || s_fsm_q == FSM_RXDATA) |-> s_xpi_io_oe == '0); +`endif +`endif + endmodule diff --git a/tests/rtl/xpi_io_tb.sv b/tests/rtl/xpi_io_tb.sv new file mode 100644 index 00000000..875894b1 --- /dev/null +++ b/tests/rtl/xpi_io_tb.sv @@ -0,0 +1,111 @@ +`timescale 1ns / 1ps + +`include "xpi_define.svh" + +module xpi_io_tb; + logic clk_i = 1'b0; + logic rst_n_i = 1'b0; + logic mode_i = 1'b0; + logic [`XPI_LNS_NUM-1:0] nss_i = '0; + logic [ 7:0] clkdiv_i = '0; + logic rdwr_i = 1'b0; + logic revdat_i = 1'b0; + logic [ 1:0] cmdtyp_i = `XPI_TYPE_NONE; + logic [ 2:0] cmdlen_i = '0; + logic [ 31:0] cmddat_i = '0; + logic [ 1:0] adrtyp_i = `XPI_TYPE_NONE; + logic [ 2:0] adrlen_i = '0; + logic [ 31:0] adrdat_i = '0; + logic [ 7:0] tdulen_i = '0; + logic [ 7:0] rdulen_i = '0; + logic [ 1:0] dattyp_i = `XPI_TYPE_NONE; + logic [ 7:0] datlen_i = '0; + logic [ 2:0] datbit_i = '0; + logic [ 7:0] hlvlen_i = '0; + logic tx_data_rdy_i = 1'b0; + logic [ 31:0] tx_data_i = '0; + logic rx_data_rdy_i = 1'b0; + logic start_i = 1'b0; + logic [ 7:0] tx_elem_num_i = '0; + logic dma_xfer_done_i = 1'b0; + logic tx_data_req_o; + logic rx_data_req_o; + logic [ 31:0] rx_data_o; + logic done_o; + xpi_if xpi (); + + always #5 clk_i = ~clk_i; + + xpi_core dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .mode_i (mode_i), + .nss_i (nss_i), + .clkdiv_i (clkdiv_i), + .rdwr_i (rdwr_i), + .revdat_i (revdat_i), + .cmdtyp_i (cmdtyp_i), + .cmdlen_i (cmdlen_i), + .cmddat_i (cmddat_i), + .adrtyp_i (adrtyp_i), + .adrlen_i (adrlen_i), + .adrdat_i (adrdat_i), + .tdulen_i (tdulen_i), + .rdulen_i (rdulen_i), + .dattyp_i (dattyp_i), + .datlen_i (datlen_i), + .datbit_i (datbit_i), + .hlvlen_i (hlvlen_i), + .tx_data_req_o (tx_data_req_o), + .tx_data_rdy_i (tx_data_rdy_i), + .tx_data_i (tx_data_i), + .rx_data_req_o (rx_data_req_o), + .rx_data_rdy_i (rx_data_rdy_i), + .rx_data_o (rx_data_o), + .start_i (start_i), + .done_o (done_o), + .tx_elem_num_i (tx_elem_num_i), + .dma_xfer_done_i(dma_xfer_done_i), + .xpi (xpi) + ); + + task automatic expect_pad(input logic [3:0] oe, input logic [3:0] data, input string state); + begin + #1; + if (xpi.io_oe_o !== oe || xpi.io_do_o !== data) begin + $fatal(1, "%s pad drive was oe=%b data=%b", state, xpi.io_oe_o, xpi.io_do_o); + end + end + endtask + + initial begin + repeat (2) @(posedge clk_i); + rst_n_i = 1'b1; + force dut.s_xfer_data_q = 32'hABCD_1234; + + force dut.s_fsm_q = 3'd0; + expect_pad(4'b0001, 4'b0000, "idle"); + + cmdtyp_i = `XPI_TYPE_QUAD; + force dut.s_fsm_q = 3'd1; + expect_pad(4'b1111, 4'b1010, "command"); + + dattyp_i = `XPI_TYPE_DUAL; + force dut.s_fsm_q = 3'd4; + expect_pad(4'b0011, 4'b0010, "transmit"); + + force dut.s_fsm_q = 3'd3; + expect_pad(4'b0000, 4'b0000, "dummy"); + + force dut.s_fsm_q = 3'd5; + xpi.io_di_i = 4'b0000; + expect_pad(4'b0000, 4'b0000, "receive low"); + xpi.io_di_i = 4'b1111; + expect_pad(4'b0000, 4'b0000, "receive high"); + + release dut.s_fsm_q; + release dut.s_xfer_data_q; + $display("XPI pad drive isolation test passed"); + $finish; + end +endmodule diff --git a/tests/test_xpi_io.py b/tests/test_xpi_io.py new file mode 100644 index 00000000..d79d3d1b --- /dev/null +++ b/tests/test_xpi_io.py @@ -0,0 +1,78 @@ +"""RTL regression for XPI output-enable isolation during input phases.""" + +from __future__ import annotations + +import shutil +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_xpi_releases_data_pads_while_receiving(tmp_path: Path) -> None: + iverilog = shutil.which("iverilog") + vvp = shutil.which("vvp") + if iverilog is None or vvp is None: + return + + generated = tmp_path / "generated" + subprocess.run( + [ + sys.executable, + str(ROOT / "rtl/mini/address_map/generate_memory_map.py"), + "--map", + str(ROOT / "rtl/mini/address_map/memory_map.json"), + "--output-dir", + str(generated), + ], + check=True, + ) + source_list = tmp_path / "xpi_io.fl" + source_list.write_text( + "\n".join( + [ + "+define+SV_ASSRT_DISABLE", + f"+incdir+{generated / 'rtl'}", + f"+incdir+{ROOT / 'rtl/ip/rib/storage'}", + f"+incdir+{ROOT / 'rtl/managed/clusterip/common/rtl'}", + str(ROOT / "rtl/managed/clusterip/common/rtl/interface/rib_if.sv"), + str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"), + str(ROOT / "rtl/ip/rib/storage/xpi.sv"), + str(ROOT / "rtl/ip/rib/storage/xpi_clkgen.sv"), + str(ROOT / "rtl/ip/rib/storage/xpi_core.sv"), + str(ROOT / "tests/rtl/xpi_io_tb.sv"), + "", + ] + ), + encoding="utf-8", + ) + converted = tmp_path / "xpi_io_tb.v" + subprocess.run( + [ + sys.executable, + str(ROOT / "rtl/mini/script/convt_sv2v.py"), + "-f", + str(source_list), + "--output", + str(converted), + ], + check=True, + ) + simulation = tmp_path / "xpi_io_tb" + subprocess.run( + [ + iverilog, + "-g2012", + "-s", + "xpi_io_tb", + "-o", + str(simulation), + str(converted), + ], + check=True, + ) + result = subprocess.run([vvp, str(simulation)], text=True, capture_output=True, check=True) + + assert "XPI pad drive isolation test passed" in result.stdout