基于Verilog HDL的axi-lite主机模块
前文对axi_lite接口协议的各个信号做了详细讲解,本文通过Verilog Hdl编写一个通用接口转axi_lite接口协议的模块。
1、生成xilinx官方提供axi源码
Xilinx其实给用户提供了axi相关模块,获取方式如下,首先打开vivado软件,然后点击Manage IP,之后选择New IP Location,如下图所示。
直接点击下一步。
之后需要选择器件型号,然后确定工程路径,之后点击Finish。
进入工程后,在Tools选项卡下点击Create and Package New IP,如下图所示。
进入向导界面后直接点击下一步。
然后选择创建AXI外围设备,如下图所示。
根据需求修改一下IP名,然后点击下一步,如下图所示。
然后选择生成IP的协议类型,此处选择lite协议,然后选择主机,数据位宽只能固定为32位。之后点击下一步,如下图所示。
通过修改上图设置,可以生成axi_lite、axi_full、axi_stream协议的主机和从机模块,后文会使用其余模块,就不再详细讲解。
最后需要选择Edit IP,才会生成IP的源文件,如下图所示。
之后会生成一个工程,生成的工程会包含axi_lite_master_v1_0_M00_AXI的一个模块,这个模块就是生成的axi_lite_master模块。
本文只是告诉读者vivado能够生成一个axi协议的相关模块,但是该模块的代码不够简洁,其实使用起来没那么方便。
后文不会对生成的模块进行解读和使用,后文会根据前文所讲协议内容自己编写一个axi_lite_master模块,而该模块的axi_lite端口信号与生成的模块保持一致。
2、axi_lite_master模块设计
用户端口肯定会设计的比较简单,只包括读写使能、地址、数据信号,由于axi_lite接口需要和从机进行应答,那么用户端口写入的数据和地址就不能立即写入,需要等待。
解决这个问题的方法有两种思路,第一种是在写和读部分都给用户输出一个模块忙闲指示信号,只有当模块对应功能空闲时,才能进行下次操作。
第二种就是在写数据、写地址、读地址端口处加一个FIFO,用来存储用户的数据和指令,当FIFO不为空,就读出FIFO中的数据通过axi_lite发送给从机。这种方式的优点是用户不需要等待模块空闲,直接将需要发送的指令、数据输入即可,简化逻辑,但是FIFO会消耗更多资源。
本文采用第二种设计方式,整个模块通过检测三个FIFO是否为空,向从机发起读写时序。
端口信号如下所示,首先是axi_lite的时钟、复位和五个通道的信号,然后是用户的端口信号,用户的写使能信号与写数据、写地址信号对齐,读使能与读地址信号对齐。用户的地址信号位宽默认为8位,而axi_lite的地址和数据信号一般均为32位,可以根据实际情况进行拼接。
module axi_lite_master #(
parameter UADDR_W = 8 ,//用户地址位宽;
parameter UDATA_W = 32 ,//用户数据位宽;
parameter ADATA_W = 32 //AXI_LITE的地址和数据位宽;
)(
input M_AXI_ACLK ,//AXI接口时钟信号;
input M_AXI_ARESETN ,//AXI接口复位信号,低电平有效;
//AXI写地址通道信号;
output reg [ADATA_W - 1 : 0] M_AXI_AWADDR ,//AXI地址信号;
output [2 : 0] M_AXI_AWPROT ,//AXI地址端口信号;
output reg M_AXI_AWVALID ,//AIX地址数据有效指示信号,高电平有效;
input M_AXI_AWREADY ,//AXI地址数据应答信号。
//AXI写数据通道信号;
output reg [ADATA_W - 1 : 0] M_AXI_WDATA ,//AXI写数据信号;
output [ADATA_W / 8 - 1 : 0] M_AXI_WSTRB ,//AXI写数据掩码信号,低电平有效;
output reg M_AXI_WVALID ,//AXI写入数据有效指示信号,高电平有效;
input M_AXI_WREADY ,//AXI写入数据应答信号,高电平有效;
//AXI写应答通道信号;
input [1 : 0] M_AXI_BRESP ,//AXI写应答信号,为0时表示写入正确;
input M_AXI_BVALID ,//AXI写应答有效指示信号,高电平有效;
output reg M_AXI_BREADY ,//AXI写应答响应信号,高电平有效;
//AXI读地址通道信号;
output reg [ADATA_W - 1 : 0] M_AXI_ARADDR ,//AXI读地址信号;
output [2 : 0] M_AXI_ARPROT ,//AXI读地址端口信号;
output reg M_AXI_ARVALID ,//AXI读地址有效指示信号,高电平有效;
input M_AXI_ARREADY ,//AXI读地址应答信号,高电平有效;
//AXI读数据通道信号;
input [ADATA_W - 1 : 0] M_AXI_RDATA ,//AXI读数据信号;
input [1 : 0] M_AXI_RRESP ,//AXI读数据状态应答信号,为0表示读数据正确;
input M_AXI_RVALID ,//读数据有效指示信号,高电平有效;
output reg M_AXI_RREADY ,//主机接收从机读出数据的应答信号,高电平有效;
//用户写数据信号;
input wr_en ,//写使能信号,高电平有效;
input [UADDR_W - 1 : 0] waddr ,//写地址信号,与写使能对齐;
input [UDATA_W - 1 : 0] wdata ,//写数据信号,与写使能对齐;
//用户读数据信号;
input rd_en ,//读使能信号,高电平有效;
input [UADDR_W - 1 : 0] raddr ,//读地址信号,与读使能对齐;
output reg [UDATA_W - 1 : 0] rdata ,//读数据信号;
output reg rdata_vld ,//读数据有效指示信号,高电平有效;
output reg error //错误指示信号,当写入错误或读出错误时为1,正常为0;
);
axi_lite的读、写地址端口类型信号一般为0,且写入数据不会加掩码,因此这几个信号直接连接对应电平输出即可。
assign M_AXI_AWPROT = 3'd0 ;//写地址端口信号始终为0;
assign M_AXI_ARPROT = 3'd0 ;//读地址端口信号始终为0;
assign M_AXI_WSTRB = {{ADATA_W/8}{1'b1}};//写数据掩码信号,本模块写入数据全部有效,因此全为高电平;
assign wfifo_empty = wa_fifo_empty & wa_fifo_empty;//由于写地址和写数据可以同时进行,所以将两个写FIFO空指示信号合并;
由于用户端口的写地址信号、写数据信号都与写使能对齐,且前文分析axi_lite的写数据和写地址信号可以对齐(隐含意思:两个FIFO可以一起读出数据)。因此可以把两个FIFO的空标志信号整理为一个空标志信号。
下面是写地址FIFO、读地址FIFO、写数据FIFO的例化,地址FIFO的数据位宽为8,数据FIFO的数据位宽为32,FIFO深度均为32。FIFO输出数据会滞后读使能一个时钟。
//用于存储写地址的FIFO;
addr_fifo_8x32 u_waddr_fifo (
.clk ( M_AXI_ACLK ),//input wire clk;
.din ( waddr ),//input wire [7 : 0] din;
.wr_en ( wr_en ),//input wire wr_en;
.rd_en ( wfifo_rd_en ),//input wire rd_en;
.dout ( wa_fifo_dout ),//output wire [7 : 0] dout;
.full ( wa_fifo_full ),//output wire full;
.empty ( wa_fifo_empty ) //output wire empty;
);
//用于存储读地址的FIFO;
addr_fifo_8x32 u_raddr_fifo (
.clk ( M_AXI_ACLK ),//input wire clk;
.din ( raddr ),//input wire [7 : 0] din;
.wr_en ( rd_en ),//input wire wr_en;
.rd_en ( ra_fifo_rd_en ),//input wire rd_en;
.dout ( ra_fifo_dout ),//output wire [7 : 0] dout;
.full ( ra_fifo_full ),//output wire full;
.empty ( ra_fifo_empty ) //output wire empty;
);
//用于存储写数据的FIFO;
data_fifo_32x32 u_wdata_fifo (
.clk ( M_AXI_ACLK ),//input wire clk;
.din ( wdata ),//input wire [31 : 0] din;
.wr_en ( wr_en ),//input wire wr_en;
.rd_en ( wfifo_rd_en ),//input wire rd_en;
.dout ( wd_fifo_dout ),//output wire [31 : 0] dout;
.full ( wd_fifo_full ),//output wire full;
.empty ( wd_fifo_empty ) //output wire empty;
);
写地址FIFO和写数据FIFO的读使能采用一个信号,即同时输出信号。
然后下面是一个写标志信号wr_flag,该信号为高电平表示正在进行axi_lite写操作。当写地址、写数据FIFO均有数据且不处于写状态时拉高,当写应答通道响应时拉低。
//写标志信号,初始值为0,当写入数据时拉高,一次写入完成后拉低;
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
wr_flag <= 1'b0;
end//当写应答通道的有效信号和应答信号均为高时,表示一次写数据完成;
else if(M_AXI_BVALID & M_AXI_BREADY)begin
wr_flag <= 1'b0;
end//当不处于写数据状态且写FIFO中有数据时拉高;
else if((~wa_fifo_empty) && (~wr_flag))begin
wr_flag <= 1'b1;
end
end
在wr_flag信号拉高时两个FIFO的读使能信号有效,将读使能信号打一拍,作为两个FIFO输出数据的有效指示信号。
//写地址FIFO读使能信号;
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
wfifo_rd_en <= 1'b0;
end
else begin//当FIFO没有空且写标志信号开始时拉高;
wfifo_rd_en <= ((~wfifo_empty) && (~wr_flag));
end
end
//将写地址FIFO读使能信号延迟一个时钟作为读出数据有效指示信号;
always@(posedge M_AXI_ACLK)begin
wfifo_rdata_vld <= wfifo_rd_en;
end
当写地址FIFO输出数据有效时,把写FIFO输出数据赋值给axi_lite写地址通道的写地址信号,由于位宽可能不同,需要进行拼接。
同时把写数据FIFO输出数据赋值给axi_lite写数据通道的写数据信号,用户读写数据位宽可能与axi_lite写数据位宽不同,也可能需要拼接。
//生成AXI写地址信号,初始值为0,当写地址FIFO读数据有效时,输出FIFO读出的数据,其余时间保持不变;
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
M_AXI_AWADDR <= {{ADATA_W}{1'b0}};
M_AXI_WDATA <= {{ADATA_W}{1'b0}};
end
else if(wfifo_rdata_vld)begin//将写地址FIFO输出数据赋值给AXI地址信号;
M_AXI_AWADDR <= {{{ADATA_W - UADDR_W}{1'b0}},wa_fifo_dout[UADDR_W - 1 : 0]};
M_AXI_WDATA <= {{{ADATA_W - UDATA_W}{1'b0}},wd_fifo_dout[UDATA_W - 1 : 0]};
end
end
之后需要生成axi_lite写地址、写数据通道的有效指示信号,当FIFO读出数据后拉高,各自应答信号为高电平时拉低。
//生成AXI写地址有效指示信号,与写地址信号对齐;
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
M_AXI_AWVALID <= 1'b0;
end
else if(M_AXI_AWREADY)begin//当写地址信号被应答后拉低;
M_AXI_AWVALID <= 1'b0;
end
else if(wfifo_rdata_vld)begin//当写地址FIFO输出有效数据后拉高;
M_AXI_AWVALID <= 1'b1;
end
end
//生成AXI写数据有效指示信号,与写地址信号对齐;
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
M_AXI_WVALID <= 1'b0;
end
else if(M_AXI_WREADY)begin//当写数据信号被应答后拉低;
M_AXI_WVALID <= 1'b0;
end
else if(wfifo_rdata_vld)begin//当FIFO输出有效数据后拉高;
M_AXI_WVALID <= 1'b1;
end
end
最后就是写应答通道的主机应答信号,该信号可以在写入数据完成时拉高,也可以在写入数据有效时就拉高,没有特别的要求。但必须在从机应答有效后拉低。
//生成应答通道的应答数据,当写入数据后拉高,等待从机有效指示信号拉高后拉低。
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
M_AXI_BREADY <= 1'b0;
end
else if(M_AXI_BVALID)begin//从机应答信号拉高;
M_AXI_BREADY <= 1'b0;
end
else if(M_AXI_WREADY & M_AXI_WVALID)begin//写入数据完成;
M_AXI_BREADY <= 1'b1;
end
end
下面是与读通道相关的信号,读地址通道与写地址通道类似。首先依旧需要一个读状态的标志信号rd_flag,但是为了避免同时读写同一地址数据,此处需要在写FIFO为空的时候,才会拉高读状态。
//读标志信号;
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
rd_flag <= 1'b0;
end
else if(M_AXI_RREADY & M_AXI_RREADY)begin//当读数据通道的应答信号和输出数据有效信号同时为高时,表示读取成功;
rd_flag <= 1'b0;
end
else if((~ra_fifo_empty) && (~rd_flag) && wfifo_empty && (~wr_flag))begin//当读地址FIFO不为空且不处于读数据状态时拉高;
rd_flag <= 1'b1;
end
end
//读地址FIFO读使能信号,当需要同时执行读写操作时,优先执行写操作,防止同时读写同一地址引发错误。
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
ra_fifo_rd_en <= 1'b0;
end
else begin//当FIFO没有空且读标志信号开始时拉高;
ra_fifo_rd_en <= ((~ra_fifo_empty) && (~rd_flag) && wfifo_empty && (~wr_flag));
end
end
//将读地址FIFO读使能信号延迟一个时钟作为读出数据有效指示信号;
always@(posedge M_AXI_ACLK)begin
ra_fifo_rdata_vld <= ra_fifo_rd_en;
end
因此注意,用户写使能不能一直拉高,会造成无法进行读操作。
读地址FIFO输出数据使能、axi_lite读地址、读地址有效指示信号与前文写地址通道信号类似,对应代码如下,不再赘述。
//生成AXI读地址信号,初始值为0,当读地址FIFO读数据有效时,输出FIFO读出的数据,其余时间保持不变;
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
M_AXI_ARADDR <= {{ADATA_W}{1'b0}};
end
else if(ra_fifo_rdata_vld)begin//将读地址FIFO输出数据赋值给AXI地址信号;
M_AXI_ARADDR <= {{{ADATA_W - UADDR_W}{1'b0}},ra_fifo_dout[UADDR_W - 1 : 0]};
end
end
//生成AXI读地址有效指示信号,与读地址信号对齐;
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
M_AXI_ARVALID <= 1'b0;
end
else if(M_AXI_ARREADY)begin//当读地址信号被应答后拉低;
M_AXI_ARVALID <= 1'b0;
end
else if(ra_fifo_rdata_vld)begin//当读地址FIFO输出有效数据后拉高;
M_AXI_ARVALID <= 1'b1;
end
end
//生成读数据通道的应答信号;
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
M_AXI_RREADY <= 1'b0;
end
else if(M_AXI_RVALID)begin//当读数据有效时拉低;
M_AXI_RREADY <= 1'b0;
end
else if(M_AXI_ARVALID & M_AXI_ARREADY)begin//当从机应答读地址信号时拉高;
M_AXI_RREADY <= 1'b1;
end
end
然后将axi_lite读出的数据和数据有效指示信号输出,对应代码如下所示。
//生成用户读数据及读数据有效信号;
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
rdata <= {{UDATA_W}{1'b0}};
rdata_vld <= 1'b0;
end
else begin//将AXI读出数据输出,并且把数据有效信号拉高一个时钟周期;
rdata <= M_AXI_RVALID ? M_AXI_RDATA[UDATA_W-1 : 0] : rdata;
rdata_vld <= M_AXI_RVALID;
end
end
最后会给用户生成一个错误指示信号,根据读数据和写应答通道从机反馈的状态信号是否为0,判断写数据和读数据是否正确,对应代码如下。
//生成错误指示信号,初始值为0;
always@(posedge M_AXI_ACLK)begin
if(M_AXI_ARESETN==1'b0)begin//初始值为0;
error <= 1'b0;
end//每次读写使能信号有效时清零;
else if(wfifo_rd_en | ra_fifo_rd_en)begin
error <= 1'b0;
end//当写入数据失败或者读出数据错误时拉高;
else if((M_AXI_ARVALID & M_AXI_ARREADY & (M_AXI_RRESP!=2'd0)) | (M_AXI_BREADY & M_AXI_BREADY & (M_AXI_BRESP != 2'd0)))begin
error <= 1'b1;
end
end
整个模块的设计就完成了,将用户端口时序转换为axi_lite接口协议。
3、axi_lite_master仿真
仿真有两种方式,第一种就是使用一个axi_lite从机模块与该模块一起仿真。第二种是使用一个axi_lite的IP作为从机进行仿真,使用IP需要阅读该IP手册,了解其内部寄存器,相对比较麻烦。
因此可以使用本文前半部分的方式,生成一个axi_lite的从机模块,作为仿真模型,生成从机的步骤省略,参考图9生成主机模块步骤即可。
编写的TestBench如下所示:
`timescale 1 ns/1 ns
module test();
parameter CYCLE = 10 ;//系统时钟周期,单位ns,默认10ns;
parameter RST_TIME = 10 ;//系统复位持续时间,默认10个系统时钟周期;
parameter UADDR_W = 8 ;//用户地址位宽;
parameter UDATA_W = 32 ;//用户数据位宽;
parameter ADATA_W = 32 ;//AXI_LITE的地址和数据位宽;
//时钟复位信号;
reg clk ;//系统时钟,默认100MHz;
reg rst_n ;//系统复位,默认低电平有效;
//用户写数据信号;
reg wr_en ;
reg [UADDR_W - 1 : 0] waddr ;
reg [UDATA_W - 1 : 0] wdata ;
//用户读数据信号;
reg rd_en ;
reg [UADDR_W - 1 : 0] raddr ;
wire [UDATA_W - 1 : 0] rdata ;
wire rdata_vld ;
wire error ;
//AXI写地址通道信号;
wire [ADATA_W - 1 : 0] M_AXI_AWADDR ;
wire [2 : 0] M_AXI_AWPROT ;
wire M_AXI_AWVALID ;
wire M_AXI_AWREADY ;
//AXI写数据通道信号;
wire [ADATA_W - 1 : 0] M_AXI_WDATA ;
wire [ADATA_W / 8 - 1 : 0] M_AXI_WSTRB ;
wire M_AXI_WVALID ;
wire M_AXI_WREADY ;
//AXI写应答通道信号;
wire [1 : 0] M_AXI_BRESP ;
wire M_AXI_BVALID ;
wire M_AXI_BREADY ;
//AXI读地址通道信号;
wire [ADATA_W - 1 : 0] M_AXI_ARADDR ;
wire [2 : 0] M_AXI_ARPROT ;
wire M_AXI_ARVALID ;
wire M_AXI_ARREADY ;
//AXI读数据通道信号;
wire [ADATA_W - 1 : 0] M_AXI_RDATA ;
wire [1 : 0] M_AXI_RRESP ;
wire M_AXI_RVALID ;
wire M_AXI_RREADY ;
//例化主机模块
axi_lite_master #(
.UADDR_W ( UADDR_W ),
.UDATA_W ( UDATA_W ),
.ADATA_W ( ADATA_W )
)
u_axi_lite_master (
.M_AXI_ACLK ( clk ),
.M_AXI_ARESETN ( rst_n ),
//AXI写地址通道信号;
.M_AXI_AWREADY ( M_AXI_AWREADY ),
.M_AXI_AWADDR ( M_AXI_AWADDR ),
.M_AXI_AWPROT ( M_AXI_AWPROT ),
.M_AXI_AWVALID ( M_AXI_AWVALID ),
//AXI写数据通道信号;
.M_AXI_WDATA ( M_AXI_WDATA ),
.M_AXI_WSTRB ( M_AXI_WSTRB ),
.M_AXI_WVALID ( M_AXI_WVALID ),
.M_AXI_WREADY ( M_AXI_WREADY ),
//AXI写应答通道信号;
.M_AXI_BRESP ( M_AXI_BRESP ),
.M_AXI_BVALID ( M_AXI_BVALID ),
.M_AXI_BREADY ( M_AXI_BREADY ),
//AXI读地址通道信号;
.M_AXI_ARREADY ( M_AXI_ARREADY ),
.M_AXI_ARADDR ( M_AXI_ARADDR ),
.M_AXI_ARPROT ( M_AXI_ARPROT ),
.M_AXI_ARVALID ( M_AXI_ARVALID ),
//AXI读数据通道信号;
.M_AXI_RDATA ( M_AXI_RDATA ),
.M_AXI_RRESP ( M_AXI_RRESP ),
.M_AXI_RVALID ( M_AXI_RVALID ),
.M_AXI_RREADY ( M_AXI_RREADY ),
//用户读写信号;
.wr_en ( wr_en ),
.waddr ( waddr ),
.wdata ( wdata ),
.rd_en ( rd_en ),
.raddr ( raddr ),
.rdata ( rdata ),
.rdata_vld ( rdata_vld ),
.error ( error )
);
//例化AXI从机模块
axi_lite_slave #(
.C_S_AXI_DATA_WIDTH ( ADATA_W ),
.C_S_AXI_ADDR_WIDTH ( ADATA_W )
)
u_axi_lite_slave (
.S_AXI_ACLK ( clk ),
.S_AXI_ARESETN ( rst_n ),
//AXI写地址通道信号;
.S_AXI_AWREADY ( M_AXI_AWREADY ),
.S_AXI_AWADDR ( M_AXI_AWADDR ),
.S_AXI_AWPROT ( M_AXI_AWPROT ),
.S_AXI_AWVALID ( M_AXI_AWVALID ),
//AXI写数据通道信号;
.S_AXI_WDATA ( M_AXI_WDATA ),
.S_AXI_WSTRB ( M_AXI_WSTRB ),
.S_AXI_WVALID ( M_AXI_WVALID ),
.S_AXI_WREADY ( M_AXI_WREADY ),
//AXI写应答通道信号;
.S_AXI_BRESP ( M_AXI_BRESP ),
.S_AXI_BVALID ( M_AXI_BVALID ),
.S_AXI_BREADY ( M_AXI_BREADY ),
//AXI读地址通道信号;
.S_AXI_ARREADY ( M_AXI_ARREADY ),
.S_AXI_ARADDR ( M_AXI_ARADDR ),
.S_AXI_ARPROT ( M_AXI_ARPROT ),
.S_AXI_ARVALID ( M_AXI_ARVALID ),
//AXI读数据通道信号;
.S_AXI_RDATA ( M_AXI_RDATA ),
.S_AXI_RRESP ( M_AXI_RRESP ),
.S_AXI_RVALID ( M_AXI_RVALID ),
.S_AXI_RREADY ( M_AXI_RREADY )
);
//生成周期为CYCLE数值的系统时钟;
initial begin
clk = 0;
forever #(CYCLE/2) clk = ~clk;
end
//生成复位信号;
initial begin
rst_n = 1;
#2;
rst_n = 0;//开始时复位10个时钟;
#(RST_TIME*CYCLE);
rst_n = 1;
end
initial begin
wr_en <= 1'b0;
rd_en <= 1'b0;
waddr <= 0;
raddr <= 0;
wdata <= 0;
wait(rst_n);//等待复位信号为高电平后;
repeat(10)@(posedge clk);
wr_data(13,133);
repeat(10)@(posedge clk);
rd_data(13);
repeat(10)@(posedge clk);
fork//并行运行写数据和读数据两个函数;
wr_data(20,311);
rd_data(20);
join
repeat(15)@(posedge clk);
rd_data(20);
repeat(20)@(posedge clk);
$stop;
end
//用户写数据任务;
task wr_data(
input [UADDR_W - 1 : 0] addr ,
input [UDATA_W - 1 : 0] data
);
begin
wr_en <= 1'b0;
@(posedge clk);
wr_en <= 1'b1;
waddr <= addr;
wdata <= data;
@(posedge clk);
wr_en <= 1'b0;
@(posedge clk);
end
endtask
//用户读数据任务;
task rd_data(
input [UADDR_W - 1 : 0] addr
);
begin
rd_en <= 1'b0;
@(posedge clk);
rd_en <= 1'b1;
raddr <= addr;
@(posedge clk);
rd_en <= 1'b0;
@(posedge clk);
end
endtask
endmodule
运行仿真结果如下,当用户向地址13中写入133时,橙色信号是主机写地址通道输出的三个信号,天蓝色是主机写数据使出的三个信号,粉红色是从机输出该主机的写应答通道信号。
写地址有效信号和写数据有效指示信号同时拉高,在各自应答有效后拉低。而黄色信号是主机的应答从机的信号,此处在写入数据后拉高。
写时序仿真正确,下图是用户端口从地址13中读出数据。
白色信号表示读地址通道主机输出的三个信号,橙黄色表示读出从机的数据信号,而紫红色表示输出给用户的读数据。
最终从地址13读出的数据为133,与前文写入数据一致,证明读写时序均没有问题。
下图中用户端口同时读写从机的同一地址数据,根据模块的仲裁,首先会向地址为20的寄存器中写入数据311,然后在从地址20中读出数据,最后读出数据也为311。
上图中axi_lite的读写时序中间有段间隔,这是由于中间判断信号造成的。
关于该模块的仿真到此结束,通过前文的讲解,实现axi_lite协议其实难度不大,相对SDRAM这些接口,axi_lite算是很简单的协议。
由于FPGA内部一般不会使用从机模块,因此先跳过从机的实现,下文将讲解axi_full接口协议,会发现axi_full其实也比较简单,很多信号和模式FPGA并不会使用。
需要本文工程的可以在公众号后台回复“axi_lite_master工程”(不包括引号)。
如果对文章内容理解有疑惑或者对代码不理解,可以在评论区或者后台留言,看到后均会回复!
如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!您的支持是我更新的最大动力!将持续更新工程!