Internals: Replace VlTriggerVec with unpacked array (#6616)

Removed the VlTriggerVec type, and refactored to use an unpacked array
of 64-bit words instead. This means the trigger vector and its
operations are now the same as for any other unpacked array. The few
special functions required for operating on a trigger vector are now
generated in V3SchedTrigger as regular AstCFunc if needed.

No functional change intended, performance should be the same.
This commit is contained in:
Geza Lore 2025-10-31 19:29:11 +01:00 committed by GitHub
parent 08330f5fe2
commit 922223a9c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 5233 additions and 3382 deletions

View File

@ -1,5 +1,5 @@
.. comment: generated by t_lint_didnotconverge_bad
.. code-block::
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] a)
-V{t#,#} 'stl' region trigger index 64 is active: @([hybrid] a)
%Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge after 100 tries

View File

@ -82,6 +82,9 @@ extern void VL_PRINTF_MT(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE;
/// Print a debug message from internals with standard prefix, with printf style format
extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE;
/// Print a debug message from string via VL_DBG_MSGF
inline void VL_DBG_MSGS(const std::string& str) VL_MT_SAFE { VL_DBG_MSGF("%s", str.c_str()); }
// EMIT_RULE: VL_RANDOM: oclean=dirty
inline IData VL_RANDOM_I() VL_MT_SAFE { return vl_rand64(); }
inline QData VL_RANDOM_Q() VL_MT_SAFE { return vl_rand64(); }

View File

@ -164,58 +164,6 @@ public:
inline std::string VL_TO_STRING(const VlProcessRef& p) { return std::string("process"); }
//===================================================================
// Activity trigger vector
template <std::size_t N_Size> //
class VlTriggerVec final {
// TODO: static assert N_Size > 0, and don't generate when empty
// MEMBERS
alignas(16) std::array<uint64_t, vlstd::roundUpToMultipleOf<64>(N_Size) / 64> m_flags;
public:
// CONSTRUCTOR
VlTriggerVec() { clear(); }
~VlTriggerVec() = default;
// METHODS
// Set all elements to false
void clear() { m_flags.fill(0); }
// Word at given 'wordIndex'
uint64_t word(size_t wordIndex) const { return m_flags[wordIndex]; }
// Set specified word to given value
void setWord(size_t wordIndex, uint64_t value) { m_flags[wordIndex] = value; }
// Set specified bit to given value
void setBit(size_t index, bool value) {
uint64_t& w = m_flags[index / 64];
const size_t bitIndex = index % 64;
w &= ~(1ULL << bitIndex);
w |= (static_cast<uint64_t>(value) << bitIndex);
}
// Return true iff at least one element is set
bool any() const {
for (size_t i = 0; i < m_flags.size(); ++i)
if (m_flags[i]) return true;
return false;
}
// Set all elements true in 'this' that are set in 'other'
void thisOr(const VlTriggerVec<N_Size>& other) {
for (size_t i = 0; i < m_flags.size(); ++i) m_flags[i] |= other.m_flags[i];
}
// Set elements of 'this' to 'a & !b' element-wise
void andNot(const VlTriggerVec<N_Size>& a, const VlTriggerVec<N_Size>& b) {
for (size_t i = 0; i < m_flags.size(); ++i) m_flags[i] = a.m_flags[i] & ~b.m_flags[i];
}
};
//===================================================================
// SystemVerilog event type

View File

@ -804,13 +804,14 @@ public:
void dumpGdbHeader() const;
// METHODS - Tree modifications
// Returns nodep, adds newp to end of nodep's list
// Returns nodep. If newp is not nullptr, then add it to end of nodep's list.
template <typename T_NodeResult, typename T_NodeNext>
static T_NodeResult* addNext(T_NodeResult* nodep, T_NodeNext* newp) {
static_assert(std::is_base_of<AstNode, T_NodeResult>::value,
"'T_NodeResult' must be a subtype of AstNode");
static_assert(std::is_base_of<T_NodeResult, T_NodeNext>::value,
"'T_NodeNext' must be a subtype of 'T_NodeResult'");
if (!newp) return nodep;
return static_cast<T_NodeResult*>(addNext<AstNode, AstNode>(nodep, newp));
}
inline AstNode* addNext(AstNode* newp);

View File

@ -427,7 +427,6 @@ public:
SCOPEPTR,
CHARPTR,
MTASKSTATE,
TRIGGERVEC,
DELAY_SCHEDULER,
TRIGGER_SCHEDULER,
DYNAMIC_TRIGGER_SCHEDULER,
@ -462,7 +461,6 @@ public:
"VerilatedScope*",
"char*",
"VlMTaskState",
"VlTriggerVec",
"VlDelayScheduler",
"VlTriggerScheduler",
"VlDynamicTriggerScheduler",
@ -477,16 +475,13 @@ public:
return names[m_e];
}
const char* dpiType() const {
static const char* const names[] = {"%E-unk", "svBit", "char",
"void*", "char", "int",
"%E-integer", "svLogic", "long long",
"double", "short", "%E-time",
"const char*", "%E-untyped", "dpiScope",
"const char*", "%E-mtaskstate", "%E-triggervec",
"%E-dly-sched", "%E-trig-sched", "%E-dyn-sched",
"%E-fork", "%E-proc-ref", "%E-rand-gen",
"%E-stdrand-gen", "IData", "QData",
"%E-logic-implct", " MAX"};
static const char* const names[]
= {"%E-unk", "svBit", "char", "void*", "char",
"int", "%E-integer", "svLogic", "long long", "double",
"short", "%E-time", "const char*", "%E-untyped", "dpiScope",
"const char*", "%E-mtaskstate", "%E-dly-sched", "%E-trig-sched", "%E-dyn-sched",
"%E-fork", "%E-proc-ref", "%E-rand-gen", "%E-stdrand-gen", "IData",
"QData", "%E-logic-implct", " MAX"};
return names[m_e];
}
static void selfTest() {
@ -520,7 +515,6 @@ public:
case SCOPEPTR: return 0; // opaque
case CHARPTR: return 0; // opaque
case MTASKSTATE: return 0; // opaque
case TRIGGERVEC: return 0; // opaque
case DELAY_SCHEDULER: return 0; // opaque
case TRIGGER_SCHEDULER: return 0; // opaque
case DYNAMIC_TRIGGER_SCHEDULER: return 0; // opaque
@ -565,10 +559,10 @@ public:
}
bool isOpaque() const VL_MT_SAFE { // IE not a simple number we can bit optimize
return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
|| m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DELAY_SCHEDULER
|| m_e == TRIGGER_SCHEDULER || m_e == DYNAMIC_TRIGGER_SCHEDULER || m_e == FORK_SYNC
|| m_e == PROCESS_REFERENCE || m_e == RANDOM_GENERATOR
|| m_e == RANDOM_STDGENERATOR || m_e == DOUBLE || m_e == UNTYPED);
|| m_e == MTASKSTATE || m_e == DELAY_SCHEDULER || m_e == TRIGGER_SCHEDULER
|| m_e == DYNAMIC_TRIGGER_SCHEDULER || m_e == FORK_SYNC || m_e == PROCESS_REFERENCE
|| m_e == RANDOM_GENERATOR || m_e == RANDOM_STDGENERATOR || m_e == DOUBLE
|| m_e == UNTYPED);
}
bool isDouble() const VL_MT_SAFE { return m_e == DOUBLE; }
bool isEvent() const { return m_e == EVENT; }
@ -614,7 +608,6 @@ public:
/* SCOPEPTR: */ "", // Should not be traced
/* CHARPTR: */ "", // Should not be traced
/* MTASKSTATE: */ "", // Should not be traced
/* TRIGGERVEC: */ "", // Should not be traced
/* DELAY_SCHEDULER: */ "", // Should not be traced
/* TRIGGER_SCHEDULER: */ "", // Should not be traced
/* DYNAMIC_TRIGGER_SCHEDULER: */ "", // Should not be traced
@ -807,13 +800,6 @@ public:
SCHED_RESUME,
SCHED_RESUMPTION,
SCHED_TRIGGER,
TRIGGER_AND_NOT,
TRIGGER_ANY,
TRIGGER_CLEAR,
TRIGGER_SET_BIT,
TRIGGER_SET_WORD,
TRIGGER_THIS_OR,
TRIGGER_WORD,
UNPACKED_ASSIGN,
UNPACKED_FILL,
UNPACKED_NEQ,
@ -942,13 +928,6 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
{SCHED_RESUME, "resume", false}, \
{SCHED_RESUMPTION, "resumption", false}, \
{SCHED_TRIGGER, "trigger", false}, \
{TRIGGER_AND_NOT, "andNot", false}, \
{TRIGGER_ANY, "any", true}, \
{TRIGGER_CLEAR, "clear", false}, \
{TRIGGER_SET_BIT, "setBit", false}, \
{TRIGGER_SET_WORD, "setWord", false}, \
{TRIGGER_THIS_OR, "thisOr", false}, \
{TRIGGER_WORD, "word", true}, \
{UNPACKED_ASSIGN, "assign", false}, \
{UNPACKED_FILL, "fill", false}, \
{UNPACKED_NEQ, "neq", true}, \

View File

@ -460,7 +460,6 @@ public:
bool isBitLogic() const { return keyword().isBitLogic(); }
bool isDouble() const VL_MT_STABLE { return keyword().isDouble(); }
bool isEvent() const VL_MT_STABLE { return keyword() == VBasicDTypeKwd::EVENT; }
bool isTriggerVec() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::TRIGGERVEC; }
bool isForkSync() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::FORK_SYNC; }
bool isProcessRef() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::PROCESS_REFERENCE; }
bool isDelayScheduler() const VL_MT_SAFE {

View File

@ -1615,7 +1615,7 @@ class AstText final : public AstNode {
// Avoid using this directly, internally usually want
// AstCStmt::add("text") or AstCExpr::add("text") instead
//
std::string m_text; // The text to emit
const std::string m_text; // The text to emit
public:
AstText(FileLine* fl, const std::string& text)
: ASTGEN_SUPER_Text(fl)
@ -1627,7 +1627,6 @@ public:
return text() == VN_DBG_AS(samep, Text)->text();
}
const std::string& text() const VL_MT_SAFE { return m_text; }
void text(const string& value) { m_text = value; }
};
class AstTextBlock final : public AstNode {
// Text block emitted into output, with some arbitrary nodes interspersed
@ -2215,7 +2214,7 @@ public:
}
bool needsCReset() const {
return !isIfaceParent() && !isIfaceRef() && !noReset() && !isParam() && !isStatementTemp()
&& !(basicp() && (basicp()->isEvent() || basicp()->isTriggerVec()));
&& !(basicp() && basicp()->isEvent());
}
static AstVar* scVarRecurse(AstNode* nodep);
};

View File

