Xilinx
HLS
Block Preview

Introduction

The Block Kurtosis block chops the input stream into consecutive blocks of N samples and, at the end of each block, publishes the kurtosis of that block:

$$ \mathrm{mean} = \frac{1}{N}\sum_{i=0}^{N-1} x_i , \qquad m_k = \frac{1}{N}\sum_{i=0}^{N-1} (x_i - \mathrm{mean})^k $$

$$ \mathrm{kurt} = \frac{m_4}{\sigma^4} = \frac{m_4}{m_2^{2}} $$

It is the normalised fourth central moment, and what it measures is TAILEDNESS: how much of the variance comes from rare large excursions rather than from many ordinary ones.

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

How to read the number

These are the reference points worth remembering:

signal kurtosis
a two-level square wave - the least tailed signal there is 1.0
a pure sine 1.5
uniform noise 1.8
GAUSSIAN noise - the number everything is compared to 3.0
impulsive: a quiet baseline with occasional big pulses » 3

Kurtosis is dimensionless and gain independent: multiply every sample by ten and it does not move. A rare pulse, on the other hand, moves it hard. That combination is the whole point of the block.

THIS IS PLAIN KURTOSIS, NOT EXCESS KURTOSIS

Some textbooks and most statistics packages report excess kurtosis, $m_4/\sigma^4 - 3$, so that Gaussian noise reads 0 instead of 3. This block reports the plain value: Gaussian noise reads 3.0.

If you want the excess convention, subtract 3.0 downstream - it is one constant subtraction in your own Q format, it costs one adder, and it is not worth a property here (nor worth making the output signed for, since only the excess form can be negative).

What it is FOR

This is the classic “is something happening in this channel” detector, and it is worth being precise about why:

  • a rare pulse moves it hard. A single full-scale outlier in a block of N otherwise-equal samples takes the kurtosis to about N - so at N = 1024 a single spike reads over a thousand while a quiet channel reads under 2. That is a dynamic range no threshold detector gives you for free.
  • gain does not move it at all. You do not have to know, or control, the channel gain. One threshold - “kurtosis above 5”, say - works on every channel of a system whose gains you never calibrated.
  • it does not need a threshold on the signal. Unlike Block Threshold Count or Block Peak Count, nothing has to be told how big a pulse is. A signal that is only ever a few LSB above the noise still has a large kurtosis if its excursions are rare.

Typical uses: pile-up and spike detection, machine condition monitoring (a rising kurtosis on a vibration channel is the textbook early bearing-fault indicator), gaussianity checks on a noise channel, and channel liveness - deciding which of many channels is worth reading out.

No square root anywhere

$\sigma^4$ is $(\sigma^2)^2$ - a SQUARE, not an odd power. Unlike Block Skewness, which needs $\sigma^3$ and therefore a root, this block contains no square root at all. The denominator is an exact integer square, and the only approximation in the whole answer is the floored quotient.

No histogram, no second pass, no buffer

The obvious way to get a central moment is to compute the mean, go back over the samples and accumulate $(x - \mathrm{mean})^4$. That needs the block stored - a memory of $2^{\mathrm{EXP}}$ samples and a second pass, which would halve the throughput and put a BRAM in the design.

This block does none of that. It keeps four ordinary accumulators,

$$ S_1 = \sum x_i , \quad S_2 = \sum x_i^2 , \quad S_3 = \sum x_i^3 , \quad S_4 = \sum x_i^4 $$

each updated once per clock, and recovers the fourth central moment at the end of the block from an exact integer identity (see the description below).

Cost

Per clock, while accumulating: three real multipliers - $x \cdot x$, $(x \cdot x) \cdot x$ and $(x \cdot x) \cdot (x \cdot x)$ - because they run at one sample per clock and cannot be serialised, plus the four accumulators.

In the tail: one reused shift-add stage (it does all five products) and one reused compare-subtract stage for the division. What the tail costs is CLOCKS, not multipliers - there is no DSP in the tail at all. This is the second most expensive tail in the family, after Block Correlation; see “Timing” below and budget a long block.

