NVIDIA 的 FP32 除法指令 Insight

NVIDIA 的 FP32 除法指令

本文从 SASS 汇编的角度解读 NVIDIA Hopper 架构下的 FP32 除法指令算法。

IEEE 754 – FP32 背景

概括

  • 正规数:有隐含 1,指数域非 0/255
  • 次正规:无隐含 1,指数域全 0 且尾数非 0,用来平滑下溢
  • NV 对正规/次正规分支处理非常显式:先判定 e,再对次正规做放大归一和指数补偿,最后再按 IEEE 754 规则舍入与打包;若使用 .FTZ,则可直接把次正规视为 0 来加速

正规数的定义与判别

  • 位布局:1 位符号 s,8 位指数 e,23 位尾数 f。

  • 正规数(normal)

    • 条件:1 ≤ e ≤ 254(指数域既不是全 0,也不是全 1)
    • 数值:$(-1)^s \times $ $2^{(e - 127)}$ $\times 1.f$ 这里有隐含的最高位 1
    • 最小正正规数:$2^{-126}$ ≈ 1.17549435e-38
  • 次正规数(subnormal/denormal)

    • 条件:e = 0 且 f ≠ 0
    • 数值:${(-1)}^s \times 2^{(1 - 127)} \times 0.f$ 没有隐含的 1,最高位为 0
    • 用途:实现“渐进下溢”(gradual underflow),在接近 0 时避免精度突然崩塌
    • 最小正次正规:$2^{-149}$ ≈ 1.401298e-45;最大次正规:$2^{-126}$ $\times$ $(1 - 2^{-23})$
  • 特殊值

    • e = 0, f = 0 → ±0
    • e = 255, f = 0 → ±∞
    • e = 255, f ≠ 0 → NaN

快速判断法:

  • 正规:e ∈ [1, 254]
  • 次正规:e = 0 且 f ≠ 0
  • 零:e = 0 且 f = 0
  • ∞/NaN:e = 255

为什么有次正规数

  • 在非常接近 0 的区域,若没有次正规,数值会从最小正规数直接“跳”到 0,精度断崖式丢失
  • 次正规通过去掉隐含 1,继续用更小的有效指数刻画更接近 0 的值,实现“渐进下溢”

对性能与实现的影响

  • 许多 GPU/CPU 在处理次正规时更慢;为性能常启用 FTZ/DAZ:

    • FTZ(flush-to-zero):遇到次正规结果,按 0 处理
    • DAZ(denormals-are-zero):将次正规输入按 0 处理
  • 在下文的 SASS 里能看到:

    • .FTZ 修饰(如 FADD.FTZ、FSETP.*.FTZ)控制对次正规按 0 处理的语义
    • 用 SHF/LOP3 提取指数判断是否正规:e 是否为 0
    • 若检测到次正规,会“归一化”到正规范围再计算:例如乘以 2^64 放大(FFMA …, 1.8446744e+19),并用 R9 累计指数修正(-64/+64),等效把没有隐含 1 的次正规尾数变成带隐含 1 的正规尾数来处理
    • 之后再做倒数/迭代、严格舍入与打包,或在启用 FTZ 的路径上直接按 0 处理

FP32 的 demo 内核实现

1
2
3
4
5
6
7
8
9
__global__ void div_kernel(const float* __restrict__ num,
                           const float* __restrict__ dnum,
                           float* __restrict__ output,
                           int n) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < n) {
        output[idx] = num[idx] / dnum[idx];
    }
}

算法分析

Dump 出的 SASS 汇编如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383

	code for sm_100a
	.target	sm_100a

		Function : _Z10div_kernelPKfS0_Pfi                          
	.headerflags	@"EF_CUDA_ACCELERATORS EF_CUDA_SM100 EF_CUDA_VIRTUAL_SM(EF_CUDA_SM100)"

//////////
// 线程绑定
//////////
        /*0000*/                   LDC R1, c[0x0][0x37c] ;                             /* 0x0000df00ff017b82 */ // 从常量内存段加载参数列表指针,取栈帧/参数基址
                                                                                       /* 0x000fe20000000800 */
        /*0010*/                   S2R R3, SR_TID.X ;                                  /* 0x0000000000037919 */ // 读入 threadIdx.x,准备计算线程线性索引
                                                                                       /* 0x000e2e0000002100 */ 
        /*0020*/                   S2UR UR4, SR_CTAID.X ;                              /* 0x00000000000479c3 */ // 将 blockIdx.x 读入 uniform 寄存器,用于后续 IMAD
                                                                                       /* 0x000e220000002500 */ 
        /*0030*/                   LDCU UR5, c[0x0][0x398] ;                           /* 0x00007300ff0577ac */ // 读参数 n到 uniform 寄存器 UR5
                                                                                       /* 0x000e6e0008000800 */ 
        /*0040*/                   LDC R2, c[0x0][0x360] ;                             /* 0x0000d800ff027b82 */ // 读 blockDim.x
                                                                                       /* 0x000e240000000800 */ 
        /*0050*/                   IMAD R2, R2, UR4, R3 ;                              /* 0x0000000402027c24 */ // 计算 idx = blockIdx.x * blockDim.x + threadIdx.x
                                                                                       /* 0x001fca000f8e0203 */
        /*0060*/                   ISETP.GE.AND P0, PT, R2, UR5, PT ;                  /* 0x0000000502007c0c */ // 判断 idx >= n,置位 P0 用于边界检查
                                                                                       /* 0x002fda000bf06270 */
        /*0070*/               @P0 EXIT ;                                              /* 0x000000000000094d */ // 若越界则提前退出线程
                                                                                       /* 0x000fea0003800000 */


