☰
AD9361 FPGA开发:Vivado TCL脚本一键构建射频收发工程
2026/10/3 6:00:58 网站建设 项目流程

如果你搞过射频收发类的FPGA开发,AD9361这颗芯片大概率避不开。它把70MHz到6GHz之间几乎全套收发链路集成到一颗芯片里,自动增益控制、可编程滤波器、频率合成器全包了,算是软件无线电领域里出镜率极高的一颗射频收发器。但真正折磨人的往往不是芯片本身,而是怎么在Xilinx Vivado里把AD9361的数字接口、引脚约束、DMA搬运这条链路完整搭起来。手动建工程的话,光是一堆IP核和几十根引脚约束就能让你折腾到怀疑人生。

ADI官方其实早就把这件事做成了自动化。在他们的hdl仓库里,针对AD9361和FMCOMMS系列评估板提供了一套完整的TCL构建脚本,配合Vivado的批处理模式,一条make命令就能完成从创建工程、生成IP、搭建Block Design到综合实现、输出比特流的全流程。这篇文章我就以FMCOMMS2+ZC706这套经典组合为例,讲讲这套TCL工作流怎么用、脚本内部怎么组织、以及我在实际开发中遇到过的坑和解决办法。内容对刚接触AD9361、或者已经受够手动建工程的同学都适用。

1. 为什么非要TCL脚本,手动建工程不香吗

1.1 手动建AD9361工程的真实体验

我第一次用AD9361的时候,还天真地打算在Vivado里手动把工程搭起来。从ADI官网下载IP包,在Vivado里一个个添加IP核,然后自己写Verilog把AD9361的引脚连到Zynq的PL端。听起来工程量不大,但真正做起来全是细节问题。

AD9361和FPGA之间不只是十几根数据线那么简单。它有两路接收两路发射,每路I/Q各12位,常用的单端口DDR模式下就有一大把数据线,再加上TX/RX使能、时钟、SPI、ENABLE、TXNRX这些控制线,总共好几十根。引脚只要错一根,或者某个Bank的io standard没设对,上板以后要么采不到数据,要么信号质量差到没法用。

还有那些IP的配置项。axi_ad9361这个核的数据通道宽度、DMA深度、寄存器映射,哪一项设错了都不好使。最痛苦的是不同Vivado版本下IP的生成方式也在变,网上搜到的教程大多数是旧版本的,照着做基本都会报错。我大概花了两个周末才把第一个工程跑通,后来才知道有官方一键生成这种操作,当时的心情不说你也懂。

1.2 ADI hdl仓库的整体结构

ADI的开源hdl仓库(github.com/analogdevicesinc/hdl)把参考设计组织得相当清晰。顶层是library、projects和scripts三大块。library下面放的是所有通用IP核的RTL源码和打包脚本,axi_ad9361、util_ad9361、axi_dmac都在这里。projects下面是以评估板命名的具体工程,fmcomms2对应AD-FMCOMMS2-EBZ这张射频子卡,下面再按FPGA载体板分目录,常见的有zc706、zc702、zed这些。

scripts目录里是构建脚本的核心,adi_make.tcl负责整个构建流程的驱动,adi_project.tcl负责创建工程和添加源文件,adi_pd.tcl管布局布线。这套脚本的核心思路是:把所有工程定义都写进TCL文件里,用Vivado自身的TCL解释器去执行。这样无论在哪台机器上、哪个版本的Vivado里,只要脚本和版本匹配,生成的工程结构就完全一致。

1.3 自动化带来的实际好处

用TCL脚本生成工程最大的优势是复现性。以前手动建工程,A机器和B机器上可能因为某个人勾选了一个设置,最终生成的比特流行为都不一样。脚本化的工程定义则是“代码即工程”,git里哪个文件改了什么,diff得一清二楚。对于需要维护多种硬件配置、多个版本的团队来说,这几乎是必备的工作方式。

对个人开发来说,省下来的时间更直接。原来可能花半天建的工程,现在一条命令跑完,期间可以去干别的。换板卡也方便,从ZC706换成ZedBoard,直接切到对应目录重新make就行。也正是因为这个原因,我在后面好几个项目里都一直沿用这套流程。

