Support constrained randomization with external solvers (#4947)

This commit is contained in:
Arkadiusz Kozdra
2024-05-17 10:38:34 -04:00
committed by GitHub
parent 25b9a16bc7
commit 739be2f782
44 changed files with 1668 additions and 263 deletions
+222 -14
View File
@@ -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