NR Channel Estimation Using CSI-RS

This example shows how to generate channel state information reference signal (CSI-RS) symbols and indices for a given carrier and CSI-RS resource configuration, as defined in TS 38.211 Section 7.4.1.5. The example shows how to map the generated symbols to the carrier resource grid, performs channel estimation at the receiver side, and compares the estimated channel against the actual channel.

Introduction

CSI-RS is a downlink-specific (DL) reference signal. The NR standard defines zero-power (ZP) and non-zero-power (NZP) CSI-RSs.

The user equipment (UE) processes utilize NZP-CSI-RSs:

  • L1-Reference signal received power (RSRP) measurements for mobility and beam management

  • DL CSI acquisition

  • Interference measurement

  • Time and frequency tracking

ZP-CSI-RS is used for DL CSI acquisition and interference measurement. It also masks certain resource elements (REs) to make them unavailable for PDSCH transmission. As the name ZP indicates, nothing is transmitted in those REs.

This example shows how to use CSI-RS to perform channel estimation which forms the basis of CSI acquisition.

Initialize Configuration Objects

Create a carrier configuration object representing a 5 MHz carrier with subcarrier spacing of 15 kHz.

carrier = nrCarrierConfig;
carrier.NSizeGrid = 25;
carrier.SubcarrierSpacing = 15;
carrier.NSlot = 1;
carrier.NFrame = 0
carrier = 
  nrCarrierConfig with properties:

    SubcarrierSpacing: 15
         CyclicPrefix: 'normal'
            NSizeGrid: 25
           NStartGrid: 0
                NSlot: 1
               NFrame: 0

   Read-only properties:
       SymbolsPerSlot: 14
     SlotsPerSubframe: 1
        SlotsPerFrame: 10

Create a CSI-RS configuration object representing two CSI-RS resources, NZP with row number 3 and ZP with row number 5.

csirs = nrCSIRSConfig;
csirs.CSIRSType = {'nzp','zp'};
csirs.CSIRSPeriod = {[5 1],[5 1]};
csirs.Density = {'one','one'};
csirs.RowNumber = [3 5];
csirs.SymbolLocations = {1,6};
csirs.SubcarrierLocations = {6,4};
csirs.NumRB = 25
csirs = 
  nrCSIRSConfig with properties:

              CSIRSType: {'nzp'  'zp'}
            CSIRSPeriod: {[5 1]  [5 1]}
              RowNumber: [3 5]
                Density: {'one'  'one'}
        SymbolLocations: {[1]  [6]}
    SubcarrierLocations: {[6]  [4]}
                  NumRB: 25
               RBOffset: 0
                    NID: 0

   Read-only properties:
          NumCSIRSPorts: [2 4]
                CDMType: {'FD-CDM2'  'FD-CDM2'}

Consider the power scaling of CSI-RS in dB.

powerCSIRS = 0;
disp(['CSI-RS power scaling: ' num2str(powerCSIRS) ' dB']);
CSI-RS power scaling: 0 dB

Generate CSI-RS Symbols and Indices

Generate CSI-RS symbols for the specified carrier and CSI-RS configuration parameters. Apply power scaling.

sym = nrCSIRS(carrier,csirs);
csirsSym = sym*db2mag(powerCSIRS);

The variable csirsSym is a column vector containing CSI-RS symbols.

Generate CSI-RS indices for the specified carrier and CSI-RS configuration parameters.

csirsInd = nrCSIRSIndices(carrier,csirs);

The variable csirsInd is also a column vector of same size as that of csirsSym.

When you configure both ZP and NZP resources, the generation of ZP signals takes priority over the generation of NZP signals.

Initialize Carrier Grid

Calculate the carrier grid dimensions and create an empty grid for one slot.

NumRB = carrier.NSizeGrid;
K = NumRB*12;                       % Number of subcarriers
L = carrier.SymbolsPerSlot;         % Number of OFDM symbols per slot
ports = max(csirs.NumCSIRSPorts);   % Number of antenna ports
gridSize = [K L ports];

% Initialize the carrier grid for one slot
txGrid = complex(zeros(gridSize));

