DSP - BLOCK PEAK COUNT
How many PULSES crossed a level during a block of N consecutive samples, counted by a SCHMITT TRIGGER rather than by a bare comparator. Two runtime pins set the two levels - upper = THRESHOLD, lower = THRESHOLD - |HYSTERESIS| - and one peak is counted per EXCURSION, on its RISING edge, after which the signal must fall back through the LOWER level before another can be counted. That is the whole point: a noisy pulse edge that rattles around the threshold on its way up is ONE count here and a dozen with a plain comparator. N is a power of two chosen at RUN TIME on the EXP pin (EXP = 10 means N = 1024), so PEAK_RATE = peaks / N is an exact arithmetic SHIFT and comes for free. Both level pins are latched per block, so a count is never a mixture of two thresholds, and the detector state CARRIES ACROSS block boundaries so a pulse that straddles one is counted once, not twice. Comparisons are STRICT. PEAK_COUNT is an integer MaxBlockExponent + 1 bits wide, which cannot overflow. The result of a block is presented exactly 2 clocks after that block’s last sample. IN_DV is the only qualifier and there is deliberately no CE pin. Optional BUSY / INTEGRATING / SAMPLE_COUNT status outputs. Blocks of up to 2^20 samples out of the box, 2^31 if you ask for it.
Introduction
The Block Peak Count block chops the input stream into consecutive blocks of N samples and, at the end of each block, publishes how many pulses crossed a level during that block.
It is a Schmitt trigger feeding a counter. Two runtime input pins define two levels:
$$ \mathrm{upper} = \mathrm{THRESHOLD}, \qquad \mathrm{lower} = \mathrm{THRESHOLD} - |\mathrm{HYSTERESIS}| $$
and the detector has exactly two states:
| state | condition | action |
|---|---|---|
| ARMED | $x >$ upper | go HIGH, and COUNT ONE PEAK |
| HIGH | $x <$ lower | go ARMED |
So one peak is counted per EXCURSION, on its RISING edge, and the signal has to come back down through the lower level before another one can be counted.
Why a Schmitt trigger and not a comparator
Because a real pulse edge is noisy. A plain threshold crossing counter
re-arms the moment the signal dips one LSB below the level, so every wobble of
the noise on the rising edge produces another count. Take
THRESHOLD = 100, and a pulse that goes
0, 60, 101, 95, 99, 102, 75, 110
- with
HYSTERESIS= 0 (a plain comparator) the detector re-arms at 95 and again at 75, and reports 3 counts - for two physical pulses; - with
HYSTERESIS= 20 (lower level 80) the 95 and the 99 are ignored because they never went below 80; the detector only re-arms at 75, and it reports 2 counts, which is the right answer.
How to choose it. Set HYSTERESIS to a few times the RMS noise of the
baseline. Three to five sigma is the usual choice: below one sigma the
hysteresis buys you nothing, above the pulse amplitude nothing is counted at
all. If you do not know the noise, measure it: Block RMS or Block Std
Dev on the baseline tells you what sigma is, and the two blocks can run side
by side on the same stream. So for a baseline with sigma = 3 LSB and a
discriminator at 100, HYSTERESIS = 12 and a lower level of 88 is a sound
starting point, and the count then stops depending on the noise.
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, PEAK_RATE = peaks / N is an exact arithmetic shift. There is no divider, no reciprocal ROM and no rounding beyond the single final requantisation 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
A discriminator with hysteresis and a counter is the standard way to measure, without leaving the fabric:
- a counting rate - how many detector pulses, particles, photons or edges
arrived in a fixed time window. With a fixed EXP the block period is a fixed
time, so
PEAK_COUNTis directly a rate; - a normalised rate -
PEAK_RATEis peaks per sample, independent of N, so it stays comparable when you change the block size; - a dead-time free trigger rate monitor running alongside the real trigger path;
- a threshold scan -
THRESHOLDis 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 counting rate; - a baseline-referred discriminator - drive
THRESHOLDfrom a slow baseline tracker and the level follows the baseline automatically.
Cost
One subtractor for the lower level, one absolute value, two comparators, a
one bit detector state, one counter of Max Block Exponent + 1 bits, a
hold register - and, if PEAK_RATE is on, one barrel shifter and one
requantiser. No multiplier, no divider, no square root and no serial
arithmetic: everything is evaluated inside the accumulation window, so the
tail is a constant 2 clocks and is the shortest in the family.
When to use this instead of Block Statistics
The all-in-one Block Statistics block is not deprecated, but note first: it has no peak counter at all. It counts ZERO CROSSINGS and samples above / below a threshold, neither of which is an excursion count with hysteresis. So the usual family rule applies with one extra clause:
- you want several statistics of the SAME block - the mean and the RMS and the min/max 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 one number - use this block. Then you synthesise only that number: the pin list, the logic and the tail are all that the count needs, and nothing else reaches the synthesiser.
- you want this number - use this block, because there is nowhere else to get it. Pair it with a Block Statistics block on the same stream if you also want the moments.
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_DV is high.
'1'. (There is deliberately no CE
pin - to stall the block, gate this.)
PEAK_RATE.
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 down to the lower level:
lower = THRESHOLD - |HYSTERESIS|. Used as a MAGNITUDE, so driving it
negative cannot lift the lower level above the upper one and turn the
detector into a latch. Set it to a few times the RMS noise of the baseline
(Block RMS or Block Std Dev will tell you what that is); 0 degenerates to a
plain threshold crossing counter; a value larger than the pulse amplitude
counts nothing at all. Same Q format as IN, latched per block like
THRESHOLD. Unconnected defaults to all zeros.
OUT_DV clock
and on no other. Present on the symbol only when Enable PEAK_COUNT =
YES. (Inside the HLS core the level pin keeps the neutral family name
lvl that the shared wiring knows, while the symbol calls it THRESHOLD -
same signal, two names, on purpose.)
PEAK_COUNT / N, in the PEAK_RATE 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 has 1.0 on 0x8000; the value cannot in
practice exceed 0.5, because a peak needs one sample above the upper level
and one below the lower one. Enabling it keeps the counter alive even when
the PEAK_COUNT pin is off. Present on the symbol only when Enable
PEAK_RATE = YES.
BUSY is still high here and falls on the next clock.
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.
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, it reads N atOUT_DVonly when the input STOPS for the whole tail. On a CONTINUOUS stream the next block has already started by then, soOUT_DVshows how far into it the input has got, not N - the clock that always reads N is the oneINTEGRATINGfalls on - the exact denominator the count should be read against. OnlyRESETclears it to 0. Fixed 32 bits. Present on the symbol only when Enable SAMPLE_COUNT = YES.
Properties
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 and HYSTERESIS pins share 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
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 and HYSTERESIS pins share 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
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 it can be compared against a level that may sit below zero.Default: SIGNED
Options: UNSIGNED SIGNED
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 theEXP input is
clamped to this value at run time. It also SIZES THE COUNT OUTPUT:
PEAK_COUNT is 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
YES: the the PEAK_COUNT output pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.
YES: thePEAK_COUNT pin exists - how many upward excursions through the
upper level occurred in the block. NO: the pin is removed before
synthesis, though the counter behind it survives if Enable PEAK_RATE is
YES. Default YES.
Default: YES
Options: NO YES
YES: the the PEAK_RATE output pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.
YES: thePEAK_RATE pin exists - PEAK_COUNT / N. 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
counter alive even when the PEAK_COUNT pin itself is off. NO: the pin,
the shifter and the requantiser are removed. Default YES.
Default: YES
Options: NO YES
Number of INTEGER bits of the PEAK_RATE output (the sign, when present, uses one of them).
Integer bits of the PEAK_RATE output. 1..64, default 1: the value cannot exceed 0.5 in practice, 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
Number of FRACTIONAL bits of the PEAK_RATE 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 PEAK_RATE 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 largestEXP 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
Select whether the PEAK_RATE output is signed (two’s complement) or unsigned.
SIGNED or UNSIGNED PEAK_RATE output. Default UNSIGNED - a rate is never negative, and UNSIGNED buys one bit.Default: UNSIGNED
Options: UNSIGNED SIGNED
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: theBUSY 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
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: theINTEGRATING 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
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: theSAMPLE_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
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 rate has to be requantised into a coarser output format. TRUNCATE: drop the bits (cheaper, adds a negative bias). It only affectsPEAK_RATE - the count is an integer and is never
requantised. Default ROUND.
Default: ROUND
Options: TRUNCATE ROUND
YES: clip to the largest representable value of each output format (symmetric for signed formats). NO: wrap around.
YES: clip the rate to the largest representable value of its format (symmetric bounds for signed formats). NO: wrap around. It only affectsPEAK_RATE - the count cannot overflow its Max Block Exponent + 1 bit
output - and with the default Q1.15 unsigned format it cannot fire either,
since the value is bounded by 0.5. Default YES.
Default: YES
Options: NO YES
The two levels, and why HYSTERESIS is a magnitude
HYSTERESIS is an input pin in the input Q format, which may be signed -
and the core takes its absolute value:
$$ \mathrm{lower} = \mathrm{THRESHOLD} - |\mathrm{HYSTERESIS}| $$
This is deliberate. If a negative drive were taken at face value the lower level would end up ABOVE the upper one, the two comparisons would overlap and the detector would degenerate into a latch that arms once and never re-arms - a silent, permanent zero count. Taking the magnitude makes that impossible: the lower level is never above the upper one, whatever is on the pin.
Two consequences worth stating plainly:
HYSTERESIS= 0 degenerates to a plain threshold crossing counter. The two levels coincide and every crossing ofTHRESHOLDstarts a new excursion. That is a legitimate configuration - it is what you want on a clean, already-shaped digital signal - and it is what you get when the pin is left unconnected.- A hysteresis wider than the pulse amplitude counts nothing. The detector arms on the first pulse and never sees a sample below the lower level again. If the count is stuck at 0 or 1, that is the first thing to check.
Internally the comparison runs two bits wider than a promoted sample,
because THRESHOLD - |HYSTERESIS| can reach one bit below the input range in
each direction. Nothing about the levels can overflow.
The on-level convention
The comparisons are STRICT, the same rule the rest of this family uses:
- a sample lying exactly on the upper level does not arm the detector;
- a sample lying exactly on the lower level does not re-arm it.
So a signal that sits exactly on a level reports nothing at all, and a
flat signal parked exactly on THRESHOLD gives a count of 0. If you want
“>=” semantics on the upper level, drive THRESHOLD one LSB lower: the
strict comparison against $t - 1,\mathrm{LSB}$ is the non-strict comparison
against $t$.
The detector state CARRIES ACROSS block boundaries
The Schmitt state - ARMED or HIGH - is not reset at a block boundary.
Only RESET clears it.
What that buys you. The stream is continuous, and so is the pulse that happens to be crossing the boundary when the block ends. Carrying the state means such a pulse is counted once, in the block where its rising edge landed. The alternative - re-arming at every boundary - would count one extra peak for every straddling pulse, which on a busy stream is a systematic over-count proportional to the pulse rate. This is exactly the convention Block Zero Crossings uses for its sign memory.
What it costs you. The state and the levels are latched independently. If
THRESHOLD or HYSTERESIS changes between blocks, the first comparison of
the new block is made against the NEW levels while the state came from the
OLD ones. On a threshold sweep that can misplace at most one count, at one
block boundary, per step of the sweep. If that matters, step the threshold
only while the input is quiet, or discard the first block after each step.
After RESET the detector starts ARMED, so the first excursion above the
upper level is counted.
The THRESHOLD and HYSTERESIS pins
Both are input pins in the input Q format, not properties - they are
compared against the samples, so a separate format would only be a way to get
it wrong. Like EXP, both are 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 scan meaningful: every published count belongs to
exactly one pair of levels, and SAMPLE_COUNT tells you the N it was taken
over.
Unconnected, THRESHOLD and HYSTERESIS both tie to all zeros, which
turns the block into a plain counter of upward crossings of zero.
PEAK_RATE is free
N is a power of two, so
$$ \mathrm{PEAK_RATE} = \frac{\mathrm{PEAK_COUNT}}{N} $$
is an arithmetic SHIFT: the count is shifted by EXP places and
requantised into the PEAK_RATE Q format in the same final state that
presents the count. No divider, no extra clocks, no DSP - the tail is the
same 2 clocks with the rate on as without it.
The default format is Q1.15 unsigned, where 1.0 lands exactly on 0x8000.
In practice the rate cannot exceed 0.5: a peak needs at least one sample
above the upper level and one below the lower one, so the densest possible
pattern is one count every two samples. The integer bit is there for the
format’s sake, not because the value uses it. Give PEAK_RATE at least as
many fractional bits as the largest EXP you drive, or neighbouring counts
collapse onto the same output code.
PEAK_RATE is derived from the counter, so enabling it keeps that counter
alive even when the PEAK_COUNT pin itself is off. That configuration -
rate only - is a normal one and costs exactly one counter, one shifter and one
requantiser.
Accuracy
PEAK_COUNT is bit exact: it is a plain counter, its width is Max Block
Exponent + 1 bits and it cannot overflow - the count can never exceed N.
Nothing about it is ever requantised, so the Rounding and Saturation
properties do not touch it.
PEAK_RATE is exact too, up to the single final requantisation into the Q
format you chose for it: the value $\mathrm{PEAK_COUNT}\cdot 2^{-\mathrm{EXP}}$
is produced by a shift of an exact integer, with the selected rounding applied
once.
That is not an aspiration. The host regression (tb/block-ops/run_tb.ps1)
demands tolerance ZERO against a Python golden (tb/block-ops/ gen_golden.py) that evaluates the state machine above in exact integer
arithmetic - not “within 1 LSB”. Any deviation at all fails the build.
Accumulation and IN_DV
IN_DV is the only qualifier. It says “this clock carries a sample”: a
sample is compared, can move the detector, 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 move the Schmitt state
or the counter - 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 both level
pins tie to all zeros, so the block free-runs with nothing wired except
IN.
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 onIN_DVhas no need to be frozen.
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
because the rate divides by exactly that N. You can sweep EXP from a
register interface while the stream runs and every result stays
self-consistent.
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: everything is a comparison and a counter evaluated
inside the accumulation window, so L does not depend on the input width, on
the rate 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. Worked example: with EXP = 10 the N-th sample of a
1024-sample block is accepted on clock $t$; OUT_DV pulses on clock $t + 2$,
and PEAK_COUNT and PEAK_RATE change on that clock and on no other.
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 counter and the detector state
are unaffected and later blocks come out correctly, but a result is silently
skipped. There is no overrun pin for it. The property page also refuses
outright any configuration whose minimum usable exponent would exceed Max
Block Exponent, and CompileHDL prints the tail length and the minimum
usable EXP into the compilation log for every placement.
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, Block Slope Stats and Block Crest Factor - and Block Mean Abs Deviation, whose tail is longer than its own block by construction.
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:
INTEGRATINGrises 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.BUSYcovers the accumulation and the tail. It rises withINTEGRATING, stays high across the tail, and its LAST HIGH CLOCK IS THEOUT_DVPULSE; it falls on the clock after. SoBUSYhigh withINTEGRATINGlow means “the samples are all in, I am computing”.- On a continuous stream the next block starts before the previous tail
ends, so
BUSYnever drops andINTEGRATINGdips for exactly one clock per block boundary - which makes it a free block marker. SAMPLE_COUNTis 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 pastOUT_DV, until the first sample of the next block takes it back to 1. so it reads N atOUT_DVonly when the input STOPS for the whole tail; on a CONTINUOUS stream the next block has already started andOUT_DVshows how far into it you are. The clock that always reads N is the oneINTEGRATINGfalls on - the exact denominator the count should be read against, and the useful thing to latch alongside it. OnlyRESETclears it to 0.
Q formats
IN, THRESHOLD and HYSTERESIS share one fixed point format (integer bits,
fractional bits, sign), the same convention as the Fixed P. family.
PEAK_COUNT has no Q format. It is an integer, and its width is not a
user choice: it is Max Block Exponent + 1 bits, which cannot overflow.
PEAK_RATE 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 0.5.
Enabling and disabling outputs
Each of the two data outputs has its own enable, both defaulting to YES.
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: PEAK_RATE needs the counter, so enabling it builds that counter even
when the PEAK_COUNT pin is off. At least one output must be enabled -
the property page refuses a configuration with both 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 detector above in exact arithmetic and shares no algorithm
with the core; the tolerance is 0. Peak-count coverage includes a clean
pulse train with no hysteresis (PK_PULSES), the same train with hysteresis
(PK_PULSES_HYS), pseudo-random input (PK_RAND), a negative drive on
the HYSTERESIS pin to pin the magnitude rule (PK_NEGHYS), a THRESHOLD
that changes between blocks (PK_THRCHG), a HYSTERESIS that changes between
blocks (PK_HYSCHG), samples sitting exactly on the upper and lower
levels (PK_ONLEVEL, which pins the strict convention), a count-only and a
rate-only configuration (PK_ONLY_COUNT, PK_ONLY_RATE), and an EXP that
changes half way through a block (PK_EXPCHG). The status outputs are checked
clock by clock against the contract above.