When to use this instead of Block Statistics

The all-in-one Block Statistics block is not deprecated, but it does not compute kurtosis - there is no KURTOSIS pin over there, so for this number there is no choice to make: this block is the only way to get it.

The usual family rule still applies to everything around it:

  • you want several statistics of the SAME block - mean and RMS and min/max of the same N samples - place one Block Statistics block. They share one accumulator set and one serial tail, so the second and third statistic are nearly free.
  • you want exactly one number - use the matching per-operator block. Then you synthesise only that number, and nothing else reaches the synthesiser.

A Block Kurtosis block placed next to a Block Statistics block accumulates the same stream a second time: $S_1$ and $S_2$ exist in both. That is the price of a statistic the all-in-one block does not have, and it is usually worth paying - the alternative is a histogram.

Pin Description

IN Input IN_BitsInt + IN_BitsFract bit BIT VECTOR
Input samples, fixed point in the IN Q format. Added into $S_1$, squared into $S_2$, cubed into $S_3$ and raised to the fourth into $S_4$ only on the clocks where IN_DV is high. The value of the kurtosis does not depend on the scaling of this port - the statistic is dimensionless.
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 accumulated, and counts towards N, exactly on the clocks where this is high; the serial 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. It must satisfy $2^{\text{EXP}} \ge L$ at run time or the block’s result is dropped without warning, and on this block L is in the hundreds of clocks

  • see “Timing”. Note also that N sets the SCALE of an impulsive reading (a lone outlier reads about N), so kurtosis values are only comparable between blocks of the same length. Unconnected defaults to 10 (N = 1024).
KURTOSIS Output KURT_BitsInt + KURT_BitsFract bit BIT VECTOR
$m_4 / \sigma^4$ over the block, in the KURT Q format - plain kurtosis, not excess kurtosis, so Gaussian noise reads 3.0 and not 0. 1.0 for a square wave, 1.5 for a sine, 1.8 for uniform noise, far above 3 for an impulsive block, and 0 for a constant block. Never negative. Updated on the OUT_DV clock and on no other; it holds the previous block’s result until then.
OUT_DV Output 1 bit BIT
One-clock pulse marking a valid result. It fires when the serial tail COMPLETES, L clocks after the clock on which the N-th sample of the block was accepted, not when that sample arrives. KURTOSIS is updated on this clock and on no other. BUSY is still high here and falls on the next clock. A block whose result was dropped for being too short produces no pulse at all - that is the only symptom.
CLK 1 bit
Clock.
RESET 1 bit
Synchronous reset: clears the four power accumulators, the block counter, the sample count and the whole serial tail.
BUSY 1 bit
High from the start of a block - its first accumulated sample - until its result is out: it covers the serial tail as well, which on this block is hundreds of clocks long. 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. BUSY high with INTEGRATING low is exactly the five-products-then-divide tail. On a continuous stream it dips for 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. 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. It enters the tail length through IN_SW, eight clocks per bit, plus four more through CNUMW.

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. It does not change the value of the kurtosis - the statistic is scale free and the input fractional bits cancel out of the identity - but it does widen IN_SW, and therefore the tail, by twelve clocks per bit.

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 accumulated - and that bit widens all five serial products and the divider, so it costs a dozen clocks of tail. An unsigned stream is measured about its own mean like any other, so a unipolar pulse train still reads a large kurtosis.

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 accumulators are 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. Raising it widens $S_1$ .. $S_4$ by one bit per unit and lengthens the serial tail by four clocks per unit through CNUMW, which is not the case on the cheaper blocks of the family. It must be at least the minimum usable exponent of the configuration, or the property page refuses the settings. 1..31, default 20, i.e. blocks of up to 1048576 samples out of the box. Keep it at the largest block you actually use.

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

KURT Integer Bits KURT_BitsInt

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

