{ "cells": [ { "cell_type": "markdown", "id": "c2c61148-3850-492a-876f-ac5d39cd0d8a", "metadata": {}, "source": [ "# Downlink CSI Feedback Using a Single Multi-Port CSI-RS\n", "\n", "This example demonstrates the simplest codebook-based downlink CSI feedback procedure in NeoRadium. A gNB periodically transmits one non-zero-power, multi-port CSI reference signal (NZP CSI-RS) without applying an additional directional beam. The UE uses the received CSI-RS to estimate the downlink MIMO channel and report:\n", "\n", "- **Rank Indicator (RI):** the recommended number of transmission layers;\n", "- **Precoding Matrix Indicator (PMI):** the preferred Type-I single-panel codebook precoder; and\n", "- **Channel Quality Indicator (CQI):** the recommended modulation and code-rate operating point.\n", "\n", "The gNB uses the reported RI, PMI, and CQI to configure and precode subsequent PDSCH transmissions. Because no separate CSI-RS beam sweep or CRI selection is performed, the Type-I codebook precoder provides the complete spatial beamforming operation. This represents a fully digital precoding example and serves as a foundation for the more advanced beam-management examples that use multiple directional CSI-RS resources or hierarchical beam sweeping and probing.\n", "\n", "The simulation includes:\n", "\n", "1. a dual-polarized MIMO channel based on a clustered delay line model;\n", "2. periodic transmission of a single multi-port CSI-RS resource;\n", "3. UE-side RI, PMI, and CQI calculation;\n", "4. gNB-side PDSCH reconfiguration based on the reported CSI;\n", "5. Type-I codebook precoding of the PDSCH; and\n", "6. receiver equalization, LDPC decoding, and transport-block CRC verification.\n", "\n", "The CSI feedback is delayed in the sense that a report generated from one CSI-RS occasion is applied to subsequent PDSCH slots. The example can use either wideband PMI or subband PMI, depending on the configured precoding resource-group size. Setting `prgSize = 0` selects wideband PMI only." ] }, { "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 RI/PMI/CQI (Set ID:1)\n", "Slot 7: Received RI/PMI/CQI (ReportID: 11)\n", " RI: 1 (Score:2.226)\n", " WB PMI: (I1:[0, 0, 0], I2:2)\n", " WB precoder shape: (16, 1)\n", " 6 SB precoders: \n", " RBs: [0, 1, 2, 3] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [4, 5, 6, 7] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:1)\n", " RBs: [8, 9, 10, 11] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [12, 13, 14, 15] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [16, 17, 18, 19] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [20, 21, 22, 23] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " CQI: 7\n", " Modulation: 16QAM\n", " Coderate: 378/1024\n", " CQI BLER: 7.95 %\n", "Slot 7: Starting PDSCH (Mod:16QAM, Coderate:378/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 RI/PMI/CQI (Set ID:1)\n", "Slot 20: TxBlock CRC Match: True\n", "Slot 21: TxBlock CRC Match: True\n", "Slot 22: TxBlock CRC Match: True\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 RI/PMI/CQI (ReportID: 11)\n", " RI: 1 (Score:2.351)\n", " WB PMI: (I1:[0, 0, 0], I2:2)\n", " WB precoder shape: (16, 1)\n", " 6 SB precoders: \n", " RBs: [0, 1, 2, 3] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [4, 5, 6, 7] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [8, 9, 10, 11] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [12, 13, 14, 15] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [16, 17, 18, 19] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [20, 21, 22, 23] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:1)\n", " CQI: 7\n", " Modulation: 16QAM\n", " Coderate: 378/1024\n", " CQI BLER: 1.28 %\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 RI/PMI/CQI (Set ID:1)\n", "Slot 40: TxBlock CRC Match: True\n", "Slot 41: TxBlock CRC Match: True\n", "Slot 42: TxBlock CRC Match: True\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 RI/PMI/CQI (ReportID: 11)\n", " RI: 2 (Score:2.327)\n", " WB PMI: (I1:[1, 0, 0], I2:0)\n", " WB precoder shape: (16, 2)\n", " 6 SB precoders: \n", " RBs: [0, 1, 2, 3] precoder shape: (16, 2) PMI: (I1:[1, 0, 0], I2:1)\n", " RBs: [4, 5, 6, 7] precoder shape: (16, 2) PMI: (I1:[1, 0, 0], I2:0)\n", " RBs: [8, 9, 10, 11] precoder shape: (16, 2) PMI: (I1:[1, 0, 0], I2:0)\n", " RBs: [12, 13, 14, 15] precoder shape: (16, 2) PMI: (I1:[1, 0, 0], I2:0)\n", " RBs: [16, 17, 18, 19] precoder shape: (16, 2) PMI: (I1:[1, 0, 0], I2:0)\n", " RBs: [20, 21, 22, 23] precoder shape: (16, 2) PMI: (I1:[1, 0, 0], I2:1)\n", " CQI: 4\n", " Modulation: QPSK\n", " Coderate: 308/1024\n", " CQI BLER: 1.35 %\n", "Slot 47: CQI/RI changed -> Mod:QPSK, Coderate:308/1024\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 RI/PMI/CQI (Set ID:1)\n", "Slot 60: TxBlock CRC Match: True\n", "Slot 61: TxBlock CRC Match: True\n", "Slot 62: TxBlock CRC Match: True\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 RI/PMI/CQI (ReportID: 11)\n", " RI: 1 (Score:2.182)\n", " WB PMI: (I1:[0, 0, 0], I2:2)\n", " WB precoder shape: (16, 1)\n", " 6 SB precoders: \n", " RBs: [0, 1, 2, 3] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [4, 5, 6, 7] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [8, 9, 10, 11] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:1)\n", " RBs: [12, 13, 14, 15] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [16, 17, 18, 19] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:2)\n", " RBs: [20, 21, 22, 23] precoder shape: (16, 1) PMI: (I1:[0, 0, 0], I2:1)\n", " CQI: 6\n", " Modulation: QPSK\n", " Coderate: 602/1024\n", " CQI BLER: 0.21 %\n", "Slot 67: CQI/RI changed -> Mod:QPSK, Coderate:602/1024\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 RI/PMI/CQI (Set ID:1)\n", "Slot 80: TxBlock CRC Match: True\n", "Slot 81: TxBlock CRC Match: True\n", "Slot 82: TxBlock CRC Match: True\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 RI/PMI/CQI (ReportID: 11)\n", " RI: 2 (Score:2.221)\n", " WB PMI: (I1:[1, 0, 0], I2:0)\n", " WB precoder shape: (16, 2)\n", " 6 SB precoders: \n", " RBs: [0, 1, 2, 3] precoder shape: (16, 2) PMI: (I1:[1, 0, 0], I2:0)\n", " RBs: [4, 5, 6, 7] precoder shape: (16, 2) PMI: (I1:[1, 0, 0], I2:1)\n", " RBs: [8, 9, 10, 11] precoder shape: (16, 2) PMI: (I1:[1, 0, 0], I2:0)\n", " RBs: [12, 13, 14, 15] precoder shape: (16, 2) PMI: (I1:[1, 0, 0], I2:0)\n", " RBs: [16, 17, 18, 19] precoder shape: (16, 2) PMI: (I1:[1, 0, 0], I2:1)\n", " RBs: [20, 21, 22, 23] precoder shape: (16, 2) PMI: (I1:[1, 0, 0], I2:0)\n", " CQI: 4\n", " Modulation: QPSK\n", " Coderate: 308/1024\n", " CQI BLER: 1.35 %\n", "Slot 87: CQI/RI changed -> Mod:QPSK, Coderate:308/1024\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 = 4 # Subband size (Set to 0 for wideband PMI)\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", "# One multi-port CSI-RS\n", "pmiSet = CsiRsSet(\"NZP\", bwp, resourceType=\"periodic\", rsId=1, period=20*(bwp.u+1), \n", " csiRsList=[CsiRs(resourceId=1, symbols=[4], numPorts=channel.txAntenna.numPorts,\n", " freqMap=\"001111\", cdmSize=4)])\n", "csiRsConfig = CsiRsConfig([pmiSet])\n", "# csiRsConfig.print() # Uncomment to print CSI-RS configuration details\n", "\n", "pmiRep = CsiReport(pmiSet, reportId=pmiSet.rsId+10, quantity=\"RiPmiCqi\", 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([pmiRep])\n", "# csiReportMan.print() # Uncomment to print CSI report configuration details\n", "\n", "# The PDSCH object is created once we have the first RI/PMI/CQI feedback and \n", "# recreated later if CQI changes.\n", "pdsch = None\n", "precoder = 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 == pmiRep.reportId: # RI/PMI/CQI report\n", " print(f\"Slot {slotNo}: Received RI/PMI/CQI (ReportID: {reportId})\")\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 pmiRep.quantity.lower():\n", " print(f\" CQI: {csiFeedback.cqi.cqi}\")\n", " modulation, coderateX1024 = pmiRep.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", " 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=pmiRep.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=pmiRep.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", " 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 == pmiSet.rsId: # CSI-RS for RI/PMI/CQI:\n", " print(f\"Slot {slotNo}: Sending CSI resources for RI/PMI/CQI (Set ID:{pmiSet.rsId})\")\n", " for resourceId, (lIdx, kIdx, pmiReValues) 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", "\n", " # pmiReValues is an nt x numCsiRsRE matrix.\n", " txGrid[:, lIdx, kIdx] = (pmiReValues*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", " effChannelMatrix = channel.getEffChannel(channelMatrix, precoder)\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", "\n", "print(f\"{failedSlots} of {numSlots} slots failed.\")" ] }, { "cell_type": "code", "execution_count": null, "id": "ce490aab-7bc5-47ad-a956-d37159d4f877", "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 }