澳门特别行政区网站建设_网站建设公司_JavaScript_seo优化
2026/1/20 7:53:45 网站建设 项目流程

从门电路到FPGA:用Verilog写最“硬”的逻辑

你有没有想过,一行简单的assign y = a & b;到底在芯片里变成了什么?

它不是教科书上那两个背靠背的三角形符号,也不是电路图里的抽象框图。在一块Xilinx或Intel的FPGA内部,这行代码会被“翻译”成查找表(LUT)中的一串比特配置、布线开关的选择,甚至可能被优化得无影无踪——因为它太简单了,简单到可以和其他逻辑合并。

这就是数字设计的魅力:你写的每一行Verilog,都在和硅片上的物理资源对话。而这一切的起点,正是那些我们再熟悉不过的门电路:与、或、非、异或……

本文不讲高级综合,也不谈AI加速器架构。我们要做的,是沉下来,回到原点,用工程师的眼光重新审视这些“最基础”的单元——看看它们是如何从纸面定义,一步步变成运行在千万赫兹时钟下的真实硬件逻辑。


为什么还要手动写门电路?HLS不是更香吗?

确实,现在有Vitis HLS、Intel HLS这些工具,能让你用C++写出乘法器,自动生成RTL代码。听起来很美好,对吧?

但现实往往没那么理想:

  • 延迟不可控:HLS生成的路径可能多绕了几级寄存器,关键路径超了200ps,你就得回头改算法结构。
  • 资源利用率黑箱:你以为只是个简单的判断逻辑,结果综合出来占了十几个LUT,因为编译器没识别出你的意图。
  • 调试困难:仿真波形里一堆自动命名的中间信号,根本看不出哪段对应你原始代码的哪个分支。

这时候,掌握底层门级建模能力的价值就体现出来了。尤其是在以下场景:

  • 构建超低延迟的数据通路(比如金融交易引擎)
  • 实现确定性行为的控制逻辑(如安全关断机制)
  • 教学与原型验证(让学生真正理解布尔代数如何映射为硬件)

换句话说,当你需要精确掌控每一个门延迟、每一份资源开销时,Verilog依然是不可替代的语言


最小的积木:从AND门说起

先看一个最简单的例子——两输入与门。

module and_gate ( input a, input b, output y ); assign y = a & b; endmodule

就这么四行代码。但它背后发生的事可不少。

它真的用了“一个门”吗?

在FPGA中,并没有独立的“与门”硬件模块。所有的组合逻辑都由查找表(LUT)实现。以Xilinx Artix-7为例,每个Slice包含多个6输入LUT。也就是说,这个两输入与门只会占用LUT的4个有效条目(00→0, 01→0, 10→0, 11→1),其余592个配置位都是闲置的。

但这没关系。现代综合器足够聪明,会把多个小逻辑打包进同一个LUT。比如,如果你还写了另一个或门a | b,只要输入相同且输出不冲突,它们很可能共用同一个6-LUT!

🔍 小知识:Xilinx综合器(Vivado Synthesis)默认启用shreg_min_sizelut_combining等优化选项,自动合并冗余逻辑。

所以别担心“浪费”,除非你在顶层强制例化原语(primitive),否则综合器不会傻乎乎地为每个门分配完整资源。


OR、NOT、XOR:风格统一,命运各异

继续往下走,其他基本门的写法几乎如出一辙:

// 或门 assign y_or = a | b; // 非门 assign y_not = ~a; // 异或门 assign y_xor = a ^ b;

语法上高度一致,但在FPGA中的待遇却大相径庭。

NOT门:最小的存在感

非门是最简单的单输入函数。它的真值表只有两项。在实际实现中,综合器往往会把它“吸收”进前级或后级逻辑中。

举个例子:

wire not_a = ~a; assign y = not_a & b;

这段代码几乎肯定会被优化成一个“与非”功能直接放在LUT里,连中间信号not_a都不会保留。

这也提醒我们一点:不要依赖中间信号名进行调试,除非你加了(* keep *)(* mark_debug *)属性。

XOR门:被偏爱的那个

如果说哪个门在FPGA里过得最好,那一定是异或门。

原因很简单:它是加法器的核心。每一位全加器的求和输出 $ S = A \oplus B \oplus C_{in} $ 都依赖XOR。因此,Xilinx 7系列及以后的FPGA,在每个LUT输出端都集成了一位专用异或门,专门用于构建高效的进位链。

这意味着什么?

  • 多位异或运算(如奇偶校验)可以用树状结构高效实现
  • 延迟远低于普通组合逻辑路径
  • 综合器会优先使用该硬核路径,提升性能

所以当你写:

assign parity = ^data[7:0]; // 归约异或

综合器不会傻傻地串接8个XOR,而是生成平衡树结构,充分利用FPGA的异或网络优势。


NAND和NOR:教学意义大于工程价值?

我们都知道,NAND和NOR是功能完备的——仅用其中之一就能构造任何逻辑函数。这也是为什么CMOS工艺中,NAND门比AND门更常见:它的晶体管结构更高效。

但在FPGA世界里,这一点并不重要。

因为无论是AND还是NAND,最终都要通过LUT实现。而LUT不在乎你是“先与后非”还是“直接查表”。所以写成:

assign y = ~(a & b); // NAND

assign y = a & b; assign yn = ~y;

在综合后可能完全一样,也可能不一样,取决于上下文和优化策略。

不过,单独封装NAND/NOR模块仍有其价值:

module nand_gate(input a, input b, output y); assign y = ~(a & b); endmodule

这种做法虽然不影响硬件结果,但能清晰表达设计意图,尤其在教学或协议实现中很有用。比如你要模拟某个老式TTL芯片的行为,就必须忠实地还原其门级结构。


FPGA里的“门”到底长什么样?一张图说清楚

想象一下,你在Verilog里写了五个门:

wire w1 = a & b; wire w2 = c | d; wire w3 = ~e; wire w4 = f ^ g; wire y = w1 & w2;

你以为它们是五个独立元件?

错。

实际上,综合器看到的是这样一个过程:

  1. 分析所有输入输出关系
  2. 提取布尔函数:y = (a·b) · (c+d)
  3. 发现w3w4没被使用(假设),直接剪枝
  4. 将剩余逻辑映射为一个5输入函数(a,b,c,d,e → y)
  5. 分配到一个6-LUT中,初始化对应的真值表(INIT值)

于是,原本看似“五级门链”的逻辑,最后只用了一个LUT + 若干布线资源

这才是现代FPGA逻辑综合的真实面貌:你描述的是行为,工具决定实现方式


常见陷阱:你以为的组合逻辑,其实是锁存器

新手最容易犯的一个错误,就是无意中生成了锁存器(Latch)。

比如这段代码:

always @(*) begin if (sel == 1'b1) out = a & b; end

看起来没问题?其实不然。当sel == 0时,out没有赋值,综合器就会推断出电平敏感的锁存行为——这是组合逻辑的大忌,极易导致时序违例和毛刺传播。

正确做法是覆盖所有分支:

always @(*) begin if (sel) out = a & b; else out = 1'b0; end

或者干脆继续用assign做连续赋值,避免过程块带来的歧义。

💡 秘籍:只要你坚持使用assign实现纯组合逻辑,就不会踩这个坑。


性能优化实战:如何让门级逻辑跑得更快?

别以为“门电路”就不涉及性能调优。恰恰相反,越是底层,越容易影响全局。

技巧一:善用归约操作符

对于多输入逻辑,别这么写:

assign y = a | b | c | d | e | f | g | h;

虽然能综合,但可能生成串行结构,延迟随输入数量线性增长。

推荐写法:

assign y = |vec[7:0]; // 归约或

综合器会将其展开为平衡树结构,显著降低关键路径延迟。

技巧二:避免不必要的层级拆分

有些人喜欢把每个门都做成独立模块,然后层层例化:

nand_gate u1 (.a(a), .b(b), .y(t1)); not_gate u2 (.a(t1), .y(y));

这会导致综合器难以跨模块优化,还可能引入额外布线延迟。

除非你需要做形式验证或IP封装,否则建议保持扁平化设计,让综合器自由重组逻辑。

技巧三:关注布线延迟而非门延迟

在FPGA中,真正的瓶颈往往不是门本身,而是信号在芯片上走过的距离

一个LUT的内在延迟可能只有0.1ns,但跨SLICE布线可能增加0.3ns以上。

因此:
- 关键路径上的逻辑尽量集中布局
- 使用Pblock或RLOC约束物理位置(高级技巧)
- 必要时插入流水级打破长组合路径


调试经验:如何确认你的门电路真的“存在”?

有时候你会怀疑:我写的这个门,到底有没有被保留?

有两个方法可以验证:

方法一:查看综合后的原理图(Schematic)

在Vivado中打开Synthesized Design → Schematic,搜索你的模块名。如果被优化掉了,就找不到;如果还在,你会看到清晰的门符号或LUT表示。

方法二:使用(* keep *)保留信号

wire keep_this = a ^ b; (* keep = "true" *) wire keep_this; // 强制保留 assign y = keep_this & c;

这样即使逻辑上可优化,综合器也会为你保留该节点,方便观测。


回归本质:门电路的现代意义

今天我们花了大量篇幅讲“如何实现一个与门”,似乎有点小题大做。

但请记住:每一个复杂的FPGA系统,都是由无数这样的基础单元堆叠而成

你在UART里做的起始位检测?用到了异或门比较采样值。
你在I2C控制器里生成ACK信号?依赖与非门完成漏极开路模拟。
你在图像处理流水线中做阈值判断?本质是一个巨大的比较器网络。

而所有这些,归根结底,都是门电路的排列组合。

更重要的是,理解门级行为,能让你在面对时序问题、资源瓶颈、仿真差异时,拥有更强的直觉和排查能力

当你知道“为什么这个地方不能收敛”,而不是只会盲目加寄存器,你就离真正的硬件专家更近了一步。


如果你正在学习FPGA开发,不妨试试这个练习:

写一个四位全加器,不用+操作符,只用与、或、非、异或门来搭建。然后观察综合后的资源占用和路径延迟,再对比直接写assign sum = a + b的结果。

你会发现,工具比你想象的聪明,但也比你想象的脆弱——它需要你提供正确的“意图”,才能给出最优的“实现”

而这,正是数字设计的艺术所在。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询