Xilinx
HLS
Block Preview

Introduction

The Block Crest Factor block chops the input stream into consecutive blocks of N samples and, at the end of each block, publishes the crest factor of that block:

$$ \mathrm{peak} = \max\bigl(|\min_i x_i|,\ |\max_i x_i|\bigr), \qquad \mathrm{rms} = \sqrt{\frac{1}{N}\sum_{i=0}^{N-1} x_i^2}, \qquad \mathrm{CREST} = \frac{\mathrm{peak}}{\mathrm{rms}} $$

PEAK IS $\max(|\min|,|\max|)$, NOT MAX alone - that is the usual meaning of crest factor - so a signal whose negative excursion is the larger one reports that excursion, and a block that lies entirely below zero still has a crest factor.

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, the division by N inside the RMS is an exact arithmetic shift - it is folded into the radicand of the square root and costs nothing. The peak / rms ratio is the real division, and it is the only one in this family.

What it is FOR

The crest factor is the standard “how peaky is this” number: about 1.41 for a sine, about 1 for a square wave, 1 for a constant, and large for a spiky or pulsed signal. It is scale invariant - it does not change when the signal is amplified - which is exactly what makes it useful as a shape discriminator:

  • pile-up and spike detection on a channel whose gain you do not control;
  • clipping detection - a clipped signal loses its peaks and its crest factor collapses towards 1;
  • pulsed versus continuous classification, and a duty-cycle-like measure for signals with no threshold to speak of;
  • machine condition monitoring, where a rising crest factor on a vibration channel is the classic early impact-fault indicator.

Cost

This is the most expensive block of the twelve. Per clock it costs one multiplier for $x^2$ (unavoidable at one sample per clock), one accumulator and two comparators for the running min and max. In the tail it costs one reused compare-subtract stage for the square root and one for the restoring division, plus their numerator / quotient / remainder / denominator registers. Both engines take one step per clock, so what you pay for the tail is clocks, not comparators, and there is no DSP in the tail at all - the only multiplier in the block is the one that squares the samples during accumulation.

When to use this instead of Block Statistics

The all-in-one Block Statistics block is not deprecated and computes this same crest factor among twenty other statistics. The rule is simple:

  • you want several statistics of the SAME block - the crest factor 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. That argument is at its strongest here: over there the square root that the crest factor needs is the same root the RMS pin uses, and the min/max registers are the same ones the MIN, MAX, PEAK_TO_PEAK, ARGMIN and ARGMAX outputs use.
  • you want exactly one number - use this block. Then you synthesise only that number, 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. Squared, accumulated, compared against the running min and max, 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 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 - see “The serial tail”. Unconnected defaults to 10 (N = 1024).
CREST Output CREST_BitsInt + CREST_BitsFract bit BIT VECTOR
$\max(|\mathrm{min}|,|\mathrm{max}|) / \mathrm{rms}$ over the block, in the CREST Q format. Saturates to the format maximum when the RMS is zero and the peak is not, and reports 0 for an all-zero block - both forced, independently of the Saturation property. 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. CREST 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 sum of squares, the min/max registers, 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 tens 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 root-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, 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. Note that it enters the divider geometry through $K$: MORE input fractional bits SHORTEN the divider.

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 squared - and that bit widens the divider by one step as well.

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 accumulator 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. Raising it widens the internal sum-of-squares register by one bit per unit; unlike on Block Variance it does not lengthen the tail, because neither engine here has an exponent term. 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.

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

CREST Integer Bits CREST_BitsInt

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

Integer bits of the CREST output. 1..64, default 4. A crest factor is ~1.41 for a sine and ~1 for a square wave, so 4 integer bits (up to 15) is generous for a real signal; raise it only for very spiky data.

Default: 4

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

CREST Fractional Bits CREST_BitsFract

Number of FRACTIONAL bits of the CREST 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 CREST output. 0..64, total width 2..64 bits, default 12 (steps of 1/4096). This is where the resolution goes - but it also enters $K$ and therefore the divider width, so every fractional bit can add a clock to the tail. Do not make it wider than you need.

Default: 12

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

CREST Sign CREST_Sign

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

SIGNED or UNSIGNED CREST output. Default UNSIGNED - a crest factor is never negative, and UNSIGNED buys one bit.

Default: UNSIGNED

Options: UNSIGNED SIGNED

RMS Internal Integer Bits RMS_BitsInt

INTEGER bits of the INTERNAL rms, the DENOMINATOR of the crest ratio. This Q format is NOT a pin – the rms itself never leaves the block – but it must be wide enough to hold the largest rms you expect, or the denominator saturates and the reported crest factor is too small. It is always unsigned (an rms is a magnitude), which is why there is no RMS Sign property.

