Xilinx
HLS
Block Preview

Introduction

The Block RMS block chops the input stream into consecutive blocks of N samples and, at the end of each block, publishes the root mean square of that block:

$$ S_2 = \sum_{i=0}^{N-1} x_i^2 , \qquad \mathrm{rms} = \sqrt{\frac{S_2}{N}} $$

One multiplier for $x^2$, one accumulator, one shift, one serial square root, one requantisation. That is the whole block.

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 N is a power of two, the division by N is an exact arithmetic shift. There is no divider and no reciprocal ROM, which is also why the block size can be changed while the design is running, for free.

What it is FOR

The RMS is the amplitude of the block in the units of the input. It is the natural reading for a level meter, the error signal of an AGC loop, and the standard noise figure of a detector channel. Unlike the standard deviation it includes the DC level: a constant signal at level $c$ has an RMS of $|c|$ and a standard deviation of 0.

Two cheaper alternatives are worth knowing before you place this block:

  • if the consumer only compares against a threshold, use Block Mean Square instead. The square root is monotone, so comparing $\overline{x^2}$ against the SQUARE of your threshold gives exactly the same decisions - and Block Mean Square has no root at all, a constant $L = 2$ tail, and no minimum usable exponent worth the name. Compare powers, square the threshold.
  • if you only want a robust “how noisy is this” number, Block Mean Abs costs no multiplier and no root ($L = 2$) and, for a Gaussian signal, reads $\sqrt{2/\pi} = 0.7979$ times the standard deviation.

Take this block when the RMS number itself has to be reported, in input units, to a display, a log or a control loop.

When to use this instead of Block Statistics

The all-in-one Block Statistics block is not deprecated and computes this same RMS among twenty other statistics. The rule is simple:

  • you want several statistics of the SAME block - RMS and mean 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: the root engine that this block runs alone is the same one Block Statistics reuses for the standard deviation.
  • you want exactly one number - use this block. Then you synthesise only that number: the pin list, the accumulator, the root and the tail are all that the RMS 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 Input IN_BitsInt + IN_BitsFract bit BIT VECTOR
Input samples, fixed point in the IN Q format. Squared and accumulated only on the clocks where IN_DV is high.
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. 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. Must not be driven below the minimum usable exponent of the configuration, or that block’s result is dropped (see “Timing: the serial tail”). Unconnected defaults to 10 (N = 1024).
RMS Output RMS_BitsInt + RMS_BitsFract bit BIT VECTOR
$\sqrt{S_2 / N}$ in the RMS Q format, within 1 LSB of the correctly rounded value. 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 sample of the block was accepted, not when that sample arrives. RMS is updated on this clock and on no other. BUSY is still high here and falls on the next clock.
CLK 1 bit
Clock.
RESET 1 bit
Synchronous reset: clears the 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, so it is low for the whole tail. On a continuous stream it dips for exactly 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, so on the OUT_DV clock it reads the length of the block being presented. 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.

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.

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 squared and accumulated.

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 accumulator is 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 internal sum of squares by one bit per unit; it does NOT lengthen the tail of this block, which depends only on the RMS width. 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

RMS Integer Bits RMS_BitsInt

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

Integer bits of the RMS output. 1..64, default 16. The RMS is in the units of the input and can never exceed the largest magnitude in the block, so the input integer bits are always enough.

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

RMS Fractional Bits RMS_BitsFract

Number of FRACTIONAL bits of the RMS 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 RMS output. 0..64, total width 2..64 bits, default 0. The total width sets the length of the serial square root (RTW = width + 5 steps, L = RTW + 3 clocks), so ask for the resolution you need and no more - every extra bit is one more clock of tail and can raise the minimum usable block exponent.

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

RMS Sign RMS_Sign

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

SIGNED or UNSIGNED RMS output. Default UNSIGNED - an RMS is never negative, so UNSIGNED buys one bit of range for free.

Default: UNSIGNED

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, serial 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, I am computing”. 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 the root has to be requantised into the output format. TRUNCATE: drop the bits (cheaper, adds a negative bias). Default ROUND. The digit recurrence itself always truncates; this setting governs the final requantisation that drops the 4 guard bits.

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 output format (symmetric bounds for signed formats). NO: wrap around. Default YES. It matters when the RMS format is too narrow for the amplitude - the pre-scaling of the radicand saturates on the same rule.

Default: YES

Options: NO YES

Accuracy

The radicand is formed exactly. $S_2$ is an exact integer accumulator, the division by N is a shift, and the whole thing is pre-scaled by $2^{2 \cdot \mathrm{SQG}}$ with $\mathrm{SQG} = 4$ so that the digit recurrence delivers

$$ \mathrm{root} = \mathrm{rms}\cdot 2^{\mathrm{SQG}} = \sqrt{S_2 \cdot 2^{,2(\mathrm{RMS_{fract}}+\mathrm{SQG}) - 2,\mathrm{IN_{fract}} - \mathrm{EXP}}} $$

i.e. the root with 4 guard bits below the output LSB. The recurrence truncates and the final requantisation rounds, so the total error is bounded by

$$ |\mathrm{rms}{\mathrm{dut}} - \mathrm{rms}{\mathrm{exact}}| \le \frac{2}{16} + \frac{1}{2} < 1\ \mathrm{LSB} $$

and the host regression allows exactly 1 LSB - never more. There is no accumulated rounding, no truncated intermediate quotient and no approximation anywhere else in the datapath.

