[ZYNQ入门宝典]定制带AXI_Lite总线的用户IP

碎碎念:

AXI总线是ZYNQ系列中连接FPGA和ARM的最重要的途径之一。在ZYNQ中AXI分三种模式,具体不去详述,读者可自行百度。这里我们想定制一个用户IP,利用AXI_Lite总线,从PS端将LED闪烁命令传达到PL端,最终实现LED的闪动。

网上对于IP定制的教程有很多,这里贴出2个供读者参考:https://blog.csdn.net/NarutoInspire/article/details/81456579  https://blog.csdn.net/long_fly/article/details/78619451 。本文主要讨论用户IP中AXI源码逻辑和用户使用,对于用户IP的定制步骤仅做简要说明。

 

手把手:

Step1:定制带AXI的用户IP,设置好AXI参数。

[ZYNQ入门宝典]定制带AXI_Lite总线的用户IP

Step2:点击Edit IP,对原IP的源码进行修改。

[ZYNQ入门宝典]定制带AXI_Lite总线的用户IP

Step3:代码修改部分如下:加入用户逻辑,顶层也要添加此信号。此段代码的作用就是将PS端下发的寄存器0的值赋值给GPIO.

[ZYNQ入门宝典]定制带AXI_Lite总线的用户IP

Step4:如封装普通IP的流程一样,将修改后的工程封装成IP。BD文件中,添加PS,连线,检查有无语法错误。添加XDC文件,生成bit文件。

[ZYNQ入门宝典]定制带AXI_Lite总线的用户IP

Step5:调用SDK,编写Main函数,函数如下:

[ZYNQ入门宝典]定制带AXI_Lite总线的用户IP

Step6:在线Debug。如果没有问题,PL端的LED将会依次流水闪动。

IP中的AXI源码分析:

模块端口:

带AXI总线的IP生成后,从其源码中可以发现,其端口信号其实对应的就是标准AXI总线所需要的信号线。而我们用户该怎么在此基础上修改呢?除了AXI总线的固定接口,其实Xilinx已经为用户预留下了定制接口的空间。如下图,用户可以根据自己需要在合适的地方添加接口。有点类似ISE环境下TEST BEACH的使用。

[ZYNQ入门宝典]定制带AXI_Lite总线的用户IP

至于整个IP默认实现了什么功能我们下文解析。

 

代码逻辑:

IP中默认存在的代码功能其实就是为了实现AXI_lite总线时序。分用了几个process模块实现,大同小异,在这,我们以其中的3个典型为例阐述其原理。

例1.

[ZYNQ入门宝典]定制带AXI_Lite总线的用户IP

如上图。该模块的最终目的是为了厘清axi_awready 、 S_AXI_AWVALID 、S_AXI_WVALID 三者的关系。而Xilinx其实已经将该模块的功能用注释的方法解释了。翻译过来就是,该模块用于实现axi_awready的时序关系:在一个时钟周期下,S_AXI_AWVALID 、S_AXI_WVALID同时有效axi_awready才有效。

与此逻辑类似的信号还有axi_awaddr,axi_wready。当然这只是写通道的部分信号时序,读通道和写反馈的逻辑是一样的,就不赘述。

 

 

 

例2.

[ZYNQ入门宝典]定制带AXI_Lite总线的用户IP

上图是写数据的逻辑时序。这里面有好几个关键信号。分别阐述。

slv_reg_wren

slv_reg_wren的逻辑是: 

slv_reg_wren <= axi_wready and S_AXI_WVALID and axi_awready and S_AXI_AWVALID ;

在这几个信号同时有效的时候slv_reg_wren有效。slv_reg_wren有效的时候主端写来的数据才能被从总线上写到对应的寄存器上。

 

loc_addr。

loc_addr的逻辑是:

loc_addr := axi_awaddr(ADDR_LSB + OPT_MEM_ADDR_BITS downto ADDR_LSB);

其中ADDR_LSB、OPT_MEM_ADDR_BITS在函数中定义为:

     constant ADDR_LSB  : integer := (C_S_AXI_DATA_WIDTH/32)+ 1;

     constant OPT_MEM_ADDR_BITS : integer := 1;

所以loc_addr:= axi_awaddr(3 downto 2),就是地址总线的2、3位。地址总线的2、3位从00变换到01、10、11其实就是地址信号axi_awaddr在不停加4。也就验证了不同寄存器的地址偏移4。 言外之意,地址总线的第2、3bit位决定了数据总线访问哪一个寄存器。

 

S_AXI_WSTRB。通道选通信号。每8个bit为一个通道,S_AXI_WSTRB信号的每个BIT位代表了一个通道。

 把这几个信号的功能搞清后可以发现,这段程序就是写数据的关键代码。代码的作用就是,当PS那边向AXI4-Lite总线写数据时,PS这边负责将数据接收到寄存器slv_reg。而slv_reg寄存器有0~3共4个。至于赋值给哪一个由axi_awaddr[3:2] (写地址中不仅包含地址,而且包含了控制位,这里的[3:2]就是控制位)决定赋值给哪个slv_reg。


           PS调用写函数时,如果不做地址偏移的话, axi_awaddr[3:2]的值默认是为0的,举个例子,如果我们自定义的IP的地址被映射为0x43C00000,那么我们在SDK软件中Xil_Out32(0x43C00000,Value)写的就是slv_reg0的值。如果地址偏移4位,如Xil_Out32(0x43C00000 + 4,Value) 写的就是slv_reg1的值,依次类推。
          我们分析时只关注slv_reg0(其他结构上也是一模一样的),当然,实际上我们修改程序时也是把
slv_reg0( 2 downto 0)赋值给了LED。

 

例3.

[ZYNQ入门宝典]定制带AXI_Lite总线的用户IP

上图是读数据的关键代码。读数据的过程也大致如此。有代码可知,先由loc_addr信号判断是哪一个寄存器的值,将寄存器的值读入到reg_data_out进行暂时缓存。在从设备读使能有效的时候,将reg_data_out放入到数据总线上。

 

后记:

分析完,好像Xilinx提供的IP源码中AXI总线时序和标准AXI总线时序不一样啊。有木有大神能解释的?例如,“在一个时钟周期下,S_AXI_AWVALID 、S_AXI_WVALID同时有效axi_awready才有效”这个逻辑,在标准的AXI总线里,axi_awready的状态好像和S_AXI_WVALID是无关的。如下图axi_awready显然只和S_AXI_AWVALID有关啊。有点费解。

[ZYNQ入门宝典]定制带AXI_Lite总线的用户IP