【ZYNQ】PS和PL数据交互丨AXI总线(主机模块RTL代码实现)

文章目录

  • 一、PS-PL数据交互桥梁:AXI总线
    • 1.1 AXI总线和AXI4总线协议
    • 1.2 PS-PL数据传输的主要场景
      • 1.2.1 PL通过AXI_HP操作DDR3 Controller读写DDR3
      • 1.2.2 PS作主机使用GP接口传输数据
    • 1.3 AXI端口带宽理论
    • 1.4 AXI 总线的读写分离机制
    • 1.5 握手机制
    • 1.6 AXI_Lite总线
    • 1.7 AXI总线的读写过程
      • 1.7.1 写过程
      • 1.7.2 读过程
  • 二、从零开始构建AXI接口
    • 2.1 获取Xilinx的官方AXI4代码
    • 2.2 代码编写及仿真
      • 2.2.1 `AxSIZE`和`AxLEN`的赋值
      • 2.2.2 AXI主机接口时序
      • 2.2.3 将RTL模块添加到Block Design中
      • 2.2.4 仿真验证


一、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

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

	assign M_AXI_AWSIZE = clogb2((C_MAX_DATA_WIDTH/8)-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;

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

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

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

在这里插入图片描述


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


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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/554808.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【软考】设计模式之命令模式

目录 1. 说明2. 应用场景3. 结构图4. 构成5. 优缺点5.1 优点5.2 缺点 6. 适用性7.java示例 1. 说明 1.命令模式&#xff08;Command Pattern&#xff09;是一种数据驱动的设计模式。2.属于行为型模式。3.请求以命令的形式被封装在对象中&#xff0c;并传递给调用对象。4.调用对…

制作直通网线和交叉网线

制作直通网线和交叉网线 1. 网络直通线2. 网络交叉线References 双绞线的连接方法有两种&#xff1a;直通连接和交叉连接 。 直通连接是将双绞线的两端分别都依次按白橙、橙、白绿、蓝、白蓝、绿、白棕、棕色的顺序 (国际 EIA/TIA 568B 标准) 压入 RJ45 水晶头内。这种方法制作…

剧本杀小程序:线上剧本杀成为行业必然趋势

剧本杀作为一个社交娱乐游戏方式&#xff0c;受到了年轻人的喜爱。剧本杀是一个新型的游戏方式&#xff0c;能够带大众带来新鲜感和刺激感&#xff0c;让玩家通过角色扮演进行游戏体验&#xff1b;并且剧本杀还具有较强的社交性&#xff0c;在当下快节奏生活下&#xff0c;以游…

【AI】在Windows10下部署本地LLM RAG服务

【背景】 上一篇介绍了如何用Ubuntu命令行部署ollama LLM+RAG服务。部署后等于拥有了基于内网的AI Saas服务,其它内网用户可以通过默认的网址访问Playground对AI进行问答。 【概念】 RAG:通过词向量技术,将文件内容向量化后,通过语言模型以自然交流的形式得到文本相关的…

MySQL表级锁——技术深度+1

引言 本文是对MySQL表级锁的学习&#xff0c;MySQL一直停留在会用的阶段&#xff0c;需要弄清楚锁和事务的原理并DEBUG查看。 PS:本文涉及到的表结构均可从https://github.com/WeiXiao-Hyy/blog中获取&#xff0c;欢迎Star&#xff01; MySQL表级锁 MySQL中表级锁主要有表锁…

【CAD建模号】学习笔记(三):图形绘制区1

图形绘制区介绍 CAD建模号的图形绘制区可以绘制我们所需要的各种3D模型&#xff0c;绘制的图形即为模型对象&#xff0c;包括线、面、体等。 1. 二维图形绘制组 二维图形是建模的基础&#xff0c;大多数复杂的模型都是基于二维图形制作出来的&#xff0c;掌握二维图形的绘制等…

png静图转换gif动图如何操作?轻松一键快速转换gif动图

想要把多张Png格式图片转换成gif格式动图时要怎么操作&#xff1f;图片常见的有静图和动图&#xff0c;而jpg、png、gif等是最常见的图片格式。想要把png格式图片转换成gif动画还不想下载任何软件的时候就可以使用gif制作工具。不需要下载软件在线就能操作。能够轻轻松松就能快…

uniapp开发之【上传图片上传文件】的功能

一、上传图片功能&#xff0b;图片回显点击图片预览&#xff1a; 是通过uview框架的u-upload进行开发的&#xff0c;先导入uview&#xff01; <template><view class""><!-- 按钮 --><view class"listBtn" click"uploadDesign()…

透过内核收包流程理解DPDK

前言 网络通信作为互联网的底座&#xff0c;其网络服务质量直接影响着用户的上网体验。如微信这类级别的应用&#xff0c;拥有上亿级别的日活&#xff0c;是典型的高并发的场景&#xff0c;简单的堆硬件无法有效的解决该类问题&#xff0c;提高单台服务器的性能成为问题的焦点…

百度文心一言与谷歌Gemini的对比

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 本文从多角度将百度文心一言与谷歌Gemini进行对比。因为不同评测基准的侧重点和难度可能有所不同&#xff0c;所以本文涉及到的评测结果仅供参考。Gemini和文心一言都是非常…

文件 IO

IO 的概念 I&#xff1a;Input 输入 O&#xff1a;Output 输出 输入和输出的规定 人为规定&#xff1a; 以CPU为视角&#xff0c;数据远离 CPU 的是输出&#xff0c;数据朝着 CPU 过来的是输入 例子&#xff1a; 1.在电脑上&#xff0c;通过网络下载文件 > 数据通过网卡…

IDM的实用功能介绍+下载地址

下载地址 &#xff1a; 下载到idm 互联网下载管理器&#xff08;IDM&#xff09;实用功能概述 1. 多线程下载 IDM使用多线程技术&#xff0c;将文件分割成多个部分同时下载&#xff0c;显著提高下载速度。 2. 计划任务 用户可以设定下载任务的开始时间&#xff0c;甚至在特…

如何解决msvcp140.dll文件丢失的问题?有效修复msvcp140.dll的方法分析

在使用Windows操作系统时&#xff0c;经常会遇到一些烦人的问题&#xff0c;其中&#xff0c;缺少dll文件是比较常见的情况之一。而其中&#xff0c;缺少msvcp140.dll文件是常见的一种情况。今天&#xff0c;我们将重点介绍如何解决msvcp140.dll文件丢失的问题&#xff0c;并向…

Docker 磁盘占用过多问题处理过程记录

一、问题描述 突然发现服务器磁盘使用超过95%了&#xff08;截图时2.1 和 2.2 已经执行过了&#xff09; 二、问题分析与解决 2.1&#xff0c;docker 无用镜像占用磁盘 # 使用 docker images 查看服务的镜像 docker images# 可以手动删除一些很大不用的 docker rmi ***## 也…

javaWeb项目-校园交友网站功能介绍

项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 1、Java语言 Java语…

怎么给一个字典进行按值或key来排序?

字典是具有指定数字或键的特定数据集或组。在 Python 以外的编程语言中&#xff0c;它们也被称为哈希映射或关联数组。 一般来说&#xff0c;它是键值对的形式&#xff0c;就像现实世界的字典一样。 要创建字典&#xff0c;请从左括号开始&#xff0c;添加键并键入一个冒号。…

GEE错误——Can‘t encode object: function()

错误 Image (Error) Cant encode object: function(){var d=Da.apply(0,arguments).map(function(f){return c.zp(f)}),e=a.hasOwnProperty("prototype")?c.zp(this):void 0;d=m5a(c,a,d,e);return c.qj(d)} Imagen Ms Reciente sin Pxeles 2720: Layer error: Ca…

财务管理驾驶舱就该按这个模板做!

今天我们来看一张财务管理驾驶舱&#xff0c;体验一下BI数据可视化分析报表的灵活自助分析效果&#xff01; 众所周知&#xff0c;驾驶舱报表的作用就是让企业运营管理者更清晰地了解、分析数据&#xff0c;发现数据中隐藏的问题或机会&#xff0c;从而针对性制定运营管理决策。…

富文本编辑器(wangEdit)+(jquery.wordexport)实现web版在线编辑导出

小插曲&#xff1a;最开始的方向是Html5的contenteditable"true"的文档可编辑属性。只能修改文档文字内容&#xff0c;不能修改样式&#xff0c;如修改字体&#xff0c;字号&#xff0c;颜色等。于是用了第一款&#xff08;quil&#xff09;富文本插件。只能说一般&a…

电表预付费管理系统:智能管理的新篇章

1.定义与概念 电表预付费管理系统是一种创新的电力消费模式&#xff0c;它颠覆了传统的后付费方式&#xff0c;实现了先付费后用电的智能化管理。用户需预先充值&#xff0c;电量消耗完后自动切断电源&#xff0c;确保了电费的及时回收&#xff0c;降低了电力公司的财务风险。…
最新文章