mirror of
https://github.com/verilator/verilator.git
synced 2026-09-04 08:33:38 +02:00
Support constrained randomization with external solvers (#4947)
This commit is contained in:
+222
-14
@@ -39,24 +39,28 @@ class RandomizeMarkVisitor final : public VNVisitorConst {
|
||||
// NODE STATE
|
||||
// Cleared on Netlist
|
||||
// AstClass::user1() -> bool. Set true to indicate needs randomize processing
|
||||
// AstConstraintExpr::user1() -> bool. Set true to indicate state-dependent
|
||||
// AstNodeExpr::user1() -> bool. Set true to indicate constraint expression depending on a
|
||||
// randomized variable
|
||||
const VNUser1InUse m_inuser1;
|
||||
|
||||
using DerivedSet = std::unordered_set<AstClass*>;
|
||||
using BaseToDerivedMap = std::unordered_map<AstClass*, DerivedSet>;
|
||||
using BaseToDerivedMap = std::unordered_map<const AstClass*, DerivedSet>;
|
||||
|
||||
BaseToDerivedMap m_baseToDerivedMap; // Mapping from base classes to classes that extend them
|
||||
AstClass* m_classp = nullptr; // Current class
|
||||
AstConstraintExpr* m_constraintExprp = nullptr; // Current constraint expression
|
||||
|
||||
// METHODS
|
||||
void markMembers(AstClass* nodep) {
|
||||
for (auto* classp = nodep; classp;
|
||||
void markMembers(const AstClass* nodep) {
|
||||
for (const AstClass* classp = nodep; classp;
|
||||
classp = classp->extendsp() ? classp->extendsp()->classp() : nullptr) {
|
||||
for (auto* memberp = classp->stmtsp(); memberp; memberp = memberp->nextp()) {
|
||||
for (const AstNode* memberp = classp->stmtsp(); memberp; memberp = memberp->nextp()) {
|
||||
// If member is rand and of class type, mark its class
|
||||
if (VN_IS(memberp, Var) && VN_AS(memberp, Var)->isRand()) {
|
||||
if (const auto* const classRefp
|
||||
if (const AstClassRefDType* const classRefp
|
||||
= VN_CAST(memberp->dtypep()->skipRefp(), ClassRefDType)) {
|
||||
auto* const rclassp = classRefp->classp();
|
||||
AstClass* const rclassp = classRefp->classp();
|
||||
if (!rclassp->user1()) {
|
||||
rclassp->user1(true);
|
||||
markMembers(rclassp);
|
||||
@@ -67,11 +71,11 @@ class RandomizeMarkVisitor final : public VNVisitorConst {
|
||||
}
|
||||
}
|
||||
}
|
||||
void markDerived(AstClass* nodep) {
|
||||
void markDerived(const AstClass* nodep) {
|
||||
const auto it = m_baseToDerivedMap.find(nodep);
|
||||
if (it != m_baseToDerivedMap.end()) {
|
||||
for (auto* classp : it->second) {
|
||||
if (!classp->user1p()) {
|
||||
if (!classp->user1()) {
|
||||
classp->user1(true);
|
||||
markMembers(classp);
|
||||
markDerived(classp);
|
||||
@@ -92,7 +96,7 @@ class RandomizeMarkVisitor final : public VNVisitorConst {
|
||||
iterateChildrenConst(nodep);
|
||||
if (nodep->extendsp()) {
|
||||
// Save pointer to derived class
|
||||
AstClass* const basep = nodep->extendsp()->classp();
|
||||
const AstClass* const basep = nodep->extendsp()->classp();
|
||||
m_baseToDerivedMap[basep].insert(nodep);
|
||||
}
|
||||
}
|
||||
@@ -111,6 +115,22 @@ class RandomizeMarkVisitor final : public VNVisitorConst {
|
||||
if (nodep->name() != "randomize") return;
|
||||
if (m_classp) m_classp->user1(true);
|
||||
}
|
||||
void visit(AstConstraintExpr* nodep) override {
|
||||
VL_RESTORER(m_constraintExprp);
|
||||
m_constraintExprp = nodep;
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
void visit(AstNodeVarRef* nodep) override {
|
||||
if (!m_constraintExprp) return;
|
||||
if (!nodep->varp()->isRand()) {
|
||||
m_constraintExprp->user1(true);
|
||||
nodep->v3warn(CONSTRAINTIGN, "State-dependent constraint ignored (unsupported)");
|
||||
return;
|
||||
}
|
||||
for (AstNode* backp = nodep; backp != m_constraintExprp && !backp->user1();
|
||||
backp = backp->backp())
|
||||
backp->user1(true);
|
||||
}
|
||||
|
||||
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
|
||||
|
||||
@@ -123,6 +143,143 @@ public:
|
||||
~RandomizeMarkVisitor() override = default;
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Visitor that turns constraints into template strings for solvers
|
||||
|
||||
class ConstraintExprVisitor final : public VNVisitor {
|
||||
// NODE STATE
|
||||
// AstVar::user4() -> bool. Handled in constraints
|
||||
// AstNodeExpr::user1() -> bool. Depending on a randomized variable
|
||||
// VNUser4InUse m_inuser4; (Allocated for use in RandomizeVisitor)
|
||||
|
||||
AstTask* const m_taskp; // X_setup_constraint() method of the constraint
|
||||
AstVar* const m_genp; // VlRandomizer variable of the class
|
||||
|
||||
bool editFormat(AstNodeExpr* nodep) {
|
||||
if (nodep->user1()) return false;
|
||||
// Replace computable expression with SMT constant
|
||||
VNRelinker handle;
|
||||
nodep->unlinkFrBack(&handle);
|
||||
AstSFormatF* const newp = new AstSFormatF{
|
||||
nodep->fileline(), (nodep->width() & 3) ? "#b%b" : "#x%x", false, nodep};
|
||||
handle.relink(newp);
|
||||
return true;
|
||||
}
|
||||
void editSMT(AstNodeExpr* nodep, AstNodeExpr* lhsp = nullptr, AstNodeExpr* rhsp = nullptr) {
|
||||
// Replace incomputable (result-dependent) expression with SMT expression
|
||||
std::string smtExpr = nodep->emitSMT(); // Might need child width (AstExtend)
|
||||
UASSERT_OBJ(smtExpr != "", nodep,
|
||||
"Node needs randomization constraint, but no emitSMT: " << nodep);
|
||||
|
||||
if (lhsp) lhsp = VN_AS(iterateSubtreeReturnEdits(lhsp->unlinkFrBack()), NodeExpr);
|
||||
if (rhsp) rhsp = VN_AS(iterateSubtreeReturnEdits(rhsp->unlinkFrBack()), NodeExpr);
|
||||
|
||||
AstNodeExpr* argsp = nullptr;
|
||||
for (string::iterator pos = smtExpr.begin(); pos != smtExpr.end(); ++pos) {
|
||||
if (pos[0] == '%') {
|
||||
++pos;
|
||||
switch (pos[0]) {
|
||||
case '%': break;
|
||||
case 'l':
|
||||
pos[0] = '@';
|
||||
UASSERT_OBJ(lhsp, nodep, "emitSMT() references undef node");
|
||||
argsp = AstNode::addNext(argsp, lhsp);
|
||||
lhsp = nullptr;
|
||||
break;
|
||||
case 'r':
|
||||
pos[0] = '@';
|
||||
UASSERT_OBJ(rhsp, nodep, "emitSMT() references undef node");
|
||||
argsp = AstNode::addNext(argsp, rhsp);
|
||||
rhsp = nullptr;
|
||||
break;
|
||||
default: nodep->v3fatalSrc("Unknown emitSMT format code: %" << pos[0]); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
UASSERT_OBJ(!lhsp, nodep, "Missing emitSMT %l for " << lhsp);
|
||||
UASSERT_OBJ(!rhsp, nodep, "Missing emitSMT %r for " << rhsp);
|
||||
AstSFormatF* const newp = new AstSFormatF{nodep->fileline(), smtExpr, false, argsp};
|
||||
nodep->replaceWith(newp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
void visit(AstNodeVarRef* nodep) override {
|
||||
if (editFormat(nodep)) return;
|
||||
// In SMT just variable name, but we also ensure write_var for the variable
|
||||
const std::string smtName = nodep->name(); // Can be anything unique
|
||||
nodep->replaceWith(new AstSFormatF{nodep->fileline(), smtName, false, nullptr});
|
||||
|
||||
AstVar* const varp = nodep->varp();
|
||||
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
|
||||
if (!varp->user4()) {
|
||||
varp->user4(true);
|
||||
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});
|
||||
methodp->addPinsp(new AstConst{varp->dtypep()->fileline(), AstConst::Unsized64{},
|
||||
(size_t)varp->width()});
|
||||
AstNodeExpr* const varnamep
|
||||
= new AstCExpr{varp->fileline(), "\"" + smtName + "\"", varp->width()};
|
||||
varnamep->dtypep(varp->dtypep());
|
||||
methodp->addPinsp(varnamep);
|
||||
m_taskp->addStmtsp(new AstStmtExpr{varp->fileline(), methodp});
|
||||
}
|
||||
}
|
||||
void visit(AstNodeBiop* nodep) override {
|
||||
if (editFormat(nodep)) return;
|
||||
editSMT(nodep, nodep->lhsp(), nodep->rhsp());
|
||||
}
|
||||
void visit(AstNodeUniop* nodep) override {
|
||||
if (editFormat(nodep)) return;
|
||||
editSMT(nodep, nodep->lhsp());
|
||||
}
|
||||
void visit(AstReplicate* nodep) override {
|
||||
// Biop, but RHS is harmful
|
||||
if (editFormat(nodep)) return;
|
||||
editSMT(nodep, nodep->srcp());
|
||||
}
|
||||
void visit(AstSFormatF* nodep) override {}
|
||||
void visit(AstConstraintExpr* nodep) override { iterateChildren(nodep); }
|
||||
void visit(AstCMethodHard* nodep) override {
|
||||
if (editFormat(nodep)) return;
|
||||
|
||||
UASSERT_OBJ(nodep->name() == "size", nodep, "Non-size method call in constraints");
|
||||
|
||||
AstNode* fromp = nodep->fromp();
|
||||
// Warn early while the dtype is still there
|
||||
fromp->v3warn(E_UNSUPPORTED, "Unsupported: random member variable with type "
|
||||
<< fromp->dtypep()->prettyDTypeNameQ());
|
||||
|
||||
iterateChildren(nodep); // Might change fromp
|
||||
fromp = nodep->fromp()->unlinkFrBack();
|
||||
fromp->dtypep(nodep->dtypep());
|
||||
nodep->replaceWith(fromp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
}
|
||||
void visit(AstNodeExpr* nodep) override {
|
||||
if (editFormat(nodep)) return;
|
||||
nodep->v3fatalSrc(
|
||||
"Visit function missing? Constraint function missing for math node: " << nodep);
|
||||
}
|
||||
void visit(AstNode* nodep) override {
|
||||
nodep->v3fatalSrc(
|
||||
"Visit function missing? Constraint function missing for node: " << nodep);
|
||||
}
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
explicit ConstraintExprVisitor(AstConstraintExpr* nodep, AstTask* taskp, AstVar* genp)
|
||||
: m_taskp(taskp)
|
||||
, m_genp(genp) {
|
||||
iterate(nodep);
|
||||
}
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Visitor that defines a randomize method where needed
|
||||
|
||||
@@ -130,10 +287,15 @@ class RandomizeVisitor final : public VNVisitor {
|
||||
// NODE STATE
|
||||
// Cleared on Netlist
|
||||
// AstClass::user1() -> bool. Set true to indicate needs randomize processing
|
||||
// AstConstraintExpr::user1() -> bool. Set true to indicate state-dependent
|
||||
// AstEnumDType::user2() -> AstVar*. Pointer to table with enum values
|
||||
// AstClass::user3() -> 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)
|
||||
const VNUser2InUse m_inuser2;
|
||||
const VNUser3InUse m_inuser3;
|
||||
const VNUser4InUse m_inuser4;
|
||||
|
||||
// STATE
|
||||
VMemberMap m_memberMap; // Member names cached for fast lookup
|
||||
@@ -274,6 +436,12 @@ class RandomizeVisitor final : public VNVisitor {
|
||||
funcp->addStmtsp(callp->makeStmt());
|
||||
}
|
||||
}
|
||||
AstTask* newSetupConstraintTask(AstClass* nodep, const std::string& name) {
|
||||
AstTask* const taskp = new AstTask{nodep->fileline(), name + "_setup_constraint", nullptr};
|
||||
taskp->classMethod(true);
|
||||
nodep->addMembersp(taskp);
|
||||
return taskp;
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
void visit(AstNodeModule* nodep) override {
|
||||
@@ -315,20 +483,28 @@ class RandomizeVisitor final : public VNVisitor {
|
||||
beginValp = baseRandCallp;
|
||||
}
|
||||
}
|
||||
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};
|
||||
solverCallp->dtypeSetBit();
|
||||
beginValp = beginValp ? new AstAnd{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});
|
||||
|
||||
for (auto* memberp = nodep->stmtsp(); memberp; memberp = memberp->nextp()) {
|
||||
for (AstNode* memberp = nodep->stmtsp(); memberp; memberp = memberp->nextp()) {
|
||||
AstVar* const memberVarp = VN_CAST(memberp, Var);
|
||||
if (!memberVarp || !memberVarp->isRand()) continue;
|
||||
if (!memberVarp || !memberVarp->isRand() || memberVarp->user4()) continue;
|
||||
const AstNodeDType* const dtypep = memberp->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};
|
||||
AstNodeStmt* const stmtp = newRandStmtsp(fl, refp, randcVarp);
|
||||
funcp->addStmtsp(stmtp);
|
||||
} else if (const auto* const classRefp = VN_CAST(dtypep, ClassRefDType)) {
|
||||
} else if (const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType)) {
|
||||
if (classRefp->classp() == nodep) {
|
||||
memberp->v3warn(
|
||||
E_UNSUPPORTED,
|
||||
@@ -358,8 +534,40 @@ class RandomizeVisitor final : public VNVisitor {
|
||||
nodep->user1(false);
|
||||
}
|
||||
void visit(AstConstraint* nodep) override {
|
||||
nodep->v3warn(CONSTRAINTIGN, "Constraint ignored (unsupported)");
|
||||
if (!v3Global.opt.xmlOnly()) VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
AstNodeFTask* const newp = VN_AS(m_memberMap.findMember(m_modp, "new"), NodeFTask);
|
||||
UASSERT_OBJ(newp, m_modp, "No new() in class");
|
||||
AstTask* const taskp = newSetupConstraintTask(VN_AS(m_modp, Class), nodep->name());
|
||||
AstTaskRef* const setupTaskRefp
|
||||
= new AstTaskRef{nodep->fileline(), taskp->name(), nullptr};
|
||||
setupTaskRefp->taskp(taskp);
|
||||
newp->addStmtsp(new AstStmtExpr{nodep->fileline(), setupTaskRefp});
|
||||
|
||||
AstVar* genp = VN_AS(m_modp->user4p(), Var);
|
||||
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);
|
||||
}
|
||||
|
||||
while (nodep->itemsp()) {
|
||||
AstConstraintExpr* const condsp = VN_CAST(nodep->itemsp(), ConstraintExpr);
|
||||
if (!condsp || condsp->user1()) {
|
||||
nodep->itemsp()->v3warn(CONSTRAINTIGN,
|
||||
"Constraint expression ignored (unsupported)");
|
||||
pushDeletep(nodep->itemsp()->unlinkFrBack());
|
||||
continue;
|
||||
}
|
||||
{ ConstraintExprVisitor{condsp->unlinkFrBack(), taskp, genp}; }
|
||||
// Only hard constraints are now supported
|
||||
AstCMethodHard* const methodp = new AstCMethodHard{
|
||||
condsp->fileline(), new AstVarRef{condsp->fileline(), genp, VAccess::READWRITE},
|
||||
"hard", condsp->exprp()->unlinkFrBack()};
|
||||
methodp->dtypeSetVoid();
|
||||
taskp->addStmtsp(new AstStmtExpr{condsp->fileline(), methodp});
|
||||
VL_DO_DANGLING(condsp->deleteTree(), condsp);
|
||||
}
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
}
|
||||
void visit(AstRandCase* nodep) override {
|
||||
// RANDCASE
|
||||
|
||||
Reference in New Issue
Block a user