forked from SJTU-YONGFU-RESEARCH-GRP/core
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdual_clock_fifo.v
More file actions
executable file
·203 lines (184 loc) · 8.04 KB
/
dual_clock_fifo.v
File metadata and controls
executable file
·203 lines (184 loc) · 8.04 KB
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/**
* Dual-Clock FIFO (Asynchronous FIFO)
*
* This module implements a dual-clock FIFO that allows data transfer between
* two independent clock domains. It uses Gray code encoding for pointers to
* ensure safe clock domain crossing and prevent metastability.
*
* Key Features:
* - Dual clock domains: Separate clocks for read and write operations
* - Gray code pointers: Only one bit changes per increment (safe for CDC)
* - Full/empty flags: Prevents overflow and underflow
* - Almost full/empty: Programmable thresholds for flow control
* - FIFO count: Current number of entries (synchronous to read clock)
*
* Clock Domain Crossing (CDC):
* - Write domain: wr_clk, wr_rst_n, wr_en, wr_data
* - Read domain: rd_clk, rd_rst_n, rd_en, rd_data
* - Pointers synchronized using Gray code and double-flop synchronizers
*
* Gray Code for CDC Safety:
* - Binary pointers: Multiple bits change simultaneously (unsafe for CDC)
* - Gray code pointers: Only one bit changes per increment (safe for CDC)
* - Reduces metastability risk when synchronizing across clock domains
*
* Pointer Synchronization:
* - Write pointer (Gray) synchronized to read clock domain (2 flops)
* - Read pointer (Gray) synchronized to write clock domain (2 flops)
* - Synchronized pointers used for full/empty detection
*
* @param DATA_WIDTH Width of data words (default: 8 bits)
* @param ADDR_WIDTH Address width - FIFO depth = 2^ADDR_WIDTH (default: 4, depth=16)
* @param ALMOST_FULL_THRESHOLD Almost full threshold (default: 12)
* @param ALMOST_EMPTY_THRESHOLD Almost empty threshold (default: 4)
*
* @input wr_clk Write clock domain
* @input wr_rst_n Write domain reset (active low)
* @input wr_en Write enable
* @input wr_data[DATA_WIDTH-1:0] Write data
* @output full Full flag (write domain)
* @output almost_full Almost full flag (write domain)
*
* @input rd_clk Read clock domain
* @input rd_rst_n Read domain reset (active low)
* @input rd_en Read enable
* @output rd_data[DATA_WIDTH-1:0] Read data
* @output empty Empty flag (read domain)
* @output almost_empty Almost empty flag (read domain)
* @output fifo_count[ADDR_WIDTH:0] FIFO count (read domain)
*/
module dual_clock_fifo #(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 4,
parameter ALMOST_FULL_THRESHOLD = 12,
parameter ALMOST_EMPTY_THRESHOLD = 4
)(
// Write domain
input wire wr_clk,
input wire wr_rst_n,
input wire wr_en,
input wire [DATA_WIDTH-1:0] wr_data,
output wire full,
output wire almost_full,
// Read domain
input wire rd_clk,
input wire rd_rst_n,
input wire rd_en,
output wire [DATA_WIDTH-1:0] rd_data,
output wire empty,
output wire almost_empty,
// Status (synchronous to read clock)
output wire [ADDR_WIDTH:0] fifo_count
);
// ============================================================================
// Memory Storage
// ============================================================================
// Memory array: stores FIFO data
// Depth = 2^ADDR_WIDTH
reg [DATA_WIDTH-1:0] mem [(1<<ADDR_WIDTH)-1:0];
// ============================================================================
// Pointer Declarations
// ============================================================================
// Pointers maintained in both binary and Gray code
// Binary: used for memory addressing
// Gray: used for safe clock domain crossing
reg [ADDR_WIDTH:0] wr_ptr_bin, rd_ptr_bin; // Binary pointers
reg [ADDR_WIDTH:0] wr_ptr_gray, rd_ptr_gray; // Gray code pointers
// Synchronization registers: double-flop synchronizers for CDC
reg [ADDR_WIDTH:0] wr_ptr_gray_rd_sync1, wr_ptr_gray_rd_sync2; // Write ptr -> Read domain
reg [ADDR_WIDTH:0] rd_ptr_gray_wr_sync1, rd_ptr_gray_wr_sync2; // Read ptr -> Write domain
// ============================================================================
// Binary to Gray Code Conversion
// ============================================================================
// Converts binary pointer to Gray code for safe clock domain crossing
// Formula: gray = binary ^ (binary >> 1)
function [ADDR_WIDTH:0] bin_to_gray(input [ADDR_WIDTH:0] bin);
bin_to_gray = bin ^ (bin >> 1);
endfunction
// ============================================================================
// Gray Code to Binary Conversion
// ============================================================================
// Converts Gray code pointer back to binary for calculations
// Uses cumulative XOR from MSB to LSB
function [ADDR_WIDTH:0] gray_to_bin(input [ADDR_WIDTH:0] gray);
integer i;
reg [ADDR_WIDTH:0] bin;
begin
bin = gray;
// Gray-to-binary: XOR from MSB down to LSB
for (i = 1; i <= ADDR_WIDTH; i = i + 1) begin
bin = bin ^ (gray >> i);
end
gray_to_bin = bin;
end
endfunction
// Synchronize write pointer to read clock domain
always @(posedge rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n) begin
wr_ptr_gray_rd_sync1 <= {(ADDR_WIDTH+1){1'b0}};
wr_ptr_gray_rd_sync2 <= {(ADDR_WIDTH+1){1'b0}};
end else begin
wr_ptr_gray_rd_sync1 <= wr_ptr_gray;
wr_ptr_gray_rd_sync2 <= wr_ptr_gray_rd_sync1;
end
end
// Synchronize read pointer to write clock domain
always @(posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n) begin
rd_ptr_gray_wr_sync1 <= {(ADDR_WIDTH+1){1'b0}};
rd_ptr_gray_wr_sync2 <= {(ADDR_WIDTH+1){1'b0}};
end else begin
rd_ptr_gray_wr_sync1 <= rd_ptr_gray;
rd_ptr_gray_wr_sync2 <= rd_ptr_gray_wr_sync1;
end
end
// Write pointer logic
always @(posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n) begin
wr_ptr_bin <= {(ADDR_WIDTH+1){1'b0}};
wr_ptr_gray <= {(ADDR_WIDTH+1){1'b0}};
end else if (wr_en && !full) begin
wr_ptr_bin <= wr_ptr_bin + 1'b1;
wr_ptr_gray <= bin_to_gray(wr_ptr_bin + 1'b1);
end
end
// Read pointer logic
always @(posedge rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n) begin
rd_ptr_bin <= {(ADDR_WIDTH+1){1'b0}};
rd_ptr_gray <= {(ADDR_WIDTH+1){1'b0}};
end else if (rd_en && !empty) begin
rd_ptr_bin <= rd_ptr_bin + 1'b1;
rd_ptr_gray <= bin_to_gray(rd_ptr_bin + 1'b1);
end
end
// Memory write
always @(posedge wr_clk) begin
if (wr_en && !full) begin
mem[wr_ptr_bin[ADDR_WIDTH-1:0]] <= wr_data;
end
end
// Memory read
reg [DATA_WIDTH-1:0] rd_data_reg;
always @(posedge rd_clk) begin
if (rd_en && !empty) begin
rd_data_reg <= mem[rd_ptr_bin[ADDR_WIDTH-1:0]];
end
end
// Convert synchronized gray pointers to binary for flag generation
wire [ADDR_WIDTH:0] wr_ptr_bin_rd_sync;
wire [ADDR_WIDTH:0] rd_ptr_bin_wr_sync;
assign wr_ptr_bin_rd_sync = gray_to_bin(wr_ptr_gray_rd_sync2);
assign rd_ptr_bin_wr_sync = gray_to_bin(rd_ptr_gray_wr_sync2);
// Calculate FIFO count (in read clock domain)
assign fifo_count = wr_ptr_bin_rd_sync - rd_ptr_bin;
// Generate flags
assign empty = (rd_ptr_gray == wr_ptr_gray_rd_sync2);
assign almost_empty = empty || (fifo_count <= ALMOST_EMPTY_THRESHOLD);
wire [ADDR_WIDTH:0] wr_count = wr_ptr_bin - rd_ptr_bin_wr_sync;
assign full = (wr_ptr_gray[ADDR_WIDTH] != rd_ptr_gray_wr_sync2[ADDR_WIDTH]) &&
(wr_ptr_gray[ADDR_WIDTH-1:0] == rd_ptr_gray_wr_sync2[ADDR_WIDTH-1:0]);
assign almost_full = full || (wr_count >= ((1<<ADDR_WIDTH) - ALMOST_FULL_THRESHOLD));
// Output data
assign rd_data = rd_data_reg;
endmodule