Xilinx
HLS
Block Preview

Introduction

The Block Threshold Count block chops the input stream into consecutive blocks of N samples and, at the end of each block, publishes how much of that block sat above and below a reference threshold $t$ driven on the THRESHOLD pin. With $d_i = x_i - t$:

$$ \mathrm{ABOVE_COUNT} = #{, i : d_i > 0 ,}, \qquad \mathrm{BELOW_COUNT} = #{, i : d_i < 0 ,} $$

$$ \mathrm{DUTY_CYCLE} = \frac{\mathrm{ABOVE_COUNT}}{N} ;\in; [0, 1] $$

Both comparisons are strict, so a sample sitting exactly on the threshold is counted in neither - see The on-threshold convention, which is the one place in this family where that rule is directly visible in a number.

N is a runtime input, not a property. You drive the exponent on the EXP pin and the block size is $N = 2^{\mathrm{EXP}}$:

EXP N EXP N
4 16 12 4096
6 64 16 65536
8 256 20 1048576

Because N is a power of two, the division by N is an exact arithmetic shift. There is no divider, no reciprocal ROM and no rounding beyond the single final requantisation of the duty cycle into your Q format - which is also why the block size can be changed while the design is running, for free.

What it is FOR

This is a discriminator plus a counter, and that combination is the standard way to measure, without leaving the fabric:

  • a duty cycle - what fraction of the block a switching or gated signal spent in its high state;
  • a comparator hit rate - with THRESHOLD at a discriminator level, ABOVE_COUNT is a time-over-threshold measure and, divided by N, an occupancy;
  • a dead-time fraction - BELOW_COUNT over a busy/veto level;
  • a threshold occupancy scan - THRESHOLD is a PIN, so a slow control loop can sweep it and read one point of the amplitude distribution per block, or servo it to hold a target occupancy;
  • a baseline-referred activity measure - drive THRESHOLD from a slow baseline tracker and the counts follow the baseline automatically.

Cost

One subtractor for $d = x - t$, one comparator per enabled direction, one counter of Max Block Exponent + 1 bits per counted direction, a hold register for each, and - if the duty cycle is on - one barrel shifter and one requantiser. No multiplier, no divider, no square root, and no serial arithmetic: the tail is a constant 2 clocks whatever is enabled.

When to use this instead of Block Statistics

The all-in-one Block Statistics block is not deprecated and computes these same counts (under the names POS_COUNT and NEG_COUNT) among twenty other statistics. The rule is simple:

  • you want several statistics of the SAME block - the counts and the mean and the RMS of the same N samples - use Block Statistics. They share one accumulator and one serial tail, so the second and third statistic are nearly free.
  • you want exactly these numbers - use this block. Then you synthesise only them: the pin list, the logic and the tail are all that the counts need, and nothing else reaches the synthesiser. This block also adds the duty cycle, which the all-in-one block does not have - there you would divide POS_COUNT by N yourself.

Two Block Statistics blocks side by side would duplicate the accumulators; two per-operator blocks side by side duplicate them too. One Block Statistics block never does.

Pin Description

IN Input IN_BitsInt + IN_BitsFract bit BIT VECTOR
Input samples, fixed point in the IN Q format. Compared, and counted towards N, only on the clocks where IN_DV is high.
Default: Must be connected
IN_DV Input 1 bit BIT
Per-sample qualifier, active high, and the ONLY qualifier this block has. A sample is compared, counted, and counts towards N, exactly on the clocks where this is high; the tail keeps running regardless. Unconnected defaults to '1'. (There is deliberately no CE pin - to stall the block, gate this.)
EXP Input 6 bit BIT VECTOR
Block size exponent, runtime programmable: the block is $N = 2^{\text{EXP}}$ samples long, and it is the divisor of the duty cycle. 6 bits unsigned, accepted range 0 .. Max Block Exponent; larger values are clamped to Max Block Exponent. Sampled on the first accepted sample of a block and held for that whole block, so a change takes effect on the NEXT block. Use EXP >= 1 (see “Timing”). Unconnected defaults to 10 (N = 1024).
THRESHOLD Input IN_BitsInt + IN_BitsFract bit BIT VECTOR
The level the samples are compared against, in the input Q format. Both comparisons are STRICT: a sample exactly on this value is counted in neither direction (for “>=” semantics, drive it one LSB lower). Latched on the first accepted sample of a block, so a mid-block change takes effect on the NEXT block and a count is never a mixture of two thresholds. Unconnected defaults to all zeros, which makes the block report the occupancy above and below zero.
ABOVE_COUNT Output MaxBlockExponent + 1 bit BIT VECTOR
How many samples of the block were strictly above THRESHOLD. Unsigned integer, no Q format: its width is Max Block Exponent + 1 bits, which cannot overflow. Updated on the OUT_DV clock and on no other. Present on the symbol only when Enable ABOVE_COUNT = YES. (Inside the HLS core this port keeps the neutral name out_pos_count that it shares with the all-in-one Block Statistics core, and its switch is EN_POSC; the symbol uses the unambiguous ABOVE name. Same signal, two names, on purpose - it is what lets the two implementations be cross-checked against each other in one translation unit.)
BELOW_COUNT Output MaxBlockExponent + 1 bit BIT VECTOR
How many samples of the block were strictly below THRESHOLD. Same width rule as ABOVE_COUNT, and the same caveat: ABOVE + BELOW <= N, with equality only when no sample sits exactly on the threshold. Present on the symbol only when Enable BELOW_COUNT = YES. (Core-side name out_neg_count, switch EN_NEGC - see the note on ABOVE_COUNT.)
OUT_DV Output 1 bit BIT
One-clock pulse marking a complete set of results. It fires L = 2 clocks after the clock on which the N-th sample of the block was accepted, not when that sample arrives. Every enabled output is updated on this clock and on no other. BUSY is still high here and falls on the next clock.
CLK 1 bit
Clock.
RESET 1 bit
Synchronous reset: clears the counters, the block counter, the sample count and the tail.
DUTY_CYCLE DUTY_CYCLE_BitsInt + DUTY_CYCLE_BitsFract bit
ABOVE_COUNT / N, a fraction in 0.0 .. 1.0, in the DUTY_CYCLE Q format. Free: N is a power of two, so the division is an arithmetic shift - no divider, no extra clocks, no DSP. The default Q1.15 unsigned spans 0.0 .. 1.0 with 1.0 landing exactly on 0x8000. It ALWAYS refers to the ABOVE count; for the below-fraction use BELOW_COUNT and divide it yourself. Enabling it keeps the above-counter alive even when the ABOVE_COUNT pin is off. Present on the symbol only when Enable DUTY_CYCLE = YES.
BUSY 1 bit
High from the start of a block - its first counted sample - until its result is out: it covers the tail as well. Its last high clock is the OUT_DV pulse, and it falls on the clock after. On a continuous stream it simply stays high. Present on the symbol only when Enable BUSY = YES.
INTEGRATING 1 bit
High only while the block is accumulating: it rises on the clock after the first sample of a block is accepted and falls on the clock after the N-th. On a continuous stream it dips for exactly one clock per block boundary, which makes it a free block marker. Present on the symbol only when Enable INTEGRATING = YES.
SAMPLE_COUNT 32 bit

How many samples have been accumulated so far in the current block: 1 after the first, N after the N-th. It is NOT cleared at the end of a block

  • it HOLDS the final count through the tail and past OUT_DV, until the first sample of the next block takes it back to 1, so on the OUT_DV clock it reads the length of the block being presented - the exact denominator the counts should be read against. Only RESET clears it to
  1. Fixed 32 bits. Present on the symbol only when Enable SAMPLE_COUNT = YES.

Properties

Property window

IN Integer Bits IN_BitsInt

Number of INTEGER bits of the input sample (the sign, when present, uses one of them).

Integer bits of the input sample (the sign, when present, uses one of them). 1..64. Default 16. The THRESHOLD pin shares this format.

Default: 16

Options: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

IN Fractional Bits IN_BitsFract

Number of FRACTIONAL bits of the input sample, i.e. the bits to the right of the binary point. Total width = integer + fractional bits, and must not exceed 64.

