前言
结合串口接收模块和 tft 显示屏控制模块,设计一个基于 DDR3 的串口传图帧缓存系统。
提示:以下是本篇文章正文内容,下面案例可供参考
一、接口转换模块设计
fifo_mig_axi_fifo模块是系统中相对比较重要的模块,涉及到与 DDR 控制器接口对接。该模块的主要是实现接口的转换,将普通的 FIFO 接口转换成 AXI 接口,用于将 FIFO 里的数据读出然后存储在 DDR 存储器以及将 DDR 存储器读出的数据存放到 FIFO 缓存。
AXI 接口包括 5 个通道,分为写事务和读事务。考虑模块设计实现的简单性(AXI 协议支持复杂的乱序读写操作等,这里就不做考虑),将一次完整的写事务流程规定为○1 主机向写地址通道写入地址和控制信息——>○2 写数据通道突发写入数据——>○3 收到设备的写数据响应。一次完整的读事务流程规定为○1 主机向读地址通道写入地址和控制信息——>○2 收到设备的读数据响应和读的数据。对于 DDR 控制器 mig_7series_0 模块,需要等到 init_calib_complete 为高后,才能进行
读/写操作。读/写操作不可同时进行,对读/写操作就需要有一个判断仲裁的过程,fifo_mig_axi_fifo模块状态机设计如下图所示。
上电初始状态为 IDLE 状态,当 DDR 完成初始化和校准(即 init_calib_complete 变为高电平)后进入读/写仲裁状态 ARB;在该状态根据是否有读/写操作请求跳转到读/写流程的各个状态;完成一次读/写流程后,状态回到 ARB 状态进行下一次的操作。状态机采用三段式,第一二段的代码如下。
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
curr_state <= S_IDLE;
else
curr_state <= next_state;
end
always@(*)
begin
case(curr_state)
//具体状态转移见下
endcase
end
用户侧逻辑的时钟和复位信号采用的 DDR 控制器 mig_7series_0 模块输出的 ui_clk 和
ui_clk_sync_rst。状态机在上电复位处于初始状态 IDLE,当 DDR 完成初始化和校准后进入
读/写仲裁状态 ARB,具体代码如下。
S_IDLE:
begin
if(init_calib_complete)
next_state = S_ARB;
else
next_state = S_IDLE;
end
在读/写仲裁状态 ARB,根据当前的读/写请求跳转到读/写操作流程中的地址通道的操作。这里为了设计简单化,将写操作优先级高于读优先级。
IDLE_transform_ABR = ((curr_state == S_IDLE) && (mmcm_locked) && (init_calib_complete)),
ARB_transform_WR_ADDR = ((curr_state == S_ARB ) && (wr_ddr3_req == 1'b1) ),
ABR_transform_RD_ADDR = ((curr_state == S_ARB ) && (rd_ddr3_req == 1'b1) ),
S_ARB:begin
if(ARB_transform_WR_ADDR)
next_state <= S_WR_ADDR;
else if(ABR_transform_RD_ADDR)
next_state <= S_RD_ADDR;
else
next_state <= curr_state;
end
当在 ARB 状态出现写操作请求后,进入到 AXI 写地址通道的操作状态 S_WR_ADDR,在该状态,传输写操作的地址和控制信息,当 awready 和 awvalid 同时为高时表明地址已经传输完成,进入到写操作的写数据通道的操作。
WR_ADDR_transform_WR_DATA = ((curr_state == S_WR_ADDR) && s_axi_awready && s_axi_awvalid),
S_WR_ADDR:begin
if(WR_ADDR_transform_WR_DATA)
next_state <= S_WR_DATA;
else
next_state <= curr_state;
end
在对写操作的写数据通道的操作中,当主机写完最后一个数据后,进入到等待写响应的状态。
WR_DATA_transform_WR_RESP = ((curr_state == S_WR_DATA) && s_axi_wready && s_axi_wvalid && s_axi_wlast),
S_WR_DATA:begin
if(WR_DATA_transform_WR_RESP)
next_state <= S_WR_RESP;
else
next_state <= curr_state;
end
在等待写响应状态,当主机接收到设备的写响应后,一次完整的写操作流程完成,状态回到仲裁状态进行下一次的操作,bresp 不同值表示不同的响应结果,bresp 为 2’b00 表示写数据成功,bid 需要与写地址通道传输的 awbid 一致。
WR_RESP_transform_ABR = ((curr_state == S_WR_RESP) && s_axi_bready && s_axi_bvalid && (s_axi_bresp == 2'b00) && (s_axi_bid == AXI_ID)),
WR_RESP_transform_IDLE = ((curr_state == S_WR_RESP) && s_axi_bready && s_axi_bvalid) ,
S_WR_RESP:begin
if(WR_RESP_transform_ABR)
next_state <= S_ARB;
else if(WR_RESP_transform_IDLE)
next_state <= S_IDLE;
else
next_state <= curr_state;
end
当在 ARB 状态出现读操作请求(此时无写请求)后,进入到 AXI 读地址通道的操作状态 S_RD_ADDR,在该状态,传输读操作的地址和控制信息,当 arready 和 arvalid 同时为高
时表明地址已经传输完成,进入到读操作的读响应通道的操作。
RD_ADDR_transform_RD_RESP = ((curr_state == S_RD_ADDR) && s_axi_arready && s_axi_arvalid),
S_RD_ADDR:begin
if(RD_ADDR_transform_RD_RESP)
next_state <= S_RD_RESP;
else
next_state <= curr_state;
end
在等待读响应状态,当主机接收到设备的读响应后,一次完整的读操作流程完成,状态
回到仲裁状态进行下一次的操作,bresp 不同值表示不同的响应结果,bresp 为 2’b00 表示读
数据成功,last 表示读取的最后一个数据的标识,rid 需要与读地址通道传输的 arbid 一致。
RD_RESP_transform_ARB = ((curr_state == S_RD_RESP) && s_axi_rready && s_axi_rvalid && s_axi_rlast && (s_axi_rresp == 2'b00) && (s_axi_rid == AXI_ID)),
RD_RESP_transform_IDLE = ((curr_state == S_RD_RESP) && s_axi_rready && s_axi_rvalid && s_axi_rlast );
S_RD_RESP:begin
if(RD_RESP_transform_ARB)
next_state <= S_ARB;
else if(RD_RESP_transform_IDLE)
next_state <= S_IDLE;
else
next_state <= curr_state;
end
状态机设计完成后,剩下的就是在各个状态中产生各种信号。对于 AXI 接口的一些信号,在写操作的写地址通道比较关键的是产生 awaddr 和 awvalid。其中,awaddr 除了在复位和清除时变为起始地址外,在完成一次写操作流程后,地址就需要增加一次突发写入的数据量,需要注意的是这里的地址是以字节为单位的,则每次地址增加量应该是突发写数据个数*每个数据的字节数。这里每次突发长读为 AWLEN 加 1,每个数据是 16 字节(数据位宽是128bit),所以每完成一次写操作,地址增加(m_axi_awlen + 1’b1)*16。
always @ (posedge ui_clk or negedge ui_clk_sync_rst)
if(ui_clk_sync_rst)
s_axi_awaddr <= WR_DDR_ADDR_BEGIN; //WR_DDR_ADDR_BEGIN = 0;
else if(wrfifo_rst) //wr_ddr3_fifo中的rst
s_axi_awaddr <= WR_DDR_ADDR_BEGIN;
else if(s_axi_awaddr >= WR_DDR_ADDR_END) //WR_DDR_ADDR_END根据实际设计【长*宽*位宽/8】(图片的(长*宽*位宽 = AXI_LEN*16*8*X)
s_axi_awaddr <= WR_DDR_ADDR_BEGIN;
else if(WR_RESP_transform_ABR)
s_axi_awaddr <= s_axi_awaddr + ((s_axi_awlen + 1'b1)<<4);
else
s_axi_awaddr <= s_axi_awaddr;
对于 awvalid 产生就相对简单些,在进入 WR_ADDR 状态到就将其输出为高,等到awready 和 awvalid 同时高的时候,就将 awvalid 输出为低,保证 awready 和 awvalid 信号只有一个时钟周期的同时高。
always @ (posedge ui_clk or negedge ui_clk_sync_rst)
if(ui_clk_sync_rst)
s_axi_awvalid <= 0;
else if(WR_ADDR_transform_WR_DATA)
s_axi_awvalid <= 0;
else if(curr_state == S_WR_ADDR)
s_axi_awvalid <= 1;
else
s_axi_awvalid <= s_axi_awvalid ;
在写操作的写数据通道比较关键的是产生 wvalid 和 wlast 信号。wvalid 在进入到WR_DATA 状态就变为高电平,在发送完最后一个数据后变为低电平。(期间,在主机给出的 wvalid 与设备给出的 wready 信号同时为高的情况下,数据写入到设备)
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
s_axi_wvalid <= 1'b0;
else if(WR_DATA_transform_WR_RESP)
s_axi_wvalid <= 1'b0;
else if(curr_state == S_WR_DATA)
s_axi_wvalid <= 1'b1;
else
s_axi_wvalid <= s_axi_wvalid;
end
wlast 信号是主机向设备传输最后一个数据的标识信号,这个信号的产生依赖于一次突发写入数据个数和当前已经传输了几个数据,主机在传输最后一个数据同时将其输出为高,在发送完最后一个数据后立马将其输出为低。这个过程首先需要对传输数据个数进行计数,当 wready 和 m_axi_wvalid 同时为高时代表传输一个数据,传输数据个数计数器代码如下。
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
wr_data_cnt <= 1'b0;
else if(curr_state == S_ARB)
wr_data_cnt <= 1'b0;
else if(curr_state == S_WR_DATA && m_axi_wready && m_axi_wvalid)
wr_data_cnt <= wr_data_cnt + 1'b1;
else
wr_data_cnt <= wr_data_cnt;
end
在产生 wlast 时,分两种情况,一是当突发写数据个数为 1,也就是 wlen 等于 0 时,那么传输的第一个数就是传输的最后一个数据,这种情况下,一进入到 WR_DATA 状态就将wlast 变为高电平;二是当突发写数据个数大于 1,也就是 wlen 等于等于 1 时,就在传输完倒数第二个数(即 wr_data_cnt 为 m_axi_awlen -1’b1)后将 wlast 变为高电平。当最后一个数据传输完成(m_axi_wready、m_axi_wvalid 和 m_axi_wlast 同时为高电平)后将 wlast 变为低,具体代码如下。
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
m_axi_wlast <= 1'b0;
else if(curr_state == S_WR_DATA && m_axi_wready && m_axi_wvalid && m_axi_wlast)
m_axi_wlast <= 1'b0;
else if(curr_state == S_WR_DATA && m_axi_awlen == 8'd0)
m_axi_wlast <= 1'b1;
else if(curr_state == S_WR_DATA && m_axi_wready && m_axi_wvalid && (wr_data_cnt ==
m_axi_awlen -1'b1))
m_axi_wlast <= 1'b1;
else
m_axi_wlast <= m_axi_wlast;
end
在读操作的写地址通道信号的产生上与写操作类似。比较关键的是产生 araddr 和 arvalid;
产生过程与 awaddr 和 awvalid 类似。awaddr 除了在复位和清除时变为起始地址外,在完成
一次读操作流程后,地址就需要增加一次突发写入的数据量。即每完成一次读操作,地址增
加(m_axi_arlen + 1’b1)*16,计算与写操作一样。具体代码如下。
//s_axi_araddr
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
s_axi_araddr <= RD_DDR_ADDR_BEGIN;
else if(rdfifo_rst)
s_axi_araddr <= RD_DDR_ADDR_BEGIN;
else if(s_axi_araddr >= RD_DDR_ADDR_END)
s_axi_araddr <= RD_DDR_ADDR_BEGIN;
else if(RD_RESP_transform_ARB)
s_axi_araddr <= s_axi_araddr + ((s_axi_arlen + 1'b1)<<4);
else
s_axi_araddr <= s_axi_araddr;
end
//s_axi_arvalid
always@(posedge ui_clk or posedge ui_clk_sync_rst)
begin
if(ui_clk_sync_rst)
s_axi_arvalid <= 1'b0;
else if(RD_ADDR_transform_RD_RESP)
s_axi_arvalid <= 1'b0;
else if(curr_state == S_RD_ADDR)
s_axi_arvalid <= 1'b1;
else
s_axi_arvalid <= s_axi_arvalid;
end
对于 AXI 接口信号,除了上述信号外,还有一些信号相对比较简单,基本就是给固定值就可以,这里直接给出代码。
assign m_axi_awid = AXI_ID ; //output [3:0] m_axi_awid
assign m_axi_awsize = 3'b100 ; //output [2:0] m_axi_awsize
assign m_axi_awburst = 2'b01 ; //output [1:0] m_axi_awburst
assign m_axi_awlock = 1'b0 ; //output [0:0] m_axi_awlock
assign m_axi_awcache = 4'b0000 ; //output [3:0] m_axi_awcache
assign m_axi_awprot = 3'b000 ; //output [2:0] m_axi_awprot
assign m_axi_awqos = 4'b0000 ; //output [3:0] m_axi_awqos
assign m_axi_awlen = AXI_LEN ;
assign m_axi_wstrb = 16'hffff ; //output [15:0] m_axi_wstrb
assign m_axi_wdata = wr_fifo_rddata;
assign m_axi_bready = 1'b1 ; //output m_axi_bready
assign m_axi_arid = AXI_ID ; //output [3:0] m_axi_arid
assign m_axi_arsize = 3'b100 ; //output [2:0] m_axi_arsize
assign m_axi_arburst = 2'b01 ; //output [1:0] m_axi_arburst
assign m_axi_arlock = 1'b0 ; //output [0:0] m_axi_arlock
assign m_axi_arcache = 4'b0000 ; //output [3:0] m_axi_arcache
assign m_axi_arprot = 3'b000 ; //output [2:0] m_axi_arprot
assign m_axi_arqos = 4'b0000 ; //output [3:0] m_axi_arqos
assign m_axi_arlen = AXI_LEN ;
assign m_axi_rready = ~rd_fifo_alfull; //output m_axi_rready
关于 AXI 接口的信号设计完后,剩下的就是与读写 FIFO 之间的接口,包括读写使能和读写数据。
assign wr_fifo_rdreq = m_axi_wvalid && m_axi_wready;
assign rd_fifo_wrreq = m_axi_rvalid && m_axi_rready;
assign rd_fifo_wrdata = m_axi_rdata;
控制状态机条状的读写 DDR 请求信号是根据当前 FIFO 中数据量进行判断产生,当写FIFO 中的数据量超过一个阈值(这里阈值使用 WLEN,)就产生写 DDR 请求;当读 FIFO中的数据量低于一个阈值(这里阈值使用 RLEN,也可以设置为其他值)就产生读 DDR 请求,信号产生需要满足在 DDR 初始化完成之后并且当前 FIFO 不处于复位。
assign wr_ddr3_req = (init_calib_complete == 1'b1) && (wr_fifo_rst_busy == 1'b0) &&
(wr_fifo_rd_cnt >= m_axi_awlen);
assign rd_ddr3_req = (init_calib_complete == 1'b1) && (rd_fifo_rst_busy == 1'b0) && (rd_fifo_wr_cnt <
m_axi_arlen);
至此关于fifo_mig_axi_fifo模块的设计就完成。完整代码分享:
`timescale 1ns / 1ps
//
// Create Date: 2023/06/20 08:58:45
// Design Name:
// Module Name: fifo_mig_axi_fifo
// Name: 小王在努力...
// Target Devices:
// Tool Versions: Vivado 2018.3
// Revision 0.01 - File Created
// Additional Comments:
//
//
//
module fifo_mig_axi_fifo
#(
parameter WR_DDR_ADDR_BEGIN = 0 ,
parameter WR_DDR_ADDR_END = 1024*2 ,
parameter RD_DDR_ADDR_BEGIN = 0 ,
parameter RD_DDR_ADDR_END = 1024*2 ,
parameter AXI_ID = 4'b0000
)
(
//wr_ddr3_fifo ports
input wrfifo_rst ,
input loc_clk50M ,
input [15:0]wrfifo_din ,
input wrfifo_wren ,
//rd_ddr3_fifo ports
input rdfifo_rst ,
input loc_clk33M ,
input rdfifo_rden ,
output [15:0]rdfifo_dout ,
//DDR3 Interface
//input
input loc_clk200M ,
input xx_sys_rst , //用于连接 pll_locked
input xx_aresetn , //用于连接 pll_locked
//output
output ui_clk ,
output ui_clk_sync_rst ,
output mmcm_locked ,
output init_calib_complete ,
//DDR3 Interface
// Inouts
inout [15:0] ddr3_dq ,
inout [1:0] ddr3_dqs_n ,
inout [1:0] ddr3_dqs_p ,
// Outputs
output [13:0] ddr3_addr ,
output [2:0] ddr3_ba ,
output ddr3_ras_n ,
output ddr3_cas_n ,
output ddr3_we_n ,
output ddr3_reset_n ,
output [0:0] ddr3_ck_p ,
output [0:0] ddr3_ck_n ,
output [0:0] ddr3_cke ,
output [0:0] ddr3_cs_n ,
output [1:0] ddr3_dm ,
output [0:0] ddr3_odt
);
//------------------------------------------
//状态机参数
localparam S_IDLE = 7'b0000001 ,
S_ARB = 7'b0000010 ,
S_WR_ADDR = 7'b0000100 ,
S_WR_DATA = 7'b0001000 ,
S_WR_RESP = 7'b0010000 ,
S_RD_ADDR = 7'b0100000 ,
S_RD_RESP = 7'b1000000 ;
//------------------------------------------
//**********************************
//信号定义说明
//**********************************
//wrfifo ports
wire[127:0]wrfifo_dout ;
wire wrfifo_rden ;
wire wrfifo_wr_rst_busy ;
wire wrfifo_rd_rst_busy ;
wire [6:0]wrfifo_rd_cnt ;
//rdfifo ports
wire[127:0]rdfifo_din ;
wire rdfifo_wren ;
wire rdfifo_wr_rst_busy ;
wire rdfifo_rd_rst_busy ;
wire [6:0]rdfifo_wr_cnt ;
//write addres ports
reg [27:0]s_axi_awaddr ;
wire[7:0] s_axi_awlen ;
reg s_axi_awvalid ;
wire s_axi_awready ;
//write data ports
wire[127:0]s_axi_wdata ;
wire s_axi_wready ;
reg s_axi_wvalid ;
reg s_axi_wlast ;
reg [7:0]wr_data_cnt ;
//write response ports
wire s_axi_bvalid ;
wire [1:0]s_axi_bresp ;
wire [3:0]s_axi_bid ;
wire s_axi_bready ;
//read address ports
reg [27:0]s_axi_araddr ;
wire s_axi_arready ;
reg s_axi_arvalid ;
wire[7:0] s_axi_arlen ;
//read data ports
wire[