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

news2024/12/27 15:50:19

文章目录

  • 一、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.coloradmin.cn/o/1605074.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++类和对象第二弹(构造,析构和拷贝构造函数)

目录 前言 1. 类的6个默认成员函数 2. 构造函数 2.1 概念 2.2 特性 3. 析构函数 3.1 概念 3.2 特性 4. 拷贝构造函数 4.1 概念 4.2 特征 总结 前言 本文主要讲解类中构造函数、析构函数和拷贝构造函数。关于这三个类中默认成员函数的知识点很多&#xff0c;有许多…

【C++20】学习笔记:在事件驱动使用携程

2021年文章:比物理线程都好用的C++20的协程,在事件驱动代码中的应用解析 大神给出了可以用的例子:例子代码执行 源码 //https://bbs.huaweicloud.com/blogs/266537 #include <iostream> #include <vector>//这只是一个极简的模型示例,真实的代码要远比它复杂得…

基于工程车辆/物流车辆/消防车辆远程通信的车队管理解决方案

交通运输对全球经济至关重要&#xff0c;特别是长途卡车在现今的供应链中发挥着重要作用。目前&#xff0c;货运物流面临许多挑战&#xff0c;包括不断上升的燃料价格和排放污染等问题。由于重型卡车的尺寸和载重量大&#xff0c;这意味着它们产生更多的二氧化碳排放足迹。在国…

【CDN产品测评-笔记】探索云服务中的安全和性能增强功能

【CDN产品测评-笔记】探索云服务中的安全和性能增强功能 写在最前面1. DNS管理&#xff1a;核心的域名服务功能2. DDoS防护&#xff1a;构筑坚不可摧的防线3. Web防护&#xff1a;智能化的网络攻击防御4. BOT管理&#xff1a;精准识别与流量控制5. 加速服务&#xff1a;提升全球…

航芯通用MCU技术常见问题 | F4专题

日常工作中&#xff0c;我们的销售或技术工程师经常会收到来自用户的问题&#xff0c;其中一些问题是比较常见的&#xff0c;所以为满足日常用户对航芯产品使用及服务的了解&#xff0c;航芯特此推出“通用MCU技术常见问题”专题&#xff0c;分为F0专题及F4专题&#xff0c;欢迎…

Ubuntu 22.04 配置VirtualBox安装Windows 10虚拟机

Ubuntu 22.04 配置VirtualBox安装Windows 10虚拟机 文章目录 Ubuntu 22.04 配置VirtualBox安装Windows 10虚拟机1.安装virtualbox2.下载Window.iso文件并载入3.问题解决3.1 Kernel driver not installed (rc-1908)3.2 VT-x is disabled in the BIOS for all CPU modes 4.安装Wi…

取模学习之Image2Lcd

使用软件Image2Lcd V0.4 1.&#xff1a;打开图片&#xff0c;图片格式可选如下图&#xff0c;本文使用的.jpg格式 转换后数组例子&#xff08;数组头数据占前8字节&#xff09;&#xff1a; 2.&#xff1a;扫描模式 由第1个字节低四位配置 &#xff08;1&#xff09;水平扫描 …

Linux 1.文件编程(dup、dup2)

重定向 重定向是什么&#xff1f;dupdup2 重定向是什么&#xff1f; 进程在最开始运行的时候&#xff0c;首先打开了三个文件&#xff0c;分别是标准输入流、标准输出流、标准错误输出流。证明的时候我是把标准输出留给关闭了&#xff0c;然后紧接着创建的文件就会占用已关闭的…

音频调试(2)

前言&#xff1a; 大家好&#xff0c;今天继续分享记录一下最近的音频调试心得&#xff01;同时这个过程中&#xff0c;也有朋友过来交流音频的问题&#xff0c;通过交流&#xff0c;也是学习到了新东西&#xff01; 视频和音频复合推流&#xff1a; 在上一篇文章里面有提到fdk…

Python100个库分享第22个—xlwings的写入与读取 (办公篇)

目录 专栏导读库的介绍库的安装基础用法1&#xff1a;打开并读取 Excel 文件基础用法2&#xff1a;读取某一行 &#xff08;注意点&#xff1a;expand(‘right’)&#xff09;基础用法3&#xff1a;读取某一列 &#xff08;注意点&#xff1a;expand(‘down’)&#xff09;基础…

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

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

Compose 基础组件

文章目录 Compose 基础组件Modifier 修饰符Scaffold 脚手架 Compose 基础组件 Modifier 修饰符 在传统视图体系中&#xff0c;使用XML文件描述组件的样式&#xff0c;而在Compose中使用Modifier&#xff0c;每个基础的Composable组件都有一个modifier参数&#xff0c;通过Mod…

ObjectMapper解析JSON数据

ObjectMapper的作用 1.背景&#xff1a; 当我们调用API的时候捕获的数据&#xff0c;往往需要结合文档所定义的类进行转换&#xff0c;也就是Java对象与JSON 字符串之间的转换 2.作用&#xff1a; ObjectMapper 是 Jackson 库中的一个关键类&#xff0c;它的作用是将 JSON 数据…

时序分解 | Matlab实现WOA-VMD鲸鱼算法WOA优化VMD变分模态分解

时序分解 | Matlab实现WOA-VMD鲸鱼算法WOA优化VMD变分模态分解 目录 时序分解 | Matlab实现WOA-VMD鲸鱼算法WOA优化VMD变分模态分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现WOA-VMD鲸鱼算法WOA优化VMD变分模态分解&#xff08;完整源码和数据) 1.利用鲸…

WT3000T8-TTS语音合成芯片及应用场景介绍

一、TTS语音合成芯片简述 TTS语音合成芯片是一种能够将文本信息转化为自然语音的专用芯片。它通过内置的语音合成算法和音频处理单元&#xff0c;实现了文本到语音的高效转换&#xff0c;为各种智能设备提供了丰富的语音交互功能。 二、TTS语音合成开发背景 TTS&#xff08;文字…

Linux sudo suid提权练习

题目比较简单&#xff0c;可以利用sudo和多种suid程序提权&#xff0c;做个记录 进入靶场题目环境 获得节点信息 远程连接上 执行命令id&#xff0c;发现只是admin普通账户 sudo提权 发现存在 /usr/bin/vim, /usr/bin/bash, /usr/bin/more, /usr/bin/less, /usr/bin/nano, /…

C——文件操作

1.前言 为什么要使用文件呢&#xff1f; 文件是储存在电脑的磁盘中的&#xff0c;如果没有文件&#xff0c;我们写程序的数据就会存储在电脑的内存中&#xff0c;程序退出&#xff0c;操作系统就会收回内存&#xff0c;数据就丢失了等再次运行程序的时候&#xff0c;是看不到…

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

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

创新与乐趣的融合 —— 探索我们独家录音变音芯片在学舌玩具领域的应用

一&#xff1a;概述 学舌玩具&#xff0c;又称作复读玩具或模仿玩具&#xff0c;是一类设计用来录制人声并重复播放的互动式玩具。这类玩具以其能够模仿人类语音的特性而受到小朋友和宠物主人的喜爱。这些玩具通常具有以下特点和功能&#xff1a; 1. 录音和播放功能&#xff…

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…