Xilinx
HLS
Block Preview

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_A Input IN_BitsInt + IN_BitsFract bit BIT VECTOR
First input stream, fixed point in the IN Q format. Squared, multiplied by 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.
Default: Must be connected
IN_B Input IN_BitsInt + IN_BitsFract bit BIT VECTOR
Second input stream, in the same IN Q format as 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.
Default: Must be connected
IN_DV Input 1 bit BIT
Per-PAIR qualifier, active high, and the ONLY qualifier this block has. 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.)
EXP Input 6 bit BIT VECTOR
Block size exponent, runtime programmable: the block is $N = 2^{\text{EXP}}$ sample PAIRS long. 6 bits unsigned, accepted range 0 .. Max Block Exponent; larger values are clamped to Max Block Exponent. Sampled on the first accepted pair of a block and held for that whole block, so a change takes effect on the NEXT block. On this block it must be LARGE: $2^{\text{EXP}} \ge L$ with $L$ in the hundreds of clocks, or every result is dropped without warning - see “The serial tail”. Unconnected defaults to 10 (N = 1024).
CORRELATION Output CORR_BitsInt + CORR_BitsFract bit BIT VECTOR
The Pearson coefficient $r$ of the block, in the CORR Q format, dimensionless and bounded to $[-1,+1]$ by the arithmetic itself. In the default Q2.14 SIGNED, $+1.0$ is exactly 16384 and $-1.0$ exactly -16384. A block in which either channel is constant reports 0. Updated on the OUT_DV clock and on no other; it holds the previous block’s result until then.
OUT_DV Output 1 bit BIT
One-clock pulse marking a valid result. It fires when the serial tail COMPLETES, L clocks after the clock on which the N-th pair of the block was accepted, not when that pair arrives. 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.
CLK 1 bit
Clock.
RESET 1 bit
Synchronous reset: clears the five accumulators, the block counter, the sample count and the whole serial tail (the products, the root and the divider).
BUSY 1 bit
High from the start of a block - its first accumulated pair - until its result is out: it covers the serial tail as well, which on this block is hundreds of clocks long. Its last high clock is the OUT_DV pulse, and it falls on the clock after. On a continuous stream it simply stays high. Present on the symbol only when Enable BUSY = YES.
INTEGRATING 1 bit
High only while the block is accumulating: it rises on the clock after the first pair of a block is accepted and falls on the clock after the N-th. 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.
SAMPLE_COUNT 32 bit

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 at OUT_DV only when the input STOPS for the whole tail. On a CONTINUOUS stream the next block has already started by then, so OUT_DV shows how far into it the input has got, not N - the clock that always reads N is the one INTEGRATING falls on. On this block that is also the cheapest run-time confirmation that the block was long enough for the tail. Only RESET clears it to 0. Fixed 32 bits. Present on the symbol only when Enable SAMPLE_COUNT = YES.

Properties

Property window

IN Integer Bits IN_BitsInt

Number of INTEGER bits of the input sample (the sign, when present, uses one of them).

Integer bits of the input samples (the sign, when present, uses one of them). 1..64. Default 16. It applies to BOTH IN_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

IN Fractional Bits IN_BitsFract

Number of FRACTIONAL bits of the input sample, i.e. the bits to the right of the binary point. Total width = integer + fractional bits, and must not exceed 64.

Fractional bits of the input 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

IN Sign IN_Sign

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

Max Block Exponent MaxBlockExponent

Largest block-size exponent the accumulators are sized for: the block can be up to 2^MaxBlockExponent samples long. The EXP input is clamped to this value at run time. Raising it widens the internal accumulators, and ON THE BLOCKS WHOSE SERIAL ENGINES ARE SIZED FROM THOSE ACCUMULATORS (Coefficient of Variation, SNR, Skewness, Kurtosis, Correlation, Autocorrelation, Linear Regression) it also LENGTHENS THE SERIAL TAIL – even when the runtime EXP is small. Keep it at the largest block you actually use. The default of 20 covers blocks of up to 1048576 samples.

Largest block-size exponent the accumulators are sized for: the block can be up to $2^{\text{MaxBlockExponent}}$ sample pairs long, and the EXP 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

CORR Integer Bits CORR_BitsInt

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

CORR Fractional Bits CORR_BitsFract

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

CORR Sign CORR_Sign

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

Enable BUSY EnableBusy

YES: the BUSY (high from the first sample of a block until its result is out – it COVERS THE SERIAL TAIL, and its last high clock IS the OUT_DV pulse) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.

YES: the BUSY pin exists. It is high from the first 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

Enable INTEGRATING EnableIntegrating

YES: the INTEGRATING (high only while the block is ACCUMULATING; it drops as soon as the N-th sample has been taken and the tail starts, so BUSY-and-not-INTEGRATING means ‘computing’) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.

YES: the INTEGRATING pin exists. It is high only while the block is accumulating, so BUSY high with INTEGRATING low means “the 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

Enable SAMPLE_COUNT EnableSampleCount

YES: the SAMPLE_COUNT (32 bit, how many samples have been accumulated so far in the current block: 1 after the first, N after the N-th. It is NOT cleared at the block end – it holds N until the NEXT block’s first accepted sample takes it back to 1. On a CONTINUOUS stream that happens DURING the serial tail, so at OUT_DV it reads how far into the next block the input has already got, NOT N. To capture the length of the block being presented, latch SAMPLE_COUNT on the clock INTEGRATING falls – that one always reads N) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.

YES: the SAMPLE_COUNT pin exists - a fixed 32 bit count of the 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

Rounding Rounding

ROUND: round to nearest when a result has to be requantised into a coarser output format. TRUNCATE: drop the bits (cheaper, adds a negative bias).

ROUND: round to nearest when a result has to be requantised into a coarser output format. TRUNCATE: drop the bits. 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

Saturation EnableSaturation

YES: clip to the largest representable value of each output format (symmetric for signed formats). NO: wrap around.

YES: clip to the largest representable value of the 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 on IN_DV has 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:

  • INTEGRATING rises 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. BUSY high with INTEGRATING low is the four-products-root-divide tail, and on this block it is hundreds of clocks wide.
  • BUSY covers the accumulation and the tail. It rises with INTEGRATING, stays high across the tail, and its LAST HIGH CLOCK IS THE OUT_DV PULSE; it falls on the clock after.
  • On a continuous stream the next block starts before the previous tail ends, so BUSY never drops and INTEGRATING dips for exactly one clock per block boundary - which makes it a free block marker.
  • SAMPLE_COUNT is a fixed 32 bits and reads 1 after the first accepted pair, N after the N-th. It is NOT cleared at the block end: it HOLDS N through the tail and past OUT_DV, until the first pair of the next block takes it back to 1. it reads N at OUT_DV only when the input STOPS for the whole tail. On a CONTINUOUS stream the next block has already started by then, so OUT_DV shows how far into it the input has got, not N - the clock that always reads N is the one INTEGRATING falls on - 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. Only RESET clears 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.