Support inline constraints for class randomization methods (#5234)

Signed-off-by: Krzysztof Boronski <kboronski@antmicro.com>
This commit is contained in:
Krzysztof Boroński 2024-07-12 16:18:18 +02:00 committed by GitHub
parent 6882f8c55e
commit 3cf9606ea9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 470 additions and 154 deletions

View File

@ -1839,6 +1839,7 @@ class LinkDotScopeVisitor final : public VNVisitor {
symp->fallbackp(m_modSymp);
// No recursion, we don't want to pick up variables
}
void visit(AstConstraintForeach* nodep) override { iterateChildren(nodep); }
void visit(AstWith* nodep) override {
VSymEnt* const symp = m_statep->insertBlock(m_modSymp, nodep->name(), nodep, nullptr);
symp->fallbackp(m_modSymp);

View File

@ -1,6 +1,6 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Expression width calculations
// DESCRIPTION: Verilator: Generate randomization procedures
//
// Code available from: https://verilator.org
//
@ -26,9 +26,18 @@
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "verilatedos.h"
#include "V3Randomize.h"
#include "V3Ast.h"
#include "V3Error.h"
#include "V3FileLine.h"
#include "V3Global.h"
#include "V3MemberMap.h"
#include "V3UniqueNames.h"
#include <utility>
VL_DEFINE_DEBUG_FUNCTIONS;
@ -48,7 +57,10 @@ class RandomizeMarkVisitor final : public VNVisitorConst {
BaseToDerivedMap m_baseToDerivedMap; // Mapping from base classes to classes that extend them
AstClass* m_classp = nullptr; // Current class
const AstNode* m_constraintExprp = nullptr; // Current constraint expression
AstNode* m_constraintExprp = nullptr; // Current constraint expression
AstNodeModule* m_modp; // Current module
std::map<AstVar*, AstNodeModule*> m_moduleMap; // Variable -> module under which it is
std::set<AstNodeVarRef*> m_staticRefs; // References to static variables under `with` clauses
// METHODS
void markMembers(const AstClass* nodep) {
@ -87,6 +99,12 @@ class RandomizeMarkVisitor final : public VNVisitorConst {
if (p.first->user1()) markDerived(p.first);
}
}
void setPackageRefs() {
for (AstNodeVarRef* staticRefp : m_staticRefs) {
UINFO(9, "Updated classOrPackage ref for " << staticRefp->name() << endl);
staticRefp->classOrPackagep(m_moduleMap[staticRefp->varp()]);
}
}
// VISITORS
void visit(AstClass* nodep) override {
@ -130,19 +148,32 @@ class RandomizeMarkVisitor final : public VNVisitorConst {
}
void visit(AstNodeVarRef* nodep) override {
if (!m_constraintExprp) return;
if (nodep->varp()->lifetime().isStatic()) m_staticRefs.emplace(nodep);
if (!nodep->varp()->isRand()) return;
for (AstNode* backp = nodep; backp != m_constraintExprp && !backp->user1();
backp = backp->backp())
backp->user1(true);
}
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
m_modp = nodep;
iterateChildrenConst(nodep);
}
void visit(AstVar* nodep) override {
m_moduleMap.emplace(nodep, m_modp);
iterateChildrenConst(nodep);
}
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
public:
// CONSTRUCTORS
explicit RandomizeMarkVisitor(AstNetlist* nodep) {
explicit RandomizeMarkVisitor(AstNode* nodep) {
iterateConst(nodep);
markAllDerived();
setPackageRefs();
}
~RandomizeMarkVisitor() override = default;
};
@ -152,12 +183,13 @@ public:
class ConstraintExprVisitor final : public VNVisitor {
// NODE STATE
// AstVar::user4() -> bool. Handled in constraints
// AstVar::user4() -> bool. Handled in constraints
// AstNodeExpr::user1() -> bool. Depending on a randomized variable
// VNUser4InUse m_inuser4; (Allocated for use in RandomizeVisitor)
// VNUser4InUse m_inuser4; (Allocated for use in RandomizeVisitor)
AstNodeFTask* const m_taskp; // method to add write_var calls to
AstNodeFTask* const m_taskp; // Method to add write_var calls to
AstVar* const m_genp; // VlRandomizer variable of the class
bool m_markDeclaredConstrs; // Mark constraints as aready setup with `write_var`.
bool m_wantSingle = false; // Whether to merge constraint expressions with LOGAND
bool editFormat(AstNodeExpr* nodep) {
@ -257,12 +289,13 @@ class ConstraintExprVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep), nodep);
if (!varp->user4()) {
varp->user4(true);
varp->user4(m_markDeclaredConstrs);
AstCMethodHard* const methodp = new AstCMethodHard{
varp->fileline(), new AstVarRef{varp->fileline(), m_genp, VAccess::READWRITE},
"write_var"};
methodp->dtypeSetVoid();
methodp->addPinsp(new AstVarRef{varp->fileline(), varp, VAccess::WRITE});
AstVarRef* varRefp = new AstVarRef{varp->fileline(), varp, VAccess::WRITE};
methodp->addPinsp(varRefp);
methodp->addPinsp(new AstConst{varp->dtypep()->fileline(), AstConst::Unsized64{},
(size_t)varp->width()});
AstNodeExpr* const varnamep
@ -377,13 +410,102 @@ class ConstraintExprVisitor final : public VNVisitor {
public:
// CONSTRUCTORS
explicit ConstraintExprVisitor(AstNode* nodep, AstNodeFTask* taskp, AstVar* genp)
explicit ConstraintExprVisitor(AstNode* nodep, AstNodeFTask* taskp, AstVar* genp,
bool markDeclaredConstrs)
: m_taskp(taskp)
, m_genp(genp) {
, m_genp(genp)
, m_markDeclaredConstrs(markDeclaredConstrs) {
iterateAndNextNull(nodep);
}
};
template <typename TreeNodeType>
class CaptureFrame final {
TreeNodeType* m_treep; // Original tree
AstArg* m_argsp; // Original references turned into arguments
AstNodeModule* m_myModulep; // Module for which static references will stay uncaptured.
// Map original var nodes to their clones
std::map<const AstVar*, AstVar*> m_varCloneMap;
bool captureVariable(FileLine* const fileline, AstNodeVarRef* varrefp, AstVar*& varp) {
auto it = m_varCloneMap.find(varrefp->varp());
if (it == m_varCloneMap.end()) {
AstVar* const newVarp = varrefp->varp()->cloneTree(false);
newVarp->fileline(fileline);
newVarp->varType(VVarType::BLOCKTEMP);
newVarp->funcLocal(true);
newVarp->direction(VDirection::INPUT);
newVarp->lifetime(VLifetime::AUTOMATIC);
m_varCloneMap.emplace(varrefp->varp(), newVarp);
varp = newVarp;
return true;
}
varp = it->second;
return false;
}
template <typename Action>
static void foreachSuperClass(AstClass* classp, Action action) {
for (AstClassExtends* extendsp = classp->extendsp(); extendsp;
extendsp = VN_AS(extendsp->nextp(), ClassExtends)) {
AstClass* const superclassp = VN_AS(extendsp->childDTypep(), ClassRefDType)->classp();
action(superclassp);
foreachSuperClass(superclassp, action);
}
}
public:
explicit CaptureFrame(TreeNodeType* const nodep, AstNodeModule* const myModulep,
const bool clone = true, VNRelinker* const linkerp = nullptr)
: m_treep(clone ? nodep->cloneTree(true) : nodep->unlinkFrBackWithNext(linkerp))
, m_argsp(nullptr)
, m_myModulep(myModulep) {
std::set<AstNodeModule*> visibleModules = {myModulep};
if (AstClass* classp = VN_CAST(m_myModulep, Class)) {
foreachSuperClass(classp,
[&](AstClass* superclassp) { visibleModules.emplace(superclassp); });
}
m_treep->foreachAndNext([&](AstNodeVarRef* varrefp) {
UASSERT_OBJ(varrefp->varp(), varrefp, "Variable unlinked");
if (!varrefp->varp()->isFuncLocal() && !VN_IS(varrefp, VarXRef)
&& (visibleModules.count(varrefp->classOrPackagep())))
return;
AstVar* newVarp;
bool newCapture = captureVariable(varrefp->fileline(), varrefp, newVarp /*ref*/);
AstNodeVarRef* const newVarRefp = newCapture ? varrefp->cloneTree(false) : nullptr;
if (!varrefp->varp()->lifetime().isStatic() || varrefp->classOrPackagep()) {
// Keeping classOrPackagep will cause a broken link after inlining
varrefp->classOrPackagep(nullptr); // AstScope will figure this out
}
varrefp->varp(newVarp);
if (!newCapture) return;
if (VN_IS(varrefp, VarXRef)) {
AstVarRef* const notXVarRefp
= new AstVarRef{varrefp->fileline(), newVarp, VAccess::READ};
notXVarRefp->classOrPackagep(varrefp->classOrPackagep());
varrefp->replaceWith(notXVarRefp);
varrefp->deleteTree();
varrefp = notXVarRefp;
}
m_argsp = AstNode::addNext(m_argsp, new AstArg{varrefp->fileline(), "", newVarRefp});
});
}
// PUBLIC METHODS
TreeNodeType* getTree() const { return m_treep; }
AstVar* getVar(AstVar* const varp) const {
const auto it = m_varCloneMap.find(varp);
if (it == m_varCloneMap.end()) { return nullptr; }
return it->second;
}
AstArg* getArgs() const { return m_argsp; }
};
//######################################################################
// Visitor that defines a randomize method where needed
@ -392,7 +514,8 @@ class RandomizeVisitor final : public VNVisitor {
// Cleared on Netlist
// AstClass::user1() -> bool. Set true to indicate needs randomize processing
// AstEnumDType::user2() -> AstVar*. Pointer to table with enum values
// AstClass::user3() -> AstFunc*. Pointer to randomize() method of a class
// AstConstraint::user2p() -> AstTask*. Pointer to constraint setup procedure
// AstClass::user3p() -> AstFunc*. Pointer to randomize() method of a class
// AstVar::user4() -> bool. Handled in constraints
// AstClass::user4() -> AstVar*. Constrained randomizer variable
// VNUser1InUse m_inuser1; (Allocated for use in RandomizeMarkVisitor)
@ -401,15 +524,17 @@ class RandomizeVisitor final : public VNVisitor {
const VNUser4InUse m_inuser4;
// STATE
V3UniqueNames m_inlineUniqueNames; // For generating unique function names
VMemberMap m_memberMap; // Member names cached for fast lookup
AstNodeModule* m_modp = nullptr; // Current module
const AstNodeFTask* m_ftaskp = nullptr; // Current function/task
size_t m_enumValueTabCount = 0; // Number of tables with enum values created
int m_randCaseNum = 0; // Randcase number within a module for var naming
std::map<std::string, AstCDType*> m_randcDtypes; // RandC data type deduplication
bool m_inline = false; // Constraints are inline
// METHODS
AstVar* enumValueTabp(AstEnumDType* nodep) {
AstVar* enumValueTabp(AstEnumDType* const nodep) {
if (nodep->user2p()) return VN_AS(nodep->user2p(), Var);
UINFO(9, "Construct Venumvaltab " << nodep << endl);
AstNodeArrayDType* const vardtypep = new AstUnpackArrayDType{
@ -425,6 +550,7 @@ class RandomizeVisitor final : public VNVisitor {
varp->valuep(initp);
// Add to root, as don't know module we are in, and aids later structure sharing
v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(varp);
UASSERT_OBJ(nodep->itemsp(), nodep, "Enum without items");
for (AstEnumItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), EnumItem)) {
@ -436,7 +562,7 @@ class RandomizeVisitor final : public VNVisitor {
return varp;
}
AstCDType* findVlRandCDType(FileLine* fl, uint64_t items) {
AstCDType* findVlRandCDType(FileLine* const fl, uint64_t items) {
// For 8 items we need to have a 9 item LFSR so items is max count
// width(items) = log2(items) + 1
const std::string type = AstCDType::typeToHold(V3Number::log2bQuad(items) + 1);
@ -451,7 +577,7 @@ class RandomizeVisitor final : public VNVisitor {
return pair.first->second;
}
AstVar* newRandcVarsp(AstVar* varp) {
AstVar* newRandcVarsp(AstVar* const varp) {
// If a randc, make a VlRandC object to hold the state
if (!varp->isRandC()) return nullptr;
uint64_t items = 0;
@ -529,18 +655,19 @@ class RandomizeVisitor final : public VNVisitor {
valp};
}
}
AstNodeExpr* newRandValue(FileLine* fl, AstVar* randcVarp, AstNodeDType* dtypep) {
AstNodeExpr* newRandValue(FileLine* const fl, AstVar* const randcVarp,
AstNodeDType* const dtypep) {
if (randcVarp) {
AstNode* argsp = new AstVarRef{fl, randcVarp, VAccess::READWRITE};
argsp->addNext(new AstText{fl, ".randomize(__Vm_rng)"});
AstCExpr* newp = new AstCExpr{fl, argsp};
AstVarRef* const argsp = new AstVarRef{fl, randcVarp, VAccess::READWRITE};
argsp->AstNode::addNext(new AstText{fl, ".randomize(__Vm_rng)"});
AstCExpr* const newp = new AstCExpr{fl, argsp};
newp->dtypep(dtypep);
return newp;
} else {
return new AstRandRNG{fl, dtypep};
}
}
void addPrePostCall(AstClass* classp, AstFunc* funcp, const string& name) {
void addPrePostCall(AstClass* const classp, AstFunc* const funcp, const string& name) {
if (AstTask* userFuncp = VN_CAST(m_memberMap.findMember(classp, name), Task)) {
AstTaskRef* const callp
= new AstTaskRef{userFuncp->fileline(), userFuncp->name(), nullptr};
@ -548,12 +675,18 @@ class RandomizeVisitor final : public VNVisitor {
funcp->addStmtsp(callp->makeStmt());
}
}
AstTask* newSetupConstraintTask(AstClass* nodep, const std::string& name) {
AstTask* newSetupConstraintTask(AstClass* const nodep, const std::string& name) {
AstTask* const taskp = new AstTask{nodep->fileline(), name + "_setup_constraint", nullptr};
taskp->classMethod(true);
nodep->addMembersp(taskp);
return taskp;
}
AstNodeStmt* implementConstraintsClear(FileLine* const fileline, AstVar* const genp) {
AstCMethodHard* const clearp = new AstCMethodHard{
fileline, new AstVarRef{fileline, genp, VAccess::READWRITE}, "clear"};
clearp->dtypeSetVoid();
return clearp->makeStmt();
}
// VISITORS
void visit(AstNodeModule* nodep) override {
@ -573,6 +706,7 @@ class RandomizeVisitor final : public VNVisitor {
VL_RESTORER(m_randCaseNum);
m_modp = nodep;
m_randCaseNum = 0;
iterateChildren(nodep);
if (!nodep->user1()) return; // Doesn't need randomize, or already processed
UINFO(9, "Define randomize() for " << nodep << endl);
@ -596,21 +730,22 @@ class RandomizeVisitor final : public VNVisitor {
}
}
if (m_modp->user4p()) {
AstNode* const argsp = new AstVarRef{nodep->fileline(), VN_AS(m_modp->user4p(), Var),
VAccess::READWRITE};
argsp->addNext(new AstText{fl, ".next(__Vm_rng)"});
AstNodeExpr* const solverCallp = new AstCExpr{fl, argsp};
AstVarRef* const genRefp = new AstVarRef{
nodep->fileline(), VN_AS(m_modp->user4p(), Var), VAccess::READWRITE};
genRefp->AstNode::addNext(new AstText{fl, ".next(__Vm_rng)"});
AstNodeExpr* const solverCallp = new AstCExpr{fl, genRefp};
solverCallp->dtypeSetBit();
beginValp = beginValp ? new AstLogAnd{fl, beginValp, solverCallp} : solverCallp;
}
if (!beginValp) beginValp = new AstConst{fl, AstConst::WidthedValue{}, 32, 1};
funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, fvarp, VAccess::WRITE}, beginValp});
AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE};
funcp->addStmtsp(new AstAssign{fl, fvarRefp, beginValp});
for (AstNode* memberp = nodep->stmtsp(); memberp; memberp = memberp->nextp()) {
AstVar* const memberVarp = VN_CAST(memberp, Var);
if (!memberVarp || !memberVarp->isRand() || memberVarp->user4()) continue;
const AstNodeDType* const dtypep = memberp->dtypep()->skipRefp();
const AstNodeDType* const dtypep = memberVarp->dtypep()->skipRefp();
if (VN_IS(dtypep, BasicDType) || VN_IS(dtypep, StructDType)) {
AstVar* const randcVarp = newRandcVarsp(memberVarp);
AstVarRef* const refp = new AstVarRef{fl, memberVarp, VAccess::WRITE};
@ -618,38 +753,40 @@ class RandomizeVisitor final : public VNVisitor {
funcp->addStmtsp(stmtp);
} else if (const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType)) {
if (classRefp->classp() == nodep) {
memberp->v3warn(
memberVarp->v3warn(
E_UNSUPPORTED,
"Unsupported: random member variable with type of a current class");
continue;
}
AstVarRef* const refp = new AstVarRef{fl, memberVarp, VAccess::WRITE};
AstFunc* const memberFuncp = V3Randomize::newRandomizeFunc(classRefp->classp());
AstMethodCall* const callp = new AstMethodCall{fl, refp, "randomize", nullptr};
AstMethodCall* const callp = new AstMethodCall{
fl, new AstVarRef{fl, memberVarp, VAccess::WRITE}, "randomize", nullptr};
callp->taskp(memberFuncp);
callp->dtypeFrom(memberFuncp);
AstAssign* const assignp = new AstAssign{
fl, new AstVarRef{fl, fvarp, VAccess::WRITE},
new AstAnd{fl, new AstVarRef{fl, fvarp, VAccess::READ}, callp}};
AstVarRef* fvarRefReadp = fvarRefp->cloneTree(false);
fvarRefReadp->access(VAccess::READ);
AstIf* const assignIfNotNullp
= new AstIf{fl,
new AstNeq{fl, new AstVarRef{fl, memberVarp, VAccess::READ},
new AstConst{fl, AstConst::Null{}}},
assignp};
new AstAssign{fl, fvarRefp->cloneTree(false),
new AstAnd{fl, fvarRefReadp, callp}}};
funcp->addStmtsp(assignIfNotNullp);
} else {
memberp->v3warn(E_UNSUPPORTED, "Unsupported: random member variable with type "
<< memberp->dtypep()->prettyDTypeNameQ());
memberVarp->v3warn(E_UNSUPPORTED, "Unsupported: random member variable with type "
<< memberVarp->dtypep()->prettyDTypeNameQ());
}
}
addPrePostCall(nodep, funcp, "post_randomize");
nodep->user1(false);
}
void visit(AstConstraint* nodep) override {
if (nodep->user2p()) return; // Already visited
AstNodeFTask* const newp = VN_AS(m_memberMap.findMember(m_modp, "new"), NodeFTask);
UASSERT_OBJ(newp, m_modp, "No new() in class");
AstFunc* const randomizep = V3Randomize::newRandomizeFunc(VN_AS(m_modp, Class));
AstTask* const taskp = newSetupConstraintTask(VN_AS(m_modp, Class), nodep->name());
nodep->user2p(taskp);
AstTaskRef* const setupTaskRefp
= new AstTaskRef{nodep->fileline(), taskp->name(), nullptr};
setupTaskRefp->taskp(taskp);
@ -657,25 +794,20 @@ class RandomizeVisitor final : public VNVisitor {
AstVar* genp = VN_AS(m_modp->user4p(), Var);
if (!genp) {
genp = VN_AS(m_memberMap.findMember(m_modp, "constraint"), Var);
if (!genp)
if (!genp) {
genp = new AstVar{nodep->fileline(), VVarType::MEMBER, "constraint",
m_modp->findBasicDType(VBasicDTypeKwd::RANDOM_GENERATOR)};
}
VN_AS(m_modp, Class)->addMembersp(genp);
m_modp->user4p(genp);
}
if (!randomizep->stmtsp()) {
AstCMethodHard* const clearp = new AstCMethodHard{
randomizep->fileline(),
new AstVarRef{randomizep->fileline(), genp, VAccess::READWRITE}, "clear"};
clearp->dtypeSetVoid();
randomizep->addStmtsp(clearp->makeStmt());
randomizep->addStmtsp(implementConstraintsClear(randomizep->fileline(), genp));
}
randomizep->addStmtsp(setupTaskRefp->makeStmt());
{ ConstraintExprVisitor{nodep->itemsp(), newp, genp}; }
{ ConstraintExprVisitor{nodep->itemsp(), (m_inline ? taskp : newp), genp, !m_inline}; }
if (nodep->itemsp()) taskp->addStmtsp(nodep->itemsp()->unlinkFrBackWithNext());
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstRandCase* nodep) override {
// RANDCASE
@ -697,7 +829,7 @@ class RandomizeVisitor final : public VNVisitor {
randVarp->noSubst(true);
if (m_ftaskp) randVarp->funcLocal(true);
AstNodeExpr* sump = new AstConst{fl, AstConst::WidthedValue{}, 64, 0};
AstNodeIf* firstIfsp
AstNodeIf* const firstIfsp
= new AstIf{fl, new AstConst{fl, AstConst::BitFalse{}}, nullptr, nullptr};
AstNodeIf* ifsp = firstIfsp;
@ -708,11 +840,10 @@ class RandomizeVisitor final : public VNVisitor {
= new AstAdd{condp->fileline(), sump, new AstExtend{itemp->fileline(), condp, 64}};
AstNode* const stmtsp
= itemp->stmtsp() ? itemp->stmtsp()->unlinkFrBackWithNext() : nullptr;
AstVarRef* const randVarRefp = new AstVarRef{fl, randVarp, VAccess::WRITE};
AstNodeIf* const newifp
= new AstIf{itemp->fileline(),
new AstLte{condp->fileline(),
new AstVarRef{condp->fileline(), randVarp, VAccess::READ},
sump->cloneTreePure(true)},
new AstLte{condp->fileline(), randVarRefp, sump->cloneTreePure(true)},
stmtsp, nullptr};
ifsp->addElsesp(newifp);
ifsp = newifp;
@ -724,10 +855,11 @@ class RandomizeVisitor final : public VNVisitor {
dispp->fmtp()->timeunit(m_modp->timeunit());
ifsp->addElsesp(dispp);
AstNode* newp = randVarp;
AstNode* const newp = randVarp;
AstNodeExpr* randp = new AstRand{fl, nullptr, false};
randp->dtypeSetUInt64();
newp->addNext(new AstAssign{fl, new AstVarRef{fl, randVarp, VAccess::WRITE},
AstVarRef* const randVarRefp = new AstVarRef{fl, randVarp, VAccess::WRITE};
newp->addNext(new AstAssign{fl, randVarRefp,
new AstAdd{fl, new AstConst{fl, AstConst::Unsized64{}, 1},
new AstModDiv{fl, randp, sump}}});
newp->addNext(firstIfsp);
@ -735,11 +867,118 @@ class RandomizeVisitor final : public VNVisitor {
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstMethodCall* nodep) override {
AstWith* const withp = VN_CAST(nodep->pinsp(), With);
if (!(nodep->name() == "randomize") || !withp) {
iterateChildren(nodep);
return;
}
VL_RESTORER(m_inline);
m_inline = true;
iterateChildren(nodep);
UASSERT_OBJ(nodep->fromp()->dtypep(), nodep->fromp(), "Object dtype is not linked");
AstClassRefDType* const classrefdtypep = VN_CAST(nodep->fromp()->dtypep(), ClassRefDType);
if (!classrefdtypep) {
nodep->v3warn(E_UNSUPPORTED,
"Inline constraints are not supported for this node type");
return;
}
AstClass* const classp = classrefdtypep->classp();
UASSERT_OBJ(classp, classrefdtypep, "Class type is unlinked to its ref type");
if (classp->user1()) {
// We need to first ensure that the class randomizer is instantiated if needed
// NOTE: This is safe only because AstClass visit function overwrites all
// nesting-dependent state variables
iterate(classp);
}
AstVar* const classGenp = VN_CAST(classp->user4p(), Var);
AstVar* const localGenp
= new AstVar{nodep->fileline(), VVarType::BLOCKTEMP, "randomizer",
classp->findBasicDType(VBasicDTypeKwd::RANDOM_GENERATOR)};
localGenp->funcLocal(true);
AstFunc* const randomizeFuncp
= V3Randomize::newRandomizeFunc(classp, m_inlineUniqueNames.get(nodep));
// Detach the expression and prepare variable copies
const CaptureFrame<AstNode> captured{withp->exprp(), classp, false};
UASSERT_OBJ(VN_IS(captured.getTree(), ConstraintExpr), captured.getTree(),
"Wrong expr type");
// Add function arguments
for (AstArg* argp = captured.getArgs(); argp; argp = VN_AS(argp->nextp(), Arg)) {
AstNodeVarRef* varrefp = VN_AS(argp->exprp(), NodeVarRef);
if ((varrefp->classOrPackagep() == m_modp) || VN_IS(varrefp, VarXRef)) {
// Keeping classOrPackagep will cause a broken link after inlining
varrefp->classOrPackagep(nullptr);
}
randomizeFuncp->addStmtsp(captured.getVar(varrefp->varp()));
}
// Add constraints clearing code
if (classGenp) {
randomizeFuncp->addStmtsp(
implementConstraintsClear(randomizeFuncp->fileline(), classGenp));
}
randomizeFuncp->addStmtsp(localGenp);
// Copy (derive) class constraints if present
if (classGenp) {
classp->foreach([&](AstConstraint* constrp) {
AstTask* constrSetupFuncp = VN_AS(constrp->user2p(), Task);
UASSERT_OBJ(constrSetupFuncp, constrp, "Constraint not linked to setup procudure");
auto callp = new AstTaskRef{nodep->fileline(), constrSetupFuncp->name(), nullptr};
callp->taskp(constrSetupFuncp);
randomizeFuncp->addStmtsp(callp->makeStmt());
});
randomizeFuncp->addStmtsp(new AstAssign{
nodep->fileline(), new AstVarRef{nodep->fileline(), localGenp, VAccess::WRITE},
new AstVarRef{nodep->fileline(), classGenp, VAccess::READ}});
}
// Generate constraint setup code and a hardcoded call to the solver
randomizeFuncp->addStmtsp(captured.getTree());
{ ConstraintExprVisitor{captured.getTree(), randomizeFuncp, localGenp, !m_inline}; }
// Call the solver and set return value
AstVarRef* const randNextp
= new AstVarRef{nodep->fileline(), localGenp, VAccess::READWRITE};
randNextp->AstNode::addNext(new AstText{nodep->fileline(), ".next(__Vm_rng)"});
AstNodeExpr* const solverCallp = new AstCExpr{nodep->fileline(), randNextp};
solverCallp->dtypeSetBit();
randomizeFuncp->addStmtsp(new AstAssign{
nodep->fileline(),
new AstVarRef{nodep->fileline(), VN_AS(randomizeFuncp->fvarp(), Var), VAccess::WRITE},
solverCallp});
// Replace the node with a call to that function
AstMethodCall* const callp
= new AstMethodCall(nodep->fileline(), nodep->fromp()->unlinkFrBack(),
randomizeFuncp->name(), captured.getArgs());
callp->taskp(randomizeFuncp);
callp->dtypeFrom(randomizeFuncp->dtypep());
callp->classOrPackagep(classp);
nodep->replaceWith(callp);
UINFO(9, "Added `%s` randomization procedure");
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit RandomizeVisitor(AstNetlist* nodep) { iterate(nodep); }
explicit RandomizeVisitor(AstNetlist* nodep)
: m_inlineUniqueNames("__Vrandwith") {
iterate(nodep);
nodep->foreach([&](AstConstraint* constrp) {
VL_DO_DANGLING(pushDeletep(constrp->unlinkFrBack()), constrp);
});
}
~RandomizeVisitor() override = default;
};
@ -750,24 +989,26 @@ void V3Randomize::randomizeNetlist(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ": " << endl);
{
const RandomizeMarkVisitor markVisitor{nodep};
RandomizeVisitor{nodep};
RandomizeVisitor randomizeVisitor{nodep};
}
V3Global::dumpCheckGlobalTree("randomize", 0, dumpTreeEitherLevel() >= 3);
}
AstFunc* V3Randomize::newRandomizeFunc(AstClass* nodep) {
AstFunc* V3Randomize::newRandomizeFunc(AstClass* nodep, const std::string& name) {
VMemberMap memberMap;
AstFunc* funcp = VN_AS(memberMap.findMember(nodep, "randomize"), Func);
AstFunc* funcp = VN_AS(memberMap.findMember(nodep, name), Func);
if (!funcp) {
v3Global.useRandomizeMethods(true);
AstNodeDType* const dtypep
= nodep->findBitDType(32, 32, VSigning::SIGNED); // IEEE says int return of 0/1
AstVar* const fvarp = new AstVar{nodep->fileline(), VVarType::MEMBER, "randomize", dtypep};
AstVar* const fvarp = new AstVar{nodep->fileline(), VVarType::MEMBER, name, dtypep};
fvarp->lifetime(VLifetime::AUTOMATIC);
fvarp->funcLocal(true);
fvarp->funcReturn(true);
fvarp->direction(VDirection::OUTPUT);
funcp = new AstFunc{nodep->fileline(), "randomize", nullptr, fvarp};
nodep->addMembersp(funcp);
funcp = new AstFunc{nodep->fileline(), name, nullptr, fvarp};
funcp->dtypep(dtypep);
funcp->classMethod(true);
funcp->isVirtual(nodep->isExtended());

View File

@ -1,6 +1,6 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Node attributes/ expression widths
// DESCRIPTION: Verilator: Generate randomization procedures
//
// Code available from: https://verilator.org
//
@ -30,7 +30,8 @@ class V3Randomize final {
public:
static void randomizeNetlist(AstNetlist* nodep) VL_MT_DISABLED;
static AstFunc* newRandomizeFunc(AstClass* nodep) VL_MT_DISABLED;
static AstFunc* newRandomizeFunc(AstClass* nodep,
const std::string& name = "randomize") VL_MT_DISABLED;
static AstFunc* newSRandomFunc(AstClass* nodep) VL_MT_DISABLED;
};

View File

@ -1626,6 +1626,7 @@ V3TaskConnects V3Task::taskConnects(AstNodeFTaskRef* nodep, AstNode* taskStmtsp,
bool reorganize = false;
for (AstNode *nextp, *pinp = nodep->pinsp(); pinp; pinp = nextp) {
nextp = pinp->nextp();
if (VN_IS(pinp, With)) continue;
AstArg* const argp = VN_AS(pinp, Arg);
UASSERT_OBJ(argp, pinp, "Non-arg under ftask reference");
if (argp->name() != "") {

View File

@ -67,6 +67,7 @@
#include "V3Width.h"
#include "V3Ast.h"
#include "V3Const.h"
#include "V3Error.h"
#include "V3Global.h"
@ -3764,11 +3765,8 @@ class WidthVisitor final : public VNVisitor {
nodep->dtypeFrom(ftaskp);
nodep->classOrPackagep(classp);
if (VN_IS(ftaskp, Task)) nodep->dtypeSetVoid();
if (withp) nodep->addPinsp(withp);
processFTaskRefArgs(nodep);
if (withp) {
withp->v3warn(CONSTRAINTIGN, "'with' constraint ignored (unsupported)");
VL_DO_DANGLING(withp->deleteTree(), withp);
}
}
return;
} else if (nodep->name() == "get_randstate" || nodep->name() == "set_randstate") {

View File

@ -1,12 +1,9 @@
%Warning-CONSTRAINTIGN: t/t_randomize.v:66:25: 'with' constraint ignored (unsupported)
66 | v = p.randomize() with { if_4 == local::if_4; header == 2; };
| ^~~~
... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest
... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message.
%Warning-CONSTRAINTIGN: t/t_randomize.v:37:7: Constraint expression ignored (unsupported)
: ... note: In instance 't'
37 | foreach (array[i]) {
| ^~~~~~~
... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest
... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message.
%Warning-CONSTRAINTIGN: t/t_randomize.v:40:7: Constraint expression ignored (unsupported)
: ... note: In instance 't'
40 | unique { array[0], array[1] };

View File

@ -22,9 +22,10 @@ endclass
module t (/*AUTOARG*/);
Cls obj;
int res;
initial begin
obj = new;
obj.randomize();
res = obj.randomize();
end
endmodule

View File

@ -0,0 +1,23 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# 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
scenarios(simulator => 1);
compile(
# Ensure we test captures of static variables
verilator_flags2 => ["--fno-inline"],
);
execute(
check_finished => 1,
);
ok(1);
1;

View File

@ -0,0 +1,112 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2024 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
class Boo;
function new();
boo = 6;
endfunction
int unsigned boo;
endclass
class Boo2;
function new();
boo = 6;
endfunction
int unsigned boo;
endclass
class Foo extends Boo;
rand int unsigned a;
rand int unsigned b;
int x;
function new(int x);
this.x = x;
endfunction
constraint constr1_c { b < x; }
endclass
// Current AstWith representation makes VARs of caller indistinguishable from VARs of randomized
// object if both the caller and callee are the same module, but different instances.
// That's why for the purpose of this test, the caller derives a different class
class Bar extends Boo2;
// Give the local variables a different scope by defining the functino under Bar
static function bit test_local_constrdep(Foo foo, int c);
return foo.randomize() with { a <= c; a > 1; x % a == 0; } == 1;
endfunction
function bit test_capture_of_callers_derived_var(Foo foo);
boo = 4;
return (foo.randomize() with { a == local::boo; } == 1) && (foo.a == 4);
endfunction
static function bit test_capture_of_callees_derived_var(Foo foo);
return (foo.randomize() with { a == boo; } == 1) && (foo.a == 6);
endfunction
endclass
class Baz;
rand int v;
endclass
module submodule();
int sub_var = 7;
endmodule
function automatic int return_2();
return 2;
endfunction
module mwith();
submodule sub1();
submodule sub2();
function automatic int return_3();
return 3;
endfunction
initial begin
int c = 30;
Foo foo = new(c);
Baz baz = new;
Bar bar = new;
$display("foo.x = %d", foo.x);
$display("-----------------");
repeat (20) begin
if (Bar::test_local_constrdep(foo, 5)) begin
$display("foo.a = %d", foo.a);
$display("foo.b = %d", foo.b);
$display("-----------------");
if (!(foo.a inside {2, 3, 5})) $stop;
if (foo.b >= foo.x) $stop;
if (foo.a > c) $stop;
if (foo.a <= 1) $stop;
sub1.sub_var = foo.a;
end else
$display("Failed to randomize foo with inline constraints");
end
// Check capture of a static variable
if (foo.randomize() with { a > sub1.sub_var; } != 1) $stop;
// Check reference to a function
if (foo.randomize() with { a > return_2(); } != 1) $stop;
// Check randomization of class with no constraints
if (baz.randomize() with { v inside {[2:10]}; } != 1) $stop;
// Check randomization with captured non-static variable from different AstNodeModule
if (!bar.test_capture_of_callers_derived_var(foo)) $stop;
// Check randomization with non-captured non-static variable from different AstNodeModule
if (!Bar::test_capture_of_callees_derived_var(foo)) $stop;
$write("*-* All Finished *-*\n");
$finish();
end
endmodule

View File

@ -0,0 +1,4 @@
%Error: t/t_randomize_method_with_bad.v:18:42: Can't find definition of task/function: 'in_mod_function'
18 | int res = foo.randomize() with { v < in_mod_function(); };
| ^~~~~~~~~~~~~~~
%Error: Exiting due to

View File

@ -2,17 +2,17 @@
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2023 by Wilson Snyder. This program is free software; you
# 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
scenarios(linter => 1);
scenarios(simulator => 1);
compile(
expect_filename => $Self->{golden_filename},
fails => 1,
expect_filename => $Self->{golden_filename},
);
ok(1);

View File

@ -0,0 +1,20 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2024 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
class Foo;
rand int unsigned v;
endclass
module t_randomize_method_with_bad();
function automatic int unsigned in_mod_function();
return 5;
endfunction
initial begin
Foo foo = new;
int res = foo.randomize() with { v < in_mod_function(); };
end
endmodule

View File

@ -1,12 +0,0 @@
%Warning-CONSTRAINTIGN: t/t_randomize_method_with_unsup.v:47:40: 'with' constraint ignored (unsupported)
47 | rand_result = obj.randomize() with { lb <= y && y <= ub; };
| ^~~~
... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest
... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message.
%Warning-CONSTRAINTIGN: t/t_randomize_method_with_unsup.v:63:37: 'with' constraint ignored (unsupported)
63 | rand_result = obj.randomize() with { 256 < y && y < 256; };
| ^~~~
%Warning-CONSTRAINTIGN: t/t_randomize_method_with_unsup.v:67:37: 'with' constraint ignored (unsupported)
67 | rand_result = obj.randomize() with { 16 <= z && z <= 32; };
| ^~~~
%Error: Exiting due to

View File

@ -1,71 +0,0 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2024 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
typedef enum bit[15:0] {
ONE = 3,
TWO = 5,
THREE = 8,
FOUR = 13
} Enum;
class Cls;
constraint A { v inside {ONE, THREE}; }
constraint B { w == 5; x inside {1,2} || x inside {4,5}; }
constraint C { z < 3 * 7; z > 5 + 8; }
rand Enum v;
rand logic[63:0] w;
rand logic[47:0] x;
rand logic[31:0] y;
rand logic[23:0] z;
function new;
v = ONE;
w = 0;
x = 0;
y = 0;
z = 0;
endfunction
endclass
module t (/*AUTOARG*/);
Cls obj;
initial begin
int rand_result;
int lb, ub;
longint prev_checksum;
$display("===================\nSatisfiable constraints:");
for (int i = 0; i < 25; i++) begin
obj = new;
lb = 16;
ub = 32;
rand_result = obj.randomize() with { lb <= y && y <= ub; };
$display("obj.v == %0d", obj.v);
$display("obj.w == %0d", obj.w);
$display("obj.x == %0d", obj.x);
$display("obj.y == %0d", obj.y);
$display("obj.z == %0d", obj.z);
$display("rand_result == %0d", rand_result);
$display("-------------------");
if (!(obj.v inside {ONE, THREE})) $stop;
if (obj.w != 5) $stop;
if (!(obj.x inside {1,2,4,5})) $stop;
if (obj.y < 16 || obj.y > 32) $stop;
if (obj.z <= 13 || obj.z >= 21) $stop;
if (lb != 16 || ub != 32) $stop;
end
$display("===================\nUnsatisfiable constraints for obj.y:");
rand_result = obj.randomize() with { 256 < y && y < 256; };
$display("obj.y == %0d", obj.y);
$display("rand_result == %0d", rand_result);
if (rand_result != 0) $stop;
rand_result = obj.randomize() with { 16 <= z && z <= 32; };
$write("*-* All Finished *-*\n");
$finish;
end
endmodule