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

news2024/10/6 10:34:58

文章目录

    • 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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/410990.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot中操作Redis通过所有可能的key查询存在的key并解析为对象实体的通用方法

场景 SpringBoot中操作Redis的特殊操作-批量查询(通过key的集合批量查杜绝模糊搜索)、查询并解析对象list&#xff1a; SpringBoot中操作Redis的特殊操作-批量查询(通过key的集合批量查杜绝模糊搜索)、查询并解析对象list_霸道流氓气质的博客-CSDN博客 在上面讲操作redis中特…

【未来已来】人人都说GPT,人人都怕GPT,人人都用GPT

文章目录前言一、GPT是什么&#xff1f;二、当GPT和AI遇到摄影总结前言 ChatGPT是由美国OpenAI研发的能够通过自然语言驱动的人工智能技术工具&#xff0c;因为它强大的执行力和任务处理能力&#xff0c;一经亮相就引起了极大的关注。与之类似&#xff0c;在图像智能生成方面&…

Java 自学 - 接口与继承 接口

设计 Java 的接口 在设计 LOL 的时候&#xff0c;进攻类英雄有两种&#xff0c;一种是进行物理系攻击&#xff0c;一种是进行魔法系攻击 这时候&#xff0c;就可以使用接口来实现这个效果。 接口就像是一种约定&#xff0c;我们约定某些英雄是物理系英雄&#xff0c;那么他们…

交接机的基本原理

第七章&#xff1a;交接机的基本原理 在网络中传输数据时需要遵循一些标准&#xff0c;以太网协议定义了数据帧在以太网上的传输标准&#xff0c;了解以太网协议是充分理解数据链路层通信的基础。以太网交换机是实现数据链路层通信的主要设备&#xff0c;了解以太网交换机的工作…

抽象轻松web

不管是求最大值&#xff0c;还是最小值&#xff0c;无论是整数还是小数&#xff0c;数据类型是不是一样的 它们的本质上都是判断&#xff0c;在判断的基础上不断的变换&#xff0c;增加判断条件&#xff0c;增加判断过程罢了 判断需要两个本质 1 &#xff1a;两个以上的元素…

Redis高可用高性能缓存的应用系列2 - 事务机制和IO多路复用、持久化

概述 Redis高可用高性能缓存的应用系列的第二篇&#xff0c;主要介绍Redis事务机制和IO多路复用、和持久化的知识点。 Redis事务机制 Redis事务机制&#xff0c;和Mysql有大的不同&#xff0c;分为4步进行执行&#xff1a; 1.事务提交前&#xff0c;先检查命令语法是否正确…

【2023版】基于部标JT808JT1078车载视频位置监控平台介绍-开源项目

演示地址&#xff1a;http://gps.lingx.com 账号&#xff1a;admin 密码&#xff1a;123456 部标/苏标设备入口 IP&#xff1a;47.100.112.218 &#xff1b; 端口&#xff1a;8808 1.项目背景 “GPS/GPRS车辆智能管理系统”具备车辆实时定位监控、远程监控调度、行驶路线偏移…

ChatGPT帮我和老板吵架,半分钟做了个思维导图...

大家好&#xff0c;我是小z&#xff0c;也可以叫我阿粥最近那个疑似成都电科的截图已经传疯了&#xff0c;贴一张图回味下&#xff1a;这种壮士断腕式的吵法&#xff0c;可以说几乎是单方面的虐杀。但是有朋友问了&#xff0c;在职场中还是有所顾忌&#xff0c;有没有更体面点的…

什么是浪涌保护器防雷过压保护

每年有数十万起雷击和过电压造成的损坏&#xff0c;造成的损失高达数千万元。过压保护装置是电气装置综合保护概念的一部分&#xff0c;可以可靠地防止过压造成的损坏。 过电压损坏 过电压是小于千分之一秒的短暂电压峰值&#xff0c;超过电气设备允许的设计工作电压的许多倍…

因为我没交周报,leader要罚款200元,怎么给他挖坑?能以敲诈勒索罪告他吗?...

