%% NR TDD Symbol Based Scheduling Performance Evaluation
% This example demonstrates the scheduling functionality of the medium access control (MAC) layer of the 5G New Radio (NR) stack. The scheduling strategy assigns physical uplink shared channel (PUSCH) and physical downlink shared channel (PDSCH) resources to a set of static user equipments (UEs) connected to a gNB. The example assumes time division duplexing (TDD) mode. Hence uplink (UL) and downlink (DL) transmissions are separated in time. This example also shows the functionality of the radio link control (RLC) layer in unacknowledged mode (UM). Note that this example abstracts the physical layer and adopts a probability-based approach using the assumed channel quality indicator (CQI) values to model packet reception failures. The performance of the scheduling strategy is evaluated in terms of achieved MAC throughput and the fairness in resource sharing.
%% Introduction
% This example shows how a scheduling strategy (controlled by the gNB) assigns UL and DL resources among the UEs. The example considers the following operations at gNB and UEs that facilitate UL and DL transmissions.
%
% The control packets required are assumed to be sent out of band without the need of frequency resources for transmission. The control packets are UL assignment, DL assignment, buffer status report (BSR), and PDSCH feedback. These control packets follow the TDD DL and UL timings. For example, BSR and PDSCH feedback are sent in UL time while assignments are sent in DL time.
% Demodulation reference signal (DM-RS) is not modeled in this example. However, two symbols for slot based scheduling and one symbol for symbol based scheduling is kept unused across the frequency domain of the assignments.
% This example models:
% Configurable TDD DL-UL pattern.
% Slot based and symbol based DL and UL scheduling. UL scheduler ensures that the UEs get the required PUSCH preparation time.
% Noncontiguous allocation of frequency domain resources in terms of resource block groups (RBGs).
% Configurable subcarrier spacing (15, 30, 60, 120 kHz) resulting in different slot durations.
% Asynchronous hybrid automatic repeat request (HARQ) mechanism.
% UL reception success/failure detection by UEs using new data indicator (NDI) flag present in UL assignment.
% DL reception success/failure detection by gNB using PDSCH feedback sent by UEs.
% Periodic DL and UL application traffic pattern.
% RLC operating in UM mode.
%
%% TDD DL-UL Pattern Configuration
% NR provides a flexible way of configuring the DL and UL resources. The parameters used to define a custom TDD configuration are:
% DL-UL transmission periodicity in ms.
% Reference subcarrier spacing to calculate the number of slots in the DL-UL pattern. In this example, it is assumed to be same as actual subcarrier spacing used for transmission.
% Number of consecutive full DL slots at the beginning of each DL-UL pattern.
% Number of consecutive DL symbols in the beginning of the slot following the last full DL slot.
% Number of consecutive full UL slots at the end of each DL-UL pattern.
% Number of consecutive UL symbols in the end of the slot preceding the first full UL slot.
% The example does not model flexible symbols, so the symbols with unspecified symbol type are assumed for guard period. Here is an example of resulting TDD DL-UL pattern based on these parameters. This DL-UL pattern repeats itself in the timeline.
% reference_scs = 15 kHz (i.e. 1 ms slot),  DLULPeriodicity = 5 ms, numDLSlots = 3, numDLSyms = 7, numULSlots = 1, numULSyms = 5
%             Number of slots in DL-UL periodicity with respect to reference scs of 15 kHz, NumSlotsDLULPeriodicity = 5
%             NumberOfGuardSymbols   =   TotalSymbolsInPattern - TotalSymbolsWithTypeSpecified
%                                                         =   (14 * NumSlotsDLULPeriodicity) - (numDLSlots*14 + numDLSyms + numULSyms + numULSlots*14)
%                                                         =    2 symbols
%
%
%
%% Scheduler
% UL and DL schedulers distribute the UL and DL resources respectively among the UEs. You can choose any one of the implemented scheduling strategies: proportional fair (PF), best CQI, or round robin (RR). The various supported inputs to the schedulers are shown along with the scheduling strategies that consider them.
%
%          Both UL and DL schedulers run at the start of a slot when the first symbol is a DL symbol. Schedulers run in DL time so that assignments can be instantly sent to UEs in the DL direction. The run time of the scheduler algorithm as well as the propagation delay is assumed to be zero. The output of scheduling operations is an array of assignments. Each assignment contains the information fields to fully define a PUSCH/PDSCH transmission.
% UL Scheduler
%            The UL scheduling operation follow these two steps.
%  Select slots to be scheduled: The criteria used in this example selects all the upcoming slots (including the current one) containing unscheduled UL symbols that must be scheduled now. These slots can be scheduled now but cannot be scheduled in the next slot with DL symbols, based on the PUSCH preparation time capability of the UEs. It ensures that the UL resources are scheduled as close as possible to the actual transmission time.
%
%
%              (i) Assuming that the UEs require PUSCH preparation time of 10 symbols, when the UL scheduler runs in Slot-A, it does not select any slot for scheduling. Because scheduling in the next slot with DL symbols (i.e. Slot-B) provides enough PUSCH preparation time (14 symbols) for the UL transmission in Slot-C. Later, when the UL scheduler runs in Slot-B, it selects both Slot-C and Slot-D for scheduling. Slot-D is not scheduled in Slot-C because Slot-C does not have any DL symbols for sending the assignments in the DL direction.
%             (ii) Assuming that the UEs require PUSCH preparation time of 16 symbols, Slot-C is scheduled in Slot-A itself. Because scheduling in Slot-B would only provide 14 symbols of PUSCH preparation time to start transmission in Slot-C. Slot-D is scheduled in Slot-B.
%         2. Resource scheduling: If any slots are selected in the first step, assign the UL resources of those slots to the UEs.
%
% DL scheduler
%             The DL scheduler runs at each slot beginning with a DL symbol and assign resources of the first upcoming slot containing DL symbols. So, when DL scheduler runs at the start of Slot-A, it schedules DL resources of Slot-B.
%
%% Symbol based scheduling
% NR allows the TTI to start at any symbol position in the slot and with TTI granularity in symbols. The figure shows the way UL scheduler operates in this example to schedule UL symbols of a slot with TTI granularity of two symbols. The slot shown contains six UL symbols. Scheduler completes the iteration of the UL symbols in three iterations with each iteration distributing the frequency resources of two symbols. The DL scheduler also follows a similar approach for DL scheduling.
%
%
%% Scenario Configuration
% Set the following key configuration parameters for the simulation:
% Simulation time
% Number of UEs
% Scheduling type: slot based or symbol based
% TDD DL-UL pattern configuration
% Distance of UEs from gNB (affects the CQI values for the UEs)
% UL and DL application traffic patterns
% Logical channel configuration
% Scheduling strategy: PF, Best CQI, RR
% Periodicity of BSRs sent by UEs to inform gNB about pending buffer amount
% PUSCH preparation time for UEs
% Bandwidth in terms of number of resource blocks (RBs)
% Subcarrier spacing: 15, 30, 60, 120 (kHz)
% Initial channel quality and its update mechanism
% Relation of successful reception probability with channel quality

