异步fifo的设计与验证

书接上文,上一篇介绍了略简单的同步fifo,接下来开始较为复杂的异步fifo。

1.同步fifo与异步fifo的区别

 当设计中只有一个时钟时,所有的寄存器否用同一个,不会出现传输速度不匹配的情况;但是当设计中存在多个时钟信号,并且在这几个时钟域中传递数据,就可能出现因时钟信号不匹配而产生数据丢失的现象,这时候需要异步fifo进行数据的缓存,保证数据能够正常的传输,因此一般异步fifo会包含一个双端口的ram.

2.异步fifo组成

图1.异步fifo

模块组成(6个):顶层模块、双口RAM、2个跨时钟域同步指针模块、空判断逻辑、满判断逻辑。

        1)顶层模块 将读写时钟域的的输入输出信号列出,定义所需的线网类型。

        2)设计双端口的RAM  在写时钟域下的对RAM执行写操作、在读时钟域下进行读操作

        3)两个跨时钟域对指针进行同步   在同步前要对比较指针进行格雷码的转化,同步方式为将转化后的比较指针在对方时钟域打两拍。 注意:比较指针要比 指针多一位, 做高位用来判断读写指针谁走的快。

        4)空逻辑判断   读写的比较指针相等则为空

        5)满逻辑判断   写的指针比读的指针多走一圈,及比较指针的最高位不相等,剩余位相等。

 3.注意事项

        1)避免使用二进制计数器实现指针,因为当前数值进位加一时,可能会出现所有的位都会发生改变,错误率可能会因此提高。eg:0111加1会变成1000,其中有三位发生改变,因此会提升错误率。

        2)针对以上问题,建议直接使用格雷码计数。

        3)将指针的位宽多定义一位举个例子说明:假设要设计深度为 8 的异步FIFO,此时定义读写指针只需要 3 位(2^3=8)就够用了,但是我们在设计时将指针的位宽设计成 4 位,最高位的作用就是区分是读空还是写满,具体理论 1 如下当最高位相同,其余位相同认为是读空;当最高位不同,其余位相同认为是写满

                格雷码计数器设计理念:格雷码到二进制转换器、加法器、二进制码到格雷码转化器、用于保存格雷值得寄存器

3.相应模块代码段

1)顶层模块

