{ "cells": [ { "cell_type": "markdown", "id": "c2c61148-3850-492a-876f-ac5d39cd0d8a", "metadata": {}, "source": [ "# Downlink CSI Feedback Using Multiple Directional Multi-Port CSI-RS Resources\n", "\n", "This example demonstrates a codebook-based downlink CSI feedback procedure in which the gNB transmits multiple **directional multi-port CSI-RS resources**. Each CSI-RS resource uses the same multi-port transmission structure but applies a different steering vector, allowing the UE to evaluate several beam directions in a single CSI reporting procedure.\n", "\n", "For each directional CSI-RS resource, the UE estimates the effective MIMO channel and evaluates the corresponding transmission performance. It then reports:\n", "\n", "- **CSI-RS Resource Indicator (CRI):** identifies the preferred directional CSI-RS resource (beam);\n", "- **Rank Indicator (RI):** the recommended number of transmission layers for the selected beam;\n", "- **Precoding Matrix Indicator (PMI):** the preferred Type-I single-panel codebook precoder for the selected beam; and\n", "- **Channel Quality Indicator (CQI):** the recommended modulation and code-rate operating point.\n", "\n", "After receiving the CSI report, the gNB configures the PDSCH using the reported RI and CQI. The transmitted PDSCH uses a **composite precoder** formed by combining:\n", "\n", "1. the steering vector associated with the selected CRI; and\n", "2. the Type-I codebook precoder indicated by the reported PMI.\n", "\n", "The same steering transformation applied to the selected multi-port CSI-RS is also applied to the PDSCH. Consequently, the effective channel observed by the UE during CSI measurement matches the effective channel experienced by the precoded data transmission.\n", "\n", "The simulation includes:\n", "\n", "1. periodic transmission of multiple directional multi-port CSI-RS resources;\n", "2. UE-side CRI, RI, PMI, and CQI calculation;\n", "3. gNB-side PDSCH reconfiguration based on the reported CSI;\n", "4. formation of a composite precoder by combining the selected steering vector and PMI precoder;\n", "5. PDSCH transmission using the composite precoder; and\n", "6. receiver equalization, LDPC decoding, and transport-block CRC verification." ] }, { "cell_type": "code", "execution_count": 1, "id": "e8e65ac4-d9e4-40a6-8c40-f8021d0f2034", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "from neoradium import BandwidthPart, PDSCH, AntennaPanel, CdlChannel, random\n", "from neoradium import CsiRsConfig, CsiRsSet, CsiRs, CsiReport, CsiReportMan\n", "from neoradium.utils import toLinear" ] }, { "cell_type": "code", "execution_count": 2, "id": "76f7044a-ea81-4b87-908c-74d515ec2e9d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Slot 0: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 1: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 2: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 3: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 7: Received CRI/RI/PMI/CQI (ReportID: 11)\n", " CRI: 7, best beam: 𝛳=90.00°, 𝝋=38.21°\n", " RI: 2 (Score:2.385)\n", " WB PMI: (I1:[9, 3, 0], I2:0)\n", " WB precoder shape: (16, 2)\n", " CQI: 4\n", " Modulation: QPSK\n", " Coderate: 308/1024\n", " CQI BLER: 1.35 %\n", "Slot 7: Starting PDSCH (Mod:QPSK, Coderate:308/1024)\n", "Slot 7: TxBlock CRC Match: True\n", "Slot 8: TxBlock CRC Match: True\n", "Slot 9: TxBlock CRC Match: True\n", "Slot 10: TxBlock CRC Match: True\n", "Slot 11: TxBlock CRC Match: True\n", "Slot 12: TxBlock CRC Match: True\n", "Slot 13: TxBlock CRC Match: True\n", "Slot 14: TxBlock CRC Match: True\n", "Slot 15: TxBlock CRC Match: True\n", "Slot 16: TxBlock CRC Match: True\n", "Slot 17: TxBlock CRC Match: True\n", "Slot 18: TxBlock CRC Match: True\n", "Slot 19: TxBlock CRC Match: True\n", "Slot 20: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 20: TxBlock CRC Match: True\n", "Slot 21: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 21: TxBlock CRC Match: True\n", "Slot 22: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 22: TxBlock CRC Match: True\n", "Slot 23: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 23: TxBlock CRC Match: True\n", "Slot 24: TxBlock CRC Match: True\n", "Slot 25: TxBlock CRC Match: True\n", "Slot 26: TxBlock CRC Match: True\n", "Slot 27: Received CRI/RI/PMI/CQI (ReportID: 11)\n", " CRI: 7, best beam: 𝛳=90.00°, 𝝋=38.21°\n", " RI: 2 (Score:2.302)\n", " WB PMI: (I1:[9, 2, 0], I2:0)\n", " WB precoder shape: (16, 2)\n", " CQI: 4\n", " Modulation: QPSK\n", " Coderate: 308/1024\n", " CQI BLER: 1.35 %\n", "Slot 27: TxBlock CRC Match: True\n", "Slot 28: TxBlock CRC Match: True\n", "Slot 29: TxBlock CRC Match: True\n", "Slot 30: TxBlock CRC Match: True\n", "Slot 31: TxBlock CRC Match: True\n", "Slot 32: TxBlock CRC Match: True\n", "Slot 33: TxBlock CRC Match: True\n", "Slot 34: TxBlock CRC Match: True\n", "Slot 35: TxBlock CRC Match: True\n", "Slot 36: TxBlock CRC Match: True\n", "Slot 37: TxBlock CRC Match: True\n", "Slot 38: TxBlock CRC Match: True\n", "Slot 39: TxBlock CRC Match: True\n", "Slot 40: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 40: TxBlock CRC Match: True\n", "Slot 41: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 41: TxBlock CRC Match: True\n", "Slot 42: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 42: TxBlock CRC Match: True\n", "Slot 43: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 43: TxBlock CRC Match: True\n", "Slot 44: TxBlock CRC Match: True\n", "Slot 45: TxBlock CRC Match: True\n", "Slot 46: TxBlock CRC Match: True\n", "Slot 47: Received CRI/RI/PMI/CQI (ReportID: 11)\n", " CRI: 3, best beam: 𝛳=90.00°, 𝝋=-21.79°\n", " RI: 2 (Score:2.333)\n", " WB PMI: (I1:[9, 7, 0], I2:0)\n", " WB precoder shape: (16, 2)\n", " CQI: 4\n", " Modulation: QPSK\n", " Coderate: 308/1024\n", " CQI BLER: 1.35 %\n", "Slot 47: TxBlock CRC Match: True\n", "Slot 48: TxBlock CRC Match: True\n", "Slot 49: TxBlock CRC Match: True\n", "Slot 50: TxBlock CRC Match: True\n", "Slot 51: TxBlock CRC Match: True\n", "Slot 52: TxBlock CRC Match: True\n", "Slot 53: TxBlock CRC Match: True\n", "Slot 54: TxBlock CRC Match: True\n", "Slot 55: TxBlock CRC Match: True\n", "Slot 56: TxBlock CRC Match: True\n", "Slot 57: TxBlock CRC Match: True\n", "Slot 58: TxBlock CRC Match: True\n", "Slot 59: TxBlock CRC Match: True\n", "Slot 60: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 60: TxBlock CRC Match: True\n", "Slot 61: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 61: TxBlock CRC Match: True\n", "Slot 62: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 62: TxBlock CRC Match: True\n", "Slot 63: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 63: TxBlock CRC Match: True\n", "Slot 64: TxBlock CRC Match: True\n", "Slot 65: TxBlock CRC Match: True\n", "Slot 66: TxBlock CRC Match: True\n", "Slot 67: Received CRI/RI/PMI/CQI (ReportID: 11)\n", " CRI: 7, best beam: 𝛳=90.00°, 𝝋=38.21°\n", " RI: 2 (Score:2.417)\n", " WB PMI: (I1:[9, 3, 0], I2:0)\n", " WB precoder shape: (16, 2)\n", " CQI: 4\n", " Modulation: QPSK\n", " Coderate: 308/1024\n", " CQI BLER: 1.35 %\n", "Slot 67: TxBlock CRC Match: True\n", "Slot 68: TxBlock CRC Match: True\n", "Slot 69: TxBlock CRC Match: True\n", "Slot 70: TxBlock CRC Match: True\n", "Slot 71: TxBlock CRC Match: True\n", "Slot 72: TxBlock CRC Match: True\n", "Slot 73: TxBlock CRC Match: True\n", "Slot 74: TxBlock CRC Match: True\n", "Slot 75: TxBlock CRC Match: True\n", "Slot 76: TxBlock CRC Match: True\n", "Slot 77: TxBlock CRC Match: True\n", "Slot 78: TxBlock CRC Match: True\n", "Slot 79: TxBlock CRC Match: True\n", "Slot 80: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 80: TxBlock CRC Match: True\n", "Slot 81: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 81: TxBlock CRC Match: True\n", "Slot 82: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 82: TxBlock CRC Match: True\n", "Slot 83: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)\n", "Slot 83: TxBlock CRC Match: True\n", "Slot 84: TxBlock CRC Match: True\n", "Slot 85: TxBlock CRC Match: True\n", "Slot 86: TxBlock CRC Match: True\n", "Slot 87: Received CRI/RI/PMI/CQI (ReportID: 11)\n", " CRI: 3, best beam: 𝛳=90.00°, 𝝋=-21.79°\n", " RI: 2 (Score:2.354)\n", " WB PMI: (I1:[9, 7, 0], I2:1)\n", " WB precoder shape: (16, 2)\n", " CQI: 4\n", " Modulation: QPSK\n", " Coderate: 308/1024\n", " CQI BLER: 1.35 %\n", "Slot 87: TxBlock CRC Match: True\n", "Slot 88: TxBlock CRC Match: True\n", "Slot 89: TxBlock CRC Match: True\n", "Slot 90: TxBlock CRC Match: True\n", "Slot 91: TxBlock CRC Match: True\n", "Slot 92: TxBlock CRC Match: True\n", "Slot 93: TxBlock CRC Match: True\n", "Slot 94: TxBlock CRC Match: True\n", "Slot 95: TxBlock CRC Match: True\n", "Slot 96: TxBlock CRC Match: True\n", "Slot 97: TxBlock CRC Match: True\n", "Slot 98: TxBlock CRC Match: True\n", "Slot 99: TxBlock CRC Match: True\n", "0 of 100 slots failed.\n" ] } ], "source": [ "numSlots = 100 # Number of slots in the communication loop\n", "snrDb = -10 # SNR in dB\n", "random.setSeed(1234) # Make results reproducible\n", "prgSize = 0 # Set to 0 for wideband, 2 or 4 for subband precoding\n", "\n", "# Create a bandwidth part with 24 resource blocks and 15 kHz subcarrier spacing\n", "bwp = BandwidthPart(numRbs=24, spacing=15) \n", "\n", "# Create a CDL channel model\n", "channel = CdlChannel(bwp, profile='C', delaySpread=30, carrierFreq=4e9, dopplerShift=5,\n", " txAntenna=AntennaPanel([2,4], polarization='x'), # 16 TX antennas\n", " rxAntenna=AntennaPanel([1,2], polarization='x'), # 4 RX antennas\n", " rxOrientation = [180,0,0])\n", "\n", "# Multiple multi-port CSI-RS (For 16 ports)\n", "csiRsList = [ CsiRs(resourceId=1, symbols=[4], numPorts=channel.txAntenna.numPorts, freqMap=\"001111\", cdmSize=4),\n", " CsiRs(resourceId=2, symbols=[8], numPorts=channel.txAntenna.numPorts, freqMap=\"001111\", cdmSize=4),\n", " CsiRs(resourceId=3, symbols=[4], numPorts=channel.txAntenna.numPorts, freqMap=\"001111\", cdmSize=4, offset=1),\n", " CsiRs(resourceId=4, symbols=[8], numPorts=channel.txAntenna.numPorts, freqMap=\"001111\", cdmSize=4, offset=1),\n", " CsiRs(resourceId=5, symbols=[4], numPorts=channel.txAntenna.numPorts, freqMap=\"001111\", cdmSize=4, offset=2),\n", " CsiRs(resourceId=6, symbols=[8], numPorts=channel.txAntenna.numPorts, freqMap=\"001111\", cdmSize=4, offset=2),\n", " CsiRs(resourceId=7, symbols=[4], numPorts=channel.txAntenna.numPorts, freqMap=\"001111\", cdmSize=4, offset=3),\n", " CsiRs(resourceId=8, symbols=[8], numPorts=channel.txAntenna.numPorts, freqMap=\"001111\", cdmSize=4, offset=3) ]\n", "\n", "csiRsSet = CsiRsSet(\"NZP\", bwp, resourceType=\"periodic\", rsId=1, period=20*(bwp.u+1), csiRsList=csiRsList)\n", "csiRsConfig = CsiRsConfig([csiRsSet])\n", "# csiRsConfig.print() # Uncomment to print CSI-RS configuration details\n", "\n", "csiRep = CsiReport(csiRsSet, reportId=csiRsSet.rsId+10, quantity=\"CriRiPmiCqi\", reportType=\"periodic\",\n", " period=20*(bwp.u+1), offset=5, prgSize=prgSize, allowedRanks=[1,2],\n", " txAntenna=channel.txAntenna, rxAntenna=channel.rxAntenna)\n", "csiReportMan = CsiReportMan([csiRep])\n", "# csiReportMan.print() # Uncomment to print CSI report configuration details\n", "\n", "numPhi = len(csiRsSet) # Number of beams to sweep horizontally\n", "numTheta = 1 # Number of beams to sweep vertically (Restrict sweeping to azimuth)\n", "ws, beams = channel.txAntenna.getSweepingBeams(numTheta, numPhi)\n", "\n", "# The PDSCH object is created once we have the first CRI/RI/PMI/CQI feedback and \n", "# recreated later if the reported RI or CQI changes.\n", "pdsch = None\n", "precoder = None\n", "pdschW = None\n", "failedSlots = 0\n", "for slotNo in range(numSlots):\n", " channelMatrix = channel.getChannelMatrix()\n", "\n", " # Retrieve CSI feedback generated from previous CSI-RS occasions\n", " csiReportInfo = csiReportMan.getFeedback() # Get all available CSI reports from CsiReport objects\n", " for reportId, csiFeedback in csiReportInfo.items(): # Get the CSI feedback for each report\n", " if reportId == csiRep.reportId:\n", " cri = csiFeedback.cri.cri # CSI-RS resource ID of the best beam\n", " print(f\"Slot {slotNo}: Received CRI/RI/PMI/CQI (ReportID: {reportId})\")\n", " print(f\" CRI: {cri}, best beam: 𝛳={beams[0][cri-1]:.2f}°, 𝝋={beams[1][cri-1]:.2f}°\")\n", " print(f\" RI: {csiFeedback.ri.ri} (Score:{csiFeedback.ri.score:.3f})\")\n", " print(f\" WB PMI: {csiFeedback.pmi.wbPMI}\")\n", " print(f\" WB precoder shape: {csiFeedback.pmi.wbW.shape}\")\n", " if csiFeedback.pmi.sbWs is not None:\n", " print(f\" {len(csiFeedback.pmi.sbWs)} SB precoders: \")\n", " for i, (rbIdx, w) in enumerate(csiFeedback.pmi.sbWs): \n", " print(f\" RBs: {str(rbIdx):<20} precoder shape: {str(w.shape):<10} PMI: {csiFeedback.pmi.sbPMIs[i]}\") \n", " if \"cqi\" in csiRep.quantity.lower():\n", " print(f\" CQI: {csiFeedback.cqi.cqi}\")\n", " modulation, coderateX1024 = csiRep.getModRate(csiFeedback.cqi.cqi)\n", " print(f\" Modulation: {modulation}\")\n", " print(f\" Coderate: {coderateX1024}/1024\")\n", " print(f\" CQI BLER: {csiFeedback.cqi.bler:.2f} %\")\n", "\n", " precoder = csiFeedback.pmi.wbW if csiFeedback.pmi.sbWs is None else csiFeedback.pmi.sbWs \n", " pdschW = ws[:,cri-1:cri] # steering vector for PDSCH\n", " if pdsch is None: \n", " # First RI/PMI/CQI feedback -> create PDSCH and LDPC codec objects\n", " print(f\"Slot {slotNo}: Starting PDSCH (Mod:{modulation}, \"\n", " f\"Coderate:{coderateX1024}/1024)\")\n", " pdsch = PDSCH(bwp, numLayers=csiFeedback.ri.ri, csiRsConfig=csiRsConfig, \n", " modulation=modulation, prgSize=csiRep.prgSize)\n", " pdsch.setDMRS(additionalPos=2)\n", " ldpc = pdsch.getLdpcCodec(coderates = coderateX1024/1024)\n", " \n", " elif ( (pdsch.modems[0].modulation != modulation) or \n", " (pdsch.numLayers != csiFeedback.ri.ri) ):\n", " # Modulation or number of layers changed -> Recreate PDSCH and LDPC codec objects\n", " print(f\"Slot {slotNo}: CQI/RI changed -> Mod:{modulation}, \"\n", " f\"Coderate:{coderateX1024}/1024\")\n", " pdsch = PDSCH(bwp, numLayers=csiFeedback.ri.ri, csiRsConfig=csiRsConfig, \n", " modulation=modulation, prgSize=csiRep.prgSize)\n", " pdsch.setDMRS(additionalPos=2)\n", " ldpc = pdsch.getLdpcCodec(coderates = coderateX1024/1024)\n", " \n", " elif ldpc.coderates[0] != (coderateX1024/1024):\n", " # Coderate changed -> Recreate the LDPC codec object only\n", " print(f\"Slot {slotNo}: CQI changed -> Coderate:{coderateX1024}/1024\")\n", " ldpc = pdsch.getLdpcCodec(coderates = coderateX1024/1024)\n", " else:\n", " print(f\"Unknown report: {reportId}\")\n", "\n", " # Create a transmitted resource grid.\n", " txGrid = bwp.createGrid(channel.txAntenna.numEl)\n", " if pdsch is not None:\n", " # Create random data, LDPC encode it, and put it in the PDSCH's internal resource grid.\n", " # Then precode the PDSCH into the transmitted resource grid - txGrid.\n", " pdsch.initGrid()\n", " numBits = pdsch.getBitCapacity()[0]\n", " txBlock = random.bits(ldpc.txBlockSizes[0])\n", " rateMatchedCodeBlocks = ldpc.encode(txBlock, numBits) \n", " pdsch.setPdschData(rateMatchedCodeBlocks)\n", "\n", " # Now we are using a precoder which is a combination of precoder and pdschW\n", " pdsch.precodeTo(txGrid, precoder, pdschW)\n", " # pdsch.precodeTo(txGrid, precoder)\n", " \n", " # Map any CSI-RS resources scheduled in the current slot\n", " csiRsResources = csiRsConfig.getResources()\n", " for csiSetId, setResources in csiRsResources.items():\n", " if csiSetId == csiRsSet.rsId: # CSI-RS for RI/PMI/CQI:\n", " print(f\"Slot {slotNo}: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:{csiRsSet.rsId})\")\n", " # Get a set of beam angles and precoders (weight vectors) for each beam\n", " for resourceId, (lIdx, kIdx, reValues) in setResources.items():\n", " # Simulation note:\n", " # Scale the CSI-RS to keep its aggregate transmit power approximately\n", " # consistent with the PDSCH. This avoids unintentionally reducing the\n", " # effective PDSCH SNR in this simulation, where the noise variance is\n", " # derived from the average received signal power. This is a simulation\n", " # convenience only; it is **NOT** a 3GPP requirement or recommendation\n", " # and is not representative of how practical systems necessarily\n", " # implement CSI-RS transmission.\n", " csiRs = csiRsConfig.getById(csiSetId, resourceId)\n", " pf = np.sqrt( csiRs.numPorts/(channel.txAntenna.numEl*csiRs.cdmSize)) # Power factor\n", " b = resourceId-1 # Beam Index\n", " w = ws[:,b:b+1] # nt x 1\n", " # reValues is a nt x numCsiRsRE matrix. nt x 1 * nt x numCsiRsRE = nt x numCsiRsRE\n", " txGrid[:,lIdx, kIdx] = (w * reValues * pf, \"CSIRS_NZP\", resourceId)\n", " \n", " # Apply the channel model and add AWGN noise\n", " rxGrid = txGrid.applyChannel(channelMatrix) \n", " noisyRxGrid = rxGrid.addNoise(snrDb=snrDb) # Add noise\n", "\n", " # UE processing of the received resource grid to generate reports\n", " csiReportMan.processRxGrid(noisyRxGrid, csiRsResources)\n", "\n", " if pdsch is not None:\n", " # Receiver side processing of the PDSCH: equalization and LDPC decoding\n", " # Using the same precoder combination as in transmitter side\n", " effChannelMatrix = channel.getEffChannel(channelMatrix, precoder, pdschW)\n", " eqGrid, llrScales = pdsch.equalize(noisyRxGrid, effChannelMatrix)\n", " llrs = pdsch.getLLRs(eqGrid, llrScales)\n", " decodedTxBlocks, crcMatch = ldpc.decode(llrs)\n", " print(f\"Slot {slotNo}: TxBlock CRC Match: {crcMatch[0][0]}\")\n", " failedSlots += 1-int(crcMatch[0][0])\n", "\n", " # Go to the next channel instance for the next slot\n", " channel.goNext()\n", "print(f\"{failedSlots} of {numSlots} slots failed.\")\n" ] }, { "cell_type": "code", "execution_count": null, "id": "9006c6e9-1a1f-4d2d-8e34-fca568c2002e", "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.12.10" } }, "nbformat": 4, "nbformat_minor": 5 }