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 -8
View File
@@ -131,8 +131,8 @@ struct LifePostLocation {
class LifePostDlyVisitor final : public VNVisitor {
private:
// NODE STATE
// Cleared on entire tree
// AstVarScope::user4() -> AstVarScope*: Passed to LifePostElim to substitute this var
// AstVarScope::user1() -> bool: referenced outside _eval__nba
// AstVarScope::user4() -> AstVarScope*: Passed to LifePostElim to substitute this var
const VNUser4InUse m_inuser4;
// STATE
@@ -155,6 +155,9 @@ private:
const V3Graph* m_mtasksGraphp = nullptr; // Mtask tracking graph
std::unique_ptr<GraphPathChecker> m_checker;
const AstCFunc* const m_evalNbap; // The _eval__nba function
bool m_inEvalNba = false; // Traversing under _eval__nba
// METHODS
VL_DEBUG_FUNC; // Declare debug()
@@ -185,8 +188,11 @@ private:
return true;
}
void squashAssignposts() {
for (auto& itr : m_assignposts) {
const LifePostLocation* const app = &itr.second;
for (auto& pair : m_assignposts) {
// If referenced external to _eval__nba, don't optimize
if (pair.first->user1()) continue;
const LifePostLocation* const app = &pair.second;
const AstVarRef* const lhsp = VN_AS(app->nodep->lhsp(), VarRef); // original var
const AstVarRef* const rhsp = VN_AS(app->nodep->rhsp(), VarRef); // dly var
AstVarScope* const dlyVarp = rhsp->varScopep();
@@ -274,6 +280,12 @@ private:
LifePostElimVisitor{nodep};
}
virtual void visit(AstVarRef* nodep) override {
// Mark variables referenced outside _eval__nba
if (!m_inEvalNba) {
nodep->varScopep()->user1(true);
return;
}
// Consumption/generation of a variable,
const AstVarScope* const vscp = nodep->varScopep();
UASSERT_OBJ(vscp, nodep, "Scope not assigned");
@@ -288,6 +300,7 @@ private:
// The pre-assignment into the dly var should not count as its
// first write; we only want to consider reads and writes that
// would still happen if the dly var were eliminated.
if (!m_inEvalNba) iterateChildren(nodep);
}
virtual void visit(AstAssignPost* nodep) override {
// Don't record ASSIGNPOST in the read/write maps, record them in a
@@ -315,9 +328,11 @@ private:
}
virtual void visit(AstExecGraph* nodep) override {
// Treat the ExecGraph like a call to each mtask body
UASSERT_OBJ(!m_mtasksGraphp, nodep, "Cannot handle more than one AstExecGraph");
m_mtasksGraphp = nodep->depGraphp();
for (V3GraphVertex* mtaskVxp = m_mtasksGraphp->verticesBeginp(); mtaskVxp;
if (m_inEvalNba) {
UASSERT_OBJ(!m_mtasksGraphp, nodep, "Cannot handle more than one AstExecGraph");
m_mtasksGraphp = nodep->depGraphp();
}
for (V3GraphVertex* mtaskVxp = nodep->depGraphp()->verticesBeginp(); mtaskVxp;
mtaskVxp = mtaskVxp->verticesNextp()) {
const ExecMTask* const mtaskp = dynamic_cast<ExecMTask*>(mtaskVxp);
m_execMTaskp = mtaskp;
@@ -328,6 +343,8 @@ private:
}
virtual void visit(AstCFunc* nodep) override {
if (!m_tracingCall && !nodep->entryPoint()) return;
VL_RESTORER(m_inEvalNba);
if (nodep == m_evalNbap) m_inEvalNba = true;
m_tracingCall = false;
iterateChildren(nodep);
}
@@ -337,7 +354,10 @@ private:
public:
// CONSTRUCTORS
explicit LifePostDlyVisitor(AstNetlist* nodep) { iterate(nodep); }
explicit LifePostDlyVisitor(AstNetlist* netlistp)
: m_evalNbap{netlistp->evalNbap()} {
iterate(netlistp);
}
virtual ~LifePostDlyVisitor() override {
V3Stats::addStat("Optimizations, Lifetime postassign deletions", m_statAssnDel);
}