文章目录
- 一、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端的作用命名),分为以下三种:
- AXI_GP:通用AXI接口,位宽为32位,适用于PS和PL端进行低速通信;
- AXI_HP:高性能AXI接口,位宽为32位或者64位,适用于PS和PL端进行高速通信,它是拥有读写FIFO的高性能端口,在有些地方被称作AXI_FIFO或AFI;
- AXI_ACP:加速器一致AXI接口,位宽为64位,适用于PS和PL进行高速通信,可以用作cache(高速数据缓冲)一致性回话(暂时不懂cache一致性回话是什么)。
AXI端口使用AXI4总线协议。AXI4协议是ARM公司提出的一种高性能,高带宽,低延迟的片内总线。它主要描述了主设备到从设备之间的数据传输方式。为了适用不同的数据传输情况,提高性能,AXI4协议有以下三种分类:
- AXI_Lite:不支持突发传输。发送一个数据的也必须发送一个地址,常用于数据量最小的传输,可以认为是一种轻量化的AXI_Full,ZYNQ的GP接口使用这种协议;
- AXI_Full:支持突发传输,突发长度为1至256,ZYNQ的HP和ACP接口使用这种协议;
- 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 ,简写为
- 写通道
- 写地址通道 Address,简写为
AW
- 写数据通道 Data,简写为
W
- 写响应 Write Response,简写为
B
- 写地址通道 Address,简写为
可以看到,读通道不存在一个单独的“读响应通道”,但是并不表示读通道没有读响应机制。读通道的读响应信号包含在了读数据通道中。
1.5 握手机制
握手信号是AXI总线数据传输中的一个重点,甚至是AXI总线的核心。发送端发出一个请求,如果接收端准备好,就向发送端返回一个“准备好了”信号;如果接收端没有准备好,发送端的请求就一直保持,直到收到一个准备好信号才开始下一步动作。从波形上理解,可以认为只有当请求信号和准备好信号同时有效(同时为高电平)时数据才正确传输过去。
一般而言,我们将请求信号称为VALID
,也可以称为命令有效;将准备好信号成为READY
。
主机如何写数据到从机?用写数据通道的部分信号为例说明:
-
WVALID
:从机通过这个信号知道主机发起了请求。当从机在时钟的上升沿检测到VALID
信号拉高,就说明接到了主机的请求; -
WREADY
:主机通过这个信号知道从机已准备好接收。当主机在时钟上升沿检测到READY
信号拉高,就说明从机准备好了; -
WDATA
:在写数据过程中,有效数据通常和VALID
信号同时发出。
VALID
和READY
信号可以同时到达,也可以先后到达。只有这两个信号同时拉高后,握手机制才正式成立,读时序也就完成了。握手机制不单单存在于读写时序中,也存在于响应时序中,换言之,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协议中不同通道的信号线如下图所示:
可以看到,这些读写通道的组成都是有规律的。除了握手机制中提到的请求VALID
,READY
和数据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.7.2 读过程
读过程和写过程同理,但是上面的时序图是相对固定的。我们最常用的模式是:
- 主机发送读地址;
- 从机接收读地址;
- 从机发送读数据和读响应。
二、从零开始构建AXI接口
虽然Xilinx官方为AXI总线提供了很多基于IP核的设计,应用这些设计可以大大简化设计流程,但是这种基于IP核的设计是以消耗了FPGA设计的灵活性为代价的,所以还是有必要自己手撕AXI代码。
AXI实现写数据的步骤:
- 写首地址,(同时)突发传输数据;
- 控制LAST信号告知从机最后一个数据什么时候传输结束;
- 等待从机发送的写响应信号,判断这次写数据是否成功。
AXI实现读数据的步骤:
- 写首地址;
- 等待从机传输数据和读响应信号,当
VALID
和READY
信号都有效时接收数据; - 接收从机发送的LAST信号。
2.1 获取Xilinx的官方AXI4代码
在写代码之前可以参考Xilinx官方的代码,用下面的方法获得官方给出的AXI4接口代码,在工具栏Tools下的Create and Package New IP,选择创建一个AXI4接口。选择端口信息后创建IP核并选择编辑IP,就可以获得Xilinx官方的AXI接口代码。
2.2 代码编写及仿真
2.2.1 AxSIZE
和AxLEN
的赋值
下面的函数描述了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方面的学习笔记。