数制与编码:二进制、补码、格雷码与奇偶校验,数字世界的第一块砖

数制与编码:二进制、补码、格雷码与奇偶校验,数字世界的第一块砖

数制与编码:二进制、补码、格雷码与奇偶校验 ​更新时间:2026-09-10。本文是「集成电路大类 · 芯片设计」**入门层(beginner)**数字逻辑基础的第一篇正文。建议先读 01 数字、模拟与混合信号,再进入本篇。

本文要回答的问题 ​为什么数字电路偏偏用二进制,而不是十进制?二/八/十/十六进制之间怎么快速心算互换?Verilog 里 8'd255、8'hFF、8'b1111_1111 是什么关系?负数为什么用补码而不是直接把最高位当符号?原码/反码/补码差在哪?BCD、格雷码各为什么场景而生?格雷码为什么是异步 FIFO 跨时钟域的关键?奇偶校验怎么用 1 个比特"抓 1 位错"?它为什么抓不了 2 位错?一、为什么偏偏是二进制 ​数字电路不直接表示"数的大小",而是用电平高低区分状态。选几个状态最可靠?答案是两个:

晶体管本质是开关,只有"导通 / 截止"两态;用一对互补开关(CMOS)输出要么被拉到电源、要么被拉到地,天然对应 1/0。两态之间留有巨大的噪声容限:只要干扰没把高电平拉过判决阈值,就不会误判。若硬做十级电平表示十进制,相邻电平间隔很小,电源波动和串扰极易把 7 读成 8。二值逻辑恰好与布尔代数(真/假)同构,可以用与或非门直接做算术与判断。所以"用二进制"不是历史偶然,而是在有噪声的物理世界里,两态最容易可靠区分。代价是人读一长串 0/1 很费劲,于是工程上用十六进制当二进制的"速记"。

约定:本文用下标区分数制,如 1010₂ = 10₁₀ = A₁₆。

二、进制转换 ​按权展开(任意进制 → 十进制)。每一位的权是"基数的位次方":

1011₂ = 1·2³ + 0·2² + 1·2¹ + 1·2⁰ = 8 + 0 + 2 + 1 = 11₁₀

2AF₁₆ = 2·16² + 10·16¹ + 15·16⁰ = 512 + 160 + 15 = 687₁₀应当背熟 2 的幂:1,2,4,8,16,32,64,128,256,512,1024…,这是后面地址空间、位宽、FIFO 深度计算的口算基础(见 FIFO 深度计算)。

除基取余(十进制 → 目标进制):反复除以基数,余数倒着读。把 13 转二进制:13÷2=6 余1,6÷2=3 余0,3÷2=1 余1,1÷2=0 余1,余数倒读 1101₂。

二 ↔ 十六(最常用):1 位十六进制恰好对应 4 位二进制(因为 16=2⁴),按"半字节"直接查表,无需计算:

十六二进制十六二进制0000081000100019100120010A101030011B101140100C110050101D110160110E111070111F11111110_1010₂ = EA₁₆,反过来 0x3F = 0011_1111₂。八进制则是 1 位对 3 位二进制(8=2³),如今主要残留在 Unix 文件权限里,硬件文档已基本被十六进制取代。看波形、读寄存器表、核对地址解码时,十六进制是默认语言(见 怎么读芯片手册、寄存器表与地址解码)。

Verilog 字面量格式为 位宽 ' 进制 数值,下划线仅作分隔、被忽略:

verilog8'd255 // 8 位宽、十进制 255 = 1111_1111

8'hFF // 8 位宽、十六进制 FF = 1111_1111(同一个数!)

8'b1111_1111 // 8 位宽、直接给二进制

4'b1010 // = 十进制 10

16'h0A0F // 16 位,地址/数据常这样写省略位宽(如 'hFF)会按仿真器默认整型宽度处理,可综合代码里永远写清位宽,避免位宽不匹配的隐式补零/截断。

三、有符号整数:为什么用补码 ​n 位能表示 2ⁿ 个位模式。无符号数范围是 0 ~ 2ⁿ−1。要表示负数,最直觉的做法是把最高位当符号位(0 正 1 负),这叫原码。但原码有两个硬伤:

