FIFO设计笔记(双口RAM、同步FIFO、异步FIFO)Verilog及仿真

0、前言

0.1、FIFO

FIFO (First-In-First-Out) 是一种先进先出的数据缓存器,在数字ASIC设计中常常被使用。在实际开发中,多数都是直接使用公司经过top-out验证的FIFO IP或者是ISE/Vivado工具自带的FIFO IP,并不需要自己造轮子。但是,作为设计者,必须要掌握FIFO的设计方法,这样可以适配于各种类型的FPGA开发板,可以实现国产化。作为求职者,FIFO设计的相关知识在面试环节出现频率极高,需要格外关注。

​ FIFO按工作时钟域的不同可以分为:同步FIFO和异步FIFO。同步FIFO的写时钟和读时钟为同一个时钟,FIFO内部所有逻辑都是同步逻辑,常常用于交互数据缓冲。异步FIFO的写时钟和读时钟为异步时钟,FIFO内部的写逻辑和读逻辑的交互需要异步处理。

  • 用途

    • 跨时钟域

      异步FIFO读写分别采用相互异步的不同时钟。在现代集成电路芯片中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,多时钟域带来的一个问题就是,如何设计异步时钟之间的接口电路。异步FIFO是这个问题的一种简便、快捷的解决方案,使用异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据。

    • 位宽变换

      对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。

0.2、FIFO与RAM

​ FIFO 与普通存储器 RAM 的区别是没有外部读写地址线,使用起来非常简单,但缺点就是只能顺序写入数据,顺序读出数据,其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可 以由地址线决定读取或写入某个指定的地址。 FIFO 本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器。因此,我们首先介绍下异步双口RAM。

1、异步双口RAM

FIFO的应用中所用的存储器一般都是RAM(Random Access Memory),所以异步的RAM对于异步FIFO实现是基础的。

这里的链接是有关不同RAM的介绍:单端口RAM、伪双端口RAM,双端口RAM和FIFO

下面实现一个16x8的异步双端口RAM

  • RAM宽度为8bit(宽度是指数据宽度)
  • RAM深度为16(可以说数据深度为16,也可以说地址宽度为4bit)
  • ADDR位宽2^4,取值范围为0~15

1.1、原理

异步RAM有三部分组成:RAM写控制逻辑、RAM读控制逻辑、RAM存储实体(如Memory、Reg)。

写控制逻辑主要功能:产生RAM写地址、写有效信号。

读控制逻辑主要功能:产生RAM读地址、读有效信号。

image-20230331141433256

1.2、Verilog代码

module  asyn_ram
#(
	parameter	DATA_WIDTH	=	8,
	parameter	ADDR_WIDTH	=	4
)
(
	input								csen		,		//全局使能

	input								write_clock	,		//写时钟
	input								write_en	,		//写使能
	input		[ADDR_WIDTH-1:0]		write_addr	,		//写地址
	input		[DATA_WIDTH-1:0]		write_data	,		//写数据

	input								read_clock	,		//读时钟
	input								read_en		,		//读使能
	input		[ADDR_WIDTH-1:0]		read_addr	,		//读地址
	output	reg [DATA_WIDTH-1:0]		read_data			//读数据
);

reg	[DATA_WIDTH-1:0]	mem	[2**ADDR_WIDTH-1:0]; //2^4-1=15

// look at the rising edge of the clock
always@(posedge write_clock) begin
	if((write_en==1'b1)&&(csen==1'b1))
		mem[write_addr]	<=	#1 write_data	;
end

always@(posedge read_clock) begin
	if((read_en==1'b1)&&(csen==1'b1))
		read_data	<=	#1 mem[read_addr]	;	
end

endmodule

1.3、tb仿真

