跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
编程语言

Verilog 描述半加器:FPGA 硬件入门实战

基于 Verilog 语言设计半加器电路,涵盖真值表分析、数据流与门级建模、Testbench 仿真验证及常见工程误区。通过 FPGA 底层逻辑实现加法运算,帮助初学者建立硬件思维,从组合逻辑入手理解数字系统设计核心原理。

氛围发布于 2026/3/27更新于 2026/4/264 浏览

为什么从半加器开始?

初学 FPGA 或数字电路时,很多人一上来就想搞图像处理、跑神经网络。结果呢?卡在第一个时钟信号就动不了了。

其实,真正该做的第一件事是理解组合逻辑的本质。而半加器,就是通往这个世界的钥匙。

它只做一件简单的事:把两个比特 A 和 B 相加,输出它们的'和'与'进位'。听起来像小学数学题,但它背后藏着三个关键知识点:

  1. 布尔代数的实际应用(异或、与运算)
  2. 无状态的组合逻辑行为
  3. 模块化设计的基本范式

更重要的是,它的结构清晰、验证简单、结果直观——非常适合点亮你的第一个 LED 灯前先点亮大脑。

半加器的工作原理:不只是公式

我们来看一组真值表:

| A | B | Sum | Carry | | --- | --- | --- | | 0 | 0 | 0 | 0 | | 0 | 1 | 1 | 0 | | 1 | 0 | 1 | 0 | | 1 | 1 | 0 | 1 |

观察一下:

  • 当输入不同(0+1),Sum=1,Carry=0;
  • 当输入相同且为 1(1+1),Sum=0,Carry=1 —— 这正是二进制进位!

所以我们可以写出两个表达式:

Sum = A ⊕ B
Carry = A · B

是不是很简洁?但这不是重点。重点是:这些符号如何变成真实世界里的电路?答案就是——Verilog。

用 Verilog 描述硬件:数据流建模方式

在 FPGA 设计中,我们不'调用函数',而是'搭建电路'。下面这段代码就是在'画一张电路图':

module half_adder (
    input wire A,
    input wire B,
    output wire Sum,
    output wire Carry
);
    assign Sum = A ^ B;      // 异或门出'和'
    assign Carry = A & B;    // 与门出'进位'
endmodule

关键细节解读

  • input wire / output wire:声明端口方向和类型。注意这里都用了 wire,因为这是纯组合逻辑输出。
  • assign:连续赋值语句,专用于 wire 类型。只要 A 或 B 变化,Sum 和 Carry 就立即重新计算。
  • 没有 always 块,没有时钟,也没有寄存器 —— 完全符合组合逻辑特征。

✅ 提示:如果你在这里用了 reg 并放在 always @(*) 中赋值,也不是错,但对初学者容易混淆组合/时序逻辑边界。建议从 assign 开始建立正确直觉。

这段代码经过综合工具(如 Vivado 或 Quartus)处理后,会被映射成 FPGA 内部的 LUT(查找表)资源,物理上等效于一个异或门加一个与门。

更贴近硬件的写法:门级描述

如果你想看得更'透'一点,可以用内置原语直接例化逻辑门:

module half_adder_gate (
    input wire A,
    input wire B,
    output wire Sum,
    output wire Carry
);
    xor(Sum, A, B);   // 使用 Verilog 内置 XOR 原语
    and(Carry, A, B); // 使用 AND 原语
endmodule

这种写法更像是在搭积木:

  • xor(Sum, A, B) 表示:'接一个异或门,输入是 A 和 B,输出连到 Sum'
  • 工具会直接将其绑定到底层硬件单元

📌 适用场景:教学演示、精确控制门延迟分析、或者你想告诉别人'我确实只用了两个门'。

但在实际工程中,推荐使用数据流建模(assign),因为它更简洁、可读性强,且综合效果一样优秀。

如何验证你的设计?别跳过仿真!

写完代码就烧进板子?No no no。硬件开发的第一法则:能仿则仿。

我们需要一个测试平台(Testbench),来自动检查四种输入组合是否全部通过。

