Refactor V3Delayed to enable algorithmic extension (#5090)
No functional change. This patch is just cleanup with some non-functional changes to enable the next patch. Most importantly createDlyOnSet, which implements NBAs for arrays, has a new streamlined implementation that does the same thing. Some output code is perturbed due to statement/local variable insertion order. Also renamed Vdlyvfoo to VdlyFoo for easier readability of the generated code.
This commit is contained in:
parent
b5b937e2f2
commit
d841a791e6
|
|
@ -2434,13 +2434,11 @@ public:
|
|||
ASTGEN_MEMBERS_AstAlwaysObserved;
|
||||
};
|
||||
class AstAlwaysPost final : public AstNodeProcedure {
|
||||
// Like always but post assignments for memory assignment IFs
|
||||
// @astgen op1 := sensesp : Optional[AstSenTree] // Sensitivity list iff clocked
|
||||
// Like always but 'post' scheduled, e.g. for array NBA commits
|
||||
|
||||
public:
|
||||
AstAlwaysPost(FileLine* fl, AstSenTree* sensesp, AstNode* stmtsp)
|
||||
: ASTGEN_SUPER_AlwaysPost(fl, stmtsp) {
|
||||
this->sensesp(sensesp);
|
||||
}
|
||||
AstAlwaysPost(FileLine* fl)
|
||||
: ASTGEN_SUPER_AlwaysPost(fl, nullptr) {}
|
||||
ASTGEN_MEMBERS_AstAlwaysPost;
|
||||
};
|
||||
class AstAlwaysPostponed final : public AstNodeProcedure {
|
||||
|
|
|
|||
|
|
@ -15,36 +15,34 @@
|
|||
//*************************************************************************
|
||||
// V3Delayed's Transformations:
|
||||
//
|
||||
// Each module:
|
||||
// Replace ASSIGNDLY var, exp
|
||||
// With ASSIGNDLY newvar, exp
|
||||
// At top of block: VAR newvar
|
||||
// At bottom of block: ASSIGNW var newvar
|
||||
// Need _x_dly = x at top of active if "x" is not always set
|
||||
// For now we'll say it's set if at top of block (not under IF, etc)
|
||||
// Need x = _x_dly at bottom of active if "x" is never referenced on LHS
|
||||
// in the active, and above rule applies too.
|
||||
// (If so, use x on LHS, not _x_dly.)
|
||||
// Convert AstAssignDly into temporaries an specially scheduled blocks.
|
||||
// For the Pre/Post scheduling semantics, see V3OrderGraph.
|
||||
//
|
||||
// If a signal is set in multiple always blocks, we need a dly read & set with
|
||||
// multiple clock sensitivities. We have 3 options:
|
||||
// 1. When detected, make a new ACTIVE and move earlier created delayed assignment there
|
||||
// 2. Form unique ACTIVE for every multiple clocked assignment
|
||||
// 3. Predetect signals from multiple always blocks and do #2 on them
|
||||
// Since all 3 require a top activation cleanup, we do #2 which is easiest.
|
||||
// A non-array LHS, outside suspendable processes and forks, e.g.::
|
||||
// a <= RHS;
|
||||
// is converted as follows:
|
||||
// - Add new "Pre-scheduled" logic:
|
||||
// __Vdly__a = a;
|
||||
// - In the original logic, replace VarRefs on LHS with __Vdly__ variables:
|
||||
// __Vdly__a = RHS;
|
||||
// - Add new "Post-scheduled" logic:
|
||||
// a = __Vdly__a;
|
||||
//
|
||||
// ASSIGNDLY (BITSEL(ARRAYSEL (VARREF(v), bits), selbits), rhs)
|
||||
// -> VAR __Vdlyvset_x
|
||||
// VAR __Vdlyvval_x
|
||||
// VAR __Vdlyvdim_x
|
||||
// VAR __Vdlyvlsb_x
|
||||
// ASSIGNW (__Vdlyvset_x,0)
|
||||
// ...
|
||||
// ASSIGNW (VARREF(__Vdlyvval_x), rhs)
|
||||
// ASSIGNW (__Vdlyvdim_x, dimension_number)
|
||||
// ASSIGNW (__Vdlyvset_x, 1)
|
||||
// ...
|
||||
// ASSIGNW (BITSEL(ARRAYSEL(VARREF(x), __Vdlyvdim_x), __Vdlyvlsb_x), __Vdlyvval_x)
|
||||
// An array LHS:
|
||||
// a[idxa][idxb] <= RHS
|
||||
// is converted:
|
||||
// - Add new "Pre_scheduled" logic:
|
||||
// __VdlySet__a = 0;
|
||||
// - In the original logic, replace the AstAssignDelay with:
|
||||
// __VdlySet__a = 1;
|
||||
// __VdlyDim0__a = idxa;
|
||||
// __VdlyDim1__a = idxb;
|
||||
// __VdlyVal__a = RHS;
|
||||
// - Add new "Post-scheduled" logic:
|
||||
// if (__VdlySet__a) a[__VdlyDim0__a][__VdlyDim1__a] = __VdlyVal__a;
|
||||
//
|
||||
// Any AstAssignDly in a suspendable process or fork also uses the
|
||||
// '__VdlySet' flag based scheme, like arrays, with some modifications.
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
|
|
@ -53,6 +51,7 @@
|
|||
#include "V3Delayed.h"
|
||||
|
||||
#include "V3AstUserAllocator.h"
|
||||
#include "V3Const.h"
|
||||
#include "V3Stats.h"
|
||||
|
||||
#include <deque>
|
||||
|
|
@ -63,41 +62,50 @@ VL_DEFINE_DEBUG_FUNCTIONS;
|
|||
// Delayed state, as a visitor of each AstNode
|
||||
|
||||
class DelayedVisitor final : public VNVisitor {
|
||||
// TYPES
|
||||
|
||||
// Components of an AstAssignDly LHS expression, e.g.:
|
||||
// Sel(ArraySel(ArraySel(_: VarRef, _: Index), _: Index), _: Lsb, _: Width))
|
||||
struct LhsComponents final {
|
||||
AstVarRef* refp = nullptr; // The referenced variable
|
||||
std::vector<AstNodeExpr*> arrIdxps; // The array indices applied to 'refp'
|
||||
AstNodeExpr* selLsbp = nullptr; // The bit select LSB expression, after the array selects
|
||||
AstConst* selWidthp = nullptr; // The width of the bit select
|
||||
};
|
||||
|
||||
// NODE STATE
|
||||
// Cleared each module:
|
||||
// AstVarScope::user1p() -> aux
|
||||
// AstVar::user2() -> bool. Set true if already made warning
|
||||
// AstVarRef::user1() -> bool. Set true if was blocking reference
|
||||
// AstVarRef::user2() -> bool. Set true if already processed
|
||||
// AstAlwaysPost::user1() -> AstIf*. Last IF (__Vdlyvset__) created under this AlwaysPost
|
||||
// AstAlwaysPost::user2() -> ActActive*. Points to activity block of signal
|
||||
// (valid when AstAlwaysPost::user4p is valid)
|
||||
// AstVar::user1() -> bool. Set true if already made warning
|
||||
// AstVarRef::user1() -> bool. Set true if is a non-blocking reference
|
||||
// AstAlwaysPost::user1() -> AstIf*. Last IF (__VdlySet__) created under this AlwaysPost
|
||||
//
|
||||
// Cleared each scope/active:
|
||||
// AstAssignDly::user3() -> AstVarScope*. __Vdlyvset__ created for this assign
|
||||
// AstAlwaysPost::user3() -> AstVarScope*. __Vdlyvset__ last referenced in IF
|
||||
// AstVarRef::user2() -> bool. Set true if already processed
|
||||
// AstAssignDly::user2() -> AstVarScope*. __VdlySet__ created for this assign
|
||||
// AstAlwaysPost::user2() -> AstVarScope*. __VdlySet__ last referenced in IF
|
||||
// AstNodeProcedure::user2() -> AstAcive*. Active block used by this suspendable process
|
||||
const VNUser1InUse m_inuser1;
|
||||
const VNUser2InUse m_inuser2;
|
||||
const VNUser3InUse m_inuser3;
|
||||
|
||||
struct AuxAstVarScope final {
|
||||
// Points to temp (shadow) variable created.
|
||||
AstVarScope* delayVscp = nullptr;
|
||||
// Points to activity block of signal (valid when AstVarScope::user1p is valid)
|
||||
// Points to AstActive block of the shadow variable 'delayVscp/post block 'postp'
|
||||
AstActive* activep = nullptr;
|
||||
// Post block for this variable used for AssignDlys in suspendable processes
|
||||
AstAlwaysPost* suspFinalp = nullptr;
|
||||
// Post block for this variable used in suspendable processes
|
||||
AstAlwaysPost* suspPostp = nullptr;
|
||||
// Post block for this variable
|
||||
AstAlwaysPost* finalp = nullptr;
|
||||
// Last blocking or non-blocking reference
|
||||
AstNodeVarRef* lastRefp = nullptr;
|
||||
AstAlwaysPost* postp = nullptr;
|
||||
// First reference encountered to the VarScope
|
||||
const AstNodeVarRef* firstRefp = nullptr;
|
||||
};
|
||||
AstUser1Allocator<AstVarScope, AuxAstVarScope> m_vscpAux;
|
||||
|
||||
// STATE - across all visitors
|
||||
std::unordered_map<const AstVarScope*, int> m_scopeVecMap; // Next var number for each scope
|
||||
std::set<AstSenTree*> m_timingDomains; // Timing resume domains
|
||||
using VarMap = std::map<const std::pair<AstNodeModule*, std::string>, AstVar*>;
|
||||
VarMap m_modVarMap; // Table of new var names created under module
|
||||
// Table of new var names created under module
|
||||
std::map<const std::pair<AstNodeModule*, std::string>, AstVar*> m_modVarMap;
|
||||
VDouble0 m_statSharedSet; // Statistic tracking
|
||||
|
||||
// STATE - for current visit position (use VL_RESTORER)
|
||||
|
|
@ -105,9 +113,8 @@ class DelayedVisitor final : public VNVisitor {
|
|||
const AstCFunc* m_cfuncp = nullptr; // Current public C Function
|
||||
AstAssignDly* m_nextDlyp = nullptr; // Next delayed assignment in a list of assignments
|
||||
AstNodeProcedure* m_procp = nullptr; // Current process
|
||||
bool m_inDly = false; // True in delayed assignments
|
||||
bool m_inDlyLhs = false; // True in lhs of AstAssignDelay
|
||||
bool m_inLoop = false; // True in for loops
|
||||
bool m_inInitial = false; // True in static initializers and initial blocks
|
||||
bool m_inSuspendableOrFork = false; // True in suspendable processes and forks
|
||||
bool m_ignoreBlkAndNBlk = false; // Suppress delayed assignment BLKANDNBLK
|
||||
|
||||
|
|
@ -118,25 +125,34 @@ class DelayedVisitor final : public VNVisitor {
|
|||
return nodep;
|
||||
}
|
||||
|
||||
void markVarUsage(AstNodeVarRef* nodep, bool blocking) {
|
||||
// Record and warn if a variable is assigned by both blocking and nonblocking assignments
|
||||
void markVarUsage(AstNodeVarRef* nodep, bool nonBlocking) {
|
||||
// Ignore references in certain contexts
|
||||
if (m_ignoreBlkAndNBlk) return;
|
||||
// Ignore if warning is disabled on this reference (used by V3Force).
|
||||
if (nodep->fileline()->warnIsOff(V3ErrorCode::BLKANDNBLK)) return;
|
||||
if (m_ignoreBlkAndNBlk) return;
|
||||
if (blocking) nodep->user1(true);
|
||||
|
||||
// Mark ref as blocking/non-blocking
|
||||
nodep->user1(nonBlocking);
|
||||
|
||||
AstVarScope* const vscp = nodep->varScopep();
|
||||
// UINFO(4, " MVU " << blocking << " " << nodep << endl);
|
||||
const AstNode* const lastrefp = m_vscpAux(vscp).lastRefp;
|
||||
if (!lastrefp) {
|
||||
m_vscpAux(vscp).lastRefp = nodep;
|
||||
} else {
|
||||
const bool last_was_blocking = lastrefp->user1();
|
||||
if (last_was_blocking != blocking) {
|
||||
const AstNode* nonblockingp = blocking ? nodep : lastrefp;
|
||||
|
||||
// Pick up/set the first reference to this variable
|
||||
const AstNodeVarRef* const firstRefp = m_vscpAux(vscp).firstRefp;
|
||||
if (!firstRefp) {
|
||||
m_vscpAux(vscp).firstRefp = nodep;
|
||||
return;
|
||||
}
|
||||
|
||||
// If both blocking/non-blocking, it's OK
|
||||
if (firstRefp->user1() == nonBlocking) return;
|
||||
|
||||
// Otherwise warn that both blocking and non-blocking assignments are used
|
||||
const AstNode* nonblockingp = nonBlocking ? nodep : firstRefp;
|
||||
if (const AstNode* np = containingAssignment(nonblockingp)) nonblockingp = np;
|
||||
const AstNode* blockingp = blocking ? lastrefp : nodep;
|
||||
const AstNode* blockingp = nonBlocking ? firstRefp : nodep;
|
||||
if (const AstNode* np = containingAssignment(blockingp)) blockingp = np;
|
||||
vscp->v3warn(
|
||||
BLKANDNBLK,
|
||||
vscp->v3warn(BLKANDNBLK,
|
||||
"Unsupported: Blocked and non-blocking assignments to same variable: "
|
||||
<< vscp->varp()->prettyNameQ() << '\n'
|
||||
<< vscp->warnContextPrimary() << '\n'
|
||||
|
|
@ -145,280 +161,261 @@ class DelayedVisitor final : public VNVisitor {
|
|||
<< nonblockingp->warnOther() << "... Location of nonblocking assignment\n"
|
||||
<< nonblockingp->warnContextSecondary());
|
||||
}
|
||||
}
|
||||
}
|
||||
AstVarScope* createVarSc(AstVarScope* oldvarscp, const string& name,
|
||||
int width /*0==fromoldvar*/, AstNodeDType* newdtypep) {
|
||||
// Because we've already scoped it, we may need to add both the AstVar and the AstVarScope
|
||||
UASSERT_OBJ(oldvarscp->scopep(), oldvarscp, "Var unscoped");
|
||||
FileLine* const flp = oldvarscp->fileline();
|
||||
AstNodeModule* const addmodp = oldvarscp->scopep()->modp();
|
||||
// We need a new AstVar, but only one for all scopes, to match the new AstVarScope
|
||||
const auto pair = m_modVarMap.emplace(std::make_pair(addmodp, name), nullptr);
|
||||
if (pair.second) {
|
||||
AstVar* varp = nullptr;
|
||||
if (newdtypep) {
|
||||
varp = new AstVar{flp, VVarType::BLOCKTEMP, name, newdtypep};
|
||||
} else if (width == 0) {
|
||||
varp = new AstVar{flp, VVarType::BLOCKTEMP, name, oldvarscp->varp()};
|
||||
varp->dtypeFrom(oldvarscp);
|
||||
} else { // Used for vset and dimensions, so can zero init
|
||||
varp = new AstVar{flp, VVarType::BLOCKTEMP, name, VFlagBitPacked{}, width};
|
||||
}
|
||||
addmodp->addStmtsp(varp);
|
||||
pair.first->second = varp;
|
||||
}
|
||||
AstVar* const varp = pair.first->second;
|
||||
|
||||
AstVarScope* const varscp = new AstVarScope{flp, oldvarscp->scopep(), varp};
|
||||
oldvarscp->scopep()->addVarsp(varscp);
|
||||
// Create new AstVarScope in the scope of the given 'vscp', with the given 'name' and 'dtypep'
|
||||
AstVarScope* createNewVarScope(AstVarScope* vscp, const string& name, AstNodeDType* dtypep) {
|
||||
FileLine* const flp = vscp->fileline();
|
||||
AstScope* const scopep = vscp->scopep();
|
||||
AstNodeModule* const modp = scopep->modp();
|
||||
// Get/create the corresponding AstVar
|
||||
AstVar*& varp = m_modVarMap[{modp, name}];
|
||||
if (!varp) {
|
||||
varp = new AstVar{flp, VVarType::BLOCKTEMP, name, dtypep};
|
||||
modp->addStmtsp(varp);
|
||||
}
|
||||
// Create the AstVarScope
|
||||
AstVarScope* const varscp = new AstVarScope{flp, scopep, varp};
|
||||
scopep->addVarsp(varscp);
|
||||
return varscp;
|
||||
}
|
||||
|
||||
AstActive* createActive(AstNode* varrefp) {
|
||||
AstActive* const newactp
|
||||
= new AstActive{varrefp->fileline(), "sequentdly", m_activep->sensesp()};
|
||||
// Was addNext(), but addNextHere() avoids a linear search.
|
||||
m_activep->addNextHere(newactp);
|
||||
return newactp;
|
||||
}
|
||||
void checkActivePost(AstVarRef* varrefp, AstActive* oldactivep) {
|
||||
// Check for MULTIDRIVEN, and if so make new sentree that joins old & new sentree
|
||||
UASSERT_OBJ(oldactivep, varrefp, "<= old dly assignment not put under sensitivity block");
|
||||
if (oldactivep->sensesp() != m_activep->sensesp()) {
|
||||
if (!varrefp->varp()->fileline()->warnIsOff(V3ErrorCode::MULTIDRIVEN)
|
||||
&& !varrefp->varp()->user2()) {
|
||||
varrefp->varp()->v3warn(
|
||||
MULTIDRIVEN,
|
||||
"Signal has multiple driving blocks with different clocking: "
|
||||
<< varrefp->varp()->prettyNameQ() << '\n'
|
||||
<< varrefp->warnOther() << "... Location of first driving block\n"
|
||||
<< varrefp->warnContextPrimary() << '\n'
|
||||
<< oldactivep->warnOther() << "... Location of other driving block\n"
|
||||
<< oldactivep->warnContextSecondary());
|
||||
varrefp->varp()->user2(true);
|
||||
}
|
||||
UINFO(4, "AssignDupDlyVar: " << varrefp << endl);
|
||||
UINFO(4, " Act: " << m_activep << endl);
|
||||
UINFO(4, " Act: " << oldactivep << endl);
|
||||
// Make a new sensitivity list, which is the combination of both blocks
|
||||
AstSenItem* const sena = m_activep->sensesp()->sensesp()->cloneTree(true);
|
||||
AstSenItem* const senb = oldactivep->sensesp()->sensesp()->cloneTree(true);
|
||||
AstSenTree* const treep = new AstSenTree{m_activep->fileline(), sena};
|
||||
if (senb) treep->addSensesp(senb);
|
||||
if (AstSenTree* const storep = oldactivep->sensesStorep()) {
|
||||
storep->unlinkFrBack();
|
||||
pushDeletep(storep);
|
||||
}
|
||||
oldactivep->sensesStorep(treep);
|
||||
oldactivep->sensesp(treep);
|
||||
}
|
||||
// Same as above but create a 2-state scalar of the given 'width'
|
||||
AstVarScope* createNewVarScope(AstVarScope* vscp, const string& name, int width) {
|
||||
AstNodeDType* const dtypep = vscp->findBitDType(width, width, VSigning::UNSIGNED);
|
||||
return createNewVarScope(vscp, name, dtypep);
|
||||
}
|
||||
|
||||
AstNodeExpr* createDlyOnSet(AstAssignDly* nodep, AstNodeExpr* lhsp) {
|
||||
// Create a new AstActive, using the sensitivity list of the given 'activep'
|
||||
static AstActive* createActiveLike(FileLine* flp, AstActive* activep) {
|
||||
AstActive* const newActivep = new AstActive{flp, "sequentdly", activep->sensesp()};
|
||||
activep->addNextHere(newActivep);
|
||||
return newActivep;
|
||||
}
|
||||
|
||||
// Check and ensure the given 'activep' contains the sensitivity of the 'currentActivep'.
|
||||
// Warn if they don't match (multiple domains assign the same signal via an NBA).
|
||||
static void checkActiveSense(AstVarRef* refp, AstActive* acivep, AstActive* currentActivep) {
|
||||
if (acivep->sensesp() == currentActivep->sensesp()) return;
|
||||
|
||||
AstVar* const varp = refp->varScopep()->varp();
|
||||
// Warn once, if not turned off
|
||||
if (!varp->user1SetOnce() && !varp->fileline()->warnIsOff(V3ErrorCode::MULTIDRIVEN)) {
|
||||
varp->v3warn(MULTIDRIVEN,
|
||||
"Signal has multiple driving blocks with different clocking: "
|
||||
<< varp->prettyNameQ() << '\n'
|
||||
<< refp->warnOther() << "... Location of first driving block\n"
|
||||
<< refp->warnContextPrimary() << '\n'
|
||||
<< acivep->warnOther() << "... Location of other driving block\n"
|
||||
<< acivep->warnContextSecondary());
|
||||
}
|
||||
|
||||
// Make a new sensitivity list, which is the combination of both blocks
|
||||
AstSenItem* const sena = currentActivep->sensesp()->sensesp()->cloneTree(true);
|
||||
AstSenItem* const senb = acivep->sensesp()->sensesp()->cloneTree(true);
|
||||
AstSenTree* const treep = new AstSenTree{currentActivep->fileline(), sena};
|
||||
V3Const::constifyExpensiveEdit(treep); // Remove duplicates
|
||||
treep->addSensesp(senb);
|
||||
if (AstSenTree* const storep = acivep->sensesStorep()) {
|
||||
VL_DO_DANGLING(storep->unlinkFrBack()->deleteTree(), storep);
|
||||
}
|
||||
acivep->sensesStorep(treep);
|
||||
acivep->sensesp(treep);
|
||||
}
|
||||
|
||||
// Gather components of the given 'lhsp'. The component sub-expressions
|
||||
// are unlinked and unnecessary AstNodes deleted.
|
||||
static LhsComponents deconstructLhs(AstNodeExpr* const lhsp) {
|
||||
UASSERT_OBJ(!lhsp->backp(), lhsp, "Should have been unlinked");
|
||||
// The result being populated
|
||||
LhsComponents components;
|
||||
// Running node pointer
|
||||
AstNode* nodep = lhsp;
|
||||
// Gather AstSel applied - there should only be one
|
||||
if (AstSel* const selp = VN_CAST(nodep, Sel)) {
|
||||
nodep = selp->fromp()->unlinkFrBack();
|
||||
components.selLsbp = selp->lsbp()->unlinkFrBack();
|
||||
components.selWidthp = VN_AS(selp->widthp()->unlinkFrBack(), Const);
|
||||
VL_DO_DANGLING(selp->deleteTree(), selp);
|
||||
}
|
||||
UASSERT_OBJ(!VN_IS(nodep, Sel), lhsp, "Multiple 'AstSel' applied to LHS reference");
|
||||
// Gather AstArraySels applied
|
||||
while (AstArraySel* const arrSelp = VN_CAST(nodep, ArraySel)) {
|
||||
nodep = arrSelp->fromp()->unlinkFrBack();
|
||||
components.arrIdxps.push_back(arrSelp->bitp()->unlinkFrBack());
|
||||
VL_DO_DANGLING(arrSelp->deleteTree(), arrSelp);
|
||||
}
|
||||
std::reverse(components.arrIdxps.begin(), components.arrIdxps.end());
|
||||
// What remains must be an AstVarRef
|
||||
components.refp = VN_AS(nodep, VarRef);
|
||||
// Done
|
||||
return components;
|
||||
}
|
||||
|
||||
// Reconstruct an LHS expression from the given 'components'. The component members
|
||||
// are linked under the returned expression without cloning, so they must be unlinked.
|
||||
static AstNodeExpr* reconstructLhs(const LhsComponents& components, FileLine* flp) {
|
||||
// Running node pointer
|
||||
AstNodeExpr* nodep = components.refp;
|
||||
// Apply AstArraySels
|
||||
for (AstNodeExpr* const idxp : components.arrIdxps) {
|
||||
nodep = new AstArraySel{flp, nodep, idxp};
|
||||
}
|
||||
// Apply AstSel, if any
|
||||
if (components.selLsbp) {
|
||||
nodep = new AstSel{flp, nodep, components.selLsbp, components.selWidthp};
|
||||
}
|
||||
// Done
|
||||
return nodep;
|
||||
}
|
||||
|
||||
void createDlyOnSet(AstAssignDly* nodep) {
|
||||
// Create delayed assignment
|
||||
// See top of this file for transformation
|
||||
// Return the new LHS for the assignment, Null = unlink
|
||||
// Find selects
|
||||
AstNodeExpr* newlhsp = nullptr; // nullptr = unlink old assign
|
||||
const AstSel* bitselp = nullptr;
|
||||
AstArraySel* arrayselp = nullptr;
|
||||
AstVarRef* varrefp = nullptr;
|
||||
if (VN_IS(lhsp, Sel)) {
|
||||
bitselp = VN_AS(lhsp, Sel);
|
||||
arrayselp = VN_CAST(bitselp->fromp(), ArraySel);
|
||||
if (!arrayselp) varrefp = VN_AS(bitselp->fromp(), VarRef);
|
||||
} else if (VN_IS(lhsp, ArraySel)) {
|
||||
arrayselp = VN_AS(lhsp, ArraySel);
|
||||
} else {
|
||||
varrefp = VN_AS(lhsp, VarRef);
|
||||
}
|
||||
if (arrayselp) {
|
||||
UASSERT_OBJ(!VN_IS(arrayselp->dtypep()->skipRefp(), UnpackArrayDType), nodep,
|
||||
"ArraySel with unpacked arrays should have been removed in V3Slice");
|
||||
UINFO(4, "AssignDlyArray: " << nodep << endl);
|
||||
} else {
|
||||
UASSERT_OBJ(varrefp, nodep, "No arraysel nor varref");
|
||||
UINFO(4, "AssignDlyOnSet: " << nodep << endl);
|
||||
}
|
||||
//=== Dimensions: __Vdlyvdim__
|
||||
std::deque<AstNodeExpr*> dimvalp; // Assignment value for each dimension of assignment
|
||||
AstNode* dimselp = arrayselp;
|
||||
for (; VN_IS(dimselp, ArraySel); dimselp = VN_AS(dimselp, ArraySel)->fromp()) {
|
||||
AstNodeExpr* const valp = VN_AS(dimselp, ArraySel)->bitp()->unlinkFrBack();
|
||||
dimvalp.push_front(valp);
|
||||
}
|
||||
if (dimselp) varrefp = VN_AS(dimselp, VarRef);
|
||||
UASSERT_OBJ(varrefp, nodep, "No var underneath arraysels");
|
||||
UASSERT_OBJ(varrefp->varScopep(), varrefp, "Var didn't get varscoped in V3Scope.cpp");
|
||||
varrefp->unlinkFrBack();
|
||||
const AstVar* const oldvarp = varrefp->varp();
|
||||
const int modVecNum = m_scopeVecMap[varrefp->varScopep()]++;
|
||||
//
|
||||
std::deque<AstNodeExpr*> dimreadps; // Read value for each dimension of assignment
|
||||
for (unsigned dimension = 0; dimension < dimvalp.size(); dimension++) {
|
||||
AstNodeExpr* const dimp = dimvalp[dimension];
|
||||
if (VN_IS(dimp, Const)) { // bit = const, can just use it
|
||||
dimreadps.push_front(dimp);
|
||||
} else {
|
||||
const string bitvarname = string{"__Vdlyvdim"} + cvtToStr(dimension) + "__"
|
||||
+ oldvarp->shortName() + "__v" + cvtToStr(modVecNum);
|
||||
AstVarScope* const bitvscp
|
||||
= createVarSc(varrefp->varScopep(), bitvarname, dimp->width(), nullptr);
|
||||
AstAssign* const bitassignp = new AstAssign{
|
||||
nodep->fileline(), new AstVarRef{nodep->fileline(), bitvscp, VAccess::WRITE},
|
||||
dimp};
|
||||
nodep->addNextHere(bitassignp);
|
||||
dimreadps.push_front(new AstVarRef{nodep->fileline(), bitvscp, VAccess::READ});
|
||||
}
|
||||
}
|
||||
//
|
||||
//=== Bitselect: __Vdlyvlsb__
|
||||
AstNodeExpr* bitreadp = nullptr; // Code to read Vdlyvlsb
|
||||
if (bitselp) {
|
||||
AstNodeExpr* const lsbvaluep = bitselp->lsbp()->unlinkFrBack();
|
||||
if (VN_IS(lsbvaluep, Const)) {
|
||||
// vlsb = constant, can just push constant into where we use it
|
||||
bitreadp = lsbvaluep;
|
||||
} else {
|
||||
const string bitvarname
|
||||
= string{"__Vdlyvlsb__"} + oldvarp->shortName() + "__v" + cvtToStr(modVecNum);
|
||||
AstVarScope* const bitvscp
|
||||
= createVarSc(varrefp->varScopep(), bitvarname, lsbvaluep->width(), nullptr);
|
||||
AstAssign* const bitassignp = new AstAssign{
|
||||
nodep->fileline(), new AstVarRef{nodep->fileline(), bitvscp, VAccess::WRITE},
|
||||
lsbvaluep};
|
||||
nodep->addNextHere(bitassignp);
|
||||
bitreadp = new AstVarRef{nodep->fileline(), bitvscp, VAccess::READ};
|
||||
}
|
||||
}
|
||||
//
|
||||
//=== Value: __Vdlyvval__
|
||||
AstNodeExpr* valreadp; // Code to read Vdlyvval
|
||||
if (VN_IS(nodep->rhsp(), Const)) {
|
||||
// vval = constant, can just push constant into where we use it
|
||||
valreadp = nodep->rhsp()->unlinkFrBack();
|
||||
} else {
|
||||
const string valvarname
|
||||
= string{"__Vdlyvval__"} + oldvarp->shortName() + "__v" + cvtToStr(modVecNum);
|
||||
AstVarScope* const valvscp
|
||||
= createVarSc(varrefp->varScopep(), valvarname, 0, nodep->rhsp()->dtypep());
|
||||
newlhsp = new AstVarRef{nodep->fileline(), valvscp, VAccess::WRITE};
|
||||
valreadp = new AstVarRef{nodep->fileline(), valvscp, VAccess::READ};
|
||||
}
|
||||
//
|
||||
//=== Setting/not setting boolean: __Vdlyvset__
|
||||
AstVarScope* setvscp;
|
||||
AstAssignPre* setinitp = nullptr;
|
||||
|
||||
if (!m_inSuspendableOrFork && nodep->user3p()) {
|
||||
// Simplistic optimization. If the previous statement in same scope was also a =>,
|
||||
// then we told this nodep->user3 we can use its Vdlyvset rather than making a new one.
|
||||
// This is good for code like:
|
||||
// for (i=0; i<5; i++) vector[i] <= something;
|
||||
setvscp = VN_AS(nodep->user3p(), VarScope);
|
||||
// Insertion point/helper for adding new statements in code order
|
||||
AstNode* insertionPointp = nodep;
|
||||
const auto insert = [&insertionPointp](AstNode* stmtp) {
|
||||
insertionPointp->addNextHere(stmtp);
|
||||
insertionPointp = stmtp;
|
||||
};
|
||||
|
||||
// Deconstruct the LHS
|
||||
LhsComponents lhsComponents = deconstructLhs(nodep->lhsp()->unlinkFrBack());
|
||||
|
||||
// The referenced variable
|
||||
AstVarScope* const vscp = lhsComponents.refp->varScopep();
|
||||
|
||||
// Name suffix for signals constructed below
|
||||
const std::string baseName
|
||||
= "__" + vscp->varp()->shortName() + "__v" + std::to_string(m_scopeVecMap[vscp]++);
|
||||
|
||||
// Given an 'expression', return a new expression that always evaluates to the value of the
|
||||
// given expression at this point in the program. That is:
|
||||
// - If given a non-constant expression, create a new temporary AstVarScope with the given
|
||||
// 'name' prefix, assign the expression to it, and return a read reference to the new
|
||||
// AstVarScope.
|
||||
// - If given a constant, just return that constant.
|
||||
const auto capture = [&](AstNodeExpr* exprp, const std::string& name) -> AstNodeExpr* {
|
||||
UASSERT_OBJ(!exprp->backp(), exprp, "Should have been unlinked");
|
||||
if (VN_IS(exprp, Const)) return exprp;
|
||||
const std::string realName = "__" + name + baseName;
|
||||
AstVarScope* const tmpVscp = createNewVarScope(vscp, realName, exprp->dtypep());
|
||||
FileLine* const flp = exprp->fileline();
|
||||
insert(new AstAssign{flp, new AstVarRef{flp, tmpVscp, VAccess::WRITE}, exprp});
|
||||
return new AstVarRef{flp, tmpVscp, VAccess::READ};
|
||||
};
|
||||
|
||||
// Unlink and Capture the RHS value
|
||||
AstNodeExpr* const rhsp = capture(nodep->rhsp()->unlinkFrBack(), "VdlyVal");
|
||||
|
||||
// Capture the AstSel LSB - The width is always an AstConst, so nothing to do with that
|
||||
if (lhsComponents.selLsbp) {
|
||||
lhsComponents.selLsbp = capture(lhsComponents.selLsbp, "VdlyLsb");
|
||||
}
|
||||
|
||||
// Capture the AstArraySel indices
|
||||
for (size_t i = 0; i < lhsComponents.arrIdxps.size(); ++i) {
|
||||
AstNodeExpr*& arrIdxpr = lhsComponents.arrIdxps[i];
|
||||
arrIdxpr = capture(arrIdxpr, "VdlyDim" + std::to_string(i));
|
||||
}
|
||||
|
||||
FileLine* const flp = nodep->fileline();
|
||||
|
||||
// Create the flag denoting an update is pending
|
||||
AstVarScope* setVscp;
|
||||
AstAssignPre* setInitp = nullptr;
|
||||
if (nodep->user2p()) {
|
||||
// Simplistic optimization. If the previous statement in same list was also an
|
||||
// AstAssignDly, then we told this node (by setting m_nextDlyp->user2p below), that
|
||||
// it can use the same 'VdlySet' variable rather than making a new one. This is
|
||||
// good for code like:
|
||||
// arrayA[0] <= something;
|
||||
// arrayB[1] <= something;
|
||||
setVscp = VN_AS(nodep->user2p(), VarScope);
|
||||
++m_statSharedSet;
|
||||
} else { // Create new one
|
||||
const string setvarname
|
||||
= string{"__Vdlyvset__"} + oldvarp->shortName() + "__v" + cvtToStr(modVecNum);
|
||||
setvscp = createVarSc(varrefp->varScopep(), setvarname, 1, nullptr);
|
||||
} else {
|
||||
// Create new one
|
||||
const std::string name = "__VdlySet" + baseName;
|
||||
setVscp = createNewVarScope(vscp, name, 1);
|
||||
insert(new AstAssign{flp, new AstVarRef{flp, setVscp, VAccess::WRITE},
|
||||
new AstConst{flp, AstConst::BitTrue{}}});
|
||||
|
||||
if (!m_inSuspendableOrFork) {
|
||||
// Suspendables reset __Vdlyvset__ in the AstAlwaysPost
|
||||
setinitp = new AstAssignPre{
|
||||
nodep->fileline(), new AstVarRef{nodep->fileline(), setvscp, VAccess::WRITE},
|
||||
new AstConst{nodep->fileline(), 0}};
|
||||
// Suspendables reset __VdlySet in the AstAlwaysPost
|
||||
setInitp = new AstAssignPre{flp, new AstVarRef{flp, setVscp, VAccess::WRITE},
|
||||
new AstConst{flp, 0}};
|
||||
}
|
||||
AstAssign* const setassignp = new AstAssign{
|
||||
nodep->fileline(), new AstVarRef{nodep->fileline(), setvscp, VAccess::WRITE},
|
||||
new AstConst{nodep->fileline(), AstConst::BitTrue{}}};
|
||||
nodep->addNextHere(setassignp);
|
||||
}
|
||||
if (m_nextDlyp) { // Tell next assigndly it can share the variable
|
||||
m_nextDlyp->user3p(setvscp);
|
||||
}
|
||||
//
|
||||
// Create ALWAYSPOST for delayed variable
|
||||
// We add all logic to the same block if it's for the same memory
|
||||
// This ensures that multiple assignments to the same memory will result
|
||||
// in correctly ordered code - the last assignment must be last.
|
||||
// Tell next AstAssignDly it can share the 'VdlySet' variable
|
||||
if (!m_inSuspendableOrFork && m_nextDlyp) m_nextDlyp->user2p(setVscp);
|
||||
|
||||
// Create 'Post' ordered commit statements for delayed variable. We add all logic to the
|
||||
// same block if it's for the same variable This ensures that multiple assignments to the
|
||||
// same memory will result in correctly ordered code - the last assignment must be last.
|
||||
// It also has the nice side effect of assisting cache locality.
|
||||
varrefp->user2Inc(); // Do not generate a warning for the blocking assignment
|
||||
// that uses this varref
|
||||
AstNodeExpr* selectsp = varrefp;
|
||||
for (int dimension = int(dimreadps.size()) - 1; dimension >= 0; --dimension) {
|
||||
selectsp = new AstArraySel{nodep->fileline(), selectsp, dimreadps[dimension]};
|
||||
}
|
||||
if (bitselp) {
|
||||
selectsp = new AstSel{nodep->fileline(), selectsp, bitreadp,
|
||||
bitselp->widthp()->cloneTreePure(false)}; // Always pure
|
||||
}
|
||||
// Build "IF (changeit) ...
|
||||
UINFO(9, " For " << setvscp << endl);
|
||||
UINFO(9, " & " << varrefp << endl);
|
||||
AstAlwaysPost* finalp = nullptr;
|
||||
|
||||
AstAlwaysPost* postp = nullptr;
|
||||
if (m_inSuspendableOrFork) {
|
||||
finalp = m_vscpAux(varrefp->varScopep()).suspFinalp;
|
||||
if (!finalp) {
|
||||
FileLine* const flp = nodep->fileline();
|
||||
finalp = new AstAlwaysPost{flp, nullptr, nullptr};
|
||||
UINFO(9, " Created " << finalp << endl);
|
||||
if (!m_procp->user3p()) {
|
||||
AstActive* const newactp = createActive(varrefp);
|
||||
m_procp->user3p(newactp);
|
||||
m_vscpAux(varrefp->varScopep()).suspFinalp = finalp;
|
||||
postp = m_vscpAux(vscp).suspPostp;
|
||||
if (!postp) {
|
||||
postp = new AstAlwaysPost{flp};
|
||||
if (!m_procp->user2p()) {
|
||||
m_procp->user2p(createActiveLike(lhsComponents.refp->fileline(), m_activep));
|
||||
// TODO: Somebody needs to explain me how it makes sense to set this
|
||||
// inside this 'if'. Shouldn't it be outside this 'if'? See #5084
|
||||
m_vscpAux(vscp).suspPostp = postp;
|
||||
}
|
||||
AstActive* const actp = VN_AS(m_procp->user3p(), Active);
|
||||
actp->addStmtsp(finalp);
|
||||
VN_AS(m_procp->user2p(), Active)->addStmtsp(postp);
|
||||
}
|
||||
} else {
|
||||
finalp = m_vscpAux(varrefp->varScopep()).finalp;
|
||||
if (finalp) {
|
||||
AstActive* const oldactivep = VN_AS(finalp->user2p(), Active);
|
||||
checkActivePost(varrefp, oldactivep);
|
||||
if (setinitp) oldactivep->addStmtsp(setinitp);
|
||||
} else { // first time we've dealt with this memory
|
||||
finalp = new AstAlwaysPost{nodep->fileline(), nullptr /*sens*/, nullptr /*body*/};
|
||||
UINFO(9, " Created " << finalp << endl);
|
||||
AstActive* const newactp = createActive(varrefp);
|
||||
newactp->addStmtsp(finalp);
|
||||
m_vscpAux(varrefp->varScopep()).finalp = finalp;
|
||||
finalp->user2p(newactp);
|
||||
if (setinitp) newactp->addStmtsp(setinitp);
|
||||
postp = m_vscpAux(vscp).postp;
|
||||
// Create the post block if not yet exists
|
||||
if (!postp) {
|
||||
// Make the new AstActive with identical sensitivity tree
|
||||
AstActive* const activep
|
||||
= createActiveLike(lhsComponents.refp->fileline(), m_activep);
|
||||
m_vscpAux(vscp).activep = activep;
|
||||
// Add the 'Post' scheduled block
|
||||
postp = new AstAlwaysPost{flp};
|
||||
activep->addStmtsp(postp);
|
||||
m_vscpAux(vscp).postp = postp;
|
||||
}
|
||||
AstActive* const activep = m_vscpAux(vscp).activep;
|
||||
// Ensure the active block contains the current sensitivities
|
||||
checkActiveSense(lhsComponents.refp, activep, m_activep);
|
||||
// Add the initializer of the __VdlySet flag
|
||||
if (setInitp) activep->addStmtsp(setInitp);
|
||||
}
|
||||
AstIf* postLogicp;
|
||||
if (finalp->user3p() == setvscp) {
|
||||
// Optimize as above; if sharing Vdlyvset *ON SAME VARIABLE*,
|
||||
// we can share the IF statement too
|
||||
postLogicp = VN_AS(finalp->user1p(), If);
|
||||
UASSERT_OBJ(postLogicp, nodep,
|
||||
"Delayed assignment misoptimized; prev var found w/o associated IF");
|
||||
|
||||
// Build/Get 'if (__VdlySet) { ... commit .. }'
|
||||
AstIf* ifp = nullptr;
|
||||
if (postp->user2p() == setVscp) {
|
||||
// Optimize as above. If sharing VdlySet *ON SAME VARIABLE*, we can share the AstIf
|
||||
ifp = VN_AS(postp->user1p(), If);
|
||||
} else {
|
||||
postLogicp = new AstIf{nodep->fileline(),
|
||||
new AstVarRef{nodep->fileline(), setvscp, VAccess::READ}};
|
||||
UINFO(9, " Created " << postLogicp << endl);
|
||||
finalp->addStmtsp(postLogicp);
|
||||
finalp->user3p(setvscp); // Remember IF's vset variable
|
||||
finalp->user1p(postLogicp); // and the associated IF, as we may be able to reuse it
|
||||
ifp = new AstIf{flp, new AstVarRef{flp, setVscp, VAccess::READ}};
|
||||
postp->addStmtsp(ifp);
|
||||
postp->user1p(ifp); // Remember the associated 'AstIf'
|
||||
postp->user2p(setVscp); // Remember the VdlySet variable used as the condition.
|
||||
}
|
||||
postLogicp->addThensp(new AstAssign{nodep->fileline(), selectsp, valreadp});
|
||||
|
||||
// Suspendables clear __VdlySet in the post block
|
||||
if (m_inSuspendableOrFork) {
|
||||
FileLine* const flp = nodep->fileline();
|
||||
postLogicp->addThensp(new AstAssign{flp, new AstVarRef{flp, setvscp, VAccess::WRITE},
|
||||
ifp->addThensp(new AstAssign{flp, new AstVarRef{flp, setVscp, VAccess::WRITE},
|
||||
new AstConst{flp, 0}});
|
||||
}
|
||||
return newlhsp;
|
||||
|
||||
// Reconstruct the delayed LHS expression
|
||||
AstNodeExpr* const newLhsp = reconstructLhs(lhsComponents, flp);
|
||||
// Finally assign the delayed value
|
||||
ifp->addThensp(new AstAssign{flp, newLhsp, rhsp});
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
void visit(AstNetlist* nodep) override {
|
||||
// VV***** We reset all userp() on the netlist
|
||||
m_modVarMap.clear();
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstNetlist* nodep) override { iterateChildren(nodep); }
|
||||
void visit(AstScope* nodep) override {
|
||||
UINFO(4, " MOD " << nodep << endl);
|
||||
AstNode::user3ClearTree();
|
||||
AstNode::user2ClearTree();
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstCFunc* nodep) override {
|
||||
|
|
@ -427,46 +424,45 @@ class DelayedVisitor final : public VNVisitor {
|
|||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstActive* nodep) override {
|
||||
UASSERT_OBJ(!m_activep, nodep, "Should not nest");
|
||||
VL_RESTORER(m_activep);
|
||||
VL_RESTORER(m_ignoreBlkAndNBlk);
|
||||
m_activep = nodep;
|
||||
VL_RESTORER(m_inInitial);
|
||||
{
|
||||
AstSenTree* const senTreep = nodep->sensesp();
|
||||
m_inInitial = senTreep->hasStatic() || senTreep->hasInitial();
|
||||
m_ignoreBlkAndNBlk = senTreep->hasStatic() || senTreep->hasInitial();
|
||||
// Two sets to same variable in different actives must use different vars.
|
||||
AstNode::user3ClearTree();
|
||||
AstNode::user2ClearTree();
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
}
|
||||
void visit(AstNodeProcedure* nodep) override {
|
||||
VL_RESTORER(m_inSuspendableOrFork);
|
||||
m_inSuspendableOrFork = nodep->isSuspendable();
|
||||
{
|
||||
VL_RESTORER(m_inSuspendableOrFork);
|
||||
VL_RESTORER(m_procp);
|
||||
m_inSuspendableOrFork = nodep->isSuspendable();
|
||||
m_procp = nodep;
|
||||
m_timingDomains.clear();
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
if (m_timingDomains.empty()) return;
|
||||
if (auto* const actp = VN_AS(nodep->user3p(), Active)) {
|
||||
// Merge all timing domains (and possibly the active's domain) to create a sentree for
|
||||
// the post logic
|
||||
// TODO: allow multiple sentrees per active, so we don't have to merge them and create
|
||||
// a new trigger
|
||||
auto* clockedDomain
|
||||
= actp->sensesp()->hasClocked() ? actp->sensesp()->cloneTree(false) : nullptr;
|
||||
for (auto* const domainp : m_timingDomains) {
|
||||
if (!clockedDomain) {
|
||||
clockedDomain = domainp->cloneTree(false);
|
||||
if (AstActive* const actp = VN_AS(nodep->user2p(), Active)) {
|
||||
// Merge all timing domains (and possibly the original active's domain)
|
||||
// to create a sentree for the pre/post logic
|
||||
// TODO: allow multiple sentrees per active, so we don't have
|
||||
// to merge them and create a new trigger
|
||||
AstSenTree* senTreep = nullptr;
|
||||
if (actp->sensesp()->hasClocked()) senTreep = actp->sensesp()->cloneTree(false);
|
||||
for (AstSenTree* const domainp : m_timingDomains) {
|
||||
if (!senTreep) {
|
||||
senTreep = domainp->cloneTree(false);
|
||||
} else {
|
||||
clockedDomain->addSensesp(domainp->sensesp()->cloneTree(true));
|
||||
clockedDomain->multi(true); // Comment that it was made from multiple domains
|
||||
senTreep->addSensesp(domainp->sensesp()->cloneTree(true));
|
||||
senTreep->multi(true); // Comment that it was made from multiple domains
|
||||
}
|
||||
}
|
||||
// We cannot constify the sentree using V3Const as user1-5 is already taken up by
|
||||
// V3Delayed
|
||||
actp->sensesp(clockedDomain);
|
||||
actp->sensesStorep(clockedDomain);
|
||||
V3Const::constifyExpensiveEdit(senTreep); // Remove duplicates
|
||||
actp->sensesp(senTreep);
|
||||
actp->sensesStorep(senTreep);
|
||||
}
|
||||
m_timingDomains.clear();
|
||||
}
|
||||
void visit(AstFork* nodep) override {
|
||||
VL_RESTORER(m_inSuspendableOrFork);
|
||||
|
|
@ -482,16 +478,16 @@ class DelayedVisitor final : public VNVisitor {
|
|||
if (nodep->isDelayed()) {
|
||||
AstVarRef* const vrefp = VN_AS(nodep->operandp(), VarRef);
|
||||
vrefp->unlinkFrBack();
|
||||
const string newvarname = string{"__Vdly__"} + vrefp->varp()->shortName();
|
||||
AstVarScope* const dlyvscp = createVarSc(vrefp->varScopep(), newvarname, 1, nullptr);
|
||||
const std::string newvarname = "__Vdly__" + vrefp->varp()->shortName();
|
||||
AstVarScope* const dlyvscp = createNewVarScope(vrefp->varScopep(), newvarname, 1);
|
||||
|
||||
const auto dlyRef = [=](VAccess access) {
|
||||
const auto dlyRef = [=](VAccess access) { //
|
||||
return new AstVarRef{flp, dlyvscp, access};
|
||||
};
|
||||
|
||||
AstAssignPre* const prep = new AstAssignPre{flp, dlyRef(VAccess::WRITE),
|
||||
new AstConst{flp, AstConst::BitFalse{}}};
|
||||
AstAlwaysPost* const postp = new AstAlwaysPost{flp, nullptr, nullptr};
|
||||
AstAlwaysPost* const postp = new AstAlwaysPost{flp};
|
||||
{
|
||||
AstIf* const ifp = new AstIf{flp, dlyRef(VAccess::READ)};
|
||||
postp->addStmtsp(ifp);
|
||||
|
|
@ -500,7 +496,7 @@ class DelayedVisitor final : public VNVisitor {
|
|||
ifp->addThensp(callp->makeStmt());
|
||||
}
|
||||
|
||||
AstActive* const activep = createActive(nodep);
|
||||
AstActive* const activep = createActiveLike(flp, m_activep);
|
||||
activep->addStmtsp(prep);
|
||||
activep->addStmtsp(postp);
|
||||
|
||||
|
|
@ -515,12 +511,16 @@ class DelayedVisitor final : public VNVisitor {
|
|||
}
|
||||
nodep->deleteTree();
|
||||
}
|
||||
|
||||
// Pre/Post logic are created here and their content need no further changes, so ignore.
|
||||
void visit(AstAssignPre*) override {}
|
||||
void visit(AstAssignPost*) override {}
|
||||
void visit(AstAlwaysPost*) override {}
|
||||
|
||||
void visit(AstAssignDly* nodep) override {
|
||||
VL_RESTORER(m_inDly);
|
||||
VL_RESTORER(m_nextDlyp);
|
||||
m_inDly = true;
|
||||
m_nextDlyp
|
||||
= VN_CAST(nodep->nextp(), AssignDly); // Next assignment in same block, maybe nullptr.
|
||||
// Next assignment in same block, maybe nullptr.
|
||||
m_nextDlyp = VN_CAST(nodep->nextp(), AssignDly);
|
||||
if (m_cfuncp) {
|
||||
if (!v3Global.rootp()->nbaEventp()) {
|
||||
nodep->v3warn(
|
||||
|
|
@ -533,92 +533,88 @@ class DelayedVisitor final : public VNVisitor {
|
|||
const bool isArray = VN_IS(nodep->lhsp(), ArraySel)
|
||||
|| (VN_IS(nodep->lhsp(), Sel)
|
||||
&& VN_IS(VN_AS(nodep->lhsp(), Sel)->fromp(), ArraySel));
|
||||
if (m_inSuspendableOrFork || isArray) {
|
||||
AstNodeExpr* const lhsp = nodep->lhsp();
|
||||
AstNodeExpr* const newlhsp = createDlyOnSet(nodep, lhsp);
|
||||
if (m_inLoop && isArray) {
|
||||
if (isArray) {
|
||||
if (m_inLoop) {
|
||||
nodep->v3warn(BLKLOOPINIT, "Unsupported: Delayed assignment to array inside for "
|
||||
"loops (non-delayed is ok - see docs)");
|
||||
}
|
||||
const AstBasicDType* const basicp = lhsp->dtypep()->basicp();
|
||||
if (basicp && basicp->isEvent()) {
|
||||
nodep->v3warn(E_UNSUPPORTED, "Unsupported: event arrays");
|
||||
if (const AstBasicDType* const basicp = nodep->lhsp()->dtypep()->basicp()) {
|
||||
// TODO: this message is not covered by tests
|
||||
if (basicp->isEvent()) nodep->v3warn(E_UNSUPPORTED, "Unsupported: event arrays");
|
||||
}
|
||||
if (newlhsp) {
|
||||
if (nodep->lhsp()) nodep->lhsp()->unlinkFrBack();
|
||||
nodep->lhsp(newlhsp);
|
||||
} else {
|
||||
|
||||
createDlyOnSet(nodep);
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
} else if (m_inSuspendableOrFork) {
|
||||
createDlyOnSet(nodep);
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
}
|
||||
if (!lhsp->backp()) VL_DO_DANGLING(pushDeletep(lhsp), lhsp);
|
||||
} else {
|
||||
{
|
||||
VL_RESTORER(m_inDlyLhs);
|
||||
m_inDlyLhs = true;
|
||||
iterate(nodep->lhsp());
|
||||
m_inDly = false;
|
||||
}
|
||||
iterate(nodep->rhsp());
|
||||
}
|
||||
}
|
||||
|
||||
void visit(AstVarRef* nodep) override {
|
||||
if (!nodep->user2Inc()) { // Not done yet
|
||||
if (m_inDly && nodep->access().isWriteOrRW()) {
|
||||
UINFO(4, "AssignDlyVar: " << nodep << endl);
|
||||
markVarUsage(nodep, true);
|
||||
UASSERT_OBJ(m_activep, nodep, "<= not under sensitivity block");
|
||||
// Ignore refs inserted by 'replaceWith' just below
|
||||
if (nodep->user2()) return;
|
||||
// Only care about write refs
|
||||
if (!nodep->access().isWriteOrRW()) return;
|
||||
// Check var usage
|
||||
markVarUsage(nodep, m_inDlyLhs);
|
||||
|
||||
// Below we rewrite the LHS of AstAssignDelay only
|
||||
if (!m_inDlyLhs) return;
|
||||
|
||||
UASSERT_OBJ(!nodep->access().isRW(), nodep, "<= on read+write method");
|
||||
if (!m_activep->hasClocked()) {
|
||||
nodep->v3error("Internal: Blocking <= assignment in non-clocked block, should "
|
||||
"have converted in V3Active");
|
||||
UASSERT_OBJ(m_activep, nodep, "<= not under sensitivity block");
|
||||
UASSERT_OBJ(m_activep->hasClocked(), nodep,
|
||||
"<= assignment in non-clocked block, should have been converted in V3Active");
|
||||
|
||||
// Replace with reference to the shadow variable
|
||||
FileLine* const flp = nodep->fileline();
|
||||
AstVarScope* const vscp = nodep->varScopep();
|
||||
AstVarScope*& dlyVscp = m_vscpAux(vscp).delayVscp;
|
||||
|
||||
// Create the shadow variable and related active block if not yet exists
|
||||
if (!dlyVscp) {
|
||||
// Create the shadow variable
|
||||
const std::string name = "__Vdly__" + vscp->varp()->shortName();
|
||||
dlyVscp = createNewVarScope(vscp, name, vscp->dtypep());
|
||||
// Make the new AstActive with identical sensitivity tree
|
||||
AstActive* const activep = createActiveLike(flp, m_activep);
|
||||
m_vscpAux(vscp).activep = activep;
|
||||
|
||||
// Add 'Pre' scheduled 'shadowVariable = originalVariable' assignment
|
||||
activep->addStmtsp(new AstAssignPre{flp, new AstVarRef{flp, dlyVscp, VAccess::WRITE},
|
||||
new AstVarRef{flp, vscp, VAccess::READ}});
|
||||
// Add 'Post' scheduled 'originalVariable = shadowVariable' assignment
|
||||
activep->addStmtsp(new AstAssignPost{flp, new AstVarRef{flp, vscp, VAccess::WRITE},
|
||||
new AstVarRef{flp, dlyVscp, VAccess::READ}});
|
||||
}
|
||||
AstVarScope* const oldvscp = nodep->varScopep();
|
||||
UASSERT_OBJ(oldvscp, nodep, "Var didn't get varscoped in V3Scope.cpp");
|
||||
AstVarScope* dlyvscp = m_vscpAux(oldvscp).delayVscp;
|
||||
if (dlyvscp) { // Multiple use of delayed variable
|
||||
AstActive* const oldactivep = m_vscpAux(dlyvscp).activep;
|
||||
checkActivePost(nodep, oldactivep);
|
||||
} else { // First use of this delayed variable
|
||||
const string newvarname = string{"__Vdly__"} + nodep->varp()->shortName();
|
||||
dlyvscp = createVarSc(oldvscp, newvarname, 0, nullptr);
|
||||
AstNodeAssign* const prep = new AstAssignPre{
|
||||
nodep->fileline(),
|
||||
new AstVarRef{nodep->fileline(), dlyvscp, VAccess::WRITE},
|
||||
new AstVarRef{nodep->fileline(), oldvscp, VAccess::READ}};
|
||||
AstNodeAssign* const postp = new AstAssignPost{
|
||||
nodep->fileline(),
|
||||
new AstVarRef{nodep->fileline(), oldvscp, VAccess::WRITE},
|
||||
new AstVarRef{nodep->fileline(), dlyvscp, VAccess::READ}};
|
||||
postp->lhsp()->user2(true); // Don't detect this assignment
|
||||
m_vscpAux(oldvscp).delayVscp = dlyvscp; // So we can find it later
|
||||
// Make new ACTIVE with identical sensitivity tree
|
||||
AstActive* const newactp = createActive(nodep);
|
||||
m_vscpAux(dlyvscp).activep = newactp;
|
||||
newactp->addStmtsp(prep); // Add to FRONT of statements
|
||||
newactp->addStmtsp(postp);
|
||||
}
|
||||
AstVarRef* const newrefp
|
||||
= new AstVarRef{nodep->fileline(), dlyvscp, VAccess::WRITE};
|
||||
newrefp->user2(true); // No reason to do it again
|
||||
nodep->replaceWith(newrefp);
|
||||
|
||||
// Ensure the active block of the shadow variable contains the current sensitivities
|
||||
checkActiveSense(nodep, m_vscpAux(vscp).activep, m_activep);
|
||||
|
||||
// Replace reference with reference to the shadow variable
|
||||
AstVarRef* const newRefp = new AstVarRef{flp, dlyVscp, VAccess::WRITE};
|
||||
newRefp->user2(true); // skip visit after repalce
|
||||
nodep->replaceWith(newRefp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
} else if (!m_inDly && nodep->access().isWriteOrRW()) {
|
||||
// UINFO(9, "NBA " << nodep << endl);
|
||||
if (!m_inInitial) {
|
||||
UINFO(4, "AssignNDlyVar: " << nodep << endl);
|
||||
markVarUsage(nodep, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visit(AstNodeReadWriteMem* nodep) override {
|
||||
VL_RESTORER(m_ignoreBlkAndNBlk);
|
||||
m_ignoreBlkAndNBlk = true; // $readmem/$writemem often used in mem models
|
||||
// so we will suppress BLKANDNBLK warnings
|
||||
// $readmem/$writemem often used in mem models so we suppress BLKANDNBLK warnings
|
||||
m_ignoreBlkAndNBlk = true;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
void visit(AstNodeFor* nodep) override { // LCOV_EXCL_LINE
|
||||
nodep->v3fatalSrc(
|
||||
"For statements should have been converted to while statements in V3Begin");
|
||||
nodep->v3fatalSrc("For statements should have been converted to while statements");
|
||||
}
|
||||
void visit(AstWhile* nodep) override {
|
||||
VL_RESTORER(m_inLoop);
|
||||
|
|
@ -626,9 +622,9 @@ class DelayedVisitor final : public VNVisitor {
|
|||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstExprStmt* nodep) override {
|
||||
VL_RESTORER(m_inDly);
|
||||
VL_RESTORER(m_inDlyLhs);
|
||||
// Restoring is needed, because AstExprStmt may contain assignments
|
||||
m_inDly = false;
|
||||
m_inDlyLhs = false;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ private:
|
|||
if (writesToVirtIface(nodep)) {
|
||||
// Convert to always, as we have to assign the trigger var
|
||||
FileLine* const flp = nodep->fileline();
|
||||
AstAlwaysPost* const postp = new AstAlwaysPost{flp, nullptr, nullptr};
|
||||
AstAlwaysPost* const postp = new AstAlwaysPost{flp};
|
||||
nodep->replaceWith(postp);
|
||||
postp->addStmtsp(
|
||||
new AstAssign{flp, nodep->lhsp()->unlinkFrBack(), nodep->rhsp()->unlinkFrBack()});
|
||||
|
|
|
|||
Loading…
Reference in New Issue