• IC基础——FIFO


    FIFO简介

    先进先出的数据缓存器,与普通存储器的区别是没有外部读写地址线,使用方便,缺点是只能顺序读写,不能随机读写。其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。

    使用场景

    1、数据缓冲:也就是数据写入过快,并且间隔时间长,也就是突发写入。那么通过设置一定深度的FIFO,可以起到数据暂存的功能,且使得后续处理流程平滑。

    2、时钟域的隔离:主要用异步FIFO。对于不同时钟域的数据传输,可以通过FIFO进行隔离,避免跨时钟域的数据传输带来的设计和约束上的复杂度。比如FIFO的一端是AD,另一端是PCI;AD的采集速率是16位100KSPS,每秒的数据量是1.6Mbps。而PCI总线的速度是33MHz,总线宽度是32位

    3、用于不同宽度的数据接口

    类别

    同步FIFO

    读写时钟为同一个时钟

    异步FIFO

    读写时钟不为同一个时钟

    参数

    FIFO宽度

    FIFO一次读写操作的数据位

    FIFO深度

    指FIFO可以存储多少个N位的数据(如果宽度为N)

    满指标

    FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)

    空指标

    FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)

    读时钟

    读操作所遵循的时钟,在每个时钟沿来临时读数据

    写时钟

    写操作所遵循的时钟,在每个时钟沿来临时写数据

    实现

    同步FIFO实现

    FIFO 读写指针(读写指针就是读写地址)的工作原理:

    • 写指针:总是指向下一个将要被写入的单元,复位时,指向第 1 个单元(编号为 0)
    • 读指针:总是指向当前要被读出的数据,复位时,指向第 1 个单元(编号为 0) FIFO 的“空”/“满”检测

    FIFO 设计的关键:产生可靠的 FIFO 读写指针和生成 FIFO“空”/“满”状态标志。

    在这里插入图片描述

    当读写指针相等时,表明 FIFO 为空,这种情况发生在复位操作时,或者当读指针读出 FIFO 中最后一 个字后,追赶上了写指针时。

    当读写指针再次相等时,表明 FIFO 为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around) 又追上了读指针。

    1、计数器法

    构建一个计数器,该计数器(fifo_cnt)用于指示当前 FIFO 中数据的个数:

    • 复位时,该计数器为0,FIFO中的数据个数为0
    • 当读写使能信号均有效时,说明又读又写,计数器不变,FIFO中的数据个数无变化
    • 当写使能有效且 full=0,则 fifo_cnt +1;表示写操作且 FIFO 未满时候,FIFO 中的数据个数增加了 1
    • 当读使能有效且 empty=0,则 fifo_cnt -1;表示读操作且 FIFO 未满时候,FIFO 中的数据个数减少了 1
    • fifo_cnt =0 的时候,表示 FIFO 空,需要设置 empty=1;fifo_cnt = fifo的深度 的时候,表示 FIFO 现在已经满,需要设置 full=1

    计数器法verilog代码

    //计数器法实现同步FIFO
    module	sync_fifo_cnt
    #(
    	parameter   DATA_WIDTH = 8  ,							//FIFO位宽
        parameter   DATA_DEPTH = 16 							//FIFO深度
    )
    (
    	input									clk		,		//系统时钟
    	input									rst_n	,       //低电平有效的复位信号
    	input	[DATA_WIDTH-1:0]				data_in	,       //写入的数据
    	input									rd_en	,       //读使能信号,高电平有效
    	input									wr_en	,       //写使能信号,高电平有效
    															
    	output	reg	[DATA_WIDTH-1:0]			data_out,	    //输出的数据
    	output									empty	,	    //空标志,高电平表示当前FIFO已被写满
    	output									full	,       //满标志,高电平表示当前FIFO已被读空
    	output	reg	[$clog2(DATA_DEPTH) : 0]	fifo_cnt		//$clog2是以2为底取对数	
    );
     
    //reg define
    reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0];	//用二维数组实现RAM	
    reg [$clog2(DATA_DEPTH) - 1 : 0]	wr_addr;				//写地址
    reg [$clog2(DATA_DEPTH) - 1 : 0]	rd_addr;				//读地址
     
    //读操作,更新读地址
    always @ (posedge clk or negedge rst_n) begin
    	if (!rst_n)
    		rd_addr <= 0;
    	else if (!empty && rd_en)begin							//读使能有效且非空
    		rd_addr <= rd_addr + 1'd1;
    		data_out <= fifo_buffer[rd_addr];
    	end
    end
    //写操作,更新写地址
    always @ (posedge clk or negedge rst_n) begin
    	if (!rst_n)
    		wr_addr <= 0;
    	else if (!full && wr_en)begin							//写使能有效且非满
    		wr_addr <= wr_addr + 1'd1;
    		fifo_buffer[wr_addr]<=data_in;
    	end
    end
    //更新计数器
    always @ (posedge clk or negedge rst_n) begin
    	if (!rst_n)
    		fifo_cnt <= 0;
    	else begin
    		case({wr_en,rd_en})									//拼接读写使能信号进行判断
    			2'b00:fifo_cnt <= fifo_cnt;						//不读不写
    			2'b01:	                               			//仅仅读
    				if(fifo_cnt != 0)				   			//fifo没有被读空
    					fifo_cnt <= fifo_cnt - 1'b1;   			//fifo个数-1
    			2'b10:                                 			//仅仅写
    				if(fifo_cnt != DATA_DEPTH)         			//fifo没有被写满
    					fifo_cnt <= fifo_cnt + 1'b1;   			//fifo个数+1
    			2'b11:fifo_cnt <= fifo_cnt;	           			//读写同时
    			default:;                              	
    		endcase
    	end
    end
    //依据计数器状态更新指示信号
    //依据不同阈值还可以设计半空、半满 、几乎空、几乎满
    assign full  = (fifo_cnt == DATA_DEPTH) ? 1'b1 : 1'b0;		//空信号
    assign empty = (fifo_cnt == 0)? 1'b1 : 1'b0;				//满信号
     
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    其中计数器和读写地址以为为底去对数的原因在于用多少位的二进制表示深度。

    2、高位扩展法

    采取扩展最高位与读写地址位共同判断是为满或为空。

    • 当最高位不同,且其他位相同,则表示读指针或者写指针多跑了一圈,而显然不会让读指针多跑一圈(多跑一圈读啥?),所以可能出现的情况只能是写指针多跑了一圈,与就意味着FIFO被写满了
    • 当最高位相同,且其他位相同,则表示读指针追到了写指针或者写指针追到了读指针,而显然不会让写指针追读指针(这种情况只能是写指针超过读指针一圈),所以可能出现的情况只能是读指针追到了写指针,也就意味着FIFO被读空了

    高位扩展法示意图:

    在这里插入图片描述

    高位扩展法verilog代码

    module	sync_fifo_ptr
    #(
    	parameter   DATA_WIDTH = 'd8  ,								//FIFO位宽
        parameter   DATA_DEPTH = 'd16 								//FIFO深度
    )
    (
    	input										clk		,		//系统时钟
    	input										rst_n	,       //低电平有效的复位信号
    	input	[DATA_WIDTH-1:0]					data_in	,       //写入的数据
    	input										rd_en	,       //读使能信号,高电平有效
    	input										wr_en	,       //写使能信号,高电平有效
    						                                        
    	output	reg	[DATA_WIDTH-1:0]				data_out,	    //输出的数据
    	output										empty	,	    //空标志,高电平表示当前FIFO已被写满
    	output										full		    //满标志,高电平表示当前FIFO已被读空
    );                                                              
     
    //reg define
    //用二维数组实现RAM
    reg [DATA_WIDTH - 1 : 0]			fifo_buffer[DATA_DEPTH - 1 : 0];	
    reg [$clog2(DATA_DEPTH) : 0]		wr_ptr;						//写地址指针,位宽多一位	
    reg [$clog2(DATA_DEPTH) : 0]		rd_ptr;						//读地址指针,位宽多一位	
     
    //wire define
    wire [$clog2(DATA_DEPTH) - 1 : 0]	wr_ptr_true;				//真实写地址指针
    wire [$clog2(DATA_DEPTH) - 1 : 0]	rd_ptr_true;				//真实读地址指针
    wire								wr_ptr_msb;					//写地址指针地址最高位
    wire								rd_ptr_msb;					//读地址指针地址最高位
     
    assign {wr_ptr_msb,wr_ptr_true} = wr_ptr;						//将最高位与其他位拼接
    assign {rd_ptr_msb,rd_ptr_true} = rd_ptr;						//将最高位与其他位拼接
     
    //读操作,更新读地址
    always @ (posedge clk or negedge rst_n) begin
    	if (rst_n == 1'b0)
    		rd_ptr <= 'd0;
    	else if (rd_en && !empty)begin								//读使能有效且非空
    		data_out <= fifo_buffer[rd_ptr_true];
    		rd_ptr <= rd_ptr + 1'd1;
    	end
    end
    //写操作,更新写地址
    always @ (posedge clk or negedge rst_n) begin
    	if (!rst_n)
    		wr_ptr <= 0;
    	else if (!full && wr_en)begin								//写使能有效且非满
    		wr_ptr <= wr_ptr + 1'd1;
    		fifo_buffer[wr_ptr_true] <= data_in;
    	end	
    end
     
    //更新指示信号
    //当所有位相等时,读指针追到到了写指针,FIFO被读空
    assign	empty = ( wr_ptr == rd_ptr ) ? 1'b1 : 1'b10;
    //当最高位不同但是其他位相等时,写指针超过读指针一圈,FIFO被写满
    assign	full  = ( (wr_ptr_msb != rd_ptr_msb ) && ( wr_ptr_true == rd_ptr_true ) )? 1'b1 : 1'b0;
     
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    实验仿真

    TestBench代码

    `timescale  1ns / 1ps
    
    module tb_sync_fifo_ptr;
    
    // sync_fifo_ptr Parameters
    parameter PERIOD      = 5  ;
    parameter DATA_WIDTH  = 'd8 ;
    parameter DATA_DEPTH  = 'd16;
    
    // sync_fifo_ptr Inputs
    reg   clk                                  = 0 ;
    reg   rst_n                                = 0 ;
    reg   [DATA_WIDTH-1:0]  data_in            = 0 ;
    reg   rd_en                                = 0 ;
    reg   wr_en                                = 0 ;
    
    // sync_fifo_ptr Outputs
    wire  [DATA_WIDTH-1:0]  data_out           ;
    wire  empty                                ;
    wire  full                                 ;
    
    
    initial
    begin
        forever #(PERIOD/2)  clk=~clk;
    end
    
    initial
    begin
        #(PERIOD*2) rst_n  =  1;
    end
    
    sync_fifo_ptr #(
        .DATA_WIDTH ( DATA_WIDTH ),
        .DATA_DEPTH ( DATA_DEPTH ))
     u_sync_fifo_ptr (
        .clk                     ( clk                        ),
        .rst_n                   ( rst_n                      ),
        .data_in                 ( data_in   [DATA_WIDTH-1:0] ),
        .rd_en                   ( rd_en                      ),
        .wr_en                   ( wr_en                      ),
    
        .data_out                ( data_out  [DATA_WIDTH-1:0] ),
        .empty                   ( empty                      ),
        .full                    ( full                       )
    );
    reg [7:0] tempdata = 0;
    initial
    begin
        $dumpfile("wave.vcd");        //生成的vcd文件名称
        $dumpvars(0, tb_sync_fifo_ptr);    //tb模块名称
        clk = 0;
        rst_n = 0;
        wr_en = 0;
        rd_en = 0;
        data_in = 0;
        #10
        rst_n = 1;
        push(1);
            fork
               push(2);
               pop(tempdata);
            join              //push and pop together   
            push(10);
            push(20);
            push(30);
            push(40);
            push(50);
            push(60);
            push(70);
            push(80);
            push(90);
            push(100);
            push(110);
            push(120);
            push(130);
    
            pop(tempdata);
            push(tempdata);
            pop(tempdata);
            pop(tempdata);
            pop(tempdata);
            pop(tempdata);
    		push(140);
            pop(tempdata);
            push(tempdata);//
            pop(tempdata);
            pop(tempdata);
            pop(tempdata);
            pop(tempdata);
            pop(tempdata);
            pop(tempdata);
            pop(tempdata);
            pop(tempdata);
            pop(tempdata);
            pop(tempdata);
            pop(tempdata);
            push(5);
            pop(tempdata);
    		$finish;
    end
    	
    	task push (input [7:0] data);
    		if(full)
    			$display("---Cannot push %d: Buffer Full---",data);
    		else begin
    			$display("Push",data);
    			data_in = data;
    			wr_en = 1;
    			@(posedge clk);
    			#5 wr_en = 0;
    		end
    	endtask
    	
    	task pop(output[7:0] data);
    		if(empty)
    			$display("---Cannot Pop: Buffer Empty---");
    		else begin
    			rd_en = 1;
    			@(posedge clk);
    			#3 rd_en = 0;
    			data = data_out;
    			$display("------Poped:",data);
    		end		
    	endtask
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126

    波形示意图

    在这里插入图片描述

    异步FIFO实现

    异步产生亚稳态问题

    异步FIFO需要考虑的是亚稳态的问题,如果采用二进制数的计数值从一个时钟域到另一个时钟域的时候就会出问题,因为二进制计数器的所有位都有可能发生变化。使用格雷码的话只有一位变化,在两个时钟域间的同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换成相应的gray码,然后将gray码同步到另一个时钟域进行对比,作为空满状态的检测。

    在这里插入图片描述

    gray码判断空满问题

    空判断

    对于“空”的判断依然依据读写指针二者完全相等(包括MSB);

    满判断

    因为gray码的特性,与同步不同的是MSB最高位不同时,其他位相同,不能作为FIFO满的判断。

    在gray码上判断为满必须同时满足以下3条:

    • wptr和同步过来的rptr的MSB不相等,因为wptr必须比rptr多折回一次。
    • wptr与rptr的次高位不相等,如上图位置7和位置15,转化为二进制对应的是0111和1111,MSB不同说明多折回一次,111相同代表同一位置。
    • 剩下的其余位完全相等。

    设计总体实现

    在这里插入图片描述

    代码:

    module AsyncFIFO#(
        parameter ASIZE = 4,    //地址位宽
        parameter DSIZE = 8     //数据位宽
    )(
        input [DSIZE-1:0] wdata,
        input             winc,wclk,wrst_n,    //写请求信号,写时钟,写复位
        input             rinc,rclk,rrst_n,    //读请求信号,读时钟,读复位 
        output [DSIZE-1:0] rdata,
        output  wfull,
        output  rempty              
    );
        wire [ASIZE-1:0] waddr, raddr;
        wire [ASIZE:0]   wptr, rptr, wq2_rptr, rq2_wptr; 
        /*在检测“满”或“空”状态之前,需要将指针同步到其它时钟域时,使用格雷码,可以降低同步过程中亚稳态出现的概率*/
        sync_r2w#(
            .ADDRSIZE ( 4 )
        )u_sync_r2w(
            .wq2_rptr ( wq2_rptr ), //out
            .rptr     ( rptr     ),
            .wclk     ( wclk     ),
            .wrst_n   ( wrst_n   )
        );
    
        sync_w2r#(
            .ADDRSIZE ( 4 )
        )u_sync_w2r(
            .rq2_wptr ( rq2_wptr ),
            .wptr     ( wptr     ),
            .rclk     ( rclk     ),
            .rrst_n   ( rrst_n   )
        );
        fifomem#(
            .DATASIZE ( 8 ),
            .ADDRSIZE ( 4 )
        )u_fifomem(
            .wclken   ( wclken   ),
            .wfull    ( wfull    ),
            .wclk     ( wclk     ),
            .wdata    ( wdata    ),
            .waddr    ( waddr    ),
            .raddr    ( raddr    ),
            .rdata    ( rdata    )
        );
        rptr_empty#(
            .ADDRSIZE ( 4 )
        )u_rptr_empty(
            .rempty   ( rempty   ),
            .raddr    ( raddr    ),
            .rptr     ( rptr     ),
            .rq2_wptr ( rq2_wptr ),
            .rinc     ( rinc     ),
            .rclk     ( rclk     ),
            .rrst_n   ( rrst_n   )
        );
        wptr_full#(
            .ADDRSIZE ( 4 )
        )u_wptr_full(
            .wfull    ( wfull    ),
            .waddr    ( waddr    ),
            .wptr     ( wptr     ),
            .wq2_rptr ( wq2_rptr ),
            .winc     ( winc     ),
            .wclk     ( wclk     ),
            .wrst_n   ( wrst_n   )
        );
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    顶层设计

    在这里插入图片描述

    信号描述

    信号名称位宽信号描述
    Wdata数据位宽写入数据
    Wfull1写满信号
    Winc1写请求信号(写使能信号)
    Wclk1写时钟
    Wrst_n1写复位信号(低电平有效
    Rdata数据位宽读出数据
    Rempty1读空信号
    Rinc1读请求信号(读使能信号)
    Rrst_n1读复位信号(低电平有效)
    RAM存储器模块

    在这里插入图片描述

    信号描述:

    信号名称位宽信号描述
    wclken1写使能信号
    wclk1写时钟信号
    raddr地址位宽读地址
    waddr地址位宽写地址
    wdata数据位宽写入的数据
    rdata数据位宽读数据
    wfull1写满信号

    代码:

    根据上面RAM的总概图,定义的一个宽度为8位,深度为8的RAM(即定义了8个寄存器,每个寄存器的宽度为8)

    module fifomem
    #(
        parameter  DATASIZE = 8, // Memory data word width               
        parameter  ADDRSIZE = 4  // 指针地址位宽设置为4,因为8=2^3,应该加一判断写满或读空
    ) // Number of mem address bits
    (
        input                 wclken, wfull, wclk,
        input  [DATASIZE-1:0] wdata, //write data
        input  [ADDRSIZE-1:0] waddr, raddr, 
        output [DATASIZE-1:0] rdata //read data
    );
     
        // RTL Verilog memory model
        localparam DEPTH = 1<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    FIFO写地址以及写满判断模块

    主要是控制是否可以写入数据,写指针与写满的顶层模块图如图所示:

    在这里插入图片描述

    信号描述:

    信号名称位宽信号描述
    Winc1写请求信号
    Wclk1写时钟信号
    Wrst_n1写复位信号
    Wq2_rptr地址位宽+1同步之后的读指针(格雷码形式)
    Wfull1写满信号
    Waddr地址位宽二进制形式的写地址
    Wptr地址位宽+1格雷码形式的写指针

    模块作用:当时钟信号来临时且写请求信号有效时,写一组数据,并且同时写地址向下加一位,然后讲写地址转化为格雷码,判断是否写满

    写满的判断:写地址指针再次追上读地址指针

    二进制转格雷码:

    ​ 二进制的最右一位(最低位)起,依次将每一位与左边一为进行异或操作,作为格雷码该位的值,而最左一位不变。

    代码:

    module wptr_full#(
        parameter ADDRSIZE = 4
    ) (
        output reg wfull,   
        output        [ADDRSIZE-1:0] waddr,//二进制形式的写地址
        output reg    [ADDRSIZE:0]   wptr, //格雷码形式的写指针
        input         [ADDRSIZE:0]   wq2_rptr,//同步后的读指针
        input         winc, wclk, wrst_n
    );
        reg  [ADDRSIZE:0] wbin;
        wire [ADDRSIZE:0] wgraynext, wbinnext;
        wire wfull_val;
        // GRAYSTYLE2 pointer
        always @(posedge wclk or negedge wrst_n) begin 
            if (!wrst_n)
                {wbin, wptr} <= 0;   
            else         
                {wbin, wptr} <= {wbinnext, wgraynext};
        end
        // Memory write-address pointer (okay to use binary to address memory) 
        assign waddr      = wbin[ADDRSIZE-1:0]; 
        assign wbinnext   = wbin + (winc & ~wfull);//写请求且没写满时,地址加一
        assign wgraynext  = (wbinnext>>1) ^ wbinnext; //将二进制码转换为格雷码
    
        //最高位表示多这一次,但由于格雷码特性,次高位也需要不同,其余位需要相同
        assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]}); 
        
        always @(posedge wclk or negedge wrst_n) begin
            if (!wrst_n)
                wfull  <= 1'b0;   
            else     
                wfull  <= wfull_val;
        end
      endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    FIFO读地址以及读空判断模块

    读控制端口主要用于是否可以读取数据,读指针与读空的顶层模块图如下图:

    在这里插入图片描述

    信号描述:

    信号名称位宽信号描述
    Rinc1读请求信号
    Rclk1读时钟信号
    Rrst_n1读复位信号
    Rq2_rptr地址位宽+1同步之后的写指针(格雷码形式)
    Rempty1读空信号
    Raddr地址位宽二进制形式的读地址
    Rptr地址位宽+1格雷码形式的读指针

    **作用:**当时钟信号来且读请求信号有效时,读出一组数据,并且同时读地址向下加一位。然后将读地址转换为格雷码,判断是否为读空。

    代码:

    1、寄存二进制地址,方便转换成格雷码

    2、根据读使能以及是否为空,赋值下一地址

    3、每到上升沿采样将下一拍地址传给当前地址变量

    4、当同步之后的写指针与读指针(格雷码形式)相同时说明为空

    module rptr_empty #(
        parameter ADDRSIZE = 4
    )(
        output reg rempty,
        output      [ADDRSIZE-1:0] raddr,
        output reg  [ADDRSIZE :0]  rptr,
        input       [ADDRSIZE :0]  rq2_wptr,
        input       rinc, rclk, rrst_n
    );
        reg  [ADDRSIZE:0] rbin;
    wire [ADDRSIZE:0] rgraynext, rbinnext;
    wire  rempty_val;
    //-------------------
    // GRAYSTYLE2 pointer: gray码读地址指针
    //-------------------
        always @(posedge rclk or negedge rrst_n) begin
            if (!rrst_n) 
                begin 
                    rbin <= 0;
                    rptr <= 0;
                end
            else
                begin
                    rbin <= rbinnext ; 
                    rptr <= rgraynext;
                end
        end
    // gray码计数逻辑
        assign raddr = rbin[ADDRSIZE-1:0];
        assign rbinnext  = rbin + (rinc & ~rempty); //不空且有读请求的时候读地址加1
        assign rgraynext = (rbinnext>>1) ^ rbinnext;      //二进制到gray码的转换
    //---------------------------------------------------------------
    // FIFO empty when the next rptr == synchronized wptr or on reset
    //---------------------------------------------------------------
    /*
    *   读指针是一个n位的gray码计数器,比FIFO寻址所需的位宽大一位
    *   当读指针和同步过来的写指针完全相等时(包括MSB),说明二者折回次数一致,FIFO为空
    *     
    */
    assign rempty_val = (rgraynext == rq2_wptr);
            always @(posedge rclk or negedge rrst_n)
                if (!rrst_n) 
                    rempty <= 1'b1;
                else 
                    rempty <= rempty_val;
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    FIFOF写时钟同步到读时钟模块

    示意图如下:

    在这里插入图片描述

    信号名称位宽信号描述
    Rclk1读时钟信号
    Rrst_n1读复位信号
    Wptr地址位宽+1写指针地址
    Rq2_wptr地址位宽+1同步写指针地址

    代码:

    module sync_w2r #(
        parameter ADDRSIZE = 4
    )(
        output reg  [ADDRSIZE:0] rq2_wptr,
        input       [ADDRSIZE:0] wptr,
        input       rclk, rrst_n
    );        
        reg [ADDRSIZE:0] rq1_wptr;
        always @(posedge rclk or negedge rrst_n) begin
            if (!rrst_n)
                {rq2_wptr,rq1_wptr} <= 0;
            else begin // {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
                rq1_wptr <= wptr;
                rq2_wptr <= rq1_wptr;
            end
               
        end
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    FIFO读时钟同步到写时钟模块

    示意图如下:

    在这里插入图片描述

    信号名称位宽信号描述
    Wclk1写时钟信号
    Wrst_n1写复位信号
    Rptr地址位宽+1读指针地址
    Wq2_rptr地址位宽+1同步读指针地址

    代码:

    module sync_r2w #(
        parameter ADDRSIZE = 4
    )(
        output reg [ADDRSIZE:0] wq2_rptr,
        input      [ADDRSIZE:0] rptr,
        input                   wclk, wrst_n
    );
        reg [ADDRSIZE:0] wq1_rptr;
        always @(posedge wclk or negedge wrst_n) begin
            if (!wrst_n) 
                {wq2_rptr,wq1_rptr} <= 0;
            else 
                {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
        end
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    实验仿真

    1、输入信号初始化

    2、FIFO复位

    3、写读时钟

    4、读写控制(写数据、读数据)

    TestBench代码:

    `timescale  1ns / 1ps
    
    module tb_AsyncFIFO;
    
    // AsyncFIFO Parameters
    
    parameter ASIZE  = 4;
    parameter DSIZE  = 8;
    
    // AsyncFIFO Inputs
    reg   [DSIZE-1:0]  wdata                   = 0 ;
    reg   winc                                 = 0 ;
    reg   wclk                                 = 0 ;
    reg   wrst_n                               = 0 ;
    reg   rinc                                 = 0 ;
    reg   rclk                                 = 0 ;
    reg   rrst_n                               = 0 ;
    
    // AsyncFIFO Outputs
    wire  [DSIZE-1:0]  rdata                   ;
    wire  wfull                                ;
    wire  rempty                               ;
    
    initial
    begin
        //输入信号初始化
        wrst_n = 1;
        rrst_n = 1;
        wclk = 0;
        rclk = 0;
        winc = 0;
        rinc = 0;
        wdata = 0;
        //raddr = 0;
        //复位信号
        # 30 
        wrst_n = 0;
        rrst_n = 0;
        #30
        wrst_n = 1;
        rrst_n = 1;
    
    end
    always //写时钟
        #2 wclk = ~wclk;
    always //读时钟
        #4 rclk = ~rclk;
    
    always @(*) begin
        if(!wfull) begin
            winc = 1;
        end
        else begin
            winc =0;
        end
    end
    
    always @(*) begin
        if(!rempty) begin
            rinc = 1;
        end
        else begin
            rinc =0;
        end
    end
    
    always @(posedge wclk) begin
        if(!wfull) begin
            wdata <= wdata + 1;
        end 
        else begin
          wdata <= wdata;
        end
    end
    
    AsyncFIFO #(
        .ASIZE ( ASIZE ),
        .DSIZE ( DSIZE ))
     u_AsyncFIFO (
        .wdata                   ( wdata   ),
        .winc                    ( winc                ),
        .wclk                    ( wclk                ),
        .wrst_n                  ( wrst_n              ),
        .rinc                    ( rinc                ),
        .rclk                    ( rclk                ),
        .rrst_n                  ( rrst_n              ),
        .rdata                   ( rdata ),
        .wfull                   ( wfull               ),
        .rempty                  ( rempty              )
    );
    
    initial
    begin
        $dumpfile("wave_test.vcd");        //生成的vcd文件名称
        $dumpvars(0, tb_AsyncFIFO);    //tb模块名称
        #1000 $stop;
        //$finish;
    end
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    仿真的波形图:

    在这里插入图片描述

    总结

    异步fifo的重点,第一是对读写信号的同步,主要用两级缓存对读写信号的同步,第二是对写满信号的判断需要二进制到格雷码之间的转换

  • 相关阅读:
    我的创作纪念日
    自媒体内容发布要求有哪些?
    /apache/lib/libapr-1.so.0: undefined symbol: dlopen
    门牌制作-蓝桥杯?-Lua 中文代码解题第3题
    关于二进制
    快来学习这个高效绘制流程图的方法
    spark3.3.x处理excel数据
    递归解析Json,实现生成可视化Tree+快速获取JsonPath
    作图从此不求人,代谢组学宝藏作图能力提升班你来不来?
    个人信息保护专业人员认证(CCRC-PIPP)
  • 原文地址:https://blog.csdn.net/Nabla_/article/details/126353973