INTEGER bits of the INTERNAL rms, the DENOMINATOR of the crest ratio. 1..64, default 16. This Q format is NOT a pin - the rms itself never leaves the block - but it must be wide enough to hold the largest rms you expect, or the denominator saturates and the reported crest factor comes out too small. It is always unsigned (an rms is a magnitude), which is why there is no RMS Sign property. Each bit adds one clock to the square root, i.e. one clock to the tail.

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

RMS Internal Fractional Bits RMS_BitsFract

FRACTIONAL bits of the INTERNAL rms, i.e. how finely the DENOMINATOR of the crest ratio is resolved. This Q format is NOT a pin. It is the main accuracy / cost trade-off of this block, and it is the EXPENSIVE knob: a fractional bit lands in BOTH serial engines – it widens the square root (one clock) AND it widens the divider through the fixed shift applied to the peak (one more) – so every fractional bit ADDS TWO CLOCKS TO THE TAIL, while an INTEGER bit adds only one. The tail must fit inside the block. Too few and the quotient degrades (the divisor is known only to a coarse LSB); too many and the tail gets long for nothing.

FRACTIONAL bits of the INTERNAL rms, i.e. how finely the DENOMINATOR of the crest ratio is resolved. 0..64, total RMS width 2..64 bits, default 0. This Q format is NOT a pin. It is the main accuracy / cost trade-off of this block: too few and the quotient degrades (the divisor is known only to a coarse LSB, and a small rms can truncate to zero and force saturation); too many and the tail gets long for nothing. Each fractional bit lengthens BOTH the square root and the divider, so it costs two clocks of tail - and the tail must fit inside the block.

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

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 root 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 the ratio has to be requantised into a coarser output format. TRUNCATE: drop the bits (cheaper, adds a negative bias). It applies to the final requantisation only - the quotient itself is always floored by the restoring divider. 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 CREST format (symmetric bounds for signed formats). NO: wrap around. The two rms == 0 cases ignore this property: a zero rms with a non-zero peak always reports the format maximum, and an all-zero block always reports 0, because neither is a requantisation overflow. Default YES.

Default: YES

Options: NO YES

Timing: the serial tail

All post-accumulation arithmetic is serial - the block has a whole block period of slack after the N-th sample, so there is no reason to build a parallel datapath for it. The state walk is

  IDLE -(block complete)-> LD -> SQRT x RTW -> CLD -> DIV x CNUMW -> FIN -> IDLE
  

the square root and the divider run back to back, each one reused compare-subtract stage taking one step per clock. OUT_DV pulses for one clock when the tail COMPLETES - not when the N-th sample arrives - and CREST is updated on that same clock and on no other. The latency from the N-th accepted sample to the OUT_DV pulse, counted in clocks, is

$$ L = 2 + (\mathrm{RTW} + 1) + (\mathrm{CNUMW} + 1) $$

with the two engine widths fixed at compile time by your formats:

$$ \mathrm{RTW} = \mathrm{RMS_{int}} + \mathrm{RMS_{fract}} + 5 $$

$$ K = \mathrm{CREST_{fract}} - \mathrm{IN_{fract}} + \mathrm{RMS_{fract}} + 4, \qquad \mathrm{CNUMW} = \max\bigl(\mathrm{IN_SW} + 1 + \max(K, 0),; \mathrm{CREST\ width} + 2\bigr) $$

where $\mathrm{IN_SW}$ is the input width, plus one bit if the input is UNSIGNED (a sample has to be promoted to signed first).

Note what is absent: CNUMW has no exponent term. $K$ is built only from Q formats, so the division always takes the same number of steps whatever N is, and $L$ does not depend on EXP at all. Only the value being divided changes with the block length.

Worked numbers

For an 8 bit signed input at the default formats (CREST Q4.12 unsigned, RMS internal Q16.0):

  • $\mathrm{RTW} = 16 + 0 + 5 = 21$
  • $K = 12 - 0 + 0 + 4 = 16$, and $\mathrm{IN_SW} + 1 = 9$, so $\mathrm{CNUMW} = \max(9 + 16,\ 16 + 2) = 25$
  • $L = 2 + (21 + 1) + (25 + 1) = \mathbf{50}$ clocks
  • the smallest exponent with $2^{\mathrm{EXP}} \ge 50$ is EXP = 6, i.e. N = 64

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

Input CREST RMS internal RTW CNUMW L minimum EXP
8 bit s Q4.12 Q16.0 21 25 50 6 (N = 64)
8 bit s Q4.12 Q16.4 25 29 58 6 (N = 64)
16 bit s Q4.12 Q16.0 21 33 58 6 (N = 64)
16 bit s Q4.12 Q16.8 29 41 74 7 (N = 128)

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

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 and the min/max registers are unaffected and the following blocks come out correctly, but a result is silently skipped. There is no error pin for it.

