Main Content

Accelerate End-to-End Simulation with Frequency-Domain Channel Modeling

Since R2023b

This example shows how to use frequency-domain channel modeling in an end-to-end simulation using 5G Toolbox™ features. The example demonstrates this workflow by modeling an NR physical downlink shared channel (PDSCH). The steps for a physical uplink shared channel (PUSCH) simulation are similar.


Time-domain modeling requires OFDM modulation, time-domain channel filtering of the OFDM modulated waveform, and OFDM demodulation. You then add noise to the received waveform. In contrast, frequency-domain modeling applies perfect OFDM channel state information (CSI) to the transmitted resource grid. In this case, you add noise directly to the received resource grid. Frequency-domain modeling does not require the signal to be OFDM-modulated and filtered through the channel. Therefore, frequency-domain modeling can have faster computation time than time-domain modeling. For example, a frequency-domain throughput simulation with a high number of antennas typically runs faster than an equivalent time-domain simulation. However, frequency-domain modeling is adequate only for low mobility channels where intercarrier interference is negligible compared to other impairments. To minimize the impact of intercarrier interference on the signal, the channel coherence time must be significantly larger than the OFDM symbol length. You can define the channel coherence time as inversely proportional to the maximum Doppler shift and the OFDM symbol length depends on the subcarrier spacing.

This example shows how to accelerate an NR PDSCH end-to-end simulation by using an nrCDLChannel System object™ to perform frequency-domain channel modeling. The example also performs time-domain channel modeling for comparison.

This figure shows the link elements that this example models in the context of a 5G downlink link:

  1. Generation of a PDSCH and corresponding demodulation reference signal (DM-RS)

  2. MIMO precoding and mapping of the PDSCH and PDSCH DM-RS to the resource grid

  3. Frequency-domain filtering

  4. Addition of noise

  5. Channel estimation

  6. Equalization of received PDSCH symbols

  7. Measurement of error vector magnitude (EVM)

Simulation of frequency-domain channel model

Configure Carrier

Create a 10 MHz bandwidth carrier configuration object and initialize the random stream.

carrier = nrCarrierConfig;
carrier.NSizeGrid = 52;
carrier.SubcarrierSpacing = 15;
rng("default") % Enable reproducible simulation results

Set Up Channel Model

Create an nrCDLChannel object and set the simulation parameters of the channel model.

channel = nrCDLChannel;
channel.DelayProfile = 'CDL-C';
channel.DelaySpread = 300e-9;

Define the maximum Doppler shift as 5 Hz.

channel.MaximumDopplerShift = 5;

Define the antenna configurations for the transmitter and receiver and extract the numbers of individual antennas.

channel.TransmitAntennaArray.Size = [1 2 2 1 1];
channel.ReceiveAntennaArray.Size = [1 1 2 1 1];
nTxAnts = prod(channel.TransmitAntennaArray.Size);
nRxAnts = prod(channel.ReceiveAntennaArray.Size);

Set the sample rate of the channel by using OFDM information from the carrier.

ofdmInfo = nrOFDMInfo(carrier);
channel.SampleRate = ofdmInfo.SampleRate;

Define the signal-to-noise ratio (SNR) in decibels.

SNRdB = 10;
SNR = 10^(SNRdB/10);

Map PDSCH and PDSCH DM-RS to the Resource Grid

Generate PDSCH and PDSCH DM-RS symbols. Then apply MIMO precoding and map the symbols to the resource grid. For details on this process, see Map 5G Physical Channels and Signals to the Resource Grid.

[pdsch,pdschSymbols,pdschIndices,w,resourceGrid] = pdschConfigurationAndMapping(carrier,nTxAnts);

Enable Frequency-Domain Modeling

By default, the nrCDLChannel object uses time-domain modeling. To enable frequency-domain modeling:

  1. Set the ChannelFiltering property to false to obtain the channel path gains without sending a signal through the channel System object.

  2. Set the correct duration of the fading process realization by obtaining the number of time samples for a single slot. Because the cyclic prefix lengths are different at the start of each half frame, this example calculates the number of time samples on a slot-by-slot basis.

channel.ChannelFiltering = false;
nSlotSamples = sum(ofdmInfo.SymbolLengths(1:ofdmInfo.SymbolsPerSlot));
channel.NumTimeSamples = nSlotSamples;

To minimize the impact of intercarrier interference on the signal, the channel coherence time must be significantly larger than the OFDM symbol length.

Calculate the OFDM symbol length in seconds for a frame length of 10 ms.

slotLength = 0.01 / carrier.SlotsPerFrame;
symbolLength = slotLength / carrier.SymbolsPerSlot
symbolLength = 7.1429e-05

