从零开始在Quartus中实现半加器:Verilog设计与仿真实战指南

你有没有试过,只用两行代码就“造出”一个能做加法的电路?听起来像魔法,但在FPGA的世界里,这就是现实。

今天我们就来动手实现一个最基础但至关重要的数字电路—— 半加器(Half Adder) 。别看它简单,这可是所有现代计算机算术运算的起点。我们将使用 Intel Quartus Prime 搭配 Verilog HDL ,从创建工程、写代码、综合分析,到用 ModelSim 做功能仿真,完整走一遍 FPGA 开发的标准流程。

无论你是电子类专业的学生,还是刚入门嵌入式逻辑设计的工程师,这篇文章都会带你一步步避开新手常踩的坑,真正理解“硬件描述语言”到底是在描述什么。


半加器:不只是两个门电路那么简单

我们先不急着打开 Quartus,先搞清楚我们要做什么。

半加器的功能非常明确:对两个一位二进制数 A 和 B 相加,输出它们的和(Sum)以及是否产生进位(Carry)。它的真值表是这样的:

A B Sum Carry
0 0 0 0
0 1 1 0
1 0 1 0
1 1 0 1

你会发现:
- 当 A 和 B 不同时,Sum = 1,Carry = 0;
- 只有当两者都为 1 时,才产生进位 Carry = 1,而本位结果 Sum = 0。

这背后其实藏着两个基本逻辑门:
- Sum = A ⊕ B → 异或门
- Carry = A · B → 与门

看起来很简单,对吧?但关键在于:这是一个 纯组合逻辑电路 ,没有寄存器、没有时钟,输出完全由当前输入决定。这种特性让它成为学习 Verilog 行为级建模的理想切入点。

更重要的是,它是构建全加器的基础。想象一下四位加法器的第一级,往往就是由一个半加器起头——因为它不需要处理来自更低位的进位输入。所以,掌握半加器的设计方法,实际上是在搭建通往复杂算术单元的第一块跳板。


在Quartus中创建工程:别让第一步卡住你

打开 Quartus Prime 后,第一步是创建新工程。很多人在这里就出了问题:路径带中文、名字乱取、器件选错……这些小细节后期会让你调试到崩溃。

正确操作步骤如下:

  1. 点击 File → New Project Wizard
  2. 设置工程目录(建议路径全英文,比如 D:\fpga_lab\half_adder_project )
  3. 工程名称填 half_adder_project ,顶层实体名也保持一致
  4. 添加文件时选择“Add all files in directory”,暂时为空也没关系
  5. 选择目标器件。如果你手头是 DE0-Nano 或类似的教学开发板,推荐选择:
    - Family: Cyclone IV E
    - Specific device: EP4CE6E22C8

✅ 小贴士:第一次选器件时容易眼花缭乱。记住,只要你的开发板型号确定了,对应的芯片也就唯一了。查手册比猜靠谱得多。

完成向导后,你会看到一个干净的工程界面。接下来,我们需要添加 Verilog 源文件。


写出第一个可综合的Verilog模块

右键左侧栏的 “Files” 区域 → New... → 选择 “Verilog HDL File”,保存为 half_adder.v 。

粘贴以下代码:

//==========================================
// 文件名:half_adder.v
// 功能:实现一位半加器(Half Adder)
// 输入:A, B(单比特)
// 输出:Sum(和),Carry(进位)
// 作者:YourName
// 日期:2025-04-05
//==========================================
module half_adder (
    input      A,
    input      B,
    output     Sum,
    output     Carry
);

    assign Sum   = A ^ B;     // XOR:生成本位和
    assign Carry = A & B;     // AND:生成进位

endmodule

这段代码虽然短,但它体现了 Verilog 设计的核心思想—— 我们不是在写程序,而是在描述硬件连接 。

  • assign 关键字表示这是一个连续赋值语句,适用于组合逻辑。
  • 每一条 assign 都对应一条物理连线:一条通向 XOR 门,另一条通向 AND 门。
  • 整个模块没有任何 always 块或 reg 类型变量,说明这是纯粹的组合逻辑,不会被综合成触发器。

编译前检查要点:

  • 所有端口方向正确吗?A/B 是 input,Sum/Carry 是 output。
  • 运算符有没有打错? ^ 是异或,不是 ~ 或 | 。
  • 模块名和文件名是否一致?这关系到后续例化和编译识别。

确认无误后,点击主菜单的 Processing → Start Compilation ,或者直接按快捷键 Ctrl+L 开始全流程编译。

如果一切顺利,你会看到最终报告:“Full Compilation successful”。


看得见的逻辑:RTL视图帮你理解综合结果

很多人写完代码就直接下载,其实错过了最重要的一步—— 验证综合结果是否符合预期 。

在 Quartus 中,你可以直观地看到你的 Verilog 被“翻译”成了什么样的电路结构。

操作路径:

Tools → Netlist Viewers → RTL Viewer

几秒后弹出的窗口中,你会看到类似下面的图形:

        A ----\
               XOR ---- Sum
        B ----/

        A ----\
               AND ---- Carry
        B ----/

没错!这就是你写的那两行 assign 语句的真实映射。Quartus 把它综合成了两个独立的逻辑门,并共享输入信号 A 和 B。

这个图的意义重大:
- 它证明了你的代码确实是可综合的;
- 它让你“看见”了硬件长什么样;
- 对于初学者来说,这是建立“软硬对应”直觉的关键一步。


功能仿真:别跳过这道安全阀

