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 Multiple Directional Multi-Port CSI-RS Resources
  • View page source

Downlink CSI Feedback Using Multiple Directional Multi-Port CSI-RS Resources

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.

For each directional CSI-RS resource, the UE estimates the effective MIMO channel and evaluates the corresponding transmission performance. It then reports:

  • CSI-RS Resource Indicator (CRI): identifies the preferred directional CSI-RS resource (beam);

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

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

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

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:

  1. the steering vector associated with the selected CRI; and

  2. the Type-I codebook precoder indicated by the reported PMI.

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.

The simulation includes:

  1. periodic transmission of multiple directional multi-port CSI-RS resources;

  2. UE-side CRI, RI, PMI, and CQI calculation;

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

  4. formation of a composite precoder by combining the selected steering vector and PMI precoder;

  5. PDSCH transmission using the composite precoder; and

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

[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 = 0                             # Set to 0 for wideband, 2 or 4 for subband precoding

# 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])

# Multiple multi-port CSI-RS (For 16 ports)
csiRsList = [ CsiRs(resourceId=1, symbols=[4], numPorts=channel.txAntenna.numPorts, freqMap="001111", cdmSize=4),
              CsiRs(resourceId=2, symbols=[8], numPorts=channel.txAntenna.numPorts, freqMap="001111", cdmSize=4),
              CsiRs(resourceId=3, symbols=[4], numPorts=channel.txAntenna.numPorts, freqMap="001111", cdmSize=4, offset=1),
              CsiRs(resourceId=4, symbols=[8], numPorts=channel.txAntenna.numPorts, freqMap="001111", cdmSize=4, offset=1),
              CsiRs(resourceId=5, symbols=[4], numPorts=channel.txAntenna.numPorts, freqMap="001111", cdmSize=4, offset=2),
              CsiRs(resourceId=6, symbols=[8], numPorts=channel.txAntenna.numPorts, freqMap="001111", cdmSize=4, offset=2),
              CsiRs(resourceId=7, symbols=[4], numPorts=channel.txAntenna.numPorts, freqMap="001111", cdmSize=4, offset=3),
              CsiRs(resourceId=8, symbols=[8], numPorts=channel.txAntenna.numPorts, freqMap="001111", cdmSize=4, offset=3) ]

csiRsSet = CsiRsSet("NZP", bwp, resourceType="periodic", rsId=1, period=20*(bwp.u+1), csiRsList=csiRsList)
csiRsConfig = CsiRsConfig([csiRsSet])
# csiRsConfig.print()                   # Uncomment to print CSI-RS configuration details

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

numPhi = len(csiRsSet)                  # Number of beams to sweep horizontally
numTheta = 1                            # Number of beams to sweep vertically (Restrict sweeping to azimuth)
ws, beams = channel.txAntenna.getSweepingBeams(numTheta, numPhi)

# The PDSCH object is created once we have the first CRI/RI/PMI/CQI feedback and
# recreated later if the reported RI or CQI changes.
pdsch = None
precoder = None
pdschW = 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 == csiRep.reportId:
            cri = csiFeedback.cri.cri                  # CSI-RS resource ID of the best beam
            print(f"Slot {slotNo}: Received CRI/RI/PMI/CQI (ReportID: {reportId})")
            print(f"  CRI: {cri}, best beam: 𝛳={beams[0][cri-1]:.2f}°, 𝝋={beams[1][cri-1]:.2f}°")
            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 csiRep.quantity.lower():
                print(f"  CQI: {csiFeedback.cqi.cqi}")
                modulation, coderateX1024 = csiRep.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
            pdschW = ws[:,cri-1:cri]   # steering vector for PDSCH
            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=csiRep.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=csiRep.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)

        # Now we are using a precoder which is a combination of precoder and pdschW
        pdsch.precodeTo(txGrid, precoder, pdschW)
        # 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 == csiRsSet.rsId:                             # CSI-RS for RI/PMI/CQI:
            print(f"Slot {slotNo}: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:{csiRsSet.rsId})")
            # Get a set of beam angles and precoders (weight vectors) for each beam
            for resourceId, (lIdx, kIdx, reValues) 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
                b = resourceId-1                                # Beam Index
                w = ws[:,b:b+1]                                 # nt x 1
                # reValues is a nt x numCsiRsRE matrix. nt x 1 * nt x numCsiRsRE = nt x numCsiRsRE
                txGrid[:,lIdx, kIdx] = (w * reValues * 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
        # Using the same precoder combination as in transmitter side
        effChannelMatrix = channel.getEffChannel(channelMatrix, precoder, pdschW)
        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 CRI/RI/PMI/CQI (Set ID:1)
Slot 1: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
Slot 2: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
Slot 3: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
Slot 7: Received CRI/RI/PMI/CQI (ReportID: 11)
  CRI: 7, best beam: 𝛳=90.00°, 𝝋=38.21°
  RI: 2 (Score:2.385)
  WB PMI: (I1:[9, 3, 0], I2:0)
  WB precoder shape: (16, 2)
  CQI: 4
  Modulation: QPSK
  Coderate: 308/1024
  CQI BLER: 1.35 %
Slot 7: Starting PDSCH (Mod:QPSK, Coderate:308/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 CRI/RI/PMI/CQI (Set ID:1)
Slot 20: TxBlock CRC Match: True
Slot 21: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
Slot 21: TxBlock CRC Match: True
Slot 22: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
Slot 22: TxBlock CRC Match: True
Slot 23: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
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 CRI/RI/PMI/CQI (ReportID: 11)
  CRI: 7, best beam: 𝛳=90.00°, 𝝋=38.21°
  RI: 2 (Score:2.302)
  WB PMI: (I1:[9, 2, 0], I2:0)
  WB precoder shape: (16, 2)
  CQI: 4
  Modulation: QPSK
  Coderate: 308/1024
  CQI BLER: 1.35 %
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 CRI/RI/PMI/CQI (Set ID:1)
Slot 40: TxBlock CRC Match: True
Slot 41: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
Slot 41: TxBlock CRC Match: True
Slot 42: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
Slot 42: TxBlock CRC Match: True
Slot 43: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
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 CRI/RI/PMI/CQI (ReportID: 11)
  CRI: 3, best beam: 𝛳=90.00°, 𝝋=-21.79°
  RI: 2 (Score:2.333)
  WB PMI: (I1:[9, 7, 0], I2:0)
  WB precoder shape: (16, 2)
  CQI: 4
  Modulation: QPSK
  Coderate: 308/1024
  CQI BLER: 1.35 %
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 CRI/RI/PMI/CQI (Set ID:1)
Slot 60: TxBlock CRC Match: True
Slot 61: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
Slot 61: TxBlock CRC Match: True
Slot 62: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
Slot 62: TxBlock CRC Match: True
Slot 63: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
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 CRI/RI/PMI/CQI (ReportID: 11)
  CRI: 7, best beam: 𝛳=90.00°, 𝝋=38.21°
  RI: 2 (Score:2.417)
  WB PMI: (I1:[9, 3, 0], I2:0)
  WB precoder shape: (16, 2)
  CQI: 4
  Modulation: QPSK
  Coderate: 308/1024
  CQI BLER: 1.35 %
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 CRI/RI/PMI/CQI (Set ID:1)
Slot 80: TxBlock CRC Match: True
Slot 81: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
Slot 81: TxBlock CRC Match: True
Slot 82: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
Slot 82: TxBlock CRC Match: True
Slot 83: Sending CSI resources for CRI/RI/PMI/CQI (Set ID:1)
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 CRI/RI/PMI/CQI (ReportID: 11)
  CRI: 3, best beam: 𝛳=90.00°, 𝝋=-21.79°
  RI: 2 (Score:2.354)
  WB PMI: (I1:[9, 7, 0], I2:1)
  WB precoder shape: (16, 2)
  CQI: 4
  Modulation: QPSK
  Coderate: 308/1024
  CQI BLER: 1.35 %
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.