第二章:数字电路基础回顾——数制与编码、组合逻辑、时序逻辑、状态机
各位同学,欢迎来到第二章。说实话,很多刚入行的验证工程师,总觉得数字电路基础是“做设计的人该操心的事”。我当年也这么想,直到有一次在验证一个复杂的握手协议时,因为对状态机跳转条件理解有偏差,白白浪费了两周时间。嗯,从那以后我明白了——验证工程师不懂电路基础,就像厨师不认识盐和糖。
这一章,我们快速过一遍数字电路的四大基石。别担心,我不会讲得太深,但我会告诉你哪些坑我替你踩过了。
2.1 数制与编码——验证工程师的“度量衡”
验证工作里,你天天跟数据打交道。数制和编码,说白了就是数据的“语言”。
2.1.1 常用数制
- 二进制(Binary):电路唯一认识的语言。0和1,简单粗暴。
- 十进制(Decimal):我们人类习惯的,但电路不认识。
- 十六进制(Hex):验证中最常用的“缩写”。4位二进制对应1位十六进制,写testbench时你绝对离不开它。
我个人习惯:在写验证代码时,所有常量尽量用十六进制表示。比如 8'hA5 比 8'b10100101 好读得多,也不容易数错位数。
2.1.2 编码方式
编码就是把信息转换成二进制。验证中你至少得懂这几种:
| 编码类型 | 说明 | 验证中的坑 |
|---|---|---|
| 原码/反码/补码 | 有符号数的表示方式 | 减法运算时,补码最容易出错。我曾经在验证一个ALU时,忘了把负数扩展成正确位宽,结果仿真结果全错。 |
| 格雷码(Gray Code) | 相邻数值只有1位不同 | 跨时钟域传递计数器时,格雷码是神器。但注意:格雷码不能直接做算术运算。 |
| BCD码 | 用4位二进制表示1位十进制 | 显示驱动芯片常用。验证时记得检查“无效状态”(比如1010~1111)的处理。 |
| 奇偶校验码 | 增加1位校验位,检测单比特错误 | 只能检错,不能纠错。别指望它对付多比特翻转。 |
小技巧:写testbench时,用 $signed() 和 $unsigned() 系统函数来显式控制符号位,能避免很多隐式转换的bug。
2.2 组合逻辑电路——没有记忆的“直性子”
组合逻辑,输出只取决于当前输入。它没有记忆,像个直性子的人——你给它什么,它立刻给你什么。
2.2.1 基本门电路
与、或、非、与非、或非、异或、同或。这些是数字电路的“原子”。你想想看,再复杂的芯片,底层都是这些门搭起来的。
2.2.2 常用组合逻辑模块
- 多路选择器(MUX):从多个输入中选一个。验证时注意“选择信号”的优先级和毛刺。
- 译码器/编码器:地址译码、指令译码都用它。我曾经在验证一个3-8译码器时,发现使能信号没处理好,导致多个输出同时有效——这在设计中是致命的。
- 加法器:半加器、全加器、超前进位加法器。验证加法器时,别忘了检查进位链的延迟。
- 比较器:大于、小于、等于。注意无符号数和有符号数的比较方式不同。
避坑指南:我曾经在验证一个组合逻辑时,发现仿真结果和预期总差一点。查了三天,最后发现是代码里用了 if-else 但漏掉了 else 分支,导致综合出了锁存器(Latch)。记住:组合逻辑一定要覆盖所有输入条件,否则综合工具会“帮你”加个锁存器,时序就全乱了。
2.3 时序逻辑电路——有记忆的“老江湖”
时序逻辑,输出不仅取决于当前输入,还取决于之前的状态。它有记忆,像个老江湖——它会记住你之前做过什么。
2.3.1 触发器(Flip-Flop)
D触发器是最基本的时序单元。时钟上升沿采样数据,然后保持到下一个时钟沿。验证时,你主要关注三点:
- 建立时间(Setup Time):数据必须在时钟沿之前稳定。
- 保持时间(Hold Time):数据必须在时钟沿之后保持稳定。
- 复位行为:同步复位还是异步复位?异步复位容易产生亚稳态,我建议尽量用同步复位。
2.3.2 寄存器与移位寄存器
寄存器就是一组D触发器。移位寄存器在串并转换、流水线设计中很常见。验证时,记得检查移位方向(左移/右移)和移位位数是否正确。
2.3.3 计数器
计数器是时序逻辑的“活字典”。二进制计数器、格雷码计数器、约翰逊计数器……验证计数器时,我最常犯的错是“初始值”没设对,导致计数结果整体偏移。
我的经验:验证时序逻辑时,一定要在仿真波形里同时观察时钟、复位、数据输入和数据输出。很多时候,问题就出在“时序错位”上——比如数据在时钟上升沿变化,但采样点却在下降沿。
2.4 状态机基础——数字电路的“大脑”
状态机(FSM),是数字电路里最核心的控制逻辑。说白了,它就是告诉电路“现在该干什么,下一步该干什么”。
2.4.1 状态机的两种类型
| 类型 | 特点 | 验证要点 |
|---|---|---|
| Moore型 | 输出只取决于当前状态 | 输出比状态变化晚一个时钟周期,注意时序对齐 |
| Mealy型 | 输出取决于当前状态和输入 | 输入变化可能立即影响输出,容易产生毛刺 |
2.4.2 状态机的三要素
- 状态寄存器:记住当前状态。
- 次态逻辑:根据当前状态和输入,计算下一个状态。
- 输出逻辑:根据当前状态(或加上输入),产生输出。
2.4.3 验证状态机的常见陷阱
我验证过不下20个状态机,踩过的坑可以写本书了。这里说三个最常见的:
- 状态编码没选对:二进制编码省寄存器,但容易产生毛刺;独热码(One-Hot)抗毛刺,但费寄存器。我建议:状态数少于8个用独热码,多于16个用二进制码。
- 漏掉了非法状态:状态机如果进入了一个未定义的状态,会怎么样?很多设计直接“死机”了。一定要加
default分支,让状态机回到安全状态。 - 输出条件不完整:Mealy型状态机的输出,很容易漏掉某些输入组合。验证时,用随机激励配合覆盖率分析,能有效发现这类问题。
实用建议:写状态机时,我习惯把状态定义、次态逻辑、输出逻辑分成三个 always 块来写。这样代码清晰,验证时也容易定位问题。另外,仿真时用 $display 打印状态变化,比看波形快得多。
本章小结
数字电路基础,是验证工程师的“内功”。数制与编码让你读懂数据,组合逻辑让你理解运算,时序逻辑让你把握节奏,状态机让你掌控流程。这四块内容,你越熟练,验证时就越能快速定位问题。
下一章,我们会进入硬件描述语言(Verilog/VHDL)的世界。到时候,我会教你如何用代码把这些电路“描述”出来。嗯,那才是验证工程师真正的战场。
公众号:蓝海资料掘金营,微信deep3321