基于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 必须带 default if-else 尽量配对
注释模块功能 文件头注明作者、日期、功能描述
避免混合逻辑类型 不要把组合逻辑和时序逻辑混在一个 always 块中
顶层统一管理时钟 让组合逻辑保持“干净”

❌ 必须规避的雷区

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

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

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

📡 通信协议中的CRC校验

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

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

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

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

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

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


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

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

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

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

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

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

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

Read more

Midjourney搞定科研论文封面图!3步出刊级作品,拒被审稿人打回

Midjourney搞定科研论文封面图!3步出刊级作品,拒被审稿人打回

点赞、关注、收藏,不迷路 点赞、关注、收藏,不迷路 搞科研的你,是不是在论文封面图上栽过太多跟头?自己用PPT画的封面又丑又廉价,完全撑不起学术格调;找设计公司定制,不仅收费贵(动辄几百上千元),沟通反复修改耗时久,还总get不到你研究的核心主题;好不容易凑出来的封面,又被审稿人质疑“不贴合研究内容”“不符合期刊规范”,直接拖慢论文发表进度;更头疼的是,想参考顶会封面风格,却不知道怎么落地,只能对着别人的作品羡慕? 如果你也深陷这些困境,别再硬扛!昨天和某重点项目评审专家聊天,专家做了分享。小编做了系统梳理。今天这篇实操指南,直接带你用Midjourney快速制作出刊级科研论文封面图,从核心要素梳理、Prompt精准撰写,到生成筛选、后期微调,每个步骤都有具体逻辑和可直接复用的模板,不管你是Midjourney新手还是科研小白,跟着做就能3步搞定符合期刊要求的封面图,让审稿人眼前一亮! 一、先搞懂:科研论文封面图的核心要求,避免踩坑 学术期刊对封面图有明确规范,盲目用Midjourney生成只会白费功夫。核心要求有3点:一是主题契合,必须精准体现研究核心内容(

实测GLM-ASR-Nano-2512:超越Whisper V3的语音识别效果

实测GLM-ASR-Nano-2512:超越Whisper V3的语音识别效果 1. 引言:端侧语音识别的新标杆 随着大模型技术向终端设备下沉,轻量化、高性能的本地语音识别模型成为开发者关注的焦点。近期,智谱AI开源了其新一代语音识别模型 GLM-ASR-Nano-2512,该模型以1.5B参数量在多个基准测试中表现优于OpenAI的Whisper V3,同时支持本地部署与实时交互,兼顾性能与隐私保护。 本文将基于实际部署和测试经验,深入分析GLM-ASR-Nano-2512的技术特性、运行方式、识别效果,并与Whisper V3进行多维度对比,帮助开发者判断其在真实场景中的适用性。 1.1 为什么需要端侧ASR? 传统云端语音识别虽精度高,但存在三大痛点: * 延迟不可控:网络传输带来额外延迟,影响交互体验; * 隐私风险:用户语音上传至服务器,敏感信息易泄露; * 离线不可用:无网络环境下无法使用。 而端侧ASR(Automatic Speech Recognition)通过在本地完成语音转文字任务,有效解决了上述问题。尤其在智能硬件、办公输入法、边缘计算等场

Qwen-Image-Lightning体验报告:中文语义理解超强的AI画师

Qwen-Image-Lightning体验报告:中文语义理解超强的AI画师 自从Qwen图像系列模型发布以来,它在中文多模态理解与生成领域持续展现出独特优势。不同于依赖英文提示词工程的主流文生图模型,Qwen系列从底层就深度适配中文语义结构——而最新推出的Qwen-Image-Lightning,正是这一技术路线的集大成者:它不是简单地“支持中文”,而是真正让中文成为创作的原生语言。 本文将从真实使用场景出发,不堆砌参数、不罗列指标,全程聚焦一个核心问题:当你输入一句地道的中文描述时,它到底能不能听懂?听懂之后,又能不能把那种只可意会的意境,稳稳当当地画出来? 1. 为什么说它是“中文语义理解超强”的AI画师? 很多用户试过用中文提示词生成图片,结果却不如英文稳定。原因往往不在模型本身,而在语义断层——中文的意象表达、虚实转换、文化隐喻,和英文的直白逻辑存在天然差异。 Qwen-Image-Lightning的突破点,恰恰在于它继承了Qwen-VL系列对中文语义空间的长期建模能力。它不把“水墨丹青中国龙”拆解为“ink painting, Chinese dragon,

Java在AI时代的崛起:从传统机器学习到AIGC的全栈解决方案

Java在AI时代的崛起:从传统机器学习到AIGC的全栈解决方案

个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[[email protected]] 📱个人微信:15279484656 🌐个人导航网站:www.forff.top 💡座右铭:总有人要赢。为什么不能是我呢? * 专栏导航: 码农阿豪系列专栏导航 面试专栏:收集了java相关高频面试题,面试实战总结🍻🎉🖥️ Spring5系列专栏:整理了Spring5重要知识点与实战演练,有案例可直接使用🚀🔧💻 Redis专栏:Redis从零到一学习分享,经验总结,案例实战💐📝💡 全栈系列专栏:海纳百川有容乃大,可能你想要的东西里面都有🤸🌱🚀 目录 * Java在AI时代的崛起:从传统机器学习到AIGC的全栈解决方案 * 一、Java AI生态概览:多样化的技术选择 * 1.1 深度学习框架:接轨主流AI技术 * Deep Java Library