@ -1027,8 +1027,6 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packe
info.m_type = "std::string";
} else if (bdtypep->keyword().isMTaskState()) {
info.m_type = "VlMTaskVertex";
} else if (bdtypep->isTriggerVec()) {
info.m_type = "VlTriggerVec<" + cvtToStr(dtypep->width()) + ">";
} else if (bdtypep->isDelayScheduler()) {
info.m_type = "VlDelayScheduler";
} else if (bdtypep->isTriggerScheduler()) {
@ -3149,13 +3147,6 @@ void AstCAwait::dump(std::ostream& str) const {
}
void AstCAwait::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
int AstCMethodHard::instrCount() const {
if (const AstBasicDType* const basicp = fromp()->dtypep()->basicp()) {
if (basicp->isTriggerVec() && m_method == VCMethod::TRIGGER_WORD) {
// This is an important special case for scheduling so we compute it precisely,
// it is simply a load.
return INSTR_COUNT_LD;
}
}
return 0; // TODO
}
void AstCMethodHard::setPurity() {

View File

@ -188,8 +188,7 @@ class CastVisitor final : public VNVisitor {
if (nodep->access().isReadOnly() && VN_IS(backp, NodeExpr) && !VN_IS(backp, CCast)
&& !VN_IS(backp, NodeCCall) && !VN_IS(backp, CMethodHard) && !VN_IS(backp, SFormatF)
&& !VN_IS(backp, ArraySel) && !VN_IS(backp, StructSel) && !VN_IS(backp, RedXor)
&& (nodep->varp()->basicp() && !nodep->varp()->basicp()->isTriggerVec()
&& !nodep->varp()->basicp()->isForkSync()
&& (nodep->varp()->basicp() && !nodep->varp()->basicp()->isForkSync()
&& !nodep->varp()->basicp()->isProcessRef() && !nodep->varp()->basicp()->isEvent())
&& backp->width() && castSize(nodep) != castSize(nodep->varp())) {
// Cast vars to IData first, else below has upper bits wrongly set

View File

@ -305,7 +305,6 @@ class EmitCImp final : EmitCFunc {
// lower level subinst code does it.
} else if (varp->isParam()) {
} else if (varp->isStatic() && varp->isConst()) {
} else if (varp->basicp() && varp->basicp()->isTriggerVec()) {
} else if (VN_IS(varp->dtypep(), NBACommitQueueDType)) {
} else {
int vects = 0;

View File

@ -106,9 +106,41 @@ AstCFunc* V3Order::order(AstNetlist* netlistp, //
bool parallel, //
bool slow, //
const ExternalDomainsProvider& externalDomains) {
FileLine* const flp = netlistp->fileline();
// 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, externalDomains);
// Build the move graph
OrderMoveDomScope::clear();
const std::unique_ptr<OrderMoveGraph> moveGraphp = OrderMoveGraph::build(*graph, trigToSen);
if (dumpGraphLevel() >= 9) moveGraphp->dumpDotFilePrefixed(tag + "_ordermv");
// The ordered statements, if there are any
AstNodeStmt* stmtsp = nullptr;
if (!moveGraphp->empty()) {
if (parallel) {
stmtsp = createParallel(*graph, *moveGraphp, tag, slow);
} else {
stmtsp = createSerial(*moveGraphp, tag, slow);
}
// Should have consumed all vertices
UASSERT(moveGraphp->empty(), "Unconsumed vertices remain in OrderMoveGraph");
}
OrderMoveDomScope::clear();
// Dump data
if (dumpGraphLevel()) graph->dumpDotFilePrefixed(tag + "_orderg_done");
// Dispose of the remnants of the inputs
for (auto* const lbsp : logic) lbsp->deleteActives();
// If there is no resulting logic, then don't create an empty function
if (!stmtsp) return nullptr;
// Create the result function
FileLine* const flp = netlistp->fileline();
AstCFunc* const funcp = [&]() {
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
AstCFunc* const resp = new AstCFunc{flp, "_eval_" + tag, scopeTopp, ""};
@ -122,36 +154,14 @@ AstCFunc* V3Order::order(AstNetlist* netlistp, //
return resp;
}();
// Assemble the body
if (v3Global.opt.profExec()) {
const string name
const std::string name
= (v3Global.opt.hierChild() ? (v3Global.opt.topModule() + " ") : "") + "func " + tag;
funcp->addStmtsp(
new AstCStmt{flp, "VL_EXEC_TRACE_ADD_RECORD(vlSymsp).sectionPush(\"" + name + "\");"});
}
// 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, externalDomains);
if (parallel) {
// Construct the parallel code
AstNodeStmt* const stmtsp = createParallel(*graph, tag, trigToSen, slow);
funcp->addStmtsp(stmtsp);
} else {
// Construct the serial code
AstNodeStmt* const stmtsp = createSerial(*graph, tag, trigToSen, slow);
funcp->addStmtsp(stmtsp);
}
// Dump data
if (dumpGraphLevel()) graph->dumpDotFilePrefixed(tag + "_orderg_done");
// Dispose of the remnants of the inputs
for (auto* const lbsp : logic) lbsp->deleteActives();
funcp->addStmtsp(stmtsp);
if (v3Global.opt.profExec()) {
funcp->addStmtsp(new AstCStmt{flp, "VL_EXEC_TRACE_ADD_RECORD(vlSymsp).sectionPop();"});
}

View File

@ -136,7 +136,7 @@ class OrderGraphBuilder final : public VNVisitor {
VL_RESTORER(m_hybridp);
VL_RESTORER(m_inClocked);
// This is the original sensitivity of the block (i.e.: not the ref into the TRIGGERVEC)
// This is the original sensitivity of the block (i.e.: not the ref into the trigger vec)
const AstSenTree* const senTreep = nodep->sentreep()->hasCombo()
? nodep->sentreep()

View File

@ -22,6 +22,7 @@
#include "V3Order.h"
#include "V3OrderGraph.h"
#include "V3OrderMoveGraph.h"
#include <string>
#include <unordered_map>
@ -50,14 +51,13 @@ void processDomains(AstNetlist* netlistp, //
const std::string& tag, //
const ExternalDomainsProvider& externalDomains);
AstNodeStmt* createSerial(OrderGraph& orderGraph, //
AstNodeStmt* createSerial(OrderMoveGraph& moveGraph, //
const std::string& tag, //
const TrigToSenMap& trigToSenMap, //
bool slow);
AstNodeStmt* createParallel(OrderGraph& orderGraph, //
OrderMoveGraph& moveGraph, //
const std::string& tag, //
const TrigToSenMap& trigToSenMap, //
bool slow);
}; // namespace V3Order

View File

@ -29,7 +29,6 @@
#include "V3List.h"
#include "V3OrderCFuncEmitter.h"
#include "V3OrderInternal.h"
#include "V3OrderMoveGraph.h"
#include "V3Os.h"
#include "V3PairingHeap.h"
#include "V3Scoreboard.h"
@ -2395,36 +2394,30 @@ struct MTaskVxIdLessThan final {
}
};
AstNodeStmt* V3Order::createParallel(OrderGraph& orderGraph, const std::string& tag,
const TrigToSenMap& trigToSen, bool slow) {
AstNodeStmt* V3Order::createParallel(OrderGraph& orderGraph, OrderMoveGraph& moveGraph,
const std::string& tag, bool slow) {
UINFO(2, " Constructing parallel code for '" + tag + "'");
// For nondeterminism debug:
hashGraphDebug(orderGraph, "V3OrderParallel's input OrderGraph");
// Build the move graph
OrderMoveDomScope::clear();
const std::unique_ptr<OrderMoveGraph> moveGraphp
= OrderMoveGraph::build(orderGraph, trigToSen);
if (dumpGraphLevel() >= 9) moveGraphp->dumpDotFilePrefixed(tag + "_ordermv");
// Partition moveGraphp into LogicMTask's. The partitioner will set userp() on each logic
// vertex in the moveGraphp to the MTask it belongs to.
const std::unique_ptr<V3Graph> mTaskGraphp = Partitioner::apply(orderGraph, *moveGraphp);
if (dumpGraphLevel() >= 9) moveGraphp->dumpDotFilePrefixed(tag + "_ordermv_mtasks");
// Partition moveGraph into LogicMTask's. The partitioner will set userp() on each logic
// vertex in the moveGraph to the MTask it belongs to.
const std::unique_ptr<V3Graph> mTaskGraphp = Partitioner::apply(orderGraph, moveGraph);
if (dumpGraphLevel() >= 9) moveGraph.dumpDotFilePrefixed(tag + "_ordermv_mtasks");
// Some variable OrderMoveVertices are not assigned to an MTask. Reroute and delete these.
for (V3GraphVertex* const vtxp : moveGraphp->vertices().unlinkable()) {
for (V3GraphVertex* const vtxp : moveGraph.vertices().unlinkable()) {
OrderMoveVertex* const mVtxp = vtxp->as<OrderMoveVertex>();
if (!mVtxp->userp()) {
UASSERT_OBJ(!mVtxp->logicp(), mVtxp, "Logic OrderMoveVertex not assigned to mtask");
mVtxp->rerouteEdges(moveGraphp.get());
VL_DO_DANGLING(mVtxp->unlinkDelete(moveGraphp.get()), mVtxp);
mVtxp->rerouteEdges(&moveGraph);
VL_DO_DANGLING(mVtxp->unlinkDelete(&moveGraph), mVtxp);
}
}
// Remove all edges from the move graph that cross between MTasks. Add logic to MTask lists.
for (V3GraphVertex& vtx : moveGraphp->vertices()) {
for (V3GraphVertex& vtx : moveGraph.vertices()) {
OrderMoveVertex* const mVtxp = vtx.as<OrderMoveVertex>();
LogicMTask* const mtaskp = static_cast<LogicMTask*>(mVtxp->userp());
// Add to list in MTask, in MoveGraph order. This should not be necessary, but see #4993.
@ -2435,7 +2428,7 @@ AstNodeStmt* V3Order::createParallel(OrderGraph& orderGraph, const std::string&
if (mtaskp != toMVtxp->userp()) VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
}
}
if (dumpGraphLevel() >= 9) moveGraphp->dumpDotFilePrefixed(tag + "_ordermv_pruned");
if (dumpGraphLevel() >= 9) moveGraph.dumpDotFilePrefixed(tag + "_ordermv_pruned");
// Create the AstExecGraph node which represents the execution of the MTask graph.
FileLine* const rootFlp = v3Global.rootp()->fileline();
@ -2445,7 +2438,7 @@ AstNodeStmt* V3Order::createParallel(OrderGraph& orderGraph, const std::string&
// Translate the LogicMTask graph into the corresponding ExecMTask graph,
// which will outlive ordering.
std::unordered_map<const LogicMTask*, ExecMTask*> logicMTaskToExecMTask;
OrderMoveGraphSerializer serializer{*moveGraphp};
OrderMoveGraphSerializer serializer{moveGraph};
V3OrderCFuncEmitter emitter{tag, slow};
GraphStream<MTaskVxIdLessThan> mtaskStream{mTaskGraphp.get()};
while (const V3GraphVertex* const vtxp = mtaskStream.nextp()) {
@ -2472,7 +2465,7 @@ AstNodeStmt* V3Order::createParallel(OrderGraph& orderGraph, const std::string&
emitter.emitLogic(logicp);
}
// Can delete the vertex now
VL_DO_DANGLING(mVtxp->unlinkDelete(moveGraphp.get()), mVtxp);
VL_DO_DANGLING(mVtxp->unlinkDelete(&moveGraph), mVtxp);
}
// We have 2 objects, because AstMTaskBody is an AstNode, and ExecMTask is a GraphVertex.
@ -2502,15 +2495,12 @@ AstNodeStmt* V3Order::createParallel(OrderGraph& orderGraph, const std::string&
}
// Delete the remaining variable vertices
for (V3GraphVertex* const vtxp : moveGraphp->vertices().unlinkable()) {
for (V3GraphVertex* const vtxp : moveGraph.vertices().unlinkable()) {
if (!vtxp->as<OrderMoveVertex>()->logicp()) {
VL_DO_DANGLING(vtxp->unlinkDelete(moveGraphp.get()), vtxp);
VL_DO_DANGLING(vtxp->unlinkDelete(&moveGraph), vtxp);
}
}
UASSERT(moveGraphp->empty(), "Waiting vertices remain, but none are ready");
OrderMoveDomScope::clear();
return execGraphp;
}

View File

@ -22,7 +22,6 @@
#include "V3OrderCFuncEmitter.h"
#include "V3OrderInternal.h"
#include "V3OrderMoveGraph.h"
#include <memory>
@ -31,21 +30,15 @@ VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// OrderSerial class
AstNodeStmt* V3Order::createSerial(OrderGraph& graph, const std::string& tag,
const TrigToSenMap& trigToSen, bool slow) {
AstNodeStmt* V3Order::createSerial(OrderMoveGraph& moveGraph, const std::string& tag, bool slow) {
UINFO(2, " Constructing serial code for '" + tag + "'");
// Build the move graph
OrderMoveDomScope::clear();
const std::unique_ptr<OrderMoveGraph> moveGraphp = OrderMoveGraph::build(graph, trigToSen);
if (dumpGraphLevel() >= 9) moveGraphp->dumpDotFilePrefixed(tag + "_ordermv");
// Serializer
OrderMoveGraphSerializer serializer{*moveGraphp};
OrderMoveGraphSerializer serializer{moveGraph};
// Add initially ready vertices (those with no dependencies) to the serializer as seeds
for (V3GraphVertex& vtx : moveGraphp->vertices()) {
for (V3GraphVertex& vtx : moveGraph.vertices()) {
if (vtx.inEmpty()) serializer.addSeed(vtx.as<OrderMoveVertex>());
}
@ -63,18 +56,15 @@ AstNodeStmt* V3Order::createSerial(OrderGraph& graph, const std::string& tag,
emitter.emitLogic(logicp);
}
// Can delete the vertex now
VL_DO_DANGLING(mVtxp->unlinkDelete(moveGraphp.get()), mVtxp);
VL_DO_DANGLING(mVtxp->unlinkDelete(&moveGraph), mVtxp);
}
// Delete the remaining variable vertices
for (V3GraphVertex* const vtxp : moveGraphp->vertices().unlinkable()) {
for (V3GraphVertex* const vtxp : moveGraph.vertices().unlinkable()) {
if (!vtxp->as<OrderMoveVertex>()->logicp()) {
VL_DO_DANGLING(vtxp->unlinkDelete(moveGraphp.get()), vtxp);
VL_DO_DANGLING(vtxp->unlinkDelete(&moveGraph), vtxp);
}
}
UASSERT(moveGraphp->empty(), "Waiting vertices remain, but none are ready");
OrderMoveDomScope::clear();
return emitter.getStmts();
}

View File

@ -105,10 +105,10 @@ findTriggeredIface(const AstVarScope* vscp, const VirtIfaceTriggers::IfaceSensMa
// Eval loop builder
struct EvalLoop final {
// Flag set to true during the first iteration of the loop
// Flag set to true on entry to the first iteration of the loop
AstVarScope* firstIterp;
// The loop itself and statements around it
AstNodeStmt* stmtsp = nullptr;
AstNodeStmt* stmtsp;
};
// Create an eval loop with all the trimmings.
@ -117,8 +117,8 @@ EvalLoop createEvalLoop(
const std::string& tag, // Tag for current phase
const string& name, // Name of current phase
bool slow, // Should create slow functions
AstVarScope* trigp, // The trigger vector
AstCFunc* dumpFuncp, // Trigger dump function for debugging only
const TriggerKit& trigKit, // The trigger kit
AstVarScope* trigp, // The trigger vector - may be nullptr if no triggers
AstNodeStmt* innerp, // The inner loop, if any
AstNodeStmt* phasePrepp, // Prep statements run before checking triggers
AstNodeStmt* phaseWorkp, // The work to do if anything triggered
@ -127,6 +127,10 @@ EvalLoop createEvalLoop(
// and must be unmodified otherwise.
std::function<AstNodeStmt*(AstVarScope*)> phaseExtra = [](AstVarScope*) { return nullptr; } //
) {
// All work is under a trigger, so if there are no triggers, there is
// nothing to do besides executing the inner loop.
if (!trigp) return {nullptr, innerp};
const std::string varPrefix = "__V" + tag;
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
FileLine* const flp = netlistp->fileline();
@ -134,32 +138,37 @@ EvalLoop createEvalLoop(
// We wrap the prep/cond/work in a function for readability
AstCFunc* const phaseFuncp = util::makeTopFunction(netlistp, "_eval_phase__" + tag, slow);
{
// Add the preparatory statements
phaseFuncp->addStmtsp(phasePrepp);
// The execute flag
AstVarScope* const executeFlagp = scopeTopp->createTemp(varPrefix + "Execute", 1);
executeFlagp->varp()->noReset(true);
// Add the preparatory statements
phaseFuncp->addStmtsp(phasePrepp);
// If there is work in this phase, execute it if any triggers fired
if (phaseWorkp) {
// Check if any triggers are fired, save the result
AstNodeExpr* const lhsp = new AstVarRef{flp, executeFlagp, VAccess::WRITE};
AstNodeExpr* const rhsp = trigKit.newAnySetCall(trigp);
phaseFuncp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
// Check if any triggers are fired, save the result
AstCMethodHard* const callp = new AstCMethodHard{
flp, new AstVarRef{flp, trigp, VAccess::READ}, VCMethod::TRIGGER_ANY};
callp->dtypeSetBit();
phaseFuncp->addStmtsp(
new AstAssign{flp, new AstVarRef{flp, executeFlagp, VAccess::WRITE}, callp});
// Add the work
AstIf* const ifp = new AstIf{flp, new AstVarRef{flp, executeFlagp, VAccess::READ}};
ifp->addThensp(phaseWorkp);
phaseFuncp->addStmtsp(ifp);
// Add the work
AstIf* const ifp = new AstIf{flp, new AstVarRef{flp, executeFlagp, VAccess::READ}};
ifp->addThensp(phaseWorkp);
phaseFuncp->addStmtsp(ifp);
}
// Construct the extra statements
if (AstNodeStmt* const extrap = phaseExtra(executeFlagp)) phaseFuncp->addStmtsp(extrap);
AstNodeStmt* const extraWorkp = phaseExtra(executeFlagp);
if (extraWorkp) phaseFuncp->addStmtsp(extraWorkp);
// The function returns ture iff it did run the work
// The function returns ture iff it did run work
phaseFuncp->rtnType("bool");
phaseFuncp->addStmtsp(
new AstCReturn{flp, new AstVarRef{flp, executeFlagp, VAccess::READ}});
AstNodeExpr* const retp
= phaseWorkp || extraWorkp
? static_cast<AstNodeExpr*>(new AstVarRef{flp, executeFlagp, VAccess::READ})
: static_cast<AstNodeExpr*>(new AstConst{flp, AstConst::BitFalse{}});
phaseFuncp->addStmtsp(new AstCReturn{flp, retp});
}
// The result statements
@ -171,46 +180,38 @@ EvalLoop createEvalLoop(
const auto addVar = [&](const std::string& name, int width, uint32_t initVal) {
AstVarScope* const vscp = scopeTopp->createTemp("__V" + tag + name, width);
vscp->varp()->noReset(true);
vscp->varp()->isInternal(true);
stmtps = AstNode::addNext(stmtps, util::setVar(vscp, initVal));
return vscp;
};
// The iteration counter
AstVarScope* const counterp = addVar("IterCount", 32, 0);
// The first iteration flag
// The first iteration flag - cleared in 'phasePrepp' if used
AstVarScope* const firstIterFlagp = addVar("FirstIteration", 1, 1);
// The continuation flag
AstVarScope* const continueFlagp = addVar("Continue", 1, 1);
// The loop
{
AstNodeExpr* const condp = new AstVarRef{flp, continueFlagp, VAccess::READ};
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(new AstLoopTest{flp, loopp, condp});
stmtps->addNext(loopp);
// Check the iteration limit (aborts if exceeded)
loopp->addStmtsp(util::checkIterationLimit(netlistp, name, counterp, dumpFuncp));
AstNodeStmt* const dumpCallp = trigKit.newDumpCall(trigp, tag, false);
loopp->addStmtsp(util::checkIterationLimit(netlistp, name, counterp, dumpCallp));
// Increment the iteration counter
loopp->addStmtsp(util::incrementVar(counterp));
// Reset continuation flag
loopp->addStmtsp(util::setVar(continueFlagp, 0));
// Execute the inner loop
loopp->addStmtsp(innerp);
// Call the phase function to execute the current work. If we did
// work, then need to loop again, so set the continuation flag
// work, then need to loop again, so set the continuation flag.
// If used, the first iteration flag is cleared when consumed, no
// need to reset it
AstCCall* const callp = new AstCCall{flp, phaseFuncp};
callp->dtypeSetBit();
AstIf* const ifp = new AstIf{flp, callp};
ifp->addThensp(util::setVar(continueFlagp, 1));
loopp->addStmtsp(ifp);
// Clear the first iteration flag
loopp->addStmtsp(util::setVar(firstIterFlagp, 0));
stmtps->addNext(loopp);
// Continues until the continuation flag is clear
loopp->addStmtsp(new AstLoopTest{flp, loopp, callp});
}
// Prof-exec section pop
@ -356,53 +357,18 @@ void createFinal(AstNetlist* netlistp, const LogicClasses& logicClasses) {
util::splitCheck(funcp);
}
//============================================================================
// EvalKit groups items that have to be passed to createEval() for a given eval region
struct EvalKit final {
// The TRIGGERVEC AstVarScope representing the region's trigger flags
AstVarScope* const m_vscp = nullptr;
// The AstCFunc that computes the region's active triggers
AstCFunc* const m_triggerComputep = nullptr;
// The AstCFunc that dumps the region's active triggers
AstCFunc* const m_dumpp = nullptr;
// The AstCFunc that evaluates the region's logic
AstCFunc* const m_funcp = nullptr;
// Is this kit used/required?
bool empty() const { return !m_funcp; }
};
// Create an AstSenTree that is sensitive to the given trigger index. Must not exist yet!
AstSenTree* createTriggerSenTree(AstNetlist* netlistp, AstVarScope* const vscp, uint32_t index) {
UASSERT_OBJ(index != std::numeric_limits<unsigned>::max(), netlistp, "Invalid trigger index");
AstTopScope* const topScopep = netlistp->topScopep();
FileLine* const flp = topScopep->fileline();
AstVarRef* const vrefp = new AstVarRef{flp, vscp, VAccess::READ};
const uint32_t wordIndex = index / 64;
const uint32_t bitIndex = index % 64;
AstCMethodHard* const callp
= new AstCMethodHard{flp, vrefp, VCMethod::TRIGGER_WORD, new AstConst{flp, wordIndex}};
callp->dtypeSetUInt64();
AstNodeExpr* const termp
= new AstAnd{flp, new AstConst{flp, AstConst::Unsized64{}, 1ULL << bitIndex}, callp};
AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_TRUE, termp};
AstSenTree* const resultp = new AstSenTree{flp, senItemp};
topScopep->addSenTreesp(resultp);
return resultp;
}
//============================================================================
// Helper that creates virtual interface trigger resets
void addVirtIfaceTriggerAssignments(const VirtIfaceTriggers& virtIfaceTriggers,
size_t vifTriggerIndex, size_t vifMemberTriggerIndex,
const TriggerKit& actTrig) {
uint32_t vifTriggerIndex, uint32_t vifMemberTriggerIndex,
const TriggerKit& trigKit) {
for (const auto& p : virtIfaceTriggers.m_ifaceTriggers) {
actTrig.addExtraTriggerAssignment(p.second, vifTriggerIndex);
trigKit.addExtraTriggerAssignment(p.second, vifTriggerIndex);
++vifTriggerIndex;
}
for (const auto& p : virtIfaceTriggers.m_memberTriggers) {
actTrig.addExtraTriggerAssignment(p.second, vifMemberTriggerIndex);
trigKit.addExtraTriggerAssignment(p.second, vifMemberTriggerIndex);
++vifMemberTriggerIndex;
}
}
@ -421,24 +387,24 @@ void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilde
if (comb.empty() && hybrid.empty()) return;
// We have an extra trigger denoting this is the first iteration of the settle loop
ExtraTriggers extraTriggers;
const size_t firstIterationTrigger = extraTriggers.allocate("first iteration");
TriggerKit::ExtraTriggers extraTriggers;
const uint32_t firstIterationTrigger = extraTriggers.allocate("first iteration");
// Gather the relevant sensitivity expressions and create the trigger kit
const auto& senTreeps = getSenTreesUsedBy({&comb, &hybrid});
const TriggerKit trig = TriggerKit::create(netlistp, initFuncp, senExprBulider, senTreeps,
"stl", extraTriggers, true);
const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBulider, senTreeps,
"stl", extraTriggers, true);
// Remap sensitivities (comb has none, so only do the hybrid)
remapSensitivities(hybrid, trig.m_map);
remapSensitivities(hybrid, trigKit.map());
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
V3Order::TrigToSenMap trigToSen;
invertAndMergeSenTreeMap(trigToSen, trig.m_map);
invertAndMergeSenTreeMap(trigToSen, trigKit.map());
// First trigger is for pure combinational triggers (first iteration)
AstSenTree* const inputChanged
= createTriggerSenTree(netlistp, trig.m_vscp, firstIterationTrigger);
= trigKit.newTriggerSenTree(trigKit.vscp(), {firstIterationTrigger});
// Create and the body function
AstCFunc* const stlFuncp = V3Order::order(
@ -448,16 +414,16 @@ void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilde
// Create the eval loop
const EvalLoop stlLoop = createEvalLoop( //
netlistp, "stl", "Settle", /* slow: */ true, trig.m_vscp, trig.m_dumpp,
netlistp, "stl", "Settle", /* slow: */ true, trigKit, trigKit.vscp(),
// Inner loop statements
nullptr,
// Prep statements: Compute the current 'stl' triggers
util::callVoidFunc(trig.m_funcp),
trigKit.newCompCall(),
// Work statements: Invoke the 'stl' function
util::callVoidFunc(stlFuncp));
// Add the first iteration trigger to the trigger computation function
trig.addFirstIterationTriggerAssignment(stlLoop.firstIterp, firstIterationTrigger);
trigKit.addExtraTriggerAssignment(stlLoop.firstIterp, firstIterationTrigger);
// Add the eval loop to the top function
funcp->addStmtsp(stlLoop.stmtsp);
@ -489,11 +455,11 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
// We have some extra trigger denoting external conditions
AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp();
ExtraTriggers extraTriggers;
const size_t firstIterationTrigger = extraTriggers.allocate("first iteration");
const size_t dpiExportTriggerIndex = dpiExportTriggerVscp
? extraTriggers.allocate("DPI export trigger")
: std::numeric_limits<unsigned>::max();
TriggerKit::ExtraTriggers extraTriggers;
const uint32_t firstIterationTrigger = extraTriggers.allocate("first iteration");
const uint32_t dpiExportTriggerIndex = dpiExportTriggerVscp
? extraTriggers.allocate("DPI export trigger")
: std::numeric_limits<uint32_t>::max();
const size_t firstVifTriggerIndex = extraTriggers.size();
for (const auto& p : virtIfaceTriggers.m_ifaceTriggers) {
extraTriggers.allocate("virtual interface: " + p.first->name());
@ -507,34 +473,34 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
// Gather the relevant sensitivity expressions and create the trigger kit
const auto& senTreeps = getSenTreesUsedBy({&logic});
const TriggerKit trig = TriggerKit::create(netlistp, initFuncp, senExprBuilder, senTreeps,
"ico", extraTriggers, false);
const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBuilder, senTreeps,
"ico", extraTriggers, false);
if (dpiExportTriggerVscp) {
trig.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
trigKit.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
}
addVirtIfaceTriggerAssignments(virtIfaceTriggers, firstVifTriggerIndex,
firstVifMemberTriggerIndex, trig);
firstVifMemberTriggerIndex, trigKit);
// Remap sensitivities
remapSensitivities(logic, trig.m_map);
remapSensitivities(logic, trigKit.map());
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
V3Order::TrigToSenMap trigToSen;
invertAndMergeSenTreeMap(trigToSen, trig.m_map);
invertAndMergeSenTreeMap(trigToSen, trigKit.map());
// The trigger top level inputs (first iteration)
AstSenTree* const inputChanged
= createTriggerSenTree(netlistp, trig.m_vscp, firstIterationTrigger);
= trigKit.newTriggerSenTree(trigKit.vscp(), {firstIterationTrigger});
// The DPI Export trigger
AstSenTree* const dpiExportTriggered
= dpiExportTriggerVscp ? createTriggerSenTree(netlistp, trig.m_vscp, dpiExportTriggerIndex)
= dpiExportTriggerVscp ? trigKit.newTriggerSenTree(trigKit.vscp(), {dpiExportTriggerIndex})
: nullptr;
const auto& vifTriggeredIco
= virtIfaceTriggers.makeIfaceToSensMap(netlistp, firstVifTriggerIndex, trig.m_vscp);
const auto& vifMemberTriggeredIco
= virtIfaceTriggers.makeMemberToSensMap(netlistp, firstVifMemberTriggerIndex, trig.m_vscp);
= virtIfaceTriggers.makeIfaceToSensMap(trigKit, firstVifTriggerIndex, trigKit.vscp());
const auto& vifMemberTriggeredIco = virtIfaceTriggers.makeMemberToSensMap(
trigKit, firstVifMemberTriggerIndex, trigKit.vscp());
// Create and Order the body function
AstCFunc* const icoFuncp = V3Order::order(
@ -555,55 +521,38 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
// Create the eval loop
const EvalLoop icoLoop = createEvalLoop( //
netlistp, "ico", "Input combinational", /* slow: */ false, trig.m_vscp, trig.m_dumpp,
netlistp, "ico", "Input combinational", /* slow: */ false, trigKit, trigKit.vscp(),
// Inner loop statements
nullptr,
// Prep statements: Compute the current 'ico' triggers
util::callVoidFunc(trig.m_funcp),
trigKit.newCompCall(),
// Work statements: Invoke the 'ico' function
util::callVoidFunc(icoFuncp));
// Add the first iteration trigger to the trigger computation function
trig.addFirstIterationTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger);
trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger);
return icoLoop.stmtsp;
}
//============================================================================
// Helpers for 'createEval'
// EvalKit groups items that have to be passed to createEval() for a given eval region
AstStmtExpr* createTriggerClearCall(FileLine* const flp, AstVarScope* const vscp) { // Trigger
AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::WRITE};
AstCMethodHard* const callp = new AstCMethodHard{flp, refp, VCMethod::TRIGGER_CLEAR};
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstStmtExpr* createTriggerSetCall(FileLine* const flp, AstVarScope* const toVscp,
AstVarScope* const fromVscp) {
AstVarRef* const lhsp = new AstVarRef{flp, toVscp, VAccess::WRITE};
AstVarRef* const argp = new AstVarRef{flp, fromVscp, VAccess::READ};
AstCMethodHard* const callp = new AstCMethodHard{flp, lhsp, VCMethod::TRIGGER_THIS_OR, argp};
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstStmtExpr* createTriggerAndNotCall(FileLine* const flp, AstVarScope* const lhsVscp,
AstVarScope* const aVscp, AstVarScope* const bVscp) {
AstVarRef* const lhsp = new AstVarRef{flp, lhsVscp, VAccess::WRITE};
AstVarRef* const opap = new AstVarRef{flp, aVscp, VAccess::READ};
AstVarRef* const opbp = new AstVarRef{flp, bVscp, VAccess::READ};
opap->addNext(opbp);
AstCMethodHard* const callp = new AstCMethodHard{flp, lhsp, VCMethod::TRIGGER_AND_NOT, opap};
callp->dtypeSetVoid();
return callp->makeStmt();
}
struct EvalKit final {
// The AstVarScope representing the region's trigger vector
AstVarScope* const m_vscp = nullptr;
// The AstCFunc that evaluates the region's logic
AstCFunc* const m_funcp = nullptr;
// Is this kit used/required?
bool empty() const { return !m_funcp; }
};
//============================================================================
// Bolt together parts to create the top level _eval function
void createEval(AstNetlist* netlistp, //
AstNode* icoLoop, //
const TriggerKit& trigKit, //
const EvalKit& actKit, //
AstVarScope* preTrigsp, //
const EvalKit& nbaKit, //
@ -619,39 +568,40 @@ void createEval(AstNetlist* netlistp, //
AstCCall* const timingResumep = timingKit.createResume(netlistp);
// Create the active eval loop
const EvalLoop actLoop = createEvalLoop( //
netlistp, "act", "Active", /* slow: */ false, actKit.m_vscp, actKit.m_dumpp,
EvalLoop topLoop = createEvalLoop( //
netlistp, "act", "Active", /* slow: */ false, trigKit, actKit.m_vscp,
// Inner loop statements
nullptr,
// Prep statements
[&]() {
// Compute the current 'act' triggers
AstNodeStmt* const stmtsp = util::callVoidFunc(actKit.m_triggerComputep);
AstNodeStmt* stmtsp = trigKit.newCompCall();
// Commit trigger awaits from the previous iteration
if (timingCommitp) stmtsp->addNext(timingCommitp->makeStmt());
if (timingCommitp) stmtsp = AstNode::addNext(stmtsp, timingCommitp->makeStmt());
// Compute the 'pre' triggers
stmtsp = AstNode::addNext(
stmtsp, trigKit.newAndNotCall(preTrigsp, actKit.m_vscp, nbaKit.m_vscp));
// Latch the 'act' triggers under the 'nba' triggers
stmtsp = AstNode::addNext(stmtsp, trigKit.newOrIntoCall(nbaKit.m_vscp, actKit.m_vscp));
//
return stmtsp;
}(),
// Work statements
[&]() {
// Compute the 'pre' triggers
AstNodeStmt* const workp
= createTriggerAndNotCall(flp, preTrigsp, actKit.m_vscp, nbaKit.m_vscp);
// Latch the 'act' triggers under the 'nba' triggers
workp->addNext(createTriggerSetCall(flp, nbaKit.m_vscp, actKit.m_vscp));
AstNodeStmt* workp = nullptr;
// Resume triggered timing schedulers
if (timingResumep) workp->addNext(timingResumep->makeStmt());
if (timingResumep) workp = timingResumep->makeStmt();
// Invoke the 'act' function
workp->addNext(util::callVoidFunc(actKit.m_funcp));
workp = AstNode::addNext(workp, util::callVoidFunc(actKit.m_funcp));
//
return workp;
}());
// Create the NBA eval loop, which is the default top level loop.
EvalLoop topLoop = createEvalLoop( //
netlistp, "nba", "NBA", /* slow: */ false, nbaKit.m_vscp, nbaKit.m_dumpp,
topLoop = createEvalLoop( //
netlistp, "nba", "NBA", /* slow: */ false, trigKit, nbaKit.m_vscp,
// Inner loop statements
actLoop.stmtsp,
topLoop.stmtsp,
// Prep statements
nullptr,
// Work statements
@ -659,14 +609,14 @@ void createEval(AstNetlist* netlistp, //
AstNodeStmt* workp = nullptr;
// Latch the 'nba' trigger flags under the following region's trigger flags
if (!obsKit.empty()) {
workp = createTriggerSetCall(flp, obsKit.m_vscp, nbaKit.m_vscp);
workp = trigKit.newOrIntoCall(obsKit.m_vscp, nbaKit.m_vscp);
} else if (!reactKit.empty()) {
workp = createTriggerSetCall(flp, reactKit.m_vscp, nbaKit.m_vscp);
workp = trigKit.newOrIntoCall(reactKit.m_vscp, nbaKit.m_vscp);
}
// Invoke the 'nba' function
workp = AstNode::addNext(workp, util::callVoidFunc(nbaKit.m_funcp));
// Clear the 'nba' triggers
workp->addNext(createTriggerClearCall(flp, nbaKit.m_vscp));
workp = AstNode::addNext(workp, trigKit.newClearCall(nbaKit.m_vscp));
//
return workp;
}(),
@ -694,7 +644,7 @@ void createEval(AstNetlist* netlistp, //
if (!obsKit.empty()) {
// Create the Observed eval loop, which becomes the top level loop.
topLoop = createEvalLoop( //
netlistp, "obs", "Observed", /* slow: */ false, obsKit.m_vscp, obsKit.m_dumpp,
netlistp, "obs", "Observed", /* slow: */ false, trigKit, obsKit.m_vscp,
// Inner loop statements
topLoop.stmtsp,
// Prep statements
@ -704,12 +654,12 @@ void createEval(AstNetlist* netlistp, //
AstNodeStmt* workp = nullptr;
// Latch the Observed trigger flags under the Reactive trigger flags
if (!reactKit.empty()) {
workp = createTriggerSetCall(flp, reactKit.m_vscp, obsKit.m_vscp);
workp = trigKit.newOrIntoCall(reactKit.m_vscp, obsKit.m_vscp);
}
// Invoke the 'obs' function
workp = AstNode::addNext(workp, util::callVoidFunc(obsKit.m_funcp));
// Clear the 'obs' triggers
workp->addNext(createTriggerClearCall(flp, obsKit.m_vscp));
workp = AstNode::addNext(workp, trigKit.newClearCall(obsKit.m_vscp));
//
return workp;
}());
@ -718,7 +668,7 @@ void createEval(AstNetlist* netlistp, //
if (!reactKit.empty()) {
// Create the Reactive eval loop, which becomes the top level loop.
topLoop = createEvalLoop( //
netlistp, "react", "Reactive", /* slow: */ false, reactKit.m_vscp, reactKit.m_dumpp,
netlistp, "react", "Reactive", /* slow: */ false, trigKit, reactKit.m_vscp,
// Inner loop statements
topLoop.stmtsp,
// Prep statements
@ -726,9 +676,9 @@ void createEval(AstNetlist* netlistp, //
// Work statements
[&]() {
// Invoke the 'react' function
AstNodeStmt* const workp = util::callVoidFunc(reactKit.m_funcp);
AstNodeStmt* workp = util::callVoidFunc(reactKit.m_funcp);
// Clear the 'react' triggers
workp->addNext(createTriggerClearCall(flp, reactKit.m_vscp));
workp = AstNode::addNext(workp, trigKit.newClearCall(reactKit.m_vscp));
return workp;
}());
}
@ -757,22 +707,22 @@ void createEval(AstNetlist* netlistp, //
// Helper that builds virtual interface trigger sentrees
VirtIfaceTriggers::IfaceSensMap
VirtIfaceTriggers::makeIfaceToSensMap(AstNetlist* const netlistp, size_t vifTriggerIndex,
VirtIfaceTriggers::makeIfaceToSensMap(const TriggerKit& trigKit, uint32_t vifTriggerIndex,
AstVarScope* trigVscp) const {
std::map<const AstIface*, AstSenTree*> map;
for (const auto& p : m_ifaceTriggers) {
map.emplace(p.first, createTriggerSenTree(netlistp, trigVscp, vifTriggerIndex));
map.emplace(p.first, trigKit.newTriggerSenTree(trigVscp, {vifTriggerIndex}));
++vifTriggerIndex;
}
return map;
}
VirtIfaceTriggers::IfaceMemberSensMap
VirtIfaceTriggers::makeMemberToSensMap(AstNetlist* const netlistp, size_t vifTriggerIndex,
VirtIfaceTriggers::makeMemberToSensMap(const TriggerKit& trigKit, uint32_t vifTriggerIndex,
AstVarScope* trigVscp) const {
IfaceMemberSensMap map;
for (const auto& p : m_memberTriggers) {
map.emplace(p.first, createTriggerSenTree(netlistp, trigVscp, vifTriggerIndex));
map.emplace(p.first, trigKit.newTriggerSenTree(trigVscp, {vifTriggerIndex}));
++vifTriggerIndex;
}
return map;
@ -871,15 +821,15 @@ void schedule(AstNetlist* netlistp) {
netlistp->dpiExportTriggerp(nullptr); // Finished with this here
// We may have an extra trigger for variable updated in DPI exports
ExtraTriggers extraTriggers;
const size_t dpiExportTriggerIndex = dpiExportTriggerVscp
? extraTriggers.allocate("DPI export trigger")
: std::numeric_limits<unsigned>::max();
const size_t firstVifTriggerIndex = extraTriggers.size();
TriggerKit::ExtraTriggers extraTriggers;
const uint32_t dpiExportTriggerIndex = dpiExportTriggerVscp
? extraTriggers.allocate("DPI export trigger")
: std::numeric_limits<uint32_t>::max();
const uint32_t firstVifTriggerIndex = extraTriggers.size();
for (const auto& p : virtIfaceTriggers.m_ifaceTriggers) {
extraTriggers.allocate("virtual interface: " + p.first->name());
}
const size_t firstVifMemberTriggerIndex = extraTriggers.size();
const uint32_t firstVifMemberTriggerIndex = extraTriggers.size();
for (const auto& p : virtIfaceTriggers.m_memberTriggers) {
const auto& item = p.first;
extraTriggers.allocate("virtual interface member: " + item.m_ifacep->name() + "."
@ -892,20 +842,20 @@ void schedule(AstNetlist* netlistp) {
&logicRegions.m_obs, //
&logicRegions.m_react, //
&timingKit.m_lbs});
const TriggerKit actTrig = TriggerKit::create(netlistp, staticp, senExprBuilder, senTreeps,
const TriggerKit trigKit = TriggerKit::create(netlistp, staticp, senExprBuilder, senTreeps,
"act", extraTriggers, false);
// Add post updates from the timing kit
if (timingKit.m_postUpdates) actTrig.m_funcp->addStmtsp(timingKit.m_postUpdates);
if (timingKit.m_postUpdates) trigKit.compp()->addStmtsp(timingKit.m_postUpdates);
if (dpiExportTriggerVscp) {
actTrig.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
trigKit.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
}
addVirtIfaceTriggerAssignments(virtIfaceTriggers, firstVifTriggerIndex,
firstVifMemberTriggerIndex, actTrig);
firstVifMemberTriggerIndex, trigKit);
AstVarScope* const actTrigVscp = actTrig.m_vscp;
AstVarScope* const preTrigVscp = scopeTopp->createTempLike("__VpreTriggered", actTrigVscp);
AstVarScope* const actTrigVscp = trigKit.vscp();
AstVarScope* const preTrigVscp = trigKit.newTrigVec("pre");
const auto cloneMapWithNewTriggerReferences
= [=](const std::unordered_map<const AstSenTree*, AstSenTree*>& map, AstVarScope* vscp) {
@ -925,7 +875,7 @@ void schedule(AstNetlist* netlistp) {
return newMap;
};
const auto& actTrigMap = actTrig.m_map;
const auto& actTrigMap = trigKit.map();
const auto preTrigMap = cloneMapWithNewTriggerReferences(actTrigMap, preTrigVscp);
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-triggers");
@ -950,14 +900,13 @@ void schedule(AstNetlist* netlistp) {
// The DPI Export trigger AstSenTree
AstSenTree* const dpiExportTriggeredAct
= dpiExportTriggerVscp
? createTriggerSenTree(netlistp, actTrig.m_vscp, dpiExportTriggerIndex)
: nullptr;
= dpiExportTriggerVscp ? trigKit.newTriggerSenTree(trigKit.vscp(), {dpiExportTriggerIndex})
: nullptr;
const auto& vifTriggeredAct
= virtIfaceTriggers.makeIfaceToSensMap(netlistp, firstVifTriggerIndex, actTrig.m_vscp);
= virtIfaceTriggers.makeIfaceToSensMap(trigKit, firstVifTriggerIndex, trigKit.vscp());
const auto& vifMemberTriggeredAct = virtIfaceTriggers.makeMemberToSensMap(
netlistp, firstVifMemberTriggerIndex, actTrig.m_vscp);
trigKit, firstVifMemberTriggerIndex, trigKit.vscp());
AstCFunc* const actFuncp = V3Order::order(
netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct,
@ -974,14 +923,13 @@ void schedule(AstNetlist* netlistp) {
util::splitCheck(actFuncp);
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-act");
const EvalKit& actKit = {actTrig.m_vscp, actTrig.m_funcp, actTrig.m_dumpp, actFuncp};
const EvalKit actKit{trigKit.vscp(), actFuncp};
// Orders a region's logic and creates the region eval function
const auto order = [&](const std::string& name,
const std::vector<V3Sched::LogicByScope*>& logic) -> EvalKit {
UINFO(2, "Scheduling " << name << " #logic = " << logic.size());
AstVarScope* const trigVscp
= scopeTopp->createTempLike("__V" + name + "Triggered", actTrigVscp);
AstVarScope* const trigVscp = trigKit.newTrigVec(name);
const auto trigMap = cloneMapWithNewTriggerReferences(actTrigMap, trigVscp);
// Remap sensitivities of the input logic to the triggers
for (LogicByScope* lbs : logic) remapSensitivities(*lbs, trigMap);
@ -991,13 +939,12 @@ void schedule(AstNetlist* netlistp) {
invertAndMergeSenTreeMap(trigToSen, trigMap);
AstSenTree* const dpiExportTriggered
= dpiExportTriggerVscp
? createTriggerSenTree(netlistp, trigVscp, dpiExportTriggerIndex)
: nullptr;
= dpiExportTriggerVscp ? trigKit.newTriggerSenTree(trigVscp, {dpiExportTriggerIndex})
: nullptr;
const auto& vifTriggered
= virtIfaceTriggers.makeIfaceToSensMap(netlistp, firstVifTriggerIndex, trigVscp);
const auto& vifMemberTriggered = virtIfaceTriggers.makeMemberToSensMap(
netlistp, firstVifMemberTriggerIndex, trigVscp);
= virtIfaceTriggers.makeIfaceToSensMap(trigKit, firstVifTriggerIndex, trigVscp);
const auto& vifMemberTriggered
= virtIfaceTriggers.makeMemberToSensMap(trigKit, firstVifMemberTriggerIndex, trigVscp);
const auto& timingDomains = timingKit.remapDomains(trigMap);
AstCFunc* const funcp = V3Order::order(
@ -1013,27 +960,11 @@ void schedule(AstNetlist* netlistp) {
}
});
// Create the trigger dumping function, which is the same as act trigger
// dumping function, but referencing this region's trigger vector.
AstCFunc* const dumpp = actTrig.m_dumpp->cloneTree(false);
actTrig.m_dumpp->addNextHere(dumpp);
dumpp->name("_dump_triggers__" + name);
dumpp->foreach([&](AstVarRef* refp) {
UASSERT_OBJ(refp->access().isReadOnly(), refp, "Should only read state");
if (refp->varScopep() == actTrig.m_vscp) {
refp->replaceWith(new AstVarRef{refp->fileline(), trigVscp, VAccess::READ});
VL_DO_DANGLING(refp->deleteTree(), refp);
}
});
dumpp->foreach([&](AstText* textp) { //
textp->text(VString::replaceWord(textp->text(), "act", name));
});
return {trigVscp, nullptr, dumpp, funcp};
return {trigVscp, funcp};
};
// Step 10: Create the 'nba' region evaluation function
const EvalKit& nbaKit = order("nba", {&logicRegions.m_nba, &logicReplicas.m_nba});
const EvalKit nbaKit = order("nba", {&logicRegions.m_nba, &logicReplicas.m_nba});
util::splitCheck(nbaKit.m_funcp);
netlistp->evalNbap(nbaKit.m_funcp); // Remember for V3LifePost
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-nba");
@ -1050,18 +981,18 @@ void schedule(AstNetlist* netlistp) {
};
// Step 11: Create the 'obs' region evaluation function
const EvalKit& obsKit = orderIfNonEmpty("obs", {&logicRegions.m_obs, &logicReplicas.m_obs});
const EvalKit obsKit = orderIfNonEmpty("obs", {&logicRegions.m_obs, &logicReplicas.m_obs});
// Step 12: Create the 're' region evaluation function
const EvalKit& reactKit
const EvalKit reactKit
= orderIfNonEmpty("react", {&logicRegions.m_react, &logicReplicas.m_react});
// Step 13: Create the 'postponed' region evaluation function
auto* const postponedFuncp = createPostponed(netlistp, logicClasses);
// Step 14: Bolt it all together to create the '_eval' function
createEval(netlistp, icoLoopp, actKit, preTrigVscp, nbaKit, obsKit, reactKit, postponedFuncp,
timingKit);
createEval(netlistp, icoLoopp, trigKit, actKit, preTrigVscp, nbaKit, obsKit, reactKit,
postponedFuncp, timingKit);
// Haven't split static initializer yet
util::splitCheck(staticp);

View File

@ -128,48 +128,99 @@ struct LogicReplicas final {
LogicReplicas& operator=(LogicReplicas&&) = default;
};
// Utility for extra trigger allocation
class ExtraTriggers final {
std::vector<string> m_descriptions; // Human readable description of extra triggers
// A TriggerKit holds all the components related to a trigger vector
class TriggerKit final {
// Triggers are storead as an UnpackedArray with a fixed word size
static constexpr uint32_t WORD_SIZE_LOG2 = 6; // 64-bits / VL_QUADSIZE
static constexpr uint32_t WORD_SIZE = 1 << WORD_SIZE_LOG2;
const std::string m_name; // TriggerKit name
const bool m_slow; // TriggerKit is for schedulign 'slow' code
const uint32_t m_nWords; // Number of word in trigger vector
// Data type of a single trigger word
AstNodeDType* m_wordDTypep = nullptr;
// Data type of a trigger vector
AstNodeDType* m_trigDTypep = nullptr;
// The AstVarScope representing the trigger vector
AstVarScope* m_vscp = nullptr;
// The AstCFunc that computes the current active triggers
AstCFunc* m_compp = nullptr;
// The AstCFunc that dumps the current active triggers
AstCFunc* m_dumpp = nullptr;
// The AstCFunc testing if a trigger vector has any bits set - create lazily
mutable AstCFunc* m_anySetp = nullptr;
// The AstCFunc setting a tigger vector to (_ & ~_) of 2 other trigger vectors - create lazily
mutable AstCFunc* m_andNotp = nullptr;
// The AstCFunc setting bits in a trigger vector that are set in another - create lazily
mutable AstCFunc* m_orIntop = nullptr;
// The AstCFunc setting a trigger vector to all zeroes - create lazily
mutable AstCFunc* m_clearp = nullptr;
// The map from input sensitivity list to trigger sensitivity list
std::unordered_map<const AstSenTree*, AstSenTree*> m_map;
// Methods to lazy construct functions processing trigger vectors
AstCFunc* createAndNotFunc() const;
AstCFunc* createAnySetFunc() const;
AstCFunc* createClearFunc() const;
AstCFunc* createOrIntoFunc() const;
TriggerKit(const std::string& name, bool slow, uint32_t nWords);
VL_UNCOPYABLE(TriggerKit);
TriggerKit& operator=(TriggerKit&&) = delete;
public:
ExtraTriggers() = default;
~ExtraTriggers() = default;
// Move constructible
TriggerKit(TriggerKit&&) = default;
~TriggerKit() = default;
size_t allocate(const string& description) {
m_descriptions.push_back(description);
return m_descriptions.size() - 1;
}
size_t size() const { return m_descriptions.size(); }
const string& description(size_t index) const { return m_descriptions[index]; }
};
// Utility for extra trigger allocation
class ExtraTriggers final {
friend class TriggerKit;
std::vector<string> m_descriptions; // Human readable description of extra triggers
// A TriggerKit holds all the components related to a TRIGGERVEC variable
struct TriggerKit final {
// The TRIGGERVEC AstVarScope representing these trigger flags
AstVarScope* const m_vscp;
// The AstCFunc that computes the current active triggers
AstCFunc* const m_funcp;
// The AstCFunc that dumps the current active triggers
AstCFunc* const m_dumpp;
// The map from input sensitivity list to trigger sensitivity list
const std::unordered_map<const AstSenTree*, AstSenTree*> m_map;
public:
ExtraTriggers() = default;
~ExtraTriggers() = default;
// No VL_UNCOPYABLE(TriggerKit) as causes C++20 errors on MSVC
// Assigns the given index trigger to fire when the given variable is zero
void addFirstIterationTriggerAssignment(AstVarScope* flagp, uint32_t index) const;
// Set then clear an extra trigger
void addExtraTriggerAssignment(AstVarScope* extraTriggerVscp, uint32_t index) const;
uint32_t allocate(const string& description) {
m_descriptions.push_back(description);
return m_descriptions.size() - 1;
}
uint32_t size() const { return m_descriptions.size(); }
};
// Create a TriggerKit for the given AstSenTree vector
static const TriggerKit create(AstNetlist* netlistp, //
AstCFunc* const initFuncp, //
SenExprBuilder& senExprBuilder, //
const std::vector<const AstSenTree*>& senTreeps, //
const string& name, //
const ExtraTriggers& extraTriggers, //
bool slow);
static TriggerKit create(AstNetlist* netlistp, //
AstCFunc* const initFuncp, //
SenExprBuilder& senExprBuilder, //
const std::vector<const AstSenTree*>& senTreeps, //
const string& name, //
const ExtraTriggers& extraTriggers, //
bool slow);
// ACCESSORS
AstVarScope* vscp() const { return m_vscp; }
AstCFunc* compp() const { return m_compp; }
const std::unordered_map<const AstSenTree*, AstSenTree*>& map() const { return m_map; }
// Helpers for code generation - lazy construct relevant functions
AstNodeStmt* newAndNotCall(AstVarScope* op, AstVarScope* ap, AstVarScope* bp) const;
AstNodeExpr* newAnySetCall(AstVarScope* vscp) const;
AstNodeStmt* newClearCall(AstVarScope* vscp) const;
AstNodeStmt* newOrIntoCall(AstVarScope* op, AstVarScope* ip) const;
// Helpers for code generation
AstNodeStmt* newCompCall() const;
AstNodeStmt* newDumpCall(AstVarScope* vscp, const std::string& tag, bool debugOnly) const;
// Create a new trigger vector - might return nullptr if there are no triggers
AstVarScope* newTrigVec(const std::string& name) const;
// Create an AstSenTree that is sensitive to the given trigger indices
AstSenTree* newTriggerSenTree(AstVarScope* vscp, const std::vector<uint32_t>& indices) const;
// Set then extra trigger bit at 'index' to the value of 'vscp', then set 'vscp' to 0
void addExtraTriggerAssignment(AstVarScope* vscp, uint32_t index) const;
};
// Everything needed for combining timing with static scheduling.
@ -238,10 +289,10 @@ public:
return nullptr;
}
IfaceMemberSensMap makeMemberToSensMap(AstNetlist* netlistp, size_t vifTriggerIndex,
IfaceMemberSensMap makeMemberToSensMap(const TriggerKit& trigKit, uint32_t vifTriggerIndex,
AstVarScope* trigVscp) const;
IfaceSensMap makeIfaceToSensMap(AstNetlist* netlistp, size_t vifTriggerIndex,
IfaceSensMap makeIfaceToSensMap(const TriggerKit& trigKit, uint32_t vifTriggerIndex,
AstVarScope* trigVscp) const;
VL_UNCOPYABLE(VirtIfaceTriggers);
@ -283,7 +334,7 @@ AstNodeStmt* incrementVar(AstVarScope* vscp);
AstNodeStmt* callVoidFunc(AstCFunc* funcp);
// Create statement that checks counterp' to see if the eval loop iteration limit is reached
AstNodeStmt* checkIterationLimit(AstNetlist* netlistp, const string& name, AstVarScope* counterp,
AstCFunc* trigDumpp);
AstNodeStmt* dumpCallp);
// Create statement that pushed a --prof-exec section
AstNodeStmt* profExecSectionPush(FileLine* flp, const string& section);
// Create statement that pops a --prof-exec section

View File

@ -32,158 +32,454 @@ VL_DEFINE_DEBUG_FUNCTIONS;
namespace V3Sched {
void TriggerKit::addFirstIterationTriggerAssignment(AstVarScope* flagp, uint32_t index) const {
FileLine* const flp = flagp->fileline();
AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, VCMethod::TRIGGER_SET_BIT};
callp->addPinsp(new AstConst{flp, index});
callp->addPinsp(new AstVarRef{flp, flagp, VAccess::READ});
callp->dtypeSetVoid();
m_funcp->stmtsp()->addHereThisAsNext(callp->makeStmt());
namespace {
AstVarScope* newArgument(AstCFunc* funcp, AstNodeDType* dtypep, const std::string& name,
VDirection direction) {
FileLine* const flp = funcp->fileline();
AstScope* const scopep = funcp->scopep();
AstVar* const varp = new AstVar{flp, VVarType::BLOCKTEMP, name, dtypep};
varp->funcLocal(true);
varp->direction(direction);
funcp->addArgsp(varp);
AstVarScope* const vscp = new AstVarScope{flp, scopep, varp};
scopep->addVarsp(vscp);
return vscp;
}
// Utility to set then clear an extra trigger
void TriggerKit::addExtraTriggerAssignment(AstVarScope* extraTriggerVscp, uint32_t index) const {
FileLine* const flp = extraTriggerVscp->fileline();
AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, VCMethod::TRIGGER_SET_BIT};
callp->addPinsp(new AstConst{flp, index});
callp->addPinsp(new AstVarRef{flp, extraTriggerVscp, VAccess::READ});
callp->dtypeSetVoid();
AstNode* const stmtp = callp->makeStmt();
stmtp->addNext(new AstAssign{flp, new AstVarRef{flp, extraTriggerVscp, VAccess::WRITE},
new AstConst{flp, AstConst::BitFalse{}}});
m_funcp->stmtsp()->addHereThisAsNext(stmtp);
AstVarScope* newLocal(AstCFunc* funcp, AstNodeDType* dtypep, const std::string& name) {
FileLine* const flp = funcp->fileline();
AstScope* const scopep = funcp->scopep();
AstVar* const varp = new AstVar{flp, VVarType::BLOCKTEMP, name, dtypep};
varp->funcLocal(true);
funcp->addVarsp(varp);
AstVarScope* const vscp = new AstVarScope{flp, scopep, varp};
scopep->addVarsp(vscp);
return vscp;
}
// Create a TRIGGERVEC and the related TriggerKit for the given AstSenTree vector
const TriggerKit TriggerKit::create(AstNetlist* netlistp, //
AstCFunc* const initFuncp, //
SenExprBuilder& senExprBuilder, //
const std::vector<const AstSenTree*>& senTreeps, //
const string& name, //
const ExtraTriggers& extraTriggers, //
bool slow) {
} // namespace
AstCFunc* TriggerKit::createAndNotFunc() const {
AstNetlist* const netlistp = v3Global.rootp();
FileLine* const flp = netlistp->topScopep()->fileline();
// Create the function
AstCFunc* const funcp = util::makeSubFunction(netlistp, "_trigger_andNot__" + m_name, m_slow);
funcp->isStatic(true);
// Add arguments
AstVarScope* const oVscp = newArgument(funcp, m_trigDTypep, "out", VDirection::OUTPUT);
AstVarScope* const aVscp = newArgument(funcp, m_trigDTypep, "inA", VDirection::CONSTREF);
AstVarScope* const bVscp = newArgument(funcp, m_trigDTypep, "inB", VDirection::CONSTREF);
// Add loop counter variable
AstVarScope* const nVscp
= newLocal(funcp, netlistp->findBitDType(32, 32, VSigning::UNSIGNED), "n");
nVscp->varp()->noReset(true);
// Creates read/write reference
const auto rd = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::READ}; };
const auto wr = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::WRITE}; };
// Function body
AstLoop* const loopp = new AstLoop{flp};
funcp->addStmtsp(util::setVar(nVscp, 0));
funcp->addStmtsp(loopp);
// Loop body
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(oVscp), rd(nVscp)};
AstNodeExpr* const aWordp = new AstArraySel{flp, rd(aVscp), rd(nVscp)};
AstNodeExpr* const bWordp = new AstArraySel{flp, rd(bVscp), rd(nVscp)};
AstNodeExpr* const rhsp = new AstAnd{flp, aWordp, new AstNot{flp, bWordp}};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, m_nWords};
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
loopp->addStmtsp(util::incrementVar(nVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
// Done
return funcp;
}
AstCFunc* TriggerKit::createAnySetFunc() const {
AstNetlist* const netlistp = v3Global.rootp();
FileLine* const flp = netlistp->topScopep()->fileline();
// Create function
AstCFunc* const funcp = util::makeSubFunction(netlistp, "_trigger_anySet__" + m_name, m_slow);
funcp->isStatic(true);
funcp->rtnType("bool");
// Add argument
AstVarScope* const iVscp = newArgument(funcp, m_trigDTypep, "in", VDirection::CONSTREF);
// Add loop counter variable
AstVarScope* const nVscp
= newLocal(funcp, netlistp->findBitDType(32, 32, VSigning::UNSIGNED), "n");
nVscp->varp()->noReset(true);
// Creates read reference
const auto rd = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::READ}; };
// Function body
AstLoop* const loopp = new AstLoop{flp};
funcp->addStmtsp(util::setVar(nVscp, 0));
funcp->addStmtsp(loopp);
funcp->addStmtsp(new AstCReturn{flp, new AstConst{flp, AstConst::BitFalse{}}});
// Loop body
AstNodeExpr* const condp = new AstArraySel{flp, rd(iVscp), rd(nVscp)};
AstNodeStmt* const thenp = new AstCReturn{flp, new AstConst{flp, AstConst::BitTrue{}}};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, m_nWords};
loopp->addStmtsp(new AstIf{flp, condp, thenp});
loopp->addStmtsp(util::incrementVar(nVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
// Done
return funcp;
}
AstCFunc* TriggerKit::createClearFunc() const {
AstNetlist* const netlistp = v3Global.rootp();
FileLine* const flp = netlistp->topScopep()->fileline();
// Create function
AstCFunc* const funcp = util::makeSubFunction(netlistp, "_trigger_clear__" + m_name, m_slow);
funcp->isStatic(true);
// Add arguments
AstVarScope* const oVscp = newArgument(funcp, m_trigDTypep, "out", VDirection::OUTPUT);
// Add loop counter variable
AstVarScope* const nVscp
= newLocal(funcp, netlistp->findBitDType(32, 32, VSigning::UNSIGNED), "n");
nVscp->varp()->noReset(true);
// Creates read/write reference
const auto rd = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::READ}; };
const auto wr = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::WRITE}; };
// Function body
AstLoop* const loopp = new AstLoop{flp};
funcp->addStmtsp(util::setVar(nVscp, 0));
funcp->addStmtsp(loopp);
// Loop body
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(oVscp), rd(nVscp)};
AstNodeExpr* const rhsp = new AstConst{flp, AstConst::DTyped{}, m_wordDTypep};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, m_nWords};
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
loopp->addStmtsp(util::incrementVar(nVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
// Done
return funcp;
}
AstCFunc* TriggerKit::createOrIntoFunc() const {
AstNetlist* const netlistp = v3Global.rootp();
FileLine* const flp = netlistp->topScopep()->fileline();
// Create function
AstCFunc* const funcp = util::makeSubFunction(netlistp, "_trigger_orInto__" + m_name, m_slow);
funcp->isStatic(true);
// Add arguments
AstVarScope* const oVscp = newArgument(funcp, m_trigDTypep, "out", VDirection::INOUT);
AstVarScope* const iVscp = newArgument(funcp, m_trigDTypep, "in", VDirection::CONSTREF);
// Add loop counter variable
AstVarScope* const nVscp
= newLocal(funcp, netlistp->findBitDType(32, 32, VSigning::UNSIGNED), "n");
nVscp->varp()->noReset(true);
// Creates read/write reference
const auto rd = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::READ}; };
const auto wr = [flp](AstVarScope* vp) { return new AstVarRef{flp, vp, VAccess::WRITE}; };
// Function body
AstLoop* const loopp = new AstLoop{flp};
funcp->addStmtsp(util::setVar(nVscp, 0));
funcp->addStmtsp(loopp);
// Loop body
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(oVscp), rd(nVscp)};
AstNodeExpr* const oWordp = new AstArraySel{flp, rd(oVscp), rd(nVscp)};
AstNodeExpr* const iWordp = new AstArraySel{flp, rd(iVscp), rd(nVscp)};
AstNodeExpr* const rhsp = new AstOr{flp, oWordp, iWordp};
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, m_nWords};
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
loopp->addStmtsp(util::incrementVar(nVscp));
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
// Done
return funcp;
}
AstNodeStmt* TriggerKit::newAndNotCall(AstVarScope* const oVscp, //
AstVarScope* const aVscp, //
AstVarScope* const bVscp) const {
if (!m_nWords) return nullptr;
if (!m_andNotp) m_andNotp = createAndNotFunc();
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
AstCCall* const callp = new AstCCall{flp, m_andNotp};
callp->addArgsp(new AstVarRef{flp, oVscp, VAccess::WRITE});
callp->addArgsp(new AstVarRef{flp, aVscp, VAccess::READ});
callp->addArgsp(new AstVarRef{flp, bVscp, VAccess::READ});
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstNodeExpr* TriggerKit::newAnySetCall(AstVarScope* const vscp) const {
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
if (!m_nWords) return new AstConst{flp, AstConst::BitFalse{}};
if (!m_anySetp) m_anySetp = createAnySetFunc();
AstCCall* const callp = new AstCCall{flp, m_anySetp};
callp->addArgsp(new AstVarRef{flp, vscp, VAccess::WRITE});
callp->dtypeSetBit();
return callp;
}
AstNodeStmt* TriggerKit::newClearCall(AstVarScope* const vscp) const {
if (!m_nWords) return nullptr;
if (!m_clearp) m_clearp = createClearFunc();
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
AstCCall* const callp = new AstCCall{flp, m_clearp};
callp->addArgsp(new AstVarRef{flp, vscp, VAccess::WRITE});
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstNodeStmt* TriggerKit::newOrIntoCall(AstVarScope* const oVscp, AstVarScope* const iVscp) const {
if (!m_nWords) return nullptr;
if (!m_orIntop) m_orIntop = createOrIntoFunc();
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
AstCCall* const callp = new AstCCall{flp, m_orIntop};
callp->addArgsp(new AstVarRef{flp, oVscp, VAccess::WRITE});
callp->addArgsp(new AstVarRef{flp, iVscp, VAccess::READ});
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstNodeStmt* TriggerKit::newCompCall() const {
if (!m_nWords) return nullptr;
return util::callVoidFunc(m_compp);
}
AstNodeStmt* TriggerKit::newDumpCall(AstVarScope* const vscp, const std::string& tag,
bool debugOnly) const {
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
if (!m_nWords) return new AstComment{flp, "No triggers - dump"};
AstCCall* const callp = new AstCCall{flp, m_dumpp};
callp->addArgsp(new AstVarRef{flp, vscp, VAccess::READ});
callp->addArgsp(new AstConst{flp, AstConst::String{}, tag});
callp->dtypeSetVoid();
AstCStmt* const cstmtp = new AstCStmt{flp};
cstmtp->add("#ifdef VL_DEBUG\n");
if (debugOnly) {
cstmtp->add("if (VL_UNLIKELY(vlSymsp->_vm_contextp__->debug())) {\n");
cstmtp->add(callp->makeStmt());
cstmtp->add("}\n");
} else {
cstmtp->add(callp->makeStmt());
}
cstmtp->add("#endif");
return cstmtp;
}
AstVarScope* TriggerKit::newTrigVec(const std::string& name) const {
if (!m_trigDTypep) return nullptr;
AstScope* const scopep = v3Global.rootp()->topScopep()->scopep();
return scopep->createTemp("__V" + name + "Triggered", m_trigDTypep);
}
AstSenTree* TriggerKit::newTriggerSenTree(AstVarScope* const vscp,
const std::vector<uint32_t>& indices) const {
AstNetlist* const netlistp = v3Global.rootp();
AstTopScope* const topScopep = netlistp->topScopep();
AstScope* const scopeTopp = topScopep->scopep();
FileLine* const flp = scopeTopp->fileline();
FileLine* const flp = topScopep->fileline();
AstSenTree* const senTreep = new AstSenTree{flp, nullptr};
topScopep->addSenTreesp(senTreep);
for (const uint32_t index : indices) {
UASSERT(index <= m_nWords * WORD_SIZE, "Invalid trigger index");
const uint32_t wordIndex = index / WORD_SIZE;
const uint32_t bitIndex = index % WORD_SIZE;
AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::READ};
AstNodeExpr* const aselp = new AstArraySel{flp, refp, static_cast<int>(wordIndex)};
// Use a mask & _ to extract the bit, V3Const can optimize this to combine terms
AstConst* const maskp
= new AstConst{flp, AstConst::WidthedValue{}, static_cast<int>(WORD_SIZE), 0};
maskp->num().setBit(bitIndex, '1');
AstNodeExpr* const termp = new AstAnd{flp, maskp, aselp};
senTreep->addSensesp(new AstSenItem{flp, VEdgeType::ET_TRUE, termp});
}
return senTreep;
}
void TriggerKit::addExtraTriggerAssignment(AstVarScope* vscp, uint32_t index) const {
const uint32_t wordIndex = index / WORD_SIZE;
const uint32_t bitIndex = index % WORD_SIZE;
FileLine* const flp = vscp->fileline();
// Set the trigger bit
AstVarRef* const refp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
AstNodeExpr* const wordp = new AstArraySel{flp, refp, static_cast<int>(wordIndex)};
AstNodeExpr* const trigLhsp = new AstSel{flp, wordp, static_cast<int>(bitIndex), 1};
AstNodeExpr* const trigRhsp = new AstVarRef{flp, vscp, VAccess::READ};
AstNodeStmt* const setp = new AstAssign{flp, trigLhsp, trigRhsp};
// Clear the input variable
AstNodeExpr* const vscpLhsp = new AstVarRef{flp, vscp, VAccess::WRITE};
AstNodeExpr* const vscpRhsp = new AstConst{flp, AstConst::BitFalse{}};
AstNodeStmt* const clrp = new AstAssign{flp, vscpLhsp, vscpRhsp};
// Note these are added in reverse order, so 'setp' executes before 'clrp'
m_compp->stmtsp()->addHereThisAsNext(clrp);
m_compp->stmtsp()->addHereThisAsNext(setp);
}
TriggerKit::TriggerKit(const std::string& name, bool slow, uint32_t nWords)
: m_name{name}
, m_slow{slow}
, m_nWords{nWords} {
// If no triggers, we don't need to generate anything
if (!nWords) return;
// Othewise construc the parts of the kit
AstNetlist* const netlistp = v3Global.rootp();
AstScope* const scopep = netlistp->topScopep()->scopep();
FileLine* const flp = scopep->fileline();
// Data type of a single trigger word
m_wordDTypep = netlistp->findBitDType(WORD_SIZE, WORD_SIZE, VSigning::UNSIGNED);
// Data type of a trigger vector
AstRange* const rangep = new AstRange{flp, static_cast<int>(m_nWords - 1), 0};
m_trigDTypep = new AstUnpackArrayDType{flp, m_wordDTypep, rangep};
netlistp->typeTablep()->addTypesp(m_trigDTypep);
// The AstVarScope representing the trigger vector
m_vscp = scopep->createTemp("__V" + m_name + "Triggered", m_trigDTypep);
m_vscp->varp()->isInternal(true);
// The trigger computation function
m_compp = util::makeSubFunction(netlistp, "_eval_triggers__" + m_name, m_slow);
// The debug dump function, always 'slow'
m_dumpp = util::makeSubFunction(netlistp, "_dump_triggers__" + m_name, true);
m_dumpp->isStatic(true);
m_dumpp->ifdef("VL_DEBUG");
}
TriggerKit TriggerKit::create(AstNetlist* netlistp, //
AstCFunc* const initFuncp, //
SenExprBuilder& senExprBuilder, //
const std::vector<const AstSenTree*>& senTreeps, //
const string& name, //
const ExtraTriggers& extraTriggers, //
bool slow) {
FileLine* const flp = netlistp->topScopep()->fileline();
// Number of extra triggers, rounded up to a full word. These occupy the lowest words.
const uint32_t nExtraTriggers = vlstd::roundUpToMultipleOf<WORD_SIZE>(extraTriggers.size());
const uint32_t nExtraWords = nExtraTriggers / WORD_SIZE;
// Gather all the unique SenItems under the SenTrees
// List of unique SenItems used by all 'senTreeps'
std::vector<const AstSenItem*> senItemps;
// Map from SenItem to the equivalent index in 'senItemps'
std::unordered_map<const AstSenItem*, size_t> senItemp2Index;
{
// Set of unique SenItems
std::unordered_set<VNRef<const AstSenItem>> uniqueSenItemps;
for (const AstSenTree* const senTreep : senTreeps) {
for (const AstSenItem *itemp = senTreep->sensesp(), *nextp; itemp; itemp = nextp) {
nextp = VN_AS(itemp->nextp(), SenItem);
const auto pair = uniqueSenItemps.emplace(*itemp);
if (pair.second) {
senItemp2Index.emplace(itemp, senItemps.size());
senItemps.push_back(itemp);
}
senItemp2Index.emplace(itemp, senItemp2Index.at(&(pair.first->get())));
}
// Map from SenItem to tigger bit standing for that SenItem. There might
// be duplicate SenItems, we map all of them to the same index.
std::unordered_map<VNRef<const AstSenItem>, size_t> senItem2TrigIdx;
for (const AstSenTree* const senTreep : senTreeps) {
for (const AstSenItem *itemp = senTreep->sensesp(), *nextp; itemp; itemp = nextp) {
nextp = VN_AS(itemp->nextp(), SenItem);
UASSERT_OBJ(itemp->isClocked() || itemp->isHybrid(), itemp,
"Cannot create trigger expression for non-clocked sensitivity");
const auto pair = senItem2TrigIdx.emplace(*itemp, nExtraTriggers + senItemps.size());
if (pair.second) senItemps.push_back(itemp);
}
}
UASSERT(senItemps.size() == senItem2TrigIdx.size(), "Inconsitent SenItem to trigger map");
std::unordered_map<const AstSenTree*, AstSenTree*> map;
// Number of sense triggers, rounded up to a full word
const uint32_t nSenseTriggers = vlstd::roundUpToMultipleOf<WORD_SIZE>(senItemps.size());
// Total number of trigger words
const uint32_t nWords = (nSenseTriggers / WORD_SIZE) + nExtraWords;
const uint32_t nTriggers = senItemps.size() + extraTriggers.size();
// Pad 'senItemps' to nSenseTriggers with nullptr
senItemps.resize(nSenseTriggers);
// Create the TRIGGERVEC variable
AstBasicDType* const tDtypep
= new AstBasicDType{flp, VBasicDTypeKwd::TRIGGERVEC, VSigning::UNSIGNED,
static_cast<int>(nTriggers), static_cast<int>(nTriggers)};
netlistp->typeTablep()->addTypesp(tDtypep);
AstVarScope* const vscp = scopeTopp->createTemp("__V" + name + "Triggered", tDtypep);
// We can now construct the trigger kit - this construct all items that will be kept
TriggerKit kit{name, slow, nWords};
// Create the trigger computation function
AstCFunc* const funcp = util::makeSubFunction(netlistp, "_eval_triggers__" + name, slow);
if (v3Global.opt.profExec()) funcp->addStmtsp(util::profExecSectionPush(flp, "trig " + name));
// If there are no triggers we are done
if (!nWords) return kit;
// Create the trigger dump function (for debugging, always 'slow')
AstCFunc* const dumpp = util::makeSubFunction(netlistp, "_dump_triggers__" + name, true);
dumpp->ifdef("VL_DEBUG");
// Construct the comp and dump functions
// Add arguments to the dump function. The trigger vector is passed into
// the dumping function via reference so one dump function can dump all
// different copies of the trigger vector. To do so, it also needs the tag
// string at runtime, which is the second argument.
AstVarScope* const dumpTrgp
= newArgument(kit.m_dumpp, kit.m_trigDTypep, "triggers", VDirection::CONSTREF);
AstVarScope* const dumpTagp
= newArgument(kit.m_dumpp, netlistp->findStringDType(), "tag", VDirection::CONSTREF);
// Add a print to the dumping function if there are no triggers pending
{
AstCMethodHard* const callp = new AstCMethodHard{
flp, new AstVarRef{flp, vscp, VAccess::READ}, VCMethod::TRIGGER_ANY};
callp->dtypeSetBit();
AstIf* const ifp = new AstIf{flp, callp};
dumpp->addStmtsp(ifp);
ifp->addElsesp(new AstCStmt{flp, "VL_DBG_MSGF(\" No triggers active\\n\");"});
AstIf* const ifp = new AstIf{flp, new AstLogNot{flp, kit.newAnySetCall(dumpTrgp)}};
kit.m_dumpp->addStmtsp(ifp);
AstCStmt* const cstmtp = new AstCStmt{flp};
ifp->addThensp(cstmtp);
cstmtp->add("VL_DBG_MSGS(\" No '\" + ");
cstmtp->add(new AstVarRef{flp, dumpTagp, VAccess::READ});
cstmtp->add(" + \"\' region triggers active\\n\");");
}
// Set the given trigger to the given value
const auto setTrigBit = [&](uint32_t index, AstNodeExpr* valp) {
AstVarRef* const vrefp = new AstVarRef{flp, vscp, VAccess::WRITE};
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, VCMethod::TRIGGER_SET_BIT};
callp->addPinsp(new AstConst{flp, index});
callp->addPinsp(valp);
callp->dtypeSetVoid();
return callp->makeStmt();
};
// Create a reference to a trigger flag
const auto getTrig = [&](uint32_t index) {
AstVarRef* const vrefp = new AstVarRef{flp, vscp, VAccess::READ};
const uint32_t wordIndex = index / 64;
const uint32_t bitIndex = index % 64;
AstCMethodHard* const callp
= new AstCMethodHard{flp, vrefp, VCMethod::TRIGGER_WORD, new AstConst{flp, wordIndex}};
callp->dtypeSetUInt64();
AstNodeExpr* const termp
= new AstAnd{flp, new AstConst{flp, AstConst::Unsized64{}, 1ULL << bitIndex}, callp};
return termp;
};
// Add a debug dumping statement for this trigger
const auto addDebug = [&](uint32_t index, const string& text = "") {
std::stringstream ss;
ss << "VL_DBG_MSGF(\" ";
ss << "'" << name << "' region trigger index " << std::to_string(index) << " is active";
if (!text.empty()) ss << ": " << text;
ss << "\\n\");";
AstIf* const ifp = new AstIf{flp, getTrig(index)};
dumpp->addStmtsp(ifp);
ifp->addThensp(new AstCStmt{flp, ss.str()});
// Adds a debug dumping statement for this trigger
const auto addDebug = [&](uint32_t index, const string& text) {
AstVarRef* const refp = new AstVarRef{flp, dumpTrgp, VAccess::READ};
const int wrdIndex = static_cast<int>(index / WORD_SIZE);
const int bitIndex = static_cast<int>(index % WORD_SIZE);
AstNodeExpr* const aselp = new AstArraySel{flp, refp, wrdIndex};
AstNodeExpr* const condp = new AstSel{flp, aselp, bitIndex, 1};
AstIf* const ifp = new AstIf{flp, condp};
kit.m_dumpp->addStmtsp(ifp);
AstCStmt* const cstmtp = new AstCStmt{flp};
ifp->addThensp(cstmtp);
cstmtp->add("VL_DBG_MSGS(\" '\" + ");
cstmtp->add(new AstVarRef{flp, dumpTagp, VAccess::READ});
cstmtp->add(" + \"' region trigger index " + std::to_string(index) + " is active: " + text
+ "\\n\");");
};
// Add a print for each of the extra triggers
for (unsigned i = 0; i < extraTriggers.size(); ++i) {
addDebug(i, "Internal '" + name + "' trigger - " + extraTriggers.description(i));
addDebug(i, "Internal '" + name + "' trigger - " + extraTriggers.m_descriptions.at(i));
}
// Add trigger computation
uint32_t triggerNumber = extraTriggers.size();
uint32_t triggerBitIdx = triggerNumber;
// Add sense trigger computation
// List of trigger computation expressions
std::vector<AstNodeExpr*> trigps;
trigps.reserve(nSenseTriggers);
// Statements to exectue at initialization time to fire initial triggers
AstNodeStmt* initialTrigsp = nullptr;
std::vector<uint32_t> senItemIndex2TriggerIndex;
senItemIndex2TriggerIndex.reserve(senItemps.size());
constexpr uint32_t TRIG_VEC_WORD_SIZE_LOG2 = 6; // 64-bits
constexpr uint32_t TRIG_VEC_WORD_SIZE = 1 << TRIG_VEC_WORD_SIZE_LOG2;
std::vector<AstNodeExpr*> trigExprps;
trigExprps.reserve(TRIG_VEC_WORD_SIZE);
for (const AstSenItem* const senItemp : senItemps) {
UASSERT_OBJ(senItemp->isClocked() || senItemp->isHybrid(), senItemp,
"Cannot create trigger expression for non-clocked sensitivity");
for (size_t i = 0; i < senItemps.size(); ++i) {
const AstSenItem* const senItemp = senItemps[i];
// Store the trigger number
senItemIndex2TriggerIndex.push_back(triggerNumber);
// If this is just paddign, use constant zero
if (!senItemp) {
trigps.emplace_back(new AstConst{flp, AstConst::BitFalse{}});
continue;
}
// Add the trigger computation
// Index of this trigger in the trigger vector
const uint32_t index = nExtraTriggers + i;
// Create the trigger computation expression
const auto& pair = senExprBuilder.build(senItemp);
trigExprps.emplace_back(pair.first);
trigps.emplace_back(pair.first);
// Add initialization time trigger
if (pair.second || v3Global.opt.xInitialEdge()) {
initialTrigsp
= AstNode::addNext(initialTrigsp, setTrigBit(triggerNumber, new AstConst{flp, 1}));
AstVarRef* const refp = new AstVarRef{flp, kit.m_vscp, VAccess::WRITE};
const int wrdIndex = static_cast<int>(index / WORD_SIZE);
const int bitIndex = static_cast<int>(index % WORD_SIZE);
AstNodeExpr* const wordp = new AstArraySel{flp, refp, wrdIndex};
AstNodeExpr* const lhsp = new AstSel{flp, wordp, bitIndex, 1};
AstNodeExpr* const rhsp = new AstConst{flp, AstConst::BitTrue{}};
initialTrigsp = AstNode::addNext(initialTrigsp, new AstAssign{flp, lhsp, rhsp});
}
// Add a debug statement for this trigger
@ -193,111 +489,85 @@ const TriggerKit TriggerKit::create(AstNetlist* netlistp, //
ss << ")";
std::string desc = VString::quoteBackslash(ss.str());
desc = VString::replaceSubstr(desc, "\n", "\\n");
addDebug(triggerNumber, desc);
addDebug(index, desc);
}
UASSERT(trigps.size() == nSenseTriggers, "Inconsistent number of trigger expressions");
//
++triggerNumber;
// Add statements on every word boundary
if (triggerNumber % TRIG_VEC_WORD_SIZE == 0) {
if (triggerBitIdx % TRIG_VEC_WORD_SIZE != 0) {
// Set leading triggers bit-wise
for (AstNodeExpr* const exprp : trigExprps) {
funcp->addStmtsp(setTrigBit(triggerBitIdx++, exprp));
}
} else {
// Set whole word as a unit
UASSERT_OBJ(triggerNumber == triggerBitIdx + TRIG_VEC_WORD_SIZE, senItemp,
"Mismatched index");
UASSERT_OBJ(trigExprps.size() == TRIG_VEC_WORD_SIZE, senItemp,
"There should be TRIG_VEC_WORD_SIZE expressions");
// Concatenate all bits in a tree
for (uint32_t level = 0; level < TRIG_VEC_WORD_SIZE_LOG2; ++level) {
const uint32_t stride = 1 << level;
for (uint32_t i = 0; i < TRIG_VEC_WORD_SIZE; i += 2 * stride) {
trigExprps[i] = new AstConcat{trigExprps[i]->fileline(),
trigExprps[i + stride], trigExprps[i]};
trigExprps[i + stride] = nullptr;
}
}
// Set the whole word in the trigger vector
AstVarRef* const vrefp = new AstVarRef{flp, vscp, VAccess::WRITE};
AstCMethodHard* const callp
= new AstCMethodHard{flp, vrefp, VCMethod::TRIGGER_SET_WORD};
callp->addPinsp(new AstConst{flp, triggerBitIdx / TRIG_VEC_WORD_SIZE});
callp->addPinsp(trigExprps[0]);
callp->dtypeSetVoid();
funcp->addStmtsp(callp->makeStmt());
triggerBitIdx += TRIG_VEC_WORD_SIZE;
// Assign trigger vector one word at a time
AstNodeStmt* trigStmtsp = nullptr;
for (size_t i = 0; i < nSenseTriggers; i += WORD_SIZE) {
// Concatenate all bits in this trigger word using a balanced
for (uint32_t level = 0; level < WORD_SIZE_LOG2; ++level) {
const uint32_t stride = 1 << level;
for (uint32_t j = 0; j < WORD_SIZE; j += 2 * stride) {
FileLine* const flp = trigps[i + j]->fileline();
trigps[i + j] = new AstConcat{flp, trigps[i + j + stride], trigps[i + j]};
trigps[i + j + stride] = nullptr;
}
UASSERT_OBJ(triggerNumber == triggerBitIdx, senItemp, "Mismatched index");
trigExprps.clear();
}
// Set the whole word in the trigger vector
const uint32_t wordIndex = nExtraWords + i / WORD_SIZE;
AstVarRef* const refp = new AstVarRef{flp, kit.m_vscp, VAccess::WRITE};
AstArraySel* const aselp = new AstArraySel{flp, refp, static_cast<int>(wordIndex)};
trigStmtsp = AstNode::addNext(trigStmtsp, new AstAssign{flp, aselp, trigps[i]});
}
// Set trailing triggers bit-wise
for (AstNodeExpr* const exprp : trigExprps) {
funcp->addStmtsp(setTrigBit(triggerBitIdx++, exprp));
}
trigExprps.clear();
trigps.clear();
// Construct the map from old SenTrees to new SenTrees
for (const AstSenTree* const senTreep : senTreeps) {
AstSenTree* const trigpSenp = new AstSenTree{flp, nullptr};
for (const AstSenItem *itemp = senTreep->sensesp(), *nextp; itemp; itemp = nextp) {
nextp = VN_AS(itemp->nextp(), SenItem);
const uint32_t tiggerIndex = senItemIndex2TriggerIndex.at(senItemp2Index.at(itemp));
trigpSenp->addSensesp(new AstSenItem{flp, VEdgeType::ET_TRUE, getTrig(tiggerIndex)});
{
std::vector<uint32_t> indices;
indices.reserve(32);
for (const AstSenTree* const senTreep : senTreeps) {
indices.clear();
for (const AstSenItem *itemp = senTreep->sensesp(), *nextp; itemp; itemp = nextp) {
nextp = VN_AS(itemp->nextp(), SenItem);
indices.push_back(senItem2TrigIdx.at(*itemp));
}
kit.m_map[senTreep] = kit.newTriggerSenTree(kit.m_vscp, indices);
}
topScopep->addSenTreesp(trigpSenp);
map[senTreep] = trigpSenp;
}
// Get the SenExprBuilder results
const SenExprBuilder::Results senResults = senExprBuilder.getAndClearResults();
// Add the init and update statements
// Add the SenExprBuilder init statements to the static initialization functino
for (AstNodeStmt* const nodep : senResults.m_inits) initFuncp->addStmtsp(nodep);
for (AstNodeStmt* const nodep : senResults.m_postUpdates) funcp->addStmtsp(nodep);
if (!senResults.m_preUpdates.empty()) {
for (AstNodeStmt* const nodep : vlstd::reverse_view(senResults.m_preUpdates)) {
UASSERT_OBJ(funcp->stmtsp(), funcp,
"No statements in trigger eval function, but there are pre updates");
funcp->stmtsp()->addHereThisAsNext(nodep);
}
}
// Add the initialization statements
if (initialTrigsp) {
AstVarScope* const tempVscp = scopeTopp->createTemp("__V" + name + "DidInit", 1);
AstVarRef* const condp = new AstVarRef{flp, tempVscp, VAccess::READ};
AstIf* const ifp = new AstIf{flp, new AstNot{flp, condp}};
funcp->addStmtsp(ifp);
ifp->branchPred(VBranchPred::BP_UNLIKELY);
ifp->addThensp(util::setVar(tempVscp, 1));
ifp->addThensp(initialTrigsp);
}
// Add a call to the dumping function if debug is enabled
// Assemble the trigger computation function
{
AstCStmt* const stmtp = new AstCStmt{flp};
funcp->addStmtsp(stmtp);
stmtp->add("#ifdef VL_DEBUG\n");
stmtp->add("if (VL_UNLIKELY(vlSymsp->_vm_contextp__->debug())) {\n");
stmtp->add(util::callVoidFunc(dumpp));
stmtp->add("}\n");
stmtp->add("#endif");
}
if (v3Global.opt.profExec()) funcp->addStmtsp(util::profExecSectionPop(flp));
AstCFunc* const fp = kit.m_compp;
// Profiling push
if (v3Global.opt.profExec()) fp->addStmtsp(util::profExecSectionPush(flp, "trig " + name));
// Trigger computation
for (AstNodeStmt* const nodep : senResults.m_preUpdates) fp->addStmtsp(nodep);
fp->addStmtsp(trigStmtsp);
for (AstNodeStmt* const nodep : senResults.m_postUpdates) fp->addStmtsp(nodep);
// Add the initialization time triggers
if (initialTrigsp) {
AstScope* const scopep = netlistp->topScopep()->scopep();
AstVarScope* const vscp = scopep->createTemp("__V" + name + "DidInit", 1);
AstVarRef* const condp = new AstVarRef{flp, vscp, VAccess::READ};
AstIf* const ifp = new AstIf{flp, new AstNot{flp, condp}};
fp->addStmtsp(ifp);
ifp->branchPred(VBranchPred::BP_UNLIKELY);
ifp->addThensp(util::setVar(vscp, 1));
ifp->addThensp(initialTrigsp);
}
// Add a call to the dumping function if debug is enabled
fp->addStmtsp(kit.newDumpCall(kit.m_vscp, name, true));
// Profiling pop
if (v3Global.opt.profExec()) fp->addStmtsp(util::profExecSectionPop(flp));
// Done with the trigger computation function, split as might be large
util::splitCheck(fp);
};
// The debug code might leak signal names, so simply delete it when using --protect-ids
if (v3Global.opt.protectIds()) dumpp->stmtsp()->unlinkFrBackWithNext()->deleteTree();
if (v3Global.opt.protectIds()) kit.m_dumpp->stmtsp()->unlinkFrBackWithNext()->deleteTree();
// Done with the trigger dump function, split as might be large
util::splitCheck(kit.m_dumpp);
// These might get large when we have a lot of triggers, so split if necessary
util::splitCheck(funcp);
util::splitCheck(dumpp);
return TriggerKit{vscp, funcp, dumpp, map};
return kit;
}
} // namespace V3Sched

View File

@ -71,13 +71,14 @@ AstNodeStmt* incrementVar(AstVarScope* vscp) {
}
AstNodeStmt* callVoidFunc(AstCFunc* funcp) {
if (!funcp) return nullptr;
AstCCall* const callp = new AstCCall{funcp->fileline(), funcp};
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstNodeStmt* checkIterationLimit(AstNetlist* netlistp, const string& name, AstVarScope* counterp,
AstCFunc* trigDumpp) {
AstNodeStmt* dumpCallp) {
FileLine* const flp = netlistp->fileline();
// If we exceeded the iteration limit, die
@ -88,14 +89,12 @@ AstNodeStmt* checkIterationLimit(AstNetlist* netlistp, const string& name, AstVa
AstNodeExpr* const condp = new AstGt{flp, counterRefp, constp};
AstIf* const ifp = new AstIf{flp, condp};
ifp->branchPred(VBranchPred::BP_UNLIKELY);
ifp->addThensp(dumpCallp);
AstCStmt* const stmtp = new AstCStmt{flp};
ifp->addThensp(stmtp);
FileLine* const locp = netlistp->topModulep()->fileline();
const std::string& file = VIdProtect::protect(locp->filename());
const std::string& line = std::to_string(locp->lineno());
stmtp->add("#ifdef VL_DEBUG\n");
stmtp->add(callVoidFunc(trigDumpp));
stmtp->add("#endif\n");
stmtp->add("VL_FATAL_MT(\"" + V3OutFormatter::quoteNameControls(file) + "\", " + line
+ ", \"\", \"" + name + " region did not converge after " + std::to_string(limit)
+ " tries\");");
@ -116,64 +115,105 @@ static AstCFunc* splitCheckCreateNewSubFunc(AstCFunc* ofuncp) {
static std::map<AstCFunc*, uint32_t> s_funcNums; // What split number to attach to a function
const uint32_t funcNum = s_funcNums[ofuncp]++;
const std::string name = ofuncp->name() + "__" + cvtToStr(funcNum);
AstCFunc* const subFuncp = new AstCFunc{ofuncp->fileline(), name, ofuncp->scopep()};
AstScope* const scopep = ofuncp->scopep();
AstCFunc* const subFuncp = new AstCFunc{ofuncp->fileline(), name, scopep};
scopep->addBlocksp(subFuncp);
subFuncp->dontCombine(true);
subFuncp->isStatic(false);
subFuncp->isStatic(ofuncp->isStatic());
subFuncp->isLoose(true);
subFuncp->slow(ofuncp->slow());
subFuncp->declPrivate(ofuncp->declPrivate());
if (ofuncp->needProcess()) subFuncp->setNeedProcess();
for (AstVar* argp = ofuncp->argsp(); argp; argp = VN_AS(argp->nextp(), Var)) {
AstVar* const clonep = argp->cloneTree(false);
subFuncp->addArgsp(clonep);
AstVarScope* const vscp = new AstVarScope{clonep->fileline(), scopep, clonep};
scopep->addVarsp(vscp);
argp->user3p(vscp);
}
return subFuncp;
};
void splitCheckFinishSubFunc(AstCFunc* ofuncp, AstCFunc* subFuncp,
const std::unordered_map<const AstVar*, AstVarScope*>& argVscps) {
FileLine* const flp = subFuncp->fileline();
AstCCall* const callp = new AstCCall{subFuncp->fileline(), subFuncp};
callp->dtypeSetVoid();
// Pass arguments through to subfunction
for (AstVar* argp = ofuncp->argsp(); argp; argp = VN_AS(argp->nextp(), Var)) {
UASSERT_OBJ(argp->direction() == VDirection::CONSTREF, argp, "Unexpected direction");
callp->addArgsp(new AstVarRef{flp, argVscps.at(argp), VAccess::READ});
}
bool containsAwait = false;
subFuncp->foreach([&](AstNodeExpr* exprp) {
// Record if it has a CAwait
if (VN_IS(exprp, CAwait)) containsAwait = true;
// Redirect references to arguments to the clone in the sub-function
if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) {
if (AstVarScope* const vscp = VN_AS(refp->varp()->user3p(), VarScope)) {
refp->varp(vscp->varp());
refp->varScopep(vscp);
}
}
});
if (ofuncp->isCoroutine() && containsAwait) { // Wrap call with co_await
subFuncp->rtnType("VlCoroutine");
AstCAwait* const awaitp = new AstCAwait{flp, callp};
awaitp->dtypeSetVoid();
ofuncp->addStmtsp(awaitp->makeStmt());
} else {
ofuncp->addStmtsp(callp->makeStmt());
}
}
// Split large function according to --output-split-cfuncs
void splitCheck(AstCFunc* ofuncp) {
void splitCheck(AstCFunc* const ofuncp) {
if (!ofuncp) return;
UASSERT_OBJ(!ofuncp->varsp(), ofuncp, "Can't split function with local variables");
if (!v3Global.opt.outputSplitCFuncs() || !ofuncp->stmtsp()) return;
if (ofuncp->nodeCount() < v3Global.opt.outputSplitCFuncs()) return;
int func_stmts = 0;
const bool is_ofuncp_coroutine = ofuncp->isCoroutine();
AstCFunc* funcp = nullptr;
const auto finishSubFuncp = [&](AstCFunc* subFuncp) {
ofuncp->scopep()->addBlocksp(subFuncp);
AstCCall* const callp = new AstCCall{subFuncp->fileline(), subFuncp};
callp->dtypeSetVoid();
if (is_ofuncp_coroutine && subFuncp->exists([](const AstCAwait*) {
return true;
})) { // Wrap call with co_await
subFuncp->rtnType("VlCoroutine");
AstCAwait* const awaitp = new AstCAwait{subFuncp->fileline(), callp};
awaitp->dtypeSetVoid();
ofuncp->addStmtsp(awaitp->makeStmt());
} else {
ofuncp->addStmtsp(callp->makeStmt());
// Need to find the AstVarScopes for the function arguments. They should be in the same Scope.
std::unordered_map<const AstVar*, AstVarScope*> argVscps;
for (AstVar* argp = ofuncp->argsp(); argp; argp = VN_AS(argp->nextp(), Var)) {
UASSERT_OBJ(argVscps.size() < 2, argp, "There should be at most 2 arguments, or O(n^2)");
bool found = false;
for (AstVarScope *vscp = ofuncp->scopep()->varsp(), *nextp; vscp; vscp = nextp) {
nextp = VN_AS(vscp->nextp(), VarScope);
if (vscp->varp() != argp) continue;
argVscps[argp] = vscp;
found = true;
break;
}
};
funcp = splitCheckCreateNewSubFunc(ofuncp);
func_stmts = 0;
// Unlink all statements, then add item by item to new sub-functions
AstBegin* const tempp = new AstBegin{ofuncp->fileline(), "[EditWrapper]",
ofuncp->stmtsp()->unlinkFrBackWithNext(), false};
while (tempp->stmtsp()) {
AstNode* const itemp = tempp->stmtsp()->unlinkFrBack();
const int stmts = itemp->nodeCount();
if ((func_stmts + stmts) > v3Global.opt.outputSplitCFuncs()) {
finishSubFuncp(funcp);
funcp = splitCheckCreateNewSubFunc(ofuncp);
func_stmts = 0;
}
funcp->addStmtsp(itemp);
func_stmts += stmts;
UASSERT_OBJ(found, argp, "Can't find VarScope for function argument");
}
finishSubFuncp(funcp);
VL_DO_DANGLING(tempp->deleteTree(), tempp);
// AstVar::user3p(): AstVarScope for function argument in clone
const VNUser3InUse user3InUse;
size_t size = 0;
AstCFunc* subFuncp = nullptr;
// Move statements one by one to the new sub-functions
AstNode* stmtsp = ofuncp->stmtsp()->unlinkFrBackWithNext();
while (AstNode* const itemp = stmtsp) {
stmtsp = stmtsp->nextp();
if (stmtsp) stmtsp->unlinkFrBackWithNext();
const size_t itemSize = static_cast<size_t>(itemp->nodeCount());
size += itemSize;
if (size > static_cast<size_t>(v3Global.opt.outputSplitCFuncs())) {
if (subFuncp) splitCheckFinishSubFunc(ofuncp, subFuncp, argVscps);
subFuncp = nullptr;
size = itemSize;
}
if (!subFuncp) subFuncp = splitCheckCreateNewSubFunc(ofuncp);
subFuncp->addStmtsp(itemp);
}
if (subFuncp) splitCheckFinishSubFunc(ofuncp, subFuncp, argVscps);
}
// Build an AstIf conditional on the given SenTree being triggered

View File

@ -63,9 +63,6 @@ class GatherMTaskAffinity final : VNVisitorConst {
// Cheaper than relying on emplace().second
if (nodep->user1SetOnce()) return;
AstVar* const varp = nodep->varp();
// Ignore TriggerVec. They are big and read-only in the MTask bodies
AstBasicDType* const basicp = varp->dtypep()->basicp();
if (basicp && basicp->isTriggerVec()) return;
// Set affinity bit
MTaskIdVec& affinity = m_results
.emplace(std::piecewise_construct, //
@ -277,7 +274,7 @@ void V3VariableOrder::orderAll(AstNetlist* netlistp) {
for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp;
modp = VN_AS(modp->nextp(), NodeModule)) {
std::vector<AstVar*>& varps = sortedVars[modp];
threadScope.enqueue([modp, mTaskAffinity, &varps]() {
threadScope.enqueue([modp, &mTaskAffinity, &varps]() {
VariableOrder::processModule(modp, mTaskAffinity, varps);
});
}

View File

@ -19,7 +19,7 @@ def check_evals():
if re.search(r'__eval_nba__[0-9]+\(.*\)\s*{', wholefile):
got += 1
if got < 3:
if got < 2:
test.error("Too few _eval functions found: " + str(got))

View File

@ -125,7 +125,7 @@ test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_clas
test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_2")
# Check combine count
test.file_grep(test.stats, r'Node count, CFILE + (\d+)', (230 if test.vltmt else 216))
test.file_grep(test.stats, r'Node count, CFILE + (\d+)', (220 if test.vltmt else 206))
test.file_grep(test.stats, r'Makefile targets, VM_CLASSES_FAST + (\d+)', 2)
test.file_grep(test.stats, r'Makefile targets, VM_CLASSES_SLOW + (\d+)', 2)

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] a)
-V{t#,#} 'stl' region trigger index 64 is active: @([hybrid] a)
%Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge after 100 tries
Aborting...

View File

@ -16,6 +16,6 @@ test.compile(verilator_flags2=["--stats"])
test.execute()
# Value must differ from that in t_opt_localize_max_size.py
test.file_grep(test.stats, r'Optimizations, Vars localized\s+(\d+)', 5)
test.file_grep(test.stats, r'Optimizations, Vars localized\s+(\d+)', 1)
test.passes()

View File

@ -5,7 +5,12 @@
// SPDX-License-Identifier: CC0-1.0
module t;
int x;
initial begin
x = $c32(1);
$display(x);
x = $c32(2);
$display(x);
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -17,6 +17,6 @@ test.compile(verilator_flags2=["--stats --localize-max-size 1"])
test.execute()
# Value must differ from that in t_opt_localize_max_size.py
test.file_grep(test.stats, r'Optimizations, Vars localized\s+(\d+)', 4)
test.file_grep(test.stats, r'Optimizations, Vars localized\s+(\d+)', 0)
test.passes()

View File

@ -14,7 +14,7 @@ test.scenarios('simulator_st')
test.compile(verilator_flags2=["--stats"])
if test.vlt_all:
test.file_grep(test.stats, r'Optimizations, Substituted temps\s+(\d+)', 42)
test.file_grep(test.stats, r'Optimizations, Substituted temps\s+(\d+)', 43)
test.execute()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] x)
-V{t#,#} 'stl' region trigger index 64 is active: @([hybrid] x)
%Error: t/t_unopt_converge_initial.v:7: Settle region did not converge after 100 tries
Aborting...

View File

@ -1,3 +1,3 @@
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] x)
-V{t#,#} 'stl' region trigger index 64 is active: @([hybrid] x)
%Error: t/t_unopt_converge.v:7: Settle region did not converge after 100 tries
Aborting...

View File

@ -1,3 +1,3 @@
-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] x)
-V{t#,#} 'stl' region trigger index 64 is active: @([hybrid] x)
%Error: t/t_unopt_converge.v:7: Settle region did not converge after 5 tries
Aborting...

View File

@ -18,8 +18,12 @@ internalsDump:
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__act
-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act
-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act
-V{t#,#} No triggers active
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
-V{t#,#} No 'act' region triggers active
-V{t#,#}+ Vt_verilated_debug___024root___trigger_andNot__act
-V{t#,#}+ Vt_verilated_debug___024root___trigger_orInto__act
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__nba
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
-V{t#,#}End-of-eval cleanup
-V{t#,#}+++++TOP Evaluate Vt_verilated_debug::eval_step
-V{t#,#}+ Vt_verilated_debug___024root___eval_debug_assertions
@ -28,20 +32,24 @@ internalsDump:
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__act
-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act
-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 0 is active: @(posedge clk)
-V{t#,#}+ Vt_verilated_debug___024root___eval_act
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__act
-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act
-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act
-V{t#,#} No triggers active
-V{t#,#}+ Vt_verilated_debug___024root___trigger_andNot__act
-V{t#,#}+ Vt_verilated_debug___024root___trigger_orInto__act
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__nba
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
-V{t#,#}+ Vt_verilated_debug___024root___eval_nba
-V{t#,#}+ Vt_verilated_debug___024root___nba_sequent__TOP__0
*-* All Finished *-*
-V{t#,#}+ Vt_verilated_debug___024root___trigger_clear__act
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__act
-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act
-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act
-V{t#,#} No triggers active
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
-V{t#,#} No 'act' region triggers active
-V{t#,#}+ Vt_verilated_debug___024root___trigger_andNot__act
-V{t#,#}+ Vt_verilated_debug___024root___trigger_orInto__act
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__nba
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
-V{t#,#}End-of-eval cleanup
-V{t#,#}+ Vt_verilated_debug___024root___eval_final

File diff suppressed because it is too large Load Diff