https://download.youkuaiyun.com/download/mvpkuku/90855619
一、AXI_FULL协议的前提知识
1. 各端口的功能

2. 4K边界问题

3. outstanding

4.时序仿真体验
可通过VIVADO自带ADMA工程观察仿真波形图
二、FPGA实现 (主要用于读写DDR)
1.功能模块及框架
将用户端的写/读相关信号转换为AXI4_MASTER接口信号。因此顶层设计主要分为用户端接口以及AXI4协议主机接口

1、换存数据量较小的时候,FIFO建议采用DRAM
DRAM是由逻辑资源搭建而成的,消耗的是LUT的资源
缓存命令、地址、帧长度等等。2、缓存数据量比较大的时候,采用BRAM,
BRAM是一种资源。18k = 18 * 1024bit = 36bit(最大宽度) * 512(深度)= 18bit * 1024 = 9bit * 2048
36k = 2 * 18k = 72bit(最大宽度) * 512(深度)= 36bit * 1024 = 18bit * 2048
37bit = 36bit + 36bitFIFO使用了一个18k的bram,即使FIFo的深度和宽度开的很小,很多空间没有使用,
这个FIFO的消耗的BRAM,不能被其他的模块(FIFO)使用
FIFO采用BRAM的时候,消耗最少相同的BARM数量,建议把FIFO的深度和宽度开到最大AXI_DATA_WIDTH == 64
64bit + 1bit = 65bit = 36bit + 36bit 至少2个18k bram
2.写通道实现
2.1 wr_ctrl模块
模块作用:
写入用户端信号(写使能,写数据,写地址)
1.对复位信号以及ddr初始化完成信号进行同步,进而对用户端信号进行有效寄存
2.对用户端写数据进行计数(位宽转换,突发写字节数)
3.生成数据及数据有效,写数据最后一位,写突发长度
4.生成写请求和写地址
ps:3、4输出信号均给wr_buffer模块进行跨时钟与缓存
下一个模块的cmd_wren在wr_req_en为1时拉高,并且此模块在此时更新地址
wr_req_en作为wr_buffer状态机启动信号

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Description: 写通道控制模块
//////////////////////////////////////////////////////////////////////////////////
module wr_ctrl#(
parameter USER_WR_DATA_WIDTH = 16 ,
parameter AXI_DATA_WIDTH = 128 ,
parameter AXI_ADDR_WIDTH = 32 ,
parameter WR_BURST_LENGTH = 4096 //可以为4096,2048,1024....字节
)(
input clk , //用户端写时钟
input reset ,
/*-----DDR初始化完成信号,与MIG核交互------------------*/
input ddr_init_done ,
/*-------------用户写端口信号------------------------*/
input user_wr_en ,
input [USER_WR_DATA_WIDTH-1:0] user_wr_data ,
input [AXI_ADDR_WIDTH-1:0] user_wr_base_addr , //初始写ddr数据地址 一定要被4096整除
input [AXI_ADDR_WIDTH-1:0] user_wr_end_addr , //一定要被4096整除
/*-------------与wr_buffer模块交互信号----------------*/
output reg wr_data_valid ,
output reg [AXI_DATA_WIDTH-1:0] wr_data_out ,
output reg wr_data_last ,
output [7:0] wr_burst_length ,
output reg wr_req_en ,
output reg [AXI_ADDR_WIDTH-1:0] wr_data_addr
);
(* dont_touch ="true" *) reg reset_sync_d0; //(* dont_touch ="true" *)的作用是防止信号被优化
(* dont_touch ="true" *) reg reset_sync_d1;
(* dont_touch ="true" *) reg reset_sync;
reg ddr_init_done_d0;
reg ddr_init_done_d1;
reg ddr_wr_enable ;
reg user_wr_en_d ;
reg [USER_WR_DATA_WIDTH-1:0] user_wr_data_d ;
localparam WR_CNT_MAX = AXI_DATA_WIDTH / USER_WR_DATA_WIDTH - 1; //用户端数据与AXI数据位宽转换计数
localparam MAX_BURST_LENGTH = WR_BURST_LENGTH / (AXI_DATA_WIDTH / 8) - 1; //AXI突发写长度
reg [$clog2(WR_CNT_MAX) - 1 :0] wr_cnt ;
reg [7:0] wr_burst_cnt ;
/*--------------------------------------------------*\
CDC process
\*--------------------------------------------------*/
always @(posedge clk) begin
reset_sync_d0 <= reset;
reset_sync_d1 <= reset_sync_d0;
reset_sync <= reset_sync_d1;
end
always @(posedge clk) begin
ddr_init_done_d0 <= ddr_init_done;
ddr_init_done_d1 <= ddr_init_done_d0;
ddr_wr_enable <= ddr_init_done_d1;
end
always @(posedge clk) begin //在DDR初始化完成之后,才开始将数据写入DDR,如果DDR没有初始化完成,用户端有数据来了,就丢掉
if (ddr_wr_enable) begin
user_wr_en_d <= user_wr_en;
user_wr_data_d <= user_wr_data;
end
else begin
user_wr_en_d <= 0;
user_wr_data_d <= 0;
end
end
/*--------------------------------------------------*\
data
\*--------------------------------------------------*/
always @(posedge clk) begin //也可以拆开写
if (reset_sync) begin
wr_cnt <= 0;
wr_burst_cnt <= 0;
end
else if (user_wr_en_d) begin
if (wr_cnt == WR_CNT_MAX)begin
wr_cnt <= 0;
wr_burst_cnt <= (wr_burst_cnt == MAX_BURST_LENGTH) ? 0 : wr_burst_cnt + 1;
end
else begin
wr_cnt <= wr_cnt + 1;
wr_burst_cnt <= wr_burst_cnt;
end
end
end
always @(posedge clk) begin
if (wr_cnt == WR_CNT_MAX && user_wr_en_d)
wr_data_valid <= 1'b1;
else
wr_data_valid <= 0;
end
//高进低出
always @(posedge clk) begin
if (user_wr_en_d)
wr_data_out <= {user_wr_data_d,wr_data_out[AXI_DATA_WIDTH-1 : USER_WR_DATA_WIDTH]};
else
wr_data_out <= wr_data_out;
end
always @(posedge clk) begin
if (wr_cnt == WR_CNT_MAX && wr_burst_cnt == MAX_BURST_LENGTH && user_wr_en_d)
wr_data_last <= 1'b1;
else
wr_data_last <= 0;
end
/*--------------------------------------------------*\
req / addr
\*--------------------------------------------------*/
always @(posedge clk) begin
if (wr_cnt == WR_CNT_MAX && wr_burst_cnt == MAX_BURST_LENGTH && user_wr_en_d)
wr_req_en <= 1'b1;
else
wr_req_en <= 0;
end
//每突发一次写完成,写地址+突发长度
always @(posedge clk) begin
if (reset)
wr_data_addr <= user_wr_base_addr;
else if (wr_req_en && wr_data_addr >= user_wr_end_addr - WR_BURST_LENGTH)
wr_data_addr <= user_wr_base_addr;
else if (wr_req_en)
wr_data_addr <= wr_data_addr + WR_BURST_LENGTH;
else
wr_data_addr <= wr_data_addr;
end
assign wr_burst_length = MAX_BURST_LENGTH;
endmodule
2.2 wr_buffer模块
1.复位信号进行跨时钟域同步(用户时钟/AXI4时钟)
2.将写数据addr和length写入CMD fifo (clk),cmd_wren在wr_req_en为1时拉高,并且此时更新地址(与2.1wr_ctrl交互的信号)
3..将要写数据data和last 写入data fifo;data_wren随着上层数据有效写入data_fifo
步骤2.3均是对上层模块的输入进行寄存;下面步骤做输出转换设计:
4.状态机设计(输出传给axi4_master模块)
(axi_aw_req_en和axi_aw_ready)握手机制控制是否开始写状态
//表示AXI4写请求 本模块时序控制,状态机一个状态产生请求信号,握手成功之后拉低
//axi_aw_req_en 和axi_aw_ready同时为高,开启一次AXI4写传输
并在该信号跳转开始写数据状态的同时,拉高cmd_rden,读出数据突发长度以及写地址
5.AXI部分信号的时序
突发长度和地址由当cmd_rden为高时,cmd_dout分配
其次是是数据有效同上,已进入写数据状态就拉高,写完最后一个数据拉低;
![]()
数据和last由当data_rden为高时由data_dout分配,其中data_rden信号设计如下:
ps:值得注意一下的是
三个信号同步
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Description: 写缓存(主要是cmd_fifo/data_fifo)
//////////////////////////////////////////////////////////////////////////////////
module wr_buffer #(
parameter AXI_DATA_WIDTH = 128 ,
parameter AXI_ADDR_WIDTH = 32
)(
input clk , //用户端写时钟
input axi_clk , //AXI4端时钟
input reset ,
/*-------------wr_ctrl模块交互信号----------------*/
input wr_req_en ,
input [AXI_ADDR_WIDTH-1:0] wr_data_addr ,
input [7:0] wr_burst_length ,
input wr_data_valid ,
input [AXI_DATA_WIDTH-1:0] wr_data_in ,
input wr_data_last ,
/*-------------axi_wr_master_模块交互信号-------------*/
output reg axi_aw_req_en , //表示AXI4写请求,需要本模块设计给出
input axi_aw_ready , //axi_aw_req_en 和axi_aw_ready同时为高,开启一次AXI4写传输
output reg [7:0] axi_aw_burst_len ,
output reg [AXI_ADDR_WIDTH-1:0] axi_aw_addr ,
output reg axi_w_valid ,
input axi_w_ready ,
output reg [AXI_DATA_WIDTH-1:0] axi_w_data ,
output reg axi_w_last
);
(* dont_touch ="true" *)reg reset_sync_d0; //用户端复位信号
(* dont_touch ="true" *)reg reset_sync_d1;
(* dont_touch ="true" *)reg reset_sync;
(* dont_touch ="true" *)reg a_reset_sync_d0; // axi4端的复位信号
(* dont_touch ="true" *)reg a_reset_sync_d1;
(* dont_touch ="true" *)reg a_reset_sync;
/*------------------------------------------*\
FIFO端口信号定义
\*------------------------------------------*/
reg cmd_wren;
wire [39:0] cmd_dout;
wire cmd_rden;
wire cmd_wrfull;
wire cmd_rdempty;
wire [4:0] cmd_wrcount;
wire [4:0] cmd_rdcount;
reg [39:0] cmd_din; //根据项目实际需要修改这边的输入,本模块 AXI_ADDR_WIDTH = 32 + wr_burst_length =8
/*------------------------------------------*\
状态机
\*------------------------------------------*/
reg [2:0] cur_status;
reg [2:0] nxt_status;
localparam WR_IDLE = 3'b000;
localparam WR_REQ = 3'b001;
localparam WR_DATA_EN = 3'b010;
localparam WR_DATA_END = 3'b100;
/*--------------------------------------------------*\
CDC process
\*--------------------------------------------------*/
always @(posedge clk) begin
reset_sync_d0 <= reset;
reset_sync_d1 <= reset_sync_d0;
reset_sync <= reset_sync_d1;
end
always @(posedge axi_clk) begin
a_reset_sync_d0 <= reset;
a_reset_sync_d1 <= a_reset_sync_d0;
a_reset_sync <= a_reset_sync_d1;
end
/*--------------------------------------------------*\
将地址和length写入CMD fifo
\*--------------------------------------------------*/
always @(posedge clk) begin
if (wr_req_en) begin
cmd_wren <= 1'b1;
cmd_din <= {wr_burst_length,wr_data_addr};
end
else begin
cmd_wren <= 0;
cmd_din <= 0;
end
end
/*--------------------------------------------------*\
状态机设计,注意时钟域,是在axi_clk时钟下
\*--------------------------------------------------*/
always @(posedge axi_clk) begin
if (a_reset_sync)
cur_status <= WR_IDLE;
else
cur_status <= nxt_status;
end
always @(*) begin
if (a_reset_sync)
nxt_status <= WR_IDLE;
else begin
case(cur_status)
WR_IDLE : begin
if (~cmd_rdempty)
nxt_status <= WR_REQ;
else
nxt_status <= cur_status;
end
WR_REQ : begin
if (axi_aw_req_en && axi_aw_ready) //握手机制控制写状态开始
nxt_status <= WR_DATA_EN;
else
nxt_status <= cur_status;
end
WR_DATA_EN : begin
if (axi_w_valid && axi_w_ready && axi_w_last)
nxt_status <= WR_DATA_END;
else
nxt_status <= cur_status;
end
WR_DATA_END : begin
nxt_status <= WR_IDLE;
end
default : nxt_status <= WR_IDLE;
endcase
end
end
always @(posedge axi_clk) begin
if (a_reset_sync)
axi_aw_req_en <= 0;
else if (axi_aw_req_en && axi_aw_ready)
axi_aw_req_en <= 0;
else if (cur_status == WR_REQ)
axi_aw_req_en <= 1;
else
axi_aw_req_en <= axi_aw_req_en;
end
assign cmd_rden = axi_aw_req_en && axi_aw_ready;
always @(*) begin
if (a_reset_sync) begin
axi_aw_burst_len <= 0;
axi_aw_addr <= 0;
end
else begin
axi_aw_burst_len <= cmd_rden ? cmd_dout[39:32]: axi_aw_burst_len;
axi_aw_addr <= cmd_rden ? cmd_dout[31:0] : axi_aw_addr;
end
end
/*--------------------------------------------------*\
cmd fifo
\*--------------------------------------------------*/
fifo_w40xd16 wr_cmd_fifo (
.rst ( reset_sync ), // input wire rst
.wr_clk ( clk ), // input wire wr_clk
.rd_clk ( axi_clk ), // input wire rd_clk
.din ( cmd_din ), // input wire [39 : 0] din
.wr_en ( cmd_wren ), // input wire wr_en
.rd_en ( cmd_rden ), // input wire rd_en
.dout ( cmd_dout ), // output wire [39 : 0] dout
.full ( cmd_wrfull ), // output wire full
.empty ( cmd_rdempty ), // output wire empty
.rd_data_count ( rd_data_count ), // output wire [4 : 0] rd_data_count
.wr_data_count ( wr_data_count ) // output wire [4 : 0] wr_data_count
);
/*--------------------------------------------------*\
根据AXI_DATA_WIDTH 生成不同位宽的fifo
\*--------------------------------------------------*/
reg data_wren;
reg data_rden;
wire data_wrfull;
wire data_rdempty;
generate
if (AXI_DATA_WIDTH == 256) begin
reg [287:0] data_din;
wire [287:0] data_dout;
wire [9:0] data_wrcount;
wire [9:0] data_rdcount;
always @(posedge clk) begin
data_din <= {31'h0,wr_data_last,wr_data_in};//256+1+31
data_wren <= wr_data_valid;
end
always @(posedge axi_clk) begin
if (axi_aw_req_en && axi_aw_ready)
axi_w_valid <= 1'b1;
else if (axi_w_valid && axi_w_ready && axi_w_last)
axi_w_valid <= 0;
else
axi_w_valid <= axi_w_valid;
end
always @(*) begin
if (data_rden) begin
axi_w_data <= data_dout[255:0];
axi_w_last <= data_dout[256];
end
else begin
axi_w_data <= 0;
axi_w_last <= 0;
end
end
always @(*) begin
data_rden <= axi_w_valid && axi_w_ready && cur_status == WR_DATA_EN;
end
fifo_w288xd512 wr_data_fifo (
.rst (reset_sync ),// input wire rst
.wr_clk (clk ),// input wire wr_clk
.rd_clk (axi_clk ),// input wire rd_clk
.din (data_din ),// input wire [287 : 0] din
.wr_en (data_wren ),// input wire wr_en
.rd_en (data_rden ),// input wire rd_en
.dout (data_dout ),// output wire [287 : 0] dout
.full (data_wrfull ),// output wire full
.empty (data_rdempty ),// output wire empty
.rd_data_count(data_rdcount ),// output wire [9 : 0] rd_data_count
.wr_data_count(data_wrcount ) // output wire [9 : 0] wr_data_count
);
end else if (AXI_DATA_WIDTH == 128) begin
reg [143:0] data_din;
wire [143:0] data_dout;
wire [9:0] data_wrcount;
wire [9:0] data_rdcount;
always @(posedge clk) begin
data_din <= {15'h0,wr_data_last,wr_data_in};
data_wren <= wr_data_valid;
end
always @(posedge axi_clk) begin
if (axi_aw_req_en && axi_aw_ready)
axi_w_valid <= 1'b1;
else if (axi_w_valid && axi_w_ready && axi_w_last)
axi_w_valid <= 0;
else
axi_w_valid <= axi_w_valid;
end
always @(*) begin
if (data_rden) begin
axi_w_data <= data_dout[127:0];
axi_w_last <= data_dout[128];
end
else begin
axi_w_data <= 0;
axi_w_last <= 0;
end
end
always @(*) begin
data_rden <= axi_w_valid && axi_w_ready && cur_status == WR_DATA_EN;
end
fifo_w144xd512 wr_data_fifo (
.rst (reset_sync ),// input wire rst
.wr_clk (clk ),// input wire wr_clk
.rd_clk (axi_clk ),// input wire rd_clk
.din (data_din ),// input wire [287 : 0] din
.wr_en (data_wren ),// input wire wr_en
.rd_en (data_rden ),// input wire rd_en
.dout (data_dout ),// output wire [287 : 0] dout
.full (data_wrfull ),// output wire full
.empty (data_rdempty ),// output wire empty
.rd_data_count(data_rdcount ),// output wire [9 : 0] rd_data_count
.wr_data_count(data_wrcount ) // output wire [9 : 0] wr_data_count
);
end else if (AXI_DATA_WIDTH == 64) begin
reg [71:0] data_din;
wire [71:0] data_dout;
wire [9:0] data_wrcount;
wire [9:0] data_rdcount;
always @(posedge clk) begin
data_din <= {7'h0,wr_data_last,wr_data_in};
data_wren <= wr_data_valid;
end
always @(posedge axi_clk) begin
if (axi_aw_req_en && axi_aw_ready)
axi_w_valid <= 1'b1;
else if (axi_w_valid && axi_w_ready && axi_w_last)
axi_w_valid <= 0;
else
axi_w_valid <= axi_w_valid;
end
always @(*) begin
if (data_rden) begin
axi_w_data <= data_dout[63:0];
axi_w_last <= data_dout[64];
end
else begin
axi_w_data <= 0;
axi_w_last <= 0;
end
end
always @(*) begin
data_rden <= axi_w_valid && axi_w_ready && cur_status == WR_DATA_EN;
end
fifo_w72xd512 wr_data_fifo (
.rst (reset_sync ),// input wire rst
.wr_clk (clk ),// input wire wr_clk
.rd_clk (axi_clk ),// input wire rd_clk
.din (data_din ),// input wire [287 : 0] din
.wr_en (data_wren ),// input wire wr_en
.rd_en (data_rden ),// input wire rd_en
.dout (data_dout ),// output wire [287 : 0] dout
.full (data_wrfull ),// output wire full
.empty (data_rdempty ),// output wire empty
.rd_data_count(data_rdcount ),// output wire [9 : 0] rd_data_count
.wr_data_count(data_wrcount ) // output wire [9 : 0] wr_data_count
);
end
endgenerate
endmodule
2.3 axi_master模块
1.对除了与下游从机交互的握手信号以及写地址,写突发长度的信号进行固定赋值;
2. axi_aw_req_en启动状态机
3.组合逻辑输出作为丛机给wr_buffer的握手准备信号,一个由状态机产生,一个由下游从机输入
4.对和下游从机交互的握手信号以及写地址,写突发长度以及数据,数据最后一位的信号 时序控制
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Description: axi_wr_master ////////////////////////////////////////////////////////////////////////////////// module axi_wr_master #( parameter AXI_DATA_WIDTH = 128 , parameter AXI_ADDR_WIDTH = 32 )( input axi_clk , input reset , /*-------------wr_buffer模块交互信号-------------*/ input axi_aw_req_en , //表示AXI4写请求,由上层wr_buffer传入 output reg axi_aw_ready , //axi_aw_req_en 和axi_aw_ready同时为高,开启一次AXI4写传输 input [7:0] axi_aw_burst_len , input [AXI_ADDR_WIDTH-1:0] axi_aw_addr , input axi_w_valid , output reg axi_w_ready , input [AXI_DATA_WIDTH-1:0] axi_w_data , input axi_w_last , /*-------------AXI写通道端口信号---------------------*/ output reg m_axi_awvalid , input m_axi_awready , output reg [AXI_ADDR_WIDTH-1:0] m_axi_awaddr , output reg [7:0] m_axi_awlen , output reg [1:0] m_axi_awburst , output reg [2:0] m_axi_awsize , output reg [3:0] m_axi_awid , output reg [2:0] m_axi_awport , output reg [3:0] m_axi_awqos , output reg m_axi_awlock , output reg [3:0] m_axi_awcache , output reg m_axi_wvalid , input m_axi_wready , output reg [AXI_DATA_WIDTH-1:0] m_axi_wdata , output reg [AXI_DATA_WIDTH/8-1:0] m_axi_wstrb , output reg m_axi_wlast , input [3:0] m_axi_bid , input [1:0] m_axi_bresp , input m_axi_bvalid , output m_axi_bready ); (* dont_touch ="true" *)reg a_reset_sync_d0; // axi4端的复位信号 (* dont_touch ="true" *)reg a_reset_sync_d1; (* dont_touch ="true" *)reg a_reset_sync; assign m_axi_bready = 1'b1; /*------------------------------------------*\ 状态机定义 \*------------------------------------------*/ reg [2:0] cur_status; reg [2:0] nxt_status; localparam AXI_WR_IDLE = 3'b000; localparam AXI_WR_PRE = 3'b001; localparam AXI_WR_DATA = 3'b010; localparam AXI_WR_END = 3'b100; /*--------------------------------------------------*\ CDC process \*--------------------------------------------------*/ always @(posedge axi_clk) begin a_reset_sync_d0 <= reset; a_reset_sync_d1 <= a_reset_sync_d0; a_reset_sync <= a_reset_sync_d1; end always @(posedge axi_clk) begin m_axi_awport <= 0; m_axi_awid <= 0; m_axi_awburst <= 2'b01; m_axi_awlock <= 0; m_axi_awcache <= 0; m_axi_awqos <= 0; m_axi_wstrb <= {AXI_DATA_WIDTH/8{1'b1}}; m_axi_awsize <= AXI_DATA_WIDTH == 512 ? 3'h6 : AXI_DATA_WIDTH == 256 ? 3'h5 : AXI_DATA_WIDTH == 128 ? 3'h4 : AXI_DATA_WIDTH == 64 ? 3'h3 : AXI_DATA_WIDTH == 32 ? 3'h2 : 3'h0; end /*--------------------------------------------------*\ 状态机代码 \*--------------------------------------------------*/ always @(posedge axi_clk) begin if (a_reset_sync) cur_status <= AXI_WR_IDLE; else cur_status <= nxt_status; end always @(*) begin if (a_reset_sync) nxt_status <= AXI_WR_IDLE; else begin case(cur_status) AXI_WR_IDLE : begin if (axi_aw_req_en) nxt_status <= AXI_WR_PRE; else nxt_status <= cur_status; end AXI_WR_PRE : begin nxt_status <= AXI_WR_DATA; end AXI_WR_DATA : begin if (m_axi_wvalid && m_axi_wready && m_axi_wlast) nxt_status <= AXI_WR_END; else nxt_status <= cur_status; end AXI_WR_END : begin nxt_status <= AXI_WR_IDLE; end default : nxt_status <= AXI_WR_IDLE; endcase end end /*------------------------------------------*\ 输出给wr_buffer模块ready信号 \*------------------------------------------*/ always @(*) begin axi_aw_ready <= cur_status == AXI_WR_PRE; axi_w_ready <= m_axi_wready; end /*------------------------------------------*\ AXI4写通道的信号 \*------------------------------------------*/ always @(posedge axi_clk) begin if (a_reset_sync) m_axi_awvalid <= 0; else if (m_axi_awvalid && m_axi_awready) m_axi_awvalid <= 0; else if (axi_aw_req_en && axi_aw_ready) m_axi_awvalid <= 1; end always @(posedge axi_clk) begin if (axi_aw_req_en && axi_aw_ready) begin m_axi_awaddr <= axi_aw_addr; m_axi_awlen <= axi_aw_burst_len; end else begin m_axi_awaddr <= m_axi_awaddr; m_axi_awlen <= m_axi_awlen; end end always @(posedge axi_clk) begin if (a_reset_sync) m_axi_wvalid <= 0; else if (m_axi_wvalid && m_axi_wready && m_axi_wlast) m_axi_wvalid <= 0; else if (axi_w_valid && axi_w_ready) m_axi_wvalid <= 1; else m_axi_wvalid <= m_axi_wvalid; end always @(posedge axi_clk) begin if (a_reset_sync) m_axi_wlast <= 0; else if (m_axi_wvalid && m_axi_wready && m_axi_wlast) m_axi_wlast <= 0; else if (axi_w_valid && axi_w_ready && axi_w_last) m_axi_wlast <= 1; end always @(posedge axi_clk) begin if (axi_w_valid && axi_w_ready) m_axi_wdata <= axi_w_data; else m_axi_wdata <= m_axi_wdata; end endmodule
2.4 实现顶层代码
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Description: 写通道
//////////////////////////////////////////////////////////////////////////////////
module axi_wr_channel#(
parameter USER_WR_DATA_WIDTH = 16 ,
parameter AXI_DATA_WIDTH = 128 ,
parameter AXI_ADDR_WIDTH = 32
)(
input user_wr_clk , //用户端写时钟
input axi_clk , // AXI4端时钟
input reset ,
input ddr_init_done ,
/*-------------用户写端口信号------------------------*/
input user_wr_en ,
input [USER_WR_DATA_WIDTH-1:0] user_wr_data ,
input [AXI_ADDR_WIDTH-1:0] user_wr_base_addr , //一定要被4096整除
input [AXI_ADDR_WIDTH-1:0] user_wr_end_addr , //一定要被4096整除
/*-------------AXI写通道端口信号---------------------*/
output m_axi_awvalid ,
input m_axi_awready ,
output [AXI_ADDR_WIDTH-1:0] m_axi_awaddr ,
output [3:0] m_axi_awid ,
output [7:0] m_axi_awlen ,
output [1:0] m_axi_awburst ,
output [2:0] m_axi_awsize ,
output [2:0] m_axi_awport ,
output [3:0] m_axi_awqos ,
output m_axi_awlock ,
output [3:0] m_axi_awcache ,
output m_axi_wvalid ,
input m_axi_wready ,
output [AXI_DATA_WIDTH-1:0] m_axi_wdata ,
output [AXI_DATA_WIDTH/8-1:0] m_axi_wstrb ,
output m_axi_wlast ,
input [3:0] m_axi_bid ,
input [1:0] m_axi_bresp ,
input m_axi_bvalid ,
output m_axi_bready
);
wire wr_req_en;
wire [7:0] wr_burst_length;
wire [AXI_ADDR_WIDTH-1:0] wr_data_addr;
wire wr_data_valid;
wire [AXI_DATA_WIDTH-1:0] wr_data_out;
wire wr_data_last;
wire axi_aw_req_en;
wire axi_aw_ready;
wire [7:0] axi_aw_burst_len;
wire [AXI_ADDR_WIDTH-1:0] axi_aw_addr;
wire axi_w_valid;
wire axi_w_ready;
wire [AXI_DATA_WIDTH-1:0] axi_w_data;
wire axi_w_last;
wr_ctrl #(
.USER_WR_DATA_WIDTH(USER_WR_DATA_WIDTH),
.AXI_DATA_WIDTH(AXI_DATA_WIDTH),
.AXI_ADDR_WIDTH(AXI_ADDR_WIDTH),
.WR_BURST_LENGTH(1024)
) wr_ctrl (
.clk (user_wr_clk),
.reset (reset),
.ddr_init_done (ddr_init_done),
.user_wr_en (user_wr_en),
.user_wr_data (user_wr_data),
.user_wr_base_addr (user_wr_base_addr),
.user_wr_end_addr (user_wr_end_addr),
.wr_req_en (wr_req_en),
.wr_burst_length (wr_burst_length),
.wr_data_addr (wr_data_addr),
.wr_data_valid (wr_data_valid),
.wr_data_out (wr_data_out),
.wr_data_last (wr_data_last)
);
wr_buffer #(
.AXI_DATA_WIDTH(AXI_DATA_WIDTH),
.AXI_ADDR_WIDTH(AXI_ADDR_WIDTH)
) wr_buffer (
.clk (user_wr_clk),
.axi_clk (axi_clk),
.reset (reset),
.wr_req_en (wr_req_en),
.wr_burst_length (wr_burst_length),
.wr_data_addr (wr_data_addr),
.wr_data_valid (wr_data_valid),
.wr_data_in (wr_data_out),
.wr_data_last (wr_data_last),
.axi_aw_req_en (axi_aw_req_en),
.axi_aw_ready (axi_aw_ready),
.axi_aw_burst_len (axi_aw_burst_len),
.axi_aw_addr (axi_aw_addr),
.axi_w_valid (axi_w_valid),
.axi_w_ready (axi_w_ready),
.axi_w_data (axi_w_data),
.axi_w_last (axi_w_last)
);
axi_wr_master #(
.AXI_DATA_WIDTH(AXI_DATA_WIDTH),
.AXI_ADDR_WIDTH(AXI_ADDR_WIDTH)
) axi_wr_master (
.axi_clk (axi_clk),
.reset (reset),
.axi_aw_req_en (axi_aw_req_en),
.axi_aw_ready (axi_aw_ready),
.axi_aw_burst_len (axi_aw_burst_len),
.axi_aw_addr (axi_aw_addr),
.axi_w_valid (axi_w_valid),
.axi_w_ready (axi_w_ready),
.axi_w_data (axi_w_data),
.axi_w_last (axi_w_last),
.m_axi_awvalid (m_axi_awvalid),
.m_axi_awready (m_axi_awready),
.m_axi_awaddr (m_axi_awaddr),
.m_axi_awid (m_axi_awid),
.m_axi_awlen (m_axi_awlen),
.m_axi_awburst (m_axi_awburst),
.m_axi_awsize (m_axi_awsize),
.m_axi_awport (m_axi_awport),
.m_axi_awqos (m_axi_awqos),
.m_axi_awlock (m_axi_awlock),
.m_axi_awcache (m_axi_awcache),
.m_axi_wvalid (m_axi_wvalid),
.m_axi_wready (m_axi_wready),
.m_axi_wdata (m_axi_wdata),
.m_axi_wstrb (m_axi_wstrb),
.m_axi_wlast (m_axi_wlast),
.m_axi_bid (m_axi_bid),
.m_axi_bresp (m_axi_bresp),
.m_axi_bvalid (m_axi_bvalid),
.m_axi_bready (m_axi_bready)
);
endmodule
2.5 部分仿真时序图

