news 2026/1/11 16:52:09

用Verilog描述半加器:FPGA设计操作指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Verilog描述半加器:FPGA设计操作指南

从半加器开始:用Verilog点亮你的第一个FPGA逻辑

你有没有想过,计算机是怎么做加法的?
不是打开计算器点两下那种“加法”,而是真正意义上——硬件层面的二进制相加
答案就藏在一个看似简单的电路里:半加器(Half Adder)

在 FPGA 设计的世界中,我们不写“程序”来运行任务,而是用代码去“搭建电路”。而Verilog HDL就是这门“造芯语言”中最常用的一种。当你写下一行assign Sum = A ^ B;,其实是在告诉综合工具:“请给我造一个异或门。”

今天,我们就从最基础、也最重要的起点出发——用 Verilog 实现一个半加器,带你走完从逻辑理解到仿真验证的完整设计流程。别小看它,所有复杂的 CPU 加法器,都是由这样一个个小小的“1+1=?”单元搭起来的。


半加器是什么?为什么它是数字系统的“第一块积木”?

想象你在纸上做二进制加法:

1 + 1 ---- 10

结果是10,也就是十进制的 2。这里有两个输出:
-和(Sum)是低位的结果 →0
-进位(Carry)是高位的溢出 →1

这个过程就是半加器要完成的任务:对两个 1 位二进制数进行相加,输出它们的和与进位。

它的输入只有两个:AB;输出也只有两个:SumCarry
但它没有考虑来自更低一位的进位输入(Cin),所以叫“半”加器。
全加器才会处理 Cin,但那个可以由两个半加器拼出来。

它的真值表长这样:

ABSumCarry
0000
0110
1010
1101

一眼就能看出规律:
-Sum = A ⊕ B(异或)
-Carry = A & B(与)

就这么简单?没错!可正是这种简洁性,让它成为学习 FPGA 设计的最佳入口。


三种写法,三种思维:如何用 Verilog 描述同一个电路?

在 Verilog 中,描述同一个功能可以有不同的“风格”。就像写作有散文、诗歌、说明文之分,HDL 也有不同建模方式。掌握它们,等于掌握了看待硬件的不同视角。

方法一:数据流建模 —— “我想表达的是关系”

这是最直观、最常用的写法。你不再关心用了什么门,只关心信号之间怎么算。

module half_adder ( input wire A, input wire B, output wire Sum, output wire Carry ); assign Sum = A ^ B; assign Carry = A & B; endmodule
  • assign是连续赋值语句,适用于组合逻辑。
  • 所有wire类型,因为它是“连线”,不是存储。
  • 综合后直接映射为一个 XOR 门和一个 AND 门。

优点:简洁清晰,易读易维护,适合大多数场景。
🧠思维方式:我关注的是“数据如何流动”。


方法二:结构化建模 —— “我要亲手搭出每根线”

如果你想看得更“底层”一点,可以直接调用 FPGA 内部的原始门级元件(primitives)。这种方式像是在画电路图。

module half_adder_structural ( input wire A, input wire B, output wire Sum, output wire Carry ); xor (Sum, A, B); // xor(out, in1, in2) and (Carry, A, B); endmodule

你看,连模块都没有名字,这就是原语实例化语法。Xilinx 和 Intel 的器件库都支持这些基本门。

优势:完全掌控硬件结构,教学时特别有用。
🔍应用场景:当你想分析最终网表结构,或者做低功耗优化时,这种写法能让你看到“真实世界”的门。

不过实际项目中很少这么写,毕竟综合工具自己就能生成最优门级结构。但知道它存在,很重要。


方法三:行为级建模 —— “当条件满足时,该做什么?”

虽然半加器是纯组合逻辑,但我们也可以用时序块always @(*)来描述它。这是构建复杂数字系统的核心范式。

module half_adder_behavioral ( input wire A, input wire B, output reg Sum, output reg Carry ); always @(*) begin Sum = A ^ B; Carry = A & B; end endmodule

注意变化:
- 输出变成了reg类型。因为在always块里赋值的信号必须声明为reg
- 敏感列表用了@(*),表示自动包含所有输入,避免遗漏导致锁存器误生成。

