基于 DDR3 的串口传图帧缓存系统设计实现(fifo_mig_axi_fifo )


前言

结合串口接收模块和 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模块状态机设计如下图所示。
1

上电初始状态为 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[
  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C.V-Pupil

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值