☰
AD9361 Vivado例程实战:从工程搭建到上板调试的完整指南
2026/10/26 5:47:48 网站建设 项目流程

简介:面向软件定义无线电与FPGA射频开发者的AD9361 Vivado工程例程,聚焦在Xilinx Vivado 2016.4环境下完成AD9361收发器的IP核集成、接口连接、参数配置与时序约束,适合已具备一定HDL基础、希望快速搭建RF通信原型的工程师与高年级学生参考。压缩包约95.07MB,共2000个文件,以Verilog源文件(550个.v)与Tcl脚本(446个.tcl)为核心,辅以C源码与头文件、XDC约束、XCI IP配置、比特流及PDF文档,覆盖从设计输入到实现下载的完整链路。已有4867人学习下载。例程保留了工程源码、配置文件与测试平台,读者可据此理解AD9361 IP核的AXI接口组织方式、时钟与数据通路设计,并借助仿真与硬件测试环节排查时序与配置问题,是上手AD9361射频收发设计的实用起点。

1. 从一块板子说起:ad9361_vivado例程到底解决什么问题

手里有一块 AD9361 射频收发板,FPGA 侧却连不上、读不到版本号、采样数据全是零——这是很多做软件无线电的开发者第一次上板时的真实处境。ad9361_vivado例程就是为这个场景准备的:它把 AD9361 的 SPI 配置、时钟与数据接口、收发数据通路在 Vivado 工程里搭好,让你不用从零啃寄存器手册,直接打开工程、综合、下载、看波形。适合两类人:一是刚拿到 AD9361 板卡、想先跑通链路的 FPGA 新手;二是已有基带处理代码、需要一套可复用的射频前端接口做二次开发的熟手。它不是一个算法库,而是一套“能跑起来的硬件底座”。

2. 工程结构与技术选型:从顶层到约束的完整骨架

2.1 为什么是 Vivado + 纯 RTL 而不是 HLS 封装

AD9361 的接口时序对时钟相位和延迟很敏感,数据线是 DDR 双沿采样,用 HLS 封装反而容易在时序收敛上翻车。这套例程走的是纯 Verilog/VHDL 的 RTL 路线,配合 Vivado 的 IP Integrator 做时钟和接口管理,好处是每一级延迟都能在代码里数清楚,出问题能定位到具体寄存器。常见做法是把 AD9361 的接口拆成三块:SPI 配置模块、接收数据通路(RX)、发送数据通路(TX),顶层只做连线和时钟分发。这样二次开发时替换任意一块都不影响其他部分。

技术栈上,工程依赖 Vivado(建议 2019.1 及以上,太老的版本对 7 系列之后的器件支持不全)、AD9361 的寄存器配置表(通常以 .txt 或 .coe 形式存在)、以及板级约束文件 XDC。约束文件是这套例程里最容易被忽视又最要命的部分,引脚分配和时钟周期约束写错,综合能过但上板必挂。

2.2 目录结构逐层拆解

拿到工程后先别急着打开,用文件管理器把目录过一遍,心里有个地图:

ad9361_vivado/ ├── rtl/ │ ├── top.v # 顶层,例化各子模块并连线 │ ├── ad9361_spi.v # SPI 读写状态机 │ ├── ad9361_rx.v # 接收数据 DDR 采样与对齐 │ ├── ad9361_tx.v # 发送数据组帧与输出 │ └── clk_gen.v # 时钟分频与相位管理 ├── ip/ │ └── clk_wiz_0/ # MMCM 时钟 IP,生成接口所需频率 ├── constr/ │ └── board.xdc # 引脚与时钟约束 ├── sim/ │ └── tb_top.v # 行为级仿真激励 └── scripts/ └── build.tcl # 命令行综合与实现脚本

这个结构的关键在于 rtl 下每个文件职责单一。top.v 里不应该出现任何寄存器地址或状态机逻辑,它只负责把 spi、rx、tx、clk_gen 的端口对接起来。我一般会先打开 top.v 看端口列表,再顺着端口去翻对应子模块,比从 SPI 状态机开始读效率高得多。scripts/build.tcl 是很多人会忽略的宝藏,它把综合、实现、生成比特流的命令固化下来,配合 Vivado 的批处理模式可以做到一键构建,省去每次点鼠标的重复劳动。

2.3 时钟与接口:AD9361 数据通路的物理层约定

AD9361 和 FPGA 之间的数据接口是 DDR 风格:DATA_CLK 作为源同步时钟,RX_FRAME 指示帧边界,六根数据线在时钟双沿传输。这意味着接收侧必须用 IDDR 原语把双沿数据拆成两路单沿,再做对齐。例程里 ad9361_rx.v 的核心就是这段:

// 接收侧 DDR 采样:把双沿数据拆成上升沿/下降沿两路 genvar i; generate for (i = 0; i < 6; i = i + 1) begin : gen_iddr IDDR #( .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), .INIT_Q1(1'b0), .INIT_Q2(1'b0), .SRTYPE("SYNC") ) u_iddr ( .Q1(rx_data_rise[i]), // 上升沿采到的数据 .Q2(rx_data_fall[i]), // 下降沿采到的数据 .C(data_clk_buf), // 缓冲后的 DATA_CLK .CE(1'b1), .D(rx_data_pad[i]), // 来自引脚的数据 .R(1'b0), .S(1'b0) ); end endgenerate

