1. 为什么学了半天FPGA,最后还是绕不开AXI
先说个我自己的事。早几年我刚接触Zynq的时候,在Vivado里搭好了一个简单的PS-PL工程,PL侧放了几个自己封装好的寄存器模块,PS端用GPIO模拟读写,跑起来倒也顺利。那时候我天真地觉得,总线协议这东西不过是“接口对得上就行”。直到后来项目里要挂DDR4、要搬图像数据、要做DMA批量传输,我才发现自己完全低估了AXI的重要性——用GPIO一根一根线去搬数据,带宽不够、时序乱成一团,代码写到后面自己都不想看。
AXI(Advanced eXtensible Interface)是ARM AMBA总线家族里的一员,在Xilinx的Zynq、MPSoC以及各种FPGA高速IP核里,它几乎是默认的互联方式。搞FPGA的工程师,不管是纯逻辑方向还是SoC方向,都躲不开它。你去看Vivado里的IP Catalog,DMA、FIFO、DDR控制器、视频处理IP,接口清一色是AXI;你想往Zynq的PS上挂自己的逻辑,也得先把AXI搞明白。说它是FPGA开发的“通用语”,一点不夸张。
这篇是“从近似0基础开始FPGA开发”系列的第11篇,我会把AXI总线协议从工作原理到工程落地,按照我实际接触它的顺序拆开讲。重点不做纯理论堆砌,而是围绕“你手头的板子、你写的Verilog、你在Vivado里点的那些按钮”,把协议讲明白,也把我在真实项目中踩过的坑一并交代。读完这篇,你能搞清楚AXI4、AXI4-Lite、AXI4-Stream分别适合什么场景,能自己在Vivado里创建并验证一个带AXI接口的IP,也知道总线不上电、握手卡死、地址对不上的时候该怎么排查。
这系列前面的内容,如果你是从零开始一路看到的,那这篇是在你熟悉了时钟、复位、状态机和简单外设驱动之后,进入“系统级互联”的一步跨越。如果你直接跳到了这一篇,也没关系,AXI的理解不要求你会写多复杂的逻辑,反而更看重对读写的时序敏感度。一句话:能看懂波形、能分清楚谁在等谁,你就已经具备入门AXI的素质了。
2. AXI总线协议到底解决什么问题
2.1 没有总线协议的世界:点对点连线有多痛
先别急着看协议细节。我们站在一个FPGA工程师的角度,仔细想想:如果不用AXI,我们要在FPGA里把CPU、DDR控制器、图像采集模块、显示模块、DMA引擎连起来,该怎么做?
最朴素的办法是点对点连线。CPU要配置采集模块,就拉一根地址线、一根数据线,再配一堆控制信号;CPU要读DDR里的数据,也得单独走一组线。采集模块要往DDR里写数据,又得拉一组线。CPU、DMA、采集模块、显示模块,每个外设之间都两两连线,地址线数据线控制线一多,布局布线就是灾难。更要命的是,每个外设的读写时序都是自己定义的,CPU每访问一个新的外设,就得适配一套新的“方言”。什么地址有效、数据什么时候稳定、读操作要不要等待周期,全靠各自约定,项目一大就乱套。
总线协议的本质,就是把不同模块之间的通信方式“标准化”。大家都按同一套规则来,地址怎么发、数据怎么传、什么时候完成、出错怎么办,协议里全定义好了。这样外设和处理器之间、外设和外设之间,不再需要专门定制的信号交互,只要都挂到同一套总线上,就能互相通信。AXI正是这套标准在FPGA和SoC领域最主流的存在。
2.2 AMBA家族:AHB、APB和AXI各自的分工
讲AXI之前,有必要把AMBA总线家族整体看一眼。AMBA是ARM制定的一套片上总线标准,发展到现在,家族里出现频率最高的是三个:APB、AHB和AXI。
APB(Advanced Peripheral Bus)是里面最“佛系”的一类,接口简单,不支持突发传输,一次只能传一笔数据,速度也不快。它适合挂那些对性能不敏感的外设,比如UART、I2C、GPIO、定时器这类配置型寄存器。Zynq里PS侧的APB主要是给低带宽外设用的,PL侧如果你想做简简单单的寄存器读写,也可以借鉴APB的思路,但Xilinx原生的IP基本不用APB。
AHB(Advanced High-performance Bus)是高带宽总线,支持突发传输,效率比APB高不少,很多MCU内部就是AHB加APB的结构。但它有一个特点:同一时刻只能有一个主机占用总线,所有传输是串行化的。在单一主机的场景下,AHB够用;但Zynq这种多主机并存的架构里,AHB就不够看了。
AXI就是为了解决AHB并发能力不足的问题出现的。它的核心设计思想是通道分离、支持乱序传输、支持多个主机并行访问,性能天花板比AHB高一个档次。FPGA里跑高速数据传输、DDR读写、视频流搬运,AXI基本是唯一的选择。
2.3 为什么AXI能成为Xilinx SoC的默认选项
Xilinx从Spartan-6系列就开始推自己的总线标准,后来和ARM合作做Zynq的时候,直接拥抱了AXI。原因很实在:ARM的Cortex-A9核,原生接口就是AXI的变种,用AXI连接PS和PL,不需要额外的协议转换,效率最高。而且AXI是开放的协议标准,第三方IP厂商按标准做出来的IP,可以直接挂进Xilinx的生态,不需要针对每个厂商改接口。生态滚起来了,大家越是统一,越没人愿意搞私有协议。
从Zynq-7000到UltraScale+,再到Versal,Xilinx的平台一直在迭代,AXI的地位不仅没动摇,反而越来越深入。现在你只要打开Vivado的Block Design,添加任何带数据搬移能力的IP,拉出来的接口几乎都是AXI。这已经不是选择题,而是必答题。FPGA工程师要么学,要么等着项目原地卡死。
3. AXI4、AXI4-Lite、AXI4-Stream:三个版本,三条不同的路
3.1 一张表分清三个版本
AXI协议家族里,工程上最常用的就是AXI4、AXI4-Lite、AXI4-Stream这仨。很多新手糊涂的地方在于:怎么一会儿叫AXI,一会儿叫AXI4,一会儿又是AXI Stream?其实都是AXI体系,只是面向的场景不同。我先把它们的差异摆出来:
| 特性 | AXI4 | AXI4-Lite | AXI4-Stream |
|---|---|---|---|
| 地址通道 | 有 | 有 | 无 |
| 突发传输 | 支持,最多256拍 | 不支持,单次读写 | 数据流,无地址概念 |
| 数据宽度 | 8~1024位,工程常见32/64/512位 | 与AXI4一致,但带宽受限 | 8/16/32/64/128位等任意宽度 |
| 适用场景 | 高速读写,DDR、DMA、大块数据传输 | 寄存器配置、状态读取 | 视频流、音频流、高速数据流 |
| 复杂度 | 高 | 低 | 中 |
| 典型IP | DDR控制器、AXI DMA、AXI BRAM Controller | AXI GPIO、AXI UART、自定义控制寄存器 | AXI Video DMA、AXIS FIFO、各种流式处理IP |
从表格能看出来,AXI4-Lite是“轻量级”的版本,牺牲了性能换来了简单,适合跑控制类信号。AXI4是“全家桶”,支持完整的突发传输,适合大块数据读写的场景。AXI4-Stream干脆去掉了地址,你不需要指定“我要读第几个数据”,而是像水管一样一股脑把数据按顺序灌进来,适合连续的数据流——视频、以太网包、模数转换采样值,这类天然按顺序处理的数据。
工程上一个最常见的组合套路是:CPU通过AXI4-Lite配置和控制外设,外设和内存之间用AXI4做大数据搬运,两个IP之间高速传输实时数据流就走AXI4-Stream。三个版本各管一段,互不冲突。
3.2 AXI4-Lite:控制面的最优解
如果你只是想写几个寄存器、读一个状态位,用AXI4-Lite就够了。它不支持突发,一次读写固定一笔数据,控制逻辑简单,适合挂在CPU的低速配置总线上。
我举个实际例子:你做了一个温控风扇模块,需要给PWM控制器写一个目标转速值,再从温度传感器模块读当前温度。这个需求用AXI4-Lite非常合适。Zynq的PS侧有M_AXI_GP接口,默认走的就是AXI这种通用协议,你在Vivado里给PL的例子工程添加一个AXI4-Lite从机,配置寄存器,然后PS端写一段简单的C代码,往固定地址写数据就能控制PWM,读地址就能拿到温度。整个流程清晰干净,几乎没有多余的等待周期。
AXI4-Lite的读写通道规则是固定的:写地址通道和写数据通道可以同时发出,写响应通道会在从机接收完成后回复;读地址通道发出后,从机在若干个周期后返回读数据。因为没有突发,每一笔事务独立开始、独立结束,从机逻辑很好写。用状态机实现AXI4-Lite从机,可以说是FPGA工程师的基本功之一,后面我会细讲代码该怎么搭。
3.3 AXI4:带突发的性能主力
AXI4和AXI4-Lite最核心的差别是突发传输,也就是一次地址请求可以连续传输多笔数据。比如你要从DDR里读一块256字节的连续数据,如果一次只读一个字,光发地址就要几百次,效率非常低。AXI4允许你一次指定起始地址,然后连续读256拍数据,地址开销被大大摊薄。
突发相关的信号有四个:AWLEN(写突发长度)、AWBURST(写突发类型)、AWSIZE(写突发大小)、AWADDR(写起始地址)。读通道对应的是ARLEN、ARBURST、ARSIZE、ARADDR。其中AWLEN表示的是“突发长度减一”,也就是说AWLEN=0表示传1笔,AWLEN=255表示传256笔。AWSIZE表示每一笔数据的字节数,8位总线时AWSIZE=0b000,16位时AWSIZE=0b001,32位时AWSIZE=0b010,以此类推。AWBURST定义的是地址递增模式,最常见的是INCR(增量突发),每次传输后地址递增一个AWSIZE;FIXED突发是每个地址都一样,适合读写FIFO这类固定地址外设;WRAP突发是回卷模式,适合缓存行操作。
AXI4还支持乱序传输,这是它性能强大的另一个原因。多个主机同时发起事务,从机可以不按请求顺序返回数据,只要带着正确的ID就行。ID信号是AXI里实现乱序的基础,事务带着源ID发出去,返回的数据带着相同的ID,接收端就能把返回的数据对号入座。对FPGA初学者来说,乱序传输的概念可以先不深究,但要知道它存在——因为你在调试AXI DMA和DDR的时候,返回数据顺序和请求顺序不一致是正常现象,不要慌张。
3.4 AXI4-Stream:没有地址的数据洪流
AXI4-Stream把“地址”概念从协议里拿掉了,剩下的就是纯粹的流动数据。它有一条主数据线(TDATA)、一个字节有效位(TKEEP)、一个滤除信号(TSTRB)、一个帧起始信号(TUSER)、一个帧结束信号(TLAST)、一个有效信号(TVALID)和一个就绪信号(TREADY)。
这几个信号里,TLAST是最常用的。在视频流处理里,TLAST代表一帧图像的最后一行数据的最后一拍;在以太网包处理里,TLAST代表一个包的最后一部分数据。TUSER则用来携带边界信息,比如视频里的行同步、场同步信号。AXI4-Stream的数据传输靠TVALID和TREADY握手完成,主机拉高TVALID表示数据有效,从机拉高TREADY表示准备好接收,两个信号同时为高,这一拍数据才算真正传输成功。
实际工程里,AXI4-Stream多半出现在“搬运大数据”的链路上。图像Sensor采集的数据先转成AXI4-Stream,经过ISP处理流水线后,再用AXI DMA把Stream数据转成AXI4的地址读写请求,写到DDR里。显示通路则相反,DDR里的图像通过AXI4读出来,转成Stream流,送往HDMI或者LCD控制器。这套“地址-流-地址”的转换模式,是Zynq视频处理最经典的架构,理解了AXI4-Stream的握手,就能看懂一整套视频通路是怎么搭起来的。
4. AXI的核心机制:从握手到突发,把时序看懂
4.1 VALID和READY:一切传输的基础
AXI的每一个通道,都有一对握手信号:VALID和READY。发送方拉高VALID,表示“我这个信号线上的数据已经是有效的了,你可以取了”;接收方拉高READY,表示“我已经准备好了,你随时可以发”。数据真正完成传输的判定条件只有一条:一个时钟上升沿,VALID和READY同时为高,这一拍的数据就被接收方锁存了。
这句话很简单,但实际写代码的时候,新手最容易犯的错就是弄反握手关系。AXI协议里有一条严格规定:VALID信号一旦拉高,就要保持到握手完成为止,中间不能撤销。READY则没有这个要求,你可以在看到VALID之后再给READY,也可以提前给READY。如果接收方还没准备好,握手就一直卡着,数据就一直悬在那里,总线事务的完成时间是不确定的。
从这个角度看,AXI的握手是一种“反压机制”。上游可以随时把数据顶到下游,但如果下游忙不过来,拉低READY就能让上游等一下。这种流控能力在高速数据链路里非常关键——视频流IP处理不过来的时候,TREADY一拉低,上游DMA自动暂停发送,不会丢数据。理解了这对信号,你读任何AXI相关的代码都会轻松一大截。
4.2 五个通道各自的任务
AXI4完整形态下,一共有五个通道:读地址通道(AR)、读数据通道(R)、写地址通道(AW)、写数据通道(W)、写响应通道(B)。名字看起来多,但它们的分工很清晰。
写事务的过程是:主机先在AW通道上发出写地址,同时在W通道上发出要写的数据,从机接收完成后,在B通道上回一个写响应。读事务相对简单:主机在AR通道上发出读地址,从机在R通道上返回数据。之所以把地址和数据分开,是为了支持流水线操作——主机在等第一批数据返回的同时,就可以发出下一批的地址请求,地址通道和数据通道不互相阻塞。
工程里写AXI从机,最常用的写法是状态机。比如AXI4-Lite从机,状态机里一般有IDLE、写地址等待、写数据等待、写响应、读地址等待、读数据返回这几个状态。每个状态里,都去判断对应的VALID和READY。最容易出错的是写事务的通道间依赖:协议允许AW和W通道同时发起,但有的AXI从机要求先收AW再收W,有的要求反过来,还有的必须最后收B。Vivado自动生成的IP模板里,写通道的握手顺序是可以配置的,默认情况下是“先AW后W再B”,理解了这个顺序,你自己写从机的时候就不容易乱。
4.3 突发传输的地址计算
突发传输的理解难点在于地址怎么算。举个例子,如果AWADDR=0x0000_1000,AWSIZE=0b010(每笔4字节),AWLEN=3(共4笔),那么有效地址是0x1000、0x1004、0x1008、0x100C。每传一笔,地址增加一个AWSIZE对应的字节数。这个逻辑简单,但你做地址对齐的时候就要小心了——比如AWADDR=0x1001,AWSIZE=4字节,那第一笔数据就会横跨两个32位字边界,总线的数据整理逻辑会变得非常复杂。
工程上我的建议是:所有AXI突发传输的起始地址,都按数据总线宽度的边界对齐。如果你用的是64位总线、每笔8字节,那起始地址至少是8的倍数。未对齐的突发虽然协议允许,但从机实现时要处理字节选通,很容易留坑。尤其是在定制自己的AXI从机时,干脆直接在模块里抛掉未对齐请求,或者用地址生成逻辑强制低几位清零,简单省事。
4.4 ID信号与乱序:性能的另一个来源
AXI通过ID信号实现多事务并发。主机给每个事务分配不同的ID,从机在处理完事务后返回数据时,带上相同的ID,主机不要求返回顺序和发起顺序一致,只要按ID把数据归位就行。这相当于给每个事务发了一张“取餐号”,你可以一次下好几个单,哪个先做好哪个先拿,没必要干等第一单。
乱序传输在单主机读多块数据的场景下特别有用。比如CPU同时要读DDR里两个不连续的区域,如果串行读,第二块区域只能等第一块区域全部读完;有了乱序,两个读事务可以并行进行,哪个先准备好就先返回哪个。实际工程里,乱序传输带来的性能提升,在DDR控制器这种随机访问场景下非常可观。不过对FPGA逻辑工程师来说,乱序是IP核内部处理的事情,你做自定义从机时能保证“回的数据ID和请求一致”,就不会出大问题。
5. 工程实战:在Vivado里从零搭一个AXI系统
5.1 创建一个AXI4-Lite自定义IP的完整过程
纸上谈兵结束了,下面进入实际操作。我以Vivado 2021.2为例,带你走一遍创建一个AXI4-Lite从机IP的完整流程。
第一步,打开Vivado,先创建或打开一个工程,然后在菜单里选择Tools -> Create and Package New IP,弹出向导后选择Create a new AXI4 peripheral,点Next。接下来填IP的名字,比如“my_axi_lite_reg”,版本号默认1.0,接口类型选AXI4-Lite,接口模式选Slave(从机),数据宽度选32。Vivado会问你要不要生成一个模板例程,务必勾上,它会自动生成一个完整的AXI4-Lite从机Verilog代码,这是你学习和二次开发最好的起点。
点击Finish后,Vivado会生成一个IP目录结构,核心文件是my_axi_lite_reg_v1_0_S00_AXI.v。用编辑器打开这个文件,你能看到一个非常经典的AXI4-Lite从机实现:模块里有S_AXI_ACLK、S_AXI_ARESETN、S_AXI_AWADDR、S_AXI_WDATA等信号,寄存器数组reg [C_S_AXI_DATA_WIDTH-1:0] slv_reg0、slv_reg1、slv_reg2、slv_reg3,默认4个32位寄存器。写逻辑在“Write transaction logic”注释下面,读逻辑在“Read transaction logic”下面。
理解这份模板比什么都重要。写事务的逻辑是:状态机收到AWVALID后锁存AWADDR的低几位,再收到WVALID后把WDATA写进对应的slv_reg,最后拉高BVALID给出响应。读事务则是:检测到ARVALID后,根据ARADDR选通对应的slv_reg,把数据放到RDATA上并拉高RVALID。模板里已经处理好了地址译码、读写通道握手和复位逻辑,你要做的事是在slv_reg0里加一个自己的功能。比如你想让slv_reg0的第0位控制LED,就在寄存器写逻辑里加一句LED_output <= slv_reg0[0],再引出一个输出端口就行。
5.2 在Block Design里连接PS和PL
IP创建好之后,回到Vivado主界面,创建一个Block Design。在里面添加Zynq Processing System(以Zynq为例),运行块自动连接后,再把你的my_axi_lite_reg拖进BD里。这时你会看到,IP的S_AXI接口需要指定连接到PS的哪个AXI接口上。Zynq的PS有M_AXI_GP0和M_AXI_GP1两个通用AXI主接口,把S_AXI接到M_AXI_GP0,再运行Connection Automation,Vivado会自动加上AXI Interconnect,帮你把PS的接口转接成IP能接收的AXI4-Lite。
这一步看起来只是点几个按钮,背后其实串联了一整套机制。AXI Interconnect本质是一个AXI交换矩阵,负责地址路由、协议转换和时钟域转换。系统里可以挂多个AXI从机,Interconnect会自动按地址段把不同访问路由到不同的从机上。默认情况下,Vivado会为每个从机自动分配一段地址空间,比如一个从机占0x4000_0000到0x4000_FFFF。你在BD里双击AXI Interconnect,在地址编辑标签里能看到完整的地址映射表,这就是CPU视角下访问PL外设的地址空间。
生成Bitstream之前,记得还要做地址的合法性检查。Zynq的M_AXI_GP0接口有固定地址范围,如果Interconnect分配的从机地址超出范围,综合会直接报错。一般Vivado会自动规避,但偶尔手动修改地址后会把地址弄乱,导致PS访问不了PL。真遇到这种情况,在地址编辑窗口里先删掉映射关系,再重新分配一次就好。
5.3 AXI4-Stream数据通路的搭建与验证
控制通路用AXI4-Lite解决后,数据通路视频流走AXI4-Stream是最高效的选择。我拿一个最简单的例子来说:用AXI Video DMA把DDR里的图像数据搬到HDMI输出端口。
视频通路的基本框架是:PS端配置好DMA和显示控制器,DMA从DDR读取图像帧,通过AXI4-Stream接口输出给显示IP,显示IP再把数据转换成HDMI时序。在Block Design里,视频显示IP的接口类型就是AXIS Master,DMA的M_AXIS接口会是AXIS Slave,两者互连。Vivado会提示你连接TVALID、TREADY、TDATA、TUSER、TLAST这组信号,全部连上后,数据流动就靠那套握手控制了。
验证AXI4-Stream通路,我习惯用Vivado自带的ILA(Integrated Logic Analyzer)去抓信号。把ILA的探针挂在AXIS的TVALID和TREADY上,跑一次硬件,你会看到握手波形的真实样子。如果TVALID一直为高但TREADY经常拉低,说明下游处理不过来,是在反压;如果TREADY一直为高但TVALID不定时拉高,说明上游数据不是持续不断的,中间有间隙。这两种都是正常现象,真正要警惕的是“既没有TVALID也没有TREADY的持续空闲”——那通常意味着上游DMA没有配好,或者DDR读写卡住了。
6. 常见问题与排查技巧实录
6.1 握手死锁:最典型的AXI总线卡死故障
我在调试自定义AXI从机的时候,遇到过最挫败的一种情况:PS端运行程序,卡在一个读操作上,一直不返回,CPU就死在那里。用ILA抓ARM接口的信号,发现MVALID拉高后,READY一直为低,握手永远完成不了。
这种问题的教科书级原因是:从机的读状态机陷入了一个错误状态,既没有正确拉高RVALID,也没有对ARVALID作出响应。排查的方法,是先检查从机模块的复位逻辑——AXI从机对复位时序有严格要求,复位释放必须发生在时钟上升沿附近,如果异步复位释放时序不干净,状态机会跑飞。其次是检查地址译码逻辑,如果ARADDR指向了一个没有定义的地址段,模板里默认返回的是0,但如果你的译码逻辑在未定义地址时没有拉高RVALID,握手也会永远卡住。我的建议是,从机的所有读操作分支,不管地址有没有定义,最终都要进入“返回数据并拉高RVALID”的状态,哪怕返回的是全0,也要让握手完成,不能让主机无限等待。
6.2 突发长度与DDR4:为什么偶发读写失败
AXI4突发长度上限是256拍,但DDR4控制器对突发长度往往有自己的限制。比如某些DDR4 IP要求单次AXI读突发长度不能超过16,超过的部分会被控制器内部自动拆分,或者直接导致事务失败。你如果突发长度设置过大,去读DDR4,会发现偶尔读回来的数据是错的,或者DDR校准直接报错。
解决这个问题,核心思路是不要在AXI层把突发长度拉满,而是通过提高数据宽度来提升带宽。AXI4的数据总线宽度拉到256位或512位,单笔传输的实际数据量已经是8字节总线的16倍或64倍,即便突发长度只有16,总吞吐也完全够用。调DMA或者自定义突发场景时,先查一下对应DDR IP或DMA IP的规格说明,看支持的最大突发长度和数据宽度,按照它的上限去设置,不要硬踩协议极限。
6.3 地址空间错乱:寄存器“幽灵写”
还有一个很隐晦的问题:用AXI4-Lite写寄存器,明明写的是0x00这个寄存器的地址,结果0x01寄存器的值也跟着变了。这种问题几乎都是字节选通和寄存器地址译码不匹配引起的。
AXI4-Lite数据总线是32位时,地址低两位通常用来区分寄存器内部字节偏移,但如果你用AWADDR的低四位来做译码,就会把一个32位寄存器当成4个8位寄存器来寻址。默认模板处理方式是把AWADDR右移两位再做比较,也就是地址0、4、8、12对应4个32位寄存器,这样译码就没问题。如果你自己写寄存器模块,一定要先看总线是多宽,把地址位处理清楚。遇到“写一个字节把整个寄存器改了”的情况,搜索下自己的代码里有没有WSTRB信号漏处理——这个信号是字节选通,WSTRB=4‘b0001表示只写最低字节,如果没有按WSTRB做掩码,就会出现半字节覆盖、未选中字节也被改成0的问题。
6.4 跨时钟域的AXI接口:不能忽视的同步问题
AXI接口在Zynq里经常跨时钟域。PS侧的M_AXI_GP通常跑在100MHz或125MHz,而你自己的PL逻辑可能跑在150MHz,甚至视频通路里跑在148.5MHz(HDMI的像素时钟)。如果直接把这些不同时钟域的AXI接口连在一起,不做同步,就会出现握手信号抖动、数据采错、状态机跑飞等一系列怪异现象。
正确的做法是,在Block Design里用AXI Interconnect的时钟转换功能,或者给自定义IP的AXI接口接上独立的时钟域同步模块。Vivado里的AXI Interconnect本身带有跨时钟域处理能力,你在BD里给两个不同时钟域的AXI接口建立连接时,Interconnect会自动插入同步逻辑。如果你自己写模块挂在AXI总线上,最简单的方案是让内部逻辑全跑在AXI接口时钟下,避免在AXI接口内部二次分频。AXI接口的ACLK就是你的主时钟,所有AXI信号逻辑都由它驱动,模块内部的其他功能逻辑如果确实需要更快的时钟,再用独立的时钟使能或者FIFO做跨时钟域处理,不要在AXI状态机里混入另一个时钟域的异步信号,这是铁律。
6.5 AXI验证的快捷手段:去学一下VIP的思路
如果是纯PL项目,没有Zynq PS,怎么验证自定义AXI模块?其实Vivado里自带AXI Verification IP,也就是AXI VIP。在IP Catalog里搜“AXI Verification IP”,添加后可以把它配置成Master或Slave模式,挂在你设计的AXI接口上,然后在仿真里通过它发起读写事务。AXI VIP最方便的地方是,它自带了很多可配置的参数,比如突发长度、数据宽度、地址、连续传输次数,你不需要手写一堆task就能模拟总线行为。
我自己的使用习惯是:全仿真验证用AXI VIP做主设备,往我的从机里写数据、读数据,然后用$display打印出来对比结果。这一步能过滤掉90%的静态错误,比如地址译码不对、握手表漏、写数据没落对寄存器。跑通仿真后,再上板用ILA抓真实总线波形,做系统级验证。AXI总线这种东西,仿真阶段发现问题成本最低,上板之后定位问题,光抓波形就能耗一天,别问我怎么知道的。
7. 从AXI开始,把视野放大到整个SoC系统
学AXI不只是学一套信号握手规范,它更像是在建立一种“系统视角”。以前你写FPGA逻辑,关注的是单个模块能不能跑、波形对不对;开始接触AXI之后,你自然就会想:这个模块挂在总线的哪个地址上?它和DMA之间怎么协作?总线的带宽会不会成为瓶颈?这些问题一旦开始想了,你就已经在从“写逻辑的工程师”变成“做系统的工程师”了。
我自己的经验是,学AXI最好的落地路径,就是拿Zynq做一个小项目,比如用PS控制PL的DMA搬运一块数据,再通过AXI4-Lite配置寄存器切换数据源。整个过程中,你会自然地用到AXI4、AXI4-Lite、AXI4-Stream三种接口,也会自然地理解Interconnect、地址映射、读写出错的应对方法。理论看三遍不如动手调一遍。
最后分享一个小的调试技巧:上板调试AXI时,不要只抓数据信号,一定要把VALID和READY同时抓下来,并且把两者之间的关系看明白。很多总线“偶发故障”,其实都是某一次握手时VALID比READY早了几个周期,或者反压时序不满足协议要求积累下来的。时序图上把握手对齐了,稳定性的问题往往就迎刃而解。AXI这条路,入门不难,但想调得顺手,一定要多看波形、多逼自己复现问题。这个系列下一篇,我打算专门写AXI的仿真验证和VIP使用,那部分内容配合今天这篇,能把AXI从看懂到用熟完整串起来。