资讯动态

黑金AX530基础例程实战:Vivado工程、XDC约束、流水灯与UART回环

发布时间:2026/10/14 18:23:26 来源:尧图企业网站定制
简介这是针对黑金AX530开发板的Verilog例程合集第一部分覆盖从LED流水灯、按键检测、锁相环PLL、串口通信、EEPROM读写、RTC实时时钟、片内ROM到DDR2、FLASH、数码管、USB2.0、千兆以太网、录音播放、SD卡读写与音乐播放、VGA/LCD显示、OV7670/OV5640摄像头采集、AD/DA转换、贪吃蛇小游戏和视频图像以太网传输等二十余个模块。压缩包为rar格式整体约169.92MB便于按需解压使用。已有410人学习例程顺序由浅入深既适合初学者夯实基础也为进阶者提供了高速接口与图像处理等综合案例。借助这些例程读者可以在AX530平台上快速完成外设驱动验证、功能模块移植和工程框架搭建并对Verilog工程的组织方式形成直观认识。用作课程实验、毕业设计或项目参考均较合适。1. 黑金AX530 配的 Verilog 例程 part1不是烧进去就能跑是“按步骤跑”把例程烧进板子灯却不亮这是拿到黑金AX530 配套 Verilog 例程 part1 后最常见的第一反应。实际上板子大概率没坏代码也大概率没错只是还没搞清楚这套例程的组织方式。part1 这类基础篇通常按“时钟复位 → LED/按键 → 数码管 → UART 回环”的递进关系排布每个例子都要先确认器件型号、约束引脚、板载时钟频率然后才能谈现象。这篇文章按拿到板子后的实际操作顺序拆一遍工程怎么建、XDC 约束哪些必写、流水灯的分频参数怎么算、UART 回环怎么仿真、跑不通时按什么顺序排查。适合刚接触 FPGA 的初学者也适合从单片机切过来的开发者后者最容易在“代码明明没错但约束不对”上耗掉半天。2. 先摸清例程目录再建 Vivado 工程从器件选择到第一个比特流2.1 一套配套例程的目录通常长什么样拿到黑金AX530 的 Verilog 例程 part1里面通常不是一个孤零零的 .v 文件而是按功能拆开的工程包。常见做法是分 src / constrs / sim / ip / doc 几类目录src 放所有 RTL 源码constrs 放 XDC 约束文件sim 放 testbenchip 放例程里用到的 Vivado IP 核doc 放原理图和数据手册。这样拆分的原因很实际——工程要能在不同版本的 Vivado 里重建也方便你只改约束或只改源码不用每次全量翻代码。part1 的例程大多不依赖复杂 IP像 MMCM 这类时钟管理单元偶尔会用而流水灯、按键、数码管这些纯逻辑一般不涉及 IP所以 src 目录下最常见的就是 led、key、seg7、uart 这类子目录。这些目录和板载资源是对应着看的。AX530 这块板子的典型资源有 LED、按键、拨码开关、数码管、USB 转串口、DDR3、HDMI 或 VGA 接口例程 part1 通常只覆盖前几项DDR3 和视频接口往往会放到后面的 part2/part3。拿到板子后第一件事不是打开 Vivado而是翻一遍 doc 目录里的原理图把三样东西抄下来板载时钟频率、目标 FPGA 器件型号、你要用的引脚编号。这三个信息后面每一步都要用抄在纸上比来回翻 PDF 快得多也能避免反复切换窗口时看错引脚号。2.2 用 Tcl 批处理建工程避免每次新建都点错建立 Vivado 工程有两条路图形界面点 New Project 最直观但例程这种要反复重建的场景我一般用 Tcl 批处理。理由很简单图形界面点错一个选项就得从头来Tcl 脚本可以放进版本管理换电脑也能一键恢复。下面这段是最小可用的建工程脚本create_project project_1 ./project_1 -part xc7a35tftg256-2 set_property target_language Verilog [current_project] add_files -norecurse ./src/led_shift.v add_files -fileset constrs_1 -norecurse ./constrs/ax530_led.xdc update_compile_order -fileset sources_1逻辑拆开看create_project 指定工程名、路径和器件型号set_property 把工程默认语言设为 Verilogadd_files 分两次把 RTL 源文件和 XDC 约束加进不同文件集update_compile_order 让 Vivado 自动排好编译顺序。-part 参数必须以 AX530 板上芯片丝印为准常见的是 xc7a35tftg256-2 这类 Artix-7 封装的器件如果原理图标注的是更大一号的型号直接改 -part 参数即可。这里不写 -board 参数因为板卡定义文件不是每台机器都有手动指定 part 最稳也方便后续换板卡时只改这一行。2.3 最小 XDC 约束时钟、引脚、电平标准缺一不可工程建好后例程里最容易翻车的环节就是约束文件。AX530 这类板卡的例程XDC 里真正必须的只有三类引脚位置约束、I/O 电平标准、时钟约束。下面这个片段是流水灯工程里最常见的写法set_property PACKAGE_PIN K17 [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports clk] set_property PACKAGE_PIN N15 [get_ports led[0]] set_property IOSTANDARD LVCMOS33 [get_ports led[0]] create_clock -period 20.000 -name sys_clk [get_ports clk]PACKAGE_PIN 的值必须来自板子原理图不同批次、不同型号的值可能有差异复制别人工程里的约束前一定要和原理图逐个对。IOSTANDARD 一般统一写 LVCMOS33因为 AX530 的普通 IO BANK 电压常见 3.3V如果涉及 DDR3 或 HDMI 的 BANK就是另一套电平标准不能照抄。create_clock 的 -period 是时钟周期板载时钟若是 50MHz周期就是 20ns所以写 20.000。这条不写也能综合、能行为仿真但时序分析会直接报错后面跑高速接口时会翻大车。2.4 从综合到比特流三步命令一次跑完工程建好、约束写好剩下就是标准三步综合、实现、生成比特流。图形界面里对应 Run Synthesis、Run Implementation、Generate Bitstream用批处理则是一条命令vivado -mode batch -source build.tcl其中 build.tcl 内部就是三个标准命令synth_design -top led_shift -part xc7a35tftg256-2 place_design route_design write_bitstream -force ./project_1/led_shift.bitsynth_design 的 -top 参数指定顶层模块名必须与 RTL 顶层模块一致写错会直接报找不到顶层。place_design 和 route_design 不需要额外参数如果出现时序违例可以在 route_design 后加 -directive Explore 再跑一轮。write_bitstream 导出的 .bit 文件就是要烧进板子的最终产物。2.5 烧录第一个比特流的两种方式拿到 .bit 文件后烧录方式有两种。图形界面在 Flow Navigator 里点 Open Hardware Manager连上板子后右键器件选 Program Device选中 .bit 文件即可。命令行方式适合脚本化操作先 open_hw_manager再 open_hw_target最后 program_hw_devices。烧录前确认板子已经上电、下载器驱动正常烧录过程中不要按复位键。烧录成功后板子并不会自动重启需要手动按一下复位按键让设计开始跑很多人在这一步误以为没烧进去。3. 流水灯例程时钟分频与移位寄存的板级入口3.1 为什么每个例程都在强调“时钟先进来”Verilog 例程 part1 里最容易被轻视的就是时钟处理。FPGA 和单片机不一样所有逻辑都挂在时钟边沿上没有可靠的时钟代码再正确也不会有现象。AX530 板载有源晶振给 FPGA 提供系统时钟常见频率是 50MHz具体以板子丝印和原理图为准。例程里的分频、计数、UART 波特率、数码管扫描全部以这个时钟为基准计算。所以拿到例程第一步是确认顶层模块的 clk 端口有没有被约束到晶振引脚上。流水灯不亮时先查这条路径比盯着代码猜效率高得多。3.2 分频参数从哪来从 50MHz 分到 1Hz 的计算流水灯要“流”起来需要把 50MHz 系统时钟分频成人眼能看到的变化速度。常见做法是用计数器对系统时钟计数计满一个目标周期就产生一个单时钟周期的脉冲作为“节拍”。目标变化频率若为 1Hz计数最大值为50_000_000 / 1 - 1 49_999_999产生一个 1Hz tick。计数器位宽用系统函数估算$clog2(49_999_999 1) 2525 位够用。常见分频参数对照如下以 50MHz 时钟为基准目标变化频率计数器上限计数器位宽1 Hz49_999_999254 Hz常见流水节奏12_499_9992410 Hz4_999_99923参数化例程通常把上限写成(CLK_FREQ / 1000 * BLINK_MS - 1)这种带常量表达式的形式而不是写死 49999999。这样换板子、换时钟时只改 CLK_FREQ 一个参数不会漏改。3.3 一个保留教学细节的流水灯模块把上面的计算落到 Verilog适合 AX530 跑通的模块长这样module led_shift #( parameter CLK_FREQ 50_000_000, // 系统时钟频率单位 Hz parameter BLINK_MS 500 // 每步间隔单位 ms )( input wire clk, input wire rst_n, // 低电平复位板上按键常用 output reg [3:0] led // 4 个 LED ); localparam CNT_MAX CLK_FREQ / 1000 * BLINK_MS - 1; localparam CNT_WIDTH $clog2(CNT_MAX 1); reg [CNT_WIDTH-1:0] cnt; reg tick; // 分频每 BLINK_MS 产生一个单周期脉冲 always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt d0; tick 1b0; end else if (cnt CNT_MAX) begin cnt d0; tick 1b1; end else begin cnt cnt 1b1; tick 1b0; end end // 移位tick 到来时循环右移 always (posedge clk or negedge rst_n) begin if (!rst_n) led 4b0001; else if (tick) led {led[0], led[3:1]}; end endmodule这段代码包含例程 part1 最核心的两个知识点。第一是复位风格这里用了异步复位敏感列表里出现negedge rst_n这种写法在板卡例程里最常见因为按键复位不需要等时钟沿响应快缺点是一旦复位信号上有毛刺会瞬间复位所以量产项目里更稳的做法是同步复位即敏感列表只留posedge clk复位判断放在 always 内部。第二是移位寄存{led[0], led[3:1]}把最低位移到最高位、其余位右移形成循环流动效果。这里有个容易踩的坑参数表达式CLK_FREQ / 1000 * BLINK_MS整型运算先做除法再做乘法如果 CLK_FREQ 不是 1000 的整数倍会引入截断误差。50_000_000 恰好整除所以没问题如果换了非整数倍的时钟建议把表达式改成CLK_FREQ * BLINK_MS / 1000先乘后除误差更小。LED 是低电平点亮还是高电平点亮要以原理图为准AX530 常见设计是 LED 阳极接电源、阴极接 FPGA 引脚那么点亮对应引脚输出 0代码里的初值和移位结果就要按低有效来写。3.4 参数化改造与按键换向例程拿到手后最值得改的第一个点就是流水灯速度。很多初学者直接在代码里搜49999999改数字这种做法不推荐一是容易改错位宽二是换工程就找不到。更符合 part1 教学意图的做法是把所有魔数收敛成参数上面的代码已经示范。想加快节奏把 BLINK_MS 从 500 改成 200想变成双灯流动把 led 初值改成4b0011移位逻辑不用动。仿真时把 CLK_FREQ 调小、BLINK_MS 调小几个仿真周期就能看到完整移位效果这是调试里非常省时间的技巧。流水灯跑通后下一个常练手的改动是按键换向。外部按键信号进 FPGA 前需要做同步化和消抖常见做法是打两拍消除亚稳态再加一个边沿检测。一小段示例reg [1:0] key_sync; wire key_press; always (posedge clk or negedge rst_n) begin if (!rst_n) key_sync 2b11; else key_sync {key_sync[0], key}; end assign key_press key_sync[1] ~key_sync[0]; // 下降沿有效key_sync打两拍是为了让异步信号与本地时钟对齐key_press检测到下降沿才产生一个单周期脉冲用来切换移位方向。这个例子把“外部输入→同步→边沿检测→控制逻辑”的链路完整串起来是 part1 里出现频率很高的组合。4. UART 回环例程用 testbench 提前看时序再上板验证4.1 回环例程的架构rx 收到什么tx 就发回什么part1 外设例程里比流水灯进阶一级的通常是 UART 回环串口助手发一个字节板子原样回传。结果看似简单但它把波特率产生、接收时序、发送时序三个基础部分全串起来了。架构上就是三块波特率分频、UART_RX 状态机、UART_TX 状态机。RX 检测到起始位下降沿后按波特率采样 8 个数据位和停止位拼成一个字节交给 TXTX 按同样的波特率把字节帧发出。链路里最容易出问题的是采样点接收侧要避开数据跳变沿发送侧要保证帧间隔不小于 1 个停止位。4.2 波特率分频怎么设不同配置的分频误差假设系统时钟 50MHz波特率 115200每 bit 需要计数50_000_000 / 115200 ≈ 434.03。计数取整为 434 后实际波特率约 115207误差约 0.006%远小于 UART 可容忍的 2% 误差整数除法直接可用。用 9600 时50_000_000 / 9600 ≈ 5208.33取 5208 时误差约 0.015%同样安全。分频参数对照如下时钟频率目标波特率分频计数值实际误差50 MHz115200434约 0.006%50 MHz57600868约 0.058%50 MHz96005208约 0.015%注意分频计数器从 0 计到 DIV-1所以比较值不是50_000_000 / 115200而是 433直接用除法商当比较值会慢一个时钟周期收发的帧边界就会偏。4.3 一个精简但完整的 UART 回环实现下面给一段能综合、能上板的简版实现UART_RX 用最朴素的“起始位后逐位采样”写法方便对时序理解。module uart_loopback #( parameter CLK_FREQ 50_000_000, parameter BAUD_RATE 115200 )( input wire clk, input wire rst_n, input wire uart_rxd, output reg uart_txd ); localparam DIV CLK_FREQ / BAUD_RATE; // 每 bit 计数50MHz/115200≈434 reg [15:0] cnt; reg rx_busy, tx_busy; reg [7:0] rx_data; reg rx_done; reg [7:0] tx_data; reg [3:0] bit_idx; // 接收空闲检测下降沿逐位中点采样 always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 0; bit_idx 0; rx_data 0; rx_done 0; rx_busy 0; end else begin rx_done 0; if (!rx_busy) begin if (!uart_rxd) begin rx_busy 1; cnt 0; bit_idx 0; end end else begin if (cnt DIV/2) begin // 在 bit 中点采样避开跳变沿 if (bit_idx 0) begin bit_idx 1; cnt 0; end else if (bit_idx 1 bit_idx 8) begin rx_data {uart_rxd, rx_data[7:1]}; bit_idx bit_idx 1; cnt 0; end else if (bit_idx 9) begin rx_done 1; rx_busy 0; bit_idx 0; end end else cnt cnt 1; end end end // 发送收到一字节立即回发 always (posedge clk or negedge rst_n) begin if (!rst_n) begin tx_busy 0; tx_data 0; bit_idx 0; cnt 0; uart_txd 1; end else begin if (rx_done) begin tx_data rx_data; tx_busy 1; bit_idx 0; cnt 0; uart_txd 0; // 起始位 end else if (tx_busy) begin if (cnt DIV-1) begin cnt 0; if (bit_idx 8) begin uart_txd tx_data[bit_idx]; // LSB 先发 bit_idx bit_idx 1; end else if (bit_idx 8) begin uart_txd 1; // 停止位 bit_idx bit_idx 1; end else begin tx_busy 0; bit_idx 0; end end else cnt cnt 1; end end end endmodule几个关键点。接收侧检测下降沿后用 DIV/2 计数到 bit 中点再采样这是为了让采样点远离数据跳变沿容忍度最大发送侧每计数到 DIV-1 推进一个 bit起始位、数据位、停止位按顺序发出rx_done 是单周期脉冲同时作为发送触发所以回环之间的延迟只有一个周期。这个实现是教学级的没有做 3 倍过采样、没有 FIFO 缓冲后面要跑 Modbus 或 DMA 场景需要升级但用来理解 part1 的 UART 链路已经足够。4.4 写一个 testbench仿真里看时序比上板快得多先仿真后上板是看完 part1 后最应该养成的习惯。下面这段 testbench 模拟上位机发 0xA5观察回环是否一致timescale 1ns / 1ps module tb_uart_loopback; reg clk; reg rst_n; reg uart_rxd; wire uart_txd; uart_loopback dut ( .clk(clk), .rst_n(rst_n), .uart_rxd(uart_rxd), .uart_txd(uart_txd) ); // 50MHz 时钟周期 20ns initial clk 0; always #10 clk ~clk; // 发送一字节起始位 LSB 先行 停止位 task send_byte(input [7:0] data); integer i; begin uart_rxd 1; #8680; // 115200 波特率下 1 bit ≈ 8680ns uart_rxd 0; // 起始位 #8680; for (i 0; i 8; i i 1) begin uart_rxd data[i]; #8680; end uart_rxd 1; // 停止位 #8680; end endtask initial begin rst_n 0; #100; rst_n 1; #100; send_byte(8hA5); #200000; $finish; end initial begin $monitor(txd%b rx_data%h, uart_txd, dut.rx_data); end endmoduletb 里最需要注意的是#8680这个数值115200 波特率下 1 bit 时长 1s / 115200 ≈ 8.68us即 8680ns。如果错写成 9600 波特率对应的 104166ns仿真时序和实际波特率就对不上波形会显得“慢半拍”。仿真里uart_rxd每个 bit 保持 8680ns接收模块会在每个 bit 中点采样收完 0xA5 后uart_txd上应出现同样的一帧。$monitor打印内部 rx_data能看到收完后的值是否为 0xA5。如果波形里停止位之后仍然 busy优先检查 bit_idx 的计数边界。4.5 上板验证把仿真通过的设计接到真实引脚仿真只验证逻辑时序不验证引脚所以上板前必须把 XDC 写完。AX530 的 UART 是板载 USB 转串口芯片和 FPGA 之间的直连串口助手通过 USB 线读到的就是这条路。XDC 里至少需要set_property PACKAGE_PIN M18 [get_ports uart_rxd] set_property IOSTANDARD LVCMOS33 [get_ports uart_rxd] set_property PACKAGE_PIN N17 [get_ports uart_txd] set_property IOSTANDARD LVCMOS33 [get_ports uart_txd]引脚号这里只是格式示例实际值一定以自己板子的原理图为准。串口助手设置波特率 115200、8 数据位、无校验、1 停止位发一个十六进制字节如 A5回显区出现 A5 就说明整条链路通了。如果发出去立刻收到乱码先不急着怀疑代码先确认串口助手的波特率是否和工程里的 BAUD_RATE 一致这是踩过最多的一步。5. 例程跑不通的排查清单现象、原因、解决排查顺序建议从“供电和烧录”开始再到“时钟与复位”最后看“接口协议”。工具只需要万用表、串口助手、Vivado 的 Hardware Manager必要时加一个逻辑分析仪。下面五条是按出现频率排序的坑每一条都按现象、原因、解决三步写。5.1 上电没现象灯完全不亮现象烧录成功但板子一个灯都不亮。原因通常有三个按概率从高到低第一XDC 里时钟引脚写错或没写时钟根本没进 FPGA第二复位信号一直处于复位状态多数例程用低电平复位若复位引脚绑定错误或按键恰好被按住模块永远出不了复位态第三LED 极性写反代码按高电平点亮写而板子是低电平点亮。解决先翻原理图抄下 clk 和 rst_n 对应的引脚号确认 LED 是共阳还是共阴然后把代码里的 led 赋值取反这类问题通常 5 分钟能定位比怀疑代码逻辑快得多。5.2 烧录失败找不到器件或 done 信号没拉高现象点 Program 后提示找不到器件或烧录卡在某个百分比后报 DATAOUT 错误。原因JTAG 链路接触不良、下载器供电不足或板子上的启动模式跳线被拨到了非 JTAG 模式。AX530 这类板子一般有启动模式选择跳线拨到 JTAG 一侧才能正常烧录。解决重新拔插下载线确认板子电源指示灯亮检查跳线位置如果下载器是排针连接额外确认 1 脚对 1 脚反插不会烧板子但一定连不上。烧录成功后如果 FPGA 没有运行手动按一次复位键再观察现象。5.3 串口收到乱码先查波特率再查时钟参数现象UART 回环发几个字节回显内容是乱码或丢字节。原因排查顺序先看串口助手的波特率是不是和工程一致这是最廉价也最常踩的一步再确认 CLK_FREQ 参数是否和板载晶振一致例程里写 50MHz 但板子实际是 12MHz 或 100MHz 的情况不少见。解决用示波器看 uart_txd 的波形没有示波器就开串口助手的 HEX 显示看帧结构然后查原理图确认晶振频率把 CLK_FREQ 改成实际值重跑仿真再上板。这里有个血泪经验先信原理图别信例程里写的参数例程带的参数是人家板子的参数。5.4 数码管显示错位或多位闪烁现象跑数码管例程显示的数字不对或者多位数字同时亮但亮度明显不足。原因动态扫描的刷新率太低或者换位时没有先消隐。动态扫描的原理是同一时刻只点亮一位靠人眼余晖形成多位同时亮的效果刷新率低于 50Hz 就能看到闪烁更隐蔽的问题是在切换位选前没先关掉上一位造成残影。解决扫描周期控制在 1ms 到 4ms 内对应 250Hz 以上刷新率切换位选时先让段选输出不显示内容再打开下一位。位选和段选若共用了引脚还要注意三态控制否则会出现串位。5.5 仿真波形正确、上板结果不对现象testbench 里数据收发完全正确但板上就是乱码或不响应。原因仿真只验证了逻辑没有验证时序收敛和引脚绑定。常见问题是内部信号被综合工具优化掉、多个 always 块对同一个 reg 赋值或者内部时钟没有加约束。仿真器对多驱动很宽容综合工具会报错或直接优化掉。解决打开 implementation 后的 timing summary 看有没有违例综合日志里搜索 multi-driver、latch 相关警告如果怀疑信号被优化用 ILA 抓内部信号对比仿真波形。这一条是最花时间的但也是最能长经验的。6. 例程别只烧要学会改三个上板阶段最实用的手段第一个手段是用内嵌逻辑分析仪抓内部信号告别黑匣子式调试。例程跑到后面会遇到“灯亮但是状态不对”的情况仿真和示波器都不顺手时用 Vivado 集成的 ILA 最合适。操作流程在源码里找到要观察的信号右键 Mark Debug重新综合、实现、生成比特流或者在 XDC 里直接加set_property MARK_DEBUG true [get_nets {cnt tick}]然后走完整流程。Open Hardware Manager 连接板子后把触发条件设为tick 1就能抓到 cnt 和 led 的变化。ILA 会占用片内 BRAMdebug 信号一次别加太多8 到 16 个信号、深度 1024 足够用。第二个手段是参数化重构。我的习惯是例程第一轮跑通后马上做一次清扫把所有 50_000_000、49999999、5208 这类魔法数值改成参数或 localparam删掉被注释掉的旧代码。仿真里不要直接改源文件里的 CLK_FREQ而是通过例化参数#()覆盖。这样做的好处是换板子时只看原理图改一处不用全局搜索后续加引脚约束时XDC 里的信号名和 RTL 端口名保持一致也靠这次清扫来保证。第三个手段是改代码前先固化一个能跑的版本。任何改动之前先把当前能跑出正确现象的比特流另存一份并记录它对应的源码状态。这样改坏了随时能回到上一个可用状态而不是在调试里反复猜“刚才还好好的到底哪里改坏了”。我每次改动后都重跑一遍仿真再上板坚持一次只改一个参数。这套流程带给我最大的教训是FPGA 调试耗时间的大多数情况不是逻辑太复杂而是毫无章法地改参数。按“仿真、约束、时序、内部信号”的顺序逐步排查绝大多数问题一小时内就能定位。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