DSP - BLOCK MIN MAX
The extremes of a block of N consecutive samples: MIN, MAX, PEAK_TO_PEAK, and the positions ARGMIN / ARGMAX of the first minimum and the first maximum inside the block. N is a power of two chosen at RUN TIME on the EXP input pin (EXP = 10 means N = 1024), so the block length can be changed while the design is running, for free. This is the cheapest non-trivial block of the family: two comparators, two registers, and two more registers only if an index pin is on - no multiplier, no divider, no square root and no serial tail arithmetic at all, so the result of a block is presented exactly 2 clocks after that block’s last sample. MIN and MAX are returned in the INPUT Q format and have no format property of their own; only PEAK_TO_PEAK has one. Each output group can be switched off, which removes its pin and its logic before synthesis. 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 Min Max block chops the input stream into consecutive blocks of N samples and, at the end of each block, publishes the extremes of that block:
$$ \mathrm{MIN} = \min_{0 \le i < N} x_i , \qquad \mathrm{MAX} = \max_{0 \le i < N} x_i $$
$$ \mathrm{PEAK_TO_PEAK} = \mathrm{MAX} - \mathrm{MIN} $$
and, optionally, where in the block they happened:
$$ \mathrm{ARGMIN} = \min {, i : x_i = \mathrm{MIN} ,} , \qquad \mathrm{ARGMAX} = \min {, i : x_i = \mathrm{MAX} ,} $$
That is the whole block. Two comparators, two registers, and one subtraction at the end if you ask for the span.
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 |
N being a power of two is what makes every division in this family an exact arithmetic shift. This particular block divides by nothing at all, so it simply inherits the free runtime block length: the block size can be changed while the design is running, for free.
What each output is FOR
- MIN and MAX - amplitude and headroom at a glance. They are the classic clipping / saturation check: a MAX pinned at full scale, block after block, is an input that is being driven too hard.
- PEAK_TO_PEAK - the span of the block in one number, which is the useful form for a level meter, a noise-band estimate or a “is anything happening” test. It costs one subtraction and no extra clock.
- ARGMIN / ARGMAX - where in time the extremum happened. This is what locates a pulse inside the block: the index of the maximum is a coarse time stamp, and the distance between ARGMIN and ARGMAX measures a pulse’s rise or fall span.
Cost
Two comparators and two registers for the running minimum and maximum, plus
two more registers only if ARGMIN or ARGMAX is enabled, plus one
subtraction and one requantiser if PEAK_TO_PEAK is enabled. No multiplier,
no divider, no square root, no serial tail arithmetic. It is the cheapest
non-trivial block of the family.
When to use this instead of Block Statistics
The all-in-one Block Statistics block is not deprecated and computes these same five numbers among twenty other statistics. The rule is simple:
- you want several statistics of the SAME block - min/max and mean and 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 extremes need, and nothing else reaches the synthesiser.
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.)
OUT_DV clock and on no other; it holds the previous block’s result
until then. Present on the symbol only when Enable MIN / MAX = YES.
MIN. Present on the symbol only when Enable MIN / MAX = YES - the
two share one enable, because they share one pair of comparators.
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, so on theOUT_DVclock it reads the length of the block being presented. 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.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.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 the PEAK_TO_PEAK subtraction (reading the raw bits as signed would turn an unsigned 200 into -56).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 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 sets the width of the ARGMIN and
ARGMAX pins, $\max(\textit{MaxBlockExponent}, 2)$ bits; 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 MIN and MAX (the smallest and the largest sample of the block, both returned in the INPUT Q format because they ARE samples) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.
YES: the MIN and MAX pins exist. NO: they are removed before synthesis
- the comparators stay, because every other output of this block needs them. The two share one enable because they share one pair of comparators. MIN and MAX are returned in the INPUT Q format, so there is no format property for them. Default YES.
Default: YES
Options: NO YES
YES: the PEAK_TO_PEAK (max - min, the span of the block; it costs one subtraction and no extra clock) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.
YES: thePEAK_TO_PEAK pin exists, along with one subtraction and one
requantiser in the final state. NO: both are removed. It costs no extra
clock either way. Default NO.
Default: NO
Options: NO YES
Number of INTEGER bits of the PEAK_TO_PEAK output (the sign, when present, uses one of them).
Integer bits of the PEAK_TO_PEAK output. 1..64, default 17. MAX - MIN can reach the full input span, so allow one bit more than the input if you want it never to clip.Default: 17
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_TO_PEAK 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_TO_PEAK output. 0..64, total width 2..64 bits, default 0. Match the input fractional bits to keep the value in the input units.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 PEAK_TO_PEAK output is signed (two’s complement) or unsigned.
SIGNED or UNSIGNED PEAK_TO_PEAK output. Default UNSIGNED: MAX - MIN is never negative, so UNSIGNED buys one bit.Default: UNSIGNED
Options: UNSIGNED SIGNED
YES: the ARGMIN (the index within the block of the FIRST minimum; ties keep the earliest position) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.
YES: theARGMIN pin exists, along with one index register. NO: both are
removed. The width follows Max Block Exponent and there is no Q format.
Ties keep the EARLIEST position. Default NO.
Default: NO
Options: NO YES
YES: the ARGMAX (the index within the block of the FIRST maximum; ties keep the earliest position) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.
YES: theARGMAX pin exists, along with one index register. NO: both are
removed. Ties keep the EARLIEST position. Default NO.
Default: NO
Options: NO YES
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 a result has to be requantised into a coarser output format. TRUNCATE: drop the bits (cheaper, adds a negative bias). On this block it only ever affectsPEAK_TO_PEAK - nothing else here is
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 to the largest representable value of the output format (symmetric bounds for signed formats). NO: wrap around. Like Rounding, on this block it only ever affectsPEAK_TO_PEAK. Default YES.
Default: YES
Options: NO YES
Accuracy
MIN and MAX are actual samples of the block, copied out unchanged, so
there is no error in them at all - not even a requantisation, because they are
returned in the input format. ARGMIN and ARGMAX are plain indices.
PEAK_TO_PEAK is one exact integer subtraction, and its only error is the
single requantisation into the Q format you chose for it.
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 definitions above in exact rational
arithmetic - not “within 1 LSB”, not “within a few counts”. Any deviation at
all fails the build.
MIN and MAX have no Q format, and why
MIN and MAX are returned in the input Q format and have no format
property of their own. They are samples: no other format makes sense for
them, and there is nothing to requantise. Only PEAK_TO_PEAK gets its own
format, because $\mathrm{MAX} - \mathrm{MIN}$ needs one bit more than a
sample and can be rescaled or clipped like any other derived quantity -
which is why its default format is 17 bits next to a 16 bit input default, and
UNSIGNED, since the span is never negative.
A direct consequence: on this block the Rounding and Saturation properties
only ever affect PEAK_TO_PEAK. Nothing else here is requantised.
Ties: ARGMIN and ARGMAX report the FIRST occurrence
If the minimum (or the maximum) occurs more than once in a block, the reported
index is the first one. The comparisons in the core are STRICT (<
and >, never <= / >=), so a later equal sample never moves the index. A
block of identical samples therefore reports index 0 for both.
The index pins are $\max(\textit{Max Block Exponent},\ 2)$ bits wide, which
covers every position of the longest block the block is sized for. If the
runtime EXP is smaller than Max Block Exponent the unused high bits simply
read 0 - the width follows the property, not the runtime exponent.
Accumulation and IN_DV
IN_DV is the only qualifier. It says “this clock carries a sample”: a
sample is compared against the running extremes, 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 block, and in
particular cannot become a spurious MIN or MAX - 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' and EXP ties to 10 (N = 1024), 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 accepts samples 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 accumulated with. You can sweep
EXP from a register interface while the stream runs and every result stays
self-consistent - including the indices, which are always indices into the
block that was actually measured.
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 output width, on EXP, or on which
outputs you enabled. The two clocks are one to enter the final state and one
to present the registered result.
All the outputs are written in that same final state, so PEAK_TO_PEAK
costs one subtraction, not a clock: enabling it - or the two index pins -
cannot lengthen L.
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 comparators 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 |
|
|---|---|---|
| accumulating 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.- 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 on theOUT_DVclock it reads the length of the block being presented - which is the useful thing to latch alongside the results. OnlyRESETclears it to 0.
Q formats
IN and PEAK_TO_PEAK carry their own fixed point format (integer bits,
fractional bits, sign), the same convention as the Fixed P. family.
PEAK_TO_PEAK is requantised into its format with the selected rounding
(nearest / truncate) and overflow policy (saturate / wrap); saturation is
symmetric for signed formats, as everywhere else in the toolchain.
Sizing is easy here:
MINandMAXare samples and come out in the input format - there is nothing to size.PEAK_TO_PEAKcan reach the full input span, so give it one integer bit more than the input if you want it never to clip. The default 17-bit unsigned format is exactly that for the default 16-bit signed input.ARGMINandARGMAXare unsigned indices with no Q format; their width follows Max Block Exponent.
Enabling and disabling outputs
Each output group has its own enable, and MIN and MAX share one - they come
from the same pair of comparators, so there is nothing to save by taking only
one of them. Turning a group 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) and it also removes the port from the
generated entity, which is why the enables take part in the IP’s unique name.
There are no hidden internal dependencies to reason about: the min/max registers exist in every configuration, because every output of this block is derived from them. At least one output must be enabled - the property window refuses a configuration in which MIN/MAX, PEAK_TO_PEAK, ARGMIN and ARGMAX are all off, since that would leave an entity with no data ports at all.
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 rational arithmetic and shares
no algorithm with the core; the tolerance is 0. Min/max coverage includes
pseudo-random input, a constant block (every sample a tie, so both indices
must read 0), a ramp (whose minimum and maximum are the first and the last
sample), an extremes stimulus into a PEAK_TO_PEAK-only configuration,
unsigned input, an EXP that changes half way through a block, and the enable
subsets MIN/MAX only, ARGMAX only and PEAK_TO_PEAK only - which is also what
proves the pin list and the entity port list agree for those combinations.
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.