Main Content

6G Link-Level Simulation

Since R2024a

This reference simulation shows how to measure the throughput of a pre-6G link. It is based on 5G but allows you to explore larger bandwidths and subcarrier spacings than those in 5G systems. It also uses parallel processing to accelerate the simulation through multiple workers on the desktop or in the cloud.


In this example you measure the physical downlink shared channel (PDSCH) throughput of a pre-6G link. The example enables you to use some parameter ranges beyond those defined by the 3GPP specifications for 5G NR. You can use more than 275 resource blocks and a subcarrier spacing bigger than 960 kHz, as described in the Get Started with 6G Exploration Library example.

The example models these features:

  • Downlink shared channel (DL-SCH) transport channel coding

  • Multiple codewords, dependent on the number of layers

  • PDSCH and PDSCH demodulation reference signal (DM-RS) generation

  • Variable subcarrier spacing and frame numerologies (2n×15kHz)

  • Normal and extended cyclic prefix

  • Clustered delay line (CDL) propagation channel model

  • PDSCH subband precoding using singular value decomposition (SVD)

  • Perfect or practical channel estimation

  • HARQ operation with up to 32 processes

  • Wideband and sub-band precoding

  • Single bandwidth part across the whole carrier

  • Parallel processing for speed of execution

The figure shows the implemented processing chain.

The diagram shows the PDSCH processing chain, including the following operations: dowlink shared channel (DL-SCH) encoding, physical downlink shared (PDSCH) channel generation, precoding, OFDM modulation, and CDL chanel. At the receiver the processing steps are: synchronization, OFDM demodulation, channel estimation and equalization, PDSCH decoding, and DL-SCH decoding.

Set Simulation Parameters

Set the length of the simulation in terms of the number of 10 ms frames (NFrames). To produce meaningful throughput results, use a large number of frames. Set the SNR points to simulate. The SNR for each layer is defined per resource element (RE), and it includes the effects of signal and noise across all antennas. For an explanation of the SNR definition that this example uses, see the SNR Definition Used in Link Simulations example.

simParameters = struct();       % Simulation parameters structure
simParameters.NFrames = 2;      % Number of 10 ms frames
simParameters.SNRdB = -10:2:-6; % SNR range (dB)

The enableParallelism variable controls whether the simulation executes on a single worker or in parallel using multiple workers. To use parallelism, you need a Parallel Computing Toolbox™ license.

simParameters.enableParallelism = true;

Disable parallelism if you want to enable the DisplayDiagnostics flag, set a breakpoint in the pdschLink function, or profile the code.

Configure Channel Estimator

The logical variable PerfectChannelEstimator controls channel estimation behavior. When set to true, the simulation uses perfect channel estimation. Otherwise, it uses the PDSCH DM-RS for practical channel estimation.

simParameters.PerfectChannelEstimator = false;

Configure Simulation Diagnostics

The example offers two diagnostics flags which help you monitor the evolution of your simulation during runtime. Both flags apply only if enableParallelism is set to false.

The DisplaySimulationInformation variable controls the display of simulation information such as the HARQ process ID for each subframe. In case of CRC error, it also displays the value of the index of the redundancy version (RV) sequence.

simParameters.DisplaySimulationInformation = true;

The DisplayDiagnostics flag enables plotting the EVM per layer. This plot monitors the quality of the received signal after equalization. The EVM per layer figure shows:

  • The EVM per layer per slot, which shows the EVM evolving with time

  • The EVM per layer per resource block, which shows the EVM in frequency

simParameters.DisplayDiagnostics = false;

Configure Carrier, PDSCH and Propagation Channel

% Set carrier parameters
simParameters.Carrier = pre6GCarrierConfig;                       % Carrier resource grid configuration
simParameters.Carrier.NSizeGrid = 330;                            % Bandwidth in number of resource blocks
simParameters.Carrier.SubcarrierSpacing = 120;                    % Subcarrier spacing

% Set PDSCH parameters
simParameters.PDSCH = pre6GPDSCHConfig;                           % PDSCH definition for all PDSCH transmissions in the BLER simulation

