// 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 with struct typedef used as type parameter
//
// Reproduces a bug where specializeNestedIfaceCells causes early
// specialization of a nested interface, leaving PARAMTYPEDTYPE child
// REFDTYPEs pointing to the template struct instead of the clone struct.
// The struct's width then resolves using the template's default parameter
// values instead of the actual overridden values.
//
// Pattern (mirrors Aerial's simple_cache / simple_cache_if / simple_cache_types_if):
//   1. A wrapper interface instantiates a nested types interface
//   2. The types interface computes localparams from cfg (using $clog2/division)
//   3. Those localparams define typedef ranges for struct members
//   4. A module receives the wrapper interface as a port, also instantiates
//      the types interface locally, and uses the struct typedef
//   5. Both the wrapper and the module use the same cfg, so they share
//      the same types_if clone via "De-parameterize to prev"

package cfg_pkg;
  typedef struct packed {
    logic [31:0] AddrBits;
    logic [31:0] Capacity;
    logic [31:0] LineSize;
    logic [31:0] Associativity;
  } cfg_t;
endpackage

// Nested types interface: derives struct typedef from computed localparams
interface types_if #(
    parameter cfg_pkg::cfg_t cfg = '0
) ();
  // Computed localparams - these use division and $clog2 of cfg fields.
  // With default cfg='0, these produce X/undefined values.
  localparam int NUM_LINES = cfg.Capacity / cfg.LineSize;
  localparam int LINES_PER_WAY = NUM_LINES / cfg.Associativity;
  localparam int BLOCK_BITS = $clog2(cfg.LineSize);
  localparam int ROW_BITS = $clog2(LINES_PER_WAY);
  localparam int TAG_BITS = cfg.AddrBits - ROW_BITS - BLOCK_BITS;

  typedef logic [TAG_BITS-1:0] tag_t;
  typedef logic [ROW_BITS-1:0] row_t;
  typedef logic [BLOCK_BITS-1:0] block_t;

  typedef struct packed {
    logic vld;
    tag_t tag;
    row_t row;
    block_t block;
  } entry_t;
endinterface

// Wrapper interface: instantiates types_if as a nested cell
// (mirrors simple_cache_if which instantiates simple_cache_types_if)
interface wrapper_if #(
    parameter cfg_pkg::cfg_t cfg = '0
) ();
  types_if #(cfg) types ();

  typedef types.tag_t tag_t;

  logic req_vld;
  tag_t req_tag;
endinterface

// Sub-module parameterized by entry width
// (mirrors flop_nr / sram_generic_1r1w parameterized by $bits(sc_tag_t))
module entry_store #(
    parameter int ENTRY_WIDTH = 8,
    parameter int DEPTH = 4
) (
    input logic clk,
    input logic wr_en,
    input logic [ENTRY_WIDTH-1:0] wr_data,
    output logic [ENTRY_WIDTH-1:0] rd_data
);
  logic [ENTRY_WIDTH-1:0] mem[DEPTH];
  always_ff @(posedge clk) begin
    if (wr_en) mem[0] <= wr_data;
  end
  assign rd_data = mem[0];
endmodule

// Inner module: receives wrapper_if as port, instantiates types_if locally,
// uses struct typedef from types_if
// (mirrors simple_cache which receives simple_cache_if, instantiates
//  simple_cache_types_if, and uses types.sc_tag_t)
module inner_mod #(
    parameter cfg_pkg::cfg_t cfg = '0
) (
    input logic clk,
    wrapper_if io
);
  // Local instantiation of types_if - same cfg, so gets same clone
  // as the one inside wrapper_if via "De-parameterize to prev"
  types_if #(cfg) types ();

  typedef types.entry_t entry_t;
  typedef types.tag_t tag_t;

  entry_t wr_entry;
  entry_t rd_entry;

  assign wr_entry.vld = io.req_vld;
  assign wr_entry.tag = io.req_tag;
  assign wr_entry.row = '0;
  assign wr_entry.block = '0;

  // Use $bits of the struct typedef as a value parameter to sub-module.
  // This is the critical pattern: $bits(entry_t) must resolve using the
  // clone's struct (correct width), not the template's (zero/X width).
  entry_store #(
      .ENTRY_WIDTH($bits(entry_t)),
      .DEPTH(8)
  ) u_store (
      .clk(clk),
      .wr_en(io.req_vld),
      .wr_data(wr_entry),
      .rd_data(rd_entry)
  );
endmodule

// Outer wrapper module: instantiates wrapper_if and inner_mod
// (mirrors mblit_simple_cache_wrap)
module outer_mod #(
    parameter cfg_pkg::cfg_t cfg = '0
) (
    input logic clk
);
  wrapper_if #(cfg) wif ();

  inner_mod #(cfg) u_inner (
      .clk(clk),
      .io(wif)
  );
endmodule

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

  int cyc = 0;

  // Non-default config:
  //   AddrBits=64, Capacity=1024, LineSize=64, Associativity=2
  //   NUM_LINES = 1024/64 = 16
  //   LINES_PER_WAY = 16/2 = 8
  //   BLOCK_BITS = $clog2(64) = 6
  //   ROW_BITS = $clog2(8) = 3
  //   TAG_BITS = 64 - 3 - 6 = 55
  //   entry_t = 1 + 55 + 3 + 6 = 65 bits
  localparam cfg_pkg::cfg_t MY_CFG = '{
      AddrBits: 64,
      Capacity: 1024,
      LineSize: 64,
      Associativity: 2
  };

  outer_mod #(.cfg(MY_CFG)) u_outer (.clk(clk));

  always @(posedge clk) begin
    cyc <= cyc + 1;

    u_outer.wif.req_vld <= (cyc[0] == 1'b1);
    u_outer.wif.req_tag <= 55'(cyc);

    if (cyc > 5) begin
      // Verify the struct round-trips correctly
      if (u_outer.u_inner.rd_entry.vld !== 1'b1 && cyc > 10) begin
        $display("FAIL cyc=%0d: rd_entry.vld=%b expected 1", cyc, u_outer.u_inner.rd_entry.vld);
        $stop;
      end
    end

    if (cyc == 20) begin
      $write("*-* All Finished *-*\n");
      $finish;
    end
  end
endmodule
