id_queue.sv 17.4 KB
Newer Older
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
// 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 <akurth@iis.ee.ethz.ch>

module id_queue #(
    parameter int ID_WIDTH  = 0,
    parameter int CAPACITY  = 0,
    parameter bit FULL_BW   = 0,
    parameter type data_t   = logic,
    // Dependent parameters, DO NOT OVERRIDE!
    localparam type id_t    = logic[ID_WIDTH-1:0],
    localparam type mask_t  = logic[$bits(data_t)-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  mask_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,
                                    idx_matches_out_id;

    logic [CAPACITY-1:0]            exists_match,
                                    linked_data_free;

    id_t                            match_in_id, match_out_id;

    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 some data is simultaneously.
    assign inp_gnt_o = ~full || (oup_data_popped && FULL_BW);
    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
        mask_t exists_match_bits;
        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.
// pragma translate_off
`ifndef VERILATOR
    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
// pragma translate_on

endmodule