Polyphase Interpolator (polyphase_interpolator_xs)

A polyphase interpolator is a resource efficient method for interpolating signals.

Introduction

To interpolate by a factor of M, a normal interpolator operates by first inserting M-1 zero samples into the data stream, after each data sample, followed by passing the resulting stream through a low pass filter (with a maximum normalised frequency of 1/M).

This is resource intensive, as it means the filtering is running at the faster sample rate of the two (i.e. the output rate). In addition, a lot of the samples passing through this filter are actually going to be zero and therefore unnecessary multiplications are performed.

Instead with a Polyphase Interpolator the filter is split into sub-filters, each set relating to an output sample which would be created from the same input values, with their relative signal phase present. This subset of filter values can be multiplied with the incoming sample data to produce an output sample, allowing the filtering to be undertaken at the incoming sample rate. In addition this also allows each of these sub-filters to be calculated in parallel.

For example, consider interpolating the following example stream by a factor of three:

Input Samples

A

B

C

D

E

F

G

H

I

with a set of taps:

Filter Taps

0

1

2

3

4

5

6

7

8

Traditional Interpolator:

Zeroes Added

x

x

A

x

x

B

x

x

C

x

x

D

Sample 0

A0 + x1 + x2 + B3 + x4 + x5 + C6 + x7 + x8

Sample 1

x0 + A1 + x2 + x3 + B4 + x5 + x6 + C7 + x8

Sample 2

x0 + x1 + A2 + x3 + x4 + B5 + x6 + x7 + C8

Polyphase Interpolator:

Sample 0

A0 + B3 + C6

Sample 1

A1 + B4 + C7

Sample 2

A2 + B5 + C8

Each line in the above corresponds to a single branch (or filter bank) of the interpolator.

Design

The Polyphase Interpolator is designed to allow runtime variable interpolation rates, along with customisable taps providing a low pass filter. 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.

This component uses an HDL primitive, or a generalised C++ class, which produces a single output set of samples for a given set of incoming samples.

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 input data buffer.

  • This buffer should be the same size as the (number of taps)/(interpolation factor).

  • Then start Processing Sample.

Processing Sample

  • Beginning at branch(m) = 0, and Tap(n) = 0.

  • For each branch:

    • For each tap:

      • Multiply Tap[m + (n * interpolation factor)] with Sample[n] (performing a normalisation shift for integer data protocols), and accumulate the result.

      • Increment n by 1.

    • Once the end of the input sample buffer (and therefore the end of the taps) has been reached, take the value in the accumulator and output it.

    • Once the value has been exported, increment to the next filter bank (m = m + 1).

  • Once all the filter banks have been outputted, return to Wait for Data.

Time Message Alignment

In order to help align messages in time, the interpolator 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="interpolation_factor" type="uchar" initial="true" writable="false" default="1"
    description="Upsample factor (number of branches)"/>
  <property name="max_number_taps" type="ushort" parameter="true" default="64"
    description="Defines the maximum number of taps across all interpolation branches"/>
  <property name="number_of_taps" type="ushort" initial="true" writable="false" default="max_number_taps"
    description="Number of taps across all interpolation branches"/>
  <property name="rounding_type" type="enum" initial="true" writable="false" enums="truncate,half_up,half_even" default="truncate"
    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: 2:
       Cannot set default as size varies with parameter values. -->
  <property name="taps" type="short" arraylength="max_number_taps" 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 interpolation to these samples and send to output port.

  • Flush

    • Input number_of_taps zeros into the sample buffer, outputting any samples generated.

    • Reset sample buffer, and sub-filter offset.

    • Forward 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 / interpolation_factor.

    • Forward calculated value to output port.

  • Time

    • Calculate output time by removing group delay value.

    • Forward calculated value to output port.

Properties

  • interpolation_factor: Upsample factor (number of branches).

    • Type: uchar

    • Access:

      • Parameter: False

      • Writable: False

      • Initial: True

      • Volatile: False

    • Default value: 1

  • max_number_taps: Defines the maximum number of taps across all interpolation branches.

    • Type: ushort

    • Access:

      • Parameter: True

      • Writable: False

      • Initial: False

      • Volatile: False

    • Default value: 64

  • number_of_taps: Number of taps across all interpolation branches.

    • Type: ushort

    • Access:

      • Parameter: False

      • Writable: False

      • Initial: True

      • Volatile: False

    • Default value: max_number_taps

  • rounding_type: Rounding type.

    • Type: enum

      • Allowed values: ['truncate', 'half_up', 'half_even']

    • Access:

      • Parameter: False

      • Writable: False

      • Initial: True

      • Volatile: False

    • Default value: truncate

  • group_delay_seconds: Group delay of the filter in seconds.

    • Type: ulong

    • Access:

      • Parameter: False

      • Writable: True

      • Initial: False

      • Volatile: False

    • Default value: 0

  • group_delay_fractional: Group delay of the filter fractional.

    • Type: ulonglong

    • Access:

      • Parameter: False

      • Writable: True

      • Initial: False

      • Volatile: False

    • Default value: 0

  • taps: Array of tap values.

    • Type: short

      • Array dimensions: ['max_number_taps']

    • Access:

      • Parameter: False

      • Writable: True

      • Initial: False

      • Volatile: False

    • Default value: None

Ports

Inputs:

  • input: Primary input samples port.

    • Protocol: complex_short_timed_sample-prot

    • Optional: False

Outputs:

  • output: Primary output samples port.

    • Protocol: complex_short_timed_sample-prot

    • Optional: False

Implementations

  • polyphase_interpolator_xs (HDL)

  • polyphase_interpolator_xs (RCC)

Example Application

<?xml version="1.0"?>
<application done="file_write">
  <instance component="ocpi.core.file_read" connect="polyphase_interpolator_xs">
    <property name="filename" value="input.bin"/>
  </instance>
  <instance component="ocpi.comp.sdr.dsp.polyphase_interpolator_xs" connect="file_write">
    <property name="interpolation_factor" value="8"/>
    <property name="number_of_taps" value="64"/>
    <property name="max_number_taps" value="64"/>
    <property name="taps" valuefile="taps.dat"/>
    <property name="rounding_type" value="half_even"/>
    <property name="group_delay_seconds" value="0"/>
    <property name="group_delay_fractional" value="0"/>
  </instance>
  <instance component="ocpi.core.file_write">
    <property name="filename" value="output.bin"/>
  </instance>
</application>

Dependencies

The dependencies to other elements in OpenCPI are:

There is also a dependency on:

  • ieee.std_logic_1164

  • ieee.numeric_std

  • ieee.math_real

Limitations

Limitations of polyphase_interpolator_xs are:

  • The interpolation_factor property must not exceed the number_of_taps property.

Testing

Tested platforms: None

Component testing not completed.