% Define PDSCH time-frequency resource allocation per slot to be full grid (single full grid BWP) and number of layers
simParameters.PDSCH.PRBSet = 0:simParameters.Carrier.NSizeGrid-1;                % PDSCH PRB allocation
simParameters.PDSCH.SymbolAllocation = [0,simParameters.Carrier.SymbolsPerSlot]; % Starting symbol and number of symbols of each PDSCH allocation
simParameters.PDSCH.NumLayers = 1;                                               % Number of PDSCH transmission layers

% This structure is to hold additional simulation parameters for the DL-SCH and PDSCH
simParameters.PDSCHExtension = struct();             

% Define codeword modulation and target coding rate
% The number of codewords is directly dependent on the number of layers so ensure that layers are set first before getting the codeword number
if simParameters.PDSCH.NumCodewords > 1                           % Multicodeword transmission (when number of layers is > 4)
    simParameters.PDSCH.Modulation = {'16QAM','16QAM'};           % 'QPSK', '16QAM', '64QAM', '256QAM', '1024QAM'
    simParameters.PDSCHExtension.TargetCodeRate = [490 490]/1024; % Code rate used to calculate transport block sizes
    simParameters.PDSCH.Modulation = '16QAM';                     % 'QPSK', '16QAM', '64QAM', '256QAM', '1024QAM'
    simParameters.PDSCHExtension.TargetCodeRate = 490/1024;       % Code rate used to calculate transport block sizes

% Disable PT-RS
simParameters.PDSCH.EnablePTRS = false;

