mmWave 5G FR2 phased-array RF path — live 28 GHz beamformer
A single-file in-browser visualizer of an mmWave 5G FR2 phased-array RF path. A 28 GHz 8×8 array on the gNB, a UE walking through a small cell, a draggable human body that blocks the line-of-sight beam, and the on-chip impairments that decide whether 256-QAM actually closes: per-element PA back-off, per-element calibration error, LO phase noise, and beam squint across the 400 MHz channel. DPD, PT-RS, TTD sub-arrays and OTA re-cal are the matching mitigations — toggle each one and watch the constellation, the beam pattern, and the EVM budget react.
The default script walks the canonical sequence:
cold start → 64 elements muted, phases random ±π
boot cal → monitor RX drives cal_err → <0.25°, PAs ramp to 8 dB OBO
P1 SSB sweep → 32-entry DFT codebook scans the sector; UE measures RSRP
P2 gNB beam refine → 8 narrow beams around the P1 winner
P3 UE panel refine → UE locks its best receive panel; reciprocity locks the link
TX 64-QAM 400 MHz → composite EVM ~4.2%, under the 8% FR2 BS limit
upgrade to 256-QAM → EVM 3.0%, clears the 3.5% ceiling
body enters LOS → +18 dB body loss (MacCartney/Rappaport @ 28 GHz)
beam failure recovery → BFR timer 40 ms (TS 38.331 enum)
NLOS reflected beam → side-wall bounce, +10 dB extra, fall back to 64-QAM
blocker leaves → LOS recovers, 256-QAM re-engages
beam squint OFF → phase shifters only; ±1° beam walk across BW
beam squint ON → TTD sub-arrays; pattern locks across all subcarriers
LO PN +10 → CPE rotates the constellation; PT-RS catches the bulk
PT-RS off → CPE uncorrected, EVM breaches 256-QAM threshold
restore LO + PT-RS → EVM back under threshold
inject 2° cal drift → side-lobes +6 dB, array gain -1 dB
re-run OTA cal → pattern recovers
idle → explore on your own
What this actually is (for someone who’s never touched RF)
If you’ve heard “5G” and pictured another step in a 3G → 4G → 5G ladder you’d be wrong: mmWave 5G (the “FR2” half of the standard, sitting at 24–52 GHz) is a different animal from the cellular signal in your phone right now. The wavelength is so short (10 mm at 28 GHz) that ordinary obstacles — a human body, a low-emissivity window, a wall, a row of leaves — are not “a little attenuation,” they are walls of 15 to 40+ dB of loss. A single person standing in your way can drop your link by 18 dB on its own. The free-space loss is about 30 dB worse than the sub-6 GHz cellular bands you’re used to. By the back-of-envelope, the link shouldn’t work at all.
The trick that makes it work — and the thing this simulator visualises — is that the base station doesn’t have one antenna. It has 64 of them, arranged in an 8 × 8 square the size of a small tile. Each antenna gets its own miniature transmitter, and the chip can dial the phase of each one independently. By choosing the phases just right, the 64 signals add up coherently in one chosen direction in the sky and cancel out everywhere else. That’s a phased array: 64 little transmitters cooperating to throw a narrow searchlight beam at you. Changing the phases re-aims the beam — no moving parts, microseconds to switch.
But you need to find the user first. That’s what the scripted scenario walks through:
- Cold start. Power-on. Phases random, transmitters muted. We start from nothing.
- Boot calibration. Each antenna is slightly out of spec — manufacturing tolerances, temperature, mutual coupling. The chip has tiny “monitor receivers” that measure each one and figure out its individual phase error. This is the per-element cal_err the simulator shows as a coloured ring.
- P1 SSB sweep. The base station fires the beam in 32 different directions in turn, each for a fraction of a millisecond. The user’s phone reports which beam it heard best. (The TLA “SSB” is the 5G “synchronisation signal block.”)
- P2 / P3 refinement. Narrower beams around the winning direction. The phone also tries its own receive antennas and picks the one pointed at the base station.
- Modulate. Now we transmit data. The simulator shows the constellation diagram — a scatter plot where every dot is one transmitted symbol. For 256-QAM, the dots should land on a 16×16 grid. The tighter the dots cluster on their grid points, the cleaner the signal. The blur is called EVM (error vector magnitude). The 3GPP standard says you need EVM below 3.5% to call yourself 256-QAM compliant.
- A person walks in. ≈ 18 dB drop. The constellation explodes. The base station declares “beam failure,” gives up on the direct path, and switches to a reflected path off a nearby wall — losing about 10 dB but staying alive.
Two phenomena from the simulator are worth singling out because they’re specific to phased arrays and easy to miss until you see them animated:
- Beam squint. The base station’s phases are set once at the centre frequency. But the actual signal is a 400 MHz wide channel. The phases that point the beam at the user at 28.0 GHz don’t quite point the same direction at 28.2 GHz — so the low edge of the channel lands slightly off-target from the high edge. The beam visibly “walks” across the channel by about a degree. The fix is to replace phase shifters with true time-delay units — toggle the “TTD” knob and watch the three frequency cuts on the Beam view snap on top of each other.
- LO phase noise. The 28 GHz tone the chip uses to up-convert the baseband isn’t a pure tone — it’s a tone with a small random wobble. That wobble rotates the whole constellation a tiny amount every symbol. The fix is a signal the standard reserves specifically for measuring that rotation, the PT-RS (phase-tracking reference signal). Toggle it off and watch the constellation smear circumferentially.
Everything else in the article — the link budget waterfall, the EVM budget bars, the polar pattern — is just the per-stage accounting of how the photons get from the chip to the user with enough fidelity for 256-QAM to close.
Views
| View | What it shows |
|---|---|
| Scene | Top-down cell map. gNB at the origin, UE roaming through a 90 m × 110 m sector, draggable human-body blocker, current beam cone, P1 sweep ghost-beams, NLOS reflected-beam path off the side wall. Side panel: live link metrics (EIRP, FSPL, body loss, Prx, noise floor, SNR, modulation, EVM contributors, throughput). |
| Array | 8×8 element grid with per-element amplitude (brightness), phase (black needle), calibration error (ring colour), die temp, PA back-off. Click any element to inspect. Dashed arrow shows the steered direction. Side panels: how-to-read, array statistics, PA & power budget. |
| Beam | Polar far-field pattern in dB, three frequencies overlaid (28.0 GHz centre, 27.8 GHz band-low, 28.2 GHz band-high) so beam squint is visible as a pattern walk; linear cut underneath; side panel with peak, side-lobe, HPBW, SLL margin and a TTD explainer. |
| EVM | Live constellation at the UE (QPSK / 16-QAM / 64-QAM / 256-QAM), with red dots for symbols that breach the per-symbol error bound. Composite EVM budget bars (PA, LO PN, IQ, OTA, AWGN, total) against 3GPP thresholds. PA AM/AM curve with/without DPD. LO phase-noise PSD anchored to TR 38.803 §6.1.10. Side panel: physical meaning of each contributor. |
| Link | Link-budget waterfall (P_per_elem → array gain → element gain → EIRP → −FSPL → −body → −NLOS → +UE gain → Prx → −noise → SNR). Rolling history graph of SNR, EVM and MCS. Side panel: specs cited (3GPP TS 38.141-2, TS 38.331, TR 38.803, FCC Part 30) and a short “why mmWave is hard”. |
What’s “precise” about it
- Real array math. The pattern is computed as the coherent sum of 64 per-element complex weights across an 8×8 grid with λ/2 spacing. Phase shifters are quantised to 6 bits (LSB 5.625°). The far-field is the array factor AF(θ, φ) = Σₘₙ aₘₙ · exp(j·k·d·(m·sin θ cos φ + n·sin θ sin φ) + jφ_steer).
- Real beam squint. When the carrier shifts off fc, the phase shifters still apply the same physical phase, so the apparent steered angle satisfies sin θapparent = (fc / f’) · sin θsteer. Toggle TTD to apply a true delay τ = d·sin θ / c per sub-array and watch the pattern lock across all 400 MHz of channel.
- Real EVM budget. Composite EVM is the RMS sum of PA (AM/AM, AM/PM), LO phase noise (with PT-RS catching the CPE term), IQ imbalance, OTA test uncertainty and the AWGN-noise floor. The thresholds drawn on the bars come straight from 3GPP TS 38.141-2 (FR2 BS): 8% for 64-QAM, 3.5% for 256-QAM, residual −42.5 dB (0.75%).
- Real phase-noise model. The PSD curve is anchored to the multi-pole/zero model in 3GPP TR 38.803 §6.1.10 at the 29.55 GHz reference frequency — flat in the inner ring, −20 dB/decade slope, integrated floor in the outer ring.
- Real Friis loss. FSPL = 20·log₁₀(4π·d / λ). At 28 GHz, λ = 10.71 mm, so FSPL ≈ 101 dB over 100 m — exactly the number that justifies the array gain in the first place.
- Real body blockage. A single adult body in the LOS path drops the link
by 18 dB at 28 GHz (MacCartney / Rappaport, arXiv:1607.00226 / IJAP 2017),
triggering beam failure recovery on the
beamFailureRecoveryTimer= {10, 20, 40, 60, 80, 100, 150, 200} ms enum from TS 38.331. - Real link budget at FCC Part 30. With P_sat = +18 dBm per element and 8 dB OBO, the 64-element array reaches +51 dBm peak EIRP — well inside the +75 dBm/100 MHz fixed/base cap and consistent with PC1 FWA UE classes in TS 38.101-2.
Controls
| Key / mouse | Action |
|---|---|
space | Run / pause the script |
s | Step one engine tick (20 ms simulated) |
shift+s | Step ×10 |
r | Reset to cold start |
v | Cycle Scene → Array → Beam → EVM → Link |
| drag the body (Scene) | Move the blocker; LOS loss appears automatically when the body crosses the gNB → UE line |
| drag the UE (Scene) | Reposition the UE; the link budget recomputes live |
| hover an element (Array) | Inspector showing amplitude, phase, cal_err, back-off, die temp |
| toolbar knobs | DPD · TTD · PT-RS · Block · LO PN +10 · Cal drift |
What the data flow looks like
Every tick of the engine produces a chain of low-level operations:
phase 'tx_256qam'
↓
codebook lock: steer to azUE → per-element phase shifter set (6-bit quantised)
↓
array factor: Σ_n a_n · exp(j·k·d·m·sinθ + jφ_n) at UE direction
→ EIRP = P_per_elem + 20·log10(|AF|) + G_elem
↓
propagation: − FSPL(28 GHz, 92 m) ≈ 101 dB
− bodyLoss(LOS crossing) = 18 dB
− NLOS extra = 0 dB (LOS active)
+ G_UE = 8 dBi
↓
RX: Prx ≈ −40 dBm, noise floor −82 dBm, SNR ≈ 42 dB
↓
demod: constellation samples += AWGN(σ = 100/√SNR_linear %)
+= CPE(LO PN, PT-RS residual)
+= PA distortion residual (post-DPD)
↓
EVM composite: √(PA² + PN² + IQ² + OTA² + noise²) ≈ 3.0%
↓
MCS check: 3.0% < 3.5% (TS 38.141-2 256-QAM ceiling) ⇒ stay 256-QAM
↓
throughput: min(BW · log₂(1+SNR), 6.4 bit/Hz · 400 MHz) ≈ 2.5 Gb/s
The “Specs cited” side panel on the Link view lists every 3GPP document the sim references; the Scene view’s timeline is the literal walkthrough of the scenario above.
How it works under the hood
The simulator is a single HTML file with embedded CSS and JavaScript — no
build, no server, no WASM, no toolchain. The engine state (64 element records,
codebook, link budget, EVM contributors, history buffer) lives inside a small
IIFE that exposes send({ cmd }), step(n) and getState(). The visualizer
is a requestAnimationFrame loop wrapped in try/catch so a single regression
can’t lock up the UI: every frame it reads the engine state and routes to one
of five drawX() functions over a 1180 × 700 logical canvas.
Per-tick cost is dominated by the 64-element array-factor evaluations done at several angles (UE direction for the link budget, steered direction for peak EIRP, plus a 181-point sweep for the polar plot) — well inside one frame at 60 Hz on a mid-range laptop.