【转】AXI_Lite 总线详解

目录:
  · 1.前言
      · 3.1 AXI 总线概述
      · 3.2 AXI 接口介绍
      · 3.3 AXI 协议概述
      · 3.4 AXI 协议之握手协议
      · 3.5 突发式读写
   · 4 AXI4-Lite 详解
      · 4.1 AXI4-Lite 源码查看
      · 4.2 AXI-Lite 源码分析
   · 6 加载到 SDK
   · 7 本章小结
 
 
1 前言
  ZYNQ拥有ARM+FPGA这个神奇的架构,那么ARM和FPGA到底是如 何进行通讯的呢?本章经过剖析AXI总线源码,来一探其中的秘密。
 
2 AXI 总线与 ZYNQ 的关系
  AXI(Advanced eXtensible Interface)本是由ARM公司提出的一种总线协议,Xilinx 从 6 系列的 FPGA 开始对 AXI 总线提供支持,此时 AXI 已经发展到了 AXI4 这个版本,因此当你用到 Xilinx 的软件的时候看到的都是“AIX4”的 IP,如 Vivado 打包一个 AXI IP的时候,看到的都是 Create a new AXI4 peripheral。到了 ZYNQ 就更没必要说了,AXI 总线更是应用普遍,双击查看 ZYNQ 的 IP 核的内部配置,随处可见 AXI 的身影。
 
3 AXI 总线和 AXI 接口以及 AXI 协议
  总线、接口和协议,这三个词经常被联系在一块儿,可是咱们内心要明白他们的区别。总线是一组传输通道,是各类逻辑器件构成的传输数据的通道,通常由由数据线、地址线、控制线等构成。接口是一种链接标准,又经常被称之为物理接口。协议就是传输数据的规则。
3.1 AXI 总线概述
  在ZYNQ中有支持三种AXI总线,拥有三种AXI接口,固然用的都是AXI协议。其中三种AXI总线分别为:
    AXI4:(For high-performance memory-mapped requirements.)主要面向高性能地址映射通讯的需求,是面向地址映射的接口,容许最大256轮的数据突发传输;
    AXI4-Lite:(For simple, low-throughput memory-mapped communication )是一个轻量级的地址映射单次传输接口,占用不多的逻辑单元。
    AXI4-Stream:(For high-speed streaming data.)面向高速流数据传输;去掉了地址项,容许无限制的数据突发传输规模。
  首先说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信号。
  AXI4总线和AXI4-Lite总线的信号也有他的命名特色:
    读地址信号都是以AR开头(A:address;R:read)
    写地址信号都是以AW开头(A:address;W:write)
    读数据信号都是以R开头(R:read)
    写数据信号都是以W开头(W:write)
    应答型号都是以B开头(B:back(answer back))
  了解到总线的组成部分以及命名特色,那么在后续的实验中您将逐渐看到他们的身影。每一个信号的做用暂停不表,放在后面一一介绍。
  而AXI4-Stream总线的组成有:
    (1)ACLK信号:总线时钟,上升沿有效;
    (2)ARESETN信号:总线复位,低电平有效
    (3)TREADY信号:从机告诉主机作好传输准备;
    (4)TDATA信号:数据,可选宽度32,64,128,256bit
    (5)TSTRB信号:每一bit对应TDATA的一个有效字节,宽度为TDATA/8
    (6)TLAST信号:主机告诉从机该次传输为突发传输的结尾;
    (7)TVALID信号:主机告诉从机数据本次传输有效;
    (8)TUSER信号 :用户定义信号,宽度为128bit。
  对于AXI4-Stream总线命名而言,除了总线时钟和总线复位,其余的信号线都是以T字母开头,后面跟上一个有意义的单词,看清这一点后,能帮助读者记忆每一个信号线的意义。如TVALID = T+单词Valid(有效),那么读者就应该马上反应该信号的做用。每一个信号的具体做用,在后面分析源码时再作分析。
 
