简介:AD9361 Vivado例程是一套面向FPGA与软件定义无线电开发者的完整工程参考包,基于Xilinx Vivado 2016.4环境,展示如何通过IP核集成、AXI接口互联、参数配置与时序约束驱动AD9361射频收发器,适用于无线通信、测试测量等场景的开发者快速上手。压缩包共2000个文件,涵盖550个VHDL/Verilog源文件、446个Tcl脚本、319个C源文件与306个头文件,同时包含XDC约束、BIT比特流、XCI核配置、Makefile编译脚本、文档及报告等,便于从源码编译到硬件下载全流程对照学习。包体约95.07MB,目录结构完整。已有4811人学习使用。通过这份例程,读者可掌握AD9361发射/接收通道配置、数字接口时序设计以及Vivado工程管理方法,为自研RF通信链路提供可直接参考的实现蓝本与排错思路。 聊到软件无线电和FPGA开发,AD9361和Vivado这个组合几乎绕不开。ADI这颗宽带收发器芯片覆盖70MHz到6GHz,内置12位ADC/DAC、可编程基带滤波器和各种射频校准逻辑,本身就是一个完整的射频前端。但它在FPGA上真正跑起来,靠的是官方维护的HDL参考例程和配套的no-OS驱动——也就是大家经常搜的"ad9361_vivado例程"。这篇文章不是翻译官方Wiki,而是我从零开始用这套例程做一个收发项目时的完整复盘,包括例程目录结构、Vivado工程生成方式、数据通路解析,以及我踩过的一些坑。
1. 例程框架与项目理解:AD9361 + Vivado 到底怎么配合工作
1.1 AD9361与FPGA之间不只是“接几根线”那么简单
很多人第一次接触AD9361,第一反应是“这不就是个射频芯片嘛,SPI配置一下,LVDS数据接进FPGA,完事”。等真正动手才发现,问题远比想象中多。AD9361和FPGA之间至少存在两个层面:控制面是SPI接口,负责寄存器读写,配置频率、增益、滤波器;数据面是并行LVDS或CMOS接口,负责收发采样数据。数据面的位宽、采样率、时钟极性、延迟对齐,任何一个细节不对,收上来的IQ数据就是乱的。
官方例程的价值就在这里。它不是一个简单跑马灯工程,而是把AD9361的HDL接口、AXI总线、DMA搬运、时钟生成、复位逻辑全部打通,甚至连管脚约束和时序约束都帮你写好了。对于大多数项目来说,你不是从零写AD9361驱动,而是在官方例程基础上做裁剪、增加自己的DSP逻辑、改接口时序。理解这套例程,比重新造轮子重要得多。
1.2 官方例程由哪几块组成
ADI维护了三套直接相关的仓库:hdl、no-OS和linux。hdl仓库里是所有FPGA参考设计,按板卡和子卡组合分目录,比如projects/fmcomms2/zed、projects/fmcomms4/zc706;no-OS仓库是裸机驱动,里面drivers/rf-transceiver/ad9361是芯片驱动核心;linux仓库则集成了内核驱动和设备树,主要给PetaLinux用户用。
我们平时说的“ad9361_vivado例程”,通常指的就是hdl/projects/下面那些带FMCOMMS子卡名字的工程。以fmcomms2为例,工程顶层会有一个system_top.v,把PS侧的GPIO、I2C、SPI、UART等外设引出,同时例化了axi_ad9361、axi_dmac、axi_clkgen这些IP。理解工程生成方式,比直接打开一个.xpr文件更重要,因为官方例程是用Tcl脚本和Makefile自动生成的,换个板卡或者换个Vivado版本,只需改参数重新跑一次脚本。
2. 快速生成 Vivado 工程:环境、脚本与版本匹配
2.1 环境准备和版本选择
Vivado版本选择是第一个坑。ADI的hdl仓库会随版本演进更新,老的release tag只对应特定Vivado版本。以我长期用的hdl-2020_r2分支为例,它对应Vivado 2020.2;如果你用2023.2打开老脚本,很大概率会遇到IP版本不匹配、IP核无法升级的问题,甚至脚本直接报错。建议装Vivado时不要追新,先确认你手上板卡对应的release分支,再选版本。
此外,Vivado安装时一定要勾选对应器件系列。很多人在创建工程时发现“没有要选择的芯片”,就是安装时没勾选Zynq或UltraScale+器件库,后面补装比较麻烦。如果你的板子是Zynq-7000,必须在安装向导里勾上Zynq-7000支持;如果是ZCU102,就要勾UltraScale+。这一步看似简单,却是我见过新手翻车最多的地方。另外,所有工程都建议在正版授权环境下使用,Vivado的license缺失会让IP无法综合,很多“生成比特流失败”其实根源在这里。
2.2 用Makefile生成工程:以fmcomms2/zed为例
官方推荐用Makefile生成工程,而不是直接打开Vivado GUI新建。下面是一套我验证过的流程,假设你已经装好Vivado 2020.2并配置好环境变量:
git clone --recursive https://github.com/analogdevicesinc/hdl.git cd hdl git checkout hdl-2020_r2 source /opt/Xilinx/Vivado/2020.2/settings64.sh cd projects/fmcomms2/zed makemake过程会调用Vivado的Tcl脚本,依次完成IP生成、综合、实现并生成比特流。这个过程比较长,第一次跑可能十几分钟,属于正常现象。完成后你会看到工程目录下出现了fmcomms2_zed.runs、fmcomms2_zed.srcs这样的Vivado工程目录,以及fmcomms2_zed.sdk这种用于SDK/Vitis的硬件导出目录。
如果想看具体结构而不想等完整跑完,可以在工程目录下运行make ip只生成IP,再用Vivado打开生成的工程;也可以直接make gui,用图形界面方式生成工程。我个人建议第一次还是完整跑一遍,因为后续改逻辑时需要确认整个链路时序能收敛。
2.3 版本匹配比“能用”更重要
ADC/DAC类接口有一个特点:接口延迟和时钟关系非常敏感。官方约束里针对AD9361的输入时钟、输出数据、TX时钟都做了约束说明,老版本工程的约束写法可能跟新版本时序引擎不兼容。比如在Vivado 2020.2之后,时序引擎对时钟命名、group的检查更严格,老脚本里写死的get_clocks名字在新版本可能找不到,直接报:
[Vivado 12-4739] set_clock_groups:no valid object(s) found for '-group [get_clocks ad9361_rx_clk]这类报错不代表逻辑错了,多半是时钟名被综合工具调整,比如原名是ad9361_rx_clk,综合后变成了ad9361_clk或带有层次前缀的时钟名。解决思路不是删约束,而是打开综合后的report_clocks,看实际时钟名,再改约束文件。这也是为什么我一直强调版本匹配:官方release tag已经针对对应Vivado版本验证过约束,你换版本就要自己接盘所有时序问题。
3. 例程内部结构拆解:数据怎么从天线流进DDR,又怎么从DDR发出去
3.1 一条完整的数据通路
把官方例程当作黑盒跑起来很简单,但真正调试问题还得理解数据通路。RX方向,AD9361输出的并行IQ数据先进入axi_ad9361IP,经过串并转换和延迟校准后,交给util_ad9361中的FIFO,再转换成AXI-Stream协议,由axi_dmac把数据搬进PS的DDR内存。TX方向正好相反:DDR里的数据经axi_dmac读出,进入FIFO,再经axi_ad9361转成AD9361需要的并行格式,最终通过LVDS引脚送到芯片。
这个链路里有一个非常关键的模块叫 AXI-DMAC,它是Xilinx/ADI维护的高性能DMA控制器。它的内部有描述符链、字节交换和突发传输逻辑,支持循环采集和单次采集。你在SDK里看到的“采集一帧、读回内存”操作,本质都是往DMAC寄存器写描述符,再由它自动搬运。理解这条链路,你才能在调试时判断数据是卡在物理接口,还是卡在DMA,还是卡在PS端内存访问。
3.2 关键IP:axi_ad9361与util_ad9361的分工
axi_ad9361更多负责“芯片侧”:SPI寄存器映射、接口模式配置、状态采集、数字接口延迟校准。它提供一个AXI-Lite接口,让CPU可以配置AD9361,同时也能在FPGA内部产生中断、读取锁定状态。而util_ad9361则负责“FPGA侧”:数据FIFO缓冲、跨时钟域处理、AXI-Stream打包解包。两个IP必须配合,单独看任何一个都很难理解数据是怎么从模拟域进来的。
我在调试时最常用的是util_ad9361里的FIFO empty/full标志。如果RX方向FIFO一直空,说明AD9361到FPGA接口没有数据进来,问题大概率在物理层或SPI配置;如果FIFO一直满但DMA没有搬走,问题在DMA描述符或中断处理。官方例程里已经把状态寄存器挂到AXI总线,你可以直接在SDK里读寄存器,不用再拉ILA。
3.3 例程中的约束:时序是射频接口的隐形敌人
官方XDC约束里除了管脚分配,还有不少set_input_delay、set_output_delay、set_clock_groups。这些约束不是摆设。AD9361的RX时钟和TX时钟是不同源时钟,必须设成异步group,否则综合工具会认为它们需要分析跨时钟路径,导致大量假时序违例。很多人跑完实现发现时序一片红,不是设计问题,而是约束里少了异步group声明。
调试时序问题时,我的建议是先用report_timing_summary看是不是只有跨时钟域路径违规,如果是,回到约束文件里检查set_clock_groups;如果数据路径本身违规,那就要看接口延迟是否写对。这里有个小技巧:AD9361数据接口延迟参数可以从数据手册的AC时序表里查到,单位是ns,但约束文件里经常用管脚到寄存器的相对值,不能直接照抄,需要结合PCB走线长度做估算。
4. 从例程到可运行代码:no-OS驱动与最小收发通路
4.1 no-OS驱动能做什么
hdl仓库解决了FPGA侧工程,但没有芯片驱动,AD9361依然一动不动。no-OS驱动的核心价值是:用纯C代码实现AD9361寄存器初始化、射频参数计算、增益控制、滤波器配置等,不依赖Linux内核。它生成的库很小,非常适合Zynq裸机或者软核MCU场景。
下载编译也很简单:
git clone --recursive https://github.com/analogdevicesinc/no-OS.git cd no-OS makeno-OS根目录下会生成对应平台的二进制。实际使用时,通常把no-OS工程导入到Vitis SDK,再配合上一层导出的硬件描述文件(如.xsa)一起编译。官方Wiki里建议把hdl生成的硬件平台导入Vitis后,再添加no-OS的ad9361软件库,这么做的原因是需要直接用硬件平台自带的BSP,避免串口、中断配置重新写。
4.2 最小初始化代码长什么样
一个最简的AD9361初始化过程,通常包括分配结构体、填写初始化参数、调用ad9361_init、设置收发频率和采样率。下面是核心伪代码风格示例:
struct ad9361_init_param init_param; struct ad9361_rf_phy *phy; memset(&init_param, 0, sizeof(init_param)); init_param.id = AD9361; init_param.rx_freq = 2400000000ULL; init_param.tx_freq = 2400000000ULL; init_param.rx_rx_bandwidth = 20000000; init_param.tx_tx_bandwidth = 20000000; init_param.rx_sampling_freq = 40000000; init_param.tx_sampling_freq = 40000000; ad9361_init(&phy, &init_param); ad9361_set_tx_lo_freq(phy, 2450000000ULL); ad9361_set_tx_gain(phy, 60000);这里面的参数不是随意填的,每个值都对应AD9361内部一系列寄存器计算。比如采样率、带宽、滤波器阶数之间是强耦合的,有些组合芯片不支持,这时ad9361_init会返回错误,需要调整参数。我最初把TX采样率设成与RX一致,但带宽设得很窄,结果AD9361初始化一直失败,后来改成带宽是采样率的2/3左右就好了。所以建议先用官方默认初始化参数跑通,再逐步改。
4.3 在例程工程里跑一个最小收发测试
FPGA工程生成、no-OS驱动编译通过后,就可以在SDK里写一个循环程序:初始化AD9361,配置DMAC描述符,启动RX DMA,发送一串单音数据,再读取RX数据。最简单的自测方法是不接天线,直接把TX输出用同轴线连回RX输入,代码里让TX发送一个低频单音,RX端收到的IQ数据在频谱上应该出现明显谱线。
如果接收数据全零,先别急着重焊板子,大概率是SPI没通。检查SPI片选极性、时钟极性和时钟分频。AD9361的SPI模式比较常规,但你用GPIO模拟SPI时极容易忽略片选保持时间,导致寄存器写不进去。官方例程默认使用PS的SPI控制器,代码里一般都有SPI初始化,排错时先读一个chip id寄存器,能读到AD9361_PRODUCT_ID说明SPI基本没问题。
5. 实战中踩过的坑:时钟约束、仿真闪退、比特流失败、芯片选不到
5.1 Vivado本身的高频问题速查
热词里经常出现“Vivado仿真闪退”“Vivado生成比特流失败”“Vivado没有要选择的芯片”,这三个我都遇到过。
仿真闪退在Windows上尤其常见,多数是显卡驱动或OpenGL加速兼容性问题。可以先尝试在Vivado里关闭硬件加速:Tools -> Settings -> Display -> Disable acceleration。如果还闪退,把工程放到纯英文路径下,并检查是不是用了远程桌面导致OpenGL上下文出错。生成比特流失败的原因更多样,我遇到过IP输出引脚未连接、BRAM资源超限、时序严重违规。处理顺序是:先看implementation目录下的report_timing_summary.rpt,确认是否有红色违规;再看report_utilization.rpt确认资源是否够;最后检查所有IP是否都已正确配置并处于Synthesized状态,Vivado在IP锁定时静默跳过的坑很隐蔽。
“没有要选择的芯片”这个问题,基本就是安装Vivado时器件库缺失。解决办法不是重新下载整个Vivado,而是打开Vivado License Manager里的Install Devices选项,选择对应系列补装。如果安装镜像里没有对应系列,那就只能下次安装时提前勾选,这也是我建议新手“一次装全”的原因。
5.2 工程移植时最容易被忽略的坑
很多人喜欢直接把官方工程拖到别的版本Vivado里打开,结果一堆IP变黄色锁图标。这时候不要强行Upgrade IP,因为有些IP升级后端口列表变了,顶层连线会断,反而增加排查成本。正确做法是先看官方release分支支持的Vivado版本,尽量用同一个版本建工程;如果你不得不用新版本,那就老老实实重新生成工程脚本,然后在system_top.v顶层检查IP端口变化。
另外,在make生成工程后,随便改动Vivado工程里的IP设置会导致脚本和工程不同步。我的习惯是:硬件逻辑改动写在hdl/projects/fmcomms2/zed下新增的RTL文件里,在顶层例化自己的模块,然后把新文件加入工程。这样即使整个工程重建,改动逻辑也不会丢。如果直接在Vivado图形界面里拖IP,下次make clean全没了。
5.3 一个实用的排查速查表
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| AD9361寄存器读回全FF | SPI配置错误或芯片未上电 | 检查SPI时序、电源域、复位引脚 |
| RX数据全零 | 收发未连接、增益为0、DMAC未使能 | 先用单音回环测试,检查DMAC描述符 |
| 仿真闪退 | 显卡加速/工程路径中文 | 关闭硬件加速、换英文路径 |
| 综合后时钟找不到 | 时钟名被优化/约束名错误 | report_clocks查看实际时钟名 |
| 生成比特流失败 | 资源或时序不满足 | 查实现报告,按顺序处理 |
| “没有要选择的芯片” | 器件库未安装 | 用License Manager补装器件支持 |
这张表是我每次做技术支持时常用的排查起点,覆盖了大多数基础问题。真正复杂的问题往往出在物理层,比如AD9361和FPGA之间的LVDS电平不匹配,这时用示波器看时钟和数据引脚才是最快的。
6. 最后分享一点个人体会
我从最开始对着官方Wiki懵懂操作,到后来能根据自己的板子裁剪例程、加自定义DSP模块,最大的体会是:不要把AD9361例程当成黑盒。Vivado和AD9361这对组合,真正的门槛在于“接口时序的理解”,而不是“跑通一个工程”。官方例程已经帮你规避了90%的底层坑,剩下10%需要你老老实实读约束、看IP代码、读寄存器。最后再分享一个实用小习惯:每次重新生成工程之前,先在hdl仓库目录下git status确认没有本地未保存的改动,否则一个make clean就能让你白改半天。这套例程能陪你从裸机调试一路走到Linux集成,值得花几个晚上把它的每个模块都看明白。
本文还有配套的精品资源,点击获取