当前位置:   article > 正文

【ZYNQ】PS和PL数据交互丨AXI总线(主机模块RTL代码实现)_ps axi访问pl寄存器

ps axi访问pl寄存器



一、PS-PL数据交互桥梁:AXI总线

1.1 AXI总线和AXI4总线协议

ZYNQ芯片操作DDR3不需要例化MIG IP(ZYNQ可能根本就没有MIG IP?Xilinx官方作了一些规定,说使用ZYNQ芯片操作DDR3必须通过AXI_HP进行操作,但此说法出自哪里仍然需要考证),只需要直接往AXI_HP总线读写数据即可。所以要想使用ZYNQ芯片操作DDR3,重点转化为如何操作AXI总线。

在这里插入图片描述

AXI端口存在于PS端(存在于PS端的意思是端口的命名使用PS端的作用命名),分为以下三种:

  1. AXI_GP:通用AXI接口,位宽为32位,适用于PS和PL端进行低速通信;
  2. AXI_HP:高性能AXI接口,位宽为32位或者64位,适用于PS和PL端进行高速通信,它是拥有读写FIFO的高性能端口,在有些地方被称作AXI_FIFO或AFI;
  3. AXI_ACP:加速器一致AXI接口,位宽为64位,适用于PS和PL进行高速通信,可以用作cache(高速数据缓冲)一致性回话(暂时不懂cache一致性回话是什么)。

AXI端口使用AXI4总线协议。AXI4协议是ARM公司提出的一种高性能,高带宽,低延迟的片内总线。它主要描述了主设备到从设备之间的数据传输方式。为了适用不同的数据传输情况,提高性能,AXI4协议有以下三种分类:

  1. AXI_Lite:不支持突发传输。发送一个数据的也必须发送一个地址,常用于数据量最小的传输,可以认为是一种轻量化的AXI_Full,ZYNQ的GP接口使用这种协议;
  2. AXI_Full:支持突发传输,突发长度为1至256,ZYNQ的HP和ACP接口使用这种协议;
  3. AXI_Stream:直接丢弃地址项,常用于高速数据传输。

AXI端口和AXI4协议的关系是什么?其实从名称上就可见一斑。AXI的GP、HP、ACP都是ZYNQ芯片上独有的片上传输端口,使用了AXI4协议进行通信;而AXI4协议不单单可以存在于ZYNQ芯片上,FPGA主模块和从模块之间也可以使用AXI4协议进行数据交互,例如MIG IP核于用户端之间就可以选择AXI4协议进行数据交互。

GP接口共有4个,其中两个PS作主机,两个PS作从机;HP接口共有4个,全部都是PS作从机;ACP接口有一个,PS作从机。主机和从机指的是控制信号由主机给出,从机进行接收,并不是指数据传输的方向。在主机和从机之间,数据是可以进行双向传输的。

1.2 PS-PL数据传输的主要场景

1.2.1 PL通过AXI_HP操作DDR3 Controller读写DDR3

一般而言,使用PL直接操作DDR3是十分复杂的,所以我们可以让PL直接读写位于PS端的DDR3 Controller,DDR3 Controller就会自动帮助开发者完成读写操作。ARM核同样可以通过Cache操作DDR3 Controller读写数据。如果要采用这种方式完成PS和PL的数据交互,可以让PL写入DDR3,PS读出DDR3;也可以让PS写入DDR3,PL读出DDR3,此时DDR3就起到“数据中介”的作用。

PL端通过ACP接口可以将数据写入Cache,那么就可以通过Cache操作DDR3 Controller进而操作DDR3芯片。PL端既可以通过HP接口操作DDR3,也可以通过ACP接口操作DDR3,哪个更好呢?显然HP更合理,因为使用ACP接口让数据的传输多了一个Cache传输,有“舍近求远”的嫌疑。

1.2.2 PS作主机使用GP接口传输数据

当PS端作为主机使用GP接口传输数据时,PL为从机,此时可以将PL看作PS的外设。与微控制器系统不同的是,以PL作为外设可以让外设成为可编程器件,大大扩展了设计的灵活性和性能上限。

当PS将PL端看作外设使用时,PL端必须定义一系列的寄存器并划定地址供PS使用。PL端仅定义了这些寄存器的地址,但是实际上这些寄存器是PS的寄存器 (这些寄存器到底在PS还是PL?有待考证)
在这里插入图片描述

上图中0x4300_0000可以是基地址,也成为起始地址;0x4300_FFFF是结束地址。每个寄存器都是32位的,这里的寄存器地址相当于寄存器首字节的起始地址(内部依然按字节编址)。起始地址和结束地址的值是可以通过Vivado软件在PL端配置的。PS通过基地址+偏移的形式就可以访问所有的寄存器。基地址相当于PL端外设的ID号,在PL端定义的不同基地址对应不同的可编程外设模块。

进行数据交互时,PS将数据写入寄存器,PL通过GP总线读出寄存器的值,就完成了PS到PL的数据传输;也可以让PL通过GP写入寄存器,PS将数据读出,就完成了PL到PS的数据传输。

从这里就可以看到HP接口和GP接口的一个区别了:PL通过HP接口写入的数据存储到了DDR芯片中,而PL通过GP接口传输的数据存储在了PS端的寄存器中

在实际操作中,在PS端常用以下两个函数:

  • xil_out32(addr, data)
  • xil_in32(addr)

这两个函数将PS和PL的数据交互封装起来了,隐藏了很多内部的时序逻辑。实际上PS和PL进行数据交互时使用握手机制(handshake)表示数据何时有效,何时准备好传输。实际的传输时序是相当比较复杂的。这一部分的实际操作相关的细节之后再进行补充,这里可以仅作简单了解。

1.3 AXI端口带宽理论

在这里插入图片描述
上图给出了各个接口的带宽理论上限。这些数据是如何计算出来的?以第一行GP接口为例,数据位宽为32bit,接口时钟(IF Clock)频率典型值为150MHz,所以单个GP的接口的峰值数据带宽为32×150=4800 Mb/s,也就是32×150/8=600MB/s。在这里需要注意Mb和MB是两个不同的概念。读写带宽重叠的带宽为1200MB/s,M_GP_AXI一共有两条,所以该通道的数据带宽峰值为2400MB/s。除了DDR和OCM的计算不能按照这种方法外,其他的总线数据带宽计算都可以参照这种方式。

1.4 AXI 总线的读写分离机制

在AXI协议中,读写数据通道是分离的,其中读通道包含两个部分,写通道包含三个部分:

  • 读通道
    • 读地址通道 Address ,简写为AR
    • 读数据通道 Data (内部包含读响应),简写为R
  • 写通道
    • 写地址通道 Address,简写为AW
    • 写数据通道 Data,简写为W
    • 写响应 Write Response,简写为B

可以看到,读通道不存在一个单独的“读响应通道”,但是并不表示读通道没有读响应机制。读通道的读响应信号包含在了读数据通道中。

1.5 握手机制

在这里插入图片描述
握手信号是AXI总线数据传输中的一个重点,甚至是AXI总线的核心。发送端发出一个请求,如果接收端准备好,就向发送端返回一个“准备好了”信号;如果接收端没有准备好,发送端的请求就一直保持,直到收到一个准备好信号才开始下一步动作。从波形上理解,可以认为只有当请求信号和准备好信号同时有效(同时为高电平)时数据才正确传输过去

一般而言,我们将请求信号称为VALID,也可以称为命令有效;将准备好信号成为READY

主机如何写数据到从机?用写数据通道的部分信号为例说明:

  • WVALID:从机通过这个信号知道主机发起了请求。当从机在时钟的上升沿检测到VALID信号拉高,就说明接到了主机的请求;

  • WREADY:主机通过这个信号知道从机已准备好接收。当主机在时钟上升沿检测到READY信号拉高,就说明从机准备好了;

  • WDATA:在写数据过程中,有效数据通常和VALID信号同时发出。

VALIDREADY信号可以同时到达,也可以先后到达。只有这两个信号同时拉高后,握手机制才正式成立,读时序也就完成了。握手机制不单单存在于读写时序中,也存在于响应时序中,换言之,AXI总线的五个通道都存在握手机制,只要主机和从机之间进行了数据传输,无论这个数据的形式是怎样的(地址、数据或响应),都必然采用握手机制保证数据传输的稳定

1.6 AXI_Lite总线

AXI总线是ARM公司设计的,想必设计之初也不单单为FPGA内部数据通信考量,而是更多地为CPU、MCU等设备内部的数据交互提供了便利,所以在FPGA中使用AXI总线并不需要过分关注AXI总线多如牛毛的端口,而是更应该将精力放在时序的理解上。

AXI_GP接口使用AXI_Lite协议进行低速的数据传输,AXI_Lite实际上就是轻量化的AXI_Full信号,这个“听起来”很唬人,实际上就是砍掉了一部分在FPGA设计中不常用的信号,剩下的都是在FPGA应用中的核心。AXI_Lite协议中不同通道的信号线如下图所示:
在这里插入图片描述
可以看到,这些读写通道的组成都是有规律的。除了握手机制中提到的请求VALIDREADY和数据DATA/ADDR外,还有以下几种信号:

  • STRB:这个信号指有效字节的位置。由于AXI_Lite协议中数据线只能是32bit,所以STRB的位宽为4bit,分别表明哪些字节的传输是有效的,1代表有效,0代表无效,低位对应低字节,高位对应高字节;
  • PORT:保护类型信号,表示传输数据的安全等级和优先级,无论传输的是指令还是数据。不常用,通常默认为3'b000
  • BRESP/RRESP:写响应通道的响应信号和读响应信号,当从机返回的值为2'b00时表示传输成功,当返回的值为2'b01是表示EXOKAY(暂时不知道是什么,AXI_Lite不支持),返回值为2'b10时表示从机发生了错误,当返回值为2'b11是表示DECERR(暂时不知道是什么,但是也不常用)。

1.7 AXI总线的读写过程

1.7.1 写过程

写过程使用写通道,通道中三个子通道的时序(先后顺序示意图)如下图所示:
在这里插入图片描述
上图的时序并不是完全固定的,写地址和写数据的先后顺序可以调整,主机可以先发送写地址,再发送写数据;也可以先发送写数据,再发送写地址,当然也可以同时发送。这就对应了三种主机发送模式,对应从机也有三种接收模式。最常用的模式如下所示:

  1. 主机同时发送写地址和写数据;
  2. 从机同时接收写地址和写数据;
  3. 从机返回写响应。

1.7.2 读过程

在这里插入图片描述
读过程和写过程同理,但是上面的时序图是相对固定的。我们最常用的模式是:

  1. 主机发送读地址;
  2. 从机接收读地址;
  3. 从机发送读数据和读响应。

二、从零开始构建AXI接口

虽然Xilinx官方为AXI总线提供了很多基于IP核的设计,应用这些设计可以大大简化设计流程,但是这种基于IP核的设计是以消耗了FPGA设计的灵活性为代价的,所以还是有必要自己手撕AXI代码。

AXI实现写数据的步骤:

  1. 写首地址,(同时)突发传输数据;
  2. 控制LAST信号告知从机最后一个数据什么时候传输结束;
  3. 等待从机发送的写响应信号,判断这次写数据是否成功。

AXI实现读数据的步骤:

  1. 写首地址;
  2. 等待从机传输数据和读响应信号,当VALIDREADY信号都有效时接收数据;
  3. 接收从机发送的LAST信号。

2.1 获取Xilinx的官方AXI4代码

在写代码之前可以参考Xilinx官方的代码,用下面的方法获得官方给出的AXI4接口代码,在工具栏Tools下的Create and Package New IP,选择创建一个AXI4接口。选择端口信息后创建IP核并选择编辑IP,就可以获得Xilinx官方的AXI接口代码。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 代码编写及仿真

2.2.1 AxSIZEAxLEN的赋值

下面的函数描述了AxSIZE和参数C_M_AXI_DATA_WIDTH/8-1之间的转换关系。C_M_AXI_DATA_WIDTH是传输的数据的位宽,单位为bit。在效果上等同于运用了一个巧妙的方法计算了一个数的以2为底的对数。
在这里插入图片描述

	/* calculate the binary bit width of number */
	function integer clogb2(input integer number);
		begin
			for (clogb2 = 0; number > 0; clogb2 = clogb2 + 1)
				number = number >> 1;
		end
	endfunction
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

实际使用时用以下的形式调用:

	assign M_AXI_AWSIZE = clogb2((C_MAX_DATA_WIDTH/8)-1);
  • 1

