Xilinx
HLS
Block Preview

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 Input IN_BitsInt + IN_BitsFract bit BIT VECTOR
Input samples, fixed point in the IN Q format. Compared against the running minimum and maximum 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 taken into the block, 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. 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).
MIN Output IN_BitsInt + IN_BitsFract bit BIT VECTOR
Smallest sample of the block, in the input Q format - it IS a sample, so there is no separate format for it and nothing is requantised. Updated on the 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.
MAX Output IN_BitsInt + IN_BitsFract bit BIT VECTOR
Largest sample of the block, in the input Q format, on the same terms as MIN. Present on the symbol only when Enable MIN / MAX = YES - the two share one enable, because they share one pair of comparators.
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 min/max registers, the index registers, the block counter, the sample count and the tail.
PEAK_TO_PEAK PEAK_TO_PEAK_BitsInt + PEAK_TO_PEAK_BitsFract bit
MAX - MIN, the span of the block, in the PEAK_TO_PEAK Q format. It needs one bit more than a sample, which is why it is the only output here with a format of its own; it is never negative, so the default format is UNSIGNED. Computed in the SAME final state as MIN and MAX, so it costs one subtraction and not a clock. Present on the symbol only when Enable PEAK_TO_PEAK = YES.
ARGMIN max(MaxBlockExponent, 2) bit
The 0-based INDEX inside the block at which the minimum occurred - the FIRST one if the minimum occurs more than once, because the comparisons are strict. Unsigned integer, no Q format. Present on the symbol only when Enable ARGMIN = YES.
ARGMAX max(MaxBlockExponent, 2) bit
The 0-based INDEX inside the block at which the maximum occurred - the FIRST one if the maximum occurs more than once. Unsigned integer, no Q format. Present on the symbol only when Enable ARGMAX = YES.
BUSY 1 bit
High from the start of a block - its first accepted 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. 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.

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.

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 PEAK_TO_PEAK subtraction (reading the raw bits as signed would turn an unsigned 200 into -56).

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

Enable MIN / MAX EnableMinMax

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

Enable PEAK_TO_PEAK EnablePeakToPeak

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

PEAK_TO_PEAK Integer Bits PEAK_TO_PEAK_BitsInt

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

PEAK_TO_PEAK Fractional Bits PEAK_TO_PEAK_BitsFract

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

PEAK_TO_PEAK Sign PEAK_TO_PEAK_Sign

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

Enable ARGMIN EnableArgMin

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

Enable ARGMAX EnableArgMax

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: the ARGMAX pin exists, along with one index register. NO: both are removed. Ties keep the EARLIEST position. Default NO.

Default: NO

Options: NO YES

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 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 affects PEAK_TO_PEAK - nothing else here is 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 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 affects PEAK_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 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 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:

  • 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 - which is the useful thing to latch alongside the results. Only RESET clears 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:

  • MIN and MAX are samples and come out in the input format - there is nothing to size.
  • PEAK_TO_PEAK can 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.
  • ARGMIN and ARGMAX are 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.