`timescale 1ns/1ns
module asyn_ram_tb();
 	
    reg			csen		;
	reg 		write_clock	;
	reg	 [7:0] 	write_data	;
	reg 		write_en	;
    reg	 [3:0] 	write_addr	;
	
	reg  		read_clock	;
	wire [7:0] 	read_data	;
	reg  		read_en		;
    reg  [3:0] 	read_addr	;
	
asyn_ram u1(
    .csen(csen),
	.write_clock(write_clock),
	.write_en(write_en),
	.write_addr(write_addr),
	.write_data(write_data),     
	.read_clock(read_clock),
	.read_en(read_en),
	.read_addr(read_addr),
	.read_data(read_data)
);
 
initial begin
	write_clock=1'b0;
	write_en=1'b0;
	forever #5 write_clock=~write_clock;
end
 
initial begin
	read_clock=1'b0;
	read_en=1'b0;
	forever #8 read_clock=~read_clock;
end
 
always begin
    	csen	  =1'b1;
	#15 write_en  =1'b1;
		write_addr=4'd0;
		write_data=8'd1;
	#10 write_addr=4'd1;write_data=8'd2;
	#10 write_addr=4'd2;write_data=8'd3;
	#10 write_addr=4'd3;write_data=8'd4;
	#10 write_addr=4'd4;write_data=8'd5;
end
always begin
	#40 read_en  =1'b1;
		read_addr=4'd0;
	#16 read_addr=4'd1;
	#16 read_addr=4'd2;
	#16 read_addr=4'd3;
	#16 read_addr=4'd4;
	$stop;
end
    
endmodule

仿真效果

image-20230331154302676

2、FIFO设计前瞻知识

2.1、格雷码

  • 格雷码简介:

    在一组数的编码中,若任意两个相邻的代码只有一位二进制数不同,则称这种编码为格雷码(Gray Code),另外由于最大数与最小数之间也仅一位数不同,即“首尾相连”,因此又称循环二进制码或反射二进制码。

  • 格雷码用途:

    格雷码的特点是从一个数变为相邻的一个数时,只有一个数据位发生跳变,由于这种特点,就可以避免二进制编码计数组合电路中出现的亚稳态。格雷码常用于通信,异步FIFO 或者 RAM 地址寻址计数器中。这里有一个链接,讲异步FIFO里为什么要用格雷码? 跨时钟域传输的黄金搭档:异步FIFO与格雷码 (qq.com)

    格雷码属于可靠性编码,是一种错误最小化的编码方式,因为,自然二进制码可以直接由数/模转换器转换成模拟信号,但某些情况,例如从十进制的 3 转换成 4 时二进制码的每一位都要变,使数字电路产生很大的尖峰电流脉冲。
    而格雷码则没有这一缺点,**它是一种数字排序系统,其中的所有相邻整数在它们的数字表示中只有一个数字不同。它在任意两个相邻的数之间转换时,只有一个数位发生变化。**它大大地减少了由一个状态到下一个状态时逻辑的混淆。

    • 自然二进制码与格雷码的对照表:
    十进制数自然二进制数格雷码十进制数自然二进制数格雷码
    000000000810001100
    100010001910011101
    2001000111010101111
    3001100101110111110
    4010001101211001010
    5010101111311011011
    6011001011411101001
    7011101001511111000

​ 格雷码相邻两项只差一位。从0开始,观察每次改的是哪一位,你可以得到这个数列

​ 1,2,1,3,1,2,1,4,1,2,1,3,1,2,1 是不是很有规律?这样便于记忆。数字代表写一个格雷码变化的数的位置。

  • 格雷码优缺点
    • 优点:属于压缩状态编码,使用的触发器位数少;相邻状态变换时,仅一位发生改变,电噪声小,转换速度较快;
    • 缺点:译码复杂,没有固定大小,很难直接进行比较大小和算术运算,需要转换为自然二进制码来判断。
2.1.1、二进制转格雷码
  • 二进制码->格雷码(编码):从最右边一位起,依次将每一位与左边一位异或(XOR),作为本位的格雷码值,最高位不变(相当于最高位的左边是 0);

    image-20230405134421568
Verilog代码
module bin2gray( 
    bin_in, 
    gray_out
);

    parameter WIDTH = 4; 
    input   [WIDTH-1:0] bin_in;  
    output  [WIDTH-1:0] gray_out;

 //===================================================
 // ------------------- MAIN CODE -------------------
 //===================================================
 assign gray_out = (bin_in >> 1) ^ bin_in; 

 endmodule
tb仿真
module bin2gray_tb();

    reg [3:0] bin_in ; 
    wire [3:0] gray_out ;

    initial begin
        bin_in = 4'd0;
        #100
        bin_in = 4'd1;
        #100
        bin_in = 4'd2;
        #100
        bin_in = 4'd3;
        #100
        bin_in = 4'd4;
        #100
        bin_in = 4'd5;
        #100
        bin_in = 4'd6;
        #100
        bin_in = 4'd7;
        #100
        bin_in = 4'd8;
        #100
        bin_in = 4'd9;
        #100
        bin_in = 4'd10;
        #100
        bin_in = 4'd11;
        #100
        bin_in = 4'd12;
        #100
        bin_in = 4'd13;
        #100
        bin_in = 4'd14;
        #100
        bin_in = 4'd15; 

    end

    bin2gray u_bin2gray (
        .bin_in (bin_in ),
        .gray_out (gray_out )
    );

 endmodule

仿真效果

image-20230331202754864

2.1.2、格雷码转二进制
  • 格雷码->二进制码(解码):从左边第二位起,将每位与左边一位解码后的值异或,作为该位解码后的值(最左边一位依然不变)。

    image-20230405134458914
Verilog代码
module gray2bin( 
    gray_in, 
    bin_out
);

    parameter WIDTH = 4; 
    input       [WIDTH-1:0] gray_in;  
    output  reg [WIDTH-1:0] bin_out;

 //===================================================
 // ------------------- MAIN CODE -------------------
 //===================================================
    integer i;
    always @(*) begin
        bin_out[WIDTH-1] = gray_in[WIDTH-1];
        for (i = 1; i < WIDTH; i = i + 1) begin
            bin_out[WIDTH-i-1] = gray_in[WIDTH-i-1]^bin_out[WIDTH-i];
        end
    end

 endmodule

tb仿真
module gray2bin_tb();

    reg [3:0] gray_in ; 
    wire [3:0] bin_out ;

    initial begin
        gray_in = 4'd0;
        #100
        gray_in = 4'd1;
        #100
        gray_in = 4'd3;
        #100
        gray_in = 4'd2;
        #100
        gray_in = 4'd6;
        #100
        gray_in = 4'd7;
        #100
        gray_in = 4'd5;
        #100
        gray_in = 4'd4;
        #100
        gray_in = 4'd12;
        #100
        gray_in = 4'd13;
        #100
        gray_in = 4'd15;
        #100
        gray_in = 4'd14;
        #100
        gray_in = 4'd10;
        #100
        gray_in = 4'd11;
        #100
        gray_in = 4'd9;
        #100
        gray_in = 4'd8;
        #100
        gray_in = 4'd0;

    end

    gray2bin u_gray2bin (
        .gray_in (gray_in ),
        .bin_out (bin_out )
    );

 endmodule

仿真效果:

image-20230331205122847

2.2、独热码

独热码,在英文文献中称做 one-hot code, 直观来说就是有多少个状态就有多少比特,而且只有一个比特为 1,其他全为 0 的一种码制。通常,在通信网络协议栈中,使用八位或者十六位状态的独热码。
例如,有 6 个状态的独热码状态编码为:000001,000010,000100,001000,010000,100000。

优点:状态比较时仅仅需要比较一个位,从而一定程度上简化了译码逻辑,译码简单,减少了毛刺产生的概率。
缺点:速度较慢,触发器资源占用较多,面积较大;

用途:在做仲裁时,独热码有

3、同步FIFO

前面介绍到,FIFO 包括同步 FIFO 和异步 FIFO 两种。 而FIFO 本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器。因此,首先介绍下同步FIFO的两种设计方法。

3.1、同步FIFO设计原理

3.1.1、设计原理

典型同步FIFO有三部分组成: (1) FIFO写控制逻辑; (2)FIFO读控制逻辑; (3)FIFO 存储实体(如Memory、Reg)。

FIFO写控制逻辑主要功能:产生FIFO写地址、写有效信号,同时产生FIFO写满、写错等状态信号;

FIFO读控制逻辑主要功能:产生FIFO读地址、读有效信号,同时产生FIFO读空、读错等状态信号。

img

FIFO参数解释:

  • FIFO 的宽度:即 FIFO 一次读写操作的数据位数
  • FIFO 的深度:指的是 FIFO 可以存储数据的个数,也就是地址宽度(如果地址宽度为N,那么就可以存储 2 N 2^N 2N-1个数)
  • 满标志:FIFO 已满或将要满时由 FIFO 的状态送出的一个信号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出(overflow)
  • 空标志:FIFO 已空或将要空时由 FIFO 的状态送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO 中读出数据而造成无效数据的读出(underflow)
  • 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据(同步FIFO中,读写时钟为同一个时钟)
  • 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据
3.1.2、设计方法

FIFO设计的关键是正确的读写控制和生成FIFO“空/满”状态标志。基本原则是**“满不能写,空不能读。”**

  • FIFO读写控制

    • 当FIFO初始化(复位)时fifo_write_addr与fifo_read_addr同指到0x0,此时FIFO为空状态;

    • 当FIFO进行写操作时,fifo_write_addr递增(增加到FIFO深度时回绕),与fifo_read_addr错开,此时FIFO处于非空状态;

    • 当FIFO进行读操作时,fifo_read_addr递增,当读到最后一个数据后,就追赶上fifo_write_addr,此时FIFO也是空状态。

      img

  • FIFO空满状态判断

    需要明确的是:

    读指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为’d0)。

    写指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为’d0)

    下面有两种判断空满状态判断的实现方法。

3.2、计数器实现同步FIFO

3.2.1、设计方法

为产生FIFO空满标志,引入fifo_data_cnt计数器,fifo_data_cnt寄数器用于指示FIFO内部存储数据个数;

  • 复位时,该计数器为0(FIFO中的数据个数为0)
  • 当读写使能信号均有效/无效时,说明同时读写或者同时不读不写,fifo_data_cnt不变(FIFO中的数据个数不变)
  • 当写使能有效且 fifo_full=0( FIFO 未满),则 fifo_data_cnt+1;
  • 当读使能有效且 fifo_empty=0(FIFO非空),则 fifo_data_cnt -1;
  • fifo_data_cnt =0 的时候,表示 FIFO 为空,需要设置 fifo_empty=1;
  • fifo_data_cnt = FIFO_ADDR_WIDTH 的时候,表示 FIFO 现在已经满,需要设置 fifo_full=1
3.2.2、Verilog代码
//计数器法实现sync_fifo
module sync_fifo_cnt
#(
    parameter FIFO_DATA_WIDTH = 'd8       ,              
    parameter FIFO_ADDR_DEPTH = 'd16      
)
(
    input                                       clk          , //fifo clock
    input                                       rst_n        , //fifo clock reset (0: reset)
    input                                       fifo_wr_en   , //fifo write enable(1: enable)

    input                                       fifo_rd_en   , //fifo read enable(1: enable)
    input       [FIFO_DATA_WIDTH-1:0]           fifo_wr_data , //fifo write data

    output                                      fifo_full    , //fifo full status(1:full 0:no full)
    output                                      fifo_wr_err  , //fifo write error status
    output                                      fifo_empty   , //fifo empty status(1:empty 0:no empty)
    output                                      fifo_rd_err  , //fifo read error status
    output  reg [$clog2(FIFO_ADDR_DEPTH) :0]    fifo_data_cnt, //fifo valid data cnt
    output  reg [FIFO_DATA_WIDTH-1:0]           fifo_rd_data   //fifo read data
);

reg     [$clog2(FIFO_ADDR_DEPTH)-1:0]   fifo_wr_addr ;         //fifo write addr
reg     [$clog2(FIFO_ADDR_DEPTH)-1:0]   fifo_rd_addr ;         //fifo write addr

reg     [FIFO_DATA_WIDTH-1:0]   fifo_mem [FIFO_ADDR_DEPTH-1:0] ;//FIFO_ADDR_DEPTH*FIFO_DATA_WIDTH RAM


//--=========================================--
// READ CONTROL :
// Read address increase when read enable AND
// Not empty;
//--=========================================--
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        fifo_rd_addr <= 0 ;
    else if (fifo_rd_en && (~ fifo_empty)) begin
        fifo_rd_addr <= fifo_rd_addr + 1'b1 ;
        fifo_rd_data <= fifo_mem[fifo_rd_addr] ;      
    end
end

//--=========================================--
// WRITE CONTROL :
// Write address increase when write enable AND
// Not full.
//--=========================================--
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        fifo_wr_addr <= 0 ;
    else if (fifo_wr_en && (~ fifo_full)) begin
        fifo_wr_addr <= fifo_wr_addr + 1'b1 ; 
        fifo_mem[fifo_wr_addr] <= fifo_wr_data ;      
    end
end

//--=========================================--
// FIFO DATA CNT :
// Valid Write Only, increase data cnt;
// Valid Read  Only, decrease data cnt;
//--=========================================--
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        fifo_data_cnt <= 0 ;
    else if (fifo_wr_en & (~ fifo_full) & (~(fifo_rd_en & (~fifo_empty)))) //Valid Write Only, increase data cnt;
        fifo_data_cnt <= fifo_data_cnt + 1'b1 ;
    else if (fifo_rd_en & (~ fifo_empty) & (~(fifo_wr_en & (~fifo_full)))) //Valid Read Only, decrease data cnt;
        fifo_data_cnt <= fifo_data_cnt - 1'b1 ;
    else 
        fifo_data_cnt <= fifo_data_cnt;
end

//--=========================================--
// FIFO Status :
// 1. fifo_empty when cnt ==0 ;
// 2. fifo full  when cnt == MAX ;
//--=========================================--
assign fifo_empty  = (fifo_data_cnt == 0 ) ;
assign fifo_rd_err = (fifo_data_cnt == 0 ) & fifo_rd_en ; 

assign fifo_full   = (fifo_data_cnt == FIFO_ADDR_DEPTH ) ;
assign fifo_wr_err = (fifo_data_cnt == FIFO_ADDR_DEPTH ) & fifo_wr_en ;

endmodule
3.2.3、tb仿真

Testbench借鉴自同步FIFO的两种Verilog设计方法(计数器法、高位扩展法)_同步fifo verilog代码_孤独的单刀的博客-CSDN博客

  • 例化1个深度为8,位宽为8的同步FIFO
  • 先对FIFO进行写操作,直到其写满,写入的数据为随机数据
  • 然后对FIFO进行读操作,直到其读空
  • 然后对FIFO写入4个随机数据后,同时对其进行读写操作
`timescale 1ns/1ns
module sync_fifo_cnt_tb();
 
