Files
WirelessNetworkingTechnologies/lab_5/HelperNRNTNThroughput.m
T
2026-06-22 22:03:28 +02:00

1222 lines
56 KiB
Matlab
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
classdef HelperNRNTNThroughput
%HelperNRNTNThroughput Class defining the supporting functions used in
%the NR NTN PDSCH Throughput example
%
% Note: This is an undocumented class and its API and/or
% functionality may change in subsequent releases.
% Copyright 2021-2026 The MathWorks, Inc.
methods (Static)
function validateNumLayers(simParameters)
% Validate the number of layers, relative to the antenna geometry
if isfield(simParameters, 'PDSCH')
numlayers = simParameters.PDSCH.NumLayers;
else
numlayers = simParameters.PUSCH.NumLayers;
end
ntxants = simParameters.NumTransmitAntennas;
nrxants = simParameters.NumReceiveAntennas;
if contains(simParameters.NTNChannelType,'Narrowband','IgnoreCase',true)
if (ntxants ~= 1) || (nrxants ~= 1)
error(['For NTN narrowband channel, ' ...
'the number of transmit and receive antennas must be 1.']);
end
end
antennaDescription = sprintf(...
'min(NumTransmitAntennas,NumReceiveAntennas) = min(%d,%d) = %d', ...
ntxants,nrxants,min(ntxants,nrxants));
if numlayers > min(ntxants,nrxants)
error('The number of layers (%d) must satisfy NumLayers <= %s', ...
numlayers,antennaDescription);
end
% Display a warning if the maximum possible rank of the channel equals
% the number of layers
if (numlayers > 2) && (numlayers == min(ntxants,nrxants))
warning(['The maximum possible rank of the channel, given by %s, is equal to' ...
' NumLayers (%d). This may result in a decoding failure under some channel' ...
' conditions. Try decreasing the number of layers or increasing the channel' ...
' rank (use more transmit or receive antennas).'],antennaDescription, ...
numlayers); %#ok<SPWRN>
end
% For NTN CDL channel, the parameters NumTrasnmitAntennas and
% NumReceiveAntennas must align with that of TxArraySize and
% RxArraySize respectively
if simParameters.NTNChannelType == "CDL"
numElementsTxArray = prod(simParameters.TxArraySize);
if numElementsTxArray ~= ntxants
error(['For NTN CDL channel, the product of all values in TxArraySize (%d) must be equal to ' ...
'number of transmit antennas (%d)'],numElementsTxArray,ntxants);
end
numElementsRxArray = prod(simParameters.RxArraySize);
if numElementsRxArray ~= nrxants
error(['For NTN CDL channel, the product of all values in RxArraySize (%d) must be equal to ' ...
'number of receive antennas (%d)'],numElementsRxArray,nrxants);
end
end
end
function [estChannelGrid,sampleTimes] = getInitialChannelEstimate(...
carrier,nTxAnts,channel,dataType)
% Obtain channel estimate before first transmission. Use this function to
% obtain a precoding matrix for the first slot.
ofdmInfo = nrOFDMInfo(carrier);
chInfo = info(channel);
maxChDelay = ceil(max(chInfo.PathDelays*channel.SampleRate)) ...
+ chInfo.ChannelFilterDelay;
% Temporary waveform (only needed for the sizes)
tmpWaveform = zeros(...
(ofdmInfo.SampleRate/1000/carrier.SlotsPerSubframe)+maxChDelay,nTxAnts,dataType);
% Filter through channel and get the path gains and path filters
[~,pathGains,sampleTimes] = channel(tmpWaveform);
if isa(channel,'nrTDLChannel') || isa(channel,'nrCDLChannel')
pathFilters = getPathFilters(channel);
else
pathFilters = chInfo.ChannelFilterCoefficients.';
end
% Perfect timing synchronization
offset = nrPerfectTimingEstimate(pathGains,pathFilters);
% Perfect channel estimate
estChannelGrid = nrPerfectChannelEstimate(...
carrier,pathGains,pathFilters,offset,double(sampleTimes));
end
function wtx = getPrecodingMatrix(carrier,pdsch,hestGrid,prgbundlesize)
% Calculate precoding matrices for all precoding resource block groups
% (PRGs) in the carrier that overlap with the PDSCH allocation
% Maximum common resource block (CRB) addressed by carrier grid
maxCRB = carrier.NStartGrid + carrier.NSizeGrid - 1;
% PRG size
if nargin==4 && ~isempty(prgbundlesize)
Pd_BWP = prgbundlesize;
else
Pd_BWP = maxCRB + 1;
end
% PRG numbers (1-based) for each RB in the carrier grid
NPRG = ceil((maxCRB + 1) / Pd_BWP);
prgset = repmat((1:NPRG),Pd_BWP,1);
prgset = prgset(carrier.NStartGrid + (1:carrier.NSizeGrid).');
[~,~,R,P] = size(hestGrid);
wtx = zeros([pdsch.NumLayers P NPRG],'like',hestGrid);
for i = 1:NPRG
% Subcarrier indices within current PRG and within the PDSCH
% allocation
thisPRG = find(prgset==i) - 1;
thisPRG = intersect(thisPRG,pdsch.PRBSet(:) + carrier.NStartGrid,'rows');
prgSc = (1:12)' + 12*thisPRG';
prgSc = prgSc(:);
if (~isempty(prgSc))
% Average channel estimate in PRG
estAllocGrid = hestGrid(prgSc,:,:,:);
Hest = permute(mean(reshape(estAllocGrid,[],R,P)),[2 3 1]);
% SVD decomposition
[~,~,V] = svd(Hest);
wtx(:,:,i) = V(:,1:pdsch.NumLayers).';
end
end
wtx = wtx / sqrt(pdsch.NumLayers); % Normalize by NumLayers
end
function estChannelGrid = precodeChannelEstimate(carrier,estChannelGrid,W)
% Apply precoding matrix W to the last dimension of the channel estimate
[K,L,R,P] = size(estChannelGrid);
estChannelGrid = reshape(estChannelGrid,[K*L R P]);
estChannelGrid = nrPDSCHPrecode( ...
carrier,estChannelGrid,reshape(1:numel(estChannelGrid),[K*L R P]),W);
estChannelGrid = reshape(estChannelGrid,K,L,R,[]);
end
function [loc,wMovSum,pho,bestAnt] = detectOFDMSymbolBoundary(rxWave,nFFT,cpLen,thres)
% Detect OFDM symbol boundary by calculating correlation of cyclic prefix
% Capture the dimensions of received waveform
[NSamples,R] = size(rxWave);
% Append zeros of length nFFT across each receive antenna to the
% received waveform
waveformZeroPadded = [rxWave;zeros(nFFT,R,'like',rxWave)];
% Get the portion of waveform till the last nFFT samples
wavePortion1 = waveformZeroPadded(1:end-nFFT,:);
% Get the portion of waveform delayed by nFFT
wavePortion2 = waveformZeroPadded(1+nFFT:end,:);
% Get the energy of each sample in both the waveform portions
eWavePortion1 = abs(wavePortion1).^2;
eWavePortion2 = abs(wavePortion2).^2;
% Initialize the temporary variables
wMovSum = zeros([NSamples R]);
wEnergyPortion1 = zeros([NSamples+cpLen-1 R]);
wEnergyPortion2 = wEnergyPortion1;
% Perform correlation for each sample with the sample delayed by nFFT
waveCorr = wavePortion1.*conj(wavePortion2);
% Calculate the moving sum value for each cpLen samples, across each
% receive antenna
oneVec = ones(cpLen,1);
for i = 1:R
wConv = conv(waveCorr(:,i),oneVec);
wMovSum(:,i) = wConv(cpLen:end);
wEnergyPortion1(:,i) = conv(eWavePortion1(:,i),oneVec);
wEnergyPortion2(:,i) = conv(eWavePortion2(:,i),oneVec);
end
% Get the normalized correlation value for the moving sum matrix
pho = abs(wMovSum)./ ...
(eps+sqrt(wEnergyPortion1(cpLen:end,:).*wEnergyPortion2(cpLen:end,:)));
% Detect the peak locations in each receive antenna based on the
% threshold. These peak locations correspond to the starting location
% of each OFDM symbol in the received waveform.
loc = cell(R,1);
m = zeros(R,1);
phoFactor = ceil(NSamples/nFFT);
phoExt = [pho; -1*ones(phoFactor*nFFT - NSamples,R)];
for col = 1:R
p1 = reshape(phoExt(:,i),[],phoFactor);
[pks,locTemp] = max(p1);
locTemp = locTemp + (0:phoFactor-1).*nFFT;
indicesToConsider = pks>=thres;
loc{col} = locTemp(indicesToConsider);
m(col) = max(pks);
end
bestAnt = find(m == max(m));
end
function [out,detFlag] = estimateFractionalDopplerShift(rxWave,scs, ...
nFFT,cpLen,thres,flag)
% Estimate the fractional Doppler shift using cyclic prefix
if flag
% Detect the OFDM boundary locations
[loc,wMovSum,~,bestAnt] = ...
HelperNRNTNThroughput.detectOFDMSymbolBoundary(rxWave, ...
nFFT,cpLen,thres);
% Get the average correlation value at the peak locations for the
% first receive antenna having maximum correlation value
wSamples = nan(1,1);
if ~isempty(loc{bestAnt(1)})
wSamples(1) = mean(wMovSum(loc{bestAnt(1)},bestAnt(1)));
end
% Compute the fractional Doppler shift
if ~all(isnan(wSamples))
out = -(mean(angle(wSamples),'omitnan')*scs*1e3)/(2*pi);
% Flag to indicate that there is at least one OFDM symbol
% detected
detFlag = 1;
else
out = 0;
detFlag = 0;
end
else
out = 0;
detFlag = 0;
end
end
function [out,shiftOut] = estimateIntegerDopplerShift(carrier,rx,refInd, ...
refSym,sampleOffset,usePrevShift,useDiffCorr,shiftIn,maxOffset,flag)
% Estimate the integer Doppler shift using demodulation reference signal
arguments
carrier
rx
refInd
refSym
sampleOffset = 0
usePrevShift = false
useDiffCorr = true
shiftIn = 0
maxOffset = 0
flag = false
end
if flag
% Get OFDM information
ofdmInfo = nrOFDMInfo(carrier);
cpLen = ofdmInfo.CyclicPrefixLengths(1); % Highest cyclic prefix length
K = carrier.NSizeGrid*12; % Number of subcarriers
L = carrier.SymbolsPerSlot; % Number of OFDM symbols in slot
P = ceil(max(double(refInd(:))/(K*L))); % Number of layers
% Find the timing offset using differential correlation
offset = HelperNRNTNThroughput.diffcorr( ...
carrier,rx,refInd,refSym);
if offset > maxOffset
offset = 0;
end
% Range of shift values to be used in integer frequency offset
% estimation
if useDiffCorr
% Use offset directly in the shift values
shiftValues = offset+1;
else
shiftValues = sampleOffset + shiftIn;
if ~(usePrevShift && (shiftIn > 0))
% Update range of shift values such that whole cyclic prefix
% length is covered
shiftValues = sampleOffset + (1:(cpLen+offset));
end
end
% Initialize temporary variables
shiftLen = length(shiftValues);
maxValue = complex(zeros(shiftLen,1));
binIndex = zeros(shiftLen,1);
[rxLen,R] = size(rx);
xWave = zeros([rxLen P],'like',rx);
% Generate reference waveform
refGrid = nrResourceGrid(carrier,P);
refGrid(refInd) = refSym;
refWave = nrOFDMModulate(carrier,refGrid,'Windowing',0);
refWave = [refWave; zeros((rxLen-size(refWave,1)),P,'like',refWave)];
% Find the fast Fourier transform (FFT) bin corresponding to
% maximum correlation value for each shift value
for shiftIdx = 1:shiftLen
% Use the waveform from the shift value and append zeros
tmp = rx(shiftValues(shiftIdx):end,:);
rx = [tmp; zeros(rxLen-size(tmp,1),R)];
% Compute the correlation of received waveform with reference
% waveform across different layers and receive antennas
for rIdx = 1:R
for p = 1:P
xWave(:,rIdx,p) = ...
rx(:,rIdx).*conj(refWave(1:length(rx(:,rIdx)),p));
end
end
% Aggregate the correlated waveform across multiple ports and
% compute energy of the resultant for each receive antenna
x1 = sum(xWave,3);
x1P = sum(abs(x1).^2);
% Find the index of first receive antenna which has maximum
% correlation energy
idx = find(x1P == max(x1P),1);
% Combine the received waveform which have maximum correlation
% energy
xWaveCombined = sum(x1(:,idx(1)),2);
% Compute FFT of the resultant waveform
xWaveCombinedTemp = buffer(xWaveCombined,ofdmInfo.Nfft);
xFFT = sum(fftshift(fft(xWaveCombinedTemp)),2);
% Store the value and location of peak
[maxValue(shiftIdx),binIndex(shiftIdx)] = max(xFFT);
end
% FFT bin values
fftBinValues = (-ofdmInfo.Nfft/2:(ofdmInfo.Nfft/2-1))*(ofdmInfo.SampleRate/ofdmInfo.Nfft);
% Find the shift index that corresponds to the maximum of peak
% value of all the shifted waveforms. Use the FFT bin index
% corresponding to this maximum shift index. The FFT bin value
% corresponding to this bin index is the integer frequency offset.
[~,maxId] = max(maxValue);
loc = binIndex(maxId);
out = fftBinValues(loc);
shiftOut = shiftValues(maxId);
else
out = 0;
shiftOut = 1+sampleOffset;
end
end
function out = compensateDopplerShift(inWave,fs,fdSat,flag)
% Perform Doppler shift correction
if flag
out = frequencyOffset(inWave,fs,-fdSat);
else
out = inWave;
end
end
function [offset,mag] = diffcorr(carrier,rx,refInd,refSym)
% Perform differential correlation for the received signal
% Get the number of subcarriers, OFDM symbols, and layers
K = carrier.NSizeGrid*12; % Number of subcarriers
L = carrier.SymbolsPerSlot; % Number of OFDM symbols in slot
P = ceil(max(double(refInd(:))/(K*L))); % Number of layers
% Generate the reference signal
refGrid = nrResourceGrid(carrier,P);
refGrid(refInd) = refSym;
refWave = nrOFDMModulate(carrier,refGrid,'Windowing',0);
% Get the differential of the received signal and reference signal
waveform = conj(rx(1:end-1,:)).*rx(2:end,:);
ref = conj(refWave(1:end-1,:)).*refWave(2:end,:);
[T,R] = size(waveform);
% To normalize the xcorr behavior, pad the input waveform to make it
% longer than the reference signal
refLen = size(ref,1);
waveformPad = [waveform; zeros([refLen-T R],'like',waveform)];
% Store correlation magnitude for each time sample, receive antenna and
% port
mag = zeros([max(T,refLen) R P],'like',waveformPad);
for r = 1:R
for p = 1:P
% Correlate the given antenna of the received signal with the
% given port of the reference signal
refcorr = xcorr(waveformPad(:,r),ref(:,p));
mag(:,r,p) = abs(refcorr(T:end));
end
end
% Sum the magnitudes of the ports
mag = sum(mag,3);
% Find timing peak in the sum of the magnitudes of the receive antennas
[~,peakindex] = max(sum(mag,2));
offset = peakindex - 1;
end
function [tO,fO] = jointTimeFreq(carrier,rx,varargin)
% Perform joint time-frequency synchronization
% jointTimeFreq(carrier,rx,refInd,refSym,fSearchSpace)
% jointTimeFreq(carrier,rx,refGrid,fSearchSpace)
fSearchSpace = varargin{end};
numFreqVals = length(fSearchSpace);
peakVal = zeros(numFreqVals,1);
peakIdx = peakVal;
ofdmInfo = nrOFDMInfo(carrier);
fs = ofdmInfo.SampleRate;
for fIdx = 1:numFreqVals
rxCorrected = ...
HelperNRNTNThroughput.compensateDopplerShift( ...
rx,fs,fSearchSpace(fIdx),true);
[~,corr] = nrTimingEstimate(carrier,rxCorrected,varargin{1:end-1});
corr = sum(abs(corr),2);
[peakVal(fIdx),peakIdx(fIdx)] = max(corr);
end
[~,id] = max(peakVal);
% Estimate frequency shift and timing offset
fO = fSearchSpace(id);
tO = peakIdx(id)-1;
end
% Functions to model power amplifier nonlinearity
function out = paMemorylessGaAs2Dot1GHz(in)
% 2.1 GHz GaAs
absIn = abs(in).^(2*(1:7));
out = (-0.618347-0.785905i) * in + (2.0831-1.69506i) * in .* absIn(:,1) + ...
(-14.7229+16.8335i) * in .* absIn(:,2) + (61.6423-76.9171i) * in .* absIn(:,3) + ...
(-145.139+184.765i) * in .* absIn(:,4) + (190.61-239.371i)* in .* absIn(:,5) + ...
(-130.184+158.957i) * in .* absIn(:,6) + (36.0047-42.5192i) * in .* absIn(:,7);
end
function out = paMemorylessGaN2Dot1GHz(in)
% 2.1 GHz GaN
absIn = abs(in).^(2*(1:4));
out = (0.999952-0.00981788i) * in + (-0.0618171+0.118845i) * in .* absIn(:,1) + ...
(-1.69917-0.464933i) * in .* absIn(:,2) + (3.27962+0.829737i) * in .* absIn(:,3) + ...
(-1.80821-0.454331i) * in .* absIn(:,4);
end
function out = paMemorylessCMOS28GHz(in)
% 28 GHz CMOS
absIn = abs(in).^(2*(1:7));
out = (0.491576+0.870835i) * in + (-1.26213+0.242689i) * in .* absIn(:,1) + ...
(7.11693+5.14105i) * in .* absIn(:,2) + (-30.7048-53.4924i) * in .* absIn(:,3) + ...
(73.8814+169.146i) * in .* absIn(:,4) + (-96.7955-253.635i)* in .* absIn(:,5) + ...
(65.0665+185.434i) * in .* absIn(:,6) + (-17.5838-53.1786i) * in .* absIn(:,7);
end
function out = paMemorylessGaN28GHz(in)
% 28 GHz GaN
absIn = abs(in).^(2*(1:5));
out = (-0.334697-0.942326i) * in + (0.89015-0.72633i) * in .* absIn(:,1) + ...
(-2.58056+4.81215i) * in .* absIn(:,2) + (4.81548-9.54837i) * in .* absIn(:,3) + ...
(-4.41452+8.63164i) * in .* absIn(:,4) + (1.54271-2.94034i)* in .* absIn(:,5);
end
function paChar = getDefaultLookup
% The operating specification for the LDMOS-based Doherty
% amplifier are:
% * A frequency of 2110 MHz
% * A peak power of 300 W
% * A small signal gain of 61 dB
% Each row in HAV08_Table specifies Pin (dBm), gain (dB), phase
% shift (degrees) as derived from figure 4 of Hammi, Oualid, et
% al. "Power amplifiers' model assessment and memory effects
% intensity quantification using memoryless post-compensation
% technique." IEEE Transactions on Microwave Theory and
% Techniques 56.12 (2008): 3170-3179.
HAV08_Table =...
[-35,60.53,0.01;
-34,60.53,0.01;
-33,60.53,0.08;
-32,60.54,0.08;
-31,60.55,0.1;
-30,60.56,0.08;
-29,60.57,0.14;
-28,60.59,0.19;
-27,60.6,0.23;
-26,60.64,0.21;
-25,60.69,0.28;
-24,60.76,0.21;
-23,60.85,0.12;
-22,60.97,0.08;
-21,61.12,-0.13;
-20,61.31,-0.44;
-19,61.52,-0.94;
-18,61.76,-1.59;
-17,62.01,-2.73;
-16,62.25,-4.31;
-15,62.47,-6.85;
-14,62.56,-9.82;
-13,62.47,-12.29;
-12,62.31,-13.82;
-11,62.2,-15.03;
-10,62.15,-16.27;
-9,62,-18.05;
-8,61.53,-20.21;
-7,60.93,-23.38;
-6,60.2,-26.64;
-5,59.38,-28.75];
% Convert the second column of the HAV08_Table from gain to
% Pout for use by the memoryless nonlinearity System object.
paChar = HAV08_Table;
paChar(:,2) = paChar(:,1) + paChar(:,2);
end
function out = getDefaultCoefficients
% The 2.44 GHz memory polynomial model defined in TR 38.803
% Appendix A. Memory-polynomial depth is 5 and
% memory-polynomial degree is 5. Rows in the output corresponds
% to memory depth.
out = [20.0875+0.4240i -6.3792-0.5507i 0.5809+0.0644i 1.6619+0.1040i -0.3561-0.1033i; ...
-59.8327-34.7815i -2.4805+0.9344i 4.2741+0.7696i -2.0014-2.3785i -1.2566+1.0495i; ...
3.2738e2+8.4121e2i 4.4019e2-3.0714e1i -3.5935e2-9.9152e0i 1.6961e2+7.3829e1i -4.1661-21.1090i; ...
-1.6352e3-5.5757e3i -2.5782e3+3.3332e2i 1.9915e3-1.4479e2i -9.0167e2-5.4617e2i -93.1907+14.2774i; ...
2.3022e3+1.2348e4i 4.6476e3-1.4477e3i -2.9998e3+1.6071e3i 9.1856e2+9.8066e2i 8.2544e2+6.1424e2i].';
end
function [txWaveform,info] = generatePDSCHWaveform(carrier,pdsch,dlsch,wtx,dt,numSlots)
arguments
carrier
pdsch
dlsch = struct
wtx = eye(pdsch.NumLayers)
dt = "double"
numSlots = 1
end
% Initialize variables
nTx = size(wtx,2);
tmpGrid = nrResourceGrid(carrier,nTx,OutputDataType=dt);
pdschGrid = repmat(tmpGrid,[1 numSlots 1]);
refGrid = pdschGrid;
nSlotSymb = carrier.SymbolsPerSlot;
initialNSlot = carrier.NSlot;
numCW = pdsch.NumCodewords;
% Process loop for each slot
for slotIdx = 0:numSlots-1
[slotGrid,refSlotGrid] = deal(tmpGrid);
carrier.NSlot = initialNSlot + slotIdx;
% Perform PDSCH modulation
[pdschIndices,pdschIndicesInfo] = nrPDSCHIndices(carrier,pdsch);
if isempty(fieldnames(dlsch))
% Update the codeword with valid bits
cw = cell(1,numCW);
for i = 1:numCW
cw{i} = randi([0 1],pdschIndicesInfo.G(i),1);
end
else
% Encode with the inputs provided in the dlsch
% structure
cw = dlsch.Encoder(pdsch.Modulation,pdsch.NumLayers, ...
pdschIndicesInfo.G,dlsch.RedundancyVersion, ...
dlsch.HARQProcessID);
end
if ~isempty(cw)
pdschSymbols = nrPDSCH(carrier,pdsch,cw,OutputDataType=dt);
% Perform implementation-specific PDSCH MIMO precoding
% and mapping
[pdschAntSymbols,pdschAntIndices] = nrPDSCHPrecode( ...
carrier,pdschSymbols,pdschIndices,wtx);
slotGrid(pdschAntIndices) = pdschAntSymbols;
end
% Perform implementation-specific PDSCH DM-RS MIMO
% precoding and mapping
dmrsSymbols = nrPDSCHDMRS(carrier,pdsch,OutputDataType=dt);
dmrsIndices = nrPDSCHDMRSIndices(carrier,pdsch);
[dmrsAntSymbols,dmrsAntIndices] = nrPDSCHPrecode( ...
carrier,dmrsSymbols,dmrsIndices,wtx);
slotGrid(dmrsAntIndices) = dmrsAntSymbols;
refSlotGrid(dmrsAntIndices) = dmrsAntSymbols;
% Perform implementation-specific PDSCH PT-RS MIMO
% precoding and mapping
ptrsSymbols = nrPDSCHPTRS(carrier,pdsch,OutputDataType=dt);
ptrsIndices = nrPDSCHPTRSIndices(carrier,pdsch);
[ptrsAntSymbols,ptrsAntIndices] = nrPDSCHPrecode( ...
carrier,ptrsSymbols,ptrsIndices,wtx);
slotGrid(ptrsAntIndices) = ptrsAntSymbols;
refSlotGrid(ptrsAntIndices) = ptrsAntSymbols;
% Map to the grid containing multiple slots
symIdx = (nSlotSymb*slotIdx)+1:(nSlotSymb*(slotIdx+1));
pdschGrid(:,symIdx,:) = slotGrid;
refGrid(:,symIdx,:) = refSlotGrid;
end
% Perform OFDM modulation
carrier.NSlot = initialNSlot;
[txWaveform,info] = nrOFDMModulate(carrier,pdschGrid);
% Append the resource grids to output structure
info.ResourceGrid = pdschGrid;
info.ReferenceGrid = refGrid;
end
function [slotTimes,symLen] = getSlotTimes(nSymbSlot,sl,fs,nf,dt)
% Get the slot time
symLen = cumsum(sl); % Lengths of each OFDM symbol in a slot
nSubFrames = 10;
samples = [0 symLen(nSymbSlot:nSymbSlot:end-1)]'./fs;
subframeTimes = (0:(nf*nSubFrames)-1)*1e-3;
slotTimes = cast(reshape(samples+subframeTimes,[],1),dt);
end
function info = initializeDelayObjects(simParameters, waveformInfo, varargin)
%initializeDelayObjects Initialize delay objects for NR NTN links.
%
% info = initializeDelayObjects(simParameters, waveformInfo)
% computes delay and path loss based on simParameters and waveformInfo.
%
% info = initializeDelayObjects(simParameters, waveformInfo, delayInSeconds)
% uses the provided delayInSeconds instead of computing it.
% Parse optional input
if nargin > 2 && ~isempty(varargin{1})
delayInSeconds = varargin{1};
computeGeometry = false;
if simParameters.EnableDelay==0
delayModel = "None";
else
delayModel = simParameters.LinkVariationModel;
end
else
computeGeometry = true;
delayModel = simParameters.DelayModel;
end
% Compute geometry-based parameters if needed
if computeGeometry
c = physconst("lightspeed");
lambda = c / simParameters.CarrierFrequency;
slotTimes = HelperNRNTNThroughput.getSlotTimes( ...
simParameters.Carrier.SymbolsPerSlot, waveformInfo.SymbolLengths, ...
waveformInfo.SampleRate, simParameters.NFrames, simParameters.DataType);
if (delayModel == "Time-varying")
SU = slantRangeCircularOrbit(simParameters.ElevationAngle, ...
simParameters.SatelliteAltitude, simParameters.MobileAltitude, slotTimes);
lambda = c / simParameters.CarrierFrequency;
pathLoss = fspl(double(SU), lambda) .* simParameters.IncludeFreeSpacePathLoss;
delayInSeconds = SU ./ c;
else
SU = slantRangeCircularOrbit(simParameters.ElevationAngle, ...
simParameters.SatelliteAltitude, simParameters.MobileAltitude);
pathLoss = fspl(SU, lambda) * simParameters.IncludeFreeSpacePathLoss; % dB
pathLoss = repmat(pathLoss,1,numel(slotTimes)); % dB
delayInSeconds = cast(SU ./ c, simParameters.DataType);
end
else
SU = [];
pathLoss = [];
end
% Initialize configuration objects for delay modeling
if delayModel == "None"
staticDelay = dsp.Delay(Length=0);
variableFractionalDelay = 0;
variableIntegerDelay = 0;
delayInSeconds = 0;
numVariableFracDelaySamples = 0;
numVariableIntegSamples = 0;
maxVarPropDelay = 0;
elseif delayModel == "Static"
delayInSamples = delayInSeconds .* waveformInfo.SampleRate;
integDelaySamples = floor(delayInSamples);
fracDelaySamples = delayInSamples - integDelaySamples;
numVariableFracDelaySamples = repmat(fracDelaySamples, 1, ...
(simParameters.Carrier.SlotsPerFrame * simParameters.NFrames) + 1);
staticDelay = dsp.Delay(Length=integDelaySamples);
variableFractionalDelay = dsp.VariableFractionalDelay( ...
InterpolationMethod="Farrow", ...
FarrowSmallDelayAction="Use off-centered kernel", ...
MaximumDelay=1);
variableIntegerDelay = 0;
numVariableIntegSamples = 0;
maxVarPropDelay = 0;
elseif delayModel == "Time-varying"
delayInSamples = delayInSeconds .* waveformInfo.SampleRate;
integDelaySamples = floor(delayInSamples);
numStaticDelaySamples = min(integDelaySamples);
remVariableDelaySamples = delayInSamples - numStaticDelaySamples;
staticDelay = dsp.Delay(Length=numStaticDelaySamples);
numVariableIntegSamples = floor(remVariableDelaySamples);
numVariableIntegSamples(numVariableIntegSamples < 0) = 0;
maxVarPropDelay = max(numVariableIntegSamples) + 2;
variableIntegerDelay = dsp.VariableIntegerDelay( ...
MaximumDelay=maxVarPropDelay);
numVariableFracDelaySamples = remVariableDelaySamples - numVariableIntegSamples;
variableFractionalDelay = dsp.VariableFractionalDelay( ...
InterpolationMethod="Farrow", ...
FarrowSmallDelayAction="Use off-centered kernel", ...
MaximumDelay=1);
else
error('Unknown DelayModel: %s', delayModel);
end
% Build output structure
info = struct;
info.StaticDelay = staticDelay;
info.VariableIntegerDelay = variableIntegerDelay;
info.VariableFractionalDelay = variableFractionalDelay;
info.MaxVariablePropDelay = maxVarPropDelay;
info.NumVariableIntegerDelaySamples = numVariableIntegSamples;
info.NumVariableFractionalDelaySamples = numVariableFracDelaySamples;
info.DelayInSeconds = delayInSeconds;
% Add path loss and slant distance only if computed
if ~isempty(SU)
info.PathLoss = pathLoss;
info.SlantDistance = SU;
end
end
function [hpa,hpaDelay,paInputScaleFactor] = initializePA( ...
paModel,hasMemory,paCharacteristics,coefficients)
% Initialize the power amplifier function handle or System
% object depending on the input configuration
paInputScaleFactor = 0; % in dB
hpaDelay = 0;
if hasMemory == 1
hpa = rf.PAmemory; % Requires RF Toolbox
if isempty(coefficients)
hpa.CoefficientMatrix = ...
HelperNRNTNThroughput.getDefaultCoefficients;
paInputScaleFactor = -35;
else
hpa.CoefficientMatrix = coefficients;
end
hpaDelay = size(hpa.CoefficientMatrix,1)-1;
else
if paModel == "Custom"
if isempty(paCharacteristics)
hpa = comm.MemorylessNonlinearity(Method="Lookup table", ...
Table=HelperNRNTNThroughput.getDefaultLookup);
paInputScaleFactor = -35;
else
hpa = comm.MemorylessNonlinearity(Method="Lookup table", ...
Table=paCharacteristics);
end
elseif paModel == "2.1GHz GaAs"
hpa = @(in) HelperNRNTNThroughput.paMemorylessGaAs2Dot1GHz(in);
elseif paModel == "2.1GHz GaN"
hpa = @(in) HelperNRNTNThroughput.paMemorylessGaN2Dot1GHz(in);
elseif paModel == "28GHz CMOS"
hpa = @(in) HelperNRNTNThroughput.paMemorylessCMOS28GHz(in);
else % "28GHz GaN"
hpa = @(in) HelperNRNTNThroughput.paMemorylessGaN28GHz(in);
end
end
end
function params = computeAccessSlotParameters(simParameters,waveformInfo)
%computeAccessSlotParameters Finds the earliest access time
%between a moving UE and a satellite, and computes elevation
%angle, path loss, Doppler shift, and propagation delay for
%each slot.
% Initialize scenario sample time and stop time
ScenarioStopTime = simParameters.ScenarioStartTime + seconds(simParameters.ScenarioRunTime);
tSlot = HelperNRNTNThroughput.getSlotTimes( ...
simParameters.Carrier.SymbolsPerSlot,waveformInfo.SymbolLengths, ...
waveformInfo.SampleRate,simParameters.NFrames,simParameters.DataType);
ScenarioSampleTime = min(diff(tSlot));
% Create a satelliteScenario object
sc = satelliteScenario(simParameters.ScenarioStartTime,ScenarioStopTime,double(ScenarioSampleTime));
% Add a moving UE to the scenario
ueStartLLA = itrf2geographic(simParameters.UEPosition);
ueEndPos = simParameters.UEPosition+simParameters.UEVelocity*simParameters.ScenarioRunTime;
ueEndLLA = itrf2geographic(ueEndPos);
trajectory = geoTrajectory([ueStartLLA'; ueEndLLA'],[0 simParameters.ScenarioRunTime]);
gs = platform(sc,trajectory,"Name","UE");
% Add a satellite to the scenario using Keplerian elements
sat = satellite(sc,simParameters.SemiMajorAxis, ...
simParameters.Eccentricity, simParameters.Inclination, ...
simParameters.RAAN, simParameters.Argofperiapsis, ...
simParameters.TrueAnomaly, Name = "Satellite 1");
% Compute required access duration to transmit the entire data
totalTime = simParameters.NFrames*10e-3; % 10msec per frame
% Find the Access intervals
ac = access(sat, gs);
accessIntrvl = accessIntervals(ac);
if isempty(accessIntrvl)
error("No access intervals found: The satellite is not visible to the UE. " + ...
"Check satellite orbital parameters, UE position and velocity, " + ...
"and scenario start time and duration.");
end
% Find first interval with sufficient duration
disp('Access interval table (periods when satellite is visible to UE):');
disp(accessIntrvl)
if simParameters.LinkVariationModel=="Static"
accessStartTime = accessIntrvl.StartTime(1);
else
accessStartTime = [];
for idx=1:size(accessIntrvl,2)
if totalTime<=seconds(accessIntrvl.EndTime(idx)-accessIntrvl.StartTime(idx))
accessStartTime = accessIntrvl.StartTime(idx);
break;
end
end
if isempty(accessStartTime)
error("Insufficient visible time: The satellite's visibility duration is not long enough for continuous data transmission.")
end
end
disp("Transmission start time : " +string(accessStartTime) + " UTC")
% Calculate slot times
if simParameters.LinkVariationModel ~= "Time-varying"
slotTimes = accessStartTime;
else
tSlot = HelperNRNTNThroughput.getSlotTimes( ...
simParameters.Carrier.SymbolsPerSlot, ...
waveformInfo.SymbolLengths, ...
waveformInfo.SampleRate, ...
simParameters.NFrames, simParameters.DataType);
slotTimes = accessStartTime + seconds(tSlot);
end
% Preallocate
nSlots = numel(slotTimes);
el = zeros(nSlots, 1);
pathLoss = zeros(nSlots, 1);
preCompDoppler = zeros(nSlots, 1);
satDoppler = zeros(nSlots, 1);
delayInSeconds = zeros(nSlots, 1);
% Wavelength
c = physconst("lightspeed");
lambda = c / simParameters.CarrierFrequency;
uevel = [0;0;0]; % Stationary UE
for k = 1:nSlots
% Elevation angle
[~, el(k)] = aer(gs, sat, slotTimes(k));
% Doppler due to satellite and UE motion
preCompDoppler(k) = simParameters.EnableDoppler*dopplershift(gs, sat, slotTimes(k), ...
Frequency=simParameters.CarrierFrequency);
% Doppler shift only due to satellite motion
[satpos,satvel] = states(sat,slotTimes(k),CoordinateFrame='ecef');
uepos = states(gs,slotTimes(k),CoordinateFrame='ecef');
satDoppler(k) = simParameters.EnableDoppler*satcom.internal.dopplerShift(simParameters.CarrierFrequency,uepos,satpos,uevel,satvel);
% Delay
delay = latency(gs, sat, slotTimes(k));
delayInSeconds(k) = simParameters.EnableDelay*delay;
% Free space path loss
range = delay * c;
pathLoss(k) = simParameters.EnablePathLoss*fspl(double(range), lambda);
end
% Total number of slots
totalSlots = simParameters.NFrames*simParameters.Carrier.SlotsPerFrame;
% Expand to totalSlots if needed
if nSlots == 1
el = repmat(el, totalSlots, 1);
pathLoss = repmat(pathLoss, totalSlots, 1);
preCompDoppler = repmat(preCompDoppler, totalSlots, 1);
satDoppler = repmat(satDoppler, totalSlots, 1);
end
params.ElevationAngle = el;
params.PathLoss = pathLoss;
params.PreCompDopplerShift = preCompDoppler;
params.SatelliteDopplerShift = satDoppler;
params.DelayInSeconds = delayInSeconds;
params.SlotTimes = slotTimes;
params.AccessStartTime = accessStartTime;
end
function [txWaveform,info] = generatePUSCHWaveform(carrier,pusch,ulsch,wtx,dmrsPower,dt,numSlots)
%generatePUSCHWaveform generates a PUSCH (Physical Uplink
%Shared Channel) transmit waveform
arguments
carrier
pusch
ulsch = struct
wtx = eye(pusch.NumLayers)
dmrsPower = 3 % dB
dt = "double"
numSlots = 1
end
% Initialize variables
nTx = size(wtx,2); % Number of transmit antennas
tmpGrid = nrResourceGrid(carrier,nTx,OutputDataType=dt);
puschGrid = repmat(tmpGrid,[1 numSlots 1]);
refGrid = puschGrid;
nSlotSymb = carrier.SymbolsPerSlot;
initialNSlot = carrier.NSlot;
numCW = pusch.NumCodewords;
% Process loop for each slot
for slotIdx = 0:numSlots-1
[slotGrid,refSlotGrid] = deal(tmpGrid);
carrier.NSlot = initialNSlot + slotIdx;
% Generate PUSCH indices and PT-RS indices
[puschIndices,puschIndicesInfo,ptrsIndices] = nrPUSCHIndices(carrier,pusch);
if isempty(fieldnames(ulsch))
% Update the codeword with valid bits
cw = cell(1,numCW);
for i = 1:numCW
cw{i} = randi([0 1],puschIndicesInfo.G(i),1);
end
else
% Perform UL-SCH encoding with the inputs provided in the ulsch structure
cw = ulsch.Encoder(pusch.Modulation,pusch.NumLayers, ...
puschIndicesInfo.G,ulsch.RedundancyVersion, ...
ulsch.HARQProcessID,ulsch.CBGTI);
end
% Perform PUSCH modulation
if ~isempty(cw)
[puschSymbols, ptrsSymbols] = nrPUSCH(carrier,pusch,cw,OutputDataType=dt);
% Implementation-specific PUSCH MIMO precoding and mapping. This
% MIMO precoding step is in addition to any codebook based
% MIMO precoding done during PUSCH modulation above
[~,puschAntIndices] = nrExtractResources(puschIndices,slotGrid);
slotGrid(puschAntIndices) = puschSymbols * wtx;
end
% Implementation-specific PUSCH DM-RS MIMO precoding and mapping
% using the nrPDSCHPrecode function which supports PUSCH MIMO
% precoding as well. The first DM-RS creation includes codebook
% based MIMO precoding if applicable
dmrsSymbols = db2mag(dmrsPower)*nrPUSCHDMRS(carrier,pusch,OutputDataType=dt);
dmrsIndices = nrPUSCHDMRSIndices(carrier,pusch);
[dmrsAntSymbols,dmrsAntIndices] = nrPDSCHPrecode( ...
carrier,dmrsSymbols,dmrsIndices,wtx);
slotGrid(dmrsAntIndices) = dmrsAntSymbols;
refSlotGrid(dmrsAntIndices) = dmrsAntSymbols;
% Implementation-specific PUSCH PT-RS MIMO precoding and mapping
% using the nrPDSCHPrecode function which supports PUSCH MIMO
% precoding as well. If TransformPrecoding = 1, puschSymbols
% already include PT-RS symbols
if ~pusch.TransformPrecoding && ~isempty(ptrsSymbols)
[ptrsAntSymbols,ptrsAntIndices] = nrPDSCHPrecode(carrier,ptrsSymbols,ptrsIndices,wtx);
slotGrid(ptrsAntIndices) = ptrsAntSymbols;
refSlotGrid(ptrsAntIndices) = ptrsAntSymbols;
end
% Map to the grid containing multiple slots
symIdx = (nSlotSymb*slotIdx)+1:(nSlotSymb*(slotIdx+1));
puschGrid(:,symIdx,:) = slotGrid;
refGrid(:,symIdx,:) = refSlotGrid;
end
% Perform OFDM modulation
[txWaveform,info] = nrOFDMModulate(carrier,puschGrid);
% Append the resource grids to output structure
info.ResourceGrid = puschGrid;
info.ReferenceGrid = refGrid;
end
function [channel, maxChDelay, ueDoppler] = configureNTNChannel(simParameters,elevation,sampleRate)
%configureNTNChannel Configure NTN channel object and compute max channel delay.
mobileSpeed = norm(simParameters.UEVelocity); % m/s
c = physconst("LightSpeed");
ueDoppler = mobileSpeed*simParameters.CarrierFrequency/c;
if strcmpi(simParameters.NTNChannelType, 'Narrowband')
channel = p681LMSChannel;
channel.Environment = simParameters.Environment;
channel.AzimuthOrientation = simParameters.AzimuthOrientation;
channel.CarrierFrequency = simParameters.CarrierFrequency;
channel.ElevationAngle = elevation(1);
channel.MobileSpeed = mobileSpeed;
elseif strcmpi(simParameters.NTNChannelType, 'TDL')
channel = nrTDLChannel;
swapTransmitAndReceive(channel);
channel.TransmissionDirection = "Uplink";
channel.DelayProfile = simParameters.DelayProfile;
channel.DelaySpread = simParameters.DelaySpread;
channel.MaximumDopplerShift = ueDoppler;
channel.NumTransmitAntennas = simParameters.NumTransmitAntennas;
channel.NumReceiveAntennas = simParameters.NumReceiveAntennas;
elseif strcmpi(simParameters.NTNChannelType, 'CDL')
channel = nrCDLChannel;
swapTransmitAndReceive(channel);
channel.DelayProfile = simParameters.DelayProfile;
channel.SatelliteElevationAngle = elevation(1);
channel.DelaySpread = simParameters.DelaySpread;
channel.TransmitAntennaArray.Size = simParameters.TxArraySize;
channel.ReceiveAntennaArray.Size = simParameters.RxArraySize;
channel.CarrierFrequency = simParameters.CarrierFrequency;
else
error('Unknown NTNChannelType: %s', simParameters.NTNChannelType);
end
% Common channel parameters
channel.SampleRate = sampleRate;
channel.RandomStream = "mt19937ar with seed";
channel.Seed = 73;
% Maximum channel delay computation
chInfo = info(channel);
maxChDelay = ceil(max(chInfo.PathDelays * channel.SampleRate)) + ...
chInfo.ChannelFilterDelay;
% CDL-specific Doppler and orientation settings
if simParameters.NTNChannelType == "CDL"
channel.UTDirectionOfTravel = [chInfo.AnglesAoA(1) chInfo.AnglesAoD(1); ... % [Rx_Sat_Azi Tx_UE_Azi; Rx_Sat_Zen Tx_UE_Zen]
chInfo.AnglesZoA(1) chInfo.AnglesZoD(1)];
channel.ReceiveArrayOrientation = [chInfo.AnglesAoA(1); chInfo.AnglesZoA(1) - 90; 0]; % Satellite RX array pointing toward UE
end
end
function initialOffset = estimateInitialTimingOffset(rxCarrier,rxData,dmrsParams,syncModel,inifValsVec)
%estimateInitialTimingOffset Estimate initial timing offset based on sync model
if syncModel == "auto corr"
initialOffset = nrTimingEstimate(rxCarrier,rxData,dmrsParams.DMRSIndices,dmrsParams.DMRSSymbols);
elseif syncModel == "diff corr"
initialOffset = HelperNRNTNThroughput.diffcorr( ...
rxCarrier,rxData,dmrsParams.DMRSIndices,dmrsParams.DMRSSymbols);
else
initialOffset = HelperNRNTNThroughput.jointTimeFreq( ...
rxCarrier,rxData,dmrsParams.DMRSIndices,dmrsParams.DMRSSymbols,inifValsVec);
end
end
function [rxWaveform,offset,shiftOut] = compensateRxDoppler(rxWaveform,rxDopplerParams,dmrsParams, waveinfo)
%compensateRxDoppler Compensate for Doppler shift in received waveform
if rxDopplerParams.CompensationMethod == "joint time-freq"
% Perform joint time-frequency synchronization
[offset,fOEst] = HelperNRNTNThroughput.jointTimeFreq( ...
rxDopplerParams.RxCarrier,rxWaveform,dmrsParams.DMRSIndices,dmrsParams.DMRSSymbols,rxDopplerParams.FreqSearchRange);
% Compensate Doppler shift
rxWaveform = HelperNRNTNThroughput.compensateDopplerShift( ...
rxWaveform,waveinfo.SampleRate, ...
fOEst,true);
% Estimate and compensate the residual (fractional) Doppler shift
fractionalDopplerShift = ...
HelperNRNTNThroughput.estimateFractionalDopplerShift( ...
rxWaveform,rxDopplerParams.RxCarrier.SubcarrierSpacing,waveinfo.Nfft, ...
waveinfo.CyclicPrefixLengths(2),0,true);
rxWaveform = HelperNRNTNThroughput.compensateDopplerShift( ...
rxWaveform,waveinfo.SampleRate, ...
fractionalDopplerShift,true);
shiftOut = [];
else
% Perform fractional Doppler frequency shift estimation and
% compensation. Use the cyclic prefix in the OFDM waveform to
% compute the fractional Doppler shift.
[fractionalDopplerShift,detFlag] = ...
HelperNRNTNThroughput.estimateFractionalDopplerShift( ...
rxWaveform,rxDopplerParams.RxCarrier.SubcarrierSpacing,waveinfo.Nfft, ...
waveinfo.CyclicPrefixLengths(2),rxDopplerParams.Threshold, ...
true);
rxWaveform = HelperNRNTNThroughput.compensateDopplerShift( ...
rxWaveform,waveinfo.SampleRate, ...
fractionalDopplerShift,true);
% Perform integer Doppler frequency shift estimation and
% compensation. Use the demodulation reference signals to
% compute the integer Doppler shift.
[integerDopplerShift,shiftOut] = ...
HelperNRNTNThroughput.estimateIntegerDopplerShift( ...
rxDopplerParams.RxCarrier,rxWaveform,dmrsParams.DMRSIndices, ...
dmrsParams.DMRSSymbols,rxDopplerParams.SampleOffset, ...
rxDopplerParams.UsePrevShift,rxDopplerParams.UseDiffCorrFlag,rxDopplerParams.Shift,rxDopplerParams.TotalDelay, ...
detFlag);
rxWaveform = HelperNRNTNThroughput.compensateDopplerShift( ...
rxWaveform,waveinfo.SampleRate, ...
integerDopplerShift,true);
offset = [];
end
end
function [decbits,blkerr,cbgerr,info] = recoverPUSCHBits(rxWaveform,rxCarrier,dmrsParams,pusch,ulschDecParams)
%recoverPUSCHBits Recovers bits from the received waveform
% Perform OFDM demodulation on the received data to recreate the
% resource grid. Include zero padding in the event that practical
% synchronization results in an incomplete slot being demodulated.
rxGrid = nrOFDMDemodulate(rxCarrier,rxWaveform);
[K,L,R] = size(rxGrid);
if (L < rxCarrier.SymbolsPerSlot)
rxGrid = cat(2,rxGrid,zeros(K,rxCarrier.SymbolsPerSlot-L,R));
end
% Perform least squares channel estimation between the received
% grid and each transmission layer, using the PUSCH DM-RS for each
% layer. This channel estimate includes the effect of transmitter
% precoding.
[estChannelGrid,noiseEst] = nrChannelEstimate(rxCarrier,rxGrid,...
dmrsParams.DMRSIndices,dmrsParams.DMRSSymbols,'CDMLengths',pusch.DMRS.CDMLengths);
[refPUSCHIndices,refPUSCHIndicesInfo] = nrPUSCHIndices(rxCarrier,pusch);
% Get PUSCH REs from the received grid and estimated channel grid
[puschRx,puschHest] = nrExtractResources(...
refPUSCHIndices,rxGrid,estChannelGrid);
% Perform equalization
[puschEq,csi] = nrEqualizeMMSE(puschRx,puschHest,noiseEst);
% Common phase error (CPE) compensation
if pusch.EnablePTRS
puschEq = hCompensateCPE(rxCarrier,pusch,puschEq,rxGrid,estChannelGrid,noiseEst);
end
if ~any(isnan(puschEq))
% Decode PUSCH symbols
[ulschLLRs,rxSymbols] = nrPUSCHDecode(rxCarrier,pusch,puschEq,noiseEst);
% Apply channel state information to LLRs supporting transform precoding & PT-RS.
ulschLLRs = HelperNRNTNThroughput.applyCSIToLLRs(rxCarrier, pusch, numel(refPUSCHIndicesInfo.PRBSet), rxSymbols, ulschLLRs, csi);
% Decode the UL-SCH transport channel
[decbits,blkerr,cbgerr] = ulschDecParams.Decoder(ulschLLRs,pusch.Modulation,...
pusch.NumLayers,ulschDecParams.RedundancyVersion,...
ulschDecParams.HARQProcessID,ulschDecParams.CBGTI);
else
decbits = [];
blkerr = true;
cbgerr = true;
end
info = refPUSCHIndicesInfo;
end
function ulschLLRs = applyCSIToLLRs(rxCarrier, pusch, MRB, rxSymbols, ulschLLRs, csi)
%applyCSIToLLRs Apply channel state information to LLRs,
%supporting transform precoding & PT-RS.
% Ensure LLRs and symbols are cell arrays (one per codeword)
if ~iscell(ulschLLRs)
ulschLLRs = num2cell(ulschLLRs, 1);
rxSymbols = num2cell(rxSymbols, 1);
end
% Apply channel state information (CSI) produced by the equalizer,
% including the effect of transform precoding if enabled
if pusch.TransformPrecoding
MSC = MRB * 12; % Number of subcarriers
% Undo transform precoding for CSI, normalize
csi = nrTransformDeprecode(csi, MRB) / sqrt(MSC);
% Repeat each de-precoded CSI value across its corresponding subcarriers
csi = repmat(csi((1:MSC:end).'), 1, MSC).';
if pusch.EnablePTRS
% Remove PT-RS CSI
ptrsLayerIndices = nrPUSCHPTRSIndices(rxCarrier, pusch);
csi(ptrsLayerIndices) = [];
end
% Reshape CSI to match the shape of rxSymbols
csi = reshape(csi, size(rxSymbols{:}));
end
% Layer de-map CSI
csi = nrLayerDemap(csi);
% Apply CSI to LLRs for each codeword
for cwIdx = 1:pusch.NumCodewords
Qm = length(ulschLLRs{cwIdx}) / length(rxSymbols{cwIdx}); % Bits per symbol
csi{cwIdx} = repmat(csi{cwIdx}.', Qm, 1); % Expand by each bit per symbol
ulschLLRs{cwIdx} = ulschLLRs{cwIdx} .* csi{cwIdx}(:); % Scale by CSI
end
end
end
end
function lla = itrf2geographic(ecef)
%itrf2geographic converts Earth-Centered Earth-Fixed (ECEF)
%coordinates to geodetic coordinates: latitude (degrees),
%longitude (degrees), and altitude (meters).
geographicCoordinates = matlabshared.orbit.internal.Transforms.itrf2geographic(ecef);
% Convert to degrees and get the LLA coordinates
lla = [rad2deg(geographicCoordinates(1,1:end)); ...
rad2deg(geographicCoordinates(2,1:end)); ...
geographicCoordinates(3,1:end)];
end