% PDSCH PRB bundling (TS 38.214 Section
simParameters.PDSCHExtension.PRGBundleSize = [];                  % Any positive power of 2, or [] to signify "wideband"

% HARQ process parameters
simParameters.PDSCHExtension.NHARQProcesses = 16;                 % Number of parallel HARQ processes to use
simParameters.PDSCHExtension.EnableHARQ = true;                   % Enable retransmissions for each process, using RV sequence [0,2,3,1]

% LDPC decoder parameters
simParameters.PDSCHExtension.LDPCDecodingAlgorithm = 'Normalized min-sum';
simParameters.PDSCHExtension.MaximumLDPCIterationCount = 20;

% Number of antennas
simParameters.NTxAnts = 32;                                       % Number of antennas (1,2,4,8,16,32,64,128,256,512,1024) >= NumLayers
simParameters.NRxAnts = 2;

% Define the general CDL propagation channel parameters
simParameters.DelayProfile = 'CDL-A';   
simParameters.DelaySpread = 10e-9;
simParameters.MaximumDopplerShift = 70;

% Cross-check the PDSCH layering against the channel geometry 

Configure Parallel Execution

If you have enabled parallel execution, configure your parallel cluster. For a more detailed explanation of the parallel execution model in this example, see the Accelerate Link-Level Simulations with Parallel Processing example.

if (simParameters.enableParallelism && canUseParallelPool)
    pool = gcp; % create parallel pool, requires PCT
    numWorkers = pool.NumWorkers;
    maxNumWorkers = pool.NumWorkers;
    if (~canUseParallelPool && simParameters.enableParallelism)
        warning("Ignoring the value of enableParallelism ("+simParameters.enableParallelism+")"+newline+ ...
            "The simulation will run using serial execution."+newline+"You need a license of Parallel Computing Toolbox to use parallelism.")
    numWorkers = 1;    % No parallelism
    maxNumWorkers = 0; % Used to convert the parfor-loop into a for-loop
Starting parallel pool (parpool) using the 'Processes' profile ...
Connected to parallel pool with 4 workers.

Create a constant random stream to avoid unnecessary copying of the random stream multiple times to each worker. Substreams provide mutually independent random streams to each worker.

str1 = RandStream('Threefry','Seed',1);
constantStream = parallel.pool.Constant(str1);

Calculate the number of slots per worker. To make full use of all the available workers, the example may simulate more slots than the value specified by NFrames.

numSlotsPerWorker = ceil((simParameters.NFrames*simParameters.Carrier.SlotsPerFrame)/numWorkers);
disp("Parallelism: "+simParameters.enableParallelism)
Parallelism: true
disp("Number of workers: "+numWorkers)
Number of workers: 4
disp("Number of slots per worker: "+numSlotsPerWorker)
Number of slots per worker: 40
disp("Total number of frames: "+(numSlotsPerWorker*numWorkers)/simParameters.Carrier.SlotsPerFrame)
Total number of frames: 2

PDSCH Link-Level Simulation

This section simulates Nworkers PDSCH links in parallel, where Nworkers is the number of workers available. Each worker simulates all SNR points. The parfor-loop uses the syntax parfor (pforIdx = 1:numWorkers,maxNumWorkers). To debug your code, switch the simulation from parallel to serial execution by setting maxNumWorkers to 0. Regardless of the value of maxNumWorkers, you cannot set breakpoints in the body of the parfor-loop. However, when maxNumWorkers = 0, you can set breakpoints within functions called from the body of the parfor-loop.

% Results storage
result = struct(NumSlots=0,NumBits=0,NumCorrectBits=0);
results = repmat(result,numWorkers,numel(simParameters.SNRdB));

% Parallel processing, worker parfor-loop
parfor (pforIdx = 1:numWorkers,maxNumWorkers)     
    % Set random streams to ensure repeatability
    % Use substreams in the generator so each worker uses mutually independent streams
    stream = constantStream.Value;      % Extract the stream from the Constant
    stream.Substream = pforIdx;         % Set substream value = parfor index
    RandStream.setGlobalStream(stream); % Set global stream per worker

    % Per worker processing
    results(pforIdx,:) = pdschLink(simParameters,numSlotsPerWorker,pforIdx);
Simulating SNR=-10.00 dB, progress: 
Simulating SNR=-8.00 dB, progress: 
Simulating SNR=-6.00 dB, progress: 


Display the measured throughput. This is calculated as the percentage of the maximum possible throughput of the link given the available resources for data transmission.

[throughput,throughputMbps,summaryTable] = processResults(simParameters,results);
    SNR    Simulated bits    Number of Tr Blocks    Number of frames    Throughput (%)    Throughput (Mbps)
    ___    ______________    ___________________    ________________    ______________    _________________

    -10      1.574e+07               160                   2                64.375             506.64      
     -8      1.574e+07               160                   2                    90             708.31      
     -6      1.574e+07               160                   2                   100             787.01      
xlabel('SNR (dB)'); ylabel('Throughput (%)'); grid on;
title(sprintf('%s (%dx%d) / NRB=%d / SCS=%dkHz', ...
              simParameters.DelayProfile,simParameters.NTxAnts,simParameters.NRxAnts, ...

Local Functions

function [throughput,throughputMbps,summaryTable] = processResults(simParameters,results)
% Process multi-worker and multi-SNR results

    numSNRPts = size(results,2);
    totalSimulatedSlots = sum(reshape([results(:).NumSlots].',[],numSNRPts),1);
    totalSimulatedBits = sum(reshape([results(:).NumBits].',[],numSNRPts),1);
    totalCorrectBits = sum(reshape([results(:).NumCorrectBits].',[],numSNRPts),1);
    totalSimulatedFrames = totalSimulatedSlots/simParameters.Carrier.SlotsPerFrame;
    % Throughput results calculation
    throughput = 100*(totalCorrectBits./totalSimulatedBits);
    throughputMbps = 1e-6*totalCorrectBits/(simParameters.NFrames*10e-3);
    summaryTable = table(simParameters.SNRdB.',totalSimulatedBits.',totalSimulatedSlots.', ...
    summaryTable.Properties.VariableNames = ["SNR" "Simulated bits" "Number of Tr Blocks" ...
        "Number of frames" "Throughput (%)" "Throughput (Mbps)"];


function resultsPerWorker = pdschLink(simParameters,totalNumSlots,workerId)
% PDSCH link simulation
    % Take copies of channel-level parameters to simplify subsequent parameter referencing 
    carrier = simParameters.Carrier;
    pdsch = simParameters.PDSCH;
    pdschextra = simParameters.PDSCHExtension;

    % Results storage
    result = struct(NumSlots=0,NumBits=0,NumCorrectBits=0);
    resultsPerWorker = repmat(result,1,numel(simParameters.SNRdB));

    % Create DL-SCH encoder/decoder
    [encodeDLSCH,decodeDLSCH] = dlschEncoderDecoder(pdschextra);

    % OFDM waveform information
    ofdmInfo = hpre6GOFDMInfo(carrier);
    % Create CDL channel
    channel = nrCDLChannel;
    channel = hArrayGeometry(channel,simParameters.NTxAnts,simParameters.NRxAnts);
    nTxAnts = prod(channel.TransmitAntennaArray.Size);
    nRxAnts = prod(channel.ReceiveAntennaArray.Size);
    channel.DelayProfile = simParameters.DelayProfile;
    channel.DelaySpread = simParameters.DelaySpread;
    channel.MaximumDopplerShift = simParameters.MaximumDopplerShift;
    channel.SampleRate = ofdmInfo.SampleRate;
    % New seed for each worker, but the same for each SNR point so they all
    % experience the same channel realization.
    channel.Seed = randi([0 2^32-1]);

    chInfo = info(channel);
    maxChDelay = chInfo.MaximumChannelDelay;

    % Set up redundancy version (RV) sequence for all HARQ processes
    if simParameters.PDSCHExtension.EnableHARQ        
        rvSeq = [0 2 3 1];
        % HARQ disabled - single transmission with RV=0, no retransmissions
        rvSeq = 0;

    % for all SNR points
    for snrIdx = 1:length(simParameters.SNRdB)

        % Noise power calculation
        SNR = 10^(simParameters.SNRdB(snrIdx)/10); % Calculate linear noise gain
        N0 = 1/sqrt(double(ofdmInfo.Nfft)*SNR*nRxAnts);
        % Get noise power per resource element (RE) from noise power in the
        % time domain (N0^2)
        nPowerPerRE = N0^2*ofdmInfo.Nfft;

        % Reset the channel and DL-SCH decoder at the start of each SNR simulation

        % Specify the fixed order in which we cycle through the HARQ process IDs
        harqSequence = 0:pdschextra.NHARQProcesses-1;

        % Initialize the state of all HARQ processes
        harqEntity = HARQEntity(harqSequence,rvSeq,pdsch.NumCodewords);

        % Obtain a precoding matrix (wtx) to be used in the transmission of the
        % first transport block
        estChannelGrid = getInitialChannelEstimate(carrier,nTxAnts,channel);    
        wtx = hSVDPrecoders(carrier,pdsch,estChannelGrid,pdschextra.PRGBundleSize);

        %  Progress when parallel processing is enabled
        if (simParameters.enableParallelism && workerId==1)
            fprintf('Simulating SNR=%.2f dB, progress: \n0%% \n',simParameters.SNRdB(snrIdx))

        % Process all the slots per worker
        for nSlot = 0:totalNumSlots-1

            % New slot number
            carrier.NSlot = nSlot;

            % Calculate the transport block sizes for the transmission in the slot
            [pdschIndices,pdschIndicesInfo] = hpre6GPDSCHIndices(carrier,pdsch);
            trBlkSizes = nrTBS(pdsch.Modulation,pdsch.NumLayers,numel(pdsch.PRBSet),pdschIndicesInfo.NREPerPRB,pdschextra.TargetCodeRate);

            % Generate new data and DL-SCH encode
            codedTrBlocks = getDLSCHCodeword(encodeDLSCH,trBlkSizes,pdsch.Modulation,pdsch.NumLayers,pdschIndicesInfo.G,harqEntity);
            % PDSCH modulation of codeword(s), MIMO precoding and OFDM
            [txWaveform,pdschSymbols]= hPDSCHTransmit(carrier,pdsch,codedTrBlocks,wtx);

            % Pass data through channel model
            txWaveform = [txWaveform; zeros(maxChDelay,size(txWaveform,2))];
            [rxWaveform,pathGains,sampleTimes] = channel(txWaveform);

            % Add noise
            noise = N0*randn(size(rxWaveform),"like",rxWaveform);
            rxWaveform = rxWaveform + noise;

            % Synchronization, OFDM demodulation, channel estimation,
            % equalization, and PDSCH demodulation            
            pathFilters = getPathFilters(channel);
            perfEstConfig = perfectEstimatorConfig(pathGains,sampleTimes,pathFilters,nPowerPerRE,simParameters.PerfectChannelEstimator);
            [dlschLLRs,wtx,pdschEq] = hPDSCHReceive(carrier,pdsch,pdschextra,rxWaveform,wtx,perfEstConfig);

            % Display EVM per layer, per slot and per RB
            if (simParameters.DisplayDiagnostics)
                gridSize = [carrier.NSizeGrid*12 carrier.SymbolsPerSlot nTxAnts];

            % Decode the DL-SCH transport channel

            % If new data because of previous RV sequence time out then flush decoder soft buffer explicitly
            for cwIdx = 1:pdsch.NumCodewords
                if harqEntity.NewData(cwIdx) && harqEntity.SequenceTimeout(cwIdx)
            decodeDLSCH.TransportBlockLength = trBlkSizes;
            [~,blkerr] = decodeDLSCH(dlschLLRs,pdsch.Modulation,pdsch.NumLayers,harqEntity.RedundancyVersion,harqEntity.HARQProcessID);

            % Update current process with CRC error and advance to next process
            procstatus = updateAndAdvance(harqEntity,blkerr,trBlkSizes,pdschIndicesInfo.G);
            if (simParameters.DisplaySimulationInformation && ~simParameters.enableParallelism)
                fprintf('(%3.2f%%), SNR=%.2f dB, NSlot=%d, %s\n',100*(nSlot+1)/totalNumSlots,simParameters.SNRdB(snrIdx),nSlot,procstatus);
            elseif (simParameters.enableParallelism && workerId==1 && ~mod((nSlot+1),ceil((10*totalNumSlots)/100))) % Progress when parallel processing is enabled
                % Update progress with approximately 10% steps
                fprintf('%3.2f%% \n',100*(nSlot+1)/totalNumSlots)

            % SNR point simulation results            
            resultsPerWorker(snrIdx).NumSlots = resultsPerWorker(snrIdx).NumSlots+1;
            resultsPerWorker(snrIdx).NumBits = resultsPerWorker(snrIdx).NumBits+sum(trBlkSizes);
            resultsPerWorker(snrIdx).NumCorrectBits = resultsPerWorker(snrIdx).NumCorrectBits+sum(~blkerr .* trBlkSizes);

        end % for nSlot = 0:totalNumSlots

    end % for all SNR points

function [encodeDLSCH,decodeDLSCH] = dlschEncoderDecoder(PDSCHExtension)
% Create and parameterize the DL-SCH encoder and decoder objects
    % Create DL-SCH encoder object
    encodeDLSCH = nrDLSCH;
    encodeDLSCH.MultipleHARQProcesses = true;
    encodeDLSCH.TargetCodeRate = PDSCHExtension.TargetCodeRate;
    % Create DL-SCH decoder object
    decodeDLSCH = nrDLSCHDecoder;
    decodeDLSCH.MultipleHARQProcesses = true;
    decodeDLSCH.TargetCodeRate = PDSCHExtension.TargetCodeRate;
    decodeDLSCH.LDPCDecodingAlgorithm = PDSCHExtension.LDPCDecodingAlgorithm;
    decodeDLSCH.MaximumLDPCIterationCount = PDSCHExtension.MaximumLDPCIterationCount;

function codedTrBlocks = getDLSCHCodeword(encodeDLSCH,trBlkSizes,Modulation,NumLayers,G,harqEntity)
% Get DL-SCH codeword

    % HARQ processing
    for cwIdx = 1:length(G)
        % If new data for current process and codeword then create a new DL-SCH transport block
        if harqEntity.NewData(cwIdx) 
            trBlk = randi([0 1],trBlkSizes(cwIdx),1);
    % Encode the DL-SCH transport blocks
    codedTrBlocks = encodeDLSCH(Modulation,NumLayers,G,harqEntity.RedundancyVersion,harqEntity.HARQProcessID);


function perfEstInfo = perfectEstimatorConfig(pathGains,sampleTimes,pathFilters,noiseEst,perfChEst)
% Perfect channel estimator configuration

    perfEstInfo.PathGains = pathGains;
    perfEstInfo.PathFilters = pathFilters;
    perfEstInfo.SampleTimes = sampleTimes;
    perfEstInfo.NoiseEstimate = noiseEst;
    perfEstInfo.PerfectChannelEstimator = perfChEst;


function estChannelGrid = getInitialChannelEstimate(carrier,nTxAnts,propchannel)
% Obtain channel estimate before first transmission. This can be used to
% obtain a precoding matrix for the first slot.

    ofdmInfo = hpre6GOFDMInfo(carrier);
    chInfo = info(propchannel);
    maxChDelay = chInfo.MaximumChannelDelay;
    % Temporary waveform (only needed for the sizes)
    tmpWaveform = zeros((ofdmInfo.SampleRate/1000/carrier.SlotsPerSubframe)+maxChDelay,nTxAnts,"single");
    % Filter through channel    
    [~,pathGains,sampleTimes] = propchannel(tmpWaveform);
    % Perfect timing synch    
    pathFilters = getPathFilters(propchannel);
    offset = nrPerfectTimingEstimate(pathGains,pathFilters);
    % Perfect channel estimate
    estChannelGrid = hpre6GPerfectChannelEstimate(carrier,pathGains,pathFilters,offset,sampleTimes);

function plotLayerEVM(NSlots,nslot,pdsch,siz,pdschIndices,pdschSymbols,pdschEq,SNRdB)
% Plot EVM information

    persistent slotEVM;
    persistent rbEVM;
    persistent evmPerSlot;
    if (nslot==0)
        slotEVM = comm.EVM;
        rbEVM = comm.EVM;
        evmPerSlot = NaN(NSlots,pdsch.NumLayers);
    evmPerSlot(nslot+1,:) = slotEVM(pdschSymbols,pdschEq);
    xlabel("Slot number");
    ylabel("EVM (%)");
    legend("layer " + (1:pdsch.NumLayers),'Location','EastOutside');
    title("EVM per layer per slot. SNR = "+SNRdB+" dB");

    [k,~,p] = ind2sub(siz,pdschIndices);
    rbsubs = floor((k-1) / 12);
    NRB = siz(1) / 12;
    evmPerRB = NaN(NRB,pdsch.NumLayers);
    for nu = 1:pdsch.NumLayers
        for rb = unique(rbsubs).'
            this = (rbsubs==rb & p==nu);
            evmPerRB(rb+1,nu) = rbEVM(pdschSymbols(this),pdschEq(this));
    xlabel("Resource block");
    ylabel("EVM (%)");
    legend("layer " + (1:pdsch.NumLayers),'Location','EastOutside');
    title("EVM per layer per resource block, slot #"+num2str(nslot)+". SNR = "+SNRdB+" dB");

function validateNumLayers(simParameters)
% Validate the number of layers, relative to the antenna geometry

    numlayers = simParameters.PDSCH.NumLayers;
    ntxants = simParameters.NTxAnts;
    nrxants = simParameters.NRxAnts;
    antennaDescription = sprintf('min(NTxAnts,NRxAnts) = min(%d,%d) = %d',ntxants,nrxants,min(ntxants,nrxants));
    if numlayers > min(ntxants,nrxants)
        error('The number of layers (%d) must satisfy NumLayers <= %s', ...
    % 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>


See Also


Related Topics