parameter   FIFO_DATA_WIDTH = 8  ;
parameter   FIFO_ADDR_DEPTH = 8  ; 
 
reg                                     clk          ;
reg                                     rst_n        ;
reg     [FIFO_DATA_WIDTH-1:0]           fifo_wr_data ;
reg                                     fifo_rd_en   ;
reg                                     fifo_wr_en   ;
                        
wire    [FIFO_DATA_WIDTH-1:0]           fifo_rd_data ;   
wire                                    fifo_empty   ;   
wire                                    fifo_full    ;
wire    [$clog2(FIFO_ADDR_DEPTH) : 0]   fifo_data_cnt;
wire                                    fifo_rd_err  ;
wire                                    fifo_wr_err  ;

sync_fifo_cnt
#(
    .FIFO_DATA_WIDTH (FIFO_DATA_WIDTH),          
    .FIFO_ADDR_DEPTH (FIFO_ADDR_DEPTH)            
)
u_sync_fifo_cnt(
    .clk                (clk            ),
    .rst_n              (rst_n          ),
    .fifo_wr_data       (fifo_wr_data   ),
    .fifo_rd_en         (fifo_rd_en     ),
    .fifo_wr_en         (fifo_wr_en     ),
                 
    .fifo_rd_data       (fifo_rd_data   ),  
    .fifo_empty         (fifo_empty     ),  
    .fifo_full          (fifo_full      ),
    .fifo_data_cnt      (fifo_data_cnt  ),
    .fifo_wr_err        (fifo_wr_err    ),
    .fifo_rd_err        (fifo_rd_err    )           
);

