DSP - BLOCK SKEWNESS
The skewness - the normalised THIRD central moment, m3 / sigma^3 - of a block of N consecutive samples. It measures ASYMMETRY: exactly 0 for anything symmetric about its mean, POSITIVE when the long tail points up (occasional large positive excursions on a quiet baseline, which is what a pulse train on a pedestal looks like) and NEGATIVE when it points down. It is dimensionless and gain independent, so it compares directly across channels and gains. NO HISTOGRAM, NO SECOND PASS, NO SAMPLE BUFFER: the third central moment is reached from the raw power sums S1, S2, S3 - three ordinary accumulators - through an EXACT INTEGER identity, which is why a symmetric block gives EXACTLY zero rather than ’nearly zero’ and the block is usable as a trigger. N is a power of two chosen at RUN TIME on the EXP input pin (EXP = 10 means N = 1024), so every N scaling in the identity is a shift and the N^3 factors cancel exactly between numerator and denominator. DSP cost is two real multipliers in the accumulation path (xx and (xx)*x); everything after the accumulators is serial shift-add - three products, one square root and one division - so the tail is LONG and 2^EXP >= L must hold or that block’s result is silently dropped. 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 Skewness block chops the input stream into consecutive blocks of N samples and, at the end of each block, publishes the skewness of that block:
$$ \mathrm{mean} = \frac{1}{N}\sum_{i=0}^{N-1} x_i , \qquad m_k = \frac{1}{N}\sum_{i=0}^{N-1} (x_i - \mathrm{mean})^k $$
$$ \mathrm{skew} = \frac{m_3}{\sigma^3} = \frac{m_3}{m_2^{3/2}} $$
It is the normalised third central moment, and what it measures is ASYMMETRY.
N is a runtime input, not a property. You drive the exponent on the
EXP pin and the block size is $N = 2^{\mathrm{EXP}}$:
| EXP | N | EXP | N |
|---|---|---|---|
| 4 | 16 | 12 | 4096 |
| 6 | 64 | 16 | 65536 |
| 8 | 256 | 20 | 1048576 |
How to read the number
| block | skewness |
|---|---|
| anything symmetric about its mean - a sine, symmetric noise, a two-level square wave | exactly 0 |
| a constant block | 0 (see below - the arithmetic reaches it on its own) |
| a quiet baseline with occasional large POSITIVE excursions - a pulse train on a pedestal | > 0, and the rarer and bigger the pulses, the larger |
| the mirror image - occasional large NEGATIVE excursions | < 0 |
Skewness is dimensionless and gain independent: multiply every sample by ten and it does not move. That is what makes it comparable across channels, across gains and across days, and it is why the output format spends its bits on the fraction rather than on range.
What it is FOR
- Detecting one-sided activity on a quiet channel. A baseline that is clean has skewness 0; the same baseline with a few unipolar pulses on it does not, even when the mean has barely moved and the variance is still small. Because the answer does not depend on gain, one threshold works for every channel.
- Telling a pulse train from noise. Noise is symmetric, pulses are not. Where the variance says “something is happening” the skewness says “and it only goes one way”.
- Polarity discrimination. The sign is the information: it tells you which way the tail points, which is exactly what distinguishes a positive pulse train from a negative one, or an over-shooting baseline restore from an under-shooting one.
- Distribution shape monitoring in slow control - a channel whose skewness starts drifting away from zero is developing an asymmetry (leakage, saturation on one rail, a pile-up tail) long before the mean or the RMS notice.
No histogram, no second pass, no buffer
The obvious way to get a central moment is to compute the mean, go back over the samples and accumulate $(x - \mathrm{mean})^3$. That needs the block stored, i.e. a memory of $2^{\mathrm{EXP}}$ samples and a second pass - which would halve the throughput and put a BRAM in the design.
This block does none of that. It keeps three ordinary accumulators,
$$ S_1 = \sum x_i , \qquad S_2 = \sum x_i^2 , \qquad S_3 = \sum x_i^3 $$
each updated once per clock, and recovers the third central moment at the end of the block from an exact integer identity (see the description below). There is no sample buffer, no histogram and no second pass anywhere in the block.
Cost
Per clock, while accumulating: two real multipliers - one for $x \cdot x$ and one for $(x \cdot x) \cdot x$ - because they run at one sample per clock and cannot be serialised, plus the three accumulators.
In the tail: one reused shift-add stage (it does all three products) and one reused compare-subtract stage each for the square root and for the division. What the tail costs is CLOCKS, not multipliers - there is no DSP in the tail at all. The clocks are what you have to budget for; see “Timing” below, because this is one of the long tails of the family.
When to use this instead of Block Statistics
The all-in-one Block Statistics block is not deprecated, but it does
not compute skewness - there is no SKEWNESS pin over there, so for this
number there is no choice to make: this block is the only way to get it.
The usual family rule still applies to everything around it:
- you want several statistics of the SAME block - mean and RMS and min/max of the same N samples - place one Block Statistics block. They share one accumulator set and one serial tail, so the second and third statistic are nearly free.
- you want exactly one number - use the matching per-operator block. Then you synthesise only that number, and nothing else reaches the synthesiser.
A Block Skewness block placed next to a Block Statistics block accumulates the same stream a second time: $S_1$ and $S_2$ exist in both. That is the price of a statistic the all-in-one block does not have, and it is usually worth paying - the alternative is a histogram.
Pin Description
IN_DV is high.
The value of the skewness does not depend on the scaling of this port -
the statistic is dimensionless.
'1'. (There is deliberately no CE pin - to stall the block,
gate this.)
Block size exponent, runtime programmable: the block is $N = 2^{\text{EXP}}$ samples long. 6 bits unsigned, accepted range 0 .. Max Block Exponent; larger values are clamped to Max Block Exponent. Sampled on the first accepted sample of a block and held for that whole block, so a change takes effect on the NEXT block. It must satisfy $2^{\text{EXP}} \ge L$ at run time or the block’s result is dropped without warning, and on this block L is in the hundreds of clocks
- see “Timing”. Unconnected defaults to 10 (N = 1024).
OUT_DV clock and on
no other; it holds the previous block’s result until then.
SKEWNESS 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
products-root-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. It enters the tail length through IN_SW, three clocks per bit.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. It does not change the value of the skewness - the statistic is scale free and the input fractional bits cancel out of the identity - but it does widen IN_SW, and therefore the tail, by three clocks per bit.Default: 0
Options: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
Select whether the input sample is signed (two’s complement) or unsigned.
SIGNED (two’s complement) or UNSIGNED input. Default SIGNED. An UNSIGNED input costs one extra bit internally, because a sample has to be promoted to signed before it can be accumulated - and that bit widens the three serial products, the root and the divider, so it costs several clocks of tail. Note that an unsigned stream can still be skewed either way: the moment is taken about the block’s own mean.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 $S_1$, $S_2$ and
$S_3$ by one bit per unit and lengthens the serial tail - it enters
both ROOTW (two clocks per bit) and CNUMW (three clocks per bit), which is
not the case on the cheaper blocks of the family. It must be at least the
minimum usable exponent of the configuration, or the property page
refuses the settings. 1..31, default 20, i.e. blocks of up to 1048576
samples out of the box. Keep it at the largest block you actually use.
Default: 20
Options: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
Number of INTEGER bits of the SKEWNESS output (the sign, when present, uses one of them).
Integer bits of the SKEWNESS output (the sign uses one of them). 1..64, default 6. Skewness is dimensionless and sits within a few units for any realistic signal, so 6 bits - up to 31 in magnitude - is generous; there is nothing to gain from more.Default: 6
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 SKEWNESS 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 SKEWNESS output. 0..64, total width 2..64 bits, default 10 (steps of 1/1024). This is where the resolution goes - but it also enters CNUMW, so every fractional bit adds three clocks to the tail, and the tail has to fit inside the block. Do not make it wider than you need.Default: 10
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 SKEWNESS output is signed (two’s complement) or unsigned.
SIGNED (two’s complement) or UNSIGNED SKEWNESS output. Default SIGNED, and you almost certainly want to keep it: the sign is the information - it says which way the tail points. An UNSIGNED format clamps every negatively-skewed block to 0 and throws that away.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 products, 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. 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 a result has to be requantised into a coarser output format. TRUNCATE: drop the bits. On this block it has no effect on the value: the quotient already carries exactly SKEW Fractional Bits fractional bits, so there is no shift left to round, and the square root is always truncated regardless. The property exists for consistency across the family. 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 SKEWNESS format (symmetric bounds for signed formats). NO: wrap around. It only matters for a block whose skewness genuinely exceeds the format range, which for the default 6 integer bits means a magnitude above 31. TheD == 0
(constant block) case never reaches it: the numerator is exactly 0 there,
so that block reports 0, not a saturated value. Default YES.
Default: YES
Options: NO YES
The exact integer identity
Expanding the third central moment and clearing the denominators turns the definition into a ratio of two integers built from the raw power sums:
$$ A = N^2 S_3 - 3 N S_1 S_2 + 2 S_1^3 ;=; N^3 m_3 $$
$$ D = N S_2 - S_1^2 ;=; N^2 m_2 ;\ge; 0 $$
$$ \mathrm{skew} = \frac{A}{D^{3/2}} $$
Every factor of $N$ in there is a shift - N is a power of two - and the $N^3$ scalings cancel exactly between numerator and denominator. So neither $N$ nor the input fractional bits appear anywhere in the arithmetic: the answer is a pure ratio of two exact integers, built without a single premature rounding.
Why a symmetric block gives EXACTLY zero
$A$ is an exact integer, and for a sample set that is symmetric about its mean it really is 0 - not “small”, not “within a few LSB”. The reported skewness is therefore exactly 0, with no rounding noise to drift through a threshold.
That is worth having, and it is the reason the block is built this way. A skewness estimator that wanders off zero on symmetric data is useless as a trigger: you would have to put a dead band around zero, and the dead band would have to be sized for the worst case of a quantity you were trying to measure. Here there is nothing to dead-band.
$D^{3/2}$ is BUILT, not approximated
The denominator is an odd power, so a square root is unavoidable. The core takes $\mathrm{root} = \sqrt{D}$ with SQG = 4 guard bits and then multiplies it by $D$:
$$ D \cdot \mathrm{root} = D^{3/2} \cdot 2^{\mathrm{SQG}} $$
and the numerator is shifted by that same SQG so the guard bits cancel. Nothing is ever cubed and square-rooted twice, and there is exactly one root in the block.
A constant block has no skewness, and the arithmetic says so
If every sample equals $c$ then
$$ A = N^3 c^3 - 3 N^3 c^3 + 2 N^3 c^3 = 0 \qquad\text{and}\qquad D = 0 $$
so the zero-denominator path always finds a zero numerator and reports
0. The truncated root cannot break that: $D \ge 1$ gives
$\mathrm{root} \ge \sqrt{2^{2,\mathrm{SQG}}} = 16$, so root == 0 if and
only if D == 0. The saturating branch of the ratio is therefore
unreachable through the ports - a constant block reports 0, never a full-scale
spike.
Accuracy
Two things happen after the exact integers: the root is up to 1 ulp low (at
$2^{-\mathrm{SQG}}$ = 1/16 of a SKEWNESS LSB), and the quotient is
floored - it already comes out at exactly SKEW Fractional Bits
fractional bits, so nothing is shifted or re-rounded afterwards. The answer is
therefore within 1 LSB of the exactly-rounded value.
The host regression (tb/block-ops/run_tb.ps1) checks that against a Python
golden (tb/block-ops/gen_golden.py) which evaluates $m_3 / m_2^{3/2}$ in
exact rational arithmetic and shares no algorithm with the core. Its
tolerance is 1 LSB, plus a small value-dependent allowance that
propagates the truncated root through the division - about
$2,|\mathrm{skew}| / \mathrm{root}$, which is a fraction of an LSB for any
block whose $D$ is of realistic size. The symmetric and constant
scenarios are checked at tolerance 0: they must be exactly 0.
Accumulation and IN_DV
IN_DV is the only qualifier. It says “this clock carries a sample”: a
sample is added into $S_1$, squared into $S_2$, cubed into $S_3$, 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.
There is no LEVEL pin either: a central moment is referred to the block’s
own mean, by definition.
When EXP changes
EXP is clamped to Max Block Exponent and then latched on the first
accepted sample of a block, and held for that whole block. A change
therefore takes effect on the NEXT block: a block in progress always
finishes against the N it was started with, and a block is never emitted
against a different N than the one it was accumulated with. The latched
exponent is the one used for every $N$, $N^2$ and $N^3$ shift in the
identity, so the arithmetic of a block is always self-consistent.
Timing: the serial tail
The state walk
A block has a whole block period of slack after its N-th sample, so all post-accumulation arithmetic is serial. Four passes reuse one shift-add stage, and the root and the division reuse one compare-subtract stage each:
IDLE -(block complete)-> MUL2 (|S1|*|S1|) -> MUL3 (|S1|^2*|S1|)
-> MULM (S2*|S1|) -> VAR (form D and A) -> LD
-> SQRT x ROOTW -> MULD (D*root) x ROOTW -> CLD -> DIV x CNUMW
-> FIN -> IDLE
OUT_DV pulses for one clock when the tail COMPLETES - not when the N-th
sample arrives - and SKEWNESS is updated on that same clock and on no
other.
The latency contract
Counted in clocks from the clock on which the N-th sample of the block was accepted:
$$ L = 3,(\mathrm{IN_SW} + \mathrm{EXP}) + 2,\mathrm{ROOTW} + \mathrm{CNUMW} + 5 $$
where $\mathrm{IN_SW}$ is the signed working width of a sample - the input width, +1 if the input is UNSIGNED (a sample has to be promoted to signed before it can be accumulated) - and the two engine widths are fixed at compile time by your formats:
$$ \mathrm{ROOTW} = \mathrm{IN_SW} + \mathrm{MaxBlockExponent} + 4 $$
$$ \mathrm{CNUMW} = \max\bigl(3,(\mathrm{IN_SW} + \mathrm{MaxBlockExponent}) + \mathrm{SKEW_{fract}} + 4,; \mathrm{SKEWNESS\ width} + 2\bigr) $$
The 4 in both is the square-root guard SQG. The three serial products consume $\mathrm{IN_SW} + \mathrm{EXP}$ bits each, which is where the runtime exponent enters; everything else is fixed at compile time by your formats and by Max Block Exponent.
Note what this means: raising Max Block Exponent lengthens the tail here, unlike on Block Mean where it costs only accumulator width. It widens both the root (one clock per bit, twice - once for the root and once for the $D \cdot \mathrm{root}$ product) and the divider (three clocks per bit). Keep it at the largest block you actually use.
Worked numbers
Take a 16 bit signed input at the default formats (SKEWNESS Q6.10,
Max Block Exponent 20):
- $\mathrm{IN_SW} = 16$
- $\mathrm{ROOTW} = 16 + 20 + 4 = 40$
- $\mathrm{CNUMW} = \max(3 \cdot 36 + 10 + 4,; 16 + 2) = \max(122, 18) = 122$
- $L = 3,(16 + \mathrm{EXP}) + 80 + 122 + 5 = 255 + 3,\mathrm{EXP}$
At EXP = 8 that is $L = 279$ clocks and $2^8 = 256 < 279$: it does not fit. At EXP = 9 it is $L = 282$ and $2^9 = 512 \ge 282$: it does. The minimum usable EXP is 9, a block of 512 samples, and every larger exponent fits too - the block grows exponentially while the tail grows linearly. At the default EXP = 10 the tail is 285 clocks inside a 1024-sample block, i.e. about 72 % idle.
A few more configurations, to show where the clocks go:
| Input | Max Block Exponent | SKEWNESS | ROOTW | CNUMW | L | minimum EXP |
|---|---|---|---|---|---|---|
| 8 bit s | 8 | Q6.10 | 20 | 62 | 131 + 3·EXP | 8 (N = 256) |
| 8 bit s | 12 | Q6.10 | 24 | 74 | 151 + 3·EXP | 8 (N = 256) |
| 8 bit s | 20 | Q6.10 | 32 | 98 | 191 + 3·EXP | 8 (N = 256) |
| 16 bit s | 20 | Q6.10 | 40 | 122 | 255 + 3·EXP | 9 (N = 512) |
| 16 bit u | 20 | Q6.10 | 41 | 125 | 263 + 3·EXP | 9 (N = 512) |
| 16 bit s | 20 | Q6.20 | 40 | 132 | 265 + 3·EXP | 9 (N = 512) |
Budget a long block. Even the cheapest configuration in that table needs N = 256; there is no useful setting of this block that works at EXP = 5. If your application really wants a short block, the number you can still get cheaply is the variance (Block Variance, whose tail is $\mathrm{IN_SW} + \mathrm{EXP} + 3$ clocks) - but it will not tell you anything about asymmetry.
The 2^EXP >= L rule, and what happens when it is broken
The tail of one block must finish before the next block completes:
$$ 2^{\mathrm{EXP}} \ge L $$
A completed block is only handed to the tail when the tail is IDLE, so if a
block completes while the previous tail is still running, that block’s
result is DROPPED: there is no OUT_DV for it, the accumulators are
unaffected and the following blocks come out correctly, but a result is
silently skipped. There is no error pin and no overrun pin for it.
Two things guard against it, and one hole remains:
- the property page refuses a configuration whose minimum exponent exceeds Max Block Exponent, telling you the tail length and the exponent it would need;
CompileHDLprints both numbers - the worst-case tail length and the minimum usable EXP - into the compilation log for every placement;- but
EXPis a PIN. Nothing can stop a design driving it too low at RUN TIME, and with L in the hundreds of clocks this is the block where that bites hardest: anEXPof 6 that would be perfectly reasonable on Block Mean drops every result here. IfEXPis under software control, clamp it in software to the minimum the compiler printed.
Knowing where the block is: BUSY, INTEGRATING and SAMPLE_COUNT
Three optional status outputs, all defaulting to NO. They answer different questions, and on this block the difference is at its most useful, because the tail is long:
INTEGRATING |
BUSY |
|
|---|---|---|
| accumulating the block | 1 | 1 |
| serial tail computing | 0 | 1 |
| idle | 0 | 0 |
Every output of this block is a register, so each status bit is observed on the clock after the event that sets it:
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.BUSYhigh withINTEGRATINGlow is precisely the three-products-root-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 the final count through the tail and pastOUT_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 - 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
Both data ports carry their own fixed point format (integer bits, fractional bits, sign), the same convention as the Fixed P. family. The quotient already carries exactly SKEW Fractional Bits fractional bits, so the final requantisation is a zero-bit shift: the value is not rounded again on the way out, only checked against the format bounds, with saturation symmetric for signed formats as everywhere else in the toolchain.
Sizing is unusual here, and easy:
- skewness is dimensionless and for any realistic signal sits within a few units, so the default 6 integer bits (up to 31 in magnitude) is generous. The bits belong in the fraction: the default 10 give steps of 1/1024, and each one costs three clocks of tail through CNUMW.
- keep it SIGNED. The sign IS the information - an UNSIGNED
SKEWNESSformat throws away the direction of the asymmetry and clamps every negatively-skewed block to 0. UNSIGNED is offered for completeness, not as a recommendation. - the input Q format does not enter the value at all: the identity is
scale free, so
IN_BitsFractchanges nothing about the answer (only the tail length, through IN_SW).
Verification
The core is regression tested by a host-side csim harness
(tb/block-ops/run_tb.ps1) that runs one simulated clock at a time and
follows OUT_DV. The expected values come from tb/block-ops/gen_golden.py,
which evaluates $m_3 / m_2^{3/2}$ in exact rational arithmetic and shares no
algorithm with the core. Coverage includes a positively skewed pattern -
a quiet baseline with large positive excursions - and its exact mirror
image, which is what pins the SIGN of the third moment (a symmetric vector
gives 0 either way and proves nothing); a symmetric extremes pattern,
which must read exactly 0; a constant block (the D == 0 path, which
must read 0 and not saturate); pseudo-random input; and an UNSIGNED 8 bit
input to exercise the promotion. The tail length is checked at compile
time: three copies of the formula exist - the BSK_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.