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:
parent
5ee938fd1c
commit
5a69321be3
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
16
src/V3Ast.h
16
src/V3Ast.h
|
|
@ -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} {}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
872
src/V3Order.cpp
872
src/V3Order.cpp
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, //
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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'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'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'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'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"/>
|
||||
|
|
|
|||
Loading…
Reference in New Issue