3.2 AXI 接口介绍
  三种AXI接口分别是:
  AXI-GP接口(4个):是通用的AXI接口,包括两个32位主设备接口和两个32位从设备接口,用过改接口能够访问PS中的片内外设。
  AXI-HP接口(4个):是高性能/带宽的标准的接口,PL模块做为主设备链接(从下图中箭头能够看出)。主要用于PL访问PS上的存储器(DDR和On-Chip RAM)
  AXI-ACP接口(1个):是ARM多核架构下定义的一种接口,中文翻译为加速器一致性端口,用来管理DMA之类的不带缓存的AXI外设,PS端是Slave接口。咱们能够双击查看ZYNQ的IP核的内部配置,就能发现上述的三种接口,图中已用红色方框标记出来,咱们能够清楚的看出接口链接与总线的走向:
 
3.3 AXI 协议概述
  讲到协议不可能说是撇开总线单讲协议,由于协议的制定也是要创建在总线构成之上的。虽说AXI4,AXI4-Lite,AXI4-Stream都是AXI4协议,可是各自细节上仍是不一样的。
  总的来讲,AXI总线协议的两端能够分为分为主(master)、从(slave)两端,他们之间通常须要经过一个AXI Interconnect相链接,做用是提供将一个或多个AXI主设备链接到一个或多个AXI从设备的一种交换机制。当咱们添加了zynq以及带AXI的IP后再进行自动连线时vivado会自动帮咱们添加上这个IP,你们应该是不陌生了。AXI Interconnect的主要做用是,当存在多个主机以及从机器时,AXIInterconnect负责将它们联系并管理起来。因为AXI支持乱序发送,乱序发送须要主机的ID信号支撑,而不一样的主机发送的ID可能相同,而AXI Interconnect解决了这一问题,他会对不一样主机的ID信号进行处理让ID变得惟一。
  AXI协议将读地址通道,读数据通道,写地址通道,写数据通道,写响应通道分开,各自通道都有本身的握手协议。每一个通道互不干扰却又彼此依赖。这也是AXI高效的缘由之一。 
 
3.4 AXI 协议之握手协议
  AXI4 所采用的是一种 READY,VALID 握手通讯机制,简单来讲主从双方进行数据通讯前,有一个握手的过程。传输源产生 VLAID 信号来指明什么时候数据或控制信息有效。而目地源产生 READY 信号来指明已经准备好接受数据或控制信息。传输发生在 VALID和 READY 信号同时为高的时候。VALID 和 READY 信号的出现有三种关系。
   (1) VALID 先变高 READY 后变高。时序图以下:
在箭头处信息传输发生。
   (2) READY 先变高 VALID 后变高。时序图以下:
一样在箭头处信息传输发生。
   (3) VALID 和 READY 信号同时变高。时序图以下: 
  在这种状况下,信息传输立马发生,如图箭头处指明信息传输发生。须要强调的是,AXI的五个通道,每一个通道都有握手机制,接下来咱们就来分析一下AXI-Lite的源码来更深刻的了解AXI机制。 
 
3.5 突发式读写
一、突发式读的时序图以下:
 
当地址出如今地址总线后,传输的数据将出如今读数据通道上。设备保持 VALID 为低直到读数据有效。为了代表一次突发式读写的完成,设备用 RLAST 信号来表示最后一个被传输的数据。
二、 突发式写时序图以下: 
这一过程的开始时,主机发送地址和控制信息到写地址通道中,而后主机发送每个写数据到写数据通道中。当主机发送最后一个数据时,WLAST 信号就变为高。当设备接收完全部数据以后他将一个写响应发送回主机来代表写事务完成。
 
4 AXI4-Lite 详解
4.1 AXI4-Lite 源码查看
  Step1:要看到AXI-Lite的源码,咱们先要自定义一个AXI-Lite的IP,新建工程以后,选择,菜单栏->Tools->Creat and Package IP:
Step2:选择Next
Step3:选择Create AXI4 Peripheral,而后Next:
Step4:给模块命名,保存,而后Next
Step5:注意这里接口类型选择Lite,选择Next:
Step6:选择Edit IP,点击Finish:
Step7:此后,Vivado会新建一个工程,专门编辑该IP,经过该工程,咱们就能够看到Vivado为咱们生成的AXI-Lite的操做源码: 
 
