PDSCH Waveform generation

Creating a 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 30 kHz subcarrier spacing, with a single Bandwidth Part starting at Resource Block 1, and 52 total Resource Blocks. We also enable the interleaving by setting the interleavingBundleSize argument to 2 and set number of layers to 2. We then call the print method to show the carrier information.

Create a Carrier object with 30 kHz subcarrier spacing, a single Bandwidth Part starting at Resource Block 0, and 52 total Resource Blocks. We
also enable interleaving by setting the interleavingBundleSize argument to 2. We then call the print method to show the carrier information.
[2]:
carrier = Carrier(startRb=0, numRbs=52, spacing=30, interleavingBundleSize=2)
carrier.print()

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:       Yes (Bundle size:2)
    Bandwidth:          18.72 MHz
    symbolsPerSlot:     14
    slotsPerSubFrame:   2
    nFFT:               1024
    frameNo:            0
    slotNo:             0

PDSCH Configuration

Create a PDSCH object. We use the only Bandwidth Part of the above carrier for this PDSCH object. The print function is then called to show all the information about this PDSCH object.

Note that we could also set the parameters of the DMRS object owned by the 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 account for the scaling factor $ \beta`^{DMRS}\ *{PDSCH} $ as specified in the 3GPP 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], numLayers=2)
# MATLAB uses NumCDMGroupsWithoutData=2 as default => otherCdmGroups=[1]
pdsch.setDMRS(epreRatioDb=0, numCdmGroupsWithoutData=2)
pdsch.print()

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:              1
    nIDs:                    []
    scID:                    0
    sameSeq:                 True
    symbols:                 Single
    typeA1stPos:             2
    additionalPos:           0
    cdmGroups (port:cdm):    0:0  1:0
    deltaShifts (port:cdm):  0:0  1:0
    numCdmGroupsWithoutData: 2
    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]:
pdsch.initGrid()   # This creates a Grid object and populates it with the DMRS values
pdsch.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 these indices. We then print out the first 10 DMRS symbols. We can now compare these results with the ones printed by the MATLAB program.

[5]:
dmrsSymbolsMatlab = scipy.io.loadmat(matlabFilesPath + '/dmrsSymbols.mat')['dmrsSymbols'].T.flatten()
dmrsSymbolsMatlab.shape
[5]:
(624,)
[6]:
# MATLAB's DMRS symbols are in PRB order. NeoRadium Keeps everything VRB order before precoding. Here we
# convert NeoRadium's DMRS symbols to PRB order before comparing them with MATLAB.
reInx = (pdsch.prb2Grb[:,None]*12+np.arange(12)).flatten()
dmrsSymbols = pdsch.grid[:,2,reInx].flatten()[::2]

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 available bits in our grid object by calling the getBitCapacity method. Then we create numBits random bits and
feed them to the setPdschData method of PDSCH. This method scrambles, modulates, and maps 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 uncomment the second line in the code below to create random bits instead of reading them from the file.

[7]:
numBits = pdsch.getBitCapacity()
# bits = np.random.randint(2,size=numBits)  # Uncomment this to generate random bits

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

pdsch.setPdschData(bits)

Comparing the results with MATLAB

Now we want to compare the generated symbols with 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.

As you can see the results exactly match the ones generated by MATLAB.

[8]:
dataSymbols = pdsch.getDataSymbols()

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.31622777-0.9486833j   0.9486833 -0.9486833j
 -0.31622777+0.9486833j  -0.31622777-0.31622777j  0.31622777+0.9486833j
 -0.31622777+0.9486833j  -0.9486833 -0.31622777j  0.31622777-0.9486833j
  0.9486833 +0.31622777j  0.31622777+0.31622777j  0.31622777+0.9486833j
 -0.31622777-0.9486833j  -0.31622777-0.31622777j  0.31622777+0.9486833j
 -0.31622777+0.31622777j -0.31622777+0.31622777j -0.9486833 +0.31622777j
  0.31622777+0.9486833j  -0.9486833 +0.31622777j]
MATLAB-Generated Data Symbols:
 [-0.31622777-0.31622777j  0.31622777-0.9486833j   0.9486833 -0.9486833j
 -0.31622777+0.9486833j  -0.31622777-0.31622777j  0.31622777+0.9486833j
 -0.31622777+0.9486833j  -0.9486833 -0.31622777j  0.31622777-0.9486833j
  0.9486833 +0.31622777j  0.31622777+0.31622777j  0.31622777+0.9486833j
 -0.31622777-0.9486833j  -0.31622777-0.31622777j  0.31622777+0.9486833j
 -0.31622777+0.31622777j -0.31622777+0.31622777j -0.9486833 +0.31622777j
  0.31622777+0.9486833j  -0.9486833 +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.

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

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.

[10]:
pdsch.grid.drawMap();
../../../../../_images/source_Playground_Notebooks_CompareWithMatlab_PDSCH_PDSCH-waveform_18_0.png

MIMO Precoding

Now we can apply precoding to our data grid. Assuming 4 transmit antennas, we create a simple precoding matrix which is based on the identity matrix. We then call the precodeTo method of PDSCH to apply precoding to the grid.

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

w.shape,w
[11]:
((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]]))
[12]:
precodedGrid = carrier.curBwp.createGrid(numTxAntenna)
pdsch.precodeTo(precodedGrid, w.T)
pdsch.grid.shape, w.shape, precodedGrid.shape
[12]:
((2, 14, 624), (2, 4), (4, 14, 624))

We can now compare these results with the ones printed by the MATLAB program.

[13]:
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.4472-0.j      0.4472-0.6708j  0.6708-0.4472j -0.    +0.6708j
 -0.4472-0.4472j -0.2236-0.j     -0.    +0.6708j -0.4472+0.2236j
  0.    -0.6708j  0.2236-0.2236j]
MATLAB-Generated Precoded Grid:
 [-0.4472-0.j      0.4472-0.6708j  0.6708-0.4472j -0.    +0.6708j
 -0.4472-0.4472j -0.2236-0.j     -0.    +0.6708j -0.4472+0.2236j
  0.    -0.6708j  0.2236-0.2236j]

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.

[14]:
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.0066+0.0006j -0.003 -0.0079j  0.0111+0.0044j  0.0026+0.0128j
 -0.0003-0.0014j  0.0223-0.0073j  0.0231+0.0106j -0.0026+0.0172j
 -0.001 -0.0004j  0.0176-0.0097j]
MATLAB-Generated Waveform Data:
 [-0.0066+0.0006j -0.003 -0.0079j  0.0111+0.0044j  0.0026+0.0128j
 -0.0003-0.0014j  0.0223-0.0073j  0.0231+0.0106j -0.0026+0.0172j
 -0.001 -0.0004j  0.0176-0.0097j]
[15]:
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)

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