FIFO
本质是RAM,其重要参数是深度depth(需要存的数据个数)与宽度width(所存数据的位宽)。FIFO有同步和异步两种,同步即读写时钟相同,异步则读写时钟不同。同步FIFO可做数据缓存,异步FIFO可以解决跨时钟域的问题,在实际应用中考虑好FIFO的深度即可。
通常情况下FIFO读空/写满标志分析
读空:复位的时候,读指针和写指针相等,即位读空(此处的读指针即读地址)。可以理解为,读指针一直跟在写指针后面读数据,当读指针终于追上写指针的时候,意味着最后一个数据被读出来,读空。
写满:当写指针超越读指针一圈的时候,写指针等于读指针意味着写满了。
由此,读空的条件是写指针等于读指针,写满的条件也是写指针等于读指针,到底如何区分呢?
解决方法:将指针的位宽多定义一位
举个例子说明:假设要设计深度为 8 的异步FIFO,此时定义读写指针只需要 3 位(2^3=8)就够用了,
但是我们在设计时将指针的位宽设计成 4 位,最高位的作用就是区分是读空还是写满, 对于二进制数之间的空满比较:
当最高位相同,其余位相同认为是读空
当最高位不同,其余位相同认为是写满
跨时钟域传输数据–异步FIFO
异步FIFO实现数据交换,防止亚稳态出现;FIFO相当于一个双端口RAM,先进先出,一个端口由发送数据的时钟域控制,用于写入数据,另一个端口由接收数据的时钟域控制,用于读取数据,因此需要两个控制信号来标志FIFO是空、满还是部分满的状态,在实际的实现数据交换的过程中,准确的指示FIFO是满还是空是比较由挑战的;so,需要考虑一下问题。
1、读写时钟不同,如何正确指示写满或者读空的状态?
方法:寄存器打两拍+格雷码
由于读指针是属于读时钟域的,写时针是属于写时钟域的,两者是异步的,自是不可不做处理之间进行比较,因此需要进行同步处理以后再比较。
另外将一个二进制的计数器数值从一个时钟域同步到另一个时钟域很容易出错,因为二进制计数器自加/减的时候变化的bit位太多,如7->8(0111–>1000)所有bit都变化;此时,同步到另一个时钟域的时候很容易出现亚稳态。而采样格雷码只有一位发送变化,则不会出现这个问题。(格雷码相邻两个数据之间只有一位变化,即使在同步过程中出现错误如,000–>001,导致的结果不过是将原来的状态同步过去,比如读同步到写,结果是在没满的时候提前报写满,不会覆盖数据,<=满。因为前后仅有1bit数据出现变化,也避免了因为多根线之间存在skew而带来的亚稳态情况)。
so,需要设计一个二进制码转换格雷码的电路,将地址值转换为格雷码,然后将该格雷码同步到另外一个时钟域进行比对,进行空满状态的检测。
简述二进制–>格雷码的转换电路实现:
二进制数右移一位,最高空位补0,逐比特异或运算。
如:10110(二进制)–>11101(格雷码)
二进制 10110
右移+补零 010110
异或 11101(格雷码)
Verilog实现:
assign gray_code=(binary_code>>1)^binary_code;
2、异步FIFO的写满/读空信号指示如何利用格雷码正确产生?
即如何判断空满? 假设FIFO空间大小为8,即0~7;当读地址是0000(格雷码0000),写地址为1000(对应的格雷码是1100,具体的转换可以见笔记),此时已经是写满的状态了,两者高位和次高位不同,其余位相同,标志写满;读空是二者格雷码完全相同,因为是先写入后读出的,当读指针追上了写指针的时候,意味着最后一个数据被读完了,即读空。
结论:
读空:读时钟域的格雷码rgray_next和被同步来的写指针rd2_wp每一bit都完全相同;
写满:写时钟域的格雷码wgray_next和被同步来的读指针wr2_rp高2位不同,其余相同;
硬件实现:RTL代码
module fifo
#(
parameter WSIZE = 8;
parameter DSIZE = 32;
)
(
input wr_clk,
input rst,
input wr_en,
input [WSIZE-1 : 0]din,
input rd_clk,
input rd_en,
output [WSIZE-1 : 0]dout,
output reg rempty,
output reg wfull
);
//定义变量
reg [WSIZE-1 :0] mem [DSIZE-1 : 0];
reg [WSIZE-1 : 0] waddr,raddr;
reg [WSIZE : 0] wbin,rbin,wbin_next,rbin_next;
reg [WSIZE : 0] wgray_next,rgray_next;
reg [WSIZE : 0] wp,rp;
reg [WSIZE : 0] wr1_rp,wr2_rp,rd1_wp,rd2_wp;
wire rempty_val,wfull_val;
//输出数据
assign dout = mem[raddr];
//输入数据
always@(posedge wr_clk)
if(wr_en && !wfull)
mem[waddr] <= din;
//1.产生存储实体的读地址raddr; 2.将普通二进制转化为格雷码,并赋给读指针rp
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)
{
rbin,rp} <= 0;
else
{
rbin,rp} <= {
rbin_next,rgray_next};
assign raddr = rbin[WSIZE-1 : 0];
assign rbin_next = rbin + (rd_en & ~rempty);
assign rgray_next = rbin_next ^ (rbin_next >> 1);
//1.产生存储实体的写地址waddr; 2.将普通二进制转化为格雷码,并赋给写指针wp
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
{
wbin,wp} <= 0;
else
{
wbin,wp} <= {
wbin_next,wgray_next};
assign waddr = wbin[WSIZE-1 : 0];
assign wbin_next = wbin + (wr_en & ~wfull);
assign wgray_next = wbin_next ^ (wbin_next >> 1);
//将读指针rp同步到写时钟域
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
{
wr2_rp,wr1_rp} <= 0;
else
{
wr2_rp,wr1_rp} <= {
wr1_rp,rp};
//将写指针wp同步到读时钟域
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)
{
rd2_wp,rd1_wp} <= 0;
else
{
rd2_wp,rd1_wp} <= {
rd1_wp,wp};
//产生读空信号rempty
assign rempty_val = (rd2_wp == rgray_next);
always@(posedge rd_clk or negedge rst_n)
if(rst_n)
rempty <= 1'b1;
else
rempty <= rempty_val;
//产生写满信号wfull
assign wfull_val = ((~(wr2_rp[WSIZE : WSIZE-1]),wr2_rp[WSIZE-2 : 0]) == wgray_next);
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
wfull <= 1'b0;
else
wfull <= wfull_val;
endmodule
3 硬件实现的原理框图