4.2 AXI-Lite 源码分析
  当打开顶层文件的时,映入眼帘的是一堆AXI的信号,这些信号是否似曾相识?
input wire s00_axi_aclk, input wire s00_axi_aresetn, input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_awaddr, input wire [2 : 0] s00_axi_awprot, input wire s00_axi_awvalid, output wire s00_axi_awready, input wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_wdata, input wire [(C_S00_AXI_DATA_WIDTH/8)-1 : 0] s00_axi_wstrb, input wire s00_axi_wvalid, output wire s00_axi_wready, output wire [1 : 0] s00_axi_bresp, output wire s00_axi_bvalid, input wire s00_axi_bready, input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_araddr, input wire [2 : 0] s00_axi_arprot, input wire s00_axi_arvalid, output wire s00_axi_arready, output wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_rdata, output wire [1 : 0] s00_axi_rresp, output wire s00_axi_rvalid, input wire  s00_axi_rready

 没错笔者曾在《AXI总线概述》这节中提到了他们,此次经过源码分析再次隆重介绍它们。 html

  Vivado为咱们生成的AXI-Lite的操做源码,是一个例子,我只须要读懂他,而后稍加修改,就能够为咱们所用。咱们先来看一段WDATA相关的代码: 
always @( posedge S_AXI_ACLK ) begin
  if ( S_AXI_ARESETN == 1'b0 )
    begin slv_reg0 <= 0; slv_reg1 <= 0; slv_reg2 <= 0; slv_reg3 <= 0; end
  else begin
    if (slv_reg_wren) begin
        case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) 2'h0:
            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin
                // Respective byte enables are asserted as per write strobes // Slave register 0 slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end  
          2'h1:
            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin
                // Respective byte enables are asserted as per write strobes // Slave register 1 slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end  
          2'h2:
            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin
                // Respective byte enables are asserted as per write strobes // Slave register 2 slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end  
          2'h3:
            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin
                // Respective byte enables are asserted as per write strobes // Slave register 3 slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end  
          default : begin slv_reg0 <= slv_reg0; slv_reg1 <= slv_reg1; slv_reg2 <= slv_reg2; slv_reg3 <= slv_reg3; end
        endcase
      end
  end
end
  这段程序的做用是,当PS那边向AXI4-Lite总线写数据时,PS这边负责将数据接收到寄存器slv_reg。而slv_reg寄存器有0~3共4个。至于赋值给哪个由axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]决定,根据宏定义其实就是由axi_awaddr[3:2] (写地址中不只包含地址,并且包含控制位,这里的[3:2]就是控制位)决定赋值给哪一个slv_reg。
  PS调用写函数时,若是不作地址偏移的话,axi_awaddr[3:2]的值默认是为0的,举个例子,若是咱们自定义的IP的地址被映射为0x43C00000,那么咱们Xil_Out32(0x43C00000,Value)写的就是slv_reg0的值。若是地址偏移4位,如Xil_Out32(0x43C00000 + 4,Value) 写的就是slv_reg1的值,依次类推。
  分析时只关注slv_reg0(其余结构上也是如出一辙的): 
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) if ( S_AXI_WSTRB[byte_index] == 1 ) begin slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8]; end

  其中,C_S_AXI_DATA_WIDTH的宏定义的值为32,也就是数据位宽,S_AXI_WSTRB就是写选通讯号,S_AXI_WDATA就是写数据信号。缓存

  存在于for循环中的最关键的一句:架构

    slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];app

  当byte_index = 0的时候这句话就等价于:函数

    slv_reg0[7:0] <= S_AXI_WDATA[7:0];源码分析

  当byte_index = 1的时候这句话就等价于:性能

    slv_reg0[15:8] <= S_AXI_WDATA[15:8];学习

  当byte_index = 2的时候这句话就等价于:测试

    slv_reg0[23:16] <= S_AXI_WDATA[23:16];ui

  当byte_index = 3的时候这句话就等价于:

    slv_reg0[31:24] <= S_AXI_WDATA[31:24];

  也就是说,只有当写选通讯号为1时,它所对应S_AXI_WDATA的字节才会被读取。

  读懂了这段话以后,咱们就知道了,若是咱们想获得PS写到总线上的数据,咱们只须要读取slv_reg0的值便可。

  那若是,咱们想写数据到总线让PS读取该数据,咱们该怎么作呢?咱们继续来看有关RADTA读数据代码:

// Output register or memory read data
always @( posedge S_AXI_ACLK ) begin
  if ( S_AXI_ARESETN == 1'b0 )
    begin axi_rdata <= 0; end
  else
    begin    
      // When there is a valid read address (S_AXI_ARVALID) with // acceptance of read address by the slave (axi_arready), // output the read dada
      if (slv_reg_rden) begin axi_rdata <= reg_data_out;     // register read data
        end   
    end
end

   观察可知,当PS读取数据时,程序会把reg_data_out复制给axi_rdata(RADTA读数据)。咱们继续追踪reg_data_out:

always @(*) begin
      // Address decoding for reading registers
      case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) 2'h0 : reg_data_out <= slv_reg0;
        2'h1 : reg_data_out <= slv_reg1;
        2'h2 : reg_data_out <= slv_reg2;
        2'h3 : reg_data_out <= slv_reg3;
        default : reg_data_out <= 0; endcase
end

  和前面分析的同样此时经过判断axi_awaddr[3:2]的值来判断将那个值给reg_data_out上,一样当PS调用读取函数时,这里axi_awaddr[3:2]默认是0,因此咱们只须要把slv_reg0替换成咱们本身数据,就可让PS经过总线读到咱们提供的数据。

  这里可能有的读者会问了,slv_reg0不是总线写过来的数据吗?由于笔者说过这个程序是Vivado为咱们提供的例子,它这么作无非是想验证我写出去的值和我读进入的值相等。可是他怎么写确实会对初看代码的人形成困扰。

  最后笔者提出一个问题,为何写通道要比读通道多了一列应答通道,这是为何呢?

  首先,你要知道这个应答信号是干什么用的?

  写应答,主要是回复主机你这个写过程是没有问题的,那读为何不须要这个过程呢?

 

  这时由于主机在读取数据时,从机能够直接经过读数据通道给主机反馈信息,所以就没有必要再来开辟一个单独的应答通道了。

小结:

  若是咱们想读AXI4_Lite总线上的数据时,只需关注slv_reg的数据,咱们可自行添加一段代码,如:

reg [11:0]rlcd_rgb; always @( posedge S_AXI_ACLK ) begin
    if ( S_AXI_ARESETN == 1'b0 )
        begin rlcd_rgb <= 12'd0;
        end
    else
    begin rlcd_rgb <= slv_reg0[11:0]; end
end  
assign lcd_rgb = rlcd_rgb;

   若是咱们想对AXI4_Lite信号写数据时,咱们只需修改对reg_data_out的赋值,如:

//写总线测试修改!!!!!!!!!
wire[31:0]wlcd_xy;// = {10'd0,lcd_xy};
assign wlcd_xy = {10'd0,lcd_xy};
assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid; always @(*) begin
    // Address decoding for reading registers
    case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) 2'h0 : reg_data_out <= wlcd_xy;//slv_reg0; 
      2'h1 : reg_data_out <= slv_reg1;
      2'h2 : reg_data_out <= slv_reg2;
      2'h3 : reg_data_out <= slv_reg3;
      default : reg_data_out <= 0; endcase
end

   最后强调下若是咱们自定义的IP的地址被映射为0x43C00000,那么咱们Xil_Out32(0x43C00000,Value)写的就是slv_reg0的值。若是地址偏移4位,如Xil_Out32(0x43C00000 + 4,Value) 写的就是slv_reg1的值,依次类推。

  目前这里只有4个寄存器,那是由于以前选择的是4个,其实咱们能够定义的更多:

  在ps的头文件里能够看到咱们自定义的IP的地址是有个范围的
#define XPAR_ MYIPFREQUENCY_ 0_ S00_ AXI_ BASEADDR 0x43C00000
#define XPAR_ MYIPFREQUENCY_ 0_ S00_ AXI_ HIGHADDR 0x43C0FFFF

   理论上只要基地址 + 偏移量不要超过HIGHADDR便可。

 

5 观察 AXI4-Lite 总线信号
  在第十章,咱们封装了一个 AXI_Lite 的 GPIO,经过本章的分析,咱们在第十章工程的基础上经过添加一个 ila 核的方式,来具体看看 AXI_Lite 总线的信号。
  Step1:作好第十章工程的备份,而后直接打开第十章的工程。 
  Step2:单击 IP icon 添加 ila CORE
  
  Step3:双击打开 ILA CORE
  
  Step4:双击打开 ILA CORE
    General Options 设置以下
  
  Probe_Ports 设置以下,以后单击 OK
  
  Step5:链接 Probe0 到 GPIO_LED。
  Step6:链接 CLK 接口到 FCLK_CLK0 接口
  Step7:选中 Processing_System7_0_axi_periph 和 GPIO_LITE_ML_0 之间的 S_AXI 总线。
  Step8:右击选择 Mark Debug 
  

  Step9:接下来依然是,右键单击Block文件,文件选择Generate the Output Products。

  Step10:继续右键单击Block文件,选择Create a HDL wrapper,根据Block文件内容产生一个HDL 的顶层文件,并选择让vivado自动完成。

  Setp11:单击Run Synthesis,若是有 Save 对话框弹出选择保存。

  Setp12:综合结束后选择Synthesized Design option单击 OK。

  Step13:在以下对话框中找到Unassigned debug nets(若是对话框没有出现选择 菜单->Window > Debug)

  

  Step14:右击 Unassigned Debug Nets 选择Set up Debug… 以后单击 Next

  Step15:删除红色错误的信号而后单击Next 到结束

  

  Step16:生成 Bit 文件。
 
6 加载到 SDK
  Step1:导出硬件。
  Step2:右击工程,选择 Debug as ->Debug configuration。
  Step3:选中 system Debugger,双击建立一个系统调试。 
  
  Step4:设置系统调试。
  
  Step5:回到 VIVADO 单击 Open Target->Auto Connect
  
  Step6:加载完成后的界面 
  
  Step7:选择菜单->window->Debugprobes 选择 AXI_WVALID 和 AXI_AWVALID 作为触发信号

   

  Step8:设置触发条件为 1 

   

  Step9:设置触发位置为 512
  
  Step10:单击箭头所指向启动触发 

   

  Step11:进入等待触发状态 
  
  Step12:单击运行 后 VIVADO HW_ILA2 窗口采集到波形输出,能够看到 AXI总线的工做时序。
  
  Step13:HW_ILA1 窗口采集到的数据是 GPIO_LED 的值为 0x02,同时可观察到开发板上的 LED2 亮起。
 
7 本章小结
  经过本章的学习,咱们首先得认识到总线和接口以及协议的区别,其次经过分析AXI4-Lite,AXI4-Stream,AXI4总线的从机代码,对AXI协议有必定的认识,那么在后面学习AXI的一些IP时就不会有恐惧的心理。
  最后,咱们再理一理AXI总线和AXI接口的关系。在ZYNQ中,支持AXI4-Lite,AXI4和AXI4-Stream三种总线协议,这前面已经说过了,要注意的是PS与PL之间的接口(AXI-GP接口,AXI-HP接口以及AXI-ACP接口)却只支持AXI-Lite和AXI协议这两种总线协议。也就是说PL这边的AXI-Stream的接口是不能直接与PS对接的,须要通过AXI4或者AXI4-Lite的转换。好比后面将用到的VDMA IP ,它就实现了在PL内部AXI4到AXI-Stream的转换,VDMA利用的接口就是AXI-HP接口。
 
注:本博客转自米联客博客:S02_CH12_ AXI_Lite 总线详解 https://www.cnblogs.com/milinker/p/6474706.html
相关文章
相关标签/搜索