⚠️ 虽然功能一样,但初学者常在这里犯错:
- 忘记写敏感列表 → 综合出错误逻辑
- 在组合逻辑中使用非阻塞赋值<=→ 可能引入意外时序
- 不完整分支导致锁存器插入

所以记住一句口诀:组合逻辑用 always @(*),阻塞赋值 =,变量声明为 reg

🧠 这种写法的价值在于——它是通往状态机、ALU、CPU 等复杂模块的大门。


综合之后发生了什么?看看 FPGA 到底“造”了啥

你以为写的只是代码?其实在综合阶段,EDA 工具已经把它变成了真正的硬件结构。

以 Xilinx Artix-7 为例:
- 每个 LUT6(查找表)可以实现任意 6 输入以下的组合函数。
- 半加器只需要两个 2 输入函数:
- XOR2 → 占用 1 个 LUT
- AND2 → 占用 1 个 LUT
- 总共消耗2 个 LUT,零寄存器,零布线资源(理想情况下)

而且现代综合器非常聪明。如果你写了三个半加器,它可能会把重复逻辑合并,甚至将部分逻辑折叠进同一个 LUT。

💡 实际项目中,像这样的小模块往往不会单独保留,而是被优化掉或内联到上级模块中。这也是为什么我们强调:不要为了省资源刻意拆分小模块,让工具去做优化


别急着烧板子!先仿真:你的第一份 Testbench

在 FPGA 开发中,有一条铁律:先仿真,再下载。否则你可能花半小时等布局布线,结果发现逻辑错了。

下面是你需要掌握的第一个测试平台(Testbench):

module tb_half_adder; reg A, B; wire Sum, Carry; // 实例化被测模块 half_adder uut (.A(A), .B(B), .Sum(Sum), .Carry(Carry)); initial begin $monitor("Time=%0t | A=%b B=%b | Sum=%b Carry=%b", $time, A, B, Sum, Carry); // 测试全部输入组合 A = 0; B = 0; #10; A = 0; B = 1; #10; A = 1; B = 0; #10; A = 1; B = 1; #10; $finish; end endmodule

关键点解析:
-$monitor:实时打印信号值,调试神器。
-#10:延迟 10 个时间单位(比如 ns),用于推进仿真时间。
-$finish:结束仿真。

运行结果应与真值表一致:

Time= 0 | A=0 B=0 | Sum=0 Carry=0 Time=10 | A=0 B=1 | Sum=1 Carry=0 Time=20 | A=1 B=0 | Sum=1 Carry=0 Time=30 | A=1 B=1 | Sum=0 Carry=1

✅ 全部匹配 → 设计正确!

这时候你才可以放心进入下一步:综合、实现、生成比特流.bit文件,最后下载到 FPGA 开发板上,接 LED 观察输出。


那么,半加器真的只能用来教学吗?

当然不是。

尽管单个半加器无法独立构成多位加法器,但它是构建更复杂算术单元的基石。例如:

如何用半加器组成全加器?

全加器需要处理三个输入:A、B、Cin。我们可以这样构造:

First HA: Second HA: ┌──────┐ ┌──────┐ A ─────┤ XOR ├─── S1 ─────┤ XOR ├──→ Sum └──────┘ └──────┘ │ ↑ B ─────────┘ ┌───┴───┐ │ AND │ └───┬───┘ ┌─────▼─────┐ Cin ────────────────────┤ OR ├──→ Carry Out └───────────┘
  • 第一个半加器计算 A+B,得到 S1 和 C1
  • 第二个半加器将 S1 与 Cin 相加,得到最终 Sum
  • 两个进位(C1 和中间与门输出)通过或门合并为最终 Carry

虽然这不是最优结构(现代 FPGA 通常用进位链 carry-chain 实现高速加法),但这个思路展示了模块化设计的力量:大功能 = 小模块 + 连接逻辑。


实战建议:写好每一个“Hello World”级别的模块

哪怕是最简单的半加器,也有值得遵循的最佳实践:

建议说明
命名规范使用sum_out,carry_outs,c更清晰
添加注释模块顶部写明功能、作者、日期,方便团队协作
保持可综合性避免在always块中使用#5这类延迟(仅仿真可用)
优先使用标准语法减少对特定厂商原语的依赖,提升跨平台兼容性
参数化预留扩展性即使当前是 1bit,也可尝试定义parameter WIDTH=1

