Main Content

LTE-M Uplink Waveform Generation

This example shows how to create an uplink LTE-M transmission consisting of the Physical Uplink Shared Channel (PUSCH) and associated demodulation reference signals (DM-RS) including repetitions and frequency hopping. When compared to pre-release 13 devices, Cat-M devices offer lower cost and complexity, enhanced coverage by the introduction of repetitions and an extended DRX for further power saving.


The cell-specific subframe bitmap broadcasted on the System Information (SI) indicates which subframes are capable of LTE-M transmissions. LTE-M devices can optionally enable repetitions for the PUSCH and PUCCH to enhance coverage. The network configures a set of possible repetitions containing 4 values for CE mode A (pusch-maxNumRepetitionCEmodeA as given in TS 36.213 Table 8-2b [ 3 ]) and 8 values for CE mode B (pusch-maxNumRepetitionCEmodeB as given in TS 36.213 Table 8-2c [ 3 ]). From this set, the network dynamically selects the actual repetitions and signals this to the UE as part of the scheduling grant. Uplink scheduling grants for LTE-M devices are carried on the MPDCCH using DCI format 6-0A for devices operating in CE mode A and 6-0B for devices operating in CE mode B. A scheduling grant ending in downlink subframe n is valid for an uplink PUSCH transmission starting in uplink subframe n+4. In the case of transmissions with frequency hopping, the grant indicates the narrowband for the first transmission. Subsequent allocations can hop between narrowbands as defined in TS 36.211 Section 5.3.4 [ 1 ].


For Cat-M1 (Release 13) devices, PUSCH is always transmitted in a single narrowband. In Cat-M2 (Release 14), devices in CE mode A can optionally transmit over up to 24 PRBs if the higher layer parameter ce-pusch-maxBandwidth-config is set to 5MHz as indicated in TS 36.211 Section 5.3.4 [ 1 ]/TS 36.306 Section [ 4 ]. LTE-M PUSCH can have up to 32 repetitions in CE mode A (see TS 36.213 Table 8-2b [ 3 ])and up to 2048 repetitions in CE mode B (see TS 36.213 Table 8-2c [ 3 ]). For BL/CE UE in CE mode A, PUSCH frequency hopping is enabled when the higher-layer parameter pusch-HoppingConfig is set and the frequency hopping flag in DCI format 6-0A indicates frequency hopping. For BL/CE UE in CE mode B, PUSCH frequency hopping is enabled when the higher-layer parameter pusch-HoppingConfig is set. If frequency hopping is not enabled for PUSCH, all PUSCH repetitions are located at the same PRB resources. If frequency hopping is enabled for PUSCH, PUSCH is transmitted in a subframe within the NabsPUSCH consecutive uplink subframes using the same number of consecutive PRBs as in the previous subframe starting from the same starting PRB resource within narrowband. If a resource assignment or frequency hopping would result in a PUSCH resource allocation outside the allocatable PRBs then the PUSCH transmission in that subframe is dropped.

ue = struct();                % Initialize the structure
ue.NULRB = 50;                % Bandwidth
ue.DuplexMode = 'FDD';        % Duplex mode
ue.TDDConfig = 1;             % UL/DL configuration if TDD duplex mode
ue.CyclicPrefixUL = 'Normal'; % The cyclic prefix length
ue.NCellID = 1;               % Cell identity
ue.RNTI = 1;                  % RNTI value
ue.NFrame = 0;                % Frame number
ue.NSubframe = 0;             % Subframe number
ue.NTxAnts = 1;               % Number of transmit antennas
ue.Shortened = 1;             % Last symbol availability (allocation for SRS)

% Set up hopping specific parameters
ue.HoppingOffset = 1;% Narrowband offset between one narrowband and the next narrowband
                     % a PUSCH hops to, expressed as a number of uplink narrowbands
ue.NChULNB = 2;      % Number of consecutive absolute subframes over which
                     % PUCCH or PUSCH stays at the same

pusch  = struct();
pusch.CEMode = 'A';         % CE mode A or CE mode B
pusch.Hopping = true;       % Enable/Disable frequency hopping
pusch.NRepPUSCH = 8;        % The total number of PUSCH repetitions
pusch.Modulation = 'QPSK';  % Symbol modulation
pusch.RV = 0;               % Redundancy version for UL-SCH processing
pusch.NLayers = 1;          % Number of layers
pusch.TrBlkSizes = 100;     % Transport block size

