在前面的博客中,介绍了AXI接口的基础的一些概念。但是并没有具体实现的例子,今天就通过一个AXI4接口的读时序,来完成从内存中读出数据的这么一个操作。AXI4接口的读时序如下图所示,首先给出读取的内存地址,然后将数据从内存中读出。
在Vivado软件中自定义一个AXI4接口的IP。
设置接口模式为主机模式,接口类型为AXI4类型,这里接口类型还有AXI_LITE和AXI_Stream类型。
进入到IP编辑界面可以看到该IP的文件结构如下,若要对IP的功能尽心修改,只需要在这两个文件中进行修改即可。进入到子模块中,将实例代码中的功能代码删除,然后自己实现一个AXI4 IP内部的功能。
在内部实现如下功能:就是将在FIFO内部的数据少于一个门限值时,启动一次突发读操作,将内存中的数据读出到FIFO中。一次突发读写的数据个数为256。
//一些参数的定义
parameter ADD_ADDR = 256 * 64/8;
parameter STOP_ADDR = (1024 * 768) * 32/8 - ADD_ADDR;
parameter THRESHOLD = 1026;
parameter BURST_MAX = 256 - 1;
reg rd_start ;//读起始信号
wire wr_en ;//写FIFO使能
wire full ;
wire empty ;
wire [32:0] dout ;//从FIFO中读出的数据
wire [11:0] rd_data_count ;
wire [10:0] wr_data_count ;
reg work_on_flag ;//复位结束信号
reg [10:0] cnt_burst ;//计数突发长度
wire add_cnt_burst ;
wire end_cnt_burst ;
//--------------------
//Write Address Channel
//--------------------
//----------------work_on_flag------------------
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
//----------------wr_en------------------
assign wr_en = work_on_flag & M_AXI_RVALID & axi_rready;
//----------------rd_start------------------
// 读起始信号
always @(posedge M_AXI_ACLK)begin
if (M_AXI_ARESETN == 0 )begin
rd_start <= 1'b0;
end
//FIFO中不足两行数据,且当前处于空闲状态,开始读数据
else if (work_on_flag && (wr_data_count <= THRESHOLD) && axi_arvalid == 1'b0 && axi_rready == 1'b0) begin
rd_start <= 1'b1;
end
else begin
rd_start <= 1'b0;
end
end
//----------------axi_arvalid------------------
always @(posedge M_AXI_ACLK)begin
if (M_AXI_ARESETN == 0 )begin
axi_arvalid <= 1'b0;
end
// 当前已经发起了一次读操作
else if (axi_arvalid == 1'b1 && M_AXI_ARREADY == 1'b1) begin
axi_arvalid <= 1'b0;
end
// 接收到读起始信号,且当前处于空闲状态
else if (rd_start == 1'b1 && axi_arvalid == 1'b0 && axi_rready == 1'b0) begin
axi_arvalid <= 1'b1;
end
end
//----------------axi_araddr------------------
//读地址
always @(posedge M_AXI_ACLK)begin
if (M_AXI_ARESETN == 0 )begin
axi_araddr <= 'd0;
end
// 每次发起读数据,地址偏移相同的长度
else if (axi_arvalid == 1'b1 && M_AXI_ARREADY == 1'b1) begin
//计数到图像最大值,清零
if (axi_araddr == STOP_ADDR) begin
axi_araddr <= 'd0;
end
else begin
axi_araddr <= axi_araddr + ADD_ADDR;
end
end
end
//----------------axi_rready------------------
always @(posedge M_AXI_ACLK)begin
if (M_AXI_ARESETN == 0 )begin
axi_rready <= 1'b0;
end
// 一次突发结束
else if (end_cnt_burst == 1'b1) begin
axi_rready <= 1'b0;
end
// 一次读开始,接收准备好信号有效
else if (axi_arvalid == 1'b1 && M_AXI_ARREADY == 1'b1) begin
axi_rready <= 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
// 当前处于传输信号时,计数器加一
assign add_cnt_burst = axi_rready && M_AXI_RVALID;
//计数器计数到最大值
assign end_cnt_burst = add_cnt_burst && cnt_burst == BURST_MAX;
rd_fifo inst_rd_fifo (
.wr_clk(M_AXI_ACLK), // input wire wr_clk
.rd_clk(hdmi_tx_clk), // input wire rd_clk
.din(M_AXI_RDATA), // input wire [63 : 0] din
.wr_en(wr_en), // input wire wr_en
.rd_en(rd_en), // input wire rd_en
.dout(dout), // output wire [31 : 0] dout
.full(full), // output wire full
.empty(empty), // output wire empty
.rd_data_count(rd_data_count), // output wire [11 : 0] rd_data_count
.wr_data_count(wr_data_count) // output wire [10 : 0] wr_data_count
);
由于想要将内存中的数据直观地显示出来,由上位机发送数据给FPGA缓存到内存中,然后由前面的AXI读数据模块将数据从内存中读出,并且将读出的数据给到VGA模块使用。在VGA模块后链接一个rgb2dvi的IP将VGA时序信号进行编码转换为tmds通道信号,最终在HDMI上能够显示图像。
module hdmi_out(
input wire rst ,//时钟复位
input wire hdmi_tx_clk ,//像素时钟
output reg hdmi_tx_de ,//输出数据有效信号
output reg hdmi_tx_hs ,//行同步信号
output reg hdmi_tx_vs ,//场同步信号
output reg [23:0] hdmi_td ,//输出图像值
output wire rd_en ,//从buffer中读取数据请求
input wire [23:0] rd_data //从buffer中读取的数据
);
//------------------------------------------------------------
//1024*768@60Hz
//------------------------------------------------------------
parameter H_TOTAL = 1344 ;//一行总共需要计数的值
parameter H_SYNC = 136 ;//行同步计数值
parameter H_START = 296 ;//行图像数据有效开始计数值
parameter H_END = 1320 ;//行图像数据有效结束计数值
parameter V_TOTAL = 806 ;//场总共需要计数的值
parameter V_SYNC = 6 ;//场同步计数值
parameter V_START = 35 ;//场图像数据有效开始计数值
parameter V_END = 803 ;//场图像数据有效结束计数值
parameter SQUARE_X = 256 ;//方块的宽度
parameter SQUARE_Y = 256 ;//方块的长度
parameter SCREEN_X = 1024;//屏幕水平长度
parameter SCREEN_Y = 768;//屏幕垂直长度
//=======================================================
// internal Signal declarations
//=======================================================
reg [12:0] cnt_h;//行计数器
reg [12:0] cnt_v;//场计数器
reg [11:0] x ;//方块左上角横坐标
reg flag_x;//方块水平移动方向指示信号
reg [11:0] y ;//方块左上角纵坐标
reg flag_y;//方块垂直移动方向指示信号
wire locked1; //时钟稳定信号
//行计数器
always @(posedge hdmi_tx_clk ) begin
if (rst==1'b1) begin
cnt_h <= 'd0;
end
else if (cnt_h == H_TOTAL - 1 ) begin//计数到最大值,清零
cnt_h <= 'd0;
end
else if(cnt_h != H_TOTAL - 1 ) begin//还没有计数到最大值,每个时钟周期加一
cnt_h <= cnt_h + 1'b1;
end
end
//场计数器
always @(posedge hdmi_tx_clk ) begin
if (rst==1'b1) begin
cnt_v <='d0;
end
else if (cnt_v == V_TOTAL - 1 && cnt_h == H_TOTAL - 1) begin//场计数器计数到最大值,清零(一帧结束)
cnt_v <= 'd0;
end
else if(cnt_h == H_TOTAL - 1) begin//一行扫描结束,场计数器加一
cnt_v <= cnt_v + 1'b1;
end
end
//行同步信号
always @(posedge hdmi_tx_clk ) begin
if (rst==1'b1) begin
hdmi_tx_hs <= 1'b0;
end
else if (cnt_h == H_TOTAL - 1) begin
hdmi_tx_hs <= 1'b1;
end
else if (cnt_h == H_SYNC - 1) begin
hdmi_tx_hs <= 1'b0;
end
end
//场同步信号
always @(posedge hdmi_tx_clk ) begin
if (rst==1'b1) begin
hdmi_tx_vs <= 1'b0;
end
else if (cnt_v == V_TOTAL - 1 && cnt_h == H_TOTAL - 1) begin
hdmi_tx_vs <= 1'b1;
end
else if (cnt_v == V_SYNC - 1 && cnt_h == H_TOTAL - 1) begin
hdmi_tx_vs <= 1'b0;
end
end
//数据有效信号
always @(posedge hdmi_tx_clk) begin
if (rst) begin
hdmi_tx_de <= 1'b0;
end
else if ((cnt_h >= H_START - 1) && (cnt_h < H_END - 1) && (cnt_v >= V_START - 1) && (cnt_v < V_END - 1)) begin
hdmi_tx_de <= 1'b1;
end
else begin
hdmi_tx_de <= 1'b0;
end
end
//hdmi_td
always @(posedge hdmi_tx_clk ) begin
if (rst==1'b1) begin
hdmi_td <='d0;
end
else if(cnt_h >=(H_START - 1) && cnt_h <(H_END - 1) && cnt_v >=(V_START - 1 )&& cnt_v <V_END - 1)begin
hdmi_td <= rd_data;//输出方块图像
end
else begin
hdmi_td <= 'd0;
end
end
endmodule