这段代码的逻辑是:AD9361 在 DATA_CLK 的上升沿和下降沿都输出有效数据,IDDR 原语负责在 FPGA 内部把两路分开,后续再按帧信号拼成 12 位样本。参数里DDR_CLK_EDGE选SAME_EDGE_PIPELINED是为了让 Q1/Q2 在同一时钟沿输出,方便下游做对齐;SRTYPE选SYNC表示复位是同步的,避免异步复位带来的亚稳态。data_clk_buf 必须先经过 BUFG 再送进 IDDR,直接拿引脚时钟驱动原语是典型错误,综合工具会报时钟布线问题。

发送侧反过来,用 ODDR 把两路单沿数据合成双沿输出,时序约束里要对 TX 数据相对 DATA_CLK 做输出延迟约束,否则接收端采样点会漂。这部分在 board.xdc 里体现为 set_output_delay,具体数值取决于板级走线长度,例程给的是保守值,实际调试时需要用示波器或眼图微调。

3. 跑通链路:从综合到上板看数据的实操路径

3.1 环境准备与工程打开

先把 Vivado 版本对齐,例程的 IP 是用某个版本生成的,跨大版本打开会提示 IP 升级,升级本身不复杂但可能改变端口行为。我的习惯是新建一个同版本的空工程,把 rtl、constr、ip 三个目录用 Add Sources 加进去,而不是直接双击 .xpr,这样能避免路径里带中文或空格导致的玄学报错。加完源文件后检查 Hierarchy 窗口,top 下面应该能看到 spi、rx、tx、clk_gen 四个实例,缺一个就说明文件没加全。

接着跑一次综合,别急着实现。综合阶段主要看两件事:有没有 latch 推断警告,有没有时钟未约束警告。前者通常来自 SPI 状态机里某个分支漏了默认赋值,后者说明 XDC 没生效。综合通过后再跑实现,实现阶段重点看时序报告里的 WNS(最差负裕量),如果为负,先别上板,回去查约束。

3.2 用 Tcl 脚本做可复现构建

图形界面点多了容易漏步骤,scripts/build.tcl 就是为此准备的。核心命令如下:

# 打开工程并依次执行综合、实现、生成比特流 open_project ./ad9361_vivado.xpr reset_run synth_1 launch_runs synth_1 -jobs 8 wait_on_run synth_1 launch_runs impl_1 -to_step write_bitstream -jobs 8 wait_on_run impl_1 # 导出时序与资源报告,便于归档对比 report_timing_summary -file ./reports/timing_summary.rpt report_utilization -file ./reports/utilization.rpt

-jobs 8是并行线程数,按机器核数调,开太大反而抢内存。wait_on_run是阻塞等待,脚本化构建必须加,否则下一步会在上一步没完成时就开始。导出的两份报告建议每次构建都保留,二次开发改代码后对比资源占用和时序裕量的变化,比凭感觉判断“应该没影响”靠谱得多。这套流程跑顺之后,改一行 RTL 到拿到新比特流可以压缩到十几分钟。

3.3 SPI 配置:让 AD9361 真正开始工作

比特流下载完,FPGA 侧逻辑跑起来了,但 AD9361 芯片本身还处于默认状态,必须通过 SPI 把寄存器配置表写进去。例程里 ad9361_spi.v 是一个四线 SPI 主控状态机,按地址递增顺序把配置表逐条写入。配置表通常是一个文本文件,每行“地址 数据”,综合前用脚本转成 Verilog 的 case 语句或 ROM 初始化文件。

// SPI 写时序核心:先拉低 CS,再在 SCLK 上升沿移出数据 always @(posedge clk) begin case (state) IDLE: if (start) begin cs_n <= 1'b0; state <= SEND; end SEND: begin sclk <= ~sclk; if (sclk) begin // 上升沿更新 MOSI mosi <= shift_reg[23]; shift_reg <= {shift_reg[22:0], 1'b0}; end else if (bit_cnt == 23) begin cs_n <= 1'b1; state <= DONE; end end DONE: state <= IDLE; endcase end

这里每个 SPI 事务是 24 位:1 位读写标志、15 位地址、8 位数据,和 AD9361 的 SPI 协议一致。sclk用计数器分频得到,频率不能超过芯片手册规定的上限,例程取的是保守值。写完配置表后,建议加一个回读校验步骤:随机挑几个关键寄存器读回来比对,确认写入生效。我见过不少“配置写了但芯片没反应”的情况,最后查出来是 CS 拉低太早或 SCLK 空闲电平设反,回读能第一时间暴露这类问题。

3.4 上板验证:用 ILA 抓第一帧数据