3.读通道实现
3.1 rd_ctrl模块
基本上与wr_ctrl模块类似,主要注意几个区别:
1.状态机由读请求的上升沿在ddr初始化完成之后启动;
2.给用户端读忙的信号,只要不处于初始态 user_rd_req_busy
3.wr-buffer输入fifo是否写满的信号 rd_req_ready
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Description: 读控制
//////////////////////////////////////////////////////////////////////////////////
module rd_ctrl#(
parameter AXI_DATA_WIDTH = 128,
parameter AXI_ADDR_WIDTH = 32,
parameter RD_BURST_LENGTH = 4096
)(
input clk , //用户端读时钟
input reset ,
/*-----DDR初始化完成信号,与MIG核交互------------------*/
input ddr_init_done ,
/*--------------用户端读请求信号----------------------*/
input user_rd_req , //上升沿触发用户读请求
input [AXI_ADDR_WIDTH-1:0] user_rd_base_addr , //一定要被4096整除
input [AXI_ADDR_WIDTH-1:0] user_rd_end_addr , //一定要被4096整除
output user_rd_req_busy , //用户读请求忙标志
/*--------------与rd_buffer模块交互信号---------------*/
output reg rd_req_en ,
output [7:0] rd_burst_length ,
output reg [AXI_ADDR_WIDTH-1:0] rd_data_addr ,
input rd_req_ready //用来指示rd_buffer模块里面的cmd_fifo是否爆满
);
localparam MAX_BURST_LENGTH = RD_BURST_LENGTH / (AXI_DATA_WIDTH/8) - 1;
/*--------------------------------------------------*\
定义状态机
\*--------------------------------------------------*/
reg [1:0] cur_status;
reg [1:0] nxt_status;
localparam RD_IDLE = 2'b00;
localparam RD_REQ = 2'b01;
localparam RD_END = 2'b10;
/*--------------------------------------------------*\
CDC
\*--------------------------------------------------*/
(* dont_touch ="true" *) reg reset_sync_d0;
(* dont_touch ="true" *) reg reset_sync_d1;
(* dont_touch ="true" *) reg reset_sync;
reg ddr_init_done_d0;
reg ddr_init_done_d1;
reg ddr_rd_enable ;
reg user_rd_req_d0 ;
reg user_rd_req_d1 ;
reg rd_req_trig ;
wire user_rd_req_rise;
always @(posedge clk) begin
reset_sync_d0 <= reset;
reset_sync_d1 <= reset_sync_d0;
reset_sync <= reset_sync_d1;
end
always @(posedge clk) begin
ddr_init_done_d0 <= ddr_init_done;
ddr_init_done_d1 <= ddr_init_done_d0;
ddr_rd_enable <= ddr_init_done_d1;
end
always @(posedge clk) begin
user_rd_req_d0 <= user_rd_req;
user_rd_req_d1 <= user_rd_req_d0;
end
assign user_rd_req_rise = user_rd_req_d0 & ~user_rd_req_d1;
always @(posedge clk) begin
if (ddr_rd_enable)
rd_req_trig <= user_rd_req_rise;
else
rd_req_trig <= 0;
end
/*--------------------------------------------------*\
状态机
\*--------------------------------------------------*/
always @(posedge clk) begin
if (reset_sync)
cur_status <= RD_IDLE;
else
cur_status <= nxt_status;
end
always @(*) begin
if (reset_sync)
nxt_status <= RD_IDLE;
else begin
case(cur_status)
RD_IDLE : begin
if (rd_req_trig)
nxt_status <= RD_REQ;
else
nxt_status <= cur_status;
end
RD_REQ : begin
if (rd_req_en && rd_req_ready)
nxt_status <= RD_END;
else
nxt_status <= cur_status;
end
RD_END : begin
nxt_status <= RD_IDLE;
end
default : nxt_status <= RD_IDLE;
endcase
end
end
/*--------------------------------------------------*\
rd req
\*--------------------------------------------------*/
always @(posedge clk) begin
if (rd_req_en && rd_req_ready)
rd_req_en <= 0;
else if (cur_status == RD_REQ)
rd_req_en <= 1;
else
rd_req_en <= rd_req_en;
end
always @(posedge clk) begin
if (reset_sync)
rd_data_addr <= user_rd_base_addr;
else if (rd_req_en && rd_req_ready && rd_data_addr >= user_rd_end_addr - RD_BURST_LENGTH)
rd_data_addr <= user_rd_base_addr;
else if (rd_req_en && rd_req_ready)
rd_data_addr <= rd_data_addr + RD_BURST_LENGTH;
end
assign user_rd_req_busy = cur_status != RD_IDLE;
assign rd_burst_length = MAX_BURST_LENGTH;
endmodule
3.2 rd_buffer模块
同理:仍然与wr_buffer对比
1. rd_cmd_fifo
通过 cmd_wrcount 给出 fifo是否写满的信号 rd_req_ready,在这个前提下cmd开始写入fifo
cmd_rden由下游axi_master的握手信号控制
2. rd_data_fifo
由下游axi_master 数据写入 (axi_clk)data_wren/data_din;
data_rden 需要data_fifo非空,并且不在data_fifo非读,一周期拉低
rd_data_fifo_out :在data_rden一次性读取位宽128的数据,然后在rd_data_flag进行数据的移位
rd_data_fifo_last:通过data_dout[64]判断是否为1,持续最后一个128位宽数据时间
3. 状态机起始跳转需要cmd非空而且data_fifo没写满;
结束跳转的条件
4.最后将rd_data_fifo_out截取16位给到用户端口,同步数据有效,rd_data_fifo_last计数最后一个移位数据给到用户端口
3.3 axi_rd_master模块
m_axi控制拉低,axi_控制拉高
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Description:读通道
/////////////////////////////////////////////////////////////////////////////////
module axi_rd_channel#(
parameter AXI_DATA_WIDTH = 128,
parameter AXI_ADDR_WIDTH = 32 ,
parameter USER_RD_DATA_WIDTH = 16
)(
input user_rd_clk , //用户端读时钟
input axi_clk , //axi的时钟
input reset ,
input ddr_init_done ,
/*--------------用户端读请求信号----------------------*/
input user_rd_req , //上升沿触发用户读请求
input [AXI_ADDR_WIDTH-1:0] user_rd_base_addr , //一定要被4096整除
input [AXI_ADDR_WIDTH-1:0] user_rd_end_addr , //一定要被4096整除
output user_rd_req_busy , //用户读请求忙标志
output user_rd_valid ,
output user_rd_last ,
output [USER_RD_DATA_WIDTH-1:0] user_rd_data ,
/*-------------AXI读通道端口信号---------------------*/
output m_axi_arvalid ,
input m_axi_arready ,
output [AXI_ADDR_WIDTH-1:0] m_axi_araddr ,
output [3:0] m_axi_arid ,
output [7:0] m_axi_arlen ,
output [1:0] m_axi_arburst ,
output [2:0] m_axi_arsize ,
output [2:0] m_axi_arport ,
output [3:0] m_axi_arqos ,
output m_axi_arlock ,
output [3:0] m_axi_arcache ,
input [3:0] m_axi_rid ,
input m_axi_rvalid ,
output m_axi_rready ,
input [AXI_DATA_WIDTH-1:0] m_axi_rdata ,
input m_axi_rlast ,
input [1:0] m_axi_rresp
);
wire rd_req_en ;
wire [7:0] rd_burst_length ;
wire [AXI_ADDR_WIDTH-1:0] rd_data_addr ;
wire rd_req_ready ;
wire axi_ar_req_en ;
wire axi_ar_ready ;
wire [7:0] axi_ar_burst_len;
wire [AXI_ADDR_WIDTH-1:0] axi_ar_addr ;
wire axi_r_valid ;
wire [AXI_DATA_WIDTH-1:0] axi_r_data ;
wire axi_r_last ;
rd_ctrl #(
.AXI_DATA_WIDTH(AXI_DATA_WIDTH),
.AXI_ADDR_WIDTH(AXI_ADDR_WIDTH),
.RD_BURST_LENGTH(1024)
) rd_ctrl (
.clk (user_rd_clk),
.reset (reset),
.ddr_init_done (ddr_init_done),
.user_rd_req (user_rd_req),
.user_rd_base_addr (user_rd_base_addr),
.user_rd_end_addr (user_rd_end_addr),
.user_rd_req_busy (user_rd_req_busy),
.rd_req_en (rd_req_en),
.rd_burst_length (rd_burst_length),
.rd_data_addr (rd_data_addr),
.rd_req_ready (rd_req_ready)
);
rd_buffer #(
.AXI_DATA_WIDTH(AXI_DATA_WIDTH),
.AXI_ADDR_WIDTH(AXI_ADDR_WIDTH),
.USER_RD_DATA_WIDTH(USER_RD_DATA_WIDTH)
) rd_buffer (
.clk (user_rd_clk),
.axi_clk (axi_clk),
.reset (reset),
.rd_req_en (rd_req_en),
.rd_burst_length (rd_burst_length),
.rd_data_addr (rd_data_addr),
.rd_req_ready (rd_req_ready),
.axi_ar_req_en (axi_ar_req_en),
.axi_ar_ready (axi_ar_ready),
.axi_ar_burst_len (axi_ar_burst_len),
.axi_ar_addr (axi_ar_addr),
.axi_r_valid (axi_r_valid),
.axi_r_data (axi_r_data),
.axi_r_last (axi_r_last),
.user_rd_valid (user_rd_valid),
.user_rd_last (user_rd_last),
.user_rd_data (user_rd_data)
);
axi_rd_master #(
.AXI_DATA_WIDTH(AXI_DATA_WIDTH),
.AXI_ADDR_WIDTH(AXI_ADDR_WIDTH)
) axi_rd_master (
.axi_clk (axi_clk),
.reset (reset),
.axi_ar_req_en (axi_ar_req_en),
.axi_ar_ready (axi_ar_ready),
.axi_ar_burst_len (axi_ar_burst_len),
.axi_ar_addr (axi_ar_addr),
.axi_r_valid (axi_r_valid),
.axi_r_data (axi_r_data),
.axi_r_last (axi_r_last),
.m_axi_arvalid (m_axi_arvalid),
.m_axi_arready (m_axi_arready),
.m_axi_araddr (m_axi_araddr),
.m_axi_arid (m_axi_arid),
.m_axi_arlen (m_axi_arlen),
.m_axi_arburst (m_axi_arburst),
.m_axi_arsize (m_axi_arsize),
.m_axi_arport (m_axi_arport),
.m_axi_arqos (m_axi_arqos),
.m_axi_arlock (m_axi_arlock),
.m_axi_arcache (m_axi_arcache),
.m_axi_rid (m_axi_rid),
.m_axi_rvalid (m_axi_rvalid),
.m_axi_rready (m_axi_rready),
.m_axi_rdata (m_axi_rdata),
.m_axi_rlast (m_axi_rlast),
.m_axi_rresp (m_axi_rresp)
);
endmodule
4.仿真实现









三个信号同步



1万+





