Xilinx
HLS
Block Preview

Introduction

The Block Linear Regression block chops the input stream into consecutive blocks of N samples and, at the end of each block, publishes the least-squares straight line fitted to that block against the sample index:

$$ x_i ;\approx; \mathrm{INTERCEPT} + \mathrm{SLOPE}\cdot i , \qquad i = 0 \ldots N-1 $$

It is the line that minimises $\sum_i (x_i - \mathrm{INTERCEPT} - \mathrm{SLOPE}\cdot i)^2$ over the block. Two numbers come out:

output what it is units
SLOPE the DRIFT RATE of the block input units per sample index
INTERCEPT the fitted value at the FIRST sample of the block ($i = 0$) the units of the input

N is a runtime input, not a property. You drive the exponent on the EXP pin and the block size is $N = 2^{\mathrm{EXP}}$:

EXP N EXP N
4 16 12 4096
6 64 16 65536
8 256 20 1048576

What it is FOR: DRIFT REMOVAL

A drifting baseline is the thing that ruins every other statistic in this family. It inflates Block Std Dev and Block Variance (the drift itself becomes “spread”), it hides a real change in Block Mean, it moves the skewness off zero, and it makes Block Threshold Count depend on when you looked rather than on what the signal did.

This block measures the drift so you can take it out. Feed SLOPE and INTERCEPT to a downstream subtracter - reconstruct $\mathrm{INTERCEPT} + \mathrm{SLOPE}\cdot i$ with a counter and an accumulator, or drive a DAC on a baseline-restore loop - and the linear trend is gone from the next block. Everything downstream then measures the signal instead of the drift.

(The fit describes the block that has just finished, so applying it to the next block assumes the drift continues at the same rate. That is the usual baseline-restore assumption, and it is a good one for thermal drift, leakage and slow bias movement - the things this block exists for.)

And as a measurement in its own right

SLOPE on its own is a direct “how fast is this detector warming up” readout: degrees, volts or counts per sample, straight out of a pin, with no differentiation and no noise amplification. Fitting a line across N samples averages the noise down instead of amplifying it, which is exactly what a first difference does not do.

THERE IS NOT ONE MULTIPLIER IN THIS BLOCK

Not one - not even a serial one. That is the headline engineering fact about this block, and it comes from two observations.

1. The regressor is the sample INDEX, which is known. $\sum i$ and $\sum i^2$ are closed forms in N, and N is a power of two, so every one of them is a SHIFT. Written about the centred index $w_i = 2i - (N-1)$ the normal equations collapse to

$$ \mathrm{SLOPE} = \frac{6,S_w}{N(N^2-1)} , \qquad \mathrm{INTERCEPT} = \frac{(N+1)S_x - 3 S_w}{N(N+1)} , \qquad S_w = \sum_i w_i x_i $$

and both denominators are built from N by shifts and one add: $N(N^2-1) = 2^{3E} - 2^{E}$ and $N(N+1) = 2^{2E} + 2^{E}$.

2. $S_w$ itself needs no multiplier either. With $R_i$ the running sum of $x_0 \ldots x_i$ and $A = \sum_i R_i$,

$$ \sum_i i,x_i = N S_x - A , \qquad S_w = (N+1)S_x - 2A $$

so TWO CASCADED ACCUMULATORS replace the index multiply completely - $S_x$ is simply $R$ at the end of the block. The whole per-sample datapath of this block is two adders.

The only real arithmetic left is the two divisions, by $N(N^2-1)$ and $N(N+1)$, which genuinely are not powers of two. They share one reused restoring-division stage.

Cost

Per clock, while accumulating: two adders. No multiplier, no DSP, no sample buffer, no memory.

In the tail: one reused compare-subtract stage, run once per enabled output. Turning an output off really does remove its division, its pin and its clocks - a slope-only placement has half the tail of a two-output one.

When to use this instead of Block Statistics

The all-in-one Block Statistics block is not deprecated, but it does not fit a line - there is no SLOPE or INTERCEPT pin over there, so for these numbers there is no choice to make: this block is the only way to get them.

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 Linear Regression block next to a Block Statistics block accumulates the same stream a second time - but here that second accumulation is two adders, which is about as cheap as a duplicate can be.

Pin Description

IN Input IN_BitsInt + IN_BitsFract bit BIT VECTOR
Input samples, fixed point in the IN Q format. Added into the running sum $R$ - which in turn is added into the second accumulator $A$ - only on the clocks where IN_DV is high. Those two adders are the whole per-sample datapath of this block.
Default: Must be connected
IN_DV Input 1 bit BIT
Per-sample qualifier, active high, and the ONLY qualifier this block has. A sample is accumulated, and counts towards N, exactly on the clocks where this is high; the serial tail keeps running regardless. Note that the fit’s index $i$ counts accepted samples, so a gap here does not leave a hole in the regressor. 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}}$ 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; both denominators are built from the latched value. It must satisfy $2^{\text{EXP}} \ge L$ at run time or the block’s result is dropped without warning - see “Timing”. EXP = 0 (N = 1) is defined: no slope exists, SLOPE reads 0 and INTERCEPT reads the sample itself. Unconnected defaults to 10 (N = 1024).
SLOPE Output SLOPE_BitsInt + SLOPE_BitsFract bit BIT VECTOR
The fitted drift rate, in input units per sample index, in the SLOPE Q format. Positive for a rising block, negative for a falling one, exactly 0 for a constant block, and 0 when N = 1. Remember the scale: a drift of one input LSB across a 1024-sample block is a slope of $2^{-10}$, so this port needs FRACTIONAL bits - see “UNITS”. Updated on the OUT_DV clock and on no other; it holds the previous block’s result until then. Present on the symbol only when Enable SLOPE = YES.
INTERCEPT Output INTERCEPT_BitsInt + INTERCEPT_BitsFract bit BIT VECTOR
The fitted value at the FIRST sample of the block ($i = 0$), in the INTERCEPT Q format and in the units of the input. For a constant block it is exactly that constant; for N = 1 it is the single sample. A noisy steep fit can place it slightly outside the range of the block’s samples, so leave Saturation on. Updated on the OUT_DV clock and on no other; it holds the previous block’s result until then. Present on the symbol only when Enable INTERCEPT = YES.
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 sample of the block was accepted, not when that sample arrives. SLOPE and INTERCEPT are both 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.
CLK 1 bit
Clock.
RESET 1 bit
Synchronous reset: clears the running sum, the second accumulator, the block counter, the sample count and the serial tail.
BUSY 1 bit
High from the start of a block - its first accumulated sample - until its result is out: it covers the serial tail as well. 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 sample of a block is accepted and falls on the clock after the N-th. BUSY high with INTEGRATING low is exactly the one or two serial divisions. 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 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 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 N the slope refers to. 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 sample (the sign, when present, uses one of them). 1..64. Default 16. It widens both numerators, so it adds one clock of tail per bit per enabled output.

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 sample. 0..64. Total input width must be 2..64 bits. Default 0. It is taken into account when the quotients are requantised, so SLOPE and INTERCEPT are always in real input units whatever you set here.

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 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 divider by one step per enabled output. Note that an UNSIGNED input can still have a negative slope and a negative fitted intercept, so keep the output formats signed.

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}}$ samples long, and the EXP input is clamped to this value at run time. Raising it widens the second accumulator by two bits per unit (it grows as $N^2$) and the two denominators as well, and it lengthens the tail by two clocks per unit per enabled output. 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

Enable SLOPE EnableSlope

YES: the the SLOPE output pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.

YES: the SLOPE pin is present. NO: the pin, its division by $N(N^2-1)$ and its CNUMW clocks of tail are all removed BEFORE synthesis, so nothing is paid for it. Default YES. At least one of SLOPE and INTERCEPT must be enabled or the property page refuses the settings.

Default: YES

Options: NO YES

Enable INTERCEPT EnableIntercept

YES: the the INTERCEPT output pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.

YES: the INTERCEPT pin is present. NO: the pin, its division by $N(N+1)$ and its CNUMW clocks of tail are all removed BEFORE synthesis. Default YES. Turning this off is the cheapest way to halve the tail when you only want the drift rate. At least one of SLOPE and INTERCEPT must be enabled. Note that the divider is sized at the wider of the two formats either way, so disabling an output shortens the tail but does not narrow the divider.

Default: YES

Options: NO YES

SLOPE Integer Bits SLOPE_BitsInt

Number of INTEGER bits of the SLOPE output (the sign, when present, uses one of them).

Integer bits of the SLOPE output (the sign uses one of them). 1..64, default 8, i.e. up to $\pm 127$ input units of change per sample - a ramp that crosses the full scale of a 16 bit input in about 500 samples. Widen it only if you fit something genuinely that steep.

Default: 8

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

SLOPE Fractional Bits SLOPE_BitsFract

Number of FRACTIONAL bits of the SLOPE 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 SLOPE output. 0..64, total width 2..64 bits, default 16. This is the property to get right on this block. The slope is per SAMPLE INDEX, so it is small: a drift of one input LSB across a 1024-sample block is a slope of $2^{-10}$, and with only 8 fractional bits that reads as 0. The default 16 gives an LSB of 1/65536 and resolves such a drift to 64 counts. Each bit costs one clock of tail (through CNUMW), which is cheap for what it buys.

Default: 16

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

SLOPE Sign SLOPE_Sign

Select whether the SLOPE output is signed (two’s complement) or unsigned.

SIGNED or UNSIGNED SLOPE output. Default SIGNED, and it should stay that way - a drift can go either way, and an UNSIGNED format clamps every falling block to 0.

Default: SIGNED

Options: UNSIGNED SIGNED

INTERCEPT Integer Bits INTERCEPT_BitsInt

Number of INTEGER bits of the INTERCEPT output (the sign, when present, uses one of them).

Integer bits of the INTERCEPT output (the sign uses one of them). 1..64, default 16 - the same as the default input, which is the right starting point since the intercept has the units of the input. Give it at least the input’s integer bits; a noisy steep fit can extrapolate slightly beyond the range of the block’s own samples.

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

INTERCEPT Fractional Bits INTERCEPT_BitsFract

Number of FRACTIONAL bits of the INTERCEPT 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 INTERCEPT output. 0..64, total width 2..64 bits, default 8. This is pure resolution and it is worth having: the fitted value at $i = 0$ is effectively an average of N samples, so it is known to far better than one input LSB. Each bit costs one clock of tail through CNUMW - and only if it makes the intercept’s term the widest of the four.

Default: 8

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

INTERCEPT Sign INTERCEPT_Sign

Select whether the INTERCEPT output is signed (two’s complement) or unsigned.

SIGNED or UNSIGNED INTERCEPT output. Default SIGNED. Keep it signed for a bipolar input, and also for a unipolar one whose fit can extrapolate below zero.

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 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

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 samples are all in, the divisions are running”. 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 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

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 quotient has to be requantised into its output format. TRUNCATE: drop the bits (cheaper, adds a negative bias). It applies on this block, because both quotients are computed with 4 guard bits underneath the requested format precisely so that there is something left to round. 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 each output format (symmetric bounds for signed formats). NO: wrap around. Keep it YES: an under-sized SLOPE integer field on a steep ramp, or an extrapolated INTERCEPT, would otherwise wrap and report a large drift as a small one of the opposite sign. Default YES.

Default: YES

Options: NO YES

The algebra, and how to check it by hand

Per accepted sample the core keeps exactly two registers:

$$ R_i = R_{i-1} + x_i \quad (\text{so } S_x = R_{N-1}) , \qquad A = A + R_i $$

and at the end of the block, in one clock of shifts and adds:

$$ S_w = (N+1)S_x - 2A , \qquad \mathrm{SLOPE} = \frac{6 S_w}{2^{3E} - 2^{E}} , \qquad \mathrm{INTERCEPT} = \frac{(N+1)S_x - 3 S_w}{2^{2E} + 2^{E}} $$

Worth checking on paper once, because it is not obvious that this is a fit at all:

block $S_x$ $A$ $S_w$ SLOPE INTERCEPT
[0, 1, 2, 3] 6 10 10 $60/60 = 1$ $(30-30)/20 = 0$
[1, 3, 5, 7] 16 30 20 $120/60 = 2$ $(80-60)/20 = 1$
[c, c, c, c] 4c 10c 0 exactly 0 $20c/20 = c$

