Optimize trigger evaluation

Pack the elements of VlTriggerVec as dense bits (instead of a 1 byte
bool per bit), and check whether they are set on a word granularity.
This effectively transforms conditions of the form `if (trig.at(0) |
trig.at(2) | trig.at(64))` into `if (trig.word(0) & 0x5 | trig.word(1) &
0x1)`. This improves OpenTitan ST by about 1%, worth more on some other
designs.
This commit is contained in:
Geza Lore 2023-04-13 13:44:54 +01:00
parent cbeb9d39ff
commit 0e769d42a1
8 changed files with 224 additions and 151 deletions

View File

@ -23,6 +23,7 @@ Verilator 5.009 devel
* Support complicated IEEE 'for' assignments.
* Support $fopen as an expression.
* Support ++/-- on dotted member variables.
* Optimize static trigger evaluation (#4142). [Geza Lore, X-EPIC]
* Change range order warning from LITENDIAN to ASCRANGE (#4010). [Iztok Jeras]
* Change ZERODLY to a warning.
* Fix random internal crashes (#666). [Dag Lem]

View File

@ -149,6 +149,15 @@ enum VerilatedVarFlags {
VLVF_DPI_CLAY = (1 << 10) // DPI compatible C standard layout
};
//=============================================================================
// Utility functions
template <size_t N>
inline constexpr size_t roundUpToMultipleOf(size_t value) {
static_assert((N & (N - 1)) == 0, "'N' must be a power of 2");
return (value + N - 1) & ~(N - 1);
}
//=========================================================================
// Mutex and threading support

View File

@ -79,9 +79,9 @@ extern std::string VL_TO_STRING_W(int words, const WDataInP obj);
template <std::size_t T_size> //
class VlTriggerVec final {
// TODO: static assert T_size > 0, and don't generate when empty
private:
// MEMBERS
std::array<bool, T_size> m_flags; // State of the assoc array
alignas(16) std::array<uint64_t, roundUpToMultipleOf<64>(T_size) / 64> m_flags; // The flags
public:
// CONSTRUCTOR
@ -91,10 +91,18 @@ public:
// METHODS
// Set all elements to false
void clear() { m_flags.fill(false); }
void clear() { m_flags.fill(0); }
// Reference to element at 'index'
bool& at(size_t index) { return m_flags.at(index); }
// Word at given 'wordIndex'
uint64_t word(size_t wordIndex) const { return m_flags[wordIndex]; }
// Set specified flag to given value
void set(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 {
@ -104,13 +112,13 @@ public:
}
// Set all elements true in 'this' that are set in 'other'
void set(const VlTriggerVec<T_size>& other) {
void thisOr(const VlTriggerVec<T_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<T_size>& a, const VlTriggerVec<T_size>& b) {
for (size_t i = 0; i < m_flags.size(); ++i) m_flags[i] = a.m_flags[i] && !b.m_flags[i];
for (size_t i = 0; i < m_flags.size(); ++i) m_flags[i] = a.m_flags[i] & ~b.m_flags[i];
}
};

View File

@ -61,16 +61,6 @@ constexpr unsigned VL_TRACE_MAX_VCD_CODE_SIZE = 5; // Maximum length of a VCD s
// cache-lines.
constexpr unsigned VL_TRACE_SUFFIX_ENTRY_SIZE = 8; // Size of a suffix entry
//=============================================================================
// Utility functions: TODO: put these in a common place and share them.
template <size_t N>
static size_t roundUpToMultipleOf(size_t value) {
static_assert((N & (N - 1)) == 0, "'N' must be a power of 2");
size_t mask = N - 1;
return (value + mask) & ~mask;
}
//=============================================================================
// Specialization of the generics for this trace format

View File

@ -2321,7 +2321,7 @@ int AstCMethodHard::instrCount() const {
if (AstBasicDType* const basicp = fromp()->dtypep()->basicp()) {
// TODO: add a more structured description of library methods, rather than using string
// matching. See #3715.
if (basicp->isTriggerVec() && m_name == "at") {
if (basicp->isTriggerVec() && m_name == "word") {
// This is an important special case for scheduling so we compute it precisely,
// it is simply a load.
return INSTR_COUNT_LD;

View File

@ -2746,52 +2746,74 @@ private:
}
}
struct SenItemCmp {
bool operator()(const AstSenItem* lhsp, const AstSenItem* rhsp) const {
if (lhsp->type() < rhsp->type()) return true;
if (lhsp->type() > rhsp->type()) return false;
// Looks visually better if we keep sorted by name
if (!lhsp->sensp() && rhsp->sensp()) return true;
if (lhsp->sensp() && !rhsp->sensp()) return false;
if (lhsp->varrefp() && !rhsp->varrefp()) return true;
if (!lhsp->varrefp() && rhsp->varrefp()) return false;
if (lhsp->varrefp() && rhsp->varrefp()) {
if (lhsp->varrefp()->name() < rhsp->varrefp()->name()) return true;
if (lhsp->varrefp()->name() > rhsp->varrefp()->name()) return false;
class SenItemCmp final {
static int cmp(const AstNodeExpr* ap, const AstNodeExpr* bp) {
const VNType aType = ap->type();
const VNType bType = bp->type();
if (aType != bType) return static_cast<int>(bType) - static_cast<int>(aType);
if (const AstVarRef* const aRefp = VN_CAST(ap, VarRef)) {
const AstVarRef* const bRefp = VN_AS(bp, VarRef);
// Looks visually better if we keep sorted by name
if (aRefp->name() < bRefp->name()) return -1;
if (aRefp->name() > bRefp->name()) return 1;
// But might be same name with different scopes
if (lhsp->varrefp()->varScopep() < rhsp->varrefp()->varScopep()) return true;
if (lhsp->varrefp()->varScopep() > rhsp->varrefp()->varScopep()) return false;
if (aRefp->varScopep() < bRefp->varScopep()) return -1;
if (aRefp->varScopep() > bRefp->varScopep()) return 1;
// Or rarely, different data types
if (lhsp->varrefp()->dtypep() < rhsp->varrefp()->dtypep()) return true;
if (lhsp->varrefp()->dtypep() > rhsp->varrefp()->dtypep()) return false;
} else if (AstCMethodHard* const lp = VN_CAST(lhsp->sensp(), CMethodHard)) {
if (AstCMethodHard* const rp = VN_CAST(rhsp->sensp(), CMethodHard)) {
if (AstVarRef* const lRefp = VN_CAST(lp->fromp(), VarRef)) {
if (AstVarRef* const rRefp = VN_CAST(rp->fromp(), VarRef)) {
if (lRefp->name() < rRefp->name()) return true;
if (lRefp->name() > rRefp->name()) return false;
// But might be same name with different scopes
if (lRefp->varScopep() < rRefp->varScopep()) return true;
if (lRefp->varScopep() > rRefp->varScopep()) return false;
// Or rarely, different data types
if (lRefp->dtypep() < rRefp->dtypep()) return true;
if (lRefp->dtypep() > rRefp->dtypep()) return false;
}
}
if (AstConst* lConstp = VN_CAST(lp->pinsp(), Const)) {
if (AstConst* rConstp = VN_CAST(rp->pinsp(), Const)) {
if (lConstp->toUInt() < rConstp->toUInt()) return true;
if (lConstp->toUInt() > rConstp->toUInt()) return false;
}
}
}
if (aRefp->dtypep() < bRefp->dtypep()) return -1;
if (aRefp->dtypep() > bRefp->dtypep()) return 1;
return 0;
}
// Sort by edge, AFTER variable, as we want multiple edges for same var adjacent.
// note the SenTree optimizer requires this order (more
// general first, less general last)
if (lhsp->edgeType() < rhsp->edgeType()) return true;
if (lhsp->edgeType() > rhsp->edgeType()) return false;
return false;
if (const AstConst* const aConstp = VN_CAST(ap, Const)) {
const AstConst* const bConstp = VN_AS(bp, Const);
if (aConstp->toUQuad() < bConstp->toUQuad()) return -1;
if (aConstp->toUQuad() > bConstp->toUQuad()) return 1;
return 0;
}
if (const AstNodeBiop* const aBiOpp = VN_CAST(ap, NodeBiop)) {
const AstNodeBiop* const bBiOpp = VN_AS(bp, NodeBiop);
// Compare RHSs first as LHS might be const, but the variable term should become
// adjacent for optimization if identical.
if (const int c = cmp(aBiOpp->rhsp(), bBiOpp->rhsp())) return c;
return cmp(aBiOpp->lhsp(), bBiOpp->lhsp());
}
if (const AstCMethodHard* const aCallp = VN_CAST(ap, CMethodHard)) {
const AstCMethodHard* const bCallp = VN_AS(bp, CMethodHard);
if (aCallp->name() < bCallp->name()) return -1;
if (aCallp->name() > bCallp->name()) return 1;
if (const int c = cmp(aCallp->fromp(), bCallp->fromp())) return c;
AstNodeExpr* aPinsp = aCallp->pinsp();
AstNodeExpr* bPinsp = bCallp->pinsp();
while (aPinsp && bPinsp) {
if (const int c = cmp(aPinsp, bPinsp)) return c;
aPinsp = VN_AS(aPinsp->nextp(), NodeExpr);
bPinsp = VN_AS(bPinsp->nextp(), NodeExpr);
}
return aPinsp ? -1 : bPinsp ? 1 : 0;
}
return 0;
}
public:
bool operator()(const AstSenItem* lhsp, const AstSenItem* rhsp) const {
AstNodeExpr* const lSensp = lhsp->sensp();
AstNodeExpr* const rSensp = rhsp->sensp();
if (lSensp && rSensp) {
// If both terms have sensitivity expressions, recursively compare them
if (const int c = cmp(lSensp, rSensp)) return c < 0;
} else if (lSensp || rSensp) {
// Terms with sensitivity expressions come after those without
return rSensp;
}
// Finally sort by edge, AFTER variable, as we want multiple edges for same var
// adjacent. note the SenTree optimizer requires this order (more general first,
// less general last)
return lhsp->edgeType() < rhsp->edgeType();
}
};
@ -2816,9 +2838,8 @@ private:
}
}
// Sort the sensitivity names so "posedge a or b" and "posedge b or a" end up together.
// Also, remove duplicate assignments, and fold POS&NEGs into ANYEDGEs
// Make things a little faster; check first if we need a sort
// Pass 1: Sort the sensitivity items so "posedge a or b" and "posedge b or a" and
// similar, optimizable expressions end up next to each other.
for (AstSenItem *nextp, *senp = nodep->sensesp(); senp; senp = nextp) {
nextp = VN_AS(senp->nextp(), SenItem);
// cppcheck-suppress unassignedVariable // cppcheck bug
@ -2838,35 +2859,53 @@ private:
}
}
// Pass2, remove dup edges
for (AstSenItem *nextp, *senp = nodep->sensesp(); senp; senp = nextp) {
// Pass 2, remove duplicates and simplify adjacent terms if possible
for (AstSenItem *senp = nodep->sensesp(), *nextp; senp; senp = nextp) {
nextp = VN_AS(senp->nextp(), SenItem);
AstSenItem* const litemp = senp;
AstSenItem* const ritemp = nextp;
if (ritemp) {
if ((litemp->sensp() && ritemp->sensp()
&& litemp->sensp()->sameGateTree(ritemp->sensp()))
|| (!litemp->sensp() && !ritemp->sensp())) {
// We've sorted in the order ANY, BOTH, POS, NEG,
// so we don't need to try opposite orders
if ((litemp->edgeType() == VEdgeType::ET_POSEDGE // POS or NEG -> BOTH
&& ritemp->edgeType() == VEdgeType::ET_NEGEDGE)
|| (litemp->edgeType() == ritemp->edgeType()) // Identical edges
) {
// Fix edge of old node
if (litemp->edgeType() == VEdgeType::ET_POSEDGE
&& ritemp->edgeType() == VEdgeType::ET_NEGEDGE)
litemp->edgeType(VEdgeType::ET_BOTHEDGE);
// Remove redundant node
VL_DO_DANGLING(ritemp->unlinkFrBack()->deleteTree(), ritemp);
VL_DANGLING(ritemp);
// Try to collapse again
nextp = litemp;
if (!nextp) break;
AstSenItem* const lItemp = senp;
AstSenItem* const rItemp = nextp;
AstNodeExpr* const lSenp = lItemp->sensp();
AstNodeExpr* const rSenp = rItemp->sensp();
if (!lSenp || !rSenp) continue;
if (lSenp->sameGateTree(rSenp)) {
// POSEDGE or NEGEDGE -> BOTHEDGE. (We've sorted POSEDGE, before NEGEDGE, so we
// do not need to test for the opposite orders.)
if (lItemp->edgeType() == VEdgeType::ET_POSEDGE
&& rItemp->edgeType() == VEdgeType::ET_NEGEDGE) {
// Make both terms BOTHEDGE, the second will be removed below
lItemp->edgeType(VEdgeType::ET_BOTHEDGE);
rItemp->edgeType(VEdgeType::ET_BOTHEDGE);
}
// Remove identical expressions
if (lItemp->edgeType() == rItemp->edgeType()) {
VL_DO_DANGLING(rItemp->unlinkFrBack()->deleteTree(), rItemp);
nextp = lItemp;
}
continue;
}
// Not identical terms, check if they can be combined
if (lSenp->width() != rSenp->width()) continue;
if (AstAnd* const lAndp = VN_CAST(lSenp, And)) {
if (AstAnd* const rAndp = VN_CAST(rSenp, And)) {
if (AstConst* const lConstp = VN_CAST(lAndp->lhsp(), Const)) {
if (AstConst* const rConstp = VN_CAST(rAndp->lhsp(), Const)) {
if (lAndp->rhsp()->sameTree(rAndp->rhsp())) {
const V3Number lNum{lConstp->num()};
lConstp->num().opOr(lNum, rConstp->num());
// Remove redundant term
VL_DO_DANGLING(rItemp->unlinkFrBack()->deleteTree(), rItemp);
nextp = lItemp;
}
}
}
}
}
}
// nodep->dumpTree("- ssou: ");
}
}

View File

@ -314,25 +314,23 @@ struct TriggerKit {
void addFirstIterationTriggerAssignment(AstVarScope* counterp, uint32_t index) const {
FileLine* const flp = counterp->fileline();
AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
AstCMethodHard* const callp
= new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, index}};
callp->dtypeSetBit();
callp->pure(true);
m_funcp->stmtsp()->addHereThisAsNext(new AstAssign{
flp, callp,
new AstEq{flp, new AstVarRef{flp, counterp, VAccess::READ}, new AstConst{flp, 0}}});
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "set"};
callp->addPinsp(new AstConst{flp, index});
callp->addPinsp(
new AstEq{flp, new AstVarRef{flp, counterp, VAccess::READ}, new AstConst{flp, 0}});
callp->dtypeSetVoid();
m_funcp->stmtsp()->addHereThisAsNext(callp->makeStmt());
}
// Utility to set then clear the dpiExportTrigger trigger
void addDpiExportTriggerAssignment(AstVarScope* dpiExportTriggerVscp, uint32_t index) const {
FileLine* const flp = dpiExportTriggerVscp->fileline();
AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
AstCMethodHard* const callp
= new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, index}};
callp->dtypeSetBit();
callp->pure(true);
AstNode* stmtp
= new AstAssign{flp, callp, new AstVarRef{flp, dpiExportTriggerVscp, VAccess::READ}};
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "set"};
callp->addPinsp(new AstConst{flp, index});
callp->addPinsp(new AstVarRef{flp, dpiExportTriggerVscp, VAccess::READ});
callp->dtypeSetVoid();
AstNode* const stmtp = callp->makeStmt();
stmtp->addNext(new AstAssign{flp, new AstVarRef{flp, dpiExportTriggerVscp, VAccess::WRITE},
new AstConst{flp, AstConst::BitFalse{}}});
m_funcp->stmtsp()->addHereThisAsNext(stmtp);
@ -359,10 +357,15 @@ AstSenTree* createTriggerSenTree(AstNetlist* netlistp, AstVarScope* const vscp,
AstTopScope* const topScopep = netlistp->topScopep();
FileLine* const flp = topScopep->fileline();
AstVarRef* const vrefp = new AstVarRef{flp, vscp, VAccess::READ};
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, index}};
callp->dtypeSetBit();
const uint32_t wordIndex = index / 64;
const uint32_t bitIndex = index % 64;
AstCMethodHard* const callp
= new AstCMethodHard{flp, vrefp, "word", new AstConst{flp, wordIndex}};
callp->dtypeSetUInt64();
callp->pure(true);
AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_TRUE, callp};
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;
@ -427,14 +430,28 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
new AstText{flp, "VL_DBG_MSGF(\" No triggers active\\n\");\n", true});
}
// Set the given trigger to the given value
const auto setTrig = [&](uint32_t index, AstNodeExpr* valp) {
AstVarRef* const vrefp = new AstVarRef{flp, vscp, VAccess::WRITE};
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "set"};
callp->addPinsp(new AstConst{flp, index});
callp->addPinsp(valp);
callp->dtypeSetVoid();
return callp->makeStmt();
};
// Create a reference to a trigger flag
const auto getTrigRef = [&](uint32_t index, VAccess access) {
AstVarRef* const vrefp = new AstVarRef{flp, vscp, access};
AstConst* const idxp = new AstConst{flp, index};
AstCMethodHard* callp = new AstCMethodHard{flp, vrefp, "at", idxp};
callp->dtypeSetBit();
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, "word", new AstConst{flp, wordIndex}};
callp->dtypeSetUInt64();
callp->pure(true);
return callp;
AstNodeExpr* const termp
= new AstAnd{flp, new AstConst{flp, AstConst::Unsized64{}, 1ULL << bitIndex}, callp};
return termp;
};
// Add a debug dumping statement for this trigger
@ -446,7 +463,7 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
ss << "\\n\");\n";
const string message{ss.str()};
AstIf* const ifp = new AstIf{flp, getTrigRef(index, VAccess::READ)};
AstIf* const ifp = new AstIf{flp, getTrig(index)};
dumpp->addStmtsp(ifp);
ifp->addThensp(new AstText{flp, message, true});
};
@ -458,13 +475,13 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
// Add trigger computation
uint32_t triggerNumber = extraTriggers.size();
AstNode* initialTrigsp = nullptr;
AstNodeStmt* initialTrigsp = nullptr;
for (const AstSenTree* const senTreep : senTreeps) {
UASSERT_OBJ(senTreep->hasClocked() || senTreep->hasHybrid(), senTreep,
"Cannot create trigger expression for non-clocked sensitivity");
// Create the trigger AstSenTrees and associate it with the original AstSenTree
AstCMethodHard* const senp = getTrigRef(triggerNumber, VAccess::READ);
// Create the trigger AstSenTrees and associate them with the original AstSenTree
AstNodeExpr* const senp = getTrig(triggerNumber);
AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_TRUE, senp};
AstSenTree* const trigpSenp = new AstSenTree{flp, senItemp};
topScopep->addSenTreesp(trigpSenp);
@ -472,14 +489,12 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
// Add the trigger computation
const auto& pair = senExprBuilder.build(senTreep);
funcp->addStmtsp(
new AstAssign{flp, getTrigRef(triggerNumber, VAccess::WRITE), pair.first});
funcp->addStmtsp(setTrig(triggerNumber, pair.first));
// Add initialization time trigger
if (pair.second || v3Global.opt.xInitialEdge()) {
AstNode* const assignp = new AstAssign{flp, getTrigRef(triggerNumber, VAccess::WRITE),
new AstConst{flp, 1}};
initialTrigsp = AstNode::addNext(initialTrigsp, assignp);
initialTrigsp
= AstNode::addNext(initialTrigsp, setTrig(triggerNumber, new AstConst{flp, 1}));
}
// Add a debug statement for this trigger
@ -803,7 +818,7 @@ 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, "set", argp};
AstCMethodHard* const callp = new AstCMethodHard{flp, lhsp, "thisOr", argp};
callp->dtypeSetVoid();
return callp->makeStmt();
}