没交周报就罚款&#xff0c;这种事你们遇到过吗&#xff1f;一位网友说&#xff1a;leader在群里通知不交周报就罚款&#xff0c;这周罚到他头上&#xff0c;要罚款200元&#xff0c;这种情况怎么办&#xff1f;能定他一个敲诈勒索罪或者抢劫罪吗&#xff1f;最差也要在离职后把…

paddle通过极简方案构建手写数字识别模型

参考&#xff1a; 飞桨PaddlePaddle-源于产业实践的开源深度学习平台 前提条件 在数据处理前&#xff0c;首先要加载飞桨平台与“手写数字识别”模型相关的类库&#xff0c;实现方法如下。 #加载飞桨和相关类库 import paddle from paddle.nn import Linear import paddle.nn…

Spring 之循环依赖

Spring 框架是一个流行的Java应用程序框架&#xff0c;它提供了许多强大的功能&#xff0c;如依赖注入和面向切面编程。然而在使用 Spring 框架时&#xff0c;我们可能会遇到循环依赖的问题。 这种情况发生在两个或多个 Bean 之间相互依赖的情况下&#xff0c;其中一个 Bean 依…

LT8711/LT8712 Type-C转HDMI设计方案|替代LT8711/LT8712芯片|GSV2201可完全替代兼容LT8711/LT8712

LT8711/LT8712 Type-C转HDMI设计方案|替代LT8711/LT8712芯片|GSV2201可完全替代兼容LT8711/LT8712 龙迅&#xff08;Lontium&#xff09;的LT8711/LT8712&#xff0c;是一款Type-C转HDMI 4K的视频转换芯片 通过USB Type-C连接器将DP RX视频信号转换为HDMI/DVI TX视频信号。DP…

【jvm系列-03】精通运行时数据区私有区域---虚拟机栈、程序计数器、本地方法栈

JVM系列整体栏目 内容链接地址【一】初识虚拟机与java虚拟机https://blog.csdn.net/zhenghuishengq/article/details/129544460【二】jvm的类加载子系统以及jclasslib的基本使用https://blog.csdn.net/zhenghuishengq/article/details/129610963【三】运行时私有区域之虚拟机栈…

前端自动化测试之葵花宝典

首先聊一下概念&#xff0c;Web 前端自动化测试是一种通过编写代码来自动化执行 Web 应用程序的测试任务的方法&#xff0c;它通常使用 JavaScript 和测试框架 (如 Selenium、Appium 等) 来实现。 Web 前端自动化测试的优点是可以提高测试效率、减少测试时间和测试成本&#x…

DMA (Direct Memory Access)

DMA&#xff08;Direct Memory Access&#xff09;&#xff1a;直接存储器访问&#xff1b; 一、DMA传输将数据从一个地址空间复制到另一个地址空间&#xff0c;提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。 DMA用来提供在外设和存储器之间或者存储器和存储…

初始SpringBoot

初始SpringBoot1. SpringBoot创建和运行1.1. SpringBoot的概念1.2. SpringBoot的优点1.3. SpringBoot的创建1.3.0. 前置工作:安装插件(这是社区版需要做的工作, 专业版可以忽略)1.3.1. 社区版创建方式1.3.2. 专业版创建方式1.3.3. 网页版创建方式1.4. 项目目录介绍1.5. SpringB…

Matlab在线IDE:MATLAB Online介绍与计算定积分案例

目录1、MATLAB Online介绍功能与特点命令行窗口和编辑器窗口适用场景计费方式使用方法2、注册登录3、计算定积分1、MATLAB Online介绍 MATLAB Online是一款在线IDE&#xff08;集成开发环境&#xff09;&#xff0c;允许用户在Web浏览器中运行MATLAB并使用MATLAB工具箱&#x…

网络安全:内网渗透实例,小白也能看懂内网渗透

一、前言 从 linux 打进去到域环境&#xff0c;到获取域控权限。全篇实操 二、外网打点 1、打开站点&#xff0c;很正常的一个登录界面 2、尝试登录后发现典型的 shiro 特征。 3、使用工具直接打 shiro 反序列化即可。 4、直接上冰蝎马&#xff0c;连接 【一>所有资源获取…

【三十天精通 Vue 3】 第四天 Vue 3的模板语法详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录引言一、Vue 3 模板语法概述1. Vue 3 模板语法的简介2. Vue 3 模板…