The pre-scaling shift saturates: a radicand that does not fit means the root does not fit either, which is precisely the output saturation you asked for with the Saturation property.

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

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. You can sweep EXP from a register interface while the stream runs and every result stays self-consistent.

Timing: the serial tail

Why it is serial

A block has a whole block period of slack after its N-th sample: the next result is not due for another $2^{\mathrm{EXP}}$ clocks. So there is no reason to build a parallel datapath for the post-accumulation arithmetic. II=1 is only needed while ACCUMULATING - one sample per clock, one multiply, one add - and the tail has all the time in the world.

The square root is therefore ONE reused compare-subtract stage stepped once per clock, producing one result bit per clock, instead of an unrolled digit recurrence costing RTW comparators. The cost is CLOCKS, not comparators, and there is no DSP in the tail - the only multiplier in the block is the $x \cdot x$ during accumulation.

A direct consequence: a wide RMS format costs clocks, not gates.

The latency contract

OUT_DV pulses for one clock when the tail COMPLETES, L clocks after the clock on which the N-th sample of the block was accepted - not when that sample arrives. RMS is updated on that same clock and on no other. The state walk is IDLE -(go)-> LD -> SQRT x RTW -> FIN -> IDLE with OUT_DV registered, so

$$ \mathrm{RTW} = \mathrm{RMS\ width} + \mathrm{SQG} + 1 = \mathrm{RMS\ width} + 5, \qquad L = 2 + (\mathrm{RTW} + 1) $$

L depends only on the RMS output width. It does not depend on the input width, and - unlike Block Variance and Block Std Dev - it does not depend on EXP either: this block’s tail is a constant for a given configuration.

The 2^EXP >= L rule

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. A completed block is only handed to the tail when the tail is IDLE, so: no OUT_DV for it, the accumulator is unaffected, later blocks come out correctly - and there is no error pin. A result is simply skipped.

Three things guard against it, and one hole remains:

  • the property validator refuses a configuration whose minimum exponent exceeds Max Block Exponent, and tells you the tail length and the minimum;
  • CompileHDL prints the tail length and the minimum usable EXP into the compilation log for every placement;
  • but EXP is a PIN. Nothing can stop a user driving it below the minimum at RUN TIME. If you make EXP runtime programmable from a register interface, clamp it in your own logic - the block will not complain, it will just stop emitting some results.

Worked numbers

Input RMS format RTW L minimum EXP
8 bit signed 16 bit (default) 21 24 5 (N = 32)
8 bit signed 8 bit 13 16 4 (N = 16)
16 bit signed 16 bit (default) 21 24 5 (N = 32)
16 bit signed 24 bit 29 32 5 (N = 32)
16 bit signed 32 bit 37 40 6 (N = 64)

Take the first row in full. An 8 bit signed input with the default RMS format (16 integer bits, 0 fractional bits, unsigned) gives $\mathrm{RTW} = 16 + 5 = 21$ and $L = 2 + 21 + 1 = \mathbf{24}$ clocks. The smallest exponent with $2^{\mathrm{EXP}} \ge 24$ is $\mathrm{EXP} = 5$ ($2^5 = 32 \ge 24$; $2^4 = 16 < 24$), so the minimum usable EXP is 5, a block of 32 samples. Every row is computed the same way - and note that the input width never enters the formula.

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 covers the accumulation and the serial 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. BUSY high with INTEGRATING low therefore means “the samples are all in, I am computing”.
  • 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. The two separate visibly only when the input pauses for longer than the tail.
  • 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 N through the whole tail and past OUT_DV, until the first sample of the next block takes it back to 1. So on the OUT_DV clock it reads the length of the block being presented - which is the useful thing to latch alongside the result. Only RESET clears it to 0.

Q formats

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

Sizing: the RMS is in the units of the input and can never exceed the largest magnitude in the block, so the input integer bits are always enough. An RMS is never negative, so UNSIGNED buys one bit - which is the default. The total RMS width is what sizes the serial root ($\mathrm{RTW} = \mathrm{width} + 5$ steps), so ask for fractional bits when you want sub-LSB amplitude resolution, but do not make the format wider than you need: every extra bit is one more clock of tail and one step closer to the minimum usable exponent.

Cost

One multiplier for $x^2$ (unavoidable at one sample per clock), one accumulator ($2\cdot\mathrm{IN_SW} - 1 + $ Max Block Exponent bits), one barrel shifter for the pre-scaling, one compare-subtract stage with a radicand, a remainder and a root register, and one requantiser. No divider, no second multiplier and no DSP in the tail.

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 $\sqrt{S_2/N}$ in exact rational arithmetic and shares no algorithm with the core; the tolerance is 1 LSB and not one count more. RMS coverage includes pseudo-random, sine, all-zero and constant inputs, a fractional output format, truncate instead of round, IN_DV gaps inside the accumulation, an EXP that alternates between blocks, and a burst input that stops for longer than the 24-clock tail so that BUSY and INTEGRATING are separated for the whole of it. The status outputs are checked clock by clock against the contract above. 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 the two clock by clock; tb/block-ops/port_check.py preprocesses the real core to prove the symbol’s pin list matches the entity’s port list for every enable combination, and the harness #errors at COMPILE TIME if the tail formula in the .cpp, in the plugin and in the generator ever disagree.