Support randsequence (#6131)

This commit is contained in:
Wilson Snyder 2025-11-30 09:04:42 -05:00
parent 35615c268b
commit b9b6eb61d9
32 changed files with 1271 additions and 280 deletions

View File

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

View File

@ -309,6 +309,7 @@ RAW_OBJS_PCH_ASTNOMT = \
V3Param.o \
V3Premit.o \
V3ProtectLib.o \
V3RandSequence.o \
V3Randomize.o \
V3Reloop.o \
V3Sampled.o \

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

633
src/V3RandSequence.cpp Normal file
View File

@ -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<AstRSProd*, AstNodeFTask*> 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<AstVar*> m_localizes; // Variables to become function inouts
std::map<std::string, AstVar*> m_localizeNames; // Ordered names of function inouts
std::unordered_map<const AstVar*, AstVar*> m_localizeRemaps; // New ports for old vars
// METHODS
void findLocalizes(AstRandSequence* nodep) {
std::set<AstVar*> 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<AstRSProdItem*> lists;
std::unordered_map<AstRSProdItem*, std::deque<AstNodeStmt*>> 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<AstVar*> 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<uint32_t>(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<double>(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<uint32_t>(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<AstRSProd*> 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);
}

30
src/V3RandSequence.h Normal file
View File

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

View File

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

View File

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

View File

@ -7087,9 +7087,11 @@ hierarchical_btf_identifier<nodep>: // ==IEEE: hierarchical_btf_identifier
randsequence_statement<nodeStmtp>: // ==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<rSProdp>: // IEEE: rs_production+
@ -7190,15 +7192,15 @@ rs_prod<nodep>: // ==IEEE: rs_prod
| rs_code_block { $$ = $1; }
// // IEEE: rs_if_else
| yIF '(' expr ')' rs_production_item %prec prLOWER_THAN_ELSE
{ $$ = new AstRSIf{$<fl>1, $3, $5, nullptr}; }
{ $$ = new AstIf{$<fl>1, $3, $5, nullptr}; }
| yIF '(' expr ')' rs_production_item yELSE rs_production_item
{ $$ = new AstRSIf{$<fl>1, $3, $5, $7}; }
{ $$ = new AstIf{$<fl>1, $3, $5, $7}; }
// // IEEE: rs_repeat
| yREPEAT '(' expr ')' rs_production_item
{ $$ = new AstRSRepeat{$<fl>1, $3, $5}; }
{ $$ = new AstRepeat{$<fl>1, $3, $5}; }
// // IEEE: rs_case
| yCASE '(' expr ')' rs_case_itemList yENDCASE
{ $$ = new AstRSCase{$<fl>1, $3, $5}; }
{ $$ = new AstCase{$<fl>1, VCaseType::CT_RANDSEQUENCE, $3, $5}; }
;
rs_production_itemList<nodep>: // IEEE: rs_production_item+

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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