Polyphase Decimator (polyphase_decimator_xs)
A polyphase decimator is a resource efficient method for decimating a signal.
Introduction
To decimate by a factor of N, a normal decimator operates by first filtering the incoming data stream with a low pass filter (with a maximum normalised frequency of 1/N), forwarding a sample to the output, and then discarding N-1 samples.
This is resource intensive, as it means the filtering is running at the faster sample rate of the two (i.e. the input rate). In addition, a lot of the samples passing through this filter are actually going to be discarded and therefore are unneeded.
Instead with a Polyphase Decimator the filter is split into sub-filters, each set relating to a single input sample. The input then steps through these sub-filters, a sample at a time. Once the last sub-filter output value has been calculated, all of the sub-filter outputs are summed together to provide the output sample value. Following this the input sample steps back to the first sub-filter, and the process continues.
For example, consider decimating the following example stream by a factor of three:
Input Samples |
A |
B |
C |
D |
E |
F |
G |
H |
I |
J |
K |
L |
M |
with a set of taps:
Filter Taps |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Traditional Decimator:
Output sample 1 |
A0 + B1 + C2 + D3 + E4 + F5 + G6 + H7 + I8 + J9 |
Output sample 2 |
B0 + C1 + D2 + E3 + F4 + G5 + H6 + I7 + J8 + K9
(This sample is then discarded by decimation)
|
Output sample 3 |
C0 + D1 + E2 + F3 + G4 + H5 + I6 + J7 + K8+ L9
(This sample is then discarded by decimation)
|
Polyphase Decimator:
Step 0 |
A0 + D3 + G6 + J9 |
Step 1 |
B1 + E4 + H7 |
Step 2 |
C2 + F5 + I8 |
Output |
Step 0 + Step 1 + Step 2 |
Design
The Polyphase Decimator is designed to allow runtime variable decimation rates, along with customisable low pass filter taps. The user of this component should ensure that the provided taps are correctly scaled, e.g. to provide a unity gain, as the taps are not scaled within this component.
Taps should be generated on the high-sample rate, as a low pass filter with a normalised cut-off frequency of 1/decimation_factor. If desired then additional filter shaping can be added.
Input values and taps should be scaled appropriately to ensure that saturation or overflow does not occur.
The internal data flow follows the following pseudo-states:
Wait for Data
Wait until a new input sample is received.
Once a sample is received, add it to the current filter-bank’s input data buffer. (This buffer shall have a minimum size of
taps_per_branch).This branch can then be processed.
Processing Sample
Multiply the taps and incoming sample data for the current filter bank.
If this is the last filter-bank:
Accumulate the outputs of all the filter-banks
Output this accumulation as a sample from the component.
Reset to the 0th filter-bank.
Return to Wait for Data.
If not the last filter-bank:
Increment to the next filter-bank
Return to Wait for Data.
Time Message Alignment
In order to help align messages in time, the decimator allows the group delay of the built-in filter to be entered as a property. If no group delay is defined, then this value is defaulted to zero.
The group delay is subtracted as a time offset from any incoming timestamps (this is the equivalent of delaying the timestamp relative to the samples).
The group delay is defined with two properties; group_delay_seconds for seconds, group_delay_fractional for fractional delay - these properties having the same format as the timestamp and sample interval opcodes.
If the subtraction of the group delay leads to a time value which is less than zero, then the value is wrapped around by the maximum value the timestamp field can hold (UQ32.64).
Interface
<?xml version="1.0"?>
<componentspec>
<property name="decimation_factor" type="uchar" initial="true" writable="false" default="1"
description="Decimation factor (number of branches)"/>
<property name="taps_per_branch" type="uchar" initial="true" writable="false" default="1"
description="Number of taps for each branch"/>
<property name="tap_array_length" type="ushort" parameter="true" writable="false" default="65535" description="Size of tap array"/>
<property name="rounding_type" type="enum" initial="true" writable="false" enums="truncate,half_up,half_even" default="half_even"
description="Rounding type"/>
<property name="group_delay_seconds" type="ulong" default="0" writable="true" description="Group delay of the filter in seconds"/>
<property name="group_delay_fractional" type="ulonglong" default="0" writable="true"
description="Group delay of the filter fractional"/>
<!-- LINT EXCEPTION: xml_012: 1: Cannot specify default value on an array -->
<property name="taps" type="short" arraylength="tap_array_length" writable="true" description="Array of tap values"/>
<port name="input" producer="false" protocol="complex_short_timed_sample-prot"/>
<port name="output" producer="true" protocol="complex_short_timed_sample-prot"/>
</componentspec>
Opcode Handling
Samples
Apply decimation to these samples and send to output port.
If there is not sufficient samples to produce a decimated sample, then no messages are forwarded.
If the number of samples is not a multiple of the decimation factor, then the number of output samples will be \(\lfloor{n_{input} \over N} \rfloor\), where \(n_{input}\) is the number of input samples, and \(N\) is the decimation factor. Any remaining samples are left in the worker input buffer.
Flush
If any data received since last reset:
Input
taps_per_branch*decimation_factorzeros into the sample buffer, forwarding any decimated output.Reset sample buffer, and sub-filter offset.
Forward Flush opcode to output port.
Otherwise, forward Flush opcode to output port.
Discontinuity
Reset input buffer.
Forward to output port.
Metadata
Forward to output port.
Sample_Interval
Calculate output sample interval as input sample_interval *
decimation_factor.Forward calculated value to output port.
Time
Resynchronises the internal input sample time (as long as there is not a previous time currently being processed).
This value is used, along with the incoming sample_interval and the group delay properties, to calculate the output sample time.
If the component is not processing samples (i.e. if no data samples have been received by the worker) then the calculated time is passed through.
If the component is currently processing samples and a time opcode is received:
The current decimated sample is completed and outputted.
The calculated time, plus sample_interval multiplied by samples since the new time was received, is outputted.
Notes:
The time opcode is affected by the group delay of the FIR filter, this is detailed above.
If a sample_interval has not been received, the time for the next outputted symbol cannot be calculated. In this case, the time is just passed straight through, resulting in a slight inaccuracy in the downstream time.
If a time opcode occurs in a processing block, there is no error checking for discontinuity
If sample_interval and time changes in the middle of a processing block then the outputted time and sample interval will occur after the end of processing the current sample block, however the ordering of these two messages might lead to a slight inaccuracy (if time is updated before sample_interval).
Properties
decimation_factor: Decimation factor (number of branches). N.B. A value of 1 means no decimation.Type:
ucharAccess:
Parameter:
FalseWritable:
FalseInitial:
TrueVolatile:
False
Default value:
1
taps_per_branch: Number of taps for each branch.Type:
ucharAccess:
Parameter:
FalseWritable:
FalseInitial:
TrueVolatile:
False
Default value:
1
tap_array_length: Size of tap array.Type:
ushortAccess:
Parameter:
TrueWritable:
FalseInitial:
FalseVolatile:
False
Default value:
65535
rounding_type: Rounding type.Type:
enumAllowed values:
['truncate', 'half_up', 'half_even']
Access:
Parameter:
FalseWritable:
FalseInitial:
TrueVolatile:
False
Default value:
half_even
group_delay_seconds: Group delay of the filter in seconds.Type:
ulongAccess:
Parameter:
FalseWritable:
TrueInitial:
FalseVolatile:
False
Default value:
0
group_delay_fractional: Group delay of the filter fractional.Type:
ulonglongAccess:
Parameter:
FalseWritable:
TrueInitial:
FalseVolatile:
False
Default value:
0
taps: Array of tap values. The actual number of taps required istaps_per_branch*decimation_factor.Type:
shortArray dimensions:
['tap_array_length']
Access:
Parameter:
FalseWritable:
TrueInitial:
FalseVolatile:
False
Default value:
None
Note. The precision of the taps property depends on the input protocol type:
For char and complex_char inputs:
the array data type is char
these are 8bit (S0.7) (i.e. in the range -1, 1).
sign bit | Fractional bits (7 bits)
To convert from float multiply by \(2^7\)
For short and complex_short inputs:
the array data type is short
these are 16bit (S0.15) (i.e. in the range -1, 1).
sign bit | Fractional bits (15 bits)
To convert from float multiply by \(2^{15}\)
For long and complex_long inputs:
the array data type is long
these are 32bit (S0.31) (i.e. in the range -1, 1).
sign bit | Fractional bits (31 bits)
To convert from float multiply by \(2^{31}\)
The array is intended to be entered “Branch First”, therefore the following shape is used. (Note that this shape is the natural shape for the taps as produced by the low pass filter calculated at the interpolated sampling rate.)
branch |
0 |
1 |
2 |
3 |
0 |
1 |
2 |
3 |
tap_addr |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
Ports
Inputs:
input: Primary input samples port.Protocol:
complex_short_timed_sample-protOptional:
False
Outputs:
output: Primary output samples port.Protocol:
complex_short_timed_sample-protOptional:
False
Implementations
Example Application
<?xml version="1.0"?>
<application done="file_write">
<instance component="ocpi.core.file_read" connect="polyphase_decimator_xs">
<property name="filename" value="input.bin"/>
</instance>
<instance component="ocpi.comp.sdr.dsp.polyphase_decimator_xs" connect="file_write">
<property name="decimation_factor" value="16"/>
<property name="taps_per_branch" value="8"/>
<property name="taps" valuefile="taps.data"/>
<property name="rounding_type" value="half_even"/>
</instance>
<instance component="ocpi.core.file_write">
<property name="filename" value="output.bin"/>
</instance>
</application>
Dependencies
The dependencies to other elements in OpenCPI are:
components/dsp/common/polyphase_decimator
components/math/common/time_utils.hh
There is also a dependency on:
ieee.std_logic_1164
ieee.numeric_std
ieee.math_real
Limitations
Limitations of polyphase_decimator_xs are:
None.
Testing
Tested platforms: None
Component testing not completed.