Merge origin/master and resolve conflicts

This commit is contained in:
Yilou Wang 2026-04-07 10:03:28 +02:00
commit 3760eb9ecc
65 changed files with 1492 additions and 466 deletions

12
Changes
View File

@ -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

View File

@ -585,6 +585,7 @@ description of these arguments.
+verilator+quiet Minimize additional printing
+verilator+rand+reset+<value> Set random reset technique
+verilator+seed+<value> Set random seed
+verilator+solver+file+<filename> Set random solver log filename
+verilator+V Show verbose version and config
+verilator+version Show version and exit
+verilator+wno+unsatconstr+<value> Disable constraint warnings

View File

@ -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+<filename>
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.

View File

@ -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<int>(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

View File

@ -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;

View File

@ -23,6 +23,7 @@
#include "verilated_random.h"
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
@ -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<std::ofstream> 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<char>(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<std::ofstream>(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() {

View File

@ -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

View File

@ -300,6 +300,7 @@ RAW_OBJS_PCH_ASTNOMT = \
V3LinkLevel.o \
V3LinkParse.o \
V3LinkResolve.o \
V3LinkWith.o \
V3Localize.o \
V3MergeCond.o \
V3Name.o \

View File

@ -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();

View File

@ -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<SeqStep>& 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<StepBounds> preAssignStates(const std::vector<SeqStep>& steps) {
std::vector<StepBounds> 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<SeqStep>& steps, AstNodeExpr* antExprp) {
const std::vector<StepBounds> 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<uint32_t>(nextState)}},
new AstConst{flp, static_cast<uint32_t>(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:

View File

@ -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(); }

View File

@ -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

View File

@ -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; }

View File

@ -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

View File

@ -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);

View File

@ -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) {

View File

@ -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:

View File

@ -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;

View File

@ -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());

View File

@ -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) {

View File

@ -1067,7 +1067,6 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
}
iterateConst(nodep->exprp());
}
// Terminals
void visit(AstVarRef* nodep) override {
if (nodep->varScopep()) {

View File

@ -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();

View File

@ -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);

View File

@ -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*)

View File

@ -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);

View File

@ -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<AstGenFor*> 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__ << ": ");

149
src/V3LinkWith.cpp Normal file
View File

@ -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);
}

32
src/V3LinkWith.h Normal file
View File

@ -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

View File

@ -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;

View File

@ -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.

View File

@ -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<uint32_t, uint32_t> 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});

View File

@ -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)

View File

@ -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};

View File

@ -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 {

View File

@ -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 "

View File

@ -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

View File

@ -6829,7 +6829,7 @@ sexpr<nodeExprp>: // ==IEEE: sequence_expr (The name sexpr is important as reg
new AstConst{$<fl>2, 0u}, nullptr, true}; }
// // IEEE: goto_repetition (single count form)
| ~p~sexpr/*sexpression_or_dist*/ yP_BRAMINUSGT constExpr ']'
{ $$ = new AstSExprGotoRep{$<fl>2, $1, $3}; }
{ $$ = new AstSGotoRep{$<fl>2, $1, $3}; }
// // IEEE: goto_repetition (range form -- unsupported)
| ~p~sexpr/*sexpression_or_dist*/ yP_BRAMINUSGT constExpr ':' constExpr ']'
{ $$ = $1; BBUNSUP($<fl>2, "Unsupported: [-> range goto repetition"); DEL($3); DEL($5); }
@ -6867,7 +6867,7 @@ sexpr<nodeExprp>: // ==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<delayp>: // 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($<fl>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($<fl>1, "Unsupported: ## [+] cycle delay range expression"); }
{ $$ = new AstDelay{$1, new AstConst{$1, 1}, true};
$$->rhsp(new AstUnbounded{$1}); }
;
sequence_match_itemList<nodep>: // IEEE: [sequence_match_item] part of sequence_expr
@ -6925,7 +6925,7 @@ boolean_abbrev<nodeExprp>: // ==IEEE: boolean_abbrev
| yP_BRAEQ constExpr ':' constExpr ']'
{ $$ = $2; BBUNSUP($<fl>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)
;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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
| ^~~~~

View File

@ -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");

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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];
| ^~

View File

@ -9,7 +9,7 @@ interface str_if;
endinterface
module t;
str_if sif();
str_if sif ();
virtual str_if vif = sif;
initial begin

View File

@ -99,9 +99,9 @@ enum class Direction : uint8_t {
};
#ifndef IVERILOG
const std::array<TestSignal, 35> TestSignals = {
const std::array<TestSignal, 36> TestSignals = {
#else // Multidimensional packed arrays aren't tested in Icarus
const std::array<TestSignal, 16> TestSignals = {
const std::array<TestSignal, 17> TestSignals = {
#endif
TestSignal{"onebit",
vpiIntVal,
@ -149,6 +149,26 @@ const std::array<TestSignal, 16> 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,

View File

@ -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);