/////////////////
// div 除法指令展开
/////////////////
        /*0080*/                   LDC.64 R6, c[0x0][0x388] ;                          /* 0x0000e200ff067b82 */ // 读分母基地址(device pointer)低/高 64 位
                                                                                       /* 0x000e220000000a00 */ 
        /*0090*/                   LDCU.64 UR4, c[0x0][0x358] ;                        /* 0x00006b00ff0477ac */ // 读取全局内存描述符(全局数据句柄)到 uniform 寄存器
                                                                                       /* 0x000e6e0008000a00 */  
        /*00a0*/                   LDC.64 R4, c[0x0][0x380] ;                          /* 0x0000e000ff047b82 */ // 读分子基地址(device pointer)
                                                                                       /* 0x000ea20000000a00 */ 
        /*00b0*/                   IMAD.WIDE R6, R2, 0x4, R6 ;                         /* 0x0000000402067825 */ // 计算 denominator[idx] 的字节地址:base + idx*4
                                                                                       /* 0x001fca00078e0206 */ 
        /*00c0*/                   LDG.E.CONSTANT R3, desc[UR4][R6.64] ;               /* 0x0000000406037981 */ // dnum: 通过描述符从全局内存读取分母值(缓存策略 constant)
                                                                                       /* 0x002ee2000c1e9900 */ 
        /*00d0*/                   IMAD.WIDE R4, R2, 0x4, R4 ;                         /* 0x0000000402047825 */ // 乘累加R4 = R2 * 0x4 + R4,得到 numerator[idx] 的地址
                                                                                       /* 0x004fca00078e0204 */ 
        /*00e0*/                   LDG.E.CONSTANT R0, desc[UR4][R4.64] ;               /* 0x0000000404007981 */ // num: 读取分子值
                                                                                       /* 0x000ea2000c1e9900 */ 

// 【收敛同步点 B0】 用 SFU 单元求解倒数 + 1 次牛顿迭代
        /*00f0*/                   BSSY.RECONVERGENT B0, 0x1c0 ;                       /* 0x000000c000007945 */
                                                                                       /* 0x000fe20003800200 */ 
// 倒数初值 R8: 1/dnum                                                                                     
        /*0100*/                   MUFU.RCP R8, R3 ;                                   /* 0x0000000300087308 */ // 调用 SFU单元, 计算分母 dnum 的倒数近似
                                                                                       /* 0x008e300000001000 */ 
        /*0110*/                   FCHK P0, R0, R3 ;                                   /* 0x0000000300007302 */ // 检查除法特殊情况(如 0、NaN、Inf),更新谓词 P0 (关联 /*0170*/)
                                                                                       /* 0x004e620000000000 */
// 牛顿迭代
        /*0120*/                   FFMA R9, -R3, R8, 1 ;                               /* 0x3f80000003097423 */ // 牛顿迭代第一步:r = 1 - d * r0
                                                                                       /* 0x001fc80000000108 */ 
        /*0130*/                   FFMA R9, R8, R9, R8 ;                               /* 0x0000000908097223 */ // 更新倒数 r = r0 + r0 * r
                                                                                       /* 0x000fc80000000008 */ 
        /*0140*/                   FFMA R8, R0, R9, RZ ;                               /* 0x0000000900087223 */ // 将分子乘改进后的倒数,得到初始商 R8 = RZ + R0 * R9   
                                                                                       /* 0x000fc800000000ff */ // RZ 表示加 0
        /*0150*/                   FFMA R10, -R3, R8, R0 ;                             /* 0x00000008030a7223 */ // 计算残差 = num - dnum * r0
                                                                                       /* 0x000fc80000000100 */
// 牛顿迭代结束,得到 num/dnum:R9                                                                                       
        /*0160*/                   FFMA R9, R9, R10, R8 ;                              /* 0x0000000a09097223 */ // 最终修正后的商输出到 R9
                                                                                       /* 0x000fe20000000008 */ 
// 若无异常 (P0!=0), 方法1正常结束,将商返回(jmp 1b0);否则继续,修正异常                                                                                       
        /*0170*/              @!P0 BRA 0x1b0 ;                                         /* 0x00000000000c8947 */ 
                                                                                       /* 0x002fec0003800000 */ 

