5G NR RLC Acknowledged Mode

In 5G NR, an RLC AM entity is bidirectional. The transmitting side of an entity communicates with its peer entity's receiving side. The transmitting side sends data PDUs while the receiving side acknowledges the transmission via control PDUs.

An AM Data (AMD) PDU always includes an RLC header, which in turn contains the Sequence Number (SN) of the RLC SDU. SDUs may be segmented. SN and Segment Offset (SO) are essential header fields that help with acknowledgements and retransmissions. STATUS PDU is the only type of control PDU and it too has a header.

Behaviour is controlled by state variables, constants and timers. Compared to the other two RLC transmission modes (TM and UM), AM is more complex.

Discussion

  • How does an AM RLC entity operate?
    Model of AM RLC entity. Source: ETSI 2021a, fig. 4.2.1.3.1-1.
    Model of AM RLC entity. Source: ETSI 2021a, fig. 4.2.1.3.1-1.

    The transmitting side receives RLC SDU from upper layer. It assigns the next available SN to this SDU. It sends an RLC header plus RLC SDU to MAC. If the entire SDU can't be sent in a single transmission, it's segmented. Header plus the largest possible segment are given to MAC.

    The receiving side acknowledges via ACK_SN (good SDUs) and NACK_SN (lost SDUs or segments). If all segments of an SDU are correctly received, it forwards the RLC SDU to upper layer. Order of delivery is not important for RLC.

    Transmitting side retransmits SDUs or segments previously lost. While retransmitting, it may re-segment lost portions of the SDU. RLC is permitted a maximum number of retransmissions, after which the error is indicated to upper layer.

    For flow control, both sides obey the window size, which depends on the SN bit length: 2048 (12-bit SN) or 131072 (18-bit SN).

    Via polling, transmitting side can request its peer to send current status. Receiving side has a timer to regular status transmissions. Likewise, transmitting side has a timer to regulate polling.

  • Could you explain the state variables used by an AM RLC entity at the transmitting side?

    The following state variables exist at the transmitting side of AM RLC:

    • TX_Next: Send state variable. Initialized to 0. Assigned as SN to the next RLC SDU received from upper layer; and then incremented.
    • TX_Next_Ack: Determines lower edge of the transmit window. It's the SN of next in-sequence SDU that needs to be positively acknowledged. Initialized to 0. Updated when there's positive acknowledgment for SDU with SN=TX_Next_Ack. It's obvious that TX_Next_Ack ≤ TX_Next.
    • POLL_SN: Highest SN among all PDUs submitted to lower layer at the time of polling. Initialized to 0.
  • Could you explain the counters used by an AM RLC entity at the transmitting side?

    The following counters exist at the transmitting side of AM RLC:

    • PDU_WITHOUT_POLL: Number of PDUs sent since the most recent poll. Initialized to 0.
    • BYTE_WITHOUT_POLL: Number of bytes sent since the most recent poll. Initialized to 0.
    • RETX_COUNT: Number of retransmissions of an SDU or its segment. One counter per RLC SDU.

    Relevant to the counters are three parameters that RRC configures:

    • maxRetxThreshold: Limits the number of retransmissions. When RETX_COUNT reaches this limit, upper layers are indicated of SDU transmission failure. This may lead to an RRC Re-establishment procedure.
    • pollPDU: When PDU_WITHOUT_POLL reaches or exceeds this value, polling is triggered.
    • pollByte: When BYTE_WITHOUT_POLL reaches or exceeds this value, polling is triggered.
  • Could you explain the state variables used by an AM RLC entity at the receiving side?

    The following state variables exist at the receiving side of AM RLC:

    • RX_Next: Determines the lower edge of the receive window. Initialized to 0. Updated when an SDU with SN=RX_Next is fully received.
    • RX_Next_Highest: Contains the next SN following the highest SN of all received SDUs, even if the highest-SN SDU is not fully received. Thus, RX_Next ≤ RX_Next_Highest. Initialized to 0.
    • RX_Highest_Status: Highest possible value to be indicated in ACK_SN field of STATUS PDU. SDUs with lower SN that haven't been fully received will be indicated with NACK_SN in the STATUS PDU. Initialized to 0.
    • RX_Next_Status_Trigger: SN following the SN of the RLC SDU that triggered t-Reassembly. Thus, if SN=2 is partially received and t-Reassembly is started for it, this variable is set to 3.
  • Could you illustrate the update of RLC AM receiving side state variables?
    Illustrating update of RLC AM receiving side state variables in four scenarios. Source: Devopedia 2021.
    Illustrating update of RLC AM receiving side state variables in four scenarios. Source: Devopedia 2021.

    This example is based on RLC specification TS 38.322. It's accompanied by code in the Sample Code section of this article.

    We present three scenarios. All start with SN=0 fully received, then SN=1 and SN=2 partially received. When SN=1 is partially received, t-Reassembly is started. When SN=2 is partially received, there's no action on the timer since it's already running for SN=1.

    In scenario (a), SN=1 is fully received. Thus, t-Reassembly is stopped and reset. However, since SN=2 was partially received earlier, t-Reassembly is started for SN=2. When SN=2 is later fully received, reassembly is completed for this SDU, and timer is stopped and reset.

    In scenario (b), SN=1 is fully received and hence t-Reassembly is restarted for SN=2. When t-Reassembly expires (could not reassemble SN=2 soon enough), status reporting is triggered. STATUS PDU will include NACK for SN=2.

    In scenario (c), SN=3 is partially received. When t-Reassembly expires (could not reassemble SN=1 soon enough), status reporting is triggered. STATUS PDU will include NACK for SN=1 and SN=2, and possibly for SN=3.

  • Could you explain the timers used by an RLC AM entity?
    Triggering and sending STATUS PDU. Source: Adapted from EventHelix 2019.
    Triggering and sending STATUS PDU. Source: Adapted from EventHelix 2019.

    An RLC AM entity maintains three timers:

    • t-PollRetransmit: At the transmitting side. Started or restarted when poll bit is set in an AMD PDU. Stopped when STATUS PDU is received indicating ACK/NACK for POLL_SN, which is the highest SN transmitted at the time of the last poll. At expiry, initiate data retransmissions and poll retransmission.
    • t-Reassembly: At the receiving side. Detects loss of RLC PDUs. Started when a segment is received but more segments are pending for that SDU. Stopped when SDU is completely received. At expiry, triggers status reporting.
    • t-StatusProhibit: At the receiving side. Prohibits frequent transmissions of STATUS PDU. Started when a STATUS PDU is sent. Expiry of t-Reassembly triggers a status report but STATUS PDU is sent only when t-StatusProhibit expires. Status reporting is triggered either by polling (transmitting side initiated) or t-Reassembly expiry (receiving side initiated).

    At most only one of each of the above timers can be running in an AM RLC entity.