Calculate the channel coherence time in seconds as the reciprocal of the maximum Doppler shift.

channelCoherenceTime = 1/channel.MaximumDopplerShift
channelCoherenceTime = 0.2000

Since the coherence time is several orders of magnitude larger than the symbol length in this scenario, frequency-domain modeling is a good candidate to accelerate the simulation.

Perform Frequency-Domain Modeling

Get the channel path gains, sample times, and path filters from the nrCDLChannel object.

[pathGains,sampleTimes] = channel();
pathFilters = getPathFilters(channel);

Use frequency-domain filtering to generate the received resource grid.

[rxGrid,ofdmChannelResponse] = hApplyFrequencyDomainChannel(carrier,pathGains,pathFilters,sampleTimes,resourceGrid);

Generate additive white Gaussian noise (AWGN) based on the SNR. Then add the noise to the received resource grid. For information on SNR calculation, see SNR Definition Used in Link Simulations.

N0 = 1 / sqrt(nRxAnts*SNR);
noiseGrid = N0*randn(size(rxGrid),'like',rxGrid);
rxGrid = rxGrid + noiseGrid;

Equalize the received PDSCH symbols. Use the OFDM channel response as a perfect channel estimate for equalization.

noiseEst = N0^2;
pdschEq = equalizePDSCH(carrier,pdsch,rxGrid,w,ofdmChannelResponse,noiseEst);

Perform Time-Domain Modeling

Reset and release the channel model object. Turn channel filtering on and perform time-domain channel modeling.

channel.ChannelFiltering = true;

Use OFDM modulation to create the transmitter waveform.

txWaveform = nrOFDMModulate(carrier,resourceGrid);

Pass this waveform through the channel to obtain the received waveform.

[rxWaveform,timePathGains,timeSampleTimes,timeNoise] = timeDomainChannelFiltering(txWaveform,ofdmInfo,channel,nRxAnts,SNR);

Get path filters from the nrCDLChannel object.

timePathFilters = getPathFilters(channel);

Use perfect channel and timing estimation, OFDM demodulation, and equalization to generate the received, equalized PDSCH symbols.

timePDSCHEq = timeDomainReceiver(carrier,pdsch,rxWaveform,w,timePathFilters,timePathGains,timeSampleTimes,timeNoise);

Compare Results

Visualise the received symbols in frequency-domain and time-domain modeling. The plots show that the results of frequency-domain modeling match the results of time-domain modeling.


Plot the EVM to compare the differences between frequency-domain modeling and time-domain modeling in the received data. The plots show that EVM follows a similar trend with respect to the resource blocks and the OFDM symbols for both frequency-domain modeling and time-domain modeling. Both types of modeling provide a similar effect in both dimensions of the resource grid.

fig2 = figure();
evmPerRB_freq = plotEVM(pdsch,pdschIndices,size(resourceGrid),pdschSymbols,pdschEq,'perRB');
hold on
evmPerRB_time = plotEVM(pdsch,pdschIndices,size(resourceGrid),pdschSymbols,timePDSCHEq,'perRB');
legend(["Frequency-domain modeling","Time-domain modeling"])
axis square
hold off
axis manual
fill([1.5 1.5 2.5 2.5], [0 100 100 0], 'b','FaceAlpha',0.05,'EdgeColor','none');
hold on
evmPerSym_freq = plotEVM(pdsch,pdschIndices,size(resourceGrid),pdschSymbols,pdschEq,'perSym');
evmPerSym_time = plotEVM(pdsch,pdschIndices,size(resourceGrid),pdschSymbols,timePDSCHEq,'perSym');
legend(["PDSCH DM-RS","Frequency-domain modeling","Time-domain modeling"])
axis square
hold off
set(fig2, 'Position', [0 0 1000 500])

Compare Execution Times

Compare the execution times of frequency-domain modeling and time-domain modeling. In this configuration, by running a link-level simulation for 10 frames, you can achieve a three-fold speedup when using frequency-domain modeling.

nFrames = 10;
[freqExecutionTime,timeExecutionTime] = compareExecutionTime(carrier,ofdmInfo,channel,nFrames,nTxAnts,nRxAnts,SNR);

for x = 1
    fprintf("Frequency-domain execution time: %f seconds\n",freqExecutionTime)
    fprintf("Time-domain execution time:      %f seconds\n",timeExecutionTime)
Frequency-domain execution time: 4.421832 seconds
Time-domain execution time:      15.360793 seconds

Further Exploration

This example demonstrates how frequency-domain channel modeling can accelerate NR end-to-end simulations in specific cases. To learn more about when frequency-domain modeling can accelerate your simulation, try increasing the channel.MaximumDopplerShift parameter and analyze how the results change as the channel coherence time approaches the OFDM symbol length.

