一、分析
由于是异步FIFO的设计,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决?
跨时钟域的问题:由于读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后仔进行比较
解决方法:加两级寄存器同步 + 格雷码(目的都是消除亚稳态)
1.使用异步信号进行使用的时候,好的设计都会对异步信号进行同步处理,同步一般采用多级D触发器级联处理,如下图。这种模型大部分资料都说的是第一级寄存器产生亚稳态后,第二级寄存器稳定输出概率为90%,第三极寄存器稳定输出的概率为99%,如果亚稳态跟随电路一直传递下去,那就会另自我修护能力较弱的系统直接崩溃。
2.将一个二进制的计数值从一个时钟域同步到另一个时钟域的时候很容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。
那么,多位二进制码如何转化为格雷码?
换一种描述方法:
verilog代码实现就一句:assign gray_code = (bin_code>>1) ^ bin_code;
使用gray码解决了一个问题,但同时也带来另一个问题,即在格雷码域如何判断空与满。
这里直接给出结论:
判断读空时:需要读时钟域的格雷码rgray_next和被同步到读时钟域的写指针rd2_wp每一位完全相同;
判断写满时:需要写时钟域的格雷码wgray_next和被同步到写时钟域的读指针wr2_rp高两位不相同,其余各位完全相同;
二、Verilog实现
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