第二讲:数字逻辑基础回顾
各位同学好,今天我们聊聊数字逻辑的基础。别小看这些内容,我做了十几年FPGA,回头看看,很多坑其实都出在基础不牢上。
布尔代数、真值表、卡诺图,还有组合逻辑和时序逻辑的区别。这些概念你搞透了,后面写Verilog会顺手很多。
布尔代数:数字世界的数学语言
说白了,布尔代数就是0和1的数学。你想想看,数字电路里只有两种状态:高电平和低电平。布尔代数正好描述这种关系。
基本运算就三个:与(AND)、或(OR)、非(NOT)。
- 与运算:全1才为1。用乘号表示,A·B
- 或运算:有1就为1。用加号表示,A+B
- 非运算:取反。用上划线表示,Ā
我在项目中遇到过一个问题:一个简单的使能信号,用与门和或门组合错了,结果整个模块工作不正常。查了两天才发现,原来是一个括号位置搞错了。嗯,布尔代数的运算优先级,大家一定要记清楚:非 > 与 > 或。
重要定律:
- 交换律:A·B = B·A,A+B = B+A
- 结合律:(A·B)·C = A·(B·C)
- 分配律:A·(B+C) = A·B + A·C
- 德摩根定律:(A·B)' = A' + B', (A+B)' = A' · B'
德摩根定律特别实用。我建议你把它背下来。化简逻辑表达式时,这玩意儿能救命。
真值表:最直观的逻辑描述
真值表就是把所有输入组合都列出来,然后写出对应的输出。简单粗暴,但非常有效。
举个例子,一个2输入与非门(NAND):
| A | B | Y = (A·B)' |
|---|---|---|
| 0 | 0 | 1 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
真值表的好处是,你不需要懂什么公式,直接看输入输出关系就行。我在调试时经常先画真值表,再反推逻辑表达式。这样不容易出错。
小技巧:写真值表时,输入变量按二进制顺序排列。比如3个变量,就从000、001、010...一直到111。这样不会漏掉任何组合。
卡诺图:化简逻辑的利器
卡诺图,说白了就是真值表的图形化版本。它帮你直观地找到可以合并的项。
为什么需要化简?因为逻辑表达式越简单,用的门电路就越少,FPGA的资源占用就越小,速度还可能更快。
卡诺图的画法有讲究:
- 2变量:2×2的格子
- 3变量:2×4的格子
- 4变量:4×4的格子
相邻的格子只能变化一个变量。这是关键!
举个例子,一个3变量的卡诺图:
| AB\C | 0 | 1 |
|---|---|---|
| 00 | 1 | 0 |
| 01 | 1 | 1 |
| 11 | 0 | 1 |
| 10 | 1 | 0 |
圈相邻的1,圈越大越好。但圈必须是矩形,大小是2的幂次方(1、2、4、8...)。
注意:卡诺图是二维的,最左边和最右边也是相邻的!最上边和最下边也是相邻的!这叫「循环相邻性」。我曾经忘了这个,化简出来的表达式多了一个项,浪费了十几个LUT。
组合逻辑:输出只取决于当前输入
组合逻辑,就是输出只跟当前输入有关。没有记忆功能,没有时钟概念。
典型的组合逻辑电路:
- 与门、或门、非门
- 多路选择器(MUX)
- 译码器、编码器
- 加法器(不带进位存储的那种)
在Verilog里,组合逻辑用assign语句或者always@(*)块来描述。
// 组合逻辑示例:2选1多路选择器
assign out = sel ? b : a;
// 或者用always块
always @(*) begin
if(sel)
out = b;
else
out = a;
end
这里要注意,always块里必须把所有输入都列在敏感列表里。用@(*)是偷懒的好办法,系统会自动推导。
避坑指南:组合逻辑容易产生毛刺。因为不同路径的延迟不一样,输出在稳定之前可能会跳变几次。我曾经在计数器模块里用组合逻辑做清零信号,结果毛刺导致计数器误清零。后来加了同步处理才解决。
时序逻辑:输出还取决于历史状态
时序逻辑就不一样了。它有记忆功能,输出不仅看当前输入,还要看之前的状态。
为什么需要时序逻辑?因为数字系统需要「记住」东西。比如计数器、状态机、寄存器,这些都是时序逻辑。
时序逻辑的核心是触发器(Flip-Flop)。它靠时钟边沿来采样数据。
在Verilog里,时序逻辑用always@(posedge clk)来描述。
// 时序逻辑示例:D触发器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
q <= 1'b0;
else
q <= d;
end
注意这里用的是非阻塞赋值(<=),不是阻塞赋值(=)。这是组合逻辑和时序逻辑写法上的关键区别。
个人习惯:我写时序逻辑时,一律用非阻塞赋值。写组合逻辑时,一律用阻塞赋值。这样不容易搞混。你想想看,如果混用了,仿真结果可能对,但综合出来的电路可能完全不是你想要的。
组合逻辑 vs 时序逻辑:怎么选?
这个问题很多新手会问。我简单总结一下:
| 特性 | 组合逻辑 | 时序逻辑 |
|---|---|---|
| 输出依赖 | 仅当前输入 | 当前输入+历史状态 |
| 时钟 | 不需要 | 需要 |
| 存储能力 | 无 | 有 |
| 毛刺 | 容易产生 | 不容易(被时钟同步) |
| Verilog写法 | assign或always@(*) | always@(posedge clk) |
| 赋值方式 | 阻塞赋值(=) | 非阻塞赋值(<=) |
实际项目中,组合逻辑和时序逻辑经常混着用。比如一个状态机,状态转移是时序逻辑,输出可以是组合逻辑也可以是时序逻辑,看你的需求。
我记得有一次做SPI控制器,输出信号用了组合逻辑,结果因为毛刺导致从机采样错误。后来改成时序逻辑输出,问题就解决了。所以,如果你不确定,优先用时序逻辑输出,更稳定。
总结一下:布尔代数是工具,真值表和卡诺图是方法,组合逻辑和时序逻辑是两大基本电路类型。这些基础打牢了,后面学FPGA设计会轻松很多。
下一讲我们开始讲Verilog语法。到时候会用到今天讲的概念,尤其是组合逻辑和时序逻辑的写法区别。大家先消化一下今天的内容。
有什么问题,欢迎交流。