HARQ (Downlink & Uplink) in 5G NR
Stop-and-wait HARQ processes, soft combining, NDI/RV, and how UL differs from DL.
A radio link makes errors, and it makes them in bursts. The natural engineering response — encode conservatively enough that errors never happen — is enormously wasteful, because the coding has to be sized for the worst instant rather than the average one. NR instead deliberately schedules to fail: the link adaptation loop aims at roughly a 10 % block error rate on first transmission, accepts that one block in ten will not decode, and recovers those with a retransmission mechanism cheap enough that the trade is worth it.
Contents
- 01Why HARQ Exists, and the Division of Labour with RLC ARQ
- 02Stop-and-Wait, Parallelised: Processes and the Pipeline
- 03Asynchronous in Both Directions — and Where LTE's Model Misleads
- 04The New Data Indicator: One Bit That Decides Everything
- 05The Circular Buffer, Rate Matching and Redundancy Versions
- 06Base Graphs, Code Blocks and the Transport Block Size
- 07Soft Combining, the Soft Buffer and Limited Buffer Rate Matching
- 08K1 and K2: the Timing Offsets, Worked to an Absolute Slot
- 09HARQ-ACK Codebooks: Type-1, Type-2 and Type-3
- 10Code Block Group Based Transmission
- 11Uplink HARQ Without a PHICH
- 12Disabled HARQ Feedback and Very Long Round Trips
- 13HARQ, DRX, and the Timers That Connect Them
- 14Parameter Reference: ASN.1, Ranges and Effects
- 15Failure Modes and What Each One Means
- 16ASN.1 Extracts
- 17Illustrative Message Traces
- 18Release Deltas: Rel-15 to Rel-18
- 19Reading HARQ in Logs: A Checklist
- 20Glossary
- 21References
1. Why HARQ Exists, and the Division of Labour with RLC ARQ
A radio link makes errors, and it makes them in bursts. The natural engineering response — encode conservatively enough that errors never happen — is enormously wasteful, because the coding has to be sized for the worst instant rather than the average one. NR instead deliberately schedules to fail: the link adaptation loop aims at roughly a 10 % block error rate on first transmission, accepts that one block in ten will not decode, and recovers those with a retransmission mechanism cheap enough that the trade is worth it.
HARQ is that mechanism. The hybrid in the name is the important part. Plain ARQ discards a failed transmission and sends it again; HARQ keeps the failed attempt as soft information and combines it with the retransmission, so the second attempt starts from an accumulated signal rather than from nothing. That means a retransmission does not have to be independently decodable — it only has to close the gap — which is why HARQ recovers from a marginal failure in one extra attempt where plain ARQ might need several.
There are two retransmission layers in NR and the separation is deliberate. Each is bad at what the other is good at:
| HARQ (MAC/PHY) | RLC ARQ (Acknowledged Mode) | |
|---|---|---|
| Timescale | A handful of slots — hundreds of microseconds to a few milliseconds | Tens of milliseconds; bounded by t-PollRetransmit and the status-report cadence |
| Unit of retransmission | A transport block, or a code block group within it (§10) | An RLC PDU or a segment of one, identified by sequence number |
| Feedback | One bit per transport block, carried on PUCCH or multiplexed on PUSCH | An RLC STATUS PDU listing missing sequence numbers — much richer, much slower |
| Combining | Soft combining of stored channel information | None — a retransmitted PDU is decoded on its own |
| Failure model | Feedback itself can be misread (an ACK/NACK error), so HARQ is not reliable on its own | Sequence numbers make loss detectable with certainty |
| Retry budget | Not configured anywhere in NR — entirely scheduler policy (§15) | maxRetxThreshold (t1 … t32); exceeding it is an RLC failure and triggers RLF |
| Typical residual error left behind | ≈10⁻² to 10⁻³ after a few attempts | ≈10⁻⁴ to 10⁻⁶, which is what TCP and the 5QI budgets assume |
Table 1. The two layers, and why both are needed. Residual error figures are typical design targets rather than specified values.
HARQ is fast but fundamentally unreliable, and that is the reason RLC ARQ exists at all. The feedback is a single bit with no CRC of its own: a NACK misread as an ACK loses a transport block silently, and an ACK misread as a NACK wastes a retransmission. PUCCH is therefore power-controlled to a much lower error target than PDSCH — commonly around 10⁻³ for the ACK-to-NACK direction — but it is never zero, so something above HARQ has to be able to notice a gap. That something is the RLC sequence number.
The error budget cascades. Reading it top to bottom is the clearest way to see why the link adaptation target is deliberately poor:
| Stage | Typical error rate | What removes it | What is left over |
|---|---|---|---|
| PDSCH first transmission | ≈10⁻¹ (the link adaptation target) | HARQ retransmission with incremental redundancy | Blocks whose second attempt also fails |
| After 2–4 HARQ attempts | ≈10⁻² … 10⁻³ | RLC ARQ, from a status report | PDUs lost because a NACK was misread as an ACK, or because the process was flushed |
| After RLC ARQ (AM) | ≈10⁻⁴ … 10⁻⁶ | PDCP duplication, or the application | Effectively nothing at the radio layer; what remains is a latency problem, not a loss problem |
Table 2. Why aiming at 10 % BLER is correct rather than sloppy: the spectral efficiency gained by coding for the average instead of the worst case far exceeds the cost of retransmitting one block in ten. Figures are design targets, not specified requirements.
2. Stop-and-Wait, Parallelised: Processes and the Pipeline
One HARQ process is the simplest possible protocol: send a transport block, wait for the answer, then either send new data or resend. The problem is the waiting. At 30 kHz a slot is 0.5 ms, and the round trip from transmission to the earliest possible retransmission is typically five to ten slots once UE decoding time, feedback timing and the gNB's own turnaround are included. A single stop-and-wait process would leave the link idle for nine slots out of ten.
NR runs many processes in parallel, each an independent stop-and-wait instance identified by a HARQ process ID carried explicitly in the scheduling DCI. While process 3 waits, processes 4, 5, 6 and the rest carry new data. Stop-and-wait applies per process; it never applies to the link.
2.1 How many processes, and why
Two things bound the count. The first is arithmetic: the HARQ process number field in DCI formats 1_0/1_1 and 0_0/0_1 is 4 bits, so Rel-15 and Rel-16 can address at most 16. The second is what the pipeline actually needs, which is roughly the round trip divided by the slot duration — and since the round trip is dominated by fixed processing time while the slot shrinks with numerology, the needed count grows with SCS. That is exactly why Rel-17, introducing 480 and 960 kHz for FR2-2 and non-terrestrial links with round trips measured in hundreds of milliseconds, had to widen the field to 5 bits and allow 32.
| Channel / use | ASN.1 | Range | Default | Notes |
|---|---|---|---|---|
| PDSCH, dynamic | nrofHARQ-ProcessesForPDSCH in PDSCH-ServingCellConfig | n2, n4, n6, n10, n12, n16 | 8 (if absent) | Per serving cell. The default is 8, not 16 — a common surprise when comparing two cells that look identically configured. |
| PDSCH, Rel-17 | nrofHARQ-ProcessesForPDSCH-v1700 | adds n32 | — | For FR2-2 numerologies and NTN. Requires the 5-bit DCI field. |
| PUSCH, dynamic | fixed in Rel-15/16 | 16 | 16 | Not configurable. Rel-17 adds nrofHARQ-ProcessesForPUSCH-r17 with an n32 option. |
| PDSCH, SPS | nrofHARQ-Processes in SPS-Config | 1 … 8 | — | A configured downlink assignment derives its process ID from the timing, so this bounds a separate small pool (§11.2). |
| PUSCH, configured grant | nrofHARQ-Processes in ConfiguredGrantConfig | 1 … 16 | — | Same idea, with harq-ProcID-Offset (Rel-16) to keep it from colliding with the dynamic pool. |
Table 3. Where the process count comes from. See the companion 08 Scheduling document for SPS and configured grants themselves.
The process count is not free. Every process needs its own soft buffer, and the UE's total soft-buffer memory is a capability, not a configuration — so doubling the process count halves the soft bits available per process unless limited buffer rate matching is in play (§7). Configuring n16 on a cell that only ever needs six processes does not improve throughput; it can reduce the combining gain available to each retransmission.
3. Asynchronous in Both Directions — and Where LTE's Model Misleads
Anyone arriving from LTE carries a mental model that is half wrong here. In LTE, downlink HARQ was asynchronous (the process ID was signalled) but uplink HARQ was synchronous: retransmissions happened on a fixed 8 ms cycle and the process ID was implied by the subframe number, with no field for it anywhere. NR abandoned that. Both directions are asynchronous, and both carry the process ID explicitly in the DCI.
| Downlink (PDSCH) | Uplink (PUSCH) | |
|---|---|---|
| Process ID | Explicit, 4 bits in DCI 1_0/1_1/1_2 | Explicit, 4 bits in DCI 0_0/0_1/0_2 — this is the LTE difference |
| Retransmission timing | Any later slot the scheduler chooses | Any later slot the scheduler chooses, subject to K2 and the UE's preparation time N2 |
| Feedback | Explicit: one HARQ-ACK bit per TB on PUCCH, or multiplexed on PUSCH | None. There is no PHICH in NR. The next grant for that process is the feedback (§11) |
| How the receiver tells new from repeat | NDI toggle | NDI toggle — identical logic, opposite direction |
| Soft combining held by | The UE | The gNB |
| Where timing is bounded | K0 (DCI→PDSCH) and K1 (PDSCH→HARQ-ACK) | K2 (DCI→PUSCH); no feedback offset exists to bound |
Table 4. Downlink and uplink HARQ in NR. The single genuinely asymmetric row is feedback: the downlink has an explicit channel for it, the uplink does not.
There is one place where timing-derived process IDs survive, and it is worth knowing because it looks like the LTE behaviour: configured grants and semi-persistent scheduling. A transmission on a configured resource has no DCI, so there is no field to carry the process ID; the specification therefore derives it from the time the transmission occurs TS 38.321 cl. 5.4.1:
Uplink configured grant, HARQ process ID:
HARQ Process ID = floor( CURRENT_symbol / periodicity )
modulo nrofHARQ-Processes
( + harq-ProcID-Offset2, Rel-16 )
CURRENT_symbol = SFN x numberOfSlotsPerFrame x numberOfSymbolsPerSlot
+ slot number in the frame x numberOfSymbolsPerSlot
+ symbol number in the slot
Downlink SPS uses the equivalent expression over slots and the SPS
periodicity, modulo the SPS nrofHARQ-Processes.This formula is a real source of confusion in traces. A configured-grant transmission that the gNB does schedule a retransmission for gets a DCI with an explicit process ID, and that ID must match the one the formula produced — if the periodicity or nrofHARQ-Processes on the two sides disagree even slightly, the gNB retransmits into a process the UE has something else in. The symptom is corrupted uplink data with no HARQ errors reported by either side, which is about as unhelpful a symptom as exists.
4. The New Data Indicator: One Bit That Decides Everything
The receiver has to answer one question before it does anything else: is this transport block new, or is it another attempt at the one I already have soft bits for? Nothing else in the DCI answers it. The process ID is the same either way. The transport block size is the same. The RNTI is the same. The redundancy version hints at it but does not decide it — a retransmission can legitimately reuse RV0. The answer is carried by a single bit, the New Data Indicator, and the semantics are a toggle, not a flag:
- NDI differs from the value stored for that process → new data. Flush the soft buffer, store the new NDI value, decode what just arrived on its own.
- NDI is unchanged → retransmission. Keep the stored soft bits, add the new coded bits at the offset the RV indicates, decode the combination.
- No stored value for that process (first use, or after a flush) → treat as new data.
A toggle rather than a flag is a deliberate robustness choice. If new data were signalled by NDI = 1 and a retransmission by NDI = 0, then two consecutive new transport blocks on the same process would both carry NDI = 1 and be indistinguishable — the receiver would soft-combine the second with the first and produce garbage. Because the semantics are "different from last time", each new TB inverts the bit and every transition is visible. The cost is that the NDI is only meaningful relative to state the receiver holds, which is why a UE that has lost that state (buffer flush after timeAlignmentTimer expiry, DRX wake-up with a stale process) treats the next reception as new data rather than guessing.
5. The Circular Buffer, Rate Matching and Redundancy Versions
The LDPC encoder does not produce a transmission — it produces a pool of coded bits far larger than any single transmission needs. For base graph 1 the encoder output per code block is 66·Z_c bits, of which 22·Z_c are systematic (the information bits themselves) and 44·Z_c are parity. That pool is written into a circular buffer, and rate matching is the act of reading a contiguous run out of it, wrapping at the end, long enough to fill the resource elements the scheduler allocated.
The redundancy version is simply where in that buffer the read starts. It is not a different encoding, not a different puncturing pattern, and not a mode. Two bits in the DCI select one of four starting offsets TS 38.212 cl. 5.4.2.1, Table 5.4.2.1-2:
| rv_id | k_0 for base graph 1 | As % of N_cb | k_0 for base graph 2 | As % of N_cb |
|---|---|---|---|---|
| 0 | 0 | 0 % | 0 | 0 % |
| 1 | ⌊17·N_cb / (66·Z_c)⌋ · Z_c | 25.8 % | ⌊13·N_cb / (50·Z_c)⌋ · Z_c | 26 % |
| 2 | ⌊33·N_cb / (66·Z_c)⌋ · Z_c | 50 % | ⌊25·N_cb / (50·Z_c)⌋ · Z_c | 50 % |
| 3 | ⌊56·N_cb / (66·Z_c)⌋ · Z_c | 84.8 % | ⌊43·N_cb / (50·Z_c)⌋ · Z_c | 86 % |
Table 5. Redundancy version starting positions. The percentages assume no limited buffer rate matching, so N_cb = N; with LBRM the offsets compress towards the start of the buffer (§7).
Three consequences follow, and all three show up in logs:
- RV0 is self-decodable and the others are generally not. RV0 starts at the systematic bits, so a receiver with an empty buffer can decode it alone if the channel is good enough. A UE that receives RV2 as its first sight of a transport block — because it missed the RV0 transmission — will usually fail, and correctly so.
- Chase combining and incremental redundancy are the same mechanism. Sending RV0 repeatedly is chase combining: identical bits, so the receiver accumulates energy. Sending RV0 then RV2 is incremental redundancy: different bits, so the receiver accumulates code rate. IR is stronger and is the default; CC is what you get when the allocation is so large that a single transmission already reads the whole buffer and wrapping makes every RV equivalent.
- The order 0/2/3/1 is not a rule the UE enforces. It is the sequence TS 38.214 mandates for repetition (
pdsch-AggregationFactorandpusch-AggregationFactor, Tables 5.1.2.1-2 and 6.1.2.1-2) and the sequence essentially every scheduler uses for dynamic retransmission. The DCI carries an arbitrary rv_id, so a scheduler is free to send 0, 0, 0, 0 — and some do, in poor coverage, because chase combining needs no buffer state at the UE.
Real numbers, from the transport block worked out in §6:
Base graph 1, Z_c = 384, so N = N_cb = 66 × 384 = 25 344 bits per code block.
rv0: k_0 = 0
rv1: ⌊17 × 25 344 / (66 × 384)⌋ × 384 = ⌊17⌋ × 384 = 6 528 (25.8 %)
rv2: ⌊33 × 25 344 / 25 344⌋ × 384 = 33 × 384 = 12 672 (50.0 %)
rv3: ⌊56 × 25 344 / 25 344⌋ × 384 = 56 × 384 = 21 504 (84.8 %)
Each transmission in that example carries E ≈ 13 200 bits per code block, i.e. about 52 % of the buffer. So:
RV0 covers 0 … 13 200 (systematic plus early parity)
RV2 covers 12 672 … 25 344 + a 528-bit wrap -> ~96 % new bits
RV3 covers 21 504 … 25 344 then 0 … 9 360 -> mixed, mostly new
After RV0 and RV2 the receiver holds nearly the entire circular buffer, which is a code rate of about 0.30 against an original 0.60 — an effective doubling of the coding, from one retransmission.
6. Base Graphs, Code Blocks and the Transport Block Size
Before there is a circular buffer there is a choice of LDPC base graph and a segmentation of the transport block into code blocks, and both depend on the transport block size and the target code rate. This is worth following once end to end, because the code block count is what sets the CBG granularity in §10 and the per-code-block rate matching length in §5.
6.1 Which base graph
| Condition on TBS (A) and target rate (R) | Base graph | K_cb | Mother code rate | Why |
|---|---|---|---|---|
| A ≤ 292 | BG2 | 3840 | ≈1/5 | Very small blocks. BG2 is optimised for short payloads and low rates; BG1's structure wastes little but gains nothing here. |
| A ≤ 3824 and R ≤ 0.67 | BG2 | 3840 | ≈1/5 | Small and not aggressively coded — the regime BG2 was designed for. |
| R ≤ 0.25 | BG2 | 3840 | ≈1/5 | Any size, if the rate is very low. BG2 reaches a lower mother rate, so it has more parity to give. |
| Otherwise | BG1 | 8448 | ≈1/3 | Large blocks or high rates. BG1 has the better high-rate performance and a bigger maximum code block. |
Table 6. LDPC base graph selection, TS 38.212 cl. 7.2.2 (downlink) and cl. 6.2.2 (uplink). The rules are evaluated in order and the first match wins.
6.2 Segmentation
B = A + 24 -- transport block CRC (CRC24A)
if B <= K_cb : C = 1, B' = B -- no segmentation,
-- no code block CRC
else : L = 24 -- CRC24B per code block
C = ceil( B / (K_cb - L) )
B' = B + C x L
K' = B' / C -- bits per code block
Z_c = smallest lifting size with K_b x Z_c >= K'
( K_b = 22 for BG1; 10, 9, 8 or 6 for BG2 depending on B )
K = K_b_max x Z_c -- 22 Z_c for BG1
filler bits = K - K' -- set to NULL, not sent
N = 66 x Z_c (BG1) or 50 x Z_c (BG2) -- circular buffer size6.3 Transport block size, worked
TBS is not a free parameter: it is derived from the allocation, the MCS and the overhead, then quantised so that both ends compute the same value without signalling it TS 38.214 cl. 5.1.3.2. Here is one, all the way through, and the numbers feed §5 and §10.
Given: 30 kHz SCS, 100 PRB, 1 layer, MCS 15 from the 64QAM table (Q_m = 4, R = 616/1024 = 0.6016), 12 OFDM symbols of PDSCH, one front-loaded DMRS symbol with two CDM groups without data (N_DMRS = 12 RE/PRB), xOverhead absent (N_oh = 0).
N_RE' = 12 × 12 − 12 − 0 = 132 RE per PRB
N_RE = min(156, 132) × 100 = 13 200 RE
N_info = 13 200 × 0.6016 × 4 × 1 = 31 764 bits
N_info > 3824, so: n = ⌊log₂(31 764 − 24)⌋ − 5 = 14 − 5 = 9, 2⁹ = 512
N_info' = max(3840, 512 × round(31 740 / 512)) = 512 × 62 = 31 744
R > 1/4 and N_info' > 8424, so:
C = ⌈(31 744 + 24) / 8424⌉ = ⌈3.77⌉ = 4 code blocks
TBS = 8 × 4 × ⌈31 768 / 32⌉ − 24 = 32 × 993 − 24 = 31 752 bits
= 3969 bytes
Check the effective rate. Coded bits available = 13 200 × 4 = 52 800. Payload plus CRCs = 31 752 + 24 + 4×24 = 31 872. Effective code rate = 31 872 / 52 800 = 0.604, against a target of 0.602 — the quantisation cost is 0.3 %.
Base graph. TBS = 31 752 > 3824 and R = 0.602 > 0.25 → BG1, K_cb = 8448. B = 31 776 > 8448, so C = ⌈31 776 / 8424⌉ = 4 — the same answer the TBS formula produced, as it must. K' = 31 872 / 4 = 7968; Z_c = 384 (the smallest lifting size with 22·Z_c ≥ 7968); K = 8448, so 480 filler bits per code block.
The min(156, N_RE') cap in step two is easy to miss and it bites. It exists so that TBS does not grow without limit as the DMRS and overhead assumptions change, but the practical effect is that above 13 REs per symbol per PRB the extra resource elements are simply not counted. A scheduler that reduces DMRS overhead expecting a proportional TBS increase gets nothing back once the cap binds. In a log this looks like TBS refusing to move while the allocation changes.
7. Soft Combining, the Soft Buffer and Limited Buffer Rate Matching
What the receiver stores after a failed attempt is not the received bits but their log-likelihood ratios — soft values expressing how confident the demodulator is about each coded bit, positive for a likely zero and negative for a likely one, with magnitude proportional to confidence. When a retransmission arrives, the receiver adds the new LLRs into the same buffer at the positions the RV offset indicates. Positions covered twice accumulate confidence; positions covered for the first time fill in previously unknown parity. Then it runs the LDPC decoder over the whole accumulated buffer, not over the retransmission.
That is why the retransmission does not have to be independently decodable, and it is also why the soft buffer is expensive: it needs several bits of storage per coded bit, per code block, per HARQ process, per carrier. A UE aggregating four carriers with 16 processes each and four code blocks per transport block is holding 256 code blocks' worth of soft values.
Limited buffer rate matching is the escape valve. Instead of the full circular buffer N, rate matching is restricted to the first N_cb bits of it TS 38.212 cl. 5.4.2.1:
N_cb = N if LBRM is not in use N_cb = min( N , N_ref ) if LBRM is in use N_ref = floor( TBS_LBRM / (C x R_LBRM) ) with R_LBRM = 2/3 TBS_LBRM is computed from a reference configuration: the maximum number of layers the UE is configured for on that cell, 256QAM if supported, the largest configured bandwidth part, and a code rate of 948/1024. Consequence: the RV offsets in S5 are computed from N_cb, not N, so with LBRM they all move towards the start of the buffer -- and the highest-parity part of the code is never transmitted at all.
LBRM is a memory-versus-performance trade, and it changes what retransmissions are worth. With N_cb clipped to two thirds of N, RV3's offset lands at 84.8 % of a smaller buffer, so the fourth attempt re-sends bits the receiver already has instead of new parity — incremental redundancy quietly degrades toward chase combining as the attempt count rises. If a link shows good gain from the first retransmission and almost none from the third, LBRM is a candidate explanation before anything to do with the channel.
8. K1 and K2: the Timing Offsets, Worked to an Absolute Slot
NR signals every scheduling relationship as a slot offset, so that the same DCI formats work at every numerology and in every TDD pattern. Three offsets matter for HARQ:
| Offset | From → to | Where the value comes from | Range | What constrains it |
|---|---|---|---|---|
| K0 | DCI → PDSCH | k0 in the selected row of pdsch-TimeDomainAllocationList | 0 … 32 slots | K0 = 0 is same-slot scheduling: lowest latency, highest UE processing demand. Cross-slot (K0 ≥ 1) lets the UE micro-sleep through the PDCCH decode. |
| K1 | PDSCH → HARQ-ACK | Indexed by the 0–3 bit PDSCH-to-HARQ_feedback timing indicator into dl-DataToUL-ACK; DCI 1_0 uses a fixed set instead | dl-DataToUL-ACK: up to 8 entries, each 0 … 15 slots. DCI 1_0: 1 … 8 | Must be at least the UE's PDSCH processing time N1, and must land on a slot with usable uplink symbols — which in TDD is the binding constraint. |
| K2 | DCI → PUSCH | k2 in the selected row of pusch-TimeDomainAllocationList | 0 … 32 slots | Must be at least the UE's PUSCH preparation time N2. There is no K1 equivalent in the uplink because there is no feedback to time. |
Table 7. The three HARQ-relevant timing offsets, TS 38.214 cl. 5.1.2.1 and 6.1.2.1, and TS 38.213 cl. 9.2.3 for the DCI 1_0 K1 table.
The processing times these offsets have to respect are capabilities, not configuration, and they are expressed in symbols so they shrink in absolute time but not in slots as numerology rises:
| µ | SCS | N1 (PDSCH decode, symbols) | N2 (PUSCH prepare, symbols) | N1 in slots | Effect |
|---|---|---|---|---|---|
| 0 | 15 kHz | 8 | 10 | 0.6 | K1 = 1 is achievable |
| 1 | 30 kHz | 10 | 12 | 0.7 | K1 = 1 still achievable |
| 2 | 60 kHz | 17 | 23 | 1.2 | K1 ≥ 2 in practice |
| 3 | 120 kHz | 20 | 36 | 1.4 | K1 ≥ 2, K2 ≥ 3 in practice |
Table 8. UE processing capability 1, front-loaded DMRS only, from TS 38.214 Table 5.3-1 and Table 6.4-1. Capability 2 (processingType2Enabled) is roughly half of these and is what makes sub-slot URLLC scheduling possible.
Given: 30 kHz SCS (20 slots per frame), TDD pattern DDDSU with a 2.5 ms periodicity, so slot s is uplink when s mod 5 = 4. dl-DataToUL-ACK = { 1, 2, 3, 4, 6, 7, 8, 9 }. A PDSCH is received in SFN 412, slot 7.
Slot 7: 7 mod 5 = 2 → a downlink slot. Good.
Candidate feedback slots: 7 + K1 for each configured K1.
K1 = 1 → slot 8 (8 mod 5 = 3, special slot)
K1 = 2 → slot 9 (9 mod 5 = 4, uplink ✓)
K1 = 3 → slot 10 (downlink ✗)
K1 = 7 → slot 14 (14 mod 5 = 4, uplink ✓)
The scheduler signals the index of K1 = 2. Absolute slot arithmetic:
absolute slot of the PDSCH = 412 × 20 + 7 = 8247
absolute slot of the PUCCH = 8247 + 2 = 8249 = SFN 412, slot 9
wall-clock offset from SFN 0 = 412 × 10 ms + 9 × 0.5 ms = 4124.5 ms
Is K1 = 2 legal? N1 at µ = 1 is 10 symbols ≈ 0.71 slots, and the PDSCH ends within slot 7, so two slots of headroom is comfortable. K1 = 1 would also satisfy N1 but slot 8 has too few uplink symbols to carry PUCCH in this pattern.
Earliest retransmission: the gNB must decode the PUCCH in slot 9 and prepare a PDSCH, so slot 11 (downlink) or slot 12 — a HARQ round trip of 5 slots = 2.5 ms.
In TDD, dl-DataToUL-ACK is not a latency knob — it is a reachability list. Every downlink slot in the pattern needs at least one configured K1 that lands on an uplink slot, or PDSCH in that slot simply cannot be acknowledged and the scheduler must avoid it. A dl-DataToUL-ACK of { 2, 4 } on a DDDSU pattern leaves slot 1 unable to reach any uplink slot (1+2 = 3 is the special slot, 1+4 = 5 is downlink), so the cell quietly loses a fifth of its downlink capacity. The symptom is a scheduler that never uses certain slots, with no error anywhere.
9. HARQ-ACK Codebooks: Type-1, Type-2 and Type-3
One PUCCH transmission usually has to acknowledge more than one PDSCH: several downlink slots' worth of transport blocks, possibly across several carriers, all mapping onto the same uplink slot through their K1 values. The HARQ-ACK codebook is the agreement between UE and gNB about how many bits that PUCCH carries and which transport block each bit refers to. If the two sides disagree about the size, every bit is misaligned and the feedback is worse than useless.
| Type-1 (semi-static) | Type-2 (dynamic) | Type-3 (one-shot) | |
|---|---|---|---|
| Configured by | pdsch-HARQ-ACK-Codebook = semiStatic | pdsch-HARQ-ACK-Codebook = dynamic | pdsch-HARQ-ACK-OneShotFeedback (Rel-16), requested by a DCI bit |
| Size determined by | Configuration alone: the union over all configured K1 values of all candidate PDSCH occasions in the TDRA table, per serving cell | What was actually scheduled, communicated through the DAI fields | Fixed: one entry per HARQ process of every configured serving cell |
| Robust to a missed DCI? | Yes — the size never depends on what was sent. An unscheduled occasion is reported as NACK. | Yes for a gap in the middle (counter DAI); across carriers only if total DAI is present | Completely — it does not depend on any DCI having been received |
| Overhead | Often large. A pattern with many candidate occasions produces a big codebook even when one PDSCH was sent. | Minimal — exactly as many bits as there were assignments | Large: 16 or 32 bits per configured cell |
| Where it earns its place | Small cells, few candidate occasions, or wherever robustness matters more than PUCCH overhead | The normal choice for a loaded carrier-aggregated UE | Recovering after a suspected codebook mismatch; NR-U, where whole bursts can be lost to listen-before-talk |
Table 9. The three codebook types, TS 38.213 cl. 9.1.2 (Type-1), 9.1.3 (Type-2) and 9.1.4 (Type-3). Rel-16 also adds an enhanced dynamic codebook for NR-U that can group and retransmit feedback.
9.1 What the DAI fields actually do
In a dynamic codebook the UE cannot know the codebook size by configuration, so each DCI carries a counter DAI: a 2-bit value counting, modulo 4, the accumulated number of assignments up to and including this one, ordered first by serving cell and then by PDCCH monitoring occasion. A UE that misses one DCI sees the counter jump, infers exactly how many it missed, and pads the codebook with NACK at the right positions. The total DAI, present only when more than one serving cell is configured, states the total number of assignments across all cells up to the current monitoring occasion — which is the only defence against a DCI missed on a different carrier, where a counter jump would be indistinguishable from correct counting.
Reconstructing the codebook. Single serving cell, four monitoring occasions, all K1 values pointing at uplink slot 9. Counter DAI is modulo 4, so the received values encode positions as (value + 1):
occasion 1: cDAI = 0 → assignment 1 decoded → ACK
occasion 2: cDAI = 1 → assignment 2 decoded → ACK
occasion 3: PDCCH missed — nothing received
occasion 4: cDAI = 3 → assignment 4
The UE sees 0, 1, 3. The step from 1 to 3 is 2 rather than 1, so exactly one assignment is unaccounted for, at position 3. Codebook size = highest position seen = 4 bits, contents [ACK, ACK, NACK, ACK].
The gNB reads four bits, sees bit 3 as NACK, and retransmits that transport block with the NDI unchanged. Total cost of the lost PDCCH: one retransmission.
The failure case. Modulo 4 wraps. If the UE misses four consecutive DCIs it sees 0 then 0 again and cannot tell four missing assignments from none — the codebook comes out one bit long instead of five. This is exactly the hole Type-3 one-shot feedback was added to plug.
9.2 Spatial bundling
On a cell configured for two transport blocks, each PDSCH produces two HARQ-ACK bits. harq-ACK-SpatialBundlingPUCCH and harq-ACK-SpatialBundlingPUSCH in PhysicalCellGroupConfig replace them with their logical AND, halving the codebook at the cost of retransmitting both transport blocks when only one failed. It is a PUCCH-overhead trade, and it is usually enabled on cells with many aggregated carriers and disabled where two-codeword MIMO is common enough for the wasted retransmissions to matter.
Codebook type is configured per cell group, and both ends must agree exactly — including on the TDRA table and the K1 list, because with a Type-1 codebook those determine the size. A reconfiguration that changes pdsch-TimeDomainAllocationList changes the semi-static codebook size as a side effect, and if the UE applies the new configuration one slot before or after the gNB does, the feedback is misaligned for that slot. The signature is a burst of apparent NACKs immediately after an RRCReconfiguration, clearing by itself. It is not a radio problem.
10. Code Block Group Based Transmission
A transport block with four code blocks fails as a whole if any one code block fails, and a retransmission normally resends all four. When the failure is caused by something localised — a burst of interference hitting part of the allocation, or a pre-empted mini-slot — that is three quarters wasted. Code block group based transmission lets the scheduler retransmit only the failed subset.
| Element | ASN.1 / DCI field | Range | What it does |
|---|---|---|---|
| Enable | codeBlockGroupTransmission in PDSCH-ServingCellConfig (and PUSCH-ServingCellConfig) | setup / release | Turns CBG operation on for that serving cell and direction. |
| Group count | maxCodeBlockGroupsPerTransportBlock | n2, n4, n6, n8 | Upper bound N. The actual number is M = min(N, C), so a transport block with fewer code blocks than N gets fewer groups. |
| Which groups are sent | CBGTI in DCI 1_1 / 0_1 | 2, 4, 6 or 8 bits | One bit per group: 1 = this group is present in this transmission. |
| Whether to flush | CBGFI in DCI 1_1 | 0 or 1 bit | 0 means the receiver should discard the soft bits it holds for the groups being retransmitted — used when the earlier attempt is known to be unusable rather than merely insufficient. Present only if codeBlockGroupFlushIndicator is configured. |
| Feedback granularity | HARQ-ACK bits per TB | M bits instead of 1 | The codebook grows by a factor of M, which is the price. This is why CBG and large aggregated codebooks interact badly. |
Table 10. Code block group configuration and signalling, TS 38.212 cl. 7.3.1.2.2 and TS 38.214 cl. 5.1.7.
CBG earns its complexity in exactly two situations: large transport blocks (where retransmitting everything is expensive in absolute terms) and bursty, localised interference — most characteristically downlink pre-emption, where a URLLC mini-slot punches a hole in an eMBB allocation and damages a predictable subset of the code blocks. See the pre-emption indication (DCI 2_1) in the companion 08 Scheduling document. Outside those cases the M-fold increase in HARQ-ACK payload usually costs more than the saved retransmission, which is why most deployments leave codeBlockGroupTransmission unconfigured.
11. Uplink HARQ Without a PHICH
LTE had a dedicated physical channel, PHICH, whose only job was to carry one ACK/NACK bit per uplink transport block. NR deleted it. There is no downlink channel anywhere in NR that carries uplink HARQ feedback, and there is no MAC control element for it either. The feedback is the next grant.
11.1 How the UE infers the outcome
When the gNB fails to decode a PUSCH, it sends a new DCI for the same HARQ process with the NDI unchanged. The UE recognises that as "send that transport block again" and does so, at whatever RV the DCI specifies. When the gNB succeeds, it either sends a DCI for that process with the NDI toggled — which both acknowledges the old block and schedules a new one — or sends nothing at all for that process, leaving the UE holding the transport block in its buffer until something else happens.
- No news is not good news, and it is not bad news either. A UE with no grant for a process simply waits. It does not retransmit on its own (except under a configured grant with
cg-RetransmissionTimer, Rel-16 NR-U) and it does not flush. - There is no configured retry limit. Unlike LTE's
maxHARQ-Tx, NR specifies no maximum number of HARQ transmissions in either direction. The scheduler decides when to stop, and the real bound comes from above: the PDCPdiscardTimer, the RLCmaxRetxThreshold, and the delay budget of the 5QI. - Blind repetition is a separate mechanism.
pusch-AggregationFactor(n2/n4/n8) and Rel-16 PUSCH repetition Type A and Type B send the same transport block several times without waiting for feedback, cycling the RV through 0/2/3/1. That is not HARQ retransmission; it is repetition, and it happens whether or not the first copy would have decoded.
11.2 Msg3 and configured grants
Two uplink cases sidestep the DCI entirely. The MAC PDU transmitted as MSG3 during Random Access uses HARQ process 0 by specification TS 38.321 cl. 5.4.2.1, because the UE has no C-RNTI-addressed DCI to carry a process ID at that point — see the companion 03 Random Access document. And a configured grant derives its process ID from the transmission time, using the formula in §3. Both are places where the process ID is not in any message you can read; you have to compute it.
The absence of a PHICH is why uplink HARQ debugging is asymmetric and harder. In the downlink you can see the UE's verdict directly, as a bit on PUCCH. In the uplink there is no verdict to see — only the gNB's reaction, which is a grant that may or may not appear, with an NDI whose meaning depends on state neither log necessarily prints. Always reconstruct the NDI history per process before drawing any conclusion about uplink HARQ.
12. Disabled HARQ Feedback and Very Long Round Trips
Everything so far assumes the round trip is short enough that a process waiting for feedback is a minor inefficiency. Over a geostationary satellite the round trip is around 270 ms, which at 30 kHz is 540 slots. Sixteen processes would leave the link idle 97 % of the time, and even 32 would not fix it. Rel-17 therefore allows HARQ feedback to be switched off per process.
| Mechanism | Signalling | Effect | What replaces the reliability |
|---|---|---|---|
| Disabled downlink HARQ feedback | downlinkHARQ-FeedbackDisabled-r17 in the Rel-17 extension of PDSCH-ServingCellConfig — a bit string with one bit per HARQ process | For a process with feedback disabled the UE reports no HARQ-ACK, and every reception on that process is treated as new data (no soft combining across transmissions) | RLC ARQ, plus blind repetition. The 10⁻¹ first-attempt target is no longer viable, so link adaptation must aim much lower. |
| More processes | nrofHARQ-ProcessesForPDSCH-v1700 = n32; 5-bit HARQ process number field | Doubles the number of transport blocks that can be in flight | Nothing — this is pure pipelining, and it helps but does not solve a 270 ms round trip. |
| Shifted feedback timing | cellSpecificKoffset-r17 and kmac-r17 in NTN-Config | Pushes K1, K2 and the RAR window far enough into the future that the propagation delay fits inside the scheduling relationship | Not a reliability mechanism — it makes the existing timing relationships expressible at all. |
Table 11. Rel-17 long-round-trip mechanisms. The timing side of this is covered in the companion 04 Timing Advance document, which carries the same NTN-Config structure.
Disabling HARQ feedback is not only an NTN feature in effect. Any mechanism where the feedback costs more than it saves reaches for the same idea — which is why the Rel-17 signalling is a general per-process bitmap rather than an NTN mode. When feedback is disabled the process behaves like a fire-and-forget pipe: the NDI still toggles, but the receiver never combines and never answers, so the only thing standing between a lost transport block and the application is RLC. Traces from such a link show HARQ processes cycling with no PUCCH activity at all, and RLC status reports doing all the visible work.
13. HARQ, DRX, and the Timers That Connect Them
A UE in DRX is asleep most of the time, and a NACK it just sent creates an obligation to be awake later — but not immediately, because no retransmission is physically possible until the gNB has decoded the feedback and prepared one. Two per-process timers encode exactly that TS 38.321 cl. 5.7:
| Timer | Unit and range | Started when | Effect while running |
|---|---|---|---|
drx-HARQ-RTT-TimerDL | Symbols, 0 … 56 | At the first symbol after the HARQ-ACK for that process has been transmitted | The UE need not monitor PDCCH for that process — no retransmission can arrive yet, so it may sleep. |
drx-RetransmissionTimerDL | Slots: sl0, sl1, sl2, sl4, sl6, sl8, sl16, sl24, sl33, sl40, sl64, sl80, sl96, sl112, sl128, sl160, sl320 | On expiry of drx-HARQ-RTT-TimerDL, if the transport block was not successfully decoded | The UE is in DRX Active Time and must monitor PDCCH for that process. Stopped as soon as the retransmission is scheduled. |
drx-HARQ-RTT-TimerUL | Symbols, 0 … 56 | At the first symbol after the end of the PUSCH transmission (the first repetition, if repetition is configured) | Same logic in the uplink: no retransmission grant can arrive yet. |
drx-RetransmissionTimerUL | Slots, same enumeration as DL | On expiry of drx-HARQ-RTT-TimerUL | Active Time, monitoring for a retransmission grant — which is the only form uplink HARQ feedback takes (§11). |
Table 12. The four HARQ-related DRX timers. The RTT timer is a permission to sleep; the retransmission timer is an obligation to wake. See the companion 11 DRX document for how these sit inside the wider Active Time rules.
drx-RetransmissionTimerDL has to be at least as long as the scheduler's actual retransmission delay, and nothing checks that it is. Set it to sl4 on a system whose scheduler typically retransmits eight slots after the NACK, and the UE stops monitoring before the retransmission arrives: the PDCCH is transmitted, missed, and the process stalls until something else wakes the UE. The symptom is downlink throughput that collapses only for UEs in DRX, with clean HARQ statistics for UEs that happen to be continuously active — because for those the timer never matters.
14. Parameter Reference: ASN.1, Ranges and Effects
| ASN.1 name | Where | Range / values | Typical | Effect |
|---|---|---|---|---|
nrofHARQ-ProcessesForPDSCH | PDSCH-ServingCellConfig | n2, n4, n6, n10, n12, n16 | n16 (default 8 when absent) | Size of the downlink process pool. Too few stalls the pipeline; too many divides the soft buffer. |
pdsch-HARQ-ACK-Codebook | PhysicalCellGroupConfig | semiStatic, dynamic | dynamic | Whether the codebook size comes from configuration or from the DAI. Must match at both ends exactly (§9). |
harq-ACK-SpatialBundlingPUCCH | PhysicalCellGroupConfig | true (present) / absent | absent | ANDs the two TBs' feedback on 2-codeword cells. Halves PUCCH payload, wastes retransmissions. |
dl-DataToUL-ACK | PUCCH-Config | 1 … 8 entries, each 0 … 15 slots | {1,2,3,4,6,7,8,9} on DDDSU | The K1 candidate set. In TDD it decides which downlink slots are usable at all (§8). |
k0 / k2 | PDSCH- / PUSCH-TimeDomainResourceAllocation | 0 … 32 slots | 0–1 / 2–4 | DCI-to-data offsets. K0 ≥ 1 enables cross-slot scheduling and UE power saving. |
codeBlockGroupTransmission | PDSCH- / PUSCH-ServingCellConfig | setup / release | absent | Enables CBG operation. Multiplies the HARQ-ACK payload by M (§10). |
maxCodeBlockGroupsPerTransportBlock | PDSCH-CodeBlockGroupTransmission | n2, n4, n6, n8 | n4 | Upper bound on groups per TB; actual M = min(N, C). |
codeBlockGroupFlushIndicator | same | BOOLEAN | false | Adds the 1-bit CBGFI to DCI 1_1. |
pdsch-AggregationFactor | PDSCH-Config | n2, n4, n8 | absent | Blind repetition of PDSCH over consecutive slots, RV cycling 0/2/3/1. Not HARQ retransmission. |
pusch-AggregationFactor | PUSCH-Config | n2, n4, n8 | absent | The uplink equivalent. |
xOverhead | PDSCH-ServingCellConfig | xOh6, xOh12, xOh18 | absent (= 0) | Assumed non-data overhead per PRB in the TBS formula. Changing it changes TBS on both sides — a mismatch is a silent decode failure. |
processingType2Enabled | PDSCH-ServingCellConfig | BOOLEAN | false | Selects UE processing capability 2, roughly halving N1/N2 and allowing smaller K1/K2. |
drx-HARQ-RTT-TimerDL / -UL | DRX-Config | 0 … 56 symbols | ≈ the round trip | Permission to sleep through the round trip (§13). |
drx-RetransmissionTimerDL / -UL | DRX-Config | sl0 … sl320 | sl16 … sl40 | Obligation to monitor for the retransmission. Too short loses it. |
downlinkHARQ-FeedbackDisabled-r17 | PDSCH-ServingCellConfig (Rel-17) | bit string, one bit per process | absent | Per-process feedback disable for long round trips (§12). |
Table 13. HARQ-relevant configuration. Note how much of HARQ has no configuration at all: the NDI semantics, the RV offsets, the base graph rules and the retry budget are all fixed or left to the scheduler.
15. Failure Modes and What Each One Means
| Failure | Detected by | What happens next | Diagnostic pointer |
|---|---|---|---|
| PDSCH CRC fails | UE | Soft bits retained, NACK reported at n + K1, process stays occupied | Entirely normal at a 10 % rate. Only the rate is informative, and only against the link adaptation target. |
| NACK misread as ACK by the gNB | Nobody at MAC level | The gNB frees the process and moves on; the transport block is lost silently | Shows up as an RLC retransmission with no corresponding HARQ retransmission. This is the single most important HARQ failure to be able to recognise, because HARQ statistics look perfect. |
| ACK misread as NACK by the gNB | Nobody | A pointless retransmission; the UE receives a duplicate of a TB it already decoded and discards it | Wasted capacity, no data impact. A high rate points at PUCCH power control or PUCCH resource collision, not at PDSCH. |
| PDCCH missed, so the UE never knows a PDSCH existed | UE, indirectly | With a dynamic codebook, the counter DAI reveals the gap and the UE reports NACK for it (§9). With a semi-static codebook the occasion was always in the codebook, so it is reported NACK anyway. | PDCCH BLER. If the codebook size is wrong rather than one bit, suspect a Type-1 configuration mismatch instead. |
| Codebook size mismatch between UE and gNB | Neither, immediately | Every bit in that PUCCH is misinterpreted; processes are freed or retransmitted at random | A burst of implausible feedback, often right after an RRCReconfiguration that touched the TDRA table or dl-DataToUL-ACK. Rel-16 Type-3 one-shot feedback exists to recover from it. |
| NDI state lost at the UE (buffer flush, DRX, TAG expiry) | UE | The next reception on that process is treated as new data, so a retransmission is decoded without combining and almost certainly fails | One extra failed attempt per process after any flush event. Correlate against timeAlignmentTimer expiry — see the companion 04 Timing Advance document. |
drx-RetransmissionTimer too short | Nobody | The UE stops monitoring before the retransmission is scheduled; the process stalls | Poor throughput only for UEs in DRX, with normal HARQ statistics for continuously active UEs (§13). |
| Configured-grant process ID mismatch | Nobody | The gNB retransmits into a process the UE has different data in | Corrupted uplink with no HARQ error reported by either side. Recompute the process ID from the formula in §3 and compare (§3). |
| HARQ exhausted by scheduler policy | gNB (its own decision) | The block is abandoned at MAC; RLC AM notices the sequence-number gap and retransmits | Look at the RLC status reports, not the HARQ counters. If maxRetxThreshold is then exceeded, RLC declares failure and RRC declares RLF — see the companion 16 RLM and RLF document. |
| Soft buffer exhausted / LBRM clipping | UE, invisibly | Later redundancy versions re-send bits already held, so combining gain plateaus | Good gain from the first retransmission, little from the third (§7). Check the configured process count and the UE's soft-buffer capability. |
Table 14. HARQ failure modes. Notice how many are detected by nobody — HARQ has almost no self-diagnosis, and its one-bit feedback has no integrity protection.
The failure worth memorising is the second row. A NACK misread as an ACK produces perfect HARQ statistics and lost data, because from HARQ's point of view nothing went wrong: the process was acknowledged and freed. The only visible trace is an RLC retransmission with no HARQ retransmission behind it. If you are chasing throughput loss that HARQ counters say cannot exist, compare RLC retransmission counts against HARQ retransmission counts — a persistent excess of the former is the signature.
16. ASN.1 Extracts
HARQ configuration is spread across four places: the per-cell PDSCH and PUSCH serving-cell configurations, the cell-group physical configuration, the PUCCH configuration for K1, and the DRX configuration. Abridged from TS 38.331; ... marks omitted fields and extension markers.
PDSCH-ServingCellConfig ::= SEQUENCE {
codeBlockGroupTransmission SetupRelease {
PDSCH-CodeBlockGroupTransmission } OPTIONAL,
xOverhead ENUMERATED { xOh6, xOh12, xOh18 } OPTIONAL,
nrofHARQ-ProcessesForPDSCH ENUMERATED { n2, n4, n6, n10, n12, n16 }
OPTIONAL,
pucch-Cell ServCellIndex OPTIONAL,
...,
[[ maxMIMO-Layers INTEGER (1..8) OPTIONAL,
processingType2Enabled BOOLEAN OPTIONAL ]],
[[ downlinkHARQ-FeedbackDisabled-r17
SetupRelease { DownlinkHARQ-FeedbackDisabled-r17 }
OPTIONAL,
nrofHARQ-ProcessesForPDSCH-v1700
ENUMERATED { n32 } OPTIONAL ]]
}
PDSCH-CodeBlockGroupTransmission ::= SEQUENCE {
maxCodeBlockGroupsPerTransportBlock ENUMERATED { n2, n4, n6, n8 },
codeBlockGroupFlushIndicator BOOLEAN,
...
}
PhysicalCellGroupConfig ::= SEQUENCE {
harq-ACK-SpatialBundlingPUCCH ENUMERATED { true } OPTIONAL,
harq-ACK-SpatialBundlingPUSCH ENUMERATED { true } OPTIONAL,
p-NR-FR1 P-Max OPTIONAL,
pdsch-HARQ-ACK-Codebook ENUMERATED { semiStatic, dynamic },
tpc-SRS-RNTI RNTI-Value OPTIONAL,
...,
[[ pdsch-HARQ-ACK-OneShotFeedback-r16 ENUMERATED { true } OPTIONAL,
pdsch-HARQ-ACK-OneShotFeedbackNDI-r16 ENUMERATED { true } OPTIONAL,
pdsch-HARQ-ACK-OneShotFeedbackCBG-r16 ENUMERATED { true } OPTIONAL ]]
}Listing 1. Downlink HARQ configuration. Note that pdsch-HARQ-ACK-Codebook is mandatory and per cell group — there is no way to run different codebook types on different carriers of one group.
PUCCH-Config ::= SEQUENCE {
resourceSetToAddModList SEQUENCE (SIZE (1..4)) OF PUCCH-ResourceSet
OPTIONAL,
resourceToAddModList SEQUENCE (SIZE (1..128)) OF PUCCH-Resource
OPTIONAL,
format1 SetupRelease { PUCCH-FormatConfig } OPTIONAL,
dl-DataToUL-ACK SEQUENCE (SIZE (1..8)) OF INTEGER (0..15)
OPTIONAL,
pucch-PowerControl PUCCH-PowerControl OPTIONAL,
...
}
PDSCH-TimeDomainResourceAllocation ::= SEQUENCE {
k0 INTEGER (0..32) OPTIONAL,
mappingType ENUMERATED { typeA, typeB },
startSymbolAndLength INTEGER (0..127)
}
PUSCH-TimeDomainResourceAllocation ::= SEQUENCE {
k2 INTEGER (0..32) OPTIONAL,
mappingType ENUMERATED { typeA, typeB },
startSymbolAndLength INTEGER (0..127)
}
DRX-Config ::= SEQUENCE {
drx-onDurationTimer CHOICE { subMilliSeconds INTEGER (1..31),
milliSeconds ENUMERATED {...} },
drx-InactivityTimer ENUMERATED { ms0, ms1, ..., ms1280 },
drx-HARQ-RTT-TimerDL INTEGER (0..56),
drx-HARQ-RTT-TimerUL INTEGER (0..56),
drx-RetransmissionTimerDL ENUMERATED { sl0, sl1, sl2, sl4, sl6, sl8,
sl16, sl24, sl33, sl40, sl64,
sl80, sl96, sl112, sl128,
sl160, sl320 },
drx-RetransmissionTimerUL ENUMERATED { ... same ... },
...
}Listing 2. The timing side: K1 candidates, K0/K2, and the two pairs of DRX HARQ timers. drx-HARQ-RTT-Timer* is in symbols while drx-RetransmissionTimer* is in slots — a units mismatch that has caused real misconfigurations.
17. Illustrative Message Traces
Illustrative trace. Field names and encodings follow 3GPP; the values are constructed for this document and are not a capture from any deployed or lab network.
17.1 A downlink retransmission, decoded field by field
14:22:08.4120 [PHY-DL] PDCCH DCI 1_1 CRC scrambled by C-RNTI 0x4601
SFN 412 slot 7 (absolute slot 8247, 30 kHz)
freq domain RA .............. 100 PRB from PRB 0
time domain RA .............. row 2: k0=0, S=2, L=12
MCS ......................... 15 -- Qm 4, R 616/1024
NDI ......................... 1 -- stored value was 0
RV .......................... 0
HARQ process number ......... 3
counter DAI ................. 0
PUCCH resource indicator .... 1
PDSCH-to-HARQ timing ind .... 1 -> dl-DataToUL-ACK[1]
-> K1 = 2 slots
14:22:08.4120 [MAC-DL] HARQ pid 3: NDI toggled 0 -> 1 => NEW DATA
flush soft buffer for pid 3, store NDI = 1
TBS ......................... 31752 bits (3969 bytes)
code blocks ................. 4 -- BG1, Z_c 384
N_RE ........................ 13200, coded bits 52800
effective code rate ......... 0.604
14:22:08.4123 [PHY-DL] PDSCH decode pid 3 CRC FAIL
LDPC iterations ............. 8 (max)
soft bits retained .......... 13200 per code block
14:22:08.4130 [PHY-UL] PUCCH format 1, 1 bit, HARQ-ACK = NACK
SFN 412 slot 9 (8247 + K1 = 8249)
14:22:08.4145 [PHY-DL] PDCCH DCI 1_1 C-RNTI 0x4601 SFN 412 slot 12
MCS ......................... 15 -- unchanged
NDI ......................... 1 -- UNCHANGED => retx
RV .......................... 2
HARQ process number ......... 3
14:22:08.4145 [MAC-DL] HARQ pid 3: NDI unchanged => RETRANSMISSION
RV 2 -> k_0 = 33 x Z_c = 12672 (50.0 % of N_cb 25344)
combine 13200 new soft bits at offset 12672
accumulated coverage ........ ~96 % of the buffer
effective code rate now ..... 0.31
14:22:08.4148 [PHY-DL] PDSCH decode pid 3 CRC PASS (3 iterations)
14:22:08.4155 [PHY-UL] PUCCH HARQ-ACK = ACK SFN 412 slot 14
14:22:08.4155 [MAC-DL] pid 3 released; TB delivered to demultiplexing
-- HARQ round trip: slot 7 -> slot 12 = 5 slots = 2.5 msListing 3. One NACK and one successful retransmission, with the RV offset and the resulting code rate carried through. The falling code rate — 0.604 to 0.31 — is the incremental redundancy gain, quantified.
17.2 Dynamic codebook reconstruction after a missed PDCCH
14:31:55.0000 [CFG] pdsch-HARQ-ACK-Codebook = dynamic,
one serving cell, no total DAI field present
14:31:55.0025 [PHY-DL] DCI 1_1 slot 5 pid 2 NDI 1 cDAI 0 K1 4
14:31:55.0028 [PHY-DL] PDSCH slot 5 pid 2 CRC PASS
14:31:55.0030 [PHY-DL] DCI 1_1 slot 6 pid 3 NDI 0 cDAI 1 K1 3
14:31:55.0033 [PHY-DL] PDSCH slot 6 pid 3 CRC PASS
14:31:55.0035 [PHY-DL] PDCCH blind decode set exhausted, slot 7
-- no DCI found; the UE does not know one was sent
14:31:55.0040 [PHY-DL] DCI 1_1 slot 8 pid 5 NDI 1 cDAI 3 K1 1
14:31:55.0043 [PHY-DL] PDSCH slot 8 pid 5 CRC PASS
14:31:55.0045 [MAC-UL] building Type-2 HARQ-ACK codebook for slot 9
cDAI values received ........ 0, 1, 3
gap detected between 1 and 3 -> 1 assignment missed
codebook size ............... 4 bits
bit 1 (cDAI 0, pid 2) ....... ACK
bit 2 (cDAI 1, pid 3) ....... ACK
bit 3 (missed) .............. NACK
bit 4 (cDAI 3, pid 5) ....... ACK
14:31:55.0047 [PHY-UL] PUCCH format 2, 4 bits: 1 1 0 1 slot 9
14:31:55.0060 [PHY-DL] DCI 1_1 slot 11 pid 4 NDI 1 (unchanged) RV 2
-- gNB retransmitting the TB the UE never saw;
-- to the UE, pid 4 has no stored NDI, so this
-- is treated as NEW DATA and decoded alone
14:31:55.0063 [PHY-DL] PDSCH slot 11 pid 4 CRC FAIL
-- RV2 is not self-decodable; expected outcome
14:31:55.0075 [PHY-DL] DCI 1_1 slot 12 pid 4 NDI 1 (unchanged) RV 3
14:31:55.0078 [PHY-DL] PDSCH slot 12 pid 4 CRC PASS
-- RV2 + RV3 together cover enough of the bufferListing 4. The codebook survives the missed PDCCH, but the retransmission does not survive it cleanly: the UE has no soft bits to combine, so a non-self-decodable RV2 fails and a second attempt is needed. This is normal and is why RV0 is sometimes repeated in poor coverage.
17.3 Uplink HARQ, where the grant is the feedback
14:44:02.1000 [PHY-DL] DCI 0_1 C-RNTI 0x4601 slot 3
HARQ process number ......... 7
NDI ......................... 0 -- stored was 1 => NEW
RV .......................... 0
MCS ......................... 11
k2 .......................... 3 -> PUSCH in slot 6
14:44:02.1015 [MAC-UL] pid 7: new transmission, TBS 6272 bits
14:44:02.1015 [PHY-UL] PUSCH slot 6 pid 7 RV 0
-- no PHICH exists. The UE now waits, holding the TB.
14:44:02.1040 [PHY-DL] DCI 0_1 C-RNTI 0x4601 slot 8
HARQ process number ......... 7
NDI ......................... 0 -- UNCHANGED
RV .......................... 2
14:44:02.1040 [MAC-UL] pid 7: NDI unchanged => this is a NACK,
expressed as a grant. Retransmit from the buffer.
14:44:02.1055 [PHY-UL] PUSCH slot 11 pid 7 RV 2
14:44:02.1090 [PHY-DL] DCI 0_1 C-RNTI 0x4601 slot 16
HARQ process number ......... 7
NDI ......................... 1 -- TOGGLED
RV .......................... 0
14:44:02.1090 [MAC-UL] pid 7: NDI toggled => the previous TB was
received. Flush pid 7, build a new TB from the
LCP procedure, transmit.
-- the toggle is the only acknowledgement that ever
-- arrives for an uplink transport blockListing 5. Three grants and no acknowledgement channel. The NDI toggling at 14:44:02.1090 is simultaneously the ACK for the old transport block and the grant for the new one. See the companion 09 LCP document for how the new TB's contents are chosen.
17.4 The failure that HARQ cannot see
15:02:11.2000 [PHY-DL] PDSCH pid 9 NDI 1 RV 0 CRC FAIL
15:02:11.2010 [PHY-UL] PUCCH HARQ-ACK = NACK for pid 9
UE transmit power .... 14.2 dBm
(gNB side) PUCCH DTX/ACK/NACK decision ... ACK
-- the gNB misread the NACK as an ACK
15:02:11.2010 [MAC-DL] (gNB) pid 9 acknowledged, process released
-- no retransmission will ever be scheduled
15:02:11.2010 [MAC-DL] (UE) pid 9 still holds soft bits, waiting
15:02:11.2500 [MAC-DL] PDSCH pid 9 NDI 0 RV 0
-- a completely different transport block
15:02:11.2500 [MAC-DL] (UE) NDI toggled => new data, flush pid 9
-- the old TB's soft bits are discarded. The TB
-- itself was never delivered and never will be
-- by HARQ.
15:02:11.3120 [RLC-DL] AM: SN gap detected, SN 4471 missing
t-Reassembly started
15:02:11.3620 [RLC-UL] RLC STATUS PDU: ACK_SN 4478, NACK_SN 4471
15:02:11.3700 [RLC-DL] retransmission of SN 4471 (segment 0)
15:02:11.3700 [MAC-DL] PDSCH pid 12 NDI 1 RV 0 CRC PASS
-- carrying the RLC retransmission
-- HARQ counters for this interval:
-- PDSCH transmissions ....... 148
-- HARQ retransmissions ...... 14 (9.5 % -- on target)
-- HARQ failures ............. 0
-- RLC AM retransmissions .... 3 <-- the real story is hereListing 6. A NACK-to-ACK error. HARQ statistics are flawless — no failures, retransmission rate exactly on target — and a transport block was lost anyway. The only evidence is the RLC retransmission count. This is the pattern described in §15.
Two habits pay for themselves when reading traces like §17.4. First, always read HARQ retransmission counts next to RLC retransmission counts — HARQ alone cannot report the failure that matters most. Second, reconstruct the NDI history per process rather than reading each DCI in isolation; the NDI means nothing on its own, and a log line showing NDI = 1 is not information until you know what the previous value for that process was.
18. Release Deltas: Rel-15 to Rel-18
| Release | Change | Why it matters when reading HARQ |
|---|---|---|
| Rel-15 | 16 asynchronous processes in both directions, explicit process ID, NDI toggle, four RVs over a circular buffer, LDPC with two base graphs, Type-1 and Type-2 codebooks, CBG transmission, spatial bundling, no PHICH | The baseline. Everything in §§2–11 is Rel-15 and unchanged. |
| Rel-16 | Type-3 (one-shot) HARQ-ACK feedback, requested by a DCI 1_1 bit | A UE can now be asked to dump feedback for every process on every cell, which is the only clean recovery from a codebook mismatch. |
| Rel-16 | Enhanced dynamic codebook and codebook retransmission for NR-U; cg-RetransmissionTimer for autonomous configured-grant retries | Uplink retransmission can now happen without a grant, which breaks the "grant = feedback" rule of §11 in exactly one place. |
| Rel-16 | DCI formats 0_2 and 1_2 with configurable field sizes; dl-DataToUL-ACKForDCIFormat1_2 | The HARQ fields can be different widths in the same cell depending on which DCI format scheduled the PDSCH. |
| Rel-16 | Physical-layer priority indicator (1 bit) in DCI 0_1/1_1; intra-UE prioritisation | Two HARQ-ACK payloads can now collide and one wins by priority rather than by rule — see the companion 08 Scheduling document. |
| Rel-16 | PUSCH repetition Type B (back-to-back nominal repetitions with segmentation) | Repeated transmissions of one TB are no longer slot-aligned, so counting "transmissions per process" in a trace needs care. |
| Rel-17 | Disabled downlink HARQ feedback per process (downlinkHARQ-FeedbackDisabled), for NTN | A process can legitimately show no PUCCH activity at all and no soft combining (§12). |
| Rel-17 | 32 HARQ processes, 5-bit process number field (nrofHARQ-ProcessesForPDSCH-v1700, nrofHARQ-ProcessesForPUSCH-r17) | A logged process ID above 15 is valid. Decoders that mask 4 bits will silently alias process 17 onto process 1. |
| Rel-17 | 480 and 960 kHz numerologies (FR2-2); cellSpecificKoffset for NTN | K1 and K2 in slots no longer imply the timings the terrestrial intuitions in §8 assume. |
| Rel-17 | Multicast/broadcast (MBS) with DCI formats 4_0/4_1/4_2 and group-common HARQ feedback modes | HARQ-ACK can now be reported for a group-addressed transmission, or not at all, depending on the mode. |
| Rel-18 | Further NTN and MBS refinements; enhancements to multi-carrier scheduling with a single DCI | One DCI can carry HARQ fields for more than one cell, so process IDs and codebook positions are no longer one-to-one with the DCI. |
Table 15. HARQ changes by release. Confirm feature presence against the UE capability exchange — see the companion 26 UE Capability document.
19. Reading HARQ in Logs: A Checklist
1. Group everything by HARQ process ID first. A HARQ trace read in time order is nearly meaningless; read in per-process order it tells a story. Watch for process IDs above 15 — valid from Rel-17, and a common decoder bug (§18).
2. Reconstruct the NDI history per process. A single NDI = 1 line carries no information. Only the change from the previous transmission on that process does, and a flush event resets the reference (§4).
3. Check the RV against the attempt number. RV0 on a first attempt is normal. RV0 on every attempt means chase combining, which is a deliberate scheduler choice in poor coverage, not a fault. RV2 as the first thing a process sees means the initial PDCCH was missed, and a failure there is expected (§5, §17.2).
4. Verify the K1 arithmetic against the TDD pattern. Compute n + K1 and confirm it lands on a slot with usable uplink symbols. If some downlink slots have no reachable uplink slot, the scheduler is losing capacity silently (§8).
5. Establish the codebook type before interpreting any PUCCH payload. A 4-bit codebook read as 1 bit, or a semi-static codebook read as dynamic, makes every subsequent conclusion wrong. If the size looks implausible, look for a recent RRCReconfiguration (§9).
6. Follow the counter DAI sequence. Gaps are the record of missed PDCCHs, and they are the cheapest available PDCCH BLER estimate. A counter that wraps without a visible gap can hide four missed assignments (§9.1).
7. Read HARQ and RLC retransmission counts together, always. An excess of RLC retransmissions over HARQ retransmissions is the signature of NACK-to-ACK errors, which HARQ itself reports as perfect health (§15).
8. For uplink, look for the grant, not for feedback. There is no PHICH. Success is an NDI toggle on the next grant for that process, and "no grant" means neither success nor failure (§11).
9. Check whether the UE is in DRX before blaming the scheduler. A stalled process on a DRX UE with healthy statistics for active UEs points at drx-RetransmissionTimer, not at radio (§13).
10. Recompute configured-grant and SPS process IDs from the formula. They are in no message. A mismatch between the two sides' periodicity or process count corrupts data with no error reported anywhere (§3).
20. Glossary
| Term | Expansion | Meaning in this document |
|---|---|---|
| HARQ | Hybrid Automatic Repeat reQuest | Retransmission with soft combining, at MAC/PHY. Hybrid = ARQ plus forward error correction that accumulates across attempts. |
| HARQ process | — | One independent stop-and-wait instance, with its own soft buffer and its own NDI state. Identified by a 4-bit (Rel-17: 5-bit) ID in the DCI. |
| NDI | New Data Indicator | One bit whose change since the last transmission on that process means new data. Not a flag (§4). |
| RV | Redundancy Version | Two bits selecting one of four starting offsets into the circular buffer. Not a coding mode (§5). |
| Circular buffer | — | The full LDPC encoder output per code block: 66·Z_c bits for BG1, 50·Z_c for BG2. Rate matching reads a wrapping run out of it. |
| N_cb | — | The portion of the circular buffer rate matching may use. Equal to N unless limited buffer rate matching applies (§7). |
| Z_c | Lifting size | The LDPC expansion factor, chosen as the smallest value large enough to hold the code block. Sets the buffer size and the RV granularity. |
| IR / CC | Incremental Redundancy / Chase Combining | Retransmitting different coded bits, versus the same ones. In NR both are just choices of RV (§5). |
| Soft buffer | — | Receiver memory holding log-likelihood ratios for a not-yet-decoded code block, per process. |
| K0 / K1 / K2 | — | Slot offsets: DCI→PDSCH, PDSCH→HARQ-ACK, DCI→PUSCH (§8). |
| DAI | Downlink Assignment Index | Counter DAI numbers the assignments so a UE can size a dynamic codebook after a missed DCI; total DAI does the same across carriers (§9.1). |
| Codebook | HARQ-ACK codebook | The agreed mapping from bits in one PUCCH transmission to transport blocks. Type-1 semi-static, Type-2 dynamic, Type-3 one-shot. |
| CBG | Code Block Group | A subset of a transport block's code blocks that can be retransmitted on its own (§10). |
| CBGTI / CBGFI | CBG Transmission / Flushing Out Information | DCI fields saying which groups are present, and whether to discard what is held for them. |
| PHICH | Physical HARQ Indicator Channel | An LTE channel carrying uplink HARQ feedback. Does not exist in NR (§11). |
| LBRM | Limited Buffer Rate Matching | Clipping N_cb to bound receiver memory, at the cost of later redundancy versions repeating rather than adding (§7). |
21. References
- 3GPP TS 38.321 — NR MAC protocol specification. Clause 5.3.2 (downlink HARQ operation and the reception procedure), 5.4.1 (configured-grant HARQ process ID derivation), 5.4.2 (uplink HARQ operation), 5.7 (DRX, including
drx-HARQ-RTT-Timeranddrx-RetransmissionTimerbehaviour). - 3GPP TS 38.212 — Multiplexing and channel coding. Clause 5.2.2 (code block segmentation and CRC attachment), 5.3.2 (LDPC coding), 5.4.2.1 (rate matching, the circular buffer, N_cb, LBRM and Table 5.4.2.1-2 giving the RV starting positions), 6.2.2 and 7.2.2 (LDPC base graph selection), 7.3.1.1 and 7.3.1.2 (DCI formats 0_x and 1_x field definitions, including NDI, RV, HARQ process number, DAI, CBGTI and CBGFI).
- 3GPP TS 38.213 — Physical layer procedures for control. Clause 9.1.2 (Type-1 semi-static codebook), 9.1.3 (Type-2 dynamic codebook and the DAI), 9.1.4 (Type-3 one-shot, Rel-16), 9.2.3 (PUCCH resource and the DCI 1_0 K1 table), 9.2.5 (multiplexing HARQ-ACK on PUSCH).
- 3GPP TS 38.214 — Physical layer procedures for data. Clause 5.1.2 (PDSCH time-domain allocation and K0), 5.1.3 (modulation order, target code rate and the transport block size determination), Table 5.1.2.1-2 (RV sequence for
pdsch-AggregationFactor), 5.1.7 (CBG-based transmission), 5.3 (UE PDSCH processing procedure time, N1), 6.1.2 (PUSCH time-domain allocation and K2), Table 6.1.2.1-2 (RV sequence forpusch-AggregationFactor), 6.4 (UE PUSCH preparation time, N2). - 3GPP TS 38.331 — RRC protocol specification.
PDSCH-ServingCellConfig,PDSCH-CodeBlockGroupTransmission,PUSCH-ServingCellConfig,PhysicalCellGroupConfig,PUCCH-Config(dl-DataToUL-ACK),PDSCH-TimeDomainResourceAllocation,PUSCH-TimeDomainResourceAllocation,DRX-Config,SPS-Config,ConfiguredGrantConfig. - 3GPP TS 38.322 — RLC protocol specification, for the ARQ layer that catches what HARQ misses:
maxRetxThreshold,t-PollRetransmit,t-Reassemblyand the STATUS PDU. - 3GPP TS 38.300 — NR overall description. Clause 5.4 and 6.2 for where HARQ sits in the protocol stack and its relationship with RLC.
Companion documents in this set
- 02 Radio Frame Structure — slots, symbols and numerology, which every K0/K1/K2 offset counts in.
- 03 Random Access — MSG3 and its use of HARQ process 0.
- 04 Timing Advance —
timeAlignmentTimerexpiry flushes every HARQ buffer, and the NTN mechanisms in §12 share itsNTN-Config. - 06 Channel Mapping — how DL-SCH and UL-SCH transport blocks relate to PDSCH and PUSCH.
- 07 MAC PDU and Control Elements — what is inside the transport block HARQ is protecting.
- 08 Scheduling — DCI formats, the TDRA tables that supply K0 and K2, SPS and configured grants, MCS tables, link adaptation and the pre-emption indication that makes CBG worthwhile.
- 09 LCP — how the contents of a new uplink transport block are chosen once an NDI toggle frees the process.
- 11 DRX — the full Active Time rules that §13's two timers plug into.
- 16 RLM and RLF — where RLC's
maxRetxThresholdfailure ends up. - 26 UE Capability — processing capability 2, soft buffer size, and which of the Rel-16/17 features in §18 the UE actually supports.