Fractional bits of the input sample. 0..64. Total input width must be 2..64 bits. Default 0. The THRESHOLD pin shares this format.

Default: 0

Options: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

IN Sign IN_Sign

Select whether the input sample is signed (two’s complement) or unsigned.

SIGNED (two’s complement) or UNSIGNED input. Default SIGNED. An UNSIGNED input costs one extra bit internally, because a sample has to be promoted to signed before the deviation from the threshold can be formed.

Default: SIGNED

Options: UNSIGNED SIGNED

Max Block Exponent MaxBlockExponent

Largest block-size exponent the accumulators are sized for: the block can be up to 2^MaxBlockExponent samples long. The EXP input is clamped to this value at run time. Raising it widens the internal accumulators, and ON THE BLOCKS WHOSE SERIAL ENGINES ARE SIZED FROM THOSE ACCUMULATORS (Coefficient of Variation, SNR, Skewness, Kurtosis, Correlation, Autocorrelation, Linear Regression) it also LENGTHENS THE SERIAL TAIL – even when the runtime EXP is small. Keep it at the largest block you actually use. The default of 20 covers blocks of up to 1048576 samples.

Largest block-size exponent the block counter is sized for: the block can be up to $2^{\text{MaxBlockExponent}}$ samples long, and the EXP input is clamped to this value at run time. It also SIZES THE COUNT OUTPUTS: ABOVE_COUNT and BELOW_COUNT are Max Block Exponent + 1 bits wide. It does NOT lengthen the latency of this block, which is a constant 2 clocks. Keep it at the largest block you actually use. 1..31, default 20, i.e. blocks of up to 1048576 samples out of the box.

Default: 20

Options: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

Enable ABOVE_COUNT EnableAboveCount

YES: the ABOVE_COUNT (how many samples of the block were STRICTLY above THRESHOLD; a sample exactly on the threshold counts in neither direction) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.

YES: the ABOVE_COUNT pin exists - how many samples of the block were STRICTLY above THRESHOLD (a sample exactly on the threshold counts in neither direction). NO: the pin is removed before synthesis, though the counter behind it survives if Enable DUTY_CYCLE is YES. Default YES.

Default: YES

Options: NO YES

Enable BELOW_COUNT EnableBelowCount

YES: the BELOW_COUNT (how many samples of the block were STRICTLY below THRESHOLD; a sample exactly on the threshold counts in neither direction) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.

YES: the BELOW_COUNT pin exists - how many samples of the block were STRICTLY below THRESHOLD. NO: the pin and its counter are removed. Default YES. Remember that BELOW is not N - ABOVE unless you know no sample sits exactly on the threshold.

Default: YES

Options: NO YES

Enable DUTY_CYCLE EnableDutyCycle

YES: the DUTY_CYCLE (ABOVE_COUNT / N, a fraction in 0.0 .. 1.0). It is FREE: N is a power of two so the division is an arithmetic shift – no divider, no extra clocks, no DSP. Enabling it keeps the above-counter alive even when the ABOVE_COUNT pin itself is off pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.

YES: the DUTY_CYCLE pin exists - ABOVE_COUNT / N, a fraction in 0.0 .. 1.0. It is FREE: N is a power of two, so the division is an arithmetic shift - no divider, no extra clocks, no DSP, and the latency is unchanged. Enabling it keeps the above-counter alive even when the ABOVE_COUNT pin itself is off. NO: the pin, the shifter and the requantiser are removed. Default NO.

Default: NO

Options: NO YES

DUTY_CYCLE Integer Bits DUTY_CYCLE_BitsInt

Number of INTEGER bits of the DUTY_CYCLE output (the sign, when present, uses one of them).

Integer bits of the DUTY_CYCLE output. 1..64, default 1: the value is bounded by 1.0, and with Q1.15 unsigned 1.0 lands exactly on 0x8000. More integer bits only cost resolution.

Default: 1

Options: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

DUTY_CYCLE Fractional Bits DUTY_CYCLE_BitsFract

Number of FRACTIONAL bits of the DUTY_CYCLE output, i.e. the bits to the right of the binary point. Total width = integer + fractional bits, and must not exceed 64.