//////////////////////////////////
// 快速路径--转入异常修复(慢速路径入口)                                                  
//////////////////////////////////
        /*0180*/                   MOV R4, 0x1a0 ;                                     /* 0x000001a000047802 */ // 为调用特殊处理子程序准备固定参数
                                                                                       /* 0x000fce0000000f00 */
        /*0190*/                   CALL.REL.NOINC 0x200 ;                              /* 0x0000000000187944 */ // 调用 IEEE 754 特殊情况处理例程
                                                                                       /* 0x000fea0003c00000 */
        /*01a0*/                   IMAD.MOV.U32 R9, RZ, RZ, R0 ;                       /* 0x000000ffff097224 */ // 将特殊例程返回的结果放入 R9
                                                                                       /* 0x000fce00078e0000 */

///////////////////////////////////////
// 无异常收尾入口,计算结束,退出线程
///////////////////////////////////////
        /*01b0*/                   BSYNC.RECONVERGENT B0 ;                             /* 0x0000000000007941 */
                                                                                       /* 0x000fea0003800200 */
        /*01c0*/                   LDC.64 R4, c[0x0][0x390] ;                          /* 0x0000e400ff047b82 */ // 读取output输出数组基址
                                                                                       /* 0x000e240000000a00 */ 
        /*01d0*/                   IMAD.WIDE R2, R2, 0x4, R4 ;                         /* 0x0000000402027825 */ // 乘累加R2 = R4 + R2 * 0x4,得到 num[idx] 的地址
                                                                                       /* 0x001fca00078e0204 */
        /*01e0*/                   STG.E desc[UR4][R2.64], R9 ;                        /* 0x0000000902007986 */ // 将最终商写回全局内存
                                                                                       /* 0x000fe2000c101904 */ 
        /*01f0*/                   EXIT ;                                              /* 0x000000000000794d */ // 内核主路径返回,线程结束
                                                                                       /* 0x000fea0003800000 */



//////////////////////////////////////////////////////////// 慢速路径 ////////////////////////////////////////////////////////////
//////////////////////
// IEEE 754 异常处理入口
//////////////////////
        /*0200*/                   SHF.R.U32.HI R6, RZ, 0x17, R3.reuse ;               /* 0x00000017ff067819 */ // 提取分母指数 R6: 提取高位(移位 0x17 位)
                                                                                       /* 0x100fe20000011603 */
// 【收敛同步点 B1】 特殊处理流程的同步点                                                                                 
        /*0210*/                   BSSY.RECONVERGENT B1, 0x860 ;                       /* 0x0000064000017945 */  
                                                                                       /* 0x000fe20003800200 */ 
                                                                                      
//////////////////////
// NaN/Inf/零 的组合判断
//////////////////////                                                                                       
        /*0220*/                   SHF.R.U32.HI R5, RZ, 0x17, R0.reuse ;               /* 0x00000017ff057819 */ // 提取分子指数 R5: 提取高位(移位 0x17 位)
                                                                                       /* 0x100fe20000011600 */
        /*0230*/                   IMAD.MOV.U32 R7, RZ, RZ, R0 ;                       /* 0x000000ffff077224 */ // 复制分子到 R7,用于后续处理
                                                                                       /* 0x000fe200078e0000 */
        /*0240*/                   LOP3.LUT R6, R6, 0xff, RZ, 0xc0, !PT ;              /* 0x000000ff06067812 */ // 保留命中的指数字节,掩掉符号位
                                                                                       /* 0x000fe200078ec0ff */
        /*0250*/                   IMAD.MOV.U32 R8, RZ, RZ, R3 ;                       /* 0x000000ffff087224 */ // 复制分母到 R8
                                                                                       /* 0x000fe200078e0003 */
        /*0260*/                   LOP3.LUT R5, R5, 0xff, RZ, 0xc0, !PT ;              /* 0x000000ff05057812 */ // 同样提取分子指数到 R5
                                                                                       /* 0x000fc600078ec0ff */
        /*0270*/                   VIADD R11, R6, 0xffffffff ;                         /* 0xffffffff060b7836 */ // 分母指数 R6 - 1,用于检测 INF/NaN
                                                                                       /* 0x000fe40000000000 */
        /*0280*/                   VIADD R10, R5, 0xffffffff ;                         /* 0xffffffff050a7836 */ // 分子指数 R5 -1
                                                                                       /* 0x000fc60000000000 */
        /*0290*/                   ISETP.GT.U32.AND P0, PT, R11, 0xfd, PT ;            /* 0x000000fd0b00780c */ // 更新谓词PT:判断分母指数是否 > 253(表示 INF/NaN)
                                                                                       /* 0x000fc80003f04070 */
        /*02a0*/                   ISETP.GT.U32.OR P0, PT, R10, 0xfd, P0 ;             /* 0x000000fd0a00780c */ // 更新谓词P0:将分子指数检查合并
                                                                                       /* 0x000fda0000704470 */ 
        /*02b0*/              @!P0 IMAD.MOV.U32 R9, RZ, RZ, RZ ;                       /* 0x000000ffff098224 */ // 若无 INF/NaN,初始化结果寄存器为 0,后续回退
                                                                                       /* 0x000fe200078e00ff */ 
