Split V3Order into further part and decouple various components (#4953)

Continuing the idea of decoupling the implementations of the various algorithms.

The main points:

-Move the former "processDomain" stuff, dealing with assigning combinational logic into the relevant sensitivity domains into V3OrderProcessDomains.cpp

-Move the parallel code construction in V3OrderParallel.cpp (Could combine this with some parts of V3Partition - those not called from V3Partition::finalize - but that's not for this patch).

-Move the serial code construction into V3OrderSerial.cpp

-Factored the very small common code between the parallel and serial code construction (processMoveOneLogic) into V3OrderCFuncEmitter.cpp
This commit is contained in:
Geza Lore 2024-03-09 12:43:09 +00:00 committed by GitHub
parent 5ee938fd1c
commit 5a69321be3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 1718 additions and 1597 deletions

View File

@ -123,7 +123,6 @@ set(HEADERS
V3Order.h
V3OrderInternal.h
V3OrderGraph.h
V3OrderMoveGraph.h
V3OrderMoveGraphBuilder.h
V3Os.h
V3PairingHeap.h
@ -278,6 +277,9 @@ set(COMMON_SOURCES
V3Options.cpp
V3Order.cpp
V3OrderGraphBuilder.cpp
V3OrderParallel.cpp
V3OrderProcessDomains.cpp
V3OrderSerial.cpp
V3Os.cpp
V3Param.cpp
V3Partition.cpp

View File

@ -266,6 +266,9 @@ RAW_OBJS_PCH_ASTNOMT = \
V3Name.o \
V3Order.o \
V3OrderGraphBuilder.o \
V3OrderParallel.o \
V3OrderProcessDomains.o \
V3OrderSerial.o \
V3Param.o \
V3Partition.o \
V3Premit.o \

View File

@ -323,7 +323,6 @@ public:
enum en : uint8_t {
// These must be in general -> most specific order, as we sort by it
// in V3Const::visit AstSenTree
ET_ILLEGAL,
// Involving a variable
ET_CHANGED, // Value changed
ET_BOTHEDGE, // POSEDGE | NEGEDGE (i.e.: 'edge' in Verilog)
@ -343,8 +342,6 @@ public:
enum en m_e;
bool clockedStmt() const {
static const bool clocked[] = {
false, // ET_ILLEGAL
true, // ET_CHANGED
true, // ET_BOTHEDGE
true, // ET_POSEDGE
@ -368,20 +365,19 @@ public:
case ET_BOTHEDGE: return ET_BOTHEDGE;
case ET_POSEDGE: return ET_NEGEDGE;
case ET_NEGEDGE: return ET_POSEDGE;
default: UASSERT_STATIC(0, "Inverting bad edgeType()");
default: UASSERT_STATIC(0, "Inverting bad edgeType()"); return ET_NEGEDGE;
}
return VEdgeType::ET_ILLEGAL;
}
const char* ascii() const {
static const char* const names[]
= {"%E-edge", "CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE",
"COMBO", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"};
= {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE",
"COMBO", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"};
return names[m_e];
}
const char* verilogKwd() const {
static const char* const names[]
= {"%E-edge", "[changed]", "edge", "posedge", "negedge", "[event]", "[true]",
"*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"};
= {"[changed]", "edge", "posedge", "negedge", "[event]", "[true]",
"*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"};
return names[m_e];
}
// Return true iff this and the other have mutually exclusive transitions
@ -397,8 +393,6 @@ public:
}
return false;
}
VEdgeType()
: m_e{ET_ILLEGAL} {}
// cppcheck-suppress noExplicitConstructor
constexpr VEdgeType(en _e)
: m_e{_e} {}

View File

@ -1514,7 +1514,6 @@ class AstSenItem final : public AstNode {
VEdgeType m_edgeType; // Edge type
public:
class Combo {}; // for constructor type-overload selection
class Illegal {}; // for constructor type-overload selection
class Static {}; // for constructor type-overload selection
class Initial {}; // for constructor type-overload selection
class Final {}; // for constructor type-overload selection
@ -1528,9 +1527,6 @@ public:
AstSenItem(FileLine* fl, Combo)
: ASTGEN_SUPER_SenItem(fl)
, m_edgeType{VEdgeType::ET_COMBO} {}
AstSenItem(FileLine* fl, Illegal)
: ASTGEN_SUPER_SenItem(fl)
, m_edgeType{VEdgeType::ET_ILLEGAL} {}
AstSenItem(FileLine* fl, Static)
: ASTGEN_SUPER_SenItem(fl)
, m_edgeType{VEdgeType::ET_STATIC} {}
@ -1562,7 +1558,6 @@ public:
bool isStatic() const { return edgeType() == VEdgeType::ET_STATIC; }
bool isInitial() const { return edgeType() == VEdgeType::ET_INITIAL; }
bool isFinal() const { return edgeType() == VEdgeType::ET_FINAL; }
bool isIllegal() const { return edgeType() == VEdgeType::ET_ILLEGAL; }
bool isNever() const { return edgeType() == VEdgeType::ET_NEVER; }
};
class AstSenTree final : public AstNode {

View File

@ -73,863 +73,89 @@
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Order.h"
#include "V3Const.h"
#include "V3EmitV.h"
#include "V3File.h"
#include "V3Graph.h"
#include "V3GraphStream.h"
#include "V3List.h"
#include "V3OrderInternal.h"
#include "V3OrderMoveGraph.h"
#include "V3OrderMoveGraphBuilder.h"
#include "V3Partition.h"
#include "V3PartitionGraph.h"
#include "V3Sched.h"
#include "V3SenTree.h"
#include "V3SplitVar.h"
#include "V3Stats.h"
#include <deque>
#include <iomanip>
#include <memory>
#include <sstream>
#include <unordered_map>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
class OrderProcess;
class OrderMoveDomScope final {
// Information stored for each unique loop, domain & scope trifecta
public:
V3ListEnt<OrderMoveDomScope*> m_readyDomScopeE; // List of next ready dom scope
V3List<OrderMoveVertex*> m_readyVertices; // Ready vertices with same domain & scope
private:
bool m_onReadyList = false; // True if DomScope is already on list of ready dom/scopes
const AstSenTree* const m_domainp; // Domain all vertices belong to
const AstScope* const m_scopep; // Scope all vertices belong to
using DomScopeKey = std::pair<const AstSenTree*, const AstScope*>;
using DomScopeMap = std::map<DomScopeKey, OrderMoveDomScope*>;
static DomScopeMap s_dsMap; // Structure registered for each dom/scope pairing
public:
OrderMoveDomScope(const AstSenTree* domainp, const AstScope* scopep)
: m_domainp{domainp}
, m_scopep{scopep} {}
OrderMoveDomScope* readyDomScopeNextp() const { return m_readyDomScopeE.nextp(); }
const AstSenTree* domainp() const { return m_domainp; }
const AstScope* scopep() const { return m_scopep; }
// Check the domScope is on ready list, add if not
void ready(OrderProcess* opp);
// Mark one vertex as finished, remove from ready list if done
void movedVertex(OrderProcess* opp, OrderMoveVertex* vertexp);
// STATIC MEMBERS (for lookup)
static void clear() {
for (const auto& itr : s_dsMap) delete itr.second;
s_dsMap.clear();
}
V3List<OrderMoveVertex*>& readyVertices() { return m_readyVertices; }
static OrderMoveDomScope* findCreate(const AstSenTree* domainp, const AstScope* scopep) {
const DomScopeKey key = std::make_pair(domainp, scopep);
const auto pair = s_dsMap.emplace(key, nullptr);
if (pair.second) pair.first->second = new OrderMoveDomScope{domainp, scopep};
return pair.first->second;
}
string name() const {
return string{"MDS:"} + " d=" + cvtToHex(domainp()) + " s=" + cvtToHex(scopep());
}
};
OrderMoveDomScope::DomScopeMap OrderMoveDomScope::s_dsMap;
std::ostream& operator<<(std::ostream& lhs, const OrderMoveDomScope& rhs) {
lhs << rhs.name();
return lhs;
}
// ######################################################################
// OrderMoveVertexMaker and related
class OrderMoveVertexMaker final
: public V3OrderMoveGraphBuilder<OrderMoveVertex>::MoveVertexMaker {
// MEMBERS
V3Graph* m_pomGraphp;
V3List<OrderMoveVertex*>* m_pomWaitingp;
public:
// CONSTRUCTORS
OrderMoveVertexMaker(V3Graph* pomGraphp, V3List<OrderMoveVertex*>* pomWaitingp)
: m_pomGraphp{pomGraphp}
, m_pomWaitingp{pomWaitingp} {}
// METHODS
OrderMoveVertex* makeVertexp(OrderLogicVertex* lvertexp, const OrderEitherVertex*,
const AstSenTree* domainp) override {
OrderMoveVertex* const resultp = new OrderMoveVertex{m_pomGraphp, lvertexp};
AstScope* const scopep = lvertexp ? lvertexp->scopep() : nullptr;
resultp->domScopep(OrderMoveDomScope::findCreate(domainp, scopep));
resultp->m_pomWaitingE.pushBack(*m_pomWaitingp, resultp);
return resultp;
}
private:
VL_UNCOPYABLE(OrderMoveVertexMaker);
};
class OrderMTaskMoveVertexMaker final
: public V3OrderMoveGraphBuilder<MTaskMoveVertex>::MoveVertexMaker {
V3Graph* m_pomGraphp;
public:
explicit OrderMTaskMoveVertexMaker(V3Graph* pomGraphp)
: m_pomGraphp{pomGraphp} {}
MTaskMoveVertex* makeVertexp(OrderLogicVertex* lvertexp, const OrderEitherVertex* varVertexp,
const AstSenTree* domainp) override {
return new MTaskMoveVertex{m_pomGraphp, lvertexp, varVertexp, domainp};
}
private:
VL_UNCOPYABLE(OrderMTaskMoveVertexMaker);
};
class OrderVerticesByDomainThenScope final {
PartPtrIdMap m_ids;
public:
bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const {
const MTaskMoveVertex* const l_vxp = static_cast<const MTaskMoveVertex*>(lhsp);
const MTaskMoveVertex* const r_vxp = static_cast<const MTaskMoveVertex*>(rhsp);
uint64_t l_id = m_ids.findId(l_vxp->domainp());
uint64_t r_id = m_ids.findId(r_vxp->domainp());
if (l_id < r_id) return true;
if (l_id > r_id) return false;
l_id = m_ids.findId(l_vxp->scopep());
r_id = m_ids.findId(r_vxp->scopep());
return l_id < r_id;
}
};
struct MTaskVxIdLessThan final {
// Sort vertex's, which must be AbstractMTask's, into a deterministic
// order by comparing their serial IDs.
bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const {
const AbstractMTask* const lmtaskp = static_cast<const AbstractLogicMTask*>(lhsp);
const AbstractMTask* const rmtaskp = static_cast<const AbstractLogicMTask*>(rhsp);
return lmtaskp->id() < rmtaskp->id();
}
};
//######################################################################
// OrderProcess class
class OrderProcess final {
// NODE STATE
// AstNode::user4 -> Used by V3Const::constifyExpensiveEdit
// STATE
OrderGraph& m_graph; // The ordering graph
// Map from Trigger reference AstSenItem to the original AstSenTree
const std::unordered_map<const AstSenItem*, const AstSenTree*>& m_trigToSen;
// This is a function provided by the invoker of the ordering that can provide additional
// sensitivity expression that when triggered indicates the passed AstVarScope might have
// changed external to the code being ordered.
const V3Order::ExternalDomainsProvider m_externalDomains;
SenTreeFinder m_finder; // Global AstSenTree manager
AstSenTree* const m_deleteDomainp; // Dummy AstSenTree indicating needs deletion
const string m_tag; // Substring to add to generated names
const bool m_slow; // Ordering slow code
std::vector<AstNode*> m_result; // The result nodes (~statements) in their sequential order
AstCFunc* m_pomNewFuncp = nullptr; // Current function being created
int m_pomNewStmts = 0; // Statements in function being created
V3Graph m_pomGraph; // Graph of logic elements to move
V3List<OrderMoveVertex*> m_pomWaiting; // List of nodes needing inputs to become ready
friend class OrderMoveDomScope;
V3List<OrderMoveDomScope*> m_pomReadyDomScope; // List of ready domain/scope pairs, by loopId
std::map<std::pair<AstNodeModule*, std::string>, unsigned> m_funcNums; // Function ordinals
VNDeleter m_deleter; // Used to delay deletion of nodes
// METHODS
void process(bool multiThreaded);
void processDomains();
void processDomainsIterate(OrderEitherVertex* vertexp);
void processEdgeReport();
// processMove* routines schedule serial execution
void processMove();
void processMoveClear();
void processMoveBuildGraph();
void processMovePrepReady();
void processMoveReadyOne(OrderMoveVertex* vertexp);
void processMoveDoneOne(OrderMoveVertex* vertexp);
void processMoveOne(OrderMoveVertex* vertexp, const OrderMoveDomScope* domScopep, int level);
AstActive* processMoveOneLogic(const OrderLogicVertex* lvertexp, AstCFunc*& newFuncpr,
int& newStmtsr);
// processMTask* routines schedule threaded execution
struct MTaskState final {
AstMTaskBody* m_mtaskBodyp = nullptr;
std::list<const OrderLogicVertex*> m_logics;
ExecMTask* m_execMTaskp = nullptr;
MTaskState() = default;
};
void processMTasks();
string cfuncName(AstNodeModule* modp, AstSenTree* domainp, AstScope* scopep,
AstNode* forWhatp) {
string name = "_" + m_tag;
name += domainp->isMulti() ? "_comb" : "_sequent";
name = name + "__" + scopep->nameDotless();
const unsigned funcnum = m_funcNums[{modp, name}]++;
name = name + "__" + cvtToStr(funcnum);
if (v3Global.opt.profCFuncs()) {
name += "__PROF__" + forWhatp->fileline()->profileFuncname();
}
return name;
}
// Make a domain that merges the two domains
AstSenTree* combineDomains(AstSenTree* ap, AstSenTree* bp) {
if (ap == m_deleteDomainp) return bp;
UASSERT_OBJ(bp != m_deleteDomainp, bp, "Should not be delete domain");
AstSenTree* const senTreep = ap->cloneTree(false);
senTreep->addSensesp(bp->sensesp()->cloneTree(true));
V3Const::constifyExpensiveEdit(senTreep); // Remove duplicates
senTreep->multi(true); // Comment that it was made from 2 domains
AstSenTree* const resultp = m_finder.getSenTree(senTreep);
VL_DO_DANGLING(senTreep->deleteTree(), senTreep); // getSenTree clones, so delete this
return resultp;
}
// Only for member initialization in constructor
static AstSenTree* makeDeleteDomainSenTree(FileLine* fl) {
return new AstSenTree{fl, new AstSenItem{fl, AstSenItem::Illegal{}}};
}
// CONSTRUCTOR
OrderProcess(AstNetlist* netlistp, OrderGraph& graph,
const std::unordered_map<const AstSenItem*, const AstSenTree*>& trigToSen,
const string& tag, bool slow,
const V3Order::ExternalDomainsProvider& externalDomains)
: m_graph{graph}
, m_trigToSen{trigToSen}
, m_externalDomains{externalDomains}
, m_finder{netlistp}
, m_deleteDomainp{makeDeleteDomainSenTree(netlistp->fileline())}
, m_tag{tag}
, m_slow{slow} {
m_deleter.pushDeletep(m_deleteDomainp);
}
~OrderProcess() = default;
public:
// Order the logic
static std::vector<AstNode*>
main(AstNetlist* netlistp, OrderGraph& graph,
const std::unordered_map<const AstSenItem*, const AstSenTree*>& trigToSen,
const string& tag, bool parallel, bool slow,
const V3Order::ExternalDomainsProvider& externalDomains) {
OrderProcess visitor{netlistp, graph, trigToSen, tag, slow, externalDomains};
visitor.process(parallel);
return std::move(visitor.m_result);
}
};
//######################################################################
// OrderMoveDomScope methods
// Check the domScope is on ready list, add if not
void OrderMoveDomScope::ready(OrderProcess* opp) {
if (!m_onReadyList) {
m_onReadyList = true;
m_readyDomScopeE.pushBack(opp->m_pomReadyDomScope, this);
}
}
// Mark one vertex as finished, remove from ready list if done
void OrderMoveDomScope::movedVertex(OrderProcess* opp, OrderMoveVertex* vertexp) {
UASSERT_OBJ(m_onReadyList, vertexp,
"Moving vertex from ready when nothing was on que as ready.");
if (m_readyVertices.empty()) { // Else more work to get to later
m_onReadyList = false;
m_readyDomScopeE.unlink(opp->m_pomReadyDomScope, this);
}
}
//######################################################################
void OrderProcess::processDomains() {
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
UASSERT(itp, "Vertex should not be null");
OrderEitherVertex* const vertexp = itp->as<OrderEitherVertex>();
processDomainsIterate(vertexp);
}
}
void OrderProcess::processDomainsIterate(OrderEitherVertex* vertexp) {
// The graph routines have already sorted the vertexes and edges into best->worst order
// Assign clock domains to each signal.
// Sequential logic is forced into the same sequential domain.
// Combo logic may be pushed into a seq domain if all its inputs are the same domain,
// else, if all inputs are from flops, it's end-of-sequential code
// else, it's full combo code
if (vertexp->domainp()) return; // Already processed, or sequential logic
UINFO(5, " pdi: " << vertexp << endl);
AstSenTree* domainp = nullptr;
if (OrderLogicVertex* const lvtxp = vertexp->cast<OrderLogicVertex>()) {
domainp = lvtxp->hybridp();
}
std::vector<AstSenTree*> externalDomainps;
for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) {
OrderEitherVertex* const fromVertexp = static_cast<OrderEitherVertex*>(edgep->fromp());
if (edgep->weight() && fromVertexp->domainMatters()) {
AstSenTree* fromDomainp = fromVertexp->domainp();
UASSERT(!fromDomainp->hasCombo(), "There should be no need for combinational domains");
if (OrderVarVertex* const varVtxp = fromVertexp->cast<OrderVarVertex>()) {
AstVarScope* const vscp = varVtxp->vscp();
// Add in any external domains
externalDomainps.clear();
m_externalDomains(vscp, externalDomainps);
for (AstSenTree* const externalDomainp : externalDomainps) {
UASSERT_OBJ(!externalDomainp->hasCombo(), vscp,
"There should be no need for combinational domains");
fromDomainp = combineDomains(fromDomainp, externalDomainp);
}
}
// Irrelevant input vertex (never triggered)
if (fromDomainp == m_deleteDomainp) continue;
// First input to this vertex
if (!domainp) domainp = fromDomainp;
// Make a domain that merges the two domains
if (domainp != fromDomainp) domainp = combineDomains(domainp, fromDomainp);
}
}
// If nothing triggers this vertex, we can delete the corresponding logic
if (!domainp) domainp = m_deleteDomainp;
// Set the domain of the vertex
vertexp->domainp(domainp);
UINFO(5, " done d=" << cvtToHex(vertexp->domainp())
<< (domainp == m_deleteDomainp ? " [DEL]"
: vertexp->domainp()->hasCombo() ? " [COMB]"
: vertexp->domainp()->isMulti() ? " [MULT]"
: "")
<< " " << vertexp << endl);
}
//######################################################################
// OrderProcess - Move graph construction
void OrderProcess::processEdgeReport() {
// Make report of all signal names and what clock edges they have
const string filename = v3Global.debugFilename(m_tag + "_order_edges.txt");
const std::unique_ptr<std::ofstream> logp{V3File::new_ofstream(filename)};
if (logp->fail()) v3fatal("Can't write " << filename);
std::deque<string> report;
// Rebuild the trigger to original AstSenTree map using equality key comparison, as
// merging domains have created new AstSenTree instances which are not in the map
std::unordered_map<VNRef<const AstSenItem>, const AstSenTree*> trigToSen;
for (const auto& pair : m_trigToSen) trigToSen.emplace(*pair.first, pair.second);
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
if (OrderVarVertex* const vvertexp = itp->cast<OrderVarVertex>()) {
string name(vvertexp->vscp()->prettyName());
if (itp->is<OrderVarPreVertex>()) {
name += " {PRE}";
} else if (itp->is<OrderVarPostVertex>()) {
name += " {POST}";
} else if (itp->is<OrderVarPordVertex>()) {
name += " {PORD}";
}
std::ostringstream os;
os.setf(std::ios::left);
os << " " << cvtToHex(vvertexp->vscp()) << " " << std::setw(50) << name << " ";
AstSenTree* const senTreep = vvertexp->domainp();
if (senTreep == m_deleteDomainp) {
os << "DELETED";
} else {
for (AstSenItem* senItemp = senTreep->sensesp(); senItemp;
senItemp = VN_AS(senItemp->nextp(), SenItem)) {
if (senItemp != senTreep->sensesp()) os << " or ";
const auto it = trigToSen.find(*senItemp);
if (it != trigToSen.end()) {
V3EmitV::verilogForTree(it->second, os);
} else {
V3EmitV::verilogForTree(senItemp, os);
}
}
}
report.push_back(os.str());
}
}
*logp << "Signals and their clock domains:\n";
stable_sort(report.begin(), report.end());
for (const string& i : report) *logp << i << '\n';
}
void OrderProcess::processMoveClear() {
OrderMoveDomScope::clear();
m_pomWaiting.reset();
m_pomReadyDomScope.reset();
m_pomGraph.clear();
}
void OrderProcess::processMoveBuildGraph() {
// Build graph of only vertices
UINFO(5, " MoveBuildGraph\n");
processMoveClear();
// Vertex::user->OrderMoveVertex*, last edge added or nullptr=none
m_pomGraph.userClearVertices();
OrderMoveVertexMaker createOrderMoveVertex(&m_pomGraph, &m_pomWaiting);
V3OrderMoveGraphBuilder<OrderMoveVertex> serialPMBG(&m_graph, &m_pomGraph, m_trigToSen,
&createOrderMoveVertex);
serialPMBG.build();
}
//######################################################################
// OrderVisitor - Moving
void OrderProcess::processMove() {
// The graph routines have already sorted the vertexes and edges into best->worst order
// Make a new waiting graph with only OrderLogicVertex's
// (Order is preserved in the recreation so the sorting is preserved)
// Move any node with all inputs ready to a "ready" graph mapped by domain and then scope
// While waiting graph ! empty (and also known: something in ready graph)
// For all scopes in domain of top ready vertex
// For all vertexes in domain&scope of top ready vertex
// Make ordered activation block for this module
// Add that new activation to the list of calls to make.
// Move logic to ordered active
// Any children that have all inputs now ready move from waiting->ready graph
// (This may add nodes the for loop directly above needs to detext)
processMovePrepReady();
// New domain... another loop
UINFO(5, " MoveIterate\n");
while (!m_pomReadyDomScope.empty()) {
// Start with top node on ready list's domain & scope
OrderMoveDomScope* domScopep = m_pomReadyDomScope.begin();
OrderMoveVertex* const topVertexp
= domScopep->readyVertices().begin(); // lintok-begin-on-ref
UASSERT(topVertexp, "domScope on ready list without any nodes ready under it");
// Work on all scopes ready inside this domain
while (domScopep) {
UINFO(6, " MoveDomain l=" << domScopep->domainp() << endl);
// Process all nodes ready under same domain & scope
m_pomNewFuncp = nullptr;
while (OrderMoveVertex* vertexp
= domScopep->readyVertices().begin()) { // lintok-begin-on-ref
processMoveOne(vertexp, domScopep, 1);
}
// Done with scope/domain pair, pick new scope under same domain, or nullptr if none
// left
OrderMoveDomScope* domScopeNextp = nullptr;
for (OrderMoveDomScope* huntp = m_pomReadyDomScope.begin(); huntp;
huntp = huntp->readyDomScopeNextp()) {
if (huntp->domainp() == domScopep->domainp()) {
domScopeNextp = huntp;
break;
}
}
domScopep = domScopeNextp;
}
}
UASSERT(m_pomWaiting.empty(),
"Didn't converge; nodes waiting, none ready, perhaps some input activations lost.");
// Cleanup memory
processMoveClear();
}
void OrderProcess::processMovePrepReady() {
// Make list of ready nodes
UINFO(5, " MovePrepReady\n");
for (OrderMoveVertex* vertexp = m_pomWaiting.begin(); vertexp;) {
OrderMoveVertex* const nextp = vertexp->pomWaitingNextp();
if (vertexp->isWait() && vertexp->inEmpty()) processMoveReadyOne(vertexp);
vertexp = nextp;
}
}
void OrderProcess::processMoveReadyOne(OrderMoveVertex* vertexp) {
// Recursive!
// Move one node from waiting to ready list
vertexp->setReady();
// Remove node from waiting list
vertexp->m_pomWaitingE.unlink(m_pomWaiting, vertexp);
if (vertexp->logicp()) {
// Add to ready list (indexed by domain and scope)
vertexp->m_readyVerticesE.pushBack(vertexp->domScopep()->m_readyVertices, vertexp);
vertexp->domScopep()->ready(this);
} else {
// vertexp represents a non-logic vertex.
// Recurse to mark its following neighbors ready.
processMoveDoneOne(vertexp);
}
}
void OrderProcess::processMoveDoneOne(OrderMoveVertex* vertexp) {
// Move one node from ready to completion
vertexp->setMoved();
// Unlink from ready lists
if (vertexp->logicp()) {
vertexp->m_readyVerticesE.unlink(vertexp->domScopep()->m_readyVertices, vertexp);
vertexp->domScopep()->movedVertex(this, vertexp);
}
// Don't need to add it to another list, as we're done with it
// Mark our outputs as one closer to ready
for (V3GraphEdge *edgep = vertexp->outBeginp(), *nextp; edgep; edgep = nextp) {
nextp = edgep->outNextp();
OrderMoveVertex* const toVertexp = static_cast<OrderMoveVertex*>(edgep->top());
UINFO(9, " Clear to " << (toVertexp->inEmpty() ? "[EMP] " : " ") << toVertexp
<< endl);
// Delete this edge
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
if (toVertexp->inEmpty()) {
// If destination node now has all inputs resolved; recurse to move that vertex
// This is thus depth first (before width) which keeps the
// resulting executable's d-cache happy.
processMoveReadyOne(toVertexp);
}
}
}
void OrderProcess::processMoveOne(OrderMoveVertex* vertexp, const OrderMoveDomScope* domScopep,
int level) {
UASSERT_OBJ(vertexp->domScopep() == domScopep, vertexp, "Domain mismatch; list misbuilt?");
const OrderLogicVertex* const lvertexp = vertexp->logicp();
const AstScope* const scopep = lvertexp->scopep();
UINFO(5, " POSmove l" << std::setw(3) << level << " d=" << cvtToHex(lvertexp->domainp())
<< " s=" << cvtToHex(scopep) << " " << lvertexp << endl);
AstActive* const newActivep
= processMoveOneLogic(lvertexp, m_pomNewFuncp /*ref*/, m_pomNewStmts /*ref*/);
if (newActivep) m_result.push_back(newActivep);
processMoveDoneOne(vertexp);
}
AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp,
AstCFunc*& newFuncpr, int& newStmtsr) {
AstActive* activep = nullptr;
AstScope* const scopep = lvertexp->scopep();
AstSenTree* const domainp = lvertexp->domainp();
AstNode* nodep = lvertexp->nodep();
AstNodeModule* const modp = scopep->modp();
UASSERT(modp, "nullptr");
// We are move the logic into a CFunc, so unlink it from the AstActive
nodep->unlinkFrBack();
// Process procedures per statement (unless profCFuncs), so we can split CFuncs within
// procedures. Everything else is handled in one go
bool suspendable = false;
bool needProcess = false;
bool slow = m_slow;
if (AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure)) {
suspendable = procp->isSuspendable();
needProcess = procp->needProcess();
if (suspendable) slow = slow && !VN_IS(procp, Always);
nodep = procp->stmtsp();
m_deleter.pushDeletep(procp);
}
// Put suspendable processes into individual functions on their own
if (suspendable) newFuncpr = nullptr;
// When profCFuncs, create a new function for all logic block
if (v3Global.opt.profCFuncs()) newFuncpr = nullptr;
while (nodep) {
// Split the CFunc if too large (but not when profCFuncs)
if (!suspendable && !v3Global.opt.profCFuncs()
&& (v3Global.opt.outputSplitCFuncs()
&& v3Global.opt.outputSplitCFuncs() < newStmtsr)) {
// Put every statement into a unique function to ease profiling or reduce function
// size
newFuncpr = nullptr;
}
if (!newFuncpr && domainp != m_deleteDomainp) {
const string name = cfuncName(modp, domainp, scopep, nodep);
newFuncpr
= new AstCFunc{nodep->fileline(), name, scopep, suspendable ? "VlCoroutine" : ""};
if (needProcess) newFuncpr->setNeedProcess();
newFuncpr->isStatic(false);
newFuncpr->isLoose(true);
newFuncpr->slow(slow);
newStmtsr = 0;
scopep->addBlocksp(newFuncpr);
// Create top call to it
AstCCall* const callp = new AstCCall{nodep->fileline(), newFuncpr};
callp->dtypeSetVoid();
// Where will we be adding the call?
AstActive* const newActivep = new AstActive{nodep->fileline(), name, domainp};
newActivep->addStmtsp(callp->makeStmt());
if (!activep) {
activep = newActivep;
} else {
activep->addNext(newActivep);
}
UINFO(6, " New " << newFuncpr << endl);
}
AstNode* const nextp = nodep->nextp();
// When processing statements in a procedure, unlink the current statement
if (nodep->backp()) nodep->unlinkFrBack();
if (domainp == m_deleteDomainp) {
VL_DO_DANGLING(m_deleter.pushDeletep(nodep), nodep);
} else {
newFuncpr->addStmtsp(nodep);
// Add in the number of nodes we're adding
if (v3Global.opt.outputSplitCFuncs()) newStmtsr += nodep->nodeCount();
}
nodep = nextp;
}
// Put suspendable processes into individual functions on their own
if (suspendable) newFuncpr = nullptr;
return activep;
}
void OrderProcess::processMTasks() {
// For nondeterminism debug:
V3Partition::hashGraphDebug(&m_graph, "V3Order's m_graph");
// We already produced a graph of every var, input, and logic
// block and all dependencies; this is 'm_graph'.
//
// Now, starting from m_graph, make a slightly-coarsened graph representing
// only logic, and discarding edges we know we can ignore.
// This is quite similar to the 'm_pomGraph' of the serial code gen:
V3Graph logicGraph;
{
OrderMTaskMoveVertexMaker create_mtask_vertex(&logicGraph);
V3OrderMoveGraphBuilder<MTaskMoveVertex> mtask_pmbg(&m_graph, &logicGraph, m_trigToSen,
&create_mtask_vertex);
mtask_pmbg.build();
}
// Needed? We do this for m_pomGraph in serial mode, so do it here too:
logicGraph.removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue);
// Partition logicGraph into LogicMTask's. The partitioner will annotate
// each vertex in logicGraph with a 'color' which is really an mtask ID
// in this context.
V3Partition partitioner{&m_graph, &logicGraph};
V3Graph mtasks;
partitioner.go(&mtasks);
std::unordered_map<unsigned /*mtask id*/, MTaskState> mtaskStates;
// Iterate through the entire logicGraph. For each logic node,
// attach it to a per-MTask ordered list of logic nodes.
// This is the order we'll execute logic nodes within the MTask.
//
// MTasks may span scopes and domains, so sort by both here:
GraphStream<OrderVerticesByDomainThenScope> emit_logic{&logicGraph};
const V3GraphVertex* moveVxp;
while ((moveVxp = emit_logic.nextp())) {
const MTaskMoveVertex* const movep = static_cast<const MTaskMoveVertex*>(moveVxp);
// Only care about logic vertices
if (!movep->logicp()) continue;
const unsigned mtaskId = movep->color();
UASSERT(mtaskId > 0, "Every MTaskMoveVertex should have an mtask assignment >0");
// Add this logic to the per-mtask order
mtaskStates[mtaskId].m_logics.push_back(movep->logicp());
// Since we happen to be iterating over every logic node,
// take this opportunity to annotate each AstVar with the id's
// of mtasks that consume it and produce it. We'll use this
// information in V3EmitC when we lay out var's in memory.
const OrderLogicVertex* const logicp = movep->logicp();
for (const V3GraphEdge* edgep = logicp->inBeginp(); edgep; edgep = edgep->inNextp()) {
const OrderVarVertex* const pre_varp = edgep->fromp()->cast<const OrderVarVertex>();
if (!pre_varp) continue;
AstVar* const varp = pre_varp->vscp()->varp();
// varp depends on logicp, so logicp produces varp,
// and vice-versa below
varp->addProducingMTaskId(mtaskId);
}
for (const V3GraphEdge* edgep = logicp->outBeginp(); edgep; edgep = edgep->outNextp()) {
const OrderVarVertex* const post_varp = edgep->top()->cast<const OrderVarVertex>();
if (!post_varp) continue;
AstVar* const varp = post_varp->vscp()->varp();
varp->addConsumingMTaskId(mtaskId);
}
// TODO? We ignore IO vars here, so those will have empty mtask
// signatures. But we could also give those mtask signatures.
}
// Create the AstExecGraph node which represents the execution
// of the MTask graph.
FileLine* const rootFlp = v3Global.rootp()->fileline();
AstExecGraph* const execGraphp = new AstExecGraph{rootFlp, m_tag};
m_result.push_back(execGraphp);
// Create CFuncs and bodies for each MTask.
GraphStream<MTaskVxIdLessThan> emit_mtasks(&mtasks);
const V3GraphVertex* mtaskVxp;
while ((mtaskVxp = emit_mtasks.nextp())) {
const AbstractLogicMTask* const mtaskp = static_cast<const AbstractLogicMTask*>(mtaskVxp);
// Create a body for this mtask
AstMTaskBody* const bodyp = new AstMTaskBody{rootFlp};
MTaskState& state = mtaskStates[mtaskp->id()];
state.m_mtaskBodyp = bodyp;
// Create leaf CFunc's to run this mtask's logic,
// and create a set of AstActive's to call those CFuncs.
// Add the AstActive's into the AstMTaskBody.
const AstSenTree* last_domainp = nullptr;
AstCFunc* leafCFuncp = nullptr;
int leafStmts = 0;
for (const OrderLogicVertex* logicp : state.m_logics) {
if (logicp->domainp() != last_domainp) {
// Start a new leaf function.
leafCFuncp = nullptr;
}
last_domainp = logicp->domainp();
AstActive* const newActivep
= processMoveOneLogic(logicp, leafCFuncp /*ref*/, leafStmts /*ref*/);
if (newActivep) bodyp->addStmtsp(newActivep);
}
// Translate the LogicMTask graph into the corresponding ExecMTask
// graph, which will outlive V3Order and persist for the remainder
// of verilator's processing.
// - The LogicMTask graph points to MTaskMoveVertex's
// and OrderLogicVertex's which are ephemeral to V3Order.
// - The ExecMTask graph and the AstMTaskBody's produced here
// persist until code generation time.
V3Graph* const depGraphp = execGraphp->depGraphp();
state.m_execMTaskp = new ExecMTask{depGraphp, bodyp, mtaskp->id()};
// Cross-link each ExecMTask and MTaskBody
// Q: Why even have two objects?
// A: One is an AstNode, the other is a GraphVertex,
// to combine them would involve multiple inheritance...
state.m_mtaskBodyp->execMTaskp(state.m_execMTaskp);
for (V3GraphEdge* inp = mtaskp->inBeginp(); inp; inp = inp->inNextp()) {
const V3GraphVertex* fromVxp = inp->fromp();
const AbstractLogicMTask* const fromp
= static_cast<const AbstractLogicMTask*>(fromVxp);
const MTaskState& fromState = mtaskStates[fromp->id()];
new V3GraphEdge{depGraphp, fromState.m_execMTaskp, state.m_execMTaskp, 1};
}
execGraphp->addMTaskBodiesp(bodyp);
}
}
//######################################################################
// OrderVisitor - Top processing
void OrderProcess::process(bool multiThreaded) {
void V3Order::orderOrderGraph(OrderGraph& graph, const std::string& tag) {
// Dump data
if (dumpGraphLevel()) m_graph.dumpDotFilePrefixed(m_tag + "_orderg_pre");
if (dumpGraphLevel()) graph.dumpDotFilePrefixed(tag + "_orderg_pre");
// Break cycles. Each strongly connected subgraph (including cutable
// edges) will have its own color, and corresponds to a loop in the
// original graph. However the new graph will be acyclic (the removed
// edges are actually still there, just with weight 0).
UINFO(2, " Acyclic and Order...\n");
m_graph.acyclic(&V3GraphEdge::followAlwaysTrue);
if (dumpGraphLevel()) m_graph.dumpDotFilePrefixed(m_tag + "_orderg_acyc");
// Break cycles. Note that the OrderGraph only contains cuttable cycles
// (soft constraints). Actual logic loops must have been eliminated by
// the introduction of Hybid sensitivity expressions, before invoking
// ordering (e.g. in V3SchedAcyclic).
graph.acyclic(&V3GraphEdge::followAlwaysTrue);
if (dumpGraphLevel()) graph.dumpDotFilePrefixed(tag + "_orderg_acyc");
// Assign ranks so we know what to follow
// Then, sort vertices and edges by that ordering
m_graph.order();
if (dumpGraphLevel()) m_graph.dumpDotFilePrefixed(m_tag + "_orderg_order");
// Assign logic vertices to new domains
UINFO(2, " Domains...\n");
processDomains();
if (dumpGraphLevel()) m_graph.dumpDotFilePrefixed(m_tag + "_orderg_domain");
if (dumpLevel()) processEdgeReport();
if (!multiThreaded) {
UINFO(2, " Construct Move Graph...\n");
processMoveBuildGraph();
// Different prefix (ordermv) as it's not the same graph
if (dumpGraphLevel() >= 4) m_pomGraph.dumpDotFilePrefixed(m_tag + "_ordermv_start");
m_pomGraph.removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue);
if (dumpGraphLevel() >= 4) m_pomGraph.dumpDotFilePrefixed(m_tag + "_ordermv_simpl");
UINFO(2, " Move...\n");
processMove();
} else {
UINFO(2, " Set up mtasks...\n");
processMTasks();
}
// Dump data
if (dumpGraphLevel()) m_graph.dumpDotFilePrefixed(m_tag + "_orderg_done");
// Assign ranks so we know what to follow, then sort vertices and edges by that ordering
graph.order();
if (dumpGraphLevel()) graph.dumpDotFilePrefixed(tag + "_orderg_order");
}
//######################################################################
AstCFunc* V3Order::order(AstNetlist* netlistp, //
const std::vector<V3Sched::LogicByScope*>& logic, //
const std::unordered_map<const AstSenItem*, const AstSenTree*>& trigToSen,
const V3Order::TrigToSenMap& trigToSen,
const string& tag, //
bool parallel, //
bool slow, //
const ExternalDomainsProvider& externalDomains) {
// Order the code
const std::unique_ptr<OrderGraph> graph = buildOrderGraph(netlistp, logic, trigToSen);
const auto& nodeps
= OrderProcess::main(netlistp, *graph, trigToSen, tag, parallel, slow, externalDomains);
FileLine* const flp = netlistp->fileline();
// Create the result function
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
FileLine* const flp = netlistp->fileline();
AstCFunc* const funcp = new AstCFunc{flp, "_eval_" + tag, scopeTopp, ""};
funcp->dontCombine(true);
funcp->isStatic(false);
funcp->isLoose(true);
funcp->slow(slow);
funcp->isConst(false);
funcp->declPrivate(true);
scopeTopp->addBlocksp(funcp);
AstCFunc* const funcp = [&]() {
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
AstCFunc* const resp = new AstCFunc{flp, "_eval_" + tag, scopeTopp, ""};
resp->dontCombine(true);
resp->isStatic(false);
resp->isLoose(true);
resp->slow(slow);
resp->isConst(false);
resp->declPrivate(true);
scopeTopp->addBlocksp(resp);
return resp;
}();
if (v3Global.opt.profExec()) {
funcp->addStmtsp(new AstCStmt{flp, "VL_EXEC_TRACE_ADD_RECORD(vlSymsp).sectionPush(\"func "
+ tag + "\");\n"});
}
// Add ordered statements to the result function
for (AstNode* const nodep : nodeps) funcp->addStmtsp(nodep);
// Build the OrderGraph
const std::unique_ptr<OrderGraph> graph = buildOrderGraph(netlistp, logic, trigToSen);
// Order it
orderOrderGraph(*graph, tag);
// Assign sensitivity domains to combinational logic
processDomains(netlistp, *graph, tag, trigToSen, externalDomains);
if (parallel) {
// Construct the parallel ExecGraph
AstExecGraph* const execGraphp = createParallel(*graph, tag, trigToSen, slow);
// Add the ExecGraph to the result function.
funcp->addStmtsp(execGraphp);
} else {
// Construct the serial code
const std::vector<AstActive*> activeps = createSerial(*graph, tag, trigToSen, slow);
// Add the resulting Active blocks to the result function
for (AstNode* const nodep : activeps) funcp->addStmtsp(nodep);
}
// Dump data
if (dumpGraphLevel()) graph->dumpDotFilePrefixed(tag + "_orderg_done");
// Dispose of the remnants of the inputs
for (auto* const lbsp : logic) lbsp->deleteActives();
if (v3Global.opt.profExec()) {
funcp->addStmtsp(new AstCStmt{flp, "VL_EXEC_TRACE_ADD_RECORD(vlSymsp).sectionPop();\n"});
}
// Dispose of the remnants of the inputs
for (auto* const lbsp : logic) lbsp->deleteActives();
// Done
return funcp;
}

