4. 数据表示与运算:二进制、十六进制、原码反码补码、定点数与浮点数

各位同学,今天我们来聊聊CPU设计中最基础、也最绕不开的话题——数据表示与运算。

说实话,我当年刚入行时,觉得这些东西太简单了。不就是0和1嘛,谁不会啊?直到有一次,我在一个定点数运算的模块里,因为符号位扩展没处理好,导致整个芯片的浮点协处理器跑出来的结果全是错的。嗯,从那以后,我再也不敢小看这些基础概念了。

4.1 二进制与十六进制:CPU的母语

CPU不认识十进制,它只认0和1。二进制就是它的母语。

但二进制有个问题——太长了。比如十进制数255,二进制是11111111,写起来费劲,读起来更费劲。所以工程师们发明了十六进制,作为二进制的“缩写”。

我个人习惯,在写RTL代码时,所有常量都用十六进制表示。为什么?因为一个十六进制位对应四个二进制位,一眼就能看出位宽和数值。

核心对应关系:

  • 1位十六进制 = 4位二进制
  • 2位十六进制 = 8位二进制(1字节)
  • 4位十六进制 = 16位二进制(半字)
  • 8位十六进制 = 32位二进制(字)

举个例子,0xA5在二进制里是10100101。你想想看,写0xA5多简洁,写10100101多痛苦。

// 二进制 vs 十六进制 对比
二进制: 1010 0101 1111 0000
十六进制: 0xA5F0
十进制: 42480

4.2 原码、反码、补码:有符号数的三种面孔

有符号数怎么表示?CPU里没有负号,只有0和1。所以我们需要一种编码方式,让二进制也能表示负数。

这就引出了原码、反码、补码。说白了,它们是同一个数值的三种不同二进制表达方式。

4.2.1 原码

原码最简单:最高位是符号位(0正1负),其余位是数值的绝对值。

+5 的原码(4位): 0101
-5 的原码(4位): 1101

但原码有个致命问题——它有两个0:0000(+0)和1000(-0)。这在运算中会出大麻烦。我在项目中遇到过,用原码做加法器,结果+0和-0相加,居然不等于0。这显然不行。

4.2.2 反码

反码是为了解决原码的减法问题而生的。正数的反码就是原码,负数的反码是符号位不变,其余位取反。

+5 的反码(4位): 0101
-5 的反码(4位): 1010

反码仍然有两个0:0000(+0)和1111(-0)。而且反码做加法时,需要额外的“循环进位”处理,硬件实现起来比较麻烦。

4.2.3 补码

补码是现代CPU的标配。正数的补码就是原码,负数的补码是反码加1。

+5 的补码(4位): 0101
-5 的补码(4位): 1011

为什么补码这么好用?

  • 只有一个0:0000
  • 减法可以统一用加法实现:A - B = A + (-B的补码)
  • 符号位和数值位可以统一处理,不需要特殊逻辑

我曾经在做一个16位ALU时,一开始用了原码做减法,结果逻辑复杂得要命。后来改成补码,代码量直接砍了一半。你想想看,这就是补码的魅力。

4.3 定点数与浮点数:小数点的两种归宿

小数怎么表示?CPU里没有小数点,只有0和1。所以我们需要约定小数点的位置。

4.3.1 定点数

定点数,就是小数点的位置是固定的。比如我们约定一个16位数,高8位是整数部分,低8位是小数部分。

// 定点数示例(Q8.8格式)
// 整数部分8位,小数部分8位
// 数值 = 整数部分 + 小数部分 / 256

// 表示 12.5
整数部分: 00001100 (12)
小数部分: 10000000 (128/256 = 0.5)
合起来: 00001100 10000000 = 0x0C80

定点数的好处是运算简单,硬件开销小。但缺点也很明显——动态范围有限。你想表示很大的数,就得牺牲小数精度;想要高精度,就得牺牲范围。

避坑指南:我曾经在一个音频处理芯片里用了定点数,结果发现信号动态范围太大,定点数根本不够用。最后不得不改成浮点数,但改设计已经来不及了,只能硬着头皮加了一个自动增益控制模块。所以,选定点数还是浮点数,一定要提前评估好动态范围。

4.3.2 浮点数

浮点数,就是小数点的位置可以“浮动”。它用科学计数法来表示数值:

数值 = (-1)^符号 × 尾数 × 2^指数

IEEE 754标准定义了两种常用格式:

格式 总位数 符号位 指数位 尾数位 精度
单精度(float) 32 1 8 23 约7位十进制
双精度(double) 64 1 11 52 约16位十进制
// 单精度浮点数示例:表示 12.5
// 12.5 = 1100.1(二进制) = 1.1001 × 2^3
// 符号位: 0
// 指数: 3 + 127(偏移量) = 130 = 10000010
// 尾数: 10010000000000000000000 (去掉整数部分的1)
// 合起来: 0 10000010 10010000000000000000000 = 0x41480000

浮点数的好处是动态范围大,精度可调。但坏处是运算复杂,硬件开销大。一个浮点加法器,比定点加法器复杂10倍以上。

4.4 知识体系总览

下面这张图,是我自己总结的数据表示与运算的知识体系。你看一遍,就能把整个章节串起来。

数据表示与运算知识体系 数据表示 数值类型 编码方式 运算单元 定点数 浮点数 原码 反码 补码 加法器 乘法器 Q格式(Qm.n) IEEE 754标准 符号扩展 溢出判断 进位链 Booth算法 核心思想:用有限的二进制位,表示无限的数值世界 精度与范围的权衡,是数据表示设计的永恒主题

这张图把数据表示分成了三大块:数值类型(定点/浮点)、编码方式(原码/反码/补码)、运算单元(加法器/乘法器)。这三块是相互关联的——你选定了数值类型和编码方式,就决定了运算单元该怎么设计。

我的建议:初学者先别急着搞浮点数。先把定点数和补码吃透,把加法器、乘法器的RTL代码写熟练了,再去看浮点数。浮点数的坑太多了,我当年踩过的坑,够写一本书。

好了,这一章的内容就到这里。数据表示是CPU设计的基石,你把这些概念搞清楚了,后面学指令集、流水线、Cache都会轻松很多。


公众号:蓝海资料掘金营,微信deep3321