forked from github/verilator
parent
24a0d2a0c9
commit
0c3ffa1841
1
Changes
1
Changes
@ -19,6 +19,7 @@ Verilator 4.217 devel
|
||||
|
||||
**Minor:**
|
||||
|
||||
* Support force/release (#2491) (#2593). [Shunyao CAD]
|
||||
* Support lower dimension looping in foreach loops (#3172). [Ehab Ibrahim]
|
||||
* Support up to 64 bit enums for .next/.prev/.name (#3244). [Alexander Grobman]
|
||||
* Reduce .rodata footprint of trace initialization (#3250). [Geza Lore, Shunyao CAD]
|
||||
|
@ -197,6 +197,7 @@ RAW_OBJS = \
|
||||
V3Expand.o \
|
||||
V3File.o \
|
||||
V3FileLine.o \
|
||||
V3Force.o \
|
||||
V3Gate.o \
|
||||
V3GenClk.o \
|
||||
V3Global.o \
|
||||
|
@ -70,6 +70,29 @@ AstNode::AstNode(AstType t, FileLine* fl)
|
||||
editCountInc();
|
||||
}
|
||||
|
||||
AstNode* AstNode::usernp(int n) const {
|
||||
switch (n) {
|
||||
case 1: return user1p();
|
||||
case 2: return user2p();
|
||||
case 3: return user3p();
|
||||
case 4: return user4p();
|
||||
case 5: return user5p();
|
||||
}
|
||||
v3fatalSrc("Bad Case");
|
||||
return nullptr; // LCOV_EXCL_LINE
|
||||
}
|
||||
void AstNode::usernp(int n, void* userp) {
|
||||
switch (n) {
|
||||
case 1: user1p(userp); return;
|
||||
case 2: user2p(userp); return;
|
||||
case 3: user3p(userp); return;
|
||||
case 4: user4p(userp); return;
|
||||
case 5: user5p(userp); return;
|
||||
}
|
||||
v3fatalSrc("Bad Case");
|
||||
VL_UNREACHABLE
|
||||
}
|
||||
|
||||
AstNode* AstNode::abovep() const {
|
||||
// m_headtailp only valid at beginning or end of list
|
||||
// Avoid supporting at other locations as would require walking
|
||||
|
11
src/V3Ast.h
11
src/V3Ast.h
@ -1511,6 +1511,14 @@ public:
|
||||
uint8_t brokenState() const { return m_brokenState; }
|
||||
void brokenState(uint8_t value) { m_brokenState = value; }
|
||||
|
||||
void prefetch() const {
|
||||
ASTNODE_PREFETCH(m_op1p);
|
||||
ASTNODE_PREFETCH(m_op2p);
|
||||
ASTNODE_PREFETCH(m_op3p);
|
||||
ASTNODE_PREFETCH(m_op4p);
|
||||
ASTNODE_PREFETCH(m_nextp);
|
||||
}
|
||||
|
||||
// Used by AstNode::broken()
|
||||
bool brokeExists() const { return V3Broken::isLinkable(this); }
|
||||
bool brokeExistsAbove() const { return brokeExists() && (m_brokenState >> 7); }
|
||||
@ -1665,6 +1673,9 @@ public:
|
||||
static void user5ClearTree() { AstUser5InUse::clear(); } // Clear userp()'s across the entire tree
|
||||
// clang-format on
|
||||
|
||||
AstNode* usernp(int n) const; // Return user1..userN based on provided n
|
||||
void usernp(int n, void* userp); // Set user1..userN based on provided n
|
||||
|
||||
vluint64_t editCount() const { return m_editCount; }
|
||||
void editCountInc() {
|
||||
m_editCount = ++s_editCntGbl; // Preincrement, so can "watch AstNode::s_editCntGbl=##"
|
||||
|
@ -1988,6 +1988,7 @@ private:
|
||||
bool m_fileDescr : 1; // File descriptor
|
||||
bool m_isRand : 1; // Random variable
|
||||
bool m_isConst : 1; // Table contains constant data
|
||||
bool m_isContinuously : 1; // Ever assigned continuously (for force/release)
|
||||
bool m_isStatic : 1; // Static C variable (for Verilog see instead isAutomatic)
|
||||
bool m_isPulldown : 1; // Tri0
|
||||
bool m_isPullup : 1; // Tri1
|
||||
@ -2026,6 +2027,7 @@ private:
|
||||
m_fileDescr = false;
|
||||
m_isRand = false;
|
||||
m_isConst = false;
|
||||
m_isContinuously = false;
|
||||
m_isStatic = false;
|
||||
m_isPulldown = false;
|
||||
m_isPullup = false;
|
||||
@ -2180,6 +2182,7 @@ public:
|
||||
void primaryIO(bool flag) { m_primaryIO = flag; }
|
||||
void isRand(bool flag) { m_isRand = flag; }
|
||||
void isConst(bool flag) { m_isConst = flag; }
|
||||
void isContinuously(bool flag) { m_isContinuously = flag; }
|
||||
void isStatic(bool flag) { m_isStatic = flag; }
|
||||
void isIfaceParent(bool flag) { m_isIfaceParent = flag; }
|
||||
void funcLocal(bool flag) { m_funcLocal = flag; }
|
||||
@ -2203,6 +2206,7 @@ public:
|
||||
virtual void tag(const string& text) override { m_tag = text; }
|
||||
virtual string tag() const override { return m_tag; }
|
||||
bool isAnsi() const { return m_ansi; }
|
||||
bool isContinuously() const { return m_isContinuously; }
|
||||
bool isDeclTyped() const { return m_declTyped; }
|
||||
bool isInoutish() const { return m_direction.isInoutish(); }
|
||||
bool isNonOutput() const { return m_direction.isNonOutput(); }
|
||||
@ -2272,6 +2276,7 @@ public:
|
||||
if (fromp->attrClockEn()) attrClockEn(true);
|
||||
if (fromp->attrFileDescr()) attrFileDescr(true);
|
||||
if (fromp->attrIsolateAssign()) attrIsolateAssign(true);
|
||||
if (fromp->isContinuously()) isContinuously(true);
|
||||
}
|
||||
bool gateMultiInputOptimizable() const {
|
||||
// Ok to gate optimize; must return false if propagateAttrFrom would do anything
|
||||
@ -3537,6 +3542,37 @@ public:
|
||||
uint32_t direction() const { return (uint32_t)m_direction; }
|
||||
};
|
||||
|
||||
class AstAssignForce final : public AstNodeAssign {
|
||||
public:
|
||||
AstAssignForce(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
|
||||
: ASTGEN_SUPER_AssignForce(fl, lhsp, rhsp) {
|
||||
v3Global.useForce(true);
|
||||
}
|
||||
ASTNODE_NODE_FUNCS(AssignForce)
|
||||
virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
|
||||
return new AstAssignForce{this->fileline(), lhsp, rhsp};
|
||||
}
|
||||
virtual bool brokeLhsMustBeLvalue() const override { return true; }
|
||||
};
|
||||
|
||||
class AstAssignRelease final : public AstNodeAssign {
|
||||
// Release is treated similar to an assign to `z
|
||||
public:
|
||||
// Only for use in parser, as V3Width needs to resolve the '0 width.
|
||||
AstAssignRelease(FileLine* fl, VFlagChildDType, AstNode* lhsp)
|
||||
: ASTGEN_SUPER_AssignRelease(fl, lhsp, new AstConst{fl, AstConst::StringToParse{}, "'0"}) {
|
||||
v3Global.useForce(true);
|
||||
}
|
||||
AstAssignRelease(FileLine* fl, AstNode* lhsp, AstNode* rhsp)
|
||||
: ASTGEN_SUPER_AssignRelease(fl, lhsp, rhsp) {}
|
||||
ASTNODE_NODE_FUNCS(AssignRelease)
|
||||
virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override {
|
||||
return new AstAssignRelease{this->fileline(), lhsp, rhsp};
|
||||
}
|
||||
virtual bool brokeLhsMustBeLvalue() const override { return true; }
|
||||
AstNode* lhsp() const { return op2p(); }
|
||||
};
|
||||
|
||||
class AstAssignPre final : public AstNodeAssign {
|
||||
// Like Assign, but predelayed assignment requiring special order handling
|
||||
public:
|
||||
|
@ -124,9 +124,15 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor {
|
||||
putqs(nodep, "*/\n");
|
||||
}
|
||||
virtual void visit(AstNodeAssign* nodep) override {
|
||||
iterateAndNextNull(nodep->lhsp());
|
||||
putfs(nodep, " " + nodep->verilogKwd() + " ");
|
||||
iterateAndNextNull(nodep->rhsp());
|
||||
if (VN_IS(nodep, AssignRelease)) {
|
||||
puts("release ");
|
||||
iterateAndNextNull(nodep->lhsp());
|
||||
} else {
|
||||
if (VN_IS(nodep, AssignForce)) puts("force ");
|
||||
iterateAndNextNull(nodep->lhsp());
|
||||
putfs(nodep, " " + nodep->verilogKwd() + " ");
|
||||
iterateAndNextNull(nodep->rhsp());
|
||||
}
|
||||
if (!m_suppressSemi) puts(";\n");
|
||||
}
|
||||
virtual void visit(AstAssignDly* nodep) override {
|
||||
|
362
src/V3Force.cpp
Normal file
362
src/V3Force.cpp
Normal file
@ -0,0 +1,362 @@
|
||||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Make lookup forces
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
|
||||
// can redistribute it and/or modify it under the terms of either the GNU
|
||||
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||
// Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//*************************************************************************
|
||||
// FORCE TRANSFORMATIONS:
|
||||
// Step 1 (ForceVisitor):
|
||||
// For forced nets, make:
|
||||
// __forceon - enable bitmask of what bits are forced
|
||||
// __forcein - value being forced
|
||||
// __preforce - value before force is applied
|
||||
//
|
||||
// Force sets the appropriate __forceon bits indicating a force is in
|
||||
// effect using the value in __forcein. Release clears the
|
||||
// appropriate __forceon bits.
|
||||
//
|
||||
// IEEE says that procedural assignments "hold" the forced value even
|
||||
// after a release, so add an assignment to the original __preforce too.
|
||||
//
|
||||
// Tristates can't be forced, that would need a __forceen and makes a
|
||||
// large mess, so just error out.
|
||||
//
|
||||
// Step 2 (ForceReplaceVisitor): (If any forces made)
|
||||
//
|
||||
// Replace any VarRef's to a forced signal to instead go to the
|
||||
// reconsiled signal.
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include "V3Global.h"
|
||||
#include "V3Force.h"
|
||||
#include "V3Simulate.h"
|
||||
#include "V3Stats.h"
|
||||
#include "V3Ast.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
//######################################################################
|
||||
// Force shared state
|
||||
|
||||
class ForceBaseVisitor VL_NOT_FINAL : public AstNVisitor {
|
||||
// TYPES
|
||||
public:
|
||||
// Enum value must correspond to which user#p is used
|
||||
enum class FVar : uint8_t { FORCEON = 2, FORCEIN = 3, PREFORCE = 4 };
|
||||
|
||||
private:
|
||||
// NODE STATE
|
||||
// Ast*::user1 -> bool - processed
|
||||
// AstVar::user1 -> bool - created here
|
||||
// AstVarScope::user1 -> bool - created here
|
||||
// AstVar::user2p -> AstVar* pointer to __forceon
|
||||
// AstVarScope::user2p -> AstVarScope* pointer to __forceon
|
||||
// AstVar::user3p -> AstVar* pointer to __forcein
|
||||
// AstVarScope::user3p -> AstVarScope* pointer to __forcein
|
||||
// AstVar::user4p -> AstVar* pointer to __preforce
|
||||
// AstVarScope::user4p -> AstVarScope* pointer to __preforce
|
||||
// Uses are in ForceVisitor
|
||||
|
||||
public:
|
||||
VL_DEBUG_FUNC; // Declare debug()
|
||||
|
||||
static string fvarName(FVar fvar) {
|
||||
switch (fvar) {
|
||||
case FVar::FORCEON: return "__forceon";
|
||||
case FVar::FORCEIN: return "__forcein";
|
||||
case FVar::PREFORCE: return "__preforce";
|
||||
}
|
||||
v3fatalSrc("bad case");
|
||||
return "";
|
||||
}
|
||||
static AstVar* getForceVarNull(const AstVar* const nodep, FVar fvar) {
|
||||
// E.g. trying to make a __perforce__FOO would be bad
|
||||
UASSERT_OBJ(!nodep->user1(), nodep, "lookup on var that Force made itself");
|
||||
return VN_AS(nodep->usernp(static_cast<int>(fvar)), Var);
|
||||
}
|
||||
static AstVar* getForceVar(AstVar* const nodep, FVar fvar) {
|
||||
AstVar* const foundp = getForceVarNull(nodep, fvar);
|
||||
if (foundp) return foundp;
|
||||
if (nodep->isPrimaryIO()) {
|
||||
nodep->v3warn(
|
||||
E_UNSUPPORTED,
|
||||
"Unsupported: Force/Release on primary input/output net "
|
||||
<< nodep->prettyNameQ() << "\n"
|
||||
<< nodep->warnMore()
|
||||
<< "... Suggest assign it to/from a temporary net and force/release that");
|
||||
}
|
||||
auto* const newp = new AstVar{nodep->fileline(), AstVarType::MODULETEMP,
|
||||
nodep->name() + fvarName(fvar), nodep};
|
||||
newp->user1(true);
|
||||
UINFO(9, "getForceVar for " << nodep << endl);
|
||||
UINFO(9, "getForceVar new " << newp << endl);
|
||||
nodep->addNextHere(newp);
|
||||
nodep->usernp(static_cast<int>(fvar), newp);
|
||||
return newp;
|
||||
}
|
||||
static AstVarScope* getForceVscNull(const AstVarScope* const nodep, FVar fvar) {
|
||||
// E.g. trying to make a __perforce__FOO would be bad
|
||||
UASSERT_OBJ(!nodep->user1(), nodep, "lookup on varscope that Force made itself");
|
||||
return VN_AS(nodep->usernp(static_cast<int>(fvar)), VarScope);
|
||||
}
|
||||
static AstVarScope* getForceVsc(AstVarScope* const nodep, FVar fvar) {
|
||||
AstVarScope* const foundp = getForceVscNull(nodep, fvar);
|
||||
if (foundp) return foundp;
|
||||
FileLine* const fl_nowarn = new FileLine{nodep->fileline()};
|
||||
fl_nowarn->warnOff(V3ErrorCode::BLKANDNBLK, true);
|
||||
auto* const newp
|
||||
= new AstVarScope{fl_nowarn, nodep->scopep(), getForceVar(nodep->varp(), fvar)};
|
||||
newp->user1(true);
|
||||
UINFO(9, "getForceVsc for " << nodep << endl);
|
||||
UINFO(9, "getForceVsc new " << newp << endl);
|
||||
nodep->addNextHere(newp);
|
||||
nodep->usernp(static_cast<int>(fvar), newp);
|
||||
return newp;
|
||||
}
|
||||
static AstVarRef* makeVarRef(AstNodeVarRef* nodep, FVar fvar, VAccess access) {
|
||||
return new AstVarRef{nodep->fileline(), getForceVsc(nodep->varScopep(), fvar), access};
|
||||
}
|
||||
static AstNode* makeForcingEquation(AstNodeVarRef* nodep) {
|
||||
// Forcing: out = ((__forceon & __forcein) | (~__forceon & __preforce))
|
||||
UINFO(9, "makeForcingEquation for " << nodep << endl);
|
||||
FileLine* const fl = nodep->fileline();
|
||||
AstNode* const orp = new AstOr{
|
||||
fl,
|
||||
new AstAnd{fl, makeVarRef(nodep, FVar::FORCEON, VAccess::READ),
|
||||
makeVarRef(nodep, FVar::FORCEIN, VAccess::READ)},
|
||||
new AstAnd{fl, new AstNot{fl, makeVarRef(nodep, FVar::FORCEON, VAccess::READ)},
|
||||
makeVarRef(nodep, FVar::PREFORCE, VAccess::READ)}};
|
||||
return orp;
|
||||
}
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Recurse left-hand-side variables to do replaces underneath a force or release
|
||||
|
||||
class ForceLhsVisitor final : public ForceBaseVisitor {
|
||||
private:
|
||||
// STATE
|
||||
FVar const m_fvar; // Which variable to replace with
|
||||
AstNodeVarRef* m_releaseVarRefp = nullptr; // Left hand side variable under release
|
||||
|
||||
virtual void visit(AstNodeVarRef* nodep) override {
|
||||
if (nodep->user1()) return;
|
||||
if (nodep->access().isWriteOrRW()) {
|
||||
if (m_releaseVarRefp) {
|
||||
nodep->v3error("Multiple variables forced in single statement: "
|
||||
<< m_releaseVarRefp->prettyNameQ() << ", "
|
||||
<< nodep->varScopep()->prettyNameQ());
|
||||
return;
|
||||
}
|
||||
m_releaseVarRefp = nodep;
|
||||
AstNode* const newp = makeVarRef(nodep, m_fvar, VAccess::WRITE);
|
||||
newp->user1(true);
|
||||
nodep->replaceWith(newp);
|
||||
pushDeletep(nodep);
|
||||
}
|
||||
}
|
||||
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
explicit ForceLhsVisitor(AstNode* nodep, FVar fvar)
|
||||
: m_fvar(fvar) {
|
||||
iterate(nodep);
|
||||
}
|
||||
virtual ~ForceLhsVisitor() override = default;
|
||||
// METHODS
|
||||
AstNodeVarRef* releaseVarRefp() const { return m_releaseVarRefp; }
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Force class functions
|
||||
|
||||
class ForceVisitor final : public ForceBaseVisitor {
|
||||
private:
|
||||
// NODE STATE
|
||||
// See ForceBaseVisitor
|
||||
const AstUser1InUse m_inuser1;
|
||||
const AstUser2InUse m_inuser2;
|
||||
const AstUser3InUse m_inuser3;
|
||||
const AstUser4InUse m_inuser4;
|
||||
|
||||
// STATE
|
||||
bool m_anyForce = false; // Any force, need reconciliation step
|
||||
VDouble0 m_statForces; // stat tracking
|
||||
|
||||
std::deque<AstAssignForce*> m_forces; // Pointer to found forces
|
||||
std::deque<AstAssignRelease*> m_releases; // Pointer to found releases
|
||||
|
||||
virtual void visit(AstAssignForce* nodep) override { m_forces.push_back(nodep); }
|
||||
void visitEarlierForce(AstAssignForce* nodep) {
|
||||
if (nodep->user1SetOnce()) return;
|
||||
if (debug() >= 9) nodep->dumpTree(cout, "-force-i- ");
|
||||
++m_statForces;
|
||||
m_anyForce = true;
|
||||
// For force/release, duplicate assignment to make
|
||||
// AssignForce __forceon = '1
|
||||
// AssignForce __forcein = {value}
|
||||
// and clone LHS's node tree to handle appropriate extractions
|
||||
{ // __forceon = '1
|
||||
AstNodeAssign* const newp = nodep->cloneTree(false);
|
||||
pushDeletep(newp->rhsp()->unlinkFrBack());
|
||||
V3Number num{nodep, nodep->width()};
|
||||
num.setAllBits1();
|
||||
newp->rhsp(new AstConst{nodep->fileline(), num});
|
||||
{ ForceLhsVisitor{newp->lhsp(), FVar::FORCEON}; }
|
||||
newp->user1(true); // Don't process it again
|
||||
nodep->addNextHere(newp);
|
||||
if (debug() >= 9) newp->dumpTree(cout, "-force-fo- ");
|
||||
}
|
||||
{ // Edit to create assignment to have VarRef that refers to __forceon
|
||||
{ ForceLhsVisitor{nodep->lhsp(), FVar::FORCEIN}; }
|
||||
nodep->user1(true); // Don't process it again
|
||||
if (debug() >= 9) nodep->dumpTree(cout, "-force-fi- ");
|
||||
}
|
||||
}
|
||||
virtual void visit(AstAssignRelease* nodep) override { m_releases.push_back(nodep); }
|
||||
void visitEarlierRelease(AstAssignRelease* nodep) {
|
||||
if (nodep->user1SetOnce()) return;
|
||||
if (debug() >= 9) nodep->dumpTree(cout, "-release-i- ");
|
||||
// RHS is not relevant, so no iterate
|
||||
// For release, edit assignment to make
|
||||
// AssignRelease __forceon = `0
|
||||
// we already have 0's on RHS, were made when AstNode created
|
||||
// Create assignment to have VarRef that refers to __forceon
|
||||
ForceLhsVisitor fvisitor{nodep->lhsp(), FVar::FORCEON};
|
||||
// releaseVarRefp might be deleted when ForceLhsVisitor destructs, so
|
||||
// keep ForceLhsVisitor in scope for now
|
||||
AstNodeVarRef* const releaseVarRefp = fvisitor.releaseVarRefp();
|
||||
UASSERT_OBJ(releaseVarRefp, nodep, "No LHS variable found under release");
|
||||
if (!getForceVscNull(releaseVarRefp->varScopep(), FVar::FORCEIN)) {
|
||||
UINFO(9, "Deleting release of variable that's never forced: " << nodep << endl);
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
return;
|
||||
}
|
||||
if (!releaseVarRefp->varp()->isContinuously()) {
|
||||
// Create assignment __out == _forceen so when release happens value sticks
|
||||
// See IEEE - a strange historical language artifact
|
||||
UINFO(9, "force var is procedural " << releaseVarRefp->varScopep() << endl);
|
||||
FileLine* const fl_nowarn = new FileLine{nodep->fileline()};
|
||||
fl_nowarn->warnOff(V3ErrorCode::BLKANDNBLK, true);
|
||||
AstNodeAssign* const newp = new AstAssignRelease{
|
||||
fl_nowarn, makeVarRef(releaseVarRefp, FVar::PREFORCE, VAccess::WRITE),
|
||||
makeForcingEquation(releaseVarRefp)};
|
||||
newp->user1(true); // Don't process it again
|
||||
nodep->addHereThisAsNext(newp); // Must go before change forceon
|
||||
if (debug() >= 9) newp->dumpTree(cout, "-release-rp- ");
|
||||
}
|
||||
if (debug() >= 9) nodep->dumpTree(cout, "-release-ro- ");
|
||||
}
|
||||
|
||||
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
explicit ForceVisitor(AstNetlist* nodep) {
|
||||
iterate(nodep);
|
||||
// Now that we know procedural markers in user5, do forces
|
||||
for (auto* const nodep : m_forces) visitEarlierForce(nodep);
|
||||
m_forces.clear(); // As dangling pointers now
|
||||
// Do releases after all forces are processed, so we can just
|
||||
// ignore any release with no corresponding force
|
||||
for (auto* const nodep : m_releases) visitEarlierRelease(nodep);
|
||||
m_releases.clear(); // As dangling pointers now
|
||||
}
|
||||
virtual ~ForceVisitor() override { //
|
||||
V3Stats::addStat("Tristate, Forces", m_statForces);
|
||||
}
|
||||
// METHODS
|
||||
bool anyForce() const { return m_anyForce; }
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Force class functions
|
||||
|
||||
class ForceReplace final : public ForceBaseVisitor {
|
||||
// This extra complete-netlist visit could be avoided by recording all
|
||||
// AstVarRefs to every AstVar, but that's a lot of data structure
|
||||
// building, faster to read-only iterate.
|
||||
// As we only care about VarRef's we use direct recusion rather than a visitor
|
||||
|
||||
private:
|
||||
void visitVarRef(AstNodeVarRef* nodep) {
|
||||
if (nodep->varScopep()->user2p()) {
|
||||
if (nodep->access().isRW()) {
|
||||
nodep->v3warn(E_UNSUPPORTED,
|
||||
"Unsupported: forced variable used in read-modify-write context");
|
||||
} else if (nodep->access().isWriteOrRW()) {
|
||||
UINFO(9, " changeRecurse-WR-replace " << nodep << endl);
|
||||
AstNode* const newp = makeVarRef(nodep, FVar::PREFORCE, VAccess::WRITE);
|
||||
newp->user1(true);
|
||||
nodep->replaceWith(newp);
|
||||
pushDeletep(nodep);
|
||||
return;
|
||||
} else if (nodep->access().isReadOrRW()) {
|
||||
// We build forcing equation on each usage rather than making
|
||||
// a variable otherwise we wouldn't know where between a statement
|
||||
// that sets a preforce and uses a forced to insert the proposed
|
||||
// assignment
|
||||
UINFO(9, " changeRecurse-RD-replace " << nodep << endl);
|
||||
AstNode* const newp = makeForcingEquation(nodep);
|
||||
newp->user1(true);
|
||||
nodep->replaceWith(newp);
|
||||
pushDeletep(nodep);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void changeRecurse(AstNode* nodep) {
|
||||
// Recurse and replace any VarRef WRITEs to refer to the force equation
|
||||
if (VL_LIKELY(!nodep->user1())) { // Else processed already
|
||||
if (auto* const varrefp = VN_CAST(nodep, NodeVarRef)) {
|
||||
visitVarRef(varrefp);
|
||||
return; // Might have been edited -- and has no children so ok to exit
|
||||
}
|
||||
}
|
||||
nodep->prefetch();
|
||||
if (nodep->op1p()) changeRecurse(nodep->op1p());
|
||||
if (nodep->op2p()) changeRecurse(nodep->op2p());
|
||||
if (nodep->op3p()) changeRecurse(nodep->op3p());
|
||||
if (nodep->op4p()) changeRecurse(nodep->op4p());
|
||||
if (nodep->nextp()) changeRecurse(nodep->nextp());
|
||||
}
|
||||
|
||||
virtual void visit(AstNode* nodep) override { v3error("Unused"); } // LCOV_EXCL_LINE
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
explicit ForceReplace(AstNetlist* nodep) { changeRecurse(nodep); }
|
||||
virtual ~ForceReplace() override = default;
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Force class functions
|
||||
|
||||
void V3Force::forceAll(AstNetlist* nodep) {
|
||||
UINFO(2, __FUNCTION__ << ": " << endl);
|
||||
{ // Destruct before final check
|
||||
ForceVisitor visitor{nodep};
|
||||
if (visitor.anyForce()) {
|
||||
V3Global::dumpCheckGlobalTree("force-mid", 0,
|
||||
v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
|
||||
ForceReplace{nodep};
|
||||
}
|
||||
}
|
||||
V3Global::dumpCheckGlobalTree("force", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
|
||||
}
|
34
src/V3Force.h
Normal file
34
src/V3Force.h
Normal file
@ -0,0 +1,34 @@
|
||||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Process force/release
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2022 by Wilson Snyder. This program is free software; you
|
||||
// can redistribute it and/or modify it under the terms of either the GNU
|
||||
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||
// Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
|
||||
//*************************************************************************
|
||||
|
||||
#ifndef VERILATOR_V3FORCE_H_
|
||||
#define VERILATOR_V3FORCE_H_
|
||||
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include "V3Error.h"
|
||||
#include "V3Ast.h"
|
||||
|
||||
//============================================================================
|
||||
|
||||
class V3Force final {
|
||||
public:
|
||||
static void forceAll(AstNetlist* nodep);
|
||||
};
|
||||
|
||||
#endif // Guard
|
@ -103,6 +103,7 @@ class V3Global final {
|
||||
// Experimenting with always requiring heavy, see (#2701)
|
||||
bool m_needTraceDumper = false; // Need __Vm_dumperp in symbols
|
||||
bool m_dpi = false; // Need __Dpi include files
|
||||
bool m_useForce = false; // Need force/release processing
|
||||
bool m_useParallelBuild = false; // Use parallel build for model
|
||||
bool m_useRandomizeMethods = false; // Need to define randomize() class methods
|
||||
|
||||
@ -150,6 +151,8 @@ public:
|
||||
UASSERT(!m_hierPlanp, "call once");
|
||||
m_hierPlanp = plan;
|
||||
}
|
||||
void useForce(bool flag) { m_useForce = flag; }
|
||||
bool useForce() const { return m_useForce; }
|
||||
void useParallelBuild(bool flag) { m_useParallelBuild = flag; }
|
||||
bool useParallelBuild() const { return m_useParallelBuild; }
|
||||
void useRandomizeMethods(bool flag) { m_useRandomizeMethods = flag; }
|
||||
|
@ -35,6 +35,7 @@ private:
|
||||
// NODE STATE
|
||||
|
||||
// STATE
|
||||
bool m_setContinuously = false; // Set that var has some continuous assignment
|
||||
VAccess m_setRefLvalue; // Set VarRefs to lvalues for pin assignments
|
||||
AstNodeFTask* m_ftaskp = nullptr; // Function or task we're inside
|
||||
|
||||
@ -47,6 +48,9 @@ private:
|
||||
// VarRef: LValue its reference
|
||||
if (m_setRefLvalue != VAccess::NOCHANGE) nodep->access(m_setRefLvalue);
|
||||
if (nodep->varp()) {
|
||||
if (nodep->access().isWriteOrRW() && m_setContinuously) {
|
||||
nodep->varp()->isContinuously(true);
|
||||
}
|
||||
if (nodep->access().isWriteOrRW() && !m_ftaskp && nodep->varp()->isReadOnly()) {
|
||||
nodep->v3warn(ASSIGNIN,
|
||||
"Assigning to input/const variable: " << nodep->prettyNameQ());
|
||||
@ -69,10 +73,13 @@ private:
|
||||
}
|
||||
virtual void visit(AstNodeAssign* nodep) override {
|
||||
VL_RESTORER(m_setRefLvalue);
|
||||
VL_RESTORER(m_setContinuously);
|
||||
{
|
||||
m_setRefLvalue = VAccess::WRITE;
|
||||
m_setContinuously = VN_IS(nodep, AssignW) || VN_IS(nodep, AssignAlias);
|
||||
iterateAndNextNull(nodep->lhsp());
|
||||
m_setRefLvalue = VAccess::NOCHANGE;
|
||||
m_setContinuously = false;
|
||||
iterateAndNextNull(nodep->rhsp());
|
||||
}
|
||||
}
|
||||
|
@ -753,7 +753,9 @@ private:
|
||||
virtual void visit(AstNodeAssign* nodep) override {
|
||||
if (jumpingOver(nodep)) return;
|
||||
if (!optimizable()) return; // Accelerate
|
||||
if (VN_IS(nodep, AssignDly)) {
|
||||
if (VN_IS(nodep, AssignForce)) {
|
||||
clearOptimizable(nodep, "Force");
|
||||
} else if (VN_IS(nodep, AssignDly)) {
|
||||
if (m_anyAssignComb) clearOptimizable(nodep, "Mix of dly/non-dly assigns");
|
||||
m_anyAssignDly = true;
|
||||
m_inDlyAssign = true;
|
||||
|
@ -51,6 +51,7 @@
|
||||
#include "V3EmitXml.h"
|
||||
#include "V3Expand.h"
|
||||
#include "V3File.h"
|
||||
#include "V3Force.h"
|
||||
#include "V3Gate.h"
|
||||
#include "V3GenClk.h"
|
||||
#include "V3Graph.h"
|
||||
@ -301,6 +302,10 @@ static void process() {
|
||||
// After V3Task so task internal variables will get renamed
|
||||
V3Name::nameAll(v3Global.rootp());
|
||||
|
||||
// Process force/releases if there are any
|
||||
// After flattening, before Life optimizations
|
||||
if (v3Global.useForce()) V3Force::forceAll(v3Global.rootp());
|
||||
|
||||
// Loop unrolling & convert FORs to WHILEs
|
||||
V3Unroll::unrollAll(v3Global.rootp());
|
||||
|
||||
|
@ -3090,9 +3090,9 @@ statement_item<nodep>: // IEEE: statement_item
|
||||
| yDEASSIGN variable_lvalue ';'
|
||||
{ $$ = nullptr; BBUNSUP($1, "Unsupported: Verilog 1995 deassign"); }
|
||||
| yFORCE expr '=' expr ';'
|
||||
{ $$ = nullptr; BBUNSUP($1, "Unsupported: Verilog 1995 force"); }
|
||||
{ $$ = new AstAssignForce{$1, $2, $4}; }
|
||||
| yRELEASE variable_lvalue ';'
|
||||
{ $$ = nullptr; BBUNSUP($1, "Unsupported: Verilog 1995 release"); }
|
||||
{ $$ = new AstAssignRelease{$1, VFlagChildDType{}, $2}; }
|
||||
//
|
||||
// // IEEE: case_statement
|
||||
| unique_priorityE caseStart caseAttrE case_itemListE yENDCASE { $$ = $2; if ($4) $2->addItemsp($4);
|
||||
|
@ -1,26 +0,0 @@
|
||||
%Error-UNSUPPORTED: t/t_force.v:25:7: Unsupported: Verilog 1995 force
|
||||
25 | force bus[1:0] = 2'b10;
|
||||
| ^~~~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_force.v:29:7: Unsupported: Verilog 1995 release
|
||||
29 | release bus;
|
||||
| ^~~~~~~
|
||||
%Error-UNSUPPORTED: t/t_force.v:43:10: Unsupported: Verilog 1995 force
|
||||
43 | force never_driven = 32'h888;
|
||||
| ^~~~~
|
||||
%Error-UNSUPPORTED: t/t_force.v:56:10: Unsupported: Verilog 1995 release
|
||||
56 | release never_forced;
|
||||
| ^~~~~~~
|
||||
%Error-UNSUPPORTED: t/t_force.v:65:10: Unsupported: Verilog 1995 force
|
||||
65 | force bus = 4'b0111;
|
||||
| ^~~~~
|
||||
%Error-UNSUPPORTED: t/t_force.v:69:10: Unsupported: Verilog 1995 force
|
||||
69 | force bus = 4'b1111;
|
||||
| ^~~~~
|
||||
%Error-UNSUPPORTED: t/t_force.v:73:10: Unsupported: Verilog 1995 release
|
||||
73 | release bus;
|
||||
| ^~~~~~~
|
||||
%Error-UNSUPPORTED: t/t_force.v:85:10: Unsupported: Verilog 1995 release
|
||||
85 | release bus[0];
|
||||
| ^~~~~~~
|
||||
%Error: Exiting due to
|
@ -11,13 +11,11 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
fails => $Self->{vlt_all},
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
) if !$Self->{vlt_all};
|
||||
);
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
@ -1,5 +1,6 @@
|
||||
%Error-UNSUPPORTED: t/t_force_mid.v:29:10: Unsupported: Verilog 1995 force
|
||||
29 | force tried = 4'b1010;
|
||||
| ^~~~~
|
||||
%Error-UNSUPPORTED: t/t_force_mid.v:18:17: Unsupported: Force/Release on primary input/output net 'tried'
|
||||
: ... Suggest assign it to/from a temporary net and force/release that
|
||||
18 | output [3:0] tried;
|
||||
| ^~~~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error: Exiting due to
|
||||
|
@ -10,14 +10,10 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||
|
||||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
lint(
|
||||
fails => $Self->{vlt_all},
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
) if !$Self->{vlt_all};
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
@ -8,13 +8,14 @@
|
||||
`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 t(/*AUTOARG*/
|
||||
// Inouts
|
||||
// Outputs
|
||||
tried,
|
||||
// Inputs
|
||||
clk
|
||||
);
|
||||
|
||||
input clk;
|
||||
inout tri [3:0] tried;
|
||||
output [3:0] tried;
|
||||
|
||||
integer cyc = 0;
|
||||
|
||||
|
21
test_regress/t/t_force_multi.pl
Executable file
21
test_regress/t/t_force_multi.pl
Executable file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env perl
|
||||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2021 by Wilson Snyder. This program is free software; you
|
||||
# can redistribute it and/or modify it under the terms of either the GNU
|
||||
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
);
|
||||
|
||||
ok(1);
|
||||
1;
|
42
test_regress/t/t_force_multi.v
Normal file
42
test_regress/t/t_force_multi.v
Normal file
@ -0,0 +1,42 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2021 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 t(/*AUTOARG*/
|
||||
// Inputs
|
||||
clk
|
||||
);
|
||||
input clk;
|
||||
|
||||
integer cyc = 0;
|
||||
|
||||
logic [3:0] busa;
|
||||
logic [3:0] busb;
|
||||
|
||||
// Test loop
|
||||
always @ (posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 0) begin
|
||||
busa <= 4'b0101;
|
||||
busb <= 4'b0111;
|
||||
end
|
||||
else if (cyc == 1) begin
|
||||
force {busa, busb} = 8'b1111_1101;
|
||||
end
|
||||
else if (cyc == 2) begin
|
||||
`checkh(busa, 4'b1111);
|
||||
`checkh(busb, 4'b1101);
|
||||
end
|
||||
//
|
||||
else if (cyc == 99) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
@ -1,11 +0,0 @@
|
||||
%Error-UNSUPPORTED: t/t_force_subnet.v:27:10: Unsupported: Verilog 1995 force
|
||||
27 | force sub1.subnet = 8'h00;
|
||||
| ^~~~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_force_subnet.v:31:10: Unsupported: Verilog 1995 force
|
||||
31 | force subnet = 8'h10;
|
||||
| ^~~~~
|
||||
%Error-UNSUPPORTED: t/t_force_subnet.v:35:10: Unsupported: Verilog 1995 release
|
||||
35 | release subnet;
|
||||
| ^~~~~~~
|
||||
%Error: Exiting due to
|
@ -11,13 +11,11 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
fails => $Self->{vlt_all},
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
) if !$Self->{vlt_all};
|
||||
);
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
@ -15,19 +15,18 @@ module t(/*AUTOARG*/
|
||||
|
||||
integer cyc = 0;
|
||||
|
||||
tri logic [7:0] subnet;
|
||||
logic [7:0] subnet;
|
||||
sub1 sub1(.*);
|
||||
sub2 sub2(.*);
|
||||
|
||||
// Test loop
|
||||
always @ (posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 10) begin
|
||||
`checkh(subnet, 8'h11);
|
||||
force sub1.subnet = 8'h00; // sub1.subnet same as subnet
|
||||
force sub1.subnet = 8'h01; // sub1.subnet same as subnet
|
||||
end
|
||||
else if (cyc == 11) begin
|
||||
`checkh(subnet, 8'h00);
|
||||
`checkh(subnet, 8'h01);
|
||||
force subnet = 8'h10; // sub1.subnet same as subnet
|
||||
end
|
||||
else if (cyc == 12) begin
|
||||
@ -46,11 +45,6 @@ module t(/*AUTOARG*/
|
||||
|
||||
endmodule
|
||||
|
||||
module sub1(inout logic [7:0] subnet);
|
||||
assign subnet = 8'hz1;
|
||||
module sub1(output logic [7:0] subnet);
|
||||
assign subnet = 8'h11;
|
||||
endmodule
|
||||
|
||||
module sub2(inout logic [7:0] subnet);
|
||||
assign subnet = 8'h1z;
|
||||
endmodule
|
||||
|
||||
|
@ -1,8 +0,0 @@
|
||||
%Error-UNSUPPORTED: t/t_force_subvar.v:26:10: Unsupported: Verilog 1995 force
|
||||
26 | force sub.subvar = 32'hffff;
|
||||
| ^~~~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_force_subvar.v:36:10: Unsupported: Verilog 1995 release
|
||||
36 | release sub.subvar;
|
||||
| ^~~~~~~
|
||||
%Error: Exiting due to
|
@ -11,13 +11,11 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
fails => $Self->{vlt_all},
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
) if !$Self->{vlt_all};
|
||||
);
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
@ -1,4 +1,5 @@
|
||||
%Error-UNSUPPORTED: t/t_force_tri.v:27:10: Unsupported: Verilog 1995 force
|
||||
%Error-UNSUPPORTED: t/t_force_tri.v:27:10: Unsupported tristate construct: ASSIGNFORCE
|
||||
: ... In instance t
|
||||
27 | force bus = 4'bzz10;
|
||||
| ^~~~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
|
@ -10,14 +10,10 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||
|
||||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
lint(
|
||||
fails => $Self->{vlt_all},
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
) if !$Self->{vlt_all};
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
Loading…
Reference in New Issue
Block a user