Xilinx
HLS
Block Preview

Introduction

The Block SNR block chops the input stream into consecutive blocks of N samples and, at the end of each block, publishes the signal to noise ratio of that block:

$$ \bar{x} = \frac{1}{N}\sum_{i=0}^{N-1} x_i, \qquad \sigma = \sqrt{\frac{1}{N}\sum_{i=0}^{N-1}(x_i-\bar{x})^2}, \qquad \mathrm{SNR} = \frac{\bar{x}}{\sigma} $$

The mean is the signal and the standard deviation is the noise. That is the right model for a DC-ish measurement corrupted by additive noise: a photodiode current, a detector baseline, an ADC reading a voltage reference, a regulated rail. It is not the right model for a modulated carrier, where the signal is the AC part - there, RMS of the band of interest over RMS of the rest is the number you want, and this block is not it.

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

THIS IS A LINEAR RATIO, NOT DECIBELS

SNR = 100 means the mean is a hundred times the standard deviation. It does not mean 100 dB. A logarithm is not cheap in fixed point and it is not this block’s job; take $20\log_{10}(\mathrm{SNR})$ downstream - in a CPU, or in a log block - if you want dB. Nothing in this core is approximated to make that conversion easier.

linear SNR dB
2 6.0
10 20.0
31.6 30.0
100 40.0
127.99 42.1 (the default Q8.8 maximum)
1000 60.0

The units cancel, and so does N

This block does no fixed-point scaling at all. In raw integer terms, with $S_1 = \sum x_i$ and $S_2 = \sum x_i^2$ and $\mathrm{var_num} = N!\cdot!S_2 - S_1^2$:

$$ \bar{x} = \frac{S_1}{N}, \qquad \sigma = \frac{\sqrt{\mathrm{var_num}}}{N}, \qquad \mathrm{SNR} = \frac{S_1}{\sqrt{\mathrm{var_num}}} $$

No $2^{-\mathrm{IN_{fract}}}$ and no N anywhere. The input Q format does not enter this core’s arithmetic at all. (The input width still sets the length of the serial engines; it is the scaling that cancels.)

Block SNR or Block Coefficient of Variation?

Block Coefficient of Variation computes $\sigma/\bar{x}$ - the exact reciprocal of this block. Both exist because a reciprocal is not free in fixed point: pick the one whose interesting values land in the middle of its Q format rather than crammed against one end.

your signal interesting values use
the mean dominates, mean $\gg \sigma$ snr in the tens or hundreds Block SNR
the spread dominates, $\sigma \gtrsim$ mean cv around or above 1 Block Coefficient of Variation

A clean 60 dB measurement is snr = 1000, which needs 10 integer bits here and would be four LSBs of a Q4.12 CV - use this block. A noisy or nearly zero-mean channel has snr near 0 and cv around 1 - use the other one. The two cost exactly the same tail for the same number of output fractional bits, so the choice is purely about where your numbers live.

Cost

Per clock: one multiplier for $x^2$ (unavoidable at one sample per clock) and two accumulators, $S_1$ and $S_2$. In the tail: three serial engines, one reused stage each - a shift-add squarer for $|S_1|\cdot|S_1|$, a digit recurrence for $\sqrt{\mathrm{var_num}}$ and a restoring divider for the ratio - all taking one step per clock. What you pay for the tail is clocks, not comparators, and there is no DSP in the tail at all.

When to use this instead of Block Statistics

The all-in-one Block Statistics block is not deprecated, but it does not compute this statistic: its outputs include MEAN, VARIANCE, STDDEV, RMS, SUM, SUM_SQ, min/max, CREST and the rest, but there is no SNR pin. So:

  • you want several statistics of the SAME block and the SNR - place a Block Statistics block for the rest and this block alongside it. They will each keep their own accumulators, which is the price of a statistic the all-in-one does not have.
  • you want exactly one number - use this block on its own. Then you synthesise only that number: two accumulators, three serial engines and nothing else reaches the synthesiser.

Note that dividing Block Statistics’ MEAN by its STDDEV downstream is not the same thing: those two are each requantised into their own Q format first, and the quotient of two rounded numbers is much worse than this block’s 1 LSB. Here the ratio is formed from the exact integers.

Pin Description

