Abstract: Verilog 同步及异步 FIFO  设计

FIFO:First Input First Output

同步 FIFO

解析

(原理很简单所以不写了)

示例代码

题目见:同步FIFO_牛客题霸_牛客网 (nowcoder.com)

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
`timescale 1ns/1ns
/**********************************RAM************************************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);

reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];

always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end

always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end

endmodule

/**********************************SFIFO************************************/
module sfifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk ,
input rst_n ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,

output reg wfull ,
output reg rempty ,
output wire [WIDTH-1:0] rdata
);

reg [$clog2(DEPTH)-1:0] waddr, raddr;
reg [$clog2(DEPTH):0] cnt;

always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
waddr <= 'd0;
end
else begin
waddr <= (winc&&(~wfull))? waddr+'d1 : waddr; // 不满时可写入
end
end

always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
raddr <= 'd0;
end
else begin
raddr <= (rinc&&(~rempty))? raddr+'d1 : raddr; // 不空时可读出
end
end

always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
cnt <= 'd0;
end
else begin
if (winc&&(~wfull)) begin // 写操作 计数器增
cnt <= cnt + 'd1;
end
else if (rinc&&(~rempty)) begin // 读操作 计数器减
cnt <= cnt - 'd1;
end
else if (winc&&(~wfull) && rinc&&(~rempty)) begin // 同时读写 不变
cnt <= cnt;
end
else begin
cnt <= cnt;
end
end
end

always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
wfull = 'd0;
rempty = 'd0;
end
else begin
wfull = (cnt==DEPTH);
rempty = (cnt=='d0);
end
end

dual_port_RAM #(
.DEPTH(DEPTH),
.WIDTH(WIDTH)
) u_ram (
.wclk(clk),
.wenc(winc&&(~wfull)),
.waddr(waddr),
.wdata(wdata),
.rclk(clk),
.renc(rinc&&(~rempty)),
.raddr(raddr),
.rdata(rdata)
);

endmodule

异步 FIFO

解析

(其他人写的非常好了)

使用格雷码进行地址比较很有借鉴价值

题解 | #异步FIFO#_牛客博客 (nowcoder.net)

示例代码

题目见:异步FIFO_牛客题霸_牛客网 (nowcoder.com)

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
`timescale 1ns/1ns

/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk,
input wenc,
input [$clog2(DEPTH)-1:0] waddr, //深度对2取对数,得到地址的位宽。
input [WIDTH-1:0] wdata, //数据写入
input rclk,
input renc,
input [$clog2(DEPTH)-1:0] raddr, //深度对2取对数,得到地址的位宽。
output reg [WIDTH-1:0] rdata //数据输出
);

reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];

always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end

always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end

endmodule

/***************************************AFIFO*****************************************/
module asyn_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,

output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
);

parameter ADDR_WIDTH = $clog2(DEPTH);

// 二进制地址生成
reg [ADDR_WIDTH:0] waddr_bin, raddr_bin;

always @(posedge wclk or negedge wrstn) begin
if (~wrstn) begin
waddr_bin <= 'b0;
end
else begin
waddr_bin <= (winc&&(~wfull))? waddr_bin+'d1 : waddr_bin;
end
end

always @(posedge rclk or negedge rrstn) begin
if (~rrstn) begin
raddr_bin <= 'b0;
end
else begin
raddr_bin <= (rinc&&(~rempty))? raddr_bin+'d1 : raddr_bin;
end
end

// 二进制地址转格雷码 减小时钟同步时的错误率
reg [ADDR_WIDTH:0] waddr_gray, raddr_gray;

always @(posedge wclk or negedge wrstn) begin
if (~wrstn) begin
waddr_gray <= 'b0;
end
else begin
waddr_gray <= waddr_bin^(waddr_bin>>1);
end
end

always @(posedge rclk or negedge rrstn) begin
if (~rrstn) begin
raddr_gray <= 'b0;
end
else begin
raddr_gray <= raddr_bin^(raddr_bin>>1);
end
end

// 格雷码地址时钟同步 打两拍
reg [ADDR_WIDTH:0] waddr_gray_sync, waddr_gray_sync_r1;
reg [ADDR_WIDTH:0] raddr_gray_sync, raddr_gray_sync_r1;

always @(posedge rclk or negedge rrstn) begin // 读地址同步到写时钟
if (~rrstn) begin
waddr_gray_sync_r1 <= 'b0;
waddr_gray_sync <= 'b0;
end
else begin
waddr_gray_sync_r1 <= waddr_gray;
waddr_gray_sync <= waddr_gray_sync_r1;
end
end

always @(posedge wclk or negedge wrstn) begin // 写地址同步到读时钟
if (~wrstn) begin
raddr_gray_sync_r1 <= 'b0;
raddr_gray_sync <= 'b0;
end
else begin
raddr_gray_sync_r1 <= raddr_gray;
raddr_gray_sync <= raddr_gray_sync_r1;
end
end

// 满空信号逻辑 使用格雷码地址
assign wfull = (waddr_gray == {~raddr_gray_sync[ADDR_WIDTH:ADDR_WIDTH-1], raddr_gray_sync[ADDR_WIDTH-2:0]}); // 写满信号 写地址超前读地址一圈 此时格雷码两地址的前两位互逆
assign rempty = (raddr_gray == waddr_gray_sync); // 读空信号 读地址与写地址相等

// RAM 实例化
dual_port_RAM #(
.WIDTH(WIDTH),
.DEPTH(DEPTH)
) u_ram (
.wclk(wclk),
.wenc(winc&&(~wfull)),
.waddr(waddr_bin[ADDR_WIDTH-1:0]), //深度对2取对数,得到地址的位宽。
.wdata(wdata), //数据写入
.rclk(rclk),
.renc(rinc&&(~rempty)),
.raddr(raddr_bin[ADDR_WIDTH-1:0]), //深度对2取对数,得到地址的位宽。
.rdata(rdata) //数据输出
);

endmodule