//************顶层模块*************************
`timescale 1 ns/ 1 ps
module asyn_fifo_top #(
	parameter DATA_WIDTH = 8,
	parameter ADDR_WIDTH = 4,
	parameter RAM_DEPTH = 16
)
(
	input 		              WCLK,
	input                         WRSTn,
	input                         RCLK,
	input                         RRSTn,
	input                         write,
	input                         read,
	input  [ DATA_WIDTH - 1 : 0 ] wdata,
	output [ DATA_WIDTH - 1 : 0 ] rdata,
	output                        full,
	output                        empty
);

wire [ ADDR_WIDTH : 0 ]    wpt;
wire [ ADDR_WIDTH : 0 ]    rpt;
wire [ ADDR_WIDTH : 0 ]    rp2_wpt;
wire [ ADDR_WIDTH : 0 ]    wp2_rpt;
wire [ ADDR_WIDTH - 1: 0 ] waddr;
wire [ ADDR_WIDTH - 1: 0 ] raddr;

double_ram#(.DATA_WIDTH(DATA_WIDTH),
			.ADDR_WIDTH(ADDR_WIDTH),
			.RAM_DEPTH(RAM_DEPTH)) 
	ram_module (.WCLK(WCLK),
	            .write(write),
				.waddr(waddr),
				.wdata(wdata),
				.raddr(raddr),
				.rdata(rdata));
r2w_sync #(.ADDR_WIDTH(ADDR_WIDTH))
	r2w_module (.WCLK(WCLK),
				.WRSTn(WRSTn),
				.rpt(rpt),
				.rp2_wpt(rp2_wpt));
w2r_sync #(.ADDR_WIDTH(ADDR_WIDTH))
	w2r_module (.RCLK(RCLK),
				.RRSTn(RRSTn),
				.wpt(wpt),
				.wp2_rpt(wp2_rpt));
full#(.ADDR_WIDTH(ADDR_WIDTH)) 
	full_module(.WCLK(WCLK),
				.WRSTn(WRSTn),
				.write(write),
				.rp2_wpt(rp2_wpt),
				.wpt(wpt),
				.waddr(waddr),
				.full(full));
empty #(.ADDR_WIDTH(ADDR_WIDTH))
	empty_module(.RCLK(RCLK),
				 .RRSTn(RRSTn),
				 .read(read),
				 .wp2_rpt(wp2_rpt),
				 .rpt(rpt),
				 .raddr(raddr),
				 .empty(empty));


endmodule

3.2RAM模块

//******************双接口RAM******************
module double_ram#(
	parameter DATA_WIDTH = 8,
	parameter ADDR_WIDTH = 4,
	parameter RAM_DEPTH = 16
)
(	
	input                       WCLK,   //write clk
	input 					    write,  //write en
	input  [ ADDR_WIDTH - 1:0 ] waddr,  //write address from full.v
	input  [ DATA_WIDTH - 1:0 ] wdata,  //write data
	input  [ ADDR_WIDTH - 1:0 ] raddr,  //read address from empty.v
	output  [ DATA_WIDTH - 1:0 ] rdata   //read data
);

reg [ DATA_WIDTH - 1:0 ] RAM [ RAM_DEPTH - 1:0 ];  //double Port ram


always @ ( posedge WCLK )
begin
	if ( write == 1'b1 )
	begin
		RAM [ waddr ] <= wdata; 
	end
	else
	begin
		RAM [ waddr ] <= RAM [ waddr ];
	end
end

assign rdata = RAM [ raddr ];

endmodule

 3)判断满逻辑模块

//*****************判断满逻辑****************
module full#(
	parameter ADDR_WIDTH = 4
)
(
	input                           WRSTn,
	input                           WCLK,
	input                           write,
	input      [ ADDR_WIDTH : 0 ]   rp2_wpt,
	output reg [ ADDR_WIDTH : 0 ]   wpt,
	output     [ ADDR_WIDTH - 1:0 ] waddr,
	output reg                      full
);

reg  [ ADDR_WIDTH :0 ] wbin;
wire [ ADDR_WIDTH :0 ] wbin_next;
wire [ ADDR_WIDTH :0 ] wgray_next;
wire                   full_reg;

always @ ( posedge WCLK or negedge WRSTn )
begin
	if (!WRSTn)
	begin
		wpt <= 0;
		wbin <= 0;
	end
	else 
	begin
		wpt <= wgray_next;
		wbin <= wbin_next;
	end
end

assign wbin_next = ( !full ) ? ( wbin + write ) : wbin;
assign wgray_next = ( wbin_next >> 1 ) ^ wbin_next;
assign waddr = wbin [ ADDR_WIDTH - 1:0 ];


always @ ( posedge WCLK or negedge WRSTn )
begin
	if (!WRSTn)
	begin
		full <= 0;
	end
	else
	begin
		full <= full_reg;
	end
end

assign full_reg = ( wgray_next == { -rp2_wpt [ ADDR_WIDTH : ADDR_WIDTH - 1],rp2_wpt [ ADDR_WIDTH - 2:0]});

endmodule 

4)判断是否为空指针

//********************判断是否为空逻辑************
module empty#(
	parameter ADDR_WIDTH = 4
)
(
	input                           RRSTn,
	input                           RCLK,
	input                           read,
	input      [ ADDR_WIDTH : 0 ]   wp2_rpt,
	output reg [ ADDR_WIDTH : 0 ]   rpt,
	output reg [ ADDR_WIDTH - 1:0 ] raddr,
	output reg                      empty
);

reg  [ ADDR_WIDTH :0 ] rbin;
wire [ ADDR_WIDTH :0 ] rbin_next;
wire [ ADDR_WIDTH :0 ] rgray_next;
wire                   empty_reg;

always @ ( posedge RCLK or negedge RRSTn )
begin
	if (!RRSTn)
	begin
		rpt <= 0;
		rbin <= 0;
	end
	else 
	begin
		rpt <= rgray_next;
		rbin <= rbin_next;
	end
end

assign rbin_next = ( !empty ) ? ( rbin + read ) : rbin;
assign rgray_next = ( rbin_next >> 1 ) ^ rbin_next;
//assign raddr = rbin [ ADDR_WIDTH - 1:0 ];

always @ ( posedge RCLK or negedge RRSTn )
begin
	if (read)
	begin
	   raddr <= rbin [ ADDR_WIDTH - 1:0 ];
	end
	
end

always @ ( posedge RCLK or negedge RRSTn )
begin
	if (!RRSTn)
	begin
		empty <= 0;
	end
	else
	begin
		empty <= empty_reg;
	end
end

assign empty_reg = ( rgray_next == wp2_rpt );

endmodule 

5)读到写跨时钟域

//****************读到写跨时钟域****************
module r2w_sync#(
	parameter ADDR_WIDTH = 4
)
(
	input 						  WRSTn,    //write RSTn
	input 					      WCLK,     //write CLK
	input      [ ADDR_WIDTH : 0 ] rpt,      //output to write port gray
	output reg [ ADDR_WIDTH : 0 ] rp2_wpt   //D trigger sync with two levels,second level
);


reg [ ADDR_WIDTH : 0 ] rp1_wpt;         //frist level
//reg [ ADDR_WIDTH : 0 ] rp2_wpt; 

always @ ( posedge WCLK or negedge WRSTn )
begin
	if ( !WRSTn )
	begin
		{ rp2_wpt,rp1_wpt } <= 0;
	end
	else 
	begin
		{ rp2_wpt,rp1_wpt } <= { rp1_wpt,rpt };
	end
end

endmodule

6)写到读跨时钟域

/**Write to Read Sync module**/
module w2r_sync#(
	parameter ADDR_WIDTH = 4
)
(
	input 						  RRSTn,    //read RSTn
	input 						  RCLK,     //reaf CLK
	input      [ ADDR_WIDTH : 0 ] wpt,      //output to read port gray
	output reg [ ADDR_WIDTH : 0 ] wp2_rpt   //D trigger sync with two levels,second level
);

reg [ ADDR_WIDTH : 0 ] wp1_rpt;         //frist level
//reg [ ADDR_WIDTH : 0 ] wp2_rpt;   

always @ ( posedge RCLK or negedge RRSTn )
begin
	if ( !RRSTn )
	begin
		{ wp2_rpt,wp1_rpt } <= 0;
	end
	else 
	begin
		{ wp2_rpt,wp1_rpt } <= { wp1_rpt,wpt};
	end
end

endmodule 

4.tb

`timescale 1 ns/ 1 ps
module asyn_fifo_tb();

