Median Filter
Sliding window median filter in fixed point. Removes impulsive noise and single sample spikes while leaving edges and fast transitions intact, which is exactly what a moving average cannot do. Input and output have independently selectable integer bits, fractional bits and sign. No DSP slice and no block RAM: the window is flip-flops and the rank network is comparators.
Introduction
The block outputs the median of the last WindowSize samples: it sorts the window and returns the middle value,
$$ \mathrm{OUT}(n) = \operatorname{median}\bigl(x(n),, x(n-1),, \ldots,, x(n-W+1)\bigr) $$
with $W$ odd, so the middle sample is unambiguous.
Why a median and not an average
A moving average spreads every disturbance over the whole window: one sample that is wildly wrong drags the output for $W$ samples, and every edge in the signal is smeared into a ramp. The median does neither. As long as fewer than half of the samples in the window are corrupted, the outlier is discarded outright – it never contributes to the result – and a step edge is reproduced as a step, with no overshoot and no ringing.
The three traces above are, from the bottom: the raw input, the same signal through the Mean Filter, and through the Median Filter. Both remove the baseline noise, but look at the pulse. The median keeps the leading edge as steep as it was and the peak at its true height; the mean rounds the peak off, lowers it and spreads it over the window. If the pulse height or its arrival time is what you are measuring, that difference is the whole story.
This makes it the natural first stage against impulsive noise: single sample glitches, pickup spikes, ADC bit errors, and the isolated hits that would otherwise trigger a downstream discriminator.
The price is that it is non linear: it has no frequency response, it cannot be cascaded or analysed like a FIR, and it slightly clips very short genuine pulses. A real pulse narrower than $\lceil W/2 \rceil$ samples is treated as an outlier and removed, so the window must stay shorter than the shortest feature you want to keep.
Pin Description
Properties
Number of samples in the sliding window. Odd values only, so the middle sample is unambiguous. Cost grows with the SQUARE of the window, so prefer the smallest window that removes your spikes.
Number of samples in the sliding window: 3, 5, 7, 9, 11, 13 or 15. Odd only, so the middle sample is unambiguous.
A window of $W$ rejects any burst of up to $\lfloor W/2 \rfloor$ consecutive corrupted samples, but also removes genuine pulses shorter than that. Cost grows with $W^2$.
Default: 5
Options: 3 5 7 9 11 13 15
Number of INTEGER bits of the input (the sign, when present, uses one of them).
Number of INTEGER bits of the input (1 to 64). When the input is SIGNED, one of these bits carries the sign.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, i.e. the bits to the right of the binary point. Total input width = integer + fractional bits.
Number of FRACTIONAL bits of the input (0 to 64), i.e. the bits to the right of the binary point. Total input width = integer + fractional bits.Default: 0
Options: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
Select whether the input is signed (two’s complement) or unsigned.
Arithmetic type of the input:
- SIGNED – two’s complement
- UNSIGNED – non negative only
Default: SIGNED
Options: UNSIGNED SIGNED
Number of INTEGER bits of the output.
Number of INTEGER bits of the output (1 to 64).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 output. Set it equal to the input to keep the result exact; use more bits only if you widen the format elsewhere.
Number of FRACTIONAL bits of the output (0 to 64). Set it equal to the input to keep the result exact – the median is always one of the input samples, so nothing is lost.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 output is signed or unsigned.
Arithmetic type of the output, SIGNED or UNSIGNED.Default: SIGNED
Options: UNSIGNED SIGNED
ROUND: round to nearest when the output has fewer fractional bits than the input. TRUNCATE: drop them (cheaper, adds a negative bias).
Only relevant when the output has fewer fractional bits than the input:
- ROUND – round to nearest
- TRUNCATE – discard the extra bits (cheaper, introduces a negative bias)
Default: ROUND
Options: TRUNCATE ROUND
YES: clip to the largest representable output value. NO: wrap around.
Only relevant when the output format cannot represent the selected sample:
- YES – clip to the largest representable value (symmetric, min = -max)
- NO – wrap around modulo the output width
Default: YES
Options: NO YES
Number of output register stages, i.e. extra latency in clock cycles. 0 takes the result straight out of the combinational logic.
Number of output register stages (0 to 8). Total latency is this value PLUS ONE, because the sliding window register is itself a clock. Does not affect the result.Default: 1
Options: 0 1 2 3 4 5 6 7 8
Functional description
A shift register holds the last WindowSize samples. Every sample is compared against every other one to compute its rank inside the window, and the sample whose rank is the middle one is sent to the output. Ties are broken by position, so the ranks are always a permutation of $0 \ldots W-1$ and exactly one sample carries the median rank – repeated values are handled correctly.
The window advances only when IN_DV is high, so the filter works on gated or decimated
streams as well as on a free running one.
Startup and reset
Reset clears the window to zero. The first $W-1$ outputs after a reset therefore see a window that is still partly filled with zeros, exactly like the initial state of a FIR filter. The output is fully meaningful from the $W$-th valid sample onwards.
Fixed-point format
Input and output carry their own Q format. A value with $N_{int}$ integer bits and $N_{frac}$ fractional bits occupies $N_{int} + N_{frac}$ bits and represents
$$ \text{value} = \frac{\text{raw integer}}{2^{N_{frac}}} $$
When the port is SIGNED one of the integer bits carries the sign (two’s complement).
Because the median is always one of the input samples, the result is exact whenever the output format is at least as wide as the input one, and the Rounding and Saturation properties then have no effect at all. They only come into play if you deliberately narrow the format on the way out.
Resource cost
The rank computation costs $W^2$ comparators, so the cost grows with the square of the window:
| WindowSize | comparators | typical use |
|---|---|---|
| 3 | 9 | single sample glitches |
| 5 | 25 | the usual default |
| 7 | 49 | noisier channels |
| 9 .. 15 | 81 .. 225 | heavy impulsive noise, slow signals |
Use the smallest window that removes your spikes. No DSP slice and no block RAM are used; the shift register is built from flip-flops.
The comparison network is combinational. PipelineLength fixes the latency of the block, and the high level synthesis scheduler distributes the network over that many cycles – so on a wide input and a large window, raising it is the first thing to try if timing closure fails; reducing the window is the second.
Implementation
The block is generated with Vitis HLS from a shared parametric core
(Resources/Code/window_filter.cpp), which also serves the Mean Filter: one source,
one requantiser, two reductions selected at synthesis time. Every property takes part in
the IP identity, so two placements with different settings get their own core and never
interfere. A Vitis HLS installation (or a remote build) is therefore required to compile
a design containing this block.
Latency
Total latency is PipelineLength + 1 clock cycles: the sliding window is itself a register, and PipelineLength adds that many further output stages on top of it. With PipelineLength = 0 the result comes straight out of the comparison network, one clock after the sample was consumed.
OUT_DV is delayed by exactly the same amount, so the flag always travels with the sample
it describes – this matters when IN_DV is gated rather than tied high.
Typical use cases
- Removing pickup spikes and single sample glitches ahead of a trigger or a discriminator
- Cleaning a baseline without rounding off the leading edge of real pulses
- Suppressing isolated ADC bit errors
- Pre-conditioning a slow signal (temperature, bias, rate) against outliers