我见过太多人省略仿真环节,直接烧录到 FPGA 上去“碰运气”。结果灯不亮,不知道是代码错了、引脚配错了,还是板子坏了。

正确的做法永远是: 先仿真,再下载 。

我们来写一个简单的 Testbench,验证半加器在四种输入组合下的行为是否正确。

新建一个 Verilog 文件,命名为 tb_half_adder.v ,内容如下:

`timescale 1ns / 1ps

module tb_half_adder;

    // 信号声明
    reg  A, B;
    wire Sum, Carry;

    // 实例化被测模块(Unit Under Test)
    half_adder uut (
        .A(A),
        .B(B),
        .Sum(Sum),
        .Carry(Carry)
    );

    initial begin
        // 初始化波形记录
        $dumpfile("half_adder_sim.vcd");
        $dumpvars(0, tb_half_adder);

        // 初始状态
        A = 0; B = 0;
        #20;

        // 测试 0+1
        B = 1;
        #20;

        // 测试 1+0
        A = 1;
        B = 0;
        #20;

        // 测试 1+1
        B = 1;
        #20;

        // 结束仿真
        $finish;
    end

endmodule

关键点解析:

  • reg 用于激励输入(需要主动驱动), wire 接收输出;
  • .uut() 是实例化语法,注意端口要一一对应;
  • $dumpfile 和 $dumpvars 生成 VCD 波形文件,可在外部工具查看;
  • 时间控制用 #20 表示每个测试状态持续 20ns,足够观察变化。

使用ModelSim进行仿真验证

回到 Quartus 主界面:

Tools → Run Simulation Tool → RTL Simulation

Quartus 会自动调用 ModelSim-Altera,并加载 testbench 和被测模块。

几秒钟后 ModelSim 启动,进入交互式命令行。你可以在 TCL 控制台输入:

run 100ns

然后切换到 Wave 窗口,右键点击信号 → Add to Wave ,把 A、B、Sum、Carry 全部加进去。

运行结束后,你应该看到清晰的波形:

  • 当 A=1 且 B=1 时,Carry 上升为高电平,Sum 回落到 0;
  • 其他三种情况,Carry 始终为低,Sum 跟随 A^B 的结果变化。

对比真值表,完全吻合!

这意味着:你的设计在逻辑层面已经通过验证。现在可以放心进入下一步——硬件实现。


下载到FPGA:让理论变成现实

假设你想把半加器下载到 DE0-Nano 开发板上验证:

  1. 打开 Assignment Editor(Assignments → Pins)
  2. 分配引脚:
    - A → GPIO_0 (如 PIN_1,对应某个拨码开关 SW0)
    - B → GPIO_1 (SW1)
    - Sum → LEDR[0]
    - Carry → LEDR[1]

⚠️ 注意:务必查阅开发板原理图,确保所选引脚是通用 I/O,不要占用专用配置引脚(如 CLK、nSTATUS 等)。

  1. 再次执行全编译,生成 .sof 文件
  2. 打开 Programmer 工具,通过 USB-Blaster 下载配置

完成后,手动拨动开关 SW0 和 SW1,观察 LED 是否按预期点亮:
- 只有一个开关闭合 → LEDR[0] 亮(Sum=1)
- 两个都闭合 → LEDR[1] 亮(Carry=1)

恭喜你!你现在拥有了一个真正能做加法的硬件电路。


常见问题与避坑指南

❌ 仿真波形没变化?

  • 检查 testbench 中是否漏写了 $dumpvars
  • 查看 ModelSim 是否真的加载了正确的 design unit
  • 确保 initial 块中有实际的赋值语句

❌ 综合失败提示“Unknown identifier”?

  • 很可能是模块名拼写错误,或 testbench 中实例化的模块未被包含进工程
  • 检查 File → Add/Remove Files in Project… 是否已加入 half_adder.v

❌ 下载后LED不响应?

  • 引脚分配是否保存并重新编译?
  • 是否选择了正确的硬件(Hardware Setup 中选对 USB-Blaster)
  • 板子供电正常吗?

❌ 误用了 reg 类型导致锁存器?

  • 在组合逻辑中不要轻易使用 always @(*) + reg ,除非你知道自己在做什么
  • 本例中全部用 assign 最安全

写在最后:为什么你应该重视这个“玩具项目”

也许你会觉得:“就为了做个异或和与操作,折腾这么多?”

但请记住:

每一个复杂的CPU,都是从这样一个半加器开始生长出来的。

通过这个看似简单的练习,你实际上掌握了:
- 如何创建标准 Quartus 工程
- 如何编写可综合的 Verilog 代码
- 如何利用 RTL Viewer 理解综合结果
- 如何构建 testbench 并完成功能仿真
- 如何将设计部署到真实硬件

这套工作流(Design → Simulate → Implement)正是所有工业级 FPGA 开发的基石。未来你要做的状态机、UART、图像处理、甚至神经网络加速器,都不过是这个流程的延伸和扩展。

所以,不要轻视任何一个“简单”的例子。正是这些点滴积累,构成了你作为硬件工程师的技术底色。


如果你正在学习数字逻辑、准备课程实验,或者想转行进入 FPGA 领域,不妨现在就打开 Quartus,亲手敲一遍这段代码。当你看到 LED 按照二进制规则亮起的那一刻,你会真正体会到—— 硬件,是可以被编程的 。

欢迎在评论区分享你的实现过程和遇到的问题,我们一起讨论进步。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