Milestones

Apr
2017

An early draft of 5G NR RLC specification TS 38.322 is released. This evolves to version 1.0.0 by September.

Dec
2017

3GPP publishes Release 15 "early drop". RLC specification TS 38.322 is updated to version 15.0.0.

Jun
2018

The setting of POLL_SN is corrected in the specification.

Jul
2020

3GPP publishes Release 16 specifications. RLC specification is TS 38.322 version 16.1.0.

Sample Code

  • import math
     
     
    class RlcAmRx(object):
        ''' Simplistic implementation of RLC AM entity receiving side.
        This code is only for demo purpose to help understand RLC operation.
        It doesn't consider receive window size or SDU segments.
        It only checks if an SDU has been fully received or not.
        Construction of STATUS PDU and t-StatusProhibit timer are not coded.
        '''
        def __init__(self):
            self.reset_entity()
     
        def reset_entity(self):
            ''' Reset RLC entity. '''
            self.rx_next = 0
            self.rx_next_highest = 0
            self.rx_highest_status = 0
            self.rx_next_status_trigger = None
            self.t_assembly_actions = []
            self.t_assembly_state = None
            self.reassembly_buffer = []
     
        def process_pdu(self, pduid):
            ''' TS 38.322, v16.2.0, sec. 5.2.3.2.3 '''
            sn = math.trunc(pduid)
            fullsdu = sn == pduid
     
            self.t_assembly_actions = []
     
            if fullsdu:
                if sn in self.reassembly_buffer:
                    self.reassembly_buffer.remove(sn)
            else:
                if sn not in self.reassembly_buffer:
                    self.reassembly_buffer.append(sn)
     
            if sn >= self.rx_next_highest:
                self.rx_next_highest = sn + 1
     
            if fullsdu:
                if sn == self.rx_highest_status:
                    for waitsn in self.reassembly_buffer:
                        if waitsn > self.rx_highest_status:
                            self.rx_highest_status = waitsn
                            break
                    else:
                        self.rx_highest_status = self.rx_next_highest
     
                if sn == self.rx_next:
                    for waitsn in self.reassembly_buffer:
                        if waitsn > self.rx_next:
                            self.rx_next = waitsn
                            break
                    else:
                        self.rx_next = self.rx_next_highest
     
            if self.t_assembly_state == 'running':
                if  self.rx_next_status_trigger == self.rx_next or \
                    self.rx_next_status_trigger == self.rx_next + 1 and \
                        self.rx_next not in self.reassembly_buffer:
                    self.t_assembly_actions.append('stopped+reset')
                    self.t_assembly_state = None
     
            if self.t_assembly_state != 'running':
                if  self.rx_next_highest > self.rx_next + 1 or \
                    self.rx_next_highest == self.rx_next + 1 and \
                        self.rx_next in self.reassembly_buffer:
                    self.t_assembly_actions.append('started')
                    self.t_assembly_state = 'running'
                    self.rx_next_status_trigger = self.rx_next_highest
     
        def t_assembly_expiry(self):
            ''' TS 38.322, v16.2.0, sec. 5.2.3.2.4 '''
            self.t_assembly_actions = ['expired']
            self.t_assembly_state = None
     
            for waitsn in self.reassembly_buffer:
                if waitsn >= self.rx_next_status_trigger:
                    self.rx_highest_status = waitsn
                    break
            else:
                self.rx_highest_status = self.rx_next_highest
     
            if  self.rx_next_highest >= self.rx_highest_status + 1 or \
                self.rx_next_highest == self.rx_highest_status + 1 and \
                    self.rx_highest_status in self.reassembly_buffer:
                    self.t_assembly_actions.append('started')
                    self.t_assembly_state = 'running'
                    self.rx_next_status_trigger = self.rx_next_highest
     
        def print_state(self):
            vars = ('rx_next', 'rx_next_highest',
                    'rx_highest_status', 'rx_next_status_trigger',
                    't_assembly_state', 't_assembly_actions')
            print('  '.join(vars))
            for v in vars:
                print('{0}'.format(str(getattr(self, v)).ljust(len(v))), end='  ')
            print('\n')
     
     
    if __name__ == '__main__':
        # Test data: decimals for missing bytes, -1 for t_assembly expiry
        seqs = (
            (0, 1.1, 2.1, 1, 2),
            (0, 1.1, 2.1, 1, -1),
            (0, 1.1, 2.1, 3.1, -1)
        )
     
        for seq in seqs:
            amrx = RlcAmRx()
            print(F'>>> Sequence: {seq}')
            for pdu in seq:
                print(F'PDU: {pdu}')
                if pdu < 0: amrx.t_assembly_expiry()
                else: amrx.process_pdu(pdu)
                amrx.print_state()
     

