同步和异步fifo设计
同步fifo设计
信号列表
-
clk:fifo操作时钟
-
wr_en:fifo写使能,在wr_en有效的情况下,上升沿采集wdata数据。
-
wdata:fifo写数据。
-
full:fifo满有效。高电平时,代表当前wdata并未写入fifo。
-
rd_en:fifo读使能。在rd_en有效,empty无效时,数据同步送出fifo。
-
empty:fifo空有效。高电平时,代表当前rdata无效。
要点
使用两个地址寄存器,分别为raddr和waddr,假设fifo深度为8,则地址位置从000->111,只需要让raddr和waddr是4个位宽,并且让对fifo读写操作后,raddr和waddr地址加1即可。
当raddr[3:0]=waddr[3:0],则代表fifo是空的。
当raddr[3]!=waddr[3] raddr[2:0]=waddr[2:0],则代表fifo是满的。
verilog
module fifo_syn #(
parameter wa = 3,
parameter wd = 4)
(
input rst_n,
input clk,
input wr_en,
input [wd-1:0] wdata,
output full,
input rd_en,
output [wd-1:0] rdata,
output empty
);
//syn [wa-1:0] raddr ^ [wa-1:0] raddr
reg [wa:0] raddr;
reg [wa:0] waddr;
wire condition_1 = &(~(raddr[wa-1:0]^waddr[wa-1:0]));
wire condition_2 = &(~(raddr[wa]^waddr[wa]));
assign full = ~condition_2&condition_1;
assign empty = condition_1&condition_2;
parameter deep = (1<<wa)-1;
reg [wd-1:0] fifo_mem[deep:0] ;
always @(posedge clk) begin
if(!rst_n)
waddr <= {wa{1'b0}};
else if(wr_en&&!full) begin
fifo_mem[waddr[wa-1:0]] <= wdata;
waddr <= waddr + 1'b1;
end
end
assign rdata = (!empty&&rd_en)?fifo_mem[raddr[wa-1:0]]:{wd{1'b0}};
always @(posedge clk) begin
if(!rst_n)
raddr <= {wa{1'b0}};
else if(rd_en&&!empty) begin
raddr <= raddr + 1'b1;
end
end
endmodule
`timescale 1ns / 1ps
module tb();
reg clk;
reg rst_n;
reg wr_en,rd_en;
wire empty,full;
reg [3:0] wdata;
wire [3:0] rdata;
fifo_syn #(
.wa(3),
.wd(4)
) fifo_syn_inst(
.rst_n (rst_n),
.clk (clk),
.wr_en(wr_en),
.wdata(wdata),
.full(full),
.rd_en(rd_en),
.rdata(rdata),
.empty(empty)
);
always #10 clk=~clk;
initial begin
clk = 0;
rd_en = 0;
rst_n = 0;
#100
rst_n = 1;
end
always @(posedge clk) begin
if(!rst_n)begin
wr_en <= 1'b0;
end else begin
wr_en <= 1'b1;
wdata <= $random%16;
end
end
always @(posedge clk) begin
if(!rst_n)begin
rd_en <= 1'b0;
end else begin
rd_en <= 1'b1;
end
end
endmodule
异步fifo设计
信号列表
-
wclk:写fifo的时钟
-
wr_en:fifo写使能,在wr_en有效的情况下,上升沿采集wdata数据。
-
wdata:fifo写数据。
-
full:fifo满有效。高电平时,代表当前wdata并未写入fifo。
-
rclk:读fifo的时钟。
-
rd_en:fifo读使能。在rd_en有效,empty无效时,数据同步送出fifo。
-
rdata_valid:高电平时,代表当前rdata有效。
-
empty:fifo空有效。
要点
raddr和waddr位宽和前面同步fifo设计一致,不过由于异步时钟域的问题,需要使用双同步触发器将其中一个时钟域同步到另一个时钟域中,同时为了避免同步使用多bit信号,需要对raddr和waddr进行格雷码编码。
假设fifo深度为8,则地址位置从000->111,只需要让raddr和waddr是4个位宽,并且让对fifo读写操作后,raddr和waddr地址加1即可。由于使用了格雷码编码。观察4bit格雷码编码表格:
| 二进制 | 格雷码 |
|---|---|
| 0000 | 0000 |
| 0001 | 0001 |
| 0010 | 0011 |
| 0011 | 0010 |
| 0100 | 0110 |
| 0101 | 0111 |
| 0110 | 0101 |
| 0111 | 0100 |
| 1000 | 1100 |
| 1001 | 1101 |
| 1010 | 1111 |
| 1011 | 1110 |
| 1100 | 1010 |
| 1101 | 1011 |
| 1110 | 1001 |
| 1111 | 1000 |
可以得到 当raddr[3]=waddr[3],raddr[2]=waddr[2],raddr[1:0]=waddr[1:0],则代表fifo是空的。
当raddr[3]!=waddr[3],raddr[2]!=waddr[2],raddr[1:0]=waddr[1:0],则代表fifo是满的。
-
二进制码转换成二进制格雷码
- 保留二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,同理其余各位的格雷码为当前位的二进制于,前1位的二进制异或。
-
同步方式
- 双同步触发器
-
空信号
- 将写时钟域写地址寄存器,同步到读时钟域,与读时钟域读地址进行对比产生空信号
-
满信号
- 将读时钟域读地址寄存器,同步到写时钟域,与写时钟域写地址进行对比产生满信号
-
同步过程中出错
- 假设格雷码写指针从000->001,将写指针同步到读时钟域同步出错,出错的结果只可能是000->000,因为相邻位的格雷码每次只有一位变化,这个出错结果实际上也就是写指针没有跳变而是保持不变。从而可能让空标志产生,但是不会空读。同样当读地址同步出错,从而可能让满标志产生,同样不会满写。
-
写快读慢
- 满标志
- 读地址同步到写时钟域,因为读慢写快,所以不会有读地址遗漏,同步消耗时钟周期,所以同步后的读地址滞后(小于等于)当前读地址,所以可能满标志会提前产生,此时满并非真满。
- 空标志
- 写地址同步到读时钟域,因为读慢写快,所以同步时会有写地址遗漏,但是漏掉的地址会对FIFO的空标志产生影响吗?实际不会,同步到读时钟的写地址要小于等于实际写地址。这样在判断空标志时会出现不是真正空的情况,但是遗漏的地址没有对FIFO的逻辑操作产生影响。
- 满标志
-
写慢读快
- 满标志
- 读地址同步到写时钟域,因为写慢读快,所以同步时会有读地址遗漏,但是漏掉的地址会对FIFO的满标志产生影响吗?实际不会,同步到写时钟的读地址要小于等于实际读地址。这样在判断满标志时会出现不是真正满的情况,但是遗漏的地址没有对FIFO的逻辑操作产生影响。
- 空标志
- 写地址同步到读时钟域,因为写慢读快,所以不会有写地址遗漏,同步消耗时钟周期,所以同步后的写地址滞后(小于等于)当前写地址,所以可能空标志会提前产生,此时空并非真空。
- 满标志
因此无论哪种情况多不会出现错误。
module fifo_asy #(
parameter wa = 3,
parameter wd = 4)
(
input rst_n,
input wclk,
input wr_en,
input [wd-1:0] wdata,
output full,
input rd_en,
input rclk,
output reg [wd-1:0] rdata,
output reg rdata_valid,
output empty
);
//syn [wa-1:0] raddr & [wa-1:0] raddr
reg [wa:0] raddr;
reg [wa:0] waddr;
//gray
wire [wa:0] gray_waddr = (waddr>>1)^waddr;
wire [wa:0] gray_raddr = (raddr>>1)^raddr;
// full rclk->wclk
reg [wa:0] gray_raddr_r1,gray_raddr_r2;
always @(posedge wclk) begin
if(!rst_n)begin
gray_raddr_r1 <= {wa{1'b0}};
gray_raddr_r2 <= {wa{1'b0}};
end else begin
gray_raddr_r1 <= gray_raddr;
gray_raddr_r2 <= gray_raddr_r1;
end
end
wire full_con1 = &(~(gray_waddr[wa-2:0]^gray_raddr_r1[wa-2:0]));
wire full_con2 = &(~(gray_waddr[wa:wa-1]^gray_raddr_r1[wa:wa-1]));
assign full = full_con1&~full_con2;
// empty wclk->rclk
reg [wa:0] gray_waddr_r1,gray_waddr_r2;
always @(posedge rclk) begin
if(!rst_n)begin
gray_waddr_r1 <= {wa{1'b0}};
gray_waddr_r2 <= {wa{1'b0}};
end else begin
gray_waddr_r1 <= gray_waddr;
gray_waddr_r2 <= gray_waddr_r1;
end
end
wire empty_con1 = &(~(gray_waddr_r2[wa-2:0]^gray_raddr[wa-2:0]));
wire empty_con2 = &(~(gray_waddr_r2[wa:wa-1]^gray_raddr[wa:wa-1]));
assign empty = empty_con1&empty_con2;
parameter deep = (1<<wa)-1;
reg [wd-1:0] fifo_mem[deep:0] ;
always @(posedge wclk) begin
if(!rst_n)
waddr <= {wa{1'b0}};
else if(wr_en&&!full) begin
fifo_mem[waddr[wa-1:0]] <= wdata;
waddr <= waddr + 1'b1;
end
end
// assign rdata = (!empty&&rd_en)?fifo_mem[raddr[wa-1:0]]:{wd{1'b0}};
always @(posedge rclk) begin
if(!rst_n)begin
raddr <= {wa{1'b0}};
rdata <= {wd{1'b0}};
rdata_valid <= 1'b0;
end else if(rd_en&&!empty) begin
raddr <= raddr + 1'b1;
rdata <= fifo_mem[raddr[wa-1:0]];
rdata_valid <= 1'b1;
end else begin
rdata <= {wd{1'b0}};
rdata_valid <= 1'b0;
end
end
endmodule
这里有两个变化:
- rdata数据将不在使用assign,而是在读时钟域寄存器输出,这样才能保证在读出来的时候被其他模块稳定读入。
- 由于寄存了rdata,所以增加一个rdata_valid信号来同步指示rdata,高电平时代表rdata数据有效。
`timescale 1ns / 1ps
module tb();
reg wclk;
reg rclk;
reg rst_n;
reg wr_en,rd_en;
wire empty,full;
wire rdata_valid;
reg [3:0] wdata;
wire [3:0] rdata;
fifo_asy #(
.wa(3),
.wd(4)
) fifo_syn_inst(
.rst_n (rst_n),
.wclk (wclk),
.wr_en(wr_en),
.wdata(wdata),
.full(full),
.rd_en(rd_en),
.rclk (rclk),
.rdata(rdata),
.rdata_valid(rdata_valid),
.empty(empty)
);
always #10 wclk=~wclk;
always #10 rclk=~rclk;
initial begin
wclk = 0;
rclk = 0;
rd_en = 0;
rst_n = 0;
#100
rst_n = 1;
end
always @(posedge wclk) begin
if(!rst_n)begin
wr_en <= 1'b0;
wdata <= 1'b0;
end else begin
if(!full)begin
wr_en <= 1'b1;
wdata <= wdata + 1'b1;
end
end
end
always @(posedge rclk) begin
if(!rst_n)begin
rd_en <= 1'b0;
end else begin
rd_en <= 1'b1;
end
end
endmodule
写快读慢仿真
延时修改如下:
always #10 wclk=~wclk;
always #20 rclk=~rclk;

写慢读快仿真
延时修改如下:
always #20 wclk=~wclk;
always #10 rclk=~rclk;