逻辑和配置都就绪后,最直接的验证手段是 Vivado 的 ILA(集成逻辑分析仪)。在 rx_data_rise 和 rx_frame 上各挂一个探针,采样深度设 8192,触发条件设成 frame 上升沿。下载比特流后,如果 AD9361 正常输出,ILA 窗口里应该能看到 frame 周期性拉高,数据线在 frame 有效期间有变化。如果 frame 一直不拉高,先查 DATA_CLK 有没有进来——用 ILA 抓时钟引脚经过 BUFG 后的信号,没波形说明时钟路径断了,常见原因是引脚约束写错或芯片供电没到位。

抓到数据后,把 ILA 导出成 CSV,用脚本按 frame 边界切分,看样本值是否在合理范围。空口环境下接收到的应该是噪声,数值随机分布;如果全是 0 或全是最大值,说明数据线某几位没接上或 IDDR 配置有误。这一步是分水岭:过了,说明物理链路通了,后面可以接自己的基带处理;没过,就老老实实回到约束和时钟上排查。

4. 避坑与排查:上板阶段最容易翻车的五个点

4.1 现象:综合通过但实现报时序违例,WNS 为负

原因多半是 DATA_CLK 的输入延迟约束缺失或数值不对。AD9361 输出的 DATA_CLK 和数据的相位关系由芯片内部延迟决定,XDC 里必须用 set_input_delay 描述这个关系,否则工具按理想情况布线,实际采样点偏移。解决办法是先查芯片手册给的输出延迟范围,在约束里取中间值,再根据 ILA 抓到的实际数据眼图微调。别指望工具自动推断,源同步接口的输入延迟必须手写。

4.2 现象:SPI 写入后回读全为 0

先确认 CS 和 SCLK 的空闲电平。AD9361 的 SPI 模式由引脚电平在上电时决定,如果模式选错,时序完全对不上。其次查配置表地址是否按 15 位格式拼接,地址高位漏了会导致写到错误寄存器。还有一种隐蔽情况:SPI 时钟分频系数太小,SCLK 频率超了芯片上限,写入被忽略但不报错。把分频调大一倍再试,往往就通了。

4.3 现象:ILA 抓到的数据在 frame 边界处错位

这是 IDDR 的 Q1/Q2 顺序搞反了。不同器件系列 IDDR 的 Q1/Q2 对应上升沿还是下降沿可能有差异,代码里注释写的是“上升沿”,实际抓出来是反的。解决办法是拿一个已知的测试图案(比如配置成内部测试模式输出固定序列)去比对,确认顺序后调整拼接逻辑。这类问题不看波形光看代码是发现不了的。

4.4 现象:换一块同型号板子,同样的比特流不工作

引脚约束是按某块板子的原理图写的,换板子后引脚定义可能不同,尤其是 DATA_CLK 和 FRAME 这类关键信号。血泪经验是:每次换板先拿万用表对一遍关键引脚的通断,再打开 XDC 逐条核对,别假设“同型号就一样”。另外不同批次的芯片上电时序可能有细微差别,复位信号保持时间不够会导致配置丢失,适当加长复位脉宽。

4.5 现象:长时间运行后数据突然中断

先查时钟是否失锁。MMCM 在温度变化或供电波动时可能失锁,失锁后输出时钟停止,整个数据通路冻结。例程里 clk_gen.v 有 locked 信号,建议把它接到一个 LED 或状态寄存器上,运行时能直观看到时钟状态。另一个常见原因是 FIFO 溢出,如果下游处理速度跟不上接收速率,需要加背压或增大缓冲深度,别让数据在无声无息中丢掉。

5. 二次开发:把例程改成自己的收发链路

5.1 替换接收通路:从原始样本到基带处理

例程的 rx 模块输出的是 12 位原始样本,二次开发时最自然的切入点是把它接到自己的数字下变频或滤波模块。做法是在 top.v 里把 rx_data_rise/fall 拼接后的样本引出来,新增一个实例化自己的处理模块,输入就是这些样本加一个有效信号。注意样本速率很高,下游模块要么能全速处理,要么加一个异步 FIFO 做跨时钟域。我一般先在仿真里用 tb_top.v 灌一段已知正弦波,确认处理链路输出正确,再上板,比直接上板盲调省时间。

5.2 改造发送通路与配置表

发送侧改动集中在 ad9361_tx.v 的组帧逻辑和配置表内容。如果只是改采样率或本振频率,改配置表对应寄存器即可,不用动 RTL;如果要改数据格式(比如从实信号改成 I/Q 两路),需要同步调整 ODDR 前的数据拼接和 frame 时序。配置表建议用脚本从 CSV 生成,别手改 Verilog 里的 case 语句,改错一个地址排查半天。验证方法是发一个单音,用频谱看输出频率和幅度是否符合预期。

5.3 一个实用的调试习惯

从那以后我每次改完 RTL,都强制走一遍“综合看警告 → 实现看时序 → ILA 抓一帧 → 回读关键寄存器”这四步,不管改动多小。有一次只改了一个计数器位宽,觉得不可能影响时序,结果实现后 WNS 掉了 0.3ns,上板数据偶发错位,回头查就是那一位宽变化改变了布线拥塞。这套流程看着繁琐,但比上板后对着黑匣子猜要快得多。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询