Fix corner case bugs in module and variable inlining (#6322)

There were a couple corner case bugs in V3Inline, and one in Dfg when
dealing with inlining of modules/variables.

V3Inline:
- Invalid code generated when inlining an input that also had an
  assignment to it (Throws an ASSIGNIN, but this is sometimes reasonable
  to do, e.g. hiererchical reference to an unonnected input port)
- Inlining (aliasing) publicly writeable input port.
- Inlining forcable port connected to constant.

Dfg:
- Inining publicly writeable variables

The tests that cover these are the same and fixing one will trigger the
other bug, so fixing them all in one go. Also cleanup V3Inline to be less
out of order and rely less on unique APIs only used by V3Inine (will
remove those in follow up patch).

Small step towards #6280.
This commit is contained in:
Geza Lore 2025-08-22 21:43:49 +01:00 committed by GitHub
parent f506aa878b
commit 1c86ff0af2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 417 additions and 365 deletions

View File

@ -1941,8 +1941,6 @@ public:
inline void iterateChildrenBackwardsConst(AstNode* nodep);
/// Call visit()s on const nodep (maybe nullptr) and nodep's nextp() list
inline void iterateAndNextConstNull(AstNode* nodep);
/// Call visit()s on const nodep (maybe nullptr) and nodep's nextp() list, in reverse order
inline void iterateAndNextConstNullBackwards(AstNode* nodep);
virtual void visit(AstNode* nodep) = 0;
virtual ~VNVisitorConst() {}
@ -3128,9 +3126,6 @@ void VNVisitorConst::iterateChildrenConst(AstNode* nodep) { nodep->iterateChildr
void VNVisitorConst::iterateChildrenBackwardsConst(AstNode* nodep) {
nodep->iterateChildrenBackwardsConst(*this);
}
void VNVisitorConst::iterateAndNextConstNullBackwards(AstNode* nodep) {
if (VL_LIKELY(nodep)) nodep->iterateListBackwardsConst(*this);
}
void VNVisitorConst::iterateAndNextConstNull(AstNode* nodep) {
if (VL_LIKELY(nodep)) nodep->iterateAndNextConst(*this);
}

View File

@ -1216,6 +1216,7 @@ class V3DfgPeephole final : public DfgVisitor {
DfgVarArray* const varp = vtxp->fromp()->cast<DfgVarArray>();
if (!varp) return;
if (varp->varp()->isForced()) return;
if (varp->varp()->isSigUserRWPublic()) return;
DfgVertex* const srcp = varp->srcp();
if (!srcp) return;

View File

@ -70,7 +70,8 @@ public:
void tmpForp(AstNode* nodep) { m_tmpForp = nodep; }
bool isDrivenFullyByDfg() const {
return srcp() && !srcp()->is<DfgVertexSplice>() && !varp()->isForced();
return srcp() && !srcp()->is<DfgVertexSplice>() && !varp()->isForced()
&& !varp()->isSigUserRWPublic();
}
// Variable referenced via an AstVarXRef (hierarchical reference)

View File

@ -250,9 +250,9 @@ class InlineRelinkVisitor final : public VNVisitor {
// STATE
std::unordered_set<std::string> m_renamedInterfaces; // Name of renamed interface variables
AstNodeModule* const m_modp; // Current module
const AstCell* const m_cellp; // Cell being cloned
bool m_initialStatic = false; // Inside InitialStatic
AstNodeModule* const m_modp; // The module we are inlining into
const AstCell* const m_cellp; // The cell being inlined
size_t m_nPlaceholders = 0; // Unique identifier sequence number for placeholder variables
// VISITORS
void visit(AstCellInline* nodep) override {
@ -279,66 +279,6 @@ class InlineRelinkVisitor final : public VNVisitor {
iterateChildren(nodep);
}
void visit(AstVar* nodep) override {
if (nodep->user2p()) {
// Make an assignment, so we'll trace it properly
// user2p is either a const or a var.
FileLine* const flp = nodep->fileline();
AstConst* const exprconstp = VN_CAST(nodep->user2p(), Const);
AstVarRef* exprvarrefp = VN_CAST(nodep->user2p(), VarRef);
UINFO(8, "connectto: " << nodep->user2p());
UASSERT_OBJ(exprconstp || exprvarrefp, nodep,
"Unknown interconnect type; pinReconnectSimple should have cleared up");
if (exprconstp) {
m_modp->addStmtsp(new AstAssignW{flp, new AstVarRef{flp, nodep, VAccess::WRITE},
exprconstp->cloneTree(false)});
nodep->user4(true); // Making assignment to it
} else if (nodep->user3()) {
// Public variable at the lower module end - we need to make sure we propagate
// the logic changes up and down; if we aliased, we might
// remove the change detection on the output variable.
UINFO(9, "public pin assign: " << exprvarrefp);
UASSERT_OBJ(!nodep->isNonOutput(), nodep, "Outputs only - inputs use AssignAlias");
m_modp->addStmtsp(new AstAssignW{flp, exprvarrefp->cloneTree(false),
new AstVarRef{flp, nodep, VAccess::READ}});
} else if (nodep->isSigPublic() && VN_IS(nodep->dtypep(), UnpackArrayDType)) {
// Public variable at this end and it is an unpacked array. We need to assign
// instead of aliased, because otherwise it will pass V3Slice and invalid
// code will be emitted.
UINFO(9, "assign to public and unpacked: " << nodep);
exprvarrefp = exprvarrefp->cloneTree(false);
exprvarrefp->access(VAccess::READ);
nodep->user4(true); // Making assignment to it
m_modp->addStmtsp(
new AstAssignW{flp, new AstVarRef{flp, nodep, VAccess::WRITE}, exprvarrefp});
} else if (nodep->isIfaceRef()) {
exprvarrefp = exprvarrefp->cloneTree(false);
exprvarrefp->access(VAccess::READ);
m_modp->addStmtsp(new AstAssignVarScope{
flp, new AstVarRef{flp, nodep, VAccess::WRITE}, exprvarrefp});
FileLine* const flbp = exprvarrefp->varp()->fileline();
flp->modifyStateInherit(flbp);
flbp->modifyStateInherit(flp);
} else {
// Do to inlining child's variable now within the same
// module, so a AstVarRef not AstVarXRef below
exprvarrefp = exprvarrefp->cloneTree(false);
exprvarrefp->access(VAccess::READ);
AstVarRef* const nodeVarRefp = new AstVarRef{flp, nodep, VAccess::WRITE};
if (nodep->isForced() && nodep->direction() == VDirection::INPUT) {
nodep->user4(true); // Making assignment to it
m_modp->addStmtsp(new AstAssignW{flp, nodeVarRefp, exprvarrefp});
} else if (nodep->isForced() && nodep->direction() == VDirection::OUTPUT) {
exprvarrefp->access(VAccess::WRITE);
nodeVarRefp->access(VAccess::READ);
m_modp->addStmtsp(new AstAssignW{flp, exprvarrefp, nodeVarRefp});
} else {
m_modp->addStmtsp(new AstAssignAlias{flp, nodeVarRefp, exprvarrefp});
}
FileLine* const flbp = exprvarrefp->varp()->fileline();
flp->modifyStateInherit(flbp);
flbp->modifyStateInherit(flp);
}
}
// Iterate won't hit AstIfaceRefDType directly as it is no longer underneath the module
if (AstIfaceRefDType* const ifacerefp = VN_CAST(nodep->dtypep(), IfaceRefDType)) {
m_renamedInterfaces.insert(nodep->name());
@ -350,7 +290,7 @@ class InlineRelinkVisitor final : public VNVisitor {
ifacerefp->addNextHere(newdp);
// Relink to point to newly cloned cell
if (newdp->cellp()) {
if (AstCell* const newcellp = VN_CAST(newdp->cellp()->user4p(), Cell)) {
if (AstCell* const newcellp = VN_CAST(newdp->cellp()->user3p(), Cell)) {
newdp->cellp(newcellp);
newdp->cellName(newcellp->name());
// Tag the old ifacerefp to ensure it leaves no stale
@ -377,41 +317,47 @@ class InlineRelinkVisitor final : public VNVisitor {
nodep->name(m_cellp->name() + "__DOT__" + nodep->name());
iterateChildren(nodep);
}
void visit(AstInitialStatic* nodep) override {
VL_RESTORER(m_initialStatic);
m_initialStatic = true;
iterateChildren(nodep);
}
void visit(AstNodeAssign* nodep) override {
if (const AstVarRef* const varrefp = VN_CAST(nodep->lhsp(), VarRef)) {
if (m_initialStatic && varrefp->varp()->user2() && varrefp->varp()->user4()) {
// Initial assignment to i/o we are overriding, can remove
UINFO(9, "Remove InitialStatic " << nodep);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
}
iterateChildren(nodep);
void visit(AstAssignAlias* nodep) override {
// Don't replace port variable in the alias
}
void visit(AstVarRef* nodep) override {
if (nodep->varp()->user2p() // It's being converted to an alias.
&& !nodep->varp()->user3()
// Don't constant propagate aliases (we just made)
&& !VN_IS(nodep->backp(), AssignAlias)
// Forced signals do not use aliases
&& !nodep->varp()->isForced()) {
const AstVar* const varp = nodep->varp();
if (AstConst* const constp = VN_CAST(varp->user2p(), Const)) {
nodep->replaceWith(constp->cloneTree(false));
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;
} else if (const AstVarRef* const vrefp = VN_CAST(varp->user2p(), VarRef)) {
nodep->varp(vrefp->varp());
nodep->classOrPackagep(vrefp->classOrPackagep());
// If the target port is being inlined, replace reference with the
// connected expression (always a Const of a VarRef).
AstNode* const pinExpr = nodep->varp()->user2p();
if (!pinExpr) return;
// If it's a constant, inline it
if (AstConst* const constp = VN_CAST(pinExpr, Const)) {
// You might think we would not try to substitute a constant for
// a written variable, but we might need to do this if for example
// there is an assignment to an input port, and that input port
// is tied to a constant on the cell we are inlining. This does
// generate an ASSIGNIN warning, but that can be downgraded to
// a warning. (Also assigning to an input can has valid uses if
// e.g. done via a hierarchical reference from outside to an input
// unconnected on the instance, so we don't want ASSIGNIN fatal.)
// Same applies when there is a static initialzier for an input.
// To avoid having to special case malformed assignment, or worse
// yet emiting code like 0 = 0, we instead substitute a placeholder
// variable that will later be pruned (it will otherwise be unreferenced).
if (!nodep->access().isReadOnly()) {
AstVar* const varp = nodep->varp();
const std::string name = "__vInlPlaceholder_" + std::to_string(++m_nPlaceholders);
AstVar* const holdep = new AstVar{varp->fileline(), VVarType::VAR, name, varp};
m_modp->addStmtsp(holdep);
AstVarRef* const newp = new AstVarRef{nodep->fileline(), holdep, nodep->access()};
nodep->replaceWith(newp);
} else {
nodep->v3fatalSrc("Null connection?");
nodep->replaceWith(constp->cloneTree(false));
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;
}
// Otherwise it must be a variable reference, retarget this ref
const AstVarRef* const vrefp = VN_AS(pinExpr, VarRef);
nodep->varp(vrefp->varp());
nodep->classOrPackagep(vrefp->classOrPackagep());
}
void visit(AstVarXRef* nodep) override {
// Track what scope it was originally under so V3LinkDot can resolve it
@ -476,175 +422,226 @@ public:
};
//######################################################################
// Inline state, as a visitor of each AstNode
// Module inliner
class InlineVisitor final : public VNVisitor {
// NODE STATE
// Cleared entire netlist
// AstIfaceRefDType::user1() // Whether the cell pointed to by this
// // AstIfaceRefDType has been inlined
// Input:
// AstNodeModule::user1p() // ModuleState instance (via m_moduleState)
// Cleared each cell
// AstVar::user2p() // AstVarRef*/AstConst* Points to signal this
// // is a direct connect to
// AstVar::user3() // bool Don't alias the user2, keep it as signal
// AstVar::user4() // bool Was input, remove InitialStatic Assign
// AstCell::user4 // AstCell* of the created clone
const VNUser4InUse m_inuser4;
namespace ModuleInliner {
ModuleStateUser1Allocator& m_moduleState;
// A port variable in an inlined module can be connected 2 ways.
// Either add a continuous assignment between the pin expression from
// the instance and the port variable, or simply inline the pin expression
// in place of the port variable. We will prefer to do the later whenever
// possible (and sometimes required). When inlining, we need to create an
// alias for the inined variable, in order to resovle hierarchical refrences
// against it later in V3Scope (and also for tracing, which is inserted
//later). Returns ture iff the given port variable should be inlined,
// and false if a continuous assignment should be used.
bool inlinePort(AstVar* nodep) {
// Interface references are always inlined
if (nodep->isIfaceRef()) return true;
// Ref ports must be always inlined
if (nodep->direction() == VDirection::REF) return true;
// Forced signals must not be inlined. The port signal can be
// forced separately from the connected signals.
if (nodep->isForced()) return false;
// STATE
AstNodeModule* m_modp = nullptr; // Current module
VDouble0 m_statCells; // Statistic tracking
// Note: For singls marked 'public' (and not 'public_flat') inlining
// of their contaning modules is disabled so they wont reach here.
// METHODS
void inlineCell(AstCell* nodep) {
UINFO(5, " Inline CELL " << nodep);
// TODO: For now, writable public signals inside the cell cannot be
// eliminated as they are entered into the VerilatedScope, and
// changes would not propagate to it when assigned. (The alias created
// for them ensures they would be read correctly, but would not
// propagate any changes.) This can be removed when the VerialtedScope
// construction in V3EmitCSyms understands aliases.
if (nodep->isSigUserRWPublic()) return false;
const VNUser2InUse user2InUse;
const VNUser3InUse user3InUse;
// Otherwise we can repalce the variable
return true;
}
++m_statCells;
// Connect the given port 'nodep' (being inlined into 'modp') to the given
// expression (from the Cell Pin)
void connectPort(AstNodeModule* modp, AstVar* nodep, AstNodeExpr* pinExprp) {
UINFO(6, "Connecting " << pinExprp);
UINFO(6, " to " << nodep);
// Before cloning simplify pin assignments. Better off before, as if the module has
// multiple instantiations we will save work, and we can't call pinReconnectSimple in this
// loop as it clone()s itself.
for (AstPin* pinp = nodep->pinsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
V3Inst::pinReconnectSimple(pinp, nodep, false);
}
// Decide whether to inline the port variable or use continuous assignments
const bool inlineIt = inlinePort(nodep);
// Is this the last cell referencing this module?
const bool lastCell = --m_moduleState(nodep->modp()).m_cellRefs == 0;
// If we deccided to inline it, record the expression to substitute this variable with
if (inlineIt) nodep->user2p(pinExprp);
// Important: If this is the last cell, then don't clone the instantiated module but
// inline the original directly. While this requires some special casing, doing so
// saves us having to temporarily clone the module for the last cell, which
// significantly reduces Verilator memory usage. This is especially true as often the
// top few levels of the hierarchy are singleton wrapper modules, which we always
// inline. In this case this special casing saves us from having to clone essentially
// the entire netlist, which would in effect double Verilator memory consumption, or
// worse if we put off deleting the inlined modules until the end. Not having to clone
// large trees also improves speed.
AstNodeModule* newmodp = nullptr;
if (!lastCell) {
// Clone original module
newmodp = nodep->modp()->cloneTree(false);
FileLine* const flp = nodep->fileline();
// Helper to creates an AstVarRef reference to the port variable
const auto portRef = [&](VAccess access) { return new AstVarRef{flp, nodep, access}; };
// If the connected expression is a constant, add an assignment to set
// the port variable. The constant can still be inlined, in which case
// this is needed for tracing the inlined port variable.
if (AstConst* const pinp = VN_CAST(pinExprp, Const)) {
modp->addStmtsp(new AstAssignW{flp, portRef(VAccess::WRITE), pinp->cloneTree(false)});
return;
}
// Otherwise it must be a variable reference due to having called pinReconnectSimple
const AstVarRef* const pinRefp = VN_AS(pinExprp, VarRef);
// Helper to create an AstVarRef reference to the pin variable
const auto pinRef = [&](VAccess access) {
AstVarRef* const p = new AstVarRef{pinRefp->fileline(), pinRefp->varp(), access};
p->classOrPackagep(pinRefp->classOrPackagep());
return p;
};
// If it is being inlined, create the alias for it
if (inlineIt) {
UINFO(6, "Inlning port variable: " << nodep);
if (nodep->isIfaceRef()) {
modp->addStmtsp(
new AstAssignVarScope{flp, portRef(VAccess::WRITE), pinRef(VAccess::READ)});
} else {
// For the last cell, reuse the original module
nodep->modp()->unlinkFrBack();
newmodp = nodep->modp();
modp->addStmtsp(
new AstAssignAlias{flp, portRef(VAccess::WRITE), pinRef(VAccess::READ)});
}
// Find cell cross-references
nodep->modp()->foreach([](AstCell* cellp) {
// clonep is nullptr when inlining the last instance, if so the use original node
cellp->user4p(cellp->clonep() ? cellp->clonep() : cellp);
});
// Create data for dotted variable resolution
AstCellInline* const inlinep
= new AstCellInline{nodep->fileline(), nodep->name(), nodep->modp()->origName(),
nodep->modp()->timeunit()};
m_modp->addInlinesp(inlinep); // Must be parsed before any AstCells
// Create assignments to the pins
for (AstPin* pinp = nodep->pinsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
if (!pinp->exprp()) continue;
UINFO(6, " Pin change from " << pinp->modVarp());
AstNode* const connectRefp = pinp->exprp();
UASSERT_OBJ(VN_IS(connectRefp, Const) || VN_IS(connectRefp, VarRef), pinp,
"Unknown interconnect type; pinReconnectSimple should have cleared up");
V3Inst::checkOutputShort(pinp);
// Make new signal; even though we'll optimize the interconnect, we
// need an alias to trace correctly. If tracing is disabled, we'll
// delete it in later optimizations.
AstVar* const pinOldVarp = pinp->modVarp();
AstVar* const pinNewVarp = lastCell ? pinOldVarp : pinOldVarp->clonep();
UASSERT_OBJ(pinNewVarp, pinOldVarp, "Cloning failed");
// Propagate any attributes across the interconnect
pinNewVarp->propagateAttrFrom(pinOldVarp);
if (const AstVarRef* const vrefp = VN_CAST(connectRefp, VarRef)) {
vrefp->varp()->propagateAttrFrom(pinOldVarp);
}
// One to one interconnect won't make a temporary variable.
// This prevents creating a lot of extra wires for clock signals.
// It will become a tracing alias.
UINFO(6, "One-to-one " << connectRefp);
UINFO(6, " -to " << pinNewVarp);
pinNewVarp->user2p(connectRefp);
// Public output inside the cell must go via an assign rather
// than alias. Else the public logic will set the alias, losing
// the value to be propagated up (InOnly isn't a problem as the
// AssignAlias will create the assignment for us)
pinNewVarp->user3(pinNewVarp->isSigUserRWPublic()
&& pinNewVarp->direction() == VDirection::OUTPUT);
}
// Cleanup var names, etc, to not conflict
{ InlineRelinkVisitor{newmodp, m_modp, nodep}; }
// Move statements under the module we are inlining into
if (AstNode* const stmtsp = newmodp->stmtsp()) {
stmtsp->unlinkFrBackWithNext();
m_modp->addStmtsp(stmtsp);
}
// Clear any leftover ports, etc
VL_DO_DANGLING(newmodp->deleteTree(), newmodp);
// Remove the cell we just inlined
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
// They will become the same variable, so propagate file-line and variable attributes
pinRefp->varp()->fileline()->modifyStateInherit(flp);
flp->modifyStateInherit(pinRefp->varp()->fileline());
pinRefp->varp()->propagateAttrFrom(nodep);
nodep->propagateAttrFrom(pinRefp->varp());
return;
}
// VISITORS
void visit(AstNetlist* nodep) override {
// Iterate modules backwards, in bottom-up order. Required!
iterateAndNextConstNullBackwards(nodep->modulesp());
// Clean up AstIfaceRefDType references
iterateChildren(nodep->typeTablep());
// Otherwise create the continuous assignment between the port var and the pin expression
UINFO(6, "Not inlning port variable: " << nodep);
if (nodep->direction() == VDirection::INPUT) {
modp->addStmtsp(new AstAssignW{flp, portRef(VAccess::WRITE), pinRef(VAccess::READ)});
} else if (nodep->direction() == VDirection::OUTPUT) {
modp->addStmtsp(new AstAssignW{flp, pinRef(VAccess::WRITE), portRef(VAccess::READ)});
} else {
pinExprp->v3fatalSrc("V3Tristate left INOUT port"); // LCOV_EXCL_LINE
}
void visit(AstNodeModule* nodep) override {
UASSERT_OBJ(!m_modp, nodep, "Unsupported: Nested modules");
VL_RESTORER(m_modp);
m_modp = nodep;
// Iterate the stored cells directly to reduce traversal
for (AstCell* const cellp : m_moduleState(nodep).m_childCells) {
if (m_moduleState(cellp->modp()).m_inlined) inlineCell(cellp);
}
m_moduleState(nodep).m_childCells.clear();
}
// Inline 'cellp' into 'modp'. 'last' indicatest this is tha last instance of the inlined module
void inlineCell(AstNodeModule* modp, AstCell* cellp, bool last) {
UINFO(5, " Inline Cell " << cellp);
UINFO(5, " into Module " << modp);
const VNUser2InUse user2InUse;
// Important: If this is the last cell, then don't clone the instantiated module but
// inline the original directly. While this requires some special casing, doing so
// saves us having to temporarily clone the module for the last cell, which
// significantly reduces Verilator memory usage. This is especially true as often the
// top few levels of the hierarchy are singleton wrapper modules, which we always
// inline. In this case this special casing saves us from having to clone essentially
// the entire netlist, which would in effect double Verilator memory consumption, or
// worse if we put off deleting the inlined modules until the end. Not having to clone
// large trees also improves speed.
// The module we will yank the contents out of and put into 'modp'
AstNodeModule* const inlinedp = last ? cellp->modp()->unlinkFrBack() //
: cellp->modp()->cloneTree(false);
// Compute map from original port variables and cells to their clones
std::unordered_map<const AstVar*, AstVar*> modVar2Clone;
for (AstNode *ap = cellp->modp()->stmtsp(), *bp = inlinedp->stmtsp(); ap || bp;
ap = ap->nextp(), bp = bp->nextp()) {
UASSERT_OBJ(ap && bp, ap ? ap : bp, "Clone has different number of children");
// We only care about AstVar and AstCell, but faster to just set them all
ap->user3p(bp);
}
void visit(AstIfaceRefDType* nodep) override {
if (nodep->user1()) {
// The cell has been removed so let's make sure we don't leave a reference to it
// This dtype may still be in use by the AstAssignVarScope created earlier
// but that'll get cleared up later
nodep->cellp(nullptr);
// Create data for resolving hierarchical references later.
modp->addInlinesp(new AstCellInline{cellp->fileline(), cellp->name(),
cellp->modp()->origName(), cellp->modp()->timeunit()});
// Connect the pins on the instance
for (AstPin* pinp = cellp->pinsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
if (!pinp->exprp()) continue;
UINFO(6, "Conecting port " << pinp->modVarp());
UINFO(6, " of instance " << cellp);
// Make sure the conneccted pin expression is always a VarRef or a Const
V3Inst::pinReconnectSimple(pinp, cellp, false);
// Warn
V3Inst::checkOutputShort(pinp);
// Pick up the old and new port variables signal (new is the same on last instance)
const AstVar* const oldModVarp = pinp->modVarp();
AstVar* const newModVarp = VN_AS(oldModVarp->user3p(), Var);
// Pick up the connected expression (a VarRef or Const due to pinReconnectSimple)
AstNodeExpr* const pinExprp = VN_AS(pinp->exprp(), NodeExpr);
// Connect up the port
connectPort(modp, newModVarp, pinExprp);
}
// Cleanup var names, etc, to not conflict, relink replaced variables
{ InlineRelinkVisitor{inlinedp, modp, cellp}; }
// Move statements from the inlined module into the module we are inlining into
if (AstNode* const stmtsp = inlinedp->stmtsp()) {
modp->addStmtsp(stmtsp->unlinkFrBackWithNext());
}
// Delete the empty shell of the inlined module
VL_DO_DANGLING(inlinedp->deleteTree(), inlinedp);
// Remove the cell we just inlined
VL_DO_DANGLING(cellp->unlinkFrBack(), cellp);
}
// Apply all inlining decisions
void process(AstNetlist* netlistp, ModuleStateUser1Allocator& moduleStates) {
// NODE STATE
// Input:
// AstNodeModule::user1p() // ModuleState instance (via moduleState)
//
// Cleared entire netlist
// AstIfaceRefDType::user1() // Whether the cell pointed to by this
// // AstIfaceRefDType has been inlined
// AstCell::user3p() // AstCell*, the clone
// AstVar::user3p() // AstVar*, the clone clone
// Cleared each cell
// AstVar::user2p() // AstVarRef*/AstConst* This port is connected to (AstPin::expr())
const VNUser3InUse m_user3InUse;
// Number of inlined instances, for statistics
VDouble0 m_nInlined;
// We want to inline bottom up. The modules under the netlist are in
// dependency order (top first, leaves last), so find the end of the list.
AstNode* nodep = netlistp->modulesp();
while (nodep->nextp()) nodep = nodep->nextp();
// Iterate module list backwards (stop when we get back to the Netlist)
while (AstNodeModule* const modp = VN_CAST(nodep, NodeModule)) {
nodep = nodep->backp();
// Consider each cell inside the current module for inling
for (AstCell* const cellp : moduleStates(modp).m_childCells) {
ModuleState& childState = moduleStates(cellp->modp());
if (!childState.m_inlined) continue;
++m_nInlined;
inlineCell(modp, cellp, --childState.m_cellRefs == 0);
}
}
//--------------------
void visit(AstCell* nodep) override { // LCOV_EXCL_START
nodep->v3fatalSrc("Traversal should have been short circuited");
}
void visit(AstNodeStmt* nodep) override {
nodep->v3fatalSrc("Traversal should have been short circuited");
} // LCOV_EXCL_STOP
void visit(AstNodeFile*) override {} // Accelerate
void visit(AstNodeDType*) override {} // Accelerate
void visit(AstNode* nodep) override { iterateChildren(nodep); }
V3Stats::addStat("Optimizations, Inlined instances", m_nInlined);
public:
// CONSTRUCTORS
explicit InlineVisitor(AstNode* nodep, ModuleStateUser1Allocator& moduleState)
: m_moduleState{moduleState} {
iterate(nodep);
}
~InlineVisitor() override {
V3Stats::addStat("Optimizations, Inlined instances", m_statCells);
}
};
// Clean up AstIfaceRefDType references
// If the cell has been removed let's make sure we don't leave a
// reference to it. This dtype may still be in use by the
// AstAssignVarScope created earlier but that'll get cleared up later
netlistp->typeTablep()->foreach([](AstIfaceRefDType* nodep) {
if (nodep->user1()) nodep->cellp(nullptr);
});
}
} //namespace ModuleInliner
//######################################################################
// Inline class functions
// V3Inline class functions
void V3Inline::inlineAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
@ -656,7 +653,8 @@ void V3Inline::inlineAll(AstNetlist* nodep) {
// Scoped to clean up temp userN's
{ InlineMarkVisitor{nodep, moduleState}; }
{ InlineVisitor{nodep, moduleState}; }
// Inline the modles we decided to inline
ModuleInliner::process(nodep, moduleState);
// Check inlined modules have been removed during traversal. Otherwise we might have blown
// up Verilator memory consumption.

View File

@ -694,7 +694,7 @@ AstAssignW* V3Inst::pinReconnectSimple(AstPin* pinp, AstCell* cellp, bool forTri
return InstStatic::pinReconnectSimple(pinp, cellp, forTristate, alwaysCvt);
}
void V3Inst::checkOutputShort(AstPin* nodep) {
void V3Inst::checkOutputShort(const AstPin* nodep) {
if (nodep->modVarp()->direction() == VDirection::OUTPUT) {
if (VN_IS(nodep->exprp(), Const) || VN_IS(nodep->exprp(), Extend)
|| (VN_IS(nodep->exprp(), Concat)

View File

@ -33,7 +33,7 @@ public:
static void dearrayAll(AstNetlist* nodep) VL_MT_DISABLED;
static AstAssignW* pinReconnectSimple(AstPin* pinp, AstCell* cellp, bool forTristate,
bool alwaysCvt = false) VL_MT_DISABLED;
static void checkOutputShort(AstPin* nodep) VL_MT_DISABLED;
static void checkOutputShort(const AstPin* nodep) VL_MT_DISABLED;
};
#endif // Guard

View File

@ -743,7 +743,7 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
ss << "@(";
V3EmitV::verilogForTree(senItemp, ss);
ss << ")";
addDebug(triggerNumber, ss.str());
addDebug(triggerNumber, VString::quoteBackslash(ss.str()));
//
++triggerNumber;

View File

@ -93,7 +93,7 @@ for filename in sorted(files.keys()):
"Use brace instead of parenthesis-style constructors e.g. ': m_...{...}'")
if re.search(r'\.(c|cpp)', filename):
check_pattern(filename, contents, r'(\w+\s+)*(inline)[^\n]*', None,
check_pattern(filename, contents, r'(\w+\s+)*(\binline\b)[^\n]*', None,
"'inline' keyword is on functions defined in .cpp files")
test.passes()

View File

@ -19,25 +19,27 @@
struct MyMon {
uint32_t* sigsp[2];
uint32_t addend = 0;
MyMon() {
sigsp[0] = NULL;
sigsp[1] = NULL;
}
};
MyMon mons[2];
MyMon mons[4];
void mon_register_a(const char* namep, void* sigp, bool isOut) {
void mon_register_a(const char* namep, void* sigp, bool isOut, int n, int addend) {
// Callback from initial block in monitor
#ifdef TEST_VERBOSE
VL_PRINTF("- mon_register_a(\"%s\", %p, %d);\n", namep, sigp, isOut);
VL_PRINTF("- mon_register_a(\"%s\", %p, %d, %d, %d);\n", namep, sigp, isOut, n, addend);
#endif
mons[0].sigsp[isOut] = (uint32_t*)sigp;
mons[n].sigsp[isOut] = (uint32_t*)sigp;
mons[n].addend = addend;
}
void mon_do(MyMon* monp) {
if (!monp->sigsp[0]) vl_fatal(__FILE__, __LINE__, "", "never registered");
if (!monp->sigsp[1]) vl_fatal(__FILE__, __LINE__, "", "never registered");
*monp->sigsp[1] = (*(monp->sigsp[0])) + 1;
*monp->sigsp[1] = (*(monp->sigsp[0])) + monp->addend;
#ifdef TEST_VERBOSE
VL_PRINTF("- mon_do(%08x(&%p) -> %08x(&%p));\n", *(monp->sigsp[0]), monp->sigsp[0],
@ -66,11 +68,10 @@ void mon_scope_name(const char* namep) {
vl_fatal(__FILE__, __LINE__, "", ("Unexp dpiscope name "s + modp).c_str());
}
extern "C" void mon_register_b(const char* namep, int isOut);
void mon_register_b(const char* namep, int isOut) {
extern "C" void mon_register_b(const char* namep, int isOut, int n, int addend) {
const char* modp = svGetNameFromScope(svGetScope());
#ifdef TEST_VERBOSE
VL_PRINTF("- mon_register_b('%s', \"%s\", %d);\n", modp, namep, isOut);
VL_PRINTF("- mon_register_b('%s', \"%s\", %d, %d %d);\n", modp, namep, isOut, n, addend);
#endif
// Use scope to get pointer and size of signal
const VerilatedScope* scopep = Verilated::dpiScope();
@ -82,7 +83,8 @@ void mon_register_b(const char* namep, int isOut) {
} else {
uint32_t* datap = (uint32_t*)(varp->datap());
VL_PRINTF("- mon_register_b('%s', \"%s\", %p, %d);\n", modp, namep, datap, isOut);
mons[1].sigsp[isOut] = (uint32_t*)(varp->datap());
mons[n].sigsp[isOut] = (uint32_t*)(varp->datap());
mons[n].addend = addend;
}
}
@ -106,6 +108,8 @@ void mon_eval() {
// Callback from always@ negedge
mon_do(&mons[0]);
mon_do(&mons[1]);
mon_do(&mons[2]);
mon_do(&mons[3]);
}
//======================================================================

View File

@ -21,19 +21,27 @@ test.compile(
if test.vlt_all:
test.file_grep(
out_filename,
r'{"type":"VAR","name":"formatted",.*"loc":"\w,56:[^"]*",.*"origName":"formatted",.*"direction":"INPUT",.*"dtypeName":"string",.*"attrSFormat":true'
r'{"type":"VAR","name":"formatted",.*"loc":"\w,69:[^"]*",.*"origName":"formatted",.*"direction":"INPUT",.*"dtypeName":"string",.*"attrSFormat":true'
)
test.file_grep(
out_filename,
r'{"type":"VAR","name":"t.sub.in",.*"loc":"\w,77:[^"]*",.*"origName":"in",.*"dtypeName":"int",.*"isSigUserRdPublic":true'
r'{"type":"VAR","name":"t.sub.in",.*"loc":"\w,91:[^"]*",.*"origName":"in",.*"dtypeName":"int",.*"isSigUserRdPublic":true'
)
test.file_grep(
out_filename,
r'{"type":"VAR","name":"t.sub.fr_a",.*"loc":"\w,78:[^"]*",.*"origName":"fr_a",.*"dtypeName":"int",.*"isSigUserRdPublic":true,.*"isSigUserRWPublic":true'
r'{"type":"VAR","name":"t.sub.in_a",.*"loc":"\w,92:[^"]*",.*"origName":"in_a",.*"dtypeName":"int",.*"isSigUserRdPublic":true,.*"isSigUserRWPublic":true'
)
test.file_grep(
out_filename,
r'{"type":"VAR","name":"t.sub.fr_b",.*"loc":"\w,79:[^"]*",.*"origName":"fr_b",.*"dtypeName":"int",.*"isSigUserRdPublic":true,.*"isSigUserRWPublic":true'
r'{"type":"VAR","name":"t.sub.in_b",.*"loc":"\w,93:[^"]*",.*"origName":"in_b",.*"dtypeName":"int",.*"isSigUserRdPublic":true,.*"isSigUserRWPublic":true'
)
test.file_grep(
out_filename,
r'{"type":"VAR","name":"t.sub.fr_a",.*"loc":"\w,94:[^"]*",.*"origName":"fr_a",.*"dtypeName":"int",.*"isSigUserRdPublic":true,.*"isSigUserRWPublic":true'
)
test.file_grep(
out_filename,
r'{"type":"VAR","name":"t.sub.fr_b",.*"loc":"\w,95:[^"]*",.*"origName":"fr_b",.*"dtypeName":"int",.*"isSigUserRdPublic":true,.*"isSigUserRWPublic":true'
)
test.execute()

View File

@ -17,27 +17,40 @@ module t (/*AUTOARG*/
wire monclk = ~clk;
int in;
int in_a;
int in_b;
int fr_a;
int fr_b;
int fr_a2;
int fr_b2;
int fr_chk;
sub sub (.*);
// Test loop
always @ (posedge clk) begin
`ifdef TEST_VERBOSE
$write("[%0t] cyc==%0d in=%x fr_a=%x b=%x fr_chk=%x\n", $time, cyc, in, fr_a, fr_b, fr_chk);
$write("[%0t] cyc==%0d in=%x sub.in_a=%x sub.in_b=%x fr_a=%x fr_b=%x fr_a2=%x fr_b2=%x fr_chk=%x\n",
$time, cyc, in, sub.in_a, sub.in_b, fr_a, fr_b, fr_a2, fr_b2, fr_chk);
`endif
cyc <= cyc + 1;
in <= {in[30:0], in[31]^in[2]^in[0]};
// The inputs to sub will be updated externally on the neg-edge so these
// don't matter for the result
in_a <= in_a + 1;
in_b <= in_b + 1;
if (cyc==0) begin
// Setup
in <= 32'hd70a4497;
in_a <= 0;
in_b <= 0;
end
else if (cyc<3) begin
end
else if (cyc<10) begin
if (fr_chk != fr_a) $stop;
if (fr_chk != fr_b) $stop;
if (fr_chk != fr_a2) $stop;
if (fr_chk != fr_b2) $stop;
end
else if (cyc==10) begin
$write("*-* All Finished *-*\n");
@ -57,33 +70,43 @@ import "DPI-C" context function void mon_scope_name (input string formatted /*ve
`else
import "DPI-C" context function void mon_scope_name (input string formatted);
`endif
import "DPI-C" context function void mon_register_b(string name, int isOut);
import "DPI-C" context function void mon_register_b(string name, int isOut, int n, int addend);
import "DPI-C" context function void mon_register_done();
import "DPI-C" context function void mon_eval();
module sub (/*AUTOARG*/
// Outputs
fr_a, fr_b, fr_chk,
fr_a, fr_b, fr_a2, fr_b2, fr_chk,
// Inputs
in
in, in_a, in_b
);
`systemc_imp_header
void mon_class_name(const char* namep);
void mon_register_a(const char* namep, void* sigp, bool isOut);
void mon_register_a(const char* namep, void* sigp, bool isOut, int n, int addend);
`verilog
/* verilator lint_off ASSIGNIN */
`ifdef ATTRIBUTES
input int in /*verilator public_flat_rd*/;
input int in_a /*verilator public_flat_rw @(posedge t.monclk)*/;
input int in_b /*verilator public_flat_rw @(posedge t.monclk)*/;
output int fr_a /*verilator public_flat_rw @(posedge t.monclk)*/;
output int fr_b /*verilator public_flat_rw @(posedge t.monclk)*/;
`else
input int in;
input int in_a;
input int in_b;
output int fr_a;
output int fr_b;
`endif
output int fr_a2;
output int fr_b2;
output int fr_chk;
/* verilator lint_on ASSIGNIN */
always @* fr_a2 = in_a + 1;
always @* fr_b2 = in_b + 1;
always @* fr_chk = in + 1;
initial begin
@ -91,11 +114,15 @@ module sub (/*AUTOARG*/
$c("mon_class_name(this->name());");
mon_scope_name("%m");
// Scheme A - pass pointer directly
$c("mon_register_a(\"in\", &", in, ", false);");
$c("mon_register_a(\"fr_a\", &", fr_a, ", true);");
$c("mon_register_a(\"in\", &", in, ", false, 0, 1);");
$c("mon_register_a(\"fr_a\", &", fr_a, ", true, 0, 1);");
$c("mon_register_a(\"in\", &", in, ", false, 1, 0);");
$c("mon_register_a(\"in_a\", &", in_a, ", true, 1, 0);");
// Scheme B - use VPIish callbacks to see what signals exist
mon_register_b("in", 0);
mon_register_b("fr_b", 1);
mon_register_b("in", 0, 2, 1);
mon_register_b("fr_b", 1, 2, 1);
mon_register_b("in", 0, 3, 0);
mon_register_b("in_b", 1, 3, 0);
mon_register_done();
end

View File

@ -8,5 +8,7 @@
sformat -task "mon_scope_name" -var "formatted"
public_flat_rd -module "sub" -var "in"
public_flat_rw -module "sub" -var "in_a" @(posedge t.monclk)
public_flat_rw -module "sub" -var "in_b" @(posedge t.monclk)
public_flat_rw -module "sub" -var "fr_a" @(posedge t.monclk)
public_flat_rw -module "sub" -var "fr_b" @(posedge t.monclk)

View File

@ -1,61 +0,0 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2024 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
module sub(
// Outputs
out,
// Inputs
clk
);
// verilator inline_module
output [3:0] out /* <-- this variable has to be marked as having external refs */;
input clk;
reg [3:0] r;
always @ (posedge clk)
r <= 4'h1;
assign out = r;
endmodule
module t(/*AUTOARG*/
// Inputs
clk
);
input clk;
reg [3:0] unused;
sub sub1(unused, clk);
integer cyc = 0;
always @ (posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) begin
`checkh(sub1.r, 4'h1);
`checkh(sub1.out, 4'h1);
end
else if (cyc == 2) begin
force sub1.r = 4'h2;
force sub1.out = 4'h3;
end
else if (cyc == 3) begin
`checkh(sub1.r, 4'h2);
`checkh(sub1.out, 4'h3);
end
//
else if (cyc == 99) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('vlt_all')
test.compile()
test.compile(verilator_flags2=['--binary'])
test.execute()

View File

@ -0,0 +1,75 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2024 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
module sub(
input wire [7:0] i,
output wire [7:0] o
);
// Must inline this module
// verilator inline_module
wire [7:0] m;
assign m = i;
assign o = m;
endmodule
module top;
// Variable input
reg [7:0] i = 8'h01;
reg [7:0] o_v;
sub sub_v(i, o_v);
// Constant input
reg [7:0] o_c;
sub sub_c(8'h10, o_c);
logic clk = 1'b0;
always #1 clk = ~clk;
int cyc = 0;
always @ (posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) begin
`checkh(i, 8'h01);
`checkh(sub_v.i, 8'h01);
`checkh(sub_v.m, 8'h01);
`checkh(sub_v.o, 8'h01);
`checkh(o_v, 8'h01);
`checkh(sub_c.i, 8'h10);
`checkh(sub_c.m, 8'h10);
`checkh(sub_c.o, 8'h10);
`checkh(o_c, 8'h10);
end
else if (cyc == 2) begin
force sub_v.i = 8'h02;
force sub_v.m = 8'h03;
force sub_v.o = 8'h04;
force sub_c.i = 8'h20;
force sub_c.m = 8'h30;
force sub_c.o = 8'h40;
end
else if (cyc == 3) begin
`checkh(i, 8'h01);
`checkh(sub_v.i, 8'h02);
`checkh(sub_v.m, 8'h03);
`checkh(sub_v.o, 8'h04);
`checkh(o_v, 8'h04);
`checkh(sub_c.i, 8'h20);
`checkh(sub_c.m, 8'h30);
`checkh(sub_c.o, 8'h40);
`checkh(o_c, 8'h40);
end
//
else if (cyc == 99) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -42,7 +42,7 @@ if test.vlt_all:
# We expect to combine sequent functions across multiple instances of
# l2, l3, l4, l5. If this number drops, please confirm this has not broken.
test.file_grep(test.stats, r'Optimizations, Combined CFuncs\s+(\d+)',
(107 if test.vltmt else 114))
(99 if test.vltmt else 82))
# Everything should use relative references
check_relative_refs("t", True)

View File

@ -65,26 +65,26 @@
"lhsp": [
{"type":"VARREF","name":"t.d","addr":"(YB)","loc":"d,14:16,14:17","dtypep":"(H)","access":"WR","varp":"(N)","varScopep":"(GB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []},
{"type":"ASSIGNALIAS","name":"","addr":"(ZB)","loc":"d,34:24,34:27","dtypep":"(J)",
{"type":"ASSIGNALIAS","name":"","addr":"(ZB)","loc":"d,36:30,36:31","dtypep":"(H)",
"rhsp": [
{"type":"VARREF","name":"t.clk","addr":"(AC)","loc":"d,21:42,21:45","dtypep":"(J)","access":"RD","varp":"(M)","varScopep":"(FB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"t.between","addr":"(AC)","loc":"d,20:14,20:21","dtypep":"(H)","access":"RD","varp":"(O)","varScopep":"(HB)","classOrPackagep":"UNLINKED"}
],
"lhsp": [
{"type":"VARREF","name":"t.cell1.clk","addr":"(BC)","loc":"d,34:24,34:27","dtypep":"(J)","access":"WR","varp":"(S)","varScopep":"(JB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"t.cell1.q","addr":"(BC)","loc":"d,36:30,36:31","dtypep":"(H)","access":"WR","varp":"(U)","varScopep":"(LB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []},
{"type":"ASSIGNALIAS","name":"","addr":"(CC)","loc":"d,35:30,35:31","dtypep":"(H)",
{"type":"ASSIGNALIAS","name":"","addr":"(CC)","loc":"d,34:24,34:27","dtypep":"(J)",
"rhsp": [
{"type":"VARREF","name":"t.d","addr":"(DC)","loc":"d,22:42,22:43","dtypep":"(H)","access":"RD","varp":"(N)","varScopep":"(GB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"t.clk","addr":"(DC)","loc":"d,21:42,21:45","dtypep":"(J)","access":"RD","varp":"(M)","varScopep":"(FB)","classOrPackagep":"UNLINKED"}
],
"lhsp": [
{"type":"VARREF","name":"t.cell1.d","addr":"(EC)","loc":"d,35:30,35:31","dtypep":"(H)","access":"WR","varp":"(T)","varScopep":"(KB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"t.cell1.clk","addr":"(EC)","loc":"d,34:24,34:27","dtypep":"(J)","access":"WR","varp":"(S)","varScopep":"(JB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []},
{"type":"ASSIGNALIAS","name":"","addr":"(FC)","loc":"d,36:30,36:31","dtypep":"(H)",
{"type":"ASSIGNALIAS","name":"","addr":"(FC)","loc":"d,35:30,35:31","dtypep":"(H)",
"rhsp": [
{"type":"VARREF","name":"t.between","addr":"(GC)","loc":"d,20:14,20:21","dtypep":"(H)","access":"RD","varp":"(O)","varScopep":"(HB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"t.d","addr":"(GC)","loc":"d,22:42,22:43","dtypep":"(H)","access":"RD","varp":"(N)","varScopep":"(GB)","classOrPackagep":"UNLINKED"}
],
"lhsp": [
{"type":"VARREF","name":"t.cell1.q","addr":"(HC)","loc":"d,36:30,36:31","dtypep":"(H)","access":"WR","varp":"(U)","varScopep":"(LB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"t.cell1.d","addr":"(HC)","loc":"d,35:30,35:31","dtypep":"(H)","access":"WR","varp":"(T)","varScopep":"(KB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []},
{"type":"ALWAYS","name":"","addr":"(IC)","loc":"d,41:4,41:10","keyword":"always","isSuspendable":false,"needProcess":false,
"sentreep": [
@ -105,26 +105,26 @@
{"type":"VARREF","name":"t.between","addr":"(OC)","loc":"d,42:6,42:7","dtypep":"(H)","access":"WR","varp":"(O)","varScopep":"(HB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []}
]},
{"type":"ASSIGNALIAS","name":"","addr":"(PC)","loc":"d,48:10,48:13","dtypep":"(J)",
{"type":"ASSIGNALIAS","name":"","addr":"(PC)","loc":"d,49:16,49:17","dtypep":"(H)",
"rhsp": [
{"type":"VARREF","name":"t.clk","addr":"(QC)","loc":"d,27:42,27:45","dtypep":"(J)","access":"RD","varp":"(M)","varScopep":"(FB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"t.between","addr":"(QC)","loc":"d,25:16,25:23","dtypep":"(H)","access":"RD","varp":"(O)","varScopep":"(HB)","classOrPackagep":"UNLINKED"}
],
"lhsp": [
{"type":"VARREF","name":"t.cell2.clk","addr":"(RC)","loc":"d,48:10,48:13","dtypep":"(J)","access":"WR","varp":"(X)","varScopep":"(NB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"t.cell2.d","addr":"(RC)","loc":"d,49:16,49:17","dtypep":"(H)","access":"WR","varp":"(Y)","varScopep":"(OB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []},
{"type":"ASSIGNALIAS","name":"","addr":"(SC)","loc":"d,49:16,49:17","dtypep":"(H)",
{"type":"ASSIGNALIAS","name":"","addr":"(SC)","loc":"d,50:22,50:23","dtypep":"(H)",
"rhsp": [
{"type":"VARREF","name":"t.between","addr":"(TC)","loc":"d,25:16,25:23","dtypep":"(H)","access":"RD","varp":"(O)","varScopep":"(HB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"t.q","addr":"(TC)","loc":"d,26:42,26:43","dtypep":"(H)","access":"RD","varp":"(L)","varScopep":"(EB)","classOrPackagep":"UNLINKED"}
],
"lhsp": [
{"type":"VARREF","name":"t.cell2.d","addr":"(UC)","loc":"d,49:16,49:17","dtypep":"(H)","access":"WR","varp":"(Y)","varScopep":"(OB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"t.cell2.q","addr":"(UC)","loc":"d,50:22,50:23","dtypep":"(H)","access":"WR","varp":"(Z)","varScopep":"(PB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []},
{"type":"ASSIGNALIAS","name":"","addr":"(VC)","loc":"d,50:22,50:23","dtypep":"(H)",
{"type":"ASSIGNALIAS","name":"","addr":"(VC)","loc":"d,48:10,48:13","dtypep":"(J)",
"rhsp": [
{"type":"VARREF","name":"t.q","addr":"(WC)","loc":"d,26:42,26:43","dtypep":"(H)","access":"RD","varp":"(L)","varScopep":"(EB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"t.clk","addr":"(WC)","loc":"d,27:42,27:45","dtypep":"(J)","access":"RD","varp":"(M)","varScopep":"(FB)","classOrPackagep":"UNLINKED"}
],
"lhsp": [
{"type":"VARREF","name":"t.cell2.q","addr":"(XC)","loc":"d,50:22,50:23","dtypep":"(H)","access":"WR","varp":"(Z)","varScopep":"(PB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"t.cell2.clk","addr":"(XC)","loc":"d,48:10,48:13","dtypep":"(J)","access":"WR","varp":"(X)","varScopep":"(NB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []},
{"type":"ASSIGNW","name":"","addr":"(YC)","loc":"d,53:13,53:14","dtypep":"(H)",
"rhsp": [

View File

@ -12,6 +12,8 @@ import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_var_in_assign_bad.v"
test.lint(verilator_flags2=['-Wpedantic -Wno-fatal'])
# Although this is mostly a lint test, do 'compile' to make sure we do not
# generate thrash code in the presence of a warning that is not fatal
test.compile(verilator_flags2=['-Wpedantic -Wno-fatal --flatten -fno-gate'])
test.passes()

View File

@ -63,6 +63,10 @@
<varref loc="d,14,16,14,17" name="d" dtype_id="1"/>
<varref loc="d,14,16,14,17" name="t.d" dtype_id="1"/>
</assignalias>
<assignalias loc="d,36,30,36,31" dtype_id="1">
<varref loc="d,20,14,20,21" name="t.between" dtype_id="1"/>
<varref loc="d,36,30,36,31" name="t.cell1.q" dtype_id="1"/>
</assignalias>
<assignalias loc="d,34,24,34,27" dtype_id="2">
<varref loc="d,21,42,21,45" name="t.clk" dtype_id="2"/>
<varref loc="d,34,24,34,27" name="t.cell1.clk" dtype_id="2"/>
@ -71,10 +75,6 @@
<varref loc="d,22,42,22,43" name="t.d" dtype_id="1"/>
<varref loc="d,35,30,35,31" name="t.cell1.d" dtype_id="1"/>
</assignalias>
<assignalias loc="d,36,30,36,31" dtype_id="1">
<varref loc="d,20,14,20,21" name="t.between" dtype_id="1"/>
<varref loc="d,36,30,36,31" name="t.cell1.q" dtype_id="1"/>
</assignalias>
<always loc="d,41,4,41,10">
<sentree loc="d,41,11,41,12">
<senitem loc="d,41,13,41,20" edgeType="POS">
@ -86,10 +86,6 @@
<varref loc="d,42,6,42,7" name="t.between" dtype_id="1"/>
</assigndly>
</always>
<assignalias loc="d,48,10,48,13" dtype_id="2">
<varref loc="d,27,42,27,45" name="t.clk" dtype_id="2"/>
<varref loc="d,48,10,48,13" name="t.cell2.clk" dtype_id="2"/>
</assignalias>
<assignalias loc="d,49,16,49,17" dtype_id="1">
<varref loc="d,25,16,25,23" name="t.between" dtype_id="1"/>
<varref loc="d,49,16,49,17" name="t.cell2.d" dtype_id="1"/>
@ -98,6 +94,10 @@
<varref loc="d,26,42,26,43" name="t.q" dtype_id="1"/>
<varref loc="d,50,22,50,23" name="t.cell2.q" dtype_id="1"/>
</assignalias>
<assignalias loc="d,48,10,48,13" dtype_id="2">
<varref loc="d,27,42,27,45" name="t.clk" dtype_id="2"/>
<varref loc="d,48,10,48,13" name="t.cell2.clk" dtype_id="2"/>
</assignalias>
<contassign loc="d,53,13,53,14" dtype_id="1">
<varref loc="d,17,22,17,29" name="t.between" dtype_id="1"/>
<varref loc="d,53,13,53,14" name="q" dtype_id="1"/>