第1章:数字电路基础——从布尔代数到状态机
各位同学,欢迎来到《软硬件一体开发实战技能大全》的第一章。我是你们的导师,一个在嵌入式领域摸爬滚打了十几年的老工程师。今天咱们聊聊数字电路的基础,这部分内容看似枯燥,但说实话,它是整个软硬件协同设计的根基。你写驱动、调时序、做底层优化,最终都会回到这些基本概念上。
1.1 布尔代数与逻辑门
先问大家一个问题:计算机为什么能算数?答案就是布尔代数。说白了,就是用0和1两个值,配合「与、或、非」三种基本运算,就能描述一切数字逻辑。
我个人习惯把布尔代数看作「数字世界的语法」。比如:
- 与门(AND):两个输入都是1,输出才是1。我常跟新人说:「与门就是两个开关串联,一个断了灯就不亮」。
- 或门(OR):只要有一个输入是1,输出就是1。像两个开关并联,按哪个灯都亮。
- 非门(NOT):输入取反。1变0,0变1。
我在项目中遇到过一个问题:一个同事用了一堆分立逻辑门搭电路,结果功耗超标。后来发现是没化简布尔表达式,白白多用了好几个门。所以记住:化简是基本功。
重要公式(德摩根定律):
!(A && B) = !A || !B
!(A || B) = !A && !B
这个在硬件设计里太常用了,尤其是做低功耗设计时,能帮你省掉不少反相器。
1.2 组合逻辑电路
组合逻辑,就是输出只取决于当前输入,跟历史无关。嗯,这里要注意:它没有记忆功能。
1.2.1 加法器
加法器是CPU里最基础的运算单元。我刚开始学的时候,觉得半加器和全加器差不多,后来做项目才发现区别大了。
| 类型 | 输入 | 输出 | 特点 |
|---|---|---|---|
| 半加器 | A, B | Sum, Carry | 不考虑进位输入 |
| 全加器 | A, B, Cin | Sum, Cout | 考虑低位进位 |
举个例子:你要算二进制 1 + 1,半加器输出 Sum=0, Carry=1。但如果是 1 + 1 + 进位1,就必须用全加器了。我曾经在做一个8位加法器时,偷懒用了半加器级联,结果高位计算全错——这就是血的教训。
1.2.2 多路选择器(MUX)
多路选择器,说白了就是一个「数据开关」。2选1 MUX的表达式很简单:
Y = (S && A) || (!S && B)
当S=1时选A,S=0时选B。我在FPGA项目里经常用MUX来做寄存器配置,比如根据模式选择不同的时钟源。你想想看,如果没有MUX,你得用一堆三态门,麻烦得很。
实战技巧:在Verilog里写MUX,建议用case语句而不是if-else。因为case语句综合出来的硬件更清晰,不容易产生锁存器(latch)。
1.3 时序逻辑电路
时序逻辑和组合逻辑最大的区别是什么?它有记忆。输出不仅看当前输入,还看之前的状态。这就引出了触发器和计数器。
1.3.1 触发器(Flip-Flop)
触发器是数字电路里最基础的存储单元。D触发器最常见:
always @(posedge clk) begin
Q <= D;
end
这段代码的意思是:在每个时钟上升沿,把D的值锁存到Q。我见过很多新手犯一个错误——把组合逻辑和时序逻辑混在一起写,结果综合出来一堆奇怪的latch。记住:时序逻辑用非阻塞赋值(<=),组合逻辑用阻塞赋值(=)。
我曾经在一个项目里,因为没注意复位信号的同步问题,导致上电后触发器状态不确定,整个系统跑飞了。后来加了一个同步复位电路才解决。
1.3.2 计数器
计数器就是触发器的级联应用。最简单的二进制计数器:
always @(posedge clk or posedge rst) begin
if (rst)
count <= 0;
else
count <= count + 1;
end
这个代码实现了一个4位计数器,从0数到15然后回绕。我在做PWM控制时,经常用计数器来产生不同占空比的波形。注意:计数器的位宽决定了最大计数值,比如8位计数器最大只能到255。
避坑指南:我曾经在做一个高速计数器时,没考虑时钟域同步问题,结果跨时钟域采样时出现了亚稳态。后来加了双级同步器才稳定。记住:跨时钟域信号一定要同步处理!
1.4 状态机设计
状态机(FSM)是数字系统设计的核心。说白了,它就是「根据当前状态和输入,决定下一个状态和输出」的模型。
我习惯把状态机分为两类:
- Moore型:输出只取决于当前状态。比如一个交通灯控制器,红灯亮多久只跟当前状态有关。
- Mealy型:输出取决于当前状态和输入。比如一个序列检测器,检测到特定序列才输出高电平。
写状态机时,我建议用三段式写法:
// 第一段:状态转移
always @(posedge clk or posedge rst) begin
if (rst) state <= IDLE;
else state <= next_state;
end
// 第二段:下一状态逻辑
always @(*) begin
case (state)
IDLE: if (start) next_state = RUN;
else next_state = IDLE;
RUN: if (done) next_state = IDLE;
else next_state = RUN;
default: next_state = IDLE;
endcase
end
// 第三段:输出逻辑
always @(*) begin
case (state)
IDLE: out = 0;
RUN: out = 1;
endcase
end
为什么推荐三段式?因为这样综合出来的硬件清晰,时序容易收敛。我见过有人把所有逻辑写在一个always块里,结果综合后时序跑不过,改起来特别痛苦。
个人经验:设计状态机时,一定要先画状态转移图。别急着写代码。我每次画完图,都能发现一些遗漏的状态或非法转移。画图花10分钟,可能省下调试的2小时。
小结
这一章我们聊了布尔代数、逻辑门、组合逻辑(加法器、MUX)、时序逻辑(触发器、计数器)和状态机。这些都是数字电路的「砖瓦」,后面学处理器架构、总线协议、外设驱动时,都会用到这些知识。
下一章,我们会深入处理器内核,看看CPU到底是怎么工作的。到时候你会发现,状态机设计的思想无处不在。
好了,今天就到这里。记住:基础不牢,地动山摇。把这些概念吃透,后面的路会越走越顺。
公众号:蓝海资料掘金营,微信deep3321