FPGA基础学习(2) -- FIFO IP核(Quartus):https://www.cnblogs.com/rouwawa/p/7066635.html
对altera的FIFO读写操作深入研究:https://www.cnblogs.com/estop-jfw/p/6027329.html
Quartus II LPM使用指南(FIFO篇):https://wenku.baidu.com/view/0dd66ef8c8d376eeaeaa31ed.html
07.IP核之FIFO使用详解:http://www.fpga.gs/h-nd-39.html#_jcp=1
软件:Quartus II 13.1
芯片:EP4C6F17C8
开发板:ALINX的AX301B
对于时序部分,可以参考链接FPGA基础学习(2) -- FIFO IP核(Quartus):https://www.cnblogs.com/rouwawa/p/7066635.html
这里将图片赋值过来做简要的补充说明。
可以看到写满8个字节之后,就不能再往fifo中添加任何数字了。这是因为设置FIFO IP时,打开了overflow的保护机制,写满之后,就不能再往FIFO中写入数据了。我们可以从图中看到写入15之后FIFO的full标志位置1,说明FIFO已经写满了。正常的理解是,FIFO写满之后,我们可以继续往FIFO中添加数据,将前面的数据排挤出。而我们从读出的数据来看,17和19两个数据并未写入,这就是overflow这个机制所起的效果。
同样,quartus II软件中还有underflow保护机制,即读取数据为空后,就不能从FIFO中读取数据了,用官方的话来说,就是禁用rdreq。而上面则是禁用wrreq, 两者是不同的,和我们想象中的FIFO也是不同的。
这里,还有一点需要特别说明。就是usedw,显示的是FIFO中存储的数据个数。需要注意的是,当FIFO中存满数据,即进入FIFO的数据个数和容量一致时,usedw输出0,表示FIFO已满。而读取数据的过程中,FIFO中存储的数据个数为0时,usedw数据也为0。
对于FIFO的使用,这里以数据接收,存入FIFO,再将FIFO中的数据转发的整个工程为例进行FIFO使用的介绍。
数据接收和数据发送的过程是两个独立过程。接收到数据后,需要立即将数据存入FIFO,而从FIFO中读取数据后,也可以立即将数据发送出去。对于FIFO来说,wrreq和rdreq信号的使用就显示至关重要。这两个信号都是高电平有效,在FIFO的clk的上升沿使能有效。读取一个数据来说,只需要wrreq和rereq的信号高电平的存在时间大于1个clk的时钟周期,就分别能将数据写入和读取。而往FIFO中写入一个数据,FIFO中的数据个数加1;从FIFO中读取一个数据,FIFO中的数据个数减1,这反应在FIFO中的输出就是usedw的数据变化。
一般来说,数据接收完毕后,标志位rx_ready置1,标志位rx_ready从数据接收到完成有一个上升沿的跳变;数据发送完成后,接收标志位tx_ready置1,标志位tx_ready从数据发送到完成也有一个上升沿的跳变。
下面我们分析FIFO的读写控制。数据接收完成时,rx_ready的上升沿跳变可以作为FIFO的写控制信号wrreq的使能,这就使得数据接收完成后,立即将数据写入FIFO中。
//--------------------------------------------------
//rx_ready上升沿,作为fifo的写使能
reg rx_ready_r0;
reg rx_ready_r1;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
reg rx_ready_r0 <= 1'b0;
reg rx_ready_r1 <= 1'b0;
end
else
begin
reg rx_ready_r0 <= rx_ready;
reg rx_ready_r1 <= rx_ready_r0 ;
end
end
assign wrreq = (~rx_ready_r1) & (rx_ready_r0); //上升沿检测
正常情况下,发送数据完成时,只要FIFO为空,我们就可以将tx_ready的上升沿跳变作为FIFO读使能控制信号rdreq的使能,这就发送完成后,立即从FIFO中读出数据,然后再次将数据发送出去。我们可以使用和上面一样的实现方式。
//--------------------------------------------------
//tx_ready上升沿,作为fifo的读使能
reg tx_ready_r0;
reg tx_ready_r1;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
reg tx_ready_r0 <= 1'b0;
reg tx_ready_r1 <= 1'b0;
end
else
begin
reg tx_ready_r0 <= tx_ready;
reg tx_ready_r1 <= tx_ready_r0 ;
end
end
assign rdreq_r0 = (~tx_ready_r1) & (tx_ready_r0); //上升沿检测
FIFO数据的读取过程中,还可能存在一种情况就是FIFO为空的时间较长,而tx_ready上升沿的保存时间只有短短的一个周期,这就不能作为读使能控制信号的使能,需要使用另一种方式。我们考虑到FIFO从空到非空的过程是由empty这个标志位作为记录的,当FIFO中的数据不为空时,空标志位empty置0,FIFO数据空的时候,空标志位empty置1。而且,数据发送完成后,标志位tx_ready一直为高,即置1。这样的话,fifo从空到非空的状态变化和tx_ready为高电平两个条件,共同决定fifo的读使能控制信号rdreq有效。这就保证了在数据发送完成后,重新接收到数据,才会从FIFO中重新读取数据,再次进行数据的发送。
//--------------------------------------------------
//empty空到非空的状态变化和tx_ready的高电平两个条件,作为fifo的读使能
reg empty_r0;
reg empty_r1;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
reg empty_r0 <= 1'b0;
reg empty_r1 <= 1'b0;
end
else
begin
reg empty_r0 <= empty;
reg empty_r1 <= empty_r0 ;
end
end
assign rdreq_r1 = (empty_r1) & (~empty_r0) & tx_ready; //两个条件
这样的话,fifo的读使能控制信号rdreq的两种情况就包括在内了。但是,还存在一种情况需要考虑,就是一开始的那个接收到的数据的写入FIFO后,FIFO数据的读取。因为第一个接收到数据还从未被发送,即发送数据这部分还未开始执行,发送完成标志位tx_ready保存的是初始值0,不可能有电平的跳变,不能作为读使能控制信号rdreq的使能。又需要考虑一种其他的实现方式,这里采用的是第一个数据的写使能控制信号wrreq控制读使能控制信号rdreq有效。即写入数据后,立马进行数据的读取,这只是第一数据需要考虑的情形。
//rdreq_r2 考虑的是第三种情况
assign rdreq = rdreq_r0 | rdreq_r1 | rdreq_r2;
三种情况考虑清楚,FIFO的读使能就能考虑全面。
你可能会说,第三种情况怎么没有代码啊。我是觉得代码不是很好,就不出来献丑了。具体的思路已经提供了,代码可以自己实现,一起分享。分享是美德,大家共同进步。