// 若无 INF/NaN,跳过复杂分支,跳到归一修正流程                                                                                       
        /*02c0*/              @!P0 BRA 0x440 ;                                         /* 0x00000000005c8947 */ 
                                                                                       /* 0x000fec0003800000 */ 
// 检查分子无穷大                                                                                       
        /*02d0*/                   FSETP.GTU.FTZ.AND P0, PT, |R0|, +INF , PT ;         /* 0x7f8000000000780b */ // 检查分子是否为 NaN/Inf,更新 P0
                                                                                       /* 0x000fe40003f1c200 */ //
// 检查分母无穷大                                                                                        
        /*02e0*/                   FSETP.GTU.FTZ.AND P1, PT, |R3|, +INF , PT ;         /* 0x7f8000000300780b */ // 检查分母是否为 NaN/Inf
                                                                                       /* 0x000fc80003f3c200 */ //
        /*02f0*/                   PLOP3.LUT P0, PT, P0, P1, PT, 0xf8, 0x8f ;          /* 0x00000000008f781c */ // 组合 NaN/Inf 标志,识别任何异常
                                                                                       /* 0x000fda0000703f70 */ // 逻辑树配置
// 若检测到 NaN/Inf,跳到0x840处理                                                                                       
        /*0300*/               @P0 BRA 0x840 ;                                         /* 0x00000004004c0947 */
                                                                                       /* 0x000fea0003800000 */
        /*0310*/                   LOP3.LUT P0, RZ, R8, 0x7fffffff, R7, 0xc8, !PT ;    /* 0x7fffffff08ff7812 */ // 检测分子/分母符号与零的组合(0 与 Inf)
                                                                                       /* 0x000fda000780c807 */ 
// 如果是 zero/Inf 组合,跳 0x820;否则继续                                                                                      
        /*0320*/              @!P0 BRA 0x820 ;                                         /* 0x00000004003c8947 */ 
                                                                                       /* 0x000fea0003800000 */
        /*0330*/                   FSETP.NEU.FTZ.AND P2, PT, |R0|.reuse, +INF , PT ;   /* 0x7f8000000000780b */ // 判断分子是否非 Inf
                                                                                       /* 0x040fe40003f5d200 */
        /*0340*/                   FSETP.NEU.FTZ.AND P1, PT, |R3|, +INF , PT ;         /* 0x7f8000000300780b */ // 判断分母是否非 Inf
                                                                                       /* 0x000fe40003f3d200 */ 
        /*0350*/                   FSETP.NEU.FTZ.AND P0, PT, |R0|, +INF , PT ;         /* 0x7f8000000000780b */ // 再次检测分子,用于组合逻辑
                                                                                       /* 0x000fd60003f1d200 */ 
// 双条件分支: 如果【分母是 Inf】 且【分子不是 Inf】,跳 0x820                                                                                      
        /*0360*/              @!P1 BRA !P2, 0x820 ;                                    /* 0x00000004002c9947 */ 
                                                                                       /* 0x000fea0005000000 */
        /*0370*/                   LOP3.LUT P2, RZ, R7, 0x7fffffff, RZ, 0xc0, !PT ;    /* 0x7fffffff07ff7812 */ // 检查分子是否为 0(取绝对值判断)
                                                                                       /* 0x000fc8000784c0ff */
        /*0380*/                   PLOP3.LUT P1, PT, P1, P2, PT, 0x2f, 0xf2 ;          /* 0x0000000000f2781c */ // LUT 组合定义: 合成“分母 Inf 且分子 0”的条件
                                                                                       /* 0x000fda0000f24577 */ 
// 如果分子分母同时0/0 或 Inf/Inf, 跳生成 NaN 的分支(0x800)                                                                                       
        /*0390*/               @P1 BRA 0x800 ;                                         /* 0x0000000400181947 */ 
                                                                                       /* 0x000fea0003800000 */ 
        /*03a0*/                   LOP3.LUT P1, RZ, R8, 0x7fffffff, RZ, 0xc0, !PT ;    /* 0x7fffffff08ff7812 */ // 检查分母是否为 0
                                                                                       /* 0x000fc8000782c0ff */ 
        /*03b0*/                   PLOP3.LUT P0, PT, P0, P1, PT, 0x2f, 0xf2 ;          /* 0x0000000000f2781c */ // 组合“【分子非inf】且【分母为 0】”条件
                                                                                       /* 0x000fda0000702577 */ 
