Cellular Emitter Algorithms

5G NR, LTE, and GSM waveform synthesis paths.

Note

Layer 3 shipped, Pass-1. The Layer 3 implementation (emitters, device-fingerprint, tx-impairments, propagation, rx-frontend) landed on branch rfgen-impl-2026-06-25-105955 (PR #94). The class names, Pydantic schemas, and Transformation enum members referenced below match the shipped surface; Pass-1 stubs (GNU Radio OOT emitters, cellular emitters, Sionna propagation backends) construct cleanly and raise an EmitterError or ChannelError tagged with stage="pass1_stub" until backend wiring lands. See Reference / rfgen.emitters and Reference / rfgen.channels for the shipped class roster.

8. Cellular

8.1 5G NR PUSCH via sionna.phy.nr

def generate_nr_pusch(*, class_label, sample_rate, duration_s, params, rng):
    """
    class_label: "cellular.nr.pusch.bw-{5,10,20,40,100}mhz.scs-{15,30,60}khz"
    params:
        scs_khz: int
        bandwidth_mhz: int
        modulation: "QPSK" | "16-QAM" | "64-QAM" | "256-QAM"
        num_layers: int
    """
    import sionna.phy.nr as nr  # lazy import
    carrier = nr.CarrierConfig(
        n_size_grid=_rb_count(params["bandwidth_mhz"], params["scs_khz"]),
        subcarrier_spacing=params["scs_khz"],
    )
    pusch_config = nr.PUSCHConfig(carrier_config=carrier)
    pusch_config.tb.mcs_table = _mcs_table(params["modulation"])
    pusch_config.num_layers = params["num_layers"]

    transmitter = nr.PUSCHTransmitter(
        pusch_config,
        return_bits=True,
        output_domain="time",
    )
    iq_native, bits = transmitter(batch_size=_slot_count(duration_s, params["scs_khz"]))

    iq_native = torch.from_numpy(iq_native.numpy()).to(torch.complex64)
    iq = _resample(iq_native, native_rate=_nr_sample_rate(carrier), target_rate=sample_rate)
    return Signal(iq=iq, metadata=SignalMetadata(bandwidth_hz=params["bandwidth_mhz"] * 1e6, ...))

This sketch is intentionally PUSCH-only. PDSCH, SSB, PRACH, CSI-RS, and NR test-model emitters are not part of the blueprint until the implementation pass verifies an upstream transmitter block or selects another mature backend.

8.2 LTE via srsRAN_4G ZMQ

srsRAN_4G is AGPL-3.0; not bundled. The user opts in via Hydra config and provides the binary on $PATH.

def generate_lte(*, class_label, sample_rate, duration_s, params, rng):
    """
    class_label: "cellular.lte.fdd.bw-{1.4,3,5,10,15,20}mhz"
    params:
        bandwidth_mhz: float
        cell_id: int
        pdsch_payload: "random" | bytes
    """
    bw_to_rb = {1.4: 6, 3: 15, 5: 25, 10: 50, 15: 75, 20: 100}
    n_rb = bw_to_rb[params["bandwidth_mhz"]]

    # Spawn srsenb subprocess writing IQ to a ZMQ socket; we read from the socket.
    handle = _spawn_srsran(
        config_template="lte_dl.conf.j2",
        n_prb=n_rb,
        cell_id=params["cell_id"],
        zmq_tx_port=_pick_free_port(),
        seed=int(rng.initial_seed()),
    )
    iq_native = _read_zmq_for(duration_s + 0.1, handle)  # buffer extra
    handle.terminate()
    iq = _resample(iq_native, native_rate=30.72e6, target_rate=sample_rate)
    iq = _trim_or_pad(iq, target_len=int(round(sample_rate * duration_s)))
    return Signal(iq=iq, metadata=SignalMetadata(bandwidth_hz=params["bandwidth_mhz"] * 1e6, ...))

Verification. Round-trip a known transport block through srsRAN_4G TX + a separate srsRAN_4G or open-source LTE RX.

8.3 GSM GMSK

Pure-NumPy GMSK at 270.833 kbit/s (legacy completeness only).

def generate_gsm(*, class_label, sample_rate, duration_s, params, rng):
    """class_label = "cellular.gsm.gmsk-200khz" """
    bit_rate = 270833.0
    bt = 0.3  # GSM uses BT = 0.3
    bw = 200e3
    bits = _gsm_burst_bitstream(rng)  # normal burst: 3 tail + 58 data + 26 training + 58 data + 3 tail + 8.25 guard
    return _gmsk_modulate(bits, bit_rate, bt, sample_rate, duration_s)

GMSK reuses the BLE GFSK algorithm (§4) with BT = 0.3, h = 0.5.


See Also

References

The 5G NR PUSCH waveform is generated by sionna.phy.nr (Sionna’s standards-grade NR transmitter); the LTE path is generated by srsRAN_4G (open-source LTE stack); the GSM GMSK path follows the public 3GPP specs. The framework does not re-implement any of these waveform chains.

  1. 3GPP TS 38.211, NR; Physical channels and modulation (Release 18). 3rd Generation Partnership Project, 2024. (PUSCH subcarrier spacing, n_size_grid, OFDM numerology used by sionna.phy.nr)

  2. 3GPP TS 38.212, NR; Multiplexing and channel coding (Release 18). 3GPP, 2024. (Transport block MCS table referenced by pusch_config.tb.mcs_table)

  3. 3GPP TS 38.214, NR; Physical layer procedures for data (Release 18). 3GPP, 2024. (PUSCH MCS-to-modulation mapping for QPSK / 16-QAM / 64-QAM / 256-QAM)

  4. 3GPP TS 36.211, E-UTRA; Physical channels and modulation (Release 17). 3GPP, 2023. (LTE PRB count vs. channel bandwidth; 30.72 MHz native sample rate at 20 MHz)

  5. 3GPP TS 45.004, GSM/EDGE; Modulation. 3GPP, 2020. (GSM GMSK at 270.833 kbit/s, BT = 0.3 Gaussian filter, h = 0.5 modulation index)

  6. 3GPP TS 45.002, GSM/EDGE; Multiplexing and multiple access on the radio path. 3GPP, 2020. (Normal burst structure: 3 + 58 + 26 + 58 + 3 + 8.25 guard symbols)

  7. Hoydis, J. et al. Sionna: An Open-Source Library for Next-Generation Physical Layer Research, NVIDIA, 2022. https://nvlabs.github.io/sionna/. (sionna.phy.nr PUSCHTransmitter API and validation against 3GPP test models)

  8. srsRAN Project. srsRAN_4G: Open-source 4G/5G software radio suite. https://github.com/srsran/srsRAN_4G. (AGPL-3.0 LTE stack used for the LTE path)