第二章:数字电路基础回顾——布尔代数、组合逻辑、时序逻辑、同步与异步设计
各位同学,欢迎来到第二章。说实话,这一章的内容看起来像是“复习课”,但我得提醒你——很多流片失败的案例,根源恰恰是这些基础概念没吃透。我自己带项目时,最怕听到的一句话就是:“这个电路我凭感觉画的,应该没问题吧?”
嗯,咱们今天就把这些“感觉”变成“确定”。
2.1 布尔代数:数字电路的数学根基
布尔代数,说白了就是0和1的游戏。你别看它简单,当年我在做一款通信芯片的译码器时,就因为一个布尔化简没做彻底,导致组合逻辑路径多了两级,时序直接崩了。
核心运算就三个:与(AND)、或(OR)、非(NOT)。
- 与门:全1出1,有0出0。我习惯记成“串联开关”——两个开关都闭合,灯才亮。
- 或门:有1出1,全0出0。像“并联开关”——任何一个闭合,灯就亮。
- 非门:取反。输入1输出0,输入0输出1。
还有几个常用定律,你写代码时经常用到:
| 定律 | 表达式 | 我的理解 |
|---|---|---|
| 交换律 | A·B = B·A | 顺序不重要 |
| 结合律 | (A·B)·C = A·(B·C) | 括号随便加 |
| 分配律 | A·(B+C) = A·B + A·C | 和普通乘法一样 |
| 德摩根定律 | ~(A·B) = ~A + ~B | 取反时,与变或 |
我的小技巧:写Verilog时,如果组合逻辑特别复杂,先画卡诺图化简。我曾经因为少化简一项,多用了200个LUT,被领导叫去喝茶。
2.2 组合逻辑:没有记忆的电路
组合逻辑的特点很简单:输出只取决于当前输入,跟历史无关。就像一台自动售货机——你投币,它立刻出货,不会记得你昨天来过。
常见的组合逻辑模块:
- 多路选择器(MUX):从多个输入中选一个。我常用它来做数据路径切换。
- 译码器:把二进制编码转成独热码。比如3-8译码器,3位输入,8位输出。
- 加法器:半加器、全加器、超前进位加法器。做DSP模块时,加法器的速度往往决定整个模块的Fmax。
写组合逻辑时,有个坑你必须注意——组合环路。我曾经在调试一个状态机时,发现仿真波形一直在跳,查了两天才发现是组合逻辑的输出直接反馈到了输入,形成了死循环。
避坑指南:组合逻辑的输出绝对不能直接连回自己的输入!除非你故意要做振荡器。否则,轻则仿真卡死,重则芯片烧毁。
2.3 时序逻辑:记住过去的状态
时序逻辑就不一样了。它有记忆功能,输出不仅取决于当前输入,还取决于之前的状态。核心元件就是触发器(Flip-Flop)。
触发器有三种常见类型:
- D触发器:最常用。时钟上升沿时,D端的数据被锁存到Q端。我90%的时序电路都用D触发器。
- JK触发器:功能更丰富,可以置位、复位、翻转。但说实话,现在写Verilog很少直接例化JK触发器了。
- T触发器:每来一个时钟沿就翻转一次。做分频器时很实用。
为什么时序逻辑这么重要?因为数字电路里,数据需要“对齐”。你想想看,如果两个信号到达触发器的时间不一样,那采到的数据就是错的。这就是后面要讲的建立时间和保持时间问题。
// 一个简单的D触发器,Verilog描述
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 1'b0;
else
q <= d;
end
这段代码我写了不下千遍。注意看,posedge clk表示时钟上升沿触发,negedge rst_n表示异步复位。嗯,这里要注意:复位信号是低电平有效,所以叫rst_n,这是业界惯例。
2.4 同步设计与异步设计
这是面试必考题,也是实际项目中踩坑最多的地方。
同步设计:所有触发器都使用同一个时钟源。数据在时钟沿到来时统一更新。好处是时序分析简单,不容易出问题。我个人的习惯是:能同步就同步,绝不搞花里胡哨的东西。
异步设计:不同模块使用不同的时钟,或者同一个时钟但触发沿不同。比如一个模块用上升沿,另一个用下降沿。这样做的好处是灵活,但代价是——亚稳态。
什么是亚稳态? 当数据变化刚好发生在时钟沿附近时,触发器可能无法判断是0还是1,输出会处于一个中间状态。这个状态可能持续几纳秒,然后随机稳定到0或1。更可怕的是,它可能传播到后面的电路,导致整个系统崩溃。
我曾经在一个项目中,为了省一个PLL,让两个模块分别用晶振的上升沿和下降沿。结果板子调试时,数据偶尔会错一位。查了三天,最后发现是异步时钟导致的亚稳态。从那以后,我再也不敢在关键路径上用异步设计。
解决亚稳态的标准方法:两级同步器。
// 异步信号同步化,两级触发器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sync1 <= 1'b0;
sync2 <= 1'b0;
end
else begin
sync1 <= async_in;
sync2 <= sync1;
end
end
assign sync_out = sync2;
原理很简单:第一级触发器可能进入亚稳态,但经过一个时钟周期后,它大概率会稳定下来。第二级触发器再采一次,基本就安全了。当然,如果时钟频率很高,可能需要三级甚至四级。
2.5 我的设计哲学
做了十几年FPGA,我总结了几条原则:
- 时钟域要清晰:每个模块只用一个时钟,跨时钟域必须用同步器或FIFO。
- 组合逻辑要简单:复杂的组合逻辑拆成多级流水线,别想着一个周期搞定所有事。
- 复位要统一:要么全部异步复位,要么全部同步复位。混用会出问题。
- 仿真要彻底:不要只仿真正常情况,还要仿真边界条件。比如时钟抖动、数据变化最坏情况。
我记得有一次,一个实习生写了个计数器,组合逻辑路径长达20级。综合后频率只能跑到50MHz,而设计要求是200MHz。我让他改成5级流水线,每级4位加法,频率直接飙到250MHz。这就是基础知识的威力。
好了,第二章就到这里。下一章我们开始讲Verilog语法,我会告诉你哪些语法是“能综合的”,哪些是“仿真用的”——这两者区别大了去了。