-
Notifications
You must be signed in to change notification settings - Fork 145
/
id_queue.sv
420 lines (391 loc) · 17.5 KB
/
id_queue.sv
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
// Copyright 2018 ETH Zurich and University of Bologna.
// Copyright and related rights are licensed under the Solderpad Hardware
// License, Version 0.51 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
// or agreed to in writing, software, hardware and materials distributed under
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
// ID Queue
//
// In an ID queue, every element has a numeric ID. Among all elements that have the same ID, the ID
// queue preserves FIFO order.
//
// This ID queue implementation allows to either push (through the `inp_*` signals) or pop (through
// the `oup_*` signals) one element per clock cycle (depending on the _FULL_BW_ operating mode
// descibed below). The `inp_` port has priority and grants a request iff the queue is not full. The
// `oup_` port dequeues an element iff `oup_pop_i` is asserted during an `oup_` handshake;
// otherwise, it performs a non-destructive read. `oup_data_o` is valid iff `oup_data_valid_o` is
// asserted during an `oup_` handshake. If `oup_data_valid_o` is not asserted, the queue did not
// contain an element with the provided ID.
//
// The queue can work in two bandwidth modes:
// * !FULL_BW: Input and output cannot be performed simultaneously (max bandwidth: 50%).
// * FULL_BW: Input and output can be performed simultaneously and a popped cell can be reused
// immediately in the same clock cycle. Area increase typically 5-10%.
//
// This ID queue additionally provides the `exists_` port, which searches for an element anywhere in
// the queue. The comparison performed during the search can be masked: for every bit that is
// asserted in `exists_mask_i`, the corresponding bit in the queue element and in `exists_data_i`
// must be equal for a match; the other bits are not compared. If masking is not required, tie
// `exists_mask_i_ to `'1` and the synthesizer should simplify the comparisons to unmasked ones. The
// `exists_` port operates independently of the `inp_` and `oup_` ports. If the `exists_` port is
// unused, tie `exists_req_i` to `1'b0` and the synthesizer should remove the internal comparators.
//
// This ID queue can store at most `CAPACITY` elements, independent of their ID. Let
// - C = `CAPACITY`
// - B = $bits(data_t)
// - I = 2**`ID_WIDTH`
// Then
// - the queue element storage requires O(C * (B + log2(C))) bit
// - the ID table requires O(H * log2(C)) bit, where H = min(C, I)
//
// Maintainers:
// - Andreas Kurth <[email protected]>
module id_queue #(
parameter int ID_WIDTH = 0,
parameter int CAPACITY = 0,
parameter bit FULL_BW = 0,
parameter bit CUT_OUP_POP_INP_GNT = 0,
parameter type data_t = logic[31:0],
// Dependent parameters, DO NOT OVERRIDE!
localparam type id_t = logic[ID_WIDTH-1:0]
) (
input logic clk_i,
input logic rst_ni,
input id_t inp_id_i,
input data_t inp_data_i,
input logic inp_req_i,
output logic inp_gnt_o,
input data_t exists_data_i,
input data_t exists_mask_i,
input logic exists_req_i,
output logic exists_o,
output logic exists_gnt_o,
input id_t oup_id_i,
input logic oup_pop_i,
input logic oup_req_i,
output data_t oup_data_o,
output logic oup_data_valid_o,
output logic oup_gnt_o
);
// Capacity of the head-tail table, which associates an ID with corresponding head and tail
// indices.
localparam int NIds = 2**ID_WIDTH;
localparam int HtCapacity = (NIds <= CAPACITY) ? NIds : CAPACITY;
localparam int unsigned HtIdxWidth = cf_math_pkg::idx_width(HtCapacity);
localparam int unsigned LdIdxWidth = cf_math_pkg::idx_width(CAPACITY);
// Type for indexing the head-tail table.
typedef logic [HtIdxWidth-1:0] ht_idx_t;
// Type for indexing the lined data table.
typedef logic [LdIdxWidth-1:0] ld_idx_t;
// Type of an entry in the head-tail table.
typedef struct packed {
id_t id;
ld_idx_t head,
tail;
logic free;
} head_tail_t;
// Type of an entry in the linked data table.
typedef struct packed {
data_t data;
ld_idx_t next;
logic free;
} linked_data_t;
head_tail_t [HtCapacity-1:0] head_tail_d, head_tail_q;
linked_data_t [CAPACITY-1:0] linked_data_d, linked_data_q;
logic full,
match_in_id_valid,
match_out_id_valid,
no_in_id_match,
no_out_id_match;
logic [HtCapacity-1:0] head_tail_free,
idx_matches_in_id /*verilator split_var*/,
idx_matches_out_id;
logic [CAPACITY-1:0] exists_match,
linked_data_free;
id_t match_in_id /*verilator split_var*/, match_out_id /*verilator split_var*/;
ht_idx_t head_tail_free_idx,
match_in_idx,
match_out_idx;
ld_idx_t linked_data_free_idx,
oup_data_free_idx;
logic oup_data_popped,
oup_ht_popped;
// Find the index in the head-tail table that matches a given ID.
for (genvar i = 0; i < HtCapacity; i++) begin: gen_idx_match
assign idx_matches_in_id[i] = match_in_id_valid && (head_tail_q[i].id == match_in_id) &&
!head_tail_q[i].free;
assign idx_matches_out_id[i] = match_out_id_valid && (head_tail_q[i].id == match_out_id) &&
!head_tail_q[i].free;
end
assign no_in_id_match = !(|idx_matches_in_id);
assign no_out_id_match = !(|idx_matches_out_id);
onehot_to_bin #(
.ONEHOT_WIDTH ( HtCapacity )
) i_id_ohb_in (
.onehot ( idx_matches_in_id ),
.bin ( match_in_idx )
);
onehot_to_bin #(
.ONEHOT_WIDTH ( HtCapacity )
) i_id_ohb_out (
.onehot ( idx_matches_out_id ),
.bin ( match_out_idx )
);
// Find the first free index in the head-tail table.
for (genvar i = 0; i < HtCapacity; i++) begin: gen_head_tail_free
assign head_tail_free[i] = head_tail_q[i].free;
end
lzc #(
.WIDTH ( HtCapacity ),
.MODE ( 0 ) // Start at index 0.
) i_ht_free_lzc (
.in_i ( head_tail_free ),
.cnt_o ( head_tail_free_idx ),
.empty_o ( )
);
// Find the first free index in the linked data table.
for (genvar i = 0; i < CAPACITY; i++) begin: gen_linked_data_free
assign linked_data_free[i] = linked_data_q[i].free;
end
lzc #(
.WIDTH ( CAPACITY ),
.MODE ( 0 ) // Start at index 0.
) i_ld_free_lzc (
.in_i ( linked_data_free ),
.cnt_o ( linked_data_free_idx ),
.empty_o ( )
);
// The queue is full if and only if there are no free items in the linked data structure.
assign full = !(|linked_data_free);
// Data potentially freed by the output.
assign oup_data_free_idx = head_tail_q[match_out_idx].head;
// Data can be accepted if the linked list pool is not full, or if some data is simultaneously
// popped (given FULL_BW & !CUT_OUP_POP_INP_GNT).
assign inp_gnt_o = ~full || (oup_data_popped && FULL_BW && ~CUT_OUP_POP_INP_GNT);
always_comb begin
match_in_id = '0;
match_out_id = '0;
match_in_id_valid = 1'b0;
match_out_id_valid = 1'b0;
head_tail_d = head_tail_q;
linked_data_d = linked_data_q;
oup_gnt_o = 1'b0;
oup_data_o = data_t'('0);
oup_data_valid_o = 1'b0;
oup_data_popped = 1'b0;
oup_ht_popped = 1'b0;
if (!FULL_BW) begin
if (inp_req_i && !full) begin
match_in_id = inp_id_i;
match_in_id_valid = 1'b1;
// If the ID does not yet exist in the queue, add a new ID entry.
if (no_in_id_match) begin
head_tail_d[head_tail_free_idx] = '{
id: inp_id_i,
head: linked_data_free_idx,
tail: linked_data_free_idx,
free: 1'b0
};
// Otherwise append it to the existing ID subqueue.
end else begin
linked_data_d[head_tail_q[match_in_idx].tail].next = linked_data_free_idx;
head_tail_d[match_in_idx].tail = linked_data_free_idx;
end
linked_data_d[linked_data_free_idx] = '{
data: inp_data_i,
next: '0,
free: 1'b0
};
end else if (oup_req_i) begin
match_in_id = oup_id_i;
match_in_id_valid = 1'b1;
if (!no_in_id_match) begin
oup_data_o = data_t'(linked_data_q[head_tail_q[match_in_idx].head].data);
oup_data_valid_o = 1'b1;
if (oup_pop_i) begin
// Set free bit of linked data entry, all other bits are don't care.
linked_data_d[head_tail_q[match_in_idx].head] = '0;
linked_data_d[head_tail_q[match_in_idx].head][0] = 1'b1;
if (head_tail_q[match_in_idx].head == head_tail_q[match_in_idx].tail) begin
head_tail_d[match_in_idx] = '{free: 1'b1, default: '0};
end else begin
head_tail_d[match_in_idx].head =
linked_data_q[head_tail_q[match_in_idx].head].next;
end
end
end
// Always grant the output request. If there was no match, the default, invalid entry
// will be returned.
oup_gnt_o = 1'b1;
end
end else begin
// FULL_BW
if (oup_req_i) begin
match_out_id = oup_id_i;
match_out_id_valid = 1'b1;
if (!no_out_id_match) begin
oup_data_o = data_t'(linked_data_q[head_tail_q[match_out_idx].head].data);
oup_data_valid_o = 1'b1;
if (oup_pop_i) begin
oup_data_popped = 1'b1;
// Set free bit of linked data entry, all other bits are don't care.
linked_data_d[head_tail_q[match_out_idx].head] = '0;
linked_data_d[head_tail_q[match_out_idx].head][0] = 1'b1;
if (head_tail_q[match_out_idx].head
== head_tail_q[match_out_idx].tail) begin
oup_ht_popped = 1'b1;
head_tail_d[match_out_idx] = '{free: 1'b1, default: '0};
end else begin
head_tail_d[match_out_idx].head =
linked_data_q[head_tail_q[match_out_idx].head].next;
end
end
end
// Always grant the output request. If there was no match, the default, invalid entry
// will be returned.
oup_gnt_o = 1'b1;
end
if (inp_req_i && inp_gnt_o) begin
match_in_id = inp_id_i;
match_in_id_valid = 1'b1;
// If the ID does not yet exist in the queue or was just popped, add a new ID entry.
if (oup_ht_popped && (oup_id_i==inp_id_i)) begin
// If output data was popped for this ID, which lead the head_tail to be popped,
// then repopulate this head_tail immediately.
head_tail_d[match_out_idx] = '{
id: inp_id_i,
head: oup_data_free_idx,
tail: oup_data_free_idx,
free: 1'b0
};
linked_data_d[oup_data_free_idx] = '{
data: inp_data_i,
next: '0,
free: 1'b0
};
end else if (no_in_id_match) begin
// Else, if no head_tail corresponds to the input id.
if (oup_ht_popped) begin
head_tail_d[match_out_idx] = '{
id: inp_id_i,
head: oup_data_free_idx,
tail: oup_data_free_idx,
free: 1'b0
};
linked_data_d[oup_data_free_idx] = '{
data: inp_data_i,
next: '0,
free: 1'b0
};
end else begin
if (oup_data_popped) begin
head_tail_d[head_tail_free_idx] = '{
id: inp_id_i,
head: oup_data_free_idx,
tail: oup_data_free_idx,
free: 1'b0
};
linked_data_d[oup_data_free_idx] = '{
data: inp_data_i,
next: '0,
free: 1'b0
};
end else begin
head_tail_d[head_tail_free_idx] = '{
id: inp_id_i,
head: linked_data_free_idx,
tail: linked_data_free_idx,
free: 1'b0
};
linked_data_d[linked_data_free_idx] = '{
data: inp_data_i,
next: '0,
free: 1'b0
};
end
end
end else begin
// Otherwise append it to the existing ID subqueue.
if (oup_data_popped) begin
linked_data_d[head_tail_q[match_in_idx].tail].next = oup_data_free_idx;
head_tail_d[match_in_idx].tail = oup_data_free_idx;
linked_data_d[oup_data_free_idx] = '{
data: inp_data_i,
next: '0,
free: 1'b0
};
end else begin
linked_data_d[head_tail_q[match_in_idx].tail].next = linked_data_free_idx;
head_tail_d[match_in_idx].tail = linked_data_free_idx;
linked_data_d[linked_data_free_idx] = '{
data: inp_data_i,
next: '0,
free: 1'b0
};
end
end
end
end
end
// Exists Lookup
for (genvar i = 0; i < CAPACITY; i++) begin: gen_lookup
data_t exists_match_bits /*verilator split_var*/;
for (genvar j = 0; j < $bits(data_t); j++) begin: gen_mask
always_comb begin
if (linked_data_q[i].free) begin
exists_match_bits[j] = 1'b0;
end else begin
if (!exists_mask_i[j]) begin
exists_match_bits[j] = 1'b1;
end else begin
exists_match_bits[j] = (linked_data_q[i].data[j] == exists_data_i[j]);
end
end
end
end
assign exists_match[i] = (&exists_match_bits);
end
always_comb begin
exists_gnt_o = 1'b0;
exists_o = '0;
if (exists_req_i) begin
exists_gnt_o = 1'b1;
exists_o = (|exists_match);
end
end
// Registers
for (genvar i = 0; i < HtCapacity; i++) begin: gen_ht_ffs
always_ff @(posedge clk_i, negedge rst_ni) begin
if (!rst_ni) begin
head_tail_q[i] <= '{free: 1'b1, default: '0};
end else begin
head_tail_q[i] <= head_tail_d[i];
end
end
end
for (genvar i = 0; i < CAPACITY; i++) begin: gen_data_ffs
always_ff @(posedge clk_i, negedge rst_ni) begin
if (!rst_ni) begin
// Set free bit of linked data entries, all other bits are don't care.
linked_data_q[i] <= '0;
linked_data_q[i][0] <= 1'b1;
end else begin
linked_data_q[i] <= linked_data_d[i];
end
end
end
// Validate parameters.
`ifndef SYNTHESIS
`ifndef COMMON_CELLS_ASSERTS_OFF
initial begin: validate_params
assert (ID_WIDTH >= 1)
else $fatal(1, "The ID must at least be one bit wide!");
assert (CAPACITY >= 1)
else $fatal(1, "The queue must have capacity of at least one entry!");
end
`endif
`endif
endmodule