{ "cells": [ { "cell_type": "markdown", "id": "c29aa093", "metadata": {}, "source": [ "# PDSCH end-to-end communication\n", "This notebook shows how an end-to-end communication through a 5G Physical Downlink Shared Channel (PDSCH) can be simulated in **NeoRadium** based on 3GPP NR standard. It demonstrate the following steps:\n", "\n", "- Creating LDPC encoder object and using it to perform transport block segmentation, LDPC encoding and rate matching based on 3GPP TS 38.212.\n", "- Creating a ``PDSCH`` object and using it to populate a transmitted resource grid based on the encoded transport blocks. During this process, the PDSCH also adds DMRS reference signals in the resource grid based on 3GPP TS 38.211 and TS 38.214.\n", "- Creating a CDL channel model as specified in 3GPP TR 38.901 using the ``CdlChannel`` class. This class and the resource gird created by the ``PDSCH``, are used to calculate a precoding matrix which is then used to precode the resource grid.\n", "- Converting the precoded resource grid to a time-domain (transmitted) waveform through OFDM modulation performed by the ``ofdmModulate`` method of the ``Grid`` class.\n", "- Applying the CDL channel to the transmitted waveform to obtain the received waveform.\n", "- Synchronizing the received waveform by calculating the timing offset using the CDL class object.\n", "- Applying OFDM demodulation to the synchronized received waveform to get a received resource grid.\n", "- Estimating the channel matrix using the DMRS reference signals.\n", "- Equalizing the received resource grid using Minimum mean-squared error (MMSE) equalization to cancel the effect of communication channel.\n", "- Using the ``getLLRsFromGrid`` function of ``PDSCH`` class to extract log-likelihood ratios from the received resource grid.\n", "- Recovering the rate and then LDPC-decoding the transport blocks from the log-likelihood ratios.\n", "- Comparing the received bitstream with the original values used at the beginning of the pipeline.\n", "\n", "![PDSCH-Pipeline](PDSCH-Pipeline.png)\n", "\n", "Let's get started by importing the **NewRadium** modules used in the notebook." ] }, { "cell_type": "code", "execution_count": 1, "id": "2415601e", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import scipy.io\n", "import time\n", "\n", "from neoradium import Carrier, PDSCH, CdlChannel, AntennaPanel, LdpcEncoder, Grid\n", "from neoradium.utils import random\n" ] }, { "cell_type": "markdown", "id": "e3afe2dd", "metadata": {}, "source": [ "### Carrier Configuration\n", "Here using the ``Carrier`` class we define a carrier with 30 KHz subcarrier spacing with 51 resource blocks. The print function prints information about the carrier. Please note that by default a single bandwidth part is defined which covers the whole carrier. Additional bandwidth parts can be added to the carrier if needed." ] }, { "cell_type": "code", "execution_count": 2, "id": "3ec0371b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Carrier Properties:\n", " startRb: 0\n", " numRbs: 51\n", " Cell Id: 1\n", " Active Bandwidth Part: 0\n", " Bandwidth Parts: 1\n", " Bandwidth Part 0:\n", " Resource Blocks: 51 RBs starting at 0 (612 subcarriers)\n", " Subcarrier Spacing: 30 KHz\n", " CP Type: normal\n", " bandwidth: 18360000 Hz\n", " symbolsPerSlot: 14\n", " slotsPerSubFrame: 2\n", " nFFT: 1024\n", "\n" ] } ], "source": [ "carrier = Carrier(numRbs=51, spacing=30) # 51*12*30000 = 18,360,000 for 20 MHz Bandwidth\n", "carrier.print()\n", "bwp = carrier.curBwp # The only bandwidth part in the carrier" ] }, { "cell_type": "markdown", "id": "b9cb7eca", "metadata": {}, "source": [ "### PDSCH and DMRS Configuration\n", "Now let's use the ``PDSCH`` class to configure the PDSCH communication parameters. Here we choose \"Mapping Type A\" and \"QAM16\" modulation (by default). We also set the number of layers to 2 and disable interleaving by setting ``interleavingBundleSize`` to zero.\n", "\n", "We then set the DMRS configuration using the ``setDMRD`` method of the ``PDSCH`` class. Here we use DMRS ``configType`` set to 2 with 2 additional symbol time allocations (``additionalPos=2``). Although **NeoRadium** supports both wide band and narrow band precoding, we set the \"Precoding RB Group (PRG)\" size to 0 which means the same precoding is used for all subcarriers (Wideband Precoding)." ] }, { "cell_type": "code", "execution_count": 3, "id": "8477566a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "PDSCH Properties:\n", " mappingType: A\n", " nID: 1\n", " rnti: 1\n", " numLayers: 2\n", " numCodewords: 1\n", " modulation: 16QAM\n", " portSet: [0, 1]\n", " symSet: 0 1 2 3 4 5 6 7 8 9 10 11 12 13\n", " prbSet: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19\n", " 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39\n", " 40 41 42 43 44 45 46 47 48 49 50\n", " interleavingBundleSize: 0\n", " PRG Size: Wideband\n", " Bandwidth Part:\n", " Resource Blocks: 51 RBs starting at 0 (612 subcarriers)\n", " Subcarrier Spacing: 30 KHz\n", " CP Type: normal\n", " bandwidth: 18360000 Hz\n", " symbolsPerSlot: 14\n", " slotsPerSubFrame: 2\n", " nFFT: 1024\n", " DMRS:\n", " configType: 2\n", " nIDs: []\n", " scID: 0\n", " sameSeq: 1\n", " symbols: Single\n", " typeA1stPos: 2\n", " additionalPos: 2\n", " cdmGroups: [0, 0]\n", " deltaShifts: [0, 0]\n", " allCdmGroups: [0]\n", " symSet: [ 2 7 11]\n", " REs (before shift): [0, 1, 6, 7]\n", " epreRatioDb: 0 (db)\n", "\n" ] } ], "source": [ "pdsch = PDSCH(bwp, interleavingBundleSize=0, numLayers=2, nID=carrier.cellId)\n", "pdsch.setDMRS(prgSize=0, configType=2, additionalPos=2)\n", "pdsch.print()" ] }, { "cell_type": "markdown", "id": "7c436ef9", "metadata": {}, "source": [ "### Channel Coding Configuration\n", "Now it is time to define the parameters of LDPC channel coding. **NeoRadium** provides the ``LdpcEncoder`` and ``LdpcDecoder`` classes. These classes can be used to perform data segmentation/de-segmentation, rate-matching/rate-recovery, and encoding/decoding. Here we first set our desired code rate, then create an ``LdpcEncoder`` object that will be used for LDPC encoding." ] }, { "cell_type": "code", "execution_count": 4, "id": "8f27e815", "metadata": {}, "outputs": [], "source": [ "codeRate = 490/1024\n", "ldpcEncoder = LdpcEncoder(baseGraphNo=1, modulation=pdsch.modems[0].modulation, \n", " txLayers=pdsch.numLayers, targetRate=codeRate)\n" ] }, { "cell_type": "markdown", "id": "3ea43f7f", "metadata": {}, "source": [ "### Resource Grid Creation and Mapping\n", "Now that we have created the PDSCH and LDPC encoder objects, we can use them to perform channel coding and resource mapping. In the following cell, we first create a resource grid object using the ``getGrid`` method of the ``PDSCH`` object. This will create a resource grid, creates the DMRS reference signals and puts them in the specified locations in the resource grid.\n", "\n", "The method ``getTxBlockSize`` is used to calculate the transport block size(s) based on the resources available in the grid for data transmission at the specified code rate. This function calculates the the transport block size based on 3GPP TS 38.214.\n", "\n", "Once we have the transport block size, we create random bits for transmission using the utility function ``bits`` of **NeoRadium**'s ``random`` module.\n", "\n", "The function ``getBitSizes`` returns total bit capacity of the resource grid for each code word. This is the total number of encoded data bits that can be transmitted using the resource grid. It is used to perform rate-matching by the ``LdpcEncoder`` class.\n", "\n", "The function ``getRateMatchedCodeWords`` performs segmentation, rate-matching, and LDPC encoding in a single call. The output contains the encoded bits for each codeword.\n", "\n", "Once we have the rated-matched encoded bits, we can map them to the available resources in the resource grid. This is exactly what the ``populateGrid`` method of the ``PDSCH`` class does. This function first converts bits to complex symbols using the QAM modulation and then assigns the symbols to the available resource elements in the resource grid at different time, frequency, and layer locations.\n", "\n", "The ``drawMap`` method of the ``Grid`` class is used here to show the allocation of data and DMRS in the resource grid." ] }, { "cell_type": "code", "execution_count": 5, "id": "a1312e0b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "TX Block Size: 30216\n", "Number of bits: 63648\n", "Size of the rate-matched coded block: 63648\n", "Grid Allocation Stats:\n", " GridSize: 17136\n", " DMRS: 1224\n", " PDSCH: 15912\n" ] }, { "data": { "image/png": "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DBERESkaywwREREpGssMERERKRrLDBERESmaSjylp9LV6XTQaDRyxyAiIlIsrVYLV1dXuWNwzwwREREpG8sMERERKZpiLzRpScNjU+SOYGBRlKf0f2YrHmvNBTBbaVlrNmvNBTBbaVlrNmvNBRhmswbcM0NERESKxjJDREREisYyQ0RERIrGMkNERESKxjJDREREisYyQ0RERIrGMkNERESKxjJDREREisYyQ0RERIqm2DLz66+/okePHvDz84NKpcKWLVvkjkREREQyUGyZycjIQEhICGJjY+WOQkRERDJS7LWZIiIiEBERIXcMIiIikpliy0xJZWVlISsrS/pZp9PJmIaIiIgsRbEfM5VUTEwMNBqNNPj7+8sdiYiIiCzgqSkz0dHR0Gq10pCUlCR3JCIiIrKAp+ZjJrVaDbVaLXcMIiIisrCnZs8MERERPZkUu2cmPT0dly9fln5OSEhAfHw8PDw8EBAQIGMyIiIiKk+KLTMnTpxA+/btpZ/HjBkDAIiMjMTSpUtlSkVERETlTbFlJjw8HEIIuWMQERGRzHjMDBERESkaywwREREpGssMERERKRrLDBERESkaywwREREpGssMERERKRrLDBERESkaywwREREpGssMERERKZpKPKWn0dXpdNBoNHLHICIiUiytVgtXV1e5Y3DPDBERESkbywwREREpmmIvNGlJw2NT5I5gYFGUp/R/Zisea80FMFtpWWs2a80FMFtpWWs2a80FGGazBtwzQ0RERIrGMkNERESKxjJDREREisYyQ0RERIrGMkNERESKxjJDREREisYyQ0RERIrGMkNERESKxjJDREREiqboMhMbG4tq1arBwcEBLVq0wPHjx+WOREREROVMsWVm7dq1GDNmDCZOnIhTp04hJCQEXbp0wZ07d+SORkREROVIsWVm9uzZeP311zF06FDUq1cPCxYsQMWKFfH999/LHY2IiIjKkSLLTHZ2Nk6ePImOHTtK4ypUqICOHTviyJEjRpfJysqCTqczGIiIiEj5FFlmkpOTkZubCx8fH4PxPj4+uHXrltFlYmJioNFopMHf3788ohIREVEZU2SZKY3o6GhotVppSEpKkjsSERERWYCt3AFKw8vLCzY2Nrh9+7bB+Nu3b8PX19foMmq1Gmq1ujziERERUTlS5J4Ze3t7NG3aFHv37pXG5eXlYe/evWjZ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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "random.setSeed(123) # Making the results reproducible.\n", "grid = pdsch.getGrid() # Create a resource grid already populated with DMRS \n", "txBlockSize = pdsch.getTxBlockSize(codeRate) # Calculate the Transport Block Size based on 3GPP TS 38.214 \n", "txBlock = random.bits(txBlockSize[0]) # Create random binary data\n", " \n", "numBits = pdsch.getBitSizes(grid) # Actual number of bits available in the resource grid\n", "\n", "# Now perform the segmentation, rate-matching, and encoding in one call:\n", "rateMatchedCodeWords = ldpcEncoder.getRateMatchedCodeWords(txBlock, numBits[0])\n", "\n", "# Now populate the resource grid with coded data. This includes QAM modulation and resource mapping.\n", "pdsch.populateGrid(grid, rateMatchedCodeWords)\n", "\n", "# Store the indexes of the PDSCH data in pdschIndexes to be used later.\n", "pdschIndexes = pdsch.getReIndexes(grid, \"PDSCH\") \n", "\n", "print(\"TX Block Size:\", txBlockSize[0])\n", "print(\"Number of bits:\", numBits[0])\n", "print(\"Size of the rate-matched coded block:\", rateMatchedCodeWords.shape[0])\n", "\n", "# Get statistics about the grid resource allocation\n", "gridStats = grid.getStats()\n", "print(\"Grid Allocation Stats:\")\n", "for key, value in gridStats.items():\n", " print(\" %s: %d\"%(key, value))\n", " \n", "# Draw the map of grid showing the data and DMRS in the first RB of the BWP.\n", "grid.drawMap()" ] }, { "cell_type": "markdown", "id": "f16642aa", "metadata": {}, "source": [ "### Channel Simulation and Precoding\n", "The next step in the pipeline is the precoding process. In practice the precoding information is extracted at the UE side (The receiver in this case) from the estimated channel. Since we are demonstrating only a downlink communication here, we assume that the precoding matrix is somehow available. \n", "\n", "To calculate the precoding matrix we first need to define the channel model. Here we use a CDL channel model as specified in 3GPP TR 38.901. The ``CdlChannel`` class is used to create a channel model object. Here we are using a CDL-C model which simulates NLOS communication. We use delay spread of 300 ns and a doppler shift of 5 Hz. We also use a small MIMO configuration with 8 transmitter antenna and 2 receiver antenna. The ``print`` function of the ``CdlChannel`` class can be used to show all properties of the CDL channel model.\n", "\n", "To calculate the precoding matrix, we first get the channel matrix from the ``CdlChannel`` object. The channel matrix is a 4-D complex tensor of size ``l``x``k``x ``Nr``x``Nt``. Where ``l``, ``k``, ``Nr``, and ``Nt`` are the number of time symbols (14), the number of subcarriers (51x12=612), the number of receiver antenna (2), and the number of transmitter antenna (8) respectively. \n", "\n", "Once we have the channel matrix, the ``getPrecodingMatrix`` method of the ``PDSCH`` class is called to calculate and return the precoding matrix.\n", "\n", "To precode the resource grid, we simply call the ``precode`` method of the ``Grid`` class passing in the precoding matrix." ] }, { "cell_type": "code", "execution_count": 6, "id": "3585a483", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Shape of Channel Matrix: (14, 612, 2, 8)\n", "Shape of Resource Grid: (2, 14, 612)\n", "Shape of Precoded Resource Grid: (8, 14, 612)\n" ] } ], "source": [ "# Creating a CdlChannel object:\n", "f0 = 4e9 # Carrier Frequency\n", "channel = CdlChannel('C', delaySpread=300, carrierFreq=f0, dopplerShift=5,\n", " txAntenna = AntennaPanel([2,4], polarization=\"|\"), # 8 TX antenna\n", " rxAntenna = AntennaPanel([1,2], polarization=\"|\")) # 2 RX antenna\n", "# channel.print() # Unremark to pring all channel information\n", "\n", "# Getting the Precoding Matrix, and precoding the resource grid\n", "channelMatrix = channel.getChannelMatrix(bwp) # Get the channel matrix\n", "precoder = pdsch.getPrecodingMatrix(channelMatrix) # Get the precoder matrix from the PDSCH object\n", "precodedGrid = grid.precode(precoder) # Perform the precoding\n", "\n", "print(\"Shape of Channel Matrix: \", channelMatrix.shape)\n", "print(\"Shape of Resource Grid: \", grid.shape)\n", "print(\"Shape of Precoded Resource Grid:\", precodedGrid.shape)\n" ] }, { "cell_type": "markdown", "id": "84be1cdf", "metadata": {}, "source": [ "### OFDM Modulation\n", "To transmit the precoded resource grid it first needs to be transformed to time-domain. The method ``ofdmModulate`` of the ``Grid`` class converts the precoded grid into a set of time-domain waveforms for each transmit antenna. The output of the OFDM modulation is a ``Waveform`` class that contains the waveforms for each transmit antenna." ] }, { "cell_type": "code", "execution_count": 7, "id": "e318ba1c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Shape of txWaveform: (8, 15360)\n" ] } ], "source": [ "txWaveform = precodedGrid.ofdmModulate()\n", "# txWaveform contains the waveforms in time domain for each transmitter antenna\n", "print(\"Shape of txWaveform:\", txWaveform.shape)" ] }, { "cell_type": "markdown", "id": "3ba7b40d", "metadata": {}, "source": [ "### Applying the channel model to the transmitted waveform\n", "Now we need to pass the transmitted waveform through our CDL channel model. Since channel models usually delay the signals, to make sure we get the whole waveform on the output end of the channel, we need to append some zeros to the end of the waveform. The number of these zeros depend on the maximum channel delay that can be obtained using the ``getMaxDelay`` method of the ``CdlChannel`` class. The ``pad`` method of ``Waveform`` class append zeros to the end of waveform.\n", "\n", "Now the channel model can be applied to the transmitted waveform to obtain the received waveform. This is exactly what the ``applyToSignal`` method of the ``CdlChannel`` class does. The output is another ``Waveform`` object containing the received waveform for each receiver antenna." ] }, { "cell_type": "code", "execution_count": 8, "id": "b2d70906", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Max Channel Delay: 87\n", "Shape of rxWaveform: (2, 15447)\n" ] } ], "source": [ "# Need to append zeros to the end of signal based on the maximum channel delay\n", "maxDelay = channel.getMaxDelay()\n", "print(\"Max Channel Delay: \", maxDelay)\n", "txWaveform = txWaveform.pad(maxDelay)\n", "\n", "# Applying channel to the transmitted signal\n", "rxWaveform = channel.applyToSignal(txWaveform)\n", "print(\"Shape of rxWaveform:\", rxWaveform.shape)" ] }, { "cell_type": "markdown", "id": "18d68734", "metadata": {}, "source": [ "### Adding Noise\n", "We can use the ``addNoise`` method of the ``Waveform`` class to add AWGN to the received signal. Since we are adding the noise in the time-domain, the FFT size is also used in the calculation noise variance. So, we are passing both SNR value and the FFT size to the ``addNoise`` method." ] }, { "cell_type": "code", "execution_count": 9, "id": "d1a73e4d", "metadata": {}, "outputs": [], "source": [ "noisyRxWaveform = rxWaveform.addNoise(snrDb=30, nFFT=bwp.nFFT)\n" ] }, { "cell_type": "markdown", "id": "e17e0bfc", "metadata": {}, "source": [ "### Synchronization and OFDM Demodulation\n", "Because of multiple different path delays involved in the channel model, we need to find the best starting point in the received waveform. This is usually done by finding the position of maximum correlation between the received signal and reference signals. The function ``getTimingOffset`` of the ``CdlChannel`` provides this \"timing offset\" which is the number of samples we need to skip from the beginning of the received waveform.\n", "\n", "After synchronization, the waveform can be used for OFDM demodulation to the received resource grid. The class method ``ofdmDemodulate`` creates a ``Grid`` class instance containing the received resource grid." ] }, { "cell_type": "code", "execution_count": 10, "id": "422aa996", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Timing Offset: 13\n", "Shape of received resource grid: (2, 14, 612)\n" ] } ], "source": [ "# Synchronization: Get the timing offset\n", "offset = channel.getTimingOffset()\n", "syncedWaveform = noisyRxWaveform.sync(offset)\n", "print(\"Timing Offset: \", offset)\n", "\n", "# OFDM Demodulation\n", "rxGrid = syncedWaveform.ofdmDemodulate(bwp)\n", "print(\"Shape of received resource grid:\", rxGrid.shape)" ] }, { "cell_type": "markdown", "id": "cd234418", "metadata": {}, "source": [ "### Applying channel in Frequency Domain\n", "You can also use a shortcut method of applying the channel model to the transmitted grid directly without going to time domain. Although this is not exactly what happens in practice, it is sometimes useful (or convenient) to avid the time domain.\n", "\n", "The method ``applyChannel`` of the ``Grid`` class can be used to apply a channel matrix to a resource grid. For example:\n", "\n", "```\n", " # Applying the channel matrix to the precoded grid\n", " rxGrid = precodedGrid.applyChannel(channelMatrix)\n", "```\n", "\n", "### Channel Estimation\n", "The input to the channel estimation process is the received resource grid and the known reference signals (in this case DMRS) and the output is the estimated channel. Note that in this case the estimated channel includes the precoding process. That is because the DMRS reference signals were added to the resource grid **before** precoding.\n", "\n", "#### Perfect Channel Estimation\n", "For perfect channel estimation, we need to apply precoding to the channel matrix. This can be done by a simple matrix multiplication as shown in the following cell.\n", "\n", "#### Practical Channel Estimation\n", "The method ``estimateChannelLS`` of the ``Grid`` class can be used to estimate the channel matrix. This method performs CDM averaging, Least-Squared channel estimation at pilot locations, and applying interpolation to get the whole channel matrix. \n" ] }, { "cell_type": "code", "execution_count": 11, "id": "220df01a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Shape of estimated channel: (14, 612, 2, 2)\n" ] } ], "source": [ "# Perfect channel estimation (Applying the precoding to the channel matrix)\n", "# estChannelMatrix = channelMatrix @ precoder[None,...]\n", "\n", "# Practical channel estimation\n", "estChannelMatrix, _ = rxGrid.estimateChannelLS(pdsch.dmrs) # Un-remark to use practical channel estimation\n", "print(\"Shape of estimated channel:\", estChannelMatrix.shape)\n" ] }, { "cell_type": "markdown", "id": "d7634744", "metadata": {}, "source": [ "### Equalization\n", "Now that we have an estimation of channel matrix, we can use it for equalization. The method ``equalize`` of the ``Grid`` class uses the Minimum mean-squared error (MMSE) equalization algorithm to cancel the effect of the channel in the received resource grid.\n", "\n", "It outputs a ``Grid`` object containing the equalized received resource grid and the LLR scaling values which is used later to scale the log-likelihood ratios from the demodulation process.\n" ] }, { "cell_type": "code", "execution_count": 12, "id": "6f4600d1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Shape of equalized grid: (2, 14, 612)\n", "Shape of LLR Scales: (2, 14, 612)\n" ] } ], "source": [ "eqGrid, llrScales = rxGrid.equalize(estChannelMatrix)\n", "print(\"Shape of equalized grid:\", eqGrid.shape)\n", "print(\"Shape of LLR Scales: \", llrScales.shape)" ] }, { "cell_type": "markdown", "id": "88a80e54", "metadata": {}, "source": [ "### Demodulation, De-mapping, Descrambling\n", "The method ``getLLRsFromGrid`` of the ``PDSCH`` object performs demodulation, de-mapping, and descrambling of the received grid in one call. The output of this process is a set of log-likelihood ratios (LLRs) for the each received codeword extracted from the received resource grid. Note that in this example there is only one codeword." ] }, { "cell_type": "code", "execution_count": 13, "id": "0b7e7530", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Length of LLRs for the first code-word: 63648\n" ] } ], "source": [ "llrs = pdsch.getLLRsFromGrid(eqGrid, pdschIndexes, llrScales)\n", "print(\"Length of LLRs for the first code-word:\", llrs[0].shape[0])" ] }, { "cell_type": "markdown", "id": "30b4299a", "metadata": {}, "source": [ "### Rate Recovery and LDPC decoding\n", "We are close to the end of pipeline. We just need to decode the LLRs to the actual transport blocks. To do this, we need an LDPC decoder object. The method ``getDecoder`` of the ``LdpcEncoder`` class conveniently creates a decoder object matching the parameters of the encoder.\n", "\n", "The function ``recoverRate`` of the LDPC decoder object can then be used to recover the rate of LDPC codewords based on the transport block size. This is based on the procedure specified in 3GPP TS 38.212.\n", "\n", "The rate-recovered coded blocks are then ready to be LDPC-decoded using the ``decode`` method of the ``LdpcDecoder`` class (Each coded block is decoded separately). The output is a set of the decoded blocks.\n" ] }, { "cell_type": "code", "execution_count": 14, "id": "392219a7", "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Shape of rxCodedBlocks: (4, 23232)\n", "Shape of Decoded Blocks: (4, 7744)\n" ] } ], "source": [ "# Create an LDPC Decoder object using the same parameters used to create the encoder. \n", "ldpcDecoder = ldpcEncoder.getDecoder()\n", "\n", "# Do rate recovery (Note: We have only one codeword in this example)\n", "rxCodedBlocks = ldpcDecoder.recoverRate(llrs[0], txBlockSize[0])\n", "print(\"Shape of rxCodedBlocks: \", rxCodedBlocks.shape)\n", "\n", "# LDPC-Decoding\n", "decodedBlocks = ldpcDecoder.decode(rxCodedBlocks, numIter=20)\n", "print(\"Shape of Decoded Blocks:\", decodedBlocks.shape)\n", "\n" ] }, { "cell_type": "markdown", "id": "56d5bece", "metadata": {}, "source": [ "### CRC checking and De-segmentation\n", "The CRC of each decoded block must be checked and then the decoded blocks need to be merged (de-segmented) to provide the transport blocks. The method ``checkCrcAndMerge`` of the ``LdpcDecoder`` class does exactly this. The output of this method is a decoded transport block wich also contains a transport block CRC appended at the end.\n", "\n", "The ``checkCrc`` method of the ``LdpcDecoder`` class can be used to check the transport block CRC." ] }, { "cell_type": "code", "execution_count": 15, "id": "6d11d60f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Shape of decoded transport block: (30240,)\n", "CRC Match for each decoded block: [ True True True True]\n", "Transport block CRC Match: True\n" ] } ], "source": [ "decodedTxBlockWithCRC, crcMatch = ldpcDecoder.checkCrcAndMerge(decodedBlocks)\n", "print(\"Shape of decoded transport block:\", decodedTxBlockWithCRC.shape)\n", "print(\"CRC Match for each decoded block:\", crcMatch)\n", "\n", "txBlockCrcMatch = ldpcDecoder.checkCrc(decodedTxBlockWithCRC,'24A')\n", "print(\"Transport block CRC Match: \", txBlockCrcMatch)\n" ] }, { "cell_type": "markdown", "id": "5aae1774", "metadata": {}, "source": [ "### Comparing the decoded transport block with the original\n", "We can get the decoded transport block by removing the 24-bit CRC at the end. Then we can compare the results with the original transport block that was created randomly at the beginning of this pipeline." ] }, { "cell_type": "code", "execution_count": 16, "id": "e4332c86", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The number of bit mismatches between the decoded transport block and original: 0\n" ] } ], "source": [ "decodedTxBlock = decodedTxBlockWithCRC[:-24] # remove the transport block CRC\n", "print(\"The number of bit mismatches between the decoded transport block and original:\",np.abs(decodedTxBlock-txBlock).sum())\n" ] }, { "cell_type": "code", "execution_count": null, "id": "78d4d52a", "metadata": { "scrolled": true }, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "a783fd19", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.6" } }, "nbformat": 4, "nbformat_minor": 5 }