A clean ramp is an exact fit - the residual is zero and both outputs are exactly right - which is the strongest single check of the algebra, and it is in the regression suite.

N == 1 has no slope

A single point has nothing to fit, and the arithmetic reaches that on its own: $N(N^2-1) = 0$ and the numerator $6 S_w$ is 0 as well, so the zero-denominator path reports SLOPE = 0. The intercept is still perfectly well defined - $N(N+1) = 2$, and it comes out as the sample itself.

No sane configuration asks for EXP = 0, but EXP is a PIN, so this is reachable at run time and it is defined rather than left to chance.

UNITS: size SLOPE’s fraction generously

This is the one thing to get right on this block.

SLOPE is in input units per SAMPLE INDEX, not per block and not per second. That makes it a small number: a baseline that drifts by one input LSB across a whole 1024-sample block has a slope of

$$ \frac{1}{1024} = 2^{-10} $$

So the slope LSB has to be much smaller than that or every slow drift quantises to zero:

SLOPE fractional bits slope LSB one input LSB of drift across 1024 samples reads
4 1/16 0 (0.0156 LSB - lost)
8 1/256 0.25 LSB - still lost
12 1/4096 4 LSB - usable
16 (default) 1/65536 64 LSB - comfortable

Give SLOPE plenty of FRACTIONAL bits. The default is 16, and that is the right order of magnitude for a drift measurement; do not cut it down to “match the input” - the input’s format has nothing to do with it.

Its integer bits are the opposite problem and rarely bind: 8 signed bits cover $\pm 127$ input units of change per sample, which is a ramp that traverses the full scale of a 16 bit input in about 500 samples. If you are fitting something that steep, widen them.

INTERCEPT has the units of the input, so size it like the input: at least the input’s integer bits. Its fractional bits (default 8) are pure resolution - the fitted value at $i = 0$ is an average of N samples, so it is known to far better than one input LSB and it is worth asking for the extra bits. Note that a noisy steep fit can extrapolate the intercept slightly outside the range of the block’s samples, so keep Saturation on.

Accuracy

Both divisions carry SQG = 4 guard bits underneath the requested Q format, so the final requantisation still has something to round with, and then the result is requantised with the selected rounding. Both outputs are therefore within 1 LSB of the exactly-rounded value, and the host regression asserts exactly that: tolerance 1 LSB against a Python golden (tb/block-ops/gen_golden.py) that solves the normal equations in exact rational arithmetic and shares no algorithm with the core.

Everything before the divisions is exact integer arithmetic: the two accumulators, the closed-form denominators and $S_w$ are all built from shifts and adds with no intermediate rounding at all.

Accumulation and IN_DV

IN_DV is the only qualifier. It says “this clock carries a sample”: a sample enters the running sum $R$, $R$ enters $A$, and the sample 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.

Note the consequence for the fit: the index $i$ counts ACCEPTED samples, not clocks. A gap in IN_DV does not leave a hole in the regressor - the samples either side of it are consecutive as far as the fit is concerned. If your IN_DV gaps are irregular and you want a drift rate per unit time rather than per sample, scale the slope downstream by the average duty cycle.

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 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 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 what both denominators - $2^{3E}-2^{E}$ and $2^{2E}+2^{E}$ - are built from, so a block is never divided by the wrong N.

Timing: the serial tail

The state walk

  IDLE -(block complete)-> CALC (one clock: all shifts and adds)
     -> DIVS x CNUMW (slope)  -> DIVI x CNUMW (intercept)
     -> FIN -> IDLE
  

A disabled output’s division state is removed at compile time, not skipped at run time. OUT_DV pulses for one clock when the tail COMPLETES - not when the N-th sample arrives - and both data outputs are 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 + \bigl(\mathrm{EN_{SLOPE}} + \mathrm{EN_{INTERCEPT}}\bigr) \cdot \mathrm{CNUMW} $$

with the divider width fixed at compile time by

$$ \mathrm{NBW} = \mathrm{IN_SW} + 2,\mathrm{MaxBlockExponent} + 5 , \qquad \mathrm{NAW} = \mathrm{IN_SW} + 2,\mathrm{MaxBlockExponent} + 4 $$

