异步FIFO相对于同步FIFO设计的要点是由于读写时钟为不同时钟,因此在判断空满标志时候需要对读写地址进行跨时钟域处理后才能进行比较

1、方法

(1)读写地址跨时钟域处理

  • 格雷码具有相邻两个数仅变化一位的特点,因此可以将读写地址转换为格雷码后再使用打两拍的方式进行跨时钟域的处理;

  • 如果出现在跳变时采样的情况,那么得到的地址要么未改变的旧地址,要么是变化后的新地址,不会出现其他地址情况,如果是新地址属于预期结果不会出现问题;如果是未改变的旧地址,那么用来判断空满信号时会出现假满、假空的信号,这种情况也不会出现逻辑错误,仅仅影响了效率;

(2)空标志信号

  • 将写地址同步到读时钟域(因为空标志用于读,空了就不能读);
  • 判断同步后的写地址是否和读地址相等,如果相等表示写地址追上了读地址,产生空信号;
  • 对于同步产生地址延迟问题,即相当于和更小的读地址进行比较,提前产生了空标志,停止读操作,实际还有数据存在,在逻辑上没有问题,依然正常工作;

(3)满信号标志

  • 将读地址同步到写时钟域(因为满信号用于写操作,满了不能再写);
  • 对同步后的读地址和写地址进行比较,如果写地址比读地址刚好多循环了一圈FIFO深度,则产生满信号,这种情况在格雷码比较的时候表现为:地址的高两位不同,剩余位相同;
  • 如果地址产生延迟问题,就会和更小的写地址比较从而提前产生满信号,从而停止写操作,而实际还有空间可以写,在逻辑上依然没有问题正常工作;
2、Verilog实现
  • 接口和参数定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module fifo_async
#(
parameter ADDR_WIDTH = 4,
parameter DATA_WIDTH = 8
)
(
input clkr,
input clkw,
input rstn,
input wen,
input [DATA_WIDTH-1:0] din,
input ren,
output [DATA_WIDTH-1:0] dout,
output empty,
output full
);

parameter DEPTH = 1<<ADDR_WIDTH;

...

endmodule
  • 写时钟下写地址的二进制和其对应的格雷码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
reg [ADDR_WIDTH:0] wr_ptr_bin;
reg [ADDR_WIDTH:0] wr_ptr_gray;

wire [ADDR_WIDTH:0] next_wr_ptr_bin;
wire [ADDR_WIDTH:0] next_wr_ptr_gray;

assign next_wr_ptr_bin = (wen & (!full)) ? (wr_ptr_bin+1) : wr_ptr_bin;
assign next_wr_ptr_gray = (next_wr_ptr_bin>>1) ^ next_wr_ptr_bin;

always @(posedge clkw or negedge rstn)
begin
if(!rstn) begin
wr_ptr_bin <= 0;
wr_ptr_gray <= 0;
end else begin
wr_ptr_bin <= next_wr_ptr_bin;
wr_ptr_gray <= next_wr_ptr_gray;
end
end
  • 读时钟下读地址的二进制和其对应的格雷码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
reg [ADDR_WIDTH:0] rd_ptr_bin;
reg [ADDR_WIDTH:0] rd_ptr_gray;

wire [ADDR_WIDTH:0] next_rd_ptr_bin;
wire [ADDR_WIDTH:0] next_rd_ptr_gray;

assign next_rd_ptr_bin = (ren & (!empty))? (rd_ptr_bin+1) : rd_ptr_bin;
assign next_rd_ptr_gray = (next_rd_ptr_bin>>1) ^ next_rd_ptr_bin;

always @(posedge clkr or negedge rstn)
begin
if(!rstn) begin
rd_ptr_bin <= 0;
rd_ptr_gray <= 0;
end else begin
rd_ptr_bin <= next_rd_ptr_bin;
rd_ptr_gray <= next_rd_ptr_gray;
end
end
  • 写地址格雷码同步到读时钟
1
2
3
4
5
6
7
8
9
10
11
12
13
reg [ADDR_WIDTH:0] wr_ptr_gray_ff1; 
reg [ADDR_WIDTH:0] wr_ptr_gray_ff2;

