What is PSS?
The Primary Synchronization Signal (PSS) is the very first signal a UE detects when searching for a cell. It is transmitted as part of every SSB on 127 specific subcarriers in the center of the SSB's frequency band.
PSS serves one purpose: give the UE a known pattern to search for. Because both gNB and UE know exactly what the PSS sequence looks like, the UE can slide this known pattern across its received samples and detect the exact moment it appears. That moment reveals the OFDM symbol timing — the single most important piece of information the UE needs to begin processing the signal.
There are exactly 3 PSS sequences, one for each value of N²_ID ∈ {0, 1, 2}. The UE does not know which one the cell is using, so it tries all three simultaneously. Whichever produces a strong match identifies the cell's N²_ID — the first component of the Physical Cell ID.
PSS sequence generation
The 3GPP specification defines the PSS as a binary m-sequence of length 127, mapped to BPSK symbols (+1 or −1). The sequence is generated by a linear feedback shift register with a specific recurrence relation. All three PSS sequences are the same underlying m-sequence, read starting from different positions.
d(n) = 1 − 2x(m), m = (n + 43·N²ID) mod 127, n = 0, 1, ..., 126
where the binary sequence x is given by
x(i+7) = (x(i+4) + x(i)) mod 2
with initial conditions x(0) = x(1) = x(2) = x(3) = x(4) = x(5) = x(6) = 1.
Step-by-step: generating the m-sequence x
The recurrence x(i+7) = (x(i+4) + x(i)) mod 2 means: XOR the bit 4 positions back with the bit 7 positions back to get the next bit.
| Step i | Formula | x(i+4) | x(i) | XOR | Result x(i+7) |
|---|---|---|---|---|---|
| 0 | x(7) = x(4) ⊕ x(0) | 1 | 1 | 1⊕1 | 0 |
| 1 | x(8) = x(5) ⊕ x(1) | 1 | 1 | 1⊕1 | 0 |
| 2 | x(9) = x(6) ⊕ x(2) | 1 | 1 | 1⊕1 | 0 |
| 3 | x(10) = x(7) ⊕ x(3) | 0 | 1 | 0⊕1 | 1 |
| 4 | x(11) = x(8) ⊕ x(4) | 0 | 1 | 0⊕1 | 1 |
| 5 | x(12) = x(9) ⊕ x(5) | 0 | 1 | 0⊕1 | 1 |
| 6 | x(13) = x(10) ⊕ x(6) | 1 | 1 | 1⊕1 | 0 |
| 7 | x(14) = x(11) ⊕ x(7) | 1 | 0 | 1⊕0 | 1 |
| 8 | x(15) = x(12) ⊕ x(8) | 1 | 0 | 1⊕0 | 1 |
| 9 | x(16) = x(13) ⊕ x(9) | 0 | 0 | 0⊕0 | 0 |
Mapping d(n) = 1−2x(n): 0→+1, 1→−1
Three PSS sequences from one m-sequence
The shift m = (n + 43·N²_ID) mod 127 reads the same x sequence starting from position 0, 43, or 86 for N²_ID = 0, 1, 2 respectively. This creates three different-looking sequences that are easy to distinguish by correlation.
How the UE detects PSS — cross-correlation
The UE has 307,200 raw complex samples and needs to find a known pattern somewhere in them. The technique is cross-correlation: slide the known PSS template across the buffer one sample at a time, computing at each position how well the template and signal match.
When the template aligns exactly with the PSS in the received signal, every multiplication rx[n] × conj(PSS[n]) gives a positive real contribution — they all add coherently to produce a large peak. Everywhere else, the products are random complex numbers that partially cancel, giving a small result near the noise floor.
// For each offset t = 0..307,199 and each N²_ID = 0,1,2: C(t, N²_ID) = | Σ rx_buffer[t+n] × conj(PSS_time[N²_ID][n]) | n=0 to 2047 // At t=0 (noise only — terms cancel): C(0, 1) = |0.1×1 + (−0.2)×(−1) + 0.3×1 − 0.1×1 + ...| = |−0.3| = 0.3 ← noise floor // At t=4523 (PSS present — coherent addition): C(4523, 1) = |0.98×1 + 1.02×1 + 0.97×1 + 1.01×1 + ...| = 68.7 ← PEAK → PSS found! // N²_ID=0 and N²_ID=2: no peak at any offset → not this cell's PSS
Frequency offset correction
After PSS detection, the UE must correct its frequency. A typical TCXO crystal oscillator has ±10 ppm accuracy. At 3498.24 MHz, this means up to ±35 kHz of frequency error — larger than one 30 kHz subcarrier. Without correction, the FFT produces completely wrong results.
The correction uses the cyclic prefix. Because CP = copy of the last 144 samples of the useful part, we have rx[t] ≈ rx[t + 2048] in the CP region. The phase difference between these two versions equals 2π × Δf × T_useful, from which Δf is directly extracted.
// CP autocorrelation (sum over 144 CP samples): R = Σ rx[t] × conj(rx[t + 2048]), t = 0..143 = 142.3 × e^(j × 0.000120) // Extract frequency offset: Δf_fine = angle(R) / (2π × T_useful) = 0.000120 / (2π × 33.33×10⁻⁶) = 573 Hz // Coarse correction from integer subcarrier shift (PSS position in FFT): Δf_coarse = −120,000 Hz // Total correction applied sample by sample: rx_corrected[t] = rx[t] × e^(−j × 2π × 120573 × t / 61,440,000)