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 知识体系总览
下面这张图,是我自己总结的数据表示与运算的知识体系。你看一遍,就能把整个章节串起来。
这张图把数据表示分成了三大块:数值类型(定点/浮点)、编码方式(原码/反码/补码)、运算单元(加法器/乘法器)。这三块是相互关联的——你选定了数值类型和编码方式,就决定了运算单元该怎么设计。
我的建议:初学者先别急着搞浮点数。先把定点数和补码吃透,把加法器、乘法器的RTL代码写熟练了,再去看浮点数。浮点数的坑太多了,我当年踩过的坑,够写一本书。
好了,这一章的内容就到这里。数据表示是CPU设计的基石,你把这些概念搞清楚了,后面学指令集、流水线、Cache都会轻松很多。
公众号:蓝海资料掘金营,微信deep3321