rng('default'); % Reset the random number generator
simParameters = []; % Clear simParameters variable
simParameters.NumFramesSim = 100; % Simulation time in terms of number of 10 ms frames

% Number of UEs in the simulation. UEs are assumed to have sequential radio
% network temporary identifiers (RNTIs) from 1 to NumUEs. If you change the
% number of UEs, ensure that the following simulation parameters are array
% of length equal to NumUEs: simParameters.UEDistance,
% simParameters.ULPacketPeriodicityUEs, simParameters.ULPacketSizesUEs,
% simParameters.DLPacketPeriodicityUEs, simParameters.DLPacketSizesUEs,
% simParameters.RLCConfig.SNFieldLength,
% simParameters.RLCConfig.ReassemblyTimer,
% simParameters.RLCConfig.MaxTxBufferLength
simParameters.NumUEs = 4;

% Set the value of scheduling type to 0 (slot based scheduling) or 1
% (symbol based scheduling). If value is not set, the default value is 0
simParameters.SchedulingType = 1;

% Set the minimum time-domain assignment in terms of symbol duration.
% Applicable only for symbol based scheduling. For slot based scheduling,
% it is always full slot (14 symbols)
simParameters.TTIGranularity = 4;

% Define the TDD DL-UL pattern. The reference subcarrier spacing used for
% calculating slot duration for the pattern is assumed to be same as actual
% subcarrier spacing used for transmission as defined by simParameters.SCS.
% Keep only the symbols intended for guard period during DLULPeriodicity
% with type (DL or UL) unspecified
simParameters.DLULPeriodicity = 5; % Duration of the DL-UL pattern in ms
simParameters.NumDLSlots = 2; % Number of consecutive full DL slots at the beginning of each DL-UL pattern
% Define the number of consecutive DL symbols following the full DL slots
% and the number of consecutive UL symbols preceding the full UL slots. In
% the case of slot based scheduling, only simParameters.NumDLSyms is
% applicable and it defines the number of DL symbols at the start of the
% slot, while the remaining symbols of the slot are assumed to be allotted
% for guard period
simParameters.NumDLSyms = 8; % Number of consecutive DL symbols in the beginning of the slot following the last full DL slot
simParameters.NumULSyms = 4; % Number of consecutive UL symbols in the end of the slot preceding the first full UL slot
simParameters.NumULSlots = 2; % Number of consecutive full UL slots at the end of each DL-UL pattern

simParameters.UEDistance = [100; 400; 300; 700]; % Distance of UEs from gNB (in meters)
% Mapping between distance from gNB (first column in meters) and maximum
% achievable UL CQI value (second column). For example, if a UE is 700
% meters away from the gNB, it can achieve a maximum CQI value of 10 as the
% distance falls within the [501, 800] meters range, as per the mapping.
% Set the distance in increasing order and the maximum achievable CQI value
% in decreasing order
simParameters.CQIvsDistance = [
    200  15;
    500  12;
    800  10;
    1000  8;
    1200  7];

% Set the periodic UL and DL application traffic pattern for UEs
simParameters.ULPacketPeriodicityUEs = [20; 30; 20; 30]; % Periodicity at which the UL packets are generated by UEs
simParameters.ULPacketSizesUEs = [2000; 3000; 1500; 3000]; % Size of the UL packets (in bytes) generated by UEs
simParameters.DLPacketPeriodicityUEs = [20; 15; 15; 20]; % Periodicity at which the DL packets are generated for UEs at gNB
simParameters.DLPacketSizesUEs = [4000; 2500; 2500; 2000]; % Size of the DL packets generated (in bytes) for UEs at gNB

% RLC configuration
simParameters.RLCConfig.SNFieldLength = [6; 6; 6; 6]; % Sequence number field length (in bits) of the logical channel for each UE
simParameters.RLCConfig.ReassemblyTimer = [5; 5; 5; 5]; % Reassembly timer (in ms) of the logical channel for each UE
% Max buffer length of the logical channel for each UE (to model Tx buffer overflow)
simParameters.RLCConfig.MaxTxBufferLength = [100000; 200000; 200000; 200000];

% MAC configuration
simParameters.SchedulerStrategy = 'PF'; % Supported scheduling strategies: 'PF', 'RR' and 'BestCQI'
% PUSCH preparation time. gNB ensures that PUSCH assignment is received at
% UEs PUSCHPrepTime ahead of the transmission time
simParameters.PUSCHPrepTime = 200; % In microseconds
% Moving average weight parameter within the range [0, 1] calculates
% average data rate for a UE. The value is used in the PF scheduling strategy.
% Parameter value closer to 1 implies more weight on the instantaneous
% data rate. Parameter value closer to 0 implies more weight on the past
% data rate
% AverageDataRate = ((1-MovingAvgDataRateWeight)*PastDataRate) + (MovingAvgDataRateWeight*InstantaneousDataRate)
simParameters.MovingAvgDataRateWeight = 0.5;
simParameters.EnableHARQ = true; % Flag to enable or disable HARQ. If disabled, there are no retransmissions
simParameters.NumHARQ = 16; % Number of UL and DL HARQ processes for each UE
% Maximum RBs allotted to a UE for PUSCH and PDSCH transmissions (limit is
% applicable for new transmission assignments and not for the retransmissions)
simParameters.RBAllocationLimitUL = 15; % For PUSCH
simParameters.RBAllocationLimitDL = 15; % For PDSCH
simParameters.BSRPeriodicity = 1; % Buffer status report transmission periodicity (in ms)

% Physical layer and channel configuration
% RB count for 5 MHz band with 15 kHz subcarrier spacing (SCS). The complete
% bandwidth is assumed to be allotted for PUSCH/PDSCH
simParameters.NumRBs = 25;
simParameters.SCS = 15;
% Configure parameters to update channel conditions for the UEs. Channel
% quality is periodically improved or deteriorated by CQIDelta every
% channelUpdatePeriodicity seconds for all RBs of a UE. Whether channel
% conditions for a particular UE improve or deteriorate is randomly
% determined: RB_CQI = RB_CQI +/- CQIDelta
simParameters.ChannelUpdatePeriodicity = 0.2; % In sec
simParameters.CQIDelta = 1;
% Expression to calculate reception success probability of a PUSCH/PDSCH
% packet based on the channel quality. 'x' represents the average CQI of
% the RBs over which the packet is received
simParameters.CQISuccessProb = '.5 + (x/20)';

% Logging and visualization configuration
% Flag to enable or disable runtime CQI visualization
simParameters.CQIVisualization = true;
% Flag to enable or disable run time visualization of RB assignment. If enabled,
% then for slot based scheduling it updates every frame (10 ms) to show RB
% allocation to the UEs for different slots of the last frame. For
% symbol based scheduling, it updates every slot to show RB allocation to
% the UEs over different symbols of the last slot
simParameters.RBVisualization = false;
% The output metrics plots are updated periodically NumMetricsSteps times within the
% simulation duration
simParameters.NumMetricsSteps = 20;
% MAT-files to write the logs into. They are used for post simulation analysis and visualization
simParameters.ParametersLogFile = 'simParameters'; % For logging the simulation parameters
simParameters.SimulationLogFile = 'simulationLogs'; % For logging the simulation logs

hNRSchedulingTDDValidateConfig(simParameters); % Validate the simulation configuration

%
%% Derived Parameters
% Based on the primary configuration parameters, compute the derived parameters. Additionally, set some example specific constants.

simParameters.DuplexMode = 1; % TDD (Example specific constant)
simParameters.NumLogicalChannels = 1; % Only 1 logical channel is assumed in each UE (Example specific constant)
slotDuration = 1/(simParameters.SCS/15); % Slot duration in ms
numSlotsFrame = 10/slotDuration; % Number of slots in 10 ms frame
numSlotsSim = simParameters.NumFramesSim * numSlotsFrame; % Simulation time in units of slot duration
numSymbolsSim = numSlotsSim * 14; % Simulation time in units of symbol duration
% Interval at which metrics visualization updates in terms of number of
% slots. Make sure that MetricsStepSize is an integer
simParameters.MetricsStepSize = ceil(numSlotsSim / simParameters.NumMetricsSteps);
if mod(numSlotsSim, simParameters.NumMetricsSteps) ~= 0
    % Update the NumMetricsSteps parameter if numSlotsSim is not
    % completely divisible by it
    simParameters.NumMetricsSteps = floor(numSlotsSim / simParameters.MetricsStepSize);
end

% DL and UL packet periodicities for UEs in terms of number of slots
appPeriodicityUEsSlotsUL = simParameters.ULPacketPeriodicityUEs ./ slotDuration;
appPeriodicityUEsSlotsDL = simParameters.DLPacketPeriodicityUEs ./ slotDuration;

% Mapping between logical channel and logical channel group id
simParameters.LCHConfig.LCGID = ones(simParameters.NumUEs, simParameters.NumLogicalChannels);
% Priority of each logical channel 
simParameters.LCHConfig.Priority = ones(simParameters.NumUEs, simParameters.NumLogicalChannels);
% Prioritized bitrate (in kilo bytes per second) of each logical channel
simParameters.LCHConfig.PBR = 8*ones(simParameters.NumUEs, simParameters.NumLogicalChannels);
% Bucket size duration (in ms) of each logical channel. However, the priority,
% PBR and BSD for logical channel is not relevant with single logical
% channel assumed in this example
simParameters.LCHConfig.BSD = 10*ones(simParameters.NumUEs, simParameters.NumLogicalChannels);
% Logical channel id
simParameters.LCHConfig.LCID = ones(simParameters.NumUEs, simParameters.NumLogicalChannels) .* (1:simParameters.NumLogicalChannels);

% RLC entity direction. Value 0 represents DL only, 1
% represents UL only and 2 represents both UL and DL
% direction. Setting entity direction to have both UL and DL
simParameters.RLCConfig.EntityDir = 2*ones(simParameters.NumUEs, simParameters.NumLogicalChannels);
% Construct information for RLC logger
lchInfo = repmat(struct('LCID',[],'EntityDir',[]), [simParameters.NumUEs 1]);
for idx=1:simParameters.NumUEs
    lchInfo(idx).LCID = simParameters.LCHConfig.LCID(idx, :);
    lchInfo(idx).EntityDir = simParameters.RLCConfig.EntityDir(idx, :);
end
% Maximum RLC SDU length (in bytes)
simParameters.maxRLCSDULength = 9000;

% Calculate maximum achievable CQI value for the UEs based on their distance
% from the gNB
maxUECQIs = zeros(simParameters.NumUEs, 1); % To store the maximum achievable CQI value for UEs
for ueIdx = 1:simParameters.NumUEs
    % Based on the distance of the UE from gNB, find matching row in CQIvsDistance mapping
    matchingRowIdx = find(simParameters.CQIvsDistance(:, 1) > simParameters.UEDistance(ueIdx));
    if isempty(matchingRowIdx)
        maxUECQIs(ueIdx) = simParameters.CQIvsDistance(end, 2);
    else
        maxUECQIs(ueIdx) = simParameters.CQIvsDistance(matchingRowIdx(1), 2);
    end
end

% Periodicity of channel update in terms of number of slots
channelUpdatePeriodicitySlots = floor((simParameters.ChannelUpdatePeriodicity * 1000)/...
    slotDuration);
% Update periodicity of RB assignment visualization in terms of number of slots
if ~isfield(simParameters, 'SchedulingType') || simParameters.SchedulingType == 0 % If no scheduling type is specified or slot based scheduling is specified
    rbAssignmentPlotPeriodicity = numSlotsFrame; % Update every frame (10 ms)
else % Symbol based scheduling
    rbAssignmentPlotPeriodicity = 1; % Update every slot
end

%
%% gNB and UEs Setup
% Create the gNB and UE objects, initialize the channel quality information for UEs and set up the logical channel at gNB and UE. The helper classes hNRGNB.m and hNRUE.m create gNB and UE node respectively, containing the RLC and MAC layer. For MAC layer, hNRGNB.m uses the helper class hNRGNBMAC.m to implement the gNB MAC functionality, and  hNRUE.m uses hNRUEMAC.m to implement the UE MAC functionality. Scheduler is implemented in hNRGNBMACScheduler.m. For RLC layer, both hNRGNB.m and hNRUE.m use hNRUMTransmitter.m to implement the functionality of the RLC transmitter, and hNRUMReceiver.m to implement the functionality of the RLC receiver.

gNB = hNRGNB(simParameters); % Create the gNB node
scheduler = hNRGNBMACScheduler(simParameters); % Create MAC scheduler
addScheduler(gNB, scheduler); % Add scheduler to gNB

% Create the set of UE nodes
UEs = cell(simParameters.NumUEs, 1); 
for ueIdx=1:simParameters.NumUEs
    UEs{ueIdx} = hNRUE(simParameters, ueIdx);
end

% Define initial UL and DL channel quality as an N-by-P matrix,
% where N is the number of UEs and P is the number of RBs in the carrier
% bandwidth. The initial value of CQI for each RB, for each UE, is given
% randomly and is limited by the maximum achievable CQI value corresponding
% to the distance of the UE from gNB
for ueIdx = 1:simParameters.NumUEs
    % Assign random CQI values for the RBs, limited by the maximum achievable CQI value
    uplinkChannelQuality = randi([1 maxUECQIs(ueIdx)], 1, simParameters.NumRBs);
    downlinkChannelQuality = uplinkChannelQuality; % For TDD, UL and DL channel quality are same
    % Update the UL and DL CQI values at gNB with respect to UE. gNB uses
    % UL CQI values to calculate UL packet reception success probability
    % and for channel quality based UL assignments. It uses DL channel
    % quality for channel quality based DL assignments
    updateChannelQualityStatus(gNB, uplinkChannelQuality, 1, ueIdx); % 1 represents UL direction
    updateChannelQualityStatus(gNB, downlinkChannelQuality, 0, ueIdx); % 0 represents DL direction
    % Update the DL CQI values at UE. UE uses DL channel quality to calculate DL packet reception success probability
    updateChannelQualityStatus(UEs{ueIdx}, downlinkChannelQuality);
end

% Setup the logical channel
lcid = 1;
for ueIdx=1:simParameters.NumUEs
    % Create RLC channel configuration structure
    rlcChannelConfigStruct.LCID = simParameters.LCHConfig.LCID(ueIdx, lcid);
    rlcChannelConfigStruct.SnFieldLength = simParameters.RLCConfig.SNFieldLength(ueIdx, lcid);
    rlcChannelConfigStruct.MaxTxBufferLength = simParameters.RLCConfig.MaxTxBufferLength(ueIdx, lcid);
    rlcChannelConfigStruct.ReassemblyTime = simParameters.RLCConfig.ReassemblyTimer(ueIdx, lcid);
    rlcChannelConfigStruct.EntityDir = simParameters.RLCConfig.EntityDir(ueIdx, lcid);
    rlcChannelConfigStruct.LCGID = simParameters.LCHConfig.LCGID(ueIdx, lcid);
    rlcChannelConfigStruct.Priority = simParameters.LCHConfig.Priority(ueIdx, lcid);
    rlcChannelConfigStruct.PBR = simParameters.LCHConfig.PBR(ueIdx, lcid);
    rlcChannelConfigStruct.BucketSizeDuration = simParameters.LCHConfig.BSD(ueIdx, lcid);
    % Setup logical channel at gNB for the UE
    configureLogicalChannel(gNB, ueIdx, rlcChannelConfigStruct);
    % Setup logical channel at UE
    configureLogicalChannel(UEs{ueIdx}, ueIdx, rlcChannelConfigStruct);
end

% Initialize buffers to exchange packets between UEs and gNB
hNRPacketExchangeBuffer(simParameters);

%
%% Processing Loop
% Simulation is run symbol by symbol to execute corresponding operations. If slot based scheduling is chosen, the execution jumps from the current slot boundary to the next slot boundary. Processing loop performs slot boundary operations followed by the symbol boundary operations. Based on the current symbol type (DL/UL/Guard), only relevant operations are performed.
% Slot boundary operations are:
% Update channel conditions: Update CQI values at RBs for each UE based on the channel update periodicity.
% Generate application traffic: Generate DL and UL traffic as per the configured periodicity of application traffic pattern.
% Run the schedulers:  Refer to Scheduler section for the time when DL and UL schedulers run and resources they schedule in a run. For a TTI (whether a slot or a set of contiguous symbols), assignments of RBs for retransmissions are done first, followed by assignments for the new transmissions.
% Control Tx (gNB): Construct and send UL and DL assignments based on the assignments done by respective schedulers.
% Control Rx (UE): UEs receive their respective UL and DL assignments. UEs process the assignments and store the information to be used for the PUSCH transmission and PDSCH reception.
% RLC logging: Log the RLC layer statistics.
% Per symbol operations are:
% Data Tx (gNB): gNB sends PDSCH packets adhering to the DL assignments sent earlier.
% Data Rx (UE): UEs receive PDSCH packet scheduled for this symbol. UEs store the reception success/failure result to send PDSCH feedback as positive acknowledgment (ACK) or negative acknowledgment (NACK). The timing of sending feedback for a PDSCH is based on feedback slot offset (k1) received in the PDSCH assignment.
% Data Tx (UE): UEs send PUSCH packet adhering to the UL assignments received earlier.
% Control Tx (UE): Based on the configured BSR periodicity, UEs send the BSR to convey the pending buffer amount. Additionally, UEs also send the PDSCH feedback.
% Data Rx (gNB): gNB receives PUSCH packets from the UEs scheduled to transmit in this symbol. If a reception fails, create the retransmission context for the particular UE, which is used for assignments for retransmissions when the scheduler runs next.
% Control Rx (gNB): Receive BSR and PDSCH feedback.
% Scheduler Logging: Log the output of scheduling operations at gNB and UEs.
% Visualization: Update the different visualizations as per the set update periodicity.
%
%
%