Fractional bits of the DUTY_CYCLE output. 0..64, total width 2..64 bits, default 15. This is where the resolution goes: give it at least as many bits as the largest EXP you drive, or neighbouring counts collapse onto the same output code.

Default: 15

Options: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

DUTY_CYCLE Sign DUTY_CYCLE_Sign

Select whether the DUTY_CYCLE output is signed (two’s complement) or unsigned.

SIGNED or UNSIGNED DUTY_CYCLE output. Default UNSIGNED - a duty cycle is never negative, and UNSIGNED buys one bit.

Default: UNSIGNED

Options: UNSIGNED SIGNED

Enable BUSY EnableBusy

YES: the BUSY (high from the first sample of a block until its result is out – it COVERS THE SERIAL TAIL, and its last high clock IS the OUT_DV pulse) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.

YES: the BUSY pin exists. It is high from the first sample of a block until its result is out, tail included, and its last high clock is the OUT_DV pulse. NO: the pin and its register are removed before synthesis. Default NO.

Default: NO

Options: NO YES

Enable INTEGRATING EnableIntegrating

YES: the INTEGRATING (high only while the block is ACCUMULATING; it drops as soon as the N-th sample has been taken and the tail starts, so BUSY-and-not-INTEGRATING means ‘computing’) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.

YES: the INTEGRATING pin exists. It is high only while the block is accumulating, so BUSY high with INTEGRATING low means “the samples are all in, I am computing”. NO: the pin and its register are removed. Default NO.

Default: NO

Options: NO YES

Enable SAMPLE_COUNT EnableSampleCount

YES: the SAMPLE_COUNT (32 bit, how many samples have been accumulated so far in the current block: 1 after the first, N after the N-th. It is NOT cleared at the block end – it holds N until the NEXT block’s first accepted sample takes it back to 1. On a CONTINUOUS stream that happens DURING the serial tail, so at OUT_DV it reads how far into the next block the input has already got, NOT N. To capture the length of the block being presented, latch SAMPLE_COUNT on the clock INTEGRATING falls – that one always reads N) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.

YES: the SAMPLE_COUNT pin exists - a fixed 32 bit count of the samples accumulated so far in the current block, holding the final count through the tail and past OUT_DV. NO: the pin and its counter are removed. Default NO.

Default: NO

Options: NO YES

Rounding Rounding

ROUND: round to nearest when a result has to be requantised into a coarser output format. TRUNCATE: drop the bits (cheaper, adds a negative bias).

ROUND: round to nearest when the duty cycle has to be requantised into a coarser output format. TRUNCATE: drop the bits (cheaper, adds a negative bias). It only affects DUTY_CYCLE - the counts are integers and are never requantised. Default ROUND.

Default: ROUND

Options: TRUNCATE ROUND

Saturation EnableSaturation

YES: clip to the largest representable value of each output format (symmetric for signed formats). NO: wrap around.

YES: clip the duty cycle to the largest representable value of its format (symmetric bounds for signed formats). NO: wrap around. It only affects DUTY_CYCLE - the counts cannot overflow their Max Block Exponent + 1 bit outputs - and with the default Q1.15 unsigned format it cannot fire either, since the value is bounded by 1.0. Default YES.

Default: YES

Options: NO YES

The on-threshold convention

BOTH COUNTS ARE STRICT. A sample with $d = 0$ - one lying exactly on the threshold - is counted in NEITHER direction. Therefore

$$ \mathrm{ABOVE_COUNT} + \mathrm{BELOW_COUNT} ;\le; N $$

with equality only when no sample sits exactly on the threshold, and a flat signal parked exactly on it reports 0 and 0.

This is the block where that convention is observable. Everywhere else in the family an on-level sample contributes zero to a sum and is invisible; here it changes a count you can read. Three things follow, and they are worth reading twice:

  • Do not assume BELOW_COUNT = N - ABOVE_COUNT. It is only true when no sample sits on the threshold. On quantised data, and especially with the threshold at a round number or on a signal that rests at its baseline, that assumption fails routinely.
  • For “>=” semantics, drive THRESHOLD one LSB lower. That is the whole fix: the strict comparison against $t - 1,\mathrm{LSB}$ is the non-strict comparison against $t$.
  • The two counts and the duty cycle are all consistent with each other, because they all come from the same strict comparisons of the same latched threshold.

