275 lines
8.1 KiB
Systemverilog
275 lines
8.1 KiB
Systemverilog
// 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
|