数字系统基础回顾:二进制与定点数、补码运算、加法器与乘法器基础、流水线概念

各位同学,咱们今天聊点基础但极其重要的东西。做深度学习加速器,说白了就是在跟数字打交道。你想想看,神经网络里那些权重、激活值、梯度,最终都要变成二进制在芯片里跑。这部分基础不牢,后面设计矩阵乘法单元时会很痛苦。我当年刚入行时就吃过这个亏,所以今天咱们好好捋一遍。

二进制与定点数表示

数字电路里只有0和1,这个大家都知道。但怎么用0和1表示小数?这就引出了定点数。

定点数,顾名思义,小数点位置是固定的。比如我们用8位表示一个数,约定高4位是整数部分,低4位是小数部分。那这个数的格式就是Q4.4。我习惯用Qm.n表示法,m是整数位数,n是小数位数。

定点数格式示例(Q4.4)

bit[7:4] → 整数部分(4位)

bit[3:0] → 小数部分(4位)

数值 = 整数部分 + 小数部分/16

举个例子:二进制 0011 1010,整数部分0011=3,小数部分1010=10/16=0.625,所以这个数就是3.625。

我在项目中遇到过一个问题:两个定点数相乘,结果位数会翻倍。比如Q4.4 × Q4.4,结果应该是Q8.8。很多人会忽略这个,直接截位,导致精度损失。嗯,这里要注意,截位前一定要做饱和或舍入处理。

补码运算

为什么用补码?说白了就是为了让加减法用同一套电路。你想想看,如果正数和负数用不同方式表示,那加法器还得区分符号,多麻烦。

补码的定义很简单:正数的补码就是它本身,负数的补码是它绝对值的反码加1。

快速求补码的小技巧

从最低位开始,找到第一个1,这个1保持不变,它左边的所有位取反。

比如:-5的二进制是1011(4位),从右往左找第一个1在bit0,bit0不变,bit1~bit3取反,得到1011。

补码运算有个重要特性:溢出判断。我曾经在某个项目中,因为没处理好溢出,导致整个加速器算出来的结果全是错的。排查了两天才发现是加法器溢出标志没接对。

判断溢出的方法很简单:两个正数相加得负数,或者两个负数相加得正数,那就是溢出了。用公式表示就是:

overflow = (A[MSB] & B[MSB] & ~S[MSB]) | (~A[MSB] & ~B[MSB] & S[MSB])

其中A[MSB]和B[MSB]是加数的符号位,S[MSB]是结果的符号位。

加法器基础

加法器是数字系统的核心。从最简单的半加器到复杂的超前进位加法器,每种都有它的用武之地。

加法器类型 面积 延迟 适用场景
行波进位加法器(RCA) 大(O(n)) 低频、面积敏感
超前进位加法器(CLA) 小(O(log n)) 高频、性能敏感
进位选择加法器(CSA) 折中方案

我个人习惯在深度学习加速器里用超前进位加法器。为什么?因为矩阵乘法单元里加法操作特别多,而且对延迟要求高。行波进位加法器虽然面积小,但延迟太大,会拖慢整个流水线。

注意:超前进位加法器的面积会随着位宽增加而急剧增大。64位以上的加法器,建议拆成多个16位或32位的子加法器,再用进位链连接。

乘法器基础

乘法器是矩阵乘法单元的核心。一个矩阵乘法单元里,乘法器的数量可能成百上千。所以乘法器的设计直接影响整个加速器的性能和面积。

最基本的乘法器是阵列乘法器,它把乘法拆成移位和加法。比如两个4位数相乘,需要4个部分积,然后加起来。

        1011  (11)
      × 1101  (13)
      --------
        1011   (部分积0)
       0000    (部分积1,左移1位)
      1011     (部分积2,左移2位)
     1011      (部分积3,左移3位)
     --------
    10001111  (143)

但阵列乘法器面积大、延迟高。现代加速器里常用的是华莱士树(Wallace Tree)乘法器。它用进位保存加法器(CSA)把多个部分积压缩成两个数,最后再用一个加法器求和。

我记得有一次设计一个8×8的矩阵乘法单元,用了华莱士树乘法器,面积比阵列乘法器小了30%,延迟也降低了40%。当然,布线会复杂一些,但值得。

流水线概念

流水线,说白了就是「把一个大任务拆成多个小任务,每个小任务用不同的硬件同时做」。你想想看,洗衣服、烘干、叠衣服,如果只有一个人,得一件一件来。但如果三个人流水线作业,效率就高多了。

在数字电路里,流水线就是在组合逻辑路径中插入寄存器,把长路径切成短路径。这样时钟频率就能提上去。

流水线的三个关键参数

  • 流水级数:切成了几段。级数越多,频率越高,但延迟也越大。
  • 吞吐率:每时钟周期能处理多少个数据。流水线不影响吞吐率(理想情况下)。
  • 延迟:一个数据从输入到输出需要多少个时钟周期。

我建议在设计矩阵乘法单元时,把乘法器和加法器分别流水化。比如乘法器用3级流水,加法器用2级流水。这样整体频率能跑得很高。

但要注意流水线冒险。数据冒险是最常见的:上一条指令的结果还没出来,下一条指令就要用。解决办法是插入气泡(stall)或者做转发(forwarding)。

避坑指南:我曾经在设计中忽略了写后读(RAW)冒险,结果仿真时数据一直对不上。后来加了转发逻辑才解决。所以做流水线设计时,一定要先画好数据流图,标清楚每个寄存器的依赖关系。

还有一个容易忽略的点:流水线的建立时间。刚上电时,流水线里全是无效数据,需要等几个时钟周期才能输出正确结果。这在做控制逻辑时要考虑到。

好了,今天的基础回顾就到这里。这些内容虽然基础,但都是后面设计矩阵乘法单元的基石。下一章咱们开始讲矩阵乘法单元的具体架构,到时候会用到今天讲的所有知识点。