This commit is contained in:
parent
1f67080a1f
commit
1b2b8afdc1
|
|
@ -800,7 +800,8 @@ class ParamProcessor final {
|
|||
const string lastCompBase
|
||||
= (braPos == string::npos) ? lastComp : lastComp.substr(0, braPos);
|
||||
if (lastComp != cloneCellp->name() && lastCompBase != cloneCellp->name()) return false;
|
||||
if (lastDot == string::npos) return true; // No parent portion to verify
|
||||
// No parent portion to verify - startModp itself must be the expected parent
|
||||
if (lastDot == string::npos) return startModp == expectModp;
|
||||
const string parentPath = cellPath.substr(0, lastDot);
|
||||
const AstNodeModule* const resolvedp
|
||||
= V3LinkDotIfaceCapture::followCellPath(startModp, parentPath);
|
||||
|
|
@ -869,6 +870,7 @@ class ParamProcessor final {
|
|||
<< (correctModp ? correctModp->name() : "<null>")
|
||||
<< endl);
|
||||
if (!correctModp || correctModp->dead()) return;
|
||||
if (correctModp->parameterizedTemplate()) return;
|
||||
|
||||
bool fixed = false;
|
||||
if (refp->typedefp()) {
|
||||
|
|
@ -1292,8 +1294,10 @@ class ParamProcessor final {
|
|||
if (resolvedp && (VN_IS(resolvedp, StructDType) || VN_IS(resolvedp, UnionDType))) {
|
||||
AstNodeModule* const ownerModp
|
||||
= V3LinkDotIfaceCapture::findOwnerModule(resolvedp);
|
||||
// Skip if owned by a parameterized template (not yet specialized)
|
||||
if (ownerModp && ownerModp->parameterizedTemplate()) {
|
||||
// Skip if owned by a parameterized interface (template or not
|
||||
// yet cloned). hasGParam() covers interfaces that haven't
|
||||
// been cloned yet (parameterizedTemplate() not set).
|
||||
if (ownerModp && VN_IS(ownerModp, Iface) && ownerModp->hasGParam()) {
|
||||
skipWidthForTemplateStruct = true;
|
||||
V3Stats::addStatSum("Param, Template struct width skips", 1);
|
||||
UINFO(9, "SKIP-WIDTH-TEMPLATE: struct="
|
||||
|
|
@ -1776,6 +1780,28 @@ class ParamProcessor final {
|
|||
}
|
||||
|
||||
public:
|
||||
// After an interface cell inside parentModp has been deparameterized
|
||||
// (rewired from template to clone), retarget REFDTYPEs that still
|
||||
// reference the old template's types so that $bits(iface_typedef)
|
||||
// evaluates with the clone's widths.
|
||||
void retargetIfaceRefs(AstNodeModule* parentModp, const string& cellName) {
|
||||
AstNodeModule* const correctModp
|
||||
= V3LinkDotIfaceCapture::followCellPath(parentModp, cellName);
|
||||
if (!correctModp || correctModp->dead() || correctModp->parameterizedTemplate()) return;
|
||||
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;
|
||||
if (retargetRefToModule(entry, correctModp)) {
|
||||
UINFO(9, "retargetIfaceRefs: " << entry.refp << " -> "
|
||||
<< correctModp->prettyNameQ() << endl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AstNodeModule* nodeDeparam(AstNode* nodep, AstNodeModule* srcModp, AstNodeModule* modp,
|
||||
const string& someInstanceName) {
|
||||
// Return new or reused de-parameterized module
|
||||
|
|
@ -1895,7 +1921,11 @@ public:
|
|||
// STATE - across all visitors
|
||||
std::vector<AstClass*> m_paramClasses; // Parameterized classes
|
||||
std::vector<AstDot*> m_dots; // Dot references to process
|
||||
std::multimap<int, AstNodeModule*> m_workQueueNext; // Modules left to process
|
||||
// Work queue keyed by (!isIface, level) so interfaces are always processed
|
||||
// before non-interfaces. This ensures interface clones have their types
|
||||
// properly widthed before any module that references those types.
|
||||
using WQKey = std::pair<bool, int>;
|
||||
std::multimap<WQKey, AstNodeModule*> m_workQueueNext; // Modules left to process
|
||||
// Map from AstNodeModule to set of all AstNodeModules that instantiates it.
|
||||
std::unordered_map<AstNodeModule*, std::unordered_set<AstNodeModule*>> m_parentps;
|
||||
};
|
||||
|
|
@ -1930,7 +1960,7 @@ class ParamVisitor final : public VNVisitor {
|
|||
|
||||
void processWorkQ() {
|
||||
UASSERT(!m_iterateModule, "Should not nest");
|
||||
std::multimap<int, AstNodeModule*> workQueue;
|
||||
std::multimap<ParamState::WQKey, AstNodeModule*> workQueue;
|
||||
m_generateHierName = "";
|
||||
m_iterateModule = true;
|
||||
|
||||
|
|
@ -2005,10 +2035,21 @@ class ParamVisitor final : public VNVisitor {
|
|||
|
||||
if (VN_IS(srcModp, Iface)) {
|
||||
logTemplateLeakRefs(modp, srcModp, "after queued nodeDeparam", cellp);
|
||||
// After the interface cell is rewired to its clone,
|
||||
// retarget REFDTYPEs in the parent module that still
|
||||
// reference the template interface's types.
|
||||
if (V3LinkDotIfaceCapture::enabled()) {
|
||||
if (const AstCell* const modCellp = VN_CAST(cellp, Cell)) {
|
||||
if (newModp != srcModp) {
|
||||
m_processor.retargetIfaceRefs(modp, modCellp->name());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the (now potentially specialized) child module to the work queue
|
||||
workQueue.emplace(newModp->level(), newModp);
|
||||
workQueue.emplace(ParamState::WQKey{!VN_IS(newModp, Iface), newModp->level()},
|
||||
newModp);
|
||||
|
||||
// Add to the hierarchy registry
|
||||
m_state.m_parentps[newModp].insert(modp);
|
||||
|
|
@ -2194,6 +2235,14 @@ class ParamVisitor final : public VNVisitor {
|
|||
// 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.
|
||||
if (V3LinkDotIfaceCapture::enabled() && cellp->modp() != srcModp) {
|
||||
m_processor.retargetIfaceRefs(m_modp, cellp->name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2225,7 +2274,8 @@ class ParamVisitor final : public VNVisitor {
|
|||
UINFO(4, " MOD-under-MOD. " << nodep);
|
||||
// Delay until current module is done.
|
||||
// processWorkQ() (which we are returning to) will process nodep later
|
||||
m_state.m_workQueueNext.emplace(nodep->level(), nodep);
|
||||
m_state.m_workQueueNext.emplace(
|
||||
ParamState::WQKey{!VN_IS(nodep, Iface), nodep->level()}, nodep);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2233,7 +2283,8 @@ class ParamVisitor final : public VNVisitor {
|
|||
if (nodep->isTop() // Tops
|
||||
|| VN_IS(nodep, Class) // Moved classes
|
||||
|| VN_IS(nodep, Package)) { // Likewise haven't done wrapTopPackages yet
|
||||
m_state.m_workQueueNext.emplace(nodep->level(), nodep);
|
||||
m_state.m_workQueueNext.emplace(
|
||||
ParamState::WQKey{!VN_IS(nodep, Iface), nodep->level()}, nodep);
|
||||
processWorkQ();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Localparam with package function call using interface param
|
||||
#
|
||||
# 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,242 @@
|
|||
// DESCRIPTION: Verilator: Localparam with package function call using interface param
|
||||
//
|
||||
// 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
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
|
||||
// verilog_format: on
|
||||
|
||||
package utils;
|
||||
function automatic int f_bits (int val);
|
||||
return (val <= 1) ? 1 : $clog2(val);
|
||||
endfunction
|
||||
endpackage
|
||||
|
||||
package soc;
|
||||
// Configuration structure
|
||||
typedef struct packed {
|
||||
// CB Configuration
|
||||
int unsigned CBRids;
|
||||
int unsigned CBPids;
|
||||
int unsigned CBFnum;
|
||||
// Coarse parameters
|
||||
int unsigned Tile;
|
||||
int unsigned NumBb;
|
||||
// CCA Parameters
|
||||
int unsigned NumDd;
|
||||
// CC Parameters
|
||||
int unsigned DDNumW;
|
||||
int unsigned DDNumWT;
|
||||
int unsigned DDIds; // check this one
|
||||
} cfg_t;
|
||||
endpackage
|
||||
|
||||
package cb;
|
||||
typedef struct packed {
|
||||
int unsigned Rids; // ring id assigned to port
|
||||
int unsigned Pids; // packet id
|
||||
int unsigned Fnum; // flit number
|
||||
int unsigned XdatSize; // raw packet data size
|
||||
} cfg_t;
|
||||
endpackage
|
||||
|
||||
package bba;
|
||||
typedef logic [11:0] csr_index_t;
|
||||
typedef logic [1:0] cmd_bnum_t;
|
||||
typedef logic [3:0] rq_cmd_t;
|
||||
typedef logic [3:0] rs_cmd_t;
|
||||
typedef union packed {
|
||||
rq_cmd_t rq;
|
||||
rs_cmd_t rs;
|
||||
} cmd_t;
|
||||
endpackage: bba
|
||||
|
||||
interface bb_types_if #(parameter soc::cfg_t cfg=0)();
|
||||
//
|
||||
typedef logic [$clog2(cfg.NumDd)-1:0] bb_index_t;
|
||||
typedef logic [$clog2(cfg.DDIds)-1:0] trans_id_t;
|
||||
|
||||
typedef struct packed {
|
||||
logic hdr_vld;
|
||||
logic [1:0] bnum;
|
||||
logic is_rq;
|
||||
bba::cmd_t cmd;
|
||||
bb_index_t cc_index;
|
||||
trans_id_t trans_id;
|
||||
} cmd_meta_t;
|
||||
|
||||
typedef union packed {
|
||||
logic [cfg.DDNumWT*64-1:0] raw;
|
||||
logic [cfg.DDNumWT-1:0][63:0] raw_a;
|
||||
} cmd_data_t;
|
||||
|
||||
typedef struct packed {
|
||||
cmd_meta_t meta;
|
||||
cmd_data_t d;
|
||||
} cmd_beat_t;
|
||||
endinterface
|
||||
|
||||
interface cb_port_vc_if #(parameter cb::cfg_t cfg=0)();
|
||||
|
||||
typedef logic [$clog2(cfg.Rids)-1:0] rid_t;
|
||||
typedef logic [$clog2(cfg.Pids)-1:0] pid_t;
|
||||
typedef logic [$clog2(cfg.Fnum)-1:0] fnum_t;
|
||||
typedef logic [cfg.XdatSize-1:0] xdat_t;
|
||||
|
||||
typedef struct packed {
|
||||
xdat_t dat;
|
||||
fnum_t fnum;
|
||||
logic first;
|
||||
logic last;
|
||||
rid_t dest_rid;
|
||||
} tx_beat_t;
|
||||
|
||||
tx_beat_t tx_beat;
|
||||
endinterface
|
||||
|
||||
module simple_mem #(
|
||||
parameter int p_num_buffered_rq = 16,
|
||||
parameter int p_storage_size = 65536
|
||||
)(
|
||||
bb_types_if bb_types,
|
||||
// CB Interface
|
||||
cb_port_vc_if cb_vc0_io,
|
||||
cb_port_vc_if cb_vc1_io
|
||||
);
|
||||
|
||||
typedef bb_types.cmd_beat_t cmd_beat_t;
|
||||
|
||||
logic flit_ot_vld_d, flit_ot_vld_q;
|
||||
cmd_beat_t flit_ot_d, flit_ot_q;
|
||||
|
||||
always_comb begin
|
||||
cb_vc1_io.tx_beat = 'b0;
|
||||
|
||||
if(flit_ot_vld_q) begin
|
||||
cb_vc1_io.tx_beat.dat = flit_ot_q;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module xm_top #(
|
||||
parameter soc::cfg_t soc_cfg=0)(
|
||||
// Interface to SMM from CB
|
||||
cb_port_vc_if cb_vc0_io,
|
||||
cb_port_vc_if cb_vc1_io
|
||||
);
|
||||
|
||||
bb_types_if #(soc_cfg) bb_types();
|
||||
|
||||
simple_mem #() simple_mem(
|
||||
.bb_types(bb_types),
|
||||
.cb_vc0_io(cb_vc0_io),
|
||||
.cb_vc1_io(cb_vc1_io)
|
||||
);
|
||||
endmodule
|
||||
|
||||
module tile_top #(
|
||||
parameter soc::cfg_t soc_cfg=0
|
||||
)(
|
||||
);
|
||||
|
||||
bb_types_if #(soc_cfg) bb_types();
|
||||
typedef bb_types.cmd_beat_t cmd_beat_t;
|
||||
|
||||
localparam cb::cfg_t cb_cfg = '{
|
||||
Rids : soc_cfg.CBRids,
|
||||
Pids : soc_cfg.CBPids,
|
||||
Fnum : soc_cfg.CBFnum,
|
||||
XdatSize:$bits(cmd_beat_t)
|
||||
};
|
||||
|
||||
cb_port_vc_if#(cb_cfg) cb_tm_vc0_io();
|
||||
cb_port_vc_if#(cb_cfg) cb_tm_vc1_io();
|
||||
|
||||
xm_top #(
|
||||
.soc_cfg(soc_cfg)
|
||||
) xm_top(
|
||||
.cb_vc0_io(cb_tm_vc0_io),
|
||||
.cb_vc1_io(cb_tm_vc1_io)
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
module soc_top #(parameter soc::cfg_t soc_cfg=0)(
|
||||
|
||||
);
|
||||
|
||||
genvar a;
|
||||
localparam int p_num_tile = soc_cfg.Tile;
|
||||
|
||||
bb_types_if #(soc_cfg) bb_types();
|
||||
typedef bb_types.cmd_beat_t cmd_beat_t;
|
||||
|
||||
// CB and RBUS
|
||||
localparam cb::cfg_t cb_cfg = '{
|
||||
Rids : soc_cfg.CBRids,
|
||||
Pids : soc_cfg.CBPids,
|
||||
Fnum : soc_cfg.CBFnum,
|
||||
XdatSize:$bits(cmd_beat_t)
|
||||
};
|
||||
|
||||
// cb interfaces (HTM dead ends)
|
||||
cb_port_vc_if#(cb_cfg) cb_vc0_io();
|
||||
cb_port_vc_if#(cb_cfg) cb_vc1_io();
|
||||
|
||||
// HTM
|
||||
xm_top #(
|
||||
.soc_cfg(soc_cfg)
|
||||
) xm_top(
|
||||
.cb_vc0_io(cb_vc0_io),
|
||||
.cb_vc1_io(cb_vc1_io)
|
||||
);
|
||||
|
||||
generate
|
||||
for(a=0; a<p_num_tile; a++) begin : gen_tiles
|
||||
tile_top #(
|
||||
.soc_cfg(soc_cfg)
|
||||
) tile(
|
||||
);
|
||||
end
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
|
||||
module t();
|
||||
|
||||
parameter soc::cfg_t soc_cfg = '{
|
||||
// Cluster Bus
|
||||
CBRids : 16,
|
||||
CBPids : 32,
|
||||
CBFnum : 4,
|
||||
// Coarse parameters
|
||||
Tile : 4,
|
||||
NumBb : 1,
|
||||
// CCA Parameters
|
||||
NumDd : 3,
|
||||
// CC Parameters
|
||||
DDNumW : 4,
|
||||
DDNumWT : 8,
|
||||
DDIds : 5
|
||||
};
|
||||
|
||||
soc_top #(
|
||||
.soc_cfg(soc_cfg)
|
||||
) soc_top();
|
||||
|
||||
// Check that cb_cfg.XdatSize is correctly computed as $bits(cmd_beat_t) = 525
|
||||
// for both soc_top and tile_top paths
|
||||
initial begin
|
||||
`checkd(soc_top.cb_cfg.XdatSize, 525);
|
||||
`checkd(soc_top.gen_tiles[0].tile.cb_cfg.XdatSize, 525);
|
||||
`checkd(soc_top.gen_tiles[1].tile.cb_cfg.XdatSize, 525);
|
||||
`checkd(soc_top.gen_tiles[2].tile.cb_cfg.XdatSize, 525);
|
||||
`checkd(soc_top.gen_tiles[3].tile.cb_cfg.XdatSize, 525);
|
||||
$display("PASS: All cb_cfg.XdatSize = 525");
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -21,9 +21,9 @@ 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 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+)', 8)
|
||||
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+)', 3)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 4)
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue