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

2023-05-16

文章目录

    • 0、前言
      • 0.1、FIFO
      • 0.2、FIFO与RAM
    • 1、异步双口RAM
      • 1.1、原理
      • 1.2、Verilog代码
      • 1.3、tb仿真
    • 2、FIFO设计前瞻知识
      • 2.1、格雷码
        • 2.1.1、二进制转格雷码
          • Verilog代码
          • tb仿真
        • 2.1.2、格雷码转二进制
          • Verilog代码
          • tb仿真
      • 2.2、独热码
    • 3、同步FIFO
      • 3.1、同步FIFO设计原理
        • 3.1.1、设计原理
        • 3.1.2、设计方法
      • 3.2、计数器实现同步FIFO
        • 3.2.1、设计方法
        • 3.2.2、Verilog代码
        • 3.2.3、tb仿真
      • 3.3、拓展地址位实现同步FIFO
        • 3.3.1、设计方法
        • 3.3.2、Verilog代码
        • 3.3.3、tb仿真
    • 4、异步FIFO
    • 5、FIFO深度问题
      • Question1:
      • Question2:
    • 6 、参考链接

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)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FIFO设计笔记(双口RAM、同步FIFO、异步FIFO)Verilog及仿真 的相关文章

  • IDEA lombok包的@Data注解报错找不到符号

    问题描述 xff1a 工程编译报错 java 找不到符号 问题抽象 定位错误可以发现是getter setter的问题 由于采用了 64 Data注解 xff0c 所以优先考虑是lombok的问题 解决方案 首先排除以下可能 lombok插
  • python配合阿里云域名解析实现DDNS功能

    前置工作 安装阿里模块 pip span class token function install span aliyun python sdk core v3 pip span class token function install s
  • linux开机自启服务简单配置

    使用任意方式在 usr lib systemd system路径下新建一个demo service文件并在其中以如下模板编写内容 span class token punctuation span Unit span class token
  • git取消超过范围大文件并重新提交

    问题报错 remote error File db383b59a623629b36cc62dd3917db21c1375cf0 span class token number 690 93 span MB exceeds span clas
  • 基于pyqt的无边框浏览器

    自己写了个基于pyqt的无边框浏览器 xff0c 可以配合源阅读的web看书 xff0c 自用自取 目录结构 xff1a ui ui webview pycall webview y 环境安装 xff1a pip span class to
  • Pycharm默认输入状态是insert状态,选中文字无法直接输入替换或删除

    最近开始学习python xff0c 使用Pycharm编辑器进行学习过程中却发现 xff1a 打开一个新的py文件 xff0c 我的光标处于加粗状态 xff0c 也就是编程软件经常出现的insert插入编辑模式 xff0c 我就点击了一下
  • 输入字体之间的间隔突然变大了

    额 xff0c 这个问题之前也遇到几次 xff0c 后来莫名其妙变好了 我就没深究 最近写代码又出现了这种情况 就是敲了个快捷键 xff0c 就变成 后来发现是IDE的快捷键和输入法快捷键冲突了 解决方法 xff1a 按一下 shift 4
  • 西瓜的JSON笔记

    1 JSON是什么 JSON 指的是 JavaScript 对象表示法 xff08 J ava S cript O bject N otation java xff0c 脚本 xff0c 对象 xff0c 注释 xff09 JSON的特点
  • Windows 下安装 Ubuntu 双系统

    转载自 xff1a Windows 下安装 Ubuntu 双系统 一 准备 xff1a xff08 一 xff09 准备工具 xff1a U盘一个 xff08 请提前备份U盘里面的资料 xff0c 因为后面操作要格式化U盘 xff09 Ub
  • FreeRTOS 互斥信号量和二值信号量对比

    互斥信号量 1 有优先级继承 2 尽量不要在中断中调用 3 xSemaphoreCreateMutex创建后 xff0c 可以直接take使用 二值信号量 1 无优先级继承 2 允许在中断中调用 3 可以当做标志位来使用 4 xSemaph
  • 工作站常见问题处理

    常见问题1 xff1a 系统重启网桥消失 1 现象 公司的工作站自从搬到新地方以来 xff0c 每次关机再启动 xff0c 或重启后 xff0c 都会出现网桥消失的情况 2 分析 查找了网桥和网卡的配置 xff0c 也觉得没有什么问题 xf
  • Ubuntu17 安装ProxyChains4

    span class hljs preprocessor 切换目录 span cd Downloads span class hljs preprocessor 下载 span git clone https span class hljs
  • c++实现ip是否在同一个网段的判断

    废话不说直接贴代码 xff1a ip的数据结构 typedef struct IP Struct ip地址划分后各个域的值 struct IpAdress Struct int first int second int third int
  • 计算ip地址是否在同一网段

    一 要判断两个IP地址是不是在同一个网段 xff0c 就将它们的IP地址分别与子网掩码做与运算 xff0c 得到的结果 gt 网络号 xff0c 如果网络号相同 xff0c 就在同一子网 xff0c 否则 xff0c 不在同一子网 例 xf
  • 面试官再问你 HashMap 底层原理,就把这篇文章甩给他看

    前言 HashMap 源码和底层原理在现在面试中是必问的 因此 xff0c 我们非常有必要搞清楚它的底层实现和思想 xff0c 才能在面试中对答如流 xff0c 跟面试官大战三百回合 文章较长 xff0c 介绍了很多原理性的问题 xff0c
  • Java核心技术读书笔记——集合

    本笔记为读 Java核心技术 卷1 第9版 而记录 目录 1 集合接口与实现相互分离1 1Java类库中集合接口和迭代器接口1 2泛型实用方法 2 具体的集合2 1链表2 2数组列表2 3散列表2 4树集2 5对象的比较2 6队列与双端队列
  • #每天一篇论文#(213/365) Joint 2D-3D-Semantic Data for Indoor Scene Understanding 结合2D-3D室内语义数据场景理解

    Joint 2D 3D Semantic Data for Indoor Scene Understanding http 3Dsemantics stanford edu A 摘要 本文提供了一个大型室内空间的数据集 xff0c 它提供了
  • 我心中的AI

    首先说一下我的身份 xff0c 一个刚刚踏入IT行业的年轻小伙 xff0c 相信在坐的大家心中都会有一个小小的梦想 拥有一个 大黄蜂 xff0c 这是我从事这个职业的原因所在 人工智能从诞生以来 xff0c 理论和技术日益成熟 xff0c
  • 2021-09-04 **mininet+flowvisor+floodlight实现网络切片功能**

    mininet 43 flowvisor 43 floodlight实现网络切片功能 这个项目所使用的软件flowvisor 和floodlight 都已经过时了网上能找到的资料太少了 xff0c 整个项目搭建过程中遇到的坑太多了 花了大量
  • CentOS 6.5 时间同步

    1 检查是否安装ntpdate rpm qa grep ntp 有返回说明已经安装 xff0c 若无返回 xff0c 执行安装命令进行安装 2 安装ntpdate yum install y ntp ntpdate 3 修改时区 vi et

随机推荐

  • 在linux安装elasticsearch-7.6.2 所遇到的坑

    64 TOC在linux安装elasticsearch 7 6 2 所遇到的坑 问题描述 刚接触学习elasticsearch xff0c 在linux环境安装就遇到了一些问题 运行角色问题 elasticsearch不建议使用root账号
  • freeRTOS多任务启动流程和源码分析

    最近学习白问网韦东山老师在B站开源的freeRTOS课程 xff0c 网址 xff1a 韦东山直播公开课 xff1a RTOS实战项目之实现多任务系统 第1节 xff1a 裸机程序框架和缺陷 哔哩哔哩 bilibili和7天物联网训练营 第
  • mkdir 创建目录命令

    mkdir命令 mkdir 命令简介 mkdir命令用来创建指定的名称的目录 xff0c 要求创建用户在当前目录权限 xff0c 并且制定的目录名不能是当前目录中已有的目录 命令格式 mkdir 选项 目录 命令参数 m mode 61 模
  • UCOS-II任务间通信(信号量、邮箱、消息队列)

    保护任务之间的共享数据和提供任务之间的通讯方法 xff1a 利用宏OS ENTER CRITICAL 和OS EXIT CRITICAL 来关闭和打开中断 xff0c 这可以用于多任务或者任务和ISR共享某些数据时可以采用这种方法 利用OS
  • 高考到程序员,从娇惯到耐艹

    现在的我刚好是走出校门没两天 xff0c 踏入it行业的程序员 此刻的心情 xff0c 有与挚友分别的不舍 xff0c 有悔恨当初的颓废 xff0c 还有一种提到望月的闯劲儿 总之心理活动错综复杂 xff0c 和高考那会儿玩世不恭的我大不相
  • AI浪潮下需要思考的事

    一 AI的意义 AI xff0c 即ArtificialIntelligence的缩写 xff0c 它是研究如何以人类的智能行为以及思考方式来解决问题的计算机科学的一个分支 目前主要研究的领域包括语音识别 图像识别 自然语言处理以及在某一特
  • Hive(二) -- ddl

    Hive支持标准SQL xff0c 同时又有自己的特点 xff0c 属于方言版SQL Hive的ddl主要包含对于数据库和表的查询 创建和删除 dml包含数据查询和插入 xff0c 其中插入有load和insert两种方式 xff0c 针对
  • autolisp的各种框(DCL)

    一 DCL是什么 前面的事情 xff0c 是通过在命令行输入参数来实现某个指令的 xff0c 而DCL是通过用户界面来实现交互的 下图就是一个典型的DCL 二 DCL怎么用 xff1f 首先说明 xff0c DCL不像lisp xff0c
  • 在hbase shell中过滤器的简单使用

    在hbase shell中查询数据 xff0c 可以在hbase shell中直接使用过滤器 xff1a span class hljs comment hbase shell span gt scan span class hljs st
  • kswapd0占用CPU过高问题处理

    项目场景 xff1a kswapd0占用CPU过高 xff0c 严重影响服务器及虚拟机的使用 问题描述 最近同事反应工作站上的虚拟机太慢了 到虚拟机上看了一下 xff0c 资料占得很满 xff0c 一点很长时间没反应 xff0c 卡得不行
  • QQ新版表情序号及对应

    在学习QQ机器人发送消息接口时遇到了新版表情发送问题 xff0c 以及QQ新版表情序号跟面板中不是完全对应的 xff0c 于是遍历了0 500号表情 xff0c 作一一输出 xff0c 得到了大部分表情的序号及对照如下 xff1a 表情使用
  • Java判断String字符串是否相等时容易出现的问题

    在程序设计中 xff0c 我们经常需要判断字符串是否相等 xff0c 如if a 61 61 b xff0c 但在java中 xff0c a和b两个字符串值相等 xff0c 但有时会判断出不相等的情况 例如 xff1a span class
  • ALDS1_2_C:Stable Sort

    题目链接 xff1a ALDS1 2 C Stable Sort 题目概要 xff1a 扑克牌中存在数字相同而花色不同的情况 xff0c 该题需要利用扑克牌这一特性来比较两种排序 xff1a 冒泡排序 选择排序 xff08 题中给出伪代码
  • jupyter notebook 安装nbextension不显示问题

    2023年4月18日 更新 评论区一位老哥的方法 xff0c 不用下载mark js xff0c 复制一份源目录里的文件改名即可 xff0c 经测试 xff0c 有效 xff0c 评论已置顶 首先放一下安装nbextensions的步骤 如
  • Python对象序列化性能比较:pickle、json、msgpack

    目录 前言三种工具介绍PickleJsonMsgpack性能参考 xff08 由ChatGPT给出 xff09 实际测试测试条件测试结果 前言 最近在做毕设 xff0c 需要读取处理大量的数据 xff0c txt中文文本 xff0c 大概有
  • IRQL_NOT_LESS_OR_EUQAL,间歇性蓝屏,4800h笔记本,暗影精灵6,解决办法,蓝屏问题排查

    目录 前言机器配置蓝屏情况已测试方法及思路前期准备使用WinDbg分析蓝屏文件软件 系统排查 xff1a 驱动排查 xff1a 系统排查 硬件排查硬件检测硬件替换 送修 已知解决办法总结 前言 本文章所列出解决方法适用于AMD Ryzen
  • 单片机PWM输出原理与实践

    一 什么是PWM xff1f PWM xff08 Pulse Width Modulation xff09 脉冲宽度调制 xff0c 它是通过对一系列脉冲的宽度进行调制 xff0c 等效出所需要的波形 xff08 包含形状以及幅值 xff0
  • 数字IC/FPGA面试笔试准备(自用填坑中)

    文章目录 前言常见的IC问题数字电路基础问题Verilog amp SV 跨时钟域信号处理类CRG 同步与异步复位综合与时序分析类低功耗方法STA 静态时序分析 DC综合RTL设计 包含手撕代码 总线问题AXIAPBAHB 体系结构的问题R
  • 时序图工具哪家强?

    设计时序是基本功 xff0c 怎样才能高效的设计时序图呢 xff1f 下面是我搜集到的工具以及我目前在用的工具 xff0c 希望大家能找到最适合自己的工具 Visio 使用步骤 Visio时序图工具 xff0c 其中有一些做好的模具 xff
  • FIFO设计笔记(双口RAM、同步FIFO、异步FIFO)Verilog及仿真

    文章目录 0 前言0 1 FIFO0 2 FIFO与RAM 1 异步双口RAM1 1 原理1 2 Verilog代码1 3 tb仿真 2 FIFO设计前瞻知识2 1 格雷码2 1 1 二进制转格雷码Verilog代码tb仿真 2 1 2 格