NeoRadium 0.5.1
  • What is NeoRadium?

Getting Started

  • Installation
  • Migration Guide
  • Playground
    • Antenna
    • Stochastic Channel Models
    • Trajectory-based Channel Model
    • Channel State Information Reference Signals (CSI-RS)
    • CSI-Feedback
      • Downlink CSI Feedback Using a Single Multi-Port CSI-RS
      • Downlink CSI Feedback Using Multiple Directional Multi-Port CSI-RS Resources
      • Hierarchical Beam Management with Sweeping, Probing, and Type-I PMI Feedback
    • DM-RS and PT-RS
    • Physical Downlink Shared Channel (PDSCH)
    • Channel Coding
    • Hybrid Automatic Repeat reQuest (HARQ)
    • A Deep Learning Case Study
    • Research Papers
    • Other Examples
    • Comparing with MATLAB

API

  • Carriers and Bandwidth Parts
  • Resource Grid
  • Waveform
  • Modulation
  • Reference Signals
  • CSI Reporting
  • Physical Channels
  • Channel Coding
  • HARQ
  • Antenna
  • Channel Models
  • Random Number Generator
  • DeepMIMO and UE Trajectories
  • SnrScheduler
  • Other Utilitiy Functions
NeoRadium 0.5.1
  • Playground
  • Downlink CSI Feedback Using a Single Multi-Port CSI-RS
  • View page source

Downlink CSI Feedback Using a Single Multi-Port CSI-RS

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:

  • Rank Indicator (RI): the recommended number of transmission layers;

  • Precoding Matrix Indicator (PMI): the preferred Type-I single-panel codebook precoder; and

  • Channel Quality Indicator (CQI): the recommended modulation and code-rate operating point.

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.

The simulation includes:

  1. a dual-polarized MIMO channel based on a clustered delay line model;

  2. periodic transmission of a single multi-port CSI-RS resource;

  3. UE-side RI, PMI, and CQI calculation;

  4. gNB-side PDSCH reconfiguration based on the reported CSI;

  5. Type-I codebook precoding of the PDSCH; and

  6. receiver equalization, LDPC decoding, and transport-block CRC verification.

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.

[1]:
import numpy as np

from neoradium import BandwidthPart, PDSCH, AntennaPanel, CdlChannel, random
from neoradium import CsiRsConfig, CsiRsSet, CsiRs, CsiReport, CsiReportMan
from neoradium.utils import toLinear
[2]:
numSlots = 100                          # Number of slots in the communication loop
snrDb = -10                             # SNR in dB
random.setSeed(1234)                    # Make results reproducible
prgSize = 4                             # Subband size (Set to 0 for wideband PMI)

# Create a bandwidth part with 24 resource blocks and 15 kHz subcarrier spacing
bwp = BandwidthPart(numRbs=24, spacing=15)

# Create a CDL channel model
channel = CdlChannel(bwp, profile='C', delaySpread=30, carrierFreq=4e9, dopplerShift=5,
                     txAntenna=AntennaPanel([2,4], polarization='x'),   # 16 TX antennas
                     rxAntenna=AntennaPanel([1,2], polarization='x'),   # 4 RX antennas
                     rxOrientation = [180,0,0])

# One multi-port CSI-RS
pmiSet = CsiRsSet("NZP", bwp, resourceType="periodic", rsId=1, period=20*(bwp.u+1),
                  csiRsList=[CsiRs(resourceId=1, symbols=[4], numPorts=channel.txAntenna.numPorts,
                                   freqMap="001111", cdmSize=4)])
csiRsConfig = CsiRsConfig([pmiSet])
# csiRsConfig.print()                   # Uncomment to print CSI-RS configuration details

pmiRep = CsiReport(pmiSet, reportId=pmiSet.rsId+10, quantity="RiPmiCqi", reportType="periodic",
                   period=20*(bwp.u+1), offset=5, prgSize=prgSize, allowedRanks=[1,2],
                   txAntenna=channel.txAntenna, rxAntenna=channel.rxAntenna)
csiReportMan = CsiReportMan([pmiRep])
# csiReportMan.print()                 # Uncomment to print CSI report configuration details

