Wireless and coded links#
The blocks of comnum.blocks include multi-antenna transmission, fading channels,
OFDM with pilots and channel estimation, MIMO detection, channel codes and soft
demapping: a whole wireless link is a Chain (or a specification) that
runs on the parallel Rust runner.
Antennas and fading#
MimoTx spreads the symbols over the transmit antennas, by spatial multiplexing
(symbol i on antenna i mod n_t, the power divided among the antennas) or by
Alamouti’s code. Rayleigh (flat Rayleigh or Rician fading, constant over coherence
blocks, with exponential antenna correlation) and Tdl (the tapped delay lines EPA,
EVA, ETU of 3GPP TS 36.101 and the ITU profiles, with Clarke/Jakes Doppler) act on
every antenna and record the channel for a genie-aided receiver. MimoRx detects a
single-carrier link with the known channel (ZF, MMSE, MMSE-SIC, ML, sphere decoder,
K-best):
import comnum
from comnum import blocks as B
alamouti = comnum.Chain(
B.Source(constellation="QPSK", n_symbols=8192),
B.MimoTx(scheme="alamouti"),
B.Rayleigh(n_rx=2, coherence_samples=2),
B.AwgnEbn0(ebn0_db=comnum.var("ebn0_db")),
B.MimoRx(),
)
The fading blocks have unit mean gain and record the power that enters them: the noise
blocks that follow take it as their reference, so that ebn0_db and snr_db are
averages over the fading, as in the closed forms of fading channels. specs/
mimo_diversity.yaml checks the chain above against the BER of maximal-ratio combining.
OFDM#
OfdmTx maps the symbols of every antenna on resource grids (block, comb or lattice
pilots, dealt to the antennas in turn so that they are orthogonal) and modulates them;
OfdmRx demodulates, estimates the channel (perfect, ls with linear
interpolation, or lmmse) and detects every data resource element. The modulator
scales the samples so that Awgn(snr_db=…) sets the SNR per resource element:
ofdm = comnum.Chain(
B.Source(constellation="16-QAM", n_symbols=4800),
B.MimoTx(n_tx=2),
B.OfdmTx(n_fft=256, n_subcarriers=200, n_cp=18),
B.Tdl(profile="EPA", doppler_hz=5, n_rx=2),
B.Awgn(snr_db=comnum.var("snr_db")),
B.OfdmRx(estimation="lmmse", detector="mmse"),
)
Channel codes#
FecEncode draws the information bits of blocks codewords and encodes them
(conv, turbo, ldpc or polar, optionally through a random bit
interleaver); Map maps the coded bits on a constellation; the receiver ends with
Demap (exact or max-log LLRs with the noise variance of every symbol, which the
OFDM and MIMO receivers provide) and FecDecode. The metrics coded_ber and
bler compare the decoded bits with the transmitted ones, ber stays the error
rate of the coded bits:
code = {"type": "ldpc", "n": 1024, "k": 512}
bicm = comnum.Chain(
B.FecEncode(code=code, blocks=4, interleave=True),
B.Map(constellation="16-QAM"),
B.Rayleigh(coherence_samples=1),
B.AwgnEbn0(ebn0_db=comnum.var("ebn0_db")),
B.MimoRx(),
B.Demap(),
B.FecDecode(code=code, interleave=True),
)
bicm.run(grid={"ebn0_db": [6, 8, 10]}, metrics=["ber", "coded_ber", "bler"],
stop={"min_errors": {"bler": 100}, "max_trials": 500})
After FecEncode, AwgnEbn0 counts the energy per information bit.
Probabilistic shaping#
Source(mb_lambda=…) draws the points with the Maxwell-Boltzmann probabilities
\(p(x) \propto e^{-\lambda |x|^2}\) on the unit-energy uniform constellation, and the
frame carries them: the metrics mi, gmi and ngmi use them (the GMI of
bit-metric decoding \(H(X) - \sum_k H(B_k \mid Y)\)), entropy reports
\(H(X)\), Demap computes the LLRs with the priors and AwgnEbn0 counts the
energy per bit of information. comnum.theory_mi() and comnum.theory_gmi()
take the same priors (px), as comnum.monte_carlo_gmi() and
comnum.information_rates().
PasEncode and PasDecode are probabilistic amplitude shaping of a square QAM
(Böcherer, Steiner & Schulte 2015): per dimension, a constant-composition distribution
matcher (comnum.Ccdm, Schulte & Böcherer 2016) shapes the amplitudes, a
systematic LDPC code protects their bits, and its parity bits become the signs:
p = {"constellation": "64-QAM", "mb_lambda": 2.55, "n": 1536}
pas = comnum.Chain(
B.PasEncode(**p, blocks=4),
B.Awgn(snr_db=comnum.var("snr_db")),
B.PasDecode(**p),
)
pas.run(grid={"snr_db": [10, 10.5, 11]}, metrics=["coded_ber", "bler", "gmi", "entropy"])
specs/shaping_awgn.yaml checks the rates of shaped 64-QAM against the closed forms,
specs/pas_64qam.yaml runs PAS at 3 bits per QAM symbol.
Satellite: DVB-S2#
comnum.Dvbs2 is the physical layer of DVB-S2 (ETSI EN 302 307-1). It covers the
28 MODCODs of Table 12, normal (64 800 bits) and short (16 200 bits) frames, with or
without pilots. A BBFRAME of k bits (comnum.dvbs2_bbframe(): BBHEADER with its
CRC-8, data field, PRBS randomisation) is encoded by the BCH and LDPC codes of the
standard. The bits are then interleaved, mapped on QPSK, 8PSK, 16APSK or 32APSK
(Figures 9 to 12, ring ratios of Tables 9 and 10), and framed into a PLFRAME:
π/2-BPSK header, pilots, Gold scrambling. The receiver descrambles, computes the
exact (or max-log) LLRs, deinterleaves and decodes with the layered sum-product
algorithm (50 iterations); comnum.dvbs2_decode_plheader() recovers the MODCOD of a
header by maximum likelihood.
import comnum
from comnum import blocks as B
link = comnum.Chain(
B.Dvbs2Tx(modulation="16APSK", rate="3/4", frame="short"),
B.Awgn(snr_db=comnum.var("esn0_db"), reference="unit"), # Es/N0
B.Dvbs2Rx(modulation="16APSK", rate="3/4", frame="short"),
)
res = link.run(grid={"esn0_db": [10.2, 10.4, 10.6]},
metrics={"report": ["bler", "coded_ber"], "scale": "none"})
Every symbol of a PLFRAME has unit energy, so that reference: unit sets the Es/N0
of Table 13. specs/validation/dvbs2_table13.yaml checks the waterfall of all 28
MODCODs against that table. The LDPC tables, the BCH polynomials, the constellations,
the interleaver and the framing follow the text of the standard; the LDPC tables are
also identical to those of AFF3CT.