DSP - BLOCK COEFFICIENT OF VARIATION
The coefficient of variation of a block of N consecutive samples, cv = sigma / mean: the standard deviation expressed as a FRACTION OF THE MEAN, so it is dimensionless and gain independent - multiply every sample by ten and it does not move. That is what makes it the right number for ‘is this channel noisier than that one’ when the two sit at different gains. Both the input Q scaling and N cancel exactly, so internally it is a plain ratio of two raw integers, sqrt(NS2 - S1S1) / S1. N is a power of two chosen at RUN TIME on the EXP input pin. Three serial engines run back to back - a shift-add square of S1, a digit-recurrence square root and a restoring division - each reusing ONE stage, so the cost is CLOCKS, not comparators, and there is no DSP in the tail: L = 2 + (IN_SW + EXP + 1) + (ROOTW + 1) + (CNUMW + 1), and 2^EXP >= L must hold or that block’s result is silently dropped. SIGNED by default, because the mean is: cv carries the sign of the mean. mean == 0 is a real case and saturates; an all-zero block reports 0. Accurate to 1 LSB. It is the reciprocal of Block SNR. IN_DV is the only qualifier and there is deliberately no CE pin. Optional BUSY / INTEGRATING / SAMPLE_COUNT status outputs.
Introduction
The Block Coefficient of Variation block chops the input stream into consecutive blocks of N samples and, at the end of each block, publishes the relative dispersion 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{CV} = \frac{\sigma}{\bar{x}} $$
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 |
What it is FOR
$\sigma$ on its own answers the wrong question whenever two channels sit at different gains: a detector at 10x gain has ten times the $\sigma$ of the same detector at 1x and is not one bit noisier. The coefficient of variation is dimensionless and scale free - multiply every sample by ten and it does not move - so it is the number that actually compares:
- “is this detector noisier than that one” across channels with different gains, different couplings or different units;
- relative stability / RSD of a slow-control reading, a supply rail or a temperature - the figure that specifications are normally written in (“stable to 0.1%”);
- normalised noise monitoring over time on a channel whose absolute level
drifts, where a rising
CVmeans the noise grew rather than the signal shrank; - Fano-like / counting statistics checks, where a Poisson process has a predictable $\sigma/\mathrm{mean}$ and a departure from it means something else is going on.
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$:
$$ \sigma = \frac{\sqrt{\mathrm{var_num}}}{N}, \qquad \bar{x} = \frac{S_1}{N}, \qquad \mathrm{CV} = \frac{\sqrt{\mathrm{var_num}}}{S_1} $$
No $2^{-\mathrm{IN_{fract}}}$ and no N anywhere. The input Q format does not enter this core’s arithmetic at all - it is a plain ratio of two raw integers. (The input width still sets the length of the serial engines; it is the scaling that cancels.)
Signed by default, and on purpose
$\sigma$ is non negative but the mean is not, so CV 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.
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.
Block Coefficient of Variation or Block SNR?
Block SNR computes $\mathrm{mean}/\sigma$ - 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 spread dominates, $\sigma \gtrsim$ mean | cv around or above 1 | Block Coefficient of Variation |
| the mean dominates, mean $\gg \sigma$ | snr in the tens or hundreds | Block SNR |
A clean 60 dB measurement has cv = 0.001, which in the default Q4.12 format is four LSBs - use Block SNR instead. A noisy or nearly-zero-mean channel has cv around 1 and an snr near 0 - use this block. The two cost exactly the same tail for the same number of output fractional bits.
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 coefficient-of-variation pin. So:
- you want several statistics of the SAME block and the coefficient of variation - 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’ STDDEV by its MEAN 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_DV is high. The Q scaling cancels out of the ratio; only the
width matters, and it sets the length of the serial squarer.
'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.
CV 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.
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.
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.
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 atOUT_DVonly when the input STOPS for the whole tail. On a CONTINUOUS stream the next block has already started by then, soOUT_DVshows how far into it the input has got, not N - the clock that always reads N is the oneINTEGRATINGfalls on. 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. 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
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 - $\text{cv}$ 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
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 UNSIGNEDCV output is safe.
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 $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 accuracy of
the ratio independent of the output width. 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
Number of INTEGER bits of the CV output (the sign, when present, uses one of them).
Integer bits of the CV output. 1..64, default 4. A coefficient of variation is well under 1 for a well behaved signal, so 4 bits (up to ±7.99 signed) is generous; raise it only if you expect the spread to dominate the mean - and consider Block SNR instead if it usually does not. Unlike the fractional bits, these cost no tail clocks.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
Number of FRACTIONAL bits of the CV 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 CV output. 0..64, total width 2..64 bits, default 12 (steps of 1/4096, i.e. 0.024%). This is where the resolution goes, and each bit adds exactly one clock to the divider and therefore to the tail. Do not make it wider than you need - but do not starve it either: a clean signal has a very small cv, and if that is your case, Block SNR is the better fit.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
Select whether the CV output is signed (two’s complement) or unsigned.
SIGNED or UNSIGNED CV output. Default SIGNED, on purpose: $\sigma$ is non negative but the mean is not, so cv carries the sign of the mean. 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.Default: SIGNED
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, 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, the squarer, the root and the divider are running”. 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. On a block with a tail this long it is 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
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 quotient has to be requantised into the output format. TRUNCATE: drop the bits (cheaper, adds a negative bias). It applies to the final requantisation only - the restoring divider always floors the quotient, and the square root always truncates below its guard bits. Default ROUND.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 CV format (symmetric bounds for signed formats). NO: wrap around. The twomean == 0 cases
ignore this property: a zero mean with a non-zero spread 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)-> 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 CV 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{ROOTW} + \mathrm{CV_{fract}},; \mathrm{CV\ width} + 6\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{CV_{fract}} + \mathrm{EXP} + 13 $$
| knob | clocks of tail per unit |
|---|---|
input bit (IN_BitsInt + IN_BitsFract, +1 if UNSIGNED) |
3 |
| Max Block Exponent | 2 |
CV_BitsFract |
1 |
CV_BitsInt |
0 (it does not reach the winning term of the $\max$) |
runtime EXP |
1 |
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 accuracy of the ratio independent of how many bits you gave the answer, 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
CV format (Q4.12 signed):
- $\mathrm{ROOTW} = 16 + 20 + 4 = 40$
- $\mathrm{CNUMW} = \max(40 + 12,\ 16 + 6) = 52$
- $L = 2 + (16 + \mathrm{EXP} + 1) + (40 + 1) + (52 + 1) = 113 + \mathrm{EXP}$
- at EXP = 10 (N = 1024) that is 123 clocks, well inside the block;
- the smallest exponent with $2^{\mathrm{EXP}} \ge 113+\mathrm{EXP}$ is EXP = 7, i.e. N = 128 ($128 \ge 120$; at EXP = 6, $64 < 119$).
A few more configurations, to show where the clocks go:
| Input | Max Block Exp | CV | ROOTW | CNUMW | L(EXP) | min EXP |
|---|---|---|---|---|---|---|
| 8 bit s | 12 | Q4.12 | 24 | 36 | 73 + EXP | 7 (N = 128) |
| 16 bit s | 12 | Q4.12 | 32 | 44 | 97 + EXP | 7 (N = 128) |
| 16 bit s | 20 | Q4.12 | 40 | 52 | 113 + EXP | 7 (N = 128) |
| 16 bit s | 20 | Q4.4 | 40 | 44 | 105 + EXP | 7 (N = 128) |
| 16 bit u | 20 | Q4.12 | 41 | 53 | 116 + EXP | 7 (N = 128) |
| 24 bit s | 24 | Q6.14 | 52 | 66 | 147 + 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.
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 the mean is zero
mean == 0 is a real case, and it is not an error: a zero-mean signal has
an unbounded coefficient of variation. The core detects it on the denominator
($S_1 = 0$) and forces the answer:
| $S_1$ | $\sigma$ | CV reports |
|---|---|---|
| 0 | > 0 | the largest value the CV format can represent (the ratio is unbounded) |
| 0 | 0 | 0 (an all-zero block has no spread) |
| ≠ 0 | any | the ratio, with the sign of the mean |
Both forced cases ignore the Saturation property, because neither is a requantisation overflow - they are the definition of the ratio breaking down. In the unbounded case the sign is meaningless (the mean is zero, so it is neither positive nor negative), and the saturation is taken as positive.
Note that a bipolar signal centred on zero will land in this case routinely
- which is a good reason to prefer
CVon unipolar quantities, and to reach for Block Std Dev when the mean genuinely is zero.
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 numerator root is taken at full internal precision, with
$\mathrm{SQG} = 4$ guard bits below the LSB the divider needs - so the
accuracy of the ratio does not depend on how many bits you gave the
answer. (Contrast Block Crest Factor, whose denominator is an internal RMS
in a user-chosen Q format, and whose errors therefore compound.) The root is
up to 1 ulp low at $2^{-\mathrm{SQG}}$ of a CV LSB, the quotient is floored
and the final requantisation rounds, so
CVis accurate to within 1 LSB.
The host regression enforces exactly 1 LSB, never more, against a Python golden computed in exact rational arithmetic.
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 - 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, CV values taken at different EXP
are directly comparable - unlike, say, Block Energy. 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:
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.BUSYhigh withINTEGRATINGlow is precisely the square-root-then-divide tail.BUSYcovers the accumulation and the tail. It rises withINTEGRATING, stays high across the tail, and its LAST HIGH CLOCK IS THEOUT_DVPULSE; it falls on the clock after.- 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. 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 N through the tail and pastOUT_DV, until the first sample of the next block takes it back to 1. so it reads N atOUT_DVonly when the input STOPS for the whole tail; on a CONTINUOUS stream the next block has already started andOUT_DVshows how far into it you are. The clock that always reads N is the oneINTEGRATINGfalls on - 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. OnlyRESETclears it to 0.
Q formats
IN and CV 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 CV 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 mean == 0 cases above, which ignore
the Saturation property.
Sizing hints:
- a coefficient of variation is usually well under 1 for a well behaved signal, so the default 4 integer bits (up to ±7.99) is generous; raise it only if you expect the spread to dominate the mean;
- the fractional bits are where the resolution goes - the default 12 give steps of 1/4096, i.e. 0.024% - and each one costs exactly one clock of tail;
- keep the default SIGNED unless you know the mean is positive; an UNSIGNED format reports 0 for every negative-mean block.
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 $\sigma/\bar{x}$ in exact rational arithmetic and shares no
algorithm with the core; the tolerance is exactly 1 LSB, never more.
Coverage includes pseudo-random and sinusoidal inputs, a constant block
(cv = 0), a zero-mean block (the forced saturation case), 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 is
what pins the scale invariance), maximum positive and maximum negative
samples, unsigned input, truncate instead of round, 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 BCF_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.