从零开始:用Vivado在ego1开发板上实现4位加法器
你是不是正为数字逻辑课的大作业发愁?
“用FPGA实现一个4位加法器”——听起来挺简单,但真正动手时却发现:Vivado怎么新建工程?Verilog代码写完之后下一步该做什么?引脚怎么连?下载后LED不亮怎么办?
别急。这篇文章就是为你量身打造的实战指南。我们不讲空话,只讲能跑通、能验证、能交作业的完整流程。
我们将使用Digilent ego1 开发板 + Xilinx Vivado 工具链,一步步带你完成:
- 创建工程
- 编写结构化 Verilog 代码
- 绑定物理引脚(XDC约束)
- 生成比特流并下载到板子
- 通过拨码开关输入数据、LED显示结果
整个过程无需外部下载器、无需额外电源,一根USB线搞定所有。适合初学者快速上手,也适合作为课程报告的技术基础。
先搞清楚:我们要做什么?
目标很明确:
做一个4位二进制加法器,把两个4位数 A 和 B 相加,得到和 S[3:0] 和进位 Cout,并用 LED 显示出来。
比如:
- A = 0101(5),B = 0011(3) → S = 1000(8),Cout = 0
- A = 1011(11),B = 0110(6) → S = 0001(1),Cout = 1(因为17超过15了)
输入靠8个拨码开关(SW0~SW7)设置,输出由5个LED(S0~S3 和 Cout)指示。
听起来像组合逻辑电路实验?没错,但它运行在一个真正的 FPGA 芯片上——Xilinx Artix-7 XC7A50T。
这就是FPGA的魅力:你可以“定制硬件”,而不仅仅是写软件。
第一步:搭建开发环境(Vivado 准备)
打开你的电脑,安装好 Xilinx Vivado Design Suite(推荐 2023.1 或更新版本)。如果你还没装,去官网注册账号免费下载 WebPACK 版本即可,它完全支持 ego1 的芯片。
新建工程
启动 Vivado 后点击Create Project,进入向导模式:
- 输入项目名称,比如
four_bit_adder_ego1 - 选择 RTL Project(我们直接写代码)
- 添加源文件时先跳过(稍后再加)
- 在器件选择页,手动填写:
Part: xc7a50ticsg324-1L这是 ego1 板载 FPGA 的精确型号。选对这个,编译出来的程序才能正确烧录。
⚠️ 小贴士:如果这里选错,后面一切白搭。务必确认是
xc7a50t,不是spartan或其他系列。
第二步:编写 Verilog 代码 —— 模块化设计更清晰
现在来写核心逻辑。我们在工程中添加两个 Verilog 文件:一个是顶层模块four_bit_adder.v,另一个是全加器子模块full_adder.v。
✅ 子模块:全加器(full_adder.v)
// 全加器单元 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这是一段纯组合逻辑,没有时钟,也没有锁存器。三个输入异或得和,两个与门加一个或门产生进位。
✅ 顶层模块:4位加法器(four_bit_adder.v)
module four_bit_adder ( input [3:0] A, input [3:0] B, input Cin, output [3:0] S, output Cout ); wire [3:0] carry; // 内部进位链 // 级联四个全加器 full_adder fa0 (.A(A[0]), .B(B[0]), .Cin(Cin), .S(S[0]), .Cout(carry[0])); full_adder fa1 (.A(A[1]), .B(B[1]), .Cin(carry[0]), .S(S[1]), .Cout(carry[1])); full_adder fa2 (.A(A[2]), .B(B[2]), .Cin(carry[1]), .S(S[2]), .Cout(carry[2])); full_adder fa3 (.A(A[3]), .B(B[3]), .Cin(carry[2]), .S(S[3]), .Cout(Cout)); endmodule注意点:
- 使用wire carry[3:0]构建内部进位信号线;
- 第一级接受外部 Cin(可接低电平固定为0),最后一级输出 Cout;
- 实例化语法清晰,易于调试。
把这个文件加入工程后,Vivado 会自动识别依赖关系。
第三步:关键一步 —— 引脚约束(XDC 文件)
很多人失败就败在这一步:写了代码却没告诉工具这些信号对应哪个物理引脚。
我们需要创建一个.xdc文件,把逻辑信号绑定到 ego1 板上的实际 I/O。
📌 ego1 常用引脚对照表(摘自官方手册)
| 功能 | 信号名 | FPGA引脚 |
|---|---|---|
| 拨码开关 SW0 | A[0] | J15 |
| 拨码开关 SW1 | A[1] | L16 |
| 拨码开关 SW2 | A[2] | M13 |
| 拨码开关 SW3 | A[3] | R15 |
| 拨码开关 SW4 | B[0] | R17 |
| 拨码开关 SW5 | B[1] | T18 |
| 拨码开关 SW6 | B[2] | U18 |
| 拨码开关 SW7 | B[3] | R13 |
| LED0 | S[0] | H17 |
| LED1 | S[1] | K15 |
| LED2 | S[2] | J13 |
| LED3 | S[3] | N14 |
| LED4 | Cout | P14 |
💡 提示:这些信息来自 Digilent 官方提供的 ego1 Master XDC ,建议下载参考。
✅ 编写 XDC 约束文件
新建一个文本文件,保存为constraints.xdc,内容如下:
## Inputs - Switches set_property PACKAGE_PIN J15 [get_ports {A[0]}] set_property PACKAGE_PIN L16 [get_ports {A[1]}] set_property PACKAGE_PIN M13 [get_ports {A[2]}] set_property PACKAGE_PIN R15 [get_ports {A[3]}] set_property PACKAGE_PIN R17 [get_ports {B[0]}] set_property PACKAGE_PIN T18 [get_ports {B[1]}] set_property PACKAGE_PIN U18 [get_ports {B[2]}] set_property PACKAGE_PIN R13 [get_ports {B[3]}] ## Outputs - LEDs set_property PACKAGE_PIN H17 [get_ports {S[0]}] set_property PACKAGE_PIN K15 [get_ports {S[1]}] set_property PACKAGE_PIN J13 [get_ports {S[2]}] set_property PACKAGE_PIN N14 [get_ports {S[3]}] set_property PACKAGE_PIN P14 [get_ports {Cout}] ## Set I/O standard (3.3V LVCMOS for ego1) set_property IOSTANDARD LVCMOS33 [get_ports]最后那句IOSTANDARD LVCMOS33很重要,确保电平匹配板载资源。
将此文件添加到工程中,并勾选 “Used in Implementation”。
第四步:综合 → 实现 → 生成比特流
回到 Vivado 主界面,按顺序执行以下操作:
Run Synthesis(综合)
→ 把 Verilog 转成门级网表
→ 查看报告:用了多少 LUT、触发器等(你应该看到只有几个 LUT,毕竟只是组合逻辑)Run Implementation(实现)
→ 布局布线,映射到真实 FPGA 资源
→ 工具检查是否满足时序(虽然本设计无时钟,仍需完成此步)Generate Bitstream(生成比特流)
→ 输出.bit文件,用于下载
如果中途报错,请查看 Tcl Console 中的具体提示:
- 常见错误包括拼写错误(如S0写成s0)、端口未连接、引脚重复分配等。
- 所有信号必须被约束,否则无法通过实现阶段。
第五步:下载到 ego1 开发板
连接硬件
用一条 Micro USB 线,连接电脑和 ego1 上标有PROG UART的接口。
Windows 会自动安装驱动吗?不一定。为了确保识别成功,请提前安装:
👉 Digilent Adept Runtime
安装完成后,在设备管理器中应能看到类似Digilent USB Device或FTDI的串口出现。
下载程序
在 Vivado 中:
- Open Hardware Manager
- Open Target → Auto Connect
- Program device → 选择你的
.bit文件 → Program
几秒钟后,Program Done!程序已烧录进 FPGA。
此时,FPGA 已经变成一台“专用加法器硬件”。
第六步:动手测试 —— 拨动开关看结果
现在轮到你亲自验证了!
尝试下面几组输入:
| A (SW3~SW0) | B (SW7~SW4) | 预期 S (LED3~LED0) | Cout (LED4) |
|---|---|---|---|
| 0000 | 0000 | 0000 | 0 |
| 0101 (5) | 0011 (3) | 1000 (8) | 0 |
| 1111 (15) | 0001 (1) | 0000 | 1 |
| 1010 (10) | 0110 (6) | 0000 | 1 |
💡观察技巧:
- LED 亮表示“1”,灭表示“0”
- 注意高低位顺序:SW0 是最低位,对应 A[0]
- 如果结果不对,先查 XDC 是否引脚接反了
常见问题排查清单
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 下载失败,设备未识别 | 驱动未安装 / USB线有问题 | 安装 Adept,换线重试 |
| LED 全不亮 | 输出引脚配置错误 | 检查 XDC 中 LED 引脚编号 |
| 结果总是错 | 输入信号反了(高位低位颠倒) | 核对 SW 与 A/B 的映射 |
| 编译报错 “Unspecified I/O” | 某信号未加约束 | 补全所有端口的 PACKAGE_PIN |
| 波形仿真正常但板上异常 | 逻辑设计假设了同步时序 | 本设计应为纯组合逻辑,勿加 clk |
🔍 推荐做法:在下载前先做一次功能仿真!
你可以写个简单的 Testbench 验证逻辑:
// testbench.v module tb; reg [3:0] A, B; reg Cin; wire [3:0] S; wire Cout; four_bit_adder uut (.A(A), .B(B), .Cin(Cin), .S(S), .Cout(Cout)); initial begin $dumpfile("tb.vcd"); $dumpvars(0, tb); Cin = 0; #10 A=4'b0101; B=4'b0011; #10 A=4'b1111; B=4'b0001; #10 $finish; end endmodule然后在 Vivado 中启用 XSIM 进行波形仿真,提前发现问题。
教学意义不止于“做完作业”
你以为这只是为了应付大作业?其实你已经走完了完整的 FPGA 开发闭环:
✅ 编写 HDL → ✅ 添加约束 → ✅ 综合实现 → ✅ 下载验证
这套流程适用于任何数字系统设计:计数器、状态机、UART通信、甚至图像处理。
更重要的是,你亲手把一段代码变成了看得见摸得着的硬件行为——这才是 FPGA 最迷人的地方。
可以怎么继续升级?
完成了基本功能,不妨挑战一下扩展任务,让答辩更有亮点:
✅ 加个使能控制(Enable)
增加一个使能信号EN,只有当 EN=1 时才更新输出,避免误触。
✅ 改成带寄存功能的加法器
引入时钟clk和寄存器,在上升沿锁存输入并计算,实现同步设计。
✅ 接七段数码管显示十进制
利用 PMOD 接口外扩数码管模块,把二进制结果显示为阿拉伯数字。
✅ 设计简易 ALU
增加一个操作选择信号OP,支持加法、减法、与、或等多种运算。
这些都不是遥不可及的功能,只要你掌握了今天的这套方法论,都可以一步步实现。
写在最后:从代码到硬件的跨越
当你第一次拨动开关,看到 LED 按照预期亮起的时候,那种“我真的让硬件动起来了”的成就感,是写普通程序很难体会到的。
这篇指南没有堆砌术语,也没有故弄玄虚,而是实实在在地告诉你:
- 怎么建工程
- 怎么写代码
- 怎么绑引脚
- 怎么下板子
- 怎么调bug
每一个步骤都经过实测可行,每一段代码都能复制粘贴直接用。
希望你能顺利完成大作业,更希望能点燃你对 FPGA 和数字系统设计的兴趣。
如果你在实现过程中遇到其他问题,欢迎留言交流。我们一起把硬件玩明白。