# The PDSCH object is created once we have the first RI/PMI/CQI feedback and
# recreated later if CQI changes.
pdsch = None
precoder = None
failedSlots = 0
for slotNo in range(numSlots):
    channelMatrix = channel.getChannelMatrix()

    # Retrieve CSI feedback generated from previous CSI-RS occasions
    csiReportInfo = csiReportMan.getFeedback()              # Get all available CSI reports from CsiReport objects
    for reportId, csiFeedback in csiReportInfo.items():     # Get the CSI feedback for each report
        if reportId == pmiRep.reportId:                     # RI/PMI/CQI report
            print(f"Slot {slotNo}: Received RI/PMI/CQI (ReportID: {reportId})")
            print(f"  RI: {csiFeedback.ri.ri} (Score:{csiFeedback.ri.score:.3f})")
            print(f"  WB PMI: {csiFeedback.pmi.wbPMI}")
            print(f"  WB precoder shape: {csiFeedback.pmi.wbW.shape}")
            if csiFeedback.pmi.sbWs is not None:
                print(f"  {len(csiFeedback.pmi.sbWs)} SB precoders: ")
                for i, (rbIdx, w) in enumerate(csiFeedback.pmi.sbWs):
                    print(f"    RBs: {str(rbIdx):<20} precoder shape: {str(w.shape):<10} PMI: {csiFeedback.pmi.sbPMIs[i]}")
            if "cqi" in pmiRep.quantity.lower():
                print(f"  CQI: {csiFeedback.cqi.cqi}")
                modulation, coderateX1024 = pmiRep.getModRate(csiFeedback.cqi.cqi)
                print(f"  Modulation: {modulation}")
                print(f"  Coderate: {coderateX1024}/1024")
                print(f"  CQI BLER: {csiFeedback.cqi.bler:.2f} %")

            precoder = csiFeedback.pmi.wbW if csiFeedback.pmi.sbWs is None else csiFeedback.pmi.sbWs
            if pdsch is None:
                # First RI/PMI/CQI feedback -> create PDSCH and LDPC codec objects
                print(f"Slot {slotNo}: Starting PDSCH (Mod:{modulation}, "
                      f"Coderate:{coderateX1024}/1024)")
                pdsch = PDSCH(bwp, numLayers=csiFeedback.ri.ri, csiRsConfig=csiRsConfig,
                              modulation=modulation, prgSize=pmiRep.prgSize)
                pdsch.setDMRS(additionalPos=2)
                ldpc = pdsch.getLdpcCodec(coderates = coderateX1024/1024)

            elif ( (pdsch.modems[0].modulation != modulation) or
                   (pdsch.numLayers != csiFeedback.ri.ri) ):
                # Modulation or number of layers changed -> Recreate PDSCH and LDPC codec objects
                print(f"Slot {slotNo}: CQI/RI changed -> Mod:{modulation}, "
                      f"Coderate:{coderateX1024}/1024")
                pdsch = PDSCH(bwp, numLayers=csiFeedback.ri.ri, csiRsConfig=csiRsConfig,
                              modulation=modulation, prgSize=pmiRep.prgSize)
                pdsch.setDMRS(additionalPos=2)
                ldpc = pdsch.getLdpcCodec(coderates = coderateX1024/1024)

            elif ldpc.coderates[0] != (coderateX1024/1024):
                # Coderate changed -> Recreate the LDPC codec object only
                print(f"Slot {slotNo}: CQI changed -> Coderate:{coderateX1024}/1024")
                ldpc = pdsch.getLdpcCodec(coderates = coderateX1024/1024)
        else:
            print(f"Unknown report: {reportId}")

    # Create a transmitted resource grid.
    txGrid = bwp.createGrid(channel.txAntenna.numEl)
    if pdsch is not None:
        # Create random data, LDPC encode it, and put it in the PDSCH's internal resource grid.
        # Then precode the PDSCH into the transmitted resource grid - txGrid.
        pdsch.initGrid()
        numBits = pdsch.getBitCapacity()[0]
        txBlock = random.bits(ldpc.txBlockSizes[0])
        rateMatchedCodeBlocks = ldpc.encode(txBlock, numBits)
        pdsch.setPdschData(rateMatchedCodeBlocks)
        pdsch.precodeTo(txGrid, precoder)

    # Map any CSI-RS resources scheduled in the current slot
    csiRsResources = csiRsConfig.getResources()
    for csiSetId, setResources in csiRsResources.items():
        if csiSetId == pmiSet.rsId:                             # CSI-RS for RI/PMI/CQI:
            print(f"Slot {slotNo}: Sending CSI resources for RI/PMI/CQI (Set ID:{pmiSet.rsId})")
            for resourceId, (lIdx, kIdx, pmiReValues) in setResources.items():
                # Simulation note:
                # Scale the CSI-RS to keep its aggregate transmit power approximately
                # consistent with the PDSCH. This avoids unintentionally reducing the
                # effective PDSCH SNR in this simulation, where the noise variance is
                # derived from the average received signal power. This is a simulation
                # convenience only; it is **NOT** a 3GPP requirement or recommendation
                # and is not representative of how practical systems necessarily
                # implement CSI-RS transmission.
                csiRs = csiRsConfig.getById(csiSetId, resourceId)
                pf = np.sqrt( csiRs.numPorts/(channel.txAntenna.numEl*csiRs.cdmSize))  # Power factor

                # pmiReValues is an nt x numCsiRsRE matrix.
                txGrid[:, lIdx, kIdx] = (pmiReValues*pf, "CSIRS_NZP", resourceId)

    # Apply the channel model and add AWGN noise
    rxGrid = txGrid.applyChannel(channelMatrix)
    noisyRxGrid = rxGrid.addNoise(snrDb=snrDb)            # Add noise

    # UE processing of the received resource grid to generate reports
    csiReportMan.processRxGrid(noisyRxGrid, csiRsResources)

    if pdsch is not None:
        # Receiver side processing of the PDSCH: equalization and LDPC decoding
        effChannelMatrix = channel.getEffChannel(channelMatrix, precoder)
        eqGrid, llrScales = pdsch.equalize(noisyRxGrid, effChannelMatrix)
        llrs = pdsch.getLLRs(eqGrid, llrScales)
        decodedTxBlocks, crcMatch = ldpc.decode(llrs)
        print(f"Slot {slotNo}: TxBlock CRC Match: {crcMatch[0][0]}")
        failedSlots += 1-int(crcMatch[0][0])

    # Go to the next channel instance for the next slot
    channel.goNext()