PUSCH Allocation - The PUSCH bandwidth is usually a single 1.4MHz narrowband. There are 6 RBs in each narrowband, all can be allocated in CE mode A and 1 or 2 RBs in CE mode B. An extended BW of 5MHz possible in Cat-M2 CE mode A configuration (See TS 36.306 Section [ 4 ]). We use the InitPRBSet and InitNarrowbandIndex to specify the PRBs in a narrowband and the narrowband used in transmission. If frequency hopping is disabled, LTE-M PDSCH will be transmitted in the PRBs specified by the InitPRBSet and InitNarrowbandIndex parameters. If hopping is enabled, the hopping rules determine the narrowband used per subframe. The 5MHz bandwidth is inferred via the use of more than 6 PRBs in the InitPRBSet parameter. In this case the hopping will be disabled and the InitNarrowbandIndex ignored.

% Specify 1-based relative indices of RBs within a narrowband for all cases
% except 5MHz Cat-M2 CE mode A. If 5MHz Cat-M2 CE mode A, these are the
% absolute PRBs used for transmission
pusch.InitPRBSet = (2:3)';
% Narrowband used for transmission (non-hopping, non-5MHz)
pusch.InitNarrowbandIndex = 1;

% Specify the power scaling in dB for PUSCH, PUSCH DM-RS
pusch.PUSCHPower = 30;
pusch.PUSCHDMRSPower = 100;

% Turn off hopping if allocation spans multiple narrowbands
if numel(pusch.InitPRBSet) > 6
    pusch.Hopping = false;

UL-SCH Encoding

For BL/CE UEs in CE mode B, resource elements in the last SC-FDMA symbol in a subframe configured with cell specific SRS shall be counted in the PUSCH mapping but not used for transmission of the PUSCH. Hence if CE mode B, turn off shortening when creating the coded transport block.

% Identify all uplink subframes in a frame
info = arrayfun(@(x)lteDuplexingInfo(setfield(ue,'NSubframe',x)),0:9);
ulsfs = arrayfun(@(x)strcmpi(x.SubframeType,'Uplink'),info);
% In this example, we assume that the first absolute subframe in which
% PUSCH is transmitted is the first available uplink subframe
pusch.InitNSubframe = find(ulsfs,1)-1;

% Calculate the allocation
pusch.PRBSet = getPUSCHAllocation(ue,pusch);
ueTemp = ue;
% Create coded transport block for all symbols
if strcmpi(pusch.CEMode,'B') && ue.Shortened
    ueTemp.Shortened = 0;
[~,info] = ltePUSCHIndices(ueTemp,pusch);
% Define UL-SCH message bits
trData = ones(pusch.TrBlkSizes(1),1);
% Create the coded UL-SCH bits
pusch.BetaCQI = 2.0;
pusch.BetaRI = 2.0;
pusch.BetaACK = 2.0;
codedTrBlock = lteULSCH(ueTemp,pusch,info.G,trData);

LTE-M PUSCH Generation

In this example, we generate the LTE-M PUSCH and the corresponding DM-RS signals with repetitions and optional frequency hopping. pusch.NRepPUSCH controls the number of PUSCH repetitions. The UE-specific parameter pusch.Hopping enables hopping and the cell-wide parameters ue.HoppingOffset and ue.NChULNB defines the hopping pattern. In this example, if the allocation spans more than one narrowband, frequency hopping will be disabled. For LTE-M, the same scrambling sequence is applied per subframe to PUSCH for a block of Nacc subframes, all other processing stages i.e. symbol modulation, layer mapping, precoding and mapping to resource elements are the same for the LTE PUSCH.

% Number of subframes in a scrambling block
Nacc = 1;
if strcmpi(ue.DuplexMode,'FDD') && strcmpi(pusch.CEMode,'B')
    Nacc = 4;
elseif strcmpi(ue.DuplexMode,'TDD') && strcmpi(pusch.CEMode,'B')
    Nacc = 5;

% Total BL/CE subframes to simulate (all uplink subframes are BL/CE
% subframes) and the PUSCH is transmitted without any subframe gaps
totmtcSubframes = pusch.NRepPUSCH;

% Total absolute subframes to simulate
startSubframe = ue.NFrame*10+ue.NSubframe; % Initial absolute subframe number
lastabssf = getlastabsSF(ulsfs,pusch.InitNSubframe,totmtcSubframes);
totSubframes = lastabssf-startSubframe+1;

% Create a resource grid for the entire transmission. The PUSCH and
% DM-RS symbols will be mapped in this array
subframeSize = lteULResourceGridSize(ue);
sfgrid = zeros([subframeSize(1) subframeSize(2)*totSubframes subframeSize(3:end)]);

mpuschSym = []; % Initialize PUSCH symbols