always #10 clk = ~clk;
 
initial begin
    clk = 1'b0;                        
    rst_n <= 1'b0;                     
    fifo_wr_data <= 'd0;     
    fifo_wr_en <= 1'b0;      
    fifo_rd_en <= 1'b0;

    //write full
    repeat(8) begin     
        @(negedge clk)begin     
            rst_n <= 1'b1;              
            fifo_wr_en <= 1'b1;      
            fifo_wr_data <= $random;        
        end
    end

    //read empty
    repeat(8) begin
        @(negedge clk)begin     
            fifo_wr_en <= 1'b0;
            fifo_rd_en <= 1'd1;
        end
    end
    
    repeat(4) begin
        @(negedge clk)begin     
            fifo_wr_en <= 1'b1;
            fifo_wr_data <= $random;
            fifo_rd_en <= 1'b0;
        end
    end

    forever begin
        @(negedge clk)begin     
            fifo_wr_en <= 1'b1;
            fifo_wr_data <= $random; 
            fifo_rd_en <= 1'b1;
        end
    end
end

       
endmodule

仿真效果

Snipaste_2023-04-01_14-32-23

3.3、拓展地址位实现同步FIFO

3.3.1、设计方法

因为FIFO的特点为先入先出,因此读写的开始的地址都是0。

  • 当读地址与写地址相同时,则可判断FIFO为空。

  • 当读地址与写地址除最高位相反,其他位相同时,则可判断FIFO为满。

  • 假设RAM深度为8,扩展后的地址为4位。

    • 开始写入4个数据,此时写地址为0100,读地址此时为0000;
    • 然后开始读数据,读完4个数据后,读地址此时为0100,此时可判断FIFO为空;
    • 后对FIFO进行写数据,连续写入8个数据,此时写地址为1100,已知深度为8,判断FIFO为满,此时的写读地址比较发现:最高位相反,其余位相同。
    image-20230402101951065
