NR PDSCH Throughput with HARQ

This notebook demonstrates how to measure the physical downlink shared channel (PDSCH) throughput of a 5G New Radio (NR) link, as specified in the 3GPP NR standard. It is similar to the MATLAB example NR PDSCH Throughput and showcases the use of the following NeoRadium features:

  • Carrier and Bandwidth Part classes.

  • PDSCH and DMRS objects.

  • LDPC and HARQ capabilities.

  • CDL channel model and extraction of channel matrix and precoding matrix.

  • Resource grids, populating them, and applying channel models to them.

[1]:
import numpy as np
import scipy.io
import time
import matplotlib.pyplot as plt

from neoradium import Carrier, PDSCH, CdlChannel, AntennaPanel, random, SnrScheduler
[2]:
# Create a Carrier object with 52 resource blocks and 30KHz subcarrier spacing
carrier = Carrier(numRbs=52, spacing=30)
carrier.print()
bwp = carrier.curBwp                      # The only bandwidth part in the carrier

Carrier Properties:
  Cell Id:              1
  Bandwidth Parts:      1
  Active BWP:           0
  Bandwidth Part 0:
    Resource Blocks:    52 RBs starting at 0 (624 subcarriers)
    Subcarrier Spacing: 30 kHz
    CP Type:            normal
    Interleaving:       No
    Bandwidth:          18.72 MHz
    symbolsPerSlot:     14
    slotsPerSubFrame:   2
    nFFT:               1024
    frameNo:            0
    slotNo:             0

[3]:
# Initialize the modulation and code rate.
modulation = '16QAM'
coderate = 490/1024
# Create a PDSCH onject with 2 tramsmission layers
pdsch = PDSCH(bwp, numLayers=2)
pdsch.setDMRS(configType=2, additionalPos=2)
pdsch.print()

# Creating a HarqEntity object. (It also creates an LdpcCodec object internally):
harq = pdsch.getHarq(coderates = coderate,      # Coderate(s) used by HARQ's internal LDPC codec
                     harqType = "IR",           # "IR" -> "Incremental Redundancy", "CC" -> "Chase Combining"
                     numProc = 16)              # Number of HARQ processes
harq.print()

# Create the CDL channel model
channel = CdlChannel(bwp, 'C', delaySpread=300, carrierFreq=4e9, dopplerShift=5,
                     txAntenna = AntennaPanel([1,4], polarization="x"),                     # 8 TX antennas
                     rxAntenna = AntennaPanel([1,1], polarization="+", beamWidth=[75,360]), # 2 RX antennas, Omni-directional
                     rxOrientation = [0,0,0])                                               # Default is [180,0,0]
print(channel)

PDSCH Properties:
  mappingType:               A
  nID:                       1
  rnti:                      1
  numLayers:                 2
  numCodewords:              1
  modulation:                16QAM
  PRG Size:                  Wideband
  portSet:                   0   1
  symSet:                    0   1   2   3   4   5   6   7   8   9   10  11  12  13
  prbSet:                    0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17  18  19
                             20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39
                             40  41  42  43  44  45  46  47  48  49  50  51
  DMRS:
    configType:              2
    nIDs:                    []
    scID:                    0
    sameSeq:                 True
    symbols:                 Single
    typeA1stPos:             2
    additionalPos:           2
    cdmGroups (port:cdm):    0:0  1:0
    deltaShifts (port:cdm):  0:0  1:0
    numCdmGroupsWithoutData: 1
    symSet:                  2   7  11
    REs (before shift):      0 1 6 7
    epreRatioDb:             0 (dB)


HARQ Entity Properties:
  HARQ Type:            IR
  Num. Processes:       16
  Num. Codewords:       1
  RV sequence:          [0, 2, 3, 1]
  maxTries:             4
  LDPC codec:
    Num layers:         2
    Num codewords:      1
    numIter:            5
    nRef:               0
    Modulation:         16QAM
    Coderate:           490/1024
    TBS:                31240
    numLayers:          2
    Base Graph:         1
    Code Block Size:    8448
    Num Code Blocks:    4
    Lifting Size:       384


