资讯动态

AX530 FPGA开发板Verilog例程part1:从流水灯到UART回环的实践指南

发布时间:2026/10/14 18:23:26 来源:尧图企业网站定制
简介黑金AX530开发板Verilog例程包part1专为FPGA入门与进阶学习者设计依托板载外设提供完整可运行的实验工程便于对照硬件逐一验证。例程由易到难涵盖发光二极管流水灯、按键消抖、锁相环、串口收发、存储器读写、实时时钟、内存与闪存操作、数码管显示、USB通信、千兆以太网、录音播放、SD卡读写及音乐播放、VGA与液晶屏显示、摄像头图像采集、模数转换等二十余个模块基本覆盖常用数字逻辑与接口应用场景。压缩包采用rar格式大小约169.92MB内部以Verilog源码工程、引脚约束文件和使用说明文档为主解压后可通过目录编号快速定位对应章节。该资源已有410人学习下载适合正在学习FPGA外设驱动、图像处理或高速通信的开发者作为参考模板节省搭建环境与调试时间直接改编复用。1. AX530 的 Verilog 例程 part1这套东西到底能让你学会什么拿到一块 AX530 开发板解压例程 part1新手最容易犯的错是跳过原理直奔编译烧录然后在报错和没反应之间来回折腾。Part1 这批例程从流水灯起步一路做到按键消抖、UART 回环表面上是「让外设动起来」实际上是在用最小代码量教会你三件事时钟怎么分频、信号怎么同步、仿真怎么验证。它解决的痛点是「板子插上电但完全不知道从哪下手」适合刚接触 FPGA、手里正好有 AX530、想用 Verilog 把时序概念落到实处的从业者。这一篇不聊芯片内部架构只讲怎么把 part1 变成你自己的调试起点。2. 搭环境与读工程AX530 例程 part1 的文件结构和编译路径2.1 例程 part1 里到底有哪些文件分别管什么打开 part1 解压目录第一眼往往是几十个文件混在一起。不要慌按用途归类后其实就五类。源码文件是核心顶层模块一般叫top.v或者led_flash.v里面用实例化把分频、计数、输出这几个子模块串起来约束文件决定了每个 Verilog 端口对应板子上哪个物理引脚没它程序烧进去就是空跑testbench 是仿真用的测试平台只在 ModelSim 里运行不参与综合还有一类是 Quartus 的工程文件保存了你选的器件型号和编译选项。文件类型后缀/特征作用不处理会怎样Verilog 源码.v逻辑实现缺失直接编译失败工程设置.qsf记录器件型号、引脚分配引脚全部悬空时序约束.sdc告诉工具时钟频率布局布线可能时序违例测试平台tb_*.v / sim 目录仿真激励生成无法用仿真验证烧录文件.sof / .jic下载到板子或固化到 Flash没有它无法上板我一般拿到例程先做一件事打开.qsf文件看器件型号。AX530 不同批次可能搭配不同封装如果工程里写的器件和你板子上的丝印不一致后面综合、布局布线会报一堆看不懂的错误。遇到这种情况别硬解报错直接把.qsf里的器件型号改成你板子对应的那款再重新编译即可。这是例程移植里最不起眼、但最高频的坑。2.2 把 part1 里第一个工程编译通过命令行跑通全流程很多教程带你用 Quartus 图形界面点点点但图形界面操作没法留痕换个工程又得重来一遍。我习惯用命令行把流程固定下来一条条执行出错能精准定位到步骤。假设你已经把例程导入并指定了工程名下面的命令就是综合、布局布线、生成烧录文件的三板斧。# 综合把 Verilog 转成门级网表 quartus_map --read_settings_fileson --write_settings_filesoff project_top # 布局布线把网表映射到具体芯片资源 quartus_fit --read_settings_filesoff --write_settings_filesoff project_top # 生成烧录文件把布线结果打包成 .sof quartus_asm --read_settings_filesoff --write_settings_filesoff project_top第一行quartus_map负责把 RTL 代码映射成芯片内部的逻辑单元和触发器报错多发生在语法错误、端口不匹配、模块实例化错误上。第二行quartus_fit真正把逻辑放进去布线这个阶段最常见的报错是资源不够、引脚冲突、时序不满足。第三行quartus_asm打包生成下载文件。--read_settings_fileson表示读取工程里已有的.qsf和.sdc设置--write_settings_filesoff则禁止命令回写覆盖你的工程配置避免跑一次脚本把引脚分配弄丢。如果这三步都通过目录下会出现.sof文件。它是 SRAM 对象文件烧进去就能跑但断电即失。要固化到板载 Flash还得用 Quartus 的「Convert Programming File」功能把.sof转成.jic再通过烧录器写入。这步不懂不影响 part1 练习但等你想把自己写的模块做成上电自启的固件时迟早要回来补这一课。2.3 把用户手册里的外设配置对应起来管脚约束不再是玄学AX530 例程的.qsf里通常已经写好了 LED、按键、UART 的引脚分配但你要知道这些约束是从哪来的才算真正接管了这个工程。板卡原理图里每个外设都连到 FPGA 的某个 BANK 引脚例程作者根据原理图把这些固定关系写进了.qsf格式长这样set_location_assignment PIN_AF15 -to led[0] set_location_assignment PIN_AF14 -to led[1] set_location_assignment PIN_AG15 -to key[0]这三行字的意思是把 FPGA 的AF15引脚分配给顶层端口led[0]AF14分配给led[1]AG15分配给key[0]。一旦你把顶层模块的端口名改了比如把led[0]改成led0引脚约束就失配了编译时 Quartus 会报告端口找不到分配而实际上板子上的 LED 到那个引脚之间的物理连接没有变你要做的是改回端口名而不是去改引脚号。另一种常见误区是照着网络上的教程重新手动分配引脚。每块板子原理图不同网上那个工程的引脚未必适用于 AX530。正确做法是打开板卡原理图搜索 LED 网络名找到它连到 FPGA 的具体引脚编号再写进约束。最多人在这里翻车不是因为他们不会写约束而是因为懒得翻原理图。3. 读透 part1 的三个基础例程分频、消抖和 UART 回环3.1 流水灯不是 LED 翻转是分频计数器在背后干活流水灯例程的核心代码往往只有二十几行但里面藏着 FPGA 设计的第一课分频。板上晶振通常是 50MHz也就是时钟信号每 20ns 翻转一次这个速度直接驱动 LED 的话人眼只能看到常亮。要让 LED 以肉眼可见的频率变化必须数出足够多的时钟周期再翻转一次。module led_flash( input wire clk, input wire rst_n, output reg led ); reg [23:0] cnt; localparam CNT_MAX 24d9_999_999; always (posedge clk or negedge rst_n) begin if (!rst_n) cnt 24d0; else if (cnt CNT_MAX) cnt 24d0; else cnt cnt 1b1; end always (posedge clk or negedge rst_n) begin if (!rst_n) led 1b0; else if (cnt CNT_MAX) led ~led; else led led; // 保持当前状态 end endmodule代码逻辑不复杂复位信号rst_n拉低时计数器清零、LED 熄灭正常工作时每个时钟上升沿计数器加一加到CNT_MAX时归零同时 LED 翻转一次。localparam把计数上限抽出来换板子改时钟频率时只要改这一处。要注意led led不是废话它保证没有触发翻转条件时 LED 保持原状态综合工具会把这个信号综合成触发器而不是组合逻辑毛刺。参数怎么调取决于你要的闪烁频率。50MHz 时钟下计数到 9,999,999 需要 10,000,000 个周期每个周期 20ns合计 200msLED 每 200ms 翻转一次整体闪烁周期是 400ms看起来节奏很舒服。如果你要更快的呼吸感把CNT_MAX改小要更慢改大。注意计数器位宽9,999,999 小于 2^2424 位足够如果改成 5 亿这种大数记得把位宽同步扩到 29 位以上否则计数到一半就溢出回绕LED 会以完全错误的节奏闪。3.2 按键消抖为什么必须用计数器而不是延时循环按键是机械结构按下和松开瞬间会有 10ms 级的高频抖动信号直接用if (key 1b0)判断会导致一次按下被识别成多次。part1 里的消抖例程用的是窗口计数法这也是工程上最稳的写法。reg [19:0] debounce_cnt; reg key_clean; always (posedge clk or negedge rst_n) begin if (!rst_n) begin debounce_cnt 20d0; key_clean 1b1; end else if (key_in ! key_clean) begin if (debounce_cnt 20d999_999) begin key_clean key_in; debounce_cnt 20d0; end else debounce_cnt debounce_cnt 1b1; end else debounce_cnt 20d0; end这个设计思路是只有当输入信号key_in与新采样的稳定值key_clean不一致时才开始计数如果信号能连续稳定 1,000,000 个时钟周期也就是 50MHz 下的 20ms就认定这次变化是真实按下而不是抖动更新key_clean。抖动通常在 10ms 以内20ms 的窗口留出了足够余量如果按键手感特别差把窗口拉长到 30ms但别超过 50ms否则快速连按会被过滤掉。新手最容易踩的坑是写#delay延时来消抖。在仿真里#20000确实能延时 20us 的逻辑时间但在综合时这种写法会被工具处理得很难看而且阻塞式延时会让整个模块在延时期间无法响应其他信号在工程里是种自杀式写法。记住一个原则FPGA 里一切延时都靠数时钟周期不靠 sleep。3.3 UART 回环例程的波特率分频从 9600 到 115200 的换算UART 是 part1 里第一个真正涉及通信协议的例程它的核心还是分频只不过这次分频得到的不是 LED 闪烁频率而是波特率。以 9600 波特率为例每个 bit 持续时间为 1/9600 秒约 104.167us换算成 50MHz 时钟周期数就是 50,000,000 / 9600约等于 5208 个周期。localparam BAUD_DIV 16d5207; // 50MHz / 9600 ≈ 5208计数从0开始所以减1 reg [12:0] baud_cnt; reg baud_pulse; always (posedge clk or negedge rst_n) begin if (!rst_n) begin baud_cnt 13d0; baud_pulse 1b0; end else if (baud_cnt BAUD_DIV) begin baud_cnt 13d0; baud_pulse 1b1; // 该周期发送/接收一个bit end else begin baud_cnt baud_cnt 1b1; baud_pulse 1b0; end endbaud_pulse每 5208 个时钟周期拉高一次这个脉冲就是 UART 收发模块的节拍。为什么用脉冲而不是直接生成一个慢时钟因为多时钟域会引入跨时钟域同步问题而脉冲信号还在原时钟域里只是用逻辑判断「这一刻该干活了」是工程上更稳的做法。换 115200 波特率时把BAUD_DIV改成 50_000_000 / 115200 减 1约等于 433注意 5207 需要 13 位计数器433 也在这个位宽范围内不用改位宽但如果你换成更低波特率如 4800除法结果是 10416就超出 13 位范围了需要扩位。part1 的 UART 例程还会包含接收状态机和发送状态机接收侧用波特率脉冲在 bit 中间采样发送侧用脉冲逐 bit 移位输出。回环就是把收到的字节原样发回去验证收发链路是否打通。这块代码我第一次读也有点绕建议结合仿真波形来看一根一根地对起始位、数据位、停止位。4. 用 ModelSim 把例程跑起来testbench 搭法与仿真参数设置4.1 一个能用的 testbench 长什么样写完 Verilog 模块直接上板是 FPGA 开发的大忌复杂的时序逻辑靠肉眼和示波器排错效率极低常见做法是先写 testbench 做行为仿真把内部信号拉出来看波形。下面这个 testbench 是 part1 例程里最常用的模板我几乎每个模块都从它改起。timescale 1ns / 1ps module tb_led_flash; reg clk; reg rst_n; wire led; led_flash dut( .clk (clk), .rst_n(rst_n), .led (led) ); initial begin clk 1b0; rst_n 1b0; #100; rst_n 1b1; // 复位释放 end always #10 clk ~clk; // 20ns周期对应50MHz initial begin #200_000; $finish; // 运行200us后结束 end endmodule第一行timescale 1ns / 1ps声明时间单位和精度仿真时间#100代表 100ns精度 1ps 够用。dut是被测模块的实例化名端口按名字连接这样即使模块端口顺序变化也不会连接错。复位逻辑上前 100ns 拉低rst_n让模块的异步复位分支生效所有寄存器清零然后释放复位开始正常跑。时钟用always #10 clk ~clk生成每 10ns 翻转一次周期 20ns正好是 50MHz。最后的$finish控制仿真在 200us 时自动结束防止无限跑下去。一个关键细节是复位时间 100ns 必须覆盖至少 3 个时钟沿也就是 60ns否则复位释放时可能会采到不确定状态波形里就会出现 X 态。100ns 是安全值如果你用#50再减半到#25就已经是危险操作了。4.2 ModelSim 命令行仿真从编译到看波形的完整流程ModelSim 的图形界面操作点起来很直观但效率和可复现性都不行把仿真命令写进脚本才是能反复用的做法。下面是四步流程的核心命令。# 建立仿真库 vlib work # 编译源码和testbench vlog ./rtl/led_flash.v ./sim/tb_led_flash.v # 启动仿真指定顶层测试模块 vsim -t ns work.tb_led_flash # 添加全部端口到波形窗口 add wave -position end sim:/tb_led_flash/* # 运行仿真200us run -allvlib work创建一个名为 work 的库这是 ModelSim 存放编译产物的地方vlog把两个 Verilog 文件编译进 work 库顺序是先编源码模块再编 testbench因为 testbench 实例化了源码里的模块前后颠倒会报找不到模块。vsim -t ns work.tb_led_flash启动仿真-t ns把波形时间精度设为纳秒显示更清爽如果你要看亚稳态这类纳秒级以下的问题改成-t ps再说。add wave那行的*通配符把 testbench 顶层所有端口加进波形但内部信号比如cnt默认看不到这部分我在下一节单独讲。run -all跑完整个仿真直到遇到$finish。仿真跑完你大概率会看到 LED 波形就是一条直线这不是代码错了而是仿真时间不够。前面提到流水灯要在计数器数到 10,000,000 个周期才翻转这在 50MHz 下是 200ms但 testbench 里只跑了 200us差了一千倍。解决办法不是把仿真时间拉长到 200ms——那样仿真要跑几十秒甚至几分钟——而是把CNT_MAX临时改成小值比如24d99仿真时 LED 应该每 1us 翻转一次波形里能清晰看到高低电平交替。改回原值再编译一次就能恢复真实效果。这个技巧几乎每个做 FPGA 仿真的人都会用。4.3 在波形窗口里看内部信号层次化路径是调试分频器的钥匙LED 翻转只是表象真正想理解cnt怎么从 0 涨到最大值再清零得把内部计数器信号拉出来看。ModelSim 里所有信号都有层次化的路径格式是sim:/tb_模块名/实例名/信号名。在命令行里输入add wave -position end sim:/tb_led_flash/dut/cnttb_led_flash是顶层模块dut是被测模块的实例名cnt是实例内的寄存器信号。执行后波形窗口会出现一条从 0 到 9999999 再归零的折线配合 LED 波形看能非常直观地理解「计数器到顶翻转」这个行为。如果cnt停在 0 不动说明复位一直没释放如果cnt跳到满值附近不动说明计数条件没满足如果cnt是红色或 X 态说明有信号没初始化。层次化路径也适用于 UART 例程调试比如想看接收状态机当前在哪个状态直接add wave sim:/tb_uart_top/dut/rx_state状态值会以数字形式显示配合波特率脉冲波形一起看收发链路是不是通的几乎一眼就能判断。这个技能是 part1 例程为你建立的最值钱习惯仿真不是为了看结果是为了看过程。5. 避坑AX530 例程 part1 跑不起来的五个常见原因5.1 程序烧进去了LED 就是不亮现象下载工具提示烧录成功板上 LED 没有反应随便怎么按复位都一样。原因引脚约束没对上。最常见的是顶层端口名和.qsf里的约束名不一致比如端口叫led_out约束里写的是led[0]Quartus 直接把这根信号当成未分配引脚烧录后 LED 对应的物理引脚是悬空状态当然不会亮。解决打开工程里的.qsf看set_location_assignment里引用的端口名再回源码顶层模块里核对。改名后重新编译烧录即可。如果.qsf里压根没有 LED 相关的引脚分配就只能翻 AX530 原理图查 LED 网络对应的 FPGA 引脚号写在约束里。5.2 编译报错一堆 pin 冲突现象综合或布局布线时报类似「pin AF15 has already been assigned to xxx」的错误。原因.qsf里同一个引脚被分配给了多个信号或者同一信号分配了多个引脚。常见的来源是从别的工程复制约束文件时没清理干净旧工程的引脚分配残留了下来。解决打开.qsf搜索报错的引脚号找到重复的set_location_assignment行删除多余的那行保留真正连接这个信号的那条。如果不确定哪条是对的用原理图查证别猜。修改.qsf后重新编译问题通常立即消失。5.3 仿真波形全是 X 态现象ModelSim 跑完所有信号都是红色 X 或高阻 Z没有正常的高低电平。原因X 态意味着信号没有被正确驱动。常见三个原因testbench 里复位信号没释放rst_n一直为低所有寄存器停留在复位分支时钟没翻转触发器采不到上升沿或者顶层模块的端口连接比 testbench 里的实例化多或少导致部分输入悬空。解决先看时钟波形如果clk是一条直线检查always #10 clk ~clk是否写对再看rst_n确认有#100; rst_n 1b1;这样的释放语句最后核对实例化的端口列表用add wave sim:/tb_led_flash/*把顶层所有端口都拉出来一眼就能看到哪个信号没接上。5.4 流水灯仿真波形死活不翻转现象代码看着没问题testbench 也照抄了但led波形就是一条直线计数器却在正常变化。原因不是逻辑错误是仿真时间远小于实际翻转周期。流水灯设计的是 200ms 翻转一次仿真只跑了 200us整整差三个数量级而 LED 信号在仿真结束时的状态和开始一样波形看起来就是平的。解决把CNT_MAX临时改小到99重新编译仿真LED 翻转就肉眼可见了。验证功能正确后改回真实值再编译。切记改回去否则烧到板子上 LED 会闪成高速频闪那种闪法不接示波器根本看不出来。这属于 FPGA 仿真里最常见的「时间尺度错位」遇上不代表代码有问题。5.5 烧录器识别不到驱动怎么装都不行现象插上烧录器系统提示未知设备Quartus 的 Programmer 里空白一片。原因驱动路径不对。AX530 配套的烧录器驱动不一定要去网上下载Quartus 安装目录的drivers文件夹里通常已经带了对应平台的驱动文件问题是系统默认去 Windows Update 找驱动自然找不到。解决在设备管理器里找到那个带黄色感叹号的未知设备手动更新驱动路径指定到 Quartus 安装目录下的drivers子目录勾选包含子文件夹让它慢慢搜。装好后重新插拔烧录器打开 Programmer 就能看到设备。Win10 以上系统偶尔需要禁用驱动签名强制验证这个按系统版本搜一下操作即可。6. 把流水灯改成呼吸灯part1 之后的第一课实践学完 part1 的例程最值得做的第一个改造就是「流水灯升级呼吸灯」。流水灯是等间隔翻转呼吸灯是亮度缓慢渐变这需要引入第二个计数器来控制亮灭占空比正好把分频、组合逻辑、状态保持这些 part1 教你的一起串起来用。reg [15:0] breath_cnt; // PWM周期计数器1ms重载 reg [15:0] duty; // 当前占空比 reg dir_bit; // 1变亮 0变暗 localparam PWM_PERIOD 16d49_999; // 1ms 50MHz always (posedge clk or negedge rst_n) begin if (!rst_n) begin breath_cnt 16d0; duty 16d0; dir_bit 1b1; end else if (breath_cnt PWM_PERIOD) begin breath_cnt 16d0; if (dir_bit) begin if (duty PWM_PERIOD) dir_bit 1b0; else duty duty 16d64; end else begin if (duty 16d0) dir_bit 1b1; else duty duty - 16d64; end end else breath_cnt breath_cnt 1b1; end assign led (breath_cnt duty) ? 1b1 : 1b0;这段代码的核心是两层计数breath_cnt每 1ms 重载一次形成 PWM 的周期duty在每 1ms 的边界上加或减 64形成亮度的渐变斜率。LED 在计数器的前duty个周期亮后PWM_PERIOD - duty个周期灭占空比越大灯越亮。dir_bit控制渐变方向到满亮度往回变暗到零亮度再重新变亮。如果想让呼吸节奏更快把步进值 64 改成 128想更平滑改 32同时确认计数器位宽足够。改造时直接把 part1 流水灯工程的顶层模块替换成这个重新综合布局布线烧录即可。如果连这部分也调试通了下一步强烈建议做的事是把 UART 回环和 LED 联动收到特定字节点亮 LED收到另一个字节熄灭。这个练习比单纯跑例程有价值得多因为你第一次把「外部输入」和「片上逻辑」连成了一个闭环系统。我个人的习惯是每次拿到新板子先不碰复杂例程花一天时间把最小系统验证过再做增量功能带过一个新人同事他跳过这一步直接调成熟项目最后定位到一个引脚错误花了整整半天。希望你不用交这份学费也希望这篇笔记能帮你少走这几段弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