FPGA学习心得——异步FIFO

 

`timescale 1ns/1ns

/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
                       parameter WIDTH = 8)
                     (input wclk,
                       input wenc,
                       input [$clog2(DEPTH)-1:0] waddr, input [WIDTH-1:0] wdata, input rclk, input renc, input [$clog2(DEPTH)-1:0] raddr, output reg [WIDTH-1:0] rdata);
    
    reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
    
	always @(posedge wclk) begin
	if (wenc)
	RAM_MEM[waddr] <= wdata;
	end

	always @(posedge rclk) begin
    if (renc)
        rdata <= RAM_MEM[raddr];
        end
    
endmodule
    
 /***************************************AFIFO*****************************************/
 module asyn_fifo#(
     parameter	WIDTH = 8,
     parameter 	DEPTH = 16
     )(
     input 					wclk	,
     input 					rclk	,
     input 					wrstn	,
     input					rrstn	,
     input 					winc	,
     input 			 		rinc	,
     input 		[WIDTH-1:0]	wdata	,
     
     output wire				wfull	,
     output wire				rempty	,
     output wire [WIDTH-1:0]	rdata
     );
     reg [$clog2(DEPTH):0] raddr_ptr,waddr_ptr;
     always@(posedge rclk or negedge rrstn)begin
         if (!rrstn)
             raddr_ptr <= 0;
         else if (~rempty&rinc)
             raddr_ptr <= raddr_ptr+1'b1;
         else
             raddr_ptr <= raddr_ptr;
         
     end
     
     always@(posedge wclk or negedge wrstn)begin
         if (!wrstn)
             waddr_ptr <= 0;
         else if (~wfull&winc)
             waddr_ptr <= waddr_ptr+1'b1;
         else
             waddr_ptr <= waddr_ptr  ;
     end
     
     wire [$clog2(DEPTH):0]    waddr_ptr_gray,raddr_ptr_gray;
     reg [$clog2(DEPTH):0]    waddr_ptr_gray1,raddr_ptr_gray1;
     reg [$clog2(DEPTH):0]    waddr_ptr_gray2,raddr_ptr_gray2;
     
     reg [$clog2(DEPTH):0]    waddr_ptr_gray3,raddr_ptr_gray3;
     
     assign  waddr_ptr_gray = (waddr_ptr>>1)^ waddr_ptr;
     assign  raddr_ptr_gray = (raddr_ptr>>1)^ raddr_ptr;
     
     
     
     always@(posedge rclk or negedge rrstn)begin
         if (!rrstn)begin
             raddr_ptr_gray1 <= 0;
             
         end
         else begin
             raddr_ptr_gray1 <= raddr_ptr_gray;
             
         end
         
     end
     always@(posedge wclk or negedge wrstn)begin
         if (!wrstn)begin
             waddr_ptr_gray1 <= 0;
             
         end
         else begin
             waddr_ptr_gray1 <= waddr_ptr_gray;
             
         end
         
     end
     always@(posedge wclk or negedge wrstn)begin
         if (!wrstn)begin
             
             raddr_ptr_gray2 <= 0;
             raddr_ptr_gray3 <= 0;
         end
         else begin
             
             raddr_ptr_gray2 <= raddr_ptr_gray1;
             raddr_ptr_gray3 <= raddr_ptr_gray2;
         end
         
     end
     always@(posedge rclk or negedge rrstn)begin
         if (!rrstn)begin
             
             waddr_ptr_gray2 <= 0;
             waddr_ptr_gray3 <= 0;
         end
         else begin
             
             waddr_ptr_gray2 <= waddr_ptr_gray1;
             waddr_ptr_gray3 <= waddr_ptr_gray2;
         end
         
     end
     assign rempty   = (raddr_ptr_gray1 == waddr_ptr_gray3)?1:0;
     // assign wfull = (waddr_ptr_gray[$clog2(DEPTH):$clog2(DEPTH)-1]! = raddr_ptr_gray2[$clog2(DEPTH):$clog2(DEPTH)-1]&&  waddr_ptr_gray[$clog2(DEPTH)-2:0] == raddr_ptr_gray2[$clog2(DEPTH)-2:0]);
     assign wfull    = (waddr_ptr_gray1 == {~raddr_ptr_gray3[$clog2(DEPTH):$clog2(DEPTH)-1], raddr_ptr_gray3[$clog2(DEPTH)-2:0]});
     wire wenc, renc;
     assign wenc = winc & !wfull;
     assign renc = rinc & !rempty;
     
     dual_port_RAM    dual_port_RAM
     (
     . wclk(wclk),
     . wenc(wenc),
     .waddr(waddr_ptr[$clog2(DEPTH)-1:0]),
     .wdata(wdata),
     .rclk(rclk),
     .renc(renc),
     .raddr (raddr_ptr[$clog2(DEPTH)-1:0]),
     .rdata(rdata)
     );
     
 endmodule

FIFO总共可以分成五部写,第一部分读写指针:

 reg [$clog2(DEPTH):0] raddr_ptr,waddr_ptr;
    always@(posedge rclk or negedge rrstn)begin
        if(!rrstn)
            raddr_ptr<=0;
        else if(~rempty&rinc)
            raddr_ptr<=raddr_ptr+1'b1;
        else
            raddr_ptr<=raddr_ptr;
            
    end
        
        always@(posedge wclk or negedge wrstn)begin
        if(!wrstn)
            waddr_ptr<=0;
            else if(~wfull&winc)
            waddr_ptr<=waddr_ptr+1'b1;
        else
            waddr_ptr<=waddr_ptr  ;
    end

读地址的格雷码打两拍同步到写时钟域,再和写地址对比(组合逻辑就行)得到满信号;写地址的格雷码同步到读时钟域,再和读地址对比得到空信号。满信号产生在写时钟域,直接输出就行,不用再打拍;空信号同理。地址的转换:

        wire [$clog2(DEPTH):0]    waddr_ptr_gray,raddr_ptr_gray;
          reg [$clog2(DEPTH):0]    waddr_ptr_gray1,raddr_ptr_gray1;
            reg [$clog2(DEPTH):0]    waddr_ptr_gray2,raddr_ptr_gray2;
            
             reg [$clog2(DEPTH):0]    waddr_ptr_gray3,raddr_ptr_gray3;
             
            assign  waddr_ptr_gray=( waddr_ptr>>1)^ waddr_ptr;
            assign  raddr_ptr_gray=( raddr_ptr>>1)^ raddr_ptr;
            
            
            
             always@(posedge rclk or negedge rrstn)begin
             if(!rrstn)begin
            raddr_ptr_gray1<=0;
                 
            end
          else begin
          raddr_ptr_gray1<=raddr_ptr_gray;

            end
      
    end
              always@(posedge wclk or negedge wrstn)begin
           if(!wrstn)begin
            waddr_ptr_gray1<=0;
        
            end
            else begin
          waddr_ptr_gray1<=waddr_ptr_gray;

            end
      
    end
                    always@(posedge wclk or negedge wrstn)begin
              if(!wrstn)begin
           
               raddr_ptr_gray2<=0;
               raddr_ptr_gray3<=0;
                       end
            else begin
       
            raddr_ptr_gray2<=raddr_ptr_gray1;
             raddr_ptr_gray3<=raddr_ptr_gray2;
            end
      
    end
                    always@(posedge rclk or negedge rrstn)begin
                       if(!rrstn)begin
         
            waddr_ptr_gray2<=0;
            waddr_ptr_gray3<=0;
                       end
            else begin
         
            waddr_ptr_gray2<=waddr_ptr_gray1;
             waddr_ptr_gray3<=waddr_ptr_gray2;
            end
      
    end

 空满判断,这个地方我觉得很容易错:

assign rempty= (raddr_ptr_gray1==waddr_ptr_gray3)?1:0;
assign wfull=  (waddr_ptr_gray1=={~raddr_ptr_gray3[$clog2(DEPTH):$clog2(DEPTH)-1], raddr_ptr_gray3[$clog2(DEPTH)-2:0]});

这是拿vivado自己生成的测试文件改的: 

`timescale 1ns / 1ps