for sf = startSubframe + (0:totSubframes -1)

    % Set current absolute subframe and frame numbers
    ue.NSubframe = mod(sf,10);
    ue.NFrame = floor((sf)/10);

    % Skip processing if this is not an uplink subframe
    duplexInfo = lteDuplexingInfo(ue);
    if ~strcmpi(duplexInfo.SubframeType,'Uplink')

   % Calculate the PRBSet used in the current subframe
    prbset = getPUSCHAllocation(ue,pusch);

    % Calculate the PDSCH indices for the current subframe. For BL/CE UEs
    % in CE mode B, resource elements in the last SC-FDMA symbol in a
    % subframe configured with cell specific SRS shall be counted in the
    % PUSCH mapping but not used for transmission of the PUSCH
    pusch.PRBSet = prbset;
    mpuschIndices = ltePUSCHIndices(ue,pusch);

    % Create an empty subframe grid
    subframe = lteULResourceGrid(ue);

    % Encode PUSCH symbols from the codeword
    % In the case of repetition, the same symbols are repeated in each of
    % a block of NRepPUSCH subframes. Frequency hopping is applied as required
    if ~mod(sf,Nacc) || isempty(mpuschSym)
        ueTemp = ue;
        if strcmpi(pusch.CEMode,'B') && ue.Shortened
            ueTemp.Shortened = 0;  % Create symbols for full subframe
        mpuschSym = ltePUSCH(ueTemp,pusch,codedTrBlock)*db2mag(pusch.PUSCHPower);
    % Map SRS punctured PUSCH symbols to the subframe grid
    subframe(mpuschIndices) = mpuschSym(1:numel(mpuschIndices));

    % Create and map the DMRS symbols.
    ue.Hopping = 'Off';    % DRS hopping
    ue.SeqGroup = 0;       % PUSCH sequence group
    ue.CyclicShift = 0;    % Used for n1DMRS
    % For LTE-M UEs, a cyclic shift field of '000' shall be assumed when
    % determining n2DMRS from Table of TS 36.211
    pusch.DynCyclicShift = 0; % Cyclic shift of '000' for n2DMRS
    pusch.OrthCover = 'Off';  % No orthogonal cover sequence
    mpuschDrs = ltePUSCHDRS(ue,pusch)*db2mag(pusch.PUSCHDMRSPower);
    mpuschDrsIndices = ltePUSCHDRSIndices(ue,pusch);
    subframe(mpuschDrsIndices) = mpuschDrs;

    % Now assign the current subframe into the overall grid
    sfgrid(:,(1:subframeSize(2))+sf*subframeSize(2),:) = subframe;


Create Time Domain Baseband Waveform

Create the time domain baseband waveform by OFDM modulating the resource grid. The resulting matrix has four columns; one of which will contain the complex baseband time-domain waveform samples for the MPDCCH

waveform = lteSCFDMAModulate(ue,sfgrid);

Plot Transmitted Grid and Baseband Waveform

Plot the grid and time domain baseband waveform. If the transmission uses more than one port, only the first port is shown. Note that the resource grid plot uses the power levels of the PUSCH and the DM-RS to assign colors to the resource elements.

% Create an image of overall resource grid. Since the PUSCH undergo
% transform precoding, we need to assign a single power level to all
% symbols to visualize in the plot
plotgrid = abs(sfgrid(:,:,1));
% Get the DM-RS positions
drspos = (plotgrid==db2mag(pusch.PUSCHDMRSPower));
plotgrid(drspos) = 0;
% Now set all PUSCH symbols to one power level to plot
plotgrid(plotgrid~=0) = db2mag(pusch.PUSCHPower);
% Now write back the DRS and plot
plotgrid(drspos) = db2mag(pusch.PUSCHDMRSPower);
im = image(plotgrid);
cmap = parula(64);
axis xy;
title(sprintf('LTE-M CEMode%s Uplink RE Grid (NRepPUSCH = %d)',pusch.CEMode,pusch.NRepPUSCH))
xlabel('OFDM symbols')
% Create the legend box to indicate the channel/signal types associated with the REs
reNames = {'PUSCH';'PUSCH DRS'};
clevels = round(db2mag([pusch.PUSCHPower pusch.PUSCHDMRSPower]));
N = numel(reNames);
L = line(ones(N),ones(N), 'LineWidth',8); % Generate lines
% Set the colors according to cmap
set(L,{'color'},mat2cell(cmap( min(1+clevels,length(cmap) ),:),ones(1,N),3));   % Set the colors according to cmap

Local Functions

The following local functions are used in this example:

  • calcNarrowbandPRBSets - Calculate narrowbands and associated PRBs

  • getPDSCHAllocation - Calculate the PUSCH subframe allocation

  • getlastabsSF - Calculate the last subframe number for PUSCH

Selected Bibliography

  1. 3GPP TS 36.211 "Physical channels and modulation"

  2. 3GPP TS 36.212 "Multiplexing and channel coding"

  3. 3GPP TS 36.213 "Physical layer procedures"

  4. 3GPP TS 36.306 "User Equipment (UE) radio access capabilities"

  5. 3GPP TS 36.331 "Radio Resource Control (RRC) Protocol specification"

  6. O. Liberg, M. Sundberg, Y.-P. Wang, J. Bergman and J. Sachs, Cellular Internet of Things: Technologies, Standards and Performance, Elsevier, 2018.

  7. E. Dahlman, S. Parkvall and J Skold 4G LTE-Advanced Pro and The Road to 5G