IN Input IN_BitsInt + IN_BitsFract bit BIT VECTOR
Input samples, fixed point in the IN Q format. Added to $S_1$, squared and added to $S_2$, and counted towards N, only on the clocks where IN_DV is high. The Q scaling cancels out of the ratio; only the width matters, and it sets the length of the serial squarer.
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. N cancels out of the ratio, so results taken at different EXP estimate the same quantity - a longer block simply estimates it better. It must satisfy $2^{\text{EXP}} \ge L$ at run time or the block’s result is dropped without warning, and on this block the minimum is typically EXP = 7 or 8 - see “The serial tail”. Unconnected defaults to 10 (N = 1024).
SNR Output SNR_BitsInt + SNR_BitsFract bit BIT VECTOR
$\bar{x}/\sigma$ over the block, in the SNR Q format - a LINEAR ratio, not decibels, carrying the sign of the mean. Saturates to the format extreme with that sign when the standard deviation is zero and the mean is not, and reports 0 when both are zero; both forced, independently of the Saturation property. Accurate to within 1 LSB provided the internal root is not tiny. 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. SNR 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 Input 1 bit
Clock.
RESET Input 1 bit
Synchronous reset: clears both accumulators, the block counter, the sample count and the whole serial tail.
BUSY Output 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 over a hundred clocks long in the default configuration. 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 Output 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 square-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 Output 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 - i.e. how many samples the SNR estimate rests 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. Only the width matters to this block - the Q scaling cancels out of the ratio - but each input bit costs three clocks of tail (one in the serial squarer, one in the root and one in the divider).

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. They do not enter the arithmetic - snr is a ratio of two raw integers and $2^{-\text{IN}_{fract}}$ cancels - but they do count towards the width, so each one still costs three clocks of tail.

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 costs three clocks of tail like any other. Note that an UNSIGNED input guarantees a non-negative mean, which is exactly when an UNSIGNED SNR output is safe.

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 the internal $S_1$ and $S_2$ registers and lengthens the tail by two clocks per unit, because the square root here is sized from the internal variance numerator rather than from the output format - that is what keeps the denominator known to full internal precision whatever the output format is. Keep it at the largest block you actually use. It must also 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

SNR Integer Bits SNR_BitsInt

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

Integer bits of the SNR output - i.e. the largest ratio you can report. 1..64, default 8, which signed reaches ±127.99 or about 42 dB. A 60 dB measurement (snr = 1000) needs 10, an 80 dB one needs 14. These bits cost no tail clocks at all, so size them for the best signal you ever expect rather than the typical one.

Default: 8

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

SNR Fractional Bits SNR_BitsFract

Number of FRACTIONAL bits of the SNR 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 SNR output. 0..64, total width 2..64 bits, default 8 (steps of 1/256). Near the top of the range a linear ratio needs very few fractional bits to be precise in dB - 1/256 is 0.0002 dB at snr = 100

  • and each bit adds exactly one clock to the divider and therefore to the tail, so do not be generous without a reason. If your ratios are near or below 1, consider Block Coefficient of Variation instead of buying resolution here.

Default: 8

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

SNR Sign SNR_Sign

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

SIGNED or UNSIGNED SNR output. Default SIGNED, on purpose: $\sigma$ is non negative but the mean is not, so snr carries the sign of the mean, and a constant negative block saturates to the format MINIMUM. With an UNSIGNED format every negative-mean block clamps to 0 - choose it only when you know the mean is positive (a rate, a count, a rectified envelope), where it buys one bit, i.e. one doubling of the reportable SNR.

Default: SIGNED

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 squarer, 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, so it reads how many samples the SNR estimate rests on. On a block with a tail this long it is also the cheapest run-time confirmation that the block really was long enough. 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. Default ROUND. On this block it makes no difference: the numerator is pre-shifted so that the quotient comes out of the divider already at the SNR resolution, leaving the final requantisation with nothing to shift and therefore nothing to round - it only applies the sign and saturates. The quotient itself is always floored by the restoring divider, which is part of the 1 LSB bound.

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 SNR format (symmetric bounds for signed formats). NO: wrap around. Worth leaving on here, because a high-SNR block genuinely can outgrow the integer bits you chose and a wrapped SNR is not even monotonic. The sigma == 0 cases ignore this property: a zero standard deviation with a non-zero mean always reports the format extreme with the sign of the mean, 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)-> MUL x (IN_SW + EXP) -> VAR -> LD -> SQRT x ROOTW
     -> CLD -> DIV x CNUMW -> FIN -> IDLE
  