View File

@ -600,22 +600,22 @@
<cfunc loc="a,0,0,0,0" name="_eval_final"/>
<cfunc loc="a,0,0,0,0" name="_eval_settle"/>
<cfunc loc="a,0,0,0,0" name="_eval_triggers__act">
<assign loc="d,10,8,10,9" dtype_id="9">
<and loc="d,60,14,60,21" dtype_id="9">
<ccast loc="d,60,22,60,25" dtype_id="9">
<varref loc="d,60,22,60,25" name="clk" dtype_id="9"/>
</ccast>
<not loc="d,60,14,60,21" dtype_id="9">
<ccast loc="d,60,14,60,21" dtype_id="9">
<varref loc="d,60,14,60,21" name="__Vtrigprevexpr___TOP__clk__0" dtype_id="9"/>
</ccast>
</not>
</and>
<cmethodhard loc="d,10,8,10,9" name="at" dtype_id="9">
<stmtexpr loc="d,10,8,10,9">
<cmethodhard loc="d,10,8,10,9" name="set" dtype_id="7">
<varref loc="d,10,8,10,9" name="__VactTriggered" dtype_id="9"/>
<const loc="d,10,8,10,9" name="32&apos;h0" dtype_id="14"/>
<and loc="d,60,14,60,21" dtype_id="9">
<ccast loc="d,60,22,60,25" dtype_id="9">
<varref loc="d,60,22,60,25" name="clk" dtype_id="9"/>
</ccast>
<not loc="d,60,14,60,21" dtype_id="9">
<ccast loc="d,60,14,60,21" dtype_id="9">
<varref loc="d,60,14,60,21" name="__Vtrigprevexpr___TOP__clk__0" dtype_id="9"/>
</ccast>
</not>
</and>
</cmethodhard>
</assign>
</stmtexpr>
<assign loc="d,60,22,60,25" dtype_id="9">
<varref loc="d,60,22,60,25" name="clk" dtype_id="9"/>
<varref loc="d,60,22,60,25" name="__Vtrigprevexpr___TOP__clk__0" dtype_id="9"/>
@ -647,10 +647,13 @@
</begin>
</if>
<if loc="d,10,8,10,9">
<cmethodhard loc="d,10,8,10,9" name="at" dtype_id="9">
<varref loc="d,10,8,10,9" name="__VactTriggered" dtype_id="9"/>
<const loc="d,10,8,10,9" name="32&apos;h0" dtype_id="14"/>
</cmethodhard>
<and loc="d,10,8,10,9" dtype_id="17">
<const loc="d,10,8,10,9" name="64&apos;h1" dtype_id="17"/>
<cmethodhard loc="d,10,8,10,9" name="word" dtype_id="18">
<varref loc="d,10,8,10,9" name="__VactTriggered" dtype_id="9"/>
<const loc="d,10,8,10,9" name="32&apos;h0" dtype_id="14"/>
</cmethodhard>
</and>
<begin>
<text loc="d,10,8,10,9"/>
</begin>
@ -673,10 +676,13 @@
</begin>
</if>
<if loc="d,10,8,10,9">
<cmethodhard loc="d,10,8,10,9" name="at" dtype_id="9">
<varref loc="d,10,8,10,9" name="__VnbaTriggered" dtype_id="9"/>
<const loc="d,10,8,10,9" name="32&apos;h0" dtype_id="14"/>
</cmethodhard>
<and loc="d,10,8,10,9" dtype_id="17">
<const loc="d,10,8,10,9" name="64&apos;h1" dtype_id="17"/>
<cmethodhard loc="d,10,8,10,9" name="word" dtype_id="18">
<varref loc="d,10,8,10,9" name="__VnbaTriggered" dtype_id="9"/>
<const loc="d,10,8,10,9" name="32&apos;h0" dtype_id="14"/>
</cmethodhard>
</and>
<begin>
<text loc="d,10,8,10,9"/>
</begin>
@ -1505,10 +1511,13 @@
</cfunc>
<cfunc loc="a,0,0,0,0" name="_eval_nba">
<if loc="d,10,8,10,9">
<cmethodhard loc="d,10,8,10,9" name="at" dtype_id="9">
<varref loc="d,10,8,10,9" name="__VnbaTriggered" dtype_id="9"/>
<const loc="d,10,8,10,9" name="32&apos;h0" dtype_id="14"/>
</cmethodhard>
<and loc="d,10,8,10,9" dtype_id="17">
<const loc="d,10,8,10,9" name="64&apos;h1" dtype_id="17"/>
<cmethodhard loc="d,10,8,10,9" name="word" dtype_id="18">
<varref loc="d,10,8,10,9" name="__VnbaTriggered" dtype_id="9"/>
<const loc="d,10,8,10,9" name="32&apos;h0" dtype_id="14"/>
</cmethodhard>
</and>
<begin>
<stmtexpr loc="d,64,10,64,11">
<ccall loc="d,64,10,64,11" dtype_id="7" func="_nba_sequent__TOP__0"/>
@ -1609,7 +1618,7 @@
</cmethodhard>
</stmtexpr>
<stmtexpr loc="a,0,0,0,0">
<cmethodhard loc="a,0,0,0,0" name="set" dtype_id="7">
<cmethodhard loc="a,0,0,0,0" name="thisOr" dtype_id="7">
<varref loc="a,0,0,0,0" name="__VnbaTriggered" dtype_id="9"/>
<varref loc="a,0,0,0,0" name="__VactTriggered" dtype_id="9"/>
</cmethodhard>
@ -1668,7 +1677,7 @@
<if loc="d,14,10,14,13">
<and loc="d,14,10,14,13" dtype_id="1">
<varref loc="d,14,10,14,13" name="clk" dtype_id="1"/>
<const loc="d,14,10,14,13" name="8&apos;hfe" dtype_id="17"/>
<const loc="d,14,10,14,13" name="8&apos;hfe" dtype_id="19"/>
</and>
<begin>
<cstmt loc="d,14,10,14,13">
@ -1763,7 +1772,7 @@
<basicdtype loc="d,32,24,32,27" id="1" name="logic"/>
<basicdtype loc="d,52,16,52,17" id="14" name="logic" left="31" right="0"/>
<basicdtype loc="d,16,17,16,18" id="2" name="logic" left="3" right="0"/>
<enumdtype loc="d,16,12,16,16" id="18" name="t.my_t" sub_dtype_id="2">
<enumdtype loc="d,16,12,16,16" id="20" name="t.my_t" sub_dtype_id="2">
<enumitem loc="d,17,24,17,27" name="E01" dtype_id="11">
<const loc="d,17,30,17,31" name="4&apos;h1" dtype_id="11"/>
</enumitem>
@ -1775,7 +1784,7 @@
</enumitem>
</enumdtype>
<basicdtype loc="d,22,4,22,11" id="4" name="integer" left="31" right="0" signed="true"/>
<refdtype loc="d,23,4,23,8" id="19" name="my_t" sub_dtype_id="2"/>
<refdtype loc="d,23,4,23,8" id="21" name="my_t" sub_dtype_id="2"/>
<basicdtype loc="d,27,4,27,10" id="10" name="string"/>
<unpackarraydtype loc="d,16,12,16,16" id="12" sub_dtype_id="2">
<range loc="d,16,12,16,16">
@ -1795,16 +1804,18 @@
<const loc="d,16,12,16,16" name="32&apos;h0" dtype_id="14"/>
</range>
</unpackarraydtype>
<refdtype loc="d,51,12,51,16" id="20" name="my_t" sub_dtype_id="2"/>
<refdtype loc="d,51,12,51,16" id="22" name="my_t" sub_dtype_id="2"/>
<basicdtype loc="d,22,23,22,24" id="8" name="logic" left="31" right="0" signed="true"/>
<voiddtype loc="d,10,8,10,9" id="7"/>
<basicdtype loc="d,10,8,10,9" id="6" name="VlTriggerVec"/>
<basicdtype loc="d,10,8,10,9" id="18" name="QData" left="63" right="0"/>
<basicdtype loc="d,10,8,10,9" id="17" name="logic" left="63" right="0"/>
<basicdtype loc="d,10,8,10,9" id="5" name="bit" left="31" right="0"/>
<basicdtype loc="d,10,8,10,9" id="3" name="bit"/>
<basicdtype loc="d,60,22,60,25" id="9" name="logic" left="31" right="0"/>
<basicdtype loc="d,31,11,31,14" id="11" name="logic" left="31" right="0"/>
<basicdtype loc="d,37,17,37,21" id="13" name="logic" left="31" right="0"/>
<basicdtype loc="d,14,10,14,13" id="17" name="logic" left="7" right="0"/>
<basicdtype loc="d,14,10,14,13" id="19" name="logic" left="7" right="0"/>
</typetable>
<constpool>
<module loc="a,0,0,0,0" name="@CONST-POOL@" origName="@CONST-POOL@">