% To store the following UE metrics for each symbol: throughput
% bytes transmitted, goodput bytes transmitted, and pending buffer amount
% bytes. The number of goodput bytes is calculated by excluding the retransmissions from the
% total transmissions. Metrics are for DL direction if symbol type is DL
% (likewise, for UL symbol)
UEMetrics = zeros(simParameters.NumUEs, 3);
% To store Rx result: 'RxSuccess', 'RxFailure', 'NoRx' for a symbol
rxResultUEs = cell(simParameters.NumUEs, 1);
% To store the current value of last received NDI flag for HARQ processes
% of UEs. NDI values are for DL HARQ processes, if symbol type is DL
% (likewise, for UL symbol)
HARQProcessStatus = zeros(simParameters.NumUEs, simParameters.NumHARQ);
% To store the RLC statistics of a slot
ueRLCStats = cell(simParameters.NumUEs, 1);
gNBRLCStats = cell(simParameters.NumUEs, 1);
% To store current CQI values on the RBs for the UEs
channelQuality = zeros(simParameters.NumUEs, simParameters.NumRBs);
% Create logging and visualization objects for MAC scheduling and RLC
simSchedulingLogger = hNRSchedulingLogger(simParameters);  
simRLCLogger = hNRRLCLogger(simParameters, lchInfo);
if ~isfield(simParameters, 'SchedulingType') || simParameters.SchedulingType == 0 % If no scheduling type is specified or slot based scheduling is specified
    tickGranularity = 14; % Operations are only at slot boundary. Simulation jumps from one slot boundary to next
else % Symbol based scheduling
    tickGranularity = 1; % Operations can be at symbol boundary. Simulation moves symbol-by-symbol
end