Local Functions

% Calculate the widebands, narrowbands and PRBSets for the LTE carrier bandwidth
function [prbsets,nNB,nWB] = calcNarrowbandPRBSets(NULRB)
    % Narrowbands & Widebands (See 36.211 Section 5.2.4)
    NULNB = floor(NULRB/6);
    nNB = 0:(NULNB-1); % Narrowbands
    if NULNB >= 4
        NULWB = floor(NULNB/4);
        NULWB = 1;
    nWB = 0:(NULWB-1); % Widebands

    % PRBs in a narrowband
    ii = 0:5;
    ii0 = floor(NULRB/2) - 6*(NULNB/2);
    prbsets = zeros(6,numel(nNB));
    for nb = 1:numel(nNB)
        if mod(NULRB,2) && nNB(nb)>= (NULNB/2)
            prbsets(:,nb) = 6*(nNB(nb))+ii0+ii + 1;
            prbsets(:,nb) = 6*(nNB(nb))+ii0+ii;

% Calculate the resource blocks allocated for PUSCH in the subframe
function prbset = getPUSCHAllocation(ue,chs)

    % If 5MHz mode (up to 24 PRBs can be used), the allocation is the same
    % as InitPRBSet
    if numel(chs.InitPRBSet) > 6
        prbset = chs.InitPRBSet;

    % Get the narrowbands and corresponding resources
    [prbsets,nNB] = calcNarrowbandPRBSets(ue.NULRB);
    if max(chs.InitNarrowbandIndex) > max(nNB)
        error('Invalid narrowband(s) specified. There are only %d narrowbands in the bandwidth from 0...%d', nNB+1, nNB);
    % If frequency hopping is disabled, the allocation is the same for
    % every subframe
    if ~chs.Hopping
        prbset = prbsets(chs.InitPRBSet,chs.InitNarrowbandIndex+1);

    % Hopping narrowband calculation according to TS 36.211 Section 5.3.4
    j0 = floor((chs.InitNSubframe)/ue.NChULNB);

    % Calculate the narrowband for this subframe
    ue.NSubframe = ue.NFrame*10+ue.NSubframe; % Get the absolute subframe number
    if mod(floor(ue.NSubframe/ue.NChULNB-j0),2) == 0
        nnBi = chs.InitNarrowbandIndex;
        nnBi = mod(chs.InitNarrowbandIndex+ue.HoppingOffset,numel(nNB));
    % Calculate the PRBSet for this subframe, they are on the same RBs
    % within the narrowband
    [rbstartIndex,nbstartIndex] = find(prbsets == chs.InitPRBSet(1));
    [rbendIndex,nbendIndex] = find(prbsets == chs.InitPRBSet(end));
    if (isempty(rbstartIndex) || isempty(rbendIndex)) || (nbstartIndex ~= nbendIndex)
       error('Invalid PRBSet specified, must be resources within single narrowband');
    prbset = prbsets(rbstartIndex:rbstartIndex+numel(chs.InitPRBSet)-1,nnBi+1);


% Get the absolute subframe number which is used for the last transmission
% of a channel
function lastabssf = getlastabsSF(ulsfs,InitNSubframe,totmtcSubframes)

    numulsfsinFrame = sum(ulsfs); % Number of active sfs in a frame
    ulsfsinFrame = find(ulsfs);   % UL subframes in the frame (1-based)

    % Find the first absolute subframe and frame
    initabssf = mod(InitNSubframe,10);
    initabsf = floor(InitNSubframe/10);

    startIdxwithinFrame = initabssf+1; % 1-based index to the UL sf
    if ~ulsfs(startIdxwithinFrame)
        error(['Invalid absolute subframe number of the first uplink subframe', ...
              ' intended for PUSCH (%d) specified. This is not an uplink subframe'],InitNSubframe)

    sfslastFrame = mod((find(ulsfsinFrame==startIdxwithinFrame)-1)+totmtcSubframes,numulsfsinFrame); % subframes to tx in the last frame
    if sfslastFrame
        % Find the subframe number corresponding to the last subframe to transmit
        sfsnumlastFrame = find(ulsfs,sfslastFrame)-1;
        sfsnumlastFrame = sfsnumlastFrame(end);
        % No partial frames required
        sfsnumlastFrame = 0;
    lastabssf = (initabsf + floor(((find(ulsfsinFrame==startIdxwithinFrame)-1)+totmtcSubframes)/numulsfsinFrame)) * 10 + sfsnumlastFrame;