DSP - BLOCK AUTOCORRELATION TM
TM (time multiplexed) twin of Block Autocorrelation: TM Factor (2..16) independent channels packed on one wide bus, each computing the mean-removed autocorrelation coefficient rho(k) of its own block of N = 2^EXP samples, at a lag k driven on ONE scalar runtime pin broadcast to every lane (latched per block, clamped to Max Lag). The lag delay line is PER LANE - lanes are fully independent channels; only the frame and the lag value are common. Sweep the LAG pin - one block per lag - and every lane reconstructs its own correlogram at once. One shared frame - one EXP, one IN_DV, one OUT_DV per block - with per-lane accumulators at II=1 and ONE serial tail serving the lanes one after the other, so the tail resources stay those of the scalar block whatever the lane count. Lane 0 sits in the LOW bits of every packed bus. Same per-lane numbers as the scalar twin, bit for bit. Optional shared BUSY / INTEGRATING / SAMPLE_COUNT status outputs.
Introduction
Per lane, over each block of N samples:
$$ \rho_i(k) = \frac{\sum_{j=k}^{N-1} (x_i[j]-\mu_i)(x_i[j-k]-\mu_i)}{\sum_{j=0}^{N-1} (x_i[j]-\mu_i)^2} $$
This is the time multiplexed (TM) twin of the scalar Block Autocorrelation block: TM Factor independent channels packed on one wide bus, each computing the biased, mean-removed sample autocorrelation of its own block of N consecutive samples, at the same lag. Nothing is shared between the channels except the frame and the lag value.
Per lane it is dimensionless, gain independent and bounded to [-1,+1]; peaks along a lag sweep are periodicity, a fast decay is white noise, a slow decay is a low-pass or drifting lane. The mean removal is the point: a raw autocorrelation on a pedestalled lane would correlate with itself at every lag simply because it is positive.
The TM contract
INis TM Factor lanes of input width bits each, lane 0 in the LOW bits (lane 0 is the oldest sample of the clock) - the same packing as every other TM block in the toolchain. Each lane is an independent channel: its own accumulators, its own result.LAGis ONE SCALAR PIN broadcast to all lanes - the same rule as LEVEL / THRESHOLD in the rest of the TM family. It is clamped to Max Lag and latched per block exactly likeEXP, so no lane’s block is ever computed against a mixture of lags. Lanes needing different lags need scalar blocks.- THE LAG DELAY LINE IS PER LANE (TM Factor separate delay lines of Max Lag samples each): lane i’s lagged partner x_i[j-k] comes from lane i’s OWN history, never from another lane’s - the lanes are fully independent channels. So are the running sums (S1, S2, Sxy, Sed), per lane, always.
- ONE shared
EXPpin, ONEIN_DV, one frame: all lanes start and end their blocks on the same accepted clocks.BUSY,INTEGRATINGandSAMPLE_COUNTtherefore stay scalar -SAMPLE_COUNTcounts per-lane samples, which are identical in every lane by construction. - Accumulation runs at II=1 on the packed bus, one accumulator set per
lane - plus TWO real multipliers per lane (xx and xx[j-k]) in the
accumulation path. The serial post-processing is ONE shared engine set
serving the lanes one after the other; ONE
OUT_DVper block, after the LAST lane’s tail completes, with all output lanes staged and committed together on that clock, so every packed output moves on theOUT_DVclock and no other.
N is a runtime input: the block size is $N = 2^{\mathrm{EXP}}$, EXP clamped to Max Block Exponent and latched on the first accepted sample of a block, so a change takes effect on the NEXT block - for every lane at once. An exact integer identity collapses the centred products into raw power sums - NO second pass and NO sample buffer, in any lane.
When to use this instead of TM Factor scalar blocks
One TM block and TM Factor scalar blocks compute the same numbers. The TM block pays the per-lane accumulators and delay lines (unavoidable either way) but shares ONE frame, ONE control FSM and ONE serial tail across all lanes - the more expensive the scalar tail, the more it saves. The price is tail latency (TM Factor times the scalar tail) and the coupling of the frame AND the lag: all lanes share the same block length, the same sample cadence and the same lag. Channels that need different block sizes or different lags need scalar blocks.
Pin Description
IN_DV is
high.
EXP - a change takes effect on
the NEXT block, for every lane at once. LAG = 0 must read exactly 1.0
in every lane (free self test); LAG >= N forces every lane’s output
for that block to 0. Unconnected ties to 0.
Properties
Number of INTEGER bits of the input sample (per lane) (the sign, when present, uses one of them).
Integer bits of ONE LANE of the input (the sign, when present, uses one of them). 1..64. Default 16.Default: 16
Options: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
Number of FRACTIONAL bits of the input sample (per lane), i.e. the bits to the right of the binary point. Total width = integer + fractional bits, and must not exceed 64.
Fractional bits of one lane of the input. 0..64, total lane width 2..64 bits. Default 0. They cancel exactly - rho is dimensionless.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 (per lane) is signed (two’s complement) or unsigned.
SIGNED (two’s complement) or UNSIGNED lanes. Default SIGNED. Applies to every lane.Default: SIGNED
Options: UNSIGNED SIGNED
Number of INDEPENDENT time-multiplexed channels packed on the IN bus and on every result bus. Lane 0 occupies the LOW bits (lane 0 = the oldest sample of the clock), the same packing as every other TM block. All lanes share one EXP / IN_DV / frame; each lane gets its own accumulators, but the serial post-processing is ONE engine serving the lanes one after the other, so the tail latency (and the minimum usable EXP) grows with this factor.
Number of independent channels packed on the buses, 2..16. Default 4. Multiplies the IN width, every packed output width, the number of delay lines AND the serial tail length (the shared tail serves the lanes one after the other), so it also raises the minimum usable EXP.Default: 4
Options: 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
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 per-lane accumulators are sized for; the EXP input is clamped to it at run time. Raising it widens every lane’s accumulators AND lengthens the shared serial engines. 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
Deepest lag the block can be asked for. It sizes the delay line (MaxLag sample registers, a shift register on Xilinx) and the runtime LAG input is CLAMPED to it. 63 is the ceiling because the LAG pin is 6 bits wide.
Deepest lag the block can be asked for. It sizes the delay lines - Max Lag sample registers PER LANE (a shift register on Xilinx) - and the runtime LAG input is CLAMPED to it. 63 is the ceiling because the LAG pin is 6 bits wide. 1..63, default 16.Default: 16
Options: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
Number of INTEGER bits of the AUTOCORR output (per lane) (the sign, when present, uses one of them).
Integer bits of ONE LANE of the AUTOCORR output. 1..64, default 2 - rho never exceeds 1 in magnitude, so 2 integer bits (sign included) are enough.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 AUTOCORR output (per lane), i.e. the bits to the right of the binary point. Total width = integer + fractional bits, and must not exceed 64.
Fractional bits of one lane. 0..64, default 14 - rho is a pure fraction, so this is where it lives. Each one also lengthens the shared divider by one clock per lane.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 AUTOCORR output (per lane) is signed (two’s complement) or unsigned.
SIGNED or UNSIGNED lanes. Default SIGNED - anticorrelation at a lag is negative and means something.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: the shared BUSY pin exists. 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: the shared INTEGRATING pin exists. 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: the shared 32 bit SAMPLE_COUNT pin exists. 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 / TRUNCATE at each lane’s final requantisation. Effectively a NO-OP on this block: the numerator is pre-shifted so each lane’s floored quotient already carries exactly the output resolution and nothing is left to shift. Saturation still applies. 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 each lane to its output format (symmetric for signed). NO: wrap. |rho| <= 1 by arithmetic, so a wide-enough lane format never engages it. Default YES.Default: YES
Options: NO YES
Accuracy, per lane
Each lane is BIT EXACT, like the scalar twin: everything before the division is exact integer arithmetic, the magnitude of the quotient is floored to the output resolution and the sign applied afterwards - the harness demands tolerance 0 per lane. The two scalar corner cases hold PER LANE: LAG = 0 gives exactly 1.0 (16384 in the default Q2.14) with no special case anywhere - drive it and every lane must read exactly 2^ACF_fract, a free self test of the whole packed datapath on live hardware; LAG >= N forces that block’s output to 0 in every lane, and that one IS a real guard (no overlap, nothing to correlate). A constant lane block (zero denominator) reads 0, reached by the arithmetic’s zero-numerator path.
Because each lane’s quotient already carries the output resolution, the Rounding property is effectively a NO-OP on this block (saturation still applies).
Timing: the TM latency contract
OUT_DV pulses ONCE per block, L clocks after the clock on which the
N-th sample was accepted, where
$$ L = 1 + \mathrm{TM},\bigl(\mathrm{IN_SW} + \mathrm{EXP} + \mathrm{SEW} + \mathrm{CNUMW} + 8\bigr) $$
(BACT_TAIL in the core, with SEW = IN_SW + 7). Note the per-lane cost
is L_scalar - 2, one clock LESS than the family’s usual
$L_{tm}(e) = \mathrm{TM},(L_{scalar}(e)-1)+1$ rule: the scalar FSM’s
separate VAR and divider-load states are MERGED into a single state here
(numerator, denominator and the divider load happen on the same clock),
and lane ln+1’s engines are reloaded during lane ln’s FIN clock. Pure data
movement - the values stay bit-identical to the scalar twin; only the
timing differs. The tail DEPENDS ON THE RUNTIME EXPONENT. At the defaults
(16 bit signed input, Q2.14 AUTOCORR, Max Lag 16, Max Block Exponent 20,
TM 4, EXP 10) SEW = 23 and CNUMW = 108, so L = 1 + 4*165 = 661 clocks,
and the minimum usable EXP is 10 - the default block just fits.
The family drop rule applies with the TM tail: the tail of one block must
finish before the NEXT block completes, $2^{{\mathrm{{EXP}}}} \ge L$, or the
completing block’s result is silently DROPPED (no OUT_DV, accumulators
unaffected, no error pin). The TM tail makes the minimum usable EXP larger
than the scalar twin’s - the compiler prints both the worst-case tail and
the minimum EXP in the compilation log, and the property window refuses a
configuration whose minimum exceeds Max Block Exponent.
Knowing where the block is: BUSY, INTEGRATING and SAMPLE_COUNT
Identical to the scalar family, and SHARED by all lanes: INTEGRATING is
high exactly while the block is accumulating (it dips one clock per block
boundary on a continuous stream), BUSY also covers the (TM-long) tail and
its last high clock IS the OUT_DV pulse, SAMPLE_COUNT reads 1 after the
first accepted sample and N after the N-th.
SAMPLE_COUNTis 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 (which on a TM block is TM Factor times longer), so atOUT_DVit reads how far into the next block the input has got, NOT N. The clock that always reads N is the oneINTEGRATINGfalls on - latch it there.
Verification
The core is regression tested by a host-side csim harness
(tb/block-ops-tm/run_tb_tm.ps1) with per-lane goldens computed by
gen_golden_tm.py in exact rational arithmetic ON EACH LANE’S STREAM ALONE
(lanes deliberately carry different signals - the generator refuses
identical lanes), from the TEXTBOOK centred definition, never from the
raw-power-sum identity the core uses - so the algebra is checked against
an independent derivation. Plus the strongest available lane-independence
check: after every run, the SCALAR twin is replayed on each lane’s stream
by itself and lane k of every TM result must match it BIT FOR BIT. The
status waveform is checked clock by clock, every packed output is checked
to move only on OUT_DV, and the TM-specific mutant classes (shared delay
line or accumulator, lane swaps, wrong-lane tail reads, early commit, drop
rule) are killed. What no host harness can prove - that Vitis accepts and
schedules the core at II=1, including mapping the per-lane delay lines to
SRLs rather than BRAM - is stated in AGENT/block_ops.log, not silently
implied.