在 LTE 时代,HARQ 反馈相对直接:基站每发一个下行传输块(TB),终端就在固定的时序位置回一个 ACK/NACK。但 5G NR 把这套规则打碎了——因为 NR 同时引入了灵活 K1 反馈时序、载波聚合(CA)多服务小区、一个 PUCCH 反馈多个 PDSCH、多时隙调度等特性。
这带来了一个致命问题:当终端漏检了某个 DCI(即没有收到某个 PDSCH 的调度授权),它就不知道这一轮本该反馈多少个 ACK/NACK,也不知道基站期望的反馈在 PUCCH 上的比特位置。位置一错,整个反馈窗口全乱:基站把 NACK 误读成 ACK,重传不再触发,数据静默丢失。
为了解决"终端与基站对反馈位置的认知必须严格对齐"这一核心矛盾,3GPP 在 TS 38.213 第 9 章定义了 HARQ-ACK codebook(码本) 机制,并在 TS 38.212 的 DCI 格式中嵌入了 DAI(Downlink Assignment Index,下行分配索引) 字段。这两者是一对孪生设计:码本规定反馈结构,DAI 保证在动态结构下终端能"补齐漏检"。
本文聚焦 TS 38.213 §9.1 系列,把这条链路从 K1 时序、三种码本类型、DAI 计数原理、Type-2 构造算法、到 PUCCH 承载与 UCI 复用,逐层拆解。
一切码本讨论都建立在 K1(PDSCH-to-HARQ-ACK timing) 之上。K1 定义的是:PDSCH 所在的时隙 n,其 HARQ-ACK 反馈在 n + K1 个上行时隙发送。
dl-DataToUL-ACK(或 R16 的 dl-DataToUL-ACK-r16)给出一组候选 K1 值,例如 {1, 2, 3, 4, 6, 8}。这个设计的关键在于:不同 PDSCH 可以指向同一个 PUCCH 反馈时隙。比如时隙 n 的两个 PDSCH 分别用 K1=4 和 K1=3 指向同一个上行时隙 n+4。于是"一个 PUCCH 携带多个 PDSCH 的反馈"成为常态——这正是码本要解决的问题。
sequenceDiagram
autonumber
participant gNB
participant UE
Note over gNB,UE: 一个 PUCCH 反馈时隙承载多个 PDSCH 的反馈
gNB->>UE: DCI 1_1 (PDSCH#A, K1=3)
gNB->>UE: PDSCH#A @ slot n
gNB->>UE: DCI 1_1 (PDSCH#B, K1=4)
gNB->>UE: PDSCH#B @ slot n+1
gNB->>UE: DCI 1_1 (PDSCH#C, K1=2)
gNB->>UE: PDSCH#C @ slot n+2
Note over UE: 三者 K1 指向同一反馈时隙 n+4
UE->>gNB: PUCCH (slot n+4) 携带 [ACK_A, NACK_B, ACK_C] 码本
理解了 K1 是"汇聚器",码本就是"被汇聚的比特集合的排列规则"。
3GPP 定义了三种 HARQ-ACK codebook,对应三种不同的"反馈排列哲学":
由 RRC 参数 pdsch-HARQ-ACK-Codebook = semiStatic 触发。码本大小与实际调度无关,预先固定。
构造逻辑:终端根据 RRC 配置的 K1 集合、PDCCH 监听时机(MO)集合、服务小区集合,预先枚举出一个"候选 PDSCH 接收时机"的二维/三维网格 M。无论基站实际是否在这些位置调度了 PDSCH,码本里每一个位置都占一格:
优点:结构完全可预测,终端和基站永远对齐,完全不需要 DAI(DAI 在 Type 1 中仅作冗余校验)。 缺点:开销大。当基站实际只在网格里调度了少量 PDSCH 时,码本里塞满了无意义的 NACK 占位比特,浪费 PUCCH 容量。
由 pdsch-HARQ-ACK-Codebook = dynamic 触发。码本大小根据实际收到的 DCI 动态确定,DAI 是其核心。
终端不再枚举固定网格,而是只对实际检测到下行分配的 DCI 进行 ACK/NACK 排列。由于结构是动态的,必须有 DAI 来告诉终端"你这一轮总共该收到几个、当前是第几个",否则漏检一处就会错位。
优点:开销最小,只反馈实际调度。 缺点:逻辑复杂,依赖 DAI 的正确计数与漏检恢复。
Rel-16 引入。一次性反馈一个或多个 HARQ 进程的当前 ACK/NACK 状态,与是否被本次调度无关。
由 DCI 中的 "HARQ-ACK request (one-shot)"(或 DCI 1_2 的对应字段)触发。用于 eMIMO、multi-TRP、URLLC 等场景下,长时间未反馈导致 gNB 与 UE 的 HARQ 进程状态失步后,强制同步一次。
三种码本的对比如下表:
| 维度 | Type 1 半静态 | Type 2 动态 | Type 3 一次性 |
|---|---|---|---|
| 引入版本 | R15 | R15 | R16 |
| TS 38.213 章节 | §9.1.2 | §9.1.3 | §9.1.4 |
| RRC 触发参数 | pdsch-HARQ-ACK-Codebook=semiStatic |
=dynamic |
DCI one-shot 字段触发 |
| 码本大小 | 固定(RRC 预计算) | 动态(随调度变化) | 固定(=HARQ 进程数) |
| 是否依赖 DAI | 否(仅冗余校验) | 是(核心) | 否 |
| 漏检 DCI 处理 | 天然占位 NACK | DAI 补齐 NACK | 反馈进程当前状态 |
| 开销 | 高 | 低 | 中(一次性) |
| 典型场景 | 调度密集、稳定 | CA、稀疏调度 | multi-TRP、URLLC、状态同步 |
DAI 不是单一字段,而是两个子字段的组合,这是初学者最容易混淆的点。
累计计数器:到当前 PDCCH 监听时机为止,基站已经下发(含当前这条)的下行分配的累计序号。计数顺序为:先按服务小区(carrier)递增,再按 PDCCH 监听时机(MO)递增。
例如:PCell 上调度 → C-DAI=1,SCell#1 调度 → C-DAI=2,SCell#2 调度 → C-DAI=3,下一个 MO 的 PCell → C-DAI=4……以此类推,模 4 循环(0→1→2→3→0)。
总数指示:到当前 PDCCH 监听时机为止,跨所有服务小区,本轮(将复用到同一个 PUCCH)下行分配的总数。同样模 4。
T-DAI 的价值在于"封口":它告诉终端"这一捆里一共有多少个分配",使终端能在最后一个监听时机就确定码本的总大小。
| DCI 格式 | Counter DAI | Total DAI | 比特数 | 说明 |
|---|---|---|---|---|
| DCI 1_0 | 有(2 bit) | 无 | 2 | 仅 counter,常用于回退调度 |
| DCI 1_1 | 有(2 bit) | 有(2 bit) | 2 + 2 | counter + total,Type-2 主力 |
| DCI 1_2 (R17) | 有 | 有 | 可变 | 更紧凑,适配 NR-Light |
两个 DAI 都是 2 bit、模 4 计数。模 4 的设计是一个精妙折衷:4 个值(0/1/2/3)能在 2 bit 内表达,覆盖一轮 4 个分配的循环;当一轮分配数超过 4 时靠多轮循环 + T-DAI 配合还原。
这是整个机制的灵魂。设想一个 CA 场景,本轮 5 个下行分配:
| 事件 | C-DAI | T-DAI | UE 是否收到 |
|---|---|---|---|
| 分配1(PCell) | 1 | 5 | ✅ 收到 |
| 分配2(SCell1) | 2 | 5 | ❌ 漏检 |
| 分配3(SCell2) | 3 | 5 | ✅ 收到 |
| 分配4(SCell3) | 4 | 5 | ✅ 收到 |
| 分配5(PCell) | 1(模4) | 5 | ✅ 收到 |
UE 的恢复逻辑(对应 TS 38.213 §9.1.3.2 伪代码):
1 → (缺2) → 3 → 4 → 1。当 UE 发现 C-DAI 从 1 直接跳到 3(增量不是 +1),判定 C-DAI=2 的位置发生了漏检,在该位置插入一个 NACK 占位。[#1=ACK, #2=NACK(补), #3=NACK, #4=ACK, #5=ACK],与基站期望的 5 比特位置完全对齐。如果连携带 T-DAI 的最后一个 DCI 也漏检了怎么办? 此时 UE 无法得到总数,使用最后一个正确接收的 T-DAI 值作为码本大小上界——这是一种尽力而为的降级,保证不会比真实值少,至多多补 NACK。
将上述原理形式化为流程:
flowchart TD
A[UE 在 PDCCH 监听时机检测到下行分配] --> B{是否携带 C-DAI / T-DAI?}
B -- 是 --> C[记录 C-DAI 当前值]
B -- 否 --> Z[忽略, 非 Type-2 调度]
C --> D{C-DAI 增量是否等于 +1?}
D -- 否 --> E[检测到跳变 → 判定漏检]
E --> F[在缺失位置插入 NACK 占位比特]
D -- 是 --> G[按序追加该 PDSCH 的 ACK/NACK]
F --> G
G --> H{是否还有后续监听时机?}
H -- 是 --> A
H -- 否 --> I[取最后一个有效 T-DAI 作为码本总数 N]
I --> J{UE 实际收集到的 C-DAI 最大值<br/>是否 < T-DAI?}
J -- 是 --> K[尾部补 NACK 至 N 比特]
J -- 否 --> L[码本长度对齐完成]
K --> L
L --> M[码本输出: N 个 HARQ-ACK 比特]
M --> N2[映射到 PUCCH 资源传输]
这个流程揭示了 Type-2 的三道对齐防线:C-DAI 防中间漏检、T-DAI 防尾部漏检、模 4 循环防计数溢出。
码本类型本身不是终端自选的,而是 RRC 配置 + DCI 触发的组合决策:
flowchart TD
S[RRC: pdsch-HARQ-ACK-Codebook 配置] --> T{配置值}
T -- semiStatic --> T1[Type 1: 固定网格<br/>枚举 K1 × MO × Cell]
T -- dynamic --> T2[Type 2: 动态结构<br/>依赖 DAI 计数]
T2 --> DCIcheck{DCI 是否带 one-shot<br/>HARQ-ACK request?}
DCIcheck -- 是 --> T3[Type 3: 一次性<br/>反馈所有 HARQ 进程状态]
DCIcheck -- 否 --> T2f[保持 Type 2 动态码本]
T1 --> Sel[确定码本比特序列]
T2f --> Sel
T3 --> Sel
Sel --> PUCCH[PUCCH Format 选择<br/>1~2bit→F0/F1<br/>多bit码本→F2]
PUCCH --> Tx[上行传输]
注意一个细节:Type 1 与 Type 3 的码本大小都依赖 RRC 配置(Type 1 依赖 K1 候选集,Type 3 依赖 HARQ 进程数),唯独 Type 2 是真正运行时动态的。这也是为什么 DAI 机制只在 Type 2 中是"必需"的。
码本生成后,需要选择 PUCCH 资源承载。承载能力由 PUCCH 格式决定:
| PUCCH 格式 | 承载 UCI 比特 | 时长 | 典型用途 |
|---|---|---|---|
| Format 0 | 1–2(HARQ-ACK/SR) | short/long | 单或少比特反馈 |
| Format 1 | ≤2 | short/long | HARQ-ACK + SR(序列扩频) |
| Format 2 | >2 | short/long | 码本较大时的主力 |
| Format 3 | ≤1150 | short/long | 大 UCI(CSI + 码本) |
| Format 4 | ≤1150 + OCC | short only | 大 UCI + 正交复用 |
经验法则:HARQ-ACK 比特少时用 Format 0/1;当 Type-2 码本因 CA 或多时隙调度膨胀到数比特以上,自动切换到 Format 2。
当 HARQ-ACK、SR(调度请求)、CSI 需要在同一上行时机反馈时,存在复用与丢弃规则:
当一个 PDSCH 携带 2 个码字(TB)时,默认需要反馈 2 个 HARQ-ACK 比特。若开启 harq-ACK-SpatialBundlingPUCCH,则对两个码字的反馈做逻辑 AND,仅反馈 1 比特——两个码字都正确才回 ACK,否则 NACK。这是一种用可靠性换开销的折衷。
DAI 与码本机制并非一成不变,每个版本都在打补丁与扩边界:
这条演进线说明:HARQ-ACK 码本始终是 5G 物理层"可靠性 ↔ 开销 ↔ 时延"三角博弈的核心调节器。
在 Wireshark/TShark 中分析 5G L1/L2 抓包(如 gNB 内部信令跟踪、或 O-RAN CU/DU 接口抓取),识别 HARQ-ACK 链路:
症状:基站频繁触发不必要的重传,吞吐量下降 排查:检查是否误用 Type-1 半静态码本导致大量占位 NACK 被解读为解码失败;或 Type-2 中 C-DAI 计数因跨载波调度顺序实现不一致而错位。
症状:CA 场景下特定 SCell 持续 NACK 排查:C-DAI 跳变检测逻辑 Bug——终端未在漏检位置插入占位,导致该 SCell 反馈错位到相邻比特。
症状:PUCCH Format 选择错误,反馈被截断
排查:码本比特数超过 Format 1 的 2 比特上限,但 RRC 未配置 Format 2 资源,导致反馈丢失。检查 PUCCH-Config 中 format1/format2 资源集。
症状:T-DAI 漏检后码本大小异常 排查:终端降级使用"最后有效 T-DAI"的逻辑是否正确实现;若直接用 C-DAI 最大值会导致码本偏小、尾部丢失。
Q1: Type 1 和 Type 2 到底该用哪个? A: 看调度密度与 CA 程度。调度稳定、密集、CA 较少 → Type 1(实现简单、鲁棒,且此时占位开销可接受);CA 多、调度稀疏/突发、PUCCH 资源紧张 → Type 2(开销小)。Type 2 是 5G 的主流选择,但 Type 1 在特殊场景仍有价值。
Q2: 为什么 DAI 只有 2 bit、还要模 4? A: 2 bit 是开销与表达力的折衷。模 4 循环配合 T-DAI 的总数指示,即使一轮分配超过 4 个也能正确还原。理论上若用更多 bit(如 LTE TDD 的 4 bit),开销翻倍而收益有限,因为 T-DAI 已提供总数封口。
Q3: 如果终端连最后一个带 T-DAI 的 DCI 都漏检了怎么办? A: 终端使用最后一个正确接收到的 T-DAI 值作为码本大小上界(TS 38.213 §9.1.3 降级规则)。这是尽力而为——码本不会比真实值小(不会丢比特),至多多补几个 NACK 占位,基站据此重传,最终仍能收敛正确。
Q4: DAI 和 HARQ Process ID 是一回事吗? A: 完全不同。HARQ Process ID(DCI 中 3-4 bit,R19 扩展支持 32 进程)标识的是软缓冲区编号,用于合并重传;DAI 标识的是本轮码本内的反馈序号,用于对齐反馈位置。一个码本里多个比特可对应不同 HARQ 进程,两者正交。
Q5: Type 3 一次性码本什么时候触发? A: 由 DCI 1_1/1_2 的 one-shot HARQ-ACK request 字段显式触发。典型用于 multi-TRP 切换、URLLC 高可靠场景,或长期未反馈导致 gNB/UE 进程状态可能失步时,强制同步所有 HARQ 进程的当前 ACK/NACK 状态。
本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