CDL-C Channel Properties:
  carrierFreq:              4 GHz
  normalizeGains:           True
  normalizeOutput:          True
  txDir:                    Downlink
  filterLen:                16 samples
  delayQuantSize:           64
  stopBandAtten:            80 dB
  dopplerShift:             5 Hz
  coherenceTime:            84.628 milliseconds
  delaySpread:              300 ns
  ueDirAZ:                  0°, 90°
  xPolPower:                7.00 dB
  angleSpreads:             2° 15° 3° 7°
  TX Antenna:
    Total Elements:         8
    spacing:                0.5𝜆, 0.5𝜆
    shape:                  1 rows x 4 columns
    polarization:           x
  RX Antenna:
    Total Elements:         2
    spacing:                0.5𝜆, 0.5𝜆
    shape:                  1 rows x 1 columns
    polarization:           +
  hasLOS:                   False
  NLOS Paths (24):
    Delays (ns):            0.000 62.97 66.57 69.87 65.28 190.9 193.4 196.8 197.5 238.0 246.3 280.0
                            368.5 392.4 651.1 813.1 1277. 1380. 1647. 1682. 1891. 1991. 2112. 2595.
    Powers (dB):            -4.40 -1.20 -3.50 -5.20 -2.50 0.000 -2.20 -3.90 -7.40 -7.10 -10.7 -11.1
                            -5.10 -6.80 -8.70 -13.2 -13.9 -13.9 -15.8 -17.1 -16.0 -15.7 -21.6 -22.8
    AODs (Deg):             -47  -23  -23  -23  -41  0    0    0    73   -64  80   -97
                            -55  -64  -78  103  99   89   -102 92   93   107  120  -124
    AOAs (Deg):             -101 120  120  120  -128 170  170  170  55   66   -48  47
                            68   -69  82   31   -16  4    -14  10   6    1    -22  34
    ZODs (Deg):             97   99   99   99   101  99   99   99   105  95   106  94
                            104  104  93   104  95   93   92   107  93   93   105  108
    ZOAs (Deg):             88   72   72   72   70   75   75   75   67   64   71   60
                            91   60   61   101  62   67   53   62   52   62   58   57

[4]:
# Print header lines:
print("SNR(dB)   Tx Bits     Rx Bits    Throughput(%)  TX Blocks  RX Blocks  BLER(%)  Avg. Retransmissions  time(Sec.)")
print("-------  ----------  ----------  -------------  ---------  ---------  -------  --------------------  ----------")

txBlockSizes = pdsch.getTxBlockSize(coderate)                   # Calculate the Transport Block Size (TBS)
snrScheduler = SnrScheduler(0, 1, loSnrVal=0, hiSnrVal=100, fastStep=5) # Start at 0 dB, use increments of 1 dB
numSlots = 1000                                                 # The number of slots transmitted for each SNR value
for snrDb in snrScheduler:
    random.setSeed(123)
    channel.restart()                                           # Reset the channel and the bandwidth part associated with it
    harq.reset()                                                # Reset HARQ state and buffers

    t0 = time.monotonic()                                       # Start the timer
    for s in range(numSlots):                                   # The inner loop doing 'numSlot' transmissions
        pdsch.initGrid()                                        # Create and initialize PDSCH's internal grid
        numBits = pdsch.getBitCapacity()                        # Total number of PDSCH data bits available in the resource grid

        # Preparing the transport blocks
        txBlocks = []                                           # Transport blocks, one per codeword.
        for c in range(harq.numCW):
            if harq.needNewData[c]:                             # New transmission.
                txBlocks += [ random.bits(txBlockSizes[c]) ]    # Create random bits for new transmissions
            else:                                               # Retransmission
                txBlocks += [ None ]                            # Set transport block to None to indicate a retransmission

        # The following function returns a coded, rate-matched bitstream, ready for transmission/retransmission
        rateMatchedCodeBlocks = harq.encode(txBlocks, numBits)

        pdsch.setPdschData(rateMatchedCodeBlocks)               # Map/modulate the data to the resource grid

        channelMatrix = channel.getChannelMatrix()              # Get channel matrix (Assuming perfect channel estimation)
        precoder = pdsch.getPrecodingMatrix(channelMatrix)      # Get precoding matrix based on the channel matrix

        txGrid = bwp.createGrid(len(channel.txAntenna))         # Create the transmitted resource grid
        pdsch.precodeTo(txGrid, precoder)                       # Perform the precoding

        rxGrid = txGrid.applyChannel(channelMatrix)             # Apply the channel to the precoded resource (Freq. domain)
        noisyRxGrid = rxGrid.addNoise(snrDb=snrDb)              # Add noise

        # Calculate the effective channel matrix with the precoding effect. This is assuming perfect channel
        # knowledge. For practical channel estimation we can use the DMRS reference signals and use the
        # "estimateChannel" method of the PDSCH object.
        effChannelMatrix = channel.getEffChannel(channelMatrix, precoder)

        # Use the effective channel matrix to equalize the received resource grid
        eqGrid, llrScales = pdsch.equalize(noisyRxGrid, effChannelMatrix)

        # Demodulate the equalized resource grid (eqGrid) to get the Log-Likelihood values
        llrs = pdsch.getLLRs(eqGrid, llrScales)

        # Use HARQ entity to decode the LLRs to transport blocks
        decodedTxBlocks, crcMatches = harq.decode(llrs)

        # Get the statistics from HARQ entity and print them:
        print(f"{snrDb:^7.2f}  {harq.totalTxBits:^10d}  {harq.totalRxBits:^10d}  {harq.throughput:^13.2f}  "
              f"{harq.totalTxBlocks:^9}  {harq.totalRxBlocks:^9d}  {harq.bler:^7.2f}  {harq.meanRetransmissions:^20.3f}  "
              f"{time.monotonic()-t0:^-10.3f}", end="\r")
        channel.goNext()
        harq.goNext()

    snrScheduler.setData(harq.throughput)
    print("")
SNR(dB)   Tx Bits     Rx Bits    Throughput(%)  TX Blocks  RX Blocks  BLER(%)  Avg. Retransmissions  time(Sec.)
-------  ----------  ----------  -------------  ---------  ---------  -------  --------------------  ----------
 0.00     31240000    31240000      100.00        1000       1000      0.00           0.000           442.852
 -5.00    31240000    27834840       89.10        1000        891      10.90          0.122           429.116
 -6.00    31240000    26210360       83.90        1000        839      16.10          0.192           420.398
 -7.00    31240000    24960760       79.90        1000        799      20.10          0.252           416.080
 -8.00    31240000    22930160       73.40        1000        734      26.60          0.362           406.844
 -9.00    31240000    15776200       50.50        1000        505      49.50          0.962           370.360
-10.00    31240000    14901480       47.70        1000        477      52.30          1.086           367.244
-11.00    31240000    13933040       44.60        1000        446      55.40          1.224           361.609
-12.00    31240000    12714680       40.70        1000        407      59.30          1.432           357.607
-13.00    31240000    9684400        31.00        1000        310      69.00          2.015           343.787
-14.00    31240000    6841560        21.90        1000        219      78.10          2.413           340.201
-15.00    31240000    4592280        14.70        1000        147      85.30          2.877           334.571
-16.00    31240000    2592920        8.30         1000        83       91.70          3.000           334.723
-17.00    31240000       0           0.00         1000         0      100.00          3.000           335.363
-18.00    31240000       0           0.00         1000         0      100.00          3.000           333.704
 -4.00    31240000    29084440       93.10        1000        931      6.90           0.074           437.282
 -3.00    31240000    30677680       98.20        1000        982      1.80           0.018           445.951
 -2.00    31240000    31240000      100.00        1000       1000      0.00           0.000           448.183
 -1.00    31240000    31240000      100.00        1000       1000      0.00           0.000           447.097
[5]:
# Draw the throughput graph
snrDbs, throughputs = snrScheduler.getSnrsAndData()
plt.figure(figsize=(12, 6))
plt.plot(snrDbs, throughputs)
plt.title("PDSCH throughput at different SNR values");
plt.grid()
plt.xlabel("SNR (dB)")
plt.xticks(snrDbs)
plt.ylabel("Throughput (%)")
plt.show()
../../../../_images/source_Playground_Notebooks_PDSCH_PDSCH-Throughput_5_0.png
[ ]: