Xilinx
HLS
Block Preview

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_COUNT is directly a rate;
  • a normalised rate - PEAK_RATE is 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 - 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 counting rate;
  • a baseline-referred discriminator - drive THRESHOLD from 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 Input IN_BitsInt + IN_BitsFract bit BIT VECTOR
Input samples, fixed point in the IN Q format. Compared against the two levels, 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, can move the Schmitt state, 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 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 Input IN_BitsInt + IN_BitsFract bit BIT VECTOR
The upper level of the Schmitt trigger, in the input Q format. The detector arms - and counts one peak - when a sample goes strictly above it. A sample exactly on it does nothing (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 count upward crossings of zero.
HYSTERESIS Input IN_BitsInt + IN_BitsFract bit BIT VECTOR
The distance from 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.
PEAK_COUNT Output MaxBlockExponent + 1 bit BIT VECTOR
How many peaks - upward excursions through the upper level - occurred in the block. 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 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_RATE Output PEAK_RATE_BitsInt + PEAK_RATE_BitsFract bit BIT VECTOR
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.
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 peak counter, the Schmitt state (to ARMED), the latched levels, the block counter, the sample count and the tail.
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, it reads N at OUT_DV only when the input STOPS for the whole tail. On a CONTINUOUS stream the next block has already started by then, so OUT_DV shows how far into it the input has got, not N - the clock that always reads N is the one INTEGRATING falls on - the exact denominator the count should be read against. Only RESET clears it to 0. 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 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

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 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

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 it can be compared against a level that may sit below zero.

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 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

Enable PEAK_COUNT EnablePeakCount

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: the PEAK_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

Enable PEAK_RATE EnablePeakRate

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: the PEAK_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

PEAK_RATE Integer Bits PEAK_RATE_BitsInt

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

PEAK_RATE Fractional Bits PEAK_RATE_BitsFract

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 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

PEAK_RATE Sign PEAK_RATE_Sign

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

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 rate has to be requantised into a coarser output format. TRUNCATE: drop the bits (cheaper, adds a negative bias). It only affects PEAK_RATE - the count is an integer and is 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 rate to the largest representable value of its format (symmetric bounds for signed formats). NO: wrap around. It only affects PEAK_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 of THRESHOLD starts 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 on IN_DV has 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:

  • 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. So BUSY high with INTEGRATING low means “the samples are all in, I am computing”.
  • 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 it reads N at OUT_DV only when the input STOPS for the whole tail; on a CONTINUOUS stream the next block has already started and OUT_DV shows how far into it you are. The clock that always reads N is the one INTEGRATING falls on - the exact denominator the count should be read against, and the useful thing to latch alongside it. Only RESET clears 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.