Map CSI-RS Symbols Onto Carrier Grid

Perform resource element mapping.

txGrid(csirsInd) = csirsSym;

Plot the locations of the CSI-RS (both ZP and NZP) in the grid.

plotGrid(gridSize,csirsInd,csirsSym);

Perform OFDM Modulation

Get OFDM modulation related information and perform OFDM modulation to generate the time-domain waveform.

% Get OFDM modulation related information
gnb.NRB = NumRB;
gnb.SubcarrierSpacing = carrier.SubcarrierSpacing;
gnb.CyclicPrefix = carrier.CyclicPrefix;
OFDMInfo = hOFDMInfo(gnb);

% Perform OFDM modulation
txWaveform = hOFDMModulate(gnb,txGrid);

Pass Time-Domain Waveform Through Channel and Add AWGN Noise

Configure the number of receiving antennas.

R = 4;

Configure the channel.

channel = nrTDLChannel;
channel.NumTransmitAntennas = ports;
channel.NumReceiveAntennas = R;
channel.DelayProfile = 'TDL-C';
channel.MaximumDopplerShift = 10;
channel.DelaySpread = 1e-8
channel = 
  nrTDLChannel with properties:

               DelayProfile: 'TDL-C'
                DelaySpread: 1.0000e-08
        MaximumDopplerShift: 10
                 SampleRate: 30720000
            MIMOCorrelation: 'Low'
               Polarization: 'Co-Polar'
      TransmissionDirection: 'Downlink'
        NumTransmitAntennas: 4
         NumReceiveAntennas: 4
         NormalizePathGains: true
                InitialTime: 0
               NumSinusoids: 48
               RandomStream: 'mt19937ar with seed'
                       Seed: 73
    NormalizeChannelOutputs: true

Based on the configured channel, append zeros to the transmitted waveform to account for the channel delay.

chInfo = info(channel);
maxChDelay = ceil(max(chInfo.PathDelays*channel.SampleRate)) + chInfo.ChannelFilterDelay;
txWaveform = [txWaveform; zeros(maxChDelay,size(txWaveform,2))];

Pass waveform through channel.

[rxWaveform,pathGains] = channel(txWaveform);

To produce the actual propagation channel H_actual, perform perfect channel estimation.

pathFilters = getPathFilters(channel);
initialNSlot = carrier.NSlot;
SCS = carrier.SubcarrierSpacing;
H_actual = nrPerfectChannelEstimate(pathGains,pathFilters,NumRB,SCS,initialNSlot);

Add AWGN noise to the waveform.

SNRdB = 50;           % in dB
SNR = 10^(SNRdB/20);  % Linear value
N0 = 1/(sqrt(2.0*R*double(OFDMInfo.Nfft))*SNR); % Noise variance
rng(0);
noise = N0*complex(randn(size(rxWaveform)),randn(size(rxWaveform)));
rxWaveform = rxWaveform + noise
rxWaveform = 7690×4 complex

   0.0266 - 0.0606i  -0.1125 + 0.0469i  -0.0763 - 0.0453i  -0.0534 + 0.0194i
   0.0906 - 0.0708i  -0.0058 + 0.0166i   0.0904 - 0.0015i  -0.0239 - 0.0495i
  -0.1116 + 0.0158i   0.0523 + 0.0048i  -0.0615 - 0.0078i  -0.1124 + 0.0279i
   0.0426 - 0.0259i   0.0216 - 0.0380i  -0.0874 + 0.0737i  -0.0034 - 0.0002i
   0.0158 + 0.0540i   0.0697 - 0.0424i   0.0474 - 0.0003i   0.0510 - 0.0279i
  -0.0646 + 0.0339i  -0.0007 + 0.0097i  -0.0387 + 0.0047i   0.0607 + 0.0045i
  -0.0214 - 0.0846i  -0.0266 - 0.0669i  -0.0938 - 0.0359i  -0.0376 - 0.0324i
   0.0169 + 0.0576i  -0.0907 - 0.0466i  -0.0021 + 0.0197i  -0.0222 - 0.0402i
   0.1768 - 0.0712i   0.1302 + 0.0570i   0.0911 + 0.0100i   0.0544 + 0.0427i
   0.1368 + 0.0114i   0.0144 + 0.0287i   0.0444 - 0.0762i   0.0409 - 0.0828i
      ⋮

