2026-03-03 12:55:59 +01:00
|
|
|
// 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 #(
|
2026-03-03 13:21:24 +01:00
|
|
|
parameter cfg_pkg::cfg_t cfg = '0
|
|
|
|
|
) ();
|
2026-03-03 12:55:59 +01:00
|
|
|
// Computed localparams - these use division and $clog2 of cfg fields.
|
|
|
|
|
// With default cfg='0, these produce X/undefined values.
|
2026-03-03 13:21:24 +01:00
|
|
|
localparam int NUM_LINES = cfg.Capacity / cfg.LineSize;
|
2026-03-03 12:55:59 +01:00
|
|
|
localparam int LINES_PER_WAY = NUM_LINES / cfg.Associativity;
|
2026-03-03 13:21:24 +01:00
|
|
|
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;
|
2026-03-03 12:55:59 +01:00
|
|
|
|
|
|
|
|
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 {
|
2026-03-03 13:21:24 +01:00
|
|
|
logic vld;
|
|
|
|
|
tag_t tag;
|
|
|
|
|
row_t row;
|
2026-03-03 12:55:59 +01:00
|
|
|
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 #(
|
2026-03-03 13:21:24 +01:00
|
|
|
parameter cfg_pkg::cfg_t cfg = '0
|
|
|
|
|
) ();
|
|
|
|
|
types_if #(cfg) types ();
|
2026-03-03 12:55:59 +01:00
|
|
|
|
2026-03-03 13:21:24 +01:00
|
|
|
typedef types.tag_t tag_t;
|
2026-03-03 12:55:59 +01:00
|
|
|
|
2026-03-03 13:21:24 +01:00
|
|
|
logic req_vld;
|
|
|
|
|
tag_t req_tag;
|
2026-03-03 12:55:59 +01:00
|
|
|
endinterface
|
|
|
|
|
|
|
|
|
|
// Sub-module parameterized by entry width
|
|
|
|
|
// (mirrors flop_nr / sram_generic_1r1w parameterized by $bits(sc_tag_t))
|
|
|
|
|
module entry_store #(
|
2026-03-03 13:21:24 +01:00
|
|
|
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
|
2026-03-03 12:55:59 +01:00
|
|
|
);
|
2026-03-03 13:21:24 +01:00
|
|
|
logic [ENTRY_WIDTH-1:0] mem[DEPTH];
|
2026-03-03 12:55:59 +01:00
|
|
|
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 #(
|
2026-03-03 13:21:24 +01:00
|
|
|
parameter cfg_pkg::cfg_t cfg = '0
|
|
|
|
|
) (
|
|
|
|
|
input logic clk,
|
|
|
|
|
wrapper_if io
|
2026-03-03 12:55:59 +01:00
|
|
|
);
|
|
|
|
|
// Local instantiation of types_if - same cfg, so gets same clone
|
|
|
|
|
// as the one inside wrapper_if via "De-parameterize to prev"
|
2026-03-03 13:21:24 +01:00
|
|
|
types_if #(cfg) types ();
|
2026-03-03 12:55:59 +01:00
|
|
|
|
|
|
|
|
typedef types.entry_t entry_t;
|
|
|
|
|
typedef types.tag_t tag_t;
|
|
|
|
|
|
|
|
|
|
entry_t wr_entry;
|
|
|
|
|
entry_t rd_entry;
|
|
|
|
|
|
2026-03-03 13:21:24 +01:00
|
|
|
assign wr_entry.vld = io.req_vld;
|
|
|
|
|
assign wr_entry.tag = io.req_tag;
|
|
|
|
|
assign wr_entry.row = '0;
|
2026-03-03 12:55:59 +01:00
|
|
|
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 #(
|
2026-03-03 13:21:24 +01:00
|
|
|
.ENTRY_WIDTH($bits(entry_t)),
|
|
|
|
|
.DEPTH(8)
|
2026-03-03 12:55:59 +01:00
|
|
|
) u_store (
|
2026-03-03 13:21:24 +01:00
|
|
|
.clk(clk),
|
|
|
|
|
.wr_en(io.req_vld),
|
|
|
|
|
.wr_data(wr_entry),
|
|
|
|
|
.rd_data(rd_entry)
|
2026-03-03 12:55:59 +01:00
|
|
|
);
|
|
|
|
|
endmodule
|
|
|
|
|
|
|
|
|
|
// Outer wrapper module: instantiates wrapper_if and inner_mod
|
|
|
|
|
// (mirrors mblit_simple_cache_wrap)
|
|
|
|
|
module outer_mod #(
|
2026-03-03 13:21:24 +01:00
|
|
|
parameter cfg_pkg::cfg_t cfg = '0
|
|
|
|
|
) (
|
|
|
|
|
input logic clk
|
2026-03-03 12:55:59 +01:00
|
|
|
);
|
2026-03-03 13:21:24 +01:00
|
|
|
wrapper_if #(cfg) wif ();
|
2026-03-03 12:55:59 +01:00
|
|
|
|
|
|
|
|
inner_mod #(cfg) u_inner (
|
2026-03-03 13:21:24 +01:00
|
|
|
.clk(clk),
|
|
|
|
|
.io(wif)
|
2026-03-03 12:55:59 +01:00
|
|
|
);
|
|
|
|
|
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 = '{
|
2026-03-03 13:21:24 +01:00
|
|
|
AddrBits: 64,
|
|
|
|
|
Capacity: 1024,
|
|
|
|
|
LineSize: 64,
|
|
|
|
|
Associativity: 2
|
2026-03-03 12:55:59 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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
|
2026-03-03 13:21:24 +01:00
|
|
|
$display("FAIL cyc=%0d: rd_entry.vld=%b expected 1", cyc, u_outer.u_inner.rd_entry.vld);
|
2026-03-03 12:55:59 +01:00
|
|
|
$stop;
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if (cyc == 20) begin
|
|
|
|
|
$write("*-* All Finished *-*\n");
|
|
|
|
|
$finish;
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
endmodule
|