slotNum = 0;
% Execute all the symbols in the simulation
for symbolNum = 1 : tickGranularity : numSymbolsSim
    resourceAssignmentsUL = [];
    resourceAssignmentsDL = [];
    UEMetrics(:) = 0;
    rxResultUEs(:) = {[]};
    HARQProcessStatus(:) = 0;
    
    symbolType = currentSymbolType(gNB); % Get current symbol type: DL/UL/Guard
    % Perform the slot boundary operations
    if mod(symbolNum - 1, 14) == 0
        slotNum = slotNum + 1;
        
        % Update the channel quality, if channel update periodicity has reached
        if mod(slotNum, channelUpdatePeriodicitySlots) == 0
            for ueIdx = 1:simParameters.NumUEs
                % Current UL CQI values for the UE (Assuming TDD channel reciprocity, DL CQIs would also be same)
                currentCQIs = getChannelQualityStatus(gNB, 1, ueIdx);
                % Update CQI values for RBs by randomly varying current
                % values by CQIDelta, limited by the minimum and maximum
                % achievable CQI index value. You can customize the
                % updating of CQI values as necessary
                updateType = [1 -1]; % Improvement/deterioration
                channelQualityChange = updateType(randi(length(updateType)));
                updatedCQIs = min(max(currentCQIs + simParameters.CQIDelta*channelQualityChange, 1), maxUECQIs(ueIdx));
                % Update the DL and UL channel quality information at gNB
                % and UEs (1 represents UL direction, 0 represents DL
                % direction)
                updateChannelQualityStatus(gNB, updatedCQIs, 1, ueIdx); 
                updateChannelQualityStatus(gNB, updatedCQIs, 0, ueIdx);
                updateChannelQualityStatus(UEs{ueIdx}, updatedCQIs);
            end
        end
        
        % Generate application traffic
        for ueIdx = 1:simParameters.NumUEs
            
            % Generate UL traffic
            if mod(slotNum-1, appPeriodicityUEsSlotsUL(ueIdx)) == 0 % Check if UL packet generation periodicity is reached for the UE
                % Receive UL data for the UE from upper layers based on UL application traffic
                % pattern
                % Divide application packet into multiple RLC SDUs if
                % it is bigger than maximum RLC SDU size
                numRLCSDU = floor(simParameters.ULPacketSizesUEs(ueIdx, lcid) / simParameters.maxRLCSDULength);
                rlcSDU = randi([0 255], simParameters.maxRLCSDULength, 1);
                for i = 1:numRLCSDU % Generate UL packets of max RLC SDU size (if any)
                    receiveRLCSDU(UEs{ueIdx}, ueIdx, lcid , rlcSDU);
                end
                if mod(simParameters.ULPacketSizesUEs(ueIdx, lcid),simParameters.maxRLCSDULength) ~= 0
                    % Generate UL packet which is smaller than maxRLCSDULength
                    rlcSDU = randi([0 255], mod(simParameters.ULPacketSizesUEs(ueIdx, lcid),simParameters.maxRLCSDULength), 1);
                    receiveRLCSDU(UEs{ueIdx}, ueIdx, lcid , rlcSDU);
                end
            end
            
            % Generate DL traffic
            if mod(slotNum-1, appPeriodicityUEsSlotsDL(ueIdx)) == 0 % If DL packet generation periodicity reached for UE
                % Receive DL data for the UE from upper layers based on application traffic
                % pattern
                % Divide application packet into multiple RLC SDUs, if it is bigger than maximum RLC SDU size
                numRLCSDU = floor(simParameters.DLPacketSizesUEs(ueIdx, lcid) / simParameters.maxRLCSDULength);
                rlcSDU = randi([0 255], simParameters.maxRLCSDULength, 1);
                for i = 1:numRLCSDU
                    receiveRLCSDU(gNB, ueIdx, lcid, rlcSDU); % Generate DL packets of max RLC SDU size (if any)
                end
                if mod(simParameters.DLPacketSizesUEs(ueIdx, lcid),simParameters.maxRLCSDULength) ~= 0
                    % Generate DL packet which is smaller than maxRLCSDULength
                    rlcSDU = randi([0 255], mod(simParameters.DLPacketSizesUEs(ueIdx, lcid),simParameters.maxRLCSDULength), 1);
                    receiveRLCSDU(gNB, ueIdx, lcid, rlcSDU);
                end
            end
        end
        
        % Run the schedulers. Functions internally check if the UL/DL
        % scheduler is set to run in this slot. If yes, schedule the
        % resources and send the corresponding UL and DL assignments
        resourceAssignmentsUL = runULSchedulerTDD(gNB);
        resourceAssignmentsDL = runDLSchedulerTDD(gNB);
        if ~isempty(resourceAssignmentsUL) || ~isempty(resourceAssignmentsDL) % Check if UL/DL assignments are done
            controlTx(gNB, resourceAssignmentsUL, resourceAssignmentsDL); % Construct and send UL and DL assignments
        end
        
        % Run the Rx operation at UEs for simultaneous reception of control packets
        for ueIdx = 1:simParameters.NumUEs % For all UEs
            controlRx(UEs{ueIdx}); % Receive UL and DL assignments, if any sent by gNB
            % Get RLC statistics
            ueRLCStats{ueIdx} = getRLCStatistics(UEs{ueIdx}, ueIdx);
            gNBRLCStats{ueIdx} = getRLCStatistics(gNB, ueIdx);
        end
        logRLCStats(simRLCLogger, ueRLCStats, gNBRLCStats); % Update RLC statistics logs
    end
    
    % Perform the symbol boundary operations
    if symbolType == 0 % DL symbol
        dataTx(gNB); % Send the PDSCH packets, if any scheduled
        for ueIdx = 1:simParameters.NumUEs % For all UEs
            % Read current NDI value at UEs for DL HARQ processes (for logging purpose)
            HARQProcessStatus(ueIdx, :) = getLastNDIFlagHarq(UEs{ueIdx}, 0); % 0 for DL
            % Receive the PDSCH packets from gNB that are scheduled to be
            % received in this symbol. Return the information of Rx
            % success/failure
            rxResultUEs{ueIdx} = dataRx(UEs{ueIdx});
            % Read the UL channel quality at gNB for each of the UEs (for logging purpose)
            channelQuality(ueIdx,:) = getChannelQualityStatus(gNB, 1, ueIdx); % 1 for UL direction
        end
        [UEMetrics(:, 1), UEMetrics(:, 2)]  = getCurrTTIBytes(gNB); % Read throughput and goodput bytes sent for each UE (for logging purpose)
        UEMetrics(:, 3) = getBufferStatus(gNB); % Read pending DL buffer (in bytes) for each UE at gNB (for logging purpose)
    end
    
    if symbolType == 1 % UL Symbol
        for ueIdx = 1:simParameters.NumUEs % For all UEs
            % Read current NDI value at UEs for UL HARQ processes (for logging purpose)
            HARQProcessStatus(ueIdx, :) = getLastNDIFlagHarq(UEs{ueIdx}, 1); % 1 for UL
            % UE sends the PUSCH packet if it is a Tx start symbol (as conveyed by gNB in
            % earlier received UL assignments)
            dataTx(UEs{ueIdx});
            % UE sends the BSR packet and ACK/NACK for PDSCHs
            controlTx(UEs{ueIdx});
            % Read throughput and goodput bytes transmitted by this UE in the current TTI (for logging purpose)
            [UEMetrics(ueIdx, 1), UEMetrics(ueIdx, 2)] = getCurrTTIBytes(UEs{ueIdx});
            % Read pending UL buffer (in bytes) on UE (for logging purpose)
            UEMetrics(ueIdx, 3) = getBufferStatus(UEs{ueIdx}); 
             % Read the DL channel quality at gNB for each of the UEs (for logging purpose)
            channelQuality(ueIdx,:) = getChannelQualityStatus(gNB, 0, ueIdx); % 0 for DL direction
        end
        % Receive PUSCH packets from UEs that are scheduled to be received
        % in this symbol. Return the information of Rx success/failure
        rxResultUEs = dataRx(gNB);
        controlRx(gNB); % Receive BSRs and PDSCH feedback
    end
    
    % Update logs based on the current symbol run of UEs and gNB
    logScheduling(simSchedulingLogger, symbolNum, [resourceAssignmentsUL resourceAssignmentsDL], UEMetrics, channelQuality, HARQProcessStatus, rxResultUEs, symbolType);
    
    % RB assignment visualization (if enabled)
    if simParameters.RBVisualization
        if symbolNum > 1 && (simParameters.SchedulingType == 1 && (mod(symbolNum, 14) == 0)) || (simParameters.SchedulingType == 0 && (mod(slotNum, rbAssignmentPlotPeriodicity) == 0))
            % Plot at slot boundary, if the update periodicity is
            % reached
            plotRBGrids(simSchedulingLogger);
        end
    end
    % CQI grid visualization (if enabled)
    if simParameters.CQIVisualization
        if symbolNum > 1 && mod(symbolNum-1, 14) == 0 && mod(slotNum, numSlotsFrame) == 0 % Plot at frame boundary
            plotCQIRBGrids(simSchedulingLogger);
        end
    end
    % Plot scheduler metrics and RLC metrics visualization at slot
    % boundary, if the update periodicity is reached
    if symbolNum > 1 && mod(symbolNum-1, 14) == 0 && mod(slotNum, simParameters.MetricsStepSize) == 0
        plotMetrics(simSchedulingLogger);
        plotMetrics(simRLCLogger);
    end
    
    % Advance timer ticks for gNB and UEs by 'tickGranularity' symbols
    advanceTimer(gNB, tickGranularity);
    for ueIdx = 1:simParameters.NumUEs % For all UEs
        advanceTimer(UEs{ueIdx}, tickGranularity);
    end
end