更重要的是:养成“先仿真”的习惯。哪怕你觉得逻辑很简单,也可能因为笔误导致功能异常。仿真是最快、最安全的验证手段。


结尾:从“1+1”走向“亿级门电路”

你可能觉得,一个只能算两位加法的电路,有什么意义?

但请记住:
- 每一台电脑的 ALU,都始于类似的加法单元;
- 每一块 GPU 的矩阵运算核心,背后是成千上万个并行加法器;
- 每一次神经网络推理,都在反复执行乘累加(MAC),而乘法本质也是加法的迭代。

半加器,是你踏入数字世界的第一步。
Verilog,是你与硬件对话的语言。
FPGA,是你亲手构建系统的沙盘。

当你第一次看到 LED 按照你的逻辑点亮时,你会明白:那不只是光,那是你设计的电路在呼吸。

而现在,你已经准备好迈出下一步了——试试用四个半加器做一个 4 位串行加法器?或者挑战一下超前进位加法器(CLA)?

路很长,但从这一刻起,你已经在路上了。

如果你在实现过程中遇到了问题,欢迎留言讨论。我们一起 debug,一起成长。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/6 8:18:50

IntelliJ IDEA插件:Java开发者听取异常日志语音播报

IntelliJ IDEA插件&#xff1a;Java开发者听取异常日志语音播报 在现代软件开发中&#xff0c;Java工程师面对的系统越来越复杂&#xff0c;日志量也呈指数级增长。一个典型的Spring Boot应用启动后&#xff0c;控制台滚动输出成百上千行日志信息&#xff0c;其中可能只有一两行…

作者头像 李华
网站建设 2026/1/6 8:18:46

OrCAD原理图导入Allegro布局的深度剖析

OrCAD原理图导入Allegro布局&#xff1a;从坑点到精通的实战全解析你有没有遇到过这样的场景&#xff1f;辛辛苦苦画完OrCAD原理图&#xff0c;信心满满地点击“生成网络表”&#xff0c;结果在Allegro里一导入——满屏报错&#xff1a;“Missing Footprint”、“Unresolved Ne…

作者头像 李华
网站建设 2026/1/6 8:18:30

RBAC权限控制:精细化分配不同用户的操作范围

RBAC权限控制&#xff1a;精细化分配不同用户的操作范围 在今天的AI应用生态中&#xff0c;越来越多的图形化工具让非技术人员也能轻松使用复杂的模型服务——比如通过一个网页界面就能生成高质量语音。这种低门槛的设计极大提升了用户体验&#xff0c;但也带来了一个不容忽视的…

作者头像 李华
网站建设 2026/1/6 8:17:27

技术演进中的开发沉思-294 计算机原理: 三大原则

写完计算机原理如何让程序运行的系列文章后&#xff0c;有朋友建议我写得再深入些。我想了一下&#xff0c;也是既然开写了&#xff0c;还是朝着纵深广度的方向去尝试。屏幕上跳动的光标渐渐平稳&#xff0c;像极了我这四十余年与计算机相伴的时光——从青涩年华里第一次触摸到…

作者头像 李华
网站建设 2026/1/6 8:17:27

NS-USBLoader终极指南:从零开始掌握Switch文件传输

NS-USBLoader终极指南&#xff1a;从零开始掌握Switch文件传输 【免费下载链接】ns-usbloader Awoo Installer and GoldLeaf uploader of the NSPs (and other files), RCM payload injector, application for split/merge files. 项目地址: https://gitcode.com/gh_mirrors/…

作者头像 李华
网站建设 2026/1/7 19:46:38

用户体验调研:95%受访者认为语音自然度超过预期

用户体验调研&#xff1a;95%受访者认为语音自然度超过预期 在播客、有声书和虚拟对话日益普及的今天&#xff0c;用户对语音合成质量的要求早已超越“能听清楚”的基本门槛。他们期待的是像真人一样自然、富有情绪起伏、角色分明的对话式音频——而这正是传统文本转语音&#…

作者头像 李华