同步异步FIFO
同步 FIFO / 异步 FIFO
FIFO 简介
FIFO 的参数
- 宽度:一次读写操作的数据位
- 深度:可以存储的 N 位数据的数目 (宽度为 N)
- 满标志:FIFO 已满或将要满时,由 FIFO 的状态电路送出的信号,阻止 FIFO 写操作
- 空标志:FIFO 已空或将要空时,由 FIFO 的状态电路送出的信号,阻止 FIFO 读操作
- 读时钟:读操作所遵循的时钟
- 写时钟:写操作所遵循的时钟
设计空满标志位电路
- 正确产生空满信号是任何 FIFO 设计的关键。其设计原则是:能写满而不溢出,能读空而不多读。
- 在写时钟域下,判断读写指针的关系,生成满标志
- 在读时钟域下,判断读写指针的关系,生成空标志
对于空满判断,可看下图
在地址中额外添加一个位,当写指针增加并越过最后一个地址位时,就将这个额外的位 (MSB) 加一,其他位回 0。读指针也进行同样的操作。额外的位作为折回标志位。如果两个指针的 MSB 不同,其余位相等,说明读指针比写指针少折回了一次,这种情况下,FIFO 为满。而读写指针所有位都相等,说明折回次数相等,此时 FIFO 为空。
同步 FIFO 和异步 FIFO 的异同
- 同步 FIFO 的写时钟和读时钟为同一个时钟,FIFO 内部所有逻辑都是同步逻辑,常常用于交互数据缓冲。
- 异步 FIFO 的写时钟和读时钟为异步时钟,FIFO 内部的写逻辑和读逻辑的交互需要异步处理,异步 FIFO 常用于跨时钟域交互。
数据流的传输与指示信号不同在于
- 数据流大多具有连续性,及背靠背传输
- 数据流要求信号具有较快的传输速度。主要的方案是利用 FIFO 进行传输。
构成
- FIFO_MEMORY
- 双口 ram 存储数据
- Sync_r2w
- 同步读数据指针到写时钟域
- Sync_w2r
- 同步写数据指针到读时钟域
- Wptr_full
- 处理写指针和满信号的逻辑
- Rptr_empty
- 处理读指针和空信号的逻辑
二进制的读写指针通常位宽超过了 1bit,而多比特信号是不可以直接使用两级同步器的。这时,考虑到 FIFO 地址是连续变化的,我们使用格雷码来进行传输。
读指针和写指针的标志位也需要加到格雷码里面,由此判断条件是
- MSB 不相等
- 次高位不相等
- 其余位相等
格雷码转换公式
assign gray=(binary>>1)^(binary);
[!note]
为什么要设置一个 fifo 上限,是因为可能传过来时,异步 fifo 一侧 wr 侧的过快,rd 侧没有反应导致溢出
为什么需要用格雷码传输?
不使用格雷码传递
假设现在 wr_ptr = 0011,rd_ptr = 0011,此时为空的情况
如果 wr 时域发生写操作,则 wr_ptr = 0100,此时 fifo 里有效存储为 1,可供 rd 端读取一个值。但 wr_ptr 在传送到写时域除了 0100 还可能出现 0001 和 0111 等多种状态。
我只举例两种情况,wr_ptr 和 rd_ptr 都在读时域下
- 如果是 wr_ptr = 0001 时,wr_ptr < rd_ptr,出现 error
- 如果是 wr_ptr = 0111 时,wr_ptr > rd_ptr,此时读时域会以为 num=4,可能发生空读情况
使用格雷码传输
同样假设现在 wr_ptr = 0011,rd_ptr = 0011,此时为空的情况
如果 wr 端发生写操作,则 wr_ptr = 0010,此时 fifo 里有效存储为 1,可供 rd 端读取一个值。但 wr_ptr 在读时域上只可能出现 0010 和 0111 两种状态。
如果依然保持 0010,则指示 FIFO 为空,不发生读取
如果变为 0111,则指示 FIFO 容量为 1,正常读取
注意
Gray 码只是在相邻两次跳变之间才会出现只有 1 位数据不一致的情形,超过两个周期则不一定
所以,地址总线 bus skew 一定不能超过一个周期,否则可能出现 gray 码多位数据跳变的情况,这个时候 gray 码就失去了作用,因为这时候同步后的地址已经不能保证只有 1 位跳变了。
设计合理的 FIFO 深度
一般来说是写采用的是 burst,单次大量数据
所以本质上需要
$$
FIFO 被填满的时间 > 数据包传送的时间
转换到读写速率里是
转换到读写速率里是
转换到读写速率里是
(FIFO 深度 / 「写速率 - 读速率) > 写入数据量/写入速率
$$
写时钟的频率为 Fa
,读时钟的频率为 Fb
场景一 Fa > Fb 读和写之间没有空闲 cycle
假设:
- 写数据时钟频率 fa=80MHz
- 读数据时钟频率 fb=50MHz
- 突发长度= number of data to be transferred = 120
- 在突发传输过程中,数据都是连续读写的
解答
- 写一个数据所需时间 = 1/80MHz = 12.5ns
- 读一个数据所需时间 = 1/50MHz = 20ns
- 一次 burst 所需时间 12.5 * 120 = 1500 ns
- 1500 ns 可以读走 1500 / 20 = 75 个数据
- 所以 FIFO 深度为 120 - 75 = 45 个
场景二 Fa > Fb 读会在写之后空两拍
背景:异步 FIFO 中采用格雷码和两级同步寄存器降低亚稳态的概率,所以会慢两拍
此时 wr_ptr 同步到读时域时,可能会出现显示为空,但实际非空的情况,并且 FIFO 可能一直还在写入。所以读端的写时钟一定小于等于当前的实际 wr_ptr。
此时 rd_ptr 同步到写时域时,可能会出现显示为满,但实际非满的情况,并且 FIFO 可能一直还在读出。所以读端的写时钟一定小于等于当前的实际 wr_ptr。
我最开始会有疑问?
[!question]
显示满,实际非满,那么会停止写入,FIFO 实际使用的深度没有设计的高,那不需要设计那么高
FIFO 的作用是存储一次 burst,为了避免一次 burst 写不完,应该使得 FIFO 深度更大一些
由此场景二 FIFO 的深度需要略大于场景一。
场景四 Fa > Fb 读和写都会有空白拍
假设
- 写数据时钟频率 fa=80MHz
- 读数据时钟频率 fb=50MHz
- 突发长度= number of data to be transferred = 120
- 每隔 1 个 cycle 写一次
- 每隔 3 个 cycle 读一次
解答
- 写一个数据所需时间 = 1/80MHz = 12.5ns
- 读一个数据所需时间 = 1/50MHz = 20ns
- 一次 burst 所需时间 (12.5) * 2 * 120 = 3000 ns
- 3000 ns 可以读走 3000 / ( 20 * 4) = 37.5 个数据
- 所以 FIFO 深度为 120 - 37.5 = 82.5 个
场景五 Fa < Fb 读和写之间没有空闲 cycle
假设
- 写数据时钟频率 fa=40MHz
- 读数据时钟频率 fb=50MHz
- 突发长度= number of data to be transferred = 120
- 在突发传输过程中,数据都是连续读写的
FIFO 深度设置为 1 就好了
已知 Fa 和 Fb,读和写随机
假设
- 写数据时钟频率 fa=80MHz
- 读数据时钟频率 fb=50MHz
- 在写时钟周期内,每 100 个周期就有 40 个数据写入 FIFO
- 在读时钟周期内,每 10 个周期可以有 8 个数据读出 FIFO
最坏的情况是连续两次背靠背的情况,需要连续写入 80 个数据
首先检验是否有解?在最复杂的情况下,读取所花费的时间 < 写入所花的时间
- 200 个周期写入 80 个数据所需时间:1 / 80MHz * ( 80 * 100 / 40) = 2500ns
- 读出 80 个数据中所需时间:1 / 50MHz * (80 * 10 / 8) = 2000ns
- 2500ns > 2000ns 有解
- 连续写入 80 个数据所需要的时间: 1/ 80MHz * 80 = 1000ns
- 读出一个数据需要的时间:1/50MHz * 10 / 8 = 25 ns
- 1000ns 可以读出的数据个数:1000 / 25 = 40 个
- 所以 FIFO 深度 80 - 40 = 40 个
总结:
- 写时钟频率为
fwr
- 读时钟频率为
frd
- 在写时钟周期内,每 m 个周期内就有 n 个数据写入 FIFO
- 在读时钟周期内,每 x 个周期内可以有 y 个数据读出 FIFO
那么:
- 首先必须满足
(1/fwr) * (m/n) ≥ (1/frd) * (x/y)
- ”背靠背“的情形下是 FIFO 读写的最坏情形,burst 长度
B = 2 * n
- 写完 burst 长度数据最快所需时间
T = (1/fwr) * B
- 从 FIFO 中读出一个数据至少需要时间
t= (1/frd) * (x/y)
- 在 T 时间内能够从都走的数据个数 =
T/t = B * (frd/fwr) * (y/x)
- 在 T 时间内还没有读走的数据个数 =
B - B * (frd/fwr) * (y/x)
- 因此 FIFO 的最小深度为
B - B * (frd/fwr) * (y/x)
- 注意保留一些余量
同步 FIFO Verilog 代码
如果有问题,欢迎留言
代码
module synchronous_fifo#(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 4,
parameter DEPTH = 1 << ADDR_WIDTH
)(
input clk,
input rst_n,
input wr_en,
input rd_en,
input [DATA_WIDTH-1:0] data_in,
output full,
output empty,
output [DATA_WIDTH-1:0] data_out
);
integer i;
reg [DATA_WIDTH-1:0] data_out_r;
reg [DATA_WIDTH-1:0] data_out;
reg [ADDR_WIDTH-1:0] wr_ptr;
reg [ADDR_WIDTH-1:0] rd_ptr;
reg [DATA_WIDTH-1:0] ram[DEPTH-1:0];
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr <= 0;
end else if (wr_en)
wr_ptr <= wr_ptr + 1 ;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_ptr <= 0;
end else if (rd_en)
rd_ptr <= rd_ptr + 1 ;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for(i=0;i < DEPTH;i++)
ram[i] <= {DATA_WIDTH{1'b0}};
end else if (wr_en)
ram[wr_ptr] <= data_in;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_out_r <= 0;
end else if (rd_en)
data_out_r <= ram[rd_ptr] ;
end
assign data_out = data_out_r;
assign empty = wr_ptr == rd_ptr;
assign full = (wr_ptr[ADDR_WIDTH-2:0] == rd_ptr[ADDR_WIDTH-2:0]) && (wr_ptr[ADDR_WIDTH-1]!=rd_ptr[ADDR_WIDTH-1]);
endmodule
Testbench
module synchronous_fifo_tb;
// Parameters definition
parameter DATA_WIDTH = 8;
parameter ADDR_WIDTH = 4;
parameter DEPTH = 1 << ADDR_WIDTH;
parameter CLK_PERIOD = 10; // 100MHz clock
// Signal declaration
reg clk;
reg rst_n;
reg wr_en;
reg rd_en;
reg [DATA_WIDTH-1:0] data_in;
wire full;
wire empty;
wire [DATA_WIDTH-1:0] data_out;
// Verification counters
integer write_cnt;
integer read_cnt;
integer error_cnt;
// DUT instantiation
synchronous_fifo #(
.DATA_WIDTH ( DATA_WIDTH ),
.ADDR_WIDTH ( ADDR_WIDTH ),
.DEPTH ( DEPTH )
) fifo_inst (
.clk ( clk ),
.rst_n ( rst_n ),
.wr_en ( wr_en ),
.rd_en ( rd_en ),
.data_in ( data_in ),
.full ( full ),
.empty ( empty ),
.data_out ( data_out )
);
// Clock generation
always #(CLK_PERIOD/2) begin
clk = ~clk;
end
// Tasks definition
task initialize;
begin
clk = 1'b0;
rst_n = 1'b1;
wr_en = 1'b0;
rd_en = 1'b0;
data_in = 'd0;
write_cnt = 0;
read_cnt = 0;
error_cnt = 0;
end
endtask
task reset;
begin
rst_n = 1'b0;
#(CLK_PERIOD*2);
rst_n = 1'b1;
#(CLK_PERIOD);
end
endtask
task write_data;
input [DATA_WIDTH-1:0] data;
begin
@(posedge clk);
if (full) begin
$display("Error: Attempting to write when FIFO is full at time %0t", $time);
error_cnt = error_cnt + 1;
end
else begin
wr_en = 1'b1;
data_in = data;
write_cnt = write_cnt + 1;
@(posedge clk);
wr_en = 1'b0;
end
end
endtask
task read_data;
begin
@(posedge clk);
if (empty) begin
$display("Error: Attempting to read when FIFO is empty at time %0t", $time);
error_cnt = error_cnt + 1;
end
else begin
rd_en = 1'b1;
read_cnt = read_cnt + 1;
@(posedge clk);
rd_en = 1'b0;
end
end
endtask
// Main test procedure
initial begin
// Initialize and reset
initialize;
reset;
// Test 1: Write until full
$display("\nTest 1: Writing until FIFO is full");
repeat(DEPTH/2) begin
write_data($random);
#(CLK_PERIOD);
end
if (full) begin
$display("PASS: FIFO is full after %0d writes", DEPTH);
end
else begin
$display("FAIL: FIFO should be full after %0d writes", DEPTH);
error_cnt = error_cnt + 1;
end
// Test 2: Read until empty
$display("\nTest 2: Reading until FIFO is empty");
repeat(DEPTH/2) begin
read_data;
#(CLK_PERIOD);
end
if (empty) begin
$display("PASS: FIFO is empty after %0d reads", DEPTH);
end
else begin
$display("FAIL: FIFO should be empty after %0d reads", DEPTH);
error_cnt = error_cnt + 1;
end
// Test 3: Alternating read/write
$display("\nTest 3: Alternating read/write operations");
repeat(5) begin
write_data($random);
#(CLK_PERIOD*2);
read_data;
#(CLK_PERIOD*2);
end
// Test 4: Write when full
$display("\nTest 4: Testing write when full condition");
repeat(DEPTH/2+1) begin
write_data($random);
#(CLK_PERIOD);
end
// Test 5: Read when empty
$display("\nTest 5: Testing read when empty condition");
repeat(DEPTH/2+1) begin
read_data;
#(CLK_PERIOD);
end
// Test results summary
$display("\n=== Test Complete ===");
$display("Total Writes: %0d", write_cnt);
$display("Total Reads: %0d", read_cnt);
$display("Total Errors: %0d", error_cnt);
if (error_cnt == 0) begin
$display("TEST PASSED!");
end
else begin
$display("TEST FAILED!");
end
#(CLK_PERIOD*5);
$finish;
end
// Watchdog timer
initial begin
#10000;
$display("Timeout Error: Simulation took too long!");
$finish;
end
initial begin
$fsdbDumpfile("tb.fsdb"); // 波形文件名
$fsdbDumpvars(0); // 0表示dump所有层级
$fsdbDumpMDA(); // 专门用于dump内存数组
$fsdbDumpMemNow; // 立即dump当前内存内容
end
endmodule
波形图
异步 FIFO Verilog 代码
参考博文
如果有问题,欢迎留言
设计思路图
设计需求
- 判断 empty 和 full ,wr_ptr 和 rd_ptr 需要过两拍对比
- 写入和读取 fifo 时使用的地址是 wr_ptr_binary 和 rd_ptr_binary,每次 +1
- 用来比较 empty 和 full,使用的是 wr_ptr_grey 和 rd_ptr_grey
- 所有的 ptr 都会多出一位,用来指示 flag
代码
module asynchronous_fifo#(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 4,
parameter DEPTH = 1 << ADDR_WIDTH
)(
input wclk,
input wen,
input wrst_n,
input rclk,
input ren,
input rrst_n,
input [DATA_WIDTH-1:0] data_in,
output reg full,
output reg empty,
output reg [DATA_WIDTH-1:0] data_out
);
//ptr of binary
reg [ADDR_WIDTH:0] wr_ptr_binary;
reg [ADDR_WIDTH:0] rd_ptr_binary;
//ptr of grey
wire [ADDR_WIDTH:0] wr_ptr_grey;
wire [ADDR_WIDTH:0] rd_ptr_grey;
reg [ADDR_WIDTH:0] wr_ptr_next;
reg [ADDR_WIDTH:0] rd_ptr_next;
reg [ADDR_WIDTH:0] wr_ptr_next2;
reg [ADDR_WIDTH:0] rd_ptr_next2;
reg [DATA_WIDTH-1:0] ram[DEPTH-1:0];
always @(posedge wclk or negedge wrst_n)begin
if(!wrst_n)
wr_ptr_binary <= 0;
else if (!full && wen)
wr_ptr_binary <= wr_ptr_binary + 1;
end
assign wr_ptr_grey = (wr_ptr_binary >> 1) ^ wr_ptr_binary;
always @(posedge rclk or negedge rrst_n)begin
if(!rrst_n)
rd_ptr_binary <= 0;
else if (!empty && ren)
rd_ptr_binary <= rd_ptr_binary + 1;
end
assign rd_ptr_grey = (rd_ptr_binary >> 1) ^ rd_ptr_binary;
always @(posedge wclk or negedge wrst_n)begin
if(!rrst_n) begin
rd_ptr_next <= 0;
rd_ptr_next2 <= 0;
end
else
{rd_ptr_next2,rd_ptr_next} <= {rd_ptr_next,rd_ptr_grey} ;
end
always @(posedge rclk or negedge rrst_n)begin
if(!rrst_n) begin
wr_ptr_next <= 0;
wr_ptr_next2 <= 0;
end
else
{wr_ptr_next2,wr_ptr_next} <= {wr_ptr_next,wr_ptr_grey} ;
end
assign empty = wr_ptr_next2[ADDR_WIDTH:0] == rd_ptr_grey[ADDR_WIDTH:0];
assign full = (rd_ptr_next2[ADDR_WIDTH:ADDR_WIDTH-1] ^ wr_ptr_grey[ADDR_WIDTH:ADDR_WIDTH-1]) && (rd_ptr_grey[ADDR_WIDTH-2:0] == wr_ptr_next2[ADDR_WIDTH-2:0]);
always @(posedge wclk )begin
if(wen && !full)
ram[wr_ptr_binary[ADDR_WIDTH-1:0]] <= data_in;
end
always @(posedge rclk )begin
if(ren && !empty)
data_out <= ram[rd_ptr_binary[ADDR_WIDTH-1:0]];
end
endmodule
Testbech
module async_fifo_tb;
// Parameters
parameter DATA_WIDTH = 8;
parameter ADDR_WIDTH = 4;
parameter DEPTH = 1 << ADDR_WIDTH;
parameter WR_CLK_PERIOD = 10; // 100MHz
parameter RD_CLK_PERIOD = 8; // 125MHz
// Signals
reg wclk;
reg wen;
reg wrst_n;
reg rclk;
reg ren;
reg rrst_n;
reg [DATA_WIDTH-1:0] data_in;
wire full;
wire empty;
wire [DATA_WIDTH-1:0] data_out;
// Instantiate DUT
asynchronous_fifo #(
.DATA_WIDTH(DATA_WIDTH),
.ADDR_WIDTH(ADDR_WIDTH),
.DEPTH(DEPTH)
) dut (
.wclk(wclk),
.wen(wen),
.wrst_n(wrst_n),
.rclk(rclk),
.ren(ren),
.rrst_n(rrst_n),
.data_in(data_in),
.full(full),
.empty(empty),
.data_out(data_out)
);
// Clock generation
initial begin
wclk = 0;
forever #(WR_CLK_PERIOD/2) wclk = ~wclk;
end
initial begin
rclk = 0;
forever #(RD_CLK_PERIOD/2) rclk = ~rclk;
end
// Test stimulus
initial begin
// Initialize signals
wen = 0;
wrst_n = 1;
ren = 0;
rrst_n = 1;
data_in = 0;
// Reset sequence
#100;
wrst_n = 0;
rrst_n = 0;
#100;
wrst_n = 1;
rrst_n = 1;
#100;
// Test case 1: Write operation
$display("Test case 1: Writing data to FIFO");
repeat(8) @(posedge wclk) begin
wen = 1;
data_in = $random;
$display("Writing data: %h", data_in);
end
wen = 0;
// Wait for synchronization
#200;
// Test case 2: Read operation
$display("Test case 2: Reading data from FIFO");
repeat(8) @(posedge rclk) begin
ren = 1;
//@(posedge rclk);
$display("Read data: %h", data_out);
end
ren = 0;
// Test case 3: Full condition test
$display("Test case 3: Testing full condition");
wen = 1;
repeat(DEPTH+2) @(posedge wclk) begin
data_in = $random;
if (full)
$display("FIFO is full at count %d", $time);
end
wen = 0;
// Test case 4: Empty condition test
$display("Test case 4: Testing empty condition");
ren = 1;
repeat(DEPTH+2) @(posedge rclk) begin
if (empty)
$display("FIFO is empty at count %d", $time);
end
ren = 0;
// Test case 5: Simultaneous read/write
$display("Test case 5: Simultaneous read/write operations");
fork
begin
repeat(5) @(posedge wclk) begin
wen = 1;
data_in = $random;
$display("Writing data: %h", data_in);
end
wen = 0;
end
begin
repeat(5) @(posedge rclk) begin
ren = 1;
@(posedge rclk);
$display("Read data: %h", data_out);
end
ren = 0;
end
join
// End simulation
#1000;
$display("Simulation completed successfully");
$finish;
end
// Monitor for full/empty conditions
always @(posedge full)
$display("FIFO full detected at time %d", $time);
always @(posedge empty)
$display("FIFO empty detected at time %d", $time);
initial begin
$fsdbDumpfile("tb.fsdb"); // 波形文件名
$fsdbDumpvars(0); // 0表示dump所有层级
$fsdbDumpMDA(); // 专门用于dump内存数组
$fsdbDumpMemNow; // 立即dump当前内存内容
end
endmodule