diff --git a/Changes b/Changes index a53b5fd16..c400489ba 100644 --- a/Changes +++ b/Changes @@ -49,6 +49,7 @@ Verilator 5.047 devel * Add VPI callback support to --main (#7145). * Add V3LiftExpr pass to lower impure expressions and calls (#7141) (#7164). [Geza Lore, Testorrent USA, Inc.] * Add --func-recursion-depth CLI option (#7175) (#7179). +* Add `+verilator+solver+file` (#7242). * Add MacOS support for address sanitizer memory limit (#7308). [Marco Bartoli] * Deprecate `--structs-packed` (#7222). * Improve assignment-compatibility type check (#2843) (#5666) (#7052). [Pawel Kojma, Antmicro Ltd.] @@ -65,13 +66,16 @@ Verilator 5.047 devel * Optimize DFG peephole until a fixed point (#7309). [Geza Lore, Testorrent USA, Inc.] * Optimize comparisons with identical operands and $countones in DFG. [Geza Lore, Testorrent USA, Inc.] * Optimize more patterns in DfgPeephole (#7332). [Geza Lore, Testorrent USA, Inc.] +* Optimize read references in DFG (#7354). [Geza Lore, Testorrent USA, Inc.] * Fix recursive default assignment for sub-arrays (#4589) (#7202). [Julian Carrier] +* Fix virtual interface member trigger convergence (#5116) (#7323). [Yilou Wang] * Fix shift width mismatch in constraint solver SMT emission (#5420) (#7265). [Yilou Wang] * Fix randomize size+element queue constraints (#5582) (#7225). [Rahul Behl, Testorrent USA, Inc.] * Fix null assignment to virtual interfaces (#5974) (#5990). [Maxim Fonarev] * Fix typedef scope resolution for parameterized class aliases (#5977) (#7319). [Nick Brereton] * Fix lambda coroutines (#6106) (#7135). [Nick Brereton] * Fix super constructor calls with local variables (#6214) (#6933). [Igor Zaworski, Antmicro Ltd.] +* Fix parameter default comparison when value contains type cast (#6281) (#7369) (#6281). [Yilou Wang] * Fix `local::` false error in randomize() with on parameterized class (#6680) (#7293). [Yilou Wang] * Fix false recursive definition error (#6769) (#7118). [Alex Zhou] * Fix port assignment to large arrays (#6904). @@ -85,9 +89,11 @@ Verilator 5.047 devel * Fix wide conditional short circuiting (#7155). * Fix eliminating assignments to DPI-read variables (#7158). [Geza Lore, Testorrent USA, Inc.] * Fix std::randomize() in static function with static class members (#7167) (#7169). [Yilou Wang] +* Fix resolving default/non-default type parameters (#7171) (#7346). [em2machine] * Fix recursive constant function in $unit scope (#7173) (#7174). * Fix class extend references between queues (#7195). * Fix library/hier_block tracing when top name is empty (#7200). [Geza Lore, Testorrent USA, Inc.] +* Fix virtual interface select from sub-interface instance (#7203) (#7370) (#7203). [Yilou Wang] * Fix VPI force of bit-selected signals (#7211) (#7301). [Christian Hecken] * Fix wrong $bits() for parameterized interface struct typedefs (#7218) (#7219). [em2machine] * Fix `dist` operator inside constraint if blocks (#7221) (#7224). [Rahul Behl, Testorrent USA, Inc.] @@ -96,6 +102,7 @@ Verilator 5.047 devel * Fix internal error when derived class calls this.randomize() with inherited rand members (#7229) (#7234). [Yilou Wang] * Fix enum range constraints missing for rand variables in sub-objects (#7230) (#7235). [Yilou Wang] * Fix vpi_put_value release on non-continuous signal (#7231) (#7241). [Christian Hecken] +* Fix functions in generate block resulting in 'Broken link in node' (#7236) (#7367). [em2machine] * Fix tracing of typedefed 1D packed arrays with --trace-structs (#7237). [Geza Lore, Testorrent USA, Inc.] * Fix rand variable used as array index in constraint evaluated as constant (#7238) (#7247). [Yilou Wang] * Fix --hierarchical dropping arguments in -f/-F files (#7240). [Clara Sparks] @@ -113,6 +120,11 @@ Verilator 5.047 devel * Fix lost `$stop` on implied assertion `$error` failures. * Fix wait() hang when interface uses process calls and VIF function (#7342). [Yilou Wang] * Fix error on illegal nand/nor binary operators (#7353). +* Fix simple array assignment unrolling in slice optimization (#7359). [Geza Lore, Testorrent USA, Inc.] +* Fix missing temporary for DfgSplicePacked (#7361). [Geza Lore, Testorrent USA, Inc.] +* Fix virtual interface function calls binding to wrong instance (#7363). [Yilou Wang] +* Fix false ASSIGNIN on interface input port connections (#7365). [Yilou Wang] +* Fix string `inside` queue (#7373). Verilator 5.046 2026-02-28 diff --git a/bin/verilator b/bin/verilator index f85798ce0..8779c7344 100755 --- a/bin/verilator +++ b/bin/verilator @@ -585,6 +585,7 @@ description of these arguments. +verilator+quiet Minimize additional printing +verilator+rand+reset+ Set random reset technique +verilator+seed+ Set random seed + +verilator+solver+file+ Set random solver log filename +verilator+V Show verbose version and config +verilator+version Show version and exit +verilator+wno+unsatconstr+ Disable constraint warnings diff --git a/docs/guide/exe_sim.rst b/docs/guide/exe_sim.rst index fe916c089..7f696073e 100644 --- a/docs/guide/exe_sim.rst +++ b/docs/guide/exe_sim.rst @@ -116,6 +116,12 @@ Options: simulation runtime random seed value. If zero or not specified picks a value from the system random number generator. +.. option:: +verilator+solver+file+ + + If specified, when the randomization solver is used, open the given + filename for writing, and log all random solver commands and responses + to it. + .. option:: +verilator+V Shows the verbose version, including configuration information. diff --git a/include/verilated.cpp b/include/verilated.cpp index 922385126..16f8a6cee 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -3001,6 +3001,14 @@ std::string VerilatedContext::profVltFilename() const VL_MT_SAFE { const VerilatedLockGuard lock{m_mutex}; return m_ns.m_profVltFilename; } +void VerilatedContext::solverLogFilename(const std::string& flag) VL_MT_SAFE { + const VerilatedLockGuard lock{m_mutex}; + m_ns.m_solverLogFilename = flag; +} +std::string VerilatedContext::solverLogFilename() const VL_MT_SAFE { + const VerilatedLockGuard lock{m_mutex}; + return m_ns.m_solverLogFilename; +} void VerilatedContext::solverProgram(const std::string& flag) VL_MT_SAFE { const VerilatedLockGuard lock{m_mutex}; m_ns.m_solverProgram = flag; @@ -3017,6 +3025,12 @@ void VerilatedContext::randReset(int val) VL_MT_SAFE { const VerilatedLockGuard lock{m_mutex}; m_s.m_randReset = val; } + +std::string VerilatedContext::timeWithUnitString() const VL_MT_SAFE { + const double simtimeInUnits = VL_TIME_Q() * vl_time_multiplier(timeunit()) + * vl_time_multiplier(timeprecision() - timeunit()); + return vl_timescaled_double(simtimeInUnits); +} void VerilatedContext::timeunit(int value) VL_MT_SAFE { if (value < 0) value = -value; // Stored as 0..15 const VerilatedLockGuard lock{m_mutex}; @@ -3233,6 +3247,8 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) { quiet(true); } else if (commandArgVlUint64(arg, "+verilator+rand+reset+", u64, 0, 2)) { randReset(static_cast(u64)); + } else if (commandArgVlString(arg, "+verilator+solver+file+", str)) { + solverLogFilename(str); } else if (commandArgVlUint64(arg, "+verilator+wno+unsatconstr+", u64, 0, 1)) { warnUnsatConstr(u64 == 0); // wno means disable, so invert } else if (commandArgVlUint64(arg, "+verilator+seed+", u64, 1, @@ -3326,7 +3342,7 @@ void VerilatedContext::statsPrintSummary() VL_MT_UNSAFE { const std::string endwhy = gotError() ? "$stop" : gotFinish() ? "$finish" : "end"; const double simtimeInUnits = VL_TIME_Q() * vl_time_multiplier(timeunit()) * vl_time_multiplier(timeprecision() - timeunit()); - const std::string simtime = vl_timescaled_double(simtimeInUnits); + const std::string simtime = timeWithUnitString(); const double walltime = statWallTimeSinceStart(); const double cputime = statCpuTimeSinceStart(); const std::string simtimePerf diff --git a/include/verilated.h b/include/verilated.h index b7f70bda7..b1c0bce15 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -415,6 +415,7 @@ protected: std::string m_coverageFilename; // +coverage+file filename std::string m_profExecFilename; // +prof+exec+file filename std::string m_profVltFilename; // +prof+vlt filename + std::string m_solverLogFilename; // SMT solver log filename std::string m_solverProgram; // SMT solver program bool m_warnUnsatConstr = true; // Warn on unsatisfied constraints VlOs::DeltaCpuTime m_cpuTimeStart{false}; // CPU time, starts when create first model @@ -586,6 +587,8 @@ public: void time(uint64_t value) VL_MT_SAFE { m_s.m_time = value; } /// Advance current simulation time. See time() for side effect details void timeInc(uint64_t add) VL_MT_UNSAFE { m_s.m_time += add; } + /// Return time as unit string + std::string timeWithUnitString() const VL_MT_SAFE; /// Return time units as power-of-ten int timeunit() const VL_MT_SAFE { return -m_s.m_timeunit; } /// Set time units as power-of-ten @@ -666,6 +669,9 @@ public: std::string profVltFilename() const VL_MT_SAFE; void profVltFilename(const std::string& flag) VL_MT_SAFE; + // Internal: Solver log filename + std::string solverLogFilename() const VL_MT_SAFE; + void solverLogFilename(const std::string& flag) VL_MT_SAFE; // Internal: SMT solver program std::string solverProgram() const VL_MT_SAFE; void solverProgram(const std::string& flag) VL_MT_SAFE; diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index 4aded2610..a9d519266 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -23,6 +23,7 @@ #include "verilated_random.h" +#include #include #include #include @@ -61,6 +62,9 @@ class VlRProcess final : private std::streambuf, public std::iostream { char m_readBuf[BUFFER_SIZE]; char m_writeBuf[BUFFER_SIZE]; + std::unique_ptr m_logfp; // Log file stream + uint64_t m_logLastTime = ~0ULL; // Last timestamp for logfile + public: typedef std::streambuf::traits_type traits_type; @@ -69,8 +73,8 @@ protected: const char c2 = static_cast(c); if (pbase() == pptr()) return 0; const size_t size = pptr() - pbase(); + log(" ", std::string(pbase(), size)); const ssize_t n = ::write(m_writeFd, pbase(), size); - // VL_PRINTF_MT("solver-write '%s'\n", std::string(pbase(), size).c_str()); if (VL_UNLIKELY(n == -1)) perror("write"); if (n <= 0) { wait_report(); @@ -91,6 +95,7 @@ protected: wait_report(); return traits_type::eof(); } + log("< ", std::string(m_readBuf, n)); setg(m_readBuf, m_readBuf, m_readBuf + n); return traits_type::to_int_type(m_readBuf[0]); } @@ -104,6 +109,7 @@ public: : std::streambuf{} , std::iostream{this} , m_cmd{cmd} { + logOpen(); open(cmd); } @@ -175,6 +181,7 @@ public: return false; } + log("", "# Open: "s + cmd[0]); const pid_t pid = fork(); if (VL_UNLIKELY(pid < 0)) { perror("VlRProcess::open: fork"); @@ -212,6 +219,35 @@ public: return false; #endif } + +private: + void logOpen() { + const std::string filename = Verilated::threadContextp()->solverLogFilename(); + if (filename.empty()) return; + m_logfp = std::make_unique(filename); + if (m_logfp.get() && m_logfp.get()->fail()) m_logfp = nullptr; + if (!m_logfp) { + const std::string msg = "%Error: Can't write '"s + filename + "'"; + VL_FATAL_MT("", 0, "", msg.c_str()); + return; + } + *m_logfp << "# Verilator solver log\n"; + } + void log(const std::string& prefix, const std::string& text) { + if (VL_LIKELY(!m_logfp.get()) || text.empty()) return; + if (m_logLastTime != Verilated::threadContextp()->time()) { + m_logLastTime = Verilated::threadContextp()->time(); + *m_logfp << "# [" << Verilated::threadContextp()->timeWithUnitString() << "]\n"; + } + std::size_t startPos = 0; + while (1) { + const std::size_t endPos = text.find('\n', startPos); + if (endPos == std::string::npos) break; + *m_logfp << prefix << text.substr(startPos, endPos - startPos) << '\n'; + startPos = endPos + 1; + } + if (startPos < text.length()) *m_logfp << prefix << text.substr(startPos) << '\n'; + } }; static VlRProcess& getSolver() { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d03324eed..8393ec3f8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -132,6 +132,7 @@ set(HEADERS V3LinkLevel.h V3LinkParse.h V3LinkResolve.h + V3LinkWith.h V3List.h V3Localize.h V3MemberMap.h @@ -306,6 +307,7 @@ set(COMMON_SOURCES V3LinkLevel.cpp V3LinkParse.cpp V3LinkResolve.cpp + V3LinkWith.cpp V3Localize.cpp V3MergeCond.cpp V3Name.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 855e0835f..00ae0273c 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -300,6 +300,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3LinkLevel.o \ V3LinkParse.o \ V3LinkResolve.o \ + V3LinkWith.o \ V3Localize.o \ V3MergeCond.o \ V3Name.o \ diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index a1f4583e5..c14618faa 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -474,6 +474,9 @@ private: AstEventControl* const controlp = new AstEventControl{ nodep->fileline(), new AstSenTree{flp, sensesp->cloneTree(false)}, nullptr}; const std::string delayName = m_cycleDlyNames.get(nodep); + AstNodeExpr* throughoutp + = nodep->throughoutp() ? nodep->throughoutp()->unlinkFrBack() : nullptr; + AstVar* const cntVarp = new AstVar{flp, VVarType::BLOCKTEMP, delayName + "__counter", nodep->findBasicDType(VBasicDTypeKwd::UINT32)}; cntVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); @@ -481,24 +484,71 @@ private: AstBegin* const beginp = new AstBegin{flp, delayName + "__block", cntVarp, true}; beginp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, valuep}); + // Throughout: create flag tracking whether condition held every tick + AstVar* throughoutOkp = nullptr; + if (throughoutp) { + throughoutOkp = new AstVar{flp, VVarType::BLOCKTEMP, delayName + "__throughoutOk", + nodep->findBasicDType(VBasicDTypeKwd::LOGIC_IMPLICIT)}; + throughoutOkp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + beginp->addStmtsp(throughoutOkp); + beginp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, throughoutOkp, VAccess::WRITE}, + new AstConst{flp, AstConst::BitTrue{}}}); + // Check condition at tick 0 (sequence start, before entering loop) + AstSampled* const initSampledp + = new AstSampled{flp, throughoutp->cloneTreePure(false)}; + initSampledp->dtypeSetBit(); + beginp->addStmtsp( + new AstIf{flp, new AstLogNot{flp, initSampledp}, + new AstAssign{flp, new AstVarRef{flp, throughoutOkp, VAccess::WRITE}, + new AstConst{flp, AstConst::BitFalse{}}}}); + } + { AstLoop* const loopp = new AstLoop{flp}; - loopp->addStmtsp(new AstLoopTest{ - flp, loopp, - new AstGt{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 0}}}); + // When throughout is present, exit loop early if condition fails + AstNodeExpr* loopCondp + = new AstGt{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 0}}; + if (throughoutOkp) { + loopCondp = new AstLogAnd{flp, loopCondp, + new AstVarRef{flp, throughoutOkp, VAccess::READ}}; + } + loopp->addStmtsp(new AstLoopTest{flp, loopp, loopCondp}); loopp->addStmtsp(controlp); loopp->addStmtsp( new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, new AstSub{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 1}}}); + // Check throughout condition at each tick during delay (IEEE 1800-2023 16.9.9) + if (throughoutp) { + AstSampled* const sampledp = new AstSampled{flp, throughoutp}; + sampledp->dtypeSetBit(); + loopp->addStmtsp( + new AstIf{flp, new AstLogNot{flp, sampledp}, + new AstAssign{flp, new AstVarRef{flp, throughoutOkp, VAccess::WRITE}, + new AstConst{flp, AstConst::BitFalse{}}}}); + } beginp->addStmtsp(loopp); } - if (m_disableSeqIfp) { + // Compose wrappers on remaining sequence: throughout gate (inner), disable iff (outer) + AstNode* remainp = nodep->nextp() ? nodep->nextp()->unlinkFrBackWithNext() : nullptr; + if (throughoutOkp) { + // If condition failed during delay, fail assertion + remainp = new AstIf{flp, new AstVarRef{flp, throughoutOkp, VAccess::READ}, remainp, + new AstPExprClause{flp, /*pass=*/false}}; + } + if (m_disableSeqIfp && remainp) { AstIf* const disableSeqIfp = m_disableSeqIfp->cloneTree(false); - AstNode* const continuationsp = nodep->nextp()->unlinkFrBackWithNext(); // Keep continuation statements in a proper statement-list container. - disableSeqIfp->addThensp(new AstBegin{flp, "", continuationsp, true}); - nodep->addNextHere(disableSeqIfp); + disableSeqIfp->addThensp(new AstBegin{flp, "", remainp, true}); + remainp = disableSeqIfp; + } + if (remainp) { + if (throughoutOkp) { + // throughoutOkp is declared in beginp scope -- check must be inside it + beginp->addStmtsp(remainp); + } else { + nodep->addNextHere(remainp); + } } nodep->replaceWith(beginp); VL_DO_DANGLING(nodep->deleteTree(), nodep); @@ -843,7 +893,7 @@ private: return new AstPExpr{flp, beginp, exprp->findBitDType()}; } - void visit(AstSExprGotoRep* nodep) override { + void visit(AstSGotoRep* nodep) override { // Standalone goto rep (not inside implication antecedent) iterateChildren(nodep); FileLine* const flp = nodep->fileline(); @@ -870,8 +920,8 @@ private: if (nodep->sentreep()) return; // Already processed // Handle goto repetition as antecedent before iterateChildren, - // so the standalone AstSExprGotoRep visitor doesn't process it - if (AstSExprGotoRep* const gotop = VN_CAST(nodep->lhsp(), SExprGotoRep)) { + // so the standalone AstSGotoRep visitor doesn't process it + if (AstSGotoRep* const gotop = VN_CAST(nodep->lhsp(), SGotoRep)) { iterateChildren(gotop); iterateAndNextNull(nodep->rhsp()); FileLine* const flp = nodep->fileline(); diff --git a/src/V3AssertProp.cpp b/src/V3AssertProp.cpp index cd5173917..2416a55b1 100644 --- a/src/V3AssertProp.cpp +++ b/src/V3AssertProp.cpp @@ -650,6 +650,45 @@ class AssertPropLowerVisitor final : public VNVisitor { VL_DO_DANGLING(nodep->deleteTree(), nodep); } } + void visit(AstSThroughout* nodep) override { + // IEEE 1800-2023 16.9.9: expr throughout seq + // Transform by AND-ing cond with every leaf expression in the sequence, + // and attaching cond to every delay for per-tick checking in V3AssertPre. + AstNodeExpr* const condp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const seqp = nodep->rhsp()->unlinkFrBack(); + if (AstSExpr* const sexprp = VN_CAST(seqp, SExpr)) { + // Walk all SExpr nodes: AND cond with leaf expressions, attach to delays + sexprp->foreach([&](AstSExpr* sp) { + if (sp->exprp() && !VN_IS(sp->exprp(), SExpr)) { + AstNodeExpr* const origp = sp->exprp()->unlinkFrBack(); + AstLogAnd* const andp + = new AstLogAnd{origp->fileline(), condp->cloneTreePure(false), origp}; + andp->dtypeSetBit(); + sp->exprp(andp); + } + if (sp->preExprp() && !VN_IS(sp->preExprp(), SExpr)) { + AstNodeExpr* const origp = sp->preExprp()->unlinkFrBack(); + AstLogAnd* const andp + = new AstLogAnd{origp->fileline(), condp->cloneTreePure(false), origp}; + andp->dtypeSetBit(); + sp->preExprp(andp); + } + if (AstDelay* const dlyp = VN_CAST(sp->delayp(), Delay)) { + dlyp->throughoutp(condp->cloneTreePure(false)); + } + }); + nodep->replaceWith(sexprp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + VL_DO_DANGLING(condp->deleteTree(), condp); + visit(sexprp); + } else { + // Single expression (no delay): degenerate to cond && seq + AstLogAnd* const andp = new AstLogAnd{nodep->fileline(), condp, seqp}; + andp->dtypeSetBit(); + nodep->replaceWith(andp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } + } void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); m_modp = nodep; @@ -829,45 +868,69 @@ class RangeDelayExpander final : public VNVisitor { struct SeqStep final { AstNodeExpr* exprp; // Expression to check (nullptr if unary leading delay) int delay; // Fixed delay after this expression (0 for tail) - bool isRange; // Whether this step's delay is a range + bool isRange; // Step's delay is a range + bool isUnbounded; // Range is unbounded (rhs is AstUnbounded) int rangeMin; - int rangeMax; + int rangeMax; // -1 for unbounded }; // Extract delay bounds from AstDelay. Clones and constifies (does not modify original AST). - bool extractDelayBounds(AstDelay* dlyp, bool& isRange, int& minVal, int& maxVal) { + // For unbounded ranges (rhs is AstUnbounded), maxVal is set to -1; rhsp is not constified. + bool extractDelayBounds(AstDelay* dlyp, bool& isRange, bool& isUnbounded, int& minVal, + int& maxVal) { isRange = dlyp->isRangeDelay(); + isUnbounded = dlyp->isUnbounded(); AstNodeExpr* const minExprp = V3Const::constifyEdit(dlyp->lhsp()->cloneTree(false)); const AstConst* const minConstp = VN_CAST(minExprp, Const); if (isRange) { - AstNodeExpr* const maxExprp = V3Const::constifyEdit(dlyp->rhsp()->cloneTree(false)); - const AstConst* const maxConstp = VN_CAST(maxExprp, Const); - if (!minConstp || !maxConstp) { - dlyp->v3error("Range delay bounds must be elaboration-time constants" - " (IEEE 1800-2023 16.7)"); + if (isUnbounded) { + // ##[M:$], ##[*], ##[+]: only min bound; max is open-ended + if (!minConstp) { + dlyp->v3error("Range delay minimum must be an elaboration-time constant" + " (IEEE 1800-2023 16.7)"); + VL_DO_DANGLING(minExprp->deleteTree(), minExprp); + return false; + } + minVal = minConstp->toSInt(); + maxVal = -1; + VL_DO_DANGLING(minExprp->deleteTree(), minExprp); + if (minVal < 0) { + dlyp->v3error("Range delay bounds must be non-negative" + " (IEEE 1800-2023 16.7)"); + return false; + } + } else { + AstNodeExpr* const maxExprp + = V3Const::constifyEdit(dlyp->rhsp()->cloneTree(false)); + const AstConst* const maxConstp = VN_CAST(maxExprp, Const); + if (!minConstp || !maxConstp) { + dlyp->v3error("Range delay bounds must be elaboration-time constants" + " (IEEE 1800-2023 16.7)"); + VL_DO_DANGLING(minExprp->deleteTree(), minExprp); + VL_DO_DANGLING(maxExprp->deleteTree(), maxExprp); + return false; + } + minVal = minConstp->toSInt(); + maxVal = maxConstp->toSInt(); VL_DO_DANGLING(minExprp->deleteTree(), minExprp); VL_DO_DANGLING(maxExprp->deleteTree(), maxExprp); - return false; - } - minVal = minConstp->toSInt(); - maxVal = maxConstp->toSInt(); - VL_DO_DANGLING(minExprp->deleteTree(), minExprp); - VL_DO_DANGLING(maxExprp->deleteTree(), maxExprp); - if (minVal < 0 || maxVal < 0) { - dlyp->v3error("Range delay bounds must be non-negative" - " (IEEE 1800-2023 16.7)"); - return false; - } - if (maxVal < minVal) { - dlyp->v3error("Range delay maximum must be >= minimum" - " (IEEE 1800-2023 16.7)"); - return false; - } - if (minVal == 0) { - dlyp->v3warn(E_UNSUPPORTED, "Unsupported: ##0 in range delays"); - return false; + if (minVal < 0 || maxVal < 0) { + dlyp->v3error("Range delay bounds must be non-negative" + " (IEEE 1800-2023 16.7)"); + return false; + } + if (maxVal < minVal) { + dlyp->v3error("Range delay maximum must be >= minimum" + " (IEEE 1800-2023 16.7)"); + return false; + } + if (minVal == 0) { + dlyp->v3warn(E_UNSUPPORTED, "Unsupported: ##0 in bounded range delays"); + return false; + } } } else { + isUnbounded = false; minVal = maxVal = minConstp ? minConstp->toSInt() : 0; VL_DO_DANGLING(minExprp->deleteTree(), minExprp); } @@ -891,127 +954,164 @@ class RangeDelayExpander final : public VNVisitor { AstDelay* const dlyp = VN_CAST(curp->delayp(), Delay); UASSERT_OBJ(dlyp, curp, "Expected AstDelay"); bool isRange = false; + bool isUnbounded = false; int minVal = 0; int maxVal = 0; - if (!extractDelayBounds(dlyp, isRange, minVal, maxVal)) return false; + if (!extractDelayBounds(dlyp, isRange, isUnbounded, minVal, maxVal)) return false; if (isRange) hasRange = true; if (curp->preExprp() && !VN_IS(curp->preExprp(), SExpr)) { - steps.push_back({curp->preExprp(), minVal, isRange, minVal, maxVal}); + steps.push_back({curp->preExprp(), minVal, isRange, isUnbounded, minVal, maxVal}); } else { - steps.push_back({nullptr, minVal, isRange, minVal, maxVal}); + steps.push_back({nullptr, minVal, isRange, isUnbounded, minVal, maxVal}); } if (AstSExpr* const nextp = VN_CAST(curp->exprp(), SExpr)) { return linearizeImpl(nextp, steps, hasRange); } - steps.push_back({curp->exprp(), 0, false, 0, 0}); + steps.push_back({curp->exprp(), 0, false, false, 0, 0}); return true; } - // Build FSM body as if/else chain on state variable. - // State 0 = IDLE. Each range delay adds 2 states (wait + check), - // each fixed delay adds 1 (wait), each tail expr adds 1 (check). - // - // Example: a ##[M:N] b ##1 c - // steps: [{a, range[M:N]}, {b, delay=1}, {c, delay=0}] - // State 1: WAIT_MIN (count down M cycles) - // State 2: CHECK_RANGE (check b each cycle, up to N-M retries) - // State 3: WAIT_FIXED (count down 1 cycle for ##1) - // State 4: CHECK_TAIL (check c, report pass/fail) - AstNode* buildFsmBody(FileLine* flp, AstVar* stateVarp, AstVar* cntVarp, AstVar* failVarp, - const std::vector& steps, AstSenItem* /*sensesp*/, - AstNodeExpr* antExprp) { + // Pre-assigned state numbers for one SeqStep. + // Range steps consume their successor (check target); successor entry is unused. + struct StepBounds final { + int waitState; // WAIT_MIN state, or -1 if not needed + int checkState; // CHECK or TAIL state; -1 for fixed-delay steps + }; + // Assign state numbers to all steps before building FSM bodies. + // + // State layout for a ##[M:N] b ##1 c (bounded, M>0): + // State 0: IDLE -- detect trigger, launch FSM + // State 1: WAIT_MIN -- count down M-1 cycles + // State 2: CHECK -- sample b; fail after N-M retries + // State 3: WAIT_FIX -- count down 1 cycle for ##1 + // State 4: TAIL -- sample c, report pass/fail + // + // For ##[M:$] b ... (unbounded, M>1): same as bounded but CHECK has no timeout. + // For ##[+] b (unbounded, M=1): WAIT_MIN skipped; CHECK is state 1. + // For ##[*] b (unbounded, M=0): handled in IDLE directly (no WAIT_MIN). + // + // LIMITATION: single-evaluation FSM -- overlapping triggers are ignored + // while the FSM is active. For ##[M:$], if the consequent never becomes + // true the FSM remains in CHECK indefinitely, blocking new evaluations. + std::vector preAssignStates(const std::vector& steps) { + std::vector bounds(steps.size(), {-1, -1}); + int s = 1; + for (size_t i = 0; i < steps.size(); ++i) { + const SeqStep& step = steps[i]; + if (step.isRange) { + // Unbounded with min<=1: no WAIT_MIN (counter starts at 0 in CHECK). + const bool needsWait = !step.isUnbounded || step.rangeMin > 1; + if (needsWait) bounds[i].waitState = s++; + bounds[i].checkState = s++; + ++i; // step[i+1] is the check target, not a separate FSM state + } else if (step.delay > 0) { + bounds[i].waitState = s++; + } else { + bounds[i].checkState = s++; // tail check + } + } + return bounds; + } + + // Build the match action for a range CHECK state. + // isTail=true: return to IDLE; isTail=false: advance to afterMatchState. + AstNode* makeOnMatchAction(FileLine* flp, AstVar* stateVarp, AstVar* cntVarp, bool isTail, + int afterMatchState, int nextDelay) { + if (isTail) { + return new AstAssign{flp, new AstVarRef{flp, stateVarp, VAccess::WRITE}, + new AstConst{flp, 0}}; + } + return makeStateTransition(flp, stateVarp, cntVarp, afterMatchState, + nextDelay > 0 ? nextDelay - 1 : 0); + } + + // Build the body of a range CHECK state. + // Bounded: fail on timeout, decrement counter otherwise. + // Unbounded: stay until match (no timeout). + AstNode* makeRangeCheckBody(FileLine* flp, AstVar* stateVarp, AstVar* cntVarp, + AstVar* failVarp, AstNodeExpr* exprp, AstNode* matchActionp, + bool isUnbounded) { + if (isUnbounded) { + return new AstIf{flp, new AstSampled{flp, exprp->cloneTree(false)}, matchActionp, + nullptr}; + } + AstBegin* const timeoutp = new AstBegin{flp, "", nullptr, true}; + timeoutp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, failVarp, VAccess::WRITE}, + new AstConst{flp, AstConst::BitTrue{}}}); + timeoutp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, stateVarp, VAccess::WRITE}, + new AstConst{flp, 0}}); + AstNode* const decrementp = new AstAssign{ + flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, + new AstSub{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 1}}}; + AstIf* const failOrRetryp = new AstIf{ + flp, new AstEq{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 0}}, + timeoutp, decrementp}; + return new AstIf{flp, new AstSampled{flp, exprp->cloneTree(false)}, matchActionp, + failOrRetryp}; + } + + AstNode* buildFsmBody(FileLine* flp, AstVar* stateVarp, AstVar* cntVarp, AstVar* failVarp, + const std::vector& steps, AstNodeExpr* antExprp) { + + const std::vector bounds = preAssignStates(steps); AstNode* fsmChainp = nullptr; - int nextState = 1; for (size_t i = 0; i < steps.size(); ++i) { const SeqStep& step = steps[i]; if (step.isRange) { - // Range delay needs two states: WAIT_MIN and CHECK_RANGE UASSERT(i + 1 < steps.size(), "Range must have next step"); - const int waitState = nextState++; - const int checkState = nextState++; - const int rangeWidth = step.rangeMax - step.rangeMin; const SeqStep& nextStep = steps[i + 1]; + const int afterMatchState = bounds[i].checkState + 1; + const bool isTail = (i + 2 >= steps.size() && nextStep.delay == 0); - const int afterMatchState = nextState; - - // WAIT_MIN: count down rangeMin cycles - { - AstNode* const bodyp = new AstIf{ + // WAIT_MIN state: count down rangeMin-1 cycles before entering CHECK + if (bounds[i].waitState >= 0) { + const int initCnt = step.isUnbounded ? 0 : (step.rangeMax - step.rangeMin); + AstNode* const waitBodyp = new AstIf{ flp, new AstEq{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 0}}, - makeStateTransition(flp, stateVarp, cntVarp, checkState, rangeWidth), + makeStateTransition(flp, stateVarp, cntVarp, bounds[i].checkState, + initCnt), new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, new AstSub{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 1}}}}; - fsmChainp = chainState(flp, fsmChainp, stateVarp, waitState, bodyp); + fsmChainp + = chainState(flp, fsmChainp, stateVarp, bounds[i].waitState, waitBodyp); } - // CHECK_RANGE: check expr each cycle, fail on timeout - { - AstNode* matchActionp = nullptr; - AstNode* const timeoutp = new AstBegin{flp, "", nullptr, true}; - VN_AS(timeoutp, Begin) - ->addStmtsp(new AstAssign{flp, - new AstVarRef{flp, failVarp, VAccess::WRITE}, - new AstConst{flp, AstConst::BitTrue{}}}); - VN_AS(timeoutp, Begin) - ->addStmtsp(new AstAssign{flp, - new AstVarRef{flp, stateVarp, VAccess::WRITE}, - new AstConst{flp, 0}}); - AstNode* const decrementp - = new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, - new AstSub{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, - new AstConst{flp, 1}}}; + // CHECK state: sample consequent each cycle + AstNode* const matchActionp = makeOnMatchAction(flp, stateVarp, cntVarp, isTail, + afterMatchState, nextStep.delay); + AstNode* const checkBodyp + = makeRangeCheckBody(flp, stateVarp, cntVarp, failVarp, nextStep.exprp, + matchActionp, step.isUnbounded); + fsmChainp + = chainState(flp, fsmChainp, stateVarp, bounds[i].checkState, checkBodyp); - AstIf* const failOrRetryp - = new AstIf{flp, - new AstEq{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, - new AstConst{flp, 0}}, - timeoutp, decrementp}; - - if (nextStep.delay > 0) { - matchActionp = makeStateTransition(flp, stateVarp, cntVarp, - afterMatchState, nextStep.delay - 1); - } else { - matchActionp - = makeStateTransition(flp, stateVarp, cntVarp, afterMatchState, 0); - } - - AstIf* const checkp - = new AstIf{flp, new AstSampled{flp, nextStep.exprp->cloneTree(false)}, - matchActionp, failOrRetryp}; - - fsmChainp = chainState(flp, fsmChainp, stateVarp, checkState, checkp); - } - - // Skip next step (already consumed as the range check target) - ++i; + ++i; // step[i+1] consumed as the CHECK target continue; } else if (step.delay > 0) { - // Fixed delay: count down then advance - const int waitState = nextState++; + // Fixed delay: count down then advance to next state + const int nextStateNum = bounds[i].waitState + 1; AstNode* const bodyp = new AstIf{ flp, new AstEq{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 0}}, new AstAssign{flp, new AstVarRef{flp, stateVarp, VAccess::WRITE}, - new AstConst{flp, static_cast(nextState)}}, + new AstConst{flp, static_cast(nextStateNum)}}, new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, new AstSub{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 1}}}}; - - fsmChainp = chainState(flp, fsmChainp, stateVarp, waitState, bodyp); + fsmChainp = chainState(flp, fsmChainp, stateVarp, bounds[i].waitState, bodyp); } else if (i == steps.size() - 1 && step.exprp) { - // Tail: check final expression, pass or fail - const int checkState = nextState++; + // Tail: sample final expression, report pass/fail AstNode* const passp = new AstAssign{ flp, new AstVarRef{flp, stateVarp, VAccess::WRITE}, new AstConst{flp, 0}}; AstBegin* const failp = new AstBegin{flp, "", nullptr, true}; @@ -1019,27 +1119,17 @@ class RangeDelayExpander final : public VNVisitor { new AstConst{flp, AstConst::BitTrue{}}}); failp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, stateVarp, VAccess::WRITE}, new AstConst{flp, 0}}); - AstIf* const bodyp = new AstIf{ flp, new AstSampled{flp, step.exprp->cloneTree(false)}, passp, failp}; - - fsmChainp = chainState(flp, fsmChainp, stateVarp, checkState, bodyp); + fsmChainp = chainState(flp, fsmChainp, stateVarp, bounds[i].checkState, bodyp); } } - // Build IDLE state (state 0): check trigger and start + // Build IDLE state (state 0) AstNode* idleBodyp = nullptr; const SeqStep& firstStep = steps[0]; - int initCnt = 0; - if (firstStep.isRange) { - initCnt = firstStep.rangeMin - 1; - } else { - initCnt = firstStep.delay - 1; - } - AstNode* const startActionp - = makeStateTransition(flp, stateVarp, cntVarp, 1, initCnt < 0 ? 0 : initCnt); - // Trigger = antecedent (from implication) AND/OR first step expression + // Trigger = antecedent AND/OR first step expression AstNodeExpr* triggerp = nullptr; if (antExprp && firstStep.exprp) { triggerp = new AstAnd{flp, new AstSampled{flp, antExprp->cloneTree(false)}, @@ -1050,12 +1140,37 @@ class RangeDelayExpander final : public VNVisitor { triggerp = new AstSampled{flp, firstStep.exprp->cloneTree(false)}; } - if (triggerp) { - triggerp->dtypeSetBit(); - idleBodyp = new AstIf{flp, triggerp, startActionp, nullptr}; + if (firstStep.isUnbounded && firstStep.rangeMin == 0 && steps.size() > 1) { + // ##[*] / ##[0:$]: check consequent immediately in IDLE. + // On ##0 match: perform match action without entering CHECK. + // On no match: enter CHECK (state bounds[0].checkState) to wait. + const SeqStep& nextStep = steps[1]; + const int checkState = bounds[0].checkState; + const int afterMatch = checkState + 1; + const bool isTail = (steps.size() == 2 && nextStep.delay == 0); + AstNodeExpr* const immCheckp = new AstSampled{flp, nextStep.exprp->cloneTree(false)}; + immCheckp->dtypeSetBit(); + AstNode* const immMatchp + = makeOnMatchAction(flp, stateVarp, cntVarp, isTail, afterMatch, nextStep.delay); + AstNode* const toCheckp = makeStateTransition(flp, stateVarp, cntVarp, checkState, 0); + AstIf* const starBodyp = new AstIf{flp, immCheckp, immMatchp, toCheckp}; + if (triggerp) { + triggerp->dtypeSetBit(); + idleBodyp = new AstIf{flp, triggerp, starBodyp, nullptr}; + } else { + idleBodyp = starBodyp; + } } else { - // Unary form with no antecedent: start unconditionally each cycle - idleBodyp = startActionp; + // Standard start: transition to state 1 with appropriate counter + int initCnt = firstStep.isRange ? firstStep.rangeMin - 1 : firstStep.delay - 1; + AstNode* const startActionp + = makeStateTransition(flp, stateVarp, cntVarp, 1, initCnt < 0 ? 0 : initCnt); + if (triggerp) { + triggerp->dtypeSetBit(); + idleBodyp = new AstIf{flp, triggerp, startActionp, nullptr}; + } else { + idleBodyp = startActionp; + } } // Chain: if (state == 0) idle else if (state == 1) ... else ... @@ -1168,16 +1283,18 @@ class RangeDelayExpander final : public VNVisitor { AstVar* const stateVarp = new AstVar{flp, VVarType::MODULETEMP, baseName + "__state", nodep->findBasicDType(VBasicDTypeKwd::UINT32)}; stateVarp->lifetime(VLifetime::STATIC_EXPLICIT); + stateVarp->noSample(true); AstVar* const cntVarp = new AstVar{flp, VVarType::MODULETEMP, baseName + "__cnt", nodep->findBasicDType(VBasicDTypeKwd::UINT32)}; cntVarp->lifetime(VLifetime::STATIC_EXPLICIT); + cntVarp->noSample(true); AstVar* const failVarp = new AstVar{flp, VVarType::MODULETEMP, baseName + "__fail", nodep->findBasicDType(VBasicDTypeKwd::BIT)}; failVarp->lifetime(VLifetime::STATIC_EXPLICIT); + failVarp->noSample(true); // Build FSM body - AstNode* const fsmBodyp - = buildFsmBody(flp, stateVarp, cntVarp, failVarp, steps, sensesp, antExprp); + AstNode* const fsmBodyp = buildFsmBody(flp, stateVarp, cntVarp, failVarp, steps, antExprp); // Create Always block for the FSM (same scheduling as assertion always blocks) AstAlways* const alwaysp = new AstAlways{ @@ -1210,6 +1327,40 @@ class RangeDelayExpander final : public VNVisitor { } } + void visit(AstSThroughout* nodep) override { + // Reject throughout with range-delay sequences before FSM expansion + // would silently lose per-tick enforcement (IEEE 1800-2023 16.9.9) + if (AstSExpr* const sexprp = VN_CAST(nodep->rhsp(), SExpr)) { + if (containsRangeDelay(sexprp)) { + nodep->v3warn(E_UNSUPPORTED, "Unsupported: throughout with range delay sequence"); + nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}}); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + return; + } + } + // Reject throughout with nested throughout or goto repetition + if (VN_IS(nodep->rhsp(), SThroughout) || VN_IS(nodep->rhsp(), SGotoRep)) { + nodep->v3warn(E_UNSUPPORTED, "Unsupported: throughout with complex sequence operator"); + nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}}); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + return; + } + // Reject throughout with temporal SAnd/SOr (containing SExpr = multi-cycle). + // Pure boolean SAnd/SOr are OK -- AssertPropLowerVisitor lowers them to LogAnd/LogOr. + if (VN_IS(nodep->rhsp(), SAnd) || VN_IS(nodep->rhsp(), SOr)) { + bool hasSExpr = false; + nodep->rhsp()->foreach([&](const AstSExpr*) { hasSExpr = true; }); + if (hasSExpr) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: throughout with complex sequence operator"); + nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}}); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + return; + } + } + iterateChildren(nodep); + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } public: diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index b9e122715..af22998f8 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -125,6 +125,8 @@ public: virtual AstNodeDType* subDTypep() const VL_MT_STABLE { return nullptr; } virtual AstNodeDType* subDType2p() const VL_MT_STABLE { return nullptr; } virtual bool isAggregateType() const { return false; } + // True for unpacked, dynamic, queue, and associative arrays (not packed arrays) + bool isNonPackedArray() const; virtual bool isFourstate() const; // Ideally an IEEE $typename virtual string prettyDTypeName(bool) const { return prettyTypeName(); } diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 4472155de..2709539b9 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -2197,22 +2197,6 @@ public: bool cleanOut() const override { V3ERROR_NA_RETURN(""); } int instrCount() const override { return widthInstrs(); } }; -class AstSExprGotoRep final : public AstNodeExpr { - // Goto repetition: expr [-> count] - // IEEE 1800-2023 16.9.2 - // @astgen op1 := exprp : AstNodeExpr - // @astgen op2 := countp : AstNodeExpr -public: - explicit AstSExprGotoRep(FileLine* fl, AstNodeExpr* exprp, AstNodeExpr* countp) - : ASTGEN_SUPER_SExprGotoRep(fl) { - this->exprp(exprp); - this->countp(countp); - } - ASTGEN_MEMBERS_AstSExprGotoRep; - string emitVerilog() override { V3ERROR_NA_RETURN(""); } - string emitC() override { V3ERROR_NA_RETURN(""); } - bool cleanOut() const override { V3ERROR_NA_RETURN(""); } -}; class AstSFormatArg final : public AstNodeExpr { // Information for formatting each argument to AstSFormat, // used to pass to (potentially) runtime decoding of format arguments @@ -2315,6 +2299,22 @@ public: bool cleanOut() const override { V3ERROR_NA_RETURN(true); } bool isSystemFunc() const override { return true; } }; +class AstSGotoRep final : public AstNodeExpr { + // Goto repetition: expr [-> count] + // IEEE 1800-2023 16.9.2 + // @astgen op1 := exprp : AstNodeExpr + // @astgen op2 := countp : AstNodeExpr +public: + explicit AstSGotoRep(FileLine* fl, AstNodeExpr* exprp, AstNodeExpr* countp) + : ASTGEN_SUPER_SGotoRep(fl) { + this->exprp(exprp); + this->countp(countp); + } + ASTGEN_MEMBERS_AstSGotoRep; + string emitVerilog() override { V3ERROR_NA_RETURN(""); } + string emitC() override { V3ERROR_NA_RETURN(""); } + bool cleanOut() const override { V3ERROR_NA_RETURN(""); } +}; class AstSScanF final : public AstNodeExpr { // @astgen op1 := exprsp : List[AstNodeExpr] // VarRefs for results // @astgen op2 := fromp : AstNodeExpr @@ -3721,6 +3721,28 @@ public: bool sizeMattersRhs() const override { return false; } int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } }; +class AstSThroughout final : public AstNodeBiop { + // expr throughout seq (IEEE 1800-2023 16.9.9) +public: + AstSThroughout(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) + : ASTGEN_SUPER_SThroughout(fl, lhsp, rhsp) { + dtypeSetBit(); + } + ASTGEN_MEMBERS_AstSThroughout; + // LCOV_EXCL_START // Lowered in V3AssertProp before these are called + void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) override { + out.opLogAnd(lhs, rhs); + } + string emitVerilog() override { return "%k(%l %fthroughout %r)"; } + string emitC() override { V3ERROR_NA_RETURN(""); } + string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } + bool cleanOut() const override { return true; } + bool cleanLhs() const override { return true; } + bool cleanRhs() const override { return true; } + bool sizeMattersLhs() const override { return false; } + bool sizeMattersRhs() const override { return false; } + // LCOV_EXCL_STOP +}; class AstSel final : public AstNodeBiop { // *Resolved* (tyep checked) multiple bit range extraction. Always const width // @astgen alias op1 := fromp diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 0c2632604..decaddbb7 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1951,6 +1951,7 @@ class AstVar final : public AstNode { bool m_noCReset : 1; // Do not do automated CReset creation bool m_noReset : 1; // Do not do automated reset/randomization bool m_noSubst : 1; // Do not substitute out references + bool m_sampled : 1; // Sampled timing region bool m_substConstOnly : 1; // Only substitute if constant bool m_overridenParam : 1; // Overridden parameter by #(...) or defparam bool m_trace : 1; // Trace this variable @@ -1969,6 +1970,7 @@ class AstVar final : public AstNode { bool m_globalConstrained : 1; // Global constraint per IEEE 1800-2023 18.5.8 bool m_isStdRandomizeArg : 1; // Argument variable created for std::randomize (__Varg*) bool m_noSample : 1; // Do not wrap with AstSampled in assertion context + bool m_processQueue : 1; // Process queue variable void init() { m_ansi = false; m_declTyped = false; @@ -2009,6 +2011,7 @@ class AstVar final : public AstNode { m_noCReset = false; m_noReset = false; m_noSubst = false; + m_sampled = false; m_substConstOnly = false; m_overridenParam = false; m_trace = false; @@ -2027,6 +2030,7 @@ class AstVar final : public AstNode { m_globalConstrained = false; m_isStdRandomizeArg = false; m_noSample = false; + m_processQueue = false; } public: @@ -2176,6 +2180,10 @@ public: void noSubst(bool flag) { m_noSubst = flag; } bool noSample() const { return m_noSample; } void noSample(bool flag) { m_noSample = flag; } + bool processQueue() const { return m_processQueue; } + void processQueue(bool flag) { m_processQueue = flag; } + bool sampled() const { return m_sampled; } + void sampled(bool flag) { m_sampled = flag; } bool substConstOnly() const { return m_substConstOnly; } void substConstOnly(bool flag) { m_substConstOnly = flag; } bool overriddenParam() const { return m_overridenParam; } diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index 5a332599c..dbf8ed151 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -544,6 +544,7 @@ class AstDelay final : public AstNodeStmt { // @astgen op1 := lhsp : AstNodeExpr // Delay value (or min for range) // @astgen op2 := stmtsp : List[AstNode] // Statements under delay // @astgen op3 := rhsp : Optional[AstNodeExpr] // Max delay value (range delay only) + // @astgen op4 := throughoutp : Optional[AstNodeExpr] // Throughout condition (IEEE 16.9.9) VTimescale m_timeunit; // Delay's time unit const bool m_isCycle; // True if it is a cycle delay @@ -562,6 +563,7 @@ public: VTimescale timeunit() const { return m_timeunit; } bool isCycleDelay() const { return m_isCycle; } bool isRangeDelay() const { return rhsp() != nullptr; } + bool isUnbounded() const { return rhsp() && VN_IS(rhsp(), Unbounded); } }; class AstDisable final : public AstNodeStmt { // @astgen op1 := targetRefp : Optional[AstNodeExpr] // Reference to link in V3LinkDot diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index ace0ece76..a669cd777 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1021,6 +1021,11 @@ bool AstNodeDType::similarDType(const AstNodeDType* samep) const { bool AstNodeDType::isFourstate() const { return basicp() && basicp()->isFourstate(); } +bool AstNodeDType::isNonPackedArray() const { + return VN_IS(this, UnpackArrayDType) || VN_IS(this, DynArrayDType) || VN_IS(this, QueueDType) + || VN_IS(this, AssocArrayDType); +} + class AstNodeDType::CTypeRecursed final { public: string m_type; // The base type, e.g.: "Foo_t"s @@ -2964,6 +2969,8 @@ void AstVar::dump(std::ostream& str) const { if (rand().isRandomizable()) str << " [" << rand() << "]"; if (noCReset()) str << " [!CRST]"; if (noReset()) str << " [!RST]"; + if (processQueue()) str << " [PROCQ]"; + if (sampled()) str << " [SAMPLED]"; if (attrIsolateAssign()) str << " [aISO]"; if (attrFileDescr()) str << " [aFD]"; if (isFuncReturn()) { @@ -2994,6 +3001,8 @@ void AstVar::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, isUsedLoopIdx); dumpJsonBoolFuncIf(str, noCReset); dumpJsonBoolFuncIf(str, noReset); + dumpJsonBoolFuncIf(str, processQueue); + dumpJsonBoolFuncIf(str, sampled); dumpJsonBoolFuncIf(str, attrIsolateAssign); dumpJsonBoolFuncIf(str, attrFileDescr); dumpJsonBoolFuncIf(str, isDpiOpenArray); diff --git a/src/V3Clock.cpp b/src/V3Clock.cpp index 6a0b3e3fa..8b3fb243c 100644 --- a/src/V3Clock.cpp +++ b/src/V3Clock.cpp @@ -100,7 +100,7 @@ class ClockVisitor final : public VNVisitor { void visit(AstVarScope* nodep) override { AstVar* const varp = nodep->varp(); if (!varp->valuep()) return; - if (!VString::startsWith(varp->name(), "__Vsampled")) return; + if (!varp->sampled()) return; // Create the containing function on first encounter if (!m_sampleCFuncp) { diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 0107b2ace..36e24baf3 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -781,7 +781,12 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const { } case VDfgType::SAnd: - case VDfgType::SOr: UASSERT_OBJ(false, this, "SAnd/SOr should be removed before DFG"); return; + case VDfgType::SOr: + case VDfgType::SThroughout: { + UASSERT_OBJ(false, this, + "SAnd/SOr/SThroughout should be removed before DFG"); // LCOV_EXCL_LINE + return; // LCOV_EXCL_LINE + } case VDfgType::LogAnd: case VDfgType::LogEq: diff --git a/src/V3DfgCse.cpp b/src/V3DfgCse.cpp index bfc0cb5c7..71fef6625 100644 --- a/src/V3DfgCse.cpp +++ b/src/V3DfgCse.cpp @@ -127,6 +127,7 @@ class V3DfgCse final { case VDfgType::StreamR: case VDfgType::SAnd: case VDfgType::SOr: + case VDfgType::SThroughout: case VDfgType::Sub: case VDfgType::Xor: return V3Hash{}; } @@ -251,6 +252,7 @@ class V3DfgCse final { case VDfgType::StreamL: case VDfgType::SAnd: case VDfgType::SOr: + case VDfgType::SThroughout: case VDfgType::StreamR: case VDfgType::Sub: case VDfgType::Xor: return true; diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 465b6b585..6192c8c33 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -1517,7 +1517,13 @@ public: void visit(AstMemberSel* nodep) override { iterateAndNextConstNull(nodep->fromp()); putnbs(nodep, "->"); - puts(nodep->varp()->nameProtect()); + if (nodep->varp()->isIfaceRef()) { + // varp is the __Viftop companion (e.g. "tx__Viftop"); use the + // MemberSel name which matches the cell's C++ member (e.g. "tx"). + puts(nodep->nameProtect()); + } else { + puts(nodep->varp()->nameProtect()); + } } void visit(AstStructSel* nodep) override { iterateAndNextConstNull(nodep->fromp()); diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index de3727e6a..62787c757 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -280,9 +280,7 @@ class EmitCHeader final : public EmitCConstInit { enum class AttributeType { Width, Dimension }; // Get member attribute based on type int getNodeAttribute(const AstMemberDType* itemp, AttributeType type) { - const bool isArrayType - = VN_IS(itemp->dtypep(), UnpackArrayDType) || VN_IS(itemp->dtypep(), DynArrayDType) - || VN_IS(itemp->dtypep(), QueueDType) || VN_IS(itemp->dtypep(), AssocArrayDType); + const bool isArrayType = itemp->dtypep()->isNonPackedArray(); switch (type) { case AttributeType::Width: { if (isArrayType) { diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 0272c62db..96724db78 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -1067,7 +1067,6 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { } iterateConst(nodep->exprp()); } - // Terminals void visit(AstVarRef* nodep) override { if (nodep->varScopep()) { diff --git a/src/V3Fork.cpp b/src/V3Fork.cpp index 2e40faa24..737b31429 100644 --- a/src/V3Fork.cpp +++ b/src/V3Fork.cpp @@ -611,7 +611,7 @@ class ForkVisitor final : public VNVisitor { const AstCMethodHard* const methodp = VN_CAST(stmtExprp->exprp(), CMethodHard); if (!methodp || methodp->name() != "push_back") return false; const AstVarRef* const queueRefp = VN_CAST(methodp->fromp(), VarRef); - return queueRefp && queueRefp->name().rfind("__VprocessQueue_", 0) == 0; + return queueRefp && queueRefp->varp()->processQueue(); } static void moveForkSentinelAfterDisableQueuePushes(AstBegin* const beginp) { AstNode* const firstStmtp = beginp->stmtsp(); diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index a5c0870e6..6f46de45a 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -236,20 +236,24 @@ class LinkJumpVisitor final : public VNVisitor { m_taskDisableBegins.emplace(taskp, taskBodyp); return taskBodyp; } - AstVar* getOrCreateTaskDisableQueuep(AstTask* const taskp, FileLine* const fl) { - const auto it = m_taskDisableQueues.find(taskp); - if (it != m_taskDisableQueues.end()) return it->second; - + AstVar* getProcessQueuep(AstNode* const nodep, FileLine* const fl) { AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp(); AstClass* const processClassp = VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class); AstVar* const processQueuep = new AstVar{ - fl, VVarType::VAR, m_queueNames.get(taskp->name()), VFlagChildDType{}, + fl, VVarType::VAR, m_queueNames.get(nodep->name()), VFlagChildDType{}, new AstQueueDType{fl, VFlagChildDType{}, new AstClassRefDType{fl, processClassp, nullptr}, nullptr}}; processQueuep->lifetime(VLifetime::STATIC_EXPLICIT); + processQueuep->processQueue(true); topPkgp->addStmtsp(processQueuep); + return processQueuep; + } + AstVar* getOrCreateTaskDisableQueuep(AstTask* const taskp, FileLine* const fl) { + const auto it = m_taskDisableQueues.find(taskp); + if (it != m_taskDisableQueues.end()) return it->second; + AstVar* const processQueuep = getProcessQueuep(taskp, fl); AstStmtExpr* const pushCurrentProcessp = getQueuePushProcessSelfp(fl, processQueuep); AstBegin* const taskBodyp = getOrCreateTaskDisableBeginp(taskp, fl); prependStmtsp(taskBodyp, pushCurrentProcessp); @@ -274,16 +278,7 @@ class LinkJumpVisitor final : public VNVisitor { const auto it = m_beginDisableQueues.find(beginp); if (it != m_beginDisableQueues.end()) return it->second; - AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp(); - AstClass* const processClassp - = VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class); - AstVar* const processQueuep = new AstVar{ - fl, VVarType::VAR, m_queueNames.get(beginp->name()), VFlagChildDType{}, - new AstQueueDType{fl, VFlagChildDType{}, - new AstClassRefDType{fl, processClassp, nullptr}, nullptr}}; - processQueuep->lifetime(VLifetime::STATIC_EXPLICIT); - topPkgp->addStmtsp(processQueuep); - + AstVar* const processQueuep = getProcessQueuep(beginp, fl); AstStmtExpr* const pushCurrentProcessp = getQueuePushProcessSelfp(fl, processQueuep); AstBegin* const beginBodyp = getOrCreateBeginDisableBeginp(beginp, fl); prependStmtsp(beginBodyp, pushCurrentProcessp); @@ -315,17 +310,9 @@ class LinkJumpVisitor final : public VNVisitor { nodep->v3warn(E_UNSUPPORTED, "Unsupported: disabling fork from task / function"); } } - AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp(); - AstClass* const processClassp - = VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class); - // Declare queue of processes (as a global variable for simplicity) - AstVar* const processQueuep = new AstVar{ - fl, VVarType::VAR, m_queueNames.get(targetp->name()), VFlagChildDType{}, - new AstQueueDType{fl, VFlagChildDType{}, - new AstClassRefDType{fl, processClassp, nullptr}, nullptr}}; - processQueuep->lifetime(VLifetime::STATIC_EXPLICIT); - topPkgp->addStmtsp(processQueuep); + AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp(); + AstVar* const processQueuep = getProcessQueuep(targetp, fl); AstVarRef* const queueWriteRefp = new AstVarRef{fl, topPkgp, processQueuep, VAccess::WRITE}; AstStmtExpr* pushCurrentProcessp = getQueuePushProcessSelfp(queueWriteRefp); diff --git a/src/V3LinkLValue.cpp b/src/V3LinkLValue.cpp index c8a701888..2fa1af559 100644 --- a/src/V3LinkLValue.cpp +++ b/src/V3LinkLValue.cpp @@ -69,6 +69,13 @@ class LinkLValueVisitor final : public VNVisitor { } if (m_setForcedByCode) { nodep->varp()->setForcedByCode(); + // If a public signal is being forced in SystemVerilog and VPI + // is enabled, mark it as forceable to ensure that the VPI + // functions read the forced value correctly + if (v3Global.opt.vpi() + && (nodep->varp()->isSigPublic() || nodep->varp()->isSigModPublic())) { + nodep->varp()->setForceable(); + } } else if (!nodep->varp()->isFuncLocal() && nodep->varp()->isReadOnly()) { // This is allowed with IEEE 1800-2009 module input with default value. // the checking now happens in V3Width::visit(AstNodeVarRef*) diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 36c7136c2..1014e6332 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -918,6 +918,17 @@ class LinkParseVisitor final : public VNVisitor { cleanFileline(nodep); iterateChildren(nodep); } + void visit(AstCaseItem* nodep) override { + // Move default caseItems to the bottom of the list + // That saves us from having to search each case list twice, for non-defaults and defaults + iterateChildren(nodep); + if (!nodep->user2() && nodep->isDefault() && nodep->nextp()) { + nodep->user2(true); + AstNode* const nextp = nodep->nextp(); + nodep->unlinkFrBack(); + nextp->addNext(nodep); + } + } void visit(AstDot* nodep) override { cleanFileline(nodep); iterateChildren(nodep); diff --git a/src/V3LinkResolve.cpp b/src/V3LinkResolve.cpp index 3690fdb39..110203633 100644 --- a/src/V3LinkResolve.cpp +++ b/src/V3LinkResolve.cpp @@ -47,19 +47,16 @@ class LinkResolveVisitor final : public VNVisitor { // Below state needs to be preserved between each module call. AstNodeModule* m_modp = nullptr; // Current module AstClass* m_classp = nullptr; // Class we're inside - string m_randcIllegalWhy; // Why randc illegal - AstNode* m_randcIllegalp = nullptr; // Node causing randc illegal AstNodeFTask* m_ftaskp = nullptr; // Function or task we're inside int m_senitemCvtNum = 0; // Temporary signal counter std::deque m_underGenFors; // Stack of GenFor underneath bool m_underGenerate = false; // Under GenFor/GenIf - AstNodeExpr* m_currentRandomizeSelectp = nullptr; // fromp() of current `randomize()` call - bool m_inRandomizeWith = false; // If in randomize() with (and no other with afterwards) // VISITORS // TODO: Most of these visitors are here for historical reasons. // TODO: ExpectDescriptor can move to data type resolution, and the rest // TODO: could move to V3LinkParse to get them out of the way of elaboration + // TODO: Some also can move to V3LinkWidth, to happen once post-LinkDot void visit(AstNodeModule* nodep) override { // Module: Create sim table for entire module and iterate UINFO(8, "MODULE " << nodep); @@ -80,39 +77,6 @@ class LinkResolveVisitor final : public VNVisitor { } iterateChildren(nodep); } - void visit(AstConstraint* nodep) override { - // V3LinkDot moved the isExternDef into the class, the extern proto was - // checked to exist, and now isn't needed - nodep->isExternDef(false); - if (nodep->isExternProto()) { - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - return; - } - iterateChildren(nodep); - } - void visit(AstConstraintBefore* nodep) override { - VL_RESTORER(m_randcIllegalWhy); - VL_RESTORER(m_randcIllegalp); - m_randcIllegalWhy = "'solve before' (IEEE 1800-2023 18.5.9)"; - m_randcIllegalp = nodep; - iterateChildrenConst(nodep); - } - void visit(AstDist* nodep) override { - VL_RESTORER(m_randcIllegalWhy); - VL_RESTORER(m_randcIllegalp); - m_randcIllegalWhy = "'constraint dist' (IEEE 1800-2023 18.5.3)"; - m_randcIllegalp = nodep; - iterateChildrenConst(nodep); - } - void visit(AstConstraintExpr* nodep) override { - VL_RESTORER(m_randcIllegalWhy); - VL_RESTORER(m_randcIllegalp); - if (nodep->isSoft()) { - m_randcIllegalWhy = "'constraint soft' (IEEE 1800-2023 18.5.13.1)"; - m_randcIllegalp = nodep; - } - iterateChildrenConst(nodep); - } void visit(AstInitialAutomatic* nodep) override { iterateChildren(nodep); @@ -151,14 +115,6 @@ class LinkResolveVisitor final : public VNVisitor { << nodep->prettyNameQ() << " used outside generate for loop (IEEE 1800-2023 27.4)"); } - if (nodep->varp()->isRandC() && m_randcIllegalp) { - nodep->v3error("Randc variables not allowed in " - << m_randcIllegalWhy << '\n' - << nodep->warnContextPrimary() << '\n' - << m_randcIllegalp->warnOther() - << "... Location of restricting expression\n" - << m_randcIllegalp->warnContextSecondary()); - } } iterateChildren(nodep); } @@ -196,22 +152,6 @@ class LinkResolveVisitor final : public VNVisitor { if (nodep->dpiExport()) nodep->scopeNamep(new AstScopeName{nodep->fileline(), false}); } void visit(AstNodeFTaskRef* nodep) override { - VL_RESTORER(m_currentRandomizeSelectp); - if (nodep->taskp()) { - if (AstSequence* const seqp = VN_CAST(nodep->taskp(), Sequence)) - seqp->isReferenced(true); - } - - if (nodep->name() == "randomize") { - if (const AstMethodCall* const methodcallp = VN_CAST(nodep, MethodCall)) { - if (m_inRandomizeWith) { - nodep->v3warn( - E_UNSUPPORTED, - "Unsupported: randomize() nested in inline randomize() constraints"); - } - m_currentRandomizeSelectp = methodcallp->fromp(); - } - } iterateChildren(nodep); if (AstLet* letp = VN_CAST(nodep->taskp(), Let)) { UINFO(7, "letSubstitute() " << nodep << " <- " << letp); @@ -260,18 +200,6 @@ class LinkResolveVisitor final : public VNVisitor { } } - void visit(AstCaseItem* nodep) override { - // Move default caseItems to the bottom of the list - // That saves us from having to search each case list twice, for non-defaults and defaults - iterateChildren(nodep); - if (!nodep->user2() && nodep->isDefault() && nodep->nextp()) { - nodep->user2(true); - AstNode* const nextp = nodep->nextp(); - nodep->unlinkFrBack(); - nextp->addNext(nodep); - } - } - void visit(AstLet* nodep) override { // Lets have been (or about to be) substituted, we can remove nodep->unlinkFrBack(); @@ -528,30 +456,6 @@ class LinkResolveVisitor final : public VNVisitor { iterateChildren(nodep); } - void visit(AstMemberSel* nodep) override { - if (m_inRandomizeWith && nodep->fromp()->isSame(m_currentRandomizeSelectp)) { - // Replace member selects to the element - // on which the randomize() is called with LambdaArgRef - // This allows V3Randomize to work properly when - // constrained variables are referred using that object - AstNodeExpr* const prevFromp = nodep->fromp(); - prevFromp->replaceWith( - new AstLambdaArgRef{prevFromp->fileline(), prevFromp->name(), false}); - pushDeletep(prevFromp); - } - iterateChildren(nodep); - } - - void visit(AstWith* nodep) override { - VL_RESTORER(m_inRandomizeWith); - if (const AstMethodCall* const methodCallp = VN_CAST(nodep->backp(), MethodCall)) { - m_inRandomizeWith = methodCallp->name() == "randomize"; - } else { - m_inRandomizeWith = false; - } - iterateChildren(nodep); - } - void visit(AstNode* nodep) override { iterateChildren(nodep); } public: @@ -594,7 +498,7 @@ public: }; //###################################################################### -// Link class functions +// V3LinkResolve class functions void V3LinkResolve::linkResolve(AstNetlist* rootp) { UINFO(4, __FUNCTION__ << ": "); diff --git a/src/V3LinkWith.cpp b/src/V3LinkWith.cpp new file mode 100644 index 000000000..e83536de8 --- /dev/null +++ b/src/V3LinkWith.cpp @@ -0,0 +1,149 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Resolve module/signal name references +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// 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-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// LinkResolve TRANSFORMATIONS: +// Top-down traversal +// With vars: Fixup LambdaRefs +//************************************************************************* + +#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT + +#include "V3LinkWith.h" + +VL_DEFINE_DEBUG_FUNCTIONS; + +//###################################################################### +// Link state, as a visitor of each AstNode + +class LinkWithVisitor final : public VNVisitor { + // NODE STATE + + // STATE + // Below state needs to be preserved between each module call. + string m_randcIllegalWhy; // Why randc illegal + AstNode* m_randcIllegalp = nullptr; // Node causing randc illegal + AstNodeExpr* m_currentRandomizeSelectp = nullptr; // fromp() of current `randomize()` call + bool m_inRandomizeWith = false; // If in randomize() with (and no other with afterwards) + + // VISITORS + void visit(AstConstraint* nodep) override { + // V3LinkDot moved the isExternDef into the class, the extern proto was + // checked to exist, and now isn't needed + nodep->isExternDef(false); + if (nodep->isExternProto()) { + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + return; + } + iterateChildren(nodep); + } + void visit(AstConstraintBefore* nodep) override { + VL_RESTORER(m_randcIllegalWhy); + VL_RESTORER(m_randcIllegalp); + m_randcIllegalWhy = "'solve before' (IEEE 1800-2023 18.5.9)"; + m_randcIllegalp = nodep; + iterateChildrenConst(nodep); + } + void visit(AstDist* nodep) override { + VL_RESTORER(m_randcIllegalWhy); + VL_RESTORER(m_randcIllegalp); + m_randcIllegalWhy = "'constraint dist' (IEEE 1800-2023 18.5.3)"; + m_randcIllegalp = nodep; + iterateChildrenConst(nodep); + } + void visit(AstConstraintExpr* nodep) override { + VL_RESTORER(m_randcIllegalWhy); + VL_RESTORER(m_randcIllegalp); + if (nodep->isSoft()) { + m_randcIllegalWhy = "'constraint soft' (IEEE 1800-2023 18.5.13.1)"; + m_randcIllegalp = nodep; + } + iterateChildrenConst(nodep); + } + + void visit(AstNodeVarRef* nodep) override { + if (nodep->varp()) { // Else due to dead code, might not have var pointer + if (nodep->varp()->isRandC() && m_randcIllegalp) { + nodep->v3error("Randc variables not allowed in " + << m_randcIllegalWhy << '\n' + << nodep->warnContextPrimary() << '\n' + << m_randcIllegalp->warnOther() + << "... Location of restricting expression\n" + << m_randcIllegalp->warnContextSecondary()); + } + } + iterateChildren(nodep); + } + + void visit(AstNodeFTaskRef* nodep) override { + VL_RESTORER(m_currentRandomizeSelectp); + if (nodep->taskp()) { + if (AstSequence* const seqp = VN_CAST(nodep->taskp(), Sequence)) + seqp->isReferenced(true); + } + + if (nodep->name() == "randomize") { + if (const AstMethodCall* const methodcallp = VN_CAST(nodep, MethodCall)) { + if (m_inRandomizeWith) { + nodep->v3warn( + E_UNSUPPORTED, + "Unsupported: randomize() nested in inline randomize() constraints"); + } + m_currentRandomizeSelectp = methodcallp->fromp(); + } + } + iterateChildren(nodep); + } + + void visit(AstMemberSel* nodep) override { + if (m_inRandomizeWith && nodep->fromp()->isSame(m_currentRandomizeSelectp)) { + // Replace member selects to the element + // on which the randomize() is called with LambdaArgRef + // This allows V3Randomize to work properly when + // constrained variables are referred using that object + AstNodeExpr* const prevFromp = nodep->fromp(); + AstNodeExpr* const newp + = new AstLambdaArgRef{prevFromp->fileline(), prevFromp->name(), false}; + prevFromp->replaceWith(newp); + pushDeletep(prevFromp); + } + iterateChildren(nodep); + } + + void visit(AstWith* nodep) override { + VL_RESTORER(m_inRandomizeWith); + if (const AstMethodCall* const methodCallp = VN_CAST(nodep->backp(), MethodCall)) { + m_inRandomizeWith = methodCallp->name() == "randomize"; + } else { + m_inRandomizeWith = false; + } + iterateChildren(nodep); + } + + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + explicit LinkWithVisitor(AstNetlist* rootp) { iterate(rootp); } + ~LinkWithVisitor() override = default; +}; + +//###################################################################### +// V3LinkWith class functions + +void V3LinkWith::linkWith(AstNetlist* rootp) { + UINFO(4, __FUNCTION__ << ": "); + { const LinkWithVisitor visitor{rootp}; } // Destruct before checking + V3Global::dumpCheckGlobalTree("linkwith", 0, dumpTreeEitherLevel() >= 6); +} diff --git a/src/V3LinkWith.h b/src/V3LinkWith.h new file mode 100644 index 000000000..3315e8614 --- /dev/null +++ b/src/V3LinkWith.h @@ -0,0 +1,32 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Link modules/signals together +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// 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-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef VERILATOR_V3LINKWITH_H_ +#define VERILATOR_V3LINKWITH_H_ + +#include "config_build.h" +#include "verilatedos.h" + +class AstNetlist; + +//============================================================================ + +class V3LinkWith final { +public: + static void linkWith(AstNetlist* rootp) VL_MT_DISABLED; +}; + +#endif // Guard diff --git a/src/V3Number.h b/src/V3Number.h index 21a917bea..c5d1a57bf 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -665,6 +665,7 @@ public: void isSigned(bool ssigned) { m_data.m_signed = ssigned; } bool isDouble() const VL_MT_SAFE { return dataType() == V3NumberDataType::DOUBLE; } bool isString() const VL_MT_SAFE { return dataType() == V3NumberDataType::STRING; } + bool isOpaque() const VL_MT_SAFE { return isDouble() || isString(); } bool isNumber() const VL_MT_SAFE { return m_data.type() == V3NumberDataType::LOGIC || m_data.type() == V3NumberDataType::DOUBLE; diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 8d25b0c6f..13e31e2e3 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1261,6 +1261,31 @@ class ParamProcessor final { return result; } + static bool paramConstsEqualAtMaxWidth(AstConst* exprp, AstConst* origp) { + // Return true if two integer constants are equal after sign-extending + // both to max(width). A typed parameter default (e.g. byte) is + // narrower than a 32-bit pin expression, so sameTree/areSame fail. + if (exprp->num().width() == origp->num().width()) return false; + if (exprp->num().isOpaque()) return false; + if (origp->num().isOpaque()) return false; + const int maxWidth = std::max(exprp->num().width(), origp->num().width()); + V3Number exprNum{exprp, maxWidth}; + if (exprp->num().isSigned()) { + exprNum.opExtendS(exprp->num(), exprp->num().width()); + } else { + exprNum.opAssign(exprp->num()); + } + V3Number origNum{origp, maxWidth}; + if (origp->num().isSigned()) { + origNum.opExtendS(origp->num(), origp->num().width()); + } else { + origNum.opAssign(origp->num()); + } + V3Number isEq{exprp, 1}; + isEq.opEq(exprNum, origNum); + return isEq.isNeqZero(); + } + void cellPinCleanup(AstNode* nodep, AstPin* pinp, AstNodeModule* srcModp, string& longnamer, bool& any_overridesr) { if (!pinp->exprp()) return; // No-connect @@ -1284,6 +1309,13 @@ class ParamProcessor final { } else { UINFO(9, "cellPinCleanup: before constify " << pinp << " " << modvarp); V3Const::constifyParamsEdit(pinp->exprp()); + // Cast/CastSize default values are not yet folded by V3Width. + // Constify here so the comparison below sees a Const node. + // Other node kinds are handled in the branches above. + if (modvarp->valuep() + && (VN_IS(modvarp->valuep(), Cast) || VN_IS(modvarp->valuep(), CastSize))) { + V3Const::constifyParamsEdit(modvarp->valuep()); + } UINFO(9, "cellPinCleanup: after constify " << pinp); // String constants are parsed as logic arrays and converted to strings in V3Const. // At this moment, some constants may have been already converted. @@ -1309,7 +1341,8 @@ class ParamProcessor final { } else if (origp && (exprp->sameTree(origp) || (exprp->num().width() == origp->num().width() - && ParameterizedHierBlocks::areSame(exprp, origp)))) { + && ParameterizedHierBlocks::areSame(exprp, origp)) + || paramConstsEqualAtMaxWidth(exprp, origp))) { // Setting parameter to its default value. Just ignore it. // This prevents making additional modules, and makes coverage more // obvious as it won't show up under a unique module page name. diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 4a782d821..c603eebbf 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -1136,8 +1136,7 @@ class ConstraintExprVisitor final : public VNVisitor { AstClassRefDType* elemClassRefDtp = nullptr; { AstNodeDType* varDtp = varp->dtypep()->skipRefp(); - if (VN_IS(varDtp, DynArrayDType) || VN_IS(varDtp, QueueDType) - || VN_IS(varDtp, UnpackArrayDType) || VN_IS(varDtp, AssocArrayDType)) { + if (varDtp->isNonPackedArray()) { AstNodeDType* const elemDtp = varDtp->subDTypep()->skipRefp(); elemClassRefDtp = VN_CAST(elemDtp, ClassRefDType); if (elemClassRefDtp) { @@ -1209,9 +1208,7 @@ class ConstraintExprVisitor final : public VNVisitor { AstVar* const memberVarp = VN_CAST(mnodep, Var); if (!memberVarp || !memberVarp->rand().isRandomizable()) continue; AstNodeDType* const memberDtp = memberVarp->dtypep()->skipRefp(); - if (VN_IS(memberDtp, ClassRefDType) || VN_IS(memberDtp, DynArrayDType) - || VN_IS(memberDtp, QueueDType) || VN_IS(memberDtp, UnpackArrayDType) - || VN_IS(memberDtp, AssocArrayDType)) + if (VN_IS(memberDtp, ClassRefDType) || memberDtp->isNonPackedArray()) continue; const int memberWidth = memberDtp->width(); @@ -1281,9 +1278,7 @@ class ConstraintExprVisitor final : public VNVisitor { VAccess::READWRITE}, VCMethod::RANDOMIZER_WRITE_VAR}; uint32_t dimension = 0; - if (VN_IS(varp->dtypep(), UnpackArrayDType) || VN_IS(varp->dtypep(), DynArrayDType) - || VN_IS(varp->dtypep(), QueueDType) - || VN_IS(varp->dtypep(), AssocArrayDType)) { + if (varp->dtypep()->isNonPackedArray()) { const std::pair dims = varp->dtypep()->dimensions(/*includeBasic=*/true); const uint32_t unpackedDimensions = dims.second; @@ -1316,9 +1311,7 @@ class ConstraintExprVisitor final : public VNVisitor { methodp->addPinsp(varRefp); } AstNodeDType* tmpDtypep = varp->dtypep(); - while (VN_IS(tmpDtypep, UnpackArrayDType) || VN_IS(tmpDtypep, DynArrayDType) - || VN_IS(tmpDtypep, QueueDType) || VN_IS(tmpDtypep, AssocArrayDType)) - tmpDtypep = tmpDtypep->subDTypep(); + while (tmpDtypep->isNonPackedArray()) tmpDtypep = tmpDtypep->subDTypep(); const size_t width = tmpDtypep->width(); methodp->addPinsp( new AstConst{varp->dtypep()->fileline(), AstConst::Unsized64{}, width}); @@ -3370,8 +3363,7 @@ class RandomizeVisitor final : public VNVisitor { dtypep->findBasicDType(VBasicDTypeKwd::UINT32)}; }; AstNodeExpr* tempElementp = nullptr; - while (VN_IS(tempDTypep, DynArrayDType) || VN_IS(tempDTypep, UnpackArrayDType) - || VN_IS(tempDTypep, AssocArrayDType) || VN_IS(tempDTypep, QueueDType)) { + while (tempDTypep->isNonPackedArray()) { AstVar* const newRandLoopIndxp = createLoopIndex(tempDTypep); randLoopIndxp = AstNode::addNext(randLoopIndxp, newRandLoopIndxp); AstNodeExpr* const tempExprp = tempElementp ? tempElementp : exprp; @@ -4397,9 +4389,7 @@ class RandomizeVisitor final : public VNVisitor { } AstNodeDType* tmpDtypep = arrVarp->dtypep(); - while (VN_IS(tmpDtypep, UnpackArrayDType) || VN_IS(tmpDtypep, DynArrayDType) - || VN_IS(tmpDtypep, QueueDType) || VN_IS(tmpDtypep, AssocArrayDType)) - tmpDtypep = tmpDtypep->subDTypep(); + while (tmpDtypep->isNonPackedArray()) tmpDtypep = tmpDtypep->subDTypep(); const size_t width = tmpDtypep->width(); methodp->addPinsp(new AstConst{fl, AstConst::Unsized64{}, width}); diff --git a/src/V3Sampled.cpp b/src/V3Sampled.cpp index d978df73f..4fb45e772 100644 --- a/src/V3Sampled.cpp +++ b/src/V3Sampled.cpp @@ -53,6 +53,7 @@ class SampledVisitor final : public VNVisitor { AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp}; newvarp->direction(VDirection::INPUT); // Inform V3Sched that it will be driven later newvarp->primaryIO(true); + newvarp->sampled(true); vscp->user1p(newvscp); m_scopep->addVarsp(newvscp); // At the top of _eval, assign them (use valuep here as temporary storage during V3Sched) diff --git a/src/V3Simulate.h b/src/V3Simulate.h index 8c7d8f3a8..5208c7306 100644 --- a/src/V3Simulate.h +++ b/src/V3Simulate.h @@ -521,24 +521,23 @@ private: } } } - if (!m_checkOnly && optimizable()) { // simulating - UASSERT_OBJ(nodep->access().isReadOnly(), nodep, - "LHS varref should be handled in AstAssign visitor."); - { - // Return simulation value - copy by reference instead of value for speed - AstNodeExpr* valuep = fetchValueNull(vscp); - if (!valuep) { - if (m_params) { - clearOptimizable( - nodep, "Language violation: reference to non-function-local variable"); - } else { - nodep->v3fatalSrc( - "Variable value should have been set before any visitor called."); - } - valuep = allocConst(nodep); // Any value; just so recover from error + if (m_checkOnly || !optimizable()) return; // Not simulating + UASSERT_OBJ(nodep->access().isReadOnly(), nodep, + "LHS varref should be handled in AstAssign visitor."); + { + // Return simulation value - copy by reference instead of value for speed + AstNodeExpr* valuep = fetchValueNull(vscp); + if (!valuep) { + if (m_params) { + clearOptimizable( + nodep, "Language violation: reference to non-function-local variable"); + } else { + nodep->v3fatalSrc( + "Variable value should have been set before any visitor called."); } - setValue(nodep, valuep); + valuep = allocConst(nodep); // Any value; just so recover from error } + setValue(nodep, valuep); } } void visit(AstVarXRef* nodep) override { @@ -606,12 +605,14 @@ private: } void visit(AstConst* nodep) override { checkNodeInfo(nodep); - if (!m_checkOnly && optimizable()) newValue(nodep, nodep); + if (m_checkOnly || !optimizable()) return; + newValue(nodep, nodep); } void visit(AstInitArray* nodep) override { checkNodeInfo(nodep); iterateChildrenConst(nodep); - if (!m_checkOnly && optimizable()) newValue(nodep, nodep); + if (m_checkOnly || !optimizable()) return; + newValue(nodep, nodep); } void visit(AstInitItem* nodep) override { checkNodeInfo(nodep); @@ -620,62 +621,55 @@ private: void visit(AstEnumItemRef* nodep) override { checkNodeInfo(nodep); UASSERT_OBJ(nodep->itemp(), nodep, "Not linked"); - if (!m_checkOnly && optimizable()) { - AstNode* const valuep = nodep->itemp()->valuep(); - if (valuep) { - iterateAndNextConstNull(valuep); - if (!optimizable()) return; - newValue(nodep, fetchValue(valuep)); - } else { - clearOptimizable(nodep, "No value found for enum item"); // LCOV_EXCL_LINE - } + if (m_checkOnly || !optimizable()) return; + AstNode* const valuep = nodep->itemp()->valuep(); + if (valuep) { + iterateAndNextConstNull(valuep); + if (!optimizable()) return; + newValue(nodep, fetchValue(valuep)); + } else { + clearOptimizable(nodep, "No value found for enum item"); // LCOV_EXCL_LINE } } void visit(AstNodeUniop* nodep) override { if (!optimizable()) return; // Accelerate checkNodeInfo(nodep); iterateChildrenConst(nodep); - if (!m_checkOnly && optimizable()) { - nodep->numberOperate(newConst(nodep)->num(), fetchConst(nodep->lhsp())->num()); - } + if (m_checkOnly || !optimizable()) return; + nodep->numberOperate(newConst(nodep)->num(), fetchConst(nodep->lhsp())->num()); } void visit(AstNodeBiop* nodep) override { if (!optimizable()) return; // Accelerate checkNodeInfo(nodep); iterateChildrenConst(nodep); - if (!m_checkOnly && optimizable()) { - AstConst* const valuep = newConst(nodep); - nodep->numberOperate(newConst(nodep)->num(), fetchConst(nodep->lhsp())->num(), - fetchConst(nodep->rhsp())->num()); - // See #5490. 'numberOperate' on partially out of range select yields 'x' bits, - // but in reality it would yield '0's without V3Table, so force 'x' bits to '0', - // to ensure the result is the same with and without V3Table. - if (!m_params && VN_IS(nodep, Sel) && valuep->num().isAnyX()) { - const V3Number num{valuep, valuep->width(), valuep->num()}; - valuep->num().opBitsOne(num); - } + if (m_checkOnly || !optimizable()) return; + AstConst* const valuep = newConst(nodep); + nodep->numberOperate(newConst(nodep)->num(), fetchConst(nodep->lhsp())->num(), + fetchConst(nodep->rhsp())->num()); + // See #5490. 'numberOperate' on partially out of range select yields 'x' bits, + // but in reality it would yield '0's without V3Table, so force 'x' bits to '0', + // to ensure the result is the same with and without V3Table. + if (!m_params && VN_IS(nodep, Sel) && valuep->num().isAnyX()) { + const V3Number num{valuep, valuep->width(), valuep->num()}; + valuep->num().opBitsOne(num); } } void visit(AstNodeTriop* nodep) override { if (!optimizable()) return; // Accelerate checkNodeInfo(nodep); iterateChildrenConst(nodep); - if (!m_checkOnly && optimizable()) { - nodep->numberOperate(newConst(nodep)->num(), fetchConst(nodep->lhsp())->num(), - fetchConst(nodep->rhsp())->num(), - fetchConst(nodep->thsp())->num()); - } + if (m_checkOnly || !optimizable()) return; + nodep->numberOperate(newConst(nodep)->num(), fetchConst(nodep->lhsp())->num(), + fetchConst(nodep->rhsp())->num(), fetchConst(nodep->thsp())->num()); } void visit(AstNodeQuadop* nodep) override { if (!optimizable()) return; // Accelerate checkNodeInfo(nodep); iterateChildrenConst(nodep); - if (!m_checkOnly && optimizable()) { - nodep->numberOperate(newConst(nodep)->num(), fetchConst(nodep->lhsp())->num(), - fetchConst(nodep->rhsp())->num(), - fetchConst(nodep->thsp())->num(), - fetchConst(nodep->fhsp())->num()); - } + if (m_checkOnly || !optimizable()) return; + nodep->numberOperate(newConst(nodep)->num(), fetchConst(nodep->lhsp())->num(), + fetchConst(nodep->rhsp())->num(), fetchConst(nodep->thsp())->num(), + fetchConst(nodep->fhsp())->num()); } void visit(AstLogAnd* nodep) override { // Need to short circuit @@ -773,62 +767,60 @@ private: clearOptimizable(nodep, "Array of non-basic dtype (e.g. array-of-array)"); return; } - if (!m_checkOnly && optimizable()) { - AstNode* const vscp = varOrScope(varrefp); - AstInitArray* initp = nullptr; - if (AstInitArray* const vscpnump = VN_CAST(fetchOutValueNull(vscp), InitArray)) { - initp = vscpnump; - } else if (AstInitArray* const vscpnump = VN_CAST(fetchValueNull(vscp), InitArray)) { - initp = vscpnump; - } else { // Assignment to unassigned variable, all bits are X - // TODO generic initialization which builds X/arrays by recursion - AstConst* const outconstp = new AstConst{ - nodep->fileline(), AstConst::WidthedValue{}, basicp->widthMin(), 0}; - if (basicp->isZeroInit()) { - outconstp->num().setAllBits0(); - } else { - outconstp->num().setAllBitsX(); - } - - initp = new AstInitArray{nodep->fileline(), arrayp, outconstp}; - m_reclaimValuesp.push_back(initp); + if (m_checkOnly || !optimizable()) return; + AstNode* const vscp = varOrScope(varrefp); + AstInitArray* initp = nullptr; + if (AstInitArray* const vscpnump = VN_CAST(fetchOutValueNull(vscp), InitArray)) { + initp = vscpnump; + } else if (AstInitArray* const vscpnump = VN_CAST(fetchValueNull(vscp), InitArray)) { + initp = vscpnump; + } else { // Assignment to unassigned variable, all bits are X + // TODO generic initialization which builds X/arrays by recursion + AstConst* const outconstp + = new AstConst{nodep->fileline(), AstConst::WidthedValue{}, basicp->widthMin(), 0}; + if (basicp->isZeroInit()) { + outconstp->num().setAllBits0(); + } else { + outconstp->num().setAllBitsX(); } - const uint32_t index = fetchConst(selp->bitp())->toUInt(); - AstNodeExpr* const valuep = newTrackedClone(fetchValue(valueFromp)); - UINFO(9, " set val[" << index << "] = " << valuep); - // Values are in the "real" tree under the InitArray so can eventually extract it, - // Not in the usual setValue (via m_varAux) - initp->addIndexValuep(index, valuep); - UINFOTREE(9, initp, "", "array"); - assignOutValue(nodep, vscp, initp); + + initp = new AstInitArray{nodep->fileline(), arrayp, outconstp}; + m_reclaimValuesp.push_back(initp); } + const uint32_t index = fetchConst(selp->bitp())->toUInt(); + AstNodeExpr* const valuep = newTrackedClone(fetchValue(valueFromp)); + UINFO(9, " set val[" << index << "] = " << valuep); + // Values are in the "real" tree under the InitArray so can eventually extract it, + // Not in the usual setValue (via m_varAux) + initp->addIndexValuep(index, valuep); + UINFOTREE(9, initp, "", "array"); + assignOutValue(nodep, vscp, initp); } void handleAssignSel(AstNodeAssign* nodep, AstSel* selp, AstNodeExpr* valueFromp) { AstVarRef* varrefp = nullptr; V3Number lsb{nodep}; handleAssignSelRecurse(nodep, selp, varrefp /*ref*/, lsb /*ref*/, 0); - if (!m_checkOnly && optimizable()) { - UASSERT_OBJ(varrefp, nodep, - "Indicated optimizable, but no variable found on RHS of select"); - AstNode* const vscp = varOrScope(varrefp); - AstConst* outconstp = nullptr; - if (AstConst* const vscpnump = fetchOutConstNull(vscp)) { - outconstp = vscpnump; - } else if (AstConst* const vscpnump = fetchConstNull(vscp)) { - outconstp = vscpnump; - } else { // Assignment to unassigned variable, all bits are X or 0 - outconstp = new AstConst{nodep->fileline(), AstConst::WidthedValue{}, - varrefp->varp()->widthMin(), 0}; - if (varrefp->varp()->basicp() && varrefp->varp()->basicp()->isZeroInit()) { - outconstp->num().setAllBits0(); - } else { - outconstp->num().setAllBitsX(); - } - m_reclaimValuesp.emplace_back(outconstp); + if (m_checkOnly || !optimizable()) return; + UASSERT_OBJ(varrefp, nodep, + "Indicated optimizable, but no variable found on RHS of select"); + AstNode* const vscp = varOrScope(varrefp); + AstConst* outconstp = nullptr; + if (AstConst* const vscpnump = fetchOutConstNull(vscp)) { + outconstp = vscpnump; + } else if (AstConst* const vscpnump = fetchConstNull(vscp)) { + outconstp = vscpnump; + } else { // Assignment to unassigned variable, all bits are X or 0 + outconstp = new AstConst{nodep->fileline(), AstConst::WidthedValue{}, + varrefp->varp()->widthMin(), 0}; + if (varrefp->varp()->basicp() && varrefp->varp()->basicp()->isZeroInit()) { + outconstp->num().setAllBits0(); + } else { + outconstp->num().setAllBitsX(); } - outconstp->num().opSelInto(fetchConst(valueFromp)->num(), lsb, selp->widthConst()); - assignOutValue(nodep, vscp, outconstp); + m_reclaimValuesp.emplace_back(outconstp); } + outconstp->num().opSelInto(fetchConst(valueFromp)->num(), lsb, selp->widthConst()); + assignOutValue(nodep, vscp, outconstp); } void handleAssignSelRecurse(AstNodeAssign* nodep, AstSel* selp, AstVarRef*& outVarrefpRef, V3Number& lsbRef, int depth) { @@ -1059,8 +1051,8 @@ private: iterateAndNextConstNull(nodep->stmtsp()); if (!optimizable()) return; iterateAndNextConstNull(nodep->resultp()); - if (!optimizable()) return; - if (!m_checkOnly) newValue(nodep, fetchValue(nodep->resultp())); + if (m_checkOnly || !optimizable()) return; + newValue(nodep, fetchValue(nodep->resultp())); } void visit(AstJumpBlock* nodep) override { @@ -1121,7 +1113,8 @@ private: if (jumpingOver()) return; checkNodeInfo(nodep); iterateConst(nodep->condp()); - if (!m_checkOnly && optimizable() && fetchConst(nodep->condp())->num().isEqZero()) { + if (m_checkOnly || !optimizable()) return; + if (fetchConst(nodep->condp())->num().isEqZero()) { UINFO(5, " LOOP TEST GO " << nodep); UASSERT_OBJ(!m_jumptargetp, nodep, "Jump inside jump"); m_jumptargetp = nodep->loopp(); @@ -1274,8 +1267,7 @@ private: if (!optimizable()) return; // Accelerate checkNodeInfo(nodep); iterateChildrenConst(nodep); - if (m_checkOnly) return; - if (!optimizable()) return; // Accelerate + if (m_checkOnly || !optimizable()) return; AstNode* nextArgp = nodep->exprsp(); string result; @@ -1357,8 +1349,7 @@ private: if (!optimizable()) return; // Accelerate checkNodeInfo(nodep); iterateChildrenConst(nodep); - if (!optimizable()) return; - if (m_checkOnly) return; + if (m_checkOnly || !optimizable()) return; const std::string result = toStringRecurse(nodep->lhsp()); if (!optimizable()) return; AstConst* const resultConstp = new AstConst{nodep->fileline(), AstConst::String{}, result}; diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 060ac2348..25b19e788 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -750,7 +750,7 @@ class TimingControlVisitor final : public VNVisitor { const AstCMethodHard* const methodp = VN_CAST(stmtExprp->exprp(), CMethodHard); if (!methodp || methodp->name() != "push_back") return false; const AstVarRef* const queueRefp = VN_CAST(methodp->fromp(), VarRef); - return queueRefp && queueRefp->name().rfind("__VprocessQueue_", 0) == 0; + return queueRefp && queueRefp->varp()->processQueue(); } // Register a callback so killing a process-backed fork branch decrements the join counter void addForkOnKill(AstBegin* const beginp, AstVarScope* const forkVscp) const { diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 224cd263b..9cccb4b2f 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -1652,7 +1652,7 @@ class WidthVisitor final : public VNVisitor { if (nodep->seedp()) iterateCheckSigned32(nodep, "seed", nodep->seedp(), BOTH); } } - void visit(AstSExprGotoRep* nodep) override { + void visit(AstSGotoRep* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { iterateCheckBool(nodep, "exprp", nodep->exprp(), BOTH); @@ -1660,6 +1660,19 @@ class WidthVisitor final : public VNVisitor { nodep->dtypeSetBit(); } } + void visit(AstSThroughout* nodep) override { + m_hasSExpr = true; + assertAtExpr(nodep); + if (m_vup->prelim()) { + // lhsp is a boolean expression, not a sequence -- clear m_underSExpr temporarily + VL_RESTORER(m_underSExpr); + m_underSExpr = false; + iterateCheckBool(nodep, "lhsp", nodep->lhsp(), BOTH); + m_underSExpr = true; + iterate(nodep->rhsp()); + nodep->dtypeSetBit(); + } + } void visit(AstSExpr* nodep) override { VL_RESTORER(m_underSExpr); m_underSExpr = true; @@ -3301,7 +3314,7 @@ class WidthVisitor final : public VNVisitor { for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) { nextip = itemp->nextp(); // iterate may cause the node to get replaced // InsideRange will get replaced with Lte&Gte and finalized later - if (!VN_IS(itemp, InsideRange)) + if (!VN_IS(itemp, InsideRange) && !itemp->dtypep()->isNonPackedArray()) iterateCheck(nodep, "Inside Item", itemp, CONTEXT_DET, FINAL, expDTypep, EXTEND_EXP); } @@ -3589,6 +3602,32 @@ class WidthVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep), nodep); return; } + if (AstCell* const cellp = VN_CAST(foundp, Cell)) { + // Sub-interface cell selection (e.g. vif.tx): resolve to the + // companion __Viftop var created by V3LinkCells for its dtype. + if (VN_IS(cellp->modp(), Iface)) { + const string viftopName = cellp->name() + "__Viftop"; + AstNodeModule* const parentIfacep = adtypep->ifaceViaCellp(); + AstVar* viftopVarp = nullptr; + for (AstNode* itemp = parentIfacep->stmtsp(); itemp; + itemp = itemp->nextp()) { + if (AstVar* const vp = VN_CAST(itemp, Var)) { + if (vp->name() == viftopName) { + viftopVarp = vp; + break; + } + } + } + UASSERT_OBJ(viftopVarp, nodep, + "No __Viftop variable for sub-interface cell"); + if (!viftopVarp->didWidth()) userIterate(viftopVarp, nullptr); + nodep->dtypep(viftopVarp->dtypep()); + nodep->varp(viftopVarp); + viftopVarp->sensIfacep(VN_AS(cellp->modp(), Iface)); + nodep->didWidth(true); + return; + } + } UINFO(1, "found object " << foundp); nodep->v3fatalSrc("MemberSel of non-variable\n" << nodep->warnContextPrimary() << '\n' @@ -3710,12 +3749,14 @@ class WidthVisitor final : public VNVisitor { AstNode* memberSelIface(AstMemberSel* nodep, AstIfaceRefDType* adtypep) { // Returns node if ok // No need to width-resolve the interface, as it was done when we did the child - AstNodeModule* const ifacep = adtypep->ifacep(); + // ifaceViaCellp() handles dtypes with cellp-only (no ifacep), as produced + // by sub-interface selection, enabling chained access (e.g. vif.tx.Tx). + AstNodeModule* const ifacep = adtypep->ifaceViaCellp(); UASSERT_OBJ(ifacep, nodep, "Unlinked"); VSpellCheck speller; for (AstNode* itemp = ifacep->stmtsp(); itemp; itemp = itemp->nextp()) { if (itemp->name() == nodep->name()) return itemp; - if (VN_IS(itemp, Var) || VN_IS(itemp, Modport)) { + if (VN_IS(itemp, Var) || VN_IS(itemp, Modport) || VN_IS(itemp, Cell)) { speller.pushCandidate(itemp->prettyName()); } } @@ -6030,12 +6071,8 @@ class WidthVisitor final : public VNVisitor { const AstNodeDType* const rhsDtp = nodep->rhsp()->dtypep()->skipRefp(); // Only check if number of states match for unpacked array to unpacked array // assignments - const bool lhsIsUnpackArray - = VN_IS(lhsDtp, UnpackArrayDType) || VN_IS(lhsDtp, DynArrayDType) - || VN_IS(lhsDtp, QueueDType) || VN_IS(lhsDtp, AssocArrayDType); - const bool rhsIsUnpackArray - = VN_IS(rhsDtp, UnpackArrayDType) || VN_IS(rhsDtp, DynArrayDType) - || VN_IS(rhsDtp, QueueDType) || VN_IS(rhsDtp, AssocArrayDType); + const bool lhsIsUnpackArray = lhsDtp->isNonPackedArray(); + const bool rhsIsUnpackArray = rhsDtp->isNonPackedArray(); if (lhsIsUnpackArray && rhsIsUnpackArray) { if (lhsDtp->isFourstate() != rhsDtp->isFourstate()) { nodep->v3error("Assignment between 2-state and 4-state types requires " diff --git a/src/Verilator.cpp b/src/Verilator.cpp index f0f21eaab..f0b4d714b 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -76,6 +76,7 @@ #include "V3LinkLevel.h" #include "V3LinkParse.h" #include "V3LinkResolve.h" +#include "V3LinkWith.h" #include "V3Localize.h" #include "V3MergeCond.h" #include "V3Name.h" @@ -182,6 +183,11 @@ static void process() { V3LinkDot::linkDotParamed(v3Global.rootp()); // Cleanup as made new modules V3LinkLValue::linkLValue(v3Global.rootp()); // Resolve new VarRefs + + // Link cleanup of 'with' as final link phase before V3Width + // (called only once, not when width a single params) + V3LinkWith::linkWith(v3Global.rootp()); + V3Error::abortIfErrors(); // Fix any remaining cross-interface refs created during V3Width::widthParamsEdit diff --git a/src/verilog.y b/src/verilog.y index 1b1771aaa..cedb51a0b 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -6829,7 +6829,7 @@ sexpr: // ==IEEE: sequence_expr (The name sexpr is important as reg new AstConst{$2, 0u}, nullptr, true}; } // // IEEE: goto_repetition (single count form) | ~p~sexpr/*sexpression_or_dist*/ yP_BRAMINUSGT constExpr ']' - { $$ = new AstSExprGotoRep{$2, $1, $3}; } + { $$ = new AstSGotoRep{$2, $1, $3}; } // // IEEE: goto_repetition (range form -- unsupported) | ~p~sexpr/*sexpression_or_dist*/ yP_BRAMINUSGT constExpr ':' constExpr ']' { $$ = $1; BBUNSUP($2, "Unsupported: [-> range goto repetition"); DEL($3); DEL($5); } @@ -6867,7 +6867,7 @@ sexpr: // ==IEEE: sequence_expr (The name sexpr is important as reg | yFIRST_MATCH '(' sexpr ',' sequence_match_itemList ')' { $$ = $3; BBUNSUP($1, "Unsupported: first_match (in sequence expression)"); DEL($5); } | ~p~sexpr/*sexpression_or_dist*/ yTHROUGHOUT sexpr - { $$ = $1; BBUNSUP($2, "Unsupported: throughout (in sequence expression)"); DEL($3); } + { $$ = new AstSThroughout{$2, $1, $3}; } // // Below pexpr's are really sequence_expr, but avoid conflict // // IEEE: sexpr yWITHIN sexpr | ~p~sexpr yWITHIN sexpr @@ -6896,11 +6896,11 @@ cycle_delay_range: // IEEE: ==cycle_delay_range { $$ = new AstDelay{$1, $3, true}; $$->rhsp($5); } | yP_POUNDPOUND yP_BRASTAR ']' - { $$ = new AstDelay{$1, new AstConst{$1, AstConst::BitFalse{}}, true}; - BBUNSUP($1, "Unsupported: ## [*] cycle delay range expression"); } + { $$ = new AstDelay{$1, new AstConst{$1, 0}, true}; + $$->rhsp(new AstUnbounded{$1}); } | yP_POUNDPOUND yP_BRAPLUSKET - { $$ = new AstDelay{$1, new AstConst{$1, AstConst::BitFalse{}}, true}; - BBUNSUP($1, "Unsupported: ## [+] cycle delay range expression"); } + { $$ = new AstDelay{$1, new AstConst{$1, 1}, true}; + $$->rhsp(new AstUnbounded{$1}); } ; sequence_match_itemList: // IEEE: [sequence_match_item] part of sequence_expr @@ -6925,7 +6925,7 @@ boolean_abbrev: // ==IEEE: boolean_abbrev | yP_BRAEQ constExpr ':' constExpr ']' { $$ = $2; BBUNSUP($1, "Unsupported: [= boolean abbrev expression"); DEL($4); } // // IEEE: goto_repetition - // // Goto repetition [->N] handled in sexpr rule (AstSExprGotoRep) + // // Goto repetition [->N] handled in sexpr rule (AstSGotoRep) // // Range form [->M:N] also handled there (unsupported) ; diff --git a/test_regress/t/t_class_default_type_param.v b/test_regress/t/t_class_default_type_param.v index a0560bfa0..b1a3c174e 100644 --- a/test_regress/t/t_class_default_type_param.v +++ b/test_regress/t/t_class_default_type_param.v @@ -8,16 +8,20 @@ // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 package p; - class W #(type T = int); + class W #( + type T = int + ); T v; endclass - class Holder #(type U = W#()); + class Holder #( + type U = W#() + ); U u; endclass - typedef Holder#() H_imp_t; // implicit default - typedef Holder#(W#(int)) H_exp_t; // explicit equivalent default + typedef Holder#() H_imp_t; // implicit default + typedef Holder#(W#(int)) H_exp_t; // explicit equivalent default endpackage module t; @@ -29,8 +33,8 @@ module t; // verilator lint_off CASTCONST // verilator lint_off WIDTHTRUNC if (!$cast(exp, imp)) begin - // verilator lint_on WIDTHTRUNC - // verilator lint_on CASTCONST + // verilator lint_on WIDTHTRUNC + // verilator lint_on CASTCONST $display("WRONG_TYPE"); $fatal; end diff --git a/test_regress/t/t_constraint_before_randc_bad.out b/test_regress/t/t_constraint_before_randc_bad.out index 54ab30db7..56f77b430 100644 --- a/test_regress/t/t_constraint_before_randc_bad.out +++ b/test_regress/t/t_constraint_before_randc_bad.out @@ -1,4 +1,5 @@ %Error: t/t_constraint_before_randc_bad.v:11:42: Randc variables not allowed in 'solve before' (IEEE 1800-2023 18.5.9) + : ... note: In instance 't' 11 | constraint raint2_bad {solve b1 before b2;} | ^~ t/t_constraint_before_randc_bad.v:11:26: ... Location of restricting expression diff --git a/test_regress/t/t_constraint_dist_randc_bad.out b/test_regress/t/t_constraint_dist_randc_bad.out index a4afdd756..c9acd0cbb 100644 --- a/test_regress/t/t_constraint_dist_randc_bad.out +++ b/test_regress/t/t_constraint_dist_randc_bad.out @@ -1,4 +1,5 @@ %Error: t/t_constraint_dist_randc_bad.v:10:22: Randc variables not allowed in 'constraint dist' (IEEE 1800-2023 18.5.3) + : ... note: In instance 't' 10 | constraint c_bad { rc dist {3 := 0, 10 := 5}; } | ^~ t/t_constraint_dist_randc_bad.v:10:25: ... Location of restricting expression diff --git a/test_regress/t/t_constraint_soft_randc_bad.out b/test_regress/t/t_constraint_soft_randc_bad.out index 5a20ca3cd..470e0e1f3 100644 --- a/test_regress/t/t_constraint_soft_randc_bad.out +++ b/test_regress/t/t_constraint_soft_randc_bad.out @@ -1,4 +1,5 @@ %Error: t/t_constraint_soft_randc_bad.v:10:27: Randc variables not allowed in 'constraint soft' (IEEE 1800-2023 18.5.13.1) + : ... note: In instance 't' 10 | constraint c_bad { soft rc > 4; } | ^~ t/t_constraint_soft_randc_bad.v:10:22: ... Location of restricting expression diff --git a/test_regress/t/t_constraint_solver_log.py b/test_regress/t/t_constraint_solver_log.py new file mode 100755 index 000000000..637c1e532 --- /dev/null +++ b/test_regress/t/t_constraint_solver_log.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +solver_log = test.obj_dir + "/solver.log" + +test.execute(all_run_flags=['+verilator+solver+file+' + solver_log]) + +test.file_grep(solver_log, '# Verilator solver log') + +test.passes() diff --git a/test_regress/t/t_constraint_solver_log.v b/test_regress/t/t_constraint_solver_log.v new file mode 100644 index 000000000..f985b812a --- /dev/null +++ b/test_regress/t/t_constraint_solver_log.v @@ -0,0 +1,27 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +class Packet; + rand int one; + constraint a {one > 0 && one < 2;} +endclass + +module t; + + Packet p; + + int v; + + initial begin + p = new; + v = p.randomize(); + if (v != 1) $stop; + if (p.one != 1) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_function_generate.v b/test_regress/t/t_function_generate.v index f6f105949..0a855e088 100644 --- a/test_regress/t/t_function_generate.v +++ b/test_regress/t/t_function_generate.v @@ -11,13 +11,14 @@ // SPDX-FileCopyrightText: 2026 Wilson Snyder // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -module t(/*AUTOARG*/); +module t ( /*AUTOARG*/); generate if (1) begin : defs function automatic logic foo; foo = 1'b1; endfunction - end else begin : defs + end + else begin : defs function automatic logic foo; foo = 1'b0; endfunction diff --git a/test_regress/t/t_inside_queue_elem.v b/test_regress/t/t_inside_queue_elem.v index 9eabc717d..b7968aac2 100644 --- a/test_regress/t/t_inside_queue_elem.v +++ b/test_regress/t/t_inside_queue_elem.v @@ -4,14 +4,43 @@ // SPDX-FileCopyrightText: 2024 Antmicro // SPDX-License-Identifier: CC0-1.0 +// verilog_format: off +`define stop $stop +`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + module t; + function string validate_time_precision(string precision); + static string valid_precision[$] = '{"ps", "ns", "us", "ms", "s"}; + if (!(precision inside {valid_precision})) begin + return "none"; + end + return precision; + endfunction + initial begin automatic int q[$] = {1, 2}; + string s; if (!(1 inside {q[0], q[1]})) $stop; if (3 inside {q[0], q[1]}) $stop; + s = validate_time_precision("ps"); + `checks(s, "ps"); + s = validate_time_precision("ns"); + `checks(s, "ns"); + s = validate_time_precision("us"); + `checks(s, "us"); + s = validate_time_precision("ms"); + `checks(s, "ms"); + s = validate_time_precision("s"); + `checks(s, "s"); + s = validate_time_precision("random"); + `checks(s, "none"); + s = validate_time_precision(""); + `checks(s, "none"); + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_interface_virtual_func_wait.v b/test_regress/t/t_interface_virtual_func_wait.v index 7136bf1bb..fcf0c56f6 100644 --- a/test_regress/t/t_interface_virtual_func_wait.v +++ b/test_regress/t/t_interface_virtual_func_wait.v @@ -6,7 +6,7 @@ interface my_if; logic clk = 0; - bit clk_active = 0; + bit clk_active = 0; initial begin wait (clk_active); @@ -27,8 +27,8 @@ class Driver; endclass module t; - my_if intf(); - my_if intf_unused(); // Second instance triggered the bug + my_if intf (); + my_if intf_unused (); // Second instance triggered the bug initial begin automatic Driver d = new; diff --git a/test_regress/t/t_interface_virtual_sub_iface.py b/test_regress/t/t_interface_virtual_sub_iface.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_interface_virtual_sub_iface.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_sub_iface.v b/test_regress/t/t_interface_virtual_sub_iface.v new file mode 100644 index 000000000..53ad587b4 --- /dev/null +++ b/test_regress/t/t_interface_virtual_sub_iface.v @@ -0,0 +1,74 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +// Issue #7203: virtual interface select from sub-interface instance. +// The original reproducer: vip_agent holds vip_vif; vip_driver selects +// agent.vif.tx (a vip_tx_if sub-interface) into tx_vif. + +interface vip_tx_if ( + output reg Tx +); +endinterface + +interface vip_if ( + output reg Tx +); + vip_tx_if tx (Tx); +endinterface + +package vip_pkg; + typedef virtual vip_if vip_vif; + typedef virtual vip_tx_if vip_tx_vif; + + class vip_agent; + vip_vif vif; + endclass + + class vip_driver; + vip_vif vif; + vip_tx_vif tx_vif; + virtual function void build_phase(vip_agent agent); + // Sub-interface select: dtype(agent.vif) -> vip_vif -> vip_if + vif = agent.vif; + tx_vif = agent.vif.tx; + endfunction + // Chained member access through sub-interface + virtual function void drive(logic val); + vif.tx.Tx = val; + endfunction + endclass +endpackage + +module t; + logic wire_Tx; + vip_if vif_inst (.Tx(wire_Tx)); + + initial begin + automatic vip_pkg::vip_agent agent = new; + automatic vip_pkg::vip_driver driver = new; + + agent.vif = vif_inst; + driver.vif = vif_inst; + + // Test 1 (issue reproducer): sub-interface select compiles and runs + driver.build_phase(agent); + + // Test 2: tx_vif now points to the sub-interface; write through it + driver.tx_vif.Tx = 1'b1; + `checkd(wire_Tx, 1'b1) + + // Test 3: chained member write through virtual interface + driver.drive(1'b0); + `checkd(wire_Tx, 1'b0) + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_param_cast_default.py b/test_regress/t/t_param_cast_default.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_param_cast_default.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_param_cast_default.v b/test_regress/t/t_param_cast_default.v new file mode 100644 index 000000000..34f01e8f6 --- /dev/null +++ b/test_regress/t/t_param_cast_default.v @@ -0,0 +1,116 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +// Original #6281 reproducer: parameter passed via localparam variable +// vs. literal constant should resolve to the same specialization. +// Fixed by ParameterizedHierBlocks::areSame fallback (landed earlier). +class ClsIntDefault #( + parameter int P = 32 +); + function int get_p; + return P; + endfunction +endclass + +// Parameter with byte cast default value +class ClsByteCast #( + parameter byte P = byte'(8) +); + function byte get_p; + return P; + endfunction +endclass + +// Parameter with int cast default value +class ClsIntCast #( + parameter int P = int'(42) +); + function int get_p; + return P; + endfunction +endclass + +// Parameter with signed cast default value +class ClsSignedCast #( + parameter int P = int'(-5) +); + function int get_p; + return P; + endfunction +endclass + +// Module with cast default (cell array test) +module sub #( + parameter byte P = byte'(8) +); + initial begin + `checkd(P, 8); + end +endmodule + +module t; + // Original #6281 case: localparam variable vs. literal constant + localparam int WIDTH = 32; + ClsIntDefault #(32) orig_a; + ClsIntDefault #(WIDTH) orig_b; + + // Byte cast default: #() and #(8) should be same type + ClsByteCast #() byte_a; + ClsByteCast #(8) byte_b; + + // Int cast default: #() and #(42) should be same type + ClsIntCast #() int_a; + ClsIntCast #(42) int_b; + + // Signed cast default: #() and #(-5) should be same type + ClsSignedCast #() signed_a; + ClsSignedCast #(-5) signed_b; + + // Multiple instances (template mutation safety) + ClsByteCast #() multi_a; + ClsByteCast #(8) multi_b; + ClsByteCast #() multi_c; + ClsByteCast #(8) multi_d; + + // Module with cast default + sub #() sub_default (); + sub #(8) sub_explicit (); + + initial begin + orig_a = new; + orig_b = orig_a; + `checkd(orig_b.get_p(), 32); + + byte_a = new; + byte_b = byte_a; + `checkd(byte_a.get_p(), 8); + `checkd(byte_b.get_p(), 8); + + int_a = new; + int_b = int_a; + `checkd(int_a.get_p(), 42); + `checkd(int_b.get_p(), 42); + + signed_a = new; + signed_b = signed_a; + `checkd(signed_a.get_p(), -5); + `checkd(signed_b.get_p(), -5); + + multi_a = new; + multi_b = multi_a; + multi_c = multi_a; + multi_d = multi_a; + `checkd(multi_d.get_p(), 8); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_param_width_loc_bad.out b/test_regress/t/t_param_width_loc_bad.out index 36795729c..ed7b2e12c 100644 --- a/test_regress/t/t_param_width_loc_bad.out +++ b/test_regress/t/t_param_width_loc_bad.out @@ -1,4 +1,4 @@ -%Warning-WIDTHTRUNC: t/t_param_width_loc_bad.v:19:21: Operator VAR 'PARAM' expects 1 bits on the Initial value, but Initial value's CONST '32'h0' generates 32 bits. +%Warning-WIDTHTRUNC: t/t_param_width_loc_bad.v:19:21: Operator VAR 'PARAM' expects 1 bits on the Initial value, but Initial value's CONST '32'h1' generates 32 bits. : ... note: In instance 't.test_i' 19 | parameter logic PARAM = 1'b0 | ^~~~~ diff --git a/test_regress/t/t_param_width_loc_bad.v b/test_regress/t/t_param_width_loc_bad.v index 9ea9a5b99..c1bfa49a0 100644 --- a/test_regress/t/t_param_width_loc_bad.v +++ b/test_regress/t/t_param_width_loc_bad.v @@ -7,7 +7,7 @@ module t; // bug1624 - test #(.PARAM(32'd0)) test_i (); + test #(.PARAM(32'd1)) test_i (); initial begin $write("*-* All Finished *-*\n"); diff --git a/test_regress/t/t_property_sexpr_range_delay.v b/test_regress/t/t_property_sexpr_range_delay.v index d8e3ef3d3..3ab84e36b 100644 --- a/test_regress/t/t_property_sexpr_range_delay.v +++ b/test_regress/t/t_property_sexpr_range_delay.v @@ -20,6 +20,8 @@ module t ( wire a = crc[0]; wire b = crc[1]; wire c = crc[2]; + wire d = crc[3]; + wire e = crc[4]; wire [63:0] result = {61'h0, c, b, a}; @@ -46,44 +48,70 @@ module t ( end end - // Basic ##[1:3] range delay (CRC-driven, always-true consequent) + // Basic ##[1:3] range delay assert property (@(posedge clk) disable iff (cyc < 2) - a |-> ##[1:3] 1'b1); + a |-> ##[1:3] (a | b | c | d | e)); // ##[2:4] range delay assert property (@(posedge clk) disable iff (cyc < 2) - b |-> ##[2:4] 1'b1); + b |-> ##[2:4] (a | b | c | d | e)); // Degenerate ##[2:2] (equivalent to ##2) assert property (@(posedge clk) disable iff (cyc < 2) - a |-> ##[2:2] 1'b1); + a |-> ##[2:2] (a | b | c | d | e)); - // Multi-step: ##[1:2] then ##1 (both consequents always true) + // Multi-step: ##[1:2] then ##1 assert property (@(posedge clk) disable iff (cyc < 2) - a |-> ##[1:2] 1'b1 ##1 1'b1); + a |-> ##[1:2] (a | b | c | d | e) ##1 (a | b | c | d | e)); // Large range ##[1:10000] (scalability, O(1) code size) assert property (@(posedge clk) disable iff (cyc < 2) - a |-> ##[1:10000] 1'b1); + a |-> ##[1:10000] (a | b | c | d | e)); // Range with binary SExpr: nextStep has delay > 0 after range match assert property (@(posedge clk) disable iff (cyc < 2) - a |-> b ##[1:2] 1'b1 ##3 1'b1); + a |-> b ##[1:2] (a | b | c | d | e) ##3 (a | b | c | d | e)); // Binary SExpr without implication (covers firstStep.exprp path without antecedent) assert property (@(posedge clk) disable iff (cyc < 2) - a ##[1:3] 1'b1); + a ##[1:3] (a | b | c | d | e)); // Implication with binary SExpr RHS (covers antExprp AND firstStep.exprp) assert property (@(posedge clk) disable iff (cyc < 2) - a |-> b ##[1:2] 1'b1); + a |-> b ##[1:2] (a | b | c | d | e)); // Fixed delay before range (covers firstStep.delay path in IDLE) assert property (@(posedge clk) disable iff (cyc < 2) - a |-> ##2 1'b1 ##[1:3] 1'b1); + a |-> ##2 (a | b | c | d | e) ##[1:3] (a | b | c | d | e)); // Unary range with no antecedent and no preExpr (covers unconditional start) assert property (@(posedge clk) disable iff (cyc < 2) - ##[1:3] 1'b1); + ##[1:3] (a | b | c | d | e)); + + // ##[+] (= ##[1:$]): wait >= 1 cycle for b (CRC-driven, exercises CHECK stay) + assert property (@(posedge clk) disable iff (cyc < 2) + a |-> ##[+] b); + + // ##[*] (= ##[0:$]): check b immediately or after >= 1 cycle + assert property (@(posedge clk) disable iff (cyc < 2) + a |-> ##[*] b); + + // ##[2:$]: explicit min > 1, wait then check c (exercises WAIT_MIN) + assert property (@(posedge clk) disable iff (cyc < 2) + b |-> ##[2:$] c); + + // ##[1:$]: explicit form equivalent to ##[+] + assert property (@(posedge clk) disable iff (cyc < 2) + a |-> ##[1:$] c); + + // Unary ##[+] and ##[*] without antecedent + assert property (@(posedge clk) disable iff (cyc < 2) + ##[+] b); + assert property (@(posedge clk) disable iff (cyc < 2) + ##[*] b); + + // Multi-step with unbounded range: ##[+] then fixed ##1 + assert property (@(posedge clk) disable iff (cyc < 2) + a |-> ##[+] b ##1 (a | b | c | d | e)); endmodule diff --git a/test_regress/t/t_property_sexpr_range_delay_bad.out b/test_regress/t/t_property_sexpr_range_delay_bad.out index d83ac4143..81e3414ab 100644 --- a/test_regress/t/t_property_sexpr_range_delay_bad.out +++ b/test_regress/t/t_property_sexpr_range_delay_bad.out @@ -11,9 +11,17 @@ : ... note: In instance 't' 24 | a3: assert property (@(posedge clk) a |-> ##[5:2] b); | ^~ -%Error-UNSUPPORTED: t/t_property_sexpr_range_delay_bad.v:27:45: Unsupported: ##0 in range delays +%Error-UNSUPPORTED: t/t_property_sexpr_range_delay_bad.v:27:45: Unsupported: ##0 in bounded range delays : ... note: In instance 't' 27 | a4: assert property (@(posedge clk) a |-> ##[0:3] b); | ^~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: t/t_property_sexpr_range_delay_bad.v:30:45: Range delay minimum must be an elaboration-time constant (IEEE 1800-2023 16.7) + : ... note: In instance 't' + 30 | a5: assert property (@(posedge clk) a |-> ##[cyc:$] b); + | ^~ +%Error: t/t_property_sexpr_range_delay_bad.v:34:45: Range delay bounds must be non-negative (IEEE 1800-2023 16.7) + : ... note: In instance 't' + 34 | a6: assert property (@(posedge clk) a |-> ##[NEG:$] b); + | ^~ %Error: Exiting due to diff --git a/test_regress/t/t_property_sexpr_range_delay_bad.v b/test_regress/t/t_property_sexpr_range_delay_bad.v index 19441f50d..91ae69e9a 100644 --- a/test_regress/t/t_property_sexpr_range_delay_bad.v +++ b/test_regress/t/t_property_sexpr_range_delay_bad.v @@ -26,4 +26,11 @@ module t; // ##0 in range a4: assert property (@(posedge clk) a |-> ##[0:3] b); + // Non-constant minimum in unbounded range + a5: assert property (@(posedge clk) a |-> ##[cyc:$] b); + + // Negative minimum in unbounded range + localparam int NEG = -1; + a6: assert property (@(posedge clk) a |-> ##[NEG:$] b); + endmodule diff --git a/test_regress/t/t_randomize_nested_unsup.out b/test_regress/t/t_randomize_nested_unsup.out index f8d432abe..90b392e1e 100644 --- a/test_regress/t/t_randomize_nested_unsup.out +++ b/test_regress/t/t_randomize_nested_unsup.out @@ -1,4 +1,5 @@ %Error-UNSUPPORTED: t/t_randomize_nested_unsup.v:17:41: Unsupported: randomize() nested in inline randomize() constraints + : ... note: In instance 't' 17 | if (a.randomize() with {rdata == aa.randomize();} == 0) $stop; | ^~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest diff --git a/test_regress/t/t_sequence_sexpr_throughout.py b/test_regress/t/t_sequence_sexpr_throughout.py new file mode 100755 index 000000000..35e44000c --- /dev/null +++ b/test_regress/t/t_sequence_sexpr_throughout.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_sequence_sexpr_throughout.v b/test_regress/t/t_sequence_sexpr_throughout.v new file mode 100644 index 000000000..f47b51685 --- /dev/null +++ b/test_regress/t/t_sequence_sexpr_throughout.v @@ -0,0 +1,67 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`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); +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +// IEEE 1800-2023 16.9.9: expr throughout seq +// CRC-driven random stimulus exercises throughout with varying cond/a/b signals. + +module t ( + input clk +); + integer cyc = 0; + reg [63:0] crc = '0; + + // Derive signals from non-adjacent CRC bits (gap > max delay to avoid LFSR correlation) + wire cond = crc[0]; + wire a = crc[4]; + wire b = crc[8]; + + int count_fail1 = 0; + int count_fail2 = 0; + int count_fail3 = 0; + + // Test 1: a |-> (cond throughout (1'b1 ##3 1'b1)) + // If a fires, cond must hold for 4 consecutive ticks (start + 3 delay ticks). + assert property (@(posedge clk) disable iff (cyc < 10) + a |-> (cond throughout (1'b1 ##3 1'b1))) + else count_fail1 <= count_fail1 + 1; + + // Test 2: a |-> (cond throughout (1'b1 ##1 b)) + // If a fires, cond must hold for 2 ticks and b must be true at tick +1. + assert property (@(posedge clk) disable iff (cyc < 10) + a |-> (cond throughout (1'b1 ##1 b))) + else count_fail2 <= count_fail2 + 1; + + // Test 3: a |-> (cond throughout b) + // No delay: degenerates to a |-> (cond && b). + assert property (@(posedge clk) disable iff (cyc < 10) + a |-> (cond throughout b)) + else count_fail3 <= count_fail3 + 1; + + always_ff @(posedge clk) begin +`ifdef TEST_VERBOSE + $write("[%0t] cyc==%0d crc=%x cond=%b a=%b b=%b\n", + $time, cyc, crc, cond, a, b); +`endif + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; + if (cyc == 0) begin + crc <= 64'h5aef0c8d_d70a4497; + end else if (cyc == 99) begin + `checkh(crc, 64'hc77bb9b3784ea091); + `checkd(count_fail1, 36); + `checkd(count_fail2, 37); + `checkd(count_fail3, 31); + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_sequence_sexpr_throughout_unsup.out b/test_regress/t/t_sequence_sexpr_throughout_unsup.out new file mode 100644 index 000000000..415250db7 --- /dev/null +++ b/test_regress/t/t_sequence_sexpr_throughout_unsup.out @@ -0,0 +1,14 @@ +%Error-UNSUPPORTED: t/t_sequence_sexpr_throughout_unsup.v:14:16: Unsupported: throughout with range delay sequence + : ... note: In instance 't' + 14 | a |-> (a throughout (b ##[1:2] c))); + | ^~~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_sequence_sexpr_throughout_unsup.v:18:16: Unsupported: throughout with complex sequence operator + : ... note: In instance 't' + 18 | a |-> (a throughout ((b ##1 c) and (c ##1 b)))); + | ^~~~~~~~~~ +%Error-UNSUPPORTED: t/t_sequence_sexpr_throughout_unsup.v:22:16: Unsupported: throughout with complex sequence operator + : ... note: In instance 't' + 22 | a |-> (a throughout (b throughout (b ##1 c)))); + | ^~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_sequence_sexpr_throughout_unsup.py b/test_regress/t/t_sequence_sexpr_throughout_unsup.py new file mode 100755 index 000000000..1c3e73246 --- /dev/null +++ b/test_regress/t/t_sequence_sexpr_throughout_unsup.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(expect_filename=test.golden_filename, + verilator_flags2=['--assert --timing --error-limit 1000'], + fails=True) + +test.passes() diff --git a/test_regress/t/t_sequence_sexpr_throughout_unsup.v b/test_regress/t/t_sequence_sexpr_throughout_unsup.v new file mode 100644 index 000000000..2b7cea58d --- /dev/null +++ b/test_regress/t/t_sequence_sexpr_throughout_unsup.v @@ -0,0 +1,24 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + logic a, b, c; + + // Unsupported: throughout with range delay on RHS (IEEE 16.9.9) + assert property (@(posedge clk) + a |-> (a throughout (b ##[1:2] c))); + + // Unsupported: throughout with temporal 'and' sequence on RHS + assert property (@(posedge clk) + a |-> (a throughout ((b ##1 c) and (c ##1 b)))); + + // Unsupported: nested throughout + assert property (@(posedge clk) + a |-> (a throughout (b throughout (b ##1 c)))); + +endmodule diff --git a/test_regress/t/t_sequence_sexpr_unsup.out b/test_regress/t/t_sequence_sexpr_unsup.out index d5e9fe632..fe146c0de 100644 --- a/test_regress/t/t_sequence_sexpr_unsup.out +++ b/test_regress/t/t_sequence_sexpr_unsup.out @@ -2,24 +2,9 @@ 34 | a within(b); | ^~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:46:7: Unsupported: throughout (in sequence expression) - 46 | a throughout b; - | ^~~~~~~~~~ %Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:50:7: Unsupported: intersect (in sequence expression) 50 | a intersect b; | ^~~~~~~~~ -%Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:63:5: Unsupported: ## [*] cycle delay range expression - 63 | ## [*] b; - | ^~ -%Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:66:5: Unsupported: ## [+] cycle delay range expression - 66 | ## [+] b; - | ^~ -%Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:79:7: Unsupported: ## [*] cycle delay range expression - 79 | a ## [*] b; - | ^~ -%Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:82:7: Unsupported: ## [+] cycle delay range expression - 82 | a ## [+] b; - | ^~ %Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:95:7: Unsupported: [= boolean abbrev expression 95 | a [= 1]; | ^~ diff --git a/test_regress/t/t_virtual_interface_trigger_unsup.v b/test_regress/t/t_virtual_interface_trigger_unsup.v index 6fb2315e2..5b26963ac 100644 --- a/test_regress/t/t_virtual_interface_trigger_unsup.v +++ b/test_regress/t/t_virtual_interface_trigger_unsup.v @@ -9,7 +9,7 @@ interface str_if; endinterface module t; - str_if sif(); + str_if sif (); virtual str_if vif = sif; initial begin diff --git a/test_regress/t/t_vpi_force.cpp b/test_regress/t/t_vpi_force.cpp index 9165c5ce0..8049f3909 100644 --- a/test_regress/t/t_vpi_force.cpp +++ b/test_regress/t/t_vpi_force.cpp @@ -99,9 +99,9 @@ enum class Direction : uint8_t { }; #ifndef IVERILOG -const std::array TestSignals = { +const std::array TestSignals = { #else // Multidimensional packed arrays aren't tested in Icarus -const std::array TestSignals = { +const std::array TestSignals = { #endif TestSignal{"onebit", vpiIntVal, @@ -149,6 +149,26 @@ const std::array TestSignals = { 0}}, // NOLINTEND (cppcoreguidelines-avoid-c-arrays) + TestSignal{"forcedNonForceable", + vpiVectorVal, + {}, + // NOLINTBEGIN (cppcoreguidelines-avoid-c-arrays) + {.vector = (t_vpi_vecval[]){{0b10101010, 0}}}, + {.vector = (t_vpi_vecval[]){{0b01010101, 0}}}, + true, + {{.vector = (t_vpi_vecval[]){{0b10100101, 0}}}, + {.vector = (t_vpi_vecval[]){{0b01011010, 0}}}, + {.vector = (t_vpi_vecval[]){{0x5, 0}}}, + {.vector = (t_vpi_vecval[]){{0xA, 0}}}, + {.lo = 0, .hi = 3}}, + {{.vector = (t_vpi_vecval[]){{0b10101011, 0}}}, + {.vector = (t_vpi_vecval[]){{0b01010100, 0}}}, + vpiVectorVal, + {.vector = (t_vpi_vecval[]){{0b1, 0}}}, + {.vector = (t_vpi_vecval[]){{0b0, 0}}}, + 0}}, + // NOLINTEND (cppcoreguidelines-avoid-c-arrays) + TestSignal{ "vectorQ", vpiVectorVal, diff --git a/test_regress/t/t_vpi_force.v b/test_regress/t/t_vpi_force.v index 623a0fa23..e9060ff71 100644 --- a/test_regress/t/t_vpi_force.v +++ b/test_regress/t/t_vpi_force.v @@ -130,6 +130,10 @@ typedef enum byte { // Verify that vpi_put_value still works with vpiInertialDelay logic [ 31:0] delayed `PUBLIC_FORCEABLE; // IData + // Verify that VPI still sees forced value if signal is forced through + // SystemVerilog, but not marked as forceable + logic [ 7:0] forcedNonForceable /*verilator public_flat_rw*/; // CData + // Clocked signals // Force with vpiIntVal @@ -208,6 +212,8 @@ typedef enum byte { // Continuously assigned signals: + wire [ 7:0] forcedNonForceableContinuously /*verilator public_flat_rw*/; // CData + // Force with vpiIntVal wire onebitContinuously `PUBLIC_FORCEABLE; // CData wire [ 31:0] intvalContinuously `PUBLIC_FORCEABLE; // IData @@ -282,6 +288,7 @@ typedef enum byte { always @(posedge clk) begin nonPublic <= 1; + forcedNonForceable <= 8'hAA; onebit <= 1; intval <= 32'hAAAAAAAA; @@ -356,6 +363,7 @@ typedef enum byte { ascPacked4dW <= '{'{'{'{32'hAAAAAAAA, 32'hAAAAAAAA, 32'hAAAAAAAA, 32'hAAAAAAAA}}}}; `endif end + assign forcedNonForceableContinuously = 8'hAA; assign onebitContinuously = 1; assign intvalContinuously = 32'hAAAAAAAA; @@ -431,6 +439,7 @@ typedef enum byte { `endif task automatic svForceValues(); + force forcedNonForceable = 8'h55; force onebit = 0; force intval = 32'h55555555; force vectorC = 8'h55; @@ -491,6 +500,7 @@ typedef enum byte { force ascPacked4dW[-3][2][-1][5] = 32'h55555555; `endif + force forcedNonForceableContinuously = 8'h55; force onebitContinuously = 0; force intvalContinuously = 32'h55555555; force vectorCContinuously = 8'h55; @@ -550,6 +560,8 @@ typedef enum byte { endtask task automatic svPartiallyForceValues(); + force forcedNonForceable[3:0] = 4'h5; + force intval[15:0] = 16'h5555; force vectorC[3:0] = 4'h5; @@ -585,6 +597,8 @@ typedef enum byte { `endif `endif + force forcedNonForceableContinuously[3:0] = 4'h5; + force intvalContinuously[15:0] = 16'h5555; force vectorCContinuously[3:0] = 4'h5; @@ -622,6 +636,7 @@ typedef enum byte { endtask task automatic svForceSingleBit(); + force forcedNonForceable[0] = 1; force intval[0] = 1; force vectorC[0] = 1; force vectorQ[0] = 1; @@ -650,6 +665,7 @@ typedef enum byte { force ascPacked4dW[-3][2][-1][5][39] = 1; `endif + force forcedNonForceableContinuously[0] = 1; force intvalContinuously[0] = 1; force vectorCContinuously[0] = 1; force vectorQContinuously[0] = 1; @@ -861,6 +877,7 @@ typedef enum byte { endtask task automatic svReleaseValues(); + release forcedNonForceable; release onebit; release intval; release vectorC; @@ -899,6 +916,7 @@ typedef enum byte { release ascPacked4dW[-3][2][-1][5]; `endif + release forcedNonForceableContinuously; release onebitContinuously; release intvalContinuously; release vectorCContinuously; @@ -968,6 +986,8 @@ typedef enum byte { endtask task automatic svPartiallyReleaseValues(); + release forcedNonForceable[3:0]; + release intval[15:0]; release vectorC[3:0]; @@ -1001,6 +1021,8 @@ typedef enum byte { release ascPacked4dW[-3][2][-1][5][24:39]; `endif + release forcedNonForceableContinuously[3:0]; + release intvalContinuously[15:0]; release vectorCContinuously[3:0]; @@ -1095,6 +1117,7 @@ typedef enum byte { endtask task automatic svReleaseSingleBit(); + release forcedNonForceable[0]; release intval[0]; release vectorC[0]; release vectorQ[0]; @@ -1123,6 +1146,7 @@ typedef enum byte { release ascPacked4dW[-3][2][-1][5][39]; `endif + release forcedNonForceableContinuously[0]; release intvalContinuously[0]; release vectorCContinuously[0]; release vectorQContinuously[0]; @@ -1224,6 +1248,7 @@ typedef enum byte { task automatic svCheckValuesForced(); svCheckNonContinuousValuesForced(); + `checkh(forcedNonForceableContinuously, 8'h55); `checkh(onebitContinuously, 0); `checkh(intvalContinuously, 32'h55555555); `checkh(vectorCContinuously, 8'h55); @@ -1290,6 +1315,7 @@ typedef enum byte { endtask task automatic svCheckNonContinuousValuesForced(); + `checkh(forcedNonForceable, 8'h55); `checkh(onebit, 0); `checkh(intval, 32'h55555555); `checkh(vectorC, 8'h55); @@ -1355,6 +1381,7 @@ typedef enum byte { endtask task automatic svCheckContinuousValuesReleased(); + `checkh(forcedNonForceableContinuously, 8'hAA); `checkh(onebitContinuously, 1); `checkh(intvalContinuously, 32'hAAAAAAAA); `checkh(vectorCContinuously, 8'hAA); @@ -1421,6 +1448,7 @@ typedef enum byte { task automatic svCheckValuesPartiallyForced(); svCheckNonContinuousValuesPartiallyForced(); + `checkh(forcedNonForceableContinuously, 8'h A5); `checkh(intvalContinuously, 32'hAAAA_5555); `checkh(vectorCContinuously, 8'h A5); `checkh(vectorQContinuously, 62'h2AAAAAAAD5555555); @@ -1457,6 +1485,7 @@ typedef enum byte { task automatic svCheckSingleBitForced(); svCheckNonContinuousSingleBitForced(); + `checkh(forcedNonForceableContinuously, 8'hAB); `checkh(intvalContinuously, 32'hAAAAAAAB); `checkh(vectorCContinuously, 8'hAB); `checkh(vectorQContinuously, 62'h2AAAAAAA_AAAAAAAB); @@ -1535,6 +1564,7 @@ typedef enum byte { endtask task automatic svCheckNonContinuousValuesPartiallyForced(); + `checkh(forcedNonForceable, 8'h A5); `checkh(intval, 32'hAAAA_5555); `checkh(vectorC, 8'h A5); `checkh(vectorQ, 62'h2AAAAAAAD5555555); @@ -1593,6 +1623,7 @@ typedef enum byte { endtask task automatic svCheckNonContinuousSingleBitForced(); + `checkh(forcedNonForceable, 8'hAB); `checkh(intval, 32'hAAAAAAAB); `checkh(vectorC, 8'hAB); `checkh(vectorQ, 62'h2AAAAAAA_AAAAAAAB); @@ -1648,6 +1679,7 @@ typedef enum byte { endtask task automatic svCheckValuesReleased(); + `checkh(forcedNonForceable, 8'hAA); `checkh(onebit, 1); `checkh(intval, 32'hAAAAAAAA); `checkh(vectorC, 8'hAA); @@ -1714,6 +1746,7 @@ typedef enum byte { endtask task automatic svCheckValuesPartiallyReleased(); + `checkh(forcedNonForceable, 'h5a); `checkh(intval, 'h5555aaaa); `checkh(vectorC, 'h5a); `checkh(vectorQ, 'h155555552aaaaaaa); @@ -1750,6 +1783,7 @@ typedef enum byte { endtask task automatic svCheckContinuousValuesPartiallyReleased(); + `checkh(forcedNonForceableContinuously, 'h5a); `checkh(intvalContinuously, 'h5555aaaa); `checkh(vectorCContinuously, 'h5a); `checkh(vectorQContinuously, 'h155555552aaaaaaa); @@ -1830,6 +1864,7 @@ typedef enum byte { endtask task automatic svCheckContinuousValuesSingleBitReleased(); + `checkh(forcedNonForceableContinuously, 8'h54); `checkh(intvalContinuously, 32'h55555554); `checkh(vectorCContinuously, 8'h54); `checkh(vectorQContinuously, 62'h15555555_55555554); @@ -1886,6 +1921,7 @@ typedef enum byte { endtask task automatic svCheckSingleBitReleased(); + `checkh(forcedNonForceable, 8'h54); `checkh(intval, 32'h55555554); `checkh(vectorC, 8'h54); `checkh(vectorQ, 62'h15555555_55555554); @@ -2443,6 +2479,7 @@ $dumpfile(`STRINGIFY(`TEST_DUMPFILE)); $display("time: %0t\tclk:%b", $time, clk); $display("nonPublic: %x", nonPublic); + $display("forcedNonForceable: %x", forcedNonForceable); $display("str1: %s", str1); $display("delayed: %x", delayed); @@ -2484,6 +2521,7 @@ $dumpfile(`STRINGIFY(`TEST_DUMPFILE)); $display("ascPacked4dQ: %x", ascPacked4dQ); $display("ascPacked4dW: %x", ascPacked4dW); + $display("forcedNonForceableContinuously: %x", forcedNonForceableContinuously); $display("onebitContinuously: %x", onebitContinuously); $display("intvalContinuously: %x", intvalContinuously); $display("vectorCContinuously: %x", vectorCContinuously);