Lower bit-scan loop idioms to $mostsetbitp1 / $countones
Recognize the common single-bit scan loop idioms in V3Unroll (before it
unrolls) and lower them to bit-reduction primitives, replacing a literal
W-iteration loop with one intrinsic-backed expression:
target=0; for (i=0;i<W;i++) if (vec[i]) target = i + 1; -> $mostsetbitp1(vec)
target=0; for (i=0;i<W;i++) if (vec[i]) target = target + 1; -> $countones(vec)
The leading-one form lowers to a new AstMostSetBitP1 node, emitted as
VL_MOSTSETBITP1_{I,Q,W}; those runtime helpers now use __builtin_clz where
available (same pattern as VL_REDXOR's __builtin_parity), with the existing
bit scan as fallback. The count-ones form reuses AstCountOnes ($countones,
popcount); as the DFG requires a 32-bit countones result it is built at 32
bits and narrowed to the accumulator width with a select.
Matching is structural to stay sound: the index must start at 0, increment
by exactly 1, and scan all W==width(vec) bits via a single 1-bit select of a
distinct vector, with the target pre-zeroed and no else branch. The loop
bound is accepted as a strict ascending 'idx < W' written either way and
signed or unsigned (Gt/GtS/Lt/LtS). Gated by -fbit-scan-loops (on at -O).
Adds t_bit_scan_loops (I/Q/W, count-ones and unsigned-index positives;
step-2, start-1, idx*2+1, vec[idx+1], target=idx and W!=width negatives, all
self-checked and asserted via --stats not to lower) plus t_bit_scan_loops_off
for the disable flag.
Motivated by a transformer inference design whose 80-bit leading-one detector
ran every cycle (~37% of runtime); the lowering is worth ~39% there.
This commit is contained in:
parent
a37e2ee94b
commit
6c2cfb31e9
|
|
@ -272,6 +272,7 @@ Teng Huang
|
||||||
Thomas Aldrian
|
Thomas Aldrian
|
||||||
Thomas Brown
|
Thomas Brown
|
||||||
Thomas Dybdahl Ahle
|
Thomas Dybdahl Ahle
|
||||||
|
Thomas Santerre
|
||||||
Tim Hutt
|
Tim Hutt
|
||||||
Tim Snyder
|
Tim Snyder
|
||||||
Tobias Jensen
|
Tobias Jensen
|
||||||
|
|
|
||||||
|
|
@ -658,6 +658,11 @@ Summary:
|
||||||
|
|
||||||
.. option:: -fno-assemble
|
.. option:: -fno-assemble
|
||||||
|
|
||||||
|
.. option:: -fno-bit-scan-loops
|
||||||
|
|
||||||
|
Rarely needed. Disable converting leading-one and count-ones loops into
|
||||||
|
bit-reduction operations.
|
||||||
|
|
||||||
.. option:: -fno-case
|
.. option:: -fno-case
|
||||||
|
|
||||||
Rarely needed. Disable all case statement optimizations.
|
Rarely needed. Disable all case statement optimizations.
|
||||||
|
|
|
||||||
|
|
@ -903,15 +903,32 @@ static inline IData VL_CLOG2_W(int words, WDataInP const lwp) VL_PURE {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline IData VL_MOSTSETBITP1_I(IData lhs) VL_PURE {
|
||||||
|
if (VL_UNLIKELY(!lhs)) return 0; // __builtin_clz is undefined for 0
|
||||||
|
#if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(VL_NO_BUILTINS)
|
||||||
|
return VL_EDATASIZE - __builtin_clz(lhs);
|
||||||
|
#else
|
||||||
|
for (int bit = VL_EDATASIZE - 1; bit >= 0; --bit) {
|
||||||
|
if (VL_BITISSET_E(lhs, bit)) return bit + 1;
|
||||||
|
}
|
||||||
|
return 0; // LCOV_EXCL_LINE // Can't get here - one bit must be set
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
static inline IData VL_MOSTSETBITP1_Q(QData lhs) VL_PURE {
|
||||||
|
if (VL_UNLIKELY(!lhs)) return 0;
|
||||||
|
#if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(VL_NO_BUILTINS)
|
||||||
|
return 64 - __builtin_clzll(static_cast<unsigned long long>(lhs));
|
||||||
|
#else
|
||||||
|
const IData hi = static_cast<IData>(lhs >> 32ULL);
|
||||||
|
return hi ? (VL_EDATASIZE + VL_MOSTSETBITP1_I(hi))
|
||||||
|
: VL_MOSTSETBITP1_I(static_cast<IData>(lhs));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
static inline IData VL_MOSTSETBITP1_W(int words, WDataInP const lwp) VL_PURE {
|
static inline IData VL_MOSTSETBITP1_W(int words, WDataInP const lwp) VL_PURE {
|
||||||
// MSB set bit plus one; similar to FLS. 0=value is zero
|
// MSB set bit plus one; similar to FLS. 0=value is zero
|
||||||
for (int i = words - 1; i >= 0; --i) {
|
for (int i = words - 1; i >= 0; --i) {
|
||||||
if (VL_UNLIKELY(lwp[i])) { // Shorter worst case if predict not taken
|
// Shorter worst case if predict not taken
|
||||||
for (int bit = VL_EDATASIZE - 1; bit >= 0; --bit) {
|
if (VL_UNLIKELY(lwp[i])) return i * VL_EDATASIZE + VL_MOSTSETBITP1_I(lwp[i]);
|
||||||
if (VL_UNLIKELY(VL_BITISSET_E(lwp[i], bit))) return i * VL_EDATASIZE + bit + 1;
|
|
||||||
}
|
|
||||||
// Can't get here - one bit must be set
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5734,6 +5734,23 @@ public:
|
||||||
void dump(std::ostream& str) const override;
|
void dump(std::ostream& str) const override;
|
||||||
void dumpJson(std::ostream& str) const override;
|
void dumpJson(std::ostream& str) const override;
|
||||||
};
|
};
|
||||||
|
class AstMostSetBitP1 final : public AstNodeUniop {
|
||||||
|
// Most-significant set bit plus one (bit-width); 0 if value is zero
|
||||||
|
public:
|
||||||
|
AstMostSetBitP1(FileLine* fl, AstNodeExpr* lhsp)
|
||||||
|
: ASTGEN_SUPER_MostSetBitP1(fl, lhsp) {
|
||||||
|
dtypeSetInteger2State();
|
||||||
|
}
|
||||||
|
ASTGEN_MEMBERS_AstMostSetBitP1;
|
||||||
|
void numberOperate(V3Number& out, const V3Number& lhs) override { out.opMostSetBitP1(lhs); }
|
||||||
|
string emitVerilog() override { return "%f$mostsetbitp1(%l)"; }
|
||||||
|
string emitC() override { return "VL_MOSTSETBITP1_%lq(%lW, %P, %li)"; }
|
||||||
|
bool cleanOut() const override { return false; }
|
||||||
|
bool cleanLhs() const override { return true; }
|
||||||
|
bool sizeMattersLhs() const override { return false; }
|
||||||
|
int instrCount() const override { return widthInstrs() * 16; }
|
||||||
|
bool isSystemFunc() const override { return true; }
|
||||||
|
};
|
||||||
class AstNToI final : public AstNodeUniop {
|
class AstNToI final : public AstNodeUniop {
|
||||||
// String to any-size integral
|
// String to any-size integral
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -1456,6 +1456,20 @@ V3Number& V3Number::opCLog2(const V3Number& lhs) {
|
||||||
setZero();
|
setZero();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
V3Number& V3Number::opMostSetBitP1(const V3Number& lhs) {
|
||||||
|
// Most-significant set bit plus one (bit-width / find-last-set); 0 if value is zero
|
||||||
|
NUM_ASSERT_OP_ARGS1(lhs);
|
||||||
|
NUM_ASSERT_LOGIC_ARGS1(lhs);
|
||||||
|
if (lhs.isFourState()) return setAllBitsX();
|
||||||
|
for (int bit = lhs.width() - 1; bit >= 0; bit--) {
|
||||||
|
if (lhs.bitIs1(bit)) {
|
||||||
|
setLong(bit + 1);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setZero();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
V3Number& V3Number::opLogNot(const V3Number& lhs) {
|
V3Number& V3Number::opLogNot(const V3Number& lhs) {
|
||||||
NUM_ASSERT_OP_ARGS1(lhs);
|
NUM_ASSERT_OP_ARGS1(lhs);
|
||||||
|
|
|
||||||
|
|
@ -760,6 +760,7 @@ public:
|
||||||
V3Number& opOneHot(const V3Number& lhs);
|
V3Number& opOneHot(const V3Number& lhs);
|
||||||
V3Number& opOneHot0(const V3Number& lhs);
|
V3Number& opOneHot0(const V3Number& lhs);
|
||||||
V3Number& opCLog2(const V3Number& lhs);
|
V3Number& opCLog2(const V3Number& lhs);
|
||||||
|
V3Number& opMostSetBitP1(const V3Number& lhs);
|
||||||
V3Number& opClean(const V3Number& lhs, uint32_t bits);
|
V3Number& opClean(const V3Number& lhs, uint32_t bits);
|
||||||
V3Number& opConcat(const V3Number& lhs, const V3Number& rhs);
|
V3Number& opConcat(const V3Number& lhs, const V3Number& rhs);
|
||||||
V3Number& opLenN(const V3Number& lhs);
|
V3Number& opLenN(const V3Number& lhs);
|
||||||
|
|
|
||||||
|
|
@ -1448,6 +1448,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
|
||||||
|
|
||||||
DECL_OPTION("-facyc-simp", FOnOff, &m_fAcycSimp);
|
DECL_OPTION("-facyc-simp", FOnOff, &m_fAcycSimp);
|
||||||
DECL_OPTION("-fassemble", FOnOff, &m_fAssemble);
|
DECL_OPTION("-fassemble", FOnOff, &m_fAssemble);
|
||||||
|
DECL_OPTION("-fbit-scan-loops", FOnOff, &m_fBitScanLoops);
|
||||||
DECL_OPTION("-fcase", CbFOnOff, [this](bool flag) {
|
DECL_OPTION("-fcase", CbFOnOff, [this](bool flag) {
|
||||||
m_fCaseDecoder = flag;
|
m_fCaseDecoder = flag;
|
||||||
m_fCaseTable = flag;
|
m_fCaseTable = flag;
|
||||||
|
|
@ -2358,6 +2359,7 @@ void V3Options::optimize(int level) {
|
||||||
const bool flag = level > 0;
|
const bool flag = level > 0;
|
||||||
m_fAcycSimp = flag;
|
m_fAcycSimp = flag;
|
||||||
m_fAssemble = flag;
|
m_fAssemble = flag;
|
||||||
|
m_fBitScanLoops = flag;
|
||||||
m_fCaseDecoder = flag;
|
m_fCaseDecoder = flag;
|
||||||
m_fCaseTable = flag;
|
m_fCaseTable = flag;
|
||||||
m_fCaseTree = flag;
|
m_fCaseTree = flag;
|
||||||
|
|
|
||||||
|
|
@ -392,6 +392,7 @@ private:
|
||||||
// MEMBERS (optimizations)
|
// MEMBERS (optimizations)
|
||||||
bool m_fAcycSimp; // main switch: -fno-acyc-simp: acyclic pre-optimizations
|
bool m_fAcycSimp; // main switch: -fno-acyc-simp: acyclic pre-optimizations
|
||||||
bool m_fAssemble; // main switch: -fno-assemble: assign assemble
|
bool m_fAssemble; // main switch: -fno-assemble: assign assemble
|
||||||
|
bool m_fBitScanLoops; // main switch: -fno-bit-scan-loops: lower priority-encoder loops to CLZ
|
||||||
bool m_fCaseDecoder; // main switch: -fno-case-decoder: case decoder conversion
|
bool m_fCaseDecoder; // main switch: -fno-case-decoder: case decoder conversion
|
||||||
bool m_fCaseTable; // main switch: -fno-case-table: case table conversion
|
bool m_fCaseTable; // main switch: -fno-case-table: case table conversion
|
||||||
bool m_fCaseTree; // main switch: -fno-case-tree: case tree conversion
|
bool m_fCaseTree; // main switch: -fno-case-tree: case tree conversion
|
||||||
|
|
@ -727,6 +728,7 @@ public:
|
||||||
// ACCESSORS (optimization options)
|
// ACCESSORS (optimization options)
|
||||||
bool fAcycSimp() const { return m_fAcycSimp; }
|
bool fAcycSimp() const { return m_fAcycSimp; }
|
||||||
bool fAssemble() const { return m_fAssemble; }
|
bool fAssemble() const { return m_fAssemble; }
|
||||||
|
bool fBitScanLoops() const { return m_fBitScanLoops; }
|
||||||
bool fCaseDecoder() const { return m_fCaseDecoder; }
|
bool fCaseDecoder() const { return m_fCaseDecoder; }
|
||||||
bool fCaseTable() const { return m_fCaseTable; }
|
bool fCaseTable() const { return m_fCaseTable; }
|
||||||
bool fCaseTree() const { return m_fCaseTree; }
|
bool fCaseTree() const { return m_fCaseTree; }
|
||||||
|
|
|
||||||
151
src/V3Unroll.cpp
151
src/V3Unroll.cpp
|
|
@ -62,6 +62,8 @@ struct UnrollStats final {
|
||||||
Stat m_nPragmaDisabled{"Pragma unroll_disable"};
|
Stat m_nPragmaDisabled{"Pragma unroll_disable"};
|
||||||
Stat m_nUnrolledLoops{"Unrolled loops"};
|
Stat m_nUnrolledLoops{"Unrolled loops"};
|
||||||
Stat m_nUnrolledIters{"Unrolled iterations"};
|
Stat m_nUnrolledIters{"Unrolled iterations"};
|
||||||
|
Stat m_bitScanLowered{"Lowered priority-encoder to mostsetbitp1"};
|
||||||
|
Stat m_countOnesLowered{"Lowered count-set-bits to countones"};
|
||||||
};
|
};
|
||||||
|
|
||||||
//######################################################################
|
//######################################################################
|
||||||
|
|
@ -420,6 +422,152 @@ class UnrollAllVisitor final : VNVisitor {
|
||||||
UnrollStats m_stats; // Statistic tracking
|
UnrollStats m_stats; // Statistic tracking
|
||||||
UnrolllBindings m_bindings; // Variable bindings
|
UnrolllBindings m_bindings; // Variable bindings
|
||||||
|
|
||||||
|
// METHODS
|
||||||
|
// Peel value-preserving width casts/truncations (Extend/ExtendS, or a low-bits Sel
|
||||||
|
// with constant lsb 0) to reach the underlying VarRef; nullptr for anything else
|
||||||
|
// (e.g. an arithmetic offset like idx+1 or idx*2).
|
||||||
|
static AstVarRef* unwrapToVarRef(AstNodeExpr* nodep) {
|
||||||
|
while (nodep) {
|
||||||
|
if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) return refp;
|
||||||
|
if (AstExtend* const ep = VN_CAST(nodep, Extend)) {
|
||||||
|
nodep = ep->lhsp();
|
||||||
|
} else if (AstExtendS* const ep = VN_CAST(nodep, ExtendS)) {
|
||||||
|
nodep = ep->lhsp();
|
||||||
|
} else if (AstSel* const sp = VN_CAST(nodep, Sel)) {
|
||||||
|
const AstConst* const lsbp = VN_CAST(sp->lsbp(), Const);
|
||||||
|
if (!lsbp || lsbp->toUInt() != 0) return nullptr;
|
||||||
|
nodep = sp->fromp();
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// True if 'nodep' is exactly 'var + 1' (commutative), where the var operand is the
|
||||||
|
// given variable reached through value-preserving casts only.
|
||||||
|
bool isVarPlus1(AstNode* nodep, const AstVarScope* vscp) {
|
||||||
|
AstAdd* const addp = VN_CAST(nodep, Add);
|
||||||
|
if (!addp) return false;
|
||||||
|
const auto isOne = [](AstNodeExpr* p) -> bool {
|
||||||
|
const AstConst* const cp = VN_CAST(p, Const);
|
||||||
|
return cp && !cp->num().isFourState() && cp->toUInt() == 1;
|
||||||
|
};
|
||||||
|
const AstVarRef* const l = unwrapToVarRef(addp->lhsp());
|
||||||
|
const AstVarRef* const r = unwrapToVarRef(addp->rhsp());
|
||||||
|
if (isOne(addp->rhsp()) && l && l->varScopep() == vscp) return true;
|
||||||
|
if (isOne(addp->lhsp()) && r && r->varScopep() == vscp) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Match a strict ascending loop bound 'idx < W', written either way and signed or
|
||||||
|
// unsigned: (W > idx) [Gt/GtS] or (idx < W) [Lt/LtS]. Sets wp and idxRefp.
|
||||||
|
static bool ascendingBound(AstNodeExpr* condp, AstConst*& wp, AstVarRef*& idxRefp) {
|
||||||
|
if (VN_IS(condp, Gt) || VN_IS(condp, GtS)) {
|
||||||
|
AstNodeBiop* const bp = VN_AS(condp, NodeBiop);
|
||||||
|
wp = VN_CAST(bp->lhsp(), Const);
|
||||||
|
idxRefp = VN_CAST(bp->rhsp(), VarRef);
|
||||||
|
} else if (VN_IS(condp, Lt) || VN_IS(condp, LtS)) {
|
||||||
|
AstNodeBiop* const bp = VN_AS(condp, NodeBiop);
|
||||||
|
idxRefp = VN_CAST(bp->lhsp(), VarRef);
|
||||||
|
wp = VN_CAST(bp->rhsp(), Const);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return wp && idxRefp && !wp->num().isFourState();
|
||||||
|
}
|
||||||
|
// Recognize a single-bit scan loop over all W bits of 'vec' (idx 0..W-1, target
|
||||||
|
// pre-zeroed) and lower it to a bit-reduction primitive. Two idioms are matched:
|
||||||
|
// target = 0; idx = 0;
|
||||||
|
// loop { looptest(W > idx); if (...vec[idx]...) target = <e>; idx = idx + 1; }
|
||||||
|
// where, when W == width(vec):
|
||||||
|
// <e> = idx + 1 => target = $mostsetbitp1(vec) (leading-one / bit-width)
|
||||||
|
// <e> = target + 1 => target = $countones(vec) (population count)
|
||||||
|
bool tryLowerBitScanLoop(AstLoop* loopp) {
|
||||||
|
// Body must be exactly: LoopTest, If, Assign(increment)
|
||||||
|
AstLoopTest* const testp = VN_CAST(loopp->stmtsp(), LoopTest);
|
||||||
|
if (!testp) return false;
|
||||||
|
AstIf* const ifp = VN_CAST(testp->nextp(), If);
|
||||||
|
if (!ifp) return false;
|
||||||
|
AstAssign* const incp = VN_CAST(ifp->nextp(), Assign);
|
||||||
|
if (!incp || incp->nextp()) return false;
|
||||||
|
// Loop test: a strict ascending bound 'idx < W' (W > idx), signed or unsigned
|
||||||
|
AstConst* wp = nullptr;
|
||||||
|
AstVarRef* idxRefp = nullptr;
|
||||||
|
if (!ascendingBound(testp->condp(), wp, idxRefp)) return false;
|
||||||
|
AstVarScope* const idxVscp = idxRefp->varScopep();
|
||||||
|
const uint32_t width = wp->toUInt();
|
||||||
|
// Index must start at 0 (so it takes exactly the values 0..W-1)
|
||||||
|
const AstConst* const idxInitp = m_bindings.get(idxVscp);
|
||||||
|
if (!idxInitp || idxInitp->num().isFourState() || idxInitp->toUInt() != 0) return false;
|
||||||
|
// Increment must be exactly: idx = idx + 1
|
||||||
|
AstVarRef* const incLhsp = VN_CAST(incp->lhsp(), VarRef);
|
||||||
|
if (!incLhsp || incLhsp->varScopep() != idxVscp) return false;
|
||||||
|
if (!isVarPlus1(incp->rhsp(), idxVscp)) return false;
|
||||||
|
// If: no else, and 'then' is exactly one assignment 'target = <expr>'
|
||||||
|
if (ifp->elsesp()) return false;
|
||||||
|
AstAssign* const thenp = VN_CAST(ifp->thensp(), Assign);
|
||||||
|
if (!thenp || thenp->nextp()) return false;
|
||||||
|
AstVarRef* const targetRefp = VN_CAST(thenp->lhsp(), VarRef);
|
||||||
|
if (!targetRefp) return false;
|
||||||
|
AstVarScope* const targetVscp = targetRefp->varScopep();
|
||||||
|
if (targetVscp == idxVscp) return false;
|
||||||
|
const bool isLeadingOne = isVarPlus1(thenp->rhsp(), idxVscp);
|
||||||
|
const bool isCountOnes = !isLeadingOne && isVarPlus1(thenp->rhsp(), targetVscp);
|
||||||
|
if (!isLeadingOne && !isCountOnes) return false;
|
||||||
|
// $countones yields a 32-bit result; only fold when the accumulator fits in 32 bits.
|
||||||
|
if (isCountOnes && targetRefp->width() > 32) return false;
|
||||||
|
// If-cond must select exactly bit 'idx' of some vector 'vec' (vec != idx/target)
|
||||||
|
AstNodeExpr* vecExprp = nullptr;
|
||||||
|
ifp->condp()->foreach([&](AstSel* selp) {
|
||||||
|
if (vecExprp || selp->width() != 1) return;
|
||||||
|
const AstVarRef* const fromp = VN_CAST(selp->fromp(), VarRef);
|
||||||
|
if (!fromp) return;
|
||||||
|
const AstVarScope* const fromVscp = fromp->varScopep();
|
||||||
|
if (fromVscp == idxVscp || fromVscp == targetVscp) return;
|
||||||
|
const AstVarRef* const idxInSel = unwrapToVarRef(selp->lsbp());
|
||||||
|
if (idxInSel && idxInSel->varScopep() == idxVscp) vecExprp = selp->fromp();
|
||||||
|
});
|
||||||
|
if (!vecExprp) return false;
|
||||||
|
// The loop must scan exactly all bits of 'vec'
|
||||||
|
if (static_cast<int>(width) != vecExprp->width()) return false;
|
||||||
|
// 'target' must be const-0 immediately before the loop (collected in m_bindings),
|
||||||
|
// so that an all-zero 'vec' yields 0, matching $mostsetbitp1's definition.
|
||||||
|
const AstConst* const targetInitp = m_bindings.get(targetVscp);
|
||||||
|
if (!targetInitp || targetInitp->num().isFourState() || targetInitp->toUInt() != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Lower the loop to: target = <reduction>(vec); idx = W;
|
||||||
|
// The 'idx = W' store preserves the counted loop's exit value, so the rewrite is
|
||||||
|
// sound even if idx is read after the loop (dead-code elimination drops it if not).
|
||||||
|
FileLine* const flp = loopp->fileline();
|
||||||
|
AstNodeExpr* reducep;
|
||||||
|
if (isLeadingOne) {
|
||||||
|
AstMostSetBitP1* const msbp = new AstMostSetBitP1{flp, vecExprp->cloneTree(false)};
|
||||||
|
msbp->dtypeFrom(targetRefp);
|
||||||
|
reducep = msbp;
|
||||||
|
} else {
|
||||||
|
// $countones must yield a 32-bit result (DFG invariant); narrow afterwards to
|
||||||
|
// the accumulator width to match the loop's wrap-around behaviour.
|
||||||
|
AstCountOnes* const conep = new AstCountOnes{flp, vecExprp->cloneTree(false)};
|
||||||
|
conep->dtypeSetInteger2State();
|
||||||
|
reducep
|
||||||
|
= (targetRefp->width() < 32)
|
||||||
|
? static_cast<AstNodeExpr*>(new AstSel{flp, conep, 0, targetRefp->width()})
|
||||||
|
: static_cast<AstNodeExpr*>(conep);
|
||||||
|
}
|
||||||
|
AstAssign* const newp = new AstAssign{flp, targetRefp->cloneTree(false), reducep};
|
||||||
|
newp->addNext(new AstAssign{flp, incLhsp->cloneTree(false), wp->cloneTree(false)});
|
||||||
|
loopp->replaceWith(newp);
|
||||||
|
VL_DO_DANGLING(pushDeletep(loopp), loopp);
|
||||||
|
if (isLeadingOne) {
|
||||||
|
UINFO(4, "Lowered priority-encoder loop to $mostsetbitp1: " << newp);
|
||||||
|
++m_stats.m_bitScanLowered;
|
||||||
|
} else {
|
||||||
|
UINFO(4, "Lowered count-set-bits loop to $countones: " << newp);
|
||||||
|
++m_stats.m_countOnesLowered;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// VISIT
|
// VISIT
|
||||||
void visit(AstLoop* nodep) override {
|
void visit(AstLoop* nodep) override {
|
||||||
// Gather variable bindings from the preceding statements
|
// Gather variable bindings from the preceding statements
|
||||||
|
|
@ -448,6 +596,9 @@ class UnrollAllVisitor final : VNVisitor {
|
||||||
m_bindings.set(lhsp->varScopep(), valp);
|
m_bindings.set(lhsp->varScopep(), valp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recognize a leading-one / priority-encoder loop and lower it to $mostsetbitp1
|
||||||
|
if (v3Global.opt.fBitScanLoops() && tryLowerBitScanLoop(nodep)) return;
|
||||||
|
|
||||||
// Attempt to unroll this loop
|
// Attempt to unroll this loop
|
||||||
const std::pair<AstNode*, bool> pair = UnrollOneVisitor::apply(m_stats, m_bindings, nodep);
|
const std::pair<AstNode*, bool> pair = UnrollOneVisitor::apply(m_stats, m_bindings, nodep);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of either the GNU Lesser General Public License Version 3
|
||||||
|
# or the Perl Artistic License Version 2.0.
|
||||||
|
# SPDX-FileCopyrightText: 2024 Wilson Snyder
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||||
|
|
||||||
|
import vltest_bootstrap
|
||||||
|
|
||||||
|
test.scenarios('simulator')
|
||||||
|
|
||||||
|
test.compile(verilator_flags2=['--stats'])
|
||||||
|
|
||||||
|
# Exactly 4 loops must lower to $mostsetbitp1: the 3 canonical I/Q/W leading-one
|
||||||
|
# loops plus the unsigned-index one. The 6 negative loops (incl. W != width) must
|
||||||
|
# be left alone -- a wrong lowering would push the count above 4.
|
||||||
|
test.file_grep(test.stats,
|
||||||
|
r'Optimizations, Loop unrolling, Lowered priority-encoder to mostsetbitp1\s+(\d+)',
|
||||||
|
4)
|
||||||
|
# And the one count-ones loop must lower to $countones.
|
||||||
|
test.file_grep(test.stats,
|
||||||
|
r'Optimizations, Loop unrolling, Lowered count-set-bits to countones\s+(\d+)',
|
||||||
|
1)
|
||||||
|
|
||||||
|
test.execute()
|
||||||
|
|
||||||
|
test.passes()
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
// DESCRIPTION: Verilator: Verilog Test module
|
||||||
|
//
|
||||||
|
// Exercises the priority-encoder / leading-one ("bit-width", find-last-set) loop
|
||||||
|
// idiom that V3Unroll lowers to $mostsetbitp1 (CLZ).
|
||||||
|
//
|
||||||
|
// Positive cases (MUST lower): canonical for(b=0;b<W;b++) if(v[b]) n=b+1
|
||||||
|
// covering the I (<=32b), Q (33-64b) and W (>64b) runtime paths.
|
||||||
|
// Negative cases (MUST NOT lower): non-canonical scans whose result differs
|
||||||
|
// from $mostsetbitp1(vec). These both (a) self-check their own values and
|
||||||
|
// (b) are counted by the .py via --stats so a wrong lowering is caught.
|
||||||
|
//
|
||||||
|
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||||
|
// SPDX-FileCopyrightText: 2024 Wilson Snyder
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
module t (
|
||||||
|
input clk
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- positive: should lower (I / Q / W paths) ----
|
||||||
|
logic [31:0] p32; logic [5:0] n32;
|
||||||
|
logic [47:0] p48; logic [6:0] n48;
|
||||||
|
logic [79:0] p80; logic [6:0] n80;
|
||||||
|
logic [31:0] pc; logic [5:0] nc; // population count (count-ones) -> $countones
|
||||||
|
logic [31:0] pu; logic [5:0] nu; // unsigned loop var (still $mostsetbitp1)
|
||||||
|
always_comb begin n32 = 0; for (int b = 0; b < 32; b++) if (p32[b]) n32 = 6'(b + 1); end
|
||||||
|
always_comb begin n48 = 0; for (int b = 0; b < 48; b++) if (p48[b]) n48 = 7'(b + 1); end
|
||||||
|
always_comb begin n80 = 0; for (int b = 0; b < 80; b++) if (p80[b]) n80 = 7'(b + 1); end
|
||||||
|
always_comb begin nc = 0; for (int b = 0; b < 32; b++) if (pc[b]) nc = nc + 1; end
|
||||||
|
always_comb begin nu = 0; for (int unsigned b = 0; b < 32; b++) if (pu[b]) nu = 6'(b + 1); end
|
||||||
|
|
||||||
|
// ---- negative: must NOT lower (each scans a subset / computes a different value) ----
|
||||||
|
logic [31:0] vn;
|
||||||
|
logic [5:0] e_step2; // step 2 (even bits only)
|
||||||
|
logic [6:0] e_start1; // starts at 1 (skips bit 0)
|
||||||
|
logic [6:0] e_mul; // target = idx*2 + 1
|
||||||
|
logic [5:0] e_off; // selects vec[idx+1]
|
||||||
|
logic [5:0] e_noP1; // target = idx (no +1)
|
||||||
|
logic [31:0] vw; // separate vec with a set bit above the scan bound
|
||||||
|
logic [5:0] e_narrow; // bound W=16 != width(vec)=32 (scans only a prefix)
|
||||||
|
always_comb begin e_step2 = 0; for (int b = 0; b < 32; b += 2) if (vn[b]) e_step2 = 6'(b + 1); end
|
||||||
|
always_comb begin e_start1 = 0; for (int b = 1; b < 32; b++) if (vn[b]) e_start1 = 7'(b + 1); end
|
||||||
|
always_comb begin e_mul = 0; for (int b = 0; b < 32; b++) if (vn[b]) e_mul = 7'(2 * b + 1); end
|
||||||
|
always_comb begin e_off = 0; for (int b = 0; b < 31; b++) if (vn[b + 1]) e_off = 6'(b + 1); end
|
||||||
|
always_comb begin e_noP1 = 0; for (int b = 0; b < 32; b++) if (vn[b]) e_noP1 = 6'(b); end
|
||||||
|
always_comb begin e_narrow = 0; for (int b = 0; b < 16; b++) if (vw[b]) e_narrow = 6'(b + 1); end
|
||||||
|
|
||||||
|
integer cyc = 0;
|
||||||
|
integer fails = 0;
|
||||||
|
always @(posedge clk) begin
|
||||||
|
cyc <= cyc + 1;
|
||||||
|
if (cyc == 0) begin
|
||||||
|
p32 <= 32'h8000_0000; // MSB -> 32
|
||||||
|
p48 <= 48'h0; p48[47] <= 1'b1; // MSB -> 48 (Q path)
|
||||||
|
p80 <= 80'h0; p80[79] <= 1'b1; // MSB -> 80 (W path)
|
||||||
|
pc <= 32'hf0f0_f0f0; // 16 set bits
|
||||||
|
pu <= 32'h0001_0000; // bit 16 -> 17
|
||||||
|
vn <= 32'h0000_00b4; // bits {2,4,5,7} set
|
||||||
|
vw <= 32'h0010_0008; // bits {3,20}: prefix-scan(16)->4, full->21
|
||||||
|
end
|
||||||
|
else if (cyc == 1) begin
|
||||||
|
if (n32 !== 6'd32) fails = fails + 1;
|
||||||
|
if (n48 !== 7'd48) fails = fails + 1;
|
||||||
|
if (n80 !== 7'd80) fails = fails + 1;
|
||||||
|
if (nc !== 6'd16) fails = fails + 1; // popcount(0xF0F0F0F0)
|
||||||
|
if (nu !== 6'd17) fails = fails + 1; // unsigned-loop leading-one, bit 16 -> 17
|
||||||
|
// negatives, hand-computed for vn = 0xB4 (set bits 2,4,5,7):
|
||||||
|
if (e_step2 !== 6'd5) fails = fails + 1; // highest even set bit (4) + 1
|
||||||
|
if (e_start1 !== 7'd8) fails = fails + 1; // highest set bit in [1,32) (7) + 1
|
||||||
|
if (e_mul !== 7'd15) fails = fails + 1; // 2*7 + 1
|
||||||
|
if (e_off !== 6'd7) fails = fails + 1; // idx where vec[idx+1]; highest = 6 -> 7
|
||||||
|
if (e_noP1 !== 6'd7) fails = fails + 1; // highest set bit (7), no +1
|
||||||
|
if (e_narrow !== 6'd4) fails = fails + 1; // W != width: only low 16 bits scanned (bit 3)
|
||||||
|
if (fails == 0) $write("*-* All Finished *-*\n");
|
||||||
|
else $write("FAILED: %0d mismatches\n", fails);
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of either the GNU Lesser General Public License Version 3
|
||||||
|
# or the Perl Artistic License Version 2.0.
|
||||||
|
# SPDX-FileCopyrightText: 2024 Wilson Snyder
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||||
|
|
||||||
|
import vltest_bootstrap
|
||||||
|
|
||||||
|
test.scenarios('simulator')
|
||||||
|
|
||||||
|
# Reuse the same design; only the optimization switch differs.
|
||||||
|
test.top_filename = "t/t_bit_scan_loops.v"
|
||||||
|
|
||||||
|
test.compile(verilator_flags2=['--stats', '-fno-bit-scan-loops'])
|
||||||
|
|
||||||
|
# With the optimization disabled, no loop may be lowered (count 0 / line absent).
|
||||||
|
test.file_grep_not(test.stats,
|
||||||
|
r'Lowered priority-encoder to mostsetbitp1\s+[1-9]')
|
||||||
|
|
||||||
|
test.execute()
|
||||||
|
|
||||||
|
test.passes()
|
||||||
Loading…
Reference in New Issue