2. 准备环境:版本匹配比你想的更重要

2.1 先确认Vivado版本和hdl分支的对应关系

这一点很多人在刚开始的时候会忽略,结果编译到一半就报错。ADI hdl仓库的master分支始终对应当前最新的Vivado版本,但Vivado每次大版本升级都会改IP核格式和TCL接口,老分支配新版本基本跑不通。正确做法是:先看你本机Vivado的版本,再checkout对应的标签。

查看Vivado版本的方法很简单:

vivado -version

或者在Vivado的TCL Console里输入version命令。

然后到hdl仓库里找对应的release标签,标签名一般是hdl_YYYY_Rn这种格式,比如hdl_2021_r2对应Vivado 2021.2。举个例子,我的开发机上是Vivado 2022.1,那就用:

git clone https://github.com/analogdevicesinc/hdl.git cd hdl git checkout hdl_2022_r1

这里要特别提醒一下,不同标签的构建脚本细节可能有差异,所以用哪个Vivado版本就老老实实选哪个标签,别总想着用新版本去跑老工程来“逆向兼容”,基本是浪费时间。ADI在版本发布说明里把对应关系写得很清楚,下载之前花十分钟看一眼,能省掉后面大量的排查时间。

2.2 构建依赖与路径配置

在Linux下构建是最省心的。需要保证系统里装了make、git,而且vivado和settings脚本都在PATH里。通常我会在~/.bashrc里加上这样几行:

source /opt/Xilinx/Vivado/2022.1/settings64.sh export PATH=$PATH:/opt/Xilinx/Vivado/2022.1/bin

这样每次打开终端,make命令就能直接找到vivado。

Windows下的玩法稍微麻烦一点。我的习惯是直接打开Vivado自带的“Vivado Tcl Shell”,先source一下settings64.bat,再手动执行TCL命令。由于make在Windows下通常依赖MinGW或Cygwin,很多时候还不如直接用TCL批处理来得顺利。这也是我在前文建议Linux环境的原因,不是为了卖弄,是真的省事。

2.3 关于Vivado安装的几个小提醒

既然话题到了环境,顺便把Vivado安装阶段的常见问题也提一下。很多人卡在安装阶段,其实多半是这几个原因:下载的安装包不完整,校验和对不上;安装路径带了中文或空格;再有就是Windows下提示WinPcap安装失败。

WinPcap这个坑我特意说一下,很多教程会让用户去到处找老版本WinPcap,其实完全没必要。Vivado里依赖WinPcap的主要是旧版的ChipScope,对于现在的HDL设计和调试影响很小,直接忽略继续安装就行。真正会影响开发的是下面几个:license没加载对、JTAG驱动装不上、以及中文注释乱码。

license文件这个问题被问得最多。WebPack版本能覆盖大部分中低端器件,Zynq-7000用WebPack license基本够用。如果用了Kintex-7 UltraScale这类器件还报license不满足,通常会提示没有对应器件的授权,这时候要去Help -> Manage License里确认加载的是不是正确的license文件。另外很多license是按年份授权的,过期以后Vivado会直接拒绝生成比特流,检查license日期是第一步。

3. 实际操作:一条make命令跑通AD9361 HDL工程

3.1 进入工程目录并执行构建

环境就绪之后,操作其实就三步。第一步是进入目标工程目录:

cd hdl/projects/fmcomms2/zc706

第二步是执行make:

make

如果是第一次构建,这一步会跑很久,顺利的话二三十分钟到一小时不等,具体看机器性能。跑的过程中终端会滚动大量日志,第一次见这个阵势不用慌,正常现象。

make的过程中,你能看到类似这样的输出:

Vivado v2022.1 (64-bit) ... INFO: [Common 17-234] Creating project: fmcomms2_zc706 ... INFO: [IP_Flow 19-234] Generated IP 'axi_ad9361_0' ... INFO: [Vivado 12-1842] Bitgen Completed Successfully

看到Bitgen Completed Successfully,说明比特流已经生成。如果中途因为某种原因断了,重新执行make一般会自动从断点继续,但有时候也需要先make clean再重来,具体看报错信息判断。

