From 8c252952dbc99bfcb3b4ac9a11ab24b0ef7f64c6 Mon Sep 17 00:00:00 2001 From: Dan Ruelas-Petrisko Date: Sat, 6 Dec 2025 06:11:20 -0800 Subject: [PATCH] Support `config` instance clauses (#5891 partial) (#6745) --- src/V3LinkCells.cpp | 193 ++++++++++++++---- test_regress/t/t_config_hier.out | 17 +- test_regress/t/t_config_hier.v | 2 +- test_regress/t/t_config_inst.out | 5 + test_regress/t/t_config_inst.py | 24 +++ test_regress/t/t_config_inst.v | 19 ++ test_regress/t/t_config_inst_missing.out | 3 + ...nfig_unsup.py => t_config_inst_missing.py} | 7 +- test_regress/t/t_config_inst_missing.v | 21 ++ test_regress/t/t_config_rules.py | 9 +- test_regress/t/t_config_unsup.out | 26 --- test_regress/t/t_config_unsup.v | 49 ----- 12 files changed, 242 insertions(+), 133 deletions(-) create mode 100644 test_regress/t/t_config_inst.out create mode 100755 test_regress/t/t_config_inst.py create mode 100644 test_regress/t/t_config_inst.v create mode 100644 test_regress/t/t_config_inst_missing.out rename test_regress/t/{t_config_unsup.py => t_config_inst_missing.py} (63%) create mode 100644 test_regress/t/t_config_inst_missing.v delete mode 100644 test_regress/t/t_config_unsup.out delete mode 100644 test_regress/t/t_config_unsup.v diff --git a/src/V3LinkCells.cpp b/src/V3LinkCells.cpp index 2951a3f95..e4378dc3b 100644 --- a/src/V3LinkCells.cpp +++ b/src/V3LinkCells.cpp @@ -73,6 +73,17 @@ public: string name() const override VL_MT_STABLE { return "*LIBRARY*"; } }; +class CellEdge final : public V3GraphEdge { + VL_RTTI_IMPL(CellEdge, V3GraphEdge) + AstCell* const m_cellp; + +public: + CellEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, int weight, bool cutable, AstCell* cellp) + : V3GraphEdge{graphp, fromp, top, weight, cutable}, m_cellp{cellp} {} + AstCell* cellp() const { return m_cellp; } + string name() const override VL_MT_STABLE { return cellp() ? cvtToHex(cellp()) + ' ' + cellp()->name() : ""; } +}; + void LinkCellsGraph::loopsMessageCb(V3GraphVertex* vertexp, V3EdgeFuncP edgeFuncp) { if (const LinkCellsVertex* const vvertexp = vertexp->cast()) { vvertexp->modp()->v3warn(E_UNSUPPORTED, @@ -94,7 +105,7 @@ void LinkCellsGraph::loopsMessageCb(V3GraphVertex* vertexp, V3EdgeFuncP edgeFunc // State to pass between config parsing and cell linking visitors. struct LinkCellsState final { // Set of possible top module names from command line and configs - std::unordered_set m_topModuleNames; + std::vector> m_designs; // Default library lists to search std::vector m_liblistDefault; // Library lists for specific cells @@ -102,56 +113,84 @@ struct LinkCellsState final { // Use list for specific cells (libname, cellname) std::unordered_map>> m_uselistCell; + // Library lists for specific insts + std::unordered_map> m_liblistInst; + // Use list for specific insts (libname, cellname) + std::unordered_map>> + m_uselistInst; }; class LinkConfigsVisitor final : public VNVisitor { // STATE LinkCellsState& m_state; // Context for linking cells + bool m_isTop = false; // Whether we're in the top-level config + bool m_isDefault = false; // Whether we're currently in a default clause + string m_cell; // Current cell being processed + string m_hierInst; // Current hierarchical instance being processed + AstDot* m_dotp = nullptr; // Current dot being processed // VISITORS void visit(AstConfig* nodep) override { - const string fullTopName = v3Global.opt.work() + '.' + v3Global.opt.topModule(); - const bool topMatch = (fullTopName == nodep->name()); - if (topMatch) { - m_state.m_topModuleNames.erase(fullTopName); - for (AstConfigCell* cellp = nodep->designp(); cellp; - cellp = VN_AS(cellp->nextp(), ConfigCell)) { - m_state.m_topModuleNames.insert(cellp->name()); - } - } - // We don't do iterateChildren here because we want to skip designp - iterateAndNextNull(nodep->itemsp()); + VL_RESTORER(m_isTop); + const auto& fullName = std::pair{nodep->libname(), nodep->configname()}; + m_isTop = std::find(m_state.m_designs.begin(), m_state.m_designs.end(), fullName) != m_state.m_designs.end(); + m_state.m_designs.erase( + std::remove(m_state.m_designs.begin(), m_state.m_designs.end(), fullName), + m_state.m_designs.end()); + iterateChildren(nodep); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } + void visit(AstConfigCell* nodep) override { + if (m_isTop) m_state.m_designs.emplace_back(nodep->libname(), nodep->cellname()); + } + void visit(AstConfigRule* nodep) override { if (!nodep->cellp()) { - for (AstNode* usep = nodep->usep(); usep; usep = usep->nextp()) { - m_state.m_liblistDefault.push_back(usep->name()); - } + VL_RESTORER(m_isDefault); + m_isDefault = true; + iterateAndNextNull(nodep->usep()); } else if (nodep->isCell()) { - string cellName = nodep->cellp()->name(); - if (VN_IS(nodep->usep(), ParseRef)) { - m_state.m_liblistCell[cellName] = std::vector{}; - for (AstParseRef* usep = VN_AS(nodep->usep(), ParseRef); usep; - usep = VN_AS(usep->nextp(), ParseRef)) { - m_state.m_liblistCell[cellName].push_back(usep->name()); - } - } else { - m_state.m_uselistCell[cellName] - = std::vector>{}; - for (AstConfigUse* usep = VN_AS(nodep->usep(), ConfigUse); usep; - usep = VN_AS(usep->nextp(), ConfigUse)) { - m_state.m_uselistCell[cellName].push_back( - std::pair{usep->libname(), usep->cellname()}); - } - } + VL_RESTORER(m_cell); + m_cell = nodep->cellp()->name(); + iterateAndNextNull(nodep->usep()); } else { - if (VN_IS(nodep->usep(), ParseRef)) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: config inst liblist rule"); - } else { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: config inst use rule"); + VL_RESTORER(m_hierInst); + { + VL_RESTORER(m_dotp); + m_dotp = VN_AS(nodep->cellp(), Dot); + iterateAndNextNull(nodep->cellp()); } + iterateAndNextNull(nodep->usep()); + } + } + + void visit(AstDot* nodep) override { + iterateChildren(nodep); + } + + void visit(AstParseRef* nodep) override { + if (m_isDefault) { + m_state.m_liblistDefault.emplace_back(nodep->name()); + } else if (!m_cell.empty()) { + m_state.m_liblistCell[m_cell].emplace_back(nodep->name()); + } else if (m_dotp) { + m_hierInst += m_hierInst.empty() ? nodep->name() : '.' + nodep->name(); + } else if (!m_hierInst.empty()) { + m_state.m_liblistInst[m_hierInst].emplace_back(nodep->name()); + } + } + + void visit(AstConfigUse* nodep) override { + if (nodep->isConfig()) { + nodep->v3warn(E_UNSUPPORTED, "Unsupported: hierarchical config rule"); + } else if (!m_cell.empty()) { + m_state.m_uselistCell[m_cell].emplace_back(nodep->libname(), nodep->cellname()); + } else if (m_dotp) { + m_hierInst += m_hierInst.empty() ? nodep->name() : '.' + nodep->name(); + } else if (!m_hierInst.empty()) { + m_state.m_uselistInst[m_hierInst].emplace_back( + nodep->libname(), nodep->cellname()); } } @@ -162,9 +201,8 @@ public: LinkConfigsVisitor(AstNetlist* nodep, LinkCellsState& state) : m_state{state} { // Initialize top module from command line option - if (!m_state.m_topModuleNames.size() && !v3Global.opt.topModule().empty()) { - const string fullTopName = v3Global.opt.work() + '.' + v3Global.opt.topModule(); - m_state.m_topModuleNames.insert(fullTopName); + if (!m_state.m_designs.size() && !v3Global.opt.topModule().empty()) { + m_state.m_designs.emplace_back(v3Global.opt.work(), v3Global.opt.topModule()); } iterate(nodep); } @@ -206,6 +244,10 @@ class LinkCellsVisitor final : public VNVisitor { const V3GraphEdge* const edgep = new V3GraphEdge{&m_graph, fromp, top, weight, cuttable}; UINFO(9, " newEdge " << edgep << " " << fromp->name() << " -> " << top->name()); } + void cellEdge(V3GraphVertex* fromp, V3GraphVertex* top, int weight, bool cuttable, AstCell* cellp) { + const V3GraphEdge* const edgep = new CellEdge{&m_graph, fromp, top, weight, cuttable, cellp}; + UINFO(9, " cellEdge " << edgep << " " << fromp->name() << " -> " << top->name()); + } void insertModInLib(const string& name, const string& libname, AstNodeModule* nodep) { // Be able to find the module under it's library using the name it was given VSymEnt* libSymp = m_mods.rootp()->findIdFlat(libname); @@ -292,10 +334,78 @@ class LinkCellsVisitor final : public VNVisitor { } } + AstCell* findCellByHier(AstNetlist* nodep, const std::string& hierPath) { + std::stringstream ss(hierPath); + std::string top; + bool topFound = false; + std::getline(ss, top, '.'); + for (auto const& pair : m_state.m_designs) { + if (top == pair.second) { + topFound = true; + break; + } + } + if (!topFound) { + nodep->v3error("Can't find top-level module for instance path: '" << hierPath << "'"); + V3Error::abortIfErrors(); + return nullptr; + } + + const V3GraphVertex* vtx = m_topVertexp; + const CellEdge* finalEdgep = nullptr; + std::string seg; + while (std::getline(ss, seg, '.')) { + finalEdgep = nullptr; + for (const V3GraphEdge& edge : vtx->outEdges()) { + if (const CellEdge* const cedgep = edge.cast()) { + if (cedgep->cellp()->name() == seg) { + vtx = cedgep->top(); + finalEdgep = cedgep; + break; + } + } + } + if (!finalEdgep) return nullptr; + } + + return finalEdgep->cellp(); + } + // VISITORS void visit(AstNetlist* nodep) override { readModNames(); iterateChildren(nodep); + + // Replace instance module pointers based on config + AstNodeModule* modp = nullptr; + AstCell* cellp = nullptr; + + // Search liblists for each instance + for (auto const& pair : m_state.m_liblistInst) { + cellp = findCellByHier(nodep, pair.first); + if (!cellp) continue; + for (auto const& libname : pair.second) { + modp = findModuleLibSym(cellp->modName(), libname); + if (modp) { + cellp->modp(modp); + break; + } + + } + } + + // Search uselists for each instance + for (auto const& pair : m_state.m_uselistInst) { + cellp = findCellByHier(nodep, pair.first); + for (auto const& u : pair.second) { + modp = findModuleLibSym(u.second, u.first); + if (modp) { + cellp->modp(modp); + break; + } + } + } + // Find levels in graph m_graph.removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue); if (dumpGraphLevel()) m_graph.dumpDotFilePrefixed("linkcells"); @@ -348,8 +458,9 @@ class LinkCellsVisitor final : public VNVisitor { if (VN_IS(nodep, Iface) || VN_IS(nodep, Package)) { nodep->inLibrary(true); // Interfaces can't be at top, unless asked } - const string fullName = nodep->libname() + "." + nodep->name(); - const bool topMatch = (m_state.m_topModuleNames.count(fullName) > 0); + + auto const& fullName = std::pair(nodep->libname(), nodep->name()); + const bool topMatch = std::find(m_state.m_designs.begin(), m_state.m_designs.end(), fullName) != m_state.m_designs.end(); if (topMatch) { m_topVertexp = vertex(nodep); UINFO(2, "Link --top-module: " << nodep); @@ -507,7 +618,7 @@ class LinkCellsVisitor final : public VNVisitor { } else { // Non-recursive // Track module depths, so can sort list from parent down to children nodep->modp(cellmodp); - newEdge(vertex(m_modp), vertex(cellmodp), 1, false); + cellEdge(vertex(m_modp), vertex(cellmodp), 1, false, nodep); } } } diff --git a/test_regress/t/t_config_hier.out b/test_regress/t/t_config_hier.out index 68c76c0c4..c24c4e178 100644 --- a/test_regress/t/t_config_hier.out +++ b/test_regress/t/t_config_hier.out @@ -1,11 +1,14 @@ -%Error-UNSUPPORTED: t/t_config_hier.v:36:3: Unsupported: config inst use rule +%Error-UNSUPPORTED: t/t_config_hier.v:36:18: Unsupported: hierarchical config rule 36 | instance t.u_1 use work.cfg2 :config; - | ^~~~~~~~ + | ^~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_config_hier.v:41:3: Unsupported: config inst use rule +%Error-UNSUPPORTED: t/t_config_hier.v:41:18: Unsupported: hierarchical config rule 41 | instance t.u_1 use cfg2 :config; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_config_hier.v:62:3: Unsupported: config inst use rule - 62 | instance u_bb use work.c2_bb; - | ^~~~~~~~ + | ^~~ +%Error-UNSUPPORTED: t/t_config_hier.v:46:16: Unsupported: hierarchical config rule + 46 | cell work.m1 use work.cfg2 :config; + | ^~~ +%Error-UNSUPPORTED: t/t_config_hier.v:51:11: Unsupported: hierarchical config rule + 51 | cell m1 use cfg2 :config; + | ^~~ %Error: Exiting due to diff --git a/test_regress/t/t_config_hier.v b/test_regress/t/t_config_hier.v index c8b8838fd..2f71a6b91 100644 --- a/test_regress/t/t_config_hier.v +++ b/test_regress/t/t_config_hier.v @@ -59,5 +59,5 @@ endconfig // Base usage config cfg2; design c2_b; - instance u_bb use work.c2_bb; + instance c2_b.u_bb use work.c2_bb; endconfig diff --git a/test_regress/t/t_config_inst.out b/test_regress/t/t_config_inst.out new file mode 100644 index 000000000..0df8def65 --- /dev/null +++ b/test_regress/t/t_config_inst.out @@ -0,0 +1,5 @@ +*-* All Finished *-* +liba:m1 %m=t.u_1 %l=liba.m1 +liba:m3 %m=t.u_2.u_23 %l=liba.m3 +libb:m2 %m=t.u_2 %l=libb.m2 +libb:m3 %m=t.u_1.u_13 %l=libb.m3 diff --git a/test_regress/t/t_config_inst.py b/test_regress/t/t_config_inst.py new file mode 100755 index 000000000..628079b8b --- /dev/null +++ b/test_regress/t/t_config_inst.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=[ + '--binary', '--top cfg1', '--work liba', 't/t_config_work__liba.v', '--work libb', + 't/t_config_work__libb.v' +]) + +test.execute() + +# Sort so that 'initial' scheduling order is not relevant +test.files_identical_sorted(test.run_log_filename, test.golden_filename, is_logfile=True) + +test.passes() diff --git a/test_regress/t/t_config_inst.v b/test_regress/t/t_config_inst.v new file mode 100644 index 000000000..1f6bc507b --- /dev/null +++ b/test_regress/t/t_config_inst.v @@ -0,0 +1,19 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + // Test config allows selecting two different libraries for these instances + m1 u_1(); + m2 u_2(); + final $write("*-* All Finished *-*\n"); +endmodule + +config cfg1; + design t; + // Use libb's version of m3 for m1 and liba's version of m3 for m2 + instance t.u_1.u_13 liblist libb; + instance t.u_2.u_23 use liba.m3; +endconfig diff --git a/test_regress/t/t_config_inst_missing.out b/test_regress/t/t_config_inst_missing.out new file mode 100644 index 000000000..efcef6810 --- /dev/null +++ b/test_regress/t/t_config_inst_missing.out @@ -0,0 +1,3 @@ +%Error: Can't find top-level module for instance path: 't2.u_2.u_23' + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_config_unsup.py b/test_regress/t/t_config_inst_missing.py similarity index 63% rename from test_regress/t/t_config_unsup.py rename to test_regress/t/t_config_inst_missing.py index e30916148..24025be5c 100755 --- a/test_regress/t/t_config_unsup.py +++ b/test_regress/t/t_config_inst_missing.py @@ -9,8 +9,11 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('simulator') -test.lint(fails=test.vlt_all, expect_filename=test.golden_filename) +test.lint(verilator_flags2=[ + '--binary', '--top cfg1', '--work liba', 't/t_config_work__liba.v', '--work libb', + 't/t_config_work__libb.v' + ], fails=test.vlt_all, expect_filename=test.golden_filename) test.passes() diff --git a/test_regress/t/t_config_inst_missing.v b/test_regress/t/t_config_inst_missing.v new file mode 100644 index 000000000..43dbfebe0 --- /dev/null +++ b/test_regress/t/t_config_inst_missing.v @@ -0,0 +1,21 @@ + +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + // Test config allows selecting two different libraries for these instances + m1 u_1(); + m2 u_2(); + final $write("*-* All Finished *-*\n"); +endmodule + +config cfg1; + design t; + // Use libb's version of m3 for m1 and liba's version of m3 for m2 + instance t.u_1.u_13 liblist libb; + // t2 not found, causes error + instance t2.u_2.u_23 use liba.m3; +endconfig diff --git a/test_regress/t/t_config_rules.py b/test_regress/t/t_config_rules.py index b3f6cdf69..2da127041 100755 --- a/test_regress/t/t_config_rules.py +++ b/test_regress/t/t_config_rules.py @@ -15,13 +15,8 @@ test.compile(verilator_flags2=[ "--binary", "--top cfg", "--work liba t/t_config_rules_sub.v", "--work libb t/t_config_rules_sub.v", "--work libc t/t_config_rules_sub.v", "--work libd t/t_config_rules_sub.v" -], - fails=True, - expect_filename=test.golden_filename) +]) -#test.execute() - -# Sort so that 'initial' scheduling order is not relevant -#test.files_identical_sorted(test.run_log_filename, test.golden_filename, is_logfile=True) +test.execute() test.passes() diff --git a/test_regress/t/t_config_unsup.out b/test_regress/t/t_config_unsup.out deleted file mode 100644 index ac5ece905..000000000 --- a/test_regress/t/t_config_unsup.out +++ /dev/null @@ -1,26 +0,0 @@ -%Error-UNSUPPORTED: t/t_config_unsup.v:32:3: Unsupported: config inst use rule - 32 | instance t.m20 liblist; - | ^~~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_config_unsup.v:33:3: Unsupported: config inst liblist rule - 33 | instance t.m21 liblist libc; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_config_unsup.v:34:3: Unsupported: config inst liblist rule - 34 | instance t.m22 liblist libc libd; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_config_unsup.v:35:3: Unsupported: config inst liblist rule - 35 | instance t.m23 liblist libc libd; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_config_unsup.v:36:3: Unsupported: config inst liblist rule - 36 | instance t.m24 liblist libc libd; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_config_unsup.v:39:3: Unsupported: config inst use rule - 39 | instance t.m30 use cell_identifier; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_config_unsup.v:40:3: Unsupported: config inst use rule - 40 | instance t.m31 use lib_id.cell_id; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_config_unsup.v:41:3: Unsupported: config inst use rule - 41 | instance t.m32 use #(); - | ^~~~~~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_config_unsup.v b/test_regress/t/t_config_unsup.v deleted file mode 100644 index 1c9583e06..000000000 --- a/test_regress/t/t_config_unsup.v +++ /dev/null @@ -1,49 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain, for -// any use, without warranty, 2025 by Wilson Snyder. -// SPDX-License-Identifier: CC0-1.0 - -module t; - m10 u_10(); - m20 u_20(); - m21 u_21(); - m22 u_22(); - m23 u_23(); - m24 u_24(); - m30 u_30(); - m31 u_31(); - m32 u_32(); - m40 u_40(); - m41 u_41(); - m42 u_42(); - m43 u_43(); - final $write("*-* All Finished *-*\n"); -endmodule - -config cfg; - design t; - - // Test uses m10 - default liblist; // Ignored - default liblist liba libb; - - // Test uses m20-29 - instance t.m20 liblist; // Use parent's cell library - instance t.m21 liblist libc; - instance t.m22 liblist libc libd; // m22 in libc - instance t.m23 liblist libc libd; // m23 in libd - instance t.m24 liblist libc libd; // m24 in default (libb) - - // Test uses m30-39 - instance t.m30 use cell_identifier; - instance t.m31 use lib_id.cell_id; - instance t.m32 use #(); - - // Test uses m40-49 - cell m40 liblist libc libd; - cell work.m41 liblist libc libd; - cell m42 use m42alt; - cell work.m43 use work.m43alt; - -endconfig