文章目录
- 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读地址、读有效信号。
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
仿真效果
2、FIFO设计前瞻知识
2.1、格雷码
-
格雷码简介:
在一组数的编码中,若任意两个相邻的代码只有一位二进制数不同,则称这种编码为格雷码(Gray Code),另外由于最大数与最小数之间也仅一位数不同,即“首尾相连”,因此又称循环二进制码或反射二进制码。
-
格雷码用途:
格雷码的特点是从一个数变为相邻的一个数时,只有一个数据位发生跳变,由于这种特点,就可以避免二进制编码计数组合电路中出现的亚稳态。格雷码常用于通信,异步FIFO 或者 RAM 地址寻址计数器中。这里有一个链接,讲异步FIFO里为什么要用格雷码? 跨时钟域传输的黄金搭档:异步FIFO与格雷码 (qq.com)。
格雷码属于可靠性编码,是一种错误最小化的编码方式,因为,自然二进制码可以直接由数/模转换器转换成模拟信号,但某些情况,例如从十进制的 3 转换成 4 时二进制码的每一位都要变,使数字电路产生很大的尖峰电流脉冲。
而格雷码则没有这一缺点,**它是一种数字排序系统,其中的所有相邻整数在它们的数字表示中只有一个数字不同。它在任意两个相邻的数之间转换时,只有一个数位发生变化。**它大大地减少了由一个状态到下一个状态时逻辑的混淆。- 自然二进制码与格雷码的对照表:
十进制数 自然二进制数 格雷码 十进制数 自然二进制数 格雷码 0 0000 0000 8 1000 1100 1 0001 0001 9 1001 1101 2 0010 0011 10 1010 1111 3 0011 0010 11 1011 1110 4 0100 0110 12 1100 1010 5 0101 0111 13 1101 1011 6 0110 0101 14 1110 1001 7 0111 0100 15 1111 1000
格雷码相邻两项只差一位。从0开始,观察每次改的是哪一位,你可以得到这个数列:
1,2,1,3,1,2,1,4,1,2,1,3,1,2,1 是不是很有规律?这样便于记忆。数字代表写一个格雷码变化的数的位置。
- 格雷码优缺点
- 优点:属于压缩状态编码,使用的触发器位数少;相邻状态变换时,仅一位发生改变,电噪声小,转换速度较快;
- 缺点:译码复杂,没有固定大小,很难直接进行比较大小和算术运算,需要转换为自然二进制码来判断。
2.1.1、二进制转格雷码
-
二进制码->格雷码(编码):从最右边一位起,依次将每一位与左边一位异或(XOR),作为本位的格雷码值,最高位不变(相当于最高位的左边是 0);
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
仿真效果
2.1.2、格雷码转二进制
-
格雷码->二进制码(解码):从左边第二位起,将每位与左边一位解码后的值异或,作为该位解码后的值(最左边一位依然不变)。
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
仿真效果:
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读空、读错等状态信号。
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也是空状态。
-
-
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
仿真效果
3.3、拓展地址位实现同步FIFO
3.3.1、设计方法
因为FIFO的特点为先入先出,因此读写的开始的地址都是0。
-
当读地址与写地址相同时,则可判断FIFO为空。
-
当读地址与写地址除最高位相反,其他位相同时,则可判断FIFO为满。
-
假设RAM深度为8,扩展后的地址为4位。
- 开始写入4个数据,此时写地址为0100,读地址此时为0000;
- 然后开始读数据,读完4个数据后,读地址此时为0100,此时可判断FIFO为空;
- 后对FIFO进行写数据,连续写入8个数据,此时写地址为1100,已知深度为8,判断FIFO为满,此时的写读地址比较发现:最高位相反,其余位相同。
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)