$$ \mathrm{CNUMW} = \max\bigl( \mathrm{NBW} + \mathrm{SLOPE_{fract}} + 4 ,; \mathrm{NAW} + \mathrm{INTERCEPT_{fract}} + 4 ,; \mathrm{SLOPE\ width} + 2 ,; \mathrm{INTERCEPT\ width} + 2 \bigr) $$

where $\mathrm{IN_SW}$ is the input width, plus one bit if the input is UNSIGNED, and the 4 is the guard SQG.

CNUMW has no EXP term: the tail does NOT depend on the runtime exponent. One divider serves both quotients, sized at the wider of the two - which also means that disabling an output halves the tail but does not narrow the divider: a slope-only placement is still sized against the intercept’s format if that one happens to be wider. Max Block Exponent does enter, at two clocks per unit, because it sizes the numerators.

Worked numbers

Take a 16 bit signed input at the default formats (SLOPE Q8.16 signed, INTERCEPT Q16.8 signed, Max Block Exponent 20, both outputs enabled):

  • $\mathrm{IN_SW} = 16$, so $\mathrm{NBW} = 16 + 40 + 5 = 61$ and $\mathrm{NAW} = 60$
  • $\mathrm{CNUMW} = \max(61 + 16 + 4,; 60 + 8 + 4,; 24 + 2,; 24 + 2) = \max(81, 72, 26, 26) = 81$
  • $L = 3 + 2 \cdot 81 = \mathbf{165}$ clocks, whatever EXP is
  • the smallest exponent with $2^{\mathrm{EXP}} \ge 165$ is EXP = 8, i.e. N = 256

Turn the intercept off and the same configuration costs $L = 3 + 81 = 84$ clocks and works from EXP = 7 (N = 128).

Input Max Block Exponent outputs CNUMW L minimum EXP
8 bit s 8 both 49 101 7 (N = 128)
8 bit s 8 one 49 52 6 (N = 64)
8 bit s 20 both 73 149 8 (N = 256)
8 bit s 20 one 73 76 7 (N = 128)
16 bit s 12 both 65 133 8 (N = 256)
16 bit s 20 both 81 165 8 (N = 256)
16 bit s 20 SLOPE only 81 84 7 (N = 128)
16 bit s 20 both, SLOPE Q8.24 89 181 8 (N = 256)

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 two 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;
  • CompileHDL prints both numbers - the tail length and the minimum usable EXP - into the compilation log for every placement;
  • but EXP is a PIN. Nothing can stop a design driving it too low at RUN TIME. If EXP is under software control, clamp it in software to the minimum the compiler printed. A drift measurement wants a long block anyway - fitting a line to 64 samples of a slow drift is mostly fitting the noise.

Knowing where the block is: BUSY, INTEGRATING and SAMPLE_COUNT

Three optional status outputs, all defaulting to NO. They answer different questions:

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 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. BUSY high with INTEGRATING low is precisely the one or two divisions.
  • 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 sample, N after the N-th. It is NOT cleared at the block end: 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 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 - which is the useful thing to latch alongside the result, and on this block it is also the N your SLOPE refers to when you go to convert it into units per second. Only RESET clears it to 0.

Q formats

All three data ports carry their own fixed point format (integer bits, fractional bits, sign), the same convention as the Fixed P. family. Each quotient is requantised into its own format with the selected rounding (nearest / truncate) and overflow policy (saturate / wrap); saturation is symmetric for signed formats, as everywhere else in the toolchain.

Both outputs default to SIGNED, and both should stay that way for any real use: a drift can go either way, and a bipolar input can obviously have a negative intercept. See “UNITS” above for how to size them - it is the one place this block will surprise you.

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 solves the normal equations about the centred index in exact rational arithmetic and shares no algorithm with the core; the tolerance is 1 LSB on each output. Coverage includes a clean ramp - an exact fit, where the residual is zero and both outputs are exactly right, which is the strongest single check of the algebra - a ramp plus noise, a constant block (slope exactly 0, intercept the constant), pseudo-random input, a sine, slope-only and intercept-only placements to exercise the removed division and the changed port list, and a block-to-block alternating EXP to exercise the per-block latch of the exponent and both denominators. The tail length is checked at compile time: three copies of the formula exist

  • the BLR_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.