The property page refuses a configuration whose minimum exponent exceeds Max Block Exponent, and CompileHDL prints both numbers - the tail length and the minimum usable EXP - in the compilation log, so the static half of the problem is caught for you.

But EXP is a PIN. Nothing can stop a design driving it too low at RUN TIME, and this is the block where that bites hardest: with L in the tens of clocks, an EXP of 4 or 5 that would be perfectly reasonable on Block Mean drops every result here, with no indication other than a silent OUT_DV. If EXP is under software control, clamp it in software to the minimum the compiler printed.

The RMS internal Q format

The crest factor divides by the RMS, and that RMS comes out of the same digit recurrence Block RMS uses. It is not a pin here - only the ratio leaves the block - but its Q format is still a real design decision, because it sizes the denominator:

  • accuracy of the quotient. The divisor is known only to one LSB of this format. Too few fractional bits and the quotient degrades, especially for small RMS values.
  • length of the serial tail. $\mathrm{RTW} = \mathrm{RMS\ width} + 5$, one clock per bit, and $\mathrm{RMS_{fract}}$ also enters $K$ and therefore CNUMW - so each fractional bit you add costs two clocks of tail. Too many and the tail gets long for nothing.
  • range. RMS Internal Integer Bits must be wide enough to hold the largest RMS you expect, or the denominator saturates and the reported crest factor comes out too small.

There is deliberately no “RMS Sign” property: the RMS is an internal magnitude, always unsigned, and it never leaves the block.

Accuracy

The divider divides by the root, which is itself accurate to about 1 LSB and biased low, and the quotient is floored. The relative error of CREST is therefore about one part in the root - a fraction of a percent for any signal with a meaningful RMS, and worse as the RMS approaches one root LSB. This is the one compounded error in the family; every other per-operator block is either bit exact or within 1 LSB.

Because that bound depends on the value, the host regression does not assert a fixed number of LSBs: the golden generator derives a per block tolerance from the published RMS accuracy and checks the quotient against it.

When the RMS is zero

rms == 0 is a real case - an all-zero block. Then:

peak rms CREST reports
> 0 0 the largest value the CREST format can represent (the ratio is unbounded)
0 0 0 (a flat zero signal has no crest)

Both are FORCED explicitly, independently of the Saturation property, because neither is a requantisation overflow - they are the definition of the ratio breaking down.

Note that “rms == 0” here means the internal root truncated to zero, which a long block of very small samples can reach even though the samples are not all zero. That still saturates, and that is still the right answer: the denominator really is below one LSB of the RMS format you chose. If you see saturation on a signal that is not silent, add fractional bits to the RMS internal format - and pay the two clocks per bit.

Accumulation and IN_DV

IN_DV is the only qualifier. It says “this clock carries a sample”: a sample is squared, accumulated, compared against the running min and max, 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 a peak - 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 - though at N = 1024 the tail hides comfortably, which is not true of every value you might drive.

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 - and note that there would be no reason to stall the tail anyway.

There is no LEVEL pin either: the crest factor is referred to zero 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 value is what the radicand is shifted by, so the RMS always refers to the right block length. Remember that EXP also has to satisfy $2^{\mathrm{EXP}} \ge L$ at run time, every time.

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. BUSY high with INTEGRATING low is precisely the root-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 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 result, and here also the cheapest way to confirm at run time that the block was long enough for the tail. Only RESET clears it to 0.

Q formats

IN and CREST carry their own fixed point format (integer bits, fractional bits, sign), the same convention as the Fixed P. family; the RMS internal format is a third one that is not a pin. The ratio is requantised into the CREST format with the selected rounding (nearest / truncate) and overflow policy (saturate / wrap); saturation is symmetric for signed formats, as everywhere else in the toolchain - except for the two forced rms == 0 cases above, which ignore the Saturation property.

Sizing hints:

  • a crest factor is ~1.41 for a sine and ~1 for a square wave, so the default 4 integer bits (up to 15) is generous for a real signal; raise it only for genuinely spiky data;
  • the fractional bits are where the resolution goes - the default 12 give steps of 1/4096 - but they also enter $K$ and therefore the divider width, so every fractional bit can cost a clock of tail;
  • CREST is never negative, so UNSIGNED buys one bit.

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, and which derives a per block tolerance from the published RMS accuracy rather than asserting a fixed LSB count. Coverage includes pseudo-random and sinusoidal inputs, a constant signal (crest factor 1), an all-zero block (rms == 0, the forced 0 case), a configuration whose root truncates to zero (the forced saturation case) and maximum positive / maximum negative inputs including an asymmetric signal whose negative excursion is the larger one - which is what pins $\mathrm{peak} = \max(|\min|,|\max|)$. The tail length is checked against the formula at compile time: three copies of it exist - the BCR_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. 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.