第二章:数字电路基础回顾
各位同学,欢迎来到第二章。在开始写FPGA代码之前,我强烈建议大家先花点时间,把数字电路的基础再过一遍。你可能会觉得:「这些我都学过啊,有什么好讲的?」
嗯,我在带新人的时候,发现很多问题其实都出在基础不牢。比如写出来的代码综合后多了一级没必要的寄存器,或者时序怎么都跑不过。说白了,都是对底层逻辑理解不够深。
2.1 二进制与逻辑电平
数字电路的世界里,只有两个数字:0 和 1。这听起来很简单,但实际工程中,这两个数字是用电压来表示的。
我举个例子。在3.3V的系统中,我们通常规定:
- 0V ~ 0.8V 代表逻辑「0」
- 2.0V ~ 3.3V 代表逻辑「1」
- 0.8V ~ 2.0V 这个区间,是禁区
为什么要有这个区间?因为实际电路中,噪声无处不在。如果没有这个噪声容限,一个微小的干扰就会让0变成1,那系统就没法用了。
2.2 基本逻辑门:与、或、非
这三个门,是数字电路的基石。你写的任何复杂逻辑,最终都会分解成这三个基本操作。
| 逻辑门 | 符号 | 真值表 | Verilog 运算符 |
|---|---|---|---|
| 与门 (AND) | & | 0&0=0, 0&1=0, 1&0=0, 1&1=1 | && 或 & |
| 或门 (OR) | | | 0|0=0, 0|1=1, 1|0=1, 1|1=1 | || 或 | |
| 非门 (NOT) | ~ | ~0=1, ~1=0 | ! |
你想想看,与门就是「全1才出1」,或门是「有1就出1」,非门就是「取反」。就这么简单。
2.3 组合逻辑与时序逻辑
这是FPGA设计中最重要的两个概念。很多初学者搞混,我简单说一下。
组合逻辑: 输出只取决于当前的输入。没有记忆功能。比如一个加法器,输入A和B,输出就是A+B。输入变了,输出立刻变。
时序逻辑: 输出不仅取决于当前输入,还取决于之前的状态。说白了,它有记忆。比如一个计数器,它记得自己数到哪了。
在FPGA里,组合逻辑用LUT(查找表)实现,时序逻辑用触发器实现。我刚开始做设计时,总喜欢把所有逻辑都写成组合逻辑,觉得这样延迟小。结果有一次,一个复杂的组合逻辑路径太长,时序根本跑不过。后来改成流水线(用触发器分段),问题就解决了。
2.4 触发器与锁存器
这两个东西,都是用来存储1位数据的。但它们的区别很大,而且在实际工程中,锁存器往往是「坑」。
触发器(Flip-Flop): 边沿触发。只在时钟的上升沿或下降沿采样数据。这是FPGA中最常用的存储单元。
锁存器(Latch): 电平触发。只要使能信号有效,输出就跟着输入变。这玩意儿在FPGA中,我建议你尽量别用。
为什么?因为锁存器对毛刺非常敏感。我曾经在一个项目中,因为代码里不小心写出了一个锁存器,导致整个模块的行为变得不可预测。查了三天,最后用综合报告一看,发现多了一个Latch。从那以后,我每次写完代码都会检查综合报告,确保没有意外生成的锁存器。
我们来看一个例子:
// 不好的写法:会生成锁存器
always @(*) begin
if (sel)
out = a;
// 缺少 else
end
// 好的写法:不会生成锁存器
always @(*) begin
if (sel)
out = a;
else
out = b;
end
嗯,这里要注意。在时序逻辑中(always @(posedge clk)),即使if没有else,也不会生成锁存器,因为触发器会保持原来的值。但组合逻辑就不行。
好了,这一章的内容就这些。二进制、逻辑门、组合逻辑和时序逻辑的区别、触发器和锁存器的对比,这些都是你写FPGA代码时天天要面对的东西。下一章,我们开始真正动手写代码。
- 数字电路只有0和1,但实际电平有噪声容限
- 与或非是基本逻辑门,所有复杂逻辑都由它们组成
- 组合逻辑无记忆,时序逻辑有记忆
- FPGA中优先使用触发器,避免意外生成锁存器
公众号:蓝海资料掘金营,微信deep3321