DSP - BLOCK CORRELATION
The PEARSON CORRELATION COEFFICIENT r = cov(a,b)/(sigma_a*sigma_b) of TWO input streams over a block of N consecutive sample PAIRS. r lives in [-1, +1], is DIMENSIONLESS and is GAIN INDEPENDENT - it answers ‘do these two channels move together’, not ‘by how much’, which is exactly what Block Covariance cannot give you. IN_A and IN_B are sampled TOGETHER by ONE IN_DV, so the pairing is positional and the two streams cannot slip inside a block. N is a power of two chosen at RUN TIME on the EXP pin. Everything is built from exact integer accumulators, so |r| <= 1 is GUARANTEED BY THE ARITHMETIC (Cauchy-Schwarz on integers) rather than by a clamp: the default Q2.14 SIGNED format never saturates on a legitimate value and +1.0 lands exactly on 16384. A constant channel is reported as 0, and the arithmetic reaches that on its own. The cost is honest and large: FOUR serial products, ONE serial square root and ONE serial division, which is THE LONGEST TAIL IN THE FAMILY - about 350 clocks at the default formats, so budget a long block. IN_DV is the only qualifier and there is deliberately no CE pin. Optional BUSY / INTEGRATING / SAMPLE_COUNT status outputs.
Introduction
The Block Correlation block chops two input streams into consecutive blocks of N sample pairs and, at the end of each block, publishes the Pearson correlation coefficient of that block:
$$ r = \frac{\mathrm{cov}(a,b)}{\sigma_a , \sigma_b} = \frac{\sum_i (a_i - \mu_a)(b_i - \mu_b)} {\sqrt{\sum_i (a_i - \mu_a)^2}\ \sqrt{\sum_i (b_i - \mu_b)^2}} \qquad \in [-1, +1] $$
$r$ is the covariance normalised by both standard deviations, so it is dimensionless and gain independent:
| r | meaning |
|---|---|
| +1 | a perfect straight line with positive slope |
| -1 | a perfect straight line with negative slope |
| 0 | no linear relationship at all |
Change the gain of either channel and $r$ does not move. Change its offset and $r$ does not move either - the means are removed. That is exactly what Block Covariance cannot give you: a covariance of 5000 means nothing on its own, while an $r$ of 0.98 says the two channels are the same signal.
TWO inputs, ONE qualifier
IN_A and IN_B are sampled on the same clock, by the same IN_DV. The
pairing is therefore positional: pair i of a block is whatever was on
the two pins on the i-th clock that IN_DV was high, and there is no way
for the two streams to slip relative to each other inside a block. There is
no second data-valid, no per-stream FIFO and no alignment logic to get wrong -
align the two channels upstream, once.
For the same reason both pins carry the SAME input Q format: one set of
IN properties covers IN_A and IN_B. That said, note that $r$ is gain
independent, so a scale mismatch between the two channels does not
matter - only a time misalignment does.
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}}$ pairs:
| EXP | N | EXP | N |
|---|---|---|---|
| 8 | 256 | 14 | 16384 |
| 9 | 512 | 16 | 65536 |
| 10 | 1024 | 20 | 1048576 |
The table starts at 256 on purpose: this block needs a long one. See “The serial tail” below.
What it is FOR
$r$ is the standard “are these two the same signal?” number, and being gain independent it works on channels whose gains you do not control:
- coincidence and common-mode detection between two detector channels;
- crosstalk and pickup hunting - a persistent non-zero $r$ between two supposedly independent channels is pickup, and its sign tells you the polarity of the coupling;
- sensor redundancy / health monitoring: two sensors watching the same thing must track each other, and $r$ falling away from 1 is a fault signature that no absolute threshold can express;
- reference subtraction: $r$ tells you whether a reference channel is worth subtracting at all, before you spend logic on doing it;
- template matching at block rate, when the template is streamed on the second pin.
Tie IN_B to IN_A and $r$ comes out exactly +1.0 - which is 16384 in
the default Q2.14 - a free self-check you can run on live hardware.
Correlation or covariance?
- Block Covariance answers how much: it keeps the units of $a \times b$ and it scales with the gain of either channel. It costs one multiplier, one serial product and a tail of a few tens of clocks.
- This block answers whether: dimensionless, bounded, gain independent. It costs three multipliers, four serial products, a square root, a division and hundreds of clocks of tail.
If you already have a Block Covariance and a Block Std Dev on each channel you could form the ratio yourself - but you would be dividing three separately requantised numbers, and the result would be neither bounded to $[-1,+1]$ nor accurate near zero. This block does the whole thing in exact integers and only requantises once, at the end.
Cost - honestly
This is the most expensive block of the family. Per clock, while accumulating: three multipliers ($a^2$, $b^2$, $ab$) and five accumulators. In the tail, one reused shift-add / compare-subtract stage driven through six phases:
| phase | what | steps |
|---|---|---|
| MULA | $S_a \cdot S_a$ | IN_SW + EXP |
| MULB | $S_b \cdot S_b$ | IN_SW + EXP |
| MULAB | $S_a \cdot S_b$ | IN_SW + EXP |
| MULP | $D_x \cdot D_y$ | VNW |
| SQRT | $\sqrt{D_x D_y}$ | RTW |
| DIV | $\mathrm{cov_num} / \mathrm{root}$ | CNUMW |
What you pay for the tail is CLOCKS, not multipliers: there is no DSP in the tail at all, only one shared shift-add stage, one shared digit-recurrence root and one shared restoring divider. But those clocks are many - see the worked numbers.
When to use this instead of Block Statistics
The all-in-one Block Statistics block is not deprecated, but it does not compute a correlation coefficient: it is a single-stream block and has no cross-product accumulator, so there is no counterpart to this statistic over there. The usual rule still applies to everything else you may want:
- you want several statistics of the SAME single stream - mean and RMS and min/max of the same N samples - use Block Statistics. They share one accumulator and one serial tail, so the second and third statistic are nearly free.
- you want exactly one number - use a per-operator block. Then you synthesise only that number, and nothing else reaches the synthesiser.
A Block Std Dev placed next to this one to get $\sigma_a$ will duplicate the $S_a$ / $S_{aa}$ accumulators and add a second square root. Given the size of this block’s tail, that is worth thinking about before you add it.
Pin Description
IN_B, accumulated, and counted towards N, only on the clocks where
IN_DV is high. Tie it to IN_B and the block reports exactly +1.0
(16384 in the default Q2.14) - a free self-check on live hardware.
IN_A - one set of
IN properties covers both pins. It is sampled on the same clock, by
the same IN_DV, so the pairing is positional and the two streams
cannot slip relative to each other inside a block. A scale difference
between the channels does not matter ($r$ is gain independent); a time
misalignment does, so align them upstream.
IN_A and IN_B are both taken, and the pair 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.)
OUT_DV clock and on no other; it holds the previous
block’s result until then.
CORRELATION 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, and on this block it is the
failure mode to look for first.
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 sample PAIRS 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 pair 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. On this block that is also the cheapest run-time confirmation that the block was long enough for the tail. 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 samples (the sign, when present, uses one of them). 1..64. Default 16. It applies to BOTHIN_A and IN_B - the
two streams share one Q format. Every input bit costs nine clocks of
serial tail, so do not ask for more width than the ADC actually delivers.
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 samples, for both pins. 0..64. Total input width must be 2..64 bits. Default 0. The value of $r$ does not depend on where the binary point is - the scalings cancel in the ratio - so fractional bits here buy nothing except tail length. They cost the same nine clocks each as integer bits.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 inputs, for both pins. 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 nine clocks of tail like any other.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}}$ sample pairs long, and theEXP
input is clamped to this value at run time. On this block it is the
expensive knob: it widens the variance numerators, and therefore the
$D_x D_y$ product, its square root and the division, so each unit costs
six clocks of serial tail on top of the register width. Keep it at the
largest block you actually use. It must also be at least the minimum
usable exponent of the configuration - with a 16 bit signed input and
Q2.14 output that means 8 or more, and anything lower is refused by
the property page. 1..31, default 20.
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 CORRELATION output (the sign, when present, uses one of them).
Integer bits of the CORRELATION output. 1..64, default 2, and 2 is the right answer: $|r| \le 1$, so two integer bits cover the value and its sign with room to spare (Q2.14 holds up to 1.99994 and $+1.0$ is 16384). Raising it only throws resolution away. It costs no clocks.Default: 2
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 CORRELATION 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 CORRELATION output. 0..64, total width 2..64 bits, default 14 - steps of $1/16384 \approx 6\times10^{-5}$. This is where all the resolution goes, since the integer part is fixed by $|r| \le 1$. Each fractional bit adds one clock to the serial divider, which is the cheapest bit in this block.Default: 14
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 CORRELATION output is signed (two’s complement) or unsigned.
SIGNED or UNSIGNED CORRELATION output. Default SIGNED, and it should stay that way: an anti-correlation ($r < 0$) is usually the whole reason for measuring $r$, and an UNSIGNED format clamps every negative block to 0. Choose UNSIGNED only when the two channels can never move in opposite directions.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 pair 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 pairs are
all in, the products, the root and the divider are running” - which on
this block is most of the time. 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 sample
pairs accumulated so far in the current block, holding the final count
through the tail and past OUT_DV. On this block it is the cheapest way
to confirm at run time that EXP really is large 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 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 serial quotient already comes out at CORR Fractional Bits (the root’s guard bits are cancelled by the numerator’s shift), so the final requantisation has nothing to shift and nothing to round. The quotient itself is always floored by the restoring divider. 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 CORRELATION format (symmetric bounds for signed formats). NO: wrap around. With the default Q2.14 it never fires, because $|r| \le 1$ is guaranteed by the arithmetic and 1.0 is well inside the format. It only becomes reachable if you choose a format too narrow for the $[-1,+1]$ range. Default YES.Default: YES
Options: NO YES
The exact-integer form
Everything is built out of exact integer accumulators, so no quotient is ever truncated before the end. With
$$ S_a = \sum_i a_i, \quad S_b = \sum_i b_i, \quad S_{aa} = \sum_i a_i^2, \quad S_{bb} = \sum_i b_i^2, \quad S_{ab} = \sum_i a_i b_i $$
the block forms
$$ D_x = N S_{aa} - S_a^2 \ \ge 0, \qquad D_y = N S_{bb} - S_b^2 \ \ge 0, \qquad \mathrm{cov_num} = N S_{ab} - S_a S_b $$
$$ r = \frac{\mathrm{cov_num}}{\sqrt{D_x D_y}} $$
Every $N\cdot$ is a shift (N is a power of two) and every product is one pass of one reused shift-add stage.
The $N^2$ scalings of the covariance and of the two variances cancel EXACTLY in the ratio, which is why neither $N$ nor the input fractional bits appear anywhere in the arithmetic - and why $r$ is dimensionless without anybody having to arrange it.
|r| <= 1 is guaranteed by the ARITHMETIC, not by a clamp
$\mathrm{cov_num}^2 \le D_x D_y$ is the Cauchy-Schwarz inequality on integers, and it holds exactly here because nothing was rounded on the way. There is no output clamp forcing the range - the range is a property of the numbers.
Two consequences:
- the default Q2.14 SIGNED format never saturates on a legitimate value. Two integer bits cover the value and its sign; the largest magnitude the arithmetic can produce is $1.0 = 16384$, and the format holds up to 32767.
- +1.0 lands exactly on 16384 and $-1.0$ exactly on $-16384$. A perfect correlation is a round number you can compare against, not “something near the top of the scale”.
A CONSTANT channel is reported as 0 - and the arithmetic gets there alone
If $a$ is constant then $S_{ab} = a,S_b$ and $S_a = N a$, so $\mathrm{cov_num}$ is exactly zero and $D_x$ is exactly zero too. The zero-denominator path therefore always finds a zero numerator and reports 0.
So there is no configuration in which a constant channel produces a saturated correlation. (The shared core does contain a “divide by zero saturates” path - it is the one place the family expresses an unbounded ratio - but on this block its saturating branch is unreachable through the ports. Proof that the truncated root cannot break this: root $= 0$ means $\sqrt{D_x D_y} < 2^{-\mathrm{SQG}}$, hence $|\mathrm{cov_num}| \le \sqrt{D_x D_y} < 1$, hence $\mathrm{cov_num} = 0$.)
Accuracy
The denominator root carries 4 guard bits, which the numerator’s shift cancels exactly, and the quotient is floored. The answer is therefore within 1 LSB of the exactly-rounded value - and the host regression enforces exactly 1, not “a few counts”.
This is one place where the block is better than doing the same job with three separate blocks and an external divider: there is exactly one rounding in the whole computation.
Accumulation and IN_DV
IN_DV is the only qualifier. It says “this clock carries a sample
PAIR”: $a^2$, $b^2$ and $ab$ are formed, all five accumulators advance, and
the pair counts towards N, exactly on the clocks where IN_DV is high. Clocks
with IN_DV low are ignored completely - whatever sits on the two input pins
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_A and IN_B - and at
N = 1024 the tail does fit, at 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- 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 pair 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. Every $N\cdot$
shift in the tail uses that latched exponent, so the arithmetic of a block is
always self-consistent. Remember that EXP also has to satisfy
$2^{\mathrm{EXP}} \ge L$ at run time, every time, and on this block that is
a real constraint rather than a formality.
Timing: the serial tail
The latency contract
OUT_DV pulses for one clock when the tail COMPLETES, L clocks after the
clock on which the N-th pair of the block was accepted - not when that pair
arrives. CORRELATION is updated on that same clock and on no other. The
state walk is
IDLE -(go)-> MULA x (IN_SW+EXP) -> MULB x (IN_SW+EXP) -> MULAB x (IN_SW+EXP)
-> VAR -> MULP x VNW -> LD -> SQRT x RTW -> CLD -> DIV x CNUMW
-> FIN -> IDLE
with OUT_DV registered, so
$$ L = 3,(\mathrm{IN_SW} + \mathrm{EXP}) + 1 + \mathrm{VNW} + 1 + \mathrm{RTW} + 1 + \mathrm{CNUMW} + 1 + 1 $$
with the engine widths fixed at compile time by your formats:
$$ \mathrm{VNW} = 2,(\mathrm{IN_SW} + \mathrm{MaxBlockExponent}), \qquad \mathrm{RTW} = \mathrm{VNW} + 4 $$
$$ \mathrm{CNUMW} = \max\bigl(\mathrm{VNW} + \mathrm{CORR_{fract}} + 4,; \mathrm{CORR\ width} + 2\bigr) $$
where $\mathrm{IN_SW}$ is the signed working width of a sample: the input width, +1 if the input is UNSIGNED. The 4 is the square root’s guard bits.
What each knob costs, in clocks
Substituting (with the usual case where the first term of CNUMW wins) gives a form worth memorising:
$$ L = 9,\mathrm{IN_SW} + 6,\mathrm{MaxBlockExponent} + 3,\mathrm{EXP} + \mathrm{CORR_{fract}} + 13 $$
| knob | cost in clocks of tail |
|---|---|
| one more input bit | +9 |
| one more Max Block Exponent | +6 |
| one more runtime EXP | +3 |
| one more CORR fractional bit | +1 |
| one more CORR integer bit | 0 |
Keep Max Block Exponent tight. It is the knob that surprises people: it costs six clocks of tail per unit here, because it widens the variance numerators and therefore the $D_x D_y$ product, its square root and the division all at once. On Block Covariance it costs nothing at all.
Worked numbers
For the default configuration - IN Q16.0 SIGNED
($\mathrm{IN_SW} = 16$), Max Block Exponent 20, CORRELATION Q2.14 SIGNED:
- $\mathrm{VNW} = 2 \times (16 + 20) = 72$
- $\mathrm{RTW} = 72 + 4 = 76$
- $\mathrm{CNUMW} = \max(72 + 14 + 4,\ 16 + 2) = 90$
- $L = 3(16 + \mathrm{EXP}) + 1 + 72 + 1 + 76 + 1 + 90 + 1 + 1 = \mathbf{291 + 3,\mathrm{EXP}}$
so at EXP = 10 the tail is 321 clocks and at the worst case EXP = 20 it
is 351 clocks. The smallest usable exponent is the smallest EXP with
$2^{\mathrm{EXP}} \ge 291 + 3,\mathrm{EXP}$:
- EXP = 8: $256 \ge 315$? no
- EXP = 9: $512 \ge 318$? yes - so EXP $\ge$ 9, i.e. N $\ge$ 512
A few configurations:
| Input | MaxBlockExp | CORR | VNW | RTW | CNUMW | L | minimum EXP |
|---|---|---|---|---|---|---|---|
| 8 bit s | 10 | Q2.14 | 36 | 40 | 54 | 159 + 3EXP | 8 (N = 256) |
| 16 bit s | 10 | Q2.14 | 52 | 56 | 70 | 231 + 3EXP | 8 (N = 256) |
| 16 bit s | 20 | Q2.14 | 72 | 76 | 90 | 291 + 3EXP | 9 (N = 512) |
| 16 bit s | 20 | Q2.10 | 72 | 76 | 86 | 287 + 3EXP | 9 (N = 512) |
| 16 bit u | 20 | Q2.14 | 74 | 78 | 92 | 300 + 3EXP | 9 (N = 512) |
The second row is worth staring at: $L = 231 + 24 = 255$ against $2^8 = 256$. One clock of margin. Add one input bit, or one unit of Max Block Exponent, and EXP = 8 stops working.
There is also a floor on Max Block Exponent itself: with a 16 bit signed input and Q2.14 output, a Max Block Exponent of 7 or less is refused by the property page, because the minimum usable exponent would then be larger than the largest block the accumulators are sized for.
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 and no overrun 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, and this is the block where driving it too low bites
hardest. With $L$ in the hundreds of clocks, an EXP of 6 or 7 - a
perfectly ordinary value on Block Mean - drops every single result here,
with no symptom other than an OUT_DV that never pulses. If EXP is under
software control, clamp it in software to the minimum the compiler
printed, and enable SAMPLE_COUNT if you want to confirm the block length at
run time.
Knowing where the block is: BUSY, INTEGRATING and SAMPLE_COUNT
Three optional status outputs, all defaulting to NO. On this block the difference between them is at its most useful, because the tail is enormous:
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 pair 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 the four-products-root-divide tail, and on this block it is hundreds of clocks wide.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 pair, 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 pair 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 - the useful thing to latch alongside the result, and here the cheapest way to confirm at run time that the block really was long enough for the tail. OnlyRESETclears it to 0.
Q formats
IN_A, IN_B (one shared format) and CORRELATION carry fixed point formats
(integer bits, fractional bits, sign), the same convention as the Fixed P.
family.
Sizing CORRELATION is unusually easy, because the range is known:
- $|r| \le 1$, so two integer bits are enough for the value AND its sign - that is the default, and there is no reason to raise it;
- every remaining bit buys resolution: the default 14 fractional bits give steps of $1/16384 \approx 6 \times 10^{-5}$;
- SIGNED is the default and should stay that way - an anti-correlation is the whole point of measuring $r$, and an UNSIGNED format would clamp every negative $r$ to 0.
Note that the quotient already comes out at CORR fractional bits - the
root’s guard bits were cancelled by the numerator’s shift - so the final
requantisation has nothing to shift. The Rounding property therefore has
no effect on this block’s value; only Saturation still applies, and only
if you choose a format narrower than the $[-1,+1]$ range needs.
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 computes $r$ from the centred products in exact rational arithmetic -
deliberately not from the raw-moment identity the core uses, so the two
share no algebra - and the tolerance is 1 LSB, the published accuracy.
All the correlation scenarios raise Max Block Exponent to 10 and run at EXP = 8, because the tail is the longest in the family and the block has to be long enough to hide it. The second stream is derived from the first so the answer is known in advance:
| scenario | second stream | expected |
|---|---|---|
CO_SAME_E8 |
$b = a$ | $r = +1$ exactly |
CO_NEG_E8 |
$b = -a$ | $r = -1$ exactly |
CO_HALF_E8 |
$b = a/2$ | still strongly positive - and this is the gain-independence check |
CO_NOISY_E8 |
$a/2$ + noise on a sine | a realistic partial correlation |
CO_INDEP_E8 |
independent | $r$ near 0 |
CO_CONSTB_E8 |
constant | $\sigma_b = 0$, so $r$ is reported as 0 |
The tail length is checked against the formula at compile time: three
copies of it exist - the BCO_TAIL macro in the core, tail_len() in the
generator and TailClocks() in the plugin - and the testbench refuses to
build if they disagree. tb/block-ops/port_check.py preprocesses the real
core and checks the symbol’s pin list against the generated entity’s ports for
every enable combination. The status outputs are checked clock by clock
against the contract above. There is no all-in-one counterpart to
cross-check against - Block Statistics has no correlation - so the
exact-rational golden is the only reference.
A note for anyone reading the core: the radicand is the widest intermediate in the whole family, $2,\mathrm{VNW} + 8$ bits. The host test stub stores everything in a 128 bit integer, so configurations with $\mathrm{IN_SW} + \mathrm{MaxBlockExponent} \ge 30$ are exercised by synthesis but not by the host harness. In hardware they are just registers.