3.3.2、Verilog代码
module sync_fifo_ptr
#(
    parameter   FIFO_DATA_WIDTH =   'd8 ,
    parameter   FIFO_ADDR_DEPTH =   'd16
)
(
    input                                       clk          , //fifo clock
    input                                       rst_n        , //fifo clock reset (0: reset)
    input                                       fifo_wr_en   , //fifo write enable(1: enable)

    input                                       fifo_rd_en   , //fifo read enable(1: enable)
    input       [FIFO_DATA_WIDTH-1:0]           fifo_wr_data , //fifo write data

    output                                      fifo_full    , //fifo full status(1:full 0:no full)
    output                                      fifo_wr_err  , //fifo write error status
    output                                      fifo_empty   , //fifo empty status(1:empty 0:no empty)
    output                                      fifo_rd_err  , //fifo read error status
    output  reg [FIFO_DATA_WIDTH-1:0]           fifo_rd_data   //fifo read data
);

wire   [$clog2(FIFO_ADDR_DEPTH)-1:0]      fifo_rd_addr    ;       //真实地址 
wire   [$clog2(FIFO_ADDR_DEPTH)-1:0]      fifo_wr_addr    ;      
reg    [$clog2(FIFO_ADDR_DEPTH):0]        fifo_rd_addr_e  ;      
reg    [$clog2(FIFO_ADDR_DEPTH):0]        fifo_wr_addr_e  ;        //真实地址,高位拓展一位

reg    [FIFO_DATA_WIDTH-1:0]   fifo_mem [FIFO_ADDR_DEPTH-1:0] ;     //FIFO_ADDR_DEPTH*FIFO_DATA_WIDTH RAM

       
assign fifo_wr_addr = fifo_wr_addr_e[$clog2(FIFO_ADDR_DEPTH)-1:0];
assign fifo_rd_addr = fifo_rd_addr_e[$clog2(FIFO_ADDR_DEPTH)-1:0];


//读数据
always @(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0)
        fifo_rd_addr_e  <= 'd0;
    else if(fifo_rd_en && (~ fifo_empty))begin
        fifo_rd_addr_e  <= fifo_rd_addr_e + 1'd1;
        fifo_rd_data    <= fifo_mem[fifo_rd_addr];
    end
end

//写数据
always @(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0)
        fifo_wr_addr_e  <= 'd0;
    else if(fifo_wr_en && (~ fifo_full))begin
        fifo_wr_addr_e  <= fifo_wr_addr_e + 1'd1;
        fifo_mem[fifo_wr_addr] <= fifo_wr_data;
    end
end



assign fifo_empty   =   (fifo_wr_addr_e==fifo_rd_addr_e);  
assign fifo_full    =   ((fifo_wr_addr_e[$clog2(FIFO_ADDR_DEPTH)]!=fifo_rd_addr_e[$clog2(FIFO_ADDR_DEPTH)]) && 
                        (fifo_rd_addr==fifo_wr_addr));

assign fifo_wr_err  =   fifo_full  & fifo_wr_en;
assign fifo_rd_err  =   fifo_empty & fifo_rd_en;   

endmodule
3.3.3、tb仿真

使用相同的tb

`timescale 1ns/1ns
module sync_fifo_ptr_tb();
 
parameter   FIFO_DATA_WIDTH = 8  ;
parameter   FIFO_ADDR_DEPTH = 8  ; 
 
reg                                     clk          ;
reg                                     rst_n        ;
reg     [FIFO_DATA_WIDTH-1:0]           fifo_wr_data ;
reg                                     fifo_rd_en   ;
reg                                     fifo_wr_en   ;
                        
wire    [FIFO_DATA_WIDTH-1:0]           fifo_rd_data ;   
wire                                    fifo_empty   ;   
wire                                    fifo_full    ;
wire                                    fifo_rd_err  ;
wire                                    fifo_wr_err  ;

sync_fifo_ptr
#(
    .FIFO_DATA_WIDTH (FIFO_DATA_WIDTH),          
    .FIFO_ADDR_DEPTH (FIFO_ADDR_DEPTH)            
)
u_sync_fifo_ptr(
    .clk                (clk            ),
    .rst_n              (rst_n          ),
    .fifo_wr_data       (fifo_wr_data   ),
    .fifo_rd_en         (fifo_rd_en     ),
    .fifo_wr_en         (fifo_wr_en     ),
                 
    .fifo_rd_data       (fifo_rd_data   ),  
    .fifo_empty         (fifo_empty     ),  
    .fifo_full          (fifo_full      ),
    .fifo_wr_err        (fifo_wr_err    ),
    .fifo_rd_err        (fifo_rd_err    )           
);

always #10 clk = ~clk;
 
initial begin
    clk = 1'b0;                        
    rst_n <= 1'b0;                     
    fifo_wr_data <= 'd0;     
    fifo_wr_en <= 1'b0;      
    fifo_rd_en <= 1'b0;

    //write full
    repeat(8) begin     
        @(negedge clk)begin     
            rst_n <= 1'b1;              
            fifo_wr_en <= 1'b1;      
            fifo_wr_data <= $random;        
        end
    end

    //read empty
    repeat(8) begin
        @(negedge clk)begin     
            fifo_wr_en <= 1'b0;
            fifo_rd_en <= 1'd1;
        end
    end
    
    repeat(4) begin
        @(negedge clk)begin     
            fifo_wr_en <= 1'b1;
            fifo_wr_data <= $random;
            fifo_rd_en <= 1'b0;
        end
    end

    forever begin
        @(negedge clk)begin     
            fifo_wr_en <= 1'b1;
            fifo_wr_data <= $random; 
            fifo_rd_en <= 1'b1;
        end
    end
end

       
endmodule

仿真效果:和上一个方法一样

4、异步FIFO

异步FIFO这里,刀哥写的太好了,大家可以直接看他的博客,里面有verilog代码,也有testbench仿真软件,记得给他点赞!
异步FIFO的Verilg实现方法_verilog 异步fifo_孤独的单刀的博客-CSDN博客

5、FIFO深度问题

这里可以看这个大佬总结的:FIFO深度计算 - 星雨夜澈 - 博客园 (cnblogs.com)

Question1:

写时钟频率为100MHz,读时钟频率为80MHz。每100个时钟周期写入80个数据,每一个周期读出一个数据。那么FIFO的深度应该是多少?
首先我们要确定最极端的情况,也就是FIFO的Burst 会写了多少个数据。这里要考虑数据是“背靠背”传输的,如下:

在这里插入图片描述

这种情况是,前100个时钟周期,数据在后80个周期内发生了连续的写操作,在后100个时钟周期内,数据在前80个周期写入。这就造成了:

  • 数据突发长度为 :160
  • burst长度(160个数据)写入需要的时间:burst长度/写时钟频率 = 160/100
  • burst长度(160个数据)写入期间可以读出的数据量:160/100 ÷ 1/80 =128
  • FIFO的深度:160 - 128 = 32

Question2:

设计一个同步FIFO,每100个时钟周期可以写80个数据,每10个周期可以读8个数据,FIFO的深度为多少?
和上面的情况一样,burst长度为160。所以:

  • burst长度(B)为:B =160

  • 写入B长度所需的时间:T = (1/fwr)*160

  • 从FIFO中读出一个数据所需的时间:t = (1/frd)*(8/10)
    在T时间读走的数据量:T/T= 160* (frd/fwr)(8/10)

  • FIFO的深度:160 - 160* (frd/fwr)(8/10)

因为是同步FIFO,所以frd/fwr = 1,代入得 深度为32。
由此我们可以推出FIFO的计算公式:

在这里插入图片描述

6 、参考链接

[1]Cummings C E. Simulation and synthesis techniques for asynchronous FIFO design[C]//SNUG 2002 (Synopsys Users Group Conference, San Jose, CA, 2002) User Papers. 2002: 281.

[2]同步FIFO学习 - IC新手 - 博客园 (cnblogs.com)

[3]同步和异步FIFO的设计_哔哩哔哩_bilibili

[4]同步FIFO的两种Verilog设计方法(计数器法、高位扩展法)_同步fifo verilog代码_孤独的单刀的博客-CSDN博客

[5]异步FIFO的Verilg实现方法_verilog 异步fifo_孤独的单刀的博客-CSDN博客

[6]异步FIFO的verilog代码实现(包含将满和将空逻辑) - love小酒窝 - 博客园 (cnblogs.com)

[7]IC基础(一):异步FIFO原理与代码实现 - 你好24h - 博客园 (cnblogs.com)

[8]异步FIFO_异步fifo读写时钟频率限制_小小verifier的博客-CSDN博客

[9] FIFO深度计算 - 星雨夜澈 - 博客园 (cnblogs.com)

  • 11
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
异步FIFO是一种常用的数据缓冲器,在异步数据传输中起到了重要的作用。在Verilog仿真中,可以通过编写Verilog代码来实现异步FIFO,并进行相应的仿真。 首先,需要定义FIFO的输入输出信号和内部寄存器。输入信号包括数据输入Din和写使能信号Write_en,输出信号包括数据输出Dout和读使能信号Read_en。内部寄存器包括存储数据的寄存器FIFO_reg和读写指针的寄存器Read_ptr和Write_ptr。 接下来,需要实现FIFO的写操作和读操作。对于写操作,当Write_en信号为高电平时,将Din数据写入到寄存器FIFO_reg中,并将写指针Write_ptr加1;对于读操作,当Read_en信号为高电平时,将寄存器FIFO_reg中的数据输出到Dout,并将读指针Read_ptr加1。 同时,需要考虑FIFO的相关控制逻辑。例如,FIFO的写指针应该在写操作完成后自增,而读指针应该在读操作完成后自增。当写指针和读指针相同时,表示FIFO为空;当写指针和读指针相差1时,表示FIFO为满。 最后,使用Verilog仿真工具(如ModelSim)对编写的异步FIFO进行仿真测试。可以通过编写测试文件,对FIFO的写入和读取进行模拟,验证其功能的正确性和稳定性。在仿真过程中,可以观察FIFO的输入输出数据是否符合预期,并检查读写指针的变化是否按照设计预期进行。 总之,通过编写Verilog代码实现异步FIFO,并进行Verilog仿真测试,可以有效验证其功能和正确性,为后续的硬件开发和应用提供可靠的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

搞IC的小冯

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

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

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

打赏作者

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

抵扣说明:

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

余额充值