parameter DATA_WIDTH = 8;

	reg                         WCLK;
	reg                         WRSTn;
	reg                         RCLK;
	reg                         RRSTn;
	reg                         write;
	reg                         read;
	reg  [ DATA_WIDTH - 1 : 0 ] wdata;
	wire [ DATA_WIDTH - 1 : 0 ] rdata;
	wire                        full;
	wire                        empty;
	//integer                     i = 0;
	
asyn_fifo_top asyn_fifo(.WCLK(WCLK),
						.WRSTn(WRSTn),
						.RCLK(RCLK),
						.RRSTn(RRSTn),
						.write(write),
						.read(read),
						.wdata(wdata),
						.rdata(rdata),
						.full(full),
						.empty(empty));
initial 
begin
	WCLK <= 0;
	forever #100 WCLK = ~WCLK;
end

initial 
begin
	RCLK <= 0;
	forever #200 RCLK = ~RCLK;
end

initial 
begin
	WRSTn = 0;
	wdata = 0;
	#100 WRSTn = 1;
end

initial
begin
	RRSTn = 0;
	#100 RRSTn = 1;
	#10000;
	$finish();
end


always @ ( posedge WCLK or negedge WRSTn )
begin
	wdata <= wdata + 1'b1;
end

//always  @(posedge WCLK or negedge WRSTn)
//begin
//      if(WRSTn==1'b0)
//      begin
//         i <= 0;
//      end
//      else if(!full)
//      begin
//         i = i+1;
//      end
//      else begin
//         i <= i;
//      end
//end

//always @ (*)
//begin
//    if (!full)
//        wdata = i;
//    else 
//        wdata = 0;
//end