%
%% Simulation Visualization
% The five types of runtime visualization shown are:
% Display of CQI values for UEs over the PUSCH/PDSCH bandwidth: For details, see the 'Channel Quality Visualization' figure.
% Display of resource grid assignment to UEs: The 2-D time-frequency grid shows the resource allocation to the UEs. For slot based scheduling, it updates every 10 ms (frame length), and shows the RB allocation to the UEs in the previous frame. For symbol based scheduling, it updates every slot, and shows the RB allocation of symbols of the previous slot. The HARQ process for the assignments is also shown alongside the RNTI of the UEs. New transmissions are shown in black and retransmissions are shown in blue. Using the HARQ process ID of a UE, a retransmission assignment can be mapped to its previously failed transmission. For details, see the 'Resource Grid Allocation' figure.
% Display of uplink metrics plots: The 'Uplink Scheduler Performance Metrics' figure includes plots of the: UL throughput (per UE and cell), UL goodput (per UE and cell), resource share percentage among UEs (out of the total UL resources) to convey the fairness of scheduling, and pending UL buffer status of the UEs to show whether UEs are getting sufficient resources. The performance metrics plots update every metricsStepSize slots.
% Display of downlink metrics plots: Like uplink metrics plots, the 'Downlink Scheduler Performance Metrics' displays corresponding subplots for DL direction. The performance metrics plots update every metricsStepSize slots
% Display of RLC metrics plot: The 'RLC Throughput Visualization' figure shows the throughput of RLC logical channel for the selected UE. The RLC metrics plot updates every metricsStepSize slots.
%
%% Simulation Logs
% The parameters used for simulation and the simulation logs are saved in MAT-files for post simulation analysis and visualization. The simulation parameters are saved in MAT-file with filename as the value of configuration parameter simParameters.ParametersLogFile. The per time step logs, scheduling assignment logs, and RLC logs are saved in the MAT-file simParameters.SimulationLogFile. After the simulation, open it to load timeStepLogs, schedulingAssignmentLogs, and rlcLogs in the workspace.
% Time step logs: The table shows a sample time step entry. Each row of the table represents a symbol or a slot, based on the chosen scheduling type (symbol based or slot based). The information in a row is for DL, if the type of symbol (or slot) is DL. Likewise, for UL symbol (or slot).
%
%  Each row contains the following information:
% Frame: Frame number.
% Slot: Slot number in the frame.
% Symbol: Symbol number in the slot (Only for symbol based scheduling).
% Type: Symbol (or Slot) type as 'DL',  'UL', or 'Guard'. For slot based scheduling, type can only be DL/UL. As the slot containing the guard symbols is assumed to be a DL slot with guard symbols at the end of the slot.
% RBG Allocation Bitmap: N-by-P bitmap matrix, where N is the number of UEs and P is the number of RBGs in the bandwidth. If an RBG is assigned to a particular UE, the corresponding bit is set to 1. For example, [ 0 0 1 1 0 1 0 1 0 1 0 0 0; 1 1 0 0 0 0 0 0 0 0 1 0 0; 0 0 0 0 1 0 1 0 1 0 0 1 1; 0 0 0 0 0 0 0 0 0 0 0 0 0] means, that the bandwidth has 13 RBGs and UE-1 is assigned the RBG indices 2, 3, 5, 7, and 9; UE-2 is assigned the RBG indices: 0, 1, and 10; UE-3 is assigned the RBG indices: 4, 6, 8, 11, and 12; and UE-4 is not assigned any RBG.
% MCS: Row vector of length N where N is the number of UEs. Each value corresponds to the modulation and coding scheme (MCS) index for the PUSCH/PDSCH transmission. For example, [10 12 8 -1] means that only UE-1, UE-2, and UE-3 are assigned UL resources (symbol type is 'UL')  for this symbol and use MCS values 10, 12, and 8, respectively.
% HARQ Process: Row vector of length N, where N is the number of UEs. The value is the HARQ process ID used by UE for the PUSCH transmission or used by gNB for PDSCH transmission. For example, [0 3 6 -1] means that only UE-1, UE-2, and UE-3 are assigned UL resources (symbol type is 'UL')  for this symbol and use the HARQ process IDs 0, 3, and 6, respectively.
% NDI: Row vector of length N, where N is the number of UEs. The value is the NDI flag value in the assignment for PUSCH/PDSCH transmission. For example, [0 0 1 -1] means that only UE-1, UE-2, and UE-3 are assigned UL resources for this symbol and use the NDI flag values (which determine whether a new transmission or a retransmission is done) are 0, 0, and 1, respectively.
% Tx Type: Tx Type specifies the transmission type (new transmission or retransmission). Row vector of length N, where N is the number of UEs. Possible values are either 'newTx', 'reTx', or 'noTx'. 'noTx' means that the UE is not allotted PUSCH/PDSCH resources. For example: ['newTx' 'newTx' 'reTx' 'noTx'] means that only UE-1, UE-2, and UE-3 are assigned UL resources for this symbol. UE-1 and UE-2 transmit a new packet from the specified HARQ process, while UE-3 retransmits the packet in the buffer of the specified HARQ process.
% CQI for UEs: N-by-P matrix, where N is the number of UEs and P is the number of RBs in the bandwidth. A matrix element at position (i, j) corresponds to the CQI value for UE with RNTI i at RB j.
% HARQ NDI Status: N-by-P matrix, where N is the number of UEs and P is the number of HARQ processes. A matrix element at position (i, j) is the last received NDI flag at UE i for DL or UL HARQ process ID j. For new transmissions, this value and the NDI flag in the PUSCH/PDSCH assignment must toggle for the HARQ process in the assignment.
% Throughput Bytes: Row vector of length N, where N is the number of UEs. The values represent UL/DL MAC bytes transmitted by/for UEs in this symbol. Note that the total throughput bytes for the complete PUSCH/PDSCH transmission are shown in the row corresponding to the first symbol of the transmission.
% Goodput Bytes: Row vector of length N, where N is the number of UEs. The values represent new UL/DL transmission MAC bytes transmitted by/for UEs in this symbol. Like throughput, all the goodput bytes for the complete PUSCH/PDSCH are shown in the row corresponding to first symbol of the transmission.
% Buffer Status of UEs: Row vector of length N, where N is the number of UEs. The values represent the amount of UL direction pending buffers at UEs (or DL direction pending buffers for UEs at gNB).
% Rx Success/Failure: Row vector of length N, where N is the number of UEs. The possible values are either 'RxSuccess', 'RxFailure', or 'noRx'. 'noRx' means no DL (or UL) transmission is scheduled. For example: ['RxSuccess' 'RxSuccess' 'RxFailure' 'noRx'] means that packets from UE-1 and UE-2 are received successfully, while the packet from UE-3 was either not received or was corrupted. UE-4 was not scheduled for this symbol.
% Scheduling assignment logs: Information of all the scheduling assignments and related information is logged in this table. Each row is one UL/DL assignment. The table shows sample log entries.
%
% RLC logs: Refer to NR PUSCH FDD Scheduling example for RLC log format.
% You can run the script NRSchedulingTDDPostSimVisualization.m to get a post simulation visualization of logs. In the post simulation script, you get the variable isLogReplay, which provides these options to visualize 'Resource Grid Allocation' and 'Channel Quality Visualization' figures.
% Set isLogReplay to true for a replay of the simulation logs.
% Set isLogReplay to false to analyze the details of a particular frame or a particular slot of a frame. In the 'Resource Grid Allocation' window, input the frame number and slot number to visualize the resource assignment of the particular slot, if scheduling type is symbol based. For slot based scheduling, enter the frame number to visualize the resource assignment for the entire frame. The frame number entered here controls the frame number for  'Channel Quality Visualization' figure too.