// 分母为 0,跳到生成带符号的无穷结果 (0x7d0)                                                                                       
        /*03c0*/               @P0 BRA 0x7d0 ;                                         /* 0x0000000400000947 */ 
                                                                                       /* 0x000fea0003800000 */ 
        /*03d0*/                   ISETP.GE.AND P0, PT, R10, RZ, PT ;                  /* 0x000000ff0a00720c */ // 判断分子是否为非次正规(指数>=0)
                                                                                       /* 0x000fe40003f06270 */ 
        /*03e0*/                   ISETP.GE.AND P1, PT, R11, RZ, PT ;                  /* 0x000000ff0b00720c */ // 判断分母是否为非次正规
                                                                                       /* 0x000fd60003f26270 */
        /*03f0*/               @P0 IMAD.MOV.U32 R9, RZ, RZ, RZ ;                       /* 0x000000ffff090224 */ // 如果分子已正规化,则重置 R9 为 0 准备后续组合
                                                                                       /* 0x000fe400078e00ff */ 
        /*0400*/              @!P0 FFMA R7, R0, 1.84467440737095516160e+19, RZ ;       /* 0x5f80000000078823 */ // 若分子为次正规,乘以 2^64 进行归一化
                                                                                       /* 0x000fe200000000ff */ // 指明使用 FMA 归一化
        /*0410*/              @!P0 IMAD.MOV.U32 R9, RZ, RZ, -0x40 ;                    /* 0xffffffc0ff098424 */ // 累计指数调整量(-64)
                                                                                       /* 0x000fe400078e00ff */
        /*0420*/              @!P1 FFMA R8, R3, 1.84467440737095516160e+19, RZ ;       /* 0x5f80000003089823 */ // 若分母为次正规,放大归一化
                                                                                       /* 0x000fe400000000ff */ 
        /*0430*/              @!P1 VIADD R9, R9, 0x40 ;                                /* 0x0000004009099836 */ // 分母归一化时抵消前面的 -64,确保指数差正确
                                                                                       /* 0x000fce0000000000 */ 
// IEEE 754 归一化分支
        /*0440*/                   LEA R3, R6, 0xc0800000, 0x17 ;                      /* 0xc080000006037811 */ // 生成分母尾数(补 1)并左移,将指数/符号打包,合成 IEEE 754 形态
                                                                                       /* 0x000fe200078eb8ff */ 
        /*0450*/                   VIADD R5, R5, 0xffffff81 ;                          /* 0xffffff8105057836 */ // 分子指数减去偏置 127
                                                                                       /* 0x000fe20000000000 */

//【收敛同步点 B2】IEEE 754 舍入流程 
        /*0460*/                   BSSY.RECONVERGENT B2, 0x7c0 ;                       /* 0x0000035000027945 */ // 设置另一个 reconvergence,用于舍入流程
                                                                                       /* 0x000fe60003800200 */
        /*0470*/                   IMAD.IADD R3, R8, 0x1, -R3 ;                        /* 0x0000000108037824 */ // 计算归一化后的分母尾数(补偿符号)
                                                                                       /* 0x000fe200078e0a03 */
        /*0480*/                   IADD3 R6, PT, PT, R5.reuse, 0x7f, -R6 ;             /* 0x0000007f05067810 */ // 组合指数差:numExp - denExp + 偏置
                                                                                       /* 0x040fe20007ffe806 */ 
        /*0490*/                   IMAD R0, R5, -0x800000, R7 ;                        /* 0xff80000005007824 */ // 还原分子尾数的隐含 1,并结合符号
                                                                                       /* 0x000fe400078e0207 */ 
        /*04a0*/                   MUFU.RCP R8, R3 ;                                   /* 0x0000000300087308 */ // 用SFU单元,对归一化后的分母尾数取倒数近似
                                                                                       /* 0x000e220000001000 */ 
        /*04b0*/                   FADD.FTZ R11, -R3, -RZ ;                            /* 0x800000ff030b7221 */ // 取反得到 remainder 初值(-denormMant)
                                                                                       /* 0x000fe20000010100 */ // FTZ 表示 flush-to-zero
        /*04c0*/                   IMAD.IADD R6, R6, 0x1, R9 ;                         /* 0x0000000106067824 */ // 在指数差上叠加归一化修正项 R9
                                                                                       /* 0x000fc600078e0209 */
// 牛顿迭代                                                                                       
        /*04d0*/                   FFMA R13, R8, R11, 1 ;                              /* 0x3f800000080d7423 */ // 牛顿迭代第一步
                                                                                       /* 0x001fc8000000000b */
        /*04e0*/                   FFMA R10, R8, R13, R8 ;                             /* 0x0000000d080a7223 */ // 迭代继续,得到更精确的倒数
                                                                                       /* 0x000fc80000000008 */ 
        /*04f0*/                   FFMA R7, R0, R10, RZ ;                              /* 0x0000000a00077223 */ // 计算商尾数(未舍入)
                                                                                       /* 0x000fc800000000ff */ 
        /*0500*/                   FFMA R8, R11, R7, R0 ;                              /* 0x000000070b087223 */ // 计算残差用于舍入
                                                                                       /* 0x000fc80000000000 */ 
        /*0510*/                   FFMA R7, R10, R8, R7 ;                              /* 0x000000080a077223 */ // 二次修正商尾数
                                                                                       /* 0x000fc80000000007 */
        /*0520*/                   FFMA R8, R11, R7, R0 ;                              /* 0x000000070b087223 */ // 再次计算残差,准备粘滞位
                                                                                       /* 0x000fc80000000000 */ 
        /*0530*/                   FFMA R0, R10, R8, R7 ;                              /* 0x000000080a007223 */ // 得到最终未打包的商尾数 R0
                                                                                       /* 0x000fca0000000007 */
        /*0540*/                   SHF.R.U32.HI R3, RZ, 0x17, R0 ;                     /* 0x00000017ff037819 */ // 提取商尾数的高位指数部分
                                                                                       /* 0x000fc80000011600 */
        /*0550*/                   LOP3.LUT R3, R3, 0xff, RZ, 0xc0, !PT ;              /* 0x000000ff03037812 */ // 获取指数,准备重新组合
                                                                                       /* 0x000fca00078ec0ff */
        /*0560*/                   IMAD.IADD R9, R3, 0x1, R6 ;                         /* 0x0000000103097824 */ // 合并指数差,得到最终指数
                                                                                       /* 0x000fc800078e0206 */
        /*0570*/                   VIADD R3, R9, 0xffffffff ;                          /* 0xffffffff09037836 */ // 指数-1,用于溢出判定
                                                                                       /* 0x000fca0000000000 */
        /*0580*/                   ISETP.GE.U32.AND P0, PT, R3, 0xfe, PT ;             /* 0x000000fe0300780c */ // 检测指数是否 >= 254(溢出)
                                                                                       /* 0x000fda0003f06070 */
