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:
the steering vector associated with the selected CRI; and
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:
periodic transmission of multiple directional multi-port CSI-RS resources;
UE-side CRI, RI, PMI, and CQI calculation;
gNB-side PDSCH reconfiguration based on the reported CSI;
formation of a composite precoder by combining the selected steering vector and PMI precoder;
PDSCH transmission using the composite precoder; and
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.
[ ]: