1 // -*- mode: C++; c-file-style: "cc-mode" -*-
2 //*************************************************************************
3 // DESCRIPTION: Verilator: Resolve module/signal name references
4 //
5 // Code available from: https://verilator.org
6 //
7 //*************************************************************************
8 //
9 // Copyright 2003-2021 by Wilson Snyder. This program is free software; you
10 // can redistribute it and/or modify it under the terms of either the GNU
11 // Lesser General Public License Version 3 or the Perl Artistic License
12 // Version 2.0.
13 // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
14 //
15 //*************************************************************************
16 // NO EDITS: Don't replace or delete nodes, as the parser symbol table
17 // has pointers into the ast tree.
18 //
19 // LINK TRANSFORMATIONS:
20 // Top-down traversal
21 // Cells:
22 // Read module if needed
23 // Link to module that instantiates it
24 //*************************************************************************
25
26 #include "config_build.h"
27 #include "verilatedos.h"
28
29 #include "V3Global.h"
30 #include "V3LinkCells.h"
31 #include "V3SymTable.h"
32 #include "V3Parse.h"
33 #include "V3Ast.h"
34 #include "V3Graph.h"
35
36 #include <algorithm>
37 #include <map>
38 #include <vector>
39 #include <unordered_set>
40
41 //######################################################################
42 // Graph subclasses
43
44 class LinkCellsGraph final : public V3Graph {
45 public:
46 LinkCellsGraph() = default;
47 virtual ~LinkCellsGraph() override = default;
48 virtual void loopsMessageCb(V3GraphVertex* vertexp) override;
49 };
50
51 class LinkCellsVertex final : public V3GraphVertex {
52 AstNodeModule* const m_modp;
53
54 public:
LinkCellsVertex(V3Graph * graphp,AstNodeModule * modp)55 LinkCellsVertex(V3Graph* graphp, AstNodeModule* modp)
56 : V3GraphVertex{graphp}
57 , m_modp{modp} {}
58 virtual ~LinkCellsVertex() override = default;
modp() const59 AstNodeModule* modp() const { return m_modp; }
name() const60 virtual string name() const override { return modp()->name(); }
fileline() const61 virtual FileLine* fileline() const override { return modp()->fileline(); }
62 // Recursive modules get space for maximum recursion
rankAdder() const63 virtual uint32_t rankAdder() const override {
64 return m_modp->recursiveClone() ? (1 + v3Global.opt.moduleRecursionDepth()) : 1;
65 }
66 };
67
68 class LibraryVertex final : public V3GraphVertex {
69 public:
LibraryVertex(V3Graph * graphp)70 explicit LibraryVertex(V3Graph* graphp)
71 : V3GraphVertex{graphp} {}
72 virtual ~LibraryVertex() override = default;
name() const73 virtual string name() const override { return "*LIBRARY*"; }
74 };
75
loopsMessageCb(V3GraphVertex * vertexp)76 void LinkCellsGraph::loopsMessageCb(V3GraphVertex* vertexp) {
77 if (const LinkCellsVertex* const vvertexp = dynamic_cast<LinkCellsVertex*>(vertexp)) {
78 vvertexp->modp()->v3warn(E_UNSUPPORTED,
79 "Unsupported: Recursive multiple modules (module instantiates "
80 "something leading back to itself): "
81 << vvertexp->modp()->prettyNameQ() << '\n'
82 << V3Error::warnMore()
83 << "... note: self-recursion (module instantiating itself "
84 "directly) is supported.");
85 V3Error::abortIfErrors();
86 } else { // Everything should match above, but...
87 v3fatalSrc("Recursive instantiations");
88 }
89 }
90
91 //######################################################################
92 // Link state, as a visitor of each AstNode
93
94 class LinkCellsVisitor final : public AstNVisitor {
95 private:
96 // NODE STATE
97 // Entire netlist:
98 // AstNodeModule::user1p() // V3GraphVertex* Vertex describing this module
99 // AstNodeModule::user2p() // AstNodeModule* clone used for de-recursing
100 // AstCell::user1p() // ==V3NodeModule* if done, != if unprocessed
101 // AstCell::user2() // bool clone renaming completed
102 // Allocated across all readFiles in V3Global::readFiles:
103 // AstNode::user4p() // VSymEnt* Package and typedef symbol names
104 const AstUser1InUse m_inuser1;
105 const AstUser2InUse m_inuser2;
106
107 // STATE
108 VInFilter* m_filterp; // Parser filter
109 V3ParseSym* m_parseSymp; // Parser symbol table
110
111 // Below state needs to be preserved between each module call.
112 AstNodeModule* m_modp = nullptr; // Current module
113 VSymGraph m_mods; // Symbol table of all module names
114 LinkCellsGraph m_graph; // Linked graph of all cell interconnects
115 LibraryVertex* m_libVertexp = nullptr; // Vertex at root of all libraries
116 const V3GraphVertex* m_topVertexp = nullptr; // Vertex of top module
117 std::unordered_set<string> m_declfnWarned; // Files we issued DECLFILENAME on
118 string m_origTopModuleName; // original name of the top module
119
120 VL_DEBUG_FUNC; // Declare debug()
121
122 // METHODS
vertex(AstNodeModule * nodep)123 V3GraphVertex* vertex(AstNodeModule* nodep) {
124 // Return corresponding vertex for this module
125 if (!nodep->user1p()) nodep->user1p(new LinkCellsVertex(&m_graph, nodep));
126 return (nodep->user1u().toGraphVertex());
127 }
128
findModuleSym(const string & modName)129 AstNodeModule* findModuleSym(const string& modName) {
130 const VSymEnt* const foundp = m_mods.rootp()->findIdFallback(modName);
131 if (!foundp) {
132 return nullptr;
133 } else {
134 return VN_AS(foundp->nodep(), NodeModule);
135 }
136 }
137
resolveModule(AstNode * nodep,const string & modName)138 AstNodeModule* resolveModule(AstNode* nodep, const string& modName) {
139 AstNodeModule* modp = findModuleSym(modName);
140 if (!modp) {
141 // Read-subfile
142 // If file not found, make AstNotFoundModule, rather than error out.
143 // We'll throw the error when we know the module will really be needed.
144 const string prettyName = AstNode::prettyName(modName);
145 V3Parse parser(v3Global.rootp(), m_filterp, m_parseSymp);
146 // true below -> other simulators treat modules in link-found files as library cells
147 parser.parseFile(nodep->fileline(), prettyName, true, "");
148 V3Error::abortIfErrors();
149 // We've read new modules, grab new pointers to their names
150 readModNames();
151 // Check again
152 modp = findModuleSym(modName);
153 if (!modp) {
154 // This shouldn't throw a message as parseFile will create
155 // a AstNotFoundModule for us
156 nodep->v3error("Can't resolve module reference: '" << prettyName << "'");
157 }
158 }
159 return modp;
160 }
161
162 // VISITs
visit(AstNetlist * nodep)163 virtual void visit(AstNetlist* nodep) override {
164 AstNode::user1ClearTree();
165 readModNames();
166 iterateChildren(nodep);
167 // Find levels in graph
168 m_graph.removeRedundantEdges(&V3GraphEdge::followAlwaysTrue);
169 m_graph.dumpDotFilePrefixed("linkcells");
170 m_graph.rank();
171 for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
172 if (const LinkCellsVertex* const vvertexp = dynamic_cast<LinkCellsVertex*>(itp)) {
173 // +1 so we leave level 1 for the new wrapper we'll make in a moment
174 AstNodeModule* const modp = vvertexp->modp();
175 modp->level(vvertexp->rank() + 1);
176 }
177 }
178 if (v3Global.opt.topModule() != "" && !m_topVertexp) {
179 v3error("Specified --top-module '" << v3Global.opt.topModule()
180 << "' was not found in design.");
181 }
182 }
visit(AstConstPool * nodep)183 virtual void visit(AstConstPool* nodep) override {}
visit(AstNodeModule * nodep)184 virtual void visit(AstNodeModule* nodep) override {
185 // Module: Pick up modnames, so we can resolve cells later
186 VL_RESTORER(m_modp);
187 {
188 m_modp = nodep;
189 UINFO(4, "Link Module: " << nodep << endl);
190 if (nodep->fileline()->filebasenameNoExt() != nodep->prettyName()
191 && !v3Global.opt.isLibraryFile(nodep->fileline()->filename())
192 && !VN_IS(nodep, NotFoundModule) && !nodep->recursiveClone()
193 && !nodep->internal()) {
194 // We only complain once per file, otherwise library-like files
195 // have a huge mess of warnings
196 if (m_declfnWarned.find(nodep->fileline()->filename()) == m_declfnWarned.end()) {
197 m_declfnWarned.insert(nodep->fileline()->filename());
198 nodep->v3warn(DECLFILENAME, "Filename '"
199 << nodep->fileline()->filebasenameNoExt()
200 << "' does not match " << nodep->typeName()
201 << " name: " << nodep->prettyNameQ());
202 }
203 }
204 if (VN_IS(nodep, Iface) || VN_IS(nodep, Package)) {
205 nodep->inLibrary(true); // Interfaces can't be at top, unless asked
206 }
207 const bool topMatch = (v3Global.opt.topModule() == nodep->prettyName());
208 if (topMatch) {
209 m_topVertexp = vertex(nodep);
210 UINFO(2, "Link --top-module: " << nodep << endl);
211 nodep->inLibrary(false); // Safer to make sure it doesn't disappear
212 }
213 if (v3Global.opt.topModule() == "" ? nodep->inLibrary() // Library cells are lower
214 : !topMatch) { // Any non-specified module is lower
215 // Put under a fake vertex so that the graph ranking won't indicate
216 // this is a top level module
217 if (!m_libVertexp) m_libVertexp = new LibraryVertex(&m_graph);
218 new V3GraphEdge(&m_graph, m_libVertexp, vertex(nodep), 1, false);
219 }
220 // Note AstBind also has iteration on cells
221 iterateChildren(nodep);
222 nodep->checkTree();
223 }
224 }
225
visit(AstIfaceRefDType * nodep)226 virtual void visit(AstIfaceRefDType* nodep) override {
227 // Cell: Resolve its filename. If necessary, parse it.
228 UINFO(4, "Link IfaceRef: " << nodep << endl);
229 // Use findIdUpward instead of findIdFlat; it doesn't matter for now
230 // but we might support modules-under-modules someday.
231 AstNodeModule* const modp = resolveModule(nodep, nodep->ifaceName());
232 if (modp) {
233 if (VN_IS(modp, Iface)) {
234 // Track module depths, so can sort list from parent down to children
235 new V3GraphEdge(&m_graph, vertex(m_modp), vertex(modp), 1, false);
236 if (!nodep->cellp()) nodep->ifacep(VN_AS(modp, Iface));
237 } else if (VN_IS(modp, NotFoundModule)) { // Will error out later
238 } else {
239 nodep->v3error("Non-interface used as an interface: " << nodep->prettyNameQ());
240 }
241 }
242 // Note cannot do modport resolution here; modports are allowed underneath generates
243 }
244
visit(AstPackageImport * nodep)245 virtual void visit(AstPackageImport* nodep) override {
246 // Package Import: We need to do the package before the use of a package
247 iterateChildren(nodep);
248 UASSERT_OBJ(nodep->packagep(), nodep, "Unlinked package"); // Parser should set packagep
249 new V3GraphEdge(&m_graph, vertex(m_modp), vertex(nodep->packagep()), 1, false);
250 }
251
visit(AstBind * nodep)252 virtual void visit(AstBind* nodep) override {
253 // Bind: Has cells underneath that need to be put into the new
254 // module, and cells which need resolution
255 // TODO this doesn't allow bind to dotted hier names, that would require
256 // this move to post param, which would mean we do not auto-read modules
257 // and means we cannot compute module levels until later.
258 UINFO(4, "Link Bind: " << nodep << endl);
259 AstNodeModule* const modp = resolveModule(nodep, nodep->name());
260 if (modp) {
261 AstNode* const cellsp = nodep->cellsp()->unlinkFrBackWithNext();
262 // Module may have already linked, so need to pick up these new cells
263 VL_RESTORER(m_modp);
264 {
265 m_modp = modp;
266 // Important that this adds to end, as next iterate assumes does all cells
267 modp->addStmtp(cellsp);
268 iterateAndNextNull(cellsp);
269 }
270 }
271 pushDeletep(nodep->unlinkFrBack());
272 }
273
visit(AstCell * nodep)274 virtual void visit(AstCell* nodep) override {
275 // Cell: Resolve its filename. If necessary, parse it.
276 // Execute only once. Complication is that cloning may result in
277 // user1 being set (for pre-clone) so check if user1() matches the
278 // m_mod, if 0 never did it, if !=, it is an unprocessed clone
279 const bool cloned = (nodep->user1p() && nodep->user1p() != m_modp);
280 if (nodep->user1p() == m_modp) return; // AstBind and AstNodeModule may call a cell twice
281 if (nodep->modName() == m_origTopModuleName) {
282 if (v3Global.opt.hierChild() && nodep->modName() == m_modp->origName()) {
283 // Only the root of the recursive instantiation can be a hierarchical block.
284 nodep->modName(m_modp->name());
285 } else {
286 // non-top module will be the top module of this run
287 VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
288 return;
289 }
290 }
291 nodep->user1p(m_modp);
292 //
293 if (!nodep->modp() || cloned) {
294 UINFO(4, "Link Cell: " << nodep << endl);
295 // Use findIdFallback instead of findIdFlat; it doesn't matter for now
296 // but we might support modules-under-modules someday.
297 AstNodeModule* cellmodp = resolveModule(nodep, nodep->modName());
298 if (cellmodp) {
299 if (cellmodp == m_modp || cellmodp->user2p() == m_modp) {
300 UINFO(1, "Self-recursive module " << cellmodp << endl);
301 cellmodp->recursive(true);
302 nodep->recursive(true);
303 if (!cellmodp->recursiveClone()) {
304 // In the non-Vrcm, which needs to point to Vrcm flavor
305 //
306 // Make a clone which this cell points to
307 // Later, the clone's cells will also point clone'd name
308 // This lets us link the XREFs between the (uncloned) children so
309 // they don't point to the same module which would
310 // break parameter resolution.
311 AstNodeModule* otherModp = VN_CAST(cellmodp->user2p(), NodeModule);
312 if (!otherModp) {
313 otherModp = cellmodp->cloneTree(false);
314 otherModp->name(otherModp->name() + "__Vrcm");
315 otherModp->user1p(nullptr); // Need new vertex
316 otherModp->user2p(cellmodp);
317 otherModp->recursiveClone(true);
318 // user1 etc will retain its pre-clone value
319 cellmodp->user2p(otherModp);
320 v3Global.rootp()->addModulep(otherModp);
321 new V3GraphEdge(&m_graph, vertex(cellmodp), vertex(otherModp), 1,
322 false);
323 }
324 cellmodp = otherModp;
325 nodep->modp(cellmodp);
326 } else {
327 // In the Vrcm, which needs to point back to Vrcm flavor
328 // The cell already has the correct resolution (to Vrcm)
329 nodep->modp(cellmodp);
330 // We don't create a V3GraphEdge (as it would be circular)
331 }
332 } else { // Non-recursive
333 // Track module depths, so can sort list from parent down to children
334 nodep->modp(cellmodp);
335 new V3GraphEdge(&m_graph, vertex(m_modp), vertex(cellmodp), 1, false);
336 }
337 }
338 }
339 // Remove AstCell(AstPin("",nullptr)), it's a side effect of how we parse "()"
340 // the empty middle is identical to the empty rule that must find pins in "(,)".
341 if (nodep->pinsp() && !nodep->pinsp()->nextp() && nodep->pinsp()->name() == ""
342 && !nodep->pinsp()->exprp()) {
343 pushDeletep(nodep->pinsp()->unlinkFrBackWithNext());
344 }
345 if (nodep->paramsp() && !nodep->paramsp()->nextp() && nodep->paramsp()->name() == ""
346 && !nodep->paramsp()->exprp()) {
347 pushDeletep(nodep->paramsp()->unlinkFrBackWithNext());
348 }
349 // Convert .* to list of pins
350 bool pinStar = false;
351 for (AstPin *nextp, *pinp = nodep->pinsp(); pinp; pinp = nextp) {
352 nextp = VN_AS(pinp->nextp(), Pin);
353 if (pinp->dotStar()) {
354 if (pinStar) pinp->v3error("Duplicate .* in an instance");
355 pinStar = true;
356 // Done with this fake pin
357 VL_DO_DANGLING(pinp->unlinkFrBack()->deleteTree(), pinp);
358 }
359 }
360 // Convert unnamed pins to pin number based assignments
361 for (AstPin* pinp = nodep->pinsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
362 if (pinp->name() == "") pinp->name("__pinNumber" + cvtToStr(pinp->pinNum()));
363 }
364 for (AstPin* pinp = nodep->paramsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
365 pinp->param(true);
366 if (pinp->name() == "") pinp->name("__paramNumber" + cvtToStr(pinp->pinNum()));
367 }
368 if (nodep->modp()) {
369 nodep->modName(nodep->modp()->name());
370 // Note what pins exist
371 std::unordered_set<string> ports; // Symbol table of all connected port names
372 for (AstPin* pinp = nodep->pinsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
373 if (pinp->name() == "")
374 pinp->v3error("Connect by position is illegal in .* connected instances");
375 if (!pinp->exprp()) {
376 if (pinp->name().substr(0, 11) == "__pinNumber") {
377 pinp->v3warn(PINNOCONNECT,
378 "Cell pin is not connected: " << pinp->prettyNameQ());
379 } else {
380 pinp->v3warn(PINCONNECTEMPTY,
381 "Cell pin connected by name with empty reference: "
382 << pinp->prettyNameQ());
383 }
384 }
385 ports.insert(pinp->name());
386 }
387 // We search ports, rather than in/out declarations as they aren't resolved yet,
388 // and it's easier to do it now than in V3LinkDot when we'd need to repeat steps.
389 for (AstNode* portnodep = nodep->modp()->stmtsp(); portnodep;
390 portnodep = portnodep->nextp()) {
391 if (const AstPort* const portp = VN_CAST(portnodep, Port)) {
392 if (ports.find(portp->name()) == ports.end()
393 && ports.find("__pinNumber" + cvtToStr(portp->pinNum())) == ports.end()) {
394 if (pinStar) {
395 UINFO(9, " need .* PORT " << portp << endl);
396 // Create any not already connected
397 AstPin* const newp = new AstPin(
398 nodep->fileline(), 0, portp->name(),
399 new AstParseRef(nodep->fileline(), VParseRefExp::PX_TEXT,
400 portp->name(), nullptr, nullptr));
401 newp->svImplicit(true);
402 nodep->addPinsp(newp);
403 } else { // warn on the CELL that needs it, not the port
404 nodep->v3warn(PINMISSING,
405 "Cell has missing pin: " << portp->prettyNameQ());
406 AstPin* const newp
407 = new AstPin(nodep->fileline(), 0, portp->name(), nullptr);
408 nodep->addPinsp(newp);
409 }
410 }
411 }
412 }
413 }
414 if (VN_IS(nodep->modp(), Iface)) {
415 // Cell really is the parent's instantiation of an interface, not a normal module
416 // Make sure we have a variable to refer to this cell, so can <ifacename>.<innermember>
417 // in the same way that a child does. Rename though to avoid conflict with cell.
418 // This is quite similar to how classes work; when unpacked
419 // classes are better supported may remap interfaces to be more
420 // like a class.
421 if (!nodep->hasIfaceVar()) {
422 const string varName
423 = nodep->name() + "__Viftop"; // V3LinkDot looks for this naming
424 AstIfaceRefDType* const idtypep = new AstIfaceRefDType(
425 nodep->fileline(), nodep->name(), nodep->modp()->name());
426 idtypep->ifacep(nullptr); // cellp overrides
427 // In the case of arrayed interfaces, we replace cellp when de-arraying in V3Inst
428 idtypep->cellp(nodep); // Only set when real parent cell known.
429 AstVar* varp;
430 if (nodep->rangep()) {
431 AstNodeArrayDType* const arrp
432 = new AstUnpackArrayDType(nodep->fileline(), VFlagChildDType(), idtypep,
433 nodep->rangep()->cloneTree(true));
434 varp = new AstVar(nodep->fileline(), AstVarType::IFACEREF, varName,
435 VFlagChildDType(), arrp);
436 } else {
437 varp = new AstVar(nodep->fileline(), AstVarType::IFACEREF, varName,
438 VFlagChildDType(), idtypep);
439 }
440 varp->isIfaceParent(true);
441 nodep->addNextHere(varp);
442 nodep->hasIfaceVar(true);
443 }
444 }
445 if (nodep->modp()) { //
446 iterateChildren(nodep);
447 }
448 UINFO(4, " Link Cell done: " << nodep << endl);
449 }
450
visit(AstRefDType * nodep)451 virtual void visit(AstRefDType* nodep) override {
452 for (AstPin* pinp = nodep->paramsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
453 pinp->param(true);
454 if (pinp->name() == "") pinp->name("__paramNumber" + cvtToStr(pinp->pinNum()));
455 }
456 }
457
458 // Accelerate the recursion
459 // Must do statements to support Generates, math though...
visit(AstNodeMath *)460 virtual void visit(AstNodeMath*) override {}
visit(AstNode * nodep)461 virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
462
463 // METHODS
readModNames()464 void readModNames() {
465 // mangled_name, BlockOptions
466 const V3HierBlockOptSet& hierBlocks = v3Global.opt.hierBlocks();
467 const auto hierIt = vlstd::as_const(hierBlocks).find(v3Global.opt.topModule());
468 UASSERT((hierIt != hierBlocks.end()) == v3Global.opt.hierChild(),
469 "information of the top module must exist if --hierarchical-child is set");
470 // Look at all modules, and store pointers to all module names
471 for (AstNodeModule *nextp, *nodep = v3Global.rootp()->modulesp(); nodep; nodep = nextp) {
472 nextp = VN_AS(nodep->nextp(), NodeModule);
473 if (v3Global.opt.hierChild() && nodep->name() == hierIt->second.origName()) {
474 nodep->name(hierIt->first); // Change name of this module to be mangled name
475 // considering parameter
476 }
477 const AstNodeModule* const foundp = findModuleSym(nodep->name());
478 if (foundp && foundp != nodep) {
479 if (!(foundp->fileline()->warnIsOff(V3ErrorCode::MODDUP)
480 || nodep->fileline()->warnIsOff(V3ErrorCode::MODDUP)
481 || hierBlocks.find(nodep->name()) != hierBlocks.end())) {
482 nodep->v3warn(MODDUP, "Duplicate declaration of module: "
483 << nodep->prettyNameQ() << '\n'
484 << nodep->warnContextPrimary() << '\n'
485 << foundp->warnOther()
486 << "... Location of original declaration\n"
487 << foundp->warnContextSecondary());
488 }
489 nodep->unlinkFrBack();
490 VL_DO_DANGLING(pushDeletep(nodep), nodep);
491 } else if (!foundp) {
492 m_mods.rootp()->insert(nodep->name(), new VSymEnt(&m_mods, nodep));
493 }
494 }
495 // if (debug() >= 9) m_mods.dump(cout, "-syms: ");
496 }
497
498 public:
499 // CONSTRUCTORS
LinkCellsVisitor(AstNetlist * nodep,VInFilter * filterp,V3ParseSym * parseSymp)500 LinkCellsVisitor(AstNetlist* nodep, VInFilter* filterp, V3ParseSym* parseSymp)
501 : m_mods{nodep} {
502 m_filterp = filterp;
503 m_parseSymp = parseSymp;
504 if (v3Global.opt.hierChild()) {
505 const V3HierBlockOptSet& hierBlocks = v3Global.opt.hierBlocks();
506 UASSERT(!v3Global.opt.topModule().empty(),
507 "top module must be explicitly specified in hierarchical mode");
508 const V3HierBlockOptSet::const_iterator hierIt
509 = hierBlocks.find(v3Global.opt.topModule());
510 UASSERT(hierIt != hierBlocks.end(),
511 "top module must be listed in --hierarchical-block");
512 m_origTopModuleName = hierIt->second.origName();
513 } else {
514 m_origTopModuleName = v3Global.opt.topModule();
515 }
516 iterate(nodep);
517 }
518 virtual ~LinkCellsVisitor() override = default;
519 };
520
521 //######################################################################
522 // Link class functions
523
link(AstNetlist * nodep,VInFilter * filterp,V3ParseSym * parseSymp)524 void V3LinkCells::link(AstNetlist* nodep, VInFilter* filterp, V3ParseSym* parseSymp) {
525 UINFO(4, __FUNCTION__ << ": " << endl);
526 { LinkCellsVisitor{nodep, filterp, parseSymp}; }
527 }
528