axi-id-remap.patch 28.3 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 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656
diff --git b/hardware/deps/axi/Bender.yml a/hardware/deps/axi/Bender.yml
index a7ba2b8..ce584e1 100644
--- b/hardware/deps/axi/Bender.yml
+++ a/hardware/deps/axi/Bender.yml
@@ -46,6 +46,7 @@ sources:
   - src/axi_mux.sv
   - src/axi_serializer.sv
   # Level 3
+  - src/axi_id_remap.sv
   - src/axi_cdc.sv
   - src/axi_err_slv.sv
   - src/axi_dw_converter.sv
diff --git b/hardware/deps/axi/src/axi_id_remap.sv a/hardware/deps/axi/src/axi_id_remap.sv
new file mode 100644
index 0000000..a2cc347
--- /dev/null
+++ a/hardware/deps/axi/src/axi_id_remap.sv
@@ -0,0 +1,638 @@
+// Copyright (c) 2014-2020 ETH Zurich, 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.
+//
+// Andreas Kurth <akurth@iis.ee.ethz.ch>
+// Florian Zaruba <zarubaf@iis.ee.ethz.ch>
+// Wolfgang Roenninger <wroennin@iis.ee.ethz.ch>
+
+`include "common_cells/registers.svh"
+
+/// Remap AXI IDs from wide IDs at the slave port to narrower IDs at the master port.
+///
+/// This module is designed to remap an overly wide, sparsely used ID space to a narrower, densely
+/// used ID space.  This scenario occurs, for example, when an AXI master has wide ID ports but
+/// effectively only uses a (not necessarily contiguous) subset of IDs.
+///
+/// This module retains the independence of IDs.  That is, if two transactions have different IDs at
+/// the slave port of this module, they are guaranteed to have different IDs at the master port of
+/// this module.  This implies a lower bound on the [width of IDs on the master
+/// port](#parameter.AxiMstPortIdWidth).  If you require narrower master port IDs and can forgo ID
+/// independence, use [`axi_id_serialize`](module.axi_id_serialize) instead.
+///
+/// Internally, a [table is used for remapping IDs](module.axi_id_remap_table).
+module axi_id_remap #(
+  /// ID width of the AXI4+ATOP slave port.
+  parameter int unsigned AxiSlvPortIdWidth = 32'd0,
+  /// Maximum number of different IDs that can be in flight at the slave port.  Reads and writes are
+  /// counted separately (except for ATOPs, which count as both read and write).
+  ///
+  /// It is legal for upstream to have transactions with more unique IDs than the maximum given by
+  /// this parameter in flight, but a transaction exceeding the maximum will be stalled until all
+  /// transactions of another ID complete.
+  ///
+  /// The maximum value of this parameter is `2**AxiSlvPortIdWidth`.
+  parameter int unsigned AxiSlvPortMaxUniqIds = 32'd0,
+  /// Maximum number of in-flight transactions with the same ID.
+  ///
+  /// It is legal for upstream to have more transactions than the maximum given by this parameter in
+  /// flight for any ID, but a transaction exceeding the maximum will be stalled until another
+  /// transaction with the same ID completes.
+  parameter int unsigned AxiMaxTxnsPerId = 32'd0,
+  /// ID width of the AXI4+ATOP master port.
+  ///
+  /// The minimum value of this parameter is the ceiled binary logarithm of `AxiSlvPortMaxUniqIds`,
+  /// because IDs at the master port must be wide enough to represent IDs up to
+  /// `AxiSlvPortMaxUniqIds-1`.
+  ///
+  /// If master IDs are wider than the minimum, they are extended by prepending zeros.
+  parameter int unsigned AxiMstPortIdWidth = 32'd0,
+  /// Request struct type of the AXI4+ATOP slave port.
+  ///
+  /// The width of all IDs in this struct must match `AxiSlvPortIdWidth`.
+  parameter type slv_req_t = logic,
+  /// Response struct type of the AXI4+ATOP slave port.
+  ///
+  /// The width of all IDs in this struct must match `AxiSlvPortIdWidth`.
+  parameter type slv_resp_t = logic,
+  /// Request struct type of the AXI4+ATOP master port
+  ///
+  /// The width of all IDs in this struct must match `AxiMstPortIdWidth`.
+  parameter type mst_req_t = logic,
+  /// Response struct type of the AXI4+ATOP master port
+  ///
+  /// The width of all IDs in this struct must match `AxiMstPortIdWidth`.
+  parameter type mst_resp_t = logic
+) (
+  /// Rising-edge clock of all ports
+  input  logic      clk_i,
+  /// Asynchronous reset, active low
+  input  logic      rst_ni,
+  /// Slave port request
+  input  slv_req_t  slv_req_i,
+  /// Slave port response
+  output slv_resp_t slv_resp_o,
+  /// Master port request
+  output mst_req_t  mst_req_o,
+  /// Master port response
+  input  mst_resp_t mst_resp_i
+);
+
+  // Feed all signals that are not ID or flow control of AW and AR through.
+  assign mst_req_o.aw.addr   = slv_req_i.aw.addr;
+  assign mst_req_o.aw.len    = slv_req_i.aw.len;
+  assign mst_req_o.aw.size   = slv_req_i.aw.size;
+  assign mst_req_o.aw.burst  = slv_req_i.aw.burst;
+  assign mst_req_o.aw.lock   = slv_req_i.aw.lock;
+  assign mst_req_o.aw.cache  = slv_req_i.aw.cache;
+  assign mst_req_o.aw.prot   = slv_req_i.aw.prot;
+  assign mst_req_o.aw.qos    = slv_req_i.aw.qos;
+  assign mst_req_o.aw.region = slv_req_i.aw.region;
+  assign mst_req_o.aw.atop   = slv_req_i.aw.atop;
+  assign mst_req_o.aw.user   = slv_req_i.aw.user;
+
+  assign mst_req_o.w.data    = slv_req_i.w.data;
+  assign mst_req_o.w.strb    = slv_req_i.w.strb;
+  assign mst_req_o.w.last    = slv_req_i.w.last;
+  assign mst_req_o.w.user    = slv_req_i.w.user;
+  assign mst_req_o.w_valid   = slv_req_i.w_valid;
+  assign slv_resp_o.w_ready  = mst_resp_i.w_ready;
+
+  assign slv_resp_o.b.resp   = mst_resp_i.b.resp;
+  assign slv_resp_o.b.user   = mst_resp_i.b.user;
+  assign slv_resp_o.b_valid  = mst_resp_i.b_valid;
+  assign mst_req_o.b_ready   = slv_req_i.b_ready;
+
+  assign mst_req_o.ar.addr   = slv_req_i.ar.addr;
+  assign mst_req_o.ar.len    = slv_req_i.ar.len;
+  assign mst_req_o.ar.size   = slv_req_i.ar.size;
+  assign mst_req_o.ar.burst  = slv_req_i.ar.burst;
+  assign mst_req_o.ar.lock   = slv_req_i.ar.lock;
+  assign mst_req_o.ar.cache  = slv_req_i.ar.cache;
+  assign mst_req_o.ar.prot   = slv_req_i.ar.prot;
+  assign mst_req_o.ar.qos    = slv_req_i.ar.qos;
+  assign mst_req_o.ar.region = slv_req_i.ar.region;
+  assign mst_req_o.ar.user   = slv_req_i.ar.user;
+
+  assign slv_resp_o.r.data   = mst_resp_i.r.data;
+  assign slv_resp_o.r.resp   = mst_resp_i.r.resp;
+  assign slv_resp_o.r.last   = mst_resp_i.r.last;
+  assign slv_resp_o.r.user   = mst_resp_i.r.user;
+  assign slv_resp_o.r_valid  = mst_resp_i.r_valid;
+  assign mst_req_o.r_ready   = slv_req_i.r_ready;
+
+
+  // Remap tables keep track of in-flight bursts and their input and output IDs.
+  localparam int unsigned IdxWidth = cf_math_pkg::idx_width(AxiSlvPortMaxUniqIds);
+  typedef logic [AxiSlvPortMaxUniqIds-1:0]  field_t;
+  typedef logic [AxiSlvPortIdWidth-1:0]     id_inp_t;
+  typedef logic [IdxWidth-1:0]              idx_t;
+  field_t   wr_free,          rd_free,          both_free;
+  id_inp_t                    rd_push_inp_id;
+  idx_t     wr_free_oup_id,   rd_free_oup_id,   both_free_oup_id,
+            wr_push_oup_id,   rd_push_oup_id,
+            wr_exists_id,     rd_exists_id;
+  logic     wr_exists,        rd_exists,
+            wr_exists_full,   rd_exists_full,
+            wr_full,          rd_full,
+            wr_push,          rd_push;
+
+  axi_id_remap_table #(
+    .InpIdWidth     ( AxiSlvPortIdWidth     ),
+    .MaxUniqInpIds  ( AxiSlvPortMaxUniqIds  ),
+    .MaxTxnsPerId   ( AxiMaxTxnsPerId       )
+  ) i_wr_table (
+    .clk_i,
+    .rst_ni,
+    .free_o          ( wr_free                                 ),
+    .free_oup_id_o   ( wr_free_oup_id                          ),
+    .full_o          ( wr_full                                 ),
+    .push_i          ( wr_push                                 ),
+    .push_inp_id_i   ( slv_req_i.aw.id                         ),
+    .push_oup_id_i   ( wr_push_oup_id                          ),
+    .exists_inp_id_i ( slv_req_i.aw.id                         ),
+    .exists_o        ( wr_exists                               ),
+    .exists_oup_id_o ( wr_exists_id                            ),
+    .exists_full_o   ( wr_exists_full                          ),
+    .pop_i           ( slv_resp_o.b_valid && slv_req_i.b_ready ),
+    .pop_oup_id_i    ( mst_resp_i.b.id[IdxWidth-1:0]           ),
+    .pop_inp_id_o    ( slv_resp_o.b.id                         )
+  );
+  axi_id_remap_table #(
+    .InpIdWidth     ( AxiSlvPortIdWidth     ),
+    .MaxUniqInpIds  ( AxiSlvPortMaxUniqIds  ),
+    .MaxTxnsPerId   ( AxiMaxTxnsPerId       )
+  ) i_rd_table (
+    .clk_i,
+    .rst_ni,
+    .free_o           ( rd_free                                                      ),
+    .free_oup_id_o    ( rd_free_oup_id                                               ),
+    .full_o           ( rd_full                                                      ),
+    .push_i           ( rd_push                                                      ),
+    .push_inp_id_i    ( rd_push_inp_id                                               ),
+    .push_oup_id_i    ( rd_push_oup_id                                               ),
+    .exists_inp_id_i  ( slv_req_i.ar.id                                              ),
+    .exists_o         ( rd_exists                                                    ),
+    .exists_oup_id_o  ( rd_exists_id                                                 ),
+    .exists_full_o    ( rd_exists_full                                               ),
+    .pop_i            ( slv_resp_o.r_valid && slv_req_i.r_ready && slv_resp_o.r.last ),
+    .pop_oup_id_i     ( mst_resp_i.r.id[IdxWidth-1:0]                                ),
+    .pop_inp_id_o     ( slv_resp_o.r.id                                              )
+  );
+  assign both_free = wr_free & rd_free;
+  lzc #(
+    .WIDTH  ( AxiSlvPortMaxUniqIds  ),
+    .MODE   ( 1'b0                  )
+  ) i_lzc (
+    .in_i     ( both_free        ),
+    .cnt_o    ( both_free_oup_id ),
+    .empty_o  ( /* unused */     )
+  );
+
+  // Zero-extend output IDs if the output IDs is are wider than the IDs from the tables.
+  localparam ZeroWidth = AxiMstPortIdWidth - IdxWidth;
+  assign mst_req_o.ar.id = {{ZeroWidth{1'b0}}, rd_push_oup_id};
+  assign mst_req_o.aw.id = {{ZeroWidth{1'b0}}, wr_push_oup_id};
+
+  // Handle requests.
+  enum logic [1:0] {Ready, HoldAR, HoldAW, HoldAx} state_d, state_q;
+  idx_t ar_id_d, ar_id_q,
+        aw_id_d, aw_id_q;
+  always_comb begin
+    mst_req_o.aw_valid  = 1'b0;
+    slv_resp_o.aw_ready = 1'b0;
+    wr_push             = 1'b0;
+    wr_push_oup_id      =   '0;
+    mst_req_o.ar_valid  = 1'b0;
+    slv_resp_o.ar_ready = 1'b0;
+    rd_push             = 1'b0;
+    rd_push_inp_id      =   '0;
+    rd_push_oup_id      =   '0;
+    ar_id_d             = ar_id_q;
+    aw_id_d             = aw_id_q;
+    state_d             = state_q;
+
+    unique case (state_q)
+      Ready: begin
+        // Reads
+        if (slv_req_i.ar_valid) begin
+          // If a burst with the same input ID is already in flight or there are free output IDs:
+          if ((rd_exists && !rd_exists_full) || (!rd_exists && !rd_full)) begin
+            // Determine the output ID: if another in-flight burst had the same input ID, we must
+            // reuse its output ID to maintain ordering; else, we assign the next free ID.
+            rd_push_inp_id     = slv_req_i.ar.id;
+            rd_push_oup_id     = rd_exists ? rd_exists_id : rd_free_oup_id;
+            // Forward the AR and push a new entry to the read table.
+            mst_req_o.ar_valid = 1'b1;
+            rd_push            = 1'b1;
+          end
+        end
+
+        // Writes
+        if (slv_req_i.aw_valid) begin
+          // If this is not an ATOP that gives rise to an R response, we can handle it in isolation
+          // on the write direction.
+          if (!slv_req_i.aw.atop[5]) begin
+            // If a burst with the same input ID is already in flight or there are free output IDs:
+            if ((wr_exists && !wr_exists_full) || (!wr_exists && !wr_full)) begin
+              // Determine the output ID: if another in-flight burst had the same input ID, we must
+              // reuse its output ID to maintain ordering; else, we assign the next free ID.
+              wr_push_oup_id     = wr_exists ? wr_exists_id : wr_free_oup_id;
+              // Forward the AW and push a new entry to the write table.
+              mst_req_o.aw_valid = 1'b1;
+              wr_push            = 1'b1;
+            end
+          // If this is an ATOP that gives rise to an R response, we must remap to an ID that is
+          // free on both read and write direction and push also to the read table.
+          end else begin
+            // Nullify a potential AR at our output.  This is legal in this state.
+            mst_req_o.ar_valid  = 1'b0;
+            slv_resp_o.ar_ready = 1'b0;
+            rd_push             = 1'b0;
+            if ((|both_free)) begin
+              // Use an output ID that is free in both directions.
+              wr_push_oup_id = both_free_oup_id;
+              rd_push_inp_id = slv_req_i.aw.id;
+              rd_push_oup_id = both_free_oup_id;
+              // Forward the AW and push a new entry to both tables.
+              mst_req_o.aw_valid = 1'b1;
+              rd_push            = 1'b1;
+              wr_push            = 1'b1;
+            end
+          end
+        end
+
+        // Hold AR, AW, or both if they are valid but not yet ready.
+        if (mst_req_o.ar_valid) begin
+          slv_resp_o.ar_ready = mst_resp_i.ar_ready;
+          if (!mst_resp_i.ar_ready) begin
+            ar_id_d = rd_push_oup_id;
+          end
+        end
+        if (mst_req_o.aw_valid) begin
+          slv_resp_o.aw_ready = mst_resp_i.aw_ready;
+          if (!mst_resp_i.aw_ready) begin
+            aw_id_d = wr_push_oup_id;
+          end
+        end
+        priority casez ({mst_req_o.ar_valid, mst_resp_i.ar_ready,
+                         mst_req_o.aw_valid, mst_resp_i.aw_ready})
+          4'b1010: state_d = HoldAx;
+          4'b10??: state_d = HoldAR;
+          4'b??10: state_d = HoldAW;
+          default: state_d = Ready;
+        endcase
+      end
+
+      HoldAR: begin
+        // Drive `mst_req_o.ar.id` through `rd_push_oup_id`.
+        rd_push_oup_id      = ar_id_q;
+        mst_req_o.ar_valid  = 1'b1;
+        slv_resp_o.ar_ready = mst_resp_i.ar_ready;
+        if (mst_resp_i.ar_ready) begin
+          state_d = Ready;
+        end
+      end
+
+      HoldAW: begin
+        // Drive mst_req_o.aw.id through `wr_push_oup_id`.
+        wr_push_oup_id      = aw_id_q;
+        mst_req_o.aw_valid  = 1'b1;
+        slv_resp_o.aw_ready = mst_resp_i.aw_ready;
+        if (mst_resp_i.aw_ready) begin
+          state_d = Ready;
+        end
+      end
+
+      HoldAx: begin
+        rd_push_oup_id      = ar_id_q;
+        mst_req_o.ar_valid  = 1'b1;
+        slv_resp_o.ar_ready = mst_resp_i.ar_ready;
+        wr_push_oup_id      = aw_id_q;
+        mst_req_o.aw_valid  = 1'b1;
+        slv_resp_o.aw_ready = mst_resp_i.aw_ready;
+        unique case ({mst_resp_i.ar_ready, mst_resp_i.aw_ready})
+          2'b01:   state_d = HoldAR;
+          2'b10:   state_d = HoldAW;
+          2'b11:   state_d = Ready;
+          default: /*do nothing / stay in this state*/;
+        endcase
+      end
+
+      default: state_d = Ready;
+    endcase
+  end
+
+  // Registers
+  `FFARN(ar_id_q, ar_id_d, '0, clk_i, rst_ni)
+  `FFARN(aw_id_q, aw_id_d, '0, clk_i, rst_ni)
+  `FFARN(state_q, state_d, Ready, clk_i, rst_ni)
+
+  // pragma translate_off
+  `ifndef VERILATOR
+  initial begin : p_assert
+    assert(AxiSlvPortIdWidth > 32'd0)
+      else $fatal(1, "Parameter AxiSlvPortIdWidth has to be larger than 0!");
+    assert(AxiMstPortIdWidth >= IdxWidth)
+      else $fatal(1, "Parameter AxiMstPortIdWidth has to be at least IdxWidth!");
+    assert (AxiSlvPortMaxUniqIds > 0)
+      else $fatal(1, "Parameter AxiSlvPortMaxUniqIds has to be larger than 0!");
+    assert (AxiSlvPortMaxUniqIds <= 2**AxiSlvPortIdWidth)
+      else $fatal(1, "Parameter AxiSlvPortMaxUniqIds may be at most 2**AxiSlvPortIdWidth!");
+    assert (AxiMaxTxnsPerId > 0)
+      else $fatal(1, "Parameter AxiMaxTxnsPerId has to be larger than 0!");
+    assert($bits(slv_req_i.aw.addr) == $bits(mst_req_o.aw.addr))
+      else $fatal(1, "AXI AW address widths are not equal!");
+    assert($bits(slv_req_i.w.data) == $bits(mst_req_o.w.data))
+      else $fatal(1, "AXI W data widths are not equal!");
+    assert($bits(slv_req_i.w.user) == $bits(mst_req_o.w.user))
+      else $fatal(1, "AXI W user widths are not equal!");
+    assert($bits(slv_req_i.ar.addr) == $bits(mst_req_o.ar.addr))
+      else $fatal(1, "AXI AR address widths are not equal!");
+    assert($bits(slv_resp_o.r.data) == $bits(mst_resp_i.r.data))
+      else $fatal(1, "AXI R data widths are not equal!");
+    assert ($bits(slv_req_i.aw.id) == AxiSlvPortIdWidth);
+    assert ($bits(slv_resp_o.b.id) == AxiSlvPortIdWidth);
+    assert ($bits(slv_req_i.ar.id) == AxiSlvPortIdWidth);
+    assert ($bits(slv_resp_o.r.id) == AxiSlvPortIdWidth);
+    assert ($bits(mst_req_o.aw.id) == AxiMstPortIdWidth);
+    assert ($bits(mst_resp_i.b.id) == AxiMstPortIdWidth);
+    assert ($bits(mst_req_o.ar.id) == AxiMstPortIdWidth);
+    assert ($bits(mst_resp_i.r.id) == AxiMstPortIdWidth);
+  end
+  default disable iff (!rst_ni);
+  assert property (@(posedge clk_i) slv_req_i.aw_valid && slv_resp_o.aw_ready
+      |-> mst_req_o.aw_valid && mst_resp_i.aw_ready);
+  assert property (@(posedge clk_i) mst_resp_i.b_valid && mst_req_o.b_ready
+      |-> slv_resp_o.b_valid && slv_req_i.b_ready);
+  assert property (@(posedge clk_i) slv_req_i.ar_valid && slv_resp_o.ar_ready
+      |-> mst_req_o.ar_valid && mst_resp_i.ar_ready);
+  assert property (@(posedge clk_i) mst_resp_i.r_valid && mst_req_o.r_ready
+      |-> slv_resp_o.r_valid && slv_req_i.r_ready);
+  assert property (@(posedge clk_i) slv_resp_o.r_valid
+      |-> slv_resp_o.r.last == mst_resp_i.r.last);
+  assert property (@(posedge clk_i) mst_req_o.ar_valid && !mst_resp_i.ar_ready
+      |=> mst_req_o.ar_valid && $stable(mst_req_o.ar.id));
+  assert property (@(posedge clk_i) mst_req_o.aw_valid && !mst_resp_i.aw_ready
+      |=> mst_req_o.aw_valid && $stable(mst_req_o.aw.id));
+  `endif
+  // pragma translate_on
+endmodule
+
+/// Internal module of [`axi_id_remap`](module.axi_id_remap): Table to remap input to output IDs.
+///
+/// This module contains a table indexed by the output ID (type `idx_t`). Each table entry has two
+/// fields: the input ID and a counter that records how many transactions with the input and output
+/// ID of the entry are in-flight.
+///
+/// The mapping from input and output IDs is injective.  Therefore, when the table contains an entry
+/// for an input ID with non-zero counter value, subsequent input IDs must use the same entry and
+/// thus the same output ID.
+///
+/// ## Relation of types and table layout
+/// ![diagram of table](axi_id_remap_table.svg)
+///
+/// ## Complexity
+/// This module has:
+/// - `MaxUniqInpIds * InpIdWidth * clog2(MaxTxnsPerId)` flip flops;
+/// - `MaxUniqInpIds` comparators of width `InpIdWidth`;
+/// - 2 leading-zero counters of width `MaxUniqInpIds`.
+module axi_id_remap_table #(
+  /// Width of input IDs, therefore width of `id_inp_t`.
+  parameter int unsigned InpIdWidth = 32'd0,
+  /// Maximum number of different input IDs that can be in-flight. This defines the number of remap
+  /// table entries.
+  ///
+  /// The maximum value of this parameter is `2**InpIdWidth`.
+  parameter int unsigned MaxUniqInpIds = 32'd0,
+  /// Maximum number of in-flight transactions with the same ID.
+  parameter int unsigned MaxTxnsPerId = 32'd0,
+  /// Derived (**=do not override**) type of input IDs.
+  localparam type id_inp_t = logic [InpIdWidth-1:0],
+  /// Derived (**=do not override**) width of table index (ceiled binary logarithm of
+  /// `MaxUniqInpIds`).
+  localparam int unsigned IdxWidth = cf_math_pkg::idx_width(MaxUniqInpIds),
+  /// Derived (**=do not override**) type of table index (width = `IdxWidth`).
+  localparam type idx_t = logic [IdxWidth-1:0],
+  /// Derived (**=do not override**) type with one bit per table entry (thus also output ID).
+  localparam type field_t = logic [MaxUniqInpIds-1:0]
+) (
+  /// Rising-edge clock of all ports
+  input  logic    clk_i,
+  /// Asynchronous reset, active low
+  input  logic    rst_ni,
+
+  /// One bit per table entry / output ID that indicates whether the entry is free.
+  output field_t  free_o,
+  /// Lowest free output ID.  Only valid if the table is not full (i.e., `!full_o`).
+  output idx_t    free_oup_id_o,
+  /// Indicates whether the table is full.
+  output logic    full_o,
+
+  /// Push an input/output ID pair to the table.
+  input  logic    push_i,
+  /// Input ID to be pushed. If the table already contains this ID, its counter must be smaller than
+  /// `MaxTxnsPerId`.
+  input  id_inp_t push_inp_id_i,
+  /// Output ID to be pushed.  If the table already contains the input ID to be pushed, the output
+  /// ID **must** match the output ID of the existing entry with the same input ID.
+  input  idx_t    push_oup_id_i,
+
+  /// Input ID to look up in the table.
+  input  id_inp_t exists_inp_id_i,
+  /// Indicates whether the given input ID exists in the table.
+  output logic    exists_o,
+  /// The output ID of the given input ID.  Only valid if the input ID exists (i.e., `exists_o`).
+  output idx_t    exists_oup_id_o,
+  /// Indicates whether the maximum number of transactions for the given input ID is reached.  Only
+  /// valid if the input ID exists (i.e., `exists_o`).
+  output logic    exists_full_o,
+
+  /// Pop an output ID from the table.  This reduces the counter for the table index given in
+  /// `pop_oup_id_i` by one.
+  input  logic    pop_i,
+  /// Output ID to be popped.  The counter for this ID must be larger than `0`.
+  input  idx_t    pop_oup_id_i,
+  /// Input ID corresponding to the popped output ID.
+  output id_inp_t pop_inp_id_o
+);
+
+  /// Counter width, derived to hold numbers up to `MaxTxnsPerId`.
+  localparam int unsigned CntWidth = $clog2(MaxTxnsPerId+1);
+  /// Counter that tracks the number of in-flight transactions with an ID.
+  typedef logic [CntWidth-1:0] cnt_t;
+
+  /// Type of a table entry.
+  typedef struct packed {
+    id_inp_t  inp_id;
+    cnt_t     cnt;
+  } entry_t;
+
+  // Table indexed by output IDs that contains the corresponding input IDs
+  entry_t [MaxUniqInpIds-1:0] table_d, table_q;
+
+  // Determine lowest free output ID.
+  for (genvar i = 0; i < MaxUniqInpIds; i++) begin : gen_free_o
+    assign free_o[i] = table_q[i].cnt == '0;
+  end
+  lzc #(
+    .WIDTH ( MaxUniqInpIds  ),
+    .MODE  ( 1'b0           )
+  ) i_lzc_free (
+    .in_i    ( free_o        ),
+    .cnt_o   ( free_oup_id_o ),
+    .empty_o ( full_o        )
+  );
+
+  // Determine the input ID for a given output ID.
+  assign pop_inp_id_o = table_q[pop_oup_id_i].inp_id;
+
+  // Determine if given output ID is already used and, if it is, by which input ID.
+  field_t match;
+  for (genvar i = 0; i < MaxUniqInpIds; i++) begin : gen_match
+    assign match[i] = table_q[i].cnt > 0 && table_q[i].inp_id == exists_inp_id_i;
+  end
+  logic no_match;
+  lzc #(
+      .WIDTH ( MaxUniqInpIds  ),
+      .MODE  ( 1'b0           )
+  ) i_lzc_match (
+      .in_i     ( match           ),
+      .cnt_o    ( exists_oup_id_o ),
+      .empty_o  ( no_match        )
+  );
+  assign exists_o      = ~no_match;
+  assign exists_full_o = table_q[exists_oup_id_o].cnt == MaxTxnsPerId;
+
+  // Push and pop table entries.
+  always_comb begin
+    table_d = table_q;
+    if (push_i) begin
+      table_d[push_oup_id_i].inp_id  = push_inp_id_i;
+      table_d[push_oup_id_i].cnt    += 1;
+    end
+    if (pop_i) begin
+      table_d[pop_oup_id_i].cnt -= 1;
+    end
+  end
+
+  // Registers
+  `FFARN(table_q, table_d, '0, clk_i, rst_ni)
+
+  // Assertions
+  // pragma translate_off
+  `ifndef VERILATOR
+    default disable iff (!rst_ni);
+    assume property (@(posedge clk_i) push_i |->
+        table_q[push_oup_id_i].cnt == '0 || table_q[push_oup_id_i].inp_id == push_inp_id_i)
+      else $error("Push must be to empty output ID or match existing input ID!");
+    assume property (@(posedge clk_i) push_i |-> table_q[push_oup_id_i].cnt < MaxTxnsPerId)
+      else $error("Maximum number of in-flight bursts must not be exceeded!");
+    assume property (@(posedge clk_i) pop_i |-> table_q[pop_oup_id_i].cnt > 0)
+      else $error("Pop must target output ID with non-zero counter!");
+    assume property (@(posedge clk_i) $onehot0(match))
+      else $error("Input ID in table must be unique!");
+    initial begin
+      assert (InpIdWidth > 0);
+      assert (MaxUniqInpIds > 0);
+      assert (MaxUniqInpIds <= (1 << InpIdWidth));
+      assert (MaxTxnsPerId > 0);
+      assert (IdxWidth >= 1);
+    end
+  `endif
+  // pragma translate_on
+
+endmodule
+
+
+`include "axi/typedef.svh"
+`include "axi/assign.svh"
+/// Interface variant of [`axi_id_remap`](module.axi_id_remap).
+///
+/// See the documentation of the main module for the definition of ports and parameters.
+module axi_id_remap_intf #(
+  parameter int unsigned AXI_SLV_PORT_ID_WIDTH = 32'd0,
+  parameter int unsigned AXI_SLV_PORT_MAX_UNIQ_IDS = 32'd0,
+  parameter int unsigned AXI_MAX_TXNS_PER_ID = 32'd0,
+  parameter int unsigned AXI_MST_PORT_ID_WIDTH = 32'd0,
+  parameter int unsigned AXI_ADDR_WIDTH = 32'd0,
+  parameter int unsigned AXI_DATA_WIDTH = 32'd0,
+  parameter int unsigned AXI_USER_WIDTH = 32'd0
+) (
+  input logic     clk_i,
+  input logic     rst_ni,
+  AXI_BUS.Slave   slv,
+  AXI_BUS.Master  mst
+);
+  typedef logic [AXI_SLV_PORT_ID_WIDTH-1:0] slv_id_t;
+  typedef logic [AXI_MST_PORT_ID_WIDTH-1:0] mst_id_t;
+  typedef logic [AXI_ADDR_WIDTH-1:0]        axi_addr_t;
+  typedef logic [AXI_DATA_WIDTH-1:0]        axi_data_t;
+  typedef logic [AXI_DATA_WIDTH/8-1:0]      axi_strb_t;
+  typedef logic [AXI_USER_WIDTH-1:0]        axi_user_t;
+
+  `AXI_TYPEDEF_AW_CHAN_T(slv_aw_chan_t, axi_addr_t, slv_id_t, axi_user_t)
+  `AXI_TYPEDEF_W_CHAN_T(slv_w_chan_t, axi_data_t, axi_strb_t, axi_user_t)
+  `AXI_TYPEDEF_B_CHAN_T(slv_b_chan_t, slv_id_t, axi_user_t)
+  `AXI_TYPEDEF_AR_CHAN_T(slv_ar_chan_t, axi_addr_t, slv_id_t, axi_user_t)
+  `AXI_TYPEDEF_R_CHAN_T(slv_r_chan_t, axi_data_t, slv_id_t, axi_user_t)
+  `AXI_TYPEDEF_REQ_T(slv_req_t, slv_aw_chan_t, slv_w_chan_t, slv_ar_chan_t)
+  `AXI_TYPEDEF_RESP_T(slv_resp_t, slv_b_chan_t, slv_r_chan_t)
+
+  `AXI_TYPEDEF_AW_CHAN_T(mst_aw_chan_t, axi_addr_t, mst_id_t, axi_user_t)
+  `AXI_TYPEDEF_W_CHAN_T(mst_w_chan_t, axi_data_t, axi_strb_t, axi_user_t)
+  `AXI_TYPEDEF_B_CHAN_T(mst_b_chan_t, mst_id_t, axi_user_t)
+  `AXI_TYPEDEF_AR_CHAN_T(mst_ar_chan_t, axi_addr_t, mst_id_t, axi_user_t)
+  `AXI_TYPEDEF_R_CHAN_T(mst_r_chan_t, axi_data_t, mst_id_t, axi_user_t)
+  `AXI_TYPEDEF_REQ_T(mst_req_t, mst_aw_chan_t, mst_w_chan_t, mst_ar_chan_t)
+  `AXI_TYPEDEF_RESP_T(mst_resp_t, mst_b_chan_t, mst_r_chan_t)
+
+  slv_req_t  slv_req;
+  slv_resp_t slv_resp;
+  mst_req_t  mst_req;
+  mst_resp_t mst_resp;
+
+  `AXI_ASSIGN_TO_REQ(slv_req, slv)
+  `AXI_ASSIGN_FROM_RESP(slv, slv_resp)
+  `AXI_ASSIGN_FROM_REQ(mst, mst_req)
+  `AXI_ASSIGN_TO_RESP(mst_resp, mst)
+
+  axi_id_remap #(
+    .AxiSlvPortIdWidth    ( AXI_SLV_PORT_ID_WIDTH     ),
+    .AxiSlvPortMaxUniqIds ( AXI_SLV_PORT_MAX_UNIQ_IDS ),
+    .AxiMaxTxnsPerId      ( AXI_MAX_TXNS_PER_ID       ),
+    .AxiMstPortIdWidth    ( AXI_MST_PORT_ID_WIDTH     ),
+    .slv_req_t            ( slv_req_t                 ),
+    .slv_resp_t           ( slv_resp_t                ),
+    .mst_req_t            ( mst_req_t                 ),
+    .mst_resp_t           ( mst_resp_t                )
+  ) i_axi_id_remap (
+    .clk_i,
+    .rst_ni,
+    .slv_req_i  ( slv_req  ),
+    .slv_resp_o ( slv_resp ),
+    .mst_req_o  ( mst_req  ),
+    .mst_resp_i ( mst_resp )
+  );
+  // pragma translate_off
+  `ifndef VERILATOR
+    initial begin
+      assert (slv.AXI_ID_WIDTH   == AXI_SLV_PORT_ID_WIDTH);
+      assert (slv.AXI_ADDR_WIDTH == AXI_ADDR_WIDTH);
+      assert (slv.AXI_DATA_WIDTH == AXI_DATA_WIDTH);
+      assert (slv.AXI_USER_WIDTH == AXI_USER_WIDTH);
+      assert (mst.AXI_ID_WIDTH   == AXI_MST_PORT_ID_WIDTH);
+      assert (mst.AXI_ADDR_WIDTH == AXI_ADDR_WIDTH);
+      assert (mst.AXI_DATA_WIDTH == AXI_DATA_WIDTH);
+      assert (mst.AXI_USER_WIDTH == AXI_USER_WIDTH);
+    end
+  `endif
+  // pragma translate_on
+endmodule