基于Verilog的组合逻辑电路FPGA完整示例

从零开始:用Verilog在FPGA上实现一个真正的组合逻辑电路

你有没有过这样的经历?明明代码写得“很对”,仿真也跑通了,结果烧进FPGA后LED就是不亮——最后发现是因为某个case语句漏了个分支,综合器悄悄给你塞了个锁存器?

这正是无数初学者在FPGA开发中踩过的坑。而这一切的根源,往往就出在组合逻辑电路设计这个看似简单的起点上。

今天,我们就来彻底讲清楚一件事:如何用Verilog,在FPGA上正确、高效地实现一个纯粹的组合逻辑电路。不只是“能跑”,而是要理解每一步背后的硬件行为


为什么组合逻辑是FPGA的“基本功”?

别看它名字普通,组合逻辑其实是整个数字系统设计的地基。

想象一下,你在做一个图像处理系统,每一帧有百万像素,每个像素都要做一次阈值判断。如果交给CPU逐个处理,早就卡死了;但如果你用组合逻辑把它做成并行电路——百万个比较器同时工作,一拍完成,这才是FPGA的真正威力。

它的核心特征非常明确:

输出只取决于当前输入,没有记忆,没有时钟驱动。

这意味着什么?
- 它响应极快(仅受门延迟限制);
- 它天然支持大规模并行;
- 它的行为完全可预测;
- 它不消耗触发器资源,省面积、省功耗。

但在FPGA的世界里,“理想”和“现实”之间,往往隔着一个综合器。我们写的Verilog代码,并不会原封不动变成电路——它会被翻译、优化、重构。所以,我们必须学会“像硬件一样思考”。


先看两个例子:一个对,一个错

✅ 正确示范:4位奇偶校验生成器

目标很简单:输入4位数据in[3:0],输出parity=1表示其中有奇数个‘1’。

module parity_gen ( input [3:0] in, output parity ); assign parity = ^in; endmodule

就这么一行?没错。

^in是Verilog的归约异或操作符,等价于in[3]^in[2]^in[1]^in[0]。由于使用了assign,这是一个纯组合逻辑赋值,综合工具会直接将其映射为一条异或链。

最终在FPGA内部,它会被放进一个LUT(查找表)里。以Xilinx Artix-7为例,一个6输入LUT足以容纳这个函数,无需任何寄存器。


❌ 经典错误:你以为是组合逻辑,其实生成了锁存器

再来看一个多路选择器的写法:

always @(*) begin if (sel == 2'b00) out = data_in[0]; else if (sel == 2'b01) out = data_in[1]; else if (sel == 2'b10) out = data_in[2]; // 注意!这里漏了 sel==2'b11 的情况!! end

看起来好像没问题?语法没错,仿真也可能“凑合”跑通。

但问题来了:当sel=2'b11时,out没有被赋值。那它该保持原来的值吗?可这是组合逻辑啊,不应该有“原来”的概念!

于是综合器陷入两难:你要我保持状态,又不给我时钟——没办法,只能推断出一个锁存器(Latch)来维持旧值。

结果就是:
- 多消耗了寄存器资源;
- 引入了不必要的存储行为;
- 可能导致时序违例或毛刺传播;
- 在某些工艺下甚至无法布线成功。

这就是典型的“因分支不全而误生成锁存器”。

🔍 提示:打开综合报告,搜索latch关键词,就能快速定位这类隐患。


那么,到底该怎么写才安全?

方法一:用assign—— 简单逻辑首选

适用于表达式可以直接写出的情况:

assign out = (a & b) | (~c); assign y = sel ? in1 : in0; // 二选一MUX

清晰、直观、不可能出错。只要看到assign,你就知道这是纯组合逻辑。

方法二:用always @(*)—— 复杂逻辑的主力

对于多条件判断或多路选择,推荐使用always @(*)

always @(*) begin case(sel) 2'b00: out = data[0]; 2'b01: out = data[1]; 2'b10: out = data[2]; 2'b11: out = data[3]; default: out = 1'b0; // 必须加default! endcase end

几点关键提醒:
- 敏感列表必须是@(*)@*,让工具自动包含所有输入;
- 所有分支必须覆盖完整,包括default
- 使用阻塞赋值=,不是<=
- 不要在块内使用时钟或复位控制。

记住一句话:在组合逻辑的always块里,永远不要出现posedge clk这种东西。


FPGA内部发生了什么?—— LUT与组合逻辑的映射原理

你可能听说过:“现代FPGA是基于查找表(LUT)架构的”。那这句话到底意味着什么?

简单说,FPGA里的每一个小逻辑单元(比如Xilinx的CLB),都包含若干个可编程的真值表。比如一个4输入LUT,本质上是一个16×1的小RAM,你可以预设每个输入组合对应的输出值。

当我们写:

assign y = a ^ b ^ c ^ d;

综合器会分析这个布尔函数,计算出它的真值表,然后把这张表“烧”进某个LUT中。从此以后,只要输入变化,LUT立刻输出对应结果——这就是组合逻辑的物理实现方式。

📌 小知识:归约异或^in在4输入情况下只需要一个4-LUT即可实现;如果是6输入,则可能需要级联多个LUT。

这也解释了为什么组合逻辑具有确定性延迟:信号从输入到输出,最多经过几级LUT和布线延迟,路径固定,时间可控。


实战全流程:从代码到板子上的LED

光说不练假把式。下面我们走一遍完整的FPGA开发流程,看看从写代码到看到LED亮起,中间究竟经历了什么。

第一步:写模块 + 写测试平台

先完成我们的奇偶校验模块,再写一个测试激励:

// tb_parity_gen.v module tb_parity_gen; reg [3:0] in; wire parity; // 实例化被测模块 parity_gen uut (.in(in), .parity(parity)); initial begin $monitor("Time=%0t | Input=%b | Parity=%b", $time, in, parity); in = 4'b0000; #10; in = 4'b0001; #10; // 1个1 → odd → parity=1 in = 4'b0011; #10; // 2个1 → even → parity=0 in = 4'b0111; #10; // 3个1 → odd → parity=1 in = 4'b1111; #10; // 4个1 → even → parity=0 $finish; end endmodule

运行仿真(ModelSim/Vivado Simulator),你会看到:

Time=0 | Input=0000 | Parity=0 Time=10 | Input=0001 | Parity=1 Time=20 | Input=0011 | Parity=0 Time=30 | Input=0111 | Parity=1 Time=40 | Input=1111 | Parity=0

完美匹配预期。说明逻辑正确。


第二步:综合(Synthesis)

进入Vivado或Quartus,创建项目,添加源文件和测试平台,执行综合。

重点检查综合报告中的以下内容:

检查项应关注点
Unconnected ports是否有悬空端口
Inferred latches是否意外生成锁存器(应为0)
LUT usage使用了多少个LUT(本例应为1)
Netlist hierarchy模块是否被正确识别

如果一切正常,你会看到类似信息:

Found 1 unisim elements for binding No latches generated Used 1 LUT4

这才敢放心往下走。


第三步:实现(Implementation)

包括三个阶段:
1.Translate:将综合后的网表转换为目标器件格式;
2.Map:将逻辑单元映射到具体FPGA资源(如LUT、IOB);
3.Place & Route:决定元件位置并连接走线,生成精确时序模型。

此时工具会告诉你:
- 最大组合路径延迟是多少(例如 2.1ns);
- 是否满足时序约束(虽然组合逻辑通常无时钟约束,但仍需关注建立/保持时间边界);
- 资源利用率统计。


第四步:生成比特流 & 下载验证

生成.bit文件,通过JTAG下载到FPGA开发板。

假设我们连接如下:
-in[3:0]接拨码开关;
-parity接一个LED。

动手测试:
- 拨动开关为0110(两个1)→ LED灭(parity=0);
- 拨动为1101(三个1)→ LED亮(parity=1)。

灯随输入实时变化,没有任何延迟感——这就是硬件并行的魅力。


工程师必须掌握的设计规范

为了避免低级错误拖慢进度,建议遵循以下实践准则:

✅ 推荐做法清单

规范说明
命名清晰输入用in_i_,输出用o_out_,内部信号用tmp_
全覆盖分支case必须带defaultif-else尽量配对
注释模块功能文件头注明作者、日期、功能描述
避免混合逻辑类型不要把组合逻辑和时序逻辑混在一个always块中
顶层统一管理时钟让组合逻辑保持“干净”

❌ 必须规避的雷区

错误后果
always @(*)中漏else分支生成锁存器
使用非阻塞赋值<=在组合逻辑中仿真与综合行为不一致
忘记声明reg类型用于always块输出综合报错
在敏感列表中手动列输入易遗漏,应使用@(*)

它能用在哪里?真实场景告诉你

别以为这只是教学玩具。组合逻辑在实际工程中无处不在:

📡 通信协议中的CRC校验

  • 输入一串数据,实时计算校验码;
  • 全靠组合逻辑并行完成多项式异或运算。

🖼️ 图像处理中的像素级操作

  • 对每个像素做if (pixel > threshold)判断;
  • 成千上万个比较器同时工作,实现毫秒级响应。

⚙️ 控制系统中的紧急停机逻辑

  • 多个传感器信号“任意一个为高则立即切断电源”;
  • 用一个大或门实现,零延迟响应。

这些任务如果交给软件轮询,要么太慢,要么占用CPU太多资源。而用组合逻辑,一次性布好线路,永远在线监听,才是硬核解决方案。


写在最后:通往复杂系统的起点

你现在掌握的,不仅仅是一个奇偶校验器,而是通往FPGA世界的大门钥匙。

几乎所有复杂的数字系统,都是由一个个小小的组合逻辑模块搭建而成:
- 加法器 → ALU → CPU;
- 译码器 → 地址总线 → 存储控制器;
- 多路选择器 → 数据通路 → 流水线结构。

当你有一天去设计一个RISC-V核心或者H.264编码器时,你会发现,那些炫酷的功能背后,依然是最基本的“输入→逻辑→输出”链条。

所以,请认真对待每一次assignalways @(*)的书写。因为它们不仅是代码,更是你亲手绘制的电路图。

如果你正在学习FPGA,不妨现在就动手:
1. 把上面的parity_gen跑一遍仿真;
2. 改成8位输入再试一次;
3. 再试着做一个3-8译码器。

实践出真知。欢迎在评论区分享你的实验结果和遇到的问题,我们一起解决。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1146027.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ESP32中断深扒:从 BOOT 按键到 NMI,一顿操作猛如虎

目录 实物图 原理图 ESP32-IDF 示例代码解析 关键知识点 硬件 中断矩阵:ESP32 的“交警系统” 映射方法:给中断“分配房间” 中断状态 软件 枚举类型 函数 使用开发板上的按键&#xff0c;开关灯 实操练习 实现功能 提示 实物图 原理图 找到 KEY 部分 可以看出 b…

ESP32中断深扒:从 BOOT 按键到 NMI,一顿操作猛如虎

目录 实物图 原理图 ESP32-IDF 示例代码解析 关键知识点 硬件 中断矩阵:ESP32 的“交警系统” 映射方法:给中断“分配房间” 中断状态 软件 枚举类型 函数 使用开发板上的按键&#xff0c;开关灯 实操练习 实现功能 提示 实物图 原理图 找到 KEY 部分 可以看出 b…

低成本信号发生器实现高精度波形输出方法

用一块STM32做出实验室级信号发生器&#xff1f;揭秘低成本高精度波形输出的底层逻辑你有没有遇到过这样的场景&#xff1a;调试一个音频电路&#xff0c;手头却没有信号源&#xff1b;做传感器激励实验&#xff0c;只能靠函数发生器租借&#xff1b;或者在嵌入式项目中想生成一…

为一个杯子开14次会,却在汽车上用手机芯片,这叫高品质?蒙谁呢!

某车企为了强调自己的电车品质高&#xff0c;说为了一个作为汽车周边产品的杯子就开了14次会议&#xff0c;以此来说明它对品质的重视&#xff0c;然而人们却清楚它在汽车上用了手机芯片&#xff0c;重要、涉及安全的汽车芯片却用了存在安全隐患、不耐用的手机芯片&#xff0c;…

UltraScale+ PCIe Gen4在Vivado2025中的实现方案

基于UltraScale的PCIe Gen4设计&#xff1a;Vivado 2025实战指南在当前高性能计算、AI推理加速和高速数据采集系统中&#xff0c;FPGA作为可编程异构计算核心&#xff0c;正越来越多地通过PCIe Gen4与主机CPU/GPU进行低延迟、高带宽互联。Xilinx&#xff08;现AMD&#xff09;的…

UltraScale+ PCIe Gen4在Vivado2025中的实现方案

基于UltraScale的PCIe Gen4设计&#xff1a;Vivado 2025实战指南在当前高性能计算、AI推理加速和高速数据采集系统中&#xff0c;FPGA作为可编程异构计算核心&#xff0c;正越来越多地通过PCIe Gen4与主机CPU/GPU进行低延迟、高带宽互联。Xilinx&#xff08;现AMD&#xff09;的…

基于功耗和散热的续流二极管选型策略系统学习

续流二极管选型的“看不见的敌人”&#xff1a;功耗与散热实战解析在一块小小的电源板上&#xff0c;你可能不会注意到那颗不起眼的贴片二极管——它没有MOSFET那样高频开关的炫技&#xff0c;也不像电感那样体积庞大引人注目。但一旦系统突然宕机、芯片莫名击穿&#xff0c;排…

基于FPGA的数字频率计设计:完整指南

从零构建高性能数字频率计&#xff1a;FPGA实战全解析你有没有遇到过这样的场景&#xff1f;手头有个信号发生器&#xff0c;输出一个神秘的方波&#xff0c;你想知道它的频率到底是多少——是1.234 kHz还是1.235 kHz&#xff1f;普通万用表只能给你个大概&#xff0c;示波器又…

Vivado版本兼容性对ego1开发板大作业的影响说明

Vivado版本问题如何悄悄毁掉你的ego1大作业&#xff1f;你有没有遇到过这种情况&#xff1a;明明代码逻辑没问题&#xff0c;仿真也通过了&#xff0c;XDC约束写得清清楚楚&#xff0c;可下载到ego1开发板上时&#xff0c;LED不亮、数码管乱码&#xff0c;甚至根本烧录失败&…

循迹小车转向机构优化:项目应用解析

从“画龙”到“点睛”&#xff1a;如何让Arduino循迹小车真正“看得准、转得稳”你有没有遇到过这样的场景&#xff1f;花了一整天时间组装好一辆Arduino循迹小车&#xff0c;代码烧录成功&#xff0c;电机嗡嗡作响&#xff0c;信心满满地把它放到赛道上——结果刚出直道就左右…

i.MX硬件加速集成指南:Yocto环境配置

i.MX硬件加速集成实战&#xff1a;从Yocto环境搭建到系统验证你有没有遇到过这样的场景&#xff1f;手头的i.MX8M Plus开发板明明配备了NPU和VPU&#xff0c;但跑起AI模型来速度还不如树莓派&#xff1b;用GStreamer播放4K视频时CPU占用飙到90%以上——这说明&#xff0c;你的硬…

i.MX硬件加速集成指南:Yocto环境配置

i.MX硬件加速集成实战&#xff1a;从Yocto环境搭建到系统验证你有没有遇到过这样的场景&#xff1f;手头的i.MX8M Plus开发板明明配备了NPU和VPU&#xff0c;但跑起AI模型来速度还不如树莓派&#xff1b;用GStreamer播放4K视频时CPU占用飙到90%以上——这说明&#xff0c;你的硬…

有源蜂鸣器和无源区分:频率控制深度剖析

蜂鸣器选型实战&#xff1a;有源与无源的本质区别&#xff0c;不只是“能不能变音”这么简单你有没有遇到过这种情况——项目快量产了&#xff0c;突然发现报警音太单调&#xff0c;想让蜂鸣器“唱个调”&#xff0c;结果一查才发现用的是有源蜂鸣器&#xff0c;压根没法换频率…

大规模工业产线中的Vivado许可证优化使用:实践分享

大规模工业产线中的Vivado许可证优化实践&#xff1a;从“抢不到”到高效复用在一家大型通信设备制造商的FPGA开发中心&#xff0c;每天早上9点刚过&#xff0c;工程师们的工位上几乎同时亮起了Vivado IDE。有人开始修改逻辑设计&#xff0c;有人启动批处理脚本跑回归测试&…

使用hbuilderx开发电商小程序多规格选择完整示例

用HBuilderX开发电商小程序&#xff0c;搞定多规格选择的完整实战你有没有遇到过这种情况&#xff1a;用户在商品详情页点来点去&#xff0c;好不容易选完颜色和尺码&#xff0c;结果一确认——“抱歉&#xff0c;该组合无货”&#xff1f;这种体验简直让人抓狂。而更糟的是&am…

VSCode - 显示EOL字符的插件

VSCode自身没有显示EOL字符的功能&#xff0c;可以通过扩展插件来实现。 在插件市场搜索到&#xff1a; Render Line Endings。 点击安装&#xff0c;Publisher&#xff1a;Josip Medved&#xff0c;选择相信第一次从此publisher安装程序。 This extension renders end of li…

大模型的“牛顿难题”:为什么AI读遍人类所有书籍,仍无法发现万有引力?

来源&#xff1a;今日头条当所有人都在追逐GPT-5的幻想时&#xff0c;一位前谷歌工程师出身的老板揭示了AI发展的真正天花板&#xff1a;大模型永远无法成为牛顿。本文深度剖析了语言局限性与概率系统本质这两大根本缺陷&#xff0c;并提出了下一代AI可能的突破方向——从神经符…

Multisim14.0到NI Ultiboard的无缝导出操作指南

从仿真到制板&#xff1a;手把手教你实现 Multisim14.0 到 NI Ultiboard 的高效协同设计 你有没有过这样的经历&#xff1f;在 Multisim 里把电路图画得清清楚楚&#xff0c;仿真波形也跑通了&#xff0c;信心满满地准备做 PCB 板——结果一导出&#xff0c;飞线乱成一团、封装…

从零实现多层PCB生产流程:实验室级小批量制作方案

实验室里的“芯片工厂”&#xff1a;如何亲手做出一块四层PCB&#xff1f;你有没有过这样的经历&#xff1f;设计好了一块精密的四层板&#xff0c;满怀期待地发给厂家打样&#xff0c;结果等了五天&#xff0c;收货一看——线宽偏差、孔铜太薄、甚至内层错位。更糟的是&#x…

构建轻量级嵌入式OS:Yocto内核裁剪全面讲解

如何用 Yocto 打造极致轻量的嵌入式 Linux 系统&#xff1f;从内核裁剪讲起你有没有遇到过这样的场景&#xff1a;一块 64MB 的 Flash&#xff0c;跑不进一个“最小”Linux系统&#xff1b;设备冷启动要等七八秒&#xff0c;用户还没操作就已经失去耐心&#xff1b;明明只是个数…