如果本机装了多个Vivado版本,或者Vivado不在默认路径,可以显式指定:

make VIVADO_SETTINGS=/opt/Xilinx/Vivado/2022.1/settings64.sh

注意不同版本的Makefile里这个变量名可能略有差异,不确定就打开Makefile搜一下VIVADO。

3.2 make内部到底调了哪些脚本

刚开始用的时候我也好奇,make背后到底是什么逻辑。后来翻了一下Makefile就明白了,它本质上是在调用Vivado的batch模式去执行TCL脚本。工程目录下的Makefile里,核心命令大概长这样:

vivado -mode batch -source ../../../scripts/adi_make.tcl

当然实际传的参数更多,但主体就是这个。

adi_make.tcl会依次调用projects/scripts目录下的几个TCL文件。先是adi_project.tcl,它负责创建Vivado工程、把顶层的system_top加进来、调用system_bd.tcl生成Block Design、把约束文件加进来。之后的流程才是综合、实现和生成比特流。整个链条是脚本驱动自动跑的,不需要人工干预,这也是能实现一键构建的根本原因。

如果你只想生成工程、不跑综合实现,可以手动执行adi_project.tcl那一层,在Vivado的TCL Console里运行:

source ../../../scripts/adi_project.tcl

然后再用adi_make.tcl去继续后面的流程。这种精细控制平时用不太到,但了解之后对理解整套架构很有帮助。

3.3 构建产物在哪里

编译完成后的东西都在工程目录下。最常见的两个输出是:

  • fmcomms2_zc706.runs/impl_1/fmcomms2_zc706.bit:比特流文件,直接用来配置FPGA;
  • fmcomms2_zc706.sdk/system.hdf:硬件描述文件,给Vitis/SDK用的,包含硬件平台和IP配置信息。

如果后续要在Zynq上跑Linux,然后通过AXI总线去配置AD9361的寄存器,这个hdf文件是必需的。Vitis新建平台工程时直接指向这个hdf就行。比特流则是在裸机跑或者只想用PL逻辑时使用。如果做PetaLinux开发,还会用到导出的.xsa或.fpg文件。这些衍生文件都在各自的输出目录里,随时可以拿来做下一步开发。

3.4 不用make,纯TCL手动跑能行吗

我自己偶尔也会在Vivado图形界面里跑TCL,主要为了调试方便。在Vivado的TCL Console里可以一行一行地看过程:

cd /path/to/hdl/projects/fmcomms2/zc706 source ../../../scripts/adi_make.tcl

这样在GUI下就能看到工程的创建过程,甚至可以在工程创建完、流程跑到一半的时候打开Block Design检查连接。不过日常构建我建议还是用make,封装更友好、参数好控制,而且不需要额外记那些TCL参数。等你需要频繁改脚本、定制工程的时候,再深入研究TCL调用细节也不迟。

4. 生成的工程里,AD9361相关部分是怎么组织的

4.1 顶层设计与Block Design

打开生成的xpr工程后,会看到工程里有顶层文件system_top,这个文件里例化了整个Block Design,以及若干与子卡接口相关的引脚逻辑。Block Design里的核心是Zynq PS核,PL侧的AXI从端口通过AXI互联连接到多个IP核上,其中最主要的就是axi_ad9361和axi_dmac。

整个数据链路可以简化成: AD9361 <-> FPGA引脚 <-> util_ad9361 <-> axi_ad9361 <-> axi_dmac <-> DDR

专业软件无线电里常见的下发/采集链路就是这样搭的。发送方向,PS通过DMA把I/Q数据从DDR搬运到PL,再经过axi_ad9361按照采样时钟速率送给AD9361;接收方向则完全反过来。理解这条链路,后面调数据的时候心里就有谱了。

4.2 AD9361相关的关键IP核

axi_ad9361是ADI自研的软核IP,它实现了与AD9361数字接口的协议,包括数据有效标志、同步、时钟切换等逻辑。它挂在AXI总线上,对PS来说就是一组寄存器空间,可以通过读写寄存器去控制收发状态。常见操作比如配置AD9361的工作模式、读取状态寄存器、控制频率和增益等,最终都是通过SPI或数据的包络寄存器去间接操作AD9361。