`timescale 1ns / 1ps
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

说说这个 Testbench 的精妙之处

  • $monitor:每当时序推进,自动打印当前状态。比手动插入 $display 更省事。
  • #10:表示等待 10 个时间单位(由 timescale 定义为 1ns),模拟信号切换间隔。
  • 最终输出应如下:
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

如果看到最后一行 Sum=0、Carry=1,恭喜你!你的半加器已经通过了功能验证。

🔍 小技巧:很多初学者漏掉某个输入组合,导致 Carry 错误未被发现。一定要遍历全部可能输入!

常见坑点与避坑指南

我在带学生做这个实验时,发现以下几个问题反复出现:

❌ 误区一:给组合逻辑输出声明为 reg,却不放在 always 块里

比如这样写:

output reg Sum; // 错误!声明为 reg 却没有过程赋值

但后面还是用 assign 赋值 → 综合报错或产生意外锁存器。

✅ 正确做法:

  • 输出是组合逻辑 → 用 wire
  • 若必须用 reg(例如在 always 中赋值),则要配合 always @(*)

❌ 误区二:误加时钟,把组合逻辑写成时序逻辑

有人习惯性加上 clk 和 always @(posedge clk),结果输出延迟一个周期。

记住一句话:半加器没有记忆,不需要时钟。

一旦加了时钟,你就不再是'即时响应'的加法器,而是一个带延迟的状态机了。

❌ 误区三:忽略端口连接顺序,例化时出错

尤其是在使用门级原语时:

xor(Sum, A, B); // 正确:第一个是输出,后两个是输入

但如果写反了:xor(A, Sum, B); —— 那就完全乱套了。

部署到物理设备:下一步做什么?

当你完成仿真并确认逻辑正确后,就可以进入物理实现阶段:

  1. 创建工程,添加 half_adder.v 和约束文件(.xdc 或 .qsf)
  2. 分配引脚:比如让 A 接按键 SW0,B 接 SW1,Sum 和 Carry 接 LED0、LED1
  3. 综合 → 实现 → 生成比特流
  4. 下载到开发板

然后动手试试:

  • 按下不同组合的开关,观察 LED 亮灭情况
  • 当两个输入都为高时,Carry 对应的 LED 应该亮起

那一刻你会感受到一种独特的成就感:我写的代码,真的在'通电运算'。

从半加器出发,你能走多远?

别看它简单,全加器(Full Adder)其实就是两个半加器拼起来再加一个或门。

你可以尝试扩展:

  • 把两个 half_adder 组合成 full_adder
  • 级联四个 full_adder 构成 4 位加法器
  • 加入进位链优化,挑战超前进位加法器(Carry-Lookahead Adder)

每一步都在复刻真实 CPU 内部的 ALU 演化路径。

甚至在未来,你可以基于这套思想去实现:

  • 快速乘法器
  • CRC 校验生成器
  • 浮点运算单元的定点部分

写在最后:硬件思维的觉醒

学 FPGA 不是学会写 Verilog 语法就够了,而是要学会用硬件的方式思考问题。

当你看到 A + B,不再想到'调用 add 函数',而是想到'一组并行传播的逻辑门网络',你就入门了。

而这一切,可以从一个只有两行赋值语句的半加器开始。

动手建议清单

  • 编写 half_adder 模块
  • 搭建 testbench 并运行仿真
  • 观察波形图(可用 GTKWave 或 Vivado Simulator)
  • 下载到开发板验证
  • 尝试改写为门级结构
  • 用两个半加器构建全加器(进阶挑战)

如果你正在学习数字系统设计,不妨把今天的代码保存下来。未来某天回看,你会感谢那个认真对待'最简单电路'的自己。

目录

  1. 为什么从半加器开始?
  2. 半加器的工作原理:不只是公式
  3. 用 Verilog 描述硬件:数据流建模方式
  4. 关键细节解读
  5. 更贴近硬件的写法:门级描述
  6. 如何验证你的设计?别跳过仿真!
  7. 说说这个 Testbench 的精妙之处
  8. 常见坑点与避坑指南
  9. ❌ 误区一:给组合逻辑输出声明为 reg,却不放在 always 块里
  10. ❌ 误区二:误加时钟,把组合逻辑写成时序逻辑
  11. ❌ 误区三:忽略端口连接顺序,例化时出错
  12. 部署到物理设备:下一步做什么?
  13. 从半加器出发,你能走多远?
  14. 写在最后:硬件思维的觉醒
  15. 动手建议清单
  • 💰 8折买阿里云服务器限时8折了解详情
  • 💰 8折买阿里云服务器限时8折购买
  • 🦞 5分钟部署阿里云小龙虾了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 系统性学习大模型:从原理到实战
  • 生产环境 Nginx 双机热备部署 - Keepalived 多模式配置
  • 基于 Python Reflex 搭建 ZeroClaw 本地 AI 管理面板
  • C# 扩展 Dynamics 365 Copilot:自定义插件与场景实战
  • GPT 模型架构与训练过程解析
  • 多模态 AI 技术解析:视觉与语言融合的新范式
  • 数据结构指南:堆
  • VS Code Copilot 接入第三方 OpenAI 兼容模型实战指南
  • 麦橘超然 WebUI 新手指南:本地部署 Flux 图像生成服务
  • 基于 Spring Boot 与 WebSocket 的 Java 实时聊天室系统
  • Layui 集成 Unity WebGL 时 Tab 切换黑屏的解决方案
  • GCC 编译器使用与调试基础指南
  • AI Agent Skills 资源合集:支持 Cursor/Claude/Copilot 一键部署
  • Spring Boot Web 后端开发核心注解详解
  • AI 不是机器人:它到底是什么?
  • JVM 运行时数据区域详解
  • 链表核心算法实战:LeetCode Hot 100 经典题目解析
  • AI 绘画报错修复:CheckpointLoaderSimple 模型缺失处理
  • NVIDIA Jetson 部署 LeRobot 端到端机器人学习流程
  • 零基础入门 8MAV:首个无人机编程实战

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online