// 指数范围正常,则跳至指数打包 0x7a0                                                                                       
        /*0590*/              @!P0 BRA 0x7a0 ;                                         /* 0x0000000000808947 */ 
                                                                                       /* 0x000fea0003800000 */
        /*05a0*/                   ISETP.GT.AND P0, PT, R9, 0xfe, PT ;                 /* 0x000000fe0900780c */ // 判断是否真正溢出
                                                                                       /* 0x000fda0003f04270 */
// 真溢出,则跳到生成 Inf (0x770)                                                                                       
        /*05b0*/               @P0 BRA 0x770 ;                                         /* 0x00000000006c0947 */ 
                                                                                       /* 0x000fea0003800000 */ 
        /*05c0*/                   ISETP.GE.AND P0, PT, R9, 0x1, PT ;                  /* 0x000000010900780c */ // 检查是否仍在正规数范围内
                                                                                       /* 0x000fda0003f06270 */ 
// 正规数,跳到尾数打包 0x7b0                                                                                       
        /*05d0*/               @P0 BRA 0x7b0 ;                                         /* 0x0000000000740947 */ 
                                                                                       /* 0x000fea0003800000 */ 
        /*05e0*/                   ISETP.GE.AND P0, PT, R9, -0x18, PT ;                /* 0xffffffe80900780c */ // 判断是否可以通过移位表示为次正规数
                                                                                       /* 0x000fe40003f06270 */
        /*05f0*/                   LOP3.LUT R0, R0, 0x80000000, RZ, 0xc0, !PT ;        /* 0x8000000000007812 */ // 提取商的符号位
                                                                                       /* 0x000fd600078ec0ff */
