DSP - BLOCK STD DEV
The standard deviation of a block of N consecutive samples - the spread of the block about its own mean, back in the units of the input. N is a power of two chosen at RUN TIME on the EXP input pin (EXP = 10 means N = 1024), so every division by N is an exact arithmetic shift and the block contains no divider at all. This is Block Variance with the serial square root of Block RMS on its output: the variance numerator NS2 - S1S1 is an EXACT integer identity, so the value handed to the root carries no error at all and the root itself adds up to 1 LSB. It is the only block of the family that runs BOTH serial engines back to back - one reused shift-add stage for S1*S1 and one reused compare-subtract stage for the root - so it has the longest tail of the family after the crest factor: L = 2 + (IN_SW + EXP + 1) + (RTW + 1) with RTW = STDDEV width + 5. If you only need to COMPARE spreads, use Block Variance instead. IN_DV is the only qualifier and there is deliberately no CE pin. Optional BUSY / INTEGRATING / SAMPLE_COUNT status outputs. Blocks of up to 2^20 samples out of the box, 2^31 if you ask for it.
Introduction
The Block Std Dev block chops the input stream into consecutive blocks of N samples and, at the end of each block, publishes the standard deviation of that block:
$$ S_1 = \sum_{i=0}^{N-1} x_i , \qquad S_2 = \sum_{i=0}^{N-1} x_i^2 $$
$$ \sigma = \sqrt{\mathrm{var}} = \sqrt{\overline{x^2} - \mathrm{mean}^2} = \sqrt{\frac{S_2}{N} - \left(\frac{S_1}{N}\right)^{!2}} $$
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, every division by N is an exact arithmetic shift. There is no divider and no reciprocal ROM, which is also why the block size can be changed while the design is running, for free.
What it is FOR
$\sigma$ is the spread of the block about its own mean, in the units of the input and with the DC level removed. It is the number you report when the channel noise has to be quoted in millivolts or ADC counts, the one a slow control loop compares against a specification, and the one an operator expects to see on a display.
Note the difference from Block RMS: the RMS includes the DC level. A constant signal at level $c$ has an RMS of $|c|$ and a standard deviation of 0. If you want “how big is this signal”, take the RMS; if you want “how noisy is this signal”, take $\sigma$.
Two cheaper answers to consider first
If you only need to COMPARE or THRESHOLD spreads, use Block Variance. It
is strictly cheaper AND strictly exact. The square root is monotone, so
thresholding the variance against the SQUARE of your threshold gives
exactly the same decisions - with no root, no root guard bits and a much
shorter tail (Block Variance stops at the VAR state this block passes
through on its way to the root). Take $\sigma$ only when the number itself
has to be reported in input units.
If you only want a robust “how noisy is this” number, consider Block Mean Abs. It costs no multiplier and no square root, its tail is a constant $L = 2$, and for a Gaussian signal it reads
$$ \overline{|x - \bar{x}|} = \sqrt{\tfrac{2}{\pi}},\sigma \approx 0.7979,\sigma $$
so it is a scaled standard deviation for Gaussian noise - and, unlike $\sigma$, a single large outlier moves it linearly instead of quadratically.
When to use this instead of Block Statistics
The all-in-one Block Statistics block is not deprecated and computes this same standard deviation among twenty other statistics. The rule is simple:
- you want several statistics of the SAME block - $\sigma$ and RMS and mean of the same N samples - use Block Statistics. They share one accumulator pair and one serial tail, and in particular the RMS root and the $\sigma$ root there share one compare-subtract stage run back to back. Two per-operator blocks would build two.
- you want exactly one number - use this block. Then you synthesise only that number: the pin list, the two accumulators, the serial squarer, the root and the tail are all that $\sigma$ needs, and nothing else reaches the synthesiser.
Two Block Statistics blocks side by side would duplicate the accumulators; two per-operator blocks side by side duplicate them too. One Block Statistics block never does.
Pin Description
IN_DV is high.
'1'. (There is deliberately no CE pin - to stall the block,
gate this.)
OUT_DV clock and on no
other; it holds the previous block’s result until then.
STDDEV is updated on this
clock and on no other. BUSY is still high here and falls on the next
clock.
OUT_DV pulse, and it falls on the clock after. On a continuous
stream it simply stays high. Present on the symbol only when
Enable BUSY = YES.
How many samples have been accumulated so far in the current block: 1 after the first, N after the N-th. It is NOT cleared at the end of a block
- it HOLDS the final count through the tail and past
OUT_DV, until the first sample of the next block takes it back to 1, so on theOUT_DVclock it reads the length of the block being presented. OnlyRESETclears it to 0. Fixed 32 bits. Present on the symbol only when Enable SAMPLE_COUNT = YES.
Properties
Number of INTEGER bits of the input sample (the sign, when present, uses one of them).
Integer bits of the input sample (the sign, when present, uses one of them). 1..64. Default 16.Default: 16
Options: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
Number of FRACTIONAL bits of the input sample, i.e. the bits to the right of the binary point. Total width = integer + fractional bits, and must not exceed 64.
Fractional bits of the input sample. 0..64. Total input width must be 2..64 bits. Default 0.Default: 0
Options: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
Select whether the input sample is signed (two’s complement) or unsigned.
SIGNED (two’s complement) or UNSIGNED input. Default SIGNED. An UNSIGNED input costs one extra bit internally, because a sample has to be promoted to signed before it can be accumulated - and that bit is one more clock of serial multiply in the tail.Default: SIGNED
Options: UNSIGNED SIGNED
Largest block-size exponent the accumulators are sized for: the block can be up to 2^MaxBlockExponent samples long. The EXP input is clamped to this value at run time. Raising it widens the internal accumulators, and ON THE BLOCKS WHOSE SERIAL ENGINES ARE SIZED FROM THOSE ACCUMULATORS (Coefficient of Variation, SNR, Skewness, Kurtosis, Correlation, Autocorrelation, Linear Regression) it also LENGTHENS THE SERIAL TAIL – even when the runtime EXP is small. Keep it at the largest block you actually use. The default of 20 covers blocks of up to 1048576 samples.
Largest block-size exponent the accumulators are sized for: the block can be up to $2^{\text{MaxBlockExponent}}$ samples long, and theEXP input is
clamped to this value at run time. Raising it widens the internal sum and
sum-of-squares registers by one bit per unit, and raises the WORST CASE
tail (which is IN_SW + MaxBlockExponent + RTW + 4 clocks) - but it also
raises the slack, so it never makes the configuration invalid. 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
Number of INTEGER bits of the STDDEV output (the sign, when present, uses one of them).
Integer bits of the STDDEV output. 1..64, default 16. $\sigma$ is in the units of the input and can never exceed the peak-to-peak span of the block, so the input integer bits are always enough.Default: 16
Options: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
Number of FRACTIONAL bits of the STDDEV 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 STDDEV output. 0..64, total width 2..64 bits, default 0. The total width sets the length of the serial square root (RTW = width + 5 steps), so ask for the resolution you need and no more - every extra bit is one more clock of tail and can raise the minimum usable block exponent. This is the property the tail validator actually bites on.Default: 0
Options: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
Select whether the STDDEV output is signed (two’s complement) or unsigned.
SIGNED or UNSIGNED STDDEV output. Default UNSIGNED - a standard deviation is never negative and the exact variance numerator can never go negative either, so UNSIGNED is safe and buys one bit of range for free.Default: UNSIGNED
Options: UNSIGNED SIGNED
YES: the BUSY (high from the first sample of a block until its result is out – it COVERS THE SERIAL TAIL, and its last high clock IS the OUT_DV pulse) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.
YES: theBUSY pin exists. It is high from the first sample of a block
until its result is out, serial tail included, and its last high clock is
the OUT_DV pulse. NO: the pin and its register are removed before
synthesis. Default NO.
Default: NO
Options: NO YES
YES: the INTEGRATING (high only while the block is ACCUMULATING; it drops as soon as the N-th sample has been taken and the tail starts, so BUSY-and-not-INTEGRATING means ‘computing’) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.
YES: theINTEGRATING pin exists. It is high only while the block is
accumulating, so BUSY high with INTEGRATING low means “the samples are
all in, I am computing”. NO: the pin and its register are removed.
Default NO.
Default: NO
Options: NO YES
YES: the SAMPLE_COUNT (32 bit, how many samples have been accumulated so far in the current block: 1 after the first, N after the N-th. It is NOT cleared at the block end – it holds N until the NEXT block’s first accepted sample takes it back to 1. On a CONTINUOUS stream that happens DURING the serial tail, so at OUT_DV it reads how far into the next block the input has already got, NOT N. To capture the length of the block being presented, latch SAMPLE_COUNT on the clock INTEGRATING falls – that one always reads N) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.
YES: theSAMPLE_COUNT pin exists - a fixed 32 bit count of the samples
accumulated so far in the current block, holding the final count through
the tail and past OUT_DV. NO: the pin and its counter are removed.
Default NO.
Default: NO
Options: NO YES
ROUND: round to nearest when a result has to be requantised into a coarser output format. TRUNCATE: drop the bits (cheaper, adds a negative bias).
ROUND: round to nearest when the root has to be requantised into the output format. TRUNCATE: drop the bits (cheaper, adds a negative bias). Default ROUND. The digit recurrence itself always truncates; this setting governs the final requantisation that drops the 4 guard bits.Default: ROUND
Options: TRUNCATE ROUND
YES: clip to the largest representable value of each output format (symmetric for signed formats). NO: wrap around.
YES: clip to the largest representable value of the output format (symmetric bounds for signed formats). NO: wrap around. Default YES. It matters when the STDDEV format is too narrow for the spread - the pre-scaling of the radicand saturates on the same rule.Default: YES
Options: NO YES
Accuracy: exact in, 1 LSB out
This block is Block Variance with the serial square root of Block RMS on its output, and everything both of those say applies here.
The variance numerator is computed from the exact integer identity, not from two truncated quotients:
$$ \mathrm{var_num} = N,S_2 - S_1^2 , \qquad \mathrm{var} = \frac{\mathrm{var_num}}{N^2} $$
where $N S_2$ is a shift, $S_1 \cdot S_1$ is an exact integer square computed serially, and the division by $N^2$ is a shift again. So the value fed to the square root carries NO error at all. By Cauchy-Schwarz,
$$ N,S_2 - S_1^2 = \sum_{i<j} (x_i - x_j)^2 \ \ge\ 0 $$
always, which is what keeps a negative radicand out of the root and what makes
an UNSIGNED STDDEV output format safe - the default.
The root is then pre-scaled by $2^{2\cdot\mathrm{SQG}}$ with $\mathrm{SQG} = 4$, so the digit recurrence delivers
$$ \mathrm{root} = \sigma \cdot 2^{\mathrm{SQG}} = \sqrt{\mathrm{var_num} \cdot 2^{,2(\mathrm{STDDEV_{fract}}+\mathrm{SQG}) - 2,\mathrm{IN_{fract}} - 2,\mathrm{EXP}}} $$
i.e. $\sigma$ with 4 guard bits below the output LSB. The recurrence truncates and the final requantisation rounds, so the total error is bounded by
$$ |\sigma_{\mathrm{dut}} - \sigma_{\mathrm{exact}}| \le \frac{2}{16} + \frac{1}{2} < 1\ \mathrm{LSB} $$
and the host regression allows exactly 1 LSB - never more. That single LSB is entirely the root’s: it is not an accumulation of upstream error, because there is no upstream error. (Block Variance, which stops one state earlier, is checked at tolerance ZERO.)
The pre-scaling shift saturates: a radicand that does not fit means the root does not fit either, which is precisely the output saturation you asked for with the Saturation property.
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 into $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 -
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 onIN_DVhas no need to be frozen, and there is no reason to stall the tail.
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 drives the $N S_2$ shift, the $N^2$ scaling of the radicand and the
number of serial multiply steps, so the arithmetic and the timing of a block
are always self-consistent.
Timing: the serial tail
Why it is serial
A block has a whole block period of slack after its N-th sample: the next
result is not due for another $2^{\mathrm{EXP}}$ clocks. So there is no reason
to build a parallel datapath for the post-accumulation arithmetic. II=1 is
only needed while ACCUMULATING - one sample per clock, one multiply, two
adds - and the tail has all the time in the world.
This is the only block of the family that runs both serial engines, back to back:
- $S_1 \cdot S_1$ is ONE reused shift-add stage stepped once per clock, consuming one bit of $|S_1|$ per clock, $\mathrm{IN_SW} + \mathrm{EXP}$ steps;
- the square root is ONE reused compare-subtract stage stepped once per clock, producing one result bit per clock, $\mathrm{RTW}$ steps.
The cost is CLOCKS, not multipliers and not comparators: the only multiplier in the whole block is the $x \cdot x$ during accumulation, and there is no DSP in the tail.
The latency contract
OUT_DV pulses for one clock when the tail COMPLETES, L clocks after the
clock on which the N-th sample of the block was accepted - not when that
sample arrives. STDDEV is updated on that same clock and on no other. The
state walk is IDLE -(go)-> MUL x (IN_SW + EXP) -> VAR -> LD -> SQRT x RTW
-> FIN -> IDLE with OUT_DV registered, so
$$ \mathrm{RTW} = \mathrm{STDDEV\ width} + \mathrm{SQG} + 1 = \mathrm{STDDEV\ width} + 5 $$
$$ L = 2 + (\mathrm{IN_SW} + \mathrm{EXP} + 1) + (\mathrm{RTW} + 1) $$
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). Both terms are user visible: a WIDE STDDEV format costs root steps and a LONG BLOCK costs multiply steps.
Block Std Dev and Block Variance are the only blocks in the family whose latency depends on the RUNTIME exponent. A longer block makes $|S_1|$ wider and therefore costs more multiply steps. But a longer block also gives more slack: the block grows exponentially in EXP and the tail only linearly, so the $2^{\mathrm{EXP}} \ge L$ constraint gets easier as EXP grows, not harder.
The 2^EXP >= L rule
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. A completed block is only handed to the tail when the
tail is IDLE, so: no OUT_DV for it, the accumulators are unaffected, later
blocks come out correctly - and there is no error pin. A result is simply
skipped. This is the block of the family where the rule bites hardest, because
it is the block with the longest tail.
Three things guard against it, and one hole remains:
- the property validator refuses a configuration whose minimum exponent exceeds Max Block Exponent, and tells you the tail length and the minimum;
CompileHDLprints the tail length and the minimum usable EXP into the compilation log for every placement;- but
EXPis a PIN. Nothing can stop a user driving it below the minimum at RUN TIME. If you makeEXPruntime programmable from a register interface, clamp it in your own logic - the block will not complain, it will just stop emitting some results.
Worked numbers
| Input | STDDEV format | RTW | L | minimum EXP |
|---|---|---|---|---|
| 8 bit signed | 16 bit (default) | 21 | 33 + EXP | 6 (N = 64) |
| 8 bit signed | 8 bit | 13 | 25 + EXP | 5 (N = 32) |
| 16 bit signed | 16 bit (default) | 21 | 41 + EXP | 6 (N = 64) |
| 16 bit signed | 32 bit | 37 | 57 + EXP | 6 (N = 64) |
Take the first row in full. An 8 bit signed input gives $\mathrm{IN_SW} = 8$; the default STDDEV format (16 integer bits, 0 fractional bits, unsigned) gives $\mathrm{RTW} = 16 + 5 = 21$. So
$$ L = 2 + (8 + \mathrm{EXP} + 1) + (21 + 1) = 33 + \mathrm{EXP} $$
At $\mathrm{EXP} = 5$ that is $L = 38$ and $2^5 = 32 < 38$, so it does not fit; at $\mathrm{EXP} = 6$ it is $L = 39$ and $2^6 = 64 \ge 39$, so it does. The minimum usable EXP is 6, a block of 64 samples - and every larger exponent fits too. With the default $\mathrm{EXP} = 10$ the tail is 43 clocks inside a 1024-sample block: still over 95% idle.
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 |
| 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:
INTEGRATINGrises on the clock after the FIRST sample of a block is accepted and falls on the clock after the N-th - it is high exactly while the block is ACCUMULATING, and never during the tail.BUSYcovers the accumulation and the serial tail. It rises withINTEGRATING, stays high across the tail, and its LAST HIGH CLOCK IS THEOUT_DVPULSE; it falls on the clock after.BUSYhigh withINTEGRATINGlow therefore means “the samples are all in, I am computing” - and on this block that phase is long enough to be genuinely useful.- On a continuous stream the next block starts before the previous tail
ends, so
BUSYnever drops andINTEGRATINGdips for exactly one clock per block boundary - which makes it a free block marker. The two separate visibly only when the input pauses for longer than the tail. SAMPLE_COUNTis a fixed 32 bits and reads 1 after the first accepted sample, N after the N-th. It is NOT cleared at the block end: it HOLDS the final count N through the whole tail and pastOUT_DV, until the first sample of the next block takes it back to 1. So on theOUT_DVclock it reads the length of the block being presented - which is the useful thing to latch alongside the result. OnlyRESETclears 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 result is
requantised into the STDDEV 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: $\sigma$ is in the units of the input and can never exceed the peak-to-peak span of the block, so the input integer bits are always enough. It is never negative, so UNSIGNED buys one bit - which is the default. The total STDDEV width is what sizes the serial root ($\mathrm{RTW} = \mathrm{width} + 5$ steps), so ask for fractional bits when you want sub-LSB noise resolution, but do not make the format wider than you need: every extra bit is one more clock of tail and one step closer to the minimum usable exponent.
Cost
One multiplier for $x^2$ (unavoidable at one sample per clock), two accumulators ($S_1$ at $\mathrm{IN_SW} + $ Max Block Exponent bits, $S_2$ at $2,\mathrm{IN_SW} - 1 + $ Max Block Exponent), one shift-add stage, one compare-subtract stage with a radicand, a remainder and a root register, two barrel shifters and one requantiser. No divider, no second multiplier and no DSP in the tail. Compared with Block Variance, the extra cost of the square root is one compare-subtract stage and $\mathrm{RTW} + 1$ clocks.
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 $\sqrt{\overline{x^2} - \mathrm{mean}^2}$ in exact rational
arithmetic and shares no algorithm with the core; the tolerance is 1 LSB and
not one count more. Std Dev coverage includes pseudo-random input, a
constant block ($\sigma$ exactly zero), a sine with a fractional output
format, an EXP that alternates between blocks - which changes the LENGTH of
the tail from block to block, since the multiply steps follow the exponent -
and IN_DV gaps inside the accumulation. 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;
tb/block-ops/port_check.py preprocesses the real core to prove the symbol’s
pin list matches the entity’s port list for every enable combination, and the
harness #errors at COMPILE TIME if the tail formula in the .cpp, in the
plugin and in the generator ever disagree.