Compare commits

...

9 Commits

Author SHA1 Message Date
Todd Strader 9eb9c2f35a
Merge 6ee55406ec into cb5f038060 2025-11-05 19:27:52 +02:00
Geza Lore cb5f038060
Internals: Optimzie unlinking rest of list after head (tail) (#6643)
AstNode::unlinkFrBackWithNext is O(n) if the subject node is not the
head of the list. We sometimes want to unlink the rest of the list
starting at the node after the head (e.g.: in
V3Sched::util::splitCheck), this patch makes that O(1) as well.
2025-11-05 15:55:30 +00:00
Geza Lore a35d4a4b4b
Add memory usage statistics on macOS (#6644)
Add memory usage statistics on macOS
2025-11-05 15:45:35 +00:00
Ryszard Rozak 96ece751fa
Internals: Simplify release handling. No functional change intended (#6647) 2025-11-05 10:20:18 -05:00
Geza Lore 4404978765
Fix command line statistics with --stats (#6645) (#6646)
Fixes #6645
2025-11-05 14:59:25 +00:00
Yilou Wang 0853aa7515
Support basic global constraints (#6551) (#6552) 2025-11-05 07:14:03 -05:00
github action 574c69c092 Apply 'make format' 2025-11-05 10:50:31 +00:00
Jens Yuechao Liu e2f5854088
Fix slice memory overflow on large output arrays (#6636) (#6638) 2025-11-05 05:48:22 -05:00
Todd Strader 6ee55406ec Intermediate variables for system calls during expression coverage 2025-11-04 10:26:50 -05:00
31 changed files with 894 additions and 63 deletions

View File

@ -105,6 +105,7 @@ Jamey Hicks
Jamie Iles
Jan Van Winkel
Jean Berniolles
Jens Yuechao Liu
Jeremy Bennett
Jesse Taube
Jevin Sweval

View File

@ -719,6 +719,11 @@ Summary:
automatically. Variables explicitly annotated with
:option:`/*verilator&32;split_var*/` are still split.
.. option:: --fslice-element-limit
Rarely needed. Set the maximum array size (number of elements)
for slice optimization to avoid excessive memory usage.
.. option:: -future0 <option>
Rarely needed. Suppress an unknown Verilator option for an option that

View File

@ -41,6 +41,10 @@
#if defined(__APPLE__) && !defined(__arm64__) && !defined(__POWERPC__)
# include <cpuid.h> // For __cpuid_count()
#endif
#if defined(__APPLE__) && defined(__MACH__)
# include <mach/mach.h> // For task_info()
#endif
// clang-format on
namespace VlOs {
@ -146,6 +150,15 @@ void memUsageBytes(uint64_t& peakr, uint64_t& currentr) VL_MT_SAFE {
peakr = pmc.PeakWorkingSetSize;
currentr = pmc.WorkingSetSize;
}
#elif defined(__APPLE__) && defined(__MACH__)
mach_task_basic_info_data_t info;
mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
const kern_return_t ret
= task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &count);
if (ret == KERN_SUCCESS && count == MACH_TASK_BASIC_INFO_COUNT) {
peakr = info.resident_size_max;
currentr = info.resident_size;
}
#else
// Highly unportable. Sorry
std::ifstream is{"/proc/self/status"};

View File

@ -613,11 +613,14 @@ AstNode* AstNode::unlinkFrBackWithNext(VNRelinker* linkerp) {
backp->m_nextp = nullptr;
// Old list gets truncated
// New list becomes a list upon itself
// Most common case is unlinking a entire operand tree
// (else we'd probably call unlinkFrBack without next)
// We may be in the middle of a list; we have no way to find head or tail!
AstNode* oldtailp = oldp;
while (oldtailp->m_nextp) oldtailp = oldtailp->m_nextp;
// Most common case is unlinking a entire operand tree, or all but the
// head (else we'd probably call unlinkFrBack without next)
AstNode* oldtailp = backp->m_headtailp;
if (!oldtailp) {
// We are in the middle of a list; we have no way to find head or tail in O(1)
oldtailp = oldp;
while (oldtailp->m_nextp) oldtailp = oldtailp->m_nextp;
}
// Create new head/tail of old list
AstNode* const oldheadp = oldtailp->m_headtailp;
oldheadp->m_headtailp = oldp->m_backp;

View File

@ -427,7 +427,7 @@ class AstNode VL_NOT_FINAL {
#endif
AstNodeDType* m_dtypep = nullptr; // Data type of output or assignment (etc)
AstNode* m_headtailp; // When at begin/end of list, the opposite end of the list
AstNode* m_headtailp; // When at begin/end of list, the opposite end of the list, else nullptr
FileLine* m_fileline; // Where it was declared
#ifdef VL_DEBUG
// Only keep track of the edit count in the node in the debug build.
@ -902,6 +902,8 @@ public:
// isUnlikely handles $stop or similar statement which means an above IF
// statement is unlikely to be taken
virtual bool isUnlikely() const { return false; }
// Is an IEEE system function (versus internally-generated)
virtual bool isSystemFunc() const { return false; }
virtual int instrCount() const { return 0; }
// Iff node is identical to another node
virtual bool isSame(const AstNode* samep) const {

View File

@ -134,6 +134,7 @@ public:
bool cleanRhs() const override { return false; }
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return INSTR_COUNT_DBL_TRIG; }
void numberOperate(V3Number&, const V3Number&, const V3Number&) override { V3ERROR_NA; }
};
@ -172,6 +173,7 @@ public:
bool cleanRhs() const override { return false; }
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return INSTR_COUNT_DBL_TRIG; }
bool doubleFlavor() const override { return true; }
};
@ -347,6 +349,7 @@ protected:
public:
ASTGEN_MEMBERS_AstNodeTermop;
bool isSystemFunc() const override { return true; }
};
class AstNodeTriop VL_NOT_FINAL : public AstNodeExpr {
// Ternary expression
@ -403,6 +406,7 @@ public:
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
bool sizeMattersThs() const override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return INSTR_COUNT_DBL_TRIG; }
void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs,
const V3Number& ths) override {
@ -449,6 +453,7 @@ public:
bool cleanOut() const override { return true; }
bool cleanLhs() const override { return false; }
bool sizeMattersLhs() const override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return INSTR_COUNT_DBL_TRIG; }
bool doubleFlavor() const override { return true; }
};
@ -1350,6 +1355,7 @@ public:
int instrCount() const override { return widthInstrs() * 64; }
bool isPredictOptimizable() const override { return false; }
bool isPure() override { return false; } // SPECIAL: $display has 'visual' ordering
bool isSystemFunc() const override { return true; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstFOpen final : public AstNodeExpr {
@ -1372,6 +1378,7 @@ public:
bool isOutputter() override { return true; }
bool isUnlikely() const override { return true; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstFOpenMcd final : public AstNodeExpr {
// @astgen op2 := filenamep : AstNodeExpr
@ -1415,6 +1422,7 @@ public:
bool isOutputter() override { return true; } // SPECIAL: makes output
bool cleanOut() const override { return false; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstFRewind final : public AstNodeExpr {
// @astgen op1 := filep : Optional[AstNode]
@ -1462,6 +1470,7 @@ public:
}
string text() const { return m_text; } // * = Text to display
void text(const string& text) { m_text = text; }
bool isSystemFunc() const override { return true; }
};
class AstFSeek final : public AstNodeExpr {
// @astgen op1 := filep : AstNode // file (must be a VarRef)
@ -1484,6 +1493,7 @@ public:
bool isOutputter() override { return true; } // SPECIAL: makes output
bool cleanOut() const override { return false; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstFTell final : public AstNodeExpr {
// @astgen op1 := filep : AstNode // file (must be a VarRef)
@ -1503,6 +1513,7 @@ public:
bool isUnlikely() const override { return true; }
bool cleanOut() const override { return false; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstFalling final : public AstNodeExpr {
// Verilog $falling_gclk
@ -1519,6 +1530,7 @@ public:
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstFell final : public AstNodeExpr {
// Verilog $fell
@ -1537,6 +1549,7 @@ public:
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstFuture final : public AstNodeExpr {
// Verilog $future_gclk
@ -1555,6 +1568,7 @@ public:
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstGatePin final : public AstNodeExpr {
// Possibly expand a gate primitive input pin value to match the range of the gate primitive
@ -1835,6 +1849,7 @@ public:
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstPatMember final : public AstNodeExpr {
// Verilog '{a} or '{a{b}}
@ -1923,6 +1938,7 @@ public:
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
bool isPure() override { return !seedp(); }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return INSTR_COUNT_PLI; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool combinable(const AstRand* samep) const {
@ -1947,6 +1963,7 @@ public:
bool cleanOut() const override { return false; }
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return INSTR_COUNT_PLI; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
@ -1965,6 +1982,7 @@ public:
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstRose final : public AstNodeExpr {
// Verilog $rose
@ -1983,6 +2001,7 @@ public:
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstSExpr final : public AstNodeExpr {
// Sequence expression
@ -2055,6 +2074,7 @@ public:
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(true); }
bool isSystemFunc() const override { return true; }
};
class AstSScanF final : public AstNodeExpr {
// @astgen op1 := exprsp : List[AstNode] // VarRefs for results
@ -2086,6 +2106,7 @@ public:
string text() const { return m_text; } // * = Text to display
VTimescale timeunit() const { return m_timeunit; }
void timeunit(const VTimescale& flag) { m_timeunit = flag; }
bool isSystemFunc() const override { return true; }
};
class AstSampled final : public AstNodeExpr {
// Verilog $sampled
@ -2102,6 +2123,7 @@ public:
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return 0; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstScopeName final : public AstNodeExpr {
// For display %m and DPI context imports
@ -2228,6 +2250,7 @@ public:
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstStackTraceF final : public AstNodeExpr {
// $stacktrace used as function
@ -2263,6 +2286,7 @@ public:
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstStructSel final : public AstNodeExpr {
// Unpacked struct/union member access
@ -2330,6 +2354,7 @@ public:
bool isUnlikely() const override { return true; }
bool cleanOut() const override { return true; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool isSystemFunc() const override { return true; }
};
class AstTestPlusArgs final : public AstNodeExpr {
// Search expression. If nullptr then this is a $test$plusargs instead of $value$plusargs.
@ -2346,6 +2371,7 @@ public:
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
// but isPure() true
bool isSystemFunc() const override { return true; }
bool cleanOut() const override { return true; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
@ -2380,6 +2406,7 @@ public:
string emitC() override { V3ERROR_NA_RETURN(""); }
string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { return true; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
@ -2396,6 +2423,7 @@ public:
string emitC() override { V3ERROR_NA_RETURN(""); }
string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { return true; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return widthInstrs(); }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
VTimescale timeunit() const { return m_timeunit; }
@ -2450,6 +2478,7 @@ public:
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
bool isPure() override { return !outp(); }
bool isSystemFunc() const override { return true; }
bool cleanOut() const override { return true; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
@ -2553,6 +2582,7 @@ public:
bool cleanRhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return widthInstrs() * 20; }
bool isPure() override { return false; }
};
@ -2731,6 +2761,7 @@ public:
bool cleanRhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return widthInstrs() * 64; }
AstNode* strgp() const { return lhsp(); }
AstNode* filep() const { return rhsp(); }
@ -2751,6 +2782,7 @@ public:
bool cleanRhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return widthInstrs() * 64; }
bool isPredictOptimizable() const override { return false; }
bool isPure() override { return false; } // SPECIAL: $display has 'visual' ordering
@ -3667,6 +3699,7 @@ public:
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
bool isPure() override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return INSTR_COUNT_PLI; }
};
@ -4452,6 +4485,7 @@ public:
bool sizeMattersRhs() const override { return false; }
bool sizeMattersThs() const override { return false; }
bool sizeMattersFhs() const override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return widthInstrs() * 16; }
};
@ -4466,6 +4500,7 @@ public:
string emitVerilog() override { return "%f$inferred_disable"; }
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { return true; }
bool isSystemFunc() const override { return true; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstTime final : public AstNodeTermop {
@ -4482,6 +4517,7 @@ public:
bool cleanOut() const override { return true; }
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return INSTR_COUNT_TIME; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
void dump(std::ostream& str = std::cout) const override;
@ -4503,6 +4539,7 @@ public:
bool cleanOut() const override { return true; }
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
bool isSystemFunc() const override { return true; }
int instrCount() const override { return INSTR_COUNT_TIME; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
void dump(std::ostream& str = std::cout) const override;
@ -4795,6 +4832,7 @@ public:
bool cleanLhs() const override { return false; } // Eliminated before matters
bool sizeMattersLhs() const override { return false; } // Eliminated before matters
int instrCount() const override { return INSTR_COUNT_DBL; }
bool isSystemFunc() const override { return true; }
};
class AstCAwait final : public AstNodeUniop {
// Emit C++'s co_await expression
@ -4867,6 +4905,7 @@ public:
bool cleanLhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
int instrCount() const override { return widthInstrs() * 16; }
bool isSystemFunc() const override { return true; }
};
class AstCastWrap final : public AstNodeUniop {
// A cast which has been expanded and the LHSP does all the lifting
@ -4896,6 +4935,7 @@ public:
bool cleanLhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
int instrCount() const override { return widthInstrs() * 16; }
bool isSystemFunc() const override { return true; }
};
class AstCvtPackString final : public AstNodeUniop {
// Convert to Verilator Packed String (aka verilog "string")
@ -4977,6 +5017,7 @@ public:
int instrCount() const override { return widthInstrs() * 16; }
bool isPredictOptimizable() const override { return false; }
bool isPure() override { return false; } // SPECIAL: $display has 'visual' ordering
bool isSystemFunc() const override { return true; }
AstNode* filep() const { return lhsp(); }
};
class AstFGetC final : public AstNodeUniop {
@ -4995,6 +5036,7 @@ public:
bool isPredictOptimizable() const override { return false; }
bool isPure() override { return false; } // SPECIAL: $display has 'visual' ordering
AstNode* filep() const { return lhsp(); }
bool isSystemFunc() const override { return true; }
};
class AstISToRD final : public AstNodeUniop {
// $itor where lhs is signed
@ -5012,6 +5054,7 @@ public:
bool cleanLhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
int instrCount() const override { return INSTR_COUNT_DBL; }
bool isSystemFunc() const override { return true; }
};
class AstIToRD final : public AstNodeUniop {
// $itor where lhs is unsigned
@ -5028,6 +5071,7 @@ public:
bool cleanLhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
int instrCount() const override { return INSTR_COUNT_DBL; }
bool isSystemFunc() const override { return true; }
};
class AstIsUnbounded final : public AstNodeUniop {
// True if is unbounded ($)
@ -5046,6 +5090,7 @@ public:
bool cleanOut() const override { return false; }
bool cleanLhs() const override { return false; }
bool sizeMattersLhs() const override { return false; }
bool isSystemFunc() const override { return true; }
};
class AstIsUnknown final : public AstNodeUniop {
// True if any unknown bits
@ -5061,6 +5106,7 @@ public:
bool cleanOut() const override { return false; }
bool cleanLhs() const override { return false; }
bool sizeMattersLhs() const override { return false; }
bool isSystemFunc() const override { return true; }
};
class AstLenN final : public AstNodeUniop {
// Length of a string
@ -5191,6 +5237,7 @@ public:
bool cleanLhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
int instrCount() const override { return widthInstrs() * 4; }
bool isSystemFunc() const override { return true; }
};
class AstOneHot0 final : public AstNodeUniop {
// True if only single bit, or no bits set in vector
@ -5207,6 +5254,7 @@ public:
bool cleanLhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
int instrCount() const override { return widthInstrs() * 3; }
bool isSystemFunc() const override { return true; }
};
class AstRToIRoundS final : public AstNodeUniop {
// Convert real to integer, with arbitrary sized output (not just "integer" format)
@ -5225,6 +5273,7 @@ public:
bool cleanLhs() const override { return false; }
bool sizeMattersLhs() const override { return false; }
int instrCount() const override { return INSTR_COUNT_DBL; }
bool isSystemFunc() const override { return true; }
};
class AstRToIS final : public AstNodeUniop {
// $rtoi(lhs)
@ -5241,6 +5290,7 @@ public:
bool cleanLhs() const override { return false; } // Eliminated before matters
bool sizeMattersLhs() const override { return false; } // Eliminated before matters
int instrCount() const override { return INSTR_COUNT_DBL; }
bool isSystemFunc() const override { return true; }
};
class AstRealToBits final : public AstNodeUniop {
public:
@ -5256,6 +5306,7 @@ public:
bool cleanLhs() const override { return false; } // Eliminated before matters
bool sizeMattersLhs() const override { return false; } // Eliminated before matters
int instrCount() const override { return INSTR_COUNT_DBL; }
bool isSystemFunc() const override { return true; }
};
class AstRedAnd final : public AstNodeUniop {
public:
@ -5337,6 +5388,7 @@ public:
bool cleanLhs() const override { return false; } // Eliminated before matters
bool sizeMattersLhs() const override { return true; } // Eliminated before matters
int instrCount() const override { return 0; }
bool isSystemFunc() const override { return true; }
};
class AstTimeImport final : public AstNodeUniop {
// Take a constant that represents a time and needs conversion based on time units
@ -5419,6 +5471,7 @@ public:
bool cleanLhs() const override { return false; } // Eliminated before matters
bool sizeMattersLhs() const override { return true; } // Eliminated before matters
int instrCount() const override { return 0; }
bool isSystemFunc() const override { return true; }
};
// === AstNodeSystemUniopD ===

View File

@ -941,6 +941,7 @@ public:
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string name() const override VL_MT_STABLE { return m_name; } // * = Scope name
void name(const string& name) override { m_name = name; } // * = Scope name
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
bool maybePointedTo() const override VL_MT_SAFE { return true; }
@ -1871,7 +1872,7 @@ class AstVar final : public AstNode {
bool m_ignorePostWrite : 1; // Ignore writes in 'Post' blocks during ordering
bool m_ignoreSchedWrite : 1; // Ignore writes in scheduling (for special optimizations)
bool m_dfgMultidriven : 1; // Singal is multidriven, used by DFG to avoid repeat processing
bool m_globalConstrained : 1; // Global constraint per IEEE 1800-2023 18.5.8
void init() {
m_ansi = false;
m_declTyped = false;
@ -1921,6 +1922,7 @@ class AstVar final : public AstNode {
m_ignorePostWrite = false;
m_ignoreSchedWrite = false;
m_dfgMultidriven = false;
m_globalConstrained = false;
}
public:
@ -2085,7 +2087,8 @@ public:
void setIgnoreSchedWrite() { m_ignoreSchedWrite = true; }
bool dfgMultidriven() const { return m_dfgMultidriven; }
void setDfgMultidriven() { m_dfgMultidriven = true; }
void globalConstrained(bool flag) { m_globalConstrained = flag; }
bool globalConstrained() const { return m_globalConstrained; }
// METHODS
void name(const string& name) override { m_name = name; }
void tag(const string& text) override { m_tag = text; }

View File

@ -141,7 +141,7 @@ class CoverageVisitor final : public VNVisitor {
const VNUser2InUse m_inuser2;
V3UniqueNames m_exprTempNames; // For generating unique temporary variable names used by
// expression coverage
std::unordered_map<VNRef<AstFuncRef>, AstVar*> m_funcTemps;
std::unordered_map<VNRef<AstNodeExpr>, AstVar*> m_funcTemps;
// STATE - across all visitors
int m_nextHandle = 0;
@ -784,8 +784,9 @@ class CoverageVisitor final : public VNVisitor {
comment += (first ? "" : " && ") + term.m_emitV
+ "==" + (term.m_objective ? "1" : "0");
AstNodeExpr* covExprp = nullptr;
if (AstFuncRef* const frefp = VN_CAST(term.m_exprp, FuncRef)) {
AstNodeDType* const dtypep = frefp->taskp()->fvarp()->dtypep();
if (VN_IS(term.m_exprp, FuncRef) || term.m_exprp->isSystemFunc()) {
AstNodeExpr* const frefp = term.m_exprp;
AstNodeDType* const dtypep = frefp->dtypep();
const auto pair = m_funcTemps.emplace(*frefp, nullptr);
AstVar* varp = pair.first->second;
if (pair.second) {

View File

@ -293,29 +293,27 @@ class ForceConvertVisitor final : public VNVisitor {
AstAssign* const resetRdp
= new AstAssign{flp, lhsp->cloneTreePure(false), lhsp->unlinkFrBack()};
// Replace write refs on the LHS
resetRdp->lhsp()->foreach([this](AstNodeVarRef* refp) {
if (refp->access() != VAccess::WRITE) return;
AstVarScope* const vscp = refp->varScopep();
AstVarScope* const newVscp = vscp->varp()->isContinuously()
? m_state.getForceComponents(vscp).m_rdVscp
: vscp;
AstVarRef* const newpRefp = new AstVarRef{refp->fileline(), newVscp, VAccess::WRITE};
refp->replaceWith(newpRefp);
VL_DO_DANGLING(refp->deleteTree(), refp);
});
// Replace write refs on RHS
resetRdp->rhsp()->foreach([this](AstNodeVarRef* refp) {
resetRdp->lhsp()->foreach([this](AstVarRef* refp) {
if (refp->access() != VAccess::WRITE) return;
AstVarScope* const vscp = refp->varScopep();
if (vscp->varp()->isContinuously()) {
AstVarRef* const newpRefp = new AstVarRef{refp->fileline(), vscp, VAccess::READ};
ForceState::markNonReplaceable(newpRefp);
AstVarRef* const newpRefp = new AstVarRef{
refp->fileline(), m_state.getForceComponents(vscp).m_rdVscp, VAccess::WRITE};
refp->replaceWith(newpRefp);
VL_DO_DANGLING(refp->deleteTree(), refp);
}
});
// Replace write refs on RHS
resetRdp->rhsp()->foreach([this](AstVarRef* refp) {
if (refp->access() != VAccess::WRITE) return;
AstVarScope* const vscp = refp->varScopep();
if (vscp->varp()->isContinuously()) {
refp->access(VAccess::READ);
ForceState::markNonReplaceable(refp);
} else {
refp->replaceWith(m_state.getForceComponents(vscp).forcedUpdate(vscp));
VL_DO_DANGLING(refp->deleteTree(), refp);
}
VL_DO_DANGLING(refp->deleteTree(), refp);
});
resetRdp->addNext(resetEnp);

View File

@ -1458,13 +1458,16 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
DECL_OPTION("-freloop", FOnOff, &m_fReloop);
DECL_OPTION("-freorder", FOnOff, &m_fReorder);
DECL_OPTION("-fslice", FOnOff, &m_fSlice);
DECL_OPTION("-fslice-element-limit", CbVal, [this, fl](const char* valp) {
m_fSliceElementLimit = std::atoi(valp);
if (m_fSliceElementLimit < 0) fl->v3fatal("--fslice-element-limit must be >= 0: " << valp);
});
DECL_OPTION("-fsplit", FOnOff, &m_fSplit);
DECL_OPTION("-fsubst", FOnOff, &m_fSubst);
DECL_OPTION("-fsubst-const", FOnOff, &m_fSubstConst);
DECL_OPTION("-ftable", FOnOff, &m_fTable);
DECL_OPTION("-ftaskify-all-forked", FOnOff, &m_fTaskifyAll).undocumented(); // Debug
DECL_OPTION("-fvar-split", FOnOff, &m_fVarSplit);
DECL_OPTION("-G", CbPartialMatch, [this](const char* optp) { addParameter(optp, false); });
DECL_OPTION("-gate-stmts", Set, &m_gateStmts);
DECL_OPTION("-gdb", CbCall, []() {}); // Processed only in bin/verilator shell

View File

@ -413,6 +413,7 @@ private:
bool m_fReloop; // main switch: -fno-reloop: reform loops
bool m_fReorder; // main switch: -fno-reorder: reorder assignments in blocks
bool m_fSlice = true; // main switch: -fno-slice: array assignment slicing
int m_fSliceElementLimit = 256; // main switch: --fslice-element-limit
bool m_fSplit; // main switch: -fno-split: always assignment splitting
bool m_fSubst; // main switch: -fno-subst: substitute expression temp values
bool m_fSubstConst; // main switch: -fno-subst-const: final constant substitution
@ -726,6 +727,7 @@ public:
bool fReloop() const { return m_fReloop; }
bool fReorder() const { return m_fReorder; }
bool fSlice() const { return m_fSlice; }
int fSliceElementLimit() const { return m_fSliceElementLimit; }
bool fSplit() const { return m_fSplit; }
bool fSubst() const { return m_fSubst; }
bool fSubstConst() const { return m_fSubstConst; }

View File

@ -54,10 +54,17 @@ VL_DEFINE_DEBUG_FUNCTIONS;
enum ClassRandom : uint8_t {
NONE, // randomize() is not called
IS_RANDOMIZED, // randomize() is called
IS_RANDOMIZED_GLOBAL, // randomize() is called with global constraints
IS_RANDOMIZED_INLINE, // randomize() with args is called
IS_STD_RANDOMIZED, // std::randomize() is called
};
// ######################################################################
// Constants for global constraint processing
static constexpr const char* GLOBAL_CONSTRAINT_SEPARATOR = "__DT__";
static constexpr const char* BASIC_RANDOMIZE_FUNC_NAME = "__VBasicRand";
// ######################################################################
// Establishes the target of a rand_mode() call
@ -138,6 +145,9 @@ class RandomizeMarkVisitor final : public VNVisitor {
AstNodeModule* m_modp; // Current module
AstNodeStmt* m_stmtp = nullptr; // Current statement
std::set<AstNodeVarRef*> m_staticRefs; // References to static variables under `with` clauses
AstWith* m_withp = nullptr; // Current 'with' constraint node
std::vector<AstConstraint*> m_clonedConstraints; // List of cloned global constraints
std::unordered_set<const AstVar*> m_processedVars; // Track by variable instance, not class
// METHODS
void markMembers(const AstClass* nodep) {
@ -196,18 +206,145 @@ class RandomizeMarkVisitor final : public VNVisitor {
staticRefp->classOrPackagep(VN_AS(staticRefp->varp()->user2p(), NodeModule));
}
}
void markNestedGlobalConstrainedRecurse(AstNode* nodep) {
if (const AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
AstVar* const varp = refp->varp();
if (varp->globalConstrained()) return;
varp->globalConstrained(true);
} else if (const AstMemberSel* const memberSelp = VN_CAST(nodep, MemberSel)) {
if (memberSelp->varp()) {
AstVar* const varp = memberSelp->varp();
if (varp->globalConstrained()) return;
varp->globalConstrained(true);
}
markNestedGlobalConstrainedRecurse(memberSelp->fromp());
}
}
// Build MemberSel chain from variable path
AstNodeExpr* buildMemberSelChain(AstVarRef* rootVarRefp, const std::vector<AstVar*>& path) {
AstNodeExpr* exprp = rootVarRefp->cloneTree(false);
for (AstVar* memberVarp : path) {
AstMemberSel* memberSelp
= new AstMemberSel{rootVarRefp->fileline(), exprp, memberVarp};
memberSelp->user2p(m_classp);
exprp = memberSelp;
}
return exprp;
}
// Process a single constraint during nested constraint cloning
void processNestedConstraint(AstConstraint* const constrp, AstVarRef* rootVarRefp,
const std::vector<AstVar*>& newPath) {
std::string pathPrefix = rootVarRefp->name();
for (AstVar* pathMemberVarp : newPath) {
pathPrefix += GLOBAL_CONSTRAINT_SEPARATOR + pathMemberVarp->name();
}
const std::string newName = pathPrefix + GLOBAL_CONSTRAINT_SEPARATOR + constrp->name();
for (const AstConstraint* existingConstrp : m_clonedConstraints) {
if (existingConstrp->name() == newName) {
// Multiple paths lead to same constraint - unsupported pattern
std::string fullPath = rootVarRefp->name();
for (AstVar* pathVar : newPath) { fullPath += "." + pathVar->name(); }
constrp->v3warn(E_UNSUPPORTED, "Unsupported: One variable '"
<< fullPath
<< "' cannot have multiple global constraints");
return;
}
}
AstConstraint* const cloneConstrp = constrp->cloneTree(false);
cloneConstrp->name(newName);
cloneConstrp->foreach([&](AstVarRef* varRefp) {
AstNodeExpr* const chainp = buildMemberSelChain(rootVarRefp, newPath);
AstMemberSel* const finalSelp
= new AstMemberSel{varRefp->fileline(), chainp, varRefp->varp()};
finalSelp->user2p(m_classp);
varRefp->replaceWith(finalSelp);
VL_DO_DANGLING(varRefp->deleteTree(), varRefp);
});
m_clonedConstraints.push_back(cloneConstrp);
}
// Clone constraints from nested rand class members
void cloneNestedConstraintsRecurse(AstVarRef* rootVarRefp, AstClass* classp,
const std::vector<AstVar*>& pathToClass) {
for (AstNode* memberNodep = classp->membersp(); memberNodep;
memberNodep = memberNodep->nextp()) {
AstVar* const memberVarp = VN_CAST(memberNodep, Var);
if (!memberVarp) continue;
if (!memberVarp->rand().isRandomizable()) continue;
const AstClassRefDType* const memberClassRefp
= VN_CAST(memberVarp->dtypep()->skipRefp(), ClassRefDType);
if (!memberClassRefp || !memberClassRefp->classp()) continue;
AstClass* nestedClassp = memberClassRefp->classp();
std::vector<AstVar*> newPath = pathToClass;
newPath.push_back(memberVarp);
// Replace all variable references inside the cloned constraint with proper
// member selections
nestedClassp->foreachMember(
[&](AstClass* const containingClassp, AstConstraint* const constrp) {
processNestedConstraint(constrp, rootVarRefp, newPath);
});
cloneNestedConstraintsRecurse(rootVarRefp, nestedClassp, newPath);
}
}
void cloneNestedConstraints(AstVarRef* rootVarRefp, AstClass* rootClass) {
std::vector<AstVar*> emptyPath;
cloneNestedConstraintsRecurse(rootVarRefp, rootClass, emptyPath);
}
void nameManipulation(AstVarRef* fromp, AstConstraint* cloneCons) {
cloneCons->name(fromp->name() + GLOBAL_CONSTRAINT_SEPARATOR + cloneCons->name());
cloneCons->foreach([&](AstVarRef* varRefp) {
AstVarRef* const clonedFromp = fromp->cloneTree(false);
AstMemberSel* const varMemberp
= new AstMemberSel{cloneCons->fileline(), clonedFromp, varRefp->varp()};
varMemberp->user2p(m_classp);
varRefp->replaceWith(varMemberp);
VL_DO_DANGLING(varRefp->deleteTree(), varRefp);
});
}
// Process a globally constrained variable by cloning its constraints
void processGlobalConstraint(AstVarRef* varRefp, AstClass* gConsClass) {
AstVar* const objVar = varRefp->varp();
// Process per-variable (object instance), not per-class
// This allows multiple objects of the same class (e.g., obj1 and obj2 of type Sub)
if (m_processedVars.insert(objVar).second) {
// Clone constraints from the top-level class (e.g., Level1 for obj_a)
gConsClass->foreachMember([&](AstClass* const classp, AstConstraint* const constrp) {
AstConstraint* const cloneConstrp = constrp->cloneTree(false);
nameManipulation(varRefp, cloneConstrp);
m_clonedConstraints.push_back(cloneConstrp);
});
cloneNestedConstraints(varRefp, gConsClass);
}
}
// VISITORS
void visit(AstClass* nodep) override {
VL_RESTORER(m_classp);
VL_RESTORER(m_modp);
VL_RESTORER(m_clonedConstraints);
m_modp = m_classp = nodep;
iterateChildrenConst(nodep);
if (nodep->extendsp()) {
// Save pointer to derived class
// Record derived class for inheritance hierarchy tracking
const AstClass* const basep = nodep->extendsp()->classp();
m_baseToDerivedMap[basep].insert(nodep);
}
for (AstConstraint* const constrp : m_clonedConstraints) m_classp->addStmtsp(constrp);
m_clonedConstraints.clear();
}
void visit(AstNodeStmt* nodep) override {
VL_RESTORER(m_stmtp);
@ -464,7 +601,51 @@ class RandomizeMarkVisitor final : public VNVisitor {
// of type AstLambdaArgRef. They are randomized too.
const bool randObject = nodep->fromp()->user1() || VN_IS(nodep->fromp(), LambdaArgRef);
nodep->user1(randObject && nodep->varp()->rand().isRandomizable());
nodep->user2p(m_modp);
if (m_withp) {
AstNode* backp = m_withp;
while (backp->backp()) {
if (const AstMethodCall* const callp = VN_CAST(backp, MethodCall)) {
AstClassRefDType* classdtype
= VN_AS(callp->fromp()->dtypep()->skipRefp(), ClassRefDType);
nodep->user2p(classdtype->classp());
break;
}
backp = backp->backp();
}
} else {
nodep->user2p(m_modp);
}
if (randObject && nodep->varp()
&& nodep->varp()->rand().isRandomizable()) { // Process global constraints
if (m_classp && m_classp->user1() == IS_RANDOMIZED) {
m_classp->user1(IS_RANDOMIZED_GLOBAL);
}
// Mark the entire nested chain as participating in global constraints
if (VN_IS(nodep->fromp(), VarRef) || VN_IS(nodep->fromp(), MemberSel)) {
markNestedGlobalConstrainedRecurse(nodep->fromp());
} else if (VN_IS(nodep->fromp(), ArraySel)) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: " << nodep->prettyTypeName()
<< " within a global constraint");
}
// Global constraint processing algorithm:
// 1. Detect globally constrained object variables in randomized classes
// 2. Clone constraint trees from the constrained object's class
// 3. Rename cloned constraints with object prefix (obj.var format)
// 4. Insert cloned constraints into current class for solver processing
// 5. Use basic randomization for non-constrained variables to avoid recursion
// Extract and validate components early to avoid repeated type checks
AstVarRef* const varRefp = VN_CAST(nodep->fromp(), VarRef);
if (!varRefp) return;
const AstClassRefDType* const classRefp
= VN_AS(varRefp->dtypep()->skipRefp(), ClassRefDType);
if (nodep->user1() && varRefp->varp()->globalConstrained()) {
processGlobalConstraint(varRefp, classRefp->classp());
}
}
}
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
@ -479,6 +660,11 @@ class RandomizeMarkVisitor final : public VNVisitor {
nodep->user2p(m_modp);
iterateChildrenConst(nodep);
}
void visit(AstWith* nodep) override {
VL_RESTORER(m_withp);
m_withp = nodep;
iterateChildrenConst(nodep);
}
void visit(AstNodeExpr* nodep) override {
iterateChildrenConst(nodep);
@ -519,6 +705,20 @@ class ConstraintExprVisitor final : public VNVisitor {
bool m_structSel = false; // Marks when inside structSel
// (used to format "%@.%@" for struct arrays)
// Build full path for a MemberSel chain (e.g., "obj.l2.l3.l4")
std::string buildMemberPath(const AstMemberSel* const memberSelp) {
const AstNode* fromp = memberSelp->fromp();
if (const AstVarRef* const refp = VN_CAST(fromp, VarRef)) {
// Base case: reached root VarRef
return refp->name() + "." + memberSelp->name();
} else if (const AstMemberSel* const selp = VN_CAST(fromp, MemberSel)) {
// Recursive case: build path from outer levels
return buildMemberPath(selp) + "." + memberSelp->name();
}
memberSelp->v3fatalSrc("Unexpected node type in MemberSel chain");
return "";
}
AstSFormatF* getConstFormat(AstNodeExpr* nodep) {
return new AstSFormatF{nodep->fileline(), (nodep->width() & 3) ? "#b%b" : "#x%x", false,
nodep};
@ -641,19 +841,30 @@ class ConstraintExprVisitor final : public VNVisitor {
CONSTRAINTIGN,
"Size constraint combined with element constraint may not work correctly");
}
AstMemberSel* membersel = VN_IS(nodep->backp(), MemberSel)
? VN_AS(nodep->backp(), MemberSel)->cloneTree(false)
: nullptr;
// Check if this variable is marked as globally constrained
const bool isGlobalConstrained = nodep->varp()->globalConstrained();
AstMemberSel* membersel = nullptr;
std::string smtName;
if (isGlobalConstrained && VN_IS(nodep->backp(), MemberSel)) {
// For global constraints: build complete path from topmost MemberSel
AstNode* topMemberSel = nodep->backp();
while (VN_IS(topMemberSel->backp(), MemberSel)) {
topMemberSel = topMemberSel->backp();
}
membersel = VN_AS(topMemberSel, MemberSel)->cloneTree(false);
smtName = buildMemberPath(membersel);
} else {
// No MemberSel: just variable name
smtName = nodep->name();
}
if (membersel) varp = membersel->varp();
AstNodeModule* const classOrPackagep = nodep->classOrPackagep();
const RandomizeMode randMode = {.asInt = varp->user1()};
if (!randMode.usesMode && editFormat(nodep)) return;
// In SMT just variable name, but we also ensure write_var for the variable
const std::string smtName = membersel
? membersel->fromp()->name() + "." + membersel->name()
: nodep->name(); // Can be anything unique
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
AstNodeExpr* exprp = new AstSFormatF{nodep->fileline(), smtName, false, nullptr};
@ -666,12 +877,18 @@ class ConstraintExprVisitor final : public VNVisitor {
VCMethod::ARRAY_AT, new AstConst{nodep->fileline(), randMode.index}};
atp->dtypeSetUInt32();
exprp = new AstCond{varp->fileline(), atp, exprp, constFormatp};
} else {
} else if (!membersel || !isGlobalConstrained) {
// Only delete nodep here if it's not a global constraint
// Global constraints need nodep for write_var processing
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
relinker.relink(exprp);
if (!varp->user3()) {
// For global constraints: always call write_var with full path even if varp->user3() is
// set For normal constraints: only call write_var if varp->user3() is not set
if (!varp->user3() || (membersel && nodep->varp()->globalConstrained())) {
// For global constraints, delete nodep here after processing
if (membersel && isGlobalConstrained) VL_DO_DANGLING(pushDeletep(nodep), nodep);
AstCMethodHard* const methodp = new AstCMethodHard{
varp->fileline(),
new AstVarRef{varp->fileline(), VN_AS(m_genp->user2p(), NodeModule), m_genp,
@ -693,10 +910,14 @@ class ConstraintExprVisitor final : public VNVisitor {
methodp->dtypeSetVoid();
AstClass* const classp
= membersel ? VN_AS(membersel->user2p(), Class) : VN_AS(varp->user2p(), Class);
AstVarRef* const varRefp
= new AstVarRef{varp->fileline(), classp, varp, VAccess::WRITE};
varRefp->classOrPackagep(classOrPackagep);
membersel ? methodp->addPinsp(membersel) : methodp->addPinsp(varRefp);
if (membersel) {
methodp->addPinsp(membersel);
} else {
AstVarRef* const varRefp
= new AstVarRef{varp->fileline(), classp, varp, VAccess::WRITE};
varRefp->classOrPackagep(classOrPackagep);
methodp->addPinsp(varRefp);
}
AstNodeDType* tmpDtypep = varp->dtypep();
while (VN_IS(tmpDtypep, UnpackArrayDType) || VN_IS(tmpDtypep, DynArrayDType)
|| VN_IS(tmpDtypep, QueueDType) || VN_IS(tmpDtypep, AssocArrayDType))
@ -911,8 +1132,21 @@ class ConstraintExprVisitor final : public VNVisitor {
editSMT(nodep, nodep->fromp(), indexp);
}
void visit(AstMemberSel* nodep) override {
if (nodep->user1()) {
nodep->v3warn(CONSTRAINTIGN, "Global constraints ignored (unsupported)");
if (nodep->varp()->rand().isRandomizable() && nodep->fromp()) {
AstNode* rootNode = nodep->fromp();
while (const AstMemberSel* const selp = VN_CAST(rootNode, MemberSel))
rootNode = selp->fromp();
// Check if the root variable participates in global constraints
if (const AstVarRef* const varRefp = VN_CAST(rootNode, VarRef)) {
AstVar* const constrainedVar = varRefp->varp();
if (constrainedVar->globalConstrained()) {
// Global constraint - unwrap the MemberSel
iterateChildren(nodep);
nodep->replaceWith(nodep->fromp()->unlinkFrBack());
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;
}
}
}
// Handle MemberSel references created by captureRefByThis()
if (VN_IS(nodep->fromp(), VarRef)
@ -1943,10 +2177,19 @@ class RandomizeVisitor final : public VNVisitor {
return;
}
AstFunc* const memberFuncp
= V3Randomize::newRandomizeFunc(m_memberMap, classRefp->classp());
= memberVarp->globalConstrained()
? V3Randomize::newRandomizeFunc(m_memberMap, classRefp->classp(),
BASIC_RANDOMIZE_FUNC_NAME)
: V3Randomize::newRandomizeFunc(m_memberMap, classRefp->classp());
AstMethodCall* const callp
= new AstMethodCall{fl, new AstVarRef{fl, classp, memberVarp, VAccess::WRITE},
"randomize", nullptr};
= memberVarp->globalConstrained()
? new AstMethodCall{fl,
new AstVarRef{fl, classp, memberVarp,
VAccess::WRITE},
BASIC_RANDOMIZE_FUNC_NAME, nullptr}
: new AstMethodCall{
fl, new AstVarRef{fl, classp, memberVarp, VAccess::WRITE},
"randomize", nullptr};
callp->taskp(memberFuncp);
callp->dtypeFrom(memberFuncp);
AstVarRef* const basicFvarRefReadp = basicFvarRefp->cloneTree(false);
@ -2123,6 +2366,8 @@ class RandomizeVisitor final : public VNVisitor {
if (!nodep->user1()) return; // Doesn't need randomize, or already processed
UINFO(9, "Define randomize() for " << nodep);
nodep->baseMostClassp()->needRNG(true);
const bool globalConstrained = nodep->user1() == IS_RANDOMIZED_GLOBAL;
AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep);
AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var);
addPrePostCall(nodep, randomizep, "pre_randomize");
@ -2184,7 +2429,18 @@ class RandomizeVisitor final : public VNVisitor {
}
AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE};
randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp});
// For global constraints: call basic randomize first (without global constraints)
if (globalConstrained) {
AstFunc* const basicRandomizep
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME);
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep, nullptr};
randomizep->addStmtsp(new AstAssign{fl, fvarRefp, basicRandomizeCallp});
} else {
// For normal classes: use beginValp (standard flow)
randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp});
}
if (AstTask* const resizeAllTaskp
= VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) {
@ -2192,15 +2448,23 @@ class RandomizeVisitor final : public VNVisitor {
randomizep->addStmtsp(resizeTaskRefp->makeStmt());
}
AstFunc* const basicRandomizep
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, "__Vbasic_randomize");
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep, nullptr};
AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false);
fvarRefReadp->access(VAccess::READ);
randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false),
new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}});
// For global constraints: combine with solver result (beginValp)
// For normal classes: call basic randomize after resize
if (globalConstrained) {
randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false),
new AstAnd{fl, fvarRefReadp, beginValp}});
} else {
AstFunc* const basicRandomizep
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME);
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep, nullptr};
randomizep->addStmtsp(
new AstAssign{fl, fvarRefp->cloneTree(false),
new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}});
}
addPrePostCall(nodep, randomizep, "post_randomize");
nodep->user1(false);
}
@ -2417,7 +2681,7 @@ class RandomizeVisitor final : public VNVisitor {
randomizeFuncp->addStmtsp(localGenp);
AstFunc* const basicRandomizeFuncp
= V3Randomize::newRandomizeFunc(m_memberMap, classp, "__Vbasic_randomize");
= V3Randomize::newRandomizeFunc(m_memberMap, classp, BASIC_RANDOMIZE_FUNC_NAME);
AstFuncRef* const basicRandomizeFuncCallp
= new AstFuncRef{nodep->fileline(), basicRandomizeFuncp, nullptr};

View File

@ -58,6 +58,7 @@ class SliceVisitor final : public VNVisitor {
// STATE - across all visitors
VDouble0 m_statAssigns; // Statistic tracking
VDouble0 m_statSliceElementSkips; // Statistic tracking
// STATE - for current visit position (use VL_RESTORER)
AstNode* m_assignp = nullptr; // Assignment we are under
@ -248,6 +249,14 @@ class SliceVisitor final : public VNVisitor {
return false;
}
// Skip optimization if array is too large
const int elements = arrayp->rangep()->elementsConst();
const int elementLimit = v3Global.opt.fSliceElementLimit();
if (elements > elementLimit && elementLimit > 0) {
++m_statSliceElementSkips;
return false;
}
UINFO(4, "Slice optimizing " << nodep);
++m_statAssigns;
@ -256,7 +265,6 @@ class SliceVisitor final : public VNVisitor {
// Assign of an ascending range slice to a descending range one must reverse
// the elements
AstNodeAssign* newlistp = nullptr;
const int elements = arrayp->rangep()->elementsConst();
for (int elemIdx = 0; elemIdx < elements; ++elemIdx) {
// Original node is replaced, so it is safe to copy it one time even if it is impure.
AstNodeAssign* const newp
@ -383,6 +391,8 @@ public:
explicit SliceVisitor(AstNetlist* nodep) { iterate(nodep); }
~SliceVisitor() override {
V3Stats::addStat("Optimizations, Slice array assignments", m_statAssigns);
V3Stats::addStat("Optimizations, Slice array skips due to size limit",
m_statSliceElementSkips);
}
};

View File

@ -50,8 +50,8 @@ class StatsReport final {
V3Statistic* lastp = nullptr;
for (const auto& itr : byName) {
V3Statistic* repp = itr.second;
if (lastp && lastp->sumit() && lastp->printit() && lastp->name() == repp->name()
&& lastp->stage() == repp->stage()) {
if (lastp && lastp->sumit() && lastp->printit() && repp->printit()
&& lastp->name() == repp->name() && lastp->stage() == repp->stage()) {
lastp->combineWith(repp);
} else {
lastp = repp;

View File

@ -0,0 +1,26 @@
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:41:20: Unsupported: MEMBERSEL 'm_x' within a global constraint
: ... note: In instance 't_constraint_global_arr_unsup'
41 | m_mid.m_arr[0].m_x == 200;
| ^~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:42:20: Unsupported: MEMBERSEL 'm_y' within a global constraint
: ... note: In instance 't_constraint_global_arr_unsup'
42 | m_mid.m_arr[0].m_y == 201;
| ^~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:47:18: Unsupported: MEMBERSEL 'm_obj' within a global constraint
: ... note: In instance 't_constraint_global_arr_unsup'
47 | m_mid_arr[0].m_obj.m_x == 300;
| ^~~~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:48:18: Unsupported: MEMBERSEL 'm_obj' within a global constraint
: ... note: In instance 't_constraint_global_arr_unsup'
48 | m_mid_arr[0].m_obj.m_y == 301;
| ^~~~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:18: Unsupported: MEMBERSEL 'm_arr' within a global constraint
: ... note: In instance 't_constraint_global_arr_unsup'
53 | m_mid_arr[1].m_arr[2].m_y == 400;
| ^~~~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:27: Unsupported: MEMBERSEL 'm_y' within a global constraint
: ... note: In instance 't_constraint_global_arr_unsup'
53 | m_mid_arr[1].m_arr[2].m_y == 400;
| ^~~
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,86 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
/* verilator lint_off WIDTHTRUNC */
class Inner;
rand int m_x;
rand int m_y;
endclass
class Middle;
rand Inner m_obj;
rand Inner m_arr[3];
endclass
class Outer;
rand Middle m_mid;
rand Middle m_mid_arr[2];
function new();
m_mid = new;
m_mid.m_obj = new;
foreach (m_mid.m_arr[i]) m_mid.m_arr[i] = new;
foreach (m_mid_arr[i]) begin
m_mid_arr[i] = new;
m_mid_arr[i].m_obj = new;
foreach (m_mid_arr[i].m_arr[j]) m_mid_arr[i].m_arr[j] = new;
end
endfunction
// Case 1: Simple nested member access (should work)
constraint c_simple {
m_mid.m_obj.m_x == 100;
m_mid.m_obj.m_y == 101;
}
// Case 2: Array indexing in the path (may not work)
constraint c_array_index {
m_mid.m_arr[0].m_x == 200;
m_mid.m_arr[0].m_y == 201;
}
// Case 3: Nested array indexing
constraint c_nested_array {
m_mid_arr[0].m_obj.m_x == 300;
m_mid_arr[0].m_obj.m_y == 301;
}
// Case 4: Multiple array indices
constraint c_multi_array {
m_mid_arr[1].m_arr[2].m_y == 400;
}
endclass
module t_constraint_global_arr_unsup;
initial begin
Outer o = new;
if (o.randomize()) begin
$display("Case 1 - Simple: mid.obj.x = %0d (expected 100)", o.m_mid.m_obj.m_x);
$display("Case 1 - Simple: mid.obj.y = %0d (expected 101)", o.m_mid.m_obj.m_y);
$display("Case 2 - Array[0]: mid.arr[0].x = %0d (expected 200)", o.m_mid.m_arr[0].m_x);
$display("Case 2 - Array[0]: mid.arr[0].y = %0d (expected 201)", o.m_mid.m_arr[0].m_y);
$display("Case 3 - Nested[0]: mid_arr[0].obj.x = %0d (expected 300)", o.m_mid_arr[0].m_obj.m_x);
$display("Case 3 - Nested[0]: mid_arr[0].obj.y = %0d (expected 301)", o.m_mid_arr[0].m_obj.m_y);
$display("Case 4 - Multi[1][2]: mid_arr[1].arr[2].y = %0d (expected 400)", o.m_mid_arr[1].m_arr[2].m_y);
// Check results
if (o.m_mid.m_obj.m_x == 100 && o.m_mid.m_obj.m_y == 101 &&
o.m_mid.m_arr[0].m_x == 200 && o.m_mid.m_arr[0].m_y == 201 &&
o.m_mid_arr[0].m_obj.m_x == 300 && o.m_mid_arr[0].m_obj.m_y == 301 &&
o.m_mid_arr[1].m_arr[2].m_y == 400) begin
$display("*-* All Finished *-*");
$finish;
end else begin
$display("*-* FAILED *-*");
$stop;
end
end else begin
$display("*-* FAILED: randomize() returned 0 *-*");
$stop;
end
end
endmodule
/* verilator lint_off WIDTHTRUNC */

View File

@ -0,0 +1,6 @@
%Error-UNSUPPORTED: t/t_constraint_global_nested_unsup.v:9:14: Unsupported: One variable 'm_mid.m_inner' cannot have multiple global constraints
: ... note: In instance 't'
9 | constraint c_inner { m_val inside {[1:10]}; }
| ^~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,51 @@
// DESCRIPTION: Verilator: Test for unsupported multiple global constraints
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
class Inner;
rand int m_val;
constraint c_inner { m_val inside {[1:10]}; }
function new(); m_val = 0; endfunction
endclass
class Mid;
rand Inner m_inner;
rand int m_x;
// Mid has global constraint on m_inner.m_val
constraint c_mid_global {
m_x > m_inner.m_val;
m_x inside {[5:15]};
}
function new();
m_inner = new();
m_x = 0;
endfunction
endclass
class Top;
rand Mid m_mid;
rand int m_y;
// Top also has global constraint on m_mid.m_inner.m_val
constraint c_top_global {
m_y < m_mid.m_inner.m_val;
m_y inside {[1:5]};
}
function new();
m_mid = new();
m_y = 0;
endfunction
endclass
module t;
Top top;
/* verilator lint_off WIDTHTRUNC */
initial begin
top = new();
if (!top.randomize()) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
/* verilator lint_off WIDTHTRUNC */
endmodule

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
if not test.have_solver:
test.skip("No constraint solver installed")
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,124 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
class Inner;
rand int m_val;
constraint c_local { m_val inside {[1:5]}; }
class NestedInner;
rand int nested_val;
constraint c_nested { nested_val inside {[1:3]}; }
endclass
rand NestedInner nested_obj;
function new();
m_val = 0;
nested_obj = new();
endfunction
endclass
class Mid;
int m_limit;
rand int m_x;
rand Inner m_inner;
constraint c_mid { m_x == m_limit; }
function new(int lim);
m_limit = lim;
m_inner = new();
endfunction
endclass
class Top;
rand Mid m_m1;
rand Mid m_m2;
rand int m_y;
constraint c_global {
m_m1.m_inner.m_val < m_m2.m_inner.m_val;
m_y > m_m1.m_x;
m_y < m_m2.m_x;
m_m1.m_inner.m_val + m_m2.m_inner.m_val < 8;
// Global constraint on nested class variable (3-level deep)
m_m1.m_inner.nested_obj.nested_val == 1;
m_m2.m_inner.nested_obj.nested_val == 3;
}
function new();
m_m1 = new(3);
m_m2 = new(5);
m_y = 0;
endfunction
endclass
// Second independent class with global constraints
class AnotherTop;
rand Mid m_m3;
rand int m_z;
constraint c_another {
m_z < m_m3.m_x;
}
function new();
m_m3 = new(10);
m_z = 0;
endfunction
endclass
module t_constraint_global_random;
int success;
Top t;
AnotherTop t2;
initial begin
t = new();
// Test 1: Regular randomize() with global constraints
success = t.randomize();
if (success != 1) $stop;
if (t.m_m1.m_x != 3 || t.m_m2.m_x != 5) $stop;
if (t.m_m1.m_inner.m_val >= t.m_m2.m_inner.m_val) $stop;
if (t.m_y <= t.m_m1.m_x || t.m_y >= t.m_m2.m_x) $stop;
if (t.m_m1.m_inner.m_val + t.m_m2.m_inner.m_val >= 8) $stop;
if (t.m_m1.m_inner.m_val < 1 || t.m_m1.m_inner.m_val > 5 ||
t.m_m2.m_inner.m_val < 1 || t.m_m2.m_inner.m_val > 5) $stop;
// Verify nested class global constraints (3-level deep: Top -> Mid -> Inner -> NestedInner)
if (t.m_m1.m_inner.nested_obj.nested_val != 1) $stop;
if (t.m_m2.m_inner.nested_obj.nested_val != 3) $stop;
// Test 2: randomize() with inline constraint on global-constrained members
success = 0;
success = t.randomize() with {
m_m1.m_inner.m_val == 2;
m_m2.m_inner.m_val == 5;
};
if (success != 1) $stop;
// Verify inline constraints
if (t.m_m1.m_inner.m_val != 2) $stop;
if (t.m_m2.m_inner.m_val != 5) $stop;
// Verify global constraints still hold
if (t.m_m1.m_x != 3 || t.m_m2.m_x != 5) $stop;
if (t.m_m1.m_inner.m_val >= t.m_m2.m_inner.m_val) $stop;
if (t.m_y <= t.m_m1.m_x || t.m_y >= t.m_m2.m_x) $stop;
if (t.m_m1.m_inner.m_val + t.m_m2.m_inner.m_val >= 8) $stop;
// Test 3: Second independent class (tests m_clonedConstraints.clear() bug)
t2 = new();
success = t2.randomize();
if (success != 1) $stop;
if (t2.m_z >= t2.m_m3.m_x) $stop;
if (t2.m_m3.m_x != 10) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.top_filename = "t/t_opt_slice_element_limit.v"
test.compile(verilator_flags2=['--stats', '--fslice-element-limit', '10'])
test.file_grep(test.stats, r'Optimizations, Slice array skips due to size limit\s+(\d+)', 4)
test.passes()

View File

@ -0,0 +1,20 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2024 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
module t (
input logic [7:0] i1 [8],
input logic [7:0] i2 [16],
input logic [7:0] i3 [512],
output logic [7:0] o1 [8],
output logic [7:0] o2 [16],
output logic [7:0] o3 [256]
);
initial begin
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.top_filename = "t/t_opt_slice_element_limit.v"
test.compile(verilator_flags2=['--stats', '--fslice-element-limit', '0'])
test.file_grep(test.stats, r'Optimizations, Slice array skips due to size limit\s+(\d+)', 0)
test.passes()

View File

@ -0,0 +1,2 @@
%Error: --fslice-element-limit must be >= 0: -100
... See the manual at https://verilator.org/verilator_doc.html?v=5.043 for more assistance.

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
test.top_filename = "t/t_opt_slice_element_limit.v"
test.golden_filename = "t/t_opt_slice_element_limit_bad.out"
test.lint(fails=True,
verilator_flags2=['--stats', '--fslice-element-limit', '-100'],
except_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.top_filename = "t/t_opt_slice_element_limit.v"
test.compile(verilator_flags2=['--stats'])
test.file_grep(test.stats, r'Optimizations, Slice array skips due to size limit\s+(\d+)', 1)
test.passes()

View File

@ -7,9 +7,6 @@
: ... note: In instance 't'
27 | q.size < 5;
| ^~~~
%Warning-CONSTRAINTIGN: t/t_randomize_method_types_unsup.v:31:10: Global constraints ignored (unsupported)
31 | foo.x < y;
| ^
%Error-UNSUPPORTED: t/t_randomize_method_types_unsup.v:15:13: Unsupported: random member variable with the type of the containing class
: ... note: In instance 't'
15 | rand Cls cls;

View File

@ -0,0 +1 @@
t_sys_file_basic.out

View File

@ -0,0 +1,26 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios("simulator")
test.top_filename = "t/t_sys_file_basic.v"
test.unlink_ok(test.obj_dir + "/t_sys_file_basic_test.log")
test.compile(
verilator_flags2=["--coverage-expr"],
# Build without cached objects, see bug363
make_flags=["VM_PARALLEL_BUILDS=0"],
)
test.execute()
test.files_identical(test.obj_dir + "/t_sys_file_basic_test.log", test.golden_filename)
test.passes()