4G/LTE  PHY Channel 

PSS (Primary Synchronization Channel)
PSS is a specific physical layer signal that is used for radio frame synchronization. It has characterstics as listed below.
This may not be an important topic for most of the case since it would be working fine for most of the device that you have for test. Otherwise it would have not been given to you for test. However, If you are a developer working at early stage of LTE chipset, this would be one of the first signal you have to implement.
The exact PSS symbol calculation is done by the following formula as described in 36.211  6.11.1. For the specific example of generated PSS, refer to Matlab : Toolbox : LTE : PSS page or PSS with default Matlab function.
Generating PSS with default Matlab function
Followings are the Matlab code for generating PSS and its result for each NID value. clear all;
u_shift = [25 29 34];
NID = 0;
d_u = [];
for n = 0:61
u = u_shift(NID+1);
if n <= 30 d = exp(j*pi*u*n*(n+1)/63); else d = exp(j*pi*u*(n+1)*(n+2)/63); end;
d_u = [d_u d];
end;
subplot(1,3,1); plot(real(d_u(1:31)),imag(d_u(1:31)),'ko','MarkerFaceColor',[0 0 0]); axis([1.5 1.5 1.5 1.5]); title('n=0..30');
subplot(1,3,2); plot(real(d_u(32:62)),imag(d_u(32:62)),'bo','MarkerFaceColor',[0 0 1]); axis([1.5 1.5 1.5 1.5]); title('n=31..61');
subplot(1,3,3); plot(real(d_u(1:62)),imag(d_u(1:62)),'ro','MarkerFaceColor',[1 0 0]); axis([1.5 1.5 1.5 1.5]); title('n=0..61');
Followings are the numberical result (print out of d_u[ ] array) for each NID;
Cross Correlation between different PSS
Threre are three types of PSSs in LTE. To make all these three type unique (distinctive to each other), it is designed that the cross correlation between each PSS be very low. Following example shows the cross correlation between each PSS.
Following is the Matlab source code that produce the result as shown above. I used Matlab dsp package to calculate the cross correlation. If you don't have Matlab dsp package, try to write your own script for calculating cross correlation clear all;
u_shift = [25 29 34];
% Generate PSS for NID = 0
NID = 0; d_u = [];
for n = 0:61
u = u_shift(NID+1);
if n <= 30 d = exp(j*pi*u*n*(n+1)/63); else d = exp(j*pi*u*(n+1)*(n+2)/63); end;
d_u = [d_u d];
end;
d_u_NID0 = d_u';
% Generate PSS for NID = 1
NID = 1; d_u = [];
for n = 0:61
u = u_shift(NID+1);
if n <= 30 d = exp(j*pi*u*n*(n+1)/63); else d = exp(j*pi*u*(n+1)*(n+2)/63); end;
d_u = [d_u d];
end;
d_u_NID1 = d_u';
% Generate PSS for NID = 2
NID = 2; d_u = [];
for n = 0:61
u = u_shift(NID+1);
if n <= 30 d = exp(j*pi*u*n*(n+1)/63); else d = exp(j*pi*u*(n+1)*(n+2)/63); end;
d_u = [d_u d];
end;
d_u_NID2 = d_u';
% Cross Correlation between PSS
Hxcorr = dsp.Crosscorrelator; taps = 0:61; taps = taps';
XCorr_0_0 = step(Hxcorr,d_u_NID0,d_u_NID0); XCorr_0_1 = step(Hxcorr,d_u_NID0,d_u_NID1); XCorr_0_2 = step(Hxcorr,d_u_NID0,d_u_NID2);
subplot(3,1,1); stem(taps,abs(XCorr_0_0(62:end)),'bo','MarkerFaceColor',[0 0 1]); xlim([0 length(taps)]); ylim([0 100]); title('Corr between PSS(NID0) and PSS(NID0)');
subplot(3,1,2); stem(taps,abs(XCorr_0_1(62:end)),'bo','MarkerFaceColor',[0 0 1]); xlim([0 length(taps)]); ylim([0 100]); title('Corr between PSS(NID0) and PSS(NID1)');
subplot(3,1,3); stem(taps,abs(XCorr_0_2(62:end)),'bo','MarkerFaceColor',[0 0 1]); xlim([0 length(taps)]); ylim([0 100]); title('Corr between PSS(NID0) and PSS(NID2)');
Correlation between a PSS and its PhaseShifted Version
This example shows the correlation between a PSS and its phaseshifted copy. As you see here, the magnitue (absolute value) of correlation does not changes even if you do phase shift. However, you can figure out the degree of phase shift by taking the angle of correlation. With this property, you can identify a PSS in a received signal without worrying about any possible phase shift that might have occurred by communication channel. In addition, you can figure out the amount of phase shift by taking the angle value of the correlation and use that value to compensate (undo) the phase shift.
Following is the matlab source code that produced the result shown above. I used Matlab dsp package to calculate the cross correlation. If you don't have Matlab dsp package, try to write your own script for calculating cross correlation clear all;
u_shift = [25 29 34];
% Generate PSS for NID = 0
NID = 0; d_u = [];
for n = 0:61
u = u_shift(NID+1);
if n <= 30 d = exp(j*pi*u*n*(n+1)/63); else d = exp(j*pi*u*(n+1)*(n+2)/63); end;
d_u = [d_u d];
end;
phShift = pi/3; d_u_NID0 = transpose(d_u); % Original PSS d_u_NID0_PhaseShift = transpose(d_u .* exp(j*phShift)); % PhaseShifted PSS
% Cross Correlation between PSS
Hxcorr = dsp.Crosscorrelator; taps = 0:61; taps = taps';
XCorr_0_0_Shifted = step(Hxcorr,d_u_NID0,d_u_NID0_PhaseShift);
subplot(3,2,1); plot(real(d_u_NID0),imag(d_u_NID0),'ro','MarkerFaceColor',[1 0 0]); title('PSS');
subplot(3,2,2); plot(real(d_u_NID0_PhaseShift),imag(d_u_NID0_PhaseShift),'bo','MarkerFaceColor',[0 0 1]); title(strcat('PSS:',num2str(phShift)));
subplot(3,2,[3 4]); stem(taps,abs(XCorr_0_0_Shifted(62:end)),'bo','MarkerFaceColor',[0 0 1]); xlim([0 length(taps)]); ylim([0 100]); title('Abs(Corr) : PSS(NID0) and PhaseShifted PSS(NID0)');
subplot(3,2,[5 6]); stem(taps,angle(XCorr_0_0_Shifted(62:end)),'bo','MarkerFaceColor',[0 0 1]); xlim([0 length(taps)]); ylim([pi pi]); title('Angle(Corr): PSS(NID0) and PhaseShifted PSS(NID0)');
Correlation between a PSS and its Noised Version
This example shows the noise tolerance of PSS. The example on the left shows the correlation between a PSS and the one with 50 dB SNR (practically no Noise condition) and the example on the right shows the correlation between a PSS and the one with 10 dB SNR.
Following is the matlab source code that produced the result shown above. I used Matlab dsp package to calculate the cross correlation. If you don't have Matlab dsp package, try to write your own script for calculating cross correlation. clear all;
u_shift = [25 29 34];
% Generate PSS for NID = 0
NID = 0; d_u = [];
for n = 0:61
u = u_shift(NID+1);
if n <= 30 d = exp(j*pi*u*n*(n+1)/63); else d = exp(j*pi*u*(n+1)*(n+2)/63); end;
d_u = [d_u d];
end;
d_u_NID0 = transpose(d_u);
% Specify SNR in dB. Try setting various different value here and see how the result changes SNR_dB = 10;
% Get the number of symbols N = length(d_u_NID0);
% Calculate Symbol Energy Eavg = sum(abs(d_u_NID0) .^ 2)/length(N);
% Convert SNR (in dB) to SNR (in Linear) SNR_lin = 10 .^ (SNR_dB/10);
% Calculate the Sigma (Standard Deviation) of AWGN awgnSigma = sqrt(Eavg/(2*SNR_lin));
% Generate a sequence of noise with Normal Distribution and rescale it with the sigma awgn = awgnSigma*(randn(1,N)+j*randn(1,N)); awgn = awgn';
% Add AWGN to PSS d_u_NID0_Awgn = d_u_NID0 + awgn;
% Cross Correlation between PSS Hxcorr = dsp.Crosscorrelator; taps = 0:61; taps = taps';
XCorr_0_0_AWGN = step(Hxcorr,d_u_NID0,d_u_NID0_Awgn);
subplot(3,2,1); plot(real(d_u_NID0),imag(d_u_NID0),'ro','MarkerFaceColor',[1 0 0]); axis([2 2 2 2]); title('PSS');
subplot(3,2,2); plot(real(d_u_NID0),imag(d_u_NID0),'bo', ... 'MarkerFaceColor',[0 0 1]); hold on; plot(real(d_u_NID0_Awgn),imag(d_u_NID0_Awgn),'bo', ... 'MarkerFaceColor',[0 0 0],'MarkerSize',1); axis([2 2 2 2]); title(strcat('PSS:',num2str(SNR_dB))); hold off;
subplot(3,2,[3 4]); stem(taps,abs(XCorr_0_0_AWGN(62:end)),'bo','MarkerFaceColor',[0 0 1]); xlim([0 length(taps)]); ylim([0 100]); title('Abs(Corr) : PSS(NID0) and Noised PSS(NID0)');
subplot(3,2,[5 6]); stem(taps,angle(XCorr_0_0_AWGN(62:end)),'bo','MarkerFaceColor',[0 0 1]); xlim([0 length(taps)]); ylim([pi pi]); title('Angle(Corr): PSS(NID0) and Noised PSS(NID0)');
Primary Synchronization Signal (PSS) detection is conducted to find the start of a frame and establish frequency synchronization. It's done by correlating the received signal with locally generated PSS sequences. Two main methods can be used: timedomain correlation, which directly compares the signal with the PSS sequence, or frequencydomain correlation, which performs the correlation done in frequency domain as described below. Basically both method shown here is a sliding window which slides along the sequence of I/Q data recieved by the reciever (UE)
Frequeny Domain Correlation Method
Description of what's happening at each step in the illustration goes as follows : (A) Fast Fourier Transform (FFT) of IQ in a window (chunk): This step converts the timedomain IQ signals in a window, or chunk, into the frequency domain. (B) Take out bins for PSS: After the FFT, the frequency bins corresponding to the PSS signal are extracted. In LTE, these are the central 62 frequency bins around DC, which contain the PSS signal. (C) Check Correlation with 3 generated PSS: In this step, the extracted frequency bins are correlated with the three possible PSS sequences that are locally generated and transformed into the frequency domain. (D) Pick the largest value of the 3 correlations: Once the correlation values are obtained for all three PSS sequences, the maximum correlation value is chosen. This step identifies the specific PSS sequence (out of the three possible ones) that matches the received signal. (E) Is the result greater than the current max? This step compares the maximum correlation value from step (D) with a predefined threshold or the maximum correlation value from the previous chunk. (F) Update the max value: If the maximum correlation value from step (D) is larger than the current maximum, this new value is set as the maximum. This updated value then serves as the threshold for the next chunk. If the PSS is successfully detected, it means the start of the LTE frame is found and frequency synchronization can be established.
Time Domain Correlation Method
Description of what's happening at each step in the illustration goes as follows : (1) Check Correlation with 3 generated PSS: In this step, the incoming timedomain IQ samples are correlated with the three possible PSS sequences. These sequences are originally generated in the frequency domain as defined by the LTE standard, then transformed into the time domain for this correlation process. (NOTE : The way in which the PSS is converted to time domain sequence is shown in the box (N)) (2) Pick the largest value of the 3 correlations: Once the correlation values are obtained for all three PSS sequences, the maximum correlation value is chosen. This step identifies the specific PSS sequence (out of the three possible ones) that matches the received signal. (3) Is the result greater than the current max? This step compares the maximum correlation value from step (2) with a predefined threshold or the maximum correlation value from the previous chunk. (4) Update the max value: If the maximum correlation value from step (2) is larger than the current maximum, this new value is set as the maximum. This updated value then serves as the threshold for the next chunk. If the PSS is successfully detected, it means the start of the LTE frame is found and time synchronization can be established.
You can check out and see how real life implementation of this process in srsUE source code. Followings are the list of functions you would refer to this process (this is based on the code that I downloaded for srs github in Jul 2023)
In addition fo finding the timing sync of a subframe / OFDM symbol, the detected PSS provide a very important information.
By comparing the expected PSS (ideal PSS) and the received PSS, we can estimate Frequency Offset. This error can be used to adjust other signals (e.g, SSS) before processing. This offset estimation is crucial in wireless communication for correcting carrier frequency offsets introduced by transmitterreceiver oscillator frequency mismatches or the Doppler effect.
The way to calculate (estimate) the frequency offset can be done by comparing the PSS sequence received and the PSS sequence (i.e, expected sequence) that is generated by the 3GPP algorithm. This comparison and frequency offset can be done in 3 steps as follows. Step 1 : Compute the phase difference between each corresponding elements of the received sequence and the expected sequence Step 2 : Sum all of the phase difference in step 2. Let's call this as total phase difference Step 3 : Convert the total phase difference into frequency offset This can be explained in more detail with a simple Python script as follows.
Let me look into the code itself.
phase_difference = np.angle(np.dot(received_pss, np.conj(expected_pss)))
This corresponds to Step 1 and Step 2 described above. np.dot(received_pss, np.conj(expected_pss) can be expressed in terms of angular value as follows. angle( sum(angle(received_pss)  angle(expected_pss)) ) The minus '' before angle(expected_pss) is done by taking conj( ) of expected_pss
frequency_offset = (phase_difference / (2 * np.pi)) / time_duration_pss
This line translates the calculated phase difference into the frequency domain to estimate the frequency offset. The factor of 2*πi converts the phase from radians to cycles, 62 is the number of subcarriers used for the PSS in LTE, and sample_rate is the rate at which the PSS signal was sampled. Phase and frequency are directly related: the change in phase over time is frequency. In the frequency offset estimation function, the phase difference is divided by the time duration of the symbol (which is the number of subcarriers divided by the sample rate), giving us the rate of change of phase, or frequency offset. In other words, the equation calculates the average change in phase per sample, which is then converted to Hz to represent the frequency offset. This frequency offset represents the difference in frequency between the received PSS and the expected PSS
Once the frequency offset is calculated (estimated) as explained above, you can use the value to compensate (correct) the frequency offset of other signal (data). The basic idea is as follows : Step 1 : Convert the frequency offset to phase shift and generate a rotating factor. Step 2 : Rotate (compensate) the given signal using the rotating factor
This can be explained by a simple python code as below :
Let me look into the code itself.
time = len(signal) / sample_rate
The time is calculated as the total duration of the signal in seconds. This is done by dividing the number of samples in the signal (len(signal)) by the sample rate (sample_rate).
correction = np.exp(1j * 2 * np.pi * frequency_offset * time)
The value correction here is the correction factor. This is a complexvalued factor that, when multiplied with the original signal, will correct the frequency offset. The correction factor is computed using the formula for a complex exponential, np.exp(1j * 2 * np.pi * frequency_offset * time),
corrected_signal = signal * correction
The corrected_signal is then computed by multiplying the original signal with the correction factor. This rotates the signal by the required amount to correct the frequency offset.
NOTE : The method implemented here is a kind of simplified method. It assumes that all the samples in the data has the same amount of frequency offset. Typically, frequency offset correction involves applying a different correction factor to each sample in the signal (i.e., each sample gets rotated by a different amount), rather than applying the same correction factor to all samples, as is done here. This can be done by computing the correction factor as a function of time for each sample. But this approach is not used in this specific function. However, if the frequency offset is not constant over the time span of the signal, this method might not fully correct the frequency offset. This is because the method assumes a constant frequency offset over the entire time span of the signal.
Reference :
[1] Synchronization and Cell Search

