AXI4 写相关通道
在前面的AXI接口部分介绍了有关AXI接口的通道和时序。在这一篇博客实现一个AXI4的接口,用来向内存中写入数据。
在写地址通道,主要进行传输 AXI 的 master 向 slave 中写入数据时的地址。
在写数据通道,主要进行传输 AXI 的 master 向 slave 中写入的数据。
在写响应通道,主要进行传输 AXI 的 master 向 slave 中写入数据时的响应。
下图是AXI接口的相关通道的连接方式。
AXI接口写时序
时序设计
下面是我设计的一个AXI接口的写时序图,
AXI接口IP设计
在Vivado工具中,创建一个AXI接口的外设。选中AXI4-FULL接口的类型,就会创建出一个实例IP,对这个IP内部的代码进行修改,就可以实现我们所需要设计的功能,使用这种方式来创建IP,主要是为了来使用这个AXI-Full的接口定义。
在示例IP内部,将其实现的逻辑删除,然后实现自己的代码就OK了。
代码实现思路
在IP内部,例化一个FIFO,FIFO用于缓冲从外部接收到的数据,当FIFO中存在足够的数据时,启动向AXI总线上写数据。其中部分
代码如下,指示实现向AXI总线上写数据的代码(Verilog代码长长长)。
//==========================================================
//用户自定义输入输出端口
input wire pixel_clk ,//输入像素时钟
input wire vsync ,//场同步信号
input wire data_vld ,//数据有效信号
input wire [23:0] pixel_data ,//像素数据
//==========================================================
//==========================================================
//用户参数定义
parameter THRESHOLD = 128 -2 ;
parameter BURST_MAX = 256 - 1;//突发读写长度
parameter ADD_ADDR = 2048 ;//突发写数据字节地址增量
parameter FIRST_FRAME = (640 * 512) * 16/8 - ADD_ADDR ;
parameter SECOND_FRAME = (640 * 512) * 2 * 16/8 - ADD_ADDR ;
//==========================================================
reg [1:0] vsync_dd ;
reg sync_flag ;
reg work_on_flag ;
reg [2:0] state ;
reg wr_start ;
reg burst_start ;// 开始突发写数据
wire wr_en ;//write fifo data enable
wire [31:0] din ;//data into fifo
wire full ;//fifo full
wire empty ;//fifo empty
wire rd_en ;//read fifo data enable
wire [63:0] dout ;//data from fifo
wire [10:0] rd_data_count ;//当前FIFO读端口的数据个数
reg [8:0] cnt_burst ;
wire add_cnt_burst ;
wire end_cnt_burst ;
//----------------work_on_flag------------------
// 检测到init_txn_pluse上升沿后才能开始工作
always @(posedge M_AXI_ACLK)begin
if (M_AXI_ARESETN == 0 )begin
work_on_flag <= 1'b0;
end
else if (init_txn_pulse == 1'b1) begin
work_on_flag <= 1'b1;
end
end
//----------------vsync_dd------------------
// 对输入的vsync进行延拍,用于检测同步信号上升沿,以便获取一帧完整的图像
always @(posedge pixel_clk)begin
if (M_AXI_ARESETN == 0 )begin
vsync_dd <= 'd0;
end
else begin
vsync_dd <= {vsync_dd[0],vsync};
end
end
//----------------sync_flag------------------
// 当前已经同步完成,从现在开始采集,可以得到完整的图像
always @(posedge pixel_clk)begin
if (M_AXI_ARESETN == 0 )begin
sync_flag <= 1'b0;
end
else if (!vsync_dd[1] && vsync_dd[0]) begin
sync_flag <= 1'b1;
end
end
// 写入FIFO的数据,需要扩充
assign din = {8'd0,pixel_data};
// 写FIFO使能,当前系统正常工作,且数据有效
assign wr_en = sync_flag & data_vld & work_on_flag;
wr_fifo inst_fifo (
.wr_clk(pixel_clk), // input wire wr_clk
.rd_clk(M_AXI_ACLK), // input wire rd_clk
.din(din), // input wire [31 : 0] din
.wr_en(wr_en), // input wire wr_en
.rd_en(rd_en), // input wire rd_en
.dout(dout), // output wire [63 : 0] dout
.full(full), // output wire full
.empty(empty), // output wire empty
.rd_data_count(rd_data_count) // output wire [9 : 0] rd_data_count
);
//----------------wr_start------------------
// 当前总线处于空闲状态,且FIFO中有足够的数据时,开始向AXI总线上写数据
always @(posedge M_AXI_ACLK)begin
if (M_AXI_ARESETN == 0 )begin
wr_start <= 1'b0;
end
else if (rd_data_count >= THRESHOLD && axi_awvalid == 1'b0 && axi_wvalid == 1'b0) begin
wr_start <= 1'b1;
end
else begin
wr_start <= 1'b0;
end
end
//----------------axi_awvalid------------------
// axi 写地址有效信号
always @(posedge M_AXI_ACLK)begin
if (M_AXI_ARESETN == 0 )begin
axi_awvalid <= 1'b0;
end
// 当接收到写地址的响应信号后,写地址有效信不使能
else if (axi_awvalid == 1'b1 && M_AXI_AWREADY == 1'b1) begin
axi_awvalid <= 1'b0;
end
//当接收到写开始信号,并且当前的axi总线处于空闲状态
else if (wr_start && axi_awvalid == 1'b0 && axi_wvalid == 1'b0 ) begin
axi_awvalid <= 1'b1;
end
end
//----------------axi_awaddr------------------
// axi 总线上的地址
always @(posedge M_AXI_ACLK)begin
if (M_AXI_ARESETN == 0 )begin
axi_awaddr <= 'd0;
end
// 每次突发写数据后地址增加(256*64)/8 = 2048
else if (axi_awvalid == 1'b1 && M_AXI_AWREADY == 1'b1) begin
// 地址增加至一幅图像的最大值
if(axi_awaddr == STOP_ADDR)begin
axi_awaddr <= 'd0;
end
// 地址每次偏移一定量
else begin
axi_awaddr <= axi_awaddr + ADD_ADDR;
end
end
end
//----------------axi_wvalid------------------
// 写数据有效信号
always @(posedge M_AXI_ACLK)begin
if (M_AXI_ARESETN == 0 )begin
axi_wvalid <= 1'b0;
end
// 一次突发结束,写数据无效
else if (end_cnt_burst == 1'b1) begin
axi_wvalid <= 1'b0;
end
// 给出写地址指令后,数据有效
else if (axi_awvalid == 1'b1 && M_AXI_AWREADY == 1'b1) begin
axi_wvalid <= 1'b1;
end
end
//---------------cnt_burst-------------------
always @(posedge M_AXI_ACLK) begin
if (M_AXI_ARESETN == 1'b0) begin
cnt_burst <= 'd0;
end
else if (add_cnt_burst) begin
if(end_cnt_burst)
cnt_burst <= 'd0;
else
cnt_burst <= cnt_burst + 1'b1;
end
end
// 当前数据有效,并且axi总线可以接收数据
assign add_cnt_burst = axi_wvalid && M_AXI_WREADY;
// 计数到最大值
assign end_cnt_burst = add_cnt_burst && cnt_burst == BURST_MAX;
//----------------rd_en------------------
assign rd_en = axi_wvalid && M_AXI_WREADY;
//----------------axi_wdata------------------
// axi_wdata 就是从FIFO中读出的数据
always @(*)begin
axi_wdata = dout;
end
//----------------axi_wlast------------------
//指示当前数据是最后一个
always @(*)begin
if (end_cnt_burst) begin
axi_wlast = 1'b1;
end
else begin
axi_wlast = 1'b0;
end
end
//----------------axi_bready------------------
// 写响应信道
always @(posedge M_AXI_ACLK)begin
if (M_AXI_ARESETN == 0 )begin
axi_bready <= 1'b0;
end
else begin
axi_bready <= 1'b1;
end
end