always @ ( full or WRSTn )
begin
	if (!WRSTn)
	begin
		write <= 0;
	end
	else if (!full)
	begin
		write <= 1;
	end
	else 
	begin
		write <= 0;
	end
end

always @ ( empty or RRSTn )
begin
	if (!RRSTn)
	begin
		read <= 0;
		
	end
	
	else if (!empty)
	begin
		read <= 1;
	end 
	else 
	begin
		read <= 0;
	end
end

        initial begin
            $fsdbDumpfile("jacky");
            $fsdbDumpvars;
            $vcdpluson;
        end


endmodule

 

 

 

 

 

二、另一种设计思路(来自csdn:FPGA小学生)

//asyn_fifo代码
module asyn_fifo #(
    parameter data_width = 16;
    parameter data_depth = 8;
    parameter ram_depth = 256;
    )
    (
        input       rst_n,

        input                       wr_clk,         //写时钟域的信号
        input                        wr_en,
        input   [data_width-1:0]    data_in,
        output                      full, 

        input                       rd_clk,         //读时钟域信号
        input                       rd_en,
        input   [data_width-1:0]    data_out,
        output                      empty,
    )
    
    reg [data_depth-1:0]            wr_adr;         //定义读写指针的地址
    reg [data_depth-1:0]            rd_adr;

    reg [data_depth:0]              wr_adr_ptr ;    //定义用来比较的指针
    reg [data_depth:0]              rd_adr_ptr ;

    wire [data_depth:0]             wr_adr_gray;    //将用来比较的指针转格雷码
    reg [data_depth:0]              wr_adr_gray1 ;  //在打两拍处用到
    reg [data_depth:0]              wr_adr_gray2 ;
    wire [data_depth:0]             rd_adr_gray;    //将用来比较的指针转格雷码
    reg [data_depth:0]              rd_adr_gray1 ;  //在打两拍处用到
    reg [data_depth:0]              rd_adr_gray2 ;


    //*********************定义在双端口ram的读写数据模块******************
    assign wr_adr  =  wr_adr_ptr[data_depth-1:0]    //定义比较指针与读写指针的关系
    assign rd_adr  =  rd_adr_ptr[data_depth-1:0]

    integer i;                                      //例化一个RAM
    reg [data_width-1:0]  ram_fifo [data_depth-1:0] ;
        //在ram中写
    always @ (posedge wr_clk or negedge rst_n) 
        begin     
        if(!rst_n)
            begin                                   //将ram全部置零
                for(i;i<ram_depth;i=i+1)
                    ram_fifo[i] <= 'd0;
            end
        else if (wr_en && ~full)
            ram_fifo[wr_adr] <= data_in;
        else
            ram_fifo[wr_adr] <= ram_fifo[wr_adr];
        end

        //在ram中读数据
    always @ (posedge rd_clk or rst_n)
        begin
        if (!rst_n)
            data_out <= 'd0;
        else if (rd_en && ~empty)
            data_out <= ram_fifo [rd_adr];
        else
            data_out <= 'd0;
         end   
//**********在进行读写后,对比较指针进行加1操作************
    always @ (posedge wr_clk or !rst_n)
        begin
            if (!rst_n)
                wr_adr_ptr <='b0;
            else if (wr_en && ~full)
                wr_adr_ptr <= wr_adr_ptr + 1'b1;
            else
                wr_adr_ptr <= wr_adr_ptr
        end
    always @ (posedge rd_clk or !rst_n)
        begin 
            if (!rst_n)
                rd_adr_ptr <= 'b0;
            else if (rd_en && ~empty)
                rd_adr_ptr <= rd_adr_ptr + 1'b0;
            else 
                rd_adr_ptr <= rd_adr_ptr
        end


 //**********************将比较指针转为格雷码*******************
    assign wr_adr_gray = (wr_adr_ptr >> 1)^ wr_adr_ptr;
    assign rd_adr_gray = (rd_adr_ptr >> 1) ^ rd_adr_ptr;