always @(posedge clkr or negedge rstn)
begin
if(!rstn) begin
wr_ptr_gray_ff1 <= 0;
wr_ptr_gray_ff2 <= 0;
end else begin
wr_ptr_gray_ff1 <= wr_ptr_gray;
wr_ptr_gray_ff2 <= wr_ptr_gray_ff1;
end
end
  • 读地址格雷码同步到写时钟
1
2
3
4
5
6
7
8
9
10
11
12
13
reg [ADDR_WIDTH:0] rd_ptr_gray_ff1;
reg [ADDR_WIDTH:0] rd_ptr_gray_ff2;

always @(posedge clkw or negedge rstn)
begin
if(!rstn) begin
rd_ptr_gray_ff1 <= 0;
rd_ptr_gray_ff2 <= 0;
end else begin
rd_ptr_gray_ff1 <= rd_ptr_gray;
rd_ptr_gray_ff2 <= rd_ptr_gray_ff1;
end
end
  • 空信号
1
assign empty = (wr_ptr_gray_ff2 == rd_ptr_gray);
  • 满信号
1
assign full = (rd_ptr_gray_ff2 == {~wr_ptr_gray[ADDR_WIDTH:ADDR_WIDTH-1], wr_ptr_gray[ADDR_WIDTH-2:0]});
  • 读写地址和读写数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
assign wr_addr = wr_ptr_bin[ADDR_WIDTH-1:0];
assign rd_addr = rd_ptr_bin[ADDR_WIDTH-1:0];

dual_ram #
(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH(DATA_WIDTH)
)
m_dual_ram(
.clkr(clkr),
.clkw(clkw),
.we(wen&(!full)),
.wr_addr(wr_addr),
.din(din),
.re(ren&(!empty)),
.rd_addr(rd_addr),
.dout(dout)
);
  • 双口RAM模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
module dual_ram
#(
parameter ADDR_WIDTH = 4,
parameter DATA_WIDTH = 8
)
(
input clkr,
input clkw,
input we,
input [ADDR_WIDTH-1:0] wr_addr,
input [DATA_WIDTH-1:0] din,
input re,
input [ADDR_WIDTH-1:0] rd_addr,
output reg [DATA_WIDTH-1:0] dout
);

parameter DEPTH = 1<<ADDR_WIDTH;
reg [DATA_WIDTH-1:0] mem[0:DEPTH-1];

always @(posedge clkw) begin
if(we) begin
mem[wr_addr] <= din;
end
end

always @(posedge clkr) begin
if(re) begin
dout <= mem[rd_addr];
end
end

endmodule
3、仿真
  • testbench
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
`timescale 1ns/100ps

module tb_fifo_async();

parameter CLK_PERIOD = 5;

reg clk;
reg rstn;

// clk gen
initial clk = 1'b0;
always #CLK_PERIOD clk = ~clk;

reg clkr;
reg clkw;

initial clkr = 1'b0;
always #10 clkr = ~clkr;

initial clkw = 1'b0;
always #40 clkw = ~clkw;

reg [7:0] din;
reg wen;
reg ren;

wire [7:0] dout;
wire empty;
wire full;


// rst
initial begin
rstn = 1'b1;
#200;
rstn = 1'b0;
repeat(4) @(posedge clk);
rstn <= 1'b1;
end

integer i = 0;
integer cnt = 30;
initial begin

din <= 8'h20;
wen <= 0;
ren <= 0;

@(posedge rstn);
wen <= 1'b1;
for(i=0; i<cnt; i = i+1) begin
@(posedge clkw);
din <= din + 1;

if(i==17) ren <= 1'b1;
end

// wen <= 1'b0;
#300;
@(posedge clkr) ren <= 1'b1;
for(i =0; i<cnt; i=i+1) begin
@(posedge clkr);
end

#400;
$finish();
end

fifo_async m_fifo_async(
.clkr(clkr),
.clkw(clkw),
.rstn(rstn),
.wen(wen),
.din(din),
.ren(ren),
.dout(dout),
.empty(empty),
.full(full)
);

endmodule
  • 波形