Integer bits of the KURTOSIS output. 1..64, default 10, i.e. up to 1023. That default is not arbitrary: a single full-scale outlier in a block of N otherwise-equal samples takes the kurtosis to about N, so 10 bits is exactly what a 1024-sample block can produce. Ordinary signals live between 1 and 10; widen this only if you want to read the actual value of an impulsive block rather than just see it saturate.

Default: 10

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

KURT Fractional Bits KURT_BitsFract

Number of FRACTIONAL bits of the KURTOSIS 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 KURTOSIS output. 0..64, total width 2..64 bits, default 6 (steps of 1/64) - enough to resolve the 1.5 / 1.8 / 3.0 reference points comfortably. It enters CNUMW, so every fractional bit adds four clocks to the tail, and the tail has to fit inside the block.

Default: 6

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

KURT Sign KURT_Sign

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

SIGNED or UNSIGNED KURTOSIS output. Default UNSIGNED, which is the right choice: plain kurtosis is never negative, so UNSIGNED buys one bit of range for free. Choose SIGNED only if something downstream insists on a signed bus. (If you want the excess convention, do not make this signed

  • subtract 3.0 downstream instead.)

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, the five products and the divider are running”. 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. On this block it has no effect on the value: the quotient already carries exactly KURT Fractional Bits fractional bits, so there is no shift left to round and the answer is the floored quotient either way. The property exists for consistency across the family. 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 KURTOSIS format. NO: wrap around. This one matters here: an impulsive block can genuinely exceed the format - a lone outlier reads about N - and wrapping would turn a huge kurtosis into a small one, which is the worst possible failure for a detector. Keep it YES unless you have a reason. The D == 0 (constant block) case never reaches it: the numerator is exactly 0 there, so that block reports 0. Default YES.

Default: YES

Options: NO YES

The exact integer identity

Expanding the fourth central moment and clearing the denominators turns the definition into a ratio of two integers built from the raw power sums:

$$ B = N^3 S_4 - 4 N^2 S_1 S_3 + 6 N S_1^2 S_2 - 3 S_1^4 ;=; N^4 m_4 $$

$$ D = N S_2 - S_1^2 ;=; N^2 m_2 ;\ge; 0 $$

$$ \mathrm{kurt} = \frac{B}{D^{2}} $$

Every factor of $N$ in there is a shift - N is a power of two - and the $N^4$ scalings cancel exactly between numerator and denominator. So neither $N$ nor the input fractional bits appear anywhere in the arithmetic: the answer is a pure ratio of two exact integers, built without a single premature rounding.

And because $\sigma^4$ is $D^2$ - a square, not an odd power - the denominator is formed by one more exact integer multiplication. There is no square root in this block, no root guard bits and no root error.

A constant block has no kurtosis, and the arithmetic says so

If every sample equals $c$ then

$$ B = (1 - 4 + 6 - 3),N^4 c^4 = 0 \qquad\text{and}\qquad D = 0 $$

so the zero-denominator path always finds a zero numerator and reports 0. The saturating branch of the ratio is unreachable through the ports: a constant block reports 0, never a full-scale spike. (Mathematically the kurtosis of a constant is undefined; 0 is what this block reports, not 1.)

Accuracy: BIT EXACT

The numerator and the denominator are exact integers and the only operation performed on them is the floored restoring division. 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) which evaluates $m_4/m_2^2$ in exact rational arithmetic and floors it the same way the divider does. Not “within 1 LSB”, not “within a few counts” - any deviation at all fails the build.

Being floored means the value is biased low by up to one LSB - with the default 6 fractional bits, up to 1/64. If that matters at your threshold, ask for more fractional bits (and pay four clocks of tail per bit, see below).

Accumulation and IN_DV

IN_DV is the only qualifier. It says “this clock carries a sample”: a sample is added into $S_1$, squared into $S_2$, cubed into $S_3$, raised to the fourth into $S_4$, 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 fake an outlier - while the serial 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 accumulates on IN_DV has no need to be frozen, and there is no reason to stall the tail.