util_ad9361这个IP负责把AD9361原始接口的数据格式转换成AXI总线用的格式,同时处理跨时钟域的问题。AD9361那边跑的是采样时钟域,AXI总线这边跑的是PL fabric时钟域,两个域之间必须通过FIFO或寄存器组缓冲,util_ad9361干的就是这个活。如果这个环节的时序没处理好,表现出来就是偶发丢数、数据错位,很难排查。

axi_dmac是ADI的DMA控制器,功能不复杂但很好用。它可以配置成存储器到外设(TX方向)或者外设到存储器(RX方向),每次传输的描述符、长度、地址都可以由软件动态配置。调试的时候可以先用简单的寄存器读写模式验证通路,再用DMA跑大量数据做压力测试。

4.3 约束文件里藏着的关键信息

工程目录下的约束文件是system_constr.xdc,里面定义了两类关键约束。第一类是顶层引脚分配,比如AD9361的数据线、时钟线、控制线都分配到了FPGA的哪个引脚。这些引脚位置不是随便定的,而是跟FMCOMMS2子卡在FPGA开发板上的FMC连接器引脚一一对应。只要硬件连接和参考设计一致,这些约束就别乱动。

第二类是时序约束,包括由AD9361提供的采样时钟、FPGA内部时钟之间的约束关系。我调试时遇到过一次情况,在工程里改了一个引脚位置,实现之后报出一堆时序违例,查了半天才发现是时钟约束和实际路径对不上。正确的做法是:除非硬件设计有变动,否则约束文件尽量使用脚本生成的原始版本,改动之前先完整理解每一行的含义。把xdc文件从头到尾读一遍,很多时候能帮你避免莫名其妙的时序问题。

4.4 Block Design里还能怎么扩展

生成好的Block Design不是只能原样用。实际项目里经常会往里加自己的逻辑,比如在AXI总线上挂一个自定义的基带处理IP,或者在DMA通路里插一个数据预处理模块。操作上,在Vivado里打开Block Design,右键点击画布就能添加IP,自定义IP只要打包好放在库目录里就能被引用。加完之后,如果不想每次都在GUI里重复操作,可以改动同步回system_bd.tcl里,下次重新make的时候就直接带上了。

要注意的是,修改system_bd.tcl后重新构建,会把原来的输出目录清掉重来。如果有不想丢的中间结果,先备份。这个机制虽然有点“暴力”,但也保证了每次构建的可复现性——你改了什么,脚本里一目了然。

5. 实操中的常见问题和排查记录

5.1 Vivado版本与IP版本不匹配

这个坑估计十个人里面能踩八次。报错信息往往是这样:

ERROR: [IP_Flow 19-366] IP 'axi_ad9361_0' uses IP definition version 1.0, but the current project uses version 1.1.

或者说某个IP的模板文件找不到。原因基本都是hdl分支和Vivado版本对应不上。解决办法就是先确认版本对应关系,git checkout到匹配的分支,然后删掉工程目录下之前生成的东西,重新make。不要试图只升级单个IP的版本,ADI的IP在版本之间改动比较大,单独升级容易引起连锁报错。

5.2 license问题导致综合或生成比特流失败

综合到一半提示没有license,比较常见的是:

ERROR: [Vivado 12-1344] No license is available for the 'Kintex-7' target device.

这种一般是Vivado本身没有对应器件的授权。打开Help里的License Manager,看一下当前加载的license文件是否覆盖目标器件。Zynq-7000的话,免费WebPack基本够用;换成其他器件系列,就得确认付费license有没有包含对应型号。有些license是按年份授权的,过期以后Vivado会直接拒绝生成比特流,检查license到期时间是排查第一步。

5.3 时序违例和布线失败的排查思路

生成比特流失败另一个常见原因,是布局布线之后时序不满足。看到这样的输出:

WARNING: [Timing 38-282] There are 12 failing timing paths in the design after implementation.

