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:
parent
f506aa878b
commit
1c86ff0af2
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
484
src/V3Inline.cpp
484
src/V3Inline.cpp
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('vlt_all')
|
||||
|
||||
test.compile()
|
||||
test.compile(verilator_flags2=['--binary'])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
Loading…
Reference in New Issue