// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
// Test: nested parameterized interface inside a types interface causes
//       $bits() to evaluate with wrong struct width.
//

package cfg3_pkg;
  typedef struct packed {
    int unsigned Capacity;
    int unsigned Slices;
    int unsigned NumThreads;
    int unsigned NumIds;
  } cfg_t;
endpackage

// Inner types interface (mirrors xyz_types_if).
// Provides types derived from cfg that the outer interface imports.
interface inner_types_if #(
    parameter cfg3_pkg::cfg_t cfg = '0
) ();
  typedef logic [$clog2(cfg.NumIds)-1:0] trans_id_t;
  typedef logic [$clog2(cfg.NumThreads)-1:0] tl_index_t;
endinterface

// Outer types interface (mirrors smem_types_if).
// Instantiates inner_types_if, imports types from it, and builds
// compound struct typedefs that include those imported types.
interface outer_types_if #(
    parameter cfg3_pkg::cfg_t cfg = '0
) ();
  localparam int NUM_ROWS = (cfg.Capacity / cfg.Slices) / 8;
  typedef logic [$clog2(NUM_ROWS)-1:0] row_addr_t;

  // Nested interface - this is the trigger.
  inner_types_if #(cfg) inner_types ();
  typedef inner_types.trans_id_t trans_id_t;
  typedef inner_types.tl_index_t tl_index_t;

  typedef logic [$clog2(cfg.NumThreads)-1:0] pkt_nid_t;

  typedef struct packed {
    pkt_nid_t src_nid;
    pkt_nid_t dst_nid;
  } pkt_hdr_t;

  typedef union packed {
    logic [$bits(row_addr_t)+$bits(tl_index_t)+6-1:0] raw;
    struct packed {
      row_addr_t row_index;
      tl_index_t tl_index;
      logic [5:0] bit_index;
    } fld;
  } addr_t;

  typedef struct packed {
    logic en;
    trans_id_t tag;
    tl_index_t tl;
    logic is_read;
    logic needs_resp;
  } meta_t;

  typedef struct packed {
    meta_t meta;
    addr_t addr;
    logic [63:0] data;
  } rq_t;

  // Compound packet type (mirrors iq_pkt_t)
  typedef struct packed {
    pkt_hdr_t hdr;
    rq_t payload;
  } pkt_t;
endinterface

// Width-parameterized FIFO (mirrors ring_fifo).
module fifo3 #(
    parameter int p_width = 1,
    parameter int p_depth = 2
) (
    input logic clk,
    input logic rst_n,
    input logic [p_width-1:0] push_dat_i,
    input logic push_vld_i,
    output logic [p_width-1:0] front_dat_o,
    output logic not_empty_o
);
  logic [p_width-1:0] mem[p_depth];
  logic [$clog2(p_depth)-1:0] wptr, rptr;
  logic [p_depth:0] count;
  assign not_empty_o = (count != 0);
  assign front_dat_o = mem[rptr];
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      wptr <= '0;
      rptr <= '0;
      count <= '0;
    end
    else if (push_vld_i) begin
      mem[wptr] <= push_dat_i;
      wptr <= wptr + 1;
      count <= count + 1;
    end
  end
endmodule

// Slice module (mirrors smem_slice).
// Receives iq_pkt_t as a TYPE PARAMETER from wrapper3, then uses
// $bits(iq_pkt_t) as a value parameter to fifo3.
// This forces $bits() evaluation during widthParamsEdit of the slice3
// cell (in wrapper3's cell loop), before outer_types_if__Az1's nested
// inner_types_if is deparameterized.
module slice3 #(
    parameter cfg3_pkg::cfg_t cfg = '0,
    parameter int SLICE_IDX = 0,
    parameter type iq_pkt_t = logic,
    parameter type oq_pkt_t = logic
) (
    input logic clk,
    input logic rst_n,
    input logic in_vld,
    output logic out_vld,
    output iq_pkt_t fe_ot_pkt_o
);
  // Local outer_types_if for other local types
  outer_types_if #(cfg) types ();
  typedef types.pkt_nid_t pkt_nid_t;

  iq_pkt_t rqq_in, rqq_ot;
  logic rqq_ot_vld;

  assign rqq_in.hdr.src_nid = pkt_nid_t'(SLICE_IDX);
  assign rqq_in.hdr.dst_nid = '0;
  assign rqq_in.payload.meta.en = in_vld;
  assign rqq_in.payload.meta.tag = '0;
  assign rqq_in.payload.meta.tl = '0;
  assign rqq_in.payload.meta.is_read = 1'b1;
  assign rqq_in.payload.meta.needs_resp = 1'b0;
  assign rqq_in.payload.addr = '0;
  assign rqq_in.payload.data = 64'hCAFE_BABE;

  // $bits(iq_pkt_t) as value parameter - the bug trigger.
  // iq_pkt_t is a type parameter whose PARAMTYPEDTYPE child REFDTYPE
  // points into the outer_types_if clone's STRUCTDTYPE.  When
  // widthParamsEdit evaluates this during wrapper3's cell loop,
  // the nested inner_types_if inside outer_types_if__Az1 hasn't
  // been deparameterized, so trans_id_t/tl_index_t have wrong widths.
  fifo3 #(
      .p_width($bits(iq_pkt_t)),
      .p_depth(4)
  ) rqq (
      .clk(clk),
      .rst_n(rst_n),
      .push_dat_i(rqq_in),
      .push_vld_i(in_vld),
      .front_dat_o(rqq_ot),
      .not_empty_o(rqq_ot_vld)
  );

  assign fe_ot_pkt_o = rqq_ot;
  assign out_vld = rqq_ot_vld;
