mmWave 5G FR2 phased-array RF path — live 28 GHz beamformer

· updated

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

Open fullscreen ↗

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:

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:

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

ViewWhat it shows
SceneTop-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).
Array8×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.
BeamPolar 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.
EVMLive 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.
LinkLink-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

Controls

Key / mouseAction
spaceRun / pause the script
sStep one engine tick (20 ms simulated)
shift+sStep ×10
rReset to cold start
vCycle 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 knobsDPD · 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.

← radio frequency