print(f"{failedSlots} of {numSlots} slots failed.")
Slot 0: Sending CSI resources for RI/PMI/CQI (Set ID:1)
Slot 7: Received RI/PMI/CQI (ReportID: 11)
  RI: 1 (Score:2.226)
  WB PMI: (I1:[0, 0, 0], I2:2)
  WB precoder shape: (16, 1)
  6 SB precoders:
    RBs: [0, 1, 2, 3]         precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [4, 5, 6, 7]         precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:1)
    RBs: [8, 9, 10, 11]       precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [12, 13, 14, 15]     precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [16, 17, 18, 19]     precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [20, 21, 22, 23]     precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
  CQI: 7
  Modulation: 16QAM
  Coderate: 378/1024
  CQI BLER: 7.95 %
Slot 7: Starting PDSCH (Mod:16QAM, Coderate:378/1024)
Slot 7: TxBlock CRC Match: True
Slot 8: TxBlock CRC Match: True
Slot 9: TxBlock CRC Match: True
Slot 10: TxBlock CRC Match: True
Slot 11: TxBlock CRC Match: True
Slot 12: TxBlock CRC Match: True
Slot 13: TxBlock CRC Match: True
Slot 14: TxBlock CRC Match: True
Slot 15: TxBlock CRC Match: True
Slot 16: TxBlock CRC Match: True
Slot 17: TxBlock CRC Match: True
Slot 18: TxBlock CRC Match: True
Slot 19: TxBlock CRC Match: True
Slot 20: Sending CSI resources for RI/PMI/CQI (Set ID:1)
Slot 20: TxBlock CRC Match: True
Slot 21: TxBlock CRC Match: True
Slot 22: TxBlock CRC Match: True
Slot 23: TxBlock CRC Match: True
Slot 24: TxBlock CRC Match: True
Slot 25: TxBlock CRC Match: True
Slot 26: TxBlock CRC Match: True
Slot 27: Received RI/PMI/CQI (ReportID: 11)
  RI: 1 (Score:2.351)
  WB PMI: (I1:[0, 0, 0], I2:2)
  WB precoder shape: (16, 1)
  6 SB precoders:
    RBs: [0, 1, 2, 3]         precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [4, 5, 6, 7]         precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [8, 9, 10, 11]       precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [12, 13, 14, 15]     precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [16, 17, 18, 19]     precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [20, 21, 22, 23]     precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:1)
  CQI: 7
  Modulation: 16QAM
  Coderate: 378/1024
  CQI BLER: 1.28 %
