diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 08e70f01c..98a125bfa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -156,6 +156,7 @@ set(HEADERS V3PreShell.h V3Premit.h V3ProtectLib.h + V3RandSequence.h V3Randomize.h V3Reloop.h V3Rtti.h @@ -319,6 +320,7 @@ set(COMMON_SOURCES V3PreShell.cpp V3Premit.cpp V3ProtectLib.cpp + V3RandSequence.cpp V3Randomize.cpp V3Reloop.cpp V3Sampled.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 983f26e62..d7b679c05 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -309,6 +309,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Param.o \ V3Premit.o \ V3ProtectLib.o \ + V3RandSequence.o \ V3Randomize.o \ V3Reloop.o \ V3Sampled.o \ diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 9ba1ca65b..fecf234f0 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -940,7 +940,7 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) { class VCaseType final { public: - enum en : uint8_t { CT_CASE, CT_CASEX, CT_CASEZ, CT_CASEINSIDE }; + enum en : uint8_t { CT_CASE, CT_CASEX, CT_CASEZ, CT_CASEINSIDE, CT_RANDSEQUENCE }; enum en m_e; VCaseType() : m_e{CT_CASE} {} diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 2d879013e..513b6cce3 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -1917,6 +1917,12 @@ public: , m_urandom{urandom} { this->seedp(seedp); } + class SRandomU32 {}; + AstRand(FileLine* fl, SRandomU32) + : ASTGEN_SUPER_Rand(fl) + , m_urandom{true} { + dtypeSetUInt32(); + } ASTGEN_MEMBERS_AstRand; string emitVerilog() override { return seedp() ? (m_urandom ? "%f$urandom(%l)" : "%f$random(%l)") diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index f1f4f66d5..2309a879d 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -199,7 +199,7 @@ public: // === AstNode === class AstCaseItem final : public AstNode { - // Single item of AstCase/AstRandCase/AstRSCase + // Single item of AstCase/AstRandCase // @astgen op1 := condsp : List[AstNodeExpr] // @astgen op2 := stmtsp : List[AstNode] public: @@ -887,42 +887,17 @@ public: void timeunit(const VTimescale& flag) { m_timeunit = flag; } VTimescale timeunit() const { return m_timeunit; } }; -class AstRSCase final : public AstNodeStmt { - // Randsequence case statement - // @astgen op1 := exprp : AstNodeExpr // Condition (scurtinee) expression - // @astgen op2 := itemsp : List[AstCaseItem] +class AstRSBreak final : public AstNodeStmt { + // randsequence break public: - AstRSCase(FileLine* fl, AstNodeExpr* exprp, AstCaseItem* itemsp) - : ASTGEN_SUPER_Case(fl) { - this->exprp(exprp); - addItemsp(itemsp); - } - ASTGEN_MEMBERS_AstRSCase; - int instrCount() const override { return INSTR_COUNT_BRANCH; } - bool sameNode(const AstNode* samep) const override { return true; } -}; -class AstRSIf final : public AstNodeStmt { - // Randsequence if - // @astgen op1 := condp : AstNodeExpr - // @astgen op2 := thensp : List[AstNode] - // @astgen op3 := elsesp : List[AstNode] -public: - AstRSIf(FileLine* fl, AstNodeExpr* condp, AstNode* thensp, AstNode* elsesp) - : ASTGEN_SUPER_RSIf(fl) { - this->condp(condp); - addThensp(thensp); - addElsesp(elsesp); - } - -public: - ASTGEN_MEMBERS_AstRSIf; - bool isGateOptimizable() const override { return false; } - bool isGateDedupable() const override { return false; } - int instrCount() const override { return INSTR_COUNT_BRANCH; } - bool sameNode(const AstNode* /*samep*/) const override { return true; } + explicit AstRSBreak(FileLine* fl) + : ASTGEN_SUPER_RSBreak(fl) {} + ASTGEN_MEMBERS_AstRSBreak; + string verilogKwd() const override { return "break"; } + bool isBrancher() const override { V3ERROR_NA_RETURN(true); } // Node removed early }; class AstRSProd final : public AstNodeStmt { - // randomsquence production, under a AstRandSequence + // randomsequence production, under a AstRandSequence // @astgen op1 := fvarp : Optional[AstVar] // @astgen op2 := portsp : List[AstNode] // @astgen op3 := rulesp : List[AstRSRule] @@ -935,12 +910,14 @@ public: addRulesp(rulesp); } ASTGEN_MEMBERS_AstRSProd; + bool maybePointedTo() const override VL_MT_SAFE { return true; } string name() const override VL_MT_STABLE { return m_name; } int instrCount() const override { return INSTR_COUNT_BRANCH; } }; class AstRSProdItem final : public AstNodeStmt { // randomsquence production item // @astgen op1 := argsp : List[AstNodeExpr] + // @astgen ptr := m_prodp : Optional[AstRSProd] // Pointer to production string m_name; // Name of block, or "" to use first production public: AstRSProdItem(FileLine* fl, const string& name, AstNodeExpr* argsp) @@ -951,6 +928,8 @@ public: ASTGEN_MEMBERS_AstRSProdItem; string name() const override VL_MT_STABLE { return m_name; } int instrCount() const override { return INSTR_COUNT_BRANCH; } + AstRSProd* prodp() const { return m_prodp; } + void prodp(AstRSProd* nodep) { m_prodp = nodep; } }; class AstRSProdList final : public AstNodeStmt { // randomsquence production list @@ -970,32 +949,26 @@ public: bool randJoin() const { return m_randJoin; } void randJoin(bool flag) { m_randJoin = flag; } }; -class AstRSRepeat final : public AstNodeStmt { - // randsequence repeat - // @astgen op1 := countp : AstNodeExpr - // @astgen op2 := stmtsp : List[AstNode] +class AstRSReturn final : public AstNodeStmt { + // randsequence return public: - AstRSRepeat(FileLine* fl, AstNodeExpr* countp, AstNode* stmtsp) - : ASTGEN_SUPER_RSRepeat(fl) { - this->countp(countp); - addStmtsp(stmtsp); - } - ASTGEN_MEMBERS_AstRSRepeat; - bool isGateOptimizable() const override { return false; } - int instrCount() const override { return INSTR_COUNT_BRANCH; } - bool sameNode(const AstNode* /*samep*/) const override { return true; } + explicit AstRSReturn(FileLine* fl) + : ASTGEN_SUPER_RSReturn(fl) {} + ASTGEN_MEMBERS_AstRSReturn; + string verilogKwd() const override { return "return"; } + bool isBrancher() const override { V3ERROR_NA_RETURN(true); } // Node removed early }; class AstRSRule final : public AstNodeStmt { // randomsquence rule // @astgen op1 := weightp : Optional[AstNodeExpr] // @astgen op2 := prodlistsp : List[AstRSProdList] - // @astgen op3 := stmtsp : List[AstNode] + // @astgen op3 := weightStmtsp : List[AstNode] public: - AstRSRule(FileLine* fl, AstNodeExpr* weightp, AstRSProdList* prodlistsp, AstNode* stmtsp) + AstRSRule(FileLine* fl, AstNodeExpr* weightp, AstRSProdList* prodlistsp, AstNode* weightStmtsp) : ASTGEN_SUPER_RSRule(fl) { this->weightp(weightp); addProdlistsp(prodlistsp); - addStmtsp(stmtsp); + addWeightStmtsp(weightStmtsp); } ASTGEN_MEMBERS_AstRSRule; int instrCount() const override { return INSTR_COUNT_BRANCH; } @@ -1013,16 +986,24 @@ public: }; class AstRandSequence final : public AstNodeStmt { // @astgen op2 := prodsp : List[AstRSProd] - string m_name; // Name of block, or "" to use first production + // @astgen ptr := m_prodp : Optional[AstRSProd] // Pointer to start production (if any) + string m_name; // Created unique name + string m_start; // Name of start production, or "" to use first production public: - AstRandSequence(FileLine* fl, const string& name, AstRSProd* prodsp) + AstRandSequence(FileLine* fl, const string& start, AstRSProd* prodsp) : ASTGEN_SUPER_RandSequence(fl) - , m_name{name} { + , m_start{start} { addProdsp(prodsp); } ASTGEN_MEMBERS_AstRandSequence; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; string name() const override VL_MT_STABLE { return m_name; } // * = Block name + void name(const string& name) override { m_name = name; } + string start() const VL_MT_STABLE { return m_start; } int instrCount() const override { return INSTR_COUNT_BRANCH; } + AstRSProd* prodp() const { return m_prodp; } + void prodp(AstRSProd* nodep) { m_prodp = nodep; } }; class AstRelease final : public AstNodeStmt { // Procedural 'release' statement diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index ee1c72019..1dad887d6 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2271,6 +2271,14 @@ void AstTypedefFwd::dumpJson(std::ostream& str) const { } void AstNodeRange::dump(std::ostream& str) const { this->AstNode::dump(str); } void AstNodeRange::dumpJson(std::ostream& str) const { dumpJsonGen(str); } +void AstRandSequence::dump(std::ostream& str) const { + this->AstNode::dump(str); + str << " start=" << start(); +} +void AstRandSequence::dumpJson(std::ostream& str) const { + if (!start().empty()) dumpJsonStr(str, "start", start()); + dumpJsonGen(str); +} void AstRange::dump(std::ostream& str) const { this->AstNodeRange::dump(str); if (fromBracket()) str << " [FB]"; diff --git a/src/V3Global.h b/src/V3Global.h index ca8258985..c6c478d91 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -125,6 +125,7 @@ class V3Global final { bool m_hasForceableSignals = false; // Need to apply V3Force pass bool m_hasSystemCSections = false; // Has AstSystemCSection that need to be emitted bool m_useParallelBuild = false; // Use parallel build for model + bool m_useRandSequence = false; // Has `randsequence` bool m_useRandomizeMethods = false; // Need to define randomize() class methods uint64_t m_currentHierBlockCost = 0; // Total cost of this hier block, used for scheduling @@ -203,6 +204,8 @@ public: void hierGraphp(V3HierGraph* graphp) { m_hierGraphp = graphp; } bool useParallelBuild() const { return m_useParallelBuild; } void useParallelBuild(bool flag) { m_useParallelBuild = flag; } + bool useRandSequence() const { return m_useRandSequence; } + void useRandSequence(bool flag) { m_useRandSequence = flag; } bool useRandomizeMethods() const { return m_useRandomizeMethods; } void useRandomizeMethods(bool flag) { m_useRandomizeMethods = flag; } void saveJsonPtrFieldName(const std::string& fieldName); diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index cf31a07e3..c1c996d4d 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -94,6 +94,10 @@ class LinkNodeMatcherModport final : public VNodeMatcher { public: bool nodeMatch(const AstNode* nodep) const override { return VN_IS(nodep, Modport); } }; +class LinkNodeMatcherProd final : public VNodeMatcher { +public: + bool nodeMatch(const AstNode* nodep) const override { return VN_IS(nodep, RSProd); } +}; class LinkNodeMatcherVar final : public VNodeMatcher { public: bool nodeMatch(const AstNode* nodep) const override { @@ -246,20 +250,22 @@ public: } else { return "local type parameter"; } + } else if (VN_IS(nodep, Begin)) { + return "block"; } else if (VN_IS(nodep, Cell)) { return "instance"; } else if (VN_IS(nodep, Constraint)) { return "constraint"; - } else if (VN_IS(nodep, Task)) { - return "task"; } else if (VN_IS(nodep, Func)) { return "function"; - } else if (VN_IS(nodep, Begin)) { - return "block"; - } else if (VN_IS(nodep, Iface)) { - return "interface"; } else if (VN_IS(nodep, GenBlock)) { return "generate block"; + } else if (VN_IS(nodep, Iface)) { + return "interface"; + } else if (VN_IS(nodep, RSProd)) { + return "randsequence production"; + } else if (VN_IS(nodep, Task)) { + return "task"; } else { return nodep->prettyTypeName(); } @@ -1856,6 +1862,23 @@ class LinkDotFindVisitor final : public VNVisitor { } } + void visit(AstRandSequence* nodep) override { + VL_RESTORER(m_curSymp); + m_curSymp = m_statep->insertBlock(m_curSymp, nodep->name(), nodep, m_classOrPackagep); + iterateChildren(nodep); + } + void visit(AstRSProd* nodep) override { + VL_RESTORER(m_curSymp); + m_curSymp = m_statep->insertBlock(m_curSymp, nodep->name(), nodep, m_classOrPackagep); + if (nodep->fvarp()) + nodep->fvarp()->v3warn(E_UNSUPPORTED, + "Unsupported: randsequence production function variable"); + if (nodep->portsp()) + nodep->portsp()->v3warn(E_UNSUPPORTED, + "Unsupported: randsequence production function ports"); + iterateChildren(nodep); + } + void visit(AstWithParse* nodep) override { // FindVisitor:: // Change WITHPARSE(FUNCREF, equation) to FUNCREF(WITH(equation)) AstNodeFTaskRef* funcrefp = VN_CAST(nodep->funcrefp(), NodeFTaskRef); @@ -2725,6 +2748,21 @@ class LinkDotResolveVisitor final : public VNVisitor { UASSERT_OBJ(ifaceTopVarp, nodep, "Can't find interface var ref: " << findName); return ifaceTopVarp; } + AstRSProd* findProd(AstNode* nodep, VSymEnt* parentEntp, const string& name) { + const VSymEnt* const foundp = parentEntp->findIdFallback(name); + AstRSProd* foundNodep = foundp ? VN_CAST(foundp->nodep(), RSProd) : nullptr; + if (!foundNodep) { + VSpellCheck speller; + LinkNodeMatcherProd matcher; + parentEntp->candidateIdFlat(&speller, &matcher); + const string suggest = speller.bestCandidateMsg(name); + UINFO(1, " ErrParseRef curSymp=se" << cvtToHex(parentEntp)); + nodep->v3error("Production " << AstNode::prettyNameQ(name) << " not found\n" + << (suggest.empty() ? "" : nodep->warnMore() + suggest)); + return nullptr; + } + return foundNodep; + } void markAndCheckPinDup(AstPin* nodep, AstNode* refp, const char* whatp) { const auto pair = m_usedPins.emplace(refp, nodep); if (!pair.second) { @@ -4722,6 +4760,27 @@ class LinkDotResolveVisitor final : public VNVisitor { checkNoDot(nodep); symIterateChildren(nodep, m_statep->getNodeSym(nodep)); } + void visit(AstRandSequence* nodep) override { + LINKDOT_VISIT_START(); + UINFO(5, indent() << "visit " << nodep); + checkNoDot(nodep); + if (!nodep->start().empty()) + nodep->prodp(findProd(nodep, m_statep->getNodeSym(nodep), nodep->start())); + symIterateChildren(nodep, m_statep->getNodeSym(nodep)); + } + void visit(AstRSProd* nodep) override { + LINKDOT_VISIT_START(); + UINFO(5, indent() << "visit " << nodep); + checkNoDot(nodep); + symIterateChildren(nodep, m_statep->getNodeSym(nodep)); + } + void visit(AstRSProdItem* nodep) override { + LINKDOT_VISIT_START(); + UINFO(5, indent() << "visit " << nodep); + checkNoDot(nodep); + nodep->prodp(findProd(nodep, m_curSymp, nodep->name())); + iterateChildren(nodep); + } void visit(AstWith* nodep) override { LINKDOT_VISIT_START(); UINFO(5, indent() << "visit " << nodep); diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index 952d772ca..030bccbf7 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -57,6 +57,7 @@ class LinkJumpVisitor final : public VNVisitor { AstNodeModule* m_modp = nullptr; // Current module AstNodeFTask* m_ftaskp = nullptr; // Current function/task AstNode* m_loopp = nullptr; // Current loop + AstRandSequence* m_randsequencep = nullptr; // Current randsequence bool m_loopInc = false; // In loop increment bool m_inFork = false; // Under fork int m_modRepeatNum = 0; // Repeat counter @@ -289,6 +290,11 @@ class LinkJumpVisitor final : public VNVisitor { iterateChildren(nodep); } } + void visit(AstRandSequence* nodep) override { + VL_RESTORER(m_randsequencep); + m_randsequencep = nodep; + iterateChildren(nodep); + } void visit(AstRepeat* nodep) override { // So later optimizations don't need to deal with them, // REPEAT(count,body) -> loop=count,WHILE(loop>0) { body, loop-- } @@ -354,6 +360,10 @@ class LinkJumpVisitor final : public VNVisitor { nodep->v3error("Return isn't legal under fork (IEEE 1800-2023 9.2.3)"); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); return; + } else if (!m_ftaskp && m_randsequencep) { + nodep->replaceWith(new AstRSReturn{nodep->fileline()}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; } else if (!m_ftaskp) { nodep->v3error("Return isn't underneath a task or function"); } else if (funcp && !nodep->lhsp() && !funcp->isConstructor()) { @@ -377,7 +387,11 @@ class LinkJumpVisitor final : public VNVisitor { } void visit(AstBreak* nodep) override { iterateChildren(nodep); - if (!m_loopp) { + if (!m_loopp && m_randsequencep) { + nodep->replaceWith(new AstRSBreak{nodep->fileline()}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } else if (!m_loopp) { nodep->v3error("break isn't underneath a loop"); } else { // Jump to the end of the loop diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index b444ec9f3..4bc6e8695 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -66,6 +66,7 @@ class LinkParseVisitor final : public VNVisitor { int m_genblkAbove = 0; // Begin block number of if/case/for above int m_genblkNum = 0; // Begin block number, 0=none seen int m_beginDepth = 0; // How many begin blocks above current node within current AstNodeModule + int m_randSequenceNum = 0; // RandSequence uniqify number VLifetime m_lifetime = VLifetime::STATIC_IMPLICIT; // Propagating lifetime bool m_insideLoop = false; // True if the node is inside a loop bool m_lifetimeAllowed = false; // True to allow lifetime settings @@ -647,30 +648,6 @@ class LinkParseVisitor final : public VNVisitor { } iterateChildren(nodep); } - void visit(AstRandSequence* nodep) override { - cleanFileline(nodep); - nodep->v3warn(E_UNSUPPORTED, "Unsupported: randsequence"); - iterateChildren(nodep); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } - void visit(AstRSCase* nodep) override { - cleanFileline(nodep); - nodep->v3warn(E_UNSUPPORTED, "Unsupported: randsequence case"); - iterateChildren(nodep); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } - void visit(AstRSIf* nodep) override { - cleanFileline(nodep); - nodep->v3warn(E_UNSUPPORTED, "Unsupported: randsequence if"); - iterateChildren(nodep); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } - void visit(AstRSRepeat* nodep) override { - cleanFileline(nodep); - nodep->v3warn(E_UNSUPPORTED, "Unsupported: randsequence repeat"); - iterateChildren(nodep); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } void visit(AstWait* nodep) override { cleanFileline(nodep); iterateChildren(nodep); @@ -701,6 +678,7 @@ class LinkParseVisitor final : public VNVisitor { VL_RESTORER(m_lifetime); VL_RESTORER(m_lifetimeAllowed); VL_RESTORER(m_moduleWithGenericIface); + VL_RESTORER(m_randSequenceNum); VL_RESTORER(m_valueModp); // Module: Create sim table for entire module and iterate @@ -718,6 +696,8 @@ class LinkParseVisitor final : public VNVisitor { m_lifetime = nodep->lifetime().makeImplicit(); m_lifetimeAllowed = VN_IS(nodep, Class); m_moduleWithGenericIface = false; + m_randSequenceNum = 0; + if (m_lifetime.isNone()) { m_lifetime = VN_IS(nodep, Class) ? VLifetime::AUTOMATIC_IMPLICIT : VLifetime::STATIC_IMPLICIT; @@ -892,6 +872,11 @@ class LinkParseVisitor final : public VNVisitor { nodep->name(m_modp->name()); nodep->timeunit(m_modp->timeunit()); } + void visit(AstRandSequence* nodep) override { + cleanFileline(nodep); + nodep->name("__Vrs" + std::to_string(m_randSequenceNum++)); + iterateChildren(nodep); + } void visit(AstSFormatF* nodep) override { cleanFileline(nodep); iterateChildren(nodep); diff --git a/src/V3RandSequence.cpp b/src/V3RandSequence.cpp new file mode 100644 index 000000000..7566cf6e0 --- /dev/null +++ b/src/V3RandSequence.cpp @@ -0,0 +1,633 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Transform randsequence statements +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-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 +// +//************************************************************************* +// V3Randomize's Transformations: +// +// Convert each RandSequence production into a production task, and hookup. +// PROD({x}) -> TASK(__Vrs_{x}, ARG(__VrsBreak), arguments) +// JUMPBLOCK +// +// Every variable referenced in the RandSequence becomes an ARG passed +// around. (Alternative would be make a VARXREF's but the needed variable may +// be on the stack, subject to recursion, or otherwise.) +// +// A production becomes a conditional execution of the PRODLIST below +// for a single production list +// RSPROD(RSRULE(weight, stmt)) +// -> IF(weight != 0, stmt) +// or RSPROD(RSRULE(weight1, stmt!), RSRULE(weight2, stmt2)) +// -> RANDCASE(CASEITEM(weight1, stmt1), CASEITEM(weight2, stmt2)) +// +// Return becomes a jump to end of production task +// RSRETURN -> JUMPBLOCK(..., JUMPGO) +// +// Break becomes a jump to end of production task, and sets a variable +// that the production call will check +// RSBREAK -> ASSIGH(__VrsBreak, 1) +// JUMPBLOCK(..., JUMPGO) +// +// Production calls become a function call +// call({x}) -> TASKCALL(__Vrs_{x}, ARG(__VrsBreak), arguments) +// IF(__VrsBreak) JUMPGO-break +// +// Rand join maps to a large complicated function, described in comments below +// +// If, Case, and Repeat map to normal non-randsequence nodes +// +//************************************************************************* + +#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT + +#include "verilatedos.h" + +#include "V3RandSequence.h" + +#include "V3Ast.h" +#include "V3Error.h" +#include "V3FileLine.h" +#include "V3Global.h" + +VL_DEFINE_DEBUG_FUNCTIONS; + +// ###################################################################### +// Matcher classes (for suggestion matching) + +class RSProdMatcher final : public VNodeMatcher { +public: + bool nodeMatch(const AstNode* nodep) const override { return VN_IS(nodep, RSProd); } +}; + +//###################################################################### +// Visitor that marks classes needing a randomize() method + +class RandSequenceVisitor final : public VNVisitor { + // STATE - global + std::map m_prodFuncps; // Generated production functions + + // STATE - for current visit position (use VL_RESTORER) + AstNodeModule* m_modp = nullptr; // Current module + AstRandSequence* m_rsp = nullptr; // Current randsequence + AstRSProd* m_startProdp = nullptr; // Starting production + AstNodeFTask* m_prodFuncp = nullptr; // Production function being built + AstJumpBlock* m_jumpBlockp = nullptr; // Building jumpblock for return/break + AstVar* m_breakVarp = nullptr; // Break variable + std::unordered_set m_localizes; // Variables to become function inouts + std::map m_localizeNames; // Ordered names of function inouts + std::unordered_map m_localizeRemaps; // New ports for old vars + + // METHODS + void findLocalizes(AstRandSequence* nodep) { + std::set localVars; + nodep->foreach([&](AstNode* const nodep) { + if (AstVarRef* const anodep = VN_CAST(nodep, VarRef)) { + m_localizes.emplace(anodep->varp()); + } else if (AstVar* const anodep = VN_CAST(nodep, Var)) { + localVars.emplace(anodep); + } + }); + // Clear every variable allocated in the RandSequence + for (AstVar* varp : localVars) m_localizes.erase(varp); + // Sort by name, so function arguments are stable + for (AstVar* varp : m_localizes) { + UINFO(9, "localize " << varp); + m_localizeNames.emplace(varp->name(), varp); + } + } + + AstVar* newBreakVar(FileLine* fl, bool asOutput) { + AstVar* const varp = new AstVar{fl, VVarType::PORT, "__VrsBreak", VFlagBitPacked{}, 1}; + varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + varp->funcLocal(true); + if (asOutput) varp->direction(VDirection::OUTPUT); + AstNode* outp = varp; + outp->addNext(new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, + new AstConst{fl, AstConst::BitFalse{}}}); + + // Also add arguments as next's + for (auto& itr : m_localizeNames) { + const AstVar* const lvarp = itr.second; + AstVar* const iovarp + = new AstVar{fl, VVarType::PORT, "__Vrsarg_" + lvarp->name(), lvarp}; + iovarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + iovarp->funcLocal(true); + iovarp->direction(VDirection::REF); + varp->addNext(iovarp); + m_localizeRemaps.emplace(lvarp, iovarp); + } + return varp; + } + + AstTask* newStartFunc(AstRandSequence* nodep) { + // Create wrapper that is called by the sequence + AstTask* const taskp = new AstTask{m_startProdp->fileline(), m_rsp->name(), nullptr}; + taskp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + m_modp->addStmtsp(taskp); + + // Create local (not output) break variable + VL_RESTORER(m_localizeRemaps); + AstVar* breakVarp = newBreakVar(nodep->fileline(), false); + taskp->addStmtsp(breakVarp); + + // Call the start production's task + taskp->addStmtsp(newProdFuncRef(nodep, m_startProdp, breakVarp)); + + UINFOTREE(9, taskp, "newStart", ""); + return taskp; + } + + AstNode* newProdFuncRef(AstNode* nodep, AstRSProd* prodp, AstVar* breakVarp) { + auto it = m_prodFuncps.find(prodp); + UASSERT_OBJ(it != m_prodFuncps.end(), nodep, "No production function made"); + AstNodeFTask* const prodFuncp = it->second; + FileLine* const fl = nodep->fileline(); + AstArg* const argsp + = new AstArg{fl, breakVarp->name(), new AstVarRef{fl, breakVarp, VAccess::WRITE}}; + for (auto& itr : m_localizeNames) { + const AstVar* const lvarp = itr.second; + AstVar* const iovarp = m_localizeRemaps[lvarp]; + UASSERT_OBJ(iovarp, nodep, "No new port variable for local variable" << lvarp); + argsp->addNext(new AstArg{nodep->fileline(), "__Vrsarg_" + lvarp->name(), + new AstVarRef{fl, iovarp, VAccess::READWRITE}}); + } + AstNode* const newp + = new AstStmtExpr{fl, new AstTaskRef{fl, VN_AS(prodFuncp, Task), argsp}}; + return newp; + } + + void newProdFunc(AstRSProd* nodep) { + // Task, as time may pass + AstTask* const newp + = new AstTask{nodep->fileline(), m_rsp->name() + "_" + nodep->name(), nullptr}; + m_modp->addStmtsp(newp); + m_prodFuncps.emplace(nodep, newp); + UINFOTREE(9, newp, "newProd", ""); + } + + AstNode* newProdRandJoin(AstRSProd* prodp, AstRSProdList* prodlistp) { + // For weight == 1.0 longer sequence (favor stay in a) + // For weight == 0.0 shorter squence (favor change a/b) + UASSERT_OBJ(prodlistp->weightp(), prodlistp, "Weight should have default CONST(0.5)"); + AstNodeExpr* weightp = prodlistp->weightp()->unlinkFrBack(); + + // Build map of production lists and productions we need to join + // Must mainain the relative order of each sequence. + std::deque lists; + std::unordered_map> listStmts; + for (AstRSProdItem* proditemp = VN_AS(prodlistp->prodsp(), RSProdItem); proditemp; + proditemp = VN_AS(proditemp->nextp(), RSProdItem)) { + lists.push_back(proditemp); + AstRSProd* const subProdp = proditemp->prodp(); + if (!subProdp) continue; + if (!subProdp->rulesp()) continue; + if (!subProdp->rulesp()->prodlistsp()) continue; + for (AstNodeStmt* subStmtp + = VN_AS(subProdp->rulesp()->prodlistsp()->prodsp(), NodeStmt); + subStmtp; subStmtp = VN_AS(subStmtp->nextp(), NodeStmt)) { + listStmts[proditemp].push_back(subStmtp); + } + } + + UINFO(9, "RandJoin productions called:"); + for (AstRSProdItem* proditemp : lists) { + UINFO(9, " list " << proditemp); + for (AstNodeStmt* prodp : listStmts[proditemp]) UINFO(9, " calls " << prodp); + } + + // Need to clone all nodes used + + FileLine* fl = prodlistp->fileline(); + AstNode* newp = nullptr; + + // "bool _Vjoin_repick = true;" // Pick a new longer sequence + AstVar* repickVarp = new AstVar{fl, VVarType::VAR, "_Vjoin_repick", VFlagBitPacked{}, 1}; + repickVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + repickVarp->funcLocal(true); + m_breakVarp->addNextHere(repickVarp); // Add to function top, not newp + newp = AstNode::addNext(newp, + new AstAssign{fl, new AstVarRef{fl, repickVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::BitTrue{}}}); + + // "int _Vjoin_nleft_0 = N(items);" // N(a) Number left to generate in list 0, starts at + // N(list0) + std::deque nleftVarps; + int i = 0; + for (AstRSProdItem* proditemp : lists) { + // 'rand join arule arule arule' is legal, so need numbered, not non-unique named + // nleft's + AstVar* const varp = new AstVar{ + fl, VVarType::VAR, "_Vjoin_nleft_" + std::to_string(i++), VFlagBitPacked{}, 32}; + varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + varp->funcLocal(true); + m_breakVarp->addNextHere(varp); // Add to function top, not newp + nleftVarps.push_back(varp); + + newp = AstNode::addNext( + newp, + new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, + new AstConst{fl, AstConst::WidthedValue{}, 32, + static_cast(listStmts[proditemp].size())}}); + } + + // "int _Vjoin_picked_elist = 0;" // 0 = pick first eligible (non-finished) list, 1 = pick + // second, etc + AstVar* const pickedVarp + = new AstVar{fl, VVarType::VAR, "_Vjoin_picked_elist", VFlagBitPacked{}, 1}; + pickedVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + pickedVarp->funcLocal(true); + m_breakVarp->addNextHere(pickedVarp); // Add to function top, not newp + + // "while(1) {" // Generate until make complete sequence + AstJumpBlock* const whilep = new AstJumpBlock{fl, nullptr}; + newp = AstNode::addNext(newp, new AstLoop{fl, whilep}); + { + // "if ((std::rand() & 0xffffUL) > (0xffff * weight)) _Vjoin_repick = true;" + whilep->addStmtsp(new AstIf{ + fl, + new AstGt{ + fl, new AstRand{fl, AstRand::SRandomU32{}}, + new AstRToIS{fl, new AstMulD{fl, + new AstConst{fl, AstConst::RealDouble{}, + static_cast(0xffffffffUL)}, + weightp}}}, + new AstAssign{fl, new AstVarRef{fl, repickVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::BitTrue{}}}}); + + // "if (_Vjoin_repick) {" + AstIf* ifp = new AstIf{fl, new AstVarRef{fl, repickVarp, VAccess::READ}}; + whilep->addStmtsp(ifp); + { + // "// _Vjoin_nlists_eligible is how many lists eligable for picking" + // "int _Vjoin_nlists_eligible = 0;" + AstVar* const eligibleVarp = new AstVar{ + fl, VVarType::VAR, "_Vjoin_nlists_eligible", VFlagBitPacked{}, 32}; + eligibleVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + eligibleVarp->funcLocal(true); + m_breakVarp->addNextHere(eligibleVarp); // Add to function top, not newp + ifp->addThensp(new AstAssign{fl, new AstVarRef{fl, eligibleVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::WidthedValue{}, 32, 0}}); + + i = 0; + for (AstRSProdItem* proditemp : lists) { + (void)proditemp; + // "_Vjoin_nlists_eligible += _Vjoin_nleft_0 ? 1 : 0;" + ifp->addThensp(new AstAssign{ + fl, new AstVarRef{fl, eligibleVarp, VAccess::WRITE}, + new AstAdd{ + fl, new AstVarRef{fl, eligibleVarp, VAccess::READ}, + new AstCond{ + fl, + new AstNeq{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, 0}, + new AstVarRef{fl, nleftVarps[i], VAccess::READ}}, + new AstConst{fl, AstConst::WidthedValue{}, 32, 1}, + new AstConst{fl, AstConst::WidthedValue{}, 32, 0}}}}); + ++i; + } + + // "if (!_Vjoin_nlists_eligible) break;" // Out of elements + ifp->addThensp( + new AstIf{fl, + new AstEq{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, 0}, + new AstVarRef{fl, eligibleVarp, VAccess::READ}}, + new AstJumpGo{fl, m_jumpBlockp}}); + + // "// +1 to simplify usage, so 0 = done" + // "_Vjoin_picked_elist = 1 + (std::rand() % _Vjoin_nlists_eligible);" + ifp->addThensp(new AstAssign{ + fl, new AstVarRef{fl, pickedVarp, VAccess::WRITE}, + new AstAdd{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, 1}, + new AstModDiv{fl, new AstRand{fl, AstRand::SRandomU32{}}, + new AstVarRef{fl, eligibleVarp, VAccess::READ}}}}); + + // "_Vjoin_repick = false; + ifp->addThensp(new AstAssign{fl, new AstVarRef{fl, repickVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::BitFalse{}}}); + } + + // "// Number of eligible lists left before hit _Vjoin_pciked one" + // "int _Vjoin_sel_elist = _Vjoin_picked_elist;" + AstVar* const selVarp + = new AstVar{fl, VVarType::VAR, "_Vjoin_sel_elist", VFlagBitPacked{}, 32}; + selVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + selVarp->funcLocal(true); + m_breakVarp->addNextHere(selVarp); // Add to function top, not newp + whilep->addStmtsp(new AstAssign{fl, new AstVarRef{fl, selVarp, VAccess::WRITE}, + new AstVarRef{fl, pickedVarp, VAccess::READ}}); + + i = -1; + for (AstRSProdItem* proditemp : lists) { + ++i; + // "if (_Vjoin_nleft_0) {" + ifp = new AstIf{fl, + new AstNeq{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, 0}, + new AstVarRef{fl, nleftVarps[i], VAccess::READ}}, + nullptr, nullptr}; + whilep->addStmtsp(ifp); + { + // "--_Vjoin_sel_elist;" + ifp->addThensp(new AstAssign{ + fl, new AstVarRef{fl, selVarp, VAccess::WRITE}, + new AstSub{fl, new AstVarRef{fl, selVarp, VAccess::READ}, + new AstConst{fl, AstConst::WidthedValue{}, 32, 1}}}); + + // "if (_Vjoin_sel_elist == 0) {" + AstIf* const jIfp = new AstIf{ + fl, + new AstEq{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, 0}, + new AstVarRef{fl, selVarp, VAccess::READ}}, + nullptr, nullptr}; + ifp->addThensp(jIfp); + { + // "switch (_Vjoin_nleft_0) {" + // "case 2 / * N(a) * /: {statement}; break;" + // "case 1 / * N(a) - 1 * /: {statement}; break;" + uint32_t j = static_cast(listStmts[proditemp].size()); + for (AstNodeStmt* prodp : listStmts[proditemp]) { + jIfp->addThensp(new AstIf{ + fl, + new AstEq{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, j}, + new AstVarRef{fl, nleftVarps[i], VAccess::READ}}, + prodp->cloneTree(false)}); + --j; + } + + // "--_Vjoin_nleft_0;" + jIfp->addThensp(new AstAssign{ + fl, new AstVarRef{fl, nleftVarps[i], VAccess::WRITE}, + new AstSub{fl, new AstVarRef{fl, nleftVarps[i], VAccess::READ}, + new AstConst{fl, AstConst::WidthedValue{}, 32, 1}}}); + + // "if (0 == _Vjoin_nleft_0) _Vjoin_repick = true;" + jIfp->addThensp(new AstIf{ + fl, + new AstEq{fl, new AstConst{fl, AstConst::WidthedValue{}, 32, 0}, + new AstVarRef{fl, nleftVarps[i], VAccess::READ}}, + new AstAssign{fl, new AstVarRef{fl, repickVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::BitTrue{}}}}); + + // "continue;" + jIfp->addThensp(new AstJumpGo{fl, whilep}); + } + } + } + } + + UINFOTREE(9, newp, "ForkJoin new", "-"); + return newp; + } + + // VISITORS + + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_modp); + m_modp = nodep; + iterateChildren(nodep); + } + + void visit(AstRandSequence* nodep) override { + UINFO(9, "visit " << nodep); + UASSERT_OBJ(m_modp, nodep, "randsequence not under module"); + if (m_rsp) { + // No examples found in the wild + nodep->v3warn(E_UNSUPPORTED, "Unsupported: randsequence under randsequence"); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + return; + } + + UINFOTREE(9, nodep, "rsIn", ""); + VL_RESTORER(m_rsp); + m_rsp = nodep; + VL_RESTORER(m_startProdp); + + VL_RESTORER(m_localizes); + VL_RESTORER(m_localizeNames); + VL_RESTORER(m_localizeRemaps); + findLocalizes(nodep); + + // Find first production + m_startProdp = nodep->prodsp(); + if (nodep->prodp()) m_startProdp = nodep->prodp(); // Has a start production selected + + // Build production functions + for (AstRSProd* prodp = nodep->prodsp(); prodp; prodp = VN_AS(prodp->nextp(), RSProd)) { + newProdFunc(prodp); + // Don't iterate yet, need to have tasks we can reference + } + + // Iterate all of the new productions, moving guts into functions just made + // Must do those with 'rand join' first, as they need to see the raw AstRS* nodes + // before they are removed. (Alternative would be to keep them until the end). + UINFOTREE(9, nodep, "RS Tree pre-it", "-"); + std::unordered_set prodHasRandJoin; + for (AstRSProd* prodp = nodep->prodsp(); prodp; prodp = VN_AS(prodp->nextp(), RSProd)) { + prodp->foreach([&](AstRSProdList* const prodlistp) { + if (prodlistp->randJoin()) prodHasRandJoin.emplace(prodp); + }); + } + for (AstRSProd *nextp, *prodp = nodep->prodsp(); prodp; prodp = nextp) { + nextp = VN_AS(prodp->nextp(), RSProd); + if (prodHasRandJoin.count(prodp)) VL_DO_DANGLING(iterate(prodp), prodp); + } + for (AstRSProd *nextp, *prodp = nodep->prodsp(); prodp; prodp = nextp) { + nextp = VN_AS(prodp->nextp(), RSProd); + if (!prodHasRandJoin.count(prodp)) VL_DO_DANGLING(iterate(prodp), prodp); + } + UINFOTREE(9, nodep, "RS Tree post-it", "-"); + + // Replace randsequence with call to randsequence start function + AstTask* const startFuncp = newStartFunc(nodep); + AstArg* argsp = nullptr; + for (auto& itr : m_localizeNames) { + AstVar* const lvarp = itr.second; + argsp = AstNode::addNext( + argsp, new AstArg{nodep->fileline(), "__Vrsarg_" + lvarp->name(), + new AstVarRef{nodep->fileline(), lvarp, VAccess::READWRITE}}); + } + AstTaskRef* const callStartp + = new AstTaskRef{nodep->fileline(), startFuncp->name(), argsp}; + callStartp->taskp(startFuncp); + nodep->replaceWith(new AstStmtExpr{nodep->fileline(), callStartp}); + + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + void visit(AstRSProd* nodep) override { + // Production, immediately under a randsequence + UINFO(9, "visit " << nodep); + UINFOTREE(9, nodep, "visit " << nodep, "-"); + UASSERT_OBJ(!m_prodFuncp, nodep, "recursive production definition?"); + VL_RESTORER(m_prodFuncp); + auto it = m_prodFuncps.find(nodep); + UASSERT_OBJ(it != m_prodFuncps.end(), nodep, "No production function made"); + m_prodFuncp = it->second; + + // Create a break variable + // TODO we could do this only if break exists in the downstream production, + // but as-is we'll optimize it away in most cases anyways + VL_RESTORER(m_breakVarp); + VL_RESTORER(m_localizeRemaps); + m_breakVarp = newBreakVar(nodep->fileline(), true); + m_prodFuncp->addStmtsp(m_breakVarp); + + // Put JumpBlock immediately under the new function to support + // a future break/return. V3Const will rip it out if unneeded. + VL_RESTORER(m_jumpBlockp); + m_jumpBlockp = new AstJumpBlock{nodep->fileline(), nullptr}; + m_prodFuncp->addStmtsp(m_jumpBlockp); + + if (nodep->fvarp()) + nodep->fvarp()->v3warn(E_UNSUPPORTED, + "Unsupported: randsequence production function variable"); + if (nodep->portsp()) + nodep->portsp()->v3warn(E_UNSUPPORTED, + "Unsupported: randsequence production function ports"); + + // Move children into m_prodFuncp, and iterate there + if (!nodep->rulesp()) { // Nothing to do + } else if (nodep->rulesp()->prodlistsp() && nodep->rulesp()->prodlistsp()->randJoin()) { + AstRSProdList* const prodlistp = nodep->rulesp()->prodlistsp(); + AstNode* const itemsp = newProdRandJoin(nodep, prodlistp); + m_jumpBlockp->addStmtsp(itemsp); + iterateAndNextNull(itemsp); + } else if (!nodep->rulesp()->nextp()) { // Single rule/list, can just do it + // RSPROD(RSRULE(weight, stmt)) -> IF(weight != 0, stmt) + AstRSRule* const rulep = nodep->rulesp(); + AstNode* itemsp = nullptr; + if (rulep->weightStmtsp()) itemsp = rulep->weightStmtsp()->unlinkFrBackWithNext(); + if (rulep->prodlistsp()) + itemsp = AstNode::addNext(itemsp, rulep->prodlistsp()->unlinkFrBackWithNext()); + // Can ignore rulep->weightp() unless is zero, in which case noop, add if + if (rulep->weightp()) + itemsp = new AstIf{rulep->weightp()->fileline(), + new AstNeq{rulep->weightp()->fileline(), + new AstConst{rulep->weightp()->fileline(), 0}, + rulep->weightp()->unlinkFrBack()}, + itemsp}; + // Move prodlist to parent (m_prodFunc) and process + if (itemsp) { + UINFOTREE(9, itemsp, "additems", "-"); + m_jumpBlockp->addStmtsp(itemsp); + iterateAndNextNull(itemsp); + UINFOTREE(9, m_jumpBlockp, "jumpBlockNow", "-"); + } + } else { + // List of rules with "OR", need to random-weight them + // RSPROD(RSRULE(weight1, stmt!), RSRULE(weight2, stmt2)) + // -> RANDCASE(CASEITEM(weight1, stmt1), CASEITEM(weight2, stmt2)) + AstRandCase* const randcasep = new AstRandCase{nodep->fileline(), nullptr}; + for (AstRSRule* rulep = nodep->rulesp(); rulep; + rulep = VN_AS(rulep->nextp(), RSRule)) { + AstNode* itemsp = nullptr; + if (rulep->weightStmtsp()) itemsp = rulep->weightStmtsp()->unlinkFrBackWithNext(); + if (rulep->prodlistsp()) + itemsp = AstNode::addNext(itemsp, rulep->prodlistsp()->unlinkFrBackWithNext()); + if (itemsp) { + AstNodeExpr* const weightp = rulep->weightp() + ? rulep->weightp()->unlinkFrBack() + : new AstConst{rulep->fileline(), 1}; + randcasep->addItemsp(new AstCaseItem{rulep->fileline(), weightp, itemsp}); + } + } + m_jumpBlockp->addStmtsp(randcasep); + iterateAndNextNull(randcasep); + } + + // V3Task needs to know if we ended up making a recursive function + m_prodFuncp->foreach([&](AstNodeFTaskRef* const nodep) { + if (nodep->taskp() == m_prodFuncp) m_prodFuncp->recursive(true); + }); + UINFOTREE(9, m_prodFuncp, "rsprod-task-done " << nodep, "-"); + + // Done with production, should have moved everything to new task + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + } + void visit(AstRSRule* nodep) override { + // Rule of what to produce (as a list) under a production + nodep->v3fatalSrc("randsequence rule should be iterated and deleted by AstRSProd"); + } + void visit(AstRSProdList* nodep) override { + // List of productions to call, potentially with a weight + UINFO(9, "visit " << nodep); + UASSERT_OBJ(m_prodFuncp, nodep, "RSProdList not under production"); + + // Move prodlist to parent (m_prodFunc) and process + if (AstNode* const itemsp = nodep->prodsp()) { + UASSERT_OBJ(!nodep->randJoin(), nodep, + "rand join should have been handled in visit(RSRule)"); + nodep->replaceWith(itemsp->unlinkFrBackWithNext()); + // Will soon iterate itemsp as part of normal visit process + // These will become sequential in the generated task + } else { + nodep->unlinkFrBack(); + } + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + void visit(AstRSProdItem* nodep) override { + // "call" to generate using another production + UINFO(9, "visit " << nodep); + UASSERT_OBJ(m_prodFuncp, nodep, "RSProdItem not under production"); + AstRSProd* const foundp = nodep->prodp(); + UASSERT_OBJ(foundp, nodep, "Unlinked production reference"); + // Convert to task call + AstNode* const newp = newProdFuncRef(nodep, foundp, m_breakVarp); + // The production might have done a "break;", skip other steps if so + newp->addNext(new AstIf{nodep->fileline(), + new AstVarRef{nodep->fileline(), m_breakVarp, VAccess::READ}, + new AstJumpGo{nodep->fileline(), m_jumpBlockp}}); + + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + + void visit(AstRSBreak* nodep) override { + UASSERT_OBJ(m_jumpBlockp, nodep, "RSBreak not under production jump block"); + UASSERT_OBJ(m_breakVarp, nodep, "no break variable"); + AstNodeStmt* const newp = new AstAssign{ + nodep->fileline(), new AstVarRef{nodep->fileline(), m_breakVarp, VAccess::WRITE}, + new AstConst{nodep->fileline(), AstConst::BitTrue{}}}; + newp->addNext(new AstJumpGo{nodep->fileline(), m_jumpBlockp}); + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + void visit(AstRSReturn* nodep) override { + UASSERT_OBJ(m_jumpBlockp, nodep, "RSReturn not under production jump block"); + nodep->replaceWith(new AstJumpGo{nodep->fileline(), m_jumpBlockp}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + void visit(AstVarRef* nodep) override { + // Change references to variables inside randsequence to references to + // ref variables on the new task + const auto it = m_localizeRemaps.find(nodep->varp()); + if (it != m_localizeRemaps.end()) { + AstVar* const iovarp = it->second; + UINFO(9, "VARREF remap " << nodep << " -> " << iovarp); + nodep->varp(iovarp); + } + } + + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + explicit RandSequenceVisitor(AstNetlist* nodep) { iterate(nodep); } + ~RandSequenceVisitor() override = default; +}; + +//###################################################################### +// RandSequence method class functions + +void V3RandSequence::randSequenceNetlist(AstNetlist* nodep) { + UINFO(2, __FUNCTION__ << ":"); + { RandSequenceVisitor randSequenceVisitor{nodep}; } + V3Global::dumpCheckGlobalTree("randsequence", 0, dumpTreeEitherLevel() >= 3); +} diff --git a/src/V3RandSequence.h b/src/V3RandSequence.h new file mode 100644 index 000000000..6b1dd9818 --- /dev/null +++ b/src/V3RandSequence.h @@ -0,0 +1,30 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Generate randomization procedures +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-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 +// +//************************************************************************* + +#ifndef VERILATOR_V3RANDSEQUENCE_H_ +#define VERILATOR_V3RANDSEQUENCE_H_ + +#include "config_build.h" +#include "verilatedos.h" + +class AstNetlist; + +class V3RandSequence final { +public: + static void randSequenceNetlist(AstNetlist* nodep) VL_MT_DISABLED; +}; + +#endif // Guard diff --git a/src/V3Width.cpp b/src/V3Width.cpp index e06a9d914..a9d8b3bfe 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -5721,6 +5721,12 @@ class WidthVisitor final : public VNVisitor { } } + void visit(AstRSRule* nodep) override { + if (nodep->weightp()) iterateCheckUInt32(nodep, "weight", nodep->weightp(), BOTH); + userIterateAndNext(nodep->prodlistsp(), nullptr); + userIterateAndNext(nodep->weightStmtsp(), nullptr); + } + void visit(AstRelease* nodep) override { userIterateAndNext(nodep->lhsp(), WidthVP{SELF, BOTH}.p()); UASSERT_OBJ(nodep->lhsp()->dtypep(), nodep, "How can LValue be untyped?"); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 139c86fef..03c89f0ae 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -83,6 +83,7 @@ #include "V3PreShell.h" #include "V3Premit.h" #include "V3ProtectLib.h" +#include "V3RandSequence.h" #include "V3Randomize.h" #include "V3Reloop.h" #include "V3Sampled.h" @@ -228,6 +229,9 @@ static void process() { // Before we do dead code elimination and inlining, or we'll lose it. if (v3Global.opt.coverage()) V3Coverage::coverage(v3Global.rootp()); + // Resolve randsequence if they are used by the design + if (v3Global.useRandSequence()) V3RandSequence::randSequenceNetlist(v3Global.rootp()); + // Add randomize() class methods if they are used by the design if (v3Global.useRandomizeMethods()) V3Randomize::randomizeNetlist(v3Global.rootp()); diff --git a/src/verilog.y b/src/verilog.y index 5de8b1f5d..3fc6e4563 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -7087,9 +7087,11 @@ hierarchical_btf_identifier: // ==IEEE: hierarchical_btf_identifier randsequence_statement: // ==IEEE: randsequence_statement yRANDSEQUENCE '(' ')' rs_productionList yENDSEQUENCE - { $$ = new AstRandSequence{$1, "", $4}; } + { $$ = new AstRandSequence{$1, "", $4}; + v3Global.useRandSequence(true); } | yRANDSEQUENCE '(' idAny/*rs_production_identifier*/ ')' rs_productionList yENDSEQUENCE - { $$ = new AstRandSequence{$1, *$3, $5}; } + { $$ = new AstRandSequence{$1, *$3, $5}; + v3Global.useRandSequence(true); } ; rs_productionList: // IEEE: rs_production+ @@ -7190,15 +7192,15 @@ rs_prod: // ==IEEE: rs_prod | rs_code_block { $$ = $1; } // // IEEE: rs_if_else | yIF '(' expr ')' rs_production_item %prec prLOWER_THAN_ELSE - { $$ = new AstRSIf{$1, $3, $5, nullptr}; } + { $$ = new AstIf{$1, $3, $5, nullptr}; } | yIF '(' expr ')' rs_production_item yELSE rs_production_item - { $$ = new AstRSIf{$1, $3, $5, $7}; } + { $$ = new AstIf{$1, $3, $5, $7}; } // // IEEE: rs_repeat | yREPEAT '(' expr ')' rs_production_item - { $$ = new AstRSRepeat{$1, $3, $5}; } + { $$ = new AstRepeat{$1, $3, $5}; } // // IEEE: rs_case | yCASE '(' expr ')' rs_case_itemList yENDCASE - { $$ = new AstRSCase{$1, $3, $5}; } + { $$ = new AstCase{$1, VCaseType::CT_RANDSEQUENCE, $3, $5}; } ; rs_production_itemList: // IEEE: rs_production_item+ diff --git a/test_regress/t/t_dist_copyright.py b/test_regress/t/t_dist_copyright.py index 26518b4a7..5411e6d94 100755 --- a/test_regress/t/t_dist_copyright.py +++ b/test_regress/t/t_dist_copyright.py @@ -44,6 +44,7 @@ EXEMPT_FILES_LIST = """ test_regress/t/t_fuzz_eof_bad.v test_regress/t/t_incr_void.v test_regress/t/t_property_unsup.v + test_regress/t/t_randsequence_svtests.v test_regress/t/t_sequence_first_match_unsup.v test_regress/t/tsub/t_flag_f_tsub.v test_regress/t/tsub/t_flag_f_tsub_inc.v diff --git a/test_regress/t/t_randsequence.out b/test_regress/t/t_randsequence.out deleted file mode 100644 index 3c75aaf5b..000000000 --- a/test_regress/t/t_randsequence.out +++ /dev/null @@ -1,59 +0,0 @@ -%Error-UNSUPPORTED: t/t_randsequence.v:23:5: Unsupported: randsequence - 23 | randsequence(main) - | ^~~~~~~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_randsequence.v:40:5: Unsupported: randsequence - 40 | randsequence(main) - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:51:5: Unsupported: randsequence - 51 | randsequence() - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:58:5: Unsupported: randsequence - 58 | randsequence() - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:65:7: Unsupported: randsequence - 65 | randsequence(main) - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:79:7: Unsupported: randsequence - 79 | randsequence(main) - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:81:17: Unsupported: randsequence if - 81 | one_if: if (i % 10 == 0) count_1 else most; - | ^~ -%Error-UNSUPPORTED: t/t_randsequence.v:87:15: Unsupported: randsequence case - 87 | most: case (i % 10) - | ^~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:103:7: Unsupported: randsequence - 103 | randsequence(main) - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:105:17: Unsupported: randsequence if - 105 | one_if: if (i % 10 == 0) count_1 else most; - | ^~ -%Error-UNSUPPORTED: t/t_randsequence.v:111:15: Unsupported: randsequence case - 111 | most: case (i % 10) - | ^~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:126:5: Unsupported: randsequence - 126 | randsequence(main) - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:127:13: Unsupported: randsequence repeat - 127 | main: repeat(10) count_1; - | ^~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:135:7: Unsupported: randsequence - 135 | randsequence(main) - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:147:7: Unsupported: randsequence - 147 | randsequence(main) - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:152:7: Unsupported: randsequence - 152 | randsequence(main) - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:167:7: Unsupported: randsequence - 167 | randsequence(main) - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:185:7: Unsupported: randsequence - 185 | randsequence(main) - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence.v:202:7: Unsupported: randsequence - 202 | randsequence(main) - | ^~~~~~~~~~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_randsequence.py b/test_regress/t/t_randsequence.py index 5761f5984..d4f986441 100755 --- a/test_regress/t/t_randsequence.py +++ b/test_regress/t/t_randsequence.py @@ -11,11 +11,8 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=['--error-limit 999'], - fails=test.vlt_all, - expect_filename=test.golden_filename) +test.compile() -if not test.vlt_all: - test.execute() +test.execute() test.passes() diff --git a/test_regress/t/t_randsequence.v b/test_regress/t/t_randsequence.v index d112d08e6..3653f141e 100644 --- a/test_regress/t/t_randsequence.v +++ b/test_regress/t/t_randsequence.v @@ -6,9 +6,8 @@ // Version 2.0. // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -`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); -`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `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); +`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); $stop; end while(0); `define check_within_30_percent(gotv,val) `check_range((gotv), (val) * 70 / 100, (val) * 130 / 100) module t; @@ -19,21 +18,107 @@ module t; int counts[8]; function automatic int sfunc(); - int o = 2; + int fv; + fv = 2; randsequence(main) main : one; - one : { o = 1; }; + one : { fv = 1; }; endsequence - return o; + return fv; endfunction - task prep(); + function void prep(); for (int i = 0; i < COUNT; ++i) counts[i] = 0; - endtask + endfunction initial begin + int switch; + int x; + int wgt; + + x = 0; + randsequence() + main : { x = 10; }; + ignore : { x = 20; }; + endsequence + `checkd(x, 10); + + x = 0; + randsequence(first) + ignore : { x = 20; }; + first : { x = 10; }; + endsequence + `checkd(x, 10); + if (sfunc() != 1) $stop; + x = 0; + randsequence(main) + main : sub; + sub : { x += 10; }; + endsequence + `checkd(x, 10); + + x = 0; + switch = 1; + randsequence(main) + main : case (switch) + default : third; // Not listed first; need to move internally to last position + 0 : zero; + 1 : first; + endcase; + zero : { x = 0; }; + first : { x += 10; }; + third : { x += 3; }; + endsequence + `checkd(x, 10); + + x = 0; + randsequence(main) + main : first; // Check single rules + first : { x += 20; }; + endsequence + `checkd(x, 20); + + x = 0; + randsequence(main) + main : zero := 0; // Check single zero-weight + zero : { x += 20; }; + endsequence + `checkd(x, 0); + + x = 0; + wgt = 1; + for (int i=0; i<2; ++i) begin + randsequence() + main : first := wgt { wgt = 0; }; + first : { x += 1000; }; + endsequence + end + `checkd(wgt, 0); + `checkd(x, 1000); + + x = 0; + wgt = 1; + for (int i=0; i<2; ++i) begin + randsequence() + main : first := wgt { wgt = 0; } + | second := (1 - wgt) { }; + first : { x += 1000; }; + second : { x += 10; }; + endsequence + end + `checkd(wgt, 0); + `checkd(x, 1010); + + x = 0; + randsequence(main) + main : first second; + first : { x += 20; }; + second : { x += 2; }; + endsequence + `checkd(x, 22); + // simple prep(); seq = 0; @@ -53,12 +138,6 @@ module t; endsequence `checkd(seq, 2); - // empty block - prep(); - randsequence() - unnamed: { }; - endsequence - // weight prep(); for (int i = 0; i < COUNT; ++i) begin @@ -129,55 +208,6 @@ module t; endsequence `checkd(counts[1], 10); - // rand join - prep(); - for (int i = 0; i < COUNT; ++i) begin - randsequence(main) - main: rand join count_1 count_2; - count_1: { ++counts[1]; }; - count_2: { ++counts[2]; }; - endsequence - end - `check_within_30_percent(counts[1], COUNT * 1 / 1); - `check_within_30_percent(counts[2], COUNT * 1 / 1); - - // rand join weight (TODO weight not tested yet) - prep(); - for (int i = 0; i < COUNT; ++i) begin - randsequence(main) - main: rand join (1.0) count_1 count_2; - count_1: { ++counts[1]; }; - count_2: { ++counts[2]; }; - endsequence - randsequence(main) - main: rand join (0.0) count_3 count_4; - count_3: { ++counts[3]; }; - count_4: { ++counts[4]; }; - endsequence - end - `check_within_30_percent(counts[1], COUNT * 1 / 1); - `check_within_30_percent(counts[2], COUNT * 1 / 1); - `check_within_30_percent(counts[3], COUNT * 1 / 1); - `check_within_30_percent(counts[4], COUNT * 1 / 1); - - // break - prep(); - for (int i = 0; i < COUNT; ++i) begin - automatic bit fiftyfifty = i[0]; - randsequence(main) - main: count_1 check count_2; - check: count_3 { if (fiftyfifty) break; } count_4; - count_1: { ++counts[1]; }; - count_2: { ++counts[2]; }; - count_3: { ++counts[3]; }; - count_4: { ++counts[4]; }; - endsequence - end - `checkd(counts[1], COUNT * 1 / 1); - `checkd(counts[2], COUNT * 1 / 2); // break - `checkd(counts[3], COUNT * 1 / 1); - `checkd(counts[4], COUNT * 1 / 2); // break or return - // return prep(); for (int i = 0; i < COUNT; ++i) begin @@ -196,21 +226,6 @@ module t; `checkd(counts[3], COUNT * 1 / 1); `checkd(counts[4], COUNT * 1 / 2); // break or return - // functions - prep(); - for (int i = 0; i < COUNT; ++i) begin - randsequence(main) - main: f_1 f_2 f_3; - f_1 : func(10); - f_2 : func(20); - f_3 : fnoarg; - void func(int n) : { counts[1] += n; }; - void fnoarg : { ++counts[2]; }; - endsequence - end - `checkd(counts[1], COUNT * (10 + 20)); - `checkd(counts[2], COUNT * 1 / 1); // return - $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_randsequence_bad.out b/test_regress/t/t_randsequence_bad.out index 3c7b195df..e1aa688b0 100644 --- a/test_regress/t/t_randsequence_bad.out +++ b/test_regress/t/t_randsequence_bad.out @@ -1,11 +1,15 @@ -%Error-UNSUPPORTED: t/t_randsequence_bad.v:12:5: Unsupported: randsequence +%Error: t/t_randsequence_bad.v:23:7: Duplicate declaration of randsequence production: 'duplicated_bad' + 23 | duplicated_bad: { $display("dup2"); }; + | ^~~~~~~~~~~~~~ + t/t_randsequence_bad.v:22:7: ... Location of original declaration + 22 | duplicated_bad: { $display("dup1"); }; + | ^~~~~~~~~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_randsequence_bad.v:12:5: Production 'no_such_production' not found + : ... Suggested alternative: 'such_production' 12 | randsequence(no_such_production) | ^~~~~~~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_randsequence_bad.v:16:5: Unsupported: randsequence - 16 | randsequence(main) - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_randsequence_bad.v:21:5: Unsupported: randsequence - 21 | randsequence() - | ^~~~~~~~~~~~ +%Error: t/t_randsequence_bad.v:17:13: Production 'production_bad' not found + 17 | main: production_bad; + | ^~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_randsequence_func.out b/test_regress/t/t_randsequence_func.out new file mode 100644 index 000000000..844aedc05 --- /dev/null +++ b/test_regress/t/t_randsequence_func.out @@ -0,0 +1,9 @@ +%Error-UNSUPPORTED: t/t_randsequence_func.v:31:23: Unsupported: randsequence production function ports + 31 | void func(int n) : { counts[1] += n; }; + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: t/t_randsequence_func.v:31:23: Input/output/inout does not appear in port list: 'n' + 31 | void func(int n) : { counts[1] += n; }; + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_randsequence_func.py b/test_regress/t/t_randsequence_func.py new file mode 100755 index 000000000..966dc53da --- /dev/null +++ b/test_regress/t/t_randsequence_func.py @@ -0,0 +1,19 @@ +#!/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.compile(fails=test.vlt_all, expect_filename=test.golden_filename) + +if not test.vlt_all: + test.execute() + +test.passes() diff --git a/test_regress/t/t_randsequence_func.v b/test_regress/t/t_randsequence_func.v new file mode 100644 index 000000000..c7ff26174 --- /dev/null +++ b/test_regress/t/t_randsequence_func.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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 + +`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); + +module t; + + localparam int COUNT = 1000; + + int seq; + int counts[8]; + + task prep(); + for (int i = 0; i < COUNT; ++i) counts[i] = 0; + endtask + + initial begin + // functions + prep(); + for (int i = 0; i < COUNT; ++i) begin + randsequence(main) + main: f_1 f_2 f_3; + f_1 : func(10); + f_2 : func(20); + f_3 : fnoarg; + void func(int n) : { counts[1] += n; }; + void fnoarg : { ++counts[2]; }; + endsequence + end + `checkd(counts[1], COUNT * (10 + 20)); + `checkd(counts[2], COUNT * 1 / 1); // return + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_randsequence_rule_code_bad.py b/test_regress/t/t_randsequence_randjoin.py similarity index 83% rename from test_regress/t/t_randsequence_rule_code_bad.py rename to test_regress/t/t_randsequence_randjoin.py index 31228c9a7..d4f986441 100755 --- a/test_regress/t/t_randsequence_rule_code_bad.py +++ b/test_regress/t/t_randsequence_randjoin.py @@ -9,8 +9,10 @@ import vltest_bootstrap -test.scenarios('linter') +test.scenarios('simulator') -test.lint(fails=True, expect_filename=test.golden_filename) +test.compile() + +test.execute() test.passes() diff --git a/test_regress/t/t_randsequence_randjoin.v b/test_regress/t/t_randsequence_randjoin.v new file mode 100644 index 000000000..857f23622 --- /dev/null +++ b/test_regress/t/t_randsequence_randjoin.v @@ -0,0 +1,71 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Copyright 2023 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 + +`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); +`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); $stop; end while(0); +`define check_within_30_percent(gotv,val) `check_range((gotv), (val) * 70 / 100, (val) * 130 / 100) + +module t; + + localparam int COUNT = 1000; + + int x; + int seq; + int counts[8]; + + task prep(); + for (int i = 0; i < COUNT; ++i) counts[i] = 0; + endtask + + initial begin + prep(); + + // rand join + x = 0; + randsequence(main) + main : rand join first second; + first : { x = x + 20; }; + second : { x = x - 9; } { x = x - 1; }; + endsequence + `checkd(x, 10); + + prep(); + for (int i = 0; i < COUNT; ++i) begin + randsequence(main) + main: rand join count_1 count_2; + count_1: { ++counts[1]; }; + count_2: { ++counts[2]; }; + endsequence + end + `check_within_30_percent(counts[1], COUNT * 1 / 1); + `check_within_30_percent(counts[2], COUNT * 1 / 1); + + // rand join weight (TODO weight not tested yet) + prep(); + for (int i = 0; i < COUNT; ++i) begin + randsequence(main) + main: rand join (1.0) count_1 count_2; + count_1: { ++counts[1]; }; + count_2: { ++counts[2]; }; + endsequence + randsequence(main) + main: rand join (0.0) count_3 count_4; + count_3: { ++counts[3]; }; + count_4: { ++counts[4]; }; + endsequence + end + `check_within_30_percent(counts[1], COUNT * 1 / 1); + `check_within_30_percent(counts[2], COUNT * 1 / 1); + `check_within_30_percent(counts[3], COUNT * 1 / 1); + `check_within_30_percent(counts[4], COUNT * 1 / 1); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_randsequence_recurse.py b/test_regress/t/t_randsequence_recurse.py index 966dc53da..d17523dd7 100755 --- a/test_regress/t/t_randsequence_recurse.py +++ b/test_regress/t/t_randsequence_recurse.py @@ -11,9 +11,9 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(fails=test.vlt_all, expect_filename=test.golden_filename) +# -fno-inline-funcs due to Issue #4698 ref arguments +test.compile(verilator_flags2=['-fno-inline-funcs']) -if not test.vlt_all: - test.execute() +test.execute() test.passes() diff --git a/test_regress/t/t_randsequence_recurse.v b/test_regress/t/t_randsequence_recurse.v index c18f20b99..490be59c0 100644 --- a/test_regress/t/t_randsequence_recurse.v +++ b/test_regress/t/t_randsequence_recurse.v @@ -19,12 +19,12 @@ module t(/*AUTOARG*/); i = 0; randsequence(main) - main : recurse recurse; - recurse: { i++; if ((i % 4) == 0) break; } add recurse; + main : add /*1*/ add /*1*/ recurse /* 2 */ recurse /* 0 */; + recurse: { i++; if (i >= 3) break; } add recurse; add: { o++; } ; endsequence - `checkd(o, 3); + `checkd(o, 4); $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_randsequence_rs_bad.out b/test_regress/t/t_randsequence_rs_bad.out index 0bf04d9ff..f26bd5bda 100644 --- a/test_regress/t/t_randsequence_rs_bad.out +++ b/test_regress/t/t_randsequence_rs_bad.out @@ -1,8 +1,6 @@ -%Error-UNSUPPORTED: t/t_randsequence_rs_bad.v:12:5: Unsupported: randsequence - 12 | randsequence() - | ^~~~~~~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_randsequence_rs_bad.v:14:10: Unsupported: randsequence +%Error-UNSUPPORTED: t/t_randsequence_rs_bad.v:14:10: Unsupported: randsequence under randsequence + : ... note: In instance 't' 14 | randsequence() | ^~~~~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_randsequence_rule_code_bad.out b/test_regress/t/t_randsequence_rule_code_bad.out deleted file mode 100644 index 1b33d9dee..000000000 --- a/test_regress/t/t_randsequence_rule_code_bad.out +++ /dev/null @@ -1,5 +0,0 @@ -%Error-UNSUPPORTED: t/t_randsequence_rule_code_bad.v:12:5: Unsupported: randsequence - 12 | randsequence() - | ^~~~~~~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to diff --git a/test_regress/t/t_randsequence_rule_code_bad.v b/test_regress/t/t_randsequence_rule_code_bad.v deleted file mode 100644 index 6d3dbc0bd..000000000 --- a/test_regress/t/t_randsequence_rule_code_bad.v +++ /dev/null @@ -1,21 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// 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 - -module t; - - initial begin - randsequence() - main : first := 1 { $stop; } | second := 0; - first : { $display("first"); }; - second : { $display("second"); }; - endsequence - $write("*-* All Finished *-*\n"); - $finish; - end - -endmodule diff --git a/test_regress/t/t_randsequence_svtests.py b/test_regress/t/t_randsequence_svtests.py new file mode 100755 index 000000000..d4f986441 --- /dev/null +++ b/test_regress/t/t_randsequence_svtests.py @@ -0,0 +1,18 @@ +#!/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.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_randsequence_svtests.v b/test_regress/t/t_randsequence_svtests.v new file mode 100644 index 000000000..0955c8b5f --- /dev/null +++ b/test_regress/t/t_randsequence_svtests.v @@ -0,0 +1,166 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Based on code Copyright (C) 2019-2021 The SymbiFlow Authors. +// SPDX-License-Identifier: ISC + +`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); + +module t; + + initial begin + int x; + bit flag = 1; + int switch = 1; + int break_on = 1; + static int return_on = 1; + + x = 0; + randsequence(main) + main : first second done; + first : { x = x + 1; }; + second : { x = x + 2; }; + done : { x = x + 3; }; + endsequence + `checkd(x, 6); + + x = 0; + randsequence(main) + main : or_first | or_second; + or_first : { x += -2; }; + or_second : { x += 2; }; + endsequence + if (x != 2 && x != -2) $stop; + + x = 0; + randsequence(main) + main : or_first := 1 | or_second := 0; + or_first : { x += 2; }; + or_second : { x += -2; }; + endsequence + `checkd(x, 2); + + x = 0; + flag = 1; + randsequence(main) + main : first; + first : { if (flag) x = 10; else x = 5; }; + endsequence + `checkd(x, 10); + + x = 0; + flag = 0; + randsequence(main) + main : first; + first : { if (flag) x = 10; else x = 5; }; + endsequence + `checkd(x, 5); + + x = 0; + flag = 1; + randsequence(main) + main : first; + first : if (flag) second else third; + second : { x = 10; }; + third : { x = 5; }; + endsequence + `checkd(x, 10); + + x = 0; + switch = 1; + randsequence(main) + main : case (switch) + 0 : zero; + 1 : first; + 2 : second; + default : third; + endcase; + zero : { x = 0; }; + first : { x = 10; }; + second : { x = 2; }; + third : { x = 3; }; + endsequence + `checkd(x, 10); + + x = 0; + randsequence(main) + main : first; + first : repeat(10) second; + second : { x = x + 1; }; + endsequence + `checkd(x, 10); + + x = 0; + randsequence(main) + main : rand join first second; + first : { x = x + 20; }; + second : { x = x - 10; }; + endsequence + `checkd(x, 10); + + x = 0; + randsequence(main) + main : rand join (0.5) first second; + first : { x = x + 20; }; + second : { x = x - 10; }; + endsequence + `checkd(x, 10); + + x = 0; + break_on = 1; + randsequence(main) + main : first second third; + first : { x = x + 10; }; + second : { if (break_on == 1) break; } fourth; + third : { x = x + 10; }; + fourth : { x = x + 15; }; + endsequence + `checkd(x, 10); + + x = 0; + break_on = 0; + randsequence(main) + main : first second third; + first : { x = x + 10; }; + second : { if (break_on == 1) break; } fourth; + third : { x = x + 10; }; + fourth : { x = x + 15; }; + endsequence + `checkd(x, 35); + + x = 0; + return_on = 1; + randsequence(main) + main : first second third; + first : { x = x + 20; }; + second : { if (return_on == 1) return; x = x + 10; }; + third : { x = x + 5;}; + endsequence + `checkd(x, 25); + + x = 0; + return_on = 0; + randsequence(main) + main : first second third; + first : { x = x + 20; }; + second : { if (return_on == 1) return; x = x + 10; }; + third : { x = x + 5;}; + endsequence + `checkd(x, 35); + +`ifndef VERILATOR // Unsupported randsequence functions + x = 0; + randsequence(main) + main : first second third; + first : add(10); + second : add(5); + third : add(2); + void add(int y) : { x = x + y; }; + void add(int y) : sub_a sub_b; // This is presumably legal, try it + endsequence + `checkd(x, 17); +`endif + + $finish; + end + +endmodule