diff --git a/docs/guide/languages.rst b/docs/guide/languages.rst index 969d1986c..23df4cbd6 100644 --- a/docs/guide/languages.rst +++ b/docs/guide/languages.rst @@ -451,17 +451,14 @@ disable force, release Verilator supports the procedural `force` (and corresponding `release`) statement. However, the behavior of the `force` statement does not - entirely comply with IEEE 1800. According to the standard, when a - procedural statement of the form `force a = b;` is executed, the - simulation should behave as if, from that point forwards, a continuous - assignment `assign a = b;` has been added to override the drivers of `a`. - More specifically: the value of `a` should be updated whenever the value - of `b` changes, until a `release a;` statement is executed. Verilator - instead evaluates the current value of `b` when the `force` statement is - executed, and forces `a` to that value, without updating it until a new - `force` or `release` statement is encountered that applies to `a`. This - non-standard behavior is nevertheless consistent with some other - simulators. + entirely comply with IEEE 1800-2023: + 1. Using forced variable as a value to another force statement is currently + not supported. The dependant force statement is forced by an initial + constant value. + 2. Force/release with procedural continuous assignment is not supported. + Assignment is treated as a procedural one. + 3. Expressions using multiple variable references or function calls on + forced right-hand side are not sensitive to dependency changes. inside Inside expressions may not include unpacked array traversal or $ as an diff --git a/src/V3AstUserAllocator.h b/src/V3AstUserAllocator.h index ec7e0cd0b..7ef2a5611 100644 --- a/src/V3AstUserAllocator.h +++ b/src/V3AstUserAllocator.h @@ -99,7 +99,7 @@ public: } // Get a pointer to the user data if exists, otherwise nullptr - T_Data* tryGet(const T_Node* nodep) { return getUserp(nodep); } + T_Data* tryGet(const T_Node* nodep) const { return getUserp(nodep); } void clear() { m_allocated.clear(); } }; diff --git a/src/V3Force.cpp b/src/V3Force.cpp index ee6b4dbb9..7be65fd53 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -19,22 +19,27 @@ // add 3 extra signals: // - __VforceRd: a net with same type as signal // - __VforceEn: a var with same type as signal, which is the bitwise force enable -// - __VforceVl: a var with same type as signal, which is the forced value +// - __VforceVal: a var with same type as signal, which is the forced value // add an initial statement: // initial __VforceEn = 0; // add a continuous assignment: -// assign __VforceRd = __VforceEn ? __VforceVl : ; +// assign __VforceRd = __VforceEn ? __VforceVal : ; // replace all READ references to with a read reference to _VforceRd // // Replace each AstAssignForce with 3 assignments: // - __VforceEn = 1 -// - __VforceVl = +// - __VforceVal = // - __VforceRd = // // Replace each AstRelease with 1 or 2 assignments: // - __VforceEn = 0 // - __VforceRd = // iff lhs is a net // +// After each WRITE of forced LHS +// reevaluate __VforceRd to support immediate force/release +// +// After each WRITE of forced RHS +// reevaluate __VforceVal to support VarRef rollback after release //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT @@ -48,7 +53,7 @@ VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### // Convert force/release statements and signals marked 'forceable' -class ForceConvertVisitor final : public VNVisitor { +class ForceState final { // TYPES struct ForceComponentsVar final { AstVar* const m_rdVarp; // New variable to replace read references with @@ -59,22 +64,24 @@ class ForceConvertVisitor final : public VNVisitor { varp->dtypep()}} , m_valVarp{new AstVar{varp->fileline(), VVarType::VAR, varp->name() + "__VforceVal", varp->dtypep()}} - , m_enVarp{new AstVar{varp->fileline(), VVarType::VAR, varp->name() + "__VforceEn", - (isRangedDType(varp) ? varp->dtypep() : varp->findBitDType())}} { + , m_enVarp{new AstVar{ + varp->fileline(), VVarType::VAR, varp->name() + "__VforceEn", + (ForceState::isRangedDType(varp) ? varp->dtypep() : varp->findBitDType())}} { m_rdVarp->addNext(m_enVarp); m_rdVarp->addNext(m_valVarp); varp->addNextHere(m_rdVarp); } }; +public: struct ForceComponentsVarScope final { AstVarScope* const m_rdVscp; // New variable to replace read references with - AstVarScope* const m_enVscp; // Force enabled signal AstVarScope* const m_valVscp; // Forced value + AstVarScope* const m_enVscp; // Force enabled signal explicit ForceComponentsVarScope(AstVarScope* vscp, ForceComponentsVar& fcv) : m_rdVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_rdVarp}} - , m_enVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_enVarp}} - , m_valVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_valVarp}} { + , m_valVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_valVarp}} + , m_enVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_enVarp}} { m_rdVscp->addNext(m_enVscp); m_rdVscp->addNext(m_valVscp); vscp->addNextHere(m_rdVscp); @@ -89,64 +96,106 @@ class ForceConvertVisitor final : public VNVisitor { AstAssign* const assignp = new AstAssign{flp, lhsp, rhsp}; AstActive* const activep = new AstActive{ flp, "force-init", - new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Initial{}}}}; + new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Static{}}}}; activep->sensesStorep(activep->sensesp()); + activep->addStmtsp(new AstInitial{flp, assignp}); vscp->scopep()->addBlocksp(activep); } - { // Add the combinational override AstVarRef* const lhsp = new AstVarRef{flp, m_rdVscp, VAccess::WRITE}; - AstVarRef* const origp = new AstVarRef{flp, vscp, VAccess::READ}; - origp->user2(1); // Don't replace this read ref with the read signal - AstNodeExpr* rhsp; - if (isRangedDType(vscp)) { - rhsp = new AstOr{ - flp, - new AstAnd{flp, new AstVarRef{flp, m_enVscp, VAccess::READ}, - new AstVarRef{flp, m_valVscp, VAccess::READ}}, - new AstAnd{flp, - new AstNot{flp, new AstVarRef{flp, m_enVscp, VAccess::READ}}, - origp}}; - } else { - rhsp = new AstCond{flp, new AstVarRef{flp, m_enVscp, VAccess::READ}, - new AstVarRef{flp, m_valVscp, VAccess::READ}, origp}; - } + AstNodeExpr* const rhsp = forcedUpdate(vscp); + // Explicitly list dependencies for update. + // Note: rdVscp is also needed to retrigger assignment for the first time. + AstSenItem* const itemsp = new AstSenItem{ + flp, VEdgeType::ET_CHANGED, new AstVarRef{flp, m_rdVscp, VAccess::READ}}; + itemsp->addNext(new AstSenItem{flp, VEdgeType::ET_CHANGED, + new AstVarRef{flp, m_valVscp, VAccess::READ}}); + itemsp->addNext(new AstSenItem{flp, VEdgeType::ET_CHANGED, + new AstVarRef{flp, m_enVscp, VAccess::READ}}); + AstVarRef* const origp = new AstVarRef{flp, vscp, VAccess::READ}; + ForceState::markNonReplaceable(origp); + itemsp->addNext( + new AstSenItem{flp, VEdgeType::ET_CHANGED, origp->cloneTree(false)}); AstActive* const activep - = new AstActive{flp, "force-comb", - new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Combo{}}}}; + = new AstActive{flp, "force-update", new AstSenTree{flp, itemsp}}; activep->sensesStorep(activep->sensesp()); - activep->addStmtsp(new AstAssignW{flp, lhsp, rhsp}); + activep->addStmtsp(new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, + new AstAssign{flp, lhsp, rhsp}}); vscp->scopep()->addBlocksp(activep); } } + AstNodeExpr* forcedUpdate(AstVarScope* const vscp) const { + FileLine* const flp = vscp->fileline(); + AstVarRef* const origp = new AstVarRef{flp, vscp, VAccess::READ}; + ForceState::markNonReplaceable(origp); + if (ForceState::isRangedDType(vscp)) { + return new AstOr{ + flp, + new AstAnd{flp, new AstVarRef{flp, m_enVscp, VAccess::READ}, + new AstVarRef{flp, m_valVscp, VAccess::READ}}, + new AstAnd{flp, new AstNot{flp, new AstVarRef{flp, m_enVscp, VAccess::READ}}, + origp}}; + } + return new AstCond{flp, new AstVarRef{flp, m_enVscp, VAccess::READ}, + new AstVarRef{flp, m_valVscp, VAccess::READ}, origp}; + } }; +private: // NODE STATE // AstVar::user1p -> ForceComponentsVar* instance (via m_forceComponentsVar) // AstVarScope::user1p -> ForceComponentsVarScope* instance (via m_forceComponentsVarScope) // AstVarRef::user2 -> Flag indicating not to replace reference + // AstVarScope::user3 -> AstVarScope*, a `valVscp` force component for each VarScope of + // forced RHS const VNUser1InUse m_user1InUse; const VNUser2InUse m_user2InUse; + const VNUser3InUse m_user3InUse; AstUser1Allocator m_forceComponentsVar; AstUser1Allocator m_forceComponentsVarScope; - // METHODS +public: + // CONSTRUCTORS + ForceState() = default; + VL_UNCOPYABLE(ForceState); + + // STATIC METHODS static bool isRangedDType(AstNode* nodep) { // If ranged we need a multibit enable to support bit-by-bit part-select forces, // otherwise forcing a real or other opaque dtype and need a single bit enable. const AstBasicDType* const basicp = nodep->dtypep()->skipRefp()->basicp(); return basicp && basicp->isRanged(); } + static bool isNotReplaceable(const AstVarRef* const nodep) { return nodep->user2(); } + static void markNonReplaceable(AstVarRef* const nodep) { nodep->user2SetOnce(); } + static AstVarScope* getValVscp(AstVarRef* const refp) { + return VN_CAST(refp->varScopep()->user3p(), VarScope); + } + static void setValVscp(AstNodeVarRef* const refp, AstVarScope* const vscp) { + refp->varScopep()->user3p(vscp); + } + + // METHODS const ForceComponentsVarScope& getForceComponents(AstVarScope* vscp) { AstVar* const varp = vscp->varp(); return m_forceComponentsVarScope(vscp, vscp, m_forceComponentsVar(varp, varp)); } + ForceComponentsVarScope* tryGetForceComponents(AstVarRef* nodep) const { + return m_forceComponentsVarScope.tryGet(nodep->varScopep()); + } +}; +class ForceConvertVisitor final : public VNVisitor { + // STATE + ForceState& m_state; + + // STATIC METHODS // Replace each AstNodeVarRef in the given 'nodep' that writes a variable by transforming the // referenced AstVarScope with the given function. - void transformWritenVarScopes(AstNode* nodep, std::function f) { + static void transformWritenVarScopes(AstNode* nodep, + std::function f) { UASSERT_OBJ(nodep->backp(), nodep, "Must have backp, otherwise will be lost if replaced"); nodep->foreach([&f](AstNodeVarRef* refp) { if (refp->access() != VAccess::WRITE) return; @@ -159,7 +208,7 @@ class ForceConvertVisitor final : public VNVisitor { }); } - // VISIT methods + // VISITORS void visit(AstNode* nodep) override { iterateChildren(nodep); } void visit(AstAssignForce* nodep) override { @@ -172,24 +221,29 @@ class ForceConvertVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep), nodep); // Set corresponding enable signals to ones - V3Number ones{lhsp, isRangedDType(lhsp) ? lhsp->width() : 1}; + V3Number ones{lhsp, ForceState::isRangedDType(lhsp) ? lhsp->width() : 1}; ones.setAllBits1(); AstAssign* const setEnp = new AstAssign{flp, lhsp->cloneTreePure(false), new AstConst{rhsp->fileline(), ones}}; transformWritenVarScopes(setEnp->lhsp(), [this](AstVarScope* vscp) { - return getForceComponents(vscp).m_enVscp; + return m_state.getForceComponents(vscp).m_enVscp; }); // Set corresponding value signals to the forced value AstAssign* const setValp = new AstAssign{flp, lhsp->cloneTreePure(false), rhsp->cloneTreePure(false)}; - transformWritenVarScopes(setValp->lhsp(), [this](AstVarScope* vscp) { - return getForceComponents(vscp).m_valVscp; + transformWritenVarScopes(setValp->lhsp(), [this, rhsp](AstVarScope* vscp) { + AstVarScope* const valVscp = m_state.getForceComponents(vscp).m_valVscp; + // TODO support multiple VarRefs on RHS + if (AstVarRef* const refp = VN_CAST(rhsp, VarRef)) + ForceState::setValVscp(refp, valVscp); + return valVscp; }); - // Set corresponding read signal directly as well, in case something in the same process - // reads it later + + // Set corresponding read signal directly as well, in case something in the same + // process reads it later AstAssign* const setRdp = new AstAssign{flp, lhsp->unlinkFrBack(), rhsp->unlinkFrBack()}; transformWritenVarScopes(setRdp->lhsp(), [this](AstVarScope* vscp) { - return getForceComponents(vscp).m_rdVscp; + return m_state.getForceComponents(vscp).m_rdVscp; }); setEnp->addNext(setValp); @@ -206,12 +260,12 @@ class ForceConvertVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep), nodep); // Set corresponding enable signals to zero - V3Number zero{lhsp, isRangedDType(lhsp) ? lhsp->width() : 1}; + V3Number zero{lhsp, ForceState::isRangedDType(lhsp) ? lhsp->width() : 1}; zero.setAllBits0(); AstAssign* const resetEnp = new AstAssign{flp, lhsp->cloneTreePure(false), new AstConst{lhsp->fileline(), zero}}; transformWritenVarScopes(resetEnp->lhsp(), [this](AstVarScope* vscp) { - return getForceComponents(vscp).m_enVscp; + return m_state.getForceComponents(vscp).m_enVscp; }); // IEEE 1800-2023 10.6.2: If this is a net, and not a variable, then reset the read // signal directly as well, in case something in the same process reads it later. Also, if @@ -226,8 +280,9 @@ class ForceConvertVisitor final : public VNVisitor { resetRdp->lhsp()->foreach([this](AstNodeVarRef* refp) { if (refp->access() != VAccess::WRITE) return; AstVarScope* const vscp = refp->varScopep(); - AstVarScope* const newVscp - = vscp->varp()->isContinuously() ? getForceComponents(vscp).m_rdVscp : vscp; + AstVarScope* const newVscp = vscp->varp()->isContinuously() + ? m_state.getForceComponents(vscp).m_rdVscp + : vscp; // Disable BLKANDNBLK for this reference FileLine* const flp = new FileLine{refp->fileline()}; flp->warnOff(V3ErrorCode::BLKANDNBLK, true); @@ -239,28 +294,14 @@ class ForceConvertVisitor final : public VNVisitor { resetRdp->rhsp()->foreach([this](AstNodeVarRef* refp) { if (refp->access() != VAccess::WRITE) return; AstVarScope* const vscp = refp->varScopep(); - FileLine* const flp = new FileLine{refp->fileline()}; - AstVarRef* const newpRefp = new AstVarRef{refp->fileline(), vscp, VAccess::READ}; - newpRefp->user2(1); // Don't replace this read ref with the read signal if (vscp->varp()->isContinuously()) { + AstVarRef* const newpRefp = new AstVarRef{refp->fileline(), vscp, VAccess::READ}; + ForceState::markNonReplaceable(newpRefp); refp->replaceWith(newpRefp); - } else if (isRangedDType(vscp)) { - refp->replaceWith(new AstOr{ - flp, - new AstAnd{ - flp, new AstVarRef{flp, getForceComponents(vscp).m_enVscp, VAccess::READ}, - new AstVarRef{flp, getForceComponents(vscp).m_valVscp, VAccess::READ}}, - new AstAnd{ - flp, - new AstNot{flp, new AstVarRef{flp, getForceComponents(vscp).m_enVscp, - VAccess::READ}}, - newpRefp}}); } else { - refp->replaceWith(new AstCond{ - flp, new AstVarRef{flp, getForceComponents(vscp).m_enVscp, VAccess::READ}, - new AstVarRef{flp, getForceComponents(vscp).m_valVscp, VAccess::READ}, - newpRefp}); + refp->replaceWith(m_state.getForceComponents(vscp).forcedUpdate(vscp)); } + VL_DO_DANGLING(refp->deleteTree(), refp); }); @@ -271,51 +312,108 @@ class ForceConvertVisitor final : public VNVisitor { void visit(AstVarScope* nodep) override { // If this signal is marked externally forceable, create the public force signals if (nodep->varp()->isForceable()) { - const ForceComponentsVarScope& fc = getForceComponents(nodep); + const ForceState::ForceComponentsVarScope& fc = m_state.getForceComponents(nodep); fc.m_enVscp->varp()->sigUserRWPublic(true); fc.m_valVscp->varp()->sigUserRWPublic(true); } } +public: // CONSTRUCTOR - explicit ForceConvertVisitor(AstNetlist* nodep) { + explicit ForceConvertVisitor(AstNetlist* nodep, ForceState& state) + : m_state{state} { // Transform all force and release statements iterateAndNextNull(nodep->modulesp()); - - // Replace references to forced signals - nodep->modulesp()->foreachAndNext([this](AstVarRef* nodep) { - if (ForceComponentsVarScope* const fcp - = m_forceComponentsVarScope.tryGet(nodep->varScopep())) { - switch (nodep->access()) { - case VAccess::READ: - // Read references replaced to read the new, possibly forced signal - if (!nodep->user2()) { - nodep->varp(fcp->m_rdVscp->varp()); - nodep->varScopep(fcp->m_rdVscp); - } - break; - case VAccess::WRITE: - // Write references use the original signal - break; - default: - nodep->v3error( - "Unsupported: Signals used via read-write reference cannot be forced"); - break; - } - } - }); } - -public: - static void apply(AstNetlist* nodep) { ForceConvertVisitor{nodep}; } }; +class ForceReplaceVisitor final : public VNVisitor { + // STATE + const ForceState& m_state; + AstNodeStmt* m_stmtp = nullptr; + bool m_inLogic = false; + + // METHODS + void iterateLogic(AstNode* logicp) { + VL_RESTORER(m_inLogic); + m_inLogic = true; + iterateChildren(logicp); + } + + // VISITORS + void visit(AstNodeStmt* nodep) override { + VL_RESTORER(m_stmtp); + m_stmtp = nodep; + iterateChildren(nodep); + } + void visit(AstAlwaysPublic* nodep) override { iterateLogic(nodep); } + void visit(AstCFunc* nodep) override { iterateLogic(nodep); } + void visit(AstCoverToggle* nodep) override { iterateLogic(nodep); } + void visit(AstNodeProcedure* nodep) override { iterateLogic(nodep); } + void visit(AstSenItem* nodep) override { iterateLogic(nodep); } + void visit(AstVarRef* nodep) override { + if (ForceState::isNotReplaceable(nodep)) return; + + switch (nodep->access()) { + case VAccess::READ: { + // Replace VarRef from forced LHS with rdVscp. + if (ForceState::ForceComponentsVarScope* const fcp + = m_state.tryGetForceComponents(nodep)) { + FileLine* const flp = nodep->fileline(); + AstVarRef* const origp = new AstVarRef{flp, nodep->varScopep(), VAccess::READ}; + ForceState::markNonReplaceable(origp); + nodep->varp(fcp->m_rdVscp->varp()); + nodep->varScopep(fcp->m_rdVscp); + } + break; + } + case VAccess::WRITE: { + if (!m_inLogic) return; + // Emit rdVscp update after each write to any VarRef on forced LHS. + if (ForceState::ForceComponentsVarScope* const fcp + = m_state.tryGetForceComponents(nodep)) { + FileLine* const flp = nodep->fileline(); + AstVarRef* const lhsp = new AstVarRef{flp, fcp->m_rdVscp, VAccess::WRITE}; + AstNodeExpr* const rhsp = fcp->forcedUpdate(nodep->varScopep()); + m_stmtp->addNextHere(new AstAssign{flp, lhsp, rhsp}); + } + // Emit valVscp update after each write to any VarRef on forced RHS. + if (AstVarScope* const valVscp = ForceState::getValVscp(nodep)) { + FileLine* const flp = nodep->fileline(); + AstVarRef* const valp = new AstVarRef{flp, valVscp, VAccess::WRITE}; + AstVarRef* const rhsp = new AstVarRef{flp, nodep->varScopep(), VAccess::READ}; + + ForceState::markNonReplaceable(valp); + ForceState::markNonReplaceable(rhsp); + + m_stmtp->addNextHere(new AstAssign{flp, valp, rhsp}); + } + break; + } + default: + nodep->v3error("Unsupported: Signals used via read-write reference cannot be forced"); + break; + } + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTOR + explicit ForceReplaceVisitor(AstNetlist* nodep, const ForceState& state) + : m_state{state} { + iterateChildren(nodep); + } +}; //###################################################################### // void V3Force::forceAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); if (!v3Global.hasForceableSignals()) return; - ForceConvertVisitor::apply(nodep); - V3Global::dumpCheckGlobalTree("force", 0, dumpTreeEitherLevel() >= 3); + { + ForceState state; + { ForceConvertVisitor{nodep, state}; } + { ForceReplaceVisitor{nodep, state}; } + V3Global::dumpCheckGlobalTree("force", 0, dumpTreeEitherLevel() >= 3); + } } diff --git a/test_regress/t/t_force_chained.out b/test_regress/t/t_force_chained.out new file mode 100644 index 000000000..ddfa52588 --- /dev/null +++ b/test_regress/t/t_force_chained.out @@ -0,0 +1,4 @@ +%Error: t/t_force_chained.v:27: got='h0 exp='h00000001 +%Error: t/t_force_chained.v:33: got='h0 exp='h00000002 +%Error: t/t_force_chained.v:40: got='h0 exp='h00000003 +%Error: t/t_force_chained.v:46: got='h0 exp='h00000003 diff --git a/test_regress/t/t_force_chained.py b/test_regress/t/t_force_chained.py new file mode 100755 index 000000000..61632dd07 --- /dev/null +++ b/test_regress/t/t_force_chained.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--exe", "--main", "--timing"]) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_force_chained.v b/test_regress/t/t_force_chained.v new file mode 100644 index 000000000..9374daf1a --- /dev/null +++ b/test_regress/t/t_force_chained.v @@ -0,0 +1,51 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); end while(0) + +module t; + reg [1:0] a; + wire [1:0] b = 1; + bit [1:0] c; + + initial begin + #1 a = 0; + force b = a; + force c = b; + `checkh(a, 0); + `checkh(b, 0); + `checkh(c, 0); + + a = 1; + #1; + `checkh(a, 1); + `checkh(b, 1); + // TODO implement inter-dependency resolution between force statements + `checkh(c, 1); + + a = 2; + #1; + `checkh(a, 2); + `checkh(b, 2); + `checkh(c, 2); + + a = 3; + c = 3; + #1; + `checkh(a, 3); + `checkh(b, 3); + `checkh(c, 3); + + release b; + release c; + `checkh(a, 3); + `checkh(b, 1); + `checkh(c, 3); + + #1 $finish; + end + +endmodule diff --git a/test_regress/t/t_force_func.out b/test_regress/t/t_force_func.out new file mode 100644 index 000000000..7ecdbb3c0 --- /dev/null +++ b/test_regress/t/t_force_func.out @@ -0,0 +1,4 @@ +%Error: t/t_force_func.v:26: got='h0 exp='h00000001 +%Error: t/t_force_func.v:34: got='h0 exp='h00000002 +%Error: t/t_force_func.v:39: got='h0 exp='h00000003 +%Error: t/t_force_func.v:43: got='h0 exp='h00000003 diff --git a/test_regress/t/t_force_func.py b/test_regress/t/t_force_func.py new file mode 100755 index 000000000..61632dd07 --- /dev/null +++ b/test_regress/t/t_force_func.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--exe", "--main", "--timing"]) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_force_func.v b/test_regress/t/t_force_func.v new file mode 100644 index 000000000..91b019421 --- /dev/null +++ b/test_regress/t/t_force_func.v @@ -0,0 +1,51 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); end while(0) + +function bit [1:0] get_arg (bit [1:0] x); + return x; +endfunction + +module t; + bit [1:0] a; + bit [1:0] b = 1; + + initial begin + #1 a = 0; + force b = get_arg(a); + `checkh(a, 0); + `checkh(b, 0); + + a = 1; + #1; + `checkh(a, 1); + `checkh(b, 1); + + a = 2; + #1; + `checkh(a, 2); + // TODO + // IEEE 1800-2023 10.6 + // Assignment shall be reevaluated while the assign or force is in effect. + `checkh(b, 2); + + a = 3; + #1; + `checkh(a, 3); + `checkh(b, 3); + + release b; + `checkh(a, 3); + `checkh(b, 3); + + b = 2; + `checkh(b, 2); + + #1 $finish; + end + +endmodule diff --git a/test_regress/t/t_force_immediate_release.py b/test_regress/t/t_force_immediate_release.py new file mode 100755 index 000000000..4ff4dfaa5 --- /dev/null +++ b/test_regress/t/t_force_immediate_release.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--exe", "--main", "--timing"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_force_immediate_release.v b/test_regress/t/t_force_immediate_release.v new file mode 100644 index 000000000..ce54f467f --- /dev/null +++ b/test_regress/t/t_force_immediate_release.v @@ -0,0 +1,33 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// 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; + + reg [2:0] a = 3'b000; + + initial begin + a = 3'b001; + `checkh(a, 1); + + force a = 3'b010; + `checkh(a, 2); + + a = 3'b011; + `checkh(a, 2); + + release a; + `checkh(a, 2); + + a = 3'b100; + `checkh(a, 4); + + $finish; + end + +endmodule diff --git a/test_regress/t/t_force_initial.py b/test_regress/t/t_force_initial.py new file mode 100755 index 000000000..4ff4dfaa5 --- /dev/null +++ b/test_regress/t/t_force_initial.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--exe", "--main", "--timing"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_force_initial.v b/test_regress/t/t_force_initial.v new file mode 100644 index 000000000..d909c07ce --- /dev/null +++ b/test_regress/t/t_force_initial.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); end while(0) + +module t; + reg [1:0] a = 0; + reg [1:0] b = 2; + + initial begin + force b = a; + `checkh(a, 0); + `checkh(b, 0); + end + + initial begin + #1; + `checkh(b, 0); + + a = 1; + #1; + `checkh(a, 1); + `checkh(b, 1); + + a = 3; + #1; + `checkh(a, 3); + `checkh(b, 3); + + release b; + `checkh(a, 3); + `checkh(b, 3); + + b = 0; + #1; + `checkh(b, 0); + + #1 $finish; + end + +endmodule diff --git a/test_regress/t/t_force_release.out b/test_regress/t/t_force_release.out new file mode 100644 index 000000000..86850b874 --- /dev/null +++ b/test_regress/t/t_force_release.out @@ -0,0 +1,4 @@ + 0 d=0,e=0 + 10 d=1,e=1 + 20 d=1,e=0 +%Error: t/t_force_release.v:36: got='h1 exp='h00000000 diff --git a/test_regress/t/t_force_release.py b/test_regress/t/t_force_release.py new file mode 100755 index 000000000..61632dd07 --- /dev/null +++ b/test_regress/t/t_force_release.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--exe", "--main", "--timing"]) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_force_release.v b/test_regress/t/t_force_release.v new file mode 100644 index 000000000..6fd13338d --- /dev/null +++ b/test_regress/t/t_force_release.v @@ -0,0 +1,41 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); end while(0) + +// Example from IEEE 1800-2023 10.6.2 + +module t; + logic a, b, c, d; + wire e; + and and1 (e, a, b, c); + + initial begin + $monitor("%d d=%b,e=%b", $stime, d, e); + assign d = a & b & c; + a = 1; + b = 0; + c = 1; + #10; + force d = (a | b | c); + force e = (a | b | c); + `checkh(d, 1); + `checkh(e, 1); + #10; + release d; + release e; + // TODO support procedural continuous assignments. + // + // As per IEEE 1800-2023 10.6.2, value of `d` should be updated + // after release. However, Verilator treats `assign` inside an initial block + // as procedural assign thus value update is not properly restored. + #10; + `checkh(d, 0); + `checkh(e, 0); + + $finish; + end +endmodule diff --git a/test_regress/t/t_force_rhs_ref.py b/test_regress/t/t_force_rhs_ref.py new file mode 100755 index 000000000..4ff4dfaa5 --- /dev/null +++ b/test_regress/t/t_force_rhs_ref.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--exe", "--main", "--timing"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_force_rhs_ref.v b/test_regress/t/t_force_rhs_ref.v new file mode 100644 index 000000000..a6515bec1 --- /dev/null +++ b/test_regress/t/t_force_rhs_ref.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, 2025 by Antmicro. +// 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; + reg [1:0] a; + wire [1:0] b = 1; + + initial begin + #1 a = 0; + force b = a; + `checkh(a, 0); + `checkh(b, 0); + + a = 1; + #1; + `checkh(a, 1); + `checkh(b, 1); + + a = 2; + #1; + `checkh(a, 2); + `checkh(b, 2); + + a = 3; + #1; + `checkh(a, 3); + `checkh(b, 3); + + release b; + `checkh(a, 3); + `checkh(b, 1); + + #1 $finish; + end + +endmodule diff --git a/test_regress/t/t_force_rhs_ref_multiple.out b/test_regress/t/t_force_rhs_ref_multiple.out new file mode 100644 index 000000000..b6245d940 --- /dev/null +++ b/test_regress/t/t_force_rhs_ref_multiple.out @@ -0,0 +1,5 @@ +%Error: t/t_force_rhs_ref_multiple.v:23: got='h0a exp='h00000009 +%Error: t/t_force_rhs_ref_multiple.v:27: got='h0a exp='h00000007 +%Error: t/t_force_rhs_ref_multiple.v:31: got='h0a exp='h00000004 +%Error: t/t_force_rhs_ref_multiple.v:35: got='h0a exp='h00000000 +%Error: t/t_force_rhs_ref_multiple.v:39: got='h0a exp='h00000000 diff --git a/test_regress/t/t_force_rhs_ref_multiple.py b/test_regress/t/t_force_rhs_ref_multiple.py new file mode 100755 index 000000000..61632dd07 --- /dev/null +++ b/test_regress/t/t_force_rhs_ref_multiple.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--exe", "--main", "--timing"]) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_force_rhs_ref_multiple.v b/test_regress/t/t_force_rhs_ref_multiple.v new file mode 100644 index 000000000..ce44a36c8 --- /dev/null +++ b/test_regress/t/t_force_rhs_ref_multiple.v @@ -0,0 +1,45 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); end while(0) + +module t; + logic [7:0] a = 1; + logic [7:0] b = 2; + logic [7:0] c = 3; + logic [7:0] d = 4; + logic [7:0] e = 5; + + initial begin + force e = a + b + c + d; + `checkh(e, 10); + #1; + a = 0; + #1; + // TODO support multiple VarRefs on RHS + `checkh(e, 9); + + b = 0; + #1; + `checkh(e, 7); + + c = 0; + #1; + `checkh(e, 4); + + d = 0; + #1; + `checkh(e, 0); + + release e; + // Not driven, change value after procedural assignment. + `checkh(e, 0); + e = 5; + `checkh(e, 5); + + $finish; + end +endmodule