module asyn_fifo_tb( );

  //reg define
 reg sys_clk;
  reg sys_clk1;
 reg [7:0] pi_data ;
 reg pi_flag ;
 reg rd_en ;
 reg sys_rst_n ;
 reg [1:0] cnt_baud ;
 
 //wire define
 wire [7:0] po_data ;
 wire empty ;
 wire full ;

 asyn_fifo asyn_fifo
 (
 .wclk (sys_clk ), // input clk
 .wrstn (sys_rst_n), // input srst
  .rclk (sys_clk), // input clk
 .rrstn (sys_rst_n), // input srst
 .wdata(pi_data ), // input [7 : 0] din
 .winc (pi_flag ), // input wr_en
 .rinc (rd_en ), // input rd_en
  .rdata (po_data ), // output [7 : 0] dout
 .wfull (full ), // output full
 .rempty (empty ) // output empty

 );

 initial begin
 sys_clk = 1'b1;
 sys_clk1 = 1'b1;
 sys_rst_n <= 1'b0;
 #200;
sys_rst_n <= 1'b1;
 end
  //sys_clk:模拟系统时钟,每 10ns 电平翻转一次,周期为 20ns,频率为 50Mhz
 always #10 sys_clk = ~sys_clk;

  //sys_clk:模拟系统时钟,每 10ns 电平翻转一次,周期为 20ns,频率为 50Mhz
 always #20 sys_clk1 = ~sys_clk1;

 //cnt_baud:计数从 0 到 3 的计数器,用于产生输入数据间的间隔
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 cnt_baud <= 2'b0;
 else if(&cnt_baud == 1'b1)
 cnt_baud <= 2'b0;
 else
 cnt_baud <= cnt_baud + 1'b1;

 //pi_flag:输入数据有效标志信号,也作为 FIFO 的写请求信号
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 pi_flag <= 1'b0;
 //每 4 个时钟周期且没有读请求时产生一个数据有效标志信号
 else if((cnt_baud == 2'd0) && (rd_en == 1'b0))
 pi_flag <= 1'b1;
 else
 pi_flag <= 1'b0;
 
 //pi_data:输入顶层模块的数据,要写入到 FIFO 中的数据
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 pi_data <= 8'b0;
 //pi_data 的值为 0~255 依次循环
else if((pi_data == 8'd16) && (pi_flag == 1'b1))
pi_data <= 8'b0;
else if(pi_flag == 1'b1) //每当 pi_flag 有效时产生一个数据
pi_data <= pi_data + 1'b1;

 //rd_en:FIFO 读请求信号
always@(posedge sys_clk1 or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 rd_en <= 1'b0;
else if(full == 1'b1) //当 FIFO 中的数据存满时,开始读取 FIFO 中的数据
 rd_en <= 1'b1;
 else if(empty == 1'b1) //当 FIFO 中的数据被读空时停止读取 FIFO 中的数据
rd_en <= 1'b0;
endmodule

仿真结果: 

  • 31
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
SV小项目-异步FIFO是一个简单的项目,旨在让学生理解FIFO缓存的原理和实现方式。 FIFO缓存是一种常用的数据存储方式,可以用于解决数据传输时的不匹配问题。在异步FIFO中,数据的写入和读取是异步的,意味着数据可以在任何时候写入或读取。这种异步的方式可以增加FIFO的灵活性,并且允许数据的写入和读取在不同的时钟域上应用。 这个小项目的主要目标是实现一个基于Verilog的异步FIFO模块,包括以下功能: 1. 内部缓存的储存和检索 2. 空和满指示器的生成 3. 数据的写入和读取 对于写入操作,当FIFO未满时,它将数据存储在内部缓存中,并更新其指针以指向下一个空位置。当缓存已满时,写入操作将被忽略,并且FIFO满指示器将被设置为高电平。同样,读取操作从内部缓存中检索数据,并将其指针更新以指向下一个位置。当缓存为空时,读操作将被忽略,并且FIFO空指示器将被设置为高电平。 在设计过程中,需要考虑解决的问题包括时序和异步信号的同步。时序问题可以通过FIFO指针和状态机解决,而异步信号可以通过信号同步器进行同步。此外,还需要考虑FIFO的读写顺序和存储器的尺寸,并确保FIFO模块的有效性和可靠性。 总之,通过实现SV异步FIFO项目,学生可以加深对FIFO缓存的理解,并学习基于Verilog的设计和实现。此外,学生还可以通过在项目中遇到的挑战来提高他们的编程和设计技能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值