电子说
前面一节我们学会了创建基于AXI总线的IP,但是对于AXI协议各信号的时序还不太了解。这个实验就是通过SDK和Vivado联合调试观察AXI总线的信号。由于我们创建的接口是基于AXI_Lite协议的,所以我们实际观察到是AXI_Lite协议的信号时序。
具体做法是创建一个基于AXI总线的加法器模块,在Vivado里将AXI总线添加到debug信号里,实际上是用逻辑分析仪探测信号,在SDK端通过debug方式依次写入两个加数,由PL计算出和,我们读出这个和打印到串口,这样AXI总线的读和写就都能观察到了。
板子使用的是zc702。
在观察信号之前我们有必要简单了解AXI是个什么:
AXI总线是一种高性能、高带宽、低延迟的片内总线,AXI协议描述了主从设备数据传输的方式。主设备和从设备通过握手信号建立连接,握手信号包括主机发送的VALID信号,表示数据有效,从机发送的READY信号,表示从机准备好了接收数据。当VALID和READY都有效的时候传输开始。
Zynq使用的是AXI协议的4.0版本,由AMBA3.0协议发展而来:
AXI的三种总线各有各的用途:
AXI4支持突发数据传输,主要用于CPU访问存储等需要高速数据交互的场合,相当于原来的AHB协议;
AXI_Lite一次传输单个数据,主要用于访问一些低速外设,相当于原来的APB;
AXI_Stream数据传输不需要地址,主设备直接连续读写数据,主要用于高速流数据的传输,使用起来类似FIFO。
AXI4总线和AXI4-Lite总线具有相同的组成部分:
(1) 读地址通道, 包含ARVALID, ARADDR, ARREADY信号;
(2) 读数据通道, 包含RVALID, RDATA, RREADY, RRESP信号;
(3) 写地址通道, 包含AWVALID, AWADDR, AWREADY信号;
(4) 写数据通道, 包含WVALID, WDATA, WSTRB, WREADY信号;
(5) 写应答通道, 包含BVALID, BRESP, BREADY信号;
(6) 系统通道, 包含: ACLK, ARESETN信号。
帮助记忆:
读地址信号都是以AR开头(A: address; R: read)
写地址信号都是以AW开头(A: address; W: write)
读数据信号都是以R开头(R: read)
写数据信号都是以W开头(W: write)
应答信号都是以B开头(B: back(answer back))
读时序:
写时序:
5个通道都有自己独立的握手机制,同时又相互协作,地址线分离后,读/写可以并行交互数据。而且AXI支持地址pipeline、错位传输等模式,大大提高了数据通路的利用率。
关于AXI Interconnect模块:
AXI协议可以描述为主/从设备之间的点对点传输,可以有多个主机,也可以有多个从机,这是进行数据交互时,需要一个管理中心确保信号发到对应的设备,AXI Interconnect模块就是这样一个管理中心,也叫做AXI互联矩阵。它类似于一个交换机,保证每个外设独享带宽,完成数据传输。Xilinx公司为我们提供了AXI Interconnect的IP核。
介绍到此,理论部分已经掌握i一二了,下面就开始实际观察AXI的信号。
创建一个基于AXI_Lite总线的加法器IP
新建Vivado工程,依然,选择这个:
修改名字和地址,我命名为AXI_Add,地址最好选择工程路径下新建一个文件夹,这样就会自动把你创建的IP更新到IP库里,其他默认,最后选择Edit IP,在Vivado给出的代码里添加上我们的加法器功能:
注意这些代码就是定义AXI协议的工作方式的,里面有注释,可以去研究一下!
添加用户端口:
这里我们没有要输出的信号
添加用户逻辑:
待会我们将两个加数写到寄存器0和1中,从寄存器2中读出和
封装好后回到我们之前建的工程,
然后在工程里添加zynq核,添加刚才创建的IP,点击自动连接,然后选中AXI总线,右击选择Make Debug,出现两个绿色的小瓢虫,将总线加入Debug模式下。
如果你没有把刚才新建的IP放在该工程目录下,先要把IP添加进库里,选择 Project Settings-->IP。点击绿色的加号,选择到你的用户IP地址 :
整个系统搭建完成是这样的:
下面就是例行操作,自动排版,检查,保存,复位,generate,产生顶层文件。然后先综合(Synthesis)一下,综合好后,打开Synthesis Design下的Set Up Debug,将显示为红色的无用信号删除(选中,点击红色的减号),
设置采样深度为1024,
其他默认,然后生成比特流文件。
软件端的设计
Lanch到SDK,新建一个空的applicaton工程,添加一个c源文件,我们在xparameters.h文件中找到我们添加的加法器AXI_ADD的寄存器首地址,待会我们就是要通过地址对寄存器进行读、写:
这个案例里我们计算16进制下的24+12=36,为了方便SDK端的断点调试,加了while循环,注释我写在后面了:
#include
#include "xparameters.h"
#include "xil_io.h"
#include "xil_types.h"
#define AXI_ADD_REG0 0 //寄存器0的偏移地址为0
#define AXI_ADD_REG1 4 //寄存器1的偏移地址为4
#define AXI_ADD_REG2 8 //寄存器2的偏移地址为8
int main(){
while(1){
u8 value = 0;
Xil_Out32(XPAR_AXI_ADD_V1_0_0_BASEADDR+AXI_ADD_REG0,0x24); //向寄存器0写入第一个加数
Xil_Out32(XPAR_AXI_ADD_V1_0_0_BASEADDR+AXI_ADD_REG1,0x12); //向寄存器1写入第二个加数
value = Xil_In32(XPAR_AXI_ADD_V1_0_0_BASEADDR+AXI_ADD_REG2); //从寄存器3中读出和
xil_printf("value = %x",value);
}
return 0;
}
软硬件联合调试
右键工程 Debug as-->Debug Configrations:
进入Debug界面后,在AXI总线读、写的地方添加断点:
打开串口:
然后回到Vivado界面,连接到Device,就会自动打开调试界面:
接下来是设置触发位置,添加触发条件:
我们的采样深度是默认的1024,在500的位置触发,
触发条件设置成WVALID信号为高时,AWVALID信号为高时:
右键逻辑分析仪,选中Enable Auto Re-trigger,设置成自动进入下一次触发:
点击运行触发,点击完之后ila进入等待触发状态:
在SDK点击运行:
首先写入24:
有一点要注意,这里的写地址VALID和READY信号是下一个地址的,这里就是对应WDATA的4,而对应地址0,也就是我们观察的数据24地址的VALID和READY信号在前面已经有效过了,我们设置写数据有效时触发,而地址有效在前面就没有捕捉到了。
然后写入12:
然后串口打印出36:
等到第二轮写入的时候,我们看到读数据通道上出现36:
我们可以换一下触发信号,换成读VALID,读READY拉高时触发:
就可以看到,读VALID和READY信号都高时,读出36,读地址线上是8:
这里可以看到读READY信号一直为高,读VALID信号拉高时36读入。但是由于ILA捕捉的是触发时刻信号,所以它将读有效之前的读数据都显示为36了。
大家要注意,触发设置里,添加的信号默认是AND的,如果你一开始就把读有效为高,写有效为高都添加到触发列表里,会观察不到信号,因为这两个信号在本例子;里不会同时出现:
我们可以添加读VALID为1,写VALID为1,然后选择OR:
SDK那边一步步Debug,先是写入24:
然后写入12:
然后读到36:
这里也是由于RREADY先于RVALID信号拉高,ILA捕捉不到READY是什么高的,就把之前都显示为高。
如果没有按你设置的运行,可以在SDK重新Debug as或者在Vivado重新连接一下Target,或者多点几次Run Trigger,这软件有时候不灵敏,比如说你虽然设置了自动进入下一次触发,但是它不一定就进入,这是手动点一下触发,进入等待模式,再点击下一次Debug。
小结
到这里,AXI信号读写时序就全部观察完了,这时候可以再去看一看AXI协议的理论部分,把封装基于AX总线的IP时生成的例化代码捋一遍,就能更好的理解AXI总线的工作原理了。提一句,我们目前观察的都是简化版的AXI_Lite协议的时序。
编辑:hfy
全部0条评论
快来发表一下你的评论吧 !