
如果你搞过射频收发类的FPGA开发AD9361这颗芯片大概率避不开。它把70MHz到6GHz之间几乎全套收发链路集成到一颗芯片里自动增益控制、可编程滤波器、频率合成器全包了算是软件无线电领域里出镜率极高的一颗射频收发器。但真正折磨人的往往不是芯片本身而是怎么在Xilinx Vivado里把AD9361的数字接口、引脚约束、DMA搬运这条链路完整搭起来。手动建工程的话光是一堆IP核和几十根引脚约束就能让你折腾到怀疑人生。ADI官方其实早就把这件事做成了自动化。在他们的hdl仓库里针对AD9361和FMCOMMS系列评估板提供了一套完整的TCL构建脚本配合Vivado的批处理模式一条make命令就能完成从创建工程、生成IP、搭建Block Design到综合实现、输出比特流的全流程。这篇文章我就以FMCOMMS2ZC706这套经典组合为例讲讲这套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第二步是执行makemake如果是第一次构建这一步会跑很久顺利的话二三十分钟到一小时不等具体看机器性能。跑的过程中终端会滚动大量日志第一次见这个阵势不用慌正常现象。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比特流文件直接用来配置FPGAfmcomms2_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的工程跑起来。