DUTY_CYCLE is free

N is a power of two, so $\mathrm{ABOVE_COUNT}/N$ is an arithmetic SHIFT: the count is shifted by $\mathrm{EXP}$ places and requantised into the DUTY_CYCLE Q format in the same final state that presents the counts. No divider, no extra clocks, no DSP - the tail is the same 2 clocks with the duty cycle on as without it.

The default format is Q1.15 unsigned, which spans 0.0 .. 1.0 with 1.0 landing exactly on 0x8000 - an all-above block reads as exactly one half of full scale, which is the natural reading of an unsigned Q1.15 number.

The duty cycle ALWAYS refers to the ABOVE count. There is no below-fraction output. If you want one, take BELOW_COUNT and divide it yourself; subtracting the duty cycle from 1 is only correct if you know that no sample sits exactly on the threshold - see the convention above.

DUTY_CYCLE is derived from the above-counter, so enabling it keeps that counter alive even when the ABOVE_COUNT pin itself is off. That configuration - duty cycle only - is a normal one and costs exactly one counter, one shifter and one requantiser.

Accuracy

ABOVE_COUNT and BELOW_COUNT are bit exact: they are plain counters and the host regression demands tolerance ZERO against a Python golden that evaluates the definitions in exact integer arithmetic.

DUTY_CYCLE is exact too, up to the single final requantisation into the Q format you chose for it: the value $\mathrm{ABOVE_COUNT} \cdot 2^{-\mathrm{EXP}}$ is produced by a shift of an exact integer, with the selected rounding applied once. Give it enough fractional bits to resolve $1/N$ - with fewer fractional bits than EXP, neighbouring counts collapse onto the same output code.

Accumulation and IN_DV

IN_DV is the only qualifier. It says “this clock carries a sample”: a sample is compared, counted, and counts towards N, exactly on the clocks where IN_DV is high. Clocks with IN_DV low are ignored completely - whatever sits on IN during them cannot corrupt the counts - while the tail keeps running, which is what you want: the tail has nothing to do with the input stream.

Unconnected, IN_DV ties to '1', EXP ties to 10 (N = 1024) and THRESHOLD ties to all zeros, so the block free-runs with nothing wired except IN and reports the occupancy above and below zero.

There is deliberately no CE pin. On the all-in-one Block Statistics block an earlier revision had one, and it did not survive synthesis: with nothing but internal state gated by it, Vitis could reason the frozen path away and delete the port from the generated entity while SciCompiler’s wrapper still wired it, which failed a real Vivado build with [VRFC 10-718] formal port <ce> does not exist in entity. The whole per-operator family was built without one. To stall this block, gate its IN_DV - a block that only counts on IN_DV has no need to be frozen.

The THRESHOLD pin

THRESHOLD is an input pin in the input Q format, not a property - it is compared against the samples, so a separate format would only be a way to get it wrong. Like EXP, it is latched on the first accepted sample of a block and held for that whole block, so a mid-block change takes effect on the NEXT block and a count is never a mixture of two thresholds. That is what makes a threshold sweep meaningful: every published count belongs to exactly one threshold value, and SAMPLE_COUNT tells you the N it was taken over.

When EXP changes

EXP is clamped to Max Block Exponent and then latched on the first accepted sample of a block, and held for that whole block. A change therefore takes effect on the NEXT block: a block in progress always finishes against the N it was started with, and a block is never emitted against a different N than the one it was counted over - which matters here more than elsewhere, because the duty cycle divides by exactly that N.

Timing: the latency contract

OUT_DV pulses for one clock, L clocks after the clock on which the N-th sample of the block was accepted - not when that sample arrives. Every enabled output is updated on that same clock and on no other. For this block

$$ L = 2 $$