View File

@ -40,12 +40,15 @@ struct LogicByScope;
namespace V3Order {
// Callable to add extra external Triggers to a variable
using ExternalDomainsProvider = std::function<void(const AstVarScope*, std::vector<AstSenTree*>&)>;
// Map from Trigger expression to original Sensitivity tree
using TrigToSenMap = std::unordered_map<const AstSenItem*, const AstSenTree*>;
AstCFunc* order(
AstNetlist* netlistp, //
const std::vector<V3Sched::LogicByScope*>& logic, //
const std::unordered_map<const AstSenItem*, const AstSenTree*>& trigToSen,
const TrigToSenMap& trigToSen, //
const string& tag, //
bool parallel, //
bool slow, //

163
src/V3OrderCFuncEmitter.h Normal file
View File

@ -0,0 +1,163 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Block code ordering
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-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
//
//*************************************************************************
//
// Class used to construct AstCFuncs from a sequence of OrderLogicVertex's
//
//*************************************************************************
#ifndef VERILATOR_V3ORDERCFUNCEMITTER_H_
#define VERILATOR_V3ORDERCFUNCEMITTER_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3Ast.h"
#include "V3Graph.h"
#include "V3OrderGraph.h"
#include <limits>
#include <map>
#include <vector>
class V3OrderCFuncEmitter final {
// Name component to add to function - must be unique
const std::string m_tag;
// True if creating slow functions
const bool m_slow;
// Whether to split functions
const bool m_split = v3Global.opt.outputSplitCFuncs();
// Size of code emitted so in the current function - for splitting
size_t m_size = 0;
// Maximum size of code per function
const size_t m_splitSize = []() -> size_t {
// Don't want to split with procCFuncs
if (v3Global.opt.profCFuncs()) return std::numeric_limits<size_t>::max();
// Otherwise split at the specified size (- 1, so we can do >= comparison)
if (const size_t limit = v3Global.opt.outputSplitCFuncs()) return limit - 1;
return std::numeric_limits<size_t>::max();
}();
// Current function being populated
AstCFunc* m_funcp = nullptr;
// Function ordinals to ensure unique names
std::map<std::pair<AstNodeModule*, std::string>, unsigned> m_funcNums;
// The result Active blocks that must be invoked to run the code in the order it was emitted
std::vector<AstActive*> m_activeps;
// Create a unique name for a new function
std::string cfuncName(FileLine* flp, AstScope* scopep, AstNodeModule* modp,
AstSenTree* domainp) {
std::string name = "_" + m_tag;
name += domainp->isMulti() ? "_comb" : "_sequent";
name += "__" + scopep->nameDotless();
name += "__" + std::to_string(m_funcNums[{modp, name}]++);
if (v3Global.opt.profCFuncs()) name += "__PROF__" + flp->profileFuncname();
return name;
}
public:
// CONSTRUCTOR
V3OrderCFuncEmitter(const std::string& tag, bool slow)
: m_tag{tag}
, m_slow{slow} {}
VL_UNCOPYABLE(V3OrderCFuncEmitter);
VL_UNMOVABLE(V3OrderCFuncEmitter);
// Force the creation of a new function
void forceNewFunction() {
m_size = 0;
m_funcp = nullptr;
}
// Retrieve Active block, which when executed will call the constructed functions
std::vector<AstActive*> getAndClearActiveps() {
forceNewFunction();
return std::move(m_activeps);
}
// Emit one logic vertex
void emitLogic(const OrderLogicVertex* lVtxp) {
// Sensitivity domain of logic we are emitting
AstSenTree* const domainp = lVtxp->domainp();
// We are move the logic into a CFunc, so unlink it from the input AstActive
AstNode* const logicp = lVtxp->nodep()->unlinkFrBack();
// If the logic is a procedure, we need to do a few special things
AstNodeProcedure* const procp = VN_CAST(logicp, NodeProcedure);
// Some properties to consider
const bool suspendable = procp && procp->isSuspendable();
const bool needProcess = procp && procp->needProcess();
// TODO: This is a bit muddy: 'initial forever @(posedge clk) begin ... end' is a fancy
// way of saying always @(posedge clk), so it might be quite hot...
// Also, if m_funcp is slow, but this one isn't we should force a new function
const bool slow = m_slow && !(suspendable && VN_IS(procp, Always));
// Put suspendable processes into individual functions on their own
if (suspendable) forceNewFunction();
// When profCFuncs, create a new function for each logic vertex
if (v3Global.opt.profCFuncs()) forceNewFunction();
// If the new domain is different, force a new function as it needs to be called separately
if (!m_activeps.empty() && m_activeps.back()->sensesp() != domainp) forceNewFunction();
// Process procedures per statement, so we can split CFuncs within procedures.
// Everything else is handled as a unit.
AstNode* const headp = [&]() -> AstNode* {
if (!procp) return logicp; // Not a procedure, handle as a unit
AstNode* const stmtsp = procp->stmtsp();
if (stmtsp) stmtsp->unlinkFrBackWithNext();
// Procedure is no longer needed and can be deleted right now
VL_DO_DANGLING(procp->deleteTree(), procp);
return stmtsp;
}();
// Process each statement in the list starting at headp
for (AstNode *currp = headp, *nextp; currp; currp = nextp) {
nextp = currp->nextp();
// Unlink the current statement from the next statement (if any)
if (nextp) nextp->unlinkFrBackWithNext();
// Split the function if too large, but don't split suspendable processes
if (!suspendable && m_size >= m_splitSize) forceNewFunction();
// Create a new function if we don't have a current one
if (!m_funcp) {
UASSERT_OBJ(!m_size, currp, "Should have used forceNewFunction");
FileLine* const flp = currp->fileline();
AstScope* const scopep = lVtxp->scopep();
AstNodeModule* const modp = scopep->modp();
const std::string name = cfuncName(flp, scopep, modp, domainp);
m_funcp = new AstCFunc{flp, name, scopep, suspendable ? "VlCoroutine" : ""};
if (needProcess) m_funcp->setNeedProcess();
m_funcp->isStatic(false);
m_funcp->isLoose(true);
m_funcp->slow(slow);
scopep->addBlocksp(m_funcp);
// Create call to the new functino
AstCCall* const callp = new AstCCall{flp, m_funcp};
callp->dtypeSetVoid();
// Call it under an AstActive with the same sensitivity
if (m_activeps.empty() || m_activeps.back()->sensesp() != domainp) {
m_activeps.emplace_back(new AstActive{flp, name, domainp});
}
m_activeps.back()->addStmtsp(callp->makeStmt());
}
// Add the code to the current function
m_funcp->addStmtsp(currp);
// If splitting, add in the size of the code we just added
if (m_split) m_size += currp->nodeCount();
}
// Put suspendable processes into individual functions on their own
if (suspendable) forceNewFunction();
}
};
#endif // Guard

View File

@ -92,7 +92,7 @@ class OrderGraphBuilder final : public VNVisitor {
OrderLogicVertex* m_logicVxp = nullptr; // Current logic block being analyzed
// Map from Trigger reference AstSenItem to the original AstSenTree
const std::unordered_map<const AstSenItem*, const AstSenTree*>& m_trigToSen;
const V3Order::TrigToSenMap& m_trigToSen;
// Current AstScope being processed
AstScope* m_scopep = nullptr;
@ -363,7 +363,7 @@ class OrderGraphBuilder final : public VNVisitor {
// CONSTRUCTOR
OrderGraphBuilder(AstNetlist* /*nodep*/, const std::vector<V3Sched::LogicByScope*>& coll,
const std::unordered_map<const AstSenItem*, const AstSenTree*>& trigToSen)
const V3Order::TrigToSenMap& trigToSen)
: m_trigToSen{trigToSen} {
// Build the graph
for (const V3Sched::LogicByScope* const lbsp : coll) {
@ -379,16 +379,16 @@ class OrderGraphBuilder final : public VNVisitor {
public:
// Process the netlist and return the constructed ordering graph. It's 'process' because
// this visitor does change the tree (removes some nodes related to DPI export trigger).
static std::unique_ptr<OrderGraph>
apply(AstNetlist* nodep, const std::vector<V3Sched::LogicByScope*>& coll,
const std::unordered_map<const AstSenItem*, const AstSenTree*>& trigToSen) {
static std::unique_ptr<OrderGraph> apply(AstNetlist* nodep,
const std::vector<V3Sched::LogicByScope*>& coll,
const V3Order::TrigToSenMap& trigToSen) {
return std::unique_ptr<OrderGraph>{OrderGraphBuilder{nodep, coll, trigToSen}.m_graphp};
}
};
std::unique_ptr<OrderGraph> V3Order::buildOrderGraph(
AstNetlist* netlistp, //
const std::vector<V3Sched::LogicByScope*>& coll, //
const std::unordered_map<const AstSenItem*, const AstSenTree*>& trigToSen) {
std::unique_ptr<OrderGraph>
V3Order::buildOrderGraph(AstNetlist* netlistp, //
const std::vector<V3Sched::LogicByScope*>& coll, //
const V3Order::TrigToSenMap& trigToSen) {
return OrderGraphBuilder::apply(netlistp, coll, trigToSen);
}

View File

@ -20,9 +20,11 @@
#include "config_build.h"
#include "verilatedos.h"
#include "V3Order.h"
#include "V3OrderGraph.h"
#include "V3ThreadSafety.h"
#include <string>
#include <unordered_map>
#include <vector>
@ -38,10 +40,27 @@ struct LogicByScope;
namespace V3Order {
std::unique_ptr<OrderGraph>
buildOrderGraph(AstNetlist* netlistp, //
const std::vector<V3Sched::LogicByScope*>& coll, //
const std::unordered_map<const AstSenItem*, const AstSenTree*>& trigToSen);
std::unique_ptr<OrderGraph> buildOrderGraph(AstNetlist* netlistp, //
const std::vector<V3Sched::LogicByScope*>& coll, //
const TrigToSenMap& trigToSen);
void orderOrderGraph(OrderGraph& graph, const std::string& tag);
void processDomains(AstNetlist* netlistp, //
OrderGraph& graph, //
const std::string& tag, //
const TrigToSenMap& trigToSen, //
const ExternalDomainsProvider& externalDomains);
std::vector<AstActive*> createSerial(const OrderGraph& graph, //
const std::string& tag, //
const TrigToSenMap& trigToSenMap, //
bool slow);
AstExecGraph* createParallel(const OrderGraph& graph, //
const std::string& tag, //
const TrigToSenMap& trigToSenMap, //
bool slow);
}; // namespace V3Order

View File

@ -1,143 +0,0 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Ordering graph
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-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
//
//*************************************************************************
//
// TODO: Fix comment
//
//*************************************************************************
#ifndef VERILATOR_V3ORDERMOVEGRAPH_H_
#define VERILATOR_V3ORDERMOVEGRAPH_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3Ast.h"
#include "V3Graph.h"
#include "V3OrderGraph.h"
#include "V3ThreadSafety.h"
#include <unordered_map>
class OrderMoveDomScope;
class OrderMoveVertex final : public V3GraphVertex {
VL_RTTI_IMPL(OrderMoveVertex, V3GraphVertex)
enum OrderMState : uint8_t { POM_WAIT, POM_READY, POM_MOVED };
OrderLogicVertex* const m_logicp;
OrderMState m_state; // Movement state
OrderMoveDomScope* m_domScopep; // Domain/scope list information
protected:
friend class OrderProcess;
friend class OrderMoveVertexMaker;
// These only contain the "next" item,
// for the head of the list, see the same var name under OrderProcess
V3ListEnt<OrderMoveVertex*> m_pomWaitingE; // List of nodes needing inputs to become ready
V3ListEnt<OrderMoveVertex*> m_readyVerticesE; // List of ready under domain/scope
public:
// CONSTRUCTORS
OrderMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp) VL_MT_DISABLED
: V3GraphVertex{graphp},
m_logicp{logicp},
m_state{POM_WAIT},
m_domScopep{nullptr} {}
~OrderMoveVertex() override = default;
// METHODS
string dotColor() const override {
if (logicp()) {
return logicp()->dotColor();
} else {
return "";
}
}
string name() const override VL_MT_STABLE {
string nm;
if (VL_UNCOVERABLE(!logicp())) { // Avoid crash when debugging
nm = "nul"; // LCOV_EXCL_LINE
} else {
nm = logicp()->name();
nm += (string{"\\nMV:"} + " d=" + cvtToHex(logicp()->domainp())
+ " s=" + cvtToHex(logicp()->scopep()));
}
return nm;
}
OrderLogicVertex* logicp() const VL_MT_STABLE { return m_logicp; }
bool isWait() const { return m_state == POM_WAIT; }
void setReady() VL_MT_DISABLED {
UASSERT(m_state == POM_WAIT, "Wait->Ready on node not in proper state");
m_state = POM_READY;
}
void setMoved() VL_MT_DISABLED {
UASSERT(m_state == POM_READY, "Ready->Moved on node not in proper state");
m_state = POM_MOVED;
}
OrderMoveDomScope* domScopep() const { return m_domScopep; }
OrderMoveVertex* pomWaitingNextp() const { return m_pomWaitingE.nextp(); }
void domScopep(OrderMoveDomScope* ds) { m_domScopep = ds; }
};
// Similar to OrderMoveVertex, but modified for threaded code generation.
class MTaskMoveVertex final : public V3GraphVertex {
VL_RTTI_IMPL(MTaskMoveVertex, V3GraphVertex)
// This could be more compact, since we know m_varp and m_logicp
// cannot both be set. Each MTaskMoveVertex represents a logic node
// or a var node, it can't be both.
OrderLogicVertex* const m_logicp; // Logic represented by this vertex
const OrderEitherVertex* const m_varp; // Var represented by this vertex
const AstSenTree* const m_domainp;
public:
MTaskMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp, const OrderEitherVertex* varp,
const AstSenTree* domainp) VL_MT_DISABLED : V3GraphVertex{graphp},
m_logicp{logicp},
m_varp{varp},
m_domainp{domainp} {
UASSERT(!(logicp && varp), "MTaskMoveVertex: logicp and varp may not both be set!\n");
}
~MTaskMoveVertex() override = default;
// ACCESSORS
OrderLogicVertex* logicp() const { return m_logicp; }
const OrderEitherVertex* varp() const { return m_varp; }
const AstScope* scopep() const { return m_logicp ? m_logicp->scopep() : nullptr; }
const AstSenTree* domainp() const { return m_domainp; }
string dotColor() const override {
if (logicp()) {
return logicp()->dotColor();
} else {
return "yellow";
}
}
string name() const override {
string nm;
if (logicp()) {
nm = logicp()->name();
nm += (string{"\\nMV:"} + " d=" + cvtToHex(logicp()->domainp()) + " s="
+ cvtToHex(logicp()->scopep())
// "color()" represents the mtask ID.
+ "\\nt=" + cvtToStr(color()));
} else {
nm = "nolog\\nt=" + cvtToStr(color());
}
return nm;
}
};
#endif // Guard

View File

@ -26,6 +26,7 @@
#include "V3Ast.h"
#include "V3Graph.h"
#include "V3Order.h"
#include "V3OrderGraph.h"
template <class T_MoveVertex>
@ -68,18 +69,16 @@ private:
const OrderGraph* const m_graphp; // Input OrderGraph
V3Graph* const m_outGraphp; // Output graph of T_MoveVertex vertices
// Map from Trigger reference AstSenItem to the original AstSenTree
const std::unordered_map<const AstSenItem*, const AstSenTree*>& m_trigToSen;
const V3Order::TrigToSenMap& m_trigToSen;
MoveVertexMaker* const m_vxMakerp; // Factory class for T_MoveVertex's
// Storage for domain -> T_MoveVertex, maps held in OrderVarVertex::userp()
std::deque<DomainMap> m_domainMaps;
public:
// CONSTRUCTORS
V3OrderMoveGraphBuilder(
const OrderGraph* logicGraphp, // Input graph of OrderLogicVertex etc.
V3Graph* outGraphp, // Output graph of T_MoveVertex's
const std::unordered_map<const AstSenItem*, const AstSenTree*>& trigToSen,
MoveVertexMaker* vxMakerp)
V3OrderMoveGraphBuilder(const OrderGraph* logicGraphp, // Input graph of OrderLogicVertex etc.
V3Graph* outGraphp, // Output graph of T_MoveVertex's
const V3Order::TrigToSenMap& trigToSen, MoveVertexMaker* vxMakerp)
: m_graphp{logicGraphp}
, m_outGraphp{outGraphp}
, m_trigToSen{trigToSen}

207
src/V3OrderParallel.cpp Normal file
View File

@ -0,0 +1,207 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Block code ordering
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-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
//
//*************************************************************************
//
// Parallel code ordering
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Graph.h"
#include "V3GraphStream.h"
#include "V3List.h"
#include "V3OrderCFuncEmitter.h"
#include "V3OrderInternal.h"
#include "V3OrderMoveGraphBuilder.h"
#include "V3Partition.h"
#include "V3PartitionGraph.h"
#include <unordered_map>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
class OrderMTaskMoveVertexMaker final
: public V3OrderMoveGraphBuilder<MTaskMoveVertex>::MoveVertexMaker {
V3Graph* m_pomGraphp;
public:
explicit OrderMTaskMoveVertexMaker(V3Graph* pomGraphp)
: m_pomGraphp{pomGraphp} {}
MTaskMoveVertex* makeVertexp(OrderLogicVertex* lvertexp, const OrderEitherVertex* varVertexp,
const AstSenTree* domainp) override {
return new MTaskMoveVertex{m_pomGraphp, lvertexp, varVertexp, domainp};
}
private:
VL_UNCOPYABLE(OrderMTaskMoveVertexMaker);
};
// Sort MTaskMoveVertex vertices by domain, then by scope, based on teh order they are encountered
class OrderVerticesByDomainThenScope final {
mutable uint64_t m_nextId = 0; // Next id to use
mutable std::unordered_map<const void*, uint64_t> m_id; // Map from ptr to id
// Map a pointer into an id, for deterministic results
uint64_t findId(const void* ptrp) const {
const auto pair = m_id.emplace(ptrp, m_nextId);
if (pair.second) ++m_nextId;
return pair.first->second;
}
public:
bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const {
const MTaskMoveVertex* const l_vxp = lhsp->as<MTaskMoveVertex>();
const MTaskMoveVertex* const r_vxp = rhsp->as<MTaskMoveVertex>();
const uint64_t l_id = findId(l_vxp->domainp());
const uint64_t r_id = findId(r_vxp->domainp());
if (l_id != r_id) return l_id < r_id;
return findId(l_vxp->scopep()) < findId(r_vxp->scopep());
}
};
// Sort AbstractMTask vertices by their serial IDs.
struct MTaskVxIdLessThan final {
bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const {
return lhsp->as<AbstractLogicMTask>()->id() < rhsp->as<AbstractLogicMTask>()->id();
}
};
AstExecGraph* V3Order::createParallel(const OrderGraph& graph, const std::string& tag,
const TrigToSenMap& trigToSen, bool slow) {
// For nondeterminism debug:
V3Partition::hashGraphDebug(&graph, "V3Order's m_graph");
// We already produced a graph of every var, input, and logic
// block and all dependencies; this is 'm_graph'.
//
// Now, starting from m_graph, make a slightly-coarsened graph representing
// only logic, and discarding edges we know we can ignore.
// This is quite similar to the 'm_pomGraph' of the serial code gen:
V3Graph logicGraph;
{
OrderMTaskMoveVertexMaker create_mtask_vertex(&logicGraph);
V3OrderMoveGraphBuilder<MTaskMoveVertex> mtask_pmbg(&graph, &logicGraph, trigToSen,
&create_mtask_vertex);
mtask_pmbg.build();
}
// Needed? We do this for m_pomGraph in serial mode, so do it here too:
logicGraph.removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue);
// Partition logicGraph into LogicMTask's. The partitioner will annotate
// each vertex in logicGraph with a 'color' which is really an mtask ID
// in this context.
V3Partition partitioner{&graph, &logicGraph};
V3Graph mtasks;
partitioner.go(&mtasks);
// processMTask* routines schedule threaded execution
struct MTaskState final {
AstMTaskBody* m_mtaskBodyp = nullptr;
std::list<const OrderLogicVertex*> m_logics;
ExecMTask* m_execMTaskp = nullptr;
MTaskState() = default;
};
std::unordered_map<unsigned /*mtask id*/, MTaskState> mtaskStates;
// Iterate through the entire logicGraph. For each logic node,
// attach it to a per-MTask ordered list of logic nodes.
// This is the order we'll execute logic nodes within the MTask.
//
// MTasks may span scopes and domains, so sort by both here:
GraphStream<OrderVerticesByDomainThenScope> logicStream{&logicGraph};
while (const V3GraphVertex* const vtxp = logicStream.nextp()) {
const MTaskMoveVertex* const movep = vtxp->as<MTaskMoveVertex>();
// Only care about logic vertices
if (!movep->logicp()) continue;
const unsigned mtaskId = movep->color();
UASSERT(mtaskId > 0, "Every MTaskMoveVertex should have an mtask assignment >0");
// Add this logic to the per-mtask order
mtaskStates[mtaskId].m_logics.push_back(movep->logicp());
// Since we happen to be iterating over every logic node,
// take this opportunity to annotate each AstVar with the id's
// of mtasks that consume it and produce it. We'll use this
// information in V3EmitC when we lay out var's in memory.
const OrderLogicVertex* const logicp = movep->logicp();
for (const V3GraphEdge* edgep = logicp->inBeginp(); edgep; edgep = edgep->inNextp()) {
const OrderVarVertex* const pre_varp = edgep->fromp()->cast<const OrderVarVertex>();
if (!pre_varp) continue;
AstVar* const varp = pre_varp->vscp()->varp();
// varp depends on logicp, so logicp produces varp,
// and vice-versa below
varp->addProducingMTaskId(mtaskId);
}
for (const V3GraphEdge* edgep = logicp->outBeginp(); edgep; edgep = edgep->outNextp()) {
const OrderVarVertex* const post_varp = edgep->top()->cast<const OrderVarVertex>();
if (!post_varp) continue;
AstVar* const varp = post_varp->vscp()->varp();
varp->addConsumingMTaskId(mtaskId);
}
// TODO? We ignore IO vars here, so those will have empty mtask
// signatures. But we could also give those mtask signatures.
}
// Create the AstExecGraph node which represents the execution
// of the MTask graph.
FileLine* const rootFlp = v3Global.rootp()->fileline();
AstExecGraph* const execGraphp = new AstExecGraph{rootFlp, tag};
// Create CFuncs and bodies for each MTask.
V3OrderCFuncEmitter emitter{tag, slow};
GraphStream<MTaskVxIdLessThan> mtaskStream{&mtasks};
while (const V3GraphVertex* const vtxp = mtaskStream.nextp()) {
const AbstractLogicMTask* const mtaskp = vtxp->as<AbstractLogicMTask>();
// Create a body for this mtask
AstMTaskBody* const bodyp = new AstMTaskBody{rootFlp};
MTaskState& state = mtaskStates[mtaskp->id()];
state.m_mtaskBodyp = bodyp;
// Emit functions with this MTaks's logic, and call them in the body.
for (const OrderLogicVertex* lVtxp : state.m_logics) emitter.emitLogic(lVtxp);
for (AstActive* const activep : emitter.getAndClearActiveps()) bodyp->addStmtsp(activep);
// Translate the LogicMTask graph into the corresponding ExecMTask
// graph, which will outlive V3Order and persist for the remainder
// of verilator's processing.
// - The LogicMTask graph points to MTaskMoveVertex's
// and OrderLogicVertex's which are ephemeral to V3Order.
// - The ExecMTask graph and the AstMTaskBody's produced here
// persist until code generation time.
V3Graph* const depGraphp = execGraphp->depGraphp();
state.m_execMTaskp = new ExecMTask{depGraphp, bodyp, mtaskp->id()};
// Cross-link each ExecMTask and MTaskBody
// Q: Why even have two objects?
// A: One is an AstNode, the other is a GraphVertex,
// to combine them would involve multiple inheritance...
state.m_mtaskBodyp->execMTaskp(state.m_execMTaskp);
for (V3GraphEdge* inp = mtaskp->inBeginp(); inp; inp = inp->inNextp()) {
const V3GraphVertex* fromVxp = inp->fromp();
const AbstractLogicMTask* const fromp
= static_cast<const AbstractLogicMTask*>(fromVxp);
const MTaskState& fromState = mtaskStates[fromp->id()];
new V3GraphEdge{depGraphp, fromState.m_execMTaskp, state.m_execMTaskp, 1};
}
execGraphp->addMTaskBodiesp(bodyp);
}
return execGraphp;
}

View File

@ -0,0 +1,245 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Block code ordering
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-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
//
//*************************************************************************
//
// Initial graph dependency builder for ordering
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Const.h"
#include "V3EmitV.h"
#include "V3File.h"
#include "V3OrderGraph.h"
#include "V3OrderInternal.h"
#include "V3SenTree.h"
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// ProcessDomains class
class V3OrderProcessDomains final {
// NODE STATE
// AstNode::user4 -> Used by V3Const::constifyExpensiveEdit
// STATE
OrderGraph& m_graph; // The ordering graph
// Map from Trigger reference AstSenItem to the original AstSenTree
const V3Order::TrigToSenMap& m_trigToSen;
// This is a function provided by the invoker of the ordering that can provide additional
// sensitivity expression that when triggered indicates the passed AstVarScope might have
// changed external to the code being ordered.
const V3Order::ExternalDomainsProvider m_externalDomains;
SenTreeFinder m_finder; // Global AstSenTree manager
// Sentinel value indicating a vertex can be deleted. Never dereferenced, so any non-nullptr
// value will do. Use something that wil crash quickly if used.
AstSenTree* const m_deleteDomainp = reinterpret_cast<AstSenTree*>(1);
// Logic that is never triggered and hence can be deleted
std::vector<OrderLogicVertex*> m_logicpsToDelete;
const string m_tag; // Substring to add to generated names
// METHODS
// Make a domain that merges the two domains
AstSenTree* combineDomains(AstSenTree* ap, AstSenTree* bp) {
if (ap == bp) return ap;
if (ap == m_deleteDomainp) return bp;
UASSERT_OBJ(bp != m_deleteDomainp, bp, "'bp' Should not be delete domain");
AstSenTree* const senTreep = ap->cloneTree(false);
senTreep->addSensesp(bp->sensesp()->cloneTree(true));
V3Const::constifyExpensiveEdit(senTreep); // Remove duplicates
senTreep->multi(true); // Comment that it was made from 2 domains
AstSenTree* const resultp = m_finder.getSenTree(senTreep);
VL_DO_DANGLING(senTreep->deleteTree(), senTreep); // getSenTree clones, so delete this
return resultp;
}
// The graph routines have already sorted the vertexes and edges into best->worst order
// Assign clock domains to each signal.
// Sequential logic already hae their domain defined.
// Combo logic may be pushed into a seq domain if all its inputs are the same domain,
// else, if all inputs are from flops, it's end-of-sequential code
// else, it's full combo code
void processDomains() {
UINFO(2, " Domains...\n");
// Buffer to hold external sensitivities
std::vector<AstSenTree*> externalDomainps;
// For each vertex
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
OrderEitherVertex* const vtxp = itp->as<OrderEitherVertex>();
UINFO(5, " pdi: " << vtxp << endl);
// Sequential logic already has its domain set
if (vtxp->domainp()) continue;
AstSenTree* domainp = nullptr;
// For logic, start with the explicit hybrid sensitivities
OrderLogicVertex* const lvtxp = vtxp->cast<OrderLogicVertex>();
if (lvtxp) domainp = lvtxp->hybridp();
// For each incoming edge, examine the source vertex
for (V3GraphEdge* edgep = vtxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
OrderEitherVertex* const fromVtxp = edgep->fromp()->as<OrderEitherVertex>();
// Cut edge
if (!edgep->weight()) continue;
//
if (!fromVtxp->domainMatters()) continue;
AstSenTree* fromDomainp = fromVtxp->domainp();
UASSERT(fromDomainp == m_deleteDomainp || !fromDomainp->hasCombo(),
"There should be no need for combinational domains");
// Add in any external domains of variables
if (OrderVarVertex* const varVtxp = fromVtxp->cast<OrderVarVertex>()) {
AstVarScope* const vscp = varVtxp->vscp();
externalDomainps.clear();
m_externalDomains(vscp, externalDomainps);
for (AstSenTree* const externalDomainp : externalDomainps) {
UASSERT_OBJ(!externalDomainp->hasCombo(), vscp,
"There should be no need for combinational domains");
fromDomainp = combineDomains(fromDomainp, externalDomainp);
}
}
// Irrelevant input vertex (never triggered, not even externally)
if (fromDomainp == m_deleteDomainp) continue;
if (!domainp) {
// First input to this vertex that we are processing
domainp = fromDomainp;
} else {
// Make a domain that merges the two domains
domainp = combineDomains(domainp, fromDomainp);
}
}
// If nothing triggers this vertex, we can delete the corresponding logic
if (!domainp) {
domainp = m_deleteDomainp;
if (lvtxp) m_logicpsToDelete.push_back(lvtxp);
}
// Set the domain of the vertex
vtxp->domainp(domainp);
UINFO(5, " done d=" << cvtToHex(domainp)
<< (domainp == m_deleteDomainp ? " [DEL]"
: domainp->hasCombo() ? " [COMB]"
: domainp->isMulti() ? " [MULT]"
: "")
<< " " << vtxp << endl);
}
}
void processEdgeReport() {
// Make report of all signal names and what clock edges they have
const string filename = v3Global.debugFilename(m_tag + "_order_edges.txt");
const std::unique_ptr<std::ofstream> logp{V3File::new_ofstream(filename)};
if (logp->fail()) v3fatal("Can't write " << filename);
std::deque<string> report;
// Rebuild the trigger to original AstSenTree map using equality key comparison, as
// merging domains have created new AstSenTree instances which are not in the map
std::unordered_map<VNRef<const AstSenItem>, const AstSenTree*> trigToSen;
for (const auto& pair : m_trigToSen) trigToSen.emplace(*pair.first, pair.second);
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
if (OrderVarVertex* const vvertexp = itp->cast<OrderVarVertex>()) {
string name(vvertexp->vscp()->prettyName());
if (itp->is<OrderVarPreVertex>()) {
name += " {PRE}";
} else if (itp->is<OrderVarPostVertex>()) {
name += " {POST}";
} else if (itp->is<OrderVarPordVertex>()) {
name += " {PORD}";
}
std::ostringstream os;
os.setf(std::ios::left);
os << " " << cvtToHex(vvertexp->vscp()) << " " << std::setw(50) << name << " ";
AstSenTree* const senTreep = vvertexp->domainp();
if (senTreep == m_deleteDomainp) {
os << "DELETED";
} else {
for (AstSenItem* senItemp = senTreep->sensesp(); senItemp;
senItemp = VN_AS(senItemp->nextp(), SenItem)) {
if (senItemp != senTreep->sensesp()) os << " or ";
const auto it = trigToSen.find(*senItemp);
if (it != trigToSen.end()) {
V3EmitV::verilogForTree(it->second, os);
} else {
V3EmitV::verilogForTree(senItemp, os);
}
}
}
report.push_back(os.str());
}
}
*logp << "Signals and their clock domains:\n";
stable_sort(report.begin(), report.end());
for (const string& i : report) *logp << i << '\n';
}
// CONSTRUCTOR
V3OrderProcessDomains(AstNetlist* netlistp, OrderGraph& graph, const string& tag,
const V3Order::TrigToSenMap& trigToSen,
const V3Order::ExternalDomainsProvider& externalDomains)
: m_graph{graph}
, m_trigToSen{trigToSen}
, m_externalDomains{externalDomains}
, m_finder{netlistp}
, m_tag{tag} {
// Assign vertices to their sensitivity domains
processDomains();
if (dumpGraphLevel()) m_graph.dumpDotFilePrefixed(m_tag + "_orderg_domain");
// Report domain assignments if requested
if (dumpLevel()) processEdgeReport();
// Delete logic that is never triggered
for (OrderLogicVertex* const lVtxp : m_logicpsToDelete) {
UASSERT_OBJ(lVtxp->domainp() == m_deleteDomainp, lVtxp,
"Should have been marked as deleted");
lVtxp->nodep()->unlinkFrBack()->deleteTree();
lVtxp->unlinkDelete(&m_graph);
}
}
~V3OrderProcessDomains() = default;
public:
// Order the logic
static void apply(AstNetlist* netlistp, OrderGraph& graph, const string& tag,
const V3Order::TrigToSenMap& trigToSen,
const V3Order::ExternalDomainsProvider& externalDomains) {
V3OrderProcessDomains{netlistp, graph, tag, trigToSen, externalDomains};
}
};
void V3Order::processDomains(AstNetlist* netlistp, //
OrderGraph& graph, //
const std::string& tag, //
const V3Order::TrigToSenMap& trigToSen, //
const ExternalDomainsProvider& externalDomains) {
V3OrderProcessDomains::apply(netlistp, graph, tag, trigToSen, externalDomains);
}

385
src/V3OrderSerial.cpp Normal file
View File

@ -0,0 +1,385 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Block code ordering
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-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
//
//*************************************************************************
//
// Serial code ordering
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Graph.h"
#include "V3List.h"
#include "V3OrderCFuncEmitter.h"
#include "V3OrderInternal.h"
#include "V3OrderMoveGraphBuilder.h"
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
class OrderMoveDomScope;
class OrderMoveVertex final : public V3GraphVertex {
VL_RTTI_IMPL(OrderMoveVertex, V3GraphVertex)
enum OrderMState : uint8_t { POM_WAIT, POM_READY, POM_MOVED };
OrderLogicVertex* const m_logicp;
OrderMState m_state; // Movement state
OrderMoveDomScope* m_domScopep; // Domain/scope list information
protected:
friend class OrderProcess;
friend class OrderMoveVertexMaker;
// These only contain the "next" item,
// for the head of the list, see the same var name under OrderProcess
V3ListEnt<OrderMoveVertex*> m_pomWaitingE; // List of nodes needing inputs to become ready
V3ListEnt<OrderMoveVertex*> m_readyVerticesE; // List of ready under domain/scope
public:
// CONSTRUCTORS
OrderMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp) VL_MT_DISABLED
: V3GraphVertex{graphp},
m_logicp{logicp},
m_state{POM_WAIT},
m_domScopep{nullptr} {}
~OrderMoveVertex() override = default;
// METHODS
string dotColor() const override {
if (logicp()) {
return logicp()->dotColor();
} else {
return "";
}
}
string name() const override VL_MT_STABLE {
string nm;
if (VL_UNCOVERABLE(!logicp())) { // Avoid crash when debugging
nm = "nul"; // LCOV_EXCL_LINE
} else {
nm = logicp()->name();
nm += (string{"\\nMV:"} + " d=" + cvtToHex(logicp()->domainp())
+ " s=" + cvtToHex(logicp()->scopep()));
}
return nm;
}
OrderLogicVertex* logicp() const VL_MT_STABLE { return m_logicp; }
bool isWait() const { return m_state == POM_WAIT; }
void setReady() VL_MT_DISABLED {
UASSERT(m_state == POM_WAIT, "Wait->Ready on node not in proper state");
m_state = POM_READY;
}
void setMoved() VL_MT_DISABLED {
UASSERT(m_state == POM_READY, "Ready->Moved on node not in proper state");
m_state = POM_MOVED;
}
OrderMoveDomScope* domScopep() const { return m_domScopep; }
OrderMoveVertex* pomWaitingNextp() const { return m_pomWaitingE.nextp(); }
void domScopep(OrderMoveDomScope* ds) { m_domScopep = ds; }
};
//######################################################################
class OrderProcess;
class OrderMoveDomScope final {
// Information stored for each unique loop, domain & scope trifecta
public:
V3ListEnt<OrderMoveDomScope*> m_readyDomScopeE; // List of next ready dom scope
V3List<OrderMoveVertex*> m_readyVertices; // Ready vertices with same domain & scope
private:
bool m_onReadyList = false; // True if DomScope is already on list of ready dom/scopes
const AstSenTree* const m_domainp; // Domain all vertices belong to
const AstScope* const m_scopep; // Scope all vertices belong to
using DomScopeKey = std::pair<const AstSenTree*, const AstScope*>;
using DomScopeMap = std::map<DomScopeKey, OrderMoveDomScope*>;
static DomScopeMap s_dsMap; // Structure registered for each dom/scope pairing
public:
OrderMoveDomScope(const AstSenTree* domainp, const AstScope* scopep)
: m_domainp{domainp}
, m_scopep{scopep} {}
OrderMoveDomScope* readyDomScopeNextp() const { return m_readyDomScopeE.nextp(); }
const AstSenTree* domainp() const { return m_domainp; }
const AstScope* scopep() const { return m_scopep; }
// Check the domScope is on ready list, add if not
void ready(OrderProcess* opp);
// Mark one vertex as finished, remove from ready list if done
void movedVertex(OrderProcess* opp, OrderMoveVertex* vertexp);
// STATIC MEMBERS (for lookup)
static void clear() {
for (const auto& itr : s_dsMap) delete itr.second;
s_dsMap.clear();
}
V3List<OrderMoveVertex*>& readyVertices() { return m_readyVertices; }
static OrderMoveDomScope* findCreate(const AstSenTree* domainp, const AstScope* scopep) {
const DomScopeKey key = std::make_pair(domainp, scopep);
const auto pair = s_dsMap.emplace(key, nullptr);
if (pair.second) pair.first->second = new OrderMoveDomScope{domainp, scopep};
return pair.first->second;
}
string name() const {
return string{"MDS:"} + " d=" + cvtToHex(domainp()) + " s=" + cvtToHex(scopep());
}
};
// ######################################################################
// OrderMoveVertexMaker and related
class OrderMoveVertexMaker final
: public V3OrderMoveGraphBuilder<OrderMoveVertex>::MoveVertexMaker {
// MEMBERS
V3Graph* m_pomGraphp;
V3List<OrderMoveVertex*>* m_pomWaitingp;
public:
// CONSTRUCTORS
OrderMoveVertexMaker(V3Graph* pomGraphp, V3List<OrderMoveVertex*>* pomWaitingp)
: m_pomGraphp{pomGraphp}
, m_pomWaitingp{pomWaitingp} {}
// METHODS
OrderMoveVertex* makeVertexp(OrderLogicVertex* lvertexp, const OrderEitherVertex*,
const AstSenTree* domainp) override {
OrderMoveVertex* const resultp = new OrderMoveVertex{m_pomGraphp, lvertexp};
AstScope* const scopep = lvertexp ? lvertexp->scopep() : nullptr;
resultp->domScopep(OrderMoveDomScope::findCreate(domainp, scopep));
resultp->m_pomWaitingE.pushBack(*m_pomWaitingp, resultp);
return resultp;
}
private:
VL_UNCOPYABLE(OrderMoveVertexMaker);
};
OrderMoveDomScope::DomScopeMap OrderMoveDomScope::s_dsMap;
std::ostream& operator<<(std::ostream& lhs, const OrderMoveDomScope& rhs) {
lhs << rhs.name();
return lhs;
}
//######################################################################
// OrderProcess class
class OrderProcess final {
// STATE
const OrderGraph& m_graph; // The ordering graph
// Map from Trigger reference AstSenItem to the original AstSenTree
const V3Order::TrigToSenMap& m_trigToSen;
const string m_tag; // Substring to add to generated names
V3Graph m_pomGraph; // Graph of logic elements to move
V3List<OrderMoveVertex*> m_pomWaiting; // List of nodes needing inputs to become ready
friend class OrderMoveDomScope;
V3List<OrderMoveDomScope*> m_pomReadyDomScope; // List of ready domain/scope pairs, by loopId
V3OrderCFuncEmitter m_emitter;
// METHODS
// processMove* routines schedule serial execution
void processMove();
void processMoveClear();
void processMoveBuildGraph();
void processMovePrepReady();
void processMoveReadyOne(OrderMoveVertex* vertexp);
void processMoveDoneOne(OrderMoveVertex* vertexp);
// CONSTRUCTOR
OrderProcess(const OrderGraph& graph, const string& tag,
const V3Order::TrigToSenMap& trigToSen, bool slow)
: m_graph{graph}
, m_trigToSen{trigToSen}
, m_tag{tag}
, m_emitter{tag, slow} {
UINFO(2, " Construct Move Graph...\n");
processMoveBuildGraph();
// Different prefix (ordermv) as it's not the same graph
if (dumpGraphLevel() >= 4) m_pomGraph.dumpDotFilePrefixed(m_tag + "_ordermv_start");
m_pomGraph.removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue);
if (dumpGraphLevel() >= 4) m_pomGraph.dumpDotFilePrefixed(m_tag + "_ordermv_simpl");
UINFO(2, " Move...\n");
processMove();
}
~OrderProcess() = default;
public:
// Order the logic
static std::vector<AstActive*> main(const OrderGraph& graph, const string& tag,
const V3Order::TrigToSenMap& trigToSen, bool slow) {
return OrderProcess{graph, tag, trigToSen, slow}.m_emitter.getAndClearActiveps();
}
};
//######################################################################
// OrderMoveDomScope methods
// Check the domScope is on ready list, add if not
void OrderMoveDomScope::ready(OrderProcess* opp) {
if (!m_onReadyList) {
m_onReadyList = true;
m_readyDomScopeE.pushBack(opp->m_pomReadyDomScope, this);
}
}
// Mark one vertex as finished, remove from ready list if done
void OrderMoveDomScope::movedVertex(OrderProcess* opp, OrderMoveVertex* vertexp) {
UASSERT_OBJ(m_onReadyList, vertexp,
"Moving vertex from ready when nothing was on que as ready.");
if (m_readyVertices.empty()) { // Else more work to get to later
m_onReadyList = false;
m_readyDomScopeE.unlink(opp->m_pomReadyDomScope, this);
}
}
void OrderProcess::processMoveClear() {
OrderMoveDomScope::clear();
m_pomWaiting.reset();
m_pomReadyDomScope.reset();
m_pomGraph.clear();
}
void OrderProcess::processMoveBuildGraph() {
// Build graph of only vertices
UINFO(5, " MoveBuildGraph\n");
processMoveClear();
// Vertex::user->OrderMoveVertex*, last edge added or nullptr=none
m_pomGraph.userClearVertices();
OrderMoveVertexMaker createOrderMoveVertex(&m_pomGraph, &m_pomWaiting);
V3OrderMoveGraphBuilder<OrderMoveVertex> serialPMBG(&m_graph, &m_pomGraph, m_trigToSen,
&createOrderMoveVertex);
serialPMBG.build();
}
//######################################################################
// OrderVisitor - Moving
void OrderProcess::processMove() {
// The graph routines have already sorted the vertexes and edges into best->worst order
// Make a new waiting graph with only OrderLogicVertex's
// (Order is preserved in the recreation so the sorting is preserved)
// Move any node with all inputs ready to a "ready" graph mapped by domain and then scope
// While waiting graph ! empty (and also known: something in ready graph)
// For all scopes in domain of top ready vertex
// For all vertexes in domain&scope of top ready vertex
// Make ordered activation block for this module
// Add that new activation to the list of calls to make.
// Move logic to ordered active
// Any children that have all inputs now ready move from waiting->ready graph
// (This may add nodes the for loop directly above needs to detext)
processMovePrepReady();
// New domain... another loop
UINFO(5, " MoveIterate\n");
while (!m_pomReadyDomScope.empty()) {
// Start with top node on ready list's domain & scope
OrderMoveDomScope* domScopep = m_pomReadyDomScope.begin();
OrderMoveVertex* const topVertexp
= domScopep->readyVertices().begin(); // lintok-begin-on-ref
UASSERT(topVertexp, "domScope on ready list without any nodes ready under it");
// Work on all scopes ready inside this domain
while (domScopep) {
UINFO(6, " MoveDomain l=" << domScopep->domainp() << endl);
// Process all nodes ready under same domain & scope
m_emitter.forceNewFunction();
V3List<OrderMoveVertex*>& readyVertices = domScopep->readyVertices();
while (OrderMoveVertex* vertexp = readyVertices.begin()) {
UASSERT_OBJ(vertexp->domScopep() == domScopep, vertexp, "Domain mismatch");
m_emitter.emitLogic(vertexp->logicp());
processMoveDoneOne(vertexp);
}
// Done with scope/domain pair, pick new scope under same domain, or nullptr if none
// left
OrderMoveDomScope* domScopeNextp = nullptr;
for (OrderMoveDomScope* huntp = m_pomReadyDomScope.begin(); huntp;
huntp = huntp->readyDomScopeNextp()) {
if (huntp->domainp() == domScopep->domainp()) {
domScopeNextp = huntp;
break;
}
}
domScopep = domScopeNextp;
}
}
UASSERT(m_pomWaiting.empty(),
"Didn't converge; nodes waiting, none ready, perhaps some input activations lost.");
// Cleanup memory
processMoveClear();
}
void OrderProcess::processMovePrepReady() {
// Make list of ready nodes
UINFO(5, " MovePrepReady\n");
for (OrderMoveVertex* vertexp = m_pomWaiting.begin(); vertexp;) {
OrderMoveVertex* const nextp = vertexp->pomWaitingNextp();
if (vertexp->isWait() && vertexp->inEmpty()) processMoveReadyOne(vertexp);
vertexp = nextp;
}
}
void OrderProcess::processMoveReadyOne(OrderMoveVertex* vertexp) {
// Recursive!
// Move one node from waiting to ready list
vertexp->setReady();
// Remove node from waiting list
vertexp->m_pomWaitingE.unlink(m_pomWaiting, vertexp);
if (vertexp->logicp()) {
// Add to ready list (indexed by domain and scope)
vertexp->m_readyVerticesE.pushBack(vertexp->domScopep()->m_readyVertices, vertexp);
vertexp->domScopep()->ready(this);
} else {
// vertexp represents a non-logic vertex.
// Recurse to mark its following neighbors ready.
processMoveDoneOne(vertexp);
}
}
void OrderProcess::processMoveDoneOne(OrderMoveVertex* vertexp) {
// Move one node from ready to completion
vertexp->setMoved();
// Unlink from ready lists
if (vertexp->logicp()) {
vertexp->m_readyVerticesE.unlink(vertexp->domScopep()->m_readyVertices, vertexp);
vertexp->domScopep()->movedVertex(this, vertexp);
}
// Don't need to add it to another list, as we're done with it
// Mark our outputs as one closer to ready
for (V3GraphEdge *edgep = vertexp->outBeginp(), *nextp; edgep; edgep = nextp) {
nextp = edgep->outNextp();
OrderMoveVertex* const toVertexp = static_cast<OrderMoveVertex*>(edgep->top());
UINFO(9, " Clear to " << (toVertexp->inEmpty() ? "[EMP] " : " ") << toVertexp
<< endl);
// Delete this edge
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
if (toVertexp->inEmpty()) {
// If destination node now has all inputs resolved; recurse to move that vertex
// This is thus depth first (before width) which keeps the
// resulting executable's d-cache happy.
processMoveReadyOne(toVertexp);
}
}
}
std::vector<AstActive*> V3Order::createSerial(const OrderGraph& graph, const std::string& tag,
const TrigToSenMap& trigToSen, bool slow) {
return OrderProcess::main(graph, tag, trigToSen, slow);
}