// 若无法表示次正规(即下溢到 0),跳到尾数打包 0x7b0                                                                                         
        /*0600*/              @!P0 BRA 0x7b0 ;                                         /* 0x0000000000688947 */ 
                                                                                       /* 0x000fea0003800000 */
        /*0610*/                   FFMA.RZ R3, R10.reuse, R8.reuse, R7.reuse ;         /* 0x000000080a037223 */ // 计算舍入残差,判断粘滞位
                                                                                       /* 0x1c0fe2000000c007 */ 
        /*0620*/                   FFMA.RM R6, R10.reuse, R8.reuse, R7.reuse ;         /* 0x000000080a067223 */ // 取向下舍入结果
                                                                                       /* 0x1c0fe20000004007 */
        /*0630*/                   ISETP.NE.AND P2, PT, R9.reuse, RZ, PT ;             /* 0x000000ff0900720c */ // 判断分子指数是否非零,用于判断是否正规数
                                                                                       /* 0x040fe40003f45270 */ 
        /*0640*/                   ISETP.NE.AND P1, PT, R9, RZ, PT ;                   /* 0x000000ff0900720c */ // 分母
                                                                                       /* 0x000fe40003f25270 */ 
        /*0650*/                   LOP3.LUT R5, R3, 0x7fffff, RZ, 0xc0, !PT ;          /* 0x007fffff03057812 */ // 取出尾数低 23 位
                                                                                       /* 0x000fe200078ec0ff */ 
        /*0660*/                   FFMA.RP R3, R10, R8, R7 ;                           /* 0x000000080a037223 */ // 向上舍入版本
                                                                                       /* 0x000fe20000008007 */
        /*0670*/                   VIADD R8, R9, 0x20 ;                                /* 0x0000002009087836 */ // 计算次正规右移量(24 + 指数)
                                                                                       /* 0x000fe40000000000 */ 
        /*0680*/                   LOP3.LUT R5, R5, 0x800000, RZ, 0xfc, !PT ;          /* 0x0080000005057812 */ // 补回隐含的最高位 1
                                                                                       /* 0x000fe200078efcff */ 
        /*0690*/                   IMAD.MOV R7, RZ, RZ, -R9 ;                          /* 0x000000ffff077224 */ // 计算负指数,用于移位
                                                                                       /* 0x000fe200078e0a09 */ 
        /*06a0*/                   FSETP.NEU.FTZ.AND P0, PT, R3, R6, PT ;              /* 0x000000060300720b */ // 比较不同舍入结果,判断是否出现 tie
                                                                                       /* 0x000fc40003f1d000 */ 
        /*06b0*/                   SHF.L.U32 R8, R5, R8, RZ ;                          /* 0x0000000805087219 */ // 将尾数左移(构造次正规数位布)
                                                                                       /* 0x000fe400000006ff */
        /*06c0*/                   SEL R6, R7, RZ, P2 ;                                /* 0x000000ff07067207 */ // 若仍为正规数,则保持移位量,否则置 0
                                                                                       /* 0x000fe40001000000 */
        /*06d0*/                   ISETP.NE.AND P1, PT, R8, RZ, P1 ;                   /* 0x000000ff0800720c */ // 判断移位后是否仍有非零位(粘滞位)
                                                                                       /* 0x000fe40000f25270 */ 
        /*06e0*/                   SHF.R.U32.HI R6, RZ, R6, R5 ;                       /* 0x00000006ff067219 */ // 根据移位量构造右移掩码
                                                                                       /* 0x000fe40000011605 */
        /*06f0*/                   PLOP3.LUT P0, PT, P0, P1, PT, 0xf8, 0x8f ;          /* 0x00000000008f781c */ // 综合 tie 和粘滞位条件,决定是否进位舍入
                                                                                       /* 0x000fe40000703f70 *
        /*0700*/                   SHF.R.U32.HI R8, RZ, 0x1, R6 ;                      /* 0x00000001ff087819 */ // 生成舍入增量(右移一位)
                                                                                       /* 0x000fc40000011606 */ 
        /*0710*/                   SEL R3, RZ, 0x1, !P0 ;                              /* 0x00000001ff037807 */ // 若需要进位,则选择 1,否则 0
                                                                                       /* 0x000fc80004000000 */ 
        /*0720*/                   LOP3.LUT R3, R3, 0x1, R8, 0xf8, !PT ;               /* 0x0000000103037812 */ // 将舍入增量合并进尾数
                                                                                       /* 0x000fc800078ef808 */
        /*0730*/                   LOP3.LUT R3, R3, R6, RZ, 0xc0, !PT ;                /* 0x0000000603037212 */ // 清理多余位,仅保留有效部分
                                                                                       /* 0x000fca00078ec0ff */ 
        /*0740*/                   IMAD.IADD R3, R8, 0x1, R3 ;                         /* 0x0000000108037824 */ // 将进位加到尾数中
                                                                                       /* 0x000fca00078e0203 */ 
        /*0750*/                   LOP3.LUT R0, R3, R0, RZ, 0xfc, !PT ;                /* 0x0000000003007212 */ // 将符号位与尾数合并
                                                                                       /* 0x000fe200078efcff */ 
// 跳到尾数打包 0x7b0                                                                                       
        /*0760*/                   BRA 0x7b0 ;                                         /* 0x0000000000107947 */
                                                                                       /* 0x000fec0003800000 */ 

////////////
// 溢出到 inf
////////////
        /*0770*/                   LOP3.LUT R0, R0, 0x80000000, RZ, 0xc0, !PT ;        /* 0x8000000000007812 */ // 溢出路径:保留符号位
                                                                                       /* 0x000fc800078ec0ff */ 
        /*0780*/                   LOP3.LUT R0, R0, 0x7f800000, RZ, 0xfc, !PT ;        /* 0x7f80000000007812 */ // 将指数字段设为全 1,得到 ±Inf
                                                                                       /* 0x000fe200078efcff */ 
// 跳到尾数打包 0x7b0                                                                                       
        /*0790*/                   BRA 0x7b0 ;                                         /* 0x0000000000047947 */ 
                                                                                       /* 0x000fec0003800000 */ 

/////////
// 指数打包
/////////
        /*07a0*/                   IMAD R0, R6, 0x800000, R0 ;                         /* 0x0080000006007824 */ // 正规数路径:将指数偏置并与尾数组合
                                                                                       /* 0x000fce00078e0200 */ 
        /*07b0*/                   BSYNC.RECONVERGENT B2 ;                             /* 0x0000000000027941 */ // 在B2舍入流程结束处同步
                                                                                       /* 0x000fea0003800200 */
// 跳回主流程的收尾部分
        /*07c0*/                   BRA 0x850 ;                                         /* 0x0000000000207947 */ 
                                                                                       /* 0x000fea0003800000 */

///////////////////
// 生成带符号的无穷结果
///////////////////
        /*07d0*/                   LOP3.LUT R0, R8, 0x80000000, R7, 0x48, !PT ;        /* 0x8000000008007812 */ // 分母为 0 时:根据输入符号构造 ±Inf
                                                                                       /* 0x000fc800078e4807 */
        /*07e0*/                   LOP3.LUT R0, R0, 0x7f800000, RZ, 0xfc, !PT ;        /* 0x7f80000000007812 */ // 设置指数为全 1
                                                                                       /* 0x000fe200078efcff */
        /*07f0*/                   BRA 0x850 ;                                         /* 0x0000000000147947 */ // 跳往返回准备
                                                                                       /* 0x000fec0003800000 */
