diff --git a/Changes b/Changes index fa07c1f39..78dbf1789 100644 --- a/Changes +++ b/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] diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index b46c6fd26..01d6340fc 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -197,6 +197,7 @@ RAW_OBJS = \ V3Expand.o \ V3File.o \ V3FileLine.o \ + V3Force.o \ V3Gate.o \ V3GenClk.o \ V3Global.o \ diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index 4f1ad9232..77211d427 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -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 diff --git a/src/V3Ast.h b/src/V3Ast.h index 751bad25c..f8d826ccc 100644 --- a/src/V3Ast.h +++ b/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=##" diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index 388429c3b..1acd086d3 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -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: diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 5c87cf36f..49774cf52 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -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 { diff --git a/src/V3Force.cpp b/src/V3Force.cpp new file mode 100644 index 000000000..123036775 --- /dev/null +++ b/src/V3Force.cpp @@ -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 +#include + +//###################################################################### +// 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(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(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(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(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 m_forces; // Pointer to found forces + std::deque 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); +} diff --git a/src/V3Force.h b/src/V3Force.h new file mode 100644 index 000000000..a4567dbc7 --- /dev/null +++ b/src/V3Force.h @@ -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 diff --git a/src/V3Global.h b/src/V3Global.h index 45bb73ed2..ba9ff302f 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -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; } diff --git a/src/V3LinkLValue.cpp b/src/V3LinkLValue.cpp index 752d01981..a480d10e1 100644 --- a/src/V3LinkLValue.cpp +++ b/src/V3LinkLValue.cpp @@ -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()); } } diff --git a/src/V3Simulate.h b/src/V3Simulate.h index ede20b512..51dc150f0 100644 --- a/src/V3Simulate.h +++ b/src/V3Simulate.h @@ -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; diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 306948c28..8be118c7d 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -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()); diff --git a/src/verilog.y b/src/verilog.y index 3e8be7058..5de0e81c8 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3090,9 +3090,9 @@ statement_item: // 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); diff --git a/test_regress/t/t_force.out b/test_regress/t/t_force.out deleted file mode 100644 index c7bc20017..000000000 --- a/test_regress/t/t_force.out +++ /dev/null @@ -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 diff --git a/test_regress/t/t_force.pl b/test_regress/t/t_force.pl index d9fe84fb9..2cb5eeaff 100755 --- a/test_regress/t/t_force.pl +++ b/test_regress/t/t_force.pl @@ -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; diff --git a/test_regress/t/t_force_mid.out b/test_regress/t/t_force_mid.out index b4c1b9dee..93552b55d 100644 --- a/test_regress/t/t_force_mid.out +++ b/test_regress/t/t_force_mid.out @@ -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 diff --git a/test_regress/t/t_force_mid.pl b/test_regress/t/t_force_mid.pl index be66c40e6..c213189c1 100755 --- a/test_regress/t/t_force_mid.pl +++ b/test_regress/t/t_force_mid.pl @@ -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; diff --git a/test_regress/t/t_force_mid.v b/test_regress/t/t_force_mid.v index 93efb8b45..f9031dd8b 100644 --- a/test_regress/t/t_force_mid.v +++ b/test_regress/t/t_force_mid.v @@ -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; diff --git a/test_regress/t/t_force_multi.pl b/test_regress/t/t_force_multi.pl new file mode 100755 index 000000000..2cb5eeaff --- /dev/null +++ b/test_regress/t/t_force_multi.pl @@ -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; diff --git a/test_regress/t/t_force_multi.v b/test_regress/t/t_force_multi.v new file mode 100644 index 000000000..30e3fbd88 --- /dev/null +++ b/test_regress/t/t_force_multi.v @@ -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 diff --git a/test_regress/t/t_force_subnet.out b/test_regress/t/t_force_subnet.out deleted file mode 100644 index 26b2dab18..000000000 --- a/test_regress/t/t_force_subnet.out +++ /dev/null @@ -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 diff --git a/test_regress/t/t_force_subnet.pl b/test_regress/t/t_force_subnet.pl index d9fe84fb9..2cb5eeaff 100755 --- a/test_regress/t/t_force_subnet.pl +++ b/test_regress/t/t_force_subnet.pl @@ -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; diff --git a/test_regress/t/t_force_subnet.v b/test_regress/t/t_force_subnet.v index 86c2fff4c..0adbf5fc0 100644 --- a/test_regress/t/t_force_subnet.v +++ b/test_regress/t/t_force_subnet.v @@ -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 - diff --git a/test_regress/t/t_force_subvar.out b/test_regress/t/t_force_subvar.out deleted file mode 100644 index c14860005..000000000 --- a/test_regress/t/t_force_subvar.out +++ /dev/null @@ -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 diff --git a/test_regress/t/t_force_subvar.pl b/test_regress/t/t_force_subvar.pl index d9fe84fb9..2cb5eeaff 100755 --- a/test_regress/t/t_force_subvar.pl +++ b/test_regress/t/t_force_subvar.pl @@ -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; diff --git a/test_regress/t/t_force_tri.out b/test_regress/t/t_force_tri.out index 6e4393be9..97b0dd018 100644 --- a/test_regress/t/t_force_tri.out +++ b/test_regress/t/t_force_tri.out @@ -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 diff --git a/test_regress/t/t_force_tri.pl b/test_regress/t/t_force_tri.pl index be66c40e6..c213189c1 100755 --- a/test_regress/t/t_force_tri.pl +++ b/test_regress/t/t_force_tri.pl @@ -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;