Fix interface derived types passed as parameters to generate loop module instantiation (#7273)
This commit is contained in:
parent
4b34bfffcb
commit
a2154e9119
|
|
@ -853,7 +853,8 @@ class ParamProcessor final {
|
|||
// Phase A: path-based fixup using ledger entries
|
||||
std::set<AstRefDType*> ledgerFixed;
|
||||
{
|
||||
const string cloneCP = VN_CAST(ifErrorp, Cell) ? VN_AS(ifErrorp, Cell)->name() : "";
|
||||
// Must match the cloneCellPath used by propagateClone (newname).
|
||||
const string cloneCP = newModp->name();
|
||||
const string srcName = srcModp->name();
|
||||
UINFO(9, "iface capture FIXUP-A: srcName=" << srcName << " cloneCP='" << cloneCP << "'"
|
||||
<< endl);
|
||||
|
|
@ -991,7 +992,8 @@ class ParamProcessor final {
|
|||
}
|
||||
// Register clone entry in ledger (no AST mutation).
|
||||
if (AstRefDType* const clonedRefp = entry.refp->clonep()) {
|
||||
const string cloneCP = cloneCellp ? cloneCellp->name() : string{};
|
||||
// Use newname (unique specialized module name) as cloneCellPath.
|
||||
const string cloneCP = newname;
|
||||
const V3LinkDotIfaceCapture::TemplateKey tkey{
|
||||
entry.ownerModp ? entry.ownerModp->name() : "", entry.refp->name(),
|
||||
entry.cellPath};
|
||||
|
|
@ -1791,10 +1793,13 @@ public:
|
|||
V3LinkDotIfaceCapture::forEach([&](const V3LinkDotIfaceCapture::CapturedEntry& entry) {
|
||||
if (!entry.refp || entry.cloneCellPath.empty()) return;
|
||||
if (entry.cellPath != cellName) return;
|
||||
// Identity check: only retarget REFDTYPEs that actually live
|
||||
// inside parentModp. Multiple clones share origName so name
|
||||
// matching alone would let one clone overwrite another's refs.
|
||||
if (V3LinkDotIfaceCapture::findOwnerModule(entry.refp) != parentModp) return;
|
||||
AstNodeModule* const ownerp = V3LinkDotIfaceCapture::findOwnerModule(entry.refp);
|
||||
// Only retarget REFDTYPEs owned by parentModp.
|
||||
// Null owner (type-table dtypes) falls back to cloneCellPath match.
|
||||
if (ownerp != parentModp
|
||||
&& !(ownerp == nullptr && entry.cloneCellPath == parentModp->name())) {
|
||||
return;
|
||||
}
|
||||
if (retargetRefToModule(entry, correctModp)) {
|
||||
UINFO(9, "retargetIfaceRefs: " << entry.refp << " -> "
|
||||
<< correctModp->prettyNameQ() << endl);
|
||||
|
|
@ -2102,6 +2107,12 @@ class ParamVisitor final : public VNVisitor {
|
|||
|
||||
// Add to the hierarchy registry
|
||||
m_state.m_parentps[newModp].insert(modp);
|
||||
|
||||
// Eagerly specialize nested iface cells so their types
|
||||
// have correct widths before sibling module cells run.
|
||||
if (VN_IS(newModp, Iface) && newModp != srcModp) {
|
||||
specializeNestedIfaceCells(newModp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2227,6 +2238,36 @@ class ParamVisitor final : public VNVisitor {
|
|||
});
|
||||
}
|
||||
|
||||
// Deparameterize and constify nested interface cells within ifaceModp.
|
||||
void specializeNestedIfaceCells(AstNodeModule* ifaceModp) {
|
||||
for (AstNode* stmtp = ifaceModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
AstCell* const nestedCellp = VN_CAST(stmtp, Cell);
|
||||
if (!nestedCellp) continue;
|
||||
if (!VN_IS(nestedCellp->modp(), Iface)) continue;
|
||||
if (!nestedCellp->paramsp()) continue;
|
||||
|
||||
AstNodeModule* const nestedSrcModp = nestedCellp->modp();
|
||||
if (AstNodeModule* const nestedNewModp = m_processor.nodeDeparam(
|
||||
nestedCellp, nestedSrcModp, ifaceModp, ifaceModp->someInstanceName())) {
|
||||
if (nestedNewModp != nestedSrcModp) {
|
||||
// Constify the nested clone's params so its types have correct widths.
|
||||
for (AstNode* sp = nestedNewModp->stmtsp(); sp; sp = sp->nextp()) {
|
||||
if (AstVar* const varp = VN_CAST(sp, Var)) {
|
||||
if (varp->isParam() && varp->valuep()) {
|
||||
V3Const::constifyParamsEdit(varp);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Retarget REFDTYPEs in the outer clone to the nested clone's types.
|
||||
if (V3LinkDotIfaceCapture::enabled()) {
|
||||
m_processor.retargetIfaceRefs(ifaceModp, nestedCellp->name());
|
||||
}
|
||||
specializeNestedIfaceCells(nestedNewModp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if cell parameters reference interface ports or local interface instances
|
||||
bool cellParamsReferenceIfacePorts(AstCell* cellp) {
|
||||
if (!cellp->paramsp()) return false;
|
||||
|
|
@ -2247,51 +2288,25 @@ class ParamVisitor final : public VNVisitor {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Recursively specialize nested interface cells within a specialized interface.
|
||||
// This handles parameter passthrough for nested interface hierarchies.
|
||||
void specializeNestedIfaceCells(AstNodeModule* ifaceModp) {
|
||||
for (AstNode* stmtp = ifaceModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
AstCell* const nestedCellp = VN_CAST(stmtp, Cell);
|
||||
if (!nestedCellp) continue;
|
||||
if (!VN_IS(nestedCellp->modp(), Iface)) continue;
|
||||
if (!nestedCellp->paramsp()) continue;
|
||||
if (cellParamsReferenceIfacePorts(nestedCellp)) continue;
|
||||
|
||||
AstNodeModule* const nestedSrcModp = nestedCellp->modp();
|
||||
if (AstNodeModule* const nestedNewModp = m_processor.nodeDeparam(
|
||||
nestedCellp, nestedSrcModp, ifaceModp, ifaceModp->someInstanceName())) {
|
||||
// Recursively process nested interfaces within this nested interface
|
||||
if (nestedNewModp != nestedSrcModp) specializeNestedIfaceCells(nestedNewModp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A generic visitor for cells and class refs
|
||||
void visitCellOrClassRef(AstNode* nodep, bool isIface) {
|
||||
// Must do ifaces first, so push to list and do in proper order
|
||||
m_strings.emplace_back(m_generateHierName);
|
||||
nodep->user2p(&m_strings.back());
|
||||
|
||||
// For interface cells with parameters, specialize first before processing children
|
||||
// Only do early specialization if parameters don't reference interface ports
|
||||
// Deparameterize iface cells early so types are available for lparams.
|
||||
if (isIface && VN_CAST(nodep, Cell) && VN_CAST(nodep, Cell)->paramsp()) {
|
||||
AstCell* const cellp = VN_CAST(nodep, Cell);
|
||||
if (!cellParamsReferenceIfacePorts(cellp)) {
|
||||
AstNodeModule* const srcModp = cellp->modp();
|
||||
// DISABLED: specializeNestedIfaceCells causes early nested
|
||||
// iface specialization where PARAMTYPEDTYPE child REFDTYPEs
|
||||
// point to template structs instead of clone structs,
|
||||
// destructively widthing the template with default (zero)
|
||||
// values. See t_interface_nested_struct_param.v.
|
||||
m_processor.nodeDeparam(cellp, srcModp, m_modp, m_modp->someInstanceName());
|
||||
// After the interface cell is rewired to its clone,
|
||||
// retarget REFDTYPEs in the parent module that still
|
||||
// reference the template interface's types. This ensures
|
||||
// $bits(iface_typedef) evaluates correctly when
|
||||
// widthParamsEdit runs on subsequent lparams.
|
||||
AstNodeModule* const newModp
|
||||
= m_processor.nodeDeparam(cellp, srcModp, m_modp, m_modp->someInstanceName());
|
||||
// Retarget template REFDTYPEs to the clone's types.
|
||||
if (V3LinkDotIfaceCapture::enabled() && cellp->modp() != srcModp) {
|
||||
m_processor.retargetIfaceRefs(m_modp, cellp->name());
|
||||
}
|
||||
// Specialize nested iface cells so their types are correct.
|
||||
if (newModp && newModp != srcModp) { specializeNestedIfaceCells(newModp); }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=["--binary"])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,275 @@
|
|||
// 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
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--binary'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
// 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: struct typedef from parameterized interface with derived
|
||||
// localparam used as type parameter to a sub-module cell.
|
||||
//
|
||||
|
||||
package cfg_pkg;
|
||||
typedef struct packed {
|
||||
int unsigned Capacity;
|
||||
int unsigned Slices;
|
||||
int unsigned NumThreads;
|
||||
} cfg_t;
|
||||
endpackage
|
||||
|
||||
// Types interface: derives localparam from cfg, uses in typedef range,
|
||||
// builds union/struct containing that typedef.
|
||||
// On the template default (cfg='0), Capacity/Slices = 0/0 = X.
|
||||
interface types_if #(
|
||||
parameter cfg_pkg::cfg_t cfg = '0
|
||||
) ();
|
||||
localparam int NUM_ROWS = (cfg.Capacity / cfg.Slices) / 8;
|
||||
typedef logic [$clog2(NUM_ROWS)-1:0] row_addr_t;
|
||||
typedef logic [$clog2(cfg.NumThreads)-1:0] tl_addr_t;
|
||||
|
||||
typedef union packed {
|
||||
logic [$bits(row_addr_t)+$bits(tl_addr_t)+6-1:0] raw;
|
||||
struct packed {
|
||||
row_addr_t row_index;
|
||||
tl_addr_t tl_index;
|
||||
logic [5:0] bit_index;
|
||||
} fld;
|
||||
} addr_t;
|
||||
|
||||
typedef struct packed {
|
||||
logic en;
|
||||
logic [3:0] tag;
|
||||
logic is_read;
|
||||
} meta_t;
|
||||
|
||||
// Compound packet type (mirrors iq_pkt_t)
|
||||
typedef struct packed {
|
||||
meta_t meta;
|
||||
addr_t addr;
|
||||
logic [63:0] data;
|
||||
} pkt_t;
|
||||
endinterface
|
||||
|
||||
// Generic type-parameterized register (mirrors tflop_nr)
|
||||
module tflop_nr #(parameter type T = logic) (
|
||||
input logic clk,
|
||||
input logic rst_n,
|
||||
output T q_o,
|
||||
input T d_i
|
||||
);
|
||||
always_ff @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) q_o <= '0;
|
||||
else q_o <= d_i;
|
||||
end
|
||||
endmodule
|
||||
|
||||
// Slice module: receives pkt_t as type param from wrapper, also
|
||||
// instantiates types_if locally, builds local struct from local types,
|
||||
// passes that struct as type param to tflop_nr.
|
||||
// (mirrors smem_slice)
|
||||
module slice #(
|
||||
parameter cfg_pkg::cfg_t cfg = '0,
|
||||
parameter int SLICE_IDX = 0,
|
||||
parameter type pkt_t = logic
|
||||
) (
|
||||
input logic clk,
|
||||
input logic rst_n,
|
||||
input logic in_vld,
|
||||
input pkt_t in_pkt,
|
||||
output logic out_vld,
|
||||
output pkt_t out_pkt
|
||||
);
|
||||
// Local types_if instantiation - same cfg as wrapper's
|
||||
types_if #(cfg) types();
|
||||
|
||||
typedef types.addr_t addr_t;
|
||||
typedef types.meta_t meta_t;
|
||||
|
||||
// Local struct containing locally-imported interface types
|
||||
typedef struct packed {
|
||||
meta_t meta;
|
||||
addr_t addr;
|
||||
} rq_t;
|
||||
|
||||
rq_t rq_d, rq_q;
|
||||
|
||||
always_comb begin
|
||||
rq_d = rq_q;
|
||||
if (in_vld) begin
|
||||
rq_d.meta = in_pkt.meta;
|
||||
rq_d.addr = in_pkt.addr;
|
||||
end
|
||||
end
|
||||
|
||||
// Pass local struct as type param to tflop_nr - this is the trigger
|
||||
tflop_nr #(.T(rq_t)) rq_reg (
|
||||
.clk(clk),
|
||||
.rst_n(rst_n),
|
||||
.q_o(rq_q),
|
||||
.d_i(rq_d)
|
||||
);
|
||||
|
||||
assign out_vld = rq_q.meta.en;
|
||||
assign out_pkt.meta = rq_q.meta;
|
||||
assign out_pkt.addr = rq_q.addr;
|
||||
assign out_pkt.data = in_pkt.data;
|
||||
endmodule
|
||||
|
||||
// Wrapper module: instantiates types_if, imports pkt_t, passes it as
|
||||
// type param to slice instances in a generate loop.
|
||||
// (mirrors smem_top)
|
||||
module wrapper #(
|
||||
parameter cfg_pkg::cfg_t cfg = '0
|
||||
) (
|
||||
input logic clk,
|
||||
input logic rst_n
|
||||
);
|
||||
types_if #(cfg) types();
|
||||
|
||||
typedef types.pkt_t pkt_t;
|
||||
typedef types.meta_t meta_t;
|
||||
|
||||
pkt_t in_pkt;
|
||||
logic in_vld;
|
||||
|
||||
assign in_vld = 1'b1;
|
||||
assign in_pkt.meta.en = 1'b1;
|
||||
assign in_pkt.meta.tag = 4'd5;
|
||||
assign in_pkt.meta.is_read = 1'b0;
|
||||
assign in_pkt.addr = '0;
|
||||
assign in_pkt.data = 64'hDEAD_BEEF;
|
||||
|
||||
// Generate loop with slices (mirrors gen_slices in smem_top)
|
||||
logic [cfg.NumThreads-1:0] out_vld;
|
||||
pkt_t [cfg.NumThreads-1:0] out_pkt;
|
||||
|
||||
generate
|
||||
for (genvar i = 0; i < 2; i++) begin : gen_slices
|
||||
slice #(
|
||||
.cfg(cfg),
|
||||
.SLICE_IDX(i),
|
||||
.pkt_t(pkt_t)
|
||||
) u_slice (
|
||||
.clk(clk),
|
||||
.rst_n(rst_n),
|
||||
.in_vld(in_vld),
|
||||
.in_pkt(in_pkt),
|
||||
.out_vld(out_vld[i]),
|
||||
.out_pkt(out_pkt[i])
|
||||
);
|
||||
end
|
||||
endgenerate
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
logic clk = 0;
|
||||
always #5 clk = ~clk;
|
||||
logic rst_n = 0;
|
||||
|
||||
int cyc = 0;
|
||||
|
||||
localparam cfg_pkg::cfg_t MY_CFG = '{
|
||||
Capacity: 8192,
|
||||
Slices: 8,
|
||||
NumThreads: 32
|
||||
};
|
||||
// Expected: NUM_ROWS = (8192/8)/8 = 128
|
||||
// row_addr_t = logic [6:0] (7 bits)
|
||||
// tl_addr_t = logic [4:0] (5 bits)
|
||||
// addr_t.raw = logic [17:0] (7+5+6 = 18 bits)
|
||||
|
||||
wrapper #(.cfg(MY_CFG)) u_wrapper (
|
||||
.clk(clk),
|
||||
.rst_n(rst_n)
|
||||
);
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 2) rst_n <= 1;
|
||||
if (cyc > 5) begin
|
||||
if (u_wrapper.out_vld[0] !== 1'b1) begin
|
||||
$display("FAIL cyc=%0d: out_vld[0]=%b expected 1", cyc, u_wrapper.out_vld[0]);
|
||||
$stop;
|
||||
end
|
||||
if (u_wrapper.out_pkt[0].meta.tag !== 4'd5) begin
|
||||
$display("FAIL cyc=%0d: tag=%0d expected 5", cyc, u_wrapper.out_pkt[0].meta.tag);
|
||||
$stop;
|
||||
end
|
||||
end
|
||||
if (cyc == 20) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -7,17 +7,11 @@
|
|||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
# Verifies that skipWidthForTemplateStruct fires in V3Param::cellPinCleanup
|
||||
# when struct typedefs from a nested parameterized interface are passed as
|
||||
# type parameters through two levels of interface nesting.
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
test.compile(v_flags2=["--binary --stats"])
|
||||
|
||||
test.file_grep(test.stats, r'Param, Template struct width skips\s+(\d+)', 2)
|
||||
test.compile(v_flags2=["--binary"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ test.compile(v_flags2=["--binary --stats"])
|
|||
test.file_grep(test.stats, r'IfaceCapture, Entries total\s+(\d+)', 18)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 8)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 10)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 10)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 8)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 10)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 6)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 2)
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ test.top_filename = "t/t_paramgraph_iface_template_nested.v"
|
|||
|
||||
test.compile(v_flags2=["--binary --stats"])
|
||||
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries total\s+(\d+)', 21)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries total\s+(\d+)', 25)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 11)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 10)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 7)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 8)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 4)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 14)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 5)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 10)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 2)
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ test.top_filename = "t/t_paramgraph_nested_iface_typedef.v"
|
|||
|
||||
test.compile(v_flags2=["--binary --stats"])
|
||||
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries total\s+(\d+)', 18)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries total\s+(\d+)', 20)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 8)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 10)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 12)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 12)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 10)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 12)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 8)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 14)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 4)
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue