PDSCH Waveform generation

Creating PDSCH time domain waveform and comparing the results with the equivalent Matlab code “MatlabFiles/PDSCH-waveform.mlx”. Here is the execution results of this code in Matlab.

[1]:
import numpy as np
import scipy.io

from neoradium import Carrier, PDSCH, CdlChannel, AntennaPanel, LdpcEncoder, LdpcDecoder, Grid

matlabFilesPath = "./MatlabFiles"

Carrier Configuration

Create an instance of Carrier object. We use 30KHz subcarrier spacing, with a single Bandwidth Part starting at Resource Block 1, and 52 total Resource Blocks. We then call the print method to show the carrier information.

[2]:
carrier = Carrier(startRb=1, numRbs=52, spacing=30)
carrier.print()

Carrier Properties:
  startRb: 1
  numRbs: 52
  Cell Id: 1
  Active Bandwidth Part: 0
  Bandwidth Parts: 1
  Bandwidth Part 0:
    Resource Blocks: 52 RBs starting at 1 (624 subcarriers)
    Subcarrier Spacing: 30 KHz
    CP Type: normal
    bandwidth: 18720000 Hz
    symbolsPerSlot: 14
    slotsPerSubFrame: 2
    nFFT: 1024

PDSCH Configuration

Create an instance of PDSCH object. We use the only Bandwidth Part of the above carrier for this PDSCH object. We then, enable the interleaving by setting the interleavingBundleSize argument to 2 and set number of layers to 2. The print function is then called to show all the information about this PDSCH object.

Note that we could also set the parameters of DMRS object owned by PDSCH by passing them when instantiating the PDSCH object. Here we are just using the default values.

Also note that the Matlab implementation does not take into account the scaling factor $ \beta`^{DMRS}\ *{PDSCH} $ as specified in newer versions of the standard. Our implementation by default uses the ``TS 38.214 V17.0.0 (2021-12), Table 4.1-1` to get the ratio of PDSCH EPRE to DMRS EPRE ($ \beta`*\ {DMRS} $). To make our results match Matlab's results, we have to force the ``epreRatioDb` value to zero here to ignore the table in the standard.

[3]:
pdsch = PDSCH(carrier.bwps[0], interleavingBundleSize=2, numLayers=2)
# Matlab uses NumCDMGroupsWithoutData=2 as default => otherCdmGroups=[1]
pdsch.setDMRS(epreRatioDb=0, otherCdmGroups=[1])
pdsch.print()

PDSCH Properties:
  mappingType: A
  nID: 1
  rnti: 1
  numLayers: 2
  numCodewords: 1
  modulation: 16QAM
  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
  interleavingBundleSize: 2
  PRG Size: Wideband
  Bandwidth Part:
    Resource Blocks: 52 RBs starting at 1 (624 subcarriers)
    Subcarrier Spacing: 30 KHz
    CP Type: normal
    bandwidth: 18720000 Hz
    symbolsPerSlot: 14
    slotsPerSubFrame: 2
    nFFT: 1024
  DMRS:
    configType: 1
    nIDs: []
    scID: 0
    sameSeq: 1
    symbols: Single
    typeA1stPos: 2
    additionalPos: 0
    cdmGroups: [0, 0]
    deltaShifts: [0, 0]
    allCdmGroups: [0, 1]
    symSet: [2]
    REs (before shift): [0, 2, 4, 6, 8, 10]
    epreRatioDb: 0 (db)

Create a grid and populate it with DMRS data

Now we can create a resource grid (A Grid object) for our PDSCH and populate it with DMRS data.

[4]:
grid = pdsch.getGrid()   # This creates a Grid object and populates it with the DMRS values
grid.shape
[4]:
(2, 14, 624)

Comparing DMRS symbols

Now we want to compare the DMRS symbols generated by our PDSCH object with Matlab’s results. Again, we first get the indexes of the DMRS symbols from the grid map. Then we get the symbols at this indexes. We then print out the first 10 DMRS symbols. We can now compare these results with the ones printed by the Matlab program Mapping5GPhysicalChannelsAndSignalsToTheResourceGridExample.mlx.

[5]:
dmrsSymbols = grid.getReValues("DMRS")  # Get all the "DMRS" Resource Element values from the grid

# Load Matlab-generated DMRS values:
dmrsSymbolsMatlab = scipy.io.loadmat(matlabFilesPath + '/dmrsSymbols.mat')['dmrsSymbols'].T.flatten()
assert np.abs(dmrsSymbolsMatlab-dmrsSymbols).max()<1e-10, "MISMATCH WITH MATLAB!!!"

# Print the first 10 DMRS Symbols:
print("DMRS Values:\n",dmrsSymbols[:10])
print("Matlab-Generated DMRS Values:\n",dmrsSymbolsMatlab[:10])
DMRS Values:
 [ 0.70710678-0.70710678j  0.70710678+0.70710678j  0.70710678-0.70710678j
 -0.70710678-0.70710678j -0.70710678+0.70710678j  0.70710678+0.70710678j
  0.70710678+0.70710678j  0.70710678+0.70710678j  0.70710678+0.70710678j
  0.70710678+0.70710678j]
Matlab-Generated DMRS Values:
 [ 0.70710678-0.70710678j  0.70710678+0.70710678j  0.70710678-0.70710678j
 -0.70710678-0.70710678j -0.70710678+0.70710678j  0.70710678+0.70710678j
  0.70710678+0.70710678j  0.70710678+0.70710678j  0.70710678+0.70710678j
  0.70710678+0.70710678j]

Generate random bits and create resource block

Here we first get the number of bits that are available in our grid object by calling the getGridBitSize method. Then we creare numBits random bits and feed them to the populateGrid method of PDSCH. This method scrambles, modulates, and map the modulated symbols to different layers of the grid.

Note: To have deterministic results and be able to compare the results with matlab, we are reading the random bits from a Matlab-generated data file. You can un-remark the second line in the code below to create random bits instead of reading them from the file.

[6]:
numBits = pdsch.getBitSizes(grid)
# bits = np.random.randint(2,size=numBits)  # Unremark this to generate random bits

# Loading the random bits from file to have deterministic resaults and be able to compare with matlab
bits = scipy.io.loadmat(matlabFilesPath + '/pdschBits.mat')['pdschBits'].flatten()
assert(numBits[0] == len(bits))

pdsch.populateGrid(grid, bits)

Comparing the results with Matlab

Now we want to compare the generated symbols with the Matlab’s results. We first get the data symbols from the pdsch object’s getDataSymbols method which returns all data symbols in the grid.

We then read all the Matlab-generated symbols (from the same random set of bits) from the file generated by the Matlab program Mapping5GPhysicalChannelsAndSignalsToTheResourceGridExample.mlx.

As you can see the results exactly matches the ones generated by Matlab.

[7]:
dataSymbols = pdsch.getDataSymbols(grid)

dataSymbolsMatlab = scipy.io.loadmat(matlabFilesPath + '/pdschSymbols.mat')['pdschSymbols'].T.flatten()
assert np.abs(dataSymbolsMatlab-dataSymbols).max()<1e-10, "MISMATCH WITH MATLAB!!!"

# Print the first 20 Data Symbols:
print("Data Symbols:\n", dataSymbols[:20])
print("Matlab-Generated Data Symbols:\n", dataSymbolsMatlab[:20])
Data Symbols:
 [ 0.31622777+0.31622777j -0.9486833 -0.9486833j  -0.9486833 -0.31622777j
 -0.31622777-0.31622777j  0.31622777-0.31622777j  0.9486833 +0.9486833j
  0.9486833 +0.31622777j -0.9486833 -0.9486833j  -0.9486833 +0.9486833j
 -0.31622777+0.31622777j  0.9486833 +0.31622777j -0.9486833 -0.9486833j
  0.9486833 -0.9486833j  -0.31622777+0.9486833j   0.9486833 -0.31622777j
 -0.31622777+0.9486833j   0.31622777+0.31622777j  0.31622777+0.9486833j
 -0.9486833 -0.31622777j -0.31622777+0.31622777j]
Matlab-Generated Data Symbols:
 [ 0.31622777+0.31622777j -0.9486833 -0.9486833j  -0.9486833 -0.31622777j
 -0.31622777-0.31622777j  0.31622777-0.31622777j  0.9486833 +0.9486833j
  0.9486833 +0.31622777j -0.9486833 -0.9486833j  -0.9486833 +0.9486833j
 -0.31622777+0.31622777j  0.9486833 +0.31622777j -0.9486833 -0.9486833j
  0.9486833 -0.9486833j  -0.31622777+0.9486833j   0.9486833 -0.31622777j
 -0.31622777+0.9486833j   0.31622777+0.31622777j  0.31622777+0.9486833j
 -0.9486833 -0.31622777j -0.31622777+0.31622777j]

Grid Statistics

Now we can get some information about the resource grid statistics. The function getStats counts the number of resource elements allocated for data, DMRS, PTRS, reserved resources, and returns the information as a dictionary.

[8]:
stats = grid.getStats()
for key,value in stats.items():  print("%-10s: %d"%(key, value))
GridSize  : 17472
NO_DATA   : 624
DMRS      : 624
PDSCH     : 16224

Draw resource allocation map

We can also draw the resource allocation map (before precoding) for different layers. For example, the following code uses the drawMap function to draw the resource map for the first two Resource Blocks of the grid in the first layer.

[9]:
grid.drawMap(reRange=(0,36))
../../../../../_images/source_Playground_Notebooks_CompareWithMatlab_PDSCH_PDSCH-waveform_17_0.png

MIMO Precoding

Now we can apply precoding to the our data grid. Assuming 4 transmit antenna, we create a simple precoding matrix which is based on Identity matrix. We then call the precode method of grid to get the precoded grid.

[10]:
numTxAntenna = 4
w = np.fft.fft(np.eye(numTxAntenna))/np.sqrt(numTxAntenna)
# Get a sub-matrix by using only the frist 'numLayers' rows and normalize it accordingly.
w = w[:pdsch.numLayers,:]/np.sqrt(pdsch.numLayers)

w.shape,w
[10]:
((2, 4),
 array([[ 0.35355339+0.j        ,  0.35355339+0.j        ,
          0.35355339+0.j        ,  0.35355339+0.j        ],
        [ 0.35355339+0.j        ,  0.        -0.35355339j,
         -0.35355339+0.j        ,  0.        +0.35355339j]]))
[11]:
precodedGrid = grid.precode(w.T)
grid.shape, w.shape, precodedGrid.shape
[11]:
((2, 14, 624), (2, 4), (4, 14, 624))

We can now compare these results with the ones printed by the Matlab program Mapping5GPhysicalChannelsAndSignalsToTheResourceGridExample.mlx.

[12]:
pdschGridMatlab = scipy.io.loadmat(matlabFilesPath + '/pdschGrid.mat')['pdschGrid']
pdschGridMatlab = np.transpose(pdschGridMatlab, (2,1,0)) # Matlab uses a different order
assert np.abs(pdschGridMatlab-precodedGrid.grid).max()<1e-10, "MISMATCH WITH MATLAB!!!"

# Print the first 10 Data Symbols:
print("Precoded Grid:\n", np.round(precodedGrid[0,0,:10],4))
print("Matlab-Generated Precoded Grid:\n", np.round(pdschGridMatlab[0,0,:10],4))
Precoded Grid:
 [ 0.2236+0.4472j -0.2236-0.6708j -0.2236-0.2236j  0.2236+0.j
  0.4472-0.2236j  0.4472+0.j      0.6708+0.j     -0.6708-0.4472j
 -0.2236+0.j     -0.4472+0.j    ]
Matlab-Generated Precoded Grid:
 [ 0.2236+0.4472j -0.2236-0.6708j -0.2236-0.2236j  0.2236-0.j
  0.4472-0.2236j  0.4472-0.j      0.6708+0.j     -0.6708-0.4472j
 -0.2236-0.j     -0.4472+0.j    ]

OFDM Modulation

Now we call the ofdmModulate method of our precodedGrid object to get the output waveform for each antenna and compare these results with the ones printed by the Matlab program Mapping5GPhysicalChannelsAndSignalsToTheResourceGridExample.mlx.

[13]:
waveForm = precodedGrid.ofdmModulate()
waveForm.print()
waveformMatlab = scipy.io.loadmat(matlabFilesPath + '/txWaveform.mat')['txWaveform'].T
assert np.abs(waveForm[:]-waveformMatlab).max()<1e-10, \
    "MISMATCH WITH MATLAB!!! (max Diff: %f)"%(np.abs(waveForm-waveformMatlab).max())

# print the first 10 samples of the waveForm for first TX antenna
print("Waveform Data:\n", np.round(waveForm[0,:10],4))
print("Matlab-Generated Waveform Data:\n", np.round(waveformMatlab[0,:10],4))

Waveform Properties:
  Number of Ports: 4
  Length: 15360

Waveform Data:
 [ 0.0053-0.0104j  0.0072+0.0016j -0.0017+0.0071j -0.0086-0.0011j
  0.0063-0.0057j  0.0191-0.0001j  0.0044+0.004j  -0.015 +0.0036j
 -0.0155+0.0084j -0.008 +0.015j ]
Matlab-Generated Waveform Data:
 [ 0.0053-0.0104j  0.0072+0.0016j -0.0017+0.0071j -0.0086-0.0011j
  0.0063-0.0057j  0.0191-0.0001j  0.0044+0.004j  -0.015 +0.0036j
 -0.0155+0.0084j -0.008 +0.015j ]
[14]:
import matplotlib.pyplot as plt

plt.figure(figsize=(10,6))
plt.plot(np.abs(waveForm[0]))
plt.xlabel("Samples", fontsize=16)
plt.ylabel("Magnitude", fontsize=16)
plt.title("Magnitude of the time-domain waveform for the first antenna", fontsize=18)

[14]:
Text(0.5, 1.0, 'Magnitude of the time-domain waveform for the first antenna')
../../../../../_images/source_Playground_Notebooks_CompareWithMatlab_PDSCH_PDSCH-waveform_25_1.png
[ ]: