异步FIFO 用于不同时钟域数据的交换。
设计难点: 在不同时钟域里,如何比较读写指针 ?
为了比较不同时钟产生的指针,需要把不同时钟域的信号同步到本时钟域中来,也就是,读指针同步到写时钟,写指针同步到读时钟。这里会带来一个问题: 亚稳态。即写时钟采样读指针时,读指针可能正在变化;读时钟采样写指针时,写指针也可能在变化。
如何减少亚稳态 ? 完全消除是不可能的,读写指针计数器可以采用著名的 格雷码 ,每次变化一位,把亚稳态影响降到最低。
保守的设计:
将(格雷码的)读指针与写时钟同步。因为每当同步读指针的时候,实际的读指针可能会变为不同的值。这意味着读指针可能会是一个失效的值(实际读指针变到了N,而在写时钟读成了N-1)。如果是这样,从写操作的角度考虑会发生少读现象(相比实际情况,认为读指针在N-1),如果条件吻合,判定FIFO为满。实际上,FIFO可能未满(还有1个位置),因为有可能读操作发生了,而从写操作的角度是“看不到”的。然而,我们只要阻止额外的写操作就OK了,如果当FIFO真的满了时我们不去阻止写操作将会出现错误。这样肯定不会溢出。
同样的从读操作的角度看——实际上当FIFO 中还有一些数据时,可能会认为FIFO为空。这种情况读操作被阻止,直到写操作“变得可被读操作一方所看见”,它将不允许进一步的读操作。
上述被称为保守的报告。简而言之,当FIFO未满时,对于写操作一方报告称FIFO已满,当FIFO未空时,报告对读操作一方称FIFO已空。这种现象好比FIFO动态的缩小了一点,这毫无坏处。
直接上代码 : 环境 QII
/////////////////////////////////////////////////////
// Function : asynchronous fifo
// Structure: SRAM
// 读写fifo注意: 满时,只能读,不能写;空时,只能写,不能读
// Date : 2014.05.29
/////////////////////////////////////////////////////
module asyn_fifo(wr_clk,rd_clk,rst_n,we,re,din,dout,full,empty);
parameter dw=8; // 数据位宽
parameter aw=5; // RAM地址线宽度 fifo深度 2^5=32
input wr_clk; // 写时钟
input rd_clk; // 读使能
input rst_n;
input we; // 写使能
input re; // 读使能
input [dw-1:0] din; // 输入数据
output [dw-1:0] dout; // 输出数据
output reg full; // 满标志
output reg empty; // 空标志
////////////////////////////////////////////////////
// SRAM 例化 , SRAM 使用 QII IP核
wire aclr;
assign aclr=~rst_n;
reg [aw-1:0] wr_ptr; // 写指针
reg [aw-1:0] wr_ptr_gray;
reg [aw-1:0] rd_ptr; // 读指针
reg [aw-1:0] rd_ptr_gray;
wire [aw-1:0] wr_ptr_next;
wire [aw-1:0] wr_ptr_gray_next;
wire [aw-1:0] rd_ptr_next;
wire [aw-1:0] rd_ptr_gray_next;
ram U1(
.data(din),
.rd_aclr(aclr),
.rdaddress(rd_ptr),
.rdclock(rd_clk),
.rden(re),
.wraddress(wr_ptr),
.wrclock(wr_clk),
.wren(we),
.q(dout)
);
////////////////////////////////////////////////////
// 读写地址(指针变化)
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
wr_ptr<={aw{1'b0}};
else if(we)
wr_ptr<=wr_ptr_next; // 二进制
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
wr_ptr_gray<={aw{1'b0}};
else
wr_ptr_gray<=wr_ptr_gray_next; // 格雷码
assign wr_ptr_next=wr_ptr+{{(aw-1){1'b0}},1'b1}; // 指向下一个地址 二进制
assign wr_ptr_gray_next=wr_ptr_next^{1'b0,wr_ptr_next[aw-1:1]}; // 指向下一个地址 自然二进制转格雷码
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)
rd_ptr<={aw{1'b0}};
else if(re)
rd_ptr<=rd_ptr_next; // 二进制
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)
rd_ptr_gray<={aw{1'b0}};
else if(re)
rd_ptr_gray<=rd_ptr_gray_next; // 格雷码
assign rd_ptr_next=rd_ptr+{{(aw-1){1'b0}},1'b1}; // 指向下一个地址 二进制
assign rd_ptr_gray_next=rd_ptr_next^{1'b0,rd_ptr_next[aw-1:1]}; // 指向下一个地址 自然二进制转格雷码
////////////////////////////////////////////////////////////////
//读指针同步到写时钟下,写指针同步到读时钟下
reg [aw-1:0]rp;
reg [aw-1:0]wp;
always@(posedge wr_clk)
rp<=rd_ptr_gray;
always@(posedge rd_clk)
wp<=wr_ptr_gray;
////////////////////////////////////////////////////
// 空满标志
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
full<=1'b0;
else if((wr_ptr_gray_next==rp)&(we)) // 写赶上读 ,满
full<=1'b1;
else if(wr_ptr_gray!=rp)
full<=1'b0;
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)
empty<=1'b1;
else if((rd_ptr_gray_next==wp)&(re)) // 读赶上写, 空
empty<=1'b1;
else if(rd_ptr_gray!=wp)
empty<=1'b0;
endmodule
仿真:
影响此设计最高时钟速率的因素: 存储器SRAM读写速率和格雷码计数器运行速率。
文章评论(0条评论)
登录后参与讨论