// 0/0 或 Inf/Inf 情况:组合符号位                                                                                       
        /*0800*/                   LOP3.LUT R0, R8, 0x80000000, R7, 0x48, !PT ;        /* 0x8000000008007812 */ 
                                                                                       /* 0x000fe200078e4807 */
        /*0810*/                   BRA 0x850 ;                                         /* 0x00000000000c7947 */ // 跳转到共享尾部
                                                                                       /* 0x000fec0003800000 */
        /*0820*/                   MUFU.RSQ R0, -QNAN  ;                               /* 0xffc0000000007908 */ // 通过对 QNaN 取倒数平方根生成 NaN
                                                                                       /* 0x000e220000001400 */ 
// 跳回统一收尾                                                                                       
        /*0830*/                   BRA 0x850 ;                                         /* 0x0000000000047947 */ 
                                                                                       /* 0x000fea0003800000 */
        /*0840*/                   FADD.FTZ R0, R0, R3 ;                               /* 0x0000000300007221 */ // 将特殊情况构造的值与保留尾数叠加(保持符号)
                                                                                       /* 0x000fce0000010000 *

/////////
// 程序结束
/////////
        /*0850*/                   BSYNC.RECONVERGENT B1 ;                             /* 0x0000000000017941 */ // 子程序尾部 reconvergence,与主流程同步
                                                                                       /* 0x000fea0003800200 */
        /*0860*/                   IMAD.MOV.U32 R5, RZ, RZ, 0x0 ;                      /* 0x00000000ff057424 */ // 将返回值寄存器清零
                                                                                       /* 0x000fc800078e00ff */
        /*0870*/                   RET.REL.NODEC R4 0x0 ;                              /* 0xfffffff404e07950 */ // 返回主程序
                                                                                       /* 0x001fea0003c3ffff */
        /*0880*/                   BRA 0x880;                                          /* 0xfffffffc00fc7947 */ 
                                                                                       /* 0x000fc0000383ffff */ 
        /*0890*/                   NOP;                                                /* 0x0000000000007918 */ 
                                                                                       /* 0x000fc00000000000 */ 
        /*08a0*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*08b0*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*08c0*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*08d0*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*08e0*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*08f0*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*0900*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*0910*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*0920*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*0930*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*0940*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*0950*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*0960*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
        /*0970*/                   NOP;                                                /* 0x0000000000007918 */
                                                                                       /* 0x000fc00000000000 */
		..........

算法流程

整体分为“快路径”和“慢路径”两段:先尝试快路径获取结果,如果检测到任一输入出现 0 / Inf / NaN 或归一化需求,则跳入慢路径继续处理。

  • 快路径(主流程)

    • 计算 idx,越界直接 EXIT。
    • 依次加载分母 R3、分子 R0,并建立收敛点 B0
    • 通过 SFU 指令 MUFU.RCP 求分母倒数,随后用一次牛顿迭代(若干条 FFMA)得到近似商 R9
    • FCHK 判定是否存在特殊情况;若谓词 P0 == 0,说明没有异常,直接写回结果并退出。
  • 慢路径(子程序)

    • 入口在 CALL 0x200,首先提取指数/符号并组合判断 NaN、Inf、零等情形:
      • 命中 NaN/Inf 组合直接生成对应的 NaN 或 ±Inf。
      • 分母为 0 按符号生成 ±Inf。
      • 其他情形继续后续处理。
    • 归一化阶段:若分子或分母是次正规数,则通过乘以 2^64 等手段放大尾数,同时记录指数修正量;正规数直接跳过。
    • 规范求值阶段(收敛点 B2 内):
      • 再次执行 SFU 倒数与牛顿迭代,得到未舍入的商尾数。
      • 完整的舍入判定:利用 FFMA.RZ/RM/RPFSETPPLOP3 等指令计算 tie/sticky,并决定是否进位。
      • 指数与范围检查:
        • 溢出 → 生成 ±Inf。
        • 正规范围内 → 通过 IMAD 打包指数域。
        • 可表示为次正规 → 构造移位与粘滞位后打包;无法表示则视为 0。
    • 子程序尾部使用 BSYNC B1 收敛并返回主流程,结果存放在 R0,随后复制到 R9,统一走写回路径。

无论来自快路径还是慢路径,最终都会在 B0 收敛后计算输出地址并使用 STG 写回。详细控制流与分支请参考 docs/div_fp32_division_flowchart.md 中的流程图。

算法具体流程移步:div_fp32.sass

主要涉及的 NV instructions:

  • VIADD
  • FFMA(浮点融合乘加FMA,包含RZ/RM/RP各种舍入模式)
  • ISETP
  • LOP3
  • PLOP3

给作者倒杯卡布奇诺 ~
Albresky 支付宝支付宝
Albresky 微信微信