References

  1. 3GPP. 2018. "R2-1808359: CR on updating POLL_SN value and selecting the RLC SDU for retransmission." Change request, 3GPP TSG-RAN WG2 Meeting #102, May 21-25. Accessed 2021-03-23.
  2. 3GPP. 2020a. "Release 16." 3GPP. Accessed 2021-03-09.
  3. Dano, Mike. 2019. "Another set of 5G standards was just released, but no one really cares." LightReading, April 5. Accessed 2021-02-25.
  4. ETSI. 2020a. "TS 138 323: 5G; NR; Packet Data Convergence Protocol (PDCP) specification." V16.2.0, November. Accessed 2021-03-18.
  5. ETSI. 2021a. "TS 138 322: 5G; NR; Radio Link Control (RLC) protocol specification." V16.2.0, January. Accessed 2021-03-18.
  6. EventHelix. 2019. "5G NR RLC Acknowledged Mode." 5G NR, on Medium, October 5. Accessed 2021-03-20.
  7. Jaji, Mangala and Prerit Jain. 2020. "5G NR Layer 2 – Radio Link Control (RLC) Overview." Techplayon, October 13. Accessed 2021-03-18.
  8. Ou, Todd, Maciej Żenczykowski, Qin Zhang, Yu Wang, and Yu-Cheng Chen. 2020. "5G DL Out-Of-Order Delivery." Technical Disclosure Commons, April 9. Accessed 2021-03-23.
  9. Parida, Uday. 2020. "Failures in 5G Implementation – Part 2." Blog, Simnovus, May 27. Updated 2020-09-24. Accessed 2021-03-23.
  10. ShareTechnote. 2021. "5G/NR - RLC." ShareTechnote. Accessed 2021-03-18.

Further Reading

  1. ETSI. 2021a. "TS 138 322: 5G; NR; Radio Link Control (RLC) protocol specification." V16.2.0, January. Accessed 2021-03-18.
  2. ShareTechnote. 2021. "5G/NR - RLC." ShareTechnote. Accessed 2021-03-18.
  3. EventHelix. 2019. "5G NR RLC Acknowledged Mode." 5G NR, on Medium, October 5. Accessed 2021-03-20.

Article Stats

Author-wise Stats for Article Edits

Author
No. of Edits
No. of Chats
DevCoins
6
0
1280
1129
Words
7
Likes
7866
Hits

Cite As

Devopedia. 2021. "5G NR RLC Acknowledged Mode." Version 6, March 24. Accessed 2023-11-13. https://devopedia.org/5g-nr-rlc-acknowledged-mode
Contributed by
1 author


Last updated on
2021-03-24 02:48:00