基于3GPP TS 38.213 V19.x Section 9.1系列,深度拆解Type 1/2/3 HARQ-ACK码本的构造机制、DAI计数博弈及多PDSCH调度场景下的反馈时序设计。
在NR下行传输中,gNB通过PDSCH向UE发送数据,UE需要在指定上行时隙通过PUCCH或PUSCH反馈ACK/NACK。核心矛盾在于:当gNB在一个反馈窗口内调度了多个PDSCH(可能跨越多个小区、多个时隙),UE必须精确构造一个码本,将所有ACK/NACK比特按照gNB期望的顺序和位置排列。码本错了,gNB无法正确解读反馈——这比不反馈更危险。
3GPP TS 38.213 Section 9.1定义了三种码本类型来解决这个矛盾,其核心设计哲学各不相同:
| 码本类型 | 规范章节 | 设计哲学 | 码本大小决定方式 |
|---|---|---|---|
| Type 1 | 9.1.2 | 半静态确定性 | RRC配置 + K1集合预计算 |
| Type 2 | 9.1.3 | 动态最小化 | DCI中DAI实时指示 |
| Type 3 | 9.1.4 | 全量一次性反馈 | 所有HARQ进程全反馈 |
flowchart TD
A[HARQ-ACK反馈触发] --> B{pdsch-HARQ-ACK-Codebook配置}
B -->|semiStatic| C[Type 1 半静态码本]
B -->|dynamic| D[Type 2 动态码本]
B -->|Enhanced Type3触发| E[Type 3 一次性码本]
C --> C1[基于RRC配置的K1集合]
C1 --> C2[遍历所有候选PDSCH位置]
C2 --> C3[固定大小码本]
D --> D1[基于DCI中C-DAI/T-DAI]
D1 --> D2[仅反馈实际调度的PDSCH]
D2 --> D3[动态大小码本]
E --> E1[所有HARQ进程ID全反馈]
E1 --> E2[按进程号顺序排列]
E2 --> E3[固定大小 = 进程数 × 每进程比特]
C3 --> F[PUCCH/PUSCH承载]
D3 --> F
E3 --> F
在深入码本构造之前,必须先理解K1(PDSCH-to-HARQ-ACK反馈时序),这是所有码本类型的公共基础设施。
K1由RRC参数dl-DataToUL-ACK(或dl-DataToUL-ACK-r16)配置,表示PDSCH所在时隙到ACK/NACK反馈所在时隙的偏移量。DCI中的PDSCH-to-HARQ_feedback timing indicator字段从RRC配置的K1集合中选择一个具体值。
当UE需要在时隙n的PUCCH上发送HARQ-ACK时,反馈窗口为:
时隙集合 = {n - K1_min, n - K1_min + 1, ..., n - K1_max}
其中K1_min和K1_max是配置的K1集合中的最小值和最大值。所有潜在映射到时隙n的PDSCH都在这个窗口内。
sequenceDiagram
participant gNB
participant UE
Note over gNB,UE: K1集合配置 = {1, 2, 3, 4}
Note over gNB,UE: 反馈时隙 n = 8
gNB->>UE: DCI (Slot 4) K1=4 → PDSCH Slot 4
gNB->>UE: DCI (Slot 5) K1=3 → PDSCH Slot 5
gNB->>UE: DCI (Slot 6) K1=2 → PDSCH Slot 6
gNB->>UE: DCI (Slot 7) K1=1 → PDSCH Slot 7
Note over UE: 反馈窗口: Slot 4~7 (n-K1_max ~ n-K1_min)
UE->>gNB: PUCCH (Slot 8) HARQ-ACK码本
Note over UE: 包含Slot 4,5,6,7所有PDSCH的ACK/NACK
当配置了载波聚合(CA)时,不同小区可能配置不同的K1集合。Type 1码本需要对所有小区取K1并集,确保覆盖所有可能的PDSCH映射。
| 参数 | 含义 | 取值范围 |
|---|---|---|
| K1 | PDSCH到HARQ-ACK的时隙偏移 | {1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 16} |
| K0 | DCI到PDSCH的时隙偏移 | TDRA表中配置 |
| M | 每时隙最大PDSCH候选数 | 取决于UE能力 |
| N_Cells | 配置的小区数 | RRC配置 |
Type 1码本的核心思想:不论gNB是否实际调度了PDSCH,UE都为反馈窗口内的每一个候选PDSCH位置预留一个ACK/NACK比特。码本大小完全由RRC配置决定,与实际调度无关。
构造步骤(TS 38.213 Section 9.1.2):
flowchart TD
subgraph "Step 1: 确定反馈窗口"
A1[K1集合] --> A2[候选DL时隙集合]
end
subgraph "Step 2: 遍历每个小区每个时隙"
A2 --> B1[小区c = 0]
B1 --> B2[时隙j = 集合中第1个]
B2 --> B3{时隙j有DL分配?}
B3 -->|Yes| B4[候选PDSCH数 = M_c,j]
B3 -->|No| B5[候选PDSCH数 = 0]
B4 --> B6[生成M_c,j个HARQ-ACK比特]
B5 --> B7[跳过]
end
subgraph "Step 3: 拼接码本"
B6 --> C1[按小区顺序拼接]
C1 --> C2[最终码本]
end
Type 1码本总比特数:
O_ACK = Σ_c Σ_j M_c,j × N_bits_per_PDSCH
其中:
- c 遍历所有配置的下行小区
- j 遍历反馈窗口内的所有候选时隙
- M_c,j 是小区c在时隙j的最大可调度PDSCH数
- N_bits_per_PDSCH 为1(空间捆绑使能)或2(空间捆绑禁用,码字0和码字1各1 bit)
关键设计:如果UE在某个候选位置没有检测到调度DCI,对应比特设为NACK。这意味着Type 1码本天生支持"哑比特"——即使用户没有收到数据,码本中也有位置。
| 场景 | 比特值 | 原因 |
|---|---|---|
| PDSCH解码成功 | ACK | 正常反馈 |
| PDSCH解码失败 | NACK | 请求重传 |
| 未检测到DCI | NACK | 安全假设,避免误判 |
| SPS PDSCH释放 | ACK | DCI指示释放,非数据接收 |
浪费问题:假设配置4个小区,K1集合为{1,2,3,4},每时隙最多调度1个PDSCH,则码本大小 = 4 × 4 × 1 = 16 bit。但如果gNB实际只调度了2个PDSCH,则14个比特是无效的NACK填充。在TDD场景下,DL时隙少但K1集合大时,浪费更为严重。
Type 2码本通过DAI(Downlink Assignment Index)来解决Type 1的浪费问题。DAI包含两个子字段:
| DAI字段 | 英文 | 含义 | 位置 |
|---|---|---|---|
| Counter DAI (C-DAI) | Counter DAI | 累计计数已调度的PDSCH | DCI format 1_0/1_1 |
| Total DAI (T-DAI) | Total DAI | 到目前为止的总调度数 | DCI format 1_1 |
C-DAI:每调度一个PDSCH(或SPS释放),C-DAI递增1。C-DAI按(服务小区组, 调度PDSCH-to-HARQ-ACK时机)两级计数。模运算大小为T_D = 2^(N_C-DAI_bits),其中N_C-DAI_bits为2或4。
T-DAI:每检测到一个DCI,T-DAI更新为该DCI中的T-DAI值。T-DAI仅在DCI format 1_1中存在,在DCI format 1_0中不可用。
sequenceDiagram
participant gNB
participant UE
Note over gNB,UE: Type 2码本 | 4小区CA | C-DAI 2bit (模4)
rect rgb(230, 245, 255)
Note over gNB,UE: 小区0 Slot调度
gNB->>UE: DCI Cell0: C-DAI=1, T-DAI=1
gNB->>UE: DCI Cell1: C-DAI=2, T-DAI=2
gNB->>UE: DCI Cell2: C-DAI=3, T-DAI=3
Note over UE: UE未检测到Cell3的DCI (漏检!)
end
rect rgb(255, 240, 230)
Note over gNB,UE: 反馈窗口结束
Note over UE: C-DAI期望: 1,2,3,4<br/>实际收到: 1,2,3<br/>T-DAI = 3 (最后收到的)
Note over UE: 检测到DAI空隙: C-DAI从3跳到?<br/>推断缺少1个调度 → 插入NACK
Note over UE: 构造码本: [ACK, ACK, NACK, NACK(漏检)]
end
UE->>gNB: PUCCH Type 2码本 (4 bits)
TS 38.213 Section 9.1.3定义了Type 2码本的精确构造过程:
Step 1 — 收集DCI信息:UE记录每个检测到的DCI中的C-DAI和T-DAI值,以及对应的小区编号和HARQ-ACK时机。
Step 2 — 生成服务小区子码本:对每个服务小区,按HARQ-ACK时机顺序排列,每个时机生成对应数量的HARQ-ACK比特。
Step 3 — 检测漏检:UE检查C-DAI的连续性。如果发现C-DAI的值不连续(如从2直接跳到4),则推断漏检了1个调度,在对应位置插入NACK。
Step 4 — 确定总码本大小:最终码本大小 = T-DAI × N_bits_per_PDSCH。如果UE没有收到任何T-DAI(所有DCI都是format 1_0),则码本大小基于最后收到的C-DAI值。
Step 5 — 拼接:按服务小区顺序,拼接所有子码本。
C-DAI的计数遵循两级循环结构(TS 38.213 Section 9.1.3):
对于每个PDSCH-to-HARQ-ACK时机 m:
对于每个服务小区 c (按ServCellIndex升序):
如果小区c在时机m有PDSCH调度:
C-DAI(c, m) = 上一个C-DAI值 + 1 (模 T_D)
这意味着C-DAI在小区维度和时序维度上连续计数。当C-DAI达到T_D - 1后回到0(模运算)。
| C-DAI比特数 | T_D | 最大可追踪调度数 | 适用场景 |
|---|---|---|---|
| 2 bit | 4 | 4 (无T-DAI辅助) | 小规模调度 |
| 4 bit | 16 | 16 (无T-DAI辅助) | 大规模CA场景 |
| 调度场景 | Type 1码本大小 | Type 2码本大小 | 节省率 |
|---|---|---|---|
| 4小区,K1={1,2,3,4},调度2个PDSCH | 16 bit | 2 bit | 87.5% |
| 4小区,K1={1,2,3,4},调度8个PDSCH | 16 bit | 8 bit | 50% |
| 8小区,K1={1,2,3,4},调度3个PDSCH | 32 bit | 3 bit | 90.6% |
| 单小区,K1={4},调度1个PDSCH | 1 bit | 1 bit | 0% |
结论:CA配置越复杂、实际调度越稀疏,Type 2的优势越明显。
Type 3码本(也称enhanced Type 3或one-shot HARQ-ACK)引入于R16,其设计动机是解决一个极端场景:当gNB与UE之间的HARQ-ACK状态出现严重失步时,需要一种机制让UE一次性报告所有HARQ进程的反馈状态。
典型触发场景: - 波束失败恢复后:UE切换到新波束,可能丢失了旧波束上的部分HARQ-ACK反馈 - DRX长周期唤醒后:UE可能错过了某些反馈时机 - 切换后:目标gNB需要获知UE侧的HARQ进程状态 - 网络请求:gNB通过DCI format 1_1或1_2主动触发Type 3码本上报
Type 3码本的构造逻辑(TS 38.213 Section 9.1.4):
对每个服务小区 c:
对每个HARQ进程 ID = 0, 1, ..., N_HARQ-1:
生成 N_HARQ-ACK_bits_c 个比特
其中:
N_HARQ-ACK_bits_c = 1 (空间捆绑使能)
或 N_max_codewords (空间捆绑禁用)
码本大小计算:
O_Type3 = Σ_c (N_HARQ_processes × N_HARQ-ACK_bits_c)
flowchart TD
A[Type 3码本触发] --> B{触发源}
B -->|DCI format 1_1| C[One-shot feedback request = 1]
B -->|DCI format 1_2| C
B -->|gNB高层触发| D[RRC配置触发]
C --> E[确定HARQ进程数]
D --> E
E --> F[对每个小区 c = 0..N_Cells-1]
F --> G[对每个HARQ进程 ID = 0..N_proc-1]
G --> H[生成对应比特]
H --> I{CBG使能?}
I -->|No| J[TB级: 1或2 bit/进程]
I -->|Yes| K[CBG级: N_CBG bit/进程]
J --> L[按小区→进程顺序拼接]
K --> L
L --> M[最终Type 3码本]
| 配置 | HARQ进程数 | 小区数 | TB级码本大小 | CBG级码本大小 (max 8 CBG) |
|---|---|---|---|---|
| FDD单小区 | 16 | 1 | 16 bit | 128 bit |
| TDD单小区 | 8 | 1 | 8 bit | 64 bit |
| 4小区CA (FDD) | 16 | 4 | 64 bit | 512 bit |
| 8小区CA (TDD) | 8 | 8 | 64 bit | 512 bit |
Type 3码本通常很大,因此常需要PUSCH承载而非PUCCH。
码块组(Code Block Group, CBG)是R15引入的细粒度反馈机制。一个TB(Transport Block)被分割为多个CB(Code Block),多个CB组成一个CBG。UE可以针对每个CBG单独反馈ACK/NACK,而非整个TB。
RRC参数maxCodeBlockGroupsPerTransportBlock配置每个TB的最大CBG数,取值为{2, 4, 6, 8}。
| 码本类型 | CBG影响 | 码本大小变化 |
|---|---|---|
| Type 1 | 每候选PDSCH位置生成N_CBG个比特而非1个 | ×N_CBG倍 |
| Type 2 | 每实际调度PDSCH生成N_CBG个比特 | ×N_CBG倍 |
| Type 3 | 每HARQ进程生成N_CBG × N_CW个比特 | ×N_CBG × N_CW倍 |
在CBG模式下,DAI的计数粒度仍然是每个PDSCH调度(即每个TB),而非每个CBG。一个PDSCH调度的N_CBG个CBG的ACK/NACK比特在码本中连续排列。
R16引入了单DCI调度多个PDSCH的能力(multi-PDSCH scheduling)。一个DCI可以指示多个PDSCH的时频资源,每个PDSCH可能映射到不同的K1值。
这对码本构造产生了深远影响:
Type 1的影响:每个PDSCH独立计算其在码本中的位置,需要考虑多PDSCH间的时序关系。
Type 2的影响:DAI计数需要为每个PDSCH独立递增。一个DCI调度N个PDSCH,C-DAI递增N次而非1次。
sequenceDiagram
participant gNB
participant UE
Note over gNB,UE: 单DCI调度3个PDSCH (multi-PDSCH scheduling)
Note over gNB,UE: K0集合 = {0, 1, 2}, K1集合 = {2, 3, 4}
gNB->>UE: DCI (Slot 5): 调度PDSCH_0(Slot5), PDSCH_1(Slot6), PDSCH_2(Slot7)
Note over gNB,UE: K1分配: PDSCH_0→K1=3, PDSCH_1→K1=2, PDSCH_2→K1=1
Note over UE: PDSCH_0: Slot 5, 反馈时隙 = 5+3 = Slot 8
Note over UE: PDSCH_1: Slot 6, 反馈时隙 = 6+2 = Slot 8
Note over UE: PDSCH_2: Slot 7, 反馈时隙 = 7+1 = Slot 8
Note over UE: 3个PDSCH映射到同一个PUCCH时隙8
Note over UE: Type 2码本: C-DAI连续递增3次
Note over UE: C-DAI = 1(PDSCH_0) → 2(PDSCH_1) → 3(PDSCH_2)
UE->>gNB: PUCCH Slot 8: [ACK_0, ACK_1, ACK_2]
R18进一步增强了multi-PDSCH调度,支持跨小区调度。DCI format 1_2可以调度多个小区的PDSCH。DAI计数在所有被调度的小区上连续递增,增加了码本构造的复杂度。
| R版本 | 多PDSCH调度增强 | DAI计数影响 |
|---|---|---|
| R15 | 单DCI单PDSCH | C-DAI每DCI递增1 |
| R16 | 单DCI多PDSCH(同小区) | C-DAI每PDSCH递增1 |
| R17 | 多PDSCH + 多TRP | 多TRP独立DAI计数 |
| R18 | 跨小区多PDSCH | C-DAI跨小区连续递增 |
码本大小直接决定了PUCCH格式选择(TS 38.213 Section 9.2):
| 码本大小 | PUCCH格式 | 特点 |
|---|---|---|
| 1-2 bit | PUCCH Format 0/1 | 短PUCCH,2符号 |
| 3-11 bit | PUCCH Format 2 | 短PUCCH,2符号,DMRS+数据 |
| 12-1706 bit | PUCCH Format 3 | 长PUCCH,4-14符号 |
| 12-1706 bit | PUCCH Format 4 | 长PUCCH,4-14符号,块扩频 |
当UE有PUSCH传输时,HARQ-ACK码本通过UCI复用到PUSCH(TS 38.212 Section 6.2.7)。此时需要考虑HARQ-ACK与CSI、SR的优先级和资源分配。
在LOG分析中识别HARQ-ACK码本相关的关键信息:
| 信息来源 | 查看内容 | 工具 |
|---|---|---|
| DCI日志 | C-DAI、T-DAI字段值 | QXDM, TEMS |
| RRC重配置 | harq-ACK-Codebook参数 |
Wireshark |
| MAC CE | PUCCH功率控制调整 | QXDM |
| PUCCH日志 | 实际发送的ACK/NACK比特 | 基站侧LOG |
故障1:Type 2码本DAI失步 - 现象:UE反馈的码本大小与gNB期望不一致,导致后续调度混乱 - 根因:UE漏检了一个PDCCH,导致C-DAI计数出现空隙 - 表现:BLER突然升高,误码率增加 - 排查:对比UE侧C-DAI序列与gNB侧调度序列,找出漏检位置
故障2:Type 1码本大小不一致
- 现象:UE与gNB对码本大小理解不同
- 根因:K1集合配置不匹配,或M值(每时隙最大PDSCH数)理解差异
- 排查:检查RRC重配置消息中dl-DataToUL-ACK的值,确认双方K1集合一致
故障3:K1值选择不当 - 现象:PUCCH资源冲突,HARQ-ACK反馈时延过大 - 根因:K1集合包含的值不适合当前TDD配置 - 排查:检查TDD上下行配比与K1集合的兼容性
flowchart TD
A[HARQ-ACK码本选型决策] --> B{CA配置?}
B -->|单小区或2小区| C{调度密度?}
B -->|4+小区CA| D{需要高效反馈?}
C -->|高密度调度| E[Type 1]
C -->|低密度调度| F[Type 2]
D -->|Yes, PUCCH资源紧张| G[Type 2]
D -->|No, 简单优先| E
E --> H{需要故障恢复?}
F --> H
G --> H
H -->|Yes| I[Type 3 作为补充]
H -->|No| J[仅Type 1或Type 2]
I --> K[最终配置: Type 1/2 + Type 3]
J --> K
| 决策因素 | 推荐Type 1 | 推荐Type 2 | 推荐Type 3 |
|---|---|---|---|
| CA配置 | ≤2小区 | ≥4小区 | 任意 |
| 调度模式 | 周期性、均匀 | 突发、不均匀 | 故障恢复 |
| PUCCH容量 | 充裕 | 紧张 | 需PUSCH承载 |
| UE复杂度要求 | 低 | 中 | 高 |
| 可靠性要求 | 中等 | 高 | 极高 |
Q1: Type 1和Type 2码本可以动态切换吗?
A: 可以。gNB通过RRC重配置消息中的harq-ACK-Codebook字段可以切换码本类型。但切换过程中存在一个过渡窗口——UE收到RRC重配置后需要在RRCReconfigurationComplete之后的新配置生效时间点才能使用新码本类型。过渡期间的HARQ-ACK反馈使用旧配置。需要注意的是,切换过程中如果UE使用了错误的码本类型,会导致gNB解析错误,因此建议在DRX非活跃期或空闲期进行切换。
Q2: 当Type 2码本中UE漏检了一个DCI,gNB如何知道UE漏检了?
A: UE通过检测C-DAI的不连续性来推断漏检,并在对应位置插入NACK比特。gNB侧知道实际发送了多少个调度(因为C-DAI的期望序列是已知的),当gNB在PUCCH中收到NACK时,会区分是真正的解码失败还是DAI空隙导致的NACK。但gNB无法仅从码本本身100%确定哪个PDSCH被漏检——它只能通过比对调度记录和收到的码本模式来推断。这也是Type 2码本的主要风险:如果漏检导致码本大小与gNB期望完全不同(例如漏检了携带新T-DAI的DCI),可能导致严重的码本失步。
Q3: Type 3码本与enhanced Type 3有什么区别?
A: 在R16中引入的Type 3码本和enhanced Type 3码本在概念上是同一类,都指一次性反馈所有HARQ进程的ACK/NACK。R18对其进行了进一步增强,主要包括:(1) 支持通过DCI format 1_3触发(新的DCI格式);(2) 支持按优先级分组反馈(高优先级和低优先级HARQ-ACK可以分别构造Type 3码本);(3) 支持多TRP场景下独立反馈各TRP的HARQ进程状态。
Q4: CBG级反馈对HARQ-ACK码本的影响是正面还是负面的?
A: 这取决于场景。CBG级反馈使得重传粒度更细——只需要重传失败的CBG而非整个TB,这在好的无线环境下(部分CBG失败)可以显著提升吞吐量。但代价是码本大小成倍增加(×N_CBG),占用更多PUCCH/PUSCH资源。因此CBG级反馈最适合大TB传输(如eMBB的高MCS场景)和信道质量波动较大的场景。对于小包传输(如URLLC的低MCS场景),TB级反馈通常更优。
Q5: 多PDSCH调度场景下DAI溢出(模运算翻转)如何处理?
A: C-DAI使用模运算(模4或模16),所以翻转是设计的一部分而非异常。UE通过追踪C-DAI的变化模式来计数——只要C-DAI是单调递增(考虑模运算)的就认为连续。但当调度数很大(超过T_D×多轮)时,如果中间有漏检,UE可能无法正确判断到底漏检了几个DCI。这就是为什么R16引入了4 bit C-DAI(T_D=16),比R15的2 bit(T_D=4)提供了更大的追踪窗口。在高负载多小区CA场景中,4 bit C-DAI几乎是必须的。
本文由「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 等场景全覆盖。
