news 2026/1/31 18:05:14

从零实现FPGA上的加法器电路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零实现FPGA上的加法器电路

手把手教你用FPGA从零搭建一个加法器:不只是“1+1=2”

你有没有想过,计算机里最简单的“1+1”,背后其实是一场精密的硬件协奏?

在如今动辄讨论AI大模型、GPU加速的时代,我们很容易忽略——所有复杂的运算,最终都建立在像加法器这样最基础的数字电路之上。而如果你想真正理解硬件是怎么“算数”的,最好的方式不是背公式,而是亲手在FPGA上搭一个出来。

本文就带你从零开始,不用任何IP核,不调现成模块,一行行写Verilog代码,一步步把两个二进制数相加的功能实现在真实的FPGA开发板上。你会看到:
- 为什么说全加器是数字世界的“原子”单元
- 如何用几个逻辑门拼出“3+5=8”?
- 为什么看似简单的“进位”会成为性能瓶颈?
- 最后,如何把你的设计烧进FPGA,用拨码开关输入、LED灯输出结果,亲眼见证硬件在“思考”。

这不仅是一个教学项目,更是一次对数字系统底层逻辑的深度还原。


加法器的本质:不只是数学,更是电路行为

我们先抛开FPGA工具链和代码,回到最原始的问题:怎么让硬件做加法?

软件里一句a + b编译后可能变成一条CPU指令,但在硬件层面,它必须被拆解为一系列物理信号的流动——高电平代表1,低电平代表0,通过晶体管组成的逻辑门完成判断与传递。

以两个1位二进制数为例:

A = 1, B = 1 → Sum = 0, Carry = 1

这个过程不能靠“记忆”,只能靠当前输入决定输出。换句话说,这是一个典型的组合逻辑电路:没有寄存器、没有状态机,只有即时响应。

于是我们定义一种基本结构:全加器(Full Adder),它可以同时处理两个数据位和一个来自低位的进位(Cin),输出本位和(Sum)与新的进位(Cout)。它的真值表长这样:

ABCinSumCout
00000
01010
10010
11001
11111

别急着记,我们可以从中推导出关键公式:

  • Sum = A ⊕ B ⊕ Cin
  • Cout = (A & B) | (Cin & (A ^ B))

这两个表达式就是整个加法器的“灵魂”。它们意味着什么?
- 异或(XOR)决定了是否产生“和”;
- 与(AND)和或(OR)则捕捉了两种进位场景:两数都是1,或者其中一者为1且已有进位。

这些操作都可以用电路上可实现的基本门来构建。也就是说,只要你有足够多的与门、或门、异或门,就能造出任意精度的加法器。

而这,正是FPGA的魅力所在:你可以直接操控硬件的行为,而不是等待操作系统调度。


第一步:打造最小单元——一位全加器

我们现在进入实战环节。目标很明确:用Verilog HDL描述一个功能正确的全加器。

module full_adder( input A, input B, input Cin, output Sum, output Cout ); wire xor1_out; wire and1_out; wire and2_out; assign xor1_out = A ^ B; assign Sum = xor1_out ^ Cin; assign and1_out = A & B; assign and2_out = Cin & xor1_out; assign Cout = and1_out | and2_out; endmodule

这段代码看起来简单,但每一步都有讲究:

  • 使用wire定义中间信号,保持组合逻辑特性;
  • 先计算A ^ B,避免重复运算;
  • 进位分为两个部分:“本位进位”(A & B)和“传递进位”(Cin & (A^B)),最后合并。

📌 小贴士:虽然现代综合器能自动优化冗余逻辑,但手动展开有助于理解内部结构,在调试时也能更快定位问题。

这个模块可以独立仿真验证。比如测试A=1, B=1, Cin=1,应得Sum=1, Cout=1—— 即十进制中的1+1+1=3,二进制表示为11

一旦单个全加器跑通,下一步就是把它当成“积木块”,搭更大的系统。


第二步:级联升级——构建4位波纹进位加法器

现在我们要处理的是真正的数值了。比如想算3 + 5,对应的二进制是:

0011 + 0101 ------- 1000

这就需要四个全加器串联起来,形成所谓的波纹进位加法器(Ripple Carry Adder, RCA)

它的结构非常直观:
- 每一位对应一个全加器;
- 上一级的Cout接下一级的Cin
- 最低位的Cin可设为0(除非用于减法补码运算);

下面是Verilog实现:

module four_bit_adder( 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), .Sum(Sum[0]), .Cout(c1)); full_adder fa1 (.A(A[1]), .B(B[1]), .Cin(c1), .Sum(Sum[1]), .Cout(c2)); full_adder fa2 (.A(A[2]), .B(B[2]), .Cin(c2), .Sum(Sum[2]), .Cout(c3)); full_adder fa3 (.A(A[3]), .B(B[3]), .Cin(c3), .Sum(Sum[3]), .Cout(Cout)); endmodule

注意这里的连接顺序:c1 → c2 → c3 → Cout,构成了清晰的进位链。