在ARM官方的AXI手册中,对AxLEN的定义如下:
在这里插入图片描述
所以需要注意,在根据参数建模时,需要将C_M_AXI_BURST_LEN减一之后赋值给AxLEN,否则会出现读时序的错误:

	assign M_AXI_AWLEN = C_M_AXI_BURST_LEN - 1;
	/* Some code... */
	assign M_AXI_ARLEN = C_M_AXI_BURST_LEN - 1;
  • 1
  • 2
  • 3

2.2.2 AXI主机接口时序

为了和Xilinx官方提供的AXI接口IP互联时省去很多操作,可以直接选择官方生成的代码中的端口命名方式。下面是笔者自己写的一个AXI主机接口,对AXI从机接口进行突发写和突发读操作,突发长度都是16。程序中的状态机在这样的一个简单的例子中没有实际应用意义,但是在数据量大时采用三段式状态机可以帮助优化代码编写的思路。

/*
 * File Created: Wednesday, 17th April 2024 23:52:15
 *
 * Last Modified: Thursday, 18th April 2024 16:59:09
 *
 * Function: Creat a AXI Bus Master module without IP
 */

module user_AXI_FULL_M #
    (
        parameter         C_M_TARGET_SLAVE_BASE_ADDR = 32'h40000000,

        parameter integer C_M_AXI_BURST_LEN	    = 16,       // Burst length must greater than 2 in this module
        parameter integer C_M_AXI_ID_WIDTH	    = 1,
        parameter integer C_M_AXI_ADDR_WIDTH	= 32,
        parameter integer C_M_AXI_DATA_WIDTH	= 32,
        parameter integer C_M_AXI_AWUSER_WIDTH	= 0,
        parameter integer C_M_AXI_ARUSER_WIDTH	= 0,
        parameter integer C_M_AXI_WUSER_WIDTH	= 0,
        parameter integer C_M_AXI_RUSER_WIDTH	= 0,
        parameter integer C_M_AXI_BUSER_WIDTH	= 0
    )
    (
        input wire  								INIT_AXI_TXN 	,
        output wire  								TXN_DONE     	,
        output reg  								ERROR        	,

        input wire  								M_AXI_ACLK   	,
        input wire  								M_AXI_ARESETN	,

        output wire [C_M_AXI_ID_WIDTH-1 : 0] 		M_AXI_AWID   	,
        output wire [C_M_AXI_ADDR_WIDTH-1 : 0] 		M_AXI_AWADDR 	,
        output wire [7 : 0] 						M_AXI_AWLEN  	,
        output wire [2 : 0] 						M_AXI_AWSIZE 	,
        output wire [1 : 0] 						M_AXI_AWBURST	,
        output wire  								M_AXI_AWLOCK 	,
        output wire [3 : 0] 						M_AXI_AWCACHE	,
        output wire [2 : 0] 						M_AXI_AWPROT 	,
        output wire [3 : 0] 						M_AXI_AWQOS  	,
        output wire [C_M_AXI_AWUSER_WIDTH-1 : 0] 	M_AXI_AWUSER 	,
        output wire  								M_AXI_AWVALID	,
        input wire  								M_AXI_AWREADY	,

        output wire [C_M_AXI_DATA_WIDTH-1 : 0] 		M_AXI_WDATA		,
        output wire [C_M_AXI_DATA_WIDTH/8-1 : 0] 	M_AXI_WSTRB		,
        output wire  								M_AXI_WLAST		,
        output wire [C_M_AXI_WUSER_WIDTH-1 : 0] 	M_AXI_WUSER		,
        output wire  								M_AXI_WVALID	,
        input wire  								M_AXI_WREADY 	,

        input wire [C_M_AXI_ID_WIDTH-1 : 0] 		M_AXI_BID		,
        input wire [1 : 0] 							M_AXI_BRESP		,
        input wire [C_M_AXI_BUSER_WIDTH-1 : 0] 		M_AXI_BUSER		,
        input wire  								M_AXI_BVALID 	,
        output wire  								M_AXI_BREADY 	,

        output wire [C_M_AXI_ID_WIDTH-1 : 0] 		M_AXI_ARID		,
        output wire [C_M_AXI_ADDR_WIDTH-1 : 0] 		M_AXI_ARADDR 	,
        output wire [7 : 0] 						M_AXI_ARLEN		,
        output wire [2 : 0] 						M_AXI_ARSIZE 	,
        output wire [1 : 0] 						M_AXI_ARBURST 	,
        output wire  								M_AXI_ARLOCK 	,
        output wire [3 : 0] 						M_AXI_ARCACHE 	,
        output wire [2 : 0] 						M_AXI_ARPROT 	,
        output wire [3 : 0] 						M_AXI_ARQOS		,
        output wire [C_M_AXI_ARUSER_WIDTH-1 : 0] 	M_AXI_ARUSER 	,
        output wire 						 		M_AXI_ARVALID 	,
        input wire  								M_AXI_ARREADY 	,

        input wire [C_M_AXI_ID_WIDTH-1 : 0] 		M_AXI_RID		,
        input wire [C_M_AXI_DATA_WIDTH-1 : 0] 		M_AXI_RDATA		,
        input wire [1 : 0] 							M_AXI_RRESP		,
        input wire  								M_AXI_RLAST		,
        input wire [C_M_AXI_RUSER_WIDTH-1 : 0] 		M_AXI_RUSER		,
        input wire  								M_AXI_RVALID 	,
        output wire  								M_AXI_RREADY
    );

    /* calculate the binary bit width of number */
    function integer clogb2(input integer number);
        begin
            for (clogb2 = 0; number > 0; clogb2 = clogb2 + 1)
                number = number >> 1;
        end
    endfunction

    /* --------------------Parameter define--------------------- */
    parameter   P_ST_IDLE        = 'd0,
                P_ST_WRITE_START = 'd1,
                P_ST_WRITE_TRANS = 'd2,
                P_ST_WRITE_END   = 'd3,
                P_ST_READ_START  = 'd4,
                P_ST_READ_TRANS  = 'd5,
                P_ST_READ_END    = 'd6;

    /* --------------------State machine------------------------ */
    reg [7:0] r_st_current_write;
    reg [7:0] r_st_next_write;

    reg [7:0] r_st_current_read;
    reg [7:0] r_st_next_read;

    /* --------------------Reg define--------------------------- */
    reg [C_M_AXI_ADDR_WIDTH - 1 : 0] r_m_axi_awaddr;
    reg                              r_m_axi_awvalid;

    reg [C_M_AXI_DATA_WIDTH - 1 : 0] r_m_axi_wdata;
    reg                              r_m_axi_wvalid;
    reg                              r_m_axi_wlast;

    reg [C_M_AXI_ADDR_WIDTH - 1 : 0] r_m_axi_araddr;
    reg                              r_m_axi_arvalid;

    reg                              r_m_axi_rready;

    reg                              r_write_start;     // write exec signal
    reg                              r_read_start;      // read exec signal
    reg [7:0]                        r_burst_cnt;       // counter for burst write

    reg [C_M_AXI_DATA_WIDTH - 1 : 0] r_axi_read_data;   // read data from slave interface

    /* --------------------Net define--------------------------- */

    /* --------------------Combinational Logic------------------ */
    assign M_AXI_AWID    = 'd0;
    assign M_AXI_AWLEN   = C_M_AXI_BURST_LEN - 1;
    assign M_AXI_AWSIZE  = clogb2((C_M_AXI_DATA_WIDTH/8)-1);
    assign M_AXI_AWBURST = 2'b01;       // burst type, select INCR here
    assign M_AXI_AWLOCK  = 1'd0;
    assign M_AXI_AWCACHE = 4'b0010;     // normal non-cacheable non-bufferable
    assign M_AXI_AWPROT  = 'd0;
    assign M_AXI_AWQOS   = 'd0;
    assign M_AXI_AWUSER  = 'd0;
    assign M_AXI_AWADDR  = r_m_axi_awaddr + C_M_TARGET_SLAVE_BASE_ADDR;
    assign M_AXI_AWVALID = r_m_axi_awvalid;

    assign M_AXI_WSTRB = {(C_M_AXI_DATA_WIDTH / 8){1'b1}};
    assign M_AXI_WUSER = 'd0;
    assign M_AXI_WDATA = r_m_axi_wdata;
    assign M_AXI_WLAST = r_m_axi_wlast;
    assign M_AXI_WVALID = r_m_axi_wvalid;

    assign M_AXI_BREADY = 1'b1;         // master ready for accept response any time

    assign M_AXI_ARID    = 'd0;
    assign M_AXI_ARADDR  = r_m_axi_araddr + C_M_TARGET_SLAVE_BASE_ADDR;
    assign M_AXI_ARLEN   = C_M_AXI_BURST_LEN - 1;
    assign M_AXI_ARSIZE  = clogb2((C_M_AXI_DATA_WIDTH/8)-1);
    assign M_AXI_ARBURST = 2'b01;
    assign M_AXI_ARLOCK  = 1'b0;
    assign M_AXI_ARCACHE = 4'b0010;
    assign M_AXI_ARPROT  = 'd0;
    assign M_AXI_ARQOS   = 'd0;
    assign M_AXI_ARUSER  = 'd0;
    assign M_AXI_ARVALID = r_m_axi_arvalid;

    assign M_AXI_RREADY = r_m_axi_rready;

    /* --------------------Sequencial Logic--------------------- */

    /* WRITE logic---------------------------------------------- */

    /* address write valid */
    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (!M_AXI_ARESETN || (M_AXI_AWVALID && M_AXI_AWREADY)) begin
            r_m_axi_awvalid <= 'd0;
        end
        else if (r_write_start) begin
            r_m_axi_awvalid <= 'd1;
        end
        else begin
            r_m_axi_awvalid <= r_m_axi_awvalid;
        end
    end

    /* send address for write */
    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (!M_AXI_ARESETN) begin
            r_m_axi_awaddr <= 'd0;
        end
        else if (r_write_start) begin
            r_m_axi_awaddr <= 'd0;          // set the write address to 0
        end
        else begin
            r_m_axi_awaddr <= r_m_axi_awaddr;
        end
    end

    /* write data valid */
    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (!M_AXI_ARESETN || M_AXI_WLAST) begin
            r_m_axi_wvalid <= 1'd0;
        end
        else if (M_AXI_AWVALID && M_AXI_AWREADY) begin  // write data when sending write address is successful
            r_m_axi_wvalid <= 1'b1;
        end
        else begin
            r_m_axi_wvalid <= r_m_axi_wvalid;
        end
    end

    /* write data */
    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (!M_AXI_ARESETN || M_AXI_WLAST) begin
            r_m_axi_wdata <= 'd1;                       // write data begin with 1, followed by 2, 3, 4...
        end
        else if (M_AXI_WVALID && M_AXI_WREADY) begin    // write data is successful
            r_m_axi_wdata <= r_m_axi_wdata + 'd1;
        end
        else begin
            r_m_axi_wdata <= r_m_axi_wdata;
        end
    end

    /* write last data signal */
    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (r_burst_cnt == C_M_AXI_BURST_LEN - 2) begin     // Burst length must greater than 2
            r_m_axi_wlast <= 1'b1;
        end
        else begin
            r_m_axi_wlast <= 1'b0;
        end
    end

    /* burst length counter */
    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (!M_AXI_ARESETN) begin
            r_burst_cnt <= 'd0;
        end
        else if (r_burst_cnt == C_M_AXI_BURST_LEN - 1) begin
            r_burst_cnt <= 'd0;
        end
        else if (M_AXI_WVALID && M_AXI_WREADY) begin
            r_burst_cnt <= r_burst_cnt + 'd1;
        end
        else begin
            r_burst_cnt <= r_burst_cnt;
        end
    end

    /* READ logic---------------------------------------------- */
    /* address read valid */
    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (!M_AXI_ARESETN || (M_AXI_ARVALID && M_AXI_ARREADY)) begin
            r_m_axi_arvalid <= 1'b0;
        end
        else if (r_read_start) begin
            r_m_axi_arvalid <= 1'b1;
        end
    end

    /* send address for read */
    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (!M_AXI_ARESETN) begin
            r_m_axi_araddr <= 'd0;
        end
        else if (r_read_start) begin
            r_m_axi_araddr <= 'd0;
        end
        else begin
            r_m_axi_araddr <= r_m_axi_araddr;
        end
    end

    /* read ready */
    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (!M_AXI_ARESETN || M_AXI_RLAST) begin
            r_m_axi_rready <= 1'b0;
        end
        else if (M_AXI_ARVALID && M_AXI_ARREADY) begin      // read data when sending read address is successful
            r_m_axi_rready <= 1'b1;
        end
    end

    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (M_AXI_RVALID && M_AXI_RREADY) begin
            r_axi_read_data <= M_AXI_RDATA;
        end
        else begin
            r_axi_read_data <= r_axi_read_data;
        end
    end

    /* State machine------------------------------------------- */
    /* WRITE machine */
    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (!M_AXI_ARESETN)
            r_st_current_write <= P_ST_IDLE;
        else
            r_st_current_write <= r_st_next_write;
    end

    always @(*) begin
        case (r_st_current_write)
            P_ST_IDLE        :
                r_st_next_write = P_ST_WRITE_START;
            P_ST_WRITE_START :
                r_st_next_write = r_write_start ? P_ST_WRITE_TRANS : P_ST_WRITE_START;
            P_ST_WRITE_TRANS :
                r_st_next_write = M_AXI_WLAST ? P_ST_WRITE_END : P_ST_WRITE_TRANS;
            P_ST_WRITE_END   :
                r_st_next_write = (r_st_current_read == P_ST_READ_END) ? P_ST_IDLE : P_ST_WRITE_END;
            default:
                r_st_next_write = P_ST_IDLE;
        endcase
    end

    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (r_st_current_write == P_ST_WRITE_START)
            r_write_start <= 1'b1;
        else
            r_write_start <= 1'b0;
    end

    /* READ machine */
    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (!M_AXI_ARESETN)
            r_st_current_read <= P_ST_IDLE;
        else
            r_st_current_read <= r_st_next_read;
    end

    always @(*) begin
        case (r_st_current_read)
            P_ST_IDLE       :
                r_st_next_read = (r_st_current_write == P_ST_WRITE_END) ? P_ST_READ_START : P_ST_IDLE;
            P_ST_READ_START :
                r_st_next_read = r_read_start ? P_ST_READ_TRANS : P_ST_READ_START;
            P_ST_READ_TRANS :
                r_st_next_read = M_AXI_RLAST ? P_ST_READ_END : P_ST_READ_TRANS;
            P_ST_READ_END   :
                r_st_next_read = P_ST_IDLE;
            default:
                r_st_next_read = P_ST_IDLE;
        endcase
    end

    always @(posedge M_AXI_ACLK or negedge M_AXI_ARESETN) begin
        if (r_st_current_read == P_ST_READ_START)
            r_read_start <= 1'b1;
        else
            r_read_start <= 1'b0;
    end

endmodule

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346

2.2.3 将RTL模块添加到Block Design中

首先创建一个Vivado工程,添加编写好的RTL代码后创建一个Block Design,在空白处右键点击Add Module…
在这里插入图片描述
出现下面的窗口,Vivado会自动识别工程中可以添加的RTL模块,点击添加即可。
在这里插入图片描述
或者直接在Sources菜单中右键写好的RTL模块,选择Add Module to Block Design,效果是一样的。
在这里插入图片描述

2.2.4 仿真验证

创建一个拥有从机AXI_Full接口的IP核,引出端口,连接对应信号,生成底层后就可以添加仿真文件进行仿真了(在这里博主写的模块进行连线验证后会出现一个警告,提示两个模块的AXI端口并不能完全连接,但是这个警告暂时不影响仿真效果)。
在这里插入图片描述

module axi_full_test_tb();

    reg txn;
    reg clk;
    reg rstn;
    
    initial begin
        clk = 0;
        rstn = 0;
        txn = 0;
        #100 rstn = 1;
        #100 txn = 1;
    end

    always #10 clk = ~clk;
    
    user_axi_full_test instent(
        .INIT_AXI_TXN_0(txn),
        .M_AXI_ACLK_0(clk),
        .M_AXI_ARESETN_0(rstn)
    );

endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

仿真波形如下所示,可以看到读写突发长度都是16,写入读出的数据都是正常的。
在这里插入图片描述

在这里插入图片描述


  持续不定期更新完善中……


  原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、FPGA方面的学习笔记。


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/830486
推荐阅读
  

闽ICP备14008679号