//*************************比较格雷码的大小************
   //判断是否为空时,要在读时钟域下判断转换后格雷码是否相等
   //判断是否为满时,要在写时钟域下比较格雷码最高位不等并且剩余位相等

   //分别将比较格雷码在相应时钟下打两拍,来同步时钟域
   always @ (posedge wr_clk or !rst_n)
        begin
        if (!rst_n)
            begin
                rd_adr_gray1 <= 'b0;
                rd_adr_gray2 <= 'b0;
            end
        else begin
            rd_adr_gray1 <= rd_adr_gray;
            rd_adr_gray2 <= rd_adr_gray1;
            end 
        end
   always @ (posedge rd_clk or !rst_n)
        begin
        if (!rst_n)
            begin
                wr_adr_gray1 <= 'b0;
                wr_adr_gray2 <= 'b0;
            end
        else begin
            wr_adr_gray1 <= wr_adr_gray;
            wr_adr_gray2 <= wr_adr_gray1;
            end 
        end        

        //判断空满
    assign empty = (rd_adr_gray2 == wr_adr_gray2) ? 1'b:1'b0;
    assign full = (rd_adr_gray2[data_depth : data_depth-1] != wr_adr_gray2[data_depth:data_depth-1]) && (wr_adr_gray2[data_depth-2:0] == rd_adr_gray2[data_depth-2:0]) 


endmodule

tb

//异步fifo的testbench
`timescale 1ns/1ps

module asyn_fifo_tb;

    reg                 rst_n;

    reg                 wr_clk;
    reg                 wr_en;
    reg     [15:0]      data_in;
    wire                full;

    reg                 rd_clk;
    reg                 rd_en;
    reg     [15:0]      data_out;
    wire                empty;


    asyn_fifo asyn_fifo_inst
    (
        .rst_n      (rst_n),

        .wr_clk     (wr_clk),
        .wr_en      (wr_en),
        .data_in    (data_in),
        .full       (full),

        .rd_clk     (rd_clk),
        .rd_en       (rd_en),
        .data_out       (data_out),
        .empty      (empty)
    )

    //定义时钟
    initial begin
        wr_clk = 0;
        forever #10 wr_clk = ~wr_clk;
    end

    initial begin
        rd_clk = 0 ;
        forever  #30 rd_clk = ~rd_clk;
    end

    // 定义输入data_in
    always @ (posedge wr_clk or negedge  rst_n)
        begin
            if (!rst_n)
                data_in <= 'd0;
            else if (wr_en)
                data_in <= data_in + 1'b1;
            else
                data_in <= data_in;
        end
    //定义其他输入信号

initial begin
    rst_n = 0;
    wr_en =0;
    rd_en = 0;
    #200
    rst_n = 1;
    wr_en = 1;
    #20000
    wr_en = 0;
    rd_en = 1;
    #20000
    rd_en = 0;

    $stop
end
endmodule

  • 9
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
异步FIFO是一种常见的硬件设计模块,用于在不同的时钟域之间传输数据。UVM(Universal Verification Methodology)是一种用于验证硬件设计的方法学。在UVM验证中,验证工程师可以使用UVM中提供的各种类和方法来验证异步FIFO的功能和正确性。 下面是一个简单的UVM验证异步FIFO的示例: 首先,我们需要创建一个Transaction类来表示FIFO中的数据项。这个类可以包含需要验证的数据字段。 然后,我们创建一个Agent类来模拟FIFO的发送和接收端。这个Agent类可以包含两个接口,一个用于发送数据到FIFO,另一个用于从FIFO接收数据。Agent类还可以包含一个Monitor来监视FIFO的状态,并将收到的数据转换为Transaction对象。 接下来,我们创建一个Sequencer类来生成数据项并将其发送到FIFO的发送端口。Sequencer类可以使用UVM提供的随机化机制来生成不同的数据项。 然后,我们创建一个Driver类来驱动Sequencer生成的数据项,并将其发送到FIFO的发送端口。 最后,我们可以创建一个Test类来实例化和连接上述组件,并编写测试用例来验证异步FIFO的功能和正确性。 在验证过程中,我们可以使用UVM提供的各种断言和功能覆盖率工具来验证异步FIFO的正确性。通过生成不同的测试用例和使用各种场景和边界条件,我们可以尽可能地覆盖所有可能的情况,并验证异步FIFO的正确性。 需要注意的是,上述只是一个简单的UVM验证异步FIFO的示例,实际的验证过程可能更为复杂,需要根据具体的设计和需求进行调整和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值