the squarer, the square root and the divider run back to back, each one reused stage taking one step per clock. OUT_DV pulses for one clock when the tail COMPLETES - not when the N-th sample arrives - and SNR 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{IN_SW} + \mathrm{EXP} + 1) + (\mathrm{ROOTW} + 1) + (\mathrm{CNUMW} + 1) $$

with the two engine widths fixed at compile time:

$$ \mathrm{ROOTW} = \mathrm{IN_SW} + \mathit{MaxBlockExponent} + 4 $$

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

where $\mathrm{IN_SW}$ is the total input width, plus one bit if the input is UNSIGNED (a sample has to be promoted to signed first). For any sane configuration the first term of the $\max$ wins, and collecting terms gives a useful rule of thumb:

$$ L = 3,\mathrm{IN_SW} + 2,\mathit{MaxBlockExponent} + \mathrm{SNR_{fract}} + \mathrm{EXP} + 13 $$

knob clocks of tail per unit
input bit (IN_BitsInt + IN_BitsFract, +1 if UNSIGNED) 3
Max Block Exponent 2
SNR_BitsFract 1
SNR_BitsInt 0 (it does not reach the winning term of the $\max$)
runtime EXP 1

which is the same formula Block Coefficient of Variation has, with its CV_BitsFract in place of SNR_BitsFract. The two blocks cost the same.

Note the Max Block Exponent row. Unlike Block Std Dev and Block Std Error of Mean, whose engines are sized by the output format, this block’s root is sized by the width of the internal $\mathrm{var_num}$ - which is what keeps the denominator known to full internal precision whatever the output format is, but which also means every unit of Max Block Exponent costs two clocks of tail. Keep it at the largest block you actually use.

Worked numbers

For a 16 bit signed input, Max Block Exponent 20, at the default SNR format (Q8.8 signed):

  • $\mathrm{ROOTW} = 16 + 20 + 4 = 40$
  • $\mathrm{CNUMW} = \max(16 + 20 + 8 + 4,\ 16 + 2) = 48$
  • $L = 2 + (16 + \mathrm{EXP} + 1) + (40 + 1) + (48 + 1) = 109 + \mathrm{EXP}$
  • at EXP = 10 (N = 1024) that is 119 clocks, well inside the block;
  • the smallest exponent with $2^{\mathrm{EXP}} \ge 109+\mathrm{EXP}$ is EXP = 7, i.e. N = 128 ($128 \ge 116$; at EXP = 6, $64 < 115$).

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

Input Max Block Exp SNR ROOTW CNUMW L(EXP) min EXP
8 bit s 12 Q8.8 24 32 69 + EXP 7 (N = 128)
16 bit s 12 Q8.8 32 40 93 + EXP 7 (N = 128)
16 bit s 20 Q8.8 40 48 109 + EXP 7 (N = 128)
16 bit u 20 Q8.8 41 49 112 + EXP 7 (N = 128)
16 bit s 20 Q8.16 40 56 117 + EXP 7 (N = 128)
24 bit s 24 Q12.12 52 64 145 + EXP 8 (N = 256)

A longer block gives more slack than it costs. $L$ grows by one clock per unit of EXP while $2^{\mathrm{EXP}}$ doubles, so $2^{\mathrm{EXP}} \ge L$ gets easier as EXP grows, not harder. The constraint only ever bites at the short end - and on this block it bites at a fairly long block, EXP = 7 or 8 rather than the EXP = 1 of Block Mean. That is rarely a real restriction here: an SNR measured over 128 samples is already a poor estimate, and you will normally want thousands.

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 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 worst-case 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 one of the blocks where that bites hardest: with L above a hundred clocks, an EXP of 5 or 6 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.

When sigma is zero

sigma == 0 is a real case, and it is not an error: a constant block has no noise at all, and so does a block that a long integration has smoothed below one internal LSB. The core detects it on the denominator (the root came out 0) and forces the answer:

root mean SNR reports
0 > 0 the format maximum (the ratio is unbounded)
0 < 0 the format minimum - the saturation keeps the sign of the mean
0 0 0
≠ 0 any the ratio, with the sign of the mean

All three forced cases ignore the Saturation property, because none of them is a requantisation overflow - they are the definition of the ratio breaking down. Note the middle row in particular: a constant $-5$ block has SNR $-\infty$, not $+\infty$, and with an UNSIGNED output format it reports 0 rather than the maximum.

In practice a zero root on a signal that is not constant means your block really is that quiet at the resolution of the input - the honest answer is “the noise is below one LSB”, and saturating is the right way to say it.

Signed by default, and on purpose