零有两种表示:+0 = 0000、−0 = 1000,浪费一个编码,且判等要特判。加减法电路不统一:1 + (−1) 不能直接按位加,CPU 得先判断符号、再决定加还是减,硬件复杂。反码规定负数为"正数按位取反",仍有 ±0 两个零。最终胜出的是补码(two's complement):负数 = 对应正数"按位取反再加 1"。以 4 位为例:

位模式无符号补码01117+7(最大正数)01106+6000000(唯一)111115−1111014−210008−8(最小负数)补码的精妙之处:

零唯一(0000),范围是 −2ⁿ⁻¹ ~ +2ⁿ⁻¹−1,比原码多表示一个最小负数。化减为加:3 + (−5) 直接把 0011 和 −5=1011 按普通二进制相加:0011+1011=1110,正是 −2。减法器因此可以复用加法器(见 加法器与算术电路)。最高位的权是负的:补码数的值 = −bₙ₋₁·2ⁿ⁻¹ + Σ bᵢ·2ⁱ。1111 = −8+4+2+1 = −1,一眼能心算。符号扩展:把窄补码数填进更宽的总线(如 4 位 1110→8 位),要在高位复制符号位:1111_1110,值仍是 −2;而不是简单补 0(那会错成 +254)。Verilog 里有符号数用 $signed/signed 关键字,无符号连线手动扩展时务必复制最高位。这也是混用有符号/无符号比较时最常见的 bug 来源。

四、BCD 与格雷码 ​**BCD(Binary-Coded Decimal,8421 码)**用 4 位二进制表示一个十进制数位,但只用 0000~1001 表示 0~9,1010~1111 是非法码。例如 59₁₀ = 0101_1001_BCD。它在"必须逐位显示十进制"的场景(数码管时钟、电子表、金融金额)有用,因为每一位可直接送显示译码;代价是浪费 6 个编码、运算需十进制调整(满 6 进位修正),算术密度低。

**格雷码(Gray code,反射码)**的核心性质:相邻两个码字之间只差 1 个比特。3 位格雷码顺序为:

000 → 001 → 011 → 010 → 110 → 111 → 101 → 100 →(回到 000)

1位变 1位变 1位变 ... 全程每次只翻 1 位构造方法是"反射":n 位格雷码 = n−1 位序列前补 0,再把 n−1 位序列逆序、前补 1。二进制转格雷码有现成公式:G = B XOR (B >> 1)。

这个性质为什么是硬件的宝贝?普通二进制计数如 011→100 有 3 位同时翻转,由于各位物理翻转时刻不可能完全一致,中间会瞬间经过 010、110 等错误值——异步采样时可能采到任何一个中间态。而格雷码每次只变 1 位,采样方最多采到"旧值"或"新值",绝不会采到错误的第三值。这正是异步 FIFO 读写指针跨时钟域传递必须先转格雷码、再用两级触发器同步的根本原因,详见 异步 FIFO 与 Gray 码指针。

五、字符与校验:ASCII 与奇偶校验 ​ASCII 用 7 位(存进 1 字节)给英文字母、数字、符号和控制符编号,如 'A'=65=0x41、'a'=97、'0'=48、换行 \n=10。记住这几个锚点,看 UART/调试串口输出时能直接心算。中文等多字节字符则用 UTF-8 等编码(与 ASCII 低 128 兼容)。

**奇偶校验(parity)**在数据后附加 1 个校验位,使整串里 1 的个数恒为奇数(奇校验)或偶数(偶校验):

数据 1011 有 3 个 1;用偶校验则校验位=1(凑成 4 个 1),用奇校验则校验位=0。传输中若翻转 1 位,1 的个数奇偶性改变,接收方立刻发现错误。局限:翻转 2 位时奇偶性不变,检不出来;且它只能报错、不能定位/纠正。它属于最简单的"检错码",更强的 CRC(循环冗余校验,本质是 LFSR 多项式除法)能检多位错、广泛用于总线与存储,见 寄存器、计数器与移位寄存器 中的 LFSR。六、补码运算的边界:溢出到底是哪一位说了算 ​很多人误以为"加法结果的最高位多出来一个进位(carry)"就是溢出,这是错的。进位(carry)和溢出(overflow)是两件不同的事,硬件用不同的标志位记录。

进位/借位(C/V 无关):无符号数相加超出位宽时丢掉的那个 1。例如 4 位 1111(15) + 0001(1) = 1_0000(16),结果回卷成 0、进位标志 C=1。对无符号运算,这个 1 表示"结果超过了 2ⁿ−1";对有符号运算,这个 1 没有意义、应忽略。溢出(overflow,常记 V 或 O):只对有符号补码有意义,表示结果超出了能表示的范围。判定规则只看符号位:两个同号数相加,结果却变成异号,就一定溢出了;两个异号数相加永远不会溢出(一正一负,绝对值只会抵消,不可能越界)。更省电路的等价判据是看最高数值位向符号位的进位与符号位向外的进位是否一致:两者不一致即溢出(V = Cₙ XOR Cₙ₋₁)。以 4 位(范围 −8~+7)举例:

正+正变负(溢出) 负+负变正(溢出)

0101 (+5) 1011 (-5)

+ 0011 (+3) + 1001 (-7)

-------- --------

1000 (-8) ✗ 得 -8! 0100 (+4) ✗ 得 +4!

符号位由 0 变 1,V=1 符号位由 1 变 0,V=1

正+负,永不溢出(无论有没有进位)

0111 (+7)

+ 1001 (-7)

--------

0000 (+0) V=0(最左的进位 1 被自然丢掉)这也是为什么 ALU 要同时给 CPU 提供进位标志(服务无符号比较 bcc/bcs)和溢出标志(服务有符号比较 jlt/jge):同一位模式,按无符号和按补码解释,"错没错"的答案可以完全不同。Verilog 里做有符号运算后判断溢出,规范写法是让结果多取 1 位、比较符号位,而不是去抓被截断的进位。

**饱和(saturation)**是工程上处理溢出的另一种思路:发现越界就把结果"夹"在最大值/最小值,而不是回卷。像素处理、音频削波、DSP 累加常用饱和,因为"亮到顶 255"比"255+1 突然变 0(黑)"视觉上合理得多。

七、减法怎么做:补码如何让加法器一统加减 ​有了补码,减法 A − B 被改写成 A + (−B),而 −B 在电路上极其便宜:按位取反再加 1。于是同一个 n 位加法器,只要对 B 端每个比特各接一个受控反相器(异或门,一端接控制信号 sub),再把"加 1"用最低位进位 C₀=sub 带进去,加减就共用一套电路:

sub = 0(加法):异或门放行 B,C₀=0,结果 = A + B。sub = 1(减法):异或门把 B 逐位取反,C₀=1,结果 = A + (~B) + 1 = A − B。这正是 加法器与算术电路 里加减法器的由来,也是补码击败原码/反码的决定性理由——不是补码的算术更优雅,而是它让硅片面积省下整整一套减法器。

用减法还能顺手完成"比较":A − B 后读标志位即可判断大小,不必专门造比较器。判断 A == B 用结果是否全零(Z 标志);无符号大小看进位/借位;有符号大小则要看 N XOR V(结果符号位与溢出标志)。CPU 的条件跳转指令本质上就是在组合这几个标志。

八、定点数:硬件工程师的"小数" ​芯片里绝大多数信号处理不用浮点,而用定点数(fixed-point),因为浮点运算单元面积大、功耗高。定点数的思想是"约定小数点固定在某两个比特之间",整数运算规则原封不动。

以 Q 格式为例,Qm.n 表示 m 位整数、n 位小数(通常另算符号位),总共 m+n+1 位。如 Q3.4(1 位符号、3 位整数、4 位小数):

最低位权值 = 2⁻⁴ = 0.0625,称为 LSB 步进(量化步长);二进制 0001.1000 = 1 + 0.5 = 1.5;1111.1100 按补码 = −0.25;可表示范围约 −8 ~ +7.9375,分辨率 0.0625。定点运算规则(硬件上全是整数加法器和移位器):

运算小数点变化注意点加减两数必须 Q 格式相同(小数点对齐)先对齐再算,否则结果错位乘法Qa·Qb 小数位 = nₐ+n_b,位宽翻倍要截位/舍位回目标宽度,有精度损失左移数值 ×2,小数位减少可能溢出右移数值 ÷2,小数位增多负数要用算术右移(复制符号位)"位宽翻倍"是定点设计里最实际的坑:两个 16 位有符号数相乘得 32 位,累加一串这样的积还要再加保护位(guard bits)防止中间和溢出,最后按需要右移、舍入(常用四舍五入而非简单截断以减偏置)、饱和回 16 位。滤波器、PID、神经网络量化(int8)全是这套流程。先定点化、再谈精度和资源,是 DSP/硬件算术与纯软件算术最大的思维差异。

九、浮点数速览:为什么硬件要单独造 FPU ​当数值动态范围极大(小到 10⁻³⁸、大到 10³⁸),定点数就得不断移动小数点、极不方便。IEEE 754 浮点数借鉴科学计数法,把一个数拆成三段:

value = (−1)^S × 1.尾数(M) × 2^(阶码(E) − 偏置)

32 位单精度:[S 1 位][E 阶码 8 位][M 尾数 23 位]阶码用**移码(偏置 127)**表示,让指数比较能直接当无符号整数做;尾数隐含整数位 1(规格化数),用 23 位存下 24 位精度;还有一批特殊编码:0、非规格化数(渐下溢)、±∞、NaN(0/0 等非法结果)。代价是浮点的加减要"对阶→尾数运算→规格化→舍入"好几步、乘法要阶码相加尾数相乘,电路复杂、延迟长,所以芯片里只有需要的场景才集成 FPU(CPU、GPU),大量 MCU 和信号处理芯片只提供定点/整数单元。对硬件设计者,理解浮点数主要是为了看懂仿真里 32'h40490FDB 为什么是 3.14、以及"为什么浮点比较不能用 =="(存在舍入误差)。

十、编码的"距离"视角:为什么格雷码能容错 ​可以用汉明距离(Hamming distance)——两个码字之间不同比特的个数——重新理解前面的编码。它衡量"一个码变成另一个码要翻几位",是判错/容错的几何语言:

普通二进制相邻数常差很多位(011→100 距离为 3),计数器各位又不可能同时翻转,异步采样会撞见中间态;格雷码相邻码字距离恒为 1,跨时钟域采样时输出只可能是"旧值"或"新值",杜绝第三种错误值(见 异步 FIFO 与 Gray 码指针);奇偶校验本质是给数据加 1 位,使任何合法码字之间的最小汉明距离从 1 拉大到 2——翻 1 位必落到"非法码字"上从而被发现,但翻 2 位又回到合法码字,所以只能检奇数位错;想纠正错误就要继续拉开距离:海明码(Hamming)通过多放校验位把最小距离拉到 3,可纠 1 位、检 2 位;ECC 内存用的就是这一类。距离每拉大一点,就要多付校验比特和译码逻辑,这是可靠性与开销的永恒权衡,也直接通向 DFT 里的测试与容错思想。十一、Verilog 里的位宽、截取与符号陷阱(实战清单) ​把这一篇的理论落到可综合代码,新手最常踩的坑集中在"位宽"和"符号":

verilog// 1) 位宽不足导致静默截断:两个 8 位相加,结果可能要 9 位

wire [7:0] a, b;

wire [8:0] sum9 = {1'b0, a} + {1'b0, b}; // 先扩 1 位再相加,保留进位

// 2) 无符号/有符号混用:默认全是无符号

wire signed [7:0] sx;

wire [7:0] ux;

// sx 与 ux 直接混算会被当成无符号!要统一 signed

wire signed [8:0] ssum = $signed({{1{sx[7]}}, sx}) + $signed({{1{ux[7]}}, ux});

// 3) 扩宽:无符号高位补 0,有符号高位复制符号位

wire [7:0] u8;

wire [15:0] u16 = {8'b0, u8}; // 零扩展

wire signed [7:0] s8;

wire signed [15:0] s16 = {{8{s8[7]}}, s8}; // 符号扩展

// 4) 右移:有符号数用 >>>(算术移位),>>> 对无符号数仍是逻辑补 0

wire signed [7:0] half = s8 >>> 1; // -3 >>> 1 = -2(向负无穷取整)

wire [7:0] lsr = u8 >> 1; // 逻辑右移补 0

// 5) 常量一律写清位宽,别让 32 位默认宽度悄悄参与运算

if (state == 4'd9) ... // 而不是 state == 9(虽多能推断,但显式更安全)两条排错经验:仿真里出现超大正数,先怀疑有符号被当无符号(符号位被当成了 2³¹ 的权);出现结果对但差几个 LSB,先怀疑截位/舍入顺序和位宽不够。这类 bug 综合不会报错、全靠波形和边界用例抓,所以《看波形基本功》和补码/定点这一关必须先过。

十二、一张图把"数"在硬件里的表示串起来 ​十三、进制在工程里的真实样子:地址、掩码与逐位操作 ​教科书在十进制和二进制之间来回换算,真实工程里 90% 的场合你只需要在十六进制和二进制之间心算切换。下面是几类每天都会遇到的写法:

地址与地址范围。一个 16 位寄存器地址 0x4000_1000,按 4 KB 对齐意味着低 12 位恒为 0;判断某地址 addr 是否落在这一页,就看 (addr & 0xFFFF_F000) == 0x4000_1000。n 位地址能选 2ⁿ 个单元,10 位是 1 KB、20 位是 1 MB、30 位是 1 GB——这些 2¹⁰≈10³ 的对应应当形成条件反射,FIFO 深度、SRAM 容量、地址译码位宽全靠它口算(见 寄存器表与地址解码)。

位掩码(mask)与逐位逻辑。配置寄存器常常用"某几位是一个字段"的方式打包,靠移位和与/或来拆拼:

verilog// 一个 32 位寄存器:[31:16] 分频系数 DIV,[7:4] 模式 MODE,[0] 使能 EN

wire [31:0] cfg;

wire en = cfg[0]; // 取 1 位

wire [3:0] mode = cfg[7:4]; // 取一个字段(自动对齐到 0)

wire [15:0] div = cfg[31:16];

wire [31:0] newcfg = (cfg & 32'hFFFF_FF00) // 清掉低 8 位

| (8'h3A); // 把低 8 位写成 0x3A

// 置位/清零/翻转某一位的三件套:

wire set_b2 = cfg | (1 << 2); // 第 2 位置 1

wire clr_b2 = cfg & ~(1 << 2); // 第 2 位清 0

wire tog_b2 = cfg ^ (1 << 2); // 第 2 位取反这几个"与清、或置、异或翻"是读手册、配外设、看驱动寄存器操作的通用语言;背后正是 布尔代数与卡诺图 的与/或/非在逐位上的应用。掩码也解释了为什么外设寄存器喜欢按位段定义而不是每个状态开一个寄存器——省地址、读写一次就能原子更新一组配置。

一个完整换算走查:把 −13 写进 8 位补码。先写 +13 = 0000_1101;取反得 1111_0010;再加 1 得 1111_0011 = 0xF3。验证:0xF3 最高位权为 −128,其余为 64+32+16+2+1=115,−128+115 = −13 ✓。反向(看到 0xF3 想知道是几):最高位为 1 是负数,取反加一 0000_1101 = 13,故为 −13。这个"取反加一"在两个方向上都成立,是补码最省事的地方。

十四、字节序:多字节数在内存里怎么摆 ​当一个数超过 8 位,它要占多个地址,于是出现"高位字节放低地址还是高地址"的问题,即字节序(endianness):

小端(little-endian,x86/ARM 默认、多数总线):低位字节放低地址。0x1234_5678 在内存中按地址递增排为 78 56 34 12。大端(big-endian,网络字节序、部分老式协议):高位字节放低地址,排成 12 34 56 78,和书写顺序一致,所以大端又叫"网络序"。地址小端内容大端内容+00x78(最低字节)0x12(最高字节)+10x560x34+20x340x56+30x120x78为什么数字电路的人要关心?因为总线和 DMA 传输按字节使能(byte enable, be)选通,多字节寄存器在不同宽度的总线之间搬运(32 位 AHB 上挂一个 8 位 UART)、或者把一帧网络数据解析成整数时,字节摆错会得到完全错乱的值。经验是:跨模块/跨协议边界(AMBA、以太网、文件格式)一律先明确字节序,AMBA/PCIe 多为小端、网络协议字段多为大端,接口处用 bswap/字节重排显式转换(见 片上总线 AMBA 入门)。字节序不改变"数"本身、只改变它在字节地址上的布局,这和补码改变"位模式如何解释"是两个正交的问题,别混在一起。

十五、工程落点小结 ​看波形/地址默认十六进制,1 hex = 4 bit;n 位地址空间 = 2ⁿ 个单元。有符号数一律按补码理解,扩宽复制符号位;可综合 RTL 写清字面量位宽。跨时钟域多 bit 计数(FIFO 指针)先转格雷码再同步——这是数电知识直接救场的典型。判错用奇偶/CRC,BCD 只在逐位十进制显示时才用。十六、全文术语速查表 ​最后用一张速查把容易混的术语钉死,复习时扫一遍即可:

位(bit)/字节(byte)/字(word):1 个二进制位;8 位为字节;字长依架构而定(32 位机一个字 32 位、64 位机 64 位),寄存器和总线宽度常以字计。MSB/LSB:最高有效位/最低有效位;有符号数的符号在 MSB,移位、截取、拼接时最容易在这两个位置出错。0x / 0b / 0d 前缀:十六进制/二进制/十进制;Verilog 里写成 8'h3F、4'b1010 这样「位宽 + 进制 + 数值」三段式。原码 / 反码 / 补码:原码符号位加绝对值、0 有两种表示;反码按位取反、0 仍有两种;补码取反加一、零唯一且加减统一,现代硬件只认补码。进位 vs 溢出:进位是最高位向外的 1(无符号数才看它);溢出是有符号结果超出范围(两个同号相加得异号),二者来源和用途完全不同。Qm.n / Q 格式:定点数记法,m 位整数、n 位小数,定标靠移位,加减要对齐小数点。大端 / 小端:多字节数在内存里高字节在前(大端)还是低字节在前(小端),网络字节序是大端,x86 和多数 ARM 配置是小端。汉明距离 / 奇偶校验 / CRC:编码间不同位的个数叫距离;距离 2 能检单比特错、距离 3 能纠单比特错;奇偶校验是距离 2 码,CRC 用移位异或抓突发错。记住这条主线:进制是给人看的写法,编码是给电路用的约定,位宽和符号是给设计者的约束——三者分清,数制部分的绝大多数坑都能避开。

与现有篇目的衔接 ​上层概念:数字、模拟与混合信号、从代码到门电路下一篇:逻辑门与晶体管直觉补码如何让加减复用一套电路:加法器与算术电路编码的直接工程用途:FIFO 深度计算与设计、异步 FIFO 与 CDC(格雷码指针)看寄存器/手册:寄存器表与地址解码、怎么读芯片手册一句话总结 ​一句话总结:数字电路用二进制是因为两态电平在物理上最抗干扰、又正好对应布尔代数;进制转换靠"按权展开、除基取余",而工程上一律以十六进制作二进制速记(1 hex=4 bit),Verilog 用 位宽'进制数值 写常量;负数统一用补码表示,换来零唯一、范围多一个最小值、且减法能直接当加法做(扩宽时复制符号位、减法只需取反加 1);要分清进位(无符号越界)与溢出(同号相加却变号,V=Cₙ⊕Cₙ₋₁)两个标志,工程上还可用饱和代替回卷;需要小数时硬件优先用定点 Q 格式(整数加法器+移位,警惕乘后位宽翻倍与截位),动态范围极大才上 IEEE754 浮点/FPU;编码可用汉明距离统一理解——格雷码相邻距离 1 所以跨时钟域只可能采到旧/新值、奇偶校验把最小距离拉到 2 只能检奇数位错、海明/ECC 继续拉开距离才能纠 1 检 2;落地到代码要盯紧位宽截断、有/无符号混用、零扩展 vs 符号扩展与算术/逻辑右移,多字节跨总线还要先定字节序(AMBA/PCIe 多小端、网络多大端);BCD 服务逐位十进制显示、ASCII 是字符锚点——这些约定是看懂波形、地址掩码、ALU、加法器与 CDC 的第一块砖。

相关推荐

Uta[尤塔]的中文翻译意思、发音、意思
bte365体育

Uta[尤塔]的中文翻译意思、发音、意思

🗓️ 08-30 👁️ 4680
[分享] 2010团体世界杯参赛队员名单 种子排名
bte365体育

[分享] 2010团体世界杯参赛队员名单 种子排名

🗓️ 10-10 👁️ 7356
索尼HXR-MC1500C
beat365平台正版

索尼HXR-MC1500C

🗓️ 10-09 👁️ 3177

友情链接