Slot 27: TxBlock CRC Match: True
Slot 28: TxBlock CRC Match: True
Slot 29: TxBlock CRC Match: True
Slot 30: TxBlock CRC Match: True
Slot 31: TxBlock CRC Match: True
Slot 32: TxBlock CRC Match: True
Slot 33: TxBlock CRC Match: True
Slot 34: TxBlock CRC Match: True
Slot 35: TxBlock CRC Match: True
Slot 36: TxBlock CRC Match: True
Slot 37: TxBlock CRC Match: True
Slot 38: TxBlock CRC Match: True
Slot 39: TxBlock CRC Match: True
Slot 40: Sending CSI resources for RI/PMI/CQI (Set ID:1)
Slot 40: TxBlock CRC Match: True
Slot 41: TxBlock CRC Match: True
Slot 42: TxBlock CRC Match: True
Slot 43: TxBlock CRC Match: True
Slot 44: TxBlock CRC Match: True
Slot 45: TxBlock CRC Match: True
Slot 46: TxBlock CRC Match: True
Slot 47: Received RI/PMI/CQI (ReportID: 11)
  RI: 2 (Score:2.327)
  WB PMI: (I1:[1, 0, 0], I2:0)
  WB precoder shape: (16, 2)
  6 SB precoders:
    RBs: [0, 1, 2, 3]         precoder shape: (16, 2)    PMI: (I1:[1, 0, 0], I2:1)
    RBs: [4, 5, 6, 7]         precoder shape: (16, 2)    PMI: (I1:[1, 0, 0], I2:0)
    RBs: [8, 9, 10, 11]       precoder shape: (16, 2)    PMI: (I1:[1, 0, 0], I2:0)
    RBs: [12, 13, 14, 15]     precoder shape: (16, 2)    PMI: (I1:[1, 0, 0], I2:0)
    RBs: [16, 17, 18, 19]     precoder shape: (16, 2)    PMI: (I1:[1, 0, 0], I2:0)
    RBs: [20, 21, 22, 23]     precoder shape: (16, 2)    PMI: (I1:[1, 0, 0], I2:1)
  CQI: 4
  Modulation: QPSK
  Coderate: 308/1024
  CQI BLER: 1.35 %
Slot 47: CQI/RI changed -> Mod:QPSK, Coderate:308/1024
Slot 47: TxBlock CRC Match: True
Slot 48: TxBlock CRC Match: True
Slot 49: TxBlock CRC Match: True
Slot 50: TxBlock CRC Match: True
Slot 51: TxBlock CRC Match: True
Slot 52: TxBlock CRC Match: True
Slot 53: TxBlock CRC Match: True
Slot 54: TxBlock CRC Match: True
Slot 55: TxBlock CRC Match: True
Slot 56: TxBlock CRC Match: True
Slot 57: TxBlock CRC Match: True
Slot 58: TxBlock CRC Match: True
Slot 59: TxBlock CRC Match: True
Slot 60: Sending CSI resources for RI/PMI/CQI (Set ID:1)
Slot 60: TxBlock CRC Match: True
Slot 61: TxBlock CRC Match: True
Slot 62: TxBlock CRC Match: True
Slot 63: TxBlock CRC Match: True
Slot 64: TxBlock CRC Match: True
Slot 65: TxBlock CRC Match: True
Slot 66: TxBlock CRC Match: True
Slot 67: Received RI/PMI/CQI (ReportID: 11)
  RI: 1 (Score:2.182)
  WB PMI: (I1:[0, 0, 0], I2:2)
  WB precoder shape: (16, 1)
  6 SB precoders:
    RBs: [0, 1, 2, 3]         precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [4, 5, 6, 7]         precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [8, 9, 10, 11]       precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:1)
    RBs: [12, 13, 14, 15]     precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [16, 17, 18, 19]     precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:2)
    RBs: [20, 21, 22, 23]     precoder shape: (16, 1)    PMI: (I1:[0, 0, 0], I2:1)
  CQI: 6
  Modulation: QPSK
  Coderate: 602/1024
  CQI BLER: 0.21 %
