Resampling the transmit OFDM waveform in the frequency domain by zero-padding the FFT prior to taking the IFFT has the advantage of simplicity and maintaining sub-carrier orthogonality (no inter-carrier interference). This would typically not be done for other waveforms due to excessive out of band emissions (OOBE), but the result here would be the same OOBE level as the original OFDM waveform, and an accepted side effect for the other benefits OFDM provides. Frequency domain resampling (zero-pad and IFFT) would be the go-to approach whenever the new sampling rate is a multiple of the sub-carrier spacing. The equivalent (exact) time domain operation is not to convolve with a Sinc filter, but to do a circular convolution with the Dirichlet Kernel (which is an aliased Sinc function; for the number of bins given in typical OFDM implementations this quite closely approximates a Sinc except at the band edges where the aliasing effect is more apparent).
Alternate approaches for interpolating to other arbitrary sample rates when we do not have a target sampling rate that is a multiple of the bin spacing are to do the resampling using traditional multi-rate sampling rate conversions in the time domain. In this case the restrictive requirement to introduce no inter-carrier interference as described above is instead pushed aside for a requirement to not degrade Tx EVM above a certain limit. For this I recommend using the least squares algorithm (firls
in Matlab, Octave and Python scipy.signal) to design the interpolation filter, where filter complexity and overall time delay is traded for meeting the performance objective. (NOTE: I recommend NOT using firpm
or equi-ripple techniques for resampling applications of high ratios as the flat stop band results in increased total aliasing noise versus what can be achieved with a filter solution that has a stop-band roll-off such as firls
.) The Tx processing in this case would proceed as IFFT on the lower rate FFT samples, add Cyclic Prefix, and then resample the resulting time domain waveform to the desired output rate. The least squares approach results in an optimized filter in the least squares sense and is preferred over a windowed Sinc for minimum error and complexity- further, multi-band filters can be used which minimize resources in resampling applications where multiple alias regions exist and lend themselves well to polyphase implementations.
Below, for educational interest only, I further detail the approach of how to do the resampling completely in the time domain using circular convolution with the Dirichlet Kernel, to demonstrate just how straight-forward the frequency domain approach is in comparison. This will provide an equivalent result which would have zero inter-carrier interference in the frequency domain, as the typical processing done of zero-padding the FFT is both simple and maintains carrier orthogonality. This answer is instructive in basic FFT properties but will also demonstrate the simplicity of doing this in the frequency domain whenever this is possible (whenever the interpolated rate is a multiple of the bin spacing).
Zero padding in the frequency domain interpolates more samples in the time domain. The value for each new time domain sample is the result of a circular convolution in the time domain with the Dirichlet Kernel, which generally has a magnitude given as:
$$|D(n, N, M)| = \bigg|\frac{\sin(N \pi n/ M)}{M \sin(\pi n/M)}\bigg| \tag{1}\label{1}$$
Where:
$N$ is the original number of FFT samples,
$M$ is the total number of samples after zero-padding.
The angle of the Dirichlet Kernel depends on where we pad the FFT sequence. If the padding is done in the proper center of the FFT sequence (which represent the higher frequency bins), and when the length of the original sequence is odd, the resulting Dirichlet Kernel will be real resulting in no additional phase shift and given by:
$$D(n, N, M) = \frac{\sin(N \pi n/ M)}{M \sin(\pi n/M)}\tag{2}\label{2}$$
For all other cases, which would be a circular rotation of $m$ samples from this zero-phase case, the additional phase shift is given by:
$$\phi(m) = e^{jm 2\pi n/M} \tag{3}\label{3}$$
Where $m$ represents the number of samples for a circular rotation to the right for $m$ positive.
The "proper center", and reason for the requirement on an odd length, results in a "zero-phase" interpolation (introducing no additional phase shift) and is understood with the aid of the graphic below, introducing terminology I will use:
The graphic above shows the resulting window that is effectively multiplied with an N-sample FFT sequence when we "zero padding in the middle of the FFT sequence". This is as typically done with OFDM waveforms, which as mentioned results in time domain interpolation without introducing an additional linear phase shift with time (which would correspond to a frequency shift in the frequency domain). The first bin in the FFT sequence is the "DC bin" representing the magnitude and phase of the DC component of the signal, the next bins correspond to "positive frequency" bins, representing the magnitude and starting phase for frequency components as rotating phasors rotating counter-clockwise on the complex plane (such as a bicycle wheel rotating forward), while the bins at the end of the sequence correspond to "negative frequency" bins, representing the magnitude and starting phase for frequency components as rotating phasors rotating clock-wise on the complex plane (such as a bicycle wheel rotating backwards). Thus we see if there is a corresponding positive frequency bin for each negative frequency bin of equal magnitude and opposite phase, the result will be real and have zero phase. We achieve this in the window above for the case of an odd number of total samples: the same number of positive and negative frequencies, plus the DC bin, will result in a total number of samples that is odd. From this we also see that it does not matter if the total length of the padded sequence is odd or even; the resulting Dirichlet Kernel which is the inverse FFT of this window will be real and result in "zero-phase interpolation" in the time domain. (When the DFT sequence is not odd, the shared middle bin is divided in half and used as the highest positive frequency, and its complex conjugate is used as the highest negative frequency for frequency domain zero-padded interpolation.)
With that understood, and recapping the bottom line is zero-padding in the center of the FFT sequence as detailed above will result in interpolating new time domain samples without introducing phase error; we now can face the complexity of the details of properly doing the interpolation in the time domain (instead of the simplicity of taking the inverse FFT of the zero-padded sequence).
Multiplication in the discrete frequency domain with such a discrete rectangular window is identical to a circular convolution in the time domain with the Dirichlet Kernel. By zero-padding the FFT sequence, we have effectively either increased the sampling rate or decreased the total time duration of the time domain signal (expand the frequency domain or compress the time domain). Let's assume for clarity of my descriptions that we have increased the sampling rate of what was effectively a pre-existing time-domain signal such that we will introduce new samples at time locations that are in between the original time domain samples.
Therefore, in order to proceed with an interpolation completely in the time domain, we need to create the new sample time indices for the interpolated samples, compute the Dirichlet Kernel for those indices, and then complete a circular convolution of the Dirichlet Kernel with the original time domain waveform (as given by the inverse FFT of the original frequency domain sequence prior to zero-padding). This is straight-forward when the inverse FFT results fall on the new sample time indices, and can proceed as described. However for all general cases would require an intermediate step of interpolating to the greatest common factor, or completely alternate interpolation approaches rather than convolving with the Dirichlet Kernel (in this case why not the alternate approach of zero pad the frequency domain and take the inverse FFT!).
To demonstrate this I created the following code in Python, doing the inverse of the zero-padded FFT completely in the time domain:
from scipy.special import diric
import numpy as np
import scipy.fftpack as fft
def convolve_with_dirich(fft_samps, M):
'''
fft_samps: frequency domain FFT sequence
M: Number of total samples including zero padding in center of sequence
(for odd length FFT: M = integer multiple of N)
(for even length FFT: M = integer multiple of N+1)
'''
fft_samps = np.array(fft_samps, dtype=complex)
N = len(fft_samps)
if N%2 ==0:
# Even length FFT, duplicate Nyquist bin as complex conjugate to share Nyquist bin
ins = fft_samps[N//2]/2
fft_samps = np.delete(fft_samps, N//2)
fft_samps = np.insert(fft_samps, N//2, [ins, np.conj(ins)])
N+=1
if M%N !=0:
raise TypeError(f"M is not an integer multiple of FFT Length. {N=}, {M=}")
# create Dirichlet Kernel
n = np.arange(M)
dkern = N/M * diric(n*2*np.pi/M,N)
# assign time domain samples
time_samps = np.zeros(M, dtype=complex)
time_samps[::int(M/N)] = fft.ifft(fft_samps)
# circular convolution with time domain operations, note this
# is identical to fft.ifft(fft.fft(a)*np.conj(fft.fft(b))
return sig.convolve(dkern, np.tile(time_samps,2)[1:], mode='valid')
Confirming the operation of the above code, and how all that is equivalent to a simple inverse FFT:
Odd length sequence:
test= [1,2+3j,3,4-1j,5]
result = convolve_with_dirich(test, 2*len(test)) # custom function
test_pad = [1,2+3j,3,0,0,0,0,0,4-1j,5] # the usual way
result2 = fft.ifft(test_pad)
print(sum(np.abs(result-result2))) # sum of absolute errors
>> 1.4357565787643215e-15
Even length sequence
test= [1,2,3+1j,4]
result = convolve_with_dirich(test, 2*(len(test)+1)) # custom function
test_pad = [1,2,(3+1j)/2,0,0,0,0,0,(3-1j)/2,4] # the usual way
result2 = fft.ifft(test_pad)
print(sum(np.abs(result-result2))) # sum of absolute errors
>> 5.839823395707141e-16
We note that once odd and even length FFT sequences were properly conditioned, and the Dirichlet Kernel properly computed, the resulting processing isn't overly complicated in the case where the zero padding is an integer multiple of the FFT length, the key lines are replicated below for discussion:
# create Dirichlet Kernel
dkern = N/M * diric(n*2*np.pi/M,N)
# zero stuff IFFT of non-zero padded FFT
time_samps = np.zeros(M, dtype=complex)
time_samps[::int(M/N)] = fft.ifft(fft_samps)
# circular convolution using linear convolution function
sig.convolve(dkern, np.tile(time_samps,2)[1:], mode='valid')
I would typically do the last line using FFT's (as
corr = ifft(fft(dkern)*conj(fft(time_samps))
), but since the goal was to do everything in the time domain, I computed it with convolution.
However whenever the zero padding is not conveniently a multiple of the FFT length, the processing gets even more cumbersome if we still wish to avoid doing an inverse FFT of the zero padded sequence. To achieve the equivalent samples without doing any frequency domain zero-padding, an approach is to interpolate to the greatest common multiple in length, and then decimate that result to achieve the equivalent solution to what an inverse FFT of the zero padded sequence would provide. When the FFT sequence however can be partially padded to be a sub-integer length, this would make more sense (in case there is some advantage to the time domain processing provided, rather than padding out completely in frequency and simply getting the result directly from the inverse FFT).
For a realistic example consider 802.11ac with an 80 MHz bandwidth channel, for this there are 256 total subcarriers in each FFT symbol with 245 occupied (including the 3 bins near DC which are nulled). If we were to support a 160 MSPS sampling rate for the Tx signal, this would pad the FFT symbols out to 512 bins. Thus we would have an interpolation ratio of 512/245. Most unfortunately there are no common factors and therefore the greatest common multiple in this case is 512 * 245 = 125440. This means if we wanted to restrict our original FFT to just the 245 occupied bins, we would need to zero-stuff the time domain with 511 zeros between each sample, operate with the product and convolution with waveforms 125,440 samples long, and then select every 245th sample from the result. This could feasibly be accomplished with polyphase structures all operating at the original sampling rate. Or, more simply in this case, use all the original 256 sub-carriers (including the nulled guard bands) to compute the inverse FFT resulting in an interpolation ratio of 512/256 =2, and process as described.
Traditional time domain resampling with an objective to meet a target performance requirement (such as EVM) would still require working with large multiples as above, but if we are no longer requiring ourselves to maintain perfect orthogonality but instead have all errors below a certain limit, then this would also open the possibility of working with smaller interpolation ratios with an allowable frequency offset.
Additional note for @Fatima who is trying to convert my code to MATLAB, here is the function to calculate the Dirichlet Kernel directly and returns an identical result to the the diric function from scipy:
def dirich(n, N, M, m=0):
# n: index for resulting time domain samples
# N: original N samples for DFT
# M: zero padded out to M total samples
denom = M * np.sin(n*np.pi/(M))
result = np.zeros(len(n), dtype = complex)
result[np.where(denom==0)] = N/M
result[np.where(denom!=0)] = np.sin(N/M * n* np.pi)[np.where(denom!=0)]/denom[np.where(denom!=0)]
return result * np.exp(1j *(m) *2*np.pi*n/(M))
So replace N/M * diric(n*2*np.pi/M,N)
in the code above with dirich(n, N, M)
using this function.
firpm()
orfirls()
to design a similar FIR of the same length. $\endgroup$