You can also try applying this frequency-domain channel modeling technique to accelerate the simulation of the NR PDSCH Throughput Using Channel State Information Feedback and NR PUSCH Throughput examples.

Local functions

function [pdsch,pdschSymbols,pdschIndices,w,resourceGrid] = pdschConfigurationAndMapping(carrier,nTxAnts)
    pdsch = nrPDSCHConfig;
    pdsch.Modulation = "16QAM";
    pdsch.NumLayers = 1;
    pdsch.PRBSet = 0:carrier.NSizeGrid-1; % Full band allocation
    [pdschIndices,pdschInfo] = nrPDSCHIndices(carrier,pdsch);
    pdschBits = randi([0 1],pdschInfo.G,1);
    pdschSymbols = nrPDSCH(carrier,pdsch,pdschBits);
    dmrsSymbols = nrPDSCHDMRS(carrier,pdsch);
    dmrsIndices = nrPDSCHDMRSIndices(carrier,pdsch);
    % Precoding weights
    W = fft(eye(nTxAnts))/sqrt(nTxAnts);              % Unitary precoding matrix
    w = W(1:pdsch.NumLayers,:)/sqrt(pdsch.NumLayers); % Normalize by number of layers
    pdschSymbolsPrecoded = pdschSymbols*w;
    resourceGrid = nrResourceGrid(carrier,nTxAnts);
    [~,pdschAntIndices] = nrExtractResources(pdschIndices,resourceGrid);
    resourceGrid(pdschAntIndices) = pdschSymbolsPrecoded;
    % PDSCH DM-RS precoding and mapping
    for p = 1:size(dmrsSymbols,2)
        [~,dmrsAntIndices] = nrExtractResources(dmrsIndices(:,p),resourceGrid);
        resourceGrid(dmrsAntIndices) = resourceGrid(dmrsAntIndices) + dmrsSymbols(:,p)*w(p,:);

function pdschEq = equalizePDSCH(carrier,pdsch,rxGrid,w,estChannelGrid,noiseEst)
    [pdschIndices,~] = nrPDSCHIndices(carrier,pdsch);
    [pdschRx,pdschHest,~,pdschHestIndices] = nrExtractResources(pdschIndices,rxGrid,estChannelGrid);
    pdschHest = nrPDSCHPrecode(carrier,pdschHest,pdschHestIndices,permute(w,[2 1 3]));
    [pdschEq,~] = nrEqualizeMMSE(pdschRx,pdschHest,noiseEst);

function  [rxWaveform,timePathGains,timeSampleTimes,timeNoise] = timeDomainChannelFiltering(txWaveform,ofdmInfo,channel,nRxAnts,SNR)
    chInfo = info(channel);
    maxChDelay = chInfo.MaximumChannelDelay;
    % Pass this waveform through the channel and obtain a received waveform, channel path gains and channel sample times.
    txWaveform = [txWaveform; zeros(maxChDelay,size(txWaveform,2))];
    [rxWaveform,timePathGains,timeSampleTimes] = channel(txWaveform);
    % Use SNR to generate time domain noise
    timeN0 = 1/sqrt(nRxAnts*double(ofdmInfo.Nfft)*SNR);
    timeNoise = timeN0*randn(size(rxWaveform),'like',rxWaveform);
    rxWaveform = rxWaveform + timeNoise;
function timePDSCHEq = timeDomainReceiver(carrier,pdsch,rxWaveform,w,timePathFilters,timePathGains,timeSampleTimes,timeNoise) 
    % Get the perfect timing offset that should be applied to the received waveform.
    timeOffset = nrPerfectTimingEstimate(timePathGains,timePathFilters);
    estChannelGrid = nrPerfectChannelEstimate(carrier,timePathGains,timePathFilters,timeOffset,timeSampleTimes);

    % Get perfect noise estimate (from the noise realization)
    timeNoiseGrid = nrOFDMDemodulate(carrier,timeNoise(1+timeOffset:end ,:));
    timeNoiseEst = var(timeNoiseGrid(:));
    % Offset received waveform
    rxWaveform = rxWaveform(1+timeOffset:end,:);
    % Use the function nrOFDMDemodulate to generate the received resource grid.
    timeRxGrid = nrOFDMDemodulate(carrier,rxWaveform);

    timePDSCHEq = equalizePDSCH(carrier,pdsch,timeRxGrid,w,estChannelGrid,timeNoiseEst);