Slot 67: CQI/RI changed -> Mod:QPSK, Coderate:602/1024
Slot 67: TxBlock CRC Match: True
Slot 68: TxBlock CRC Match: True
Slot 69: TxBlock CRC Match: True
Slot 70: TxBlock CRC Match: True
Slot 71: TxBlock CRC Match: True
Slot 72: TxBlock CRC Match: True
Slot 73: TxBlock CRC Match: True
Slot 74: TxBlock CRC Match: True
Slot 75: TxBlock CRC Match: True
Slot 76: TxBlock CRC Match: True
Slot 77: TxBlock CRC Match: True
Slot 78: TxBlock CRC Match: True
Slot 79: TxBlock CRC Match: True
Slot 80: Sending CSI resources for RI/PMI/CQI (Set ID:1)
Slot 80: TxBlock CRC Match: True
Slot 81: TxBlock CRC Match: True
Slot 82: TxBlock CRC Match: True
Slot 83: TxBlock CRC Match: True
Slot 84: TxBlock CRC Match: True
Slot 85: TxBlock CRC Match: True
Slot 86: TxBlock CRC Match: True
Slot 87: Received RI/PMI/CQI (ReportID: 11)
  RI: 2 (Score:2.221)
  WB PMI: (I1:[1, 0, 0], I2:0)
  WB precoder shape: (16, 2)
  6 SB precoders:
    RBs: [0, 1, 2, 3]         precoder shape: (16, 2)    PMI: (I1:[1, 0, 0], I2:0)
    RBs: [4, 5, 6, 7]         precoder shape: (16, 2)    PMI: (I1:[1, 0, 0], I2:1)
    RBs: [8, 9, 10, 11]       precoder shape: (16, 2)    PMI: (I1:[1, 0, 0], I2:0)
    RBs: [12, 13, 14, 15]     precoder shape: (16, 2)    PMI: (I1:[1, 0, 0], I2:0)
    RBs: [16, 17, 18, 19]     precoder shape: (16, 2)    PMI: (I1:[1, 0, 0], I2:1)
    RBs: [20, 21, 22, 23]     precoder shape: (16, 2)    PMI: (I1:[1, 0, 0], I2:0)
  CQI: 4
  Modulation: QPSK
  Coderate: 308/1024
  CQI BLER: 1.35 %
Slot 87: CQI/RI changed -> Mod:QPSK, Coderate:308/1024
Slot 87: TxBlock CRC Match: True
Slot 88: TxBlock CRC Match: True
Slot 89: TxBlock CRC Match: True
Slot 90: TxBlock CRC Match: True
Slot 91: TxBlock CRC Match: True
Slot 92: TxBlock CRC Match: True
Slot 93: TxBlock CRC Match: True
Slot 94: TxBlock CRC Match: True
Slot 95: TxBlock CRC Match: True
Slot 96: TxBlock CRC Match: True
Slot 97: TxBlock CRC Match: True
Slot 98: TxBlock CRC Match: True
Slot 99: TxBlock CRC Match: True
0 of 100 slots failed.
[ ]:

Previous Next

© Copyright 2025, InterDigital, Inc. All Rights Reserved.

Built with Sphinx using a theme provided by Read the Docs.