View File

@ -22,14 +22,12 @@
#include "V3Graph.h"
#include "V3OrderGraph.h"
#include "V3OrderMoveGraph.h"
#include "V3ThreadSafety.h"
#include <list>
#include <unordered_map>
class LogicMTask;
using Vx2MTaskMap = std::unordered_map<const MTaskMoveVertex*, LogicMTask*>;
//*************************************************************************
/// V3Partition takes the fine-grained logic graph from V3Order and
@ -78,24 +76,4 @@ private:
VL_UNCOPYABLE(V3Partition);
};
//*************************************************************************
// Map a pointer into a id, for e.g. nodep to mtask mappings
class PartPtrIdMap final {
// TYPES
// MEMBERS
mutable uint64_t m_nextId = 0;
mutable std::unordered_map<const void*, uint64_t> m_id;
public:
// CONSTRUCTORS
PartPtrIdMap() = default;
// METHODS
uint64_t findId(const void* ptrp) const {
const auto pair = m_id.emplace(ptrp, m_nextId);
if (pair.second) ++m_nextId;
return pair.first->second;
}
};
#endif // Guard

View File

@ -21,10 +21,58 @@
#include "verilatedos.h"
#include "V3Graph.h"
#include "V3OrderMoveGraph.h"
#include "V3OrderGraph.h"
#include <list>
// Similar to OrderMoveVertex, but modified for threaded code generation.
class MTaskMoveVertex final : public V3GraphVertex {
VL_RTTI_IMPL(MTaskMoveVertex, V3GraphVertex)
// This could be more compact, since we know m_varp and m_logicp
// cannot both be set. Each MTaskMoveVertex represents a logic node
// or a var node, it can't be both.
OrderLogicVertex* const m_logicp; // Logic represented by this vertex
const OrderEitherVertex* const m_varp; // Var represented by this vertex
const AstSenTree* const m_domainp;
public:
MTaskMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp, const OrderEitherVertex* varp,
const AstSenTree* domainp) VL_MT_DISABLED : V3GraphVertex{graphp},
m_logicp{logicp},
m_varp{varp},
m_domainp{domainp} {
UASSERT(!(logicp && varp), "MTaskMoveVertex: logicp and varp may not both be set!\n");
}
~MTaskMoveVertex() override = default;
// ACCESSORS
OrderLogicVertex* logicp() const { return m_logicp; }
const OrderEitherVertex* varp() const { return m_varp; }
const AstScope* scopep() const { return m_logicp ? m_logicp->scopep() : nullptr; }
const AstSenTree* domainp() const { return m_domainp; }
string dotColor() const override {
if (logicp()) {
return logicp()->dotColor();
} else {
return "yellow";
}
}
string name() const override {
string nm;
if (logicp()) {
nm = logicp()->name();
nm += (string{"\\nMV:"} + " d=" + cvtToHex(logicp()->domainp()) + " s="
+ cvtToHex(logicp()->scopep())
// "color()" represents the mtask ID.
+ "\\nt=" + cvtToStr(color()));
} else {
nm = "nolog\\nt=" + cvtToStr(color());
}
return nm;
}
};
//*************************************************************************
// MTasks and graph structures

View File

@ -98,7 +98,7 @@ void remapSensitivities(const LogicByScope& lbs,
}
void invertAndMergeSenTreeMap(
std::unordered_map<const AstSenItem*, const AstSenTree*>& result,
V3Order::TrigToSenMap& result,
const std::unordered_map<const AstSenTree*, AstSenTree*>& senTreeMap) {
for (const auto& pair : senTreeMap) {
UASSERT_OBJ(!pair.second->sensesp()->nextp(), pair.second, "Should be single AstSenIem");
@ -791,7 +791,7 @@ void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilde
remapSensitivities(hybrid, trig.m_map);
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
std::unordered_map<const AstSenItem*, const AstSenTree*> trigToSen;
V3Order::TrigToSenMap trigToSen;
invertAndMergeSenTreeMap(trigToSen, trig.m_map);
// First trigger is for pure combinational triggers (first iteration)
@ -871,7 +871,7 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
remapSensitivities(logic, trig.m_map);
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
std::unordered_map<const AstSenItem*, const AstSenTree*> trigToSen;
V3Order::TrigToSenMap trigToSen;
invertAndMergeSenTreeMap(trigToSen, trig.m_map);
// The trigger top level inputs (first iteration)
@ -1268,7 +1268,7 @@ void schedule(AstNetlist* netlistp) {
const auto& actTimingDomains = timingKit.remapDomains(actTrigMap);
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
std::unordered_map<const AstSenItem*, const AstSenTree*> trigToSenAct;
V3Order::TrigToSenMap trigToSenAct;
invertAndMergeSenTreeMap(trigToSenAct, preTrigMap);
invertAndMergeSenTreeMap(trigToSenAct, actTrigMap);
@ -1307,7 +1307,7 @@ void schedule(AstNetlist* netlistp) {
for (LogicByScope* lbs : logic) remapSensitivities(*lbs, trigMap);
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
std::unordered_map<const AstSenItem*, const AstSenTree*> trigToSen;
V3Order::TrigToSenMap trigToSen;
invertAndMergeSenTreeMap(trigToSen, trigMap);
AstSenTree* const dpiExportTriggered

View File

@ -185,7 +185,6 @@ class SchedGraphBuilder final : public VNVisitor {
// Clocked or hybrid logic has explicit sensitivity, so add edge from sensitivity vertex
if (!m_senTreep->hasCombo()) {
m_senTreep->foreach([this, nodep, logicVtxp](AstSenItem* senItemp) {
if (senItemp->isIllegal()) return;
UASSERT_OBJ(senItemp->isClocked() || senItemp->isHybrid(), nodep,
"Non-clocked SenItem under clocked SenTree");
V3GraphVertex* const eventVtxp = getSenVertex(senItemp);

View File

@ -164,8 +164,6 @@ class SenExprBuilder final {
// All event signals should be 1-bit at this point
switch (senItemp->edgeType()) {
case VEdgeType::ET_ILLEGAL:
return {nullptr, false}; // We already warn for this in V3LinkResolve
case VEdgeType::ET_CHANGED:
case VEdgeType::ET_HYBRID: //
if (VN_IS(senp->dtypep()->skipRefp(), UnpackArrayDType)) {

File diff suppressed because it is too large Load Diff

View File

@ -687,6 +687,22 @@
</if>
</cfunc>
<cfunc loc="a,0,0,0,0" name="_eval_act"/>
<cfunc loc="a,0,0,0,0" name="_eval_nba">
<if loc="d,11,8,11,9">
<and loc="d,11,8,11,9" dtype_id="17">
<const loc="d,11,8,11,9" name="64&apos;h1" dtype_id="17"/>
<cmethodhard loc="d,11,8,11,9" name="word" dtype_id="18">
<varref loc="d,11,8,11,9" name="__VnbaTriggered" dtype_id="9"/>
<const loc="d,11,8,11,9" name="32&apos;h0" dtype_id="14"/>
</cmethodhard>
</and>
<begin>
<stmtexpr loc="d,65,10,65,11">
<ccall loc="d,65,10,65,11" dtype_id="7" func="_nba_sequent__TOP__0"/>
</stmtexpr>
</begin>
</if>
</cfunc>
<cfunc loc="d,65,10,65,11" name="_nba_sequent__TOP__0">
<var loc="d,23,17,23,20" name="__Vdly__t.cyc" dtype_id="4" vartype="integer" origName="__Vdly__t__DOT__cyc"/>
<creset loc="d,23,17,23,20">
@ -1507,22 +1523,6 @@
<varref loc="d,65,10,65,11" name="t.e" dtype_id="11"/>
</assignpost>
</cfunc>
<cfunc loc="a,0,0,0,0" name="_eval_nba">
<if loc="d,11,8,11,9">
<and loc="d,11,8,11,9" dtype_id="17">
<const loc="d,11,8,11,9" name="64&apos;h1" dtype_id="17"/>
<cmethodhard loc="d,11,8,11,9" name="word" dtype_id="18">
<varref loc="d,11,8,11,9" name="__VnbaTriggered" dtype_id="9"/>
<const loc="d,11,8,11,9" name="32&apos;h0" dtype_id="14"/>
</cmethodhard>
</and>
<begin>
<stmtexpr loc="d,65,10,65,11">
<ccall loc="d,65,10,65,11" dtype_id="7" func="_nba_sequent__TOP__0"/>
</stmtexpr>
</begin>
</if>
</cfunc>
<cfunc loc="a,0,0,0,0" name="_eval_phase__act">
<var loc="d,11,8,11,9" name="__VpreTriggered" dtype_id="6" vartype="VlTriggerVec" origName="__VpreTriggered"/>
<var loc="d,11,8,11,9" name="__VactExecute" dtype_id="3" vartype="bit" origName="__VactExecute"/>