Perform timing synchronization using NZP-CSI-RS. To estimate timing offset, use nrTimingEstimate and consider the NZP-CSI-RS as a reference.

% Disable the CSI-RS resource that is not going to be used for channel
% estimation
csirs.CSIRSPeriod = {[5 1],'off'};
% Generate reference symbols and apply power scaling
refSym = db2mag(powerCSIRS)*nrCSIRS(carrier,csirs);
% Generate reference indices
refInd = nrCSIRSIndices(carrier,csirs);
offset = nrTimingEstimate(rxWaveform,NumRB,SCS,initialNSlot,refInd,refSym)
offset = 7
rxWaveform = rxWaveform(1+offset:end,:);

OFDM demodulate the received time-domain waveform.

rxGrid = hOFDMDemodulate(gnb,rxWaveform); % Of size K-by-L-by-R

Compare Estimated Channel Against Actual Channel

Perform practical channel estimation using NZP-CSI-RS. Make sure the CSI-RS symbols csirsSym are of the same CDM type.

cdmLen = [2 1]; % Corresponds to CDMType = 'FD-CDM2'
[H_est,nVar] = nrChannelEstimate(rxGrid,refInd,refSym,'CDMLengths',cdmLen);
estSNR = -10*log10(nVar);
disp(['estimated SNR = ' num2str(estSNR) ' dB'])
estimated SNR = 27.4579 dB

Plot the estimated channel and actual channel between first transmitting antenna and first receiving antenna.

figure;

% Plot the estimated channel
subplot(1,2,1)
imagesc(abs(H_est(:,:,1,1)));
colorbar;
title('Estimated Channel')
axis xy;
xlabel('OFDM Symbols');
ylabel('Subcarriers');

% Plot the actual channel
subplot(1,2,2)
imagesc(abs(H_actual(:,:,1,1)));
colorbar;
title('Actual Channel')
axis xy;
xlabel('OFDM Symbols');
ylabel('Subcarriers');

Calculate the channel estimation error.

H_err = (H_est - H_actual(:,:,:,1:size(H_est,4)));
[minErr,maxErr] = bounds(abs(H_err),'all');
disp(['Absolute value of the channel estimation error is in the range of [' num2str(minErr) ', ' num2str(maxErr) ']'])
Absolute value of the channel estimation error is in the range of [1.4479e-05, 0.035061]

Local Functions

function plotGrid(gridSize,csirsInd,csirsSym)
%    plotGrid(GRIDSIZE,CSIRSIND,CSIRSSYM) plots the carrier grid of size GRIDSIZE
%    by populating the grid with CSI-RS symbols using CSIRSIND and CSIRSSYM.

    figure()
    tempSym = csirsSym;
    tempSym(tempSym ~= 0) = 20; % Replacing non-zero-power symbols
    tempSym(tempSym == 0) = 2; % Replacing zero-power symbols
    tempGrid = complex(zeros(gridSize));
    tempGrid(csirsInd) = tempSym;

    image(50*tempGrid(:,:,1)); % Multiplied with some large number for better visualization
    axis xy;
    names = {'NZP CSI-RS','ZP CSI-RS'};
    cmap = colormap(gcf);
    chpval = {20,2};
    clevels = 50*[chpval{:}];
    N = length(clevels);
    L = line(ones(N),ones(N), 'LineWidth',8); % Generate lines
    % Index the color map and associated the selected colors with the lines
    set(L,{'color'},mat2cell(cmap( min(1+clevels,length(cmap) ),:),ones(1,N),3));   % Set the colors according to cmap
    % Create legend 
    legend(names{:});

    title('Carrier Grid Containing CSI-RS')
    xlabel('OFDM Symbols');
    ylabel('Subcarriers');
end

References

[1] 3GPP TS 38.211. “NR; Physical channels and modulation.” 3rd Generation Partnership Project; Technical Specification Group Radio Access Network.

See Also

Functions

Objects