FPGA的FIFO实现过程

fifo队列是一种数据缓冲器,用于数据的缓存。他是一种先入先出的存储器,即最先写入的数据,最先读。fifo的参数有数据深度和数据宽度。数据宽度是指存储数据的宽度。深度是指存储器可以存储多少个数据。
fifo队列有两个标志位。一个满和一个空标志位。分别表示fifo是数据写满,还是数据读空。在数据写满状态下,数据写入是不允许的,因此在这个状态下,写入的数据无效。而数据读空状态下,数据读取是不允许的,因此在这个状态下,读取的数据无效。
fifo队列有两个位置指示指针。一个是写指针,指向队列的第一个存储单元。一个读指针,指向队列的最后一个存储单元。当有写命令的时候,数据写入写指针指向的存储单元,然后指针加一。当有读命令的时候,读指针加一,在读出读指针指向的存储单元的数据。这里读命令,指针要加一,是定义读数据,是读出读指针的下一个存储单元的数据。
当写指针和读指针的指向存储单元一样时,这时候根据之前是读命令还是写命令来判断队列是空,还是满。在读命令,两个指针值一样时候,则队列空。在写命令,两个指针值一样,则队列满。
以后就开始写代码实现上诉fifo队列,并进行仿真。
以下,是实现数据宽度为8.深度为2^4的深度的fifo。。读/写时钟是同一个。
module fifo_cus
#(
parameter n = 8, //数据宽度
parameter m = 4 //fifo的地址宽度
)
//对队列的参数设置。建议这样写,便于以后代码的移植。
//如果以后要实现数据宽度为16,深度为2^8的fifo。只需改n =16 m=8即可
(
input clk, //输入时钟
input rst_n, //输入复位信号
input wr, //输入写使能
input[n-1:0] w_data, //输入输入
input rd, //输入读使能
output empty, //输出fifo空标志
output full, //输出fifo满标志
output[n-1:0] r_data //输出读取的数据
);
//寄存器组,用来充当fifo队列
reg [n-1:0] array_reg [2**m - 1:0];
//定义写指针,指示当前写的位置,下一个状态写的位置,写位置的下一个位置
reg [m-1:0] w_ptr_reg, w_ptr_next,w_ptr_succ;
//定义读指针,指示当前读的位置,下一个状态读的位置,读位置的下一个位置
reg [m-1:0] r_ptr_reg, r_ptr_next,r_ptr_succ;
//定义fifo满和空的信号
reg full_reg, full_next;
reg empty_reg, empty_next;
wire wr_en;
//数据的写入,在数据的上升沿的时候,有写使能信号,将数据写入。而
always@( posedge clk ) begin
if( wr_en )
array_reg[w_ptr_reg] <= w_data;
else
array_reg[w_ptr_reg] <= array_reg[w_ptr_reg];
end
// 数据的读取。数据读取是一直在读取的,不过读取的是之前的值。
assign r_data = array_reg[r_ptr_reg];
assign wr_en = wr & ~full_reg;
/*状态跳转
在复位信号有效,读/写指针都指向0地址。此时队列状态为空。
在复位不有效,且在时钟的上升沿,读/写指针的值,队列空,满状态的值又下一状态决定。否则保持 */
always@( posedge clk ) begin
if( !rst_n )
begin
w_ptr_reg <= 0;
r_ptr_reg <= 0;
full_reg <= 1'b0;
empty_reg <= 1'b1;
end
else
begin
w_ptr_reg <= w_ptr_next;
r_ptr_reg <= r_ptr_next;
full_reg <= full_next;
empty_reg <= empty_next;
end
end
//下一个状态的判定
always@ * begin
w_ptr_next = w_ptr_reg;
r_ptr_next = r_ptr_reg;
full_next = full_reg;
empty_next = empty;
w_ptr_succ = w_ptr_reg + 1'b1;
r_ptr_succ = r_ptr_reg + 1'b1;
case( {wr,rd} )
/*读命令:在读命令下,如果队列不为空,讲当前读指针的下一个指针赋值给读指针的下一个状态,同时将队列的满标志置0。
然后判断读指针的下一个指针是否和写指针的值一样。一样的话,说明,队列为空。否则不为空。 */
2'b01:
begin
if( ~empty_reg )
begin
r_ptr_next = r_ptr_succ;
full_next = 0;
if( r_ptr_succ == w_ptr_reg )
empty_next = 1'b1;
else
empty_next = 1'b0;
end
end
/*写命令:在写命令下,如果队列不为满,将当前写指针的下一个指针赋值给读指针的下一个状态,同时将队列的空标志置0。
然后判断写指针的下一个指针是否和读指针的值一样。一样的话,说明,队列为满。否则不为满。
*/
2'b10:
begin
if( ~full_reg )
begin
w_ptr_next = w_ptr_succ;
empty_next= 0;
if( w_ptr_succ == r_ptr_reg )
full_next = 1'b1;
else
full_next = 1'b0;
end
end
/*读写命令:在读写命令下, 直接改变对应指针的下一个状态值。
*/
2'b11:
begin
w_ptr_next = w_ptr_succ;
r_ptr_next = r_ptr_succ;
endcase
end
// 满/空输出信号的赋值。
assign full = full_reg;
assign empty = empty_reg;
endmodule
好了,终于搞定fifo的代码了。下面来仿真看看结果。
以下分析仿真的结果:
写数据:
从下图仿真,可看出。在最开始的时候,队列是空的状态。读指针和写指针都是0。在写使能情况下,在每个时钟的上升沿(蓝色线),数据写入队列array_reg中。同时,写指针加一。而读指针是不变的。
从下图发现,在队列满状态下,即使写使能,fifo也不接受写数据。依旧保持原来的值。
读数据
从下图中看出,最开始,数据读出是有值的。为初始化的读指针指向的存储单元的值。这里为4。
当有读命令时候,在时钟的上升沿(蓝色线),读指针加一。读取的数据随之改变。
在数据读完后,即队列为空状态下。此时对数据的读取是无效的。从图中可看出,读完后,读指针为0.回到存储器的第一个地址。而此时读出的值是无效的。
读写命令:
在同时读同时写的时候。从下图,可看出,结果有问题了。在队列为空的状态下,此时读取的值,应为此时写的数据才对了。但是从图中,可看出,读取的值不是当前写的数据的值。而是之前存储在fifo中的值。这样的话,读取的值就不是正确的值了。
从上图仿真结果,可知。程序在读写命令时候,编写得不正确。造成结果不对。
返回程序分析。程序不对的地方在于读写命令的时候,处理 得不正确。在空的状态下,数据写入是先写入,然后写指针加一。而读取命令是,指针先加一,然后再读取。而读和写指针的值一样的。这样造成,读取的fifo的存储单元的值,为写的存储单元的下一个存储单元的值。因此造成读取不正确。
改正的程序如下:
2'b11:
begin
if( ~full_reg && ~empty_reg )
begin
w_ptr_next = w_ptr_succ;
r_ptr_next = r_ptr_succ;
end
else if( full_reg ) //在满的状态,不允许写
begin
r_ptr_next = r_ptr_succ;
full_next = 0;
end
else if( empty_reg ) //在空的状态,不允许写
begin
w_ptr_next = w_ptr_succ;
empty_next = 0;
end
end
只需要规定以下:在满的状态,不允许写,在空的状态下,不允许读。这样就可以了。
然后再进行仿真:
这里只看读写命令的图。从下图中,可看出,此时读取的数据,为刚刚写的数据。这样就正确了。
这样,就完成了fpga的fifo了。通过这样一个简单的练习,可看出,仿真,是很重要的,能发现程序中的问题。
以上仿真没有覆盖到所有情况,有兴趣的,可以自己仿真看看仿真图,验证程序写得是否正确。

云呼叫中心功能场景化 让效率不再是口号
工业智能网关实现工业设备数据采集和状态监控
升压调压器的工作原理 如何调节升压调压器的速度呢?
小默相机怎么样 值不值得买
激光测距的原理是什么,它的应用有哪些
FPGA的FIFO实现过程
关于特斯拉电动半自动你需要知道的一切
智能镜面显示屏将逐渐成为我们智慧生活的理想之选
探讨本田、大众智能路口的研究新进展
世强先进与电阻制造商SEI签署合作协议
苹果建议创造无线缆的头显 可使用无线基站来负责图形处理
奥维云网:海信激光电视75L9FL荣膺中国线下市场最畅销机型
TD-HSDPA网络规划及组网策略
带32位MCU和高精度ADC的SoC产品(四)
Custom MMIC驱动器放大器的主要特性以及应用
小米汽车屏幕供应商曝光
Ashling为兆易创新GD32 RISC-V MCU提供高效RiscFree™ C/C++工具链
基于PC的温度测量系统的参考设计
荣耀V40系列将搭载哪款处理器?
ADMV8416/ADMV8432与PLL/VCO IC配合实现PLL/VCO技术的提升