I have recently been trying to understand the Phaser implementation, and I have noticed a few elements of the interpolation filter chain implementation which do not seem consistent with DSP theory.
This primarily concerns the MAC_HBF_Upsampler
module, which should perform two things (1) upsampling (2) Filtering of the up-sampled output. To start with we review the standard FIR filter with 4N-1 taps:
We know for a half band filter every odd indexed tap except the centre is zero. Hence we can remove them as follows:
Now, as is typical, the MAC_HBF_Upsampler
implementation used a folded FIR filter to exploit the symmetry of the FIR filter coefficients, leaving our filter as follows:
This should be how the HBF of this module is implemented, notably the middle filter coefficient still contributes to the output y(n)
. What we see however from reading the Migen is that the filter coefficients are built up from the input as follows:
coef = []
for i, c in enumerate(coeff[: (len(coeff) + 1) // 2 : 2]):
coef.append(Signal((width_coef + 1, True), reset_less=True, reset=c))
With this implementation, if our filter coefficients were [1, 0, 2, 3, 2, 0, 1] (as is the format of a HBF), the above code would yield coef = [1, 2]
, which does not contain the centre coefficient. This is evidenced by looking at the traces for a simulation of the hbf0
component in the InterpolateChannel
module of Phaser, where we see the following:
The taps used in the design are the first 10 non-zero taps, missing out the middle tap (which should have value 131072). So, my first questions is, is this an intentional design decision, or is this just an error in implementation? I currently could not implement the HBF behaviour using the MAC_SYM_FIR
module, which indicates to me that there is an error here, as they are both fundamentally FIR filters.
My second issue is with the way the upsampling is implemented. Typically the upsampling procedure is performed before the HBF, and is usually implemented with zero stuffing. I understand there are switched architectures which avoid the need to us zero stuffing, however I don't think that is used here. What is used here is that to implement a 2x upsampling the we see the following code fragment:
If(
# emit trivial sample at halfway computation + pipelen
pos == (len(coef) // 2) + dsp_pipelen - 1,
self.output.data.eq(x[len(coef)]),
self.output.stb.eq(1),
)
By my reading the 'upsampled' output is just the HBF output with random samples from the input inserted. I do not understand how this implementation follows from any of the theory. So, not only is the HBF not being applied to an upsampled signal, the upsampled signal contains arbitrary samples which (by my understanding) should be zeros.
If someone could explain the rationale between these design decision i'd appreciate it as there is not much supporting evidence other than the filtering jupyter notebook found at https://github.com/quartiq/phaser/blob/master/filter.ipynb. Inside this we see the test setup is
steps = [
# 1/10 samples per cycles
Gain(5**4/2.**9), # compensate ciccomp+cic+shift gain
# 1/10
ciccomp,
# 1/10
US(2), HBF(10, .4).quantize(1 << 18),
# 1/5
US(2), HBF(5, 1/4.).quantize(1 << 18),
# 2/5
US(5), CIC(5, 1, 5),
# 2/1
]
We can see this implements the zero stuffing upsampling prior to the HBF as expected, but then as discussed this behaviour is not reflected in the Migen implementation.
Thank you!