1. 前言:FPGA 没有玄学,只有你没查到的点
很多人做 FPGA 项目,上板后总会遇到各种'离谱'现象,越查越懵,总以为是芯片坏了、是玄学,其实都是有迹可循的:
- 有时正常、有时不正常,没有固定规律;
- 仿真全对、波形完美,一上板就报错、跑飞;
- 拍一下板子就好,动一下接线、碰一下芯片就挂;
- 低频运行一切正常,频率一拉高就乱码、死机。
核心观点:绝大多数此类问题并非 FPGA 芯片本身的问题,而是代码、约束、硬件接线等细节未处理好。以下 10 个 BUG 覆盖了大部分'玄学故障',掌握排查思路可快速定位。
2. BUG 1:复位接法错误(占比最高,新手首踩坑)
现象:上电不稳定、程序随机出错、状态机乱跑,有时上电正常,重启后就出问题,甚至偶尔死机后重启又恢复。
原因:
- 复位电平搞反:代码里写的高有效复位(rst),硬件接成低有效(rst_n),或反之;
- 复位没有做同步:直接把按键信号硬拉当复位,没有加同步释放电路,异步复位信号不稳定;
- 多个复位网络干扰:工程里有多个复位信号(比如模块复位、全局复位),没有统一管理,互相干扰。
排查思路:
- 第一步:核对复位电平,确保硬件接线(rst_n/rst)与代码里的复位逻辑完全一致;
- 第二步:异步复位必须加同步释放,避免亚稳态;
- 第三步:不要把按键信号不处理直接当复位,按键需加消抖 + 同步,再作为复位信号;
- 第四步:统一复位网络,尽量用一个全局复位,模块复位由全局复位派生,避免多复位干扰。
3. BUG 2:亚稳态 / 跨时钟域没处理(CDC 相关,玄学重灾区)
现象:信号偶尔跳变、状态机莫名跑飞、数据偶尔错一次(不是必现),低频正常,高频报错概率大幅增加。
原因:本质是异步信号处理不当:
- 异步信号(不同时钟域)直接打一拍就用,没有做两级同步,产生亚稳态;
- 多 bit 数据直接跨时钟域传输,没有用握手机制或异步 FIFO,导致数据错乱;
- 异步 FIFO 读写指针没有用格雷码编码,跨时钟域时出现多 bit 同时变化。
排查思路:
- 单 bit 跨时钟域信号:必须用两级寄存器同步,规避亚稳态;
- 多 bit 跨时钟域信号:禁止直接打拍,必须用握手机制或异步 FIFO;
- 查看开发工具(Vivado)的 CDC 警告,不要全部屏蔽,90% 的警告都是亚稳态隐患;
- 异步 FIFO 排查:确认读写指针是否用格雷码,满空信号是否由 IP 核内部同步生成,避免手动同步导致隐患。
4. BUG 3:组合逻辑产生毛刺(高速场景必踩,仿真查不到)
现象:低速运行一切正常,高速运行就报错;仿真波形完美,上板后信号跳变异常、输出乱码。
原因:组合逻辑本身的延迟不一致,导致信号出现毛刺,高速场景下毛刺被放大:
- 关键输出信号直接由组合逻辑给出(比如 assign 直接驱动输出),没有经过时序寄存器锁存;
- 状态机用一段式编写,输出直接由状态判断给出,没有单独的输出寄存器,产生毛刺;
- 多层 assign 嵌套、组合逻辑链路过长,不同路径的延迟不一致,出现竞争冒险。
排查思路:
- 核心原则:所有外部输出信号、关键控制信号,一律经过时序寄存器(reg)锁存后再输出;
- 状态机整改:摒弃一段式,改用三段式状态机(次态、现态、输出分开),输出单独寄存;
- 简化组合逻辑:减少多层 assign 嵌套,复杂控制逻辑拆分模块,避免过长的组合逻辑链路;
- 工具排查:用 Vivado 的波形抓取功能,查看关键信号是否有毛刺,针对性优化。
5. BUG 4:没有写时序约束(时序隐患,低频侥幸、高频必炸)
现象:频率不高(比如 25MHz 以下)时能正常运行,频率一拉高(50MHz 以上)就跑飞;逻辑很简单,却频繁出现时序违规报错。
原因:本质是没有告诉工具'你的时钟频率、信号要求':
- 工程里完全没有写时序约束,工具不知道时钟频率,默认按最低频率布线,路径延迟不可控;
- 只写了时钟约束,没有给复位、跨时钟域信号设伪路径,导致大量无效时序报错,掩盖真实问题;
- 时钟周期写错(比如 100MHz 写成 period 100ns,而非 10ns),约束完全失效。
排查思路:
- 第一步:给工程添加基础时序约束,修改时钟频率即可;
- 第二步:复位信号设为伪路径(set_false_path -from [get_ports rst_n]),减少无效报错;
- 第三步:跨时钟域信号明确设置伪路径,避免工具误判时序违规;
- 核对时钟周期:用公式 1000/频率 (MHz)=周期 (ns),确认时钟约束没有写错。
6. BUG 5:管脚分配错误(低级但致命,仿真查不到)
现象:代码逻辑完全正确、仿真全过,上板后 LED 不亮、串口无数据、按键无响应,甚至芯片发热。
原因:硬件层面的基础错误,与代码逻辑无关,新手容易忽略核对:
- 管脚号写错:比如 LED 应该接 PIN20,却分配到了 PIN21,与硬件接线不匹配;
- IO 电平标准不对:FPGA 管脚设置为 3.3V 电平,外部芯片却是 1.8V,电平不匹配导致信号无法识别;
- 时钟、复位管脚没有分配在专用全局时钟脚,导致时钟不稳定、复位信号异常;
- 输入输出方向搞反:把输出信号分配到了输入管脚上,或反之。
排查思路:
- 核心步骤:对照 FPGA 芯片手册、硬件原理图,逐脚核对管脚分配,确保管脚号、输入输出方向一致;
- 时钟管脚:时钟信号必须分配在专用全局时钟脚(比如 Vivado 里的 GT_CLK、CLK_PIN),不能随便分配;
- 电平标准:统一 IO 电平标准(大多工程用 3.3V),确保 FPGA 管脚与外部芯片电平一致;
- 快速验证:给一个简单的 LED 闪烁代码,分配对应管脚,上板测试,排除管脚分配问题。
7. BUG 6:锁存器(latch)意外生成(隐蔽性强,仿真难发现)
现象:信号状态保持不住、莫名保持高电平,程序运行一段时间后卡死,状态机无法切换。
原因:代码编写不规范,综合工具意外生成了锁存器(latch),锁存器对毛刺敏感,容易出现不稳定:
- 组合逻辑的 if 语句没有写 else 分支,条件不完整,工具默认生成锁存器;
- case 语句没有写 default 分支,部分状态没有对应的处理逻辑,生成锁存器;
- 组合逻辑 always 块(always @(*))里,没有给所有输出信号赋值,信号状态无法确定。
排查思路:
- 工具排查:查看综合报告,搜索'latch',快速定位生成锁存器的模块和代码行;
- 代码整改:所有 if 语句必须有 else 分支,所有 case 语句必须有 default 分支;
- 组合逻辑规范:always @(*) 块里,确保所有输出信号(reg 定义的信号)都有赋值,不遗漏任何情况;
- 简化方案:尽量减少组合逻辑,能用时序逻辑(always @(posedge clk))就用时序逻辑,避免意外生成 latch。
8. BUG 7:时钟问题:门控时钟 / 内部分频(高速设计必踩)
现象:高速运行不稳定、时序极差,开发工具疯狂报时钟相关警告,程序偶尔死机、跑飞。
原因:时钟网络设计不规范,导致时钟信号不稳定、有抖动:
- 用计数器分频直接当时钟(比如用 always 块计数,输出作为新时钟),分频后的时钟有抖动、占空比不稳定;
- 大量使用门控时钟(比如用使能信号直接控制时钟的通断),导致时钟 skew 过大,时序违规;
- 时钟网络混乱,多个时钟没有统一管理,没有使用专用全局时钟网络。
排查思路:
- 核心原则:禁止用计数器分频直接生成新时钟,一律用'使能信号'替代(分频后作为使能,时钟仍用原时钟);
- 高速设计:必须用 FPGA 内部的 PLL/MMCM 模块生成所需时钟,确保时钟稳定、占空比可控;
- 时钟网络优化:所有时钟优先使用专用全局时钟脚和全局时钟网络,减少时钟 skew;
- 避免门控时钟:尽量不用使能信号控制时钟通断,若必须使用,启用工具的门控时钟优化功能。
9. BUG 8:仿真与上板结果不一致(新手最崩溃,找不到原因)
现象:仿真波形完美运行,所有逻辑都符合预期,一上板就完全不对,甚至没有任何有效输出。
原因:仿真环境太理想化,没有模拟真实上板场景,代码或 testbench 存在问题:
- testbench 写得太理想化,没有模拟异步信号、外部延迟、毛刺等真实场景;
- 代码里使用了仿真专用语法(不可综合语法),比如#延迟、force、release,仿真能过,上板无法综合;
- 没有模拟复位、时钟的上电时序,仿真时复位、时钟直接有效,而上板时有上电延迟。
排查思路:
- 代码规范:只写可综合代码,禁止使用不可综合语法,不确定的语法先查'是否可综合';
- testbench 优化:加入随机信号、异步信号模拟,添加亚稳态、毛刺模拟,贴合真实上板场景;
- 模拟上电时序:在 testbench 里,让复位信号先有效,延迟一段时间后释放,时钟再开始工作,和上板一致;
- 快速验证:用最简单的