这种设计体现了FPGA开发的核心思想之一:模块化复用。你不需要每次都重新设计逻辑,只要确保子模块正确,就可以像搭乐高一样构建复杂系统。

不过也要意识到它的局限性——速度受限于进位传播延迟。因为第4位必须等第3位算完才能开始,就像接力赛跑一样,每一棒都要等前一棒交棒。

对于4位来说还好,延迟微乎其微;但如果是32位甚至64位整数加法,这种结构就会严重拖慢整体性能。这也是后来出现超前进位加法器(CLA)的原因。

但现在,我们的目标是“从零实现”,所以RCA是最合适的选择。


第三步:仿真验证——让代码先在虚拟世界跑通

写完代码不等于万事大吉。下一步是功能仿真,确保逻辑无误再上板。

我们写一个简单的Testbench来驱动four_bit_adder

module four_bit_adder_tb; reg [3:0] A, B; reg Cin; wire [3:0] Sum; wire Cout; // 实例化被测模块 four_bit_adder uut ( .A(A), .B(B), .Cin(Cin), .Sum(Sum), .Cout(Cout) ); initial begin $monitor("T=%0t | A=%b (%d), B=%b (%d), Cin=%b | Sum=%b (%d), Cout=%b", $time, A, A, B, B, Cin, Sum, Sum, Cout); // 测试用例 #10 A = 4'b0011; B = 4'b0101; Cin = 0; // 3 + 5 = 8 #10 A = 4'b1111; B = 4'b0001; Cin = 0; // 15 + 1 = 16 → 应产生进位 #10 A = 4'b1010; B = 4'b0110; Cin = 1; // 10 + 6 + 1 = 17 → 多进位测试 #10 $finish; end endmodule

运行ModelSim或Vivado Simulator后,你会看到类似输出:

T=0 | A=0011 (3), B=0101 (5), Cin=0 | Sum=1000 (8), Cout=0 T=10 | A=1111 (15), B=0001 (1), Cin=0 | Sum=0000 (0), Cout=1 T=20 | A=1010 (10), B=0110 (6), Cin=1 | Sum=0001 (1), Cout=1

全部符合预期!说明我们的加法器在逻辑层面已经可靠。

💡 坑点提醒:如果发现Sum错乱,优先检查引脚映射是否错位;若Cout始终为0,可能是最后一级Cout未正确连接到模块输出。


第四步:部署到真实FPGA——让硬件“亮”起来

终于到了激动人心的时刻:把设计下载到实际的FPGA芯片上。

这里以Xilinx Artix-7系列开发板(如Nexys A7)为例,使用Vivado工具链完成全流程。

1. 创建工程并添加源文件

  • 新建RTL工程,选择目标器件(如XC7A35TCSG324-1);
  • 添加full_adder.vfour_bit_adder.v
  • 添加测试平台用于仿真。

2. 引脚约束(XDC文件)

为了让FPGA知道哪个引脚接开关、哪个接LED,我们需要编写约束文件:

## 输入:使用8位拨码开关控制 A[3:0] 和 B[3:0] set_property PACKAGE_PIN J15 [get_ports {A[0]}] # 对应SW0 set_property PACKAGE_PIN L16 [get_ports {A[1]}] # SW1 set_property PACKAGE_PIN M13 [get_ports {A[2]}] # SW2 set_property PACKAGE_PIN R15 [get_ports {A[3]}] # SW3 set_property PACKAGE_PIN R17 [get_ports {B[0]}] # SW4 set_property PACKAGE_PIN T18 [get_ports {B[1]}] # SW5 set_property PACKAGE_PIN U17 [get_ports {B[2]}] # SW6 set_property PACKAGE_PIN W16 [get_ports {B[3]}] # SW7 set_property PACKAGE_PIN H18 [get_ports Cin] # SW8 控制进位输入 ## 输出:使用LED显示结果 set_property PACKAGE_PIN U16 [get_ports {Sum[0]}] # LD0 set_property PACKAGE_PIN E19 [get_ports {Sum[1]}] # LD1 set_property PACKAGE_PIN F18 [get_ports {Sum[2]}] # LD2 set_property PACKAGE_PIN D17 [get_ports {Sum[3]}] # LD3 set_property PACKAGE_PIN D18 [get_ports Cout] # LD4 显示进位 ## 设置电气标准 set_property IOSTANDARD LVCMOS33 [get_ports]

✅ 提示:务必对照开发板原理图确认引脚编号,否则可能导致烧录失败或IO损坏。

3. 综合、实现、生成比特流

  • 点击Run Synthesis→ 查看资源使用情况(典型情况下:约8~10个LUTs);
  • 执行Implementation→ 工具会进行布局布线;
  • 查看Timing Summary→ 确保无建立/保持时间违例;
  • 生成.bit文件并通过JTAG下载到板子。

4. 实物验证

打开电源,拨动开关设置A=0011,B=0101,观察LED:
- LD3~LD0 应显示1000(即8)
- LD4(Cout)熄灭

