DSP - BLOCK ENERGY
The energy of a block of N consecutive samples - the UNNORMALISED sum of squares, deliberately NOT divided by N, so doubling the block length doubles the number. N is a power of two chosen at RUN TIME on the EXP input pin (EXP = 10 means N = 1024). The whole core is ONE accumulator: no divider, no square root, no serial arithmetic, so the result of a block is presented exactly 2 clocks after that block’s last sample - the shortest tail in the family. The accumulator is 2*IN_SW-1+MaxBlockExponent bits wide (51 for a 16 bit input and a 2^20 block), so watch the ENERGY output width: a narrow format saturates on a loud block, which is a deliberate trade against a 51 bit pin. IN_DV is the only qualifier and there is deliberately no CE pin. Optional BUSY / INTEGRATING / SAMPLE_COUNT status outputs. Blocks of up to 2^20 samples out of the box, 2^31 if you ask for it.
Introduction
The Block Energy block chops the input stream into consecutive blocks of N samples and, at the end of each block, publishes the energy of that block:
$$ S_2 = \sum_{i=0}^{N-1} x_i^2 , \qquad \mathrm{ENERGY} = S_2 $$
That is the whole block. One multiplier for $x^2$, one accumulator, one requantisation.
The sum is NOT divided by N, on purpose. This is the discrete equivalent of $\int x^2,dt$, which is what energy means for a sampled signal: it is an extensive quantity and it grows with the block length. Double N and the number doubles.
| you want | use |
|---|---|
| $\sum x^2$, grows with N | Block Energy (this block) |
| $\frac{1}{N}\sum x^2$, length independent | Block Mean Square |
| $\sqrt{\frac{1}{N}\sum x^2}$, length independent, in input units | Block RMS |
Both of the length-independent forms are exactly one shift or one square root away from this number, and both already exist as their own IP - do not build them out of this one.
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 |
Because the energy has no $1/N$ in it, the latched exponent is not even used by the arithmetic here: it only decides when the block ends. That is why this is the one block of the family whose tail is a constant.
What it is FOR
Energy is the natural number wherever a quantity of signal matters rather than an average level:
- pulse integration - the energy of a gate is proportional to the charge collected in it, which is the basis of energy spectroscopy in a detector chain;
- power over a fixed window - divide by the (constant) gate length in software once, instead of paying for a divider per block in the fabric;
- feeding a downstream ratio - two energies over the same block length compare directly, so a fast/slow energy ratio for pulse shape discrimination needs no normalisation at all;
- the raw material for RMS and mean square, when you want the sum itself on a bus and the derived numbers computed by a CPU.
Cost
One multiplier for $x^2$ (unavoidable at one sample per clock), one accumulator of $2\cdot\mathrm{IN_SW}-1+\mathit{MaxBlockExponent}$ bits and one requantiser. No divider, no square root, no serial arithmetic and no exponent shift. Together with Block Sum this is the cheapest block of the family, and it has the shortest tail of all of them: $L = 2$ clocks.
When to use this instead of Block Statistics
This is the one statistic of this round that the all-in-one Block
Statistics block already computes - it is its SUM_SQ output, bit for bit
the same quantity from the same accumulator. (That is why the regression can
compile both cores into one binary and compare them on every clock; see
“Verification”.) The rule is the usual one:
- you want several statistics of the SAME block - the energy and the RMS
and the 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, and
SUM_SQin particular is already there next toRMSandMEAN_SQ, which are derived from the very same $S_2$. - you want exactly one number - use this block. Then you synthesise only that number: the pin list, the accumulator and the two-clock tail are all that the energy needs, and nothing else reaches the synthesiser.
Two Block Statistics blocks side by side would duplicate the accumulators; two per-operator blocks side by side duplicate them too. One Block Statistics block never does.
Pin Description
IN_DV is high.
'1'. (There is deliberately no CE pin - to stall
the block, gate this.)
EXP. Use EXP >= 1 (see “Timing”).
Unconnected defaults to 10 (N = 1024).
OUT_DV clock and on no other; it holds the previous
block’s result until then.
ENERGY is updated on this clock and on no other. BUSY
is still high here and falls on the next clock.
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.
How many samples have been accumulated so far in the current block: 1 after the first, N after the N-th. It is NOT cleared at the end of a block
- it HOLDS the final count through the tail and past
OUT_DV, until the first sample of the next block takes it back to 1, it reads N atOUT_DVonly when the input STOPS for the whole tail. On a CONTINUOUS stream the next block has already started by then, soOUT_DVshows how far into it the input has got, not N - the clock that always reads N is the oneINTEGRATINGfalls on. On this block that is also the divisor a CPU needs to turn the energy into a mean square. OnlyRESETclears it to 0. Fixed 32 bits. Present on the symbol only when Enable SAMPLE_COUNT = YES.
Properties
Number of INTEGER bits of the input sample (the sign, when present, uses one of them).
Integer bits of the input sample (the sign, when present, uses one of them). 1..64. Default 16.Default: 16
Options: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
Number of FRACTIONAL bits of the input sample, i.e. the bits to the right of the binary point. Total width = integer + fractional bits, and must not exceed 64.
Fractional bits of the input sample. 0..64. Total input width must be 2..64 bits. Default 0. They enter the output scaling doubled: the requantiser shifts by $\text{ENERGY}{fract} - 2,\text{IN}{fract}$.Default: 0
Options: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
Select whether the input sample is signed (two’s complement) or unsigned.
SIGNED (two’s complement) or UNSIGNED input. Default SIGNED. An UNSIGNED input costs one extra bit internally, because a sample has to be promoted to signed before it can be squared - and the accumulator is $2\cdot\text{IN_SW}-1+\textit{MaxBlockExponent}$ bits, so that one bit costs two bits of accumulator.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 accumulator is sized for: the block can be up to $2^{\text{MaxBlockExponent}}$ samples long, and theEXP input is
clamped to this value at run time. Raising it widens the internal
sum-of-squares register by one bit per unit; it does NOT lengthen the
latency of this block, which is a constant 2 clocks whatever you set here.
Keep it at the largest block you actually use. 1..31, default 20, i.e.
blocks of up to 1048576 samples out of the box.
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 ENERGY output (the sign, when present, uses one of them).
Integer bits of the ENERGY output. 1..64, default 40. This is the number to think about: the value can reach $2\cdot\text{IN_SW}-1+\text{EXP}$ bits, which is 51 for a 16 bit signed input and a 2^20 block, so the default of 40 saturates on a loud long block - deliberately, because the alternative is a 51 bit pin. Size it from the table in “Sizing the ENERGY output” if saturation is not acceptable.Default: 40
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 ENERGY 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 ENERGY output. 0..64, total width 2..64 bits, default 0. A sum of squares is already an integer count of $2^{-2,\text{IN}_{fract}}$ units, so fractional bits here only buy resolution below that; each one also costs a bit of the width budget you probably need for range instead.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 ENERGY output is signed (two’s complement) or unsigned.
SIGNED or UNSIGNED ENERGY output. Default UNSIGNED - a sum of squares is never negative, and UNSIGNED buys one bit, i.e. one extra doubling of block length before saturation. Choose SIGNED only for a downstream bus that requires it.Default: UNSIGNED
Options: UNSIGNED SIGNED
YES: the BUSY (high from the first sample of a block until its result is out – it COVERS THE SERIAL TAIL, and its last high clock IS the OUT_DV pulse) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.
YES: theBUSY pin exists. It is high from the first sample of a block
until its result is out, tail included, and its last high clock is the
OUT_DV pulse. NO: the pin and its register are removed before synthesis.
Default NO.
Default: NO
Options: NO YES
YES: the INTEGRATING (high only while the block is ACCUMULATING; it drops as soon as the N-th sample has been taken and the tail starts, so BUSY-and-not-INTEGRATING means ‘computing’) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.
YES: theINTEGRATING pin exists. It is high only while the block is
accumulating, so BUSY high with INTEGRATING low means “the samples are
all in, I am computing” - a two-clock window on this block. NO: the pin
and its register are removed. Default NO.
Default: NO
Options: NO YES
YES: the SAMPLE_COUNT (32 bit, how many samples have been accumulated so far in the current block: 1 after the first, N after the N-th. It is NOT cleared at the block end – it holds N until the NEXT block’s first accepted sample takes it back to 1. On a CONTINUOUS stream that happens DURING the serial tail, so at OUT_DV it reads how far into the next block the input has already got, NOT N. To capture the length of the block being presented, latch SAMPLE_COUNT on the clock INTEGRATING falls – that one always reads N) pin is present. NO: the pin AND all of its logic are removed BEFORE synthesis, so nothing is paid for it.
YES: theSAMPLE_COUNT pin exists - a fixed 32 bit count of the samples
accumulated so far in the current block, holding the final count through
the tail and past OUT_DV. Particularly useful here, because the energy
is not normalised and the count is what normalises it. 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 the sum has to be requantised into a coarser output format. TRUNCATE: drop the bits (cheaper, adds a negative bias). 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 ENERGY format (symmetric bounds for signed formats). NO: wrap around. This one matters on this block: unlike a mean, the energy really can outgrow a sensible output format on a loud or long block, and a wrapped energy is worse than a clipped one because it is not even monotonic. Default YES.Default: YES
Options: NO YES
Accuracy
The accumulator $S_2$ is an exact integer: each sample is squared at full
width and added, with no rounding and no truncated intermediate anywhere. The
only error in this block is the single final requantisation into the Q
format you chose for the ENERGY pin.
That is not an aspiration. The host regression (tb/block-ops/run_tb.ps1)
demands tolerance ZERO against a Python golden (tb/block-ops/ gen_golden.py) that evaluates $\sum x_i^2$ in exact integer arithmetic - not
“within 1 LSB”, not “within a few counts”. Any deviation at all fails the
build.
Sizing the ENERGY output - the one thing to get right here
Energy grows with both the sample width and the block length, so it is the one block of the family where the default output format is not automatically safe. Internally the accumulator is sized for the worst case:
$$ S_2 \le 2^{,2\cdot\mathrm{IN_SW}-2}\cdot 2^{\mathrm{EXP}} \qquad\Longrightarrow\qquad \text{accumulator width} = 2\cdot\mathrm{IN_SW}-1+\mathit{MaxBlockExponent} $$
where $\mathrm{IN_SW}$ is the total input width, plus one bit if the input
is UNSIGNED (a sample has to be promoted to signed before it can be
squared). The accumulator therefore never overflows. The ENERGY pin is a
different matter:
| Input | Max Block Exponent | accumulator | ENERGY width for no saturation |
|---|---|---|---|
| 8 bit signed | 10 | 2·8−1+10 = 25 | 25 |
| 12 bit signed | 16 | 2·12−1+16 = 39 | 39 |
| 16 bit signed | 10 | 2·16−1+10 = 41 | 41 |
| 16 bit signed | 20 | 2·16−1+20 = 51 | 51 |
| 16 bit unsigned | 20 | 2·17−1+20 = 53 | 53 |
(the table assumes IN and ENERGY both have zero fractional bits; each
ENERGY fractional bit adds one to the width needed, each IN fractional bit
removes two.)
The default ENERGY format is Q40.0 unsigned, which is wide but not 51
bits - a 51 bit pin is worse than a saturating one for almost every design.
Work out what you actually need:
- 16 bit signed samples reach $|x| = 32768$, so $x^2 \le 2^{30}$;
- a full scale block of N samples therefore reaches $N\cdot 2^{30}$;
- at N = 512 that is $2^{39}$ and fits in 40 bits; at N = 1024 it is exactly $2^{40}$ and saturates by one LSB, because a 40 bit unsigned stops at $2^{40}-1$;
- a half scale sine over the same N = 1024 block is far smaller - $\tfrac{1}{2}(16384)^2\cdot 1024 \approx 1.37\times10^{11} \approx 2^{37}$ - and fits comfortably.
So the default is right for a real signal at a sane level and wrong for a
deliberately clipped one. If you cannot tolerate saturation, size ENERGY
from the table above; if you can, leave 40 bits and keep the routing. With
Saturation = YES an over-range block clamps to the format maximum, which at
least stays monotonic; with Saturation = NO it wraps, which does not.
The one-bit trap. $S_2$ is an unsigned accumulator and the requantiser takes a signed value, so the core widens it by one bit before handing it over. A same-width signed cast would read a top-bit-set accumulator back as negative and the saturation path would then clamp to zero instead of to the maximum - the exact opposite of the right answer. That widening is done for you; it is mentioned only because it is the kind of thing that looks like a mysterious “loud blocks read zero” bug if it is ever removed.
Accumulation and IN_DV
IN_DV is the only qualifier. It says “this clock carries a sample”: a
sample is squared, accumulated, and counts towards N, exactly on the clocks
where IN_DV is high. Clocks with IN_DV low are ignored completely -
whatever sits on IN during them cannot corrupt the block - while the tail
keeps running, which is what you want: the tail has nothing to do with the
input stream.
Unconnected, IN_DV ties to '1' and EXP ties to 10 (N = 1024), so the
block free-runs with nothing wired except IN.
There is deliberately no CE pin. On the all-in-one Block Statistics block an earlier revision had one, and it did not survive synthesis: with nothing but internal state gated by it, Vitis could reason the frozen path away and delete the port from the generated entity while SciCompiler’s wrapper still wired it, which failed a real Vivado build with [VRFC 10-718] formal port <ce> does not exist in entity. The whole per-operator family was built without one. To stall this block, gate its
IN_DV- a block that only accumulates onIN_DVhas no need to be frozen.
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.
Because the energy carries no $1/N$, the latched exponent never reaches the
arithmetic - it only decides where the block ends. That also means an energy
is only comparable with another energy taken over the same N: if you sweep
EXP at run time, latch SAMPLE_COUNT alongside every result and normalise
downstream.
Timing: the latency contract
OUT_DV pulses for one clock, L clocks after the clock on which the N-th
sample of the block was accepted - not when that sample arrives. ENERGY is
updated on that same clock and on no other. For this block
$$ L = 2 $$
and it is a constant: there is no serial arithmetic here at all, so L does
not depend on the input width, on the output width, on Max Block Exponent or
on EXP. The two clocks are one to enter the final state and one to present
the registered result. This is the shortest tail in the family.
The rule that governs the whole family is that the tail of one block must finish before the next block completes, i.e.
$$ 2^{\mathrm{EXP}} \ge L $$
If a block completes while the previous tail is still running, that block’s
result is DROPPED: no OUT_DV for it, the accumulator is unaffected and
later blocks come out correctly, but a result is silently skipped. There is
no error pin for it.
With $L = 2$ that condition is $2^{\mathrm{EXP}} \ge 2$, i.e. EXP $\ge$ 1,
so it cannot bite here: the only value that violates it is EXP = 0, a
block of a single sample. The property page still refuses any configuration
whose minimum exponent would exceed Max Block Exponent, and CompileHDL
still prints the tail length and the minimum usable EXP in the compilation log
- here they will always read 2 and 1. The blocks where this rule really matters are the ones with a serial tail: Block Std Dev, Block Std Error of Mean, Block Coefficient of Variation, Block SNR and Block Crest Factor, whose L runs to tens or hundreds of clocks.
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 |
| tail computing | 0 | 1 |
| idle | 0 | 0 |
Every output of this block is a register, so each status bit is observed on the clock after the event that sets it:
INTEGRATINGrises on the clock after the FIRST sample of a block is accepted and falls on the clock after the N-th - it is high exactly while the block is ACCUMULATING.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. With a two-clock tail,BUSYhigh andINTEGRATINGlow is a two-clock window.- On a continuous stream the next block starts before the previous tail
ends, so
BUSYnever drops andINTEGRATINGdips for exactly one clock per block boundary - which makes it a free block marker. SAMPLE_COUNTis a fixed 32 bits and reads 1 after the first accepted sample, N after the N-th. It is NOT cleared at the block end: it HOLDS N through the tail and pastOUT_DV, until the first sample of the next block takes it back to 1. so it reads N atOUT_DVonly when the input STOPS for the whole tail; on a CONTINUOUS stream the next block has already started andOUT_DVshows how far into it you are. The clock that always reads N is the oneINTEGRATINGfalls on - which is the useful thing to latch alongside the result, and on this block it is the number you need to turn an extensive energy back into an intensive one. OnlyRESETclears it to 0.
Q formats
Both ports carry their own fixed point format (integer bits, fractional bits,
sign), the same convention as the Fixed P. family. The accumulated sum is
requantised into the ENERGY format with the selected rounding (nearest /
truncate) and overflow policy (saturate / wrap); saturation is symmetric for
signed formats, as everywhere else in the toolchain.
The scaling is the obvious one: a sample carries $2^{-\mathrm{IN_{fract}}}$ per LSB, so its square carries $2^{-2,\mathrm{IN_{fract}}}$, and the requantiser shifts the exact integer sum by $\mathrm{ENERGY_{fract}} - 2,\mathrm{IN_{fract}}$ places. Two input fractional bits are worth one output fractional bit when you are counting the width you need.
A sum of squares is never negative, so UNSIGNED is the right choice for
ENERGY and buys you one bit - which is exactly one doubling of block
length before saturation. SIGNED is offered only for downstream buses that
insist on it.
Verification
The core is regression tested by a host-side csim harness
(tb/block-ops/run_tb.ps1) that runs one simulated clock at a time and
follows OUT_DV. The expected values come from tb/block-ops/gen_golden.py,
which evaluates $\sum x_i^2$ in exact integer arithmetic and shares no
algorithm with the core; the tolerance is 0. Coverage includes
pseudo-random and ramp inputs, maximum positive and maximum negative samples,
unsigned input, fractional input and output formats, truncate instead of
round, a deliberately narrow saturating output format (the case this guide
warns about), IN_DV gaps inside the accumulation, an EXP that changes half
way through a block, and an EXP driven above Max Block Exponent to
exercise the clamp. The status outputs are checked clock by clock against
the contract above. Finally - and uniquely among the operators added in this
round - a cross-check compiles this core and the all-in-one block_stats.cpp
into the same binary, drives them with identical stimulus, and compares this
block’s ENERGY against that block’s SUM_SQ bit for bit on every clock.