同步FIFO设计代码实现
FIFO类似于一根管道,先进的数据先出。FIFO的要点在于对于写满和读空的判断,而写满与读空的判断又依赖于读指针/读地址寄存器和写指针/写地址寄存器的比较。
一组递增的读/写指针用来实现先写的数据先被读出。初始时,读写指针都为0,即指向双端口Memory的同一地址,每一次FIFO写动作都会将数据写入当前写指针对应的存储器地址,然后写指针加1,指向一个新的未写的Memory空间;每次读动作,FIFO当前的读指针对应的数据将会被读出,然后读指针加1,指向下一个待读数据的地址空间。
一开始写指针和读指针都为0,当写入时,写指针增加,当读出时,读指针增加,指针范围为0~DEPTH-1。当写指针和读指针相同时,可能为读空状态,也可能为写完一轮的写满状态;
为了判断究竟是写满状态还是读空状态,可以设置一个计数器,当每写入一个数据时,计数器加1,每读出一个数据时,计数器减1。这样,当计数器为0时,说明数据都已经被读完了,为读空状态,当计数器等于管道深度时,说明写比读多了一轮,为写满状态。
注意,读写指针指向的都是下一个要写/读的数据,意思是说写指针为2时,说明第0,1个数据已经写入了,下一个要写入的数据是第二个。
同步FIFO的时钟复位共用,并定义写入和读出端口:
接着定义RAM,计数器、读写指针:
$clog2函数为以2为底求对数函数,当数据深度为8时,读写指针为3位。
每当仅写入时,计数器加1,每当仅读出时,计数器减1:
当写使能拉高且未读满时,写指针地址加1;当读使能拉高且未读空时,读指针地址加1:
当写使能时,数据写入RAM;当读使能时,数据从RAM读出:
最后,对写满和读空条件进行判断:
testbench:
`timescale 1ns / 1ns module sync_fifo_tb(); parameter DATA_WIDTH=8 ; parameter DATA_DEPTH=8 ; reg clk ; reg rstn ; reg wr_en ; reg [DATA_WIDTH-1:0] wr_data ; wire wr_full ; reg rd_en ; wire [DATA_WIDTH-1:0] rd_data ; wire rd_empty ; sync_fifo #( .DATA_WIDTH (DATA_WIDTH ), .DATA_DEPTH (DATA_DEPTH ) ) u_sync_fifo( .clk (clk ), .rstn (rstn ), .wr_en (wr_en ), .wr_data (wr_data ), .wr_full (wr_full ), .rd_en (rd_en ), .rd_data (rd_data ), .rd_empty (rd_empty ) ); initial clk=1; always#10 clk=!clk; initial begin rstn=0; wr_en=0; rd_en=0; //@(negedge clk)rstn=1; //@(negedge clk)wr_en=1; #21; rstn=1; #100; wr_en=1; wr_data=8'h53; #20; wr_data=8'h7a; #20; wr_data=8'hff; #20; wr_data=8'h69; #20; wr_en=0; #100; rd_en=1; #80; rd_en=0; #100; wr_en=1; wr_data=8'h11; #20; wr_data=8'h22; #20; wr_data=8'h33; #20; wr_data=8'h66; #20; wr_data=8'h99; #20; wr_en=0; rd_en=1; #100; rd_en=0; #100 $finish; end endmodule
仿真结果:
写入RAM时,0地址对应53,1地址对应7a,2地址对应ff,3地址对应69,读出时53,7a,ff,69分别对应读指针为0,1,2,3;当写使能信号拉高时,在下一个时钟上升沿写指针才加1,这是由于阻塞赋值,这也对应了上文所说的每一次FIFO写动作都会将数据写入当前写指针对应的存储器地址,然后写指针加1,指向一个新的未写的Memory空间。当计数器为0时,说明所有数据被读出,读空信号拉高。一开始读空信号也是拉高的,直到开始写入数据。最后可以看出,当写入第九个数据后,buffer[0]变为8‘h99。在FIFO设计中,一定要注意不要往写满的FIFO中写入,不要从读空的FIFO中的读出。