AXI_lite代码简解-AXI-Lite 源码分析

原创
10/11 09:00
阅读数 32


AXI-Lite 源码分析

对于使用AXI总线,最开始肯定要了解顶层接口定义,这样才能针对顶层接口进行调用和例化,打开axi_lite_v1_0.v文件,第一段就是顶层的接口定义:

代码41 axi_lite接口定义

1.    // Ports of Axi Slave Bus Interface S00_AXI  

2.input wire  s00_axi_aclk,  

3.input wire  s00_axi_aresetn,  

4.input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_awaddr,  

5.input wire [2 : 0] s00_axi_awprot,  

6.input wire  s00_axi_awvalid,  

7.output wire  s00_axi_awready,  

8.input wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_wdata,  

9.input wire [(C_S00_AXI_DATA_WIDTH/8)-1 : 0] s00_axi_wstrb,  

10.input wire  s00_axi_wvalid,  

11.output wire  s00_axi_wready,  

12.output wire [1 : 0] s00_axi_bresp,  

13.output wire  s00_axi_bvalid,  

14.input wire  s00_axi_bready,  

15.input wire [C_S00_AXI_ADDR_WIDTH-1 : 0] s00_axi_araddr,  

16.input wire [2 : 0] s00_axi_arprot,  

17.input wire  s00_axi_arvalid,  

18.output wire  s00_axi_arready,  

19.output wire [C_S00_AXI_DATA_WIDTH-1 : 0] s00_axi_rdata,  

20.output wire [1 : 0] s00_axi_rresp,  

21.output wire  s00_axi_rvalid,  

22.input wire  s00_axi_rready 

上诉接口定义可以简单分成以下几类:

读通道

地址通道

数据通道

 

ARVALID

读地址有效。此信号表明该信道此时能有效读出地址和控制信息

RVALID

读数据有效。此信号表明该信道此时能有效读出数据

 

ARADDR

读地址

RDATA

读数据

 

ARREADY

读地址准备好了。该信号指示从器件准备好接受一个地址和相关联的控制信号

RREADY

读数据准备好了。该信号指示从器件准备好接收数据

 

ARPROT

保护类型。这个信号表示该事务的特权和安全级别,并确定是否该事务是一个数据存取或指令的访问

RRESP

读取响应。这个信号表明读事务处理的状态。

 

写 通 道

地址通道

数据通道

应答通道

AWVALID

写地址有效。这个信号表示该主信令有效的写地址和控制信息。

WVALID

写有效。这个信号表示有效的写数据和选通 信 号 都 可用。

BVALID

写响应有效。此信号表明写命令的有效写入响应。

AWADDR

写地址

WDATA

写数据

BREADY

响应准备。该信号指示在主主机可以接受一个响应
信号

AWREADY

写地址准备好了。该信号指示从器件准备好接受一个地址和相关联的控制信号

WSTRB

写选通。这个信号表明该字节通道持有效数据。每一bit对应 WDATA一个字节

BRESP

写响应。这个信号表示写事务处理的状态。

AWPROT

写通道保护类型。这个信号表示该事务的特权和安全级别,并确定是否该事务是一个数据存取或指令的访问

WREADY

写准备好了。该信号指示从器件可以接受











AXI_Lite代码其实相对来说比较简单,主要包括写数据和读数据两部分,先来看看写数据(WDATA)部分代码:


代码42 AXI_Lite写数据代码

1.    // Implement memory mapped register select and write logic generation  

2.// The write data is accepted and written to memory mapped registers when  

3.// axi_awready, S_AXI_WVALID, axi_wready and S_AXI_WVALID are asserted. Write strobes are used to  

4.// select byte enables of slave registers while writing.  

5.// These registers are cleared when reset (active low) is applied.  

6.// Slave register write enable is asserted when valid address and data are available  

7.// and the slave is ready to accept the write address and write data.  

8.assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID;  

9.   //锁存S_AXI_WDATA信号的条件为:

10.   //两个ready 两个valid

11.    //axi_wready , 输出信号, 从设备可以接受写数据

12.    //S_AXI_WVALID, 输入信号,主设备写数据有效

13.    //axi_awready,  输出信号,表示从设备可以接受写地址信号

14.    //S_AXI_AWVALID, 输入信号,表示主设备写地址有效

15.always @( posedge S_AXI_ACLK )  

16.begin  

17.  if ( S_AXI_ARESETN == 1'b0 )  

18.    begin  

19.      slv_reg0 <= 0;  

20.      slv_reg1 <= 0;  

21.      slv_reg2 <= 0;  

22.      slv_reg3 <= 0;  

23.    end   

24.  else begin  

25.    if (slv_reg_wren)  //接受写地址和写数据使能

26.      begin  

27.        case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] ) 

28.//数据存储在哪一个寄存器中取决于axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]中的两

29.位,即这两位中存储的是控制信号 

30.          2'h0:  

31.            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )  

32.              if ( S_AXI_WSTRB[byte_index] == 1 ) begin  //写选通信号

33.                // Respective byte enables are asserted as per write strobes   

34.                // Slave register 0  

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

36.              end    

37.          2'h1:  

38.            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )  

39.              if ( S_AXI_WSTRB[byte_index] == 1 ) begin  

40.                // Respective byte enables are asserted as per write strobes   

41.                // Slave register 1  

42.                slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];  

43.              end    

44.          2'h2:  

45.            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )  

46.              if ( S_AXI_WSTRB[byte_index] == 1 ) begin  

47.                // Respective byte enables are asserted as per write strobes   

48.                // Slave register 2  

49.                slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];  

50.              end    

51.          2'h3:  

52.            for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )  

53.              if ( S_AXI_WSTRB[byte_index] == 1 ) begin  

54.                // Respective byte enables are asserted as per write strobes   

55.                // Slave register 3  

56.                slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];  

57.              end    

58.          default : begin  

59.                      slv_reg0 <= slv_reg0;  

60.                      slv_reg1 <= slv_reg1;  

61.                      slv_reg2 <= slv_reg2;  

62.                      slv_reg3 <= slv_reg3;  

63.                    end  

64.        endcase  

65.      end  

66.  end  

67.end      

这里应用到的知识点如下:

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

这是Verilog2001新加的语法:Verilog-2001向量部分选择
在Verilog-1995中,可以选择向量的任一位输出,也可以选择向量的连续几位输出,不过此时连续几位的始末数值的index需要是常量。而在Verilog-2001中,可以用变量作为index,进行part select。 


      [base_expr +: width_expr] //positive offset

      [base_expr -: width_expr] //negative offset

      其中base_expr可以是变量,而width_expr必须是常量。+:表示由base_expr向上增长width_expr位,-:表示由base_expr向上递减width_expr位。例如:

     

      reg [63:0] word;
      reg [3:0] byte_num; //a value from 0 to 7
      wire [7:0] byteN = word[byte_num*8 +: 8];     

如果byte_num的值为4,则word[39:32]赋值给byteN。


这段程序的作用就是,当需要向AXI_Lite写数据时,模块负责将数据接收到slv_reg。而slv_reg寄存器有0~3共4个。赋值到哪个寄存器是由axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]决定,根据宏定义

      localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/32) + 1;

      localparam integer OPT_MEM_ADDR_BITS = 1;

其实就是由axi_awaddr[3:2] (写地址中不仅包含地址,而且包含了控制位,这里的[3:2]就是控制位)决定赋值给哪个slv_reg。

在调用写函数时,如果不做地址偏移的话, axi_awaddr[3:2]的值默认是为0的,举个例子,如果我们自定义的IP的地址被映射被调用时地址映射为为0x43C00000,那么Write(0x43C00000,Value)写的就是slv_reg0的值。如果地址偏移4位,如Write(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];

当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];

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

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

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

读懂了这段话之后,我们就知道了,如果我们想得到PS端写到总线上的数据,我们只需要读取slv_reg0的值即可。

那如果,我们想写数据到总线让PS读取该数据,我们该怎么做呢?我们继续来看有关RADTA读数据代码:

读操作:

代码43 读操作代码

1.  // Output register or memory read data  

2.always @( posedge S_AXI_ACLK )  

3.begin  

4.  if ( S_AXI_ARESETN == 1'b0 )  

5.    begin  

6.      axi_rdata  <= 0;  

7.    end   

8.  else  

9.    begin      

10.      // When there is a valid read address (S_AXI_ARVALID) with   

11.      // acceptance of read address by the slave (axi_arready),   

12.      // output the read dada   

13.      if (slv_reg_rden)  

14.        begin  

15.          axi_rdata <= reg_data_out;     // register read data  

16.        end     

17.    end  

18.end 

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

代码44 reg_data_out

1.   // Implement memory mapped register select and read logic generation  

2.    // Slave register read enable is asserted when valid address is available  

3.    // and the slave is ready to accept the read address.  

4.    assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;  

5.  

6.    // axi_arready, 输出信号, 表示从设备可以接受读地址信号  

7.    // S_AXI_ARVALID 输入信号,表示读地址信号有效  

8.    // axi_rvalid  输出信号, 表示读取的数据有效  

9.  

10.// read  

11.  

12.    always @(*)  

13.    begin  

14.          // Address decoding for reading registers  

15.          case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )  

16.//由给定的axi_araddr信号中的[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]中的两位来决定哪一个寄存器中的值来驱动reg_data_out  

17.            2'h0   : reg_data_out <= slv_reg0;  

18.            2'h1   : reg_data_out <= slv_reg1;  

19.            2'h2   : reg_data_out <= slv_reg2;  

20.            2'h3   : reg_data_out <= slv_reg3;  

21.            default : reg_data_out <= 0;  

22.          endcase  

23.    end  

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

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

为什么写通道要比读通道多了一列应答通道?

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

 

在读交易中,主设备先发送ARADDR和ARVALID给从设备,从设备回发ARREADY,通知主设备该地址有效,当ARVALID和ARREADY都为高电平时,主设备发出RREADY,表示主设备准备好接受读数据和相应信号了。从设备发送RVALID、RDATA以及RRESP,当RVALID和RREADY都为高电平时,数据被写入主设备。

小结:

如果我们想读AXI4_Lite总线上的数据时(对软核或者硬核来说就是写总线数据),只需关注slv_reg的数据,我们可自行添加一段代码,如:

代码45 读AXI4_Lite总线上的数据格式

1.   reg [11:0]rlcd_rgb;  

2.always @( posedge S_AXI_ACLK )  

3.     begin  

4.           if ( S_AXI_ARESETN == 1'b0 )  

5.           begin  

6.                rlcd_rgb <= 12'd0;  

7.           end  

8.          else  

9.         begin  

10.              rlcd_rgb <= slv_reg0[11:0];  

11.          end  

12.     end  

13.assign lcd_rgb = rlcd_rgb; 

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

代码46 AXI4_Lite写总线测试修改

1.   //写总线测试修改!!!!!!!!!  

2.wire[31:0]wlcd_xy;  

3.  

4.assign wlcd_xy = {10'd0,lcd_xy};  

5.assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;  

6.  

7.always @(*)  

8.     begin  

9.          // Address decoding for reading registers  

10.            case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )  

11.  

12.             2'h0 : reg_data_out <= wlcd_xy;//slv_reg0;  

13.             2'h1 : reg_data_out <= slv_reg1;  

14.             2'h2 : reg_data_out <= slv_reg2;  

15.             2'h3 : reg_data_out <= slv_reg3;  

16.             default : reg_data_out <= 0;  

17.  

18.            endcase  

19.     end  

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

目前这里只有 4 个寄存器,那是因为之前选择的是 4 个,其实我们可以定义的更多:

449 定义寄存器数量


NOW


推荐阅读


AXI总线详解

AXI总线详解-总线、接口以及协议

AXI接口协议详解-AXI总线、接口、协议

AXI协议中的通道结构

AXI总线详解-AXI4读写操作时序及AXI4猝发地址及选择

高级FPGA设计技巧!多时钟域和异步信号处理解决方案

AXI总线详解-AXI4交换机制

计算机基础知识总结与操作系统.PDF

IC技术圈期刊 2020年第09期

ZYNQ中DMA与AXI4总线-DMA简介
AXI总线详解-不同类型的DMA

不了解FPGA工作原理?看看世界第一颗FPGA芯片级拆解

几种应用DMA的典型应用

AXI_lite代码简解-查看源码



点击上方字体即可跳转阅读哟






本文分享自微信公众号 - OpenFPGA(OpenFPGA)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部