异步FIFO请看: 异步FIFO
===========------------------
👉注:本文所使用的的所有代码均已编译并仿真通过,仿真结果附于文中。
👉注:更多精彩请看: 面试常问的verilog代码汇总 一文
===========------------
前言
FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
FIFO使用场景
- 异步FIFO一般用于不同时钟域之间的数据传输,在两个时钟域之间采用FIFO作为数据缓冲;
- 对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。
FIFO参数
- 宽度:THE WIDTH,是FIFO一次读写操作的数据位宽
- 深度:THE DEEPTH,是FIFO可以存储多少个N位的数据(如果宽度为N)。如一个8位的FIFO,若深度为8,它可以存储8个8位的数据,深度为12 ,就可以存储12个8位的数据
- 满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow
- 空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)
- 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据
- 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据
- 读指针:指向下一个读出地址。读完后自动加1
- 写指针:指向下一个要写入的地址的,写完自动加1。
读写指针其实就是读写的地址,只不过这个地址不能任意选择,而是连续的。
同步FIFO
同步FIFO的读写指令由同一个时钟控制,主要由两个模块构成:控制模块和RAM。
- 控制模块:地址控制模块可以根据读写指令,生成RAM地址;
- RAM:根据控制模块生成的地址信号进行数据的存储和读取操作;
- 用计数器进行空满标志的判断
读写过程:
- 堆栈空:读指针和写指针指向第一个存储单元;
- 写数据:当写入一个数据时,写数据指针指向下一个存储单元;经过(深度 -1)次写数据操作后,写指针指向最后一个存储单元;当经过写操作次数为FIFO深度时,写指针将回到第一个存储单元,并且显示堆栈为满。
- 读操作:类似于写操作,当读出全部数据后,指针指向首单元。
下面是一个8x8同步FIFO的verilog代码:
module syn_fifo(clk,rst_n,write_to_stack,read_from_stack,data_in,data_out);
parameter stack_width = 8;//FIFO宽度
parameter stack_heigh = 8;//FIFO深度
parameter stack_addr_width =3;//读写地址位宽
input clk,rst_n;
input write_to_stack,read_from_stack;//数据读写使能信号
input [stack_width-1:0]data_in;//写入FIFO的数据
output [stack_width-1:0]data_out;//读出FIFO的数据
reg [stack_width-1:0]data_out;
reg [stack_addr_width-1:0]write_ptr;//每写一次,加1
reg [stack_addr_width-1:0]read_ptr;//每读一次,加1
reg [stack_addr_width:0]gap_ptr;//计数器,FIFO内部数据量
reg [7:0]raml[7:0];// 8*8 bit寄存器
//空满逻辑生成
wire stack_full,stack_empty;
assign stack_full = (gap_ptr == stack_heigh);//当数据存储量为堆栈深度时,此时堆栈满
assign stack_empty = (gap_ptr == 0);//当数据存储量为0时,此时堆栈空
//写操作
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
write_ptr <= 0;
gap_ptr <= 0;
end
//如果堆栈不满,并且此时写指令有效 ———>写操作
else if(write_to_stack && (!stack_full) ) begin
write_ptr <= write_ptr + 1;//写指针加1
gap_ptr <= gap_ptr + 1;//FIFO内部数据数量加1
raml[write_ptr] <= data_in;
end
end
//读操作
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
read_ptr <= 0;
gap_ptr <= 0;
end
//如果堆栈不空,并且读指令有效 ———>读操作
else if( (!stack_empty) && read_from_stack) begin
read_ptr <= read_ptr + 1;//读指针加1
gap_ptr <= gap_ptr - 1;//FIFO内部数据数量减1
data_out <= raml[read_ptr];
end
end
endmodule
//------------------TB-----------------------
`timescale 1ns/1ps
module TB;
reg clk,rst_n;
reg write_to_stack,read_from_stack;//数据读写使能信号
reg [7:0]data_in;
wire [7:0]data_out;
FIFO_buffer dut(.clk(clk),
.rst_n(rst_n),
.write_to_stack(write_to_stack),
.read_from_stack(read_from_stack),
.data_in(data_in),
.data_out(data_out)
);
initial begin
clk <= 0;
forever begin
#5 clk <= ~clk;
end
end
initial begin
rst_n <= 0;
#5 rst_n <= 1;
end
initial begin
#10 write_to_stack <= 1;
read_from_stack <= 0;
repeat(7) begin
@(posedge clk);
data_in = #(0.01) $random;
end
data_in = #(0.01) $random;
write_to_stack <= 0;
read_from_stack <= 1;
repeat(8) begin
@(posedge clk);
end
write_to_stack <= 0;
read_from_stack <= 0;
rst_n <= 0;
repeat(2) begin
@(posedge clk);
end
rst_n <= 1;
#5 write_to_stack <= 1;
read_from_stack <= 0;
repeat(10) begin
@(posedge clk);
data_in = #(0.01) $random;
end
write_to_stack <= 0;
read_from_stack <= 1;
repeat(10) begin
@(posedge clk);
end
rst_n <= 0;
repeat(2) begin
@(posedge clk);
end
rst_n <= 1;
#5 write_to_stack <= 1;
read_from_stack <= 1;
repeat(10) begin
@(posedge clk);
data_in = #(0.01) $random;
end
data_in = #(0.01) $random;
write_to_stack <= 0;
read_from_stack <= 1;
repeat(10) begin
@(posedge clk);
end
write_to_stack <= 0;
read_from_stack <= 0;
#200 $finish();
end
endmodule
下图测试:为连续写入7个数据,再读出。
结果:FIFO存储数据一致,空满标致正常;
下图测试:为写入10个数据,再读出。