IEEE compliant scheduler (#3384)

This is a major re-design of the way code is scheduled in Verilator,
with the goal of properly supporting the Active and NBA regions of the
SystemVerilog scheduling model, as defined in IEEE 1800-2017 chapter 4.

With this change, all internally generated clocks should simulate
correctly, and there should be no more need for the `clock_enable` and
`clocker` attributes for correctness in the absence of Verilator
generated library models (`--lib-create`).

Details of the new scheduling model and algorithm are provided in
docs/internals.rst.

Implements #3278
This commit is contained in:
Geza Lore
2022-05-15 16:03:32 +01:00
committed by GitHub
parent 39b7c47e7b
commit 599d23697d
157 changed files with 6386 additions and 4395 deletions
+28 -304
View File
@@ -33,6 +33,7 @@
#include "V3Global.h"
#include "V3Clock.h"
#include "V3Ast.h"
#include "V3Sched.h"
#include <algorithm>
@@ -69,116 +70,19 @@ public:
class ClockVisitor final : public VNVisitor {
private:
// NODE STATE
// Cleared each Module:
// AstVarScope::user1p() -> AstVarScope*. Temporary signal that was created.
const VNUser1InUse m_inuser1;
// STATE
AstNodeModule* m_modp = nullptr; // Current module
const AstTopScope* m_topScopep = nullptr; // Current top scope
AstScope* m_scopep = nullptr; // Current scope
AstCFunc* m_evalFuncp = nullptr; // Top eval function we are creating
AstCFunc* m_initFuncp = nullptr; // Top initial function we are creating
AstCFunc* m_finalFuncp = nullptr; // Top final function we are creating
AstCFunc* m_settleFuncp = nullptr; // Top settlement function we are creating
AstSenTree* m_lastSenp = nullptr; // Last sensitivity match, so we can detect duplicates.
AstIf* m_lastIfp = nullptr; // Last sensitivity if active to add more under
AstMTaskBody* m_mtaskBodyp = nullptr; // Current mtask body
// METHODS
VL_DEBUG_FUNC; // Declare debug()
AstVarScope* getCreateLastClk(AstVarScope* vscp) {
if (vscp->user1p()) return static_cast<AstVarScope*>(vscp->user1p());
const AstVar* const varp = vscp->varp();
if (!varp->width1()) {
varp->v3warn(E_UNSUPPORTED, "Unsupported: Clock edge on non-single bit signal: "
<< varp->prettyNameQ());
}
const string newvarname
= (string("__Vclklast__") + vscp->scopep()->nameDotless() + "__" + varp->name());
AstVar* const newvarp = new AstVar(vscp->fileline(), VVarType::MODULETEMP, newvarname,
VFlagLogicPacked(), 1);
newvarp->noReset(true); // Reset by below assign
m_modp->addStmtp(newvarp);
AstVarScope* const newvscp = new AstVarScope(vscp->fileline(), m_scopep, newvarp);
vscp->user1p(newvscp);
m_scopep->addVarp(newvscp);
// Add init
AstNode* fromp = new AstVarRef(newvarp->fileline(), vscp, VAccess::READ);
if (v3Global.opt.xInitialEdge()) fromp = new AstNot(fromp->fileline(), fromp);
AstNode* const newinitp = new AstAssign(
vscp->fileline(), new AstVarRef(newvarp->fileline(), newvscp, VAccess::WRITE), fromp);
addToInitial(newinitp);
// At bottom, assign them
AstAssign* const finalp = new AstAssign(
vscp->fileline(), new AstVarRef(vscp->fileline(), newvscp, VAccess::WRITE),
new AstVarRef(vscp->fileline(), vscp, VAccess::READ));
m_evalFuncp->addFinalsp(finalp);
//
UINFO(4, "New Last: " << newvscp << endl);
return newvscp;
}
AstNode* createSenItemEquation(AstSenItem* nodep) {
// We know the var is clean, and one bit, so we use binary ops
// for speed instead of logical ops.
// POSEDGE: var & ~var_last
// NEGEDGE: ~var & var_last
// BOTHEDGE: var ^ var_last
// HIGHEDGE: var
// LOWEDGE: ~var
// ANYEDGE: var ^ var_last
AstNode* newp = nullptr;
if (nodep->edgeType() == VEdgeType::ET_ILLEGAL) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Complicated event expression in sensitive activity list");
return nullptr;
}
UASSERT_OBJ(nodep->varrefp(), nodep, "No clock found on sense item");
AstVarScope* const clkvscp = nodep->varrefp()->varScopep();
if (nodep->edgeType() == VEdgeType::ET_POSEDGE) {
AstVarScope* const lastVscp = getCreateLastClk(clkvscp);
newp = new AstAnd(
nodep->fileline(),
new AstVarRef(nodep->fileline(), nodep->varrefp()->varScopep(), VAccess::READ),
new AstNot(nodep->fileline(),
new AstVarRef(nodep->fileline(), lastVscp, VAccess::READ)));
} else if (nodep->edgeType() == VEdgeType::ET_NEGEDGE) {
AstVarScope* const lastVscp = getCreateLastClk(clkvscp);
newp = new AstAnd(
nodep->fileline(),
new AstNot(nodep->fileline(),
new AstVarRef(nodep->fileline(), nodep->varrefp()->varScopep(),
VAccess::READ)),
new AstVarRef(nodep->fileline(), lastVscp, VAccess::READ));
} else if (nodep->edgeType() == VEdgeType::ET_BOTHEDGE) {
AstVarScope* const lastVscp = getCreateLastClk(clkvscp);
newp = new AstXor(
nodep->fileline(),
new AstVarRef(nodep->fileline(), nodep->varrefp()->varScopep(), VAccess::READ),
new AstVarRef(nodep->fileline(), lastVscp, VAccess::READ));
} else if (nodep->edgeType() == VEdgeType::ET_HIGHEDGE) {
newp = new AstVarRef(nodep->fileline(), clkvscp, VAccess::READ);
} else if (nodep->edgeType() == VEdgeType::ET_LOWEDGE) {
newp = new AstNot(nodep->fileline(),
new AstVarRef(nodep->fileline(), clkvscp, VAccess::READ));
} else {
nodep->v3fatalSrc("Bad edge type");
}
return newp;
}
AstNode* createSenseEquation(AstSenItem* nodesp) {
// Nodep may be a list of elements; we need to walk it
AstNode* senEqnp = nullptr;
for (AstSenItem* senp = nodesp; senp; senp = VN_AS(senp->nextp(), SenItem)) {
AstNode* const senOnep = createSenItemEquation(senp);
if (senEqnp) {
// Add new OR to the sensitivity list equation
senEqnp = new AstOr(senp->fileline(), senEqnp, senOnep);
} else {
senEqnp = senOnep;
}
UASSERT_OBJ(senp->edgeType() == VEdgeType::ET_TRUE, senp, "Should have been lowered");
AstNode* const senOnep = senp->sensp()->cloneTree(false);
senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep;
}
return senEqnp;
}
@@ -192,116 +96,7 @@ private:
m_lastSenp = nullptr;
m_lastIfp = nullptr;
}
AstCFunc* makeTopFunction(const string& name, bool slow = false) {
AstCFunc* const funcp = new AstCFunc{m_topScopep->fileline(), name, m_topScopep->scopep()};
funcp->dontCombine(true);
funcp->isStatic(false);
funcp->isLoose(true);
funcp->entryPoint(true);
funcp->slow(slow);
funcp->isConst(false);
funcp->declPrivate(true);
m_topScopep->scopep()->addActivep(funcp);
return funcp;
}
void splitCheck(AstCFunc* ofuncp) {
if (!v3Global.opt.outputSplitCFuncs() || !ofuncp->stmtsp()) return;
if (ofuncp->nodeCount() < v3Global.opt.outputSplitCFuncs()) return;
int funcnum = 0;
int func_stmts = 0;
AstCFunc* funcp = nullptr;
// Unlink all statements, then add item by item to new sub-functions
AstBegin* const tempp = new AstBegin{ofuncp->fileline(), "[EditWrapper]",
ofuncp->stmtsp()->unlinkFrBackWithNext()};
if (ofuncp->finalsp()) tempp->addStmtsp(ofuncp->finalsp()->unlinkFrBackWithNext());
while (tempp->stmtsp()) {
AstNode* const itemp = tempp->stmtsp()->unlinkFrBack();
const int stmts = itemp->nodeCount();
if (!funcp || (func_stmts + stmts) > v3Global.opt.outputSplitCFuncs()) {
// Make a new function
funcp
= new AstCFunc{ofuncp->fileline(), ofuncp->name() + "__" + cvtToStr(funcnum++),
m_topScopep->scopep()};
funcp->dontCombine(true);
funcp->isStatic(false);
funcp->isLoose(true);
funcp->slow(ofuncp->slow());
m_topScopep->scopep()->addActivep(funcp);
//
AstCCall* const callp = new AstCCall{funcp->fileline(), funcp};
ofuncp->addStmtsp(callp);
func_stmts = 0;
}
funcp->addStmtsp(itemp);
func_stmts += stmts;
}
VL_DO_DANGLING(tempp->deleteTree(), tempp);
}
// VISITORS
virtual void visit(AstTopScope* nodep) override {
UINFO(4, " TOPSCOPE " << nodep << endl);
m_topScopep = nodep;
m_scopep = nodep->scopep();
UASSERT_OBJ(m_scopep, nodep,
"No scope found on top level, perhaps you have no statements?");
// VV***** We reset all user1p()
AstNode::user1ClearTree();
// Make top functions
m_evalFuncp = makeTopFunction("_eval");
m_initFuncp = makeTopFunction("_eval_initial", /* slow: */ true);
m_settleFuncp = makeTopFunction("_eval_settle", /* slow: */ true);
m_finalFuncp = makeTopFunction("_final", /* slow: */ true);
// Process the activates
iterateChildren(nodep);
UINFO(4, " TOPSCOPE iter done " << nodep << endl);
// Clear the DPI export trigger flag at the end of eval
if (AstVarScope* const dpiExportTriggerp = v3Global.rootp()->dpiExportTriggerp()) {
FileLine* const fl = dpiExportTriggerp->fileline();
AstAssign* const assignp
= new AstAssign{fl, new AstVarRef{fl, dpiExportTriggerp, VAccess::WRITE},
new AstConst{fl, AstConst::BitFalse{}}};
m_evalFuncp->addFinalsp(assignp);
}
// Split large functions
splitCheck(m_evalFuncp);
splitCheck(m_initFuncp);
splitCheck(m_finalFuncp);
splitCheck(m_settleFuncp);
// Done, clear so we can detect errors
UINFO(4, " TOPSCOPEDONE " << nodep << endl);
clearLastSen();
m_topScopep = nullptr;
m_scopep = nullptr;
}
virtual void visit(AstNodeModule* nodep) override {
// UINFO(4, " MOD " << nodep << endl);
VL_RESTORER(m_modp);
{
m_modp = nodep;
iterateChildren(nodep);
}
}
virtual void visit(AstScope* nodep) override {
// UINFO(4, " SCOPE " << nodep << endl);
m_scopep = nodep;
iterateChildren(nodep);
if (AstNode* const movep = nodep->finalClksp()) {
UASSERT_OBJ(m_topScopep, nodep, "Final clocks under non-top scope");
movep->unlinkFrBackWithNext();
m_evalFuncp->addFinalsp(movep);
}
m_scopep = nullptr;
}
virtual void visit(AstNodeProcedure* nodep) override {
if (AstNode* const stmtsp = nodep->bodysp()) {
stmtsp->unlinkFrBackWithNext();
nodep->addNextHere(stmtsp);
}
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
virtual void visit(AstCoverToggle* nodep) override {
// nodep->dumpTree(cout, "ct:");
// COVERTOGGLE(INC, ORIG, CHANGE) ->
@@ -319,110 +114,44 @@ private:
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
virtual void visit(AstCFunc* nodep) override {
iterateChildren(nodep);
// Link to global function
if (nodep->isFinal()) {
UINFO(4, " isFinal " << nodep << endl);
AstCCall* const callp = new AstCCall(nodep->fileline(), nodep);
m_finalFuncp->addStmtsp(callp);
}
}
virtual void visit(AstSenTree* nodep) override {
// Delete it later; Actives still pointing to it
nodep->unlinkFrBack();
pushDeletep(nodep);
}
void addToEvalLoop(AstNode* stmtsp) {
m_evalFuncp->addStmtsp(stmtsp); // add to top level function
}
void addToSettleLoop(AstNode* stmtsp) {
m_settleFuncp->addStmtsp(stmtsp); // add to top level function
}
void addToInitial(AstNode* stmtsp) {
m_initFuncp->addStmtsp(stmtsp); // add to top level function
pushDeletep(nodep); // Delete it later, AstActives still pointing to it
}
virtual void visit(AstActive* nodep) override {
// Careful if adding variables here, ACTIVES can be under other ACTIVES
// Need to save and restore any member state in AstUntilStable block
if (!m_topScopep || !nodep->stmtsp()) {
// Not at the top or empty block...
// Only empty blocks should be leftover on the non-top. Killem.
UASSERT_OBJ(!nodep->stmtsp(), nodep, "Non-empty lower active");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (m_mtaskBodyp) {
UINFO(4, " TR ACTIVE " << nodep << endl);
AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext();
if (nodep->hasClocked()) {
UASSERT_OBJ(!nodep->hasInitial(), nodep,
"Initial block should not have clock sensitivity");
if (m_lastSenp && nodep->sensesp()->sameTree(m_lastSenp)) {
UINFO(4, " sameSenseTree\n");
} else {
clearLastSen();
m_lastSenp = nodep->sensesp();
// Make a new if statement
m_lastIfp = makeActiveIf(m_lastSenp);
m_mtaskBodyp->addStmtsp(m_lastIfp);
}
// Move statements to if
m_lastIfp->addIfsp(stmtsp);
} else if (nodep->hasInitial() || nodep->hasSettle()) {
nodep->v3fatalSrc("MTask should not include initial/settle logic.");
} else {
// Combo logic. Move statements to mtask func.
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
UASSERT_OBJ(nodep->stmtsp(), nodep, "Should not have been created if empty");
AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext();
if (nodep->hasClocked()) {
// Create 'if' statement, if needed
if (!m_lastSenp || !nodep->sensesp()->sameTree(m_lastSenp)) {
clearLastSen();
m_mtaskBodyp->addStmtsp(stmtsp);
m_lastSenp = nodep->sensesp();
// Make a new if statement
m_lastIfp = makeActiveIf(m_lastSenp);
relinker.relink(m_lastIfp);
}
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
// Move statements to if
m_lastIfp->addIfsp(stmtsp);
} else if (nodep->hasCombo()) {
clearLastSen();
// Move statements to body
relinker.relink(stmtsp);
} else {
UINFO(4, " ACTIVE " << nodep << endl);
AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext();
if (nodep->hasClocked()) {
// Remember the latest sensitivity so we can compare it next time
UASSERT_OBJ(!nodep->hasInitial(), nodep,
"Initial block should not have clock sensitivity");
if (m_lastSenp && nodep->sensesp()->sameTree(m_lastSenp)) {
UINFO(4, " sameSenseTree\n");
} else {
clearLastSen();
m_lastSenp = nodep->sensesp();
// Make a new if statement
m_lastIfp = makeActiveIf(m_lastSenp);
addToEvalLoop(m_lastIfp);
}
// Move statements to if
m_lastIfp->addIfsp(stmtsp);
} else if (nodep->hasInitial()) {
// Don't need to: clearLastSen();, as we're adding it to different cfunc
// Move statements to function
addToInitial(stmtsp);
} else if (nodep->hasSettle()) {
// Don't need to: clearLastSen();, as we're adding it to different cfunc
// Move statements to function
addToSettleLoop(stmtsp);
} else {
// Combo
clearLastSen();
// Move statements to function
addToEvalLoop(stmtsp);
}
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
nodep->v3fatalSrc("Should have been removed by V3Sched::schedule");
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
virtual void visit(AstExecGraph* nodep) override {
VL_RESTORER(m_mtaskBodyp);
for (m_mtaskBodyp = nodep->mTaskBodiesp(); m_mtaskBodyp;
m_mtaskBodyp = VN_AS(m_mtaskBodyp->nextp(), MTaskBody)) {
for (AstMTaskBody* mtaskBodyp = nodep->mTaskBodiesp(); mtaskBodyp;
mtaskBodyp = VN_AS(mtaskBodyp->nextp(), MTaskBody)) {
clearLastSen();
iterate(m_mtaskBodyp);
iterate(mtaskBodyp);
}
clearLastSen();
// Move the ExecGraph into _eval. Its location marks the
// spot where the graph will execute, relative to other
// (serial) logic in the cycle.
nodep->unlinkFrBack();
addToEvalLoop(nodep);
}
//--------------------
@@ -430,12 +159,7 @@ private:
public:
// CONSTRUCTORS
explicit ClockVisitor(AstNetlist* nodep) {
iterate(nodep);
// Allow downstream modules to find _eval()
// easily without iterating through the tree.
nodep->evalp(m_evalFuncp);
}
explicit ClockVisitor(AstNetlist* netlistp) { iterate(netlistp); }
virtual ~ClockVisitor() override = default;
};