建议先打开时序报告,找到关键路径。一般几个方向排查:Block Design里的时钟频率设置是否合理,跨时钟域的部分有没有做同步处理,约束文件有没有被误改。如果时序余量只是差一点点,可以试试把综合策略调成时序优化优先、提高Effort Level,或者调整某些IP的实现选项。做射频数据流这种场景,时钟频率通常不算太高,不太容易出现极端的时序压力,所以时序一出问题,先怀疑约束文件。

引脚约束冲突也会导致实现失败,报错一般在Place阶段,比如某个Bank的电压标准不匹配。检查FMC连接器对应的引脚Bank电压是否设成一致的LVDS或CMOS标准,我就遇到过一边设成LVDS25、另一边设成LVDS18,最后布线直接失败的案例。

5.4 ILA调试AD9361数据接口的注意事项

很多人在工程里加ILA(集成逻辑分析仪)去看AD9361的数据线和有效信号。ILA本身挺好用,但有几点要上心。首先,ILA的采样时钟必须和被测信号的时钟域同源,否则采出来的波形全是乱的。AD9361的并行数据有自己的采样时钟,最好直接用这个采样时钟作为ILA的时钟。其次,采样深度不要开太大,ILA数据存在BRAM里,深度4096已经能看到比较长的序列了,不用贪多,否则占用资源太多会影响布局布线。

关于ILA采样频率,经常有人问“是不是有上限”。实际上它没有独立的高频采样能力,上限就是你所选时钟域的运行频率。一般几百MHz没问题,但别指望ILA能像示波器那样做高倍频采样。它的定位是同步逻辑调试工具,抓到的是时钟沿上的数据,既然数据是采样时钟同步的,只要时钟域正确,抓出来的时序就是可靠的。

5.5 综合避坑清单

再列几个我实际踩过的细节问题,做成一个速查表方便大家对照:

现象原因解决办法
Windows下安装Vivado提示WinPcap失败ChipScope组件需要老库,新系统兼容性差不影响HDL开发,忽略继续安装
Vivado工程里中文注释显示乱码默认编码不是UTF-8使用外部编辑器如VSCode并保持UTF-8,或改用英文注释
命令行输入vivado打不开PATH没配置source settings64.sh,或者手动执行vivado绝对路径
JTAG驱动无法识别开发板驱动未安装或版本不对到Vivado安装目录下的cable_drivers目录安装Cable Driver,然后重插USB
综合报告里的Fmax和预期差很远Block Design时钟约束不对检查create_clock和set_clock_groups是否覆盖了所有时钟域
工程拷到别的机器打不开IP路径和中间产物失效不要拷贝xpr,拷贝源码仓库后重新make一次

这张表里的很多问题,单看起来和AD9361无关,但开发中一旦碰到,排查起来非常耗时。先把环境基础打稳,才能把更多精力放在数据通路和业务逻辑本身。

5.6 工程克隆后需要重新生成的东西

最后再说一个容易被忽略的点。如果你把整个工程目录复制到另一台机器,或者不小心清理了中间产物,直接双击xpr继续跑,经常会报IP missing、文件不存在之类的错误。正确的做法是:不要依赖拷出来的xpr,而是把源码仓库原样拷过去,在新机器上重新make一次。TCL脚本的好处就在这里,别人拿到你的工程,只要环境和版本对上,一条命令就能复现出完全一样的构建过程。这比打包一堆xpr、runs目录要可靠得多。

写在最后的一点体会

这套TCL脚本构建流程,我前前后后用了好几年,从ZC706做到ZedBoard,再到国产的Zynq板卡,核心思路都没变。我的建议是,第一次用的时候按官方流程跑通,然后打开生成的工程、认真翻一遍Block Design和约束文件,把每个环节和脚本对上号。等你知道哪段TCL干了哪件事之后,后面不管换平台还是扩展功能,都可以在脚本基础上改,效率会明显上来。如果只是照着点鼠标复制工程,遇到问题还是一头雾水。

最后分享一个实用小技巧:构建的时候不用把并行线程拉到很高,我一般开4到8个就好,机器负载不会太满,也不会慢到不能接受。还有就是构建日志建议保留一份,出问题时翻日志比重新跑一遍快多了,日志里其实已经把关键错误都告诉我们了,只是需要耐心去看。希望这篇能帮大家少踩几个坑,顺利把AD9361的工程跑起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询