endmodule

// Wrapper module (mirrors smem_top).
// Creates outer_types_if, imports pkt_t, and passes it as a type
// parameter to slice3 instances in a generate loop.
module wrapper3 #(
    parameter cfg3_pkg::cfg_t cfg = '0
) (
    input logic clk,
    input logic rst_n
);
  outer_types_if #(cfg) types ();
  typedef types.pkt_t iq_pkt_t;
  typedef types.rq_t oq_pkt_t;

  // Capture $bits of the type parameter for external checking.
  // If the nested inner_types_if hasn't been deparameterized before
  // this is evaluated, the width will be wrong (e.g. 90 instead of 92).
  localparam int PKT_WIDTH = $bits(iq_pkt_t);

  logic [cfg.NumThreads-1:0] out_vld;

  generate
    for (genvar i = 0; i < cfg.NumThreads; i++) begin : gen_slices
      iq_pkt_t fe_pkt;
      slice3 #(
          .cfg(cfg),
          .SLICE_IDX(i),
          .iq_pkt_t(iq_pkt_t),
          .oq_pkt_t(oq_pkt_t)
      ) u_slice (
          .clk(clk),
          .rst_n(rst_n),
          .in_vld(1'b1),
          .out_vld(out_vld[i]),
          .fe_ot_pkt_o(fe_pkt)
      );
    end
  endgenerate
endmodule

module t;
  logic clk = 0;
  always #5 clk = ~clk;
  logic rst_n = 0;

  int cyc = 0;

  localparam cfg3_pkg::cfg_t MY_CFG = '{Capacity: 8192, Slices: 8, NumThreads: 4, NumIds: 16};
  // Expected widths:
  //   trans_id_t = $clog2(16) = 4 bits
  //   tl_index_t = $clog2(4)  = 2 bits
  //   row_addr_t = $clog2((8192/8)/8) = $clog2(128) = 7 bits
  //   pkt_nid_t  = $clog2(4) = 2 bits
  //   pkt_hdr_t  = 2+2 = 4 bits
  //   addr_t     = 7+2+6 = 15 bits
  //   meta_t     = 1+4+2+1+1 = 9 bits
  //   rq_t       = 9+15+64 = 88 bits
  //   pkt_t      = 4+88 = 92 bits

  // Compute expected pkt_t width from first principles.
  // These computations happen in the testbench module context where
  // the cfg parameter values are known constants, not through the
  // nested interface PARAMTYPEDTYPE chain.
  localparam int EXP_TRANS_ID_W = $clog2(MY_CFG.NumIds);  // 4
  localparam int EXP_TL_INDEX_W = $clog2(MY_CFG.NumThreads);  // 2
  localparam int EXP_ROW_ADDR_W = $clog2((MY_CFG.Capacity / MY_CFG.Slices) / 8);  // 7
  localparam int EXP_PKT_NID_W = $clog2(MY_CFG.NumThreads);  // 2
  localparam int EXP_PKT_HDR_W = 2 * EXP_PKT_NID_W;  // 4
  localparam int EXP_ADDR_W = EXP_ROW_ADDR_W + EXP_TL_INDEX_W + 6;  // 15
  localparam int EXP_META_W = 1 + EXP_TRANS_ID_W + EXP_TL_INDEX_W + 1 + 1;  // 9
  localparam int EXP_RQ_W = EXP_META_W + EXP_ADDR_W + 64;  // 88
  localparam int EXP_PKT_W = EXP_PKT_HDR_W + EXP_RQ_W;  // 92

  wrapper3 #(
      .cfg(MY_CFG)
  ) u_wrapper (
      .clk(clk),
      .rst_n(rst_n)
  );

  // Self-check: verify that $bits(pkt_t) as seen through the nested
  // interface type parameter chain matches the expected width.
  // If the bug is present, u_wrapper.PKT_WIDTH will be < EXP_PKT_W
  // because trans_id_t was evaluated with the template default
  // cfg.NumIds=0 instead of the actual value 16.
  initial begin
    if (u_wrapper.PKT_WIDTH != EXP_PKT_W) begin
      $display("%%Error: t_iface_nested_width3.v: $bits(pkt_t) = %0d, expected %0d",
               u_wrapper.PKT_WIDTH, EXP_PKT_W);
      $stop;
    end
  end

  always @(posedge clk) begin
    cyc <= cyc + 1;
    if (cyc == 2) rst_n <= 1;
    if (cyc > 5) begin
      if (u_wrapper.out_vld !== {MY_CFG.NumThreads{1'b1}}) begin
        $display("FAIL cyc=%0d: out_vld=%b", cyc, u_wrapper.out_vld);
        $stop;
      end
    end
    if (cyc == 20) begin
      $write("*-* All Finished *-*\n");
      $finish;
    end
  end
endmodule