There is no LEVEL pin either: a central moment is referred to the block’s own mean, by definition.

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. The latched exponent is the one used for every $N$, $N^2$ and $N^3$ shift in the identity, so the arithmetic of a block is always self-consistent.

Remember that N is also the scale of the answer on an impulsive signal: a single outlier reads about N, so halving the block halves the reading of a lone spike. A kurtosis threshold is only comparable between blocks of the same length.

Timing: the serial tail

The state walk

A block has a whole block period of slack after its N-th sample, so all post-accumulation arithmetic is serial. Five passes reuse one shift-add stage, and the ratio reuses one restoring-division stage:

  IDLE -(block complete)-> MUL2 (|S1|*|S1|) -> MULQ (|S1|*|S3|)
     -> MULR (|S1|^2*S2) -> MULF (|S1|^2*|S1|^2) -> VAR (form D and B)
     -> MULD (D*D) -> CLD -> DIV x CNUMW -> FIN -> IDLE
  

OUT_DV pulses for one clock when the tail COMPLETES - not when the N-th sample arrives - and KURTOSIS is updated on that same clock and on no other.

The latency contract

Counted in clocks from the clock on which the N-th sample of the block was accepted:

$$ L = 8,\mathrm{IN_SW} + 7,\mathrm{EXP} + 1 + \mathrm{CNUMW} $$

where $\mathrm{IN_SW}$ is the signed working width of a sample - the input width, +1 if the input is UNSIGNED (a sample has to be promoted to signed before it can be accumulated) - and the divider width is fixed at compile time by your formats:

$$ \mathrm{CNUMW} = \max\bigl(4,(\mathrm{IN_SW} + \mathrm{MaxBlockExponent}) + \mathrm{KURT_{fract}},; \mathrm{KURTOSIS\ width} + 2\bigr) $$

The $7,\mathrm{EXP}$ comes from four of the five products (|S1| needs $\mathrm{IN_SW} + \mathrm{EXP}$ steps, $S_2$ needs $2,\mathrm{IN_SW} - 1 + \mathrm{EXP}$, and $|S_1|^2$ and $D$ need $2,\mathrm{IN_SW} - 1 + 2,\mathrm{EXP}$ each); the divider is fixed at compile time by your formats and by Max Block Exponent.

Note what this means: raising Max Block Exponent lengthens the tail here, unlike on Block Mean where it costs only accumulator width - it widens CNUMW by four clocks per unit. And the runtime exponent enters at seven clocks per unit, the steepest in the family. Both still lose to $2^{\mathrm{EXP}}$, so a longer block always has more slack, not less.

Worked numbers

Take a 16 bit signed input at the default formats (KURTOSIS Q10.6 unsigned, Max Block Exponent 20):

  • $\mathrm{IN_SW} = 16$
  • $\mathrm{CNUMW} = \max(4 \cdot 36 + 6,; 16 + 2) = \max(150, 18) = 150$
  • $L = 128 + 7,\mathrm{EXP} + 1 + 150 = 279 + 7,\mathrm{EXP}$

At EXP = 8 that is $L = 335$ clocks and $2^8 = 256 < 335$: it does not fit. At EXP = 9 it is $L = 342$ and $2^9 = 512 \ge 342$: it does. The minimum usable EXP is 9, a block of 512 samples, and every larger exponent fits too - the block grows exponentially while the tail grows linearly. At the default EXP = 10 the tail is 349 clocks inside a 1024-sample block, i.e. about 66 % idle.

A few more configurations, to show where the clocks go:

Input Max Block Exponent KURTOSIS CNUMW L minimum EXP
8 bit s 8 Q10.6 70 135 + 7·EXP 8 (N = 256)
8 bit s 12 Q10.6 86 151 + 7·EXP 8 (N = 256)
8 bit s 20 Q10.6 118 183 + 7·EXP 8 (N = 256)
16 bit s 12 Q10.6 118 247 + 7·EXP 9 (N = 512)
16 bit s 20 Q10.6 150 279 + 7·EXP 9 (N = 512)
16 bit u 20 Q10.6 154 291 + 7·EXP 9 (N = 512)
32 bit s 20 Q10.6 214 471 + 7·EXP 10 (N = 1024)