$\sigma$ is non negative but the mean is not, so SNR carries the sign of the mean and the default output format is SIGNED. If you select an UNSIGNED format, a block whose mean is negative clamps to 0 - so pick UNSIGNED only when you know the mean is positive: a rate, a count, a rectified envelope, a unipolar supply rail. UNSIGNED buys one bit, which here is one doubling of the SNR you can report.

Accuracy

$\mathrm{var_num} = N!\cdot!S_2 - S_1^2$ is an exact integer: $N\cdot S_2$ is a shift (N is a power of two) and $S_1^2$ is an exact serial shift-add product. It is never two truncated quotients subtracted from each other.

The denominator root is taken at full internal precision, with $\mathrm{SQG} = 4$ guard bits; the numerator $|S_1|$ is shifted left by the same $\mathrm{SQG}$ plus $\mathrm{SNR_{fract}}$, so the guard bits cancel in the quotient and the divider delivers the answer already at the output resolution. The root is up to 1 ulp low at $2^{-\mathrm{SQG}}$ of an SNR LSB and the quotient is floored, so

SNR is accurate to within 1 LSB - provided the root is not tiny.

A block whose root is only a few ulps (a nearly constant signal) has a genuinely ill-conditioned ratio: a one-ulp error in a three-ulp denominator is a 33% error in the answer, and no amount of output bits fixes it. That is the arithmetic being honest, not a defect. The host regression enforces the 1 LSB bound and its scenarios keep the root away from 0; the sigma == 0 corner is covered separately, by the forced cases above.

Because the quotient already sits at the output resolution, the final requantisation has nothing left to shift: it only applies the sign and saturates. That makes the Rounding property effectively a no-op on this block (see its description).

Accumulation and IN_DV

IN_DV is the only qualifier. It says “this clock carries a sample”: a sample is added to $S_1$, squared and added to $S_2$, 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 noise that is not there - 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 - and at N = 1024 the tail hides comfortably for the default formats, 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.

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 $S_2$ is shifted by to form $\mathrm{var_num}$, and it also sets the length of the serial squarer.

Because N cancels out of the ratio, SNR values taken at different EXP are directly comparable as estimates of the same quantity - though a longer block always gives a better estimate of it, since $\sigma$ itself is measured from more samples. Remember that EXP still 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 square-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 it reads N at OUT_DV only when the input STOPS for the whole tail; on a CONTINUOUS stream the next block has already started and OUT_DV shows how far into it you are. The clock that always reads N is the one INTEGRATING falls on - which is the useful thing to latch alongside the result (it tells you how many samples the estimate rests on), 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 SNR carry their own fixed point format (integer bits, fractional bits, sign), the same convention as the Fixed P. family - but remember that the IN scaling cancels out of the ratio and only its width matters here. The quotient is requantised into the SNR format with the selected overflow policy (saturate / wrap); saturation is symmetric for signed formats, as everywhere else in the toolchain - except for the forced sigma == 0 cases above, which ignore the Saturation property.

Sizing hints:

  • the integer bits set the largest ratio you can report: the default 8 (signed) reach ±127.99, i.e. about 42 dB. If you are measuring a clean reference at 60 dB you need 10 integer bits, at 80 dB you need 14 - and they are free in tail clocks;
  • the fractional bits are the resolution: the default 8 give steps of 1/256, which is 0.03 dB at snr = 1 and about 0.0002 dB at snr = 100. Near the top of the range you need very few of them - each one costs one clock of tail, so do not be generous without a reason;
  • keep the default SIGNED unless you know the mean is positive; an UNSIGNED format reports 0 for every negative-mean block, including the constant-negative one that would otherwise saturate to the minimum.

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 $\bar{x}/\sigma$ in exact rational arithmetic and shares no algorithm with the core; the tolerance is exactly 1 LSB, never more, and the scenarios keep the root away from zero so that the bound is meaningful. Coverage includes pseudo-random and sinusoidal inputs on a DC pedestal, a constant positive block and a constant negative block (the forced saturation cases, which is what pins “the saturation keeps the sign of the mean”), an all-zero block (the forced 0 case), a negative-mean block against both a SIGNED and an UNSIGNED output format, gain-scaled versions of the same signal (which pins the scale invariance), maximum positive and maximum negative samples, unsigned input, IN_DV gaps inside the accumulation, an EXP that changes half way through a block, and an EXP driven above Max Block Exponent to exercise the clamp. The tail length is checked against the formula at compile time: three copies of it exist - the BSN_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.