and it is a constant: there is no serial arithmetic here at all, so L does not depend on the input width, on the duty cycle format, on which outputs are enabled, on Max Block Exponent or on EXP. The two clocks are one to enter the final state and one to present the registered results.

The rule that governs the whole family is that the tail of one block must finish before the next block completes, i.e.

$$ 2^{\mathrm{EXP}} \ge L $$

If a block completes while the previous tail is still running, that block’s result is DROPPED: no OUT_DV for it, the counters are unaffected and later blocks come out correctly, but a result is silently skipped. There is no error pin for it.

With $L = 2$ that condition is $2^{\mathrm{EXP}} \ge 2$, i.e. EXP $\ge$ 1, so it cannot bite here: the only value that violates it is EXP = 0, a block of a single sample. The blocks where this rule really matters are the ones with a serial tail - Block RMS, Block Variance, Block Std Dev and Block Crest Factor, whose L runs to tens of clocks and whose minimum usable exponent the compiler prints in the compilation log.

Knowing where the block is: BUSY, INTEGRATING and SAMPLE_COUNT

Three optional status outputs, all defaulting to NO. They answer different questions:

INTEGRATING BUSY
counting the block 1 1
tail computing 0 1
idle 0 0

Every output of this block is a register, so each status bit is observed on the clock after the event that sets it:

  • INTEGRATING rises on the clock after the FIRST sample of a block is accepted and falls on the clock after the N-th - it is high exactly while the block is ACCUMULATING.
  • BUSY covers the accumulation and the tail. It rises with INTEGRATING, stays high across the tail, and its LAST HIGH CLOCK IS THE OUT_DV PULSE; it falls on the clock after.
  • On a continuous stream the next block starts before the previous tail ends, so BUSY never drops and INTEGRATING dips for exactly one clock per block boundary - which makes it a free block marker.
  • SAMPLE_COUNT is a fixed 32 bits and reads 1 after the first accepted sample, N after the N-th. It is NOT cleared at the block end: it HOLDS N through the tail and past OUT_DV, until the first sample of the next block takes it back to 1. So on the OUT_DV clock it reads the length of the block being presented - the exact denominator the counts should be read against, and the useful thing to latch alongside them. Only RESET clears it to 0.

Q formats

IN and THRESHOLD share one fixed point format (integer bits, fractional bits, sign), the same convention as the Fixed P. family.

The counts have no Q format. They are integers, and their width is not a user choice: it is Max Block Exponent + 1 bits, which cannot overflow - neither count can exceed N. Nothing about them is ever requantised, so the Rounding and Saturation properties do not touch them.

DUTY_CYCLE has its own format and is the only output that is requantised, so it is the only place where Rounding (nearest / truncate) and Saturation (clip / wrap) apply. The default Q1.15 unsigned is the right choice for a fraction of 1: it cannot overflow, because the value is bounded by 1.0 and 1.0 is exactly representable.

Enabling and disabling outputs

Each of the three outputs has its own enable. Turning one off removes the pin and all of its logic before synthesis (the enables are preprocessor switches on the HLS core, so nothing reaches the synthesiser at all). One internal dependency is resolved by the block, not by you: DUTY_CYCLE needs the above-counter, so enabling it builds that counter even when the ABOVE_COUNT pin is off. At least one output must be enabled - the property page refuses a configuration with all three off, because the block would have nothing to say.

Verification

The core is regression tested by a host-side csim harness (tb/block-ops/run_tb.ps1) that runs one simulated clock at a time and follows OUT_DV. The expected values come from tb/block-ops/gen_golden.py, which evaluates the definitions above in exact arithmetic and shares no algorithm with the core; the tolerance is 0. Coverage includes pseudo-random input, a pulse train against a non-zero threshold, a signal with samples sitting exactly on the threshold (which pins the strict convention, and with it the fact that the two counts need not add up to N), a THRESHOLD pin that changes between blocks, a duty-cycle-only configuration with the ABOVE_COUNT pin disabled, an all-outputs configuration, and an EXP that changes half way through a block. The status outputs are checked clock by clock against the contract above. A cross-check compiles this core and the all-in-one block_stats.cpp into the same binary, drives them with identical stimulus, and compares the two clock by clock.