Budget a long block. Even the cheapest configuration in that table needs N = 256, and a 32 bit input needs N = 1024; there is no useful setting of this block that works at EXP = 5. That is rarely a real constraint, because a kurtosis estimated over 64 samples is a noisy statistic anyway - the fourth moment needs samples before it means anything - but it does mean you cannot use this block as a fast per-pulse discriminator.

The 2^EXP >= L rule, and what happens when it is broken

The tail of one block must finish before the next block completes:

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

A completed block is only handed to the tail when the tail is IDLE, so if a block completes while the previous tail is still running, that block’s result is DROPPED: there is no OUT_DV for it, the accumulators are unaffected and the following blocks come out correctly, but a result is silently skipped. There is no error pin and no overrun pin for it.

Two things guard against it, and one hole remains:

  • the property page refuses a configuration whose minimum exponent exceeds Max Block Exponent, telling you the tail length and the exponent it would need;
  • CompileHDL prints both numbers - the worst-case tail length and the minimum usable EXP - into the compilation log for every placement;
  • but EXP is a PIN. Nothing can stop a design driving it too low at RUN TIME, and with L in the hundreds of clocks this is one of the two blocks where that bites hardest: an EXP of 6 that would be perfectly reasonable on Block Mean drops every result here. If EXP is under software control, clamp it in software to the minimum the compiler printed.

Knowing where the block is: BUSY, INTEGRATING and SAMPLE_COUNT

Three optional status outputs, all defaulting to NO. They answer different questions, and on this block the difference is at its most useful, because the tail is long:

INTEGRATING BUSY
accumulating the block 1 1
serial 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, and never during the tail. BUSY high with INTEGRATING low is precisely the five-products-then-divide tail.
  • 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 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 - which is the useful thing to latch alongside the result, and here doubly so, because the reading of an impulsive block scales with N. Only RESET clears it to 0.

Q formats

Both data ports carry their own fixed point format (integer bits, fractional bits, sign), the same convention as the Fixed P. family. The quotient already carries exactly KURT Fractional Bits fractional bits, so the final requantisation is a zero-bit shift: the value is not rounded again on the way out, only checked against the format bounds.

Sizing:

  • the default output format is WIDE for a reason. A single full-scale outlier in a block of N otherwise-equal samples takes the kurtosis to about N, so at the default N = 1024 a lone spike reads about 1022. The default 10 integer bits hold up to 1023 - matched to exactly that case. If you run longer blocks and care about the value of an impulsive reading rather than just “it is large”, widen it or let it saturate deliberately.
  • kurtosis is never negative and is always $\ge 1$ for a real block, so UNSIGNED is the default and buys you a bit. Choose SIGNED only if something downstream insists on a signed bus - the block will never drive a negative value.
  • the fractional bits (default 6, steps of 1/64) are the resolution around the interesting thresholds - 1.5, 1.8, 3.0 - and each one costs four clocks of tail through CNUMW.
  • the input Q format does not enter the value at all: the identity is scale free, so IN_BitsFract changes nothing about the answer (only the tail length, through IN_SW).

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 $m_4/m_2^2$ in exact rational arithmetic and shares no algorithm with the core; the tolerance is 0. Coverage includes an impulse pattern - rare full-scale spikes on a flat baseline, which drives the kurtosis into the tens and exercises the top of the output format - a two-level extremes pattern (the least tailed signal there is, kurtosis exactly 1), a sine (1.5), pseudo-random input (about 1.8), a constant block (the D == 0 path, which must read 0 and not saturate) and an UNSIGNED 8 bit input to exercise the promotion. The tail length is checked at compile time: three copies of the formula exist - the BKU_TAIL macro in the core, tail_len() in the generator and TailClocks() in the plugin - and the testbench refuses to build if they disagree. The status outputs are checked clock by clock against the contract above.