function plotSymbols(pdschEq,timePDSCHEq,pdschSymbols)
    fig1 = figure;
    hold on;
    xlim([min(real(pdschEq(:)))-0.5 max(real(pdschEq(:)))+0.5])
    ylim([min(imag(pdschEq(:)))-0.5 max(imag(pdschEq(:)))+0.5])
    title("Received PDSCH Symbols, Equalized");
    subtitle("Frequency-Domain Modeling");
    xlabel("In-Phase Amplitude");
    ylabel("Quadrature Amplitude");
    legend(["Equalized PDSCH symbols" "Transmitted PDSCH symbols"]);
    axis square;
    hold off
    hold on;
    xlim([min(real(timePDSCHEq(:)))-0.5 max(real(timePDSCHEq(:)))+0.5])
    ylim([min(imag(timePDSCHEq(:)))-0.5 max(imag(timePDSCHEq(:)))+0.5])
    title("Received PDSCH Symbols, Equalized");
    subtitle("Time-Domain Modeling");
    xlabel("In-Phase Amplitude");
    ylabel("Quadrature Amplitude");
    legend(["Equalized PDSCH symbols" "Transmitted PDSCH symbols"]);
    axis square;
    hold off
    set(fig1,'Position',[0 0 1000 500])

function evm = plotEVM(pdsch,pdschIndices,siz,pdschSymbols,pdschEq,type)
    rbEVM = comm.EVM;
    symEVM = comm.EVM;
    NRB = siz(1) / 12;
    Nsym = siz(2);
    evmPerRB = NaN(NRB,pdsch.NumLayers);
    evmPerSym = NaN(Nsym,pdsch.NumLayers);
    [k,l,m] = ind2sub(siz,pdschIndices);
    rbsubs = floor((k-1) / 12);
    symsubs = l;
    for nu = 1:pdsch.NumLayers
        if strcmp(type,'perRB')
            for rb = unique(rbsubs).'
                this = (rbsubs==rb & m==nu);
                evmPerRB(rb+1,nu) = rbEVM(pdschSymbols(this),pdschEq(this));
            evm = evmPerRB;
        elseif strcmp(type,'perSym')
            for sym = unique(symsubs).'
                this = (symsubs==sym & m==nu);
                evmPerSym(sym,nu) = symEVM(pdschSymbols(this),pdschEq(this));
            evmPerSym = rmmissing(evmPerSym);
            evm = evmPerSym;
    if strcmp(type,'perRB')
        xlabel('Resource Block');
        ylabel('EVM (%)');
        xlim([0 NRB-1]);
        title('EVM per Resource Block');
    elseif strcmp(type,'perSym')
        xlabel('OFDM Symbol');
        ylabel('EVM (%)');
        xlim([0 Nsym-1]);
        ylim([min(evmPerSym(:))-3 max(evmPerSym(:))+3])
        title('EVM per OFDM Symbol');

function [freqTime,timeTime] = compareExecutionTime(carrier,ofdmInfo,channel,nFrames,nTxAnts,nRxAnts,SNR)
    % Reset channel object
    % Get path filters from nrCDLChannel object
    pathFilters = getPathFilters(channel);
    % Calculate the number of slots from the slots per frame
    nSlots = nFrames * carrier.SlotsPerFrame;

    for slot = 1:nSlots-1
        carrier.NSlot = slot;
        [pdsch,~,~,w,pdschGrid] = pdschConfigurationAndMapping(carrier,nTxAnts);
        txWaveform = nrOFDMModulate(carrier,pdschGrid);
        [rxWaveform,timePathGains,timeSampleTimes,timeNoise] = timeDomainChannelFiltering(txWaveform,ofdmInfo,channel,nRxAnts,SNR);
        timePDSCHEq = timeDomainReceiver(carrier,pdsch,rxWaveform,w,pathFilters,timePathGains,timeSampleTimes,timeNoise);
    timeTime = toc;

    channel.ChannelFiltering = false;

    for slot = 1:nSlots-1
        carrier.NSlot = slot;
        [pdsch,~,~,w,pdschGrid] = pdschConfigurationAndMapping(carrier,nTxAnts);
        [pathGains,sampleTimes] = channel();
        [rxGrid,ofdmChannelResponse] = hApplyFrequencyDomainChannel(carrier,pathGains,pathFilters,sampleTimes,pdschGrid);
        N0 = 1 / sqrt(nRxAnts*SNR);
        noiseGrid = N0*randn(size(rxGrid),'like',rxGrid);
        rxGrid = rxGrid + noiseGrid;
        noiseEst = N0^2;
        pdschEq = equalizePDSCH(carrier,pdsch,rxGrid,w,ofdmChannelResponse,noiseEst);
    freqTime = toc;

Related Topics