再试一组:A=1111, B=0001→ 结果应为0000并点亮进位灯,表示溢出。

这一刻,你不再是“调别人写的代码”,而是真正创造了能计算的硬件


性能与优化:当“进位”成了瓶颈

虽然RCA结构简单易懂,但它有个致命弱点:延迟随位宽线性增长

假设每个全加器的进位延迟为 Δt,那么n位加法器的最大延迟就是 n×Δt。这对高速应用来说不可接受。

解决办法是什么?

超前进位加法器(Carry Look-Ahead Adder, CLA)

其核心思想是:提前预测每一位是否会生成或传播进位,从而打破串行依赖。

引入两个新概念:
-Generate (G)= A & B → 无论Cin如何都会产生进位
-Propagate (P)= A ^ B → 当Cin=1时将进位传下去

然后可以直接写出各级进位:
- C1 = G0 | (P0 & Cin)
- C2 = G1 | (P1 & G0) | (P1 & P0 & Cin)
- …

这样就不必逐级等待,大大缩短关键路径。

虽然实现稍复杂,但在高位宽场景下性能提升显著。这也是现代处理器ALU中普遍采用的技术。

但对于初学者而言,先掌握RCA的意义在于:你清楚地看到了性能瓶颈的根源在哪里


写在最后:这不是终点,而是起点

当你第一次用手拨动开关,看着LED亮起那一刻的结果,你会有一种特别的成就感——这不是程序输出,而是电流在硅片中流动的真实痕迹

这个小小的加法器项目,涵盖了许多深远的主题:
-组合逻辑设计原则
-模块化与层次化构建方法
-HDL编码风格与可综合性
-仿真验证流程
-FPGA工具链实战
-硬件调试技巧

更重要的是,它教会你一种思维方式:自底向上,层层抽象

未来的ALU、状态机、流水线CPU、甚至神经网络加速器,本质上都是由这样的基础单元堆叠而来。

所以别小看这个“只会加法”的电路。它是通往数字世界深处的第一扇门。


如果你正在学习FPGA,不妨今晚就动手试试。找一块入门级开发板,照着这篇教程走一遍。遇到问题没关系,调试的过程本身就是最好的学习。

毕竟,最好的硬件工程师,从来都不是只懂理论的人,而是能让灯亮起来的那个。

欢迎在评论区晒出你的实物照片或仿真截图,一起交流踩过的坑、绕过的弯路。我们一起,把“不可能”变成“已实现”。

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

Hourglass:Windows系统终极免费倒计时工具完整指南

Hourglass:Windows系统终极免费倒计时工具完整指南 【免费下载链接】hourglass The simple countdown timer for Windows. 项目地址: https://gitcode.com/gh_mirrors/ho/hourglass 在快节奏的数字时代,精准的时间管理已成为提升工作效率的关键因…

作者头像 李华
网站建设 2026/1/31 13:36:34

终极GSE宏编译器完整指南:5分钟快速上手魔兽世界自动化

终极GSE宏编译器完整指南:5分钟快速上手魔兽世界自动化 【免费下载链接】GSE-Advanced-Macro-Compiler GSE is an alternative advanced macro editor and engine for World of Warcraft. It uses Travis for UnitTests, Coveralls to report on test coverage and …

作者头像 李华
网站建设 2026/1/26 19:57:49

ASMR下载实战:用asmr-downloader轻松打造个人专属音频库

ASMR下载实战:用asmr-downloader轻松打造个人专属音频库 【免费下载链接】asmr-downloader A tool for download asmr media from asmr.one(Thanks for the asmr.one) 项目地址: https://gitcode.com/gh_mirrors/as/asmr-downloader 想要随时随地享受优质ASM…

作者头像 李华
网站建设 2026/1/25 4:52:57

CHFSGUI终极指南:3分钟搭建个人文件共享服务器

CHFSGUI终极指南:3分钟搭建个人文件共享服务器 【免费下载链接】chfsgui This is just a GUI WRAPPER for chfs(cute http file server) 项目地址: https://gitcode.com/gh_mirrors/ch/chfsgui 还在为复杂的文件服务器配置而头疼吗?CHFSGUI项目正…

作者头像 李华
网站建设 2026/1/30 13:02:45

GSE宏编辑器终极指南:5分钟掌握魔兽世界高级宏编写

GSE(Gnome Sequencer Enhanced)是一款专为魔兽世界设计的高级宏编译器,它彻底改变了传统宏的编写方式,让玩家能够轻松创建复杂的技能序列和智能循环。无论你是新手玩家还是资深高手,GSE都能为你的游戏体验带来革命性的…

作者头像 李华
网站建设 2026/1/31 17:35:30

FAE医学影像分析平台:让放射组学研究变得简单高效

FAE医学影像分析平台:让放射组学研究变得简单高效 【免费下载链接】FAE FeAture Explorer 项目地址: https://gitcode.com/gh_mirrors/fae/FAE 还在为复杂的医学影像分析而头疼吗?FAE(FeAture Explorer)医学影像分析平台正…

作者头像 李华