拓冰建站拓冰建站
首页 / 资讯中心 / 正文

从零开始学FPGA:基于小梅哥AC620的Verilog与Vivado实战笔记

FPGA这玩意光看理论是真的学不进去的。我当年也是把王金明的《数字系统设计与Verilog HDL》翻了大半本合上书发现自己连一个能跑的工程都搭不出来真正开始有感觉还是靠一块板子、一个完整的工程、一次成功点亮LED的经历。这篇笔记是我基于小梅哥的Xilinx FPGA开发板AC620芯片是Artix-7系列的XC7A35T从零开始学Verilog、用Vivado做开发的全过程记录覆盖了Vivado环境搭建、Verilog编码习惯、UART/I2C/SPI这些常用接口的实现、IP核的使用以及那些在工程里反复出现的报错和最终解法。不管你是刚接触数字电路的学生还是在工作中第一次接触到FPGA的开发人员这篇学习笔记都会比教科书更有参考价值。因为我记录的不只是“怎么做”更包括“为什么这么做”以及“踩坑之后怎么跳出来”——这些都是文档里不会写的东西。1. 学习路线与开发板选型小梅哥这套方案为什么适合入门先说开发板。小梅哥这块AC620用的是Xilinx Artix-7家族的XC7A35T属于中低端但足够折腾的芯片逻辑单元有33280个DSP Slice有90个块RAM有225个对于入门到进阶的各种实验完全够用而且配套资料真的全。它跟正点原子、黑金这些板子的核心差异是教程节奏小梅哥的视频是“手把手带你写代码”不是只给你一个例程让你抄而是从模块划分开始讲这对我这种一开始根本不知道代码该怎么组织的人来说帮助极大。选型这件事我有几句实在话不要一上来就买VU9P那种大芯片没必要资源多了反而让你不知道自己在学什么。入门阶段的目标是把“编辑器里写出代码到板上跑起来”这条链路跑通Artix-7这个级别完全满足。配套的Vivado版本也很关键AC620的例程在Vivado 2015.4到2019.1这些版本上都跑过我自己用的是Vivado 2020.2后面章节会详细说明版本选择的理由。学习路线的安排上我建议按照“环境搭建 → 语法基础 → 简单时序逻辑 → 状态机 → 接口协议 → 系统集成”这个顺序推进别跳级。很多新手一上来就想搞DDR3、搞图像处理结果连always块和连续赋值都没搞明白项目卡住之后特别打击信心。这条路线我实际走下来大约花了两到三个月前提是每天能保证两小时以上的连续时间。还有一个容易被忽略的地方仿真一定要从第一天就开始练。不要觉得上板能跑就行仿真能看到内部信号变化是排查逻辑问题的根本手段。我后面写UART接收模块时如果没有Testbench仿真光靠上板调试根本不知道是波特率不对、采样点不对还是状态机跳飞了。任何一次上板成功前都应该先让仿真通过。2. Vivado开发环境搭建与安装过程中的典型坑2.1 版本选择与安装流程Vivado的版本选择是有讲究的。官方每年出一个大版本但并不是“越新越好”。如果你买的开发板例程是较早写的新版本Vivado打开旧工程时经常需要迁移IP这一迁移就是一堆报错。我自己测下来2020.2是一个兼容性和稳定性都比较好的版本既能打开2018、2019年的工程也能正常创建新项目。2022以后的版本对系统配置要求明显提高性能一般的笔记本跑综合会特别慢。安装的时候有几个硬性条件提前确认好磁盘剩余空间至少60GB注意是至少因为Vivado本体加库文件加生成文件很容易飙到80GB以上内存16GB起步8GB跑大工程会出现卡死实测20分钟左右无响应是常事系统路径千万不能有中文否则Synth阶段会报一些莫名其妙的路径错误。安装组件选择上新手用“Vivado”单版本就够了不需要装SDK和Vitis那东西体积巨大还会拖慢安装速度。等到后面需要做Zynq软核开发再补装也不迟。安装过程总耗时通常在20到40分钟取决于磁盘速度。这里要提醒的是安装过程中千万别频繁取消或切换窗口Vivado安装程序对中断处理得不好中断后残留的注册表信息会导致重装失败到时候清理起来非常痛苦。2.2 License配置不掏钱也能正常用很多人卡在License这一步其实Xilinx提供了免费的WebPACK License针对Artix-7、Spartan-7这些中低端芯片完全够用。在Vivado启动时选择“Obtain License”里的“Get Free Vivado WebPACK Edition License”它会引导你登录Xilinx官网账号在线生成License文件绑定的是你的电脑网卡MAC地址所以别在虚拟机里申请会绑定虚拟网卡的MAC导致换环境失效。生成后的License文件放在本地然后在Vivado的Help - License Manager里加载。这里有个我踩过的坑License文件路径不要放在带空格的目录里比如“Program Files”就不太合适因为Vivado读取时偶尔会解析出错导致提示license找不到放在D:/license/这种自定义目录最稳。检查License是否生效就看软件启动后首页右上角是否有“WebPACK”标识有就说明芯片资源限制的模式已激活。2.3 WinPcap安装失败与SDK卸载残留问题安装Vivado过程中经常遇到的一个报错就是“WinPcap 4.1.3 installation failed”这是Vivado为了网络调试功能自动调用的外部组件。WinPcap如果之前装过旧版本或者系统里有其他软件占用了它的驱动安装就会失败。这时候不要直接点“Retry”死磕正确做法是先取消当前的Vivado安装手动安装WinPcap 4.1.3的完整安装包装好后再重新运行Vivado安装程序它检测到WinPcap已存在就会跳过这一步。还有一个高频问题是有同学机器上装了Vivado 2015.4的SDK组件后来想卸载却发现控制面板里根本找不到卸载入口。这是因为Xilinx的安装器在早期版本里把卸载信息写在了自己的注册表分支里需要通过运行安装光盘里的“xsetup”命令进入维护模式选择Uninstall或者用Vivado安装目录下的“Uninstall.exe”挨个清理。强行删除安装目录会导致后续新版本装不上因为注册表残留的组件信息会让新安装器认为老版本还在。3. Verilog语法学习中的关键点与编码习惯养成3.1 组合逻辑与时序逻辑脑子里的那道分界线Verilog语法本身不复杂难的是你写完代码后脑子里得有一张电路图。组合逻辑就是不寄存信号、输出随时跟随输入变化的电路时序逻辑则是依赖时钟沿触发的寄存器电路。在写代码前先问自己一句这个模块是接到哪个时钟域的输出需要打几拍这个问题想清楚代码组织结构就清晰了。我一开始经常犯的错是把所有逻辑都写在同一个always块里导致组合逻辑和时序逻辑混在一起代码看着跑通了但一到时序分析就乱套。后来按小梅哥课程里反复强调的方式执行组合逻辑用assign连续赋值或者用always (*)块时序逻辑一律用always (posedge clk)块并且只在里面给寄存器赋值。保持这个习惯后代码的可读性和可维护性提升了一大截。这里补充一个组合逻辑的例子一个简单的全加器module full_adder ( input wire a, input wire b, input wire cin, output wire sum, output wire cout ); assign sum a ^ b ^ cin; assign cout (a b) | (a cin) | (b cin); endmodule全加器虽然简单但它能帮助你理解“硬件描述语言”和“编程语言”的本质区别你不是在“执行”语句你是在“描述电路”。多写几个这样的基础模块后再进状态机会顺很多。3.2 计数器与for循环最容易翻车的两个基础点计数器是FPGA里最基础的时序逻辑没有之一。用计数器实现分频、定时、波特率产生、按键消抖全是同一套思路。计数器的核心就是两句话always (posedge clk or negedge rst_n) begin if (!rst_n) cnt 0; else if (cnt CNT_MAX) cnt 0; else cnt cnt 1; end写计数器时最容易翻车的有三个地方计数上限多一还是少一边界条件用大于等于还是等于复位是高有效还是低有效。我建议统一采用“低有效复位等于上限清零”的模板因为小梅哥的板子复位键是低有效和模板匹配可以少烧脑。另外计数器的位宽要预先算好要计的数是N计数器位宽就是ceil(log2(N1))不够位宽时计数器回绕会产生意想不到的分频结果。for循环在Verilog里很容易被误解成“软件循环”其实它在综合时会被展开成一大片重复电路。所以循环变量的声明必须是integer类型而且循环次数必须是常数否则工具没法展开。很多编译错误都是因为循环边界里用了变量比如for (i 0; i n; i i 1)如果n是输入信号综合直接报错。实际工程里for循环一般用来做重复的位拼接操作、寄存器数组的批量复位或者滑动窗口滤波里的数据搬移这些场景用起来是很舒服的但别拿它去“跑算法流程”。3.3 阻塞赋值与非阻塞赋值血泪教训后的统一规则在always块里赋值的等号有阻塞赋值()和非阻塞赋值()之分两者生成的电路结构完全不同。阻塞赋值在语句执行时立即更新值表现为串联的逻辑计算非阻塞赋值则是所有语句并行计算在时钟沿到达时统一更新表现的是寄存器行为。给初学者的建议是在时序逻辑的always块里只用非阻塞赋值在组合逻辑的always块里只用阻塞赋值。这条规则看似机械但它能避免90%的竞争冒险问题。我曾经在一个移位寄存器模块里混用了两种赋值方式仿真波形上看数据总是比预期慢一拍后来对照RTL原理图才明白是因为阻塞赋值打乱了并行更新顺序这里特别感谢小梅哥课程里“先画电路再写代码”的那句告诫。多写几个有意识的测试用例去对比两者差异比背一百遍规则都管用。4. 第一个真正跑起来的项目从LED流水灯到UART收发4.1 流水灯背后发生了什么很多教程会拿流水灯当第一个例程因为代码短、效果直观但它背后的流程是完整的。新建工程选择芯片型号——板载的XC7A35T是FTG256封装Vivado里可以直接选择对应的器件型号然后编写源码、编写约束文件、综合、实现、生成比特流、下载到板子。这一串流程第一次走通后你对FPGA开发的整体节奏就有了体感。流水灯代码其实就是一个随时间变化的移位寄存器但这里面隐藏着两个重要概念分频和复位。板载50MHz晶振直接驱动计数器产生1kHz的时钟再用这个时基去控制LED的移位快慢。你可以试着改改分频系数看看LED亮灭速度的变化这比看任何仿真波形都更能建立时钟周期与时间的对应感。上板前最后一步是写XDC约束文件这个文件是把代码里的信号名和芯片引脚对应的关键。比如流水灯模块的led[0]要接到板上J12排针的某个引脚得先查原理图手册找到引脚号再在XDC里写set_property PACKAGE_PIN K17 [get_ports {led[0]}]。引脚约束写错或者漏写Implement阶段会直接报错或警告严重时下载后板子毫无反应。这里要专门提醒下约束文件的重要性。很多初学朋友只写引脚约束不写时钟约束结果工程也能跑但遇到稍微复杂的时序逻辑就开始随机崩溃。正确做法是从第一个工程开始就在XDC里加上时钟约束create_clock -period 20.000 -name sys_clk [get_ports clk]50MHz对应的周期就是20ns这个约束告诉Vivado芯片的时钟输入频率工具才能正确评估内部时序是否收敛。4.2 按键消抖与边沿检测时序敏感性的入门训练做流水灯的时候板上的复位键用得不多做交互控制后必然要处理按键。物理按键按下和松开时由于弹跳会产生多次高低电平跳变如果不消抖一次按键会被逻辑识别成很多次。消抖的标准做法是用计数器维持20毫秒的稳定窗口在这段时间内如果采样到的电平始终如一才认为按键状态改变。对应的核心逻辑在小梅哥例程里写得很清楚检测到按键电平与上一个周期不同时计数器清零重新计数只有计数器达到20ms对应的计数值才输出有效按键电平。消抖之后还得做边沿检测因为一个按键事件的本质是“按下”这个边沿而不是“按下后的持续电平”。边沿检测的标准姿势是先用一级寄存器打拍再用组合逻辑产生上升沿或下降沿脉冲always (posedge clk) begin key_r key_in; end assign key_pos key_in ~key_r; // 上升沿这种打拍取边沿的手法在UART、SPI、I2C等所有异步信号处理里都会反复用到。我后来回看自己的学习路程按键消抖和边沿检测才是真正把“时序逻辑”装进脑子的转折点。4.3 UART接收模块从仿真到上板的一次通过UART是串行通信中最常用的协议之一结构不复杂但很有代表性。发送端把数据按起始位、数据位、校验位、停止位的方式逐比特发送接收端则要根据波特率在每个bit的中心时刻采样。实现UART接收端的关键有三个波特率计数、位采样点、状态机。我用16倍频采样来实现UART_RX也就是说把每个bit的宽度再细分16段在中间位置采样可靠性更高。状态机用典型的空闲、起始位、数据位、停止位四状态每接收完一个bit就把波特率计数器清零。这里最让我受益的是花时间把仿真Testbench写好用模拟的串行波形注入模块观察rx_data和rx_done信号是否和输入一致。实测下来只要仿真里能正确解析一帧8-8-1格式的数据上板基本不会出问题。上板调试时比较坑的是按键触发的毛刺或者电气连接不稳但最终确认了用USB转串口助手发数据接收端用逻辑分析仪抓信号就一目了然。UART模块是后续一切调试功能如ILA在线逻辑分析仪、上位机通信的基础所以值得多花时间打磨。写完UART_RX后我建议立刻写UART_TX发送模块收发都通了你对串行通信的理解就立住了。5. 常用接口协议与IP核的实战拆解5.1 I2C读写EEPROM状态机和时序对齐的练习我在小梅哥例程里接触到的I2C器件是EEPROM 24LC04B。I2C协议看着简单实际上手就会碰到麻烦因为它要求严格的主从时序配合SCL和SDA的每个上升沿、下降沿都有讲究。写I2C控制器时状态机的跳转直接对应总线上的一帧字节比如启动信号、设备地址、寄存器地址、数据字节、停止信号每个状态都要精确控制SCL高电平期间SDA的稳定和跳变窗口。波形对齐是I2C最大的坑。SCL低电平期间SDA可以变化SCL高电平期间SDA要保持稳定这个规则理解不到位写出来的时序偶尔能通偶尔就挂。我当时的调试策略是先仿真抓SCL和SDA的波形对比数据手册上的时序图一个边沿一个边沿地核对然后上板读回EEPROM里的数据对比是否写入一致。用状态机写I2C控制器的过程也极大增强了我在状态编码、状态跳转条件、输出时序这三者间的协调能力这是做UART时感受不到的。I2C例程里一个细节值得提EEPROM在写入一个字节后需要等待内部写周期通常几毫秒这个等待要在控制器里用计数器产生延时不能在代码里写#5ms这样的仿真延时。行为仿真里的延时语句只能仿真用综合时会被工具忽略上了板根本不存在“等待”效果这是区分可综合代码和不可综合代码的经典例子。5.2 SPI与ADC数据采集: 时钟极性和相位别搞反SPI协议比I2C简洁得多但它的核心难点在四种时钟模式的选择。SPI有CPOL时钟极性和CPHA时钟相位两个参数决定数据在SCK的哪个边沿被采样、哪个边沿被切换。一款具体的ADC芯片会明确标注它工作在模式几比如ADI的AD7606一般是模式2或模式3如果你的控制器配置成模式0去读它数据位就是对不齐。先从概念上理清CPOL0表示空闲时SCK为低CPOL1表示空闲时SCK为高CPHA0表示第一个跳变沿采样CPHA1表示第二个跳变沿采样。写SPI控制器前先查器件手册确认模式再把主控模式设为一致这是最省心的路径。我在学SPI驱动ADC时反复验证最终总结出一套规律: 读芯片手册里的时序图看它标注的采样点是落在SCK的哪个沿照着那个沿来设计输出和采样逻辑。SPI的Verilog实现里还有个小技巧数据位的移入移出经常要在一个循环或状态里完成两层状态机的嵌套字节级状态和bit级状态是标准解法。很多同学一口气写完整个模块发现时序一团糟就是因为没把“选通CS”、“逐bit移位”、“字节完成”这三层节奏分开。5.3 时钟与FIFO IP核的正确打开方式Vivado自带了很多IP核最常用也最值得先掌握的是Clocking Wizard和FIFO Generator。Clocking Wizard用来从板载50MHz时钟生成不同频率的时钟比如要跑一个125MHz的以太网接口直接用MMCM/PLL IP比自己写分频器靠谱得多。Vivado IP核的好处是工具会帮你做时钟约束和合法性检查你只需要关心输入频率、输出频率和时钟类型。FIFO则是跨时钟域缓存数据的标准手段。两个时钟域比如ADC的采样时钟和系统处理时钟之间传数据直接用寄存器同步很容易丢数据或产生亚稳态FIFO的作用就是用内部读写指针隔离两个时钟域的耦合。用FIFO IP时需要注意读时钟、写时钟是否相同如果不同要选择“独立时钟FIFO”。另外要关注满、空、almost full/empty这些状态信号的时序实际项目中读写时钟频率差异会导致FIFO水位波动水位信号延时几个周期才能反映到控制逻辑上处理不好就会出现覆盖或者空读。Vivado的IP核目录里还有Block Memory Generator、DDS Compiler、FIR Compiler这些后续做图像处理和信号处理都用得上。但我建议大家不要一上来就依赖IP而是先尝试用Verilog手写一遍简单的FIFO或者RAM逻辑理解了内部机制后再用IP核提速这样遇到IP配置异常时才有排查思路。6. 综合实现与下载调试中的报错排查心得6.1 Implement Design变红先看粒度再动手Vivado的流程是综合Synthesis→ 实现Implementation→ 生成比特流Generate Bitstream任何一个环节出了问题对应步骤的状态都会变红。Implement Design变红是我被问得最多的问题因为综合通过了到布局布线时报错往往更隐蔽。遇到Implement变红我的排查顺序是固定的先切到Implementation的Log窗口看末尾两屏的ERROR如果只有Critical Warnings就点开Reports里的Utilization和Timing Summary。我遇到的大部分实现失败原因有两类一类是引脚约束与原理图不符或者引脚功能冲突另一类是时序约束缺失或过紧导致布局布线后仍无法收敛。如果是第一种修改XDC后重新跑实现如果是时序问题就要看是路径约束不够精确还是时钟本身就没有被正确约束。有一点很多初学者不知道的在Vivado里综合和实现是可以分离查看报告的。综合后的资源占用报告能提前暴露模块过大、端口悬空等问题。不要每次都从RTL一步跑到比特流学会阶段性地检查报告问题出在哪一目了然。6.2 DRC错误的重点检查项Vivado实现阶段会跑Design Rule Check比较常遇到的是“DRC RTSTAT-2”这类与时序约束相关的错误。RTSTAT-2通常是说部分路径缺少时序约束或者时钟定义不完整导致工具无法正确计算时序。它的含义就是时序约束的覆盖范围不够。排查方法打开Reports里的Clock Interaction或Timing Summary看看是否所有寄存器路径都被约束覆盖了。如果只是个别路径没有约束可以在XDC里用set_false_path把不关心的跨时钟域路径排除掉如果整体都没约束那就说明最基础的create_clock没写或写错了。DRC错误和时序错误是两回事DRC是“规则不允许”时序是“约束不满足”这两种情况处理方法不同先分清类别再动手改。6.3 布局布线与比特流生成失败的背后逻辑很多自学同学分不清布局Placement和布线Routing的区别。通俗理解布局是把逻辑单元和寄存器分配到SLICE里的具体位置布线则是用芯片内部的金属连线把这些位置连起来。布局的结果影响布线的可行性和时序布线完成后工具还要做时序分析不满足就报警告甚至错误。生成比特流失败最常见的原因是实现阶段还有未满足的时序路径生成比特流的阶段会把时序违例视为致命错误。解决办法是回到实现阶段打开Timing Summary查看WNS和TNS为负的路径。如果只是少数几条路径违例可以通过修改代码降低逻辑级数、在XDC里放宽不必要的约束、或者调整实现策略比如让工具更偏向优化时序来解决。能生成比特流并不代表工程没问题建议每次下载前看一下实现报告里的WNS值保持正数才算稳。7. 从学习到实战几个值得继续深挖的方向基础接口都打通之后就该往真正的应用方向发展了。我对新人推荐的进阶方向是FPGA图像处理因为视觉反馈最直观且工程量大、涉及面广。用OV5640摄像头采集图像数据通过I2C配置寄存器再写图像预处理算法典型的就是滑动窗口滤波。所谓滑动窗口滤波就是用当前像素周围3x3或者5x5邻域算出均值来替换当前像素值实现平滑去噪。实现层面需要行缓存可以用FIFO或Block RAM、窗口寄存器和像素边界处理是小梅哥进阶教程里很经典的项目。另一个让我觉得FPGA真正强大的是并行性。同样的均值滤波算法在ARM上需要逐像素循环而在FPGA中可以用流水线结构让每类像素在时钟沿并行更新。当你在工程里看到多路数据同时流过不同的滤波器、处理延时只差一个时钟周期时就能理解为什么雷达、相控阵这些对实时性要求极高的系统普遍选择FPGA。你可能会好奇FPGA能不能做相控阵的相位控制答案是完全可以。相控阵每个通道的移相本质上是通过可变延迟或相位累加器实现的FPGA可以在极短时间内完成所有通道的相位更新。这就是FPGA的优势并行通道一致性高、时序控制精度高、可灵活重构。后续如果想走得更深可以研究一下DDR3读写控制、LVDS高速接收、PCIeXDMA这些方向。但我的体会是不要为了做项目而做项目而是要先理解需求、理解时序、理解工具怎么实现你的描述。把这些基本功打牢新协议、新IP对你来说都只是时间问题。最后分享一个我一直保持的习惯每学一个模块就自己写一个最小可运行的工程配上Testbench仿真通过后再上板调试调试通过后记录一下报错和解决过程。这个学习路径看起来慢但扎实程度远超看一百个视频。FPGA这东西和软件不同代码能不能综合、时序能不能收敛、上板能不能跑每关都逃不掉老老实实把基本链路走通后面自然就快了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门