从门电路到数码管:亲手搭建一个会“算数”的数字系统
你有没有想过,计算机到底是怎么“算数”的?
不是打开计算器点几下,而是真正从最底层的晶体管、逻辑门开始——两个二进制数进来,经过一堆与非或异或,最后在屏幕上亮出结果。这听起来像魔法,但其实它就藏在每一个嵌入式系统、每一块FPGA开发板里。
今天,我们就来动手搭一个能加法、会显示的小系统:输入两个4位二进制数,让它算出和,并用七段数码管把答案亮出来。整个过程不靠软件算法,全靠组合逻辑硬刚到底。你会发现,所谓的“计算”,不过是一连串光速响应的开关动作。
先看成品效果:拨码一调,结果就上屏
想象这个场景:
- 左边一组拨码开关设成
1001(也就是9); - 右边另一组设成
0111(也就是7); - 它们同时送进你的电路;
- 数码管“啪”地一下,显示出16——当然,如果是单个数码管,可能只显示个位“6”,高位溢出提醒闪烁。
这不是单片机跑程序,没有printf("sum=%d", a+b);这种高级操作。这是纯硬件在“思考”:信号从开关出发,穿过层层逻辑门,在几十纳秒内完成加法运算,再通过译码驱动点亮LED段。整个过程像一场精密的接力赛,而你,是这场赛事的设计总监。
我们拆解这条链路的核心模块:4位全加器 + BCD译码 + 数码管驱动。它们全是组合逻辑,没有寄存器、没有时钟,输入变了,输出立刻跟着变。
加法器的本质:从半加器到4位全加器
一切始于最简单的加法单元。
半加器 vs 全加器:差一个进位的距离
两个1位二进制数相加,比如1 + 1,结果是10—— 本位为0,进位为1。要实现这个功能,只需要两个输出:
- 和 S = A ⊕ B
- 进位 C = A · B
这就是半加器(Half Adder)。但它有个致命缺陷:没法处理来自低位的进位。所以只能用于最低位。
真正的通用单元是全加器(Full Adder, FA),它有三个输入:A、B 和 Cin(进位输入),输出依然是 S 和 Cout。
它的逻辑表达式长这样:
S = A ⊕ B ⊕ Cin Cout = (A & B) | (Cin & (A ^ B))别被公式吓到。你可以把它理解成:“我先看看A和B能不能产生进位,再看看加上之前的进位后会不会再次进位。” 所有这些判断,都能用基本门电路实现。
四级串联:构建4位加法器
现在我们要处理的是4位数,比如A[3:0] + B[3:0]。怎么办?很简单——级联四个全加器。
最低位 FA0 的 Cin 接地(默认为0),它的 Cout 给到 FA1 的 Cin,依此类推,直到最高位 FA3 输出最终的进位 Cout。
这种结构叫串行进位加法器(Ripple Carry Adder),名字很形象:进位像波纹一样一级一级传上去。
⚠️ 缺点也很明显:高位必须等低位稳定才能得出结果。假设每个FA延迟2ns,那第4位就要等6ns才能拿到正确的Cin。位数越多,延迟越严重。这也是为什么高性能CPU不用这种方式,而是改用“超前进位”。
但对于教学和简单应用来说,它的优势太突出:结构清晰、易于理解和实现。尤其对初学者,它是通往复杂ALU的第一步。
Verilog 实现:模块化连接更直观
下面是基于结构化建模的Verilog代码,直接对应物理连接:
module full_adder ( input A, input B, input Cin, output S, output Cout ); assign S = A ^ B ^ Cin; assign Cout = (A & B) | (Cin & (A ^ B)); endmodule module adder_4bit ( input [3:0] A, input [3:0] B, input Cin, output [3:0] Sum, output Cout ); wire C1, C2, C3; full_adder fa0 (.A(A[0]), .B(B[0]), .Cin(Cin), .S(Sum[0]), .Cout(C1)); full_adder fa1 (.A(A[1]), .B(B[1]), .Cin(C1), .S(Sum[1]), .Cout(C2)); full_adder fa2 (.A(A[2]), .B(B[2]), .Cin(C2), .S(Sum[2]), .Cout(C3)); full_adder fa3 (.A(A[3]), .B(B[3]), .Cin(C3), .S(Sum[3]), .Cout(Cout)); endmodule这段代码可以直接综合进FPGA。你可以用ModelSim仿真验证:当A=5(0101),B=6(0110),输出Sum=11(1011),Cout=0?不对!应该是11,但4位最大表示15,这里没问题;但如果A=9+B=7=16,就会溢出,Cout=1,提示超范围。
这时候你就知道,该上双数码管了。
结果怎么亮出来?七段数码管登场
算出来了,怎么让人看得见?
最经济实惠的方式就是七段数码管。它由a~g七个LED段组成,排列成“日”字形,通过控制哪几段亮,就能拼出0~9这些数字。
常见的有两种类型:
-共阴极:所有LED负极接在一起接地,要亮哪个段,就给对应引脚高电平;
-共阳极:所有正极接VCC,要点亮就得拉低对应段。
我们在设计译码逻辑时必须明确这一点,否则会出现“该亮不亮,不该亮的瞎亮”。
译码逻辑:把二进制变成“亮灯指令”
假设我们的加法结果是Sum[3:0],范围0~15。但我们只想显示0~9,超过的部分要么熄灭,要么报错。
我们需要一个BCD-to-7Segment译码器,将4位输入转换为7位输出(a~g)。例如:
| 输入 (bcd) | 显示字符 | 段状态(a~g,高有效) |
|---|---|---|
| 4’d0 | 0 | 1111110 |
| 4’d1 | 1 | 0110000 |
| 4’d5 | 5 | 1011011 |
| 4’d9 | 9 | 1111011 |
下面是针对共阴极数码管的Verilog实现:
module bcd_to_7seg ( input [3:0] bcd, output reg [6:0] seg // {a,b,c,d,e,f,g} ); always @(*) begin case(bcd) 4'd0: seg = 7'b1111110; 4'd1: seg = 7'b0110000; 4'd2: seg = 7'b1101101; 4'd3: seg = 7'b1111001; 4'd4: seg = 7'b0110011; 4'd5: seg = 7'b1011011; 4'd6: seg = 7'b1011111; 4'd7: seg = 7'b1110000; 4'd8: seg = 7'b1111111; 4'd9: seg = 7'b1111011; default: seg = 7'b0000000; // 非法输入则熄灭 endcase end endmodule注意always @(*)是组合逻辑的标准写法,确保任何输入变化都会立即触发更新。
如果你用的是共阳极数码管,只需要把输出取反即可:
assign seg_n = ~seg; // 或者在PCB上加反相器系统整合:从理论到实物的关键跨越
光有模块还不行,得把它们串起来,形成完整信号流。
整体架构图(文字版)
[拨码开关 A] → → [adder_4bit] → [bcd_to_7seg] → [GPIO] → [限流电阻] → [七段数码管] [拨码开关 B] ↗具体组件说明:
| 模块 | 实现方式 |
|---|---|
| 输入 | 拨码开关 + 上拉电阻,接入FPGA IO |
| 加法器 | Verilog实现的4位全加器 |
| 译码器 | 上述BCD译码模块 |
| 驱动 | FPGA IO直接驱动(小电流)或外接74HC595 + ULN2803增强驱动能力 |
| 显示 | 共阴/共阳数码管 + 每段串联220Ω~330Ω限流电阻 |
实际布线注意事项
去抖动处理
机械开关按下瞬间会有弹跳,导致输入信号毛刺。虽然组合逻辑反应极快,可能误读多次。解决办法:
- 硬件:RC滤波 + 施密特触发器整形
- 软件:FPGA内部加消抖逻辑(延时采样)限流保护不能少
LED段工作电流一般5~20mA,若直接连GPIO且电压3.3V,需至少(3.3V - 1.8V)/20mA ≈ 75Ω,推荐使用220Ω以上以防烧毁。电源裕量要充足
一个数码管全亮(如显示8)时,7个LED同时导通,总电流可达140mA以上。多个数码管轮显时瞬态电流更大,务必保证电源稳压芯片带载能力强。非法值处理机制
当Sum >= 10时,单一数码管无法完整显示。常见做法:
- 显示个位(自动mod 10)
- 熄灭或闪烁报警
- 使用两个数码管分别显示十位和个位
后者需要扩展设计:增加一位译码器,提取Sum / 10和Sum % 10,然后动态扫描两位数码管。
为什么这个设计值得深挖?
别小看这个“小学生项目”,它浓缩了数字系统设计的精髓。
它教会你“信号是如何流动的”
在高级编程中,a + b是一条语句。但在硬件世界,你要清楚:
- 数据从哪里来?
- 经过了哪些逻辑门?
- 延迟有多长?
- 出错了往哪查?
当你看到某个段没亮,你是先检查译码器输出?还是怀疑加法器算错了?还是驱动电流不够?这种端到端的问题排查能力,是成为合格硬件工程师的必修课。
它暴露了组合逻辑的真实挑战
虽然是纯组合逻辑,但也存在隐患:
-竞争与冒险:不同路径延迟差异可能导致短暂毛刺;
-扇出问题:一个输出驱动多个负载时可能响应变慢;
-功耗突变:从显示“1”切换到“8”,瞬间电流翻倍,引起电压跌落。
这些问题在仿真中未必显现,只有实测才会暴露。而这正是工程实践的价值所在。
它是你走向更复杂系统的起点
今天的4位加法器,明天就可以升级为:
- 支持减法的ALU(加补码)
- 多位乘法器(移位+累加)
- 浮点运算单元(配合状态机)
- 完整的8位简易CPU
而数码管显示也可以演进为:
- 动态扫描多位数码管
- SPI驱动LCD/OLED
- VGA输出图形界面
所有的复杂系统,都是从这样一个个小模块堆叠起来的。
写在最后:动手,是最好的学习方式
你看再多文档、听再多课,都不如自己在FPGA上跑一遍这个例子。
建议你这样做:
1. 用Verilog写出全加器和译码器;
2. 在开发板上连接拨码开关和数码管;
3. 下载程序,亲自试几组数据;
4. 故意制造错误(比如接错段、忘记限流电阻),观察现象并修复。
你会突然明白:原来“计算”不是魔法,而是无数个开关精确协作的结果;原来“显示”也不是理所当然,背后有一整套电平匹配与驱动设计。
这才是工程师的成长之路。
如果你正在学习数字逻辑、准备FPGA项目,或者想重温基础知识,不妨从这个小小的加法器开始。它简单,却不平凡。
动手试试吧,让电流替你“算一道题”。