% Read the logs and write them to MAT-files
timeStepLogs = getSchedulingLogs(simSchedulingLogger); % MAC scheduling logs
schedulingGrantLogs = getGrantLogs(simSchedulingLogger); % Scheduling assignments log
rlcLogs = getRLCLogs(simRLCLogger); % RLC statistics logs
save(simParameters.SimulationLogFile, 'timeStepLogs', 'schedulingGrantLogs', 'rlcLogs'); % Save simulation logs in a MAT-file
save(simParameters.ParametersLogFile, 'simParameters'); % Save simulation parameters in a MAT-file

%
%% Further Exploration
% You can modify the existing UL and DL scheduling functionality to schedule resources of a slot. The scheduling of UL resources of a slot is implemented in scheduleULResourcesSlot function of hNRGNBMACScheduler.m. Populate the output uplinkGrants of this function with an array of valid UL scheduling assignments. For slot based scheduling, uplinkGrants can have maximum one assignment per UE. For symbol based scheduling, a slot can have multiple assignments to a UE corresponding to different TTIs. Each element in this array is PUSCH grant for a UE. The PUSCH assignment structure has these fields:
% RNTI: Assignment is for this UE. This field is not part of the actual assignment packet sent to UEs.
% Type: Whether assignment is for new transmission ('newTx'), or retransmission ('reTx') ). This field is not part of actual assignment packet sent to UEs, because the NDI field is sufficient to convey that.
% HARQIndex: Selected UL HARQ process ID.
% RBGAllocationBitmap: Frequency-domain resource assignment. A bitmap of resource-block-groups of the PUSCH bandwidth. Value 1 indicates RBG is assigned to the UE.
% StartSymbol: Start symbol of PUSCH assignment.
% NumSymbols: Number of symbols allotted for PUSCH assignment.
% SlotOffset: Slot offset of PUSCH assignments with respect to the current slot (where scheduling is being done).
% MCS: Selected modulation and coding scheme.
% NDI: New data indicator flag.
% RV: Redundancy version.
% The screenshot shows UL resources of a slot being distributed in four PUSCH assignments. It also shows content of the fourth assignment.
%
% Similarly, the scheduleDLResourcesSlot function of hNRGNBMACScheduler.m implements the corresponding DL functionality. PDSCH assignment has one extra field 'FeedbackSlotOffset' as shown in the figure. The field is the slot offset of ACK/NACK transmission time from the PDSCH reception slot.
%
% Setting output for functions scheduleULResourcesSlot and scheduleDLResourcesSlot enables you to control the assignment of all the RBGs of a slot at once. You can also control the assignment of each RBG of a TTI. For reference, you can see the existing scheduling strategies present in the helper functions hNRSchedulingStrategyBestCQI.m, hNRSchedulingStrategyPF.m, and hNRSchedulingStrategyRR.m. Refer to NR PUSCH FDD Scheduling example for the procedure to implement a custom scheduling strategy.
% To extend the functionality of this example to support multiple logical channels, refer to NR PUSCH FDD Scheduling example.
% Based on the chosen scheduling strategy, this example demonstrates the assignment of UL and DL resources to multiple UEs by the gNB. The physical layer is abstracted in the example. UL and DL scheduling performance is analyzed based on runtime plots of MAC throughput, MAC goodput, resource share fairness, and pending buffer status of the UEs. A more thorough post simulation analysis by using the saved logs gives a detailed picture of the operations happening on a per symbol/slot basis.
%% Appendix
% The example uses these helper functions and classes:
% hNRSchedulingTDDValidateConfig.m: Validates simulation configuration
% hNRNodeBase.m: NR node base class
% hNRGNB.m: gNB node functionality
% hNRUE.m: UE node functionality
% hNRMACBase.m: NR MAC base class functionality
% hNRGNBMAC.m: gNB MAC functionality
% hNRGNBMACScheduler.m: gNB MAC scheduler functionality
% hNRUEMAC.m: UE MAC functionality
% hNewHARQProcesses.m: Creates new HARQ process
% hUpdateHARQProcess.m: Updates HARQ process
% hPDSCHTBS.m: PDSCH transport block size
% hPUSCHTBS.m: PUSCH transport block size
% hNRMACBSR.m: Generates buffer status report
% hNRMACBSRParser.m: Parses buffer status report
% hNRMACSubPDU.m: Generates MAC subPDU
% hNRMACPaddingSubPDU.m: Generates MAC subPDU with padding
% hNRMACMultiplex.m: Generates MAC PDU
% hNRMACPDUParser.m: Parses MAC PDU
% hNRPacketExchangeBuffer.m: Implements packet exchange buffers between UE and gNB
% hNRUplinkGrantFormat.m: UL assignment format
% hNRDownlinkGrantFormat.m: DL assignment format
% hNRSchedulingLogger.m: Implements scheduling information logging and visualization functionality
% hNRSchedulingStrategyBestCQI.m: Implements best CQI scheduling strategy
% hNRSchedulingStrategyPF.m: Implements proportional fair scheduling strategy
% hNRSchedulingStrategyRR.m: Implements round robin scheduling strategy
% hNRUMTransmitter.m: RLC UM transmitter functionality
% hNRUMReceiver.m: RLC UM receiver functionality
% hNRUMDataHeader.m: Generates RLC UM PDU header
% hNRUMDataPDUDecode.m: Decodes RLC PDU
% hNRUMDataPDUInfo.m: Creates RLC PDU information object
% hNRRLCDataReassemblyInfo.m: Create an RLC SDU reassembly information object
% hNRRLCBufferStatus.m: Generates RLC buffer status information object
% hNRRLCLogger.m: Implements RLC statistics logging and visualization functionality
% Copyright 2019 The MathWorks, Inc.
%