// Copyright (c) 2020 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.
//
// Authors:
// - Matheus Cavalcante <matheusd@iis.ee.ethz.ch>

// `axi_dw_upsizer_monitor` implements an AXI bus monitor that is tuned
// for the AXI Data Width Converters. It snoops on the slave and master port
// of the DWCs and populates FIFOs and ID queues to validate that no
// AXI beats get lost.

package tb_axi_dw_pkg       ;
  import axi_pkg::len_t     ;
  import axi_pkg::burst_t   ;
  import axi_pkg::size_t    ;
  import axi_pkg::cache_t   ;
  import axi_pkg::modifiable;

  /****************
   *  BASE CLASS  *
   ****************/

  class axi_dw_monitor #(
      parameter int unsigned AxiAddrWidth       ,
      parameter int unsigned AxiSlvPortDataWidth,
      parameter int unsigned AxiMstPortDataWidth,
      parameter int unsigned AxiIdWidth         ,
      parameter int unsigned AxiUserWidth       ,
      // Stimuli application and test time
      parameter time TimeTest
    );

    localparam AxiSlvPortStrbWidth = AxiSlvPortDataWidth / 8;
    localparam AxiMstPortStrbWidth = AxiMstPortDataWidth / 8;

    localparam AxiSlvPortMaxSize = $clog2(AxiSlvPortStrbWidth);
    localparam AxiMstPortMaxSize = $clog2(AxiMstPortStrbWidth);

    typedef logic [AxiIdWidth-1:0] axi_id_t    ;
    typedef logic [AxiAddrWidth-1:0] axi_addr_t;

    typedef logic [AxiSlvPortDataWidth-1:0] slv_port_data_t;
    typedef logic [AxiSlvPortStrbWidth-1:0] slv_port_strb_t;
    typedef logic [AxiMstPortDataWidth-1:0] mst_port_data_t;
    typedef logic [AxiMstPortStrbWidth-1:0] mst_port_strb_t;

    typedef struct packed {
      axi_id_t axi_id;
      logic axi_last ;
    } exp_b_t;
    typedef struct packed {
      axi_id_t axi_id         ;
      slv_port_data_t axi_data;
      slv_port_strb_t axi_strb;
      logic axi_last          ;
    } exp_slv_rw_t;
    typedef struct packed {
      axi_id_t axi_id         ;
      mst_port_data_t axi_data;
      mst_port_strb_t axi_strb;
      logic axi_last          ;
    } exp_mst_rw_t;
    typedef struct packed {
      axi_id_t axi_id    ;
      axi_addr_t axi_addr;
      len_t axi_len      ;
      burst_t axi_burst  ;
      size_t axi_size    ;
      cache_t axi_cache  ;
    } exp_ax_t;

    typedef rand_id_queue_pkg::rand_id_queue #(
      .data_t  (exp_ax_t  ),
      .ID_WIDTH(AxiIdWidth)
    ) ax_queue_t;
    typedef rand_id_queue_pkg::rand_id_queue #(
      .data_t  (exp_b_t   ),
      .ID_WIDTH(AxiIdWidth)
    ) b_queue_t;
    typedef rand_id_queue_pkg::rand_id_queue #(
      .data_t  (exp_slv_rw_t ),
      .ID_WIDTH(AxiIdWidth   )
    ) slv_r_queue_t;
    typedef rand_id_queue_pkg::rand_id_queue #(
      .data_t  (exp_mst_rw_t ),
      .ID_WIDTH(AxiIdWidth   )
    ) mst_r_queue_t;

    /**********************
     *  Helper functions  *
     **********************/

    // Returns a byte mask corresponding to the size of the AXI transaction
    function automatic axi_addr_t size_mask(axi_pkg::size_t size);
      return (axi_addr_t'(1) << size) - 1;
    endfunction

    /**
     * Returns the conversion rate between tgt_size and src_size.
     * @return ceil(num_bytes(tgt_size)/num_bytes(src_size))
     */
    function automatic int unsigned conv_ratio(axi_pkg::size_t tgt_size, axi_pkg::size_t src_size)         ;
      return (axi_pkg::num_bytes(tgt_size) + axi_pkg::num_bytes(src_size) - 1)/axi_pkg::num_bytes(src_size);
    endfunction: conv_ratio

    /************************
     *  Virtual Interfaces  *
     ************************/

    virtual AXI_BUS_DV #(
      .AXI_ADDR_WIDTH(AxiAddrWidth       ),
      .AXI_DATA_WIDTH(AxiSlvPortDataWidth),
      .AXI_ID_WIDTH  (AxiIdWidth         ),
      .AXI_USER_WIDTH(AxiUserWidth       )
    ) slv_port_axi;
    virtual AXI_BUS_DV #(
      .AXI_ADDR_WIDTH(AxiAddrWidth       ),
      .AXI_DATA_WIDTH(AxiMstPortDataWidth),
      .AXI_ID_WIDTH  (AxiIdWidth         ),
      .AXI_USER_WIDTH(AxiUserWidth       )
    ) mst_port_axi;

    /*****************
     *  Bookkeeping  *
     *****************/

    longint unsigned tests_expected;
    longint unsigned tests_conducted;
    longint unsigned tests_failed;
    semaphore        cnt_sem;

    // Queues and FIFOs to hold the expected AXIDs

    // Write transactions
    ax_queue_t   exp_mst_port_aw_queue;
    exp_ax_t     act_mst_port_aw_queue [$];
    exp_ax_t     act_slv_port_aw_queue [$];
    exp_mst_rw_t exp_mst_port_w_queue [$];
    exp_mst_rw_t act_mst_port_w_queue [$];
    exp_slv_rw_t act_slv_port_w_queue [$];
    b_queue_t    exp_slv_port_b_queue;

    // Read transactions
    ax_queue_t    exp_mst_port_ar_queue;
    ax_queue_t    act_mst_port_ar_queue;
    ax_queue_t    act_slv_port_ar_queue;
    exp_slv_rw_t  act_slv_port_r_queue [$];
    slv_r_queue_t exp_slv_port_r_queue;

    /*****************
     *  Constructor  *
     *****************/

    function new (
        virtual AXI_BUS_DV #(
          .AXI_ADDR_WIDTH(AxiAddrWidth       ),
          .AXI_DATA_WIDTH(AxiSlvPortDataWidth),
          .AXI_ID_WIDTH  (AxiIdWidth         ),
          .AXI_USER_WIDTH(AxiUserWidth       )
        ) slv_port_vif,
        virtual AXI_BUS_DV #(
          .AXI_ADDR_WIDTH(AxiAddrWidth       ),
          .AXI_DATA_WIDTH(AxiMstPortDataWidth),
          .AXI_ID_WIDTH  (AxiIdWidth         ),
          .AXI_USER_WIDTH(AxiUserWidth       )
        ) mst_port_vif
      );
      begin
        this.slv_port_axi          = slv_port_vif;
        this.mst_port_axi          = mst_port_vif;
        this.tests_expected        = 0           ;
        this.tests_conducted       = 0           ;
        this.tests_failed          = 0           ;
        this.exp_slv_port_b_queue  = new         ;
        this.exp_slv_port_r_queue  = new         ;
        this.exp_mst_port_aw_queue = new         ;
        this.exp_mst_port_ar_queue = new         ;
        this.act_mst_port_ar_queue = new         ;
        this.act_slv_port_ar_queue = new         ;
        this.cnt_sem               = new(1)      ;
      end
    endfunction

    task cycle_start;
      #TimeTest;
    endtask: cycle_start

    task cycle_end;
      @(posedge slv_port_axi.clk_i);
    endtask: cycle_end

    /**************
     *  Monitors  *
     **************/

    /*
     * You need to override this task. Use it to push the expected AW requests on
     * the slave side, and the B and R responses expected on the master side.
     */
    virtual task automatic mon_slv_port_aw ()    ;
      $error("This task needs to be overridden.");
    endtask : mon_slv_port_aw

    /*
     * You need to override this task. Use it to push the expected W requests on
     * the slave side.
     */
    virtual task automatic mon_slv_port_w ()     ;
      $error("This task needs to be overridden.");
    endtask : mon_slv_port_w

    /*
     * You need to override this task. Use it to push the expected R responses on
     * the master side.
     */
    virtual task automatic mon_mst_port_r ()     ;
      $error("This task needs to be overridden.");
    endtask : mon_mst_port_r

    /*
     * You need to override this task. Use it to push the expected AR requests on
     * the slave side, and the R responses expected on the master side.
     */
    virtual task automatic mon_slv_port_ar ()    ;
      $error("This task needs to be overridden.");
    endtask : mon_slv_port_ar

    /*
     * This tasks stores the beats seen by the AR, AW and W channels
     * into the respective queues.
     */
    virtual task automatic store_channels ();
      if (slv_port_axi.ar_valid && slv_port_axi.ar_ready)
        act_slv_port_ar_queue.push(slv_port_axi.ar_id,
          '{
            axi_id   : slv_port_axi.ar_id   ,
            axi_burst: slv_port_axi.ar_burst,
            axi_size : slv_port_axi.ar_size ,
            axi_addr : slv_port_axi.ar_addr ,
            axi_len  : slv_port_axi.ar_len  ,
            axi_cache: slv_port_axi.ar_cache
          });

      if (slv_port_axi.aw_valid && slv_port_axi.aw_ready) begin
        act_slv_port_aw_queue.push_back('{
            axi_id   : slv_port_axi.aw_id   ,
            axi_burst: slv_port_axi.aw_burst,
            axi_size : slv_port_axi.aw_size ,
            axi_addr : slv_port_axi.aw_addr ,
            axi_len  : slv_port_axi.aw_len  ,
            axi_cache: slv_port_axi.aw_cache
          });

        // This request generates an R response.
        // Push this to the AR queue.
        if (slv_port_axi.aw_atop[axi_pkg::ATOP_R_RESP])
          act_slv_port_ar_queue.push(slv_port_axi.aw_id,
            '{
              axi_id   : slv_port_axi.aw_id   ,
              axi_burst: slv_port_axi.aw_burst,
              axi_size : slv_port_axi.aw_size ,
              axi_addr : slv_port_axi.aw_addr ,
              axi_len  : slv_port_axi.aw_len  ,
              axi_cache: slv_port_axi.aw_cache
            });
      end

      if (slv_port_axi.w_valid && slv_port_axi.w_ready)
        this.act_slv_port_w_queue.push_back('{
            axi_id  : {AxiIdWidth{1'b?}} ,
            axi_data: slv_port_axi.w_data,
            axi_strb: slv_port_axi.w_strb,
            axi_last: slv_port_axi.w_last
          });

      if (slv_port_axi.r_valid && slv_port_axi.r_ready)
        this.act_slv_port_r_queue.push_back('{
            axi_id  : slv_port_axi.r_id          ,
            axi_data: slv_port_axi.r_data        ,
            axi_strb: {AxiSlvPortStrbWidth{1'b?}},
            axi_last: slv_port_axi.r_last
          });

      if (mst_port_axi.ar_valid && mst_port_axi.ar_ready)
        act_mst_port_ar_queue.push(mst_port_axi.ar_id,
          '{
            axi_id   : mst_port_axi.ar_id   ,
            axi_burst: mst_port_axi.ar_burst,
            axi_size : mst_port_axi.ar_size ,
            axi_addr : mst_port_axi.ar_addr ,
            axi_len  : mst_port_axi.ar_len  ,
            axi_cache: mst_port_axi.ar_cache
          });

      if (mst_port_axi.aw_valid && mst_port_axi.aw_ready) begin
        act_mst_port_aw_queue.push_back('{
            axi_id   : mst_port_axi.aw_id   ,
            axi_burst: mst_port_axi.aw_burst,
            axi_size : mst_port_axi.aw_size ,
            axi_addr : mst_port_axi.aw_addr ,
            axi_len  : mst_port_axi.aw_len  ,
            axi_cache: mst_port_axi.aw_cache
          });

        // This request generates an R response.
        // Push this to the AR queue.
        if (mst_port_axi.aw_atop[axi_pkg::ATOP_R_RESP])
          act_mst_port_ar_queue.push(mst_port_axi.aw_id,
            '{
              axi_id   : mst_port_axi.aw_id   ,
              axi_burst: mst_port_axi.aw_burst,
              axi_size : mst_port_axi.aw_size ,
              axi_addr : mst_port_axi.aw_addr ,
              axi_len  : mst_port_axi.aw_len  ,
              axi_cache: mst_port_axi.aw_cache
            });
      end

      if (mst_port_axi.w_valid && mst_port_axi.w_ready)
        this.act_mst_port_w_queue.push_back('{
            axi_id  : {AxiIdWidth{1'b?}} ,
            axi_data: mst_port_axi.w_data,
            axi_strb: mst_port_axi.w_strb,
            axi_last: mst_port_axi.w_last
          });
    endtask

    /*
     * This task monitors the master port of the DW converter. Every time it gets an AW transaction,
     * it gets checked for its contents against the expected beat on the `exp_aw_queue`.
     */
    task automatic mon_mst_port_aw ();
      exp_ax_t exp_aw;
      if (mst_port_axi.aw_valid && mst_port_axi.aw_ready) begin
        // Test if the AW beat was expected
        exp_aw = this.exp_mst_port_aw_queue.pop_id(mst_port_axi.aw_id);
        if (exp_aw.axi_id != mst_port_axi.aw_id) begin
          incr_failed_tests(1)                                            ;
          $warning("Slave: Unexpected AW with ID: %b", mst_port_axi.aw_id);
        end
        if (exp_aw.axi_addr != mst_port_axi.aw_addr) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected AW with ID: %b and ADDR: %h, exp: %h",
            mst_port_axi.aw_id, mst_port_axi.aw_addr, exp_aw.axi_addr);
        end
        if (exp_aw.axi_len != mst_port_axi.aw_len) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected AW with ID: %b and LEN: %h, exp: %h",
            mst_port_axi.aw_id, mst_port_axi.aw_len, exp_aw.axi_len);
        end
        if (exp_aw.axi_burst != mst_port_axi.aw_burst) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected AW with ID: %b and BURST: %h, exp: %h",
            mst_port_axi.aw_id, mst_port_axi.aw_burst, exp_aw.axi_burst);
        end
        if (exp_aw.axi_size != mst_port_axi.aw_size) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected AW with ID: %b and SIZE: %h, exp: %h",
            mst_port_axi.aw_id, mst_port_axi.aw_size, exp_aw.axi_size);
        end
        if (exp_aw.axi_cache != mst_port_axi.aw_cache) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected AW with ID: %b and CACHE: %b, exp: %b",
            mst_port_axi.aw_id, mst_port_axi.aw_cache, exp_aw.axi_cache);
        end
        incr_conducted_tests(6);
      end
    endtask : mon_mst_port_aw

    /*
     * This task compares the expected and actual W beats on the master port.
     */
    task automatic mon_mst_port_w ();
      exp_mst_rw_t exp_w, act_w;
      while (this.exp_mst_port_w_queue.size() != 0 && this.act_mst_port_w_queue.size() != 0) begin
        exp_w = this.exp_mst_port_w_queue.pop_front();
        act_w = this.act_mst_port_w_queue.pop_front();
        // Do the checks
        if (exp_w.axi_data != act_w.axi_data) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected W with DATA: %h, exp: %h",
            act_w.axi_data, exp_w.axi_data);
        end
        if (exp_w.axi_strb != act_w.axi_strb) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected W with STRB: %h, exp: %h",
            act_w.axi_strb, exp_w.axi_strb);
        end
        if (exp_w.axi_last != act_w.axi_last) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected W with LAST: %b, exp: %b",
            act_w.axi_last, exp_w.axi_last);
        end
        incr_conducted_tests(3);
      end
    endtask : mon_mst_port_w

    /*
     * This task checks if a B response is allowed on a slave port of the DW converter.
     */
    task automatic mon_slv_port_b ();
      exp_b_t  exp_b;
      axi_id_t axi_b_id;
      if (slv_port_axi.b_valid && slv_port_axi.b_ready) begin
        incr_conducted_tests(1);
        axi_b_id = slv_port_axi.b_id;
        $display("%0tns > Master: Got last B with ID: %b",
          $time, axi_b_id);
        if (this.exp_slv_port_b_queue.empty()) begin
          incr_failed_tests(1)                                                 ;
          $warning("Master: unexpected B beat with ID: %b detected!", axi_b_id);
        end else begin
          exp_b = this.exp_slv_port_b_queue.pop_id(axi_b_id);
          if (axi_b_id != exp_b.axi_id) begin
            incr_failed_tests(1)                                      ;
            $warning("Master: got unexpected B with ID: %b", axi_b_id);
          end
        end
      end
    endtask : mon_slv_port_b

    /*
     * This task monitors a the master port of the DW converter and checks
     * if the AR beats were all expected.
     */
    task automatic mon_mst_port_ar ();
      exp_ax_t exp_ar;
      if (mst_port_axi.ar_valid && mst_port_axi.ar_ready) begin
        // Test if the AR beat was expected
        exp_ar = this.exp_mst_port_ar_queue.pop_id(mst_port_axi.ar_id);
        if (exp_ar.axi_id != mst_port_axi.ar_id) begin
          incr_failed_tests(1)                                            ;
          $warning("Slave: Unexpected AR with ID: %b", mst_port_axi.ar_id);
        end
        if (exp_ar.axi_addr != mst_port_axi.ar_addr) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected AR with ID: %b and ADDR: %h, exp: %h",
            mst_port_axi.ar_id, mst_port_axi.ar_addr, exp_ar.axi_addr);
        end
        if (exp_ar.axi_len != mst_port_axi.ar_len) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected AR with ID: %b and LEN: %h, exp: %h",
            mst_port_axi.ar_id, mst_port_axi.ar_len, exp_ar.axi_len);
        end
        if (exp_ar.axi_burst != mst_port_axi.ar_burst) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected AR with ID: %b and BURST: %h, exp: %h",
            mst_port_axi.ar_id, mst_port_axi.ar_burst, exp_ar.axi_burst);
        end
        if (exp_ar.axi_size != mst_port_axi.ar_size) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected AR with ID: %b and SIZE: %h, exp: %h",
            mst_port_axi.ar_id, mst_port_axi.ar_size, exp_ar.axi_size);
        end
        if (exp_ar.axi_cache != mst_port_axi.ar_cache) begin
          incr_failed_tests(1);
          $warning("Slave: Unexpected AR with ID: %b and CACHE: %b, exp: %b",
            mst_port_axi.ar_id, mst_port_axi.ar_cache, exp_ar.axi_cache);
        end
        incr_conducted_tests(6);
      end
    endtask : mon_mst_port_ar

    /*
     * This task does the R channel monitoring on a slave port. It compares the last flags,
     * which are determined by the sequence of previously sent AR vectors.
     */
    task automatic mon_slv_port_r ();
      exp_slv_rw_t exp_r;
      if (act_slv_port_r_queue.size() != 0) begin
        exp_slv_rw_t act_r = act_slv_port_r_queue[0] ;
        if (exp_slv_port_r_queue.queues[act_r.axi_id].size() != 0) begin
          exp_r = exp_slv_port_r_queue.pop_id(act_r.axi_id);
          void'(act_slv_port_r_queue.pop_front());

          // Do the checks
          if (exp_r.axi_id != act_r.axi_id) begin
            incr_failed_tests(1);
            $warning("Slave: Unexpected R with ID: %b",
              act_r.axi_id);
          end
          if (exp_r.axi_last != act_r.axi_last) begin
            incr_failed_tests(1);
            $warning("Slave: Unexpected R with ID: %b and LAST: %b, exp: %b",
              act_r.axi_id, act_r.axi_last, exp_r.axi_last);
          end
          if (exp_r.axi_data != act_r.axi_data) begin
            incr_failed_tests(1);
            $warning("Slave: Unexpected R with ID: %b and DATA: %h, exp: %h",
              act_r.axi_id, act_r.axi_data, exp_r.axi_data);
          end
          incr_conducted_tests(3);
        end
      end
    endtask : mon_slv_port_r

    // Some tasks to manage bookkeeping of the tests conducted.
    task incr_expected_tests(input int unsigned times);
      cnt_sem.get()               ;
      this.tests_expected += times;
      cnt_sem.put()               ;
    endtask : incr_expected_tests

    task incr_conducted_tests(input int unsigned times);
      cnt_sem.get()                ;
      this.tests_conducted += times;
      cnt_sem.put()                ;
    endtask : incr_conducted_tests

    task incr_failed_tests(input int unsigned times);
      cnt_sem.get()             ;
      this.tests_failed += times;
      cnt_sem.put()             ;
    endtask : incr_failed_tests

    /*
     * This task invokes the various monitoring tasks. First, all processes that only
     * push something in the FIFOs are invoked. After they are finished, the processes
     * that pop something from them are invoked.
     */
    task run();
      forever begin
        // At every cycle, spawn some monitoring processes.
        cycle_start();

        // Execute all processes that push something into the queues
        PushMon: fork
          proc_mst_aw       : mon_slv_port_aw();
          proc_mst_ar       : mon_slv_port_ar();
          proc_mst_w        : mon_slv_port_w() ;
          proc_slv_r        : mon_mst_port_r() ;
          proc_store_channel: store_channels() ;
        join: PushMon

        // These only pop something from the queues
        PopMon: fork
          proc_slv_aw: mon_mst_port_aw();
          proc_mst_b : mon_slv_port_b() ;
          proc_slv_ar: mon_mst_port_ar();
          proc_mst_r : mon_slv_port_r() ;
        join : PopMon

        // Check the slave W FIFOs last
        proc_check_slv_w: mon_mst_port_w();

        cycle_end();
      end
    endtask : run

    task print_result()                                    ;
      $info("Simulation has ended!")                       ;
      $display("Tests Expected:  %d", this.tests_expected) ;
      $display("Tests Conducted: %d", this.tests_conducted);
      $display("Tests Failed:    %d", this.tests_failed)   ;
      if (tests_failed > 0) begin
        $error("Simulation encountered unexpected transactions!");
      end
    endtask : print_result

  endclass : axi_dw_monitor

  /*************
   *  UPSIZER  *
   *************/

  class axi_dw_upsizer_monitor #(
      parameter int unsigned AxiAddrWidth       ,
      parameter int unsigned AxiSlvPortDataWidth,
      parameter int unsigned AxiMstPortDataWidth,
      parameter int unsigned AxiIdWidth         ,
      parameter int unsigned AxiUserWidth       ,
      // Stimuli application and test time
      parameter time TimeTest
    ) extends axi_dw_monitor #(
      .AxiAddrWidth       (AxiAddrWidth       ),
      .AxiSlvPortDataWidth(AxiSlvPortDataWidth),
      .AxiMstPortDataWidth(AxiMstPortDataWidth),
      .AxiIdWidth         (AxiIdWidth         ),
      .AxiUserWidth       (AxiUserWidth       ),
      .TimeTest           (TimeTest           )
    );

    local static shortint unsigned slv_port_r_cnt[axi_id_t];
    local static shortint unsigned mst_port_r_cnt[axi_id_t];
    local static shortint unsigned slv_port_w_cnt;
    local static shortint unsigned mst_port_w_cnt;
    local static exp_mst_rw_t      mst_port_w;
    local static shortint unsigned mst_port_w_pnt;

    /*****************
     *  Constructor  *
     *****************/

    function new (
        virtual AXI_BUS_DV #(
          .AXI_ADDR_WIDTH(AxiAddrWidth       ),
          .AXI_DATA_WIDTH(AxiSlvPortDataWidth),
          .AXI_ID_WIDTH  (AxiIdWidth         ),
          .AXI_USER_WIDTH(AxiUserWidth       )
        ) slv_port_vif,
        virtual AXI_BUS_DV #(
          .AXI_ADDR_WIDTH(AxiAddrWidth       ),
          .AXI_DATA_WIDTH(AxiMstPortDataWidth),
          .AXI_ID_WIDTH  (AxiIdWidth         ),
          .AXI_USER_WIDTH(AxiUserWidth       )
        ) mst_port_vif
      );
      begin
        super.new(slv_port_vif, mst_port_vif);
        slv_port_w_cnt = '0;
        mst_port_w_cnt = '0;
        mst_port_w_pnt = '1;
        mst_port_w     = '0;
        for (int unsigned id = 0; id < 2**AxiIdWidth; id++) begin
          slv_port_r_cnt[id] = '0;
          mst_port_r_cnt[id] = '0;
        end
      end
    endfunction

    /**************
     *  Monitors  *
     **************/

    task automatic mon_slv_port_aw ();
      exp_ax_t exp_aw;

      if (slv_port_axi.aw_valid && slv_port_axi.aw_ready) begin
        // Non-modifiable transaction
        if (!axi_pkg::modifiable(slv_port_axi.aw_cache)) begin
          // We expect that the transaction will not be modified
          exp_aw = '{
            axi_id   : slv_port_axi.aw_id   ,
            axi_addr : slv_port_axi.aw_addr ,
            axi_len  : slv_port_axi.aw_len  ,
            axi_burst: slv_port_axi.aw_burst,
            axi_size : slv_port_axi.aw_size ,
            axi_cache: slv_port_axi.aw_cache
          } ;
        end
        // Modifiable transaction
        else begin
          case (slv_port_axi.aw_burst)
            // Passthrough upsize
            axi_pkg::BURST_FIXED: begin
              exp_aw = '{
                axi_id   : slv_port_axi.aw_id   ,
                axi_addr : slv_port_axi.aw_addr ,
                axi_len  : slv_port_axi.aw_len  ,
                axi_burst: slv_port_axi.aw_burst,
                axi_size : slv_port_axi.aw_size ,
                axi_cache: slv_port_axi.aw_cache
              };
            end
            // INCR upsize
            axi_pkg::BURST_INCR: begin
              automatic axi_addr_t aligned_start = axi_pkg::aligned_addr(slv_port_axi.aw_addr, AxiMstPortMaxSize)                                                                                                        ;
              automatic axi_addr_t aligned_end   = axi_pkg::aligned_addr(axi_pkg::aligned_addr(slv_port_axi.aw_addr, slv_port_axi.aw_size) + (unsigned'(slv_port_axi.aw_len) << slv_port_axi.aw_size), AxiMstPortMaxSize);

              exp_aw = '{
                axi_id   : slv_port_axi.aw_id                                 ,
                axi_addr : slv_port_axi.aw_addr                               ,
                axi_len  : (aligned_end - aligned_start) >> AxiMstPortMaxSize ,
                axi_burst: slv_port_axi.aw_burst                              ,
                axi_size : slv_port_axi.aw_len == 0 ? slv_port_axi.aw_size : AxiMstPortMaxSize,
                axi_cache: slv_port_axi.aw_cache
              };
            end
            // WRAP upsize
            axi_pkg::BURST_WRAP: begin
              exp_aw = '0;
              $warning("WRAP bursts are not supported.");
            end
          endcase
          this.exp_mst_port_aw_queue.push(slv_port_axi.aw_id, exp_aw);
          incr_expected_tests(6)                                     ;
          $display("%0tns > Master: AW with ID: %b",
            $time, slv_port_axi.aw_id);
        end

        // Populate the expected B responses
        this.exp_slv_port_b_queue.push(slv_port_axi.aw_id, '{
            axi_id  : slv_port_axi.aw_id,
            axi_last: 1'b1
          })                                   ;
        incr_expected_tests(1)                 ;
        $display("        Expect B response.") ;
      end
    endtask : mon_slv_port_aw

    task automatic mon_slv_port_w ();
      if (act_slv_port_w_queue.size() != 0) begin
        exp_slv_rw_t act_slv_w = act_slv_port_w_queue[0];

        if (act_mst_port_aw_queue.size() != 0 && act_slv_port_aw_queue.size() != 0) begin
          // Retrieve the AW requests related to this W beat
          exp_ax_t act_mst_aw = act_mst_port_aw_queue[0];
          exp_ax_t act_slv_aw = act_slv_port_aw_queue[0];

          // Calculate the offsets
          shortint unsigned mst_port_lower_byte =
          axi_pkg::beat_lower_byte(act_mst_aw.axi_addr, act_mst_aw.axi_size, act_mst_aw.axi_len, act_mst_aw.axi_burst, AxiMstPortStrbWidth, mst_port_w_cnt);
          shortint unsigned mst_port_upper_byte =
          axi_pkg::beat_upper_byte(act_mst_aw.axi_addr, act_mst_aw.axi_size, act_mst_aw.axi_len, act_mst_aw.axi_burst, AxiMstPortStrbWidth, mst_port_w_cnt);
          shortint unsigned slv_port_lower_byte =
          axi_pkg::beat_lower_byte(act_slv_aw.axi_addr, act_slv_aw.axi_size, act_slv_aw.axi_len, act_slv_aw.axi_burst, AxiSlvPortStrbWidth, slv_port_w_cnt);
          shortint unsigned slv_port_upper_byte =
          axi_pkg::beat_upper_byte(act_slv_aw.axi_addr, act_slv_aw.axi_size, act_slv_aw.axi_len, act_slv_aw.axi_burst, AxiSlvPortStrbWidth, slv_port_w_cnt);

          shortint unsigned bytes_copied = 0;
          // Pointer inside the outcoming word
          if (mst_port_w_pnt == '1)
            mst_port_w_pnt = mst_port_lower_byte;

          mst_port_w.axi_last = mst_port_w_cnt == act_mst_aw.axi_len;
          for (shortint unsigned b = slv_port_lower_byte; b <= slv_port_upper_byte; b++) begin
            if (b + mst_port_w_pnt - slv_port_lower_byte == AxiMstPortStrbWidth)
              break;
            mst_port_w.axi_data[8*(b + mst_port_w_pnt - slv_port_lower_byte) +: 8] = act_slv_w.axi_data[8*b +: 8];
            mst_port_w.axi_strb[b + mst_port_w_pnt - slv_port_lower_byte]          = act_slv_w.axi_strb[b]       ;
            bytes_copied++;
          end

          // Increment the len counters
          slv_port_w_cnt++              ;
          mst_port_w_pnt += bytes_copied;

          if (act_mst_aw.axi_burst == axi_pkg::BURST_FIXED // No upsizing
              || mst_port_w_pnt == AxiMstPortStrbWidth     // Filled up an outcoming W beat
              || act_slv_w.axi_last                        // Last beat of a W burst
            ) begin
            // Don't care for the bits outside these accessed by this request
            for (int unsigned b = 0; b < AxiMstPortStrbWidth; b++)
              if (!(mst_port_lower_byte <= b && b <= mst_port_upper_byte))
                mst_port_w.axi_data[8*b +: 8] = {8{1'b?}};

            this.exp_mst_port_w_queue.push_back(mst_port_w);
            incr_expected_tests(3)                         ;

            // Increment the len counter
            mst_port_w_cnt++;

            // Reset W beat
            mst_port_w     = '0;
            mst_port_w_pnt = '1;
          end

          // Pop the AW request from the queues
          if (slv_port_w_cnt == act_slv_aw.axi_len + 1) begin
            void'(act_slv_port_aw_queue.pop_front());
            slv_port_w_cnt = 0;
          end
          if (mst_port_w_cnt == act_mst_aw.axi_len + 1) begin
            void'(act_mst_port_aw_queue.pop_front());
            mst_port_w_cnt = 0;
          end

          // Pop the W request
          void'(act_slv_port_w_queue.pop_front());
        end
      end
    endtask : mon_slv_port_w

    task automatic mon_slv_port_ar ();
      exp_ax_t exp_slv_ar;
      exp_b_t  exp_mst_r;

      if (slv_port_axi.ar_valid && slv_port_axi.ar_ready) begin
        // Non-modifiable transaction
        if (!axi_pkg::modifiable(slv_port_axi.ar_cache)) begin
          // We expect that the transaction will not be modified
          exp_slv_ar = '{
            axi_id   : slv_port_axi.ar_id   ,
            axi_addr : slv_port_axi.ar_addr ,
            axi_len  : slv_port_axi.ar_len  ,
            axi_burst: slv_port_axi.ar_burst,
            axi_size : slv_port_axi.ar_size ,
            axi_cache: slv_port_axi.ar_cache
          };
        end
        // Modifiable transaction
        else begin
          case (slv_port_axi.ar_burst)
            // Passthrough upsize
            axi_pkg::BURST_FIXED: begin
              exp_slv_ar = '{
                axi_id   : slv_port_axi.ar_id   ,
                axi_addr : slv_port_axi.ar_addr ,
                axi_len  : slv_port_axi.ar_len  ,
                axi_burst: slv_port_axi.ar_burst,
                axi_size : slv_port_axi.ar_size ,
                axi_cache: slv_port_axi.ar_cache
              };
            end
            // INCR upsize
            axi_pkg::BURST_INCR: begin
              automatic axi_addr_t aligned_start = axi_pkg::aligned_addr(slv_port_axi.ar_addr, AxiMstPortMaxSize)                                                                                                        ;
              automatic axi_addr_t aligned_end   = axi_pkg::aligned_addr(axi_pkg::aligned_addr(slv_port_axi.ar_addr, slv_port_axi.ar_size) + (unsigned'(slv_port_axi.ar_len) << slv_port_axi.ar_size), AxiMstPortMaxSize);

              exp_slv_ar = '{
                axi_id   : slv_port_axi.ar_id                                 ,
                axi_addr : slv_port_axi.ar_addr                               ,
                axi_len  : (aligned_end - aligned_start) >> AxiMstPortMaxSize ,
                axi_burst: slv_port_axi.ar_burst                              ,
                axi_size : slv_port_axi.ar_len == 0 ? slv_port_axi.ar_size : AxiMstPortMaxSize,
                axi_cache: slv_port_axi.ar_cache
              };
            end
            // WRAP upsize
            axi_pkg::BURST_WRAP: begin
              exp_slv_ar = '0;
              $warning("WRAP bursts are not supported.");
            end
          endcase
          this.exp_mst_port_ar_queue.push(slv_port_axi.ar_id, exp_slv_ar);
          incr_expected_tests(6)                                         ;
          $display("%0tns > Master: AR with ID: %b",
            $time, slv_port_axi.ar_id);
        end
      end
    endtask : mon_slv_port_ar

    task automatic mon_mst_port_r ();
      if (mst_port_axi.r_valid && mst_port_axi.r_ready) begin
        // Retrieve the AR requests related to this R beat
        exp_ax_t act_mst_ar = act_mst_port_ar_queue.get(mst_port_axi.r_id);
        exp_ax_t act_slv_ar = act_slv_port_ar_queue.get(mst_port_axi.r_id);
        axi_id_t id         = mst_port_axi.r_id                           ;

        // Calculate the offsets inside the incoming word
        shortint unsigned mst_port_lower_byte =
        axi_pkg::beat_lower_byte(act_mst_ar.axi_addr, act_mst_ar.axi_size, act_mst_ar.axi_len, act_mst_ar.axi_burst, AxiMstPortStrbWidth, mst_port_r_cnt[id]);
        shortint unsigned mst_port_upper_byte =
        axi_pkg::beat_upper_byte(act_mst_ar.axi_addr, act_mst_ar.axi_size, act_mst_ar.axi_len, act_mst_ar.axi_burst, AxiMstPortStrbWidth, mst_port_r_cnt[id]);
        // Pointer inside the incoming word
        shortint unsigned mst_port_data_pointer = mst_port_lower_byte;

        // Conversion ratio. How many R beats are generated from this incoming R beat.
        int unsigned conversion_ratio = axi_pkg::modifiable(act_mst_ar.axi_cache) ? conv_ratio(act_mst_ar.axi_size, act_slv_ar.axi_size) : 1;

        // Several R beats generated from this incoming R beat
        for (int unsigned beat = 0; beat < conversion_ratio; beat++) begin
          exp_slv_rw_t act_slv_r = '0;

          // Calculate the offsets inside the outcoming word
          shortint unsigned slv_port_lower_byte =
          axi_pkg::beat_lower_byte(act_slv_ar.axi_addr, act_slv_ar.axi_size, act_slv_ar.axi_len, act_slv_ar.axi_burst, AxiSlvPortStrbWidth, slv_port_r_cnt[id]);
          shortint unsigned slv_port_upper_byte =
          axi_pkg::beat_upper_byte(act_slv_ar.axi_addr, act_slv_ar.axi_size, act_slv_ar.axi_len, act_slv_ar.axi_burst, AxiSlvPortStrbWidth, slv_port_r_cnt[id]);

          shortint unsigned bytes_copied = 0;

          act_slv_r.axi_id   = mst_port_axi.r_id                       ;
          act_slv_r.axi_last = slv_port_r_cnt[id] == act_slv_ar.axi_len;
          act_slv_r.axi_data = {AxiSlvPortDataWidth{1'b?}}             ;
          act_slv_r.axi_strb = {AxiSlvPortStrbWidth{1'b?}}             ;
          for (shortint unsigned b = mst_port_data_pointer; b <= mst_port_upper_byte; b++) begin
            act_slv_r.axi_data[8*(b + slv_port_lower_byte - mst_port_data_pointer) +: 8] = mst_port_axi.r_data[8*b +: 8];
            bytes_copied++;
            if (b + slv_port_lower_byte - mst_port_data_pointer == slv_port_upper_byte)
              break;
          end
          this.exp_slv_port_r_queue.push(act_slv_r.axi_id, act_slv_r);
          incr_expected_tests(3)                                     ;

          // Increment the len counters
          slv_port_r_cnt[id]++                 ;
          mst_port_data_pointer += bytes_copied;

          // Used the whole R beat
          if (mst_port_data_pointer == AxiMstPortStrbWidth)
            break;
          // Finished the R beat
          if (act_slv_r.axi_last)
            break;
        end

        // Increment the len counter
        mst_port_r_cnt[id]++;

        // Pop the AR request from the queues
        if (mst_port_r_cnt[id] == act_mst_ar.axi_len + 1) begin
          void'(act_mst_port_ar_queue.pop_id(act_mst_ar.axi_id));
          mst_port_r_cnt[id] = 0;
        end
        if (slv_port_r_cnt[id] == act_slv_ar.axi_len + 1) begin
          void'(act_slv_port_ar_queue.pop_id(act_slv_ar.axi_id));
          slv_port_r_cnt[id] = 0;
        end
      end
    endtask: mon_mst_port_r
  endclass : axi_dw_upsizer_monitor

  /***************
   *  DOWNSIZER  *
   ***************/

  class axi_dw_downsizer_monitor #(
      parameter int unsigned AxiAddrWidth       ,
      parameter int unsigned AxiSlvPortDataWidth,
      parameter int unsigned AxiMstPortDataWidth,
      parameter int unsigned AxiIdWidth         ,
      parameter int unsigned AxiUserWidth       ,
      // Stimuli application and test time
      parameter time TimeTest
    ) extends axi_dw_monitor #(
      .AxiAddrWidth       (AxiAddrWidth       ),
      .AxiSlvPortDataWidth(AxiSlvPortDataWidth),
      .AxiMstPortDataWidth(AxiMstPortDataWidth),
      .AxiIdWidth         (AxiIdWidth         ),
      .AxiUserWidth       (AxiUserWidth       ),
      .TimeTest           (TimeTest           )
    );

    local static shortint unsigned slv_port_r_cnt[axi_id_t];
    local static shortint unsigned mst_port_r_cnt[axi_id_t];
    local static exp_slv_rw_t      slv_port_r[axi_id_t];
    local static shortint unsigned slv_port_r_pnt[axi_id_t];
    local static shortint unsigned slv_port_w_cnt;
    local static shortint unsigned mst_port_w_cnt;

    /**********************
     *  Helper functions  *
     **********************/

    /*
     * Returns how many beats of the incoming AXI transaction will be dropped after downsizing
     * due to an unaligned memory address.
     */
    function automatic len_t aligned_adjustment(axi_addr_t addr, axi_pkg::size_t size)                     ;
      return (addr & size_mask(size) & ~size_mask(AxiMstPortMaxSize))/axi_pkg::num_bytes(AxiMstPortMaxSize);
    endfunction: aligned_adjustment

    /*****************
     *  Constructor  *
     *****************/

    function new (
        virtual AXI_BUS_DV #(
          .AXI_ADDR_WIDTH(AxiAddrWidth       ),
          .AXI_DATA_WIDTH(AxiSlvPortDataWidth),
          .AXI_ID_WIDTH  (AxiIdWidth         ),
          .AXI_USER_WIDTH(AxiUserWidth       )
        ) slv_port_vif,
        virtual AXI_BUS_DV #(
          .AXI_ADDR_WIDTH(AxiAddrWidth       ),
          .AXI_DATA_WIDTH(AxiMstPortDataWidth),
          .AXI_ID_WIDTH  (AxiIdWidth         ),
          .AXI_USER_WIDTH(AxiUserWidth       )
        ) mst_port_vif
      );
      begin
        super.new(slv_port_vif, mst_port_vif);

        slv_port_w_cnt = 0;
        mst_port_w_cnt = 0;
        for (int unsigned id = 0; id < 2**AxiIdWidth; id++) begin
          slv_port_r_cnt[id] = '0;
          mst_port_r_cnt[id] = '0;
          slv_port_r[id]     = '0;
          slv_port_r_pnt[id] = '1;
        end
      end
    endfunction

    /**************
     *  Monitors  *
     **************/

    task automatic mon_slv_port_aw ();
      exp_ax_t exp_aw;

      if (slv_port_axi.aw_valid && slv_port_axi.aw_ready) begin
        case (slv_port_axi.aw_burst)
          axi_pkg::BURST_INCR: begin
            // Transaction unchanged
            if (conv_ratio(slv_port_axi.aw_size, AxiMstPortMaxSize) == 1) begin
              exp_aw = '{
                axi_id   : slv_port_axi.aw_id   ,
                axi_addr : slv_port_axi.aw_addr ,
                axi_len  : slv_port_axi.aw_len  ,
                axi_burst: slv_port_axi.aw_burst,
                axi_size : slv_port_axi.aw_size ,
                axi_cache: slv_port_axi.aw_cache
              };

              this.exp_mst_port_aw_queue.push(slv_port_axi.aw_id, exp_aw);
              incr_expected_tests(6)                                     ;
            end
            // INCR downsize
            else begin
              automatic int unsigned num_beats = (slv_port_axi.aw_len + 1) * conv_ratio(slv_port_axi.aw_size, AxiMstPortMaxSize) - aligned_adjustment(slv_port_axi.aw_addr, slv_port_axi.aw_size);
              // One burst
              if (num_beats <= 256) begin
                exp_aw = '{
                  axi_id   : slv_port_axi.aw_id   ,
                  axi_addr : slv_port_axi.aw_addr ,
                  axi_len  : num_beats - 1        ,
                  axi_burst: slv_port_axi.aw_burst,
                  axi_size : AxiMstPortMaxSize    ,
                  axi_cache: slv_port_axi.aw_cache
                };

                this.exp_mst_port_aw_queue.push(slv_port_axi.aw_id, exp_aw);
                incr_expected_tests(6)                                     ;
              end
              // Need to split the incoming burst into several INCR bursts
              else begin
                automatic axi_addr_t burst_addr;
                automatic len_t burst_len      ;

                // First burst is a "partial" burst
                burst_len = 255 - aligned_adjustment(slv_port_axi.aw_addr, slv_port_axi.aw_size);
                exp_aw    = '{
                  axi_id   : slv_port_axi.aw_id   ,
                  axi_addr : slv_port_axi.aw_addr ,
                  axi_len  : burst_len            ,
                  axi_burst: slv_port_axi.aw_burst,
                  axi_size : AxiMstPortMaxSize    ,
                  axi_cache: slv_port_axi.aw_cache
                }                                                          ;
                this.exp_mst_port_aw_queue.push(slv_port_axi.aw_id, exp_aw);
                incr_expected_tests(6)                                     ;

                // Push the other bursts in a loop
                num_beats  = num_beats - burst_len - 1                                                                                                                         ;
                burst_addr = axi_pkg::beat_addr(axi_pkg::aligned_addr(slv_port_axi.aw_addr, AxiMstPortMaxSize), AxiMstPortMaxSize, burst_len, axi_pkg::BURST_INCR, burst_len+1);
                while (num_beats != 0) begin
                  burst_len  = num_beats >= 256 ? 255 : num_beats - 1;
                  exp_aw    = '{
                    axi_id   : slv_port_axi.aw_id   ,
                    axi_addr : burst_addr           ,
                    axi_len  : burst_len            ,
                    axi_burst: slv_port_axi.aw_burst,
                    axi_size : AxiMstPortMaxSize    ,
                    axi_cache: slv_port_axi.aw_cache
                  }                                                          ;
                  this.exp_mst_port_aw_queue.push(slv_port_axi.aw_id, exp_aw);
                  incr_expected_tests(6)                                     ;

                  num_beats  = num_beats - burst_len - 1                                                                     ;
                  burst_addr = axi_pkg::beat_addr(burst_addr, AxiMstPortMaxSize, burst_len, axi_pkg::BURST_INCR, burst_len+1);
                end;
              end
            end
          end
          // Passthrough downsize
          axi_pkg::BURST_FIXED: begin
            // Transaction unchanged
            if (conv_ratio(slv_port_axi.aw_size, AxiMstPortMaxSize) == 1) begin
              exp_aw = '{
                axi_id   : slv_port_axi.aw_id   ,
                axi_addr : slv_port_axi.aw_addr ,
                axi_len  : slv_port_axi.aw_len  ,
                axi_burst: slv_port_axi.aw_burst,
                axi_size : slv_port_axi.aw_size ,
                axi_cache: slv_port_axi.aw_cache
              };

              this.exp_mst_port_aw_queue.push(slv_port_axi.aw_id, exp_aw);
              incr_expected_tests(6)                                     ;
            end
            // Split into master_axi.aw_len + 1 INCR bursts
            else begin
              for (int unsigned j = 0; j <= slv_port_axi.aw_len; j++) begin
                exp_aw.axi_id    = slv_port_axi.aw_id   ;
                exp_aw.axi_addr  = slv_port_axi.aw_addr ;
                exp_aw.axi_burst = axi_pkg::BURST_INCR  ;
                exp_aw.axi_size  = AxiMstPortMaxSize    ;
                exp_aw.axi_cache = slv_port_axi.aw_cache;
                if (conv_ratio(slv_port_axi.aw_size, AxiMstPortMaxSize) >= aligned_adjustment(slv_port_axi.aw_addr, slv_port_axi.aw_size) + 1)
                  exp_aw.axi_len = conv_ratio(slv_port_axi.aw_size, AxiMstPortMaxSize) - aligned_adjustment(slv_port_axi.aw_addr, slv_port_axi.aw_size) - 1;
                else
                  exp_aw.axi_len = 0;

                this.exp_mst_port_aw_queue.push(slv_port_axi.aw_id, exp_aw);
                incr_expected_tests(6)                                     ;
              end
            end
          end
          // WRAP downsize
          axi_pkg::BURST_WRAP: begin
            exp_aw = '0;
            $warning("WRAP bursts are not supported.");
          end
        endcase

        $display("%0tns > Master: AW with ID: %b",
          $time, slv_port_axi.aw_id);

        // Populate the expected B queue
        this.exp_slv_port_b_queue.push(slv_port_axi.aw_id, '{
            axi_id  : slv_port_axi.aw_id,
            axi_last: 1'b1
          })                                  ;
        incr_expected_tests(1)                ;
        $display("        Expect B response.");
      end
    endtask : mon_slv_port_aw

    task automatic mon_slv_port_w ();
      if (act_slv_port_w_queue.size() != 0) begin
        exp_slv_rw_t act_slv_w = act_slv_port_w_queue[0];

        if (act_mst_port_aw_queue.size() != 0 && act_slv_port_aw_queue.size() != 0) begin
          // Retrieve the AW requests related to this W beat
          exp_ax_t act_mst_aw = act_mst_port_aw_queue[0];
          exp_ax_t act_slv_aw = act_slv_port_aw_queue[0];

          // Address of the current beat
          axi_addr_t slv_aw_addr = axi_pkg::beat_addr(act_slv_aw.axi_addr, act_slv_aw.axi_size, act_slv_aw.axi_len, act_slv_aw.axi_burst, slv_port_w_cnt);

          // Calculate the offsets inside the incoming word
          shortint unsigned slv_port_lower_byte =
          axi_pkg::beat_lower_byte(act_slv_aw.axi_addr, act_slv_aw.axi_size, act_slv_aw.axi_len, act_slv_aw.axi_burst, AxiSlvPortStrbWidth, slv_port_w_cnt);
          // Pointer inside the incoming word
          shortint unsigned slv_port_data_pointer = slv_port_lower_byte;

          // Several W beats generated from this incoming W beat
          int unsigned beat_cnt = conv_ratio(act_slv_aw.axi_size, AxiMstPortMaxSize) - aligned_adjustment(slv_aw_addr, act_slv_aw.axi_size);

          for (int unsigned beat = 0; beat < beat_cnt; beat++) begin
            exp_mst_rw_t act_mst_w = '0;

            // Calculate the offsets inside the outcoming word
            shortint unsigned mst_port_lower_byte =
            axi_pkg::beat_lower_byte(act_mst_aw.axi_addr, act_mst_aw.axi_size, act_mst_aw.axi_len, act_mst_aw.axi_burst, AxiMstPortStrbWidth, mst_port_w_cnt);
            shortint unsigned mst_port_upper_byte =
            axi_pkg::beat_upper_byte(act_mst_aw.axi_addr, act_mst_aw.axi_size, act_mst_aw.axi_len, act_mst_aw.axi_burst, AxiMstPortStrbWidth, mst_port_w_cnt);

            shortint unsigned bytes_copied = 0;

            act_mst_w.axi_id   = act_mst_aw.axi_id                   ;
            act_mst_w.axi_last = mst_port_w_cnt == act_mst_aw.axi_len;
            act_mst_w.axi_data = '0                                  ;
            for (shortint unsigned b = slv_port_data_pointer; b < AxiSlvPortStrbWidth; b++) begin
              if (b + mst_port_lower_byte - slv_port_data_pointer == AxiMstPortStrbWidth)
                break;
              act_mst_w.axi_data[8*(b + mst_port_lower_byte - slv_port_data_pointer) +: 8] = act_slv_w.axi_data[8*b +: 8];
              act_mst_w.axi_strb[b + mst_port_lower_byte - slv_port_data_pointer]          = act_slv_w.axi_strb[b]       ;
              bytes_copied++;
            end
            // Don't care for the bits outside these accessed by this request
            for (int unsigned b = 0; b < AxiMstPortStrbWidth; b++)
              if (!(mst_port_lower_byte <= b && b <= mst_port_upper_byte))
                act_mst_w.axi_data[8*b +: 8] = {8{1'b?}};

            this.exp_mst_port_w_queue.push_back(act_mst_w);
            incr_expected_tests(3)                        ;

            // Increment the len counters
            mst_port_w_cnt++                     ;
            slv_port_data_pointer += bytes_copied;

            // Used the whole W beat
            if (slv_port_data_pointer == AxiSlvPortStrbWidth)
              break;
          end

          // Increment the len counter
          slv_port_w_cnt++;

          // Pop the AW request from the queues
          if (slv_port_w_cnt == act_slv_aw.axi_len + 1) begin
            void'(act_slv_port_aw_queue.pop_front());
            slv_port_w_cnt = 0;
          end
          if (mst_port_w_cnt == act_mst_aw.axi_len + 1) begin
            void'(act_mst_port_aw_queue.pop_front());
            mst_port_w_cnt = 0;
          end

          // Pop the W request
          void'(act_slv_port_w_queue.pop_front());
        end
      end
    endtask: mon_slv_port_w

    task automatic mon_slv_port_ar ();
      exp_ax_t exp_slv_ar;
      exp_b_t  exp_mst_r;

      if (slv_port_axi.ar_valid && slv_port_axi.ar_ready) begin
        case (slv_port_axi.ar_burst)
          axi_pkg::BURST_INCR: begin
            // Transaction unchanged
            if (conv_ratio(slv_port_axi.ar_size, AxiMstPortMaxSize) == 1) begin
              exp_slv_ar = '{
                axi_id   : slv_port_axi.ar_id   ,
                axi_addr : slv_port_axi.ar_addr ,
                axi_len  : slv_port_axi.ar_len  ,
                axi_burst: slv_port_axi.ar_burst,
                axi_size : slv_port_axi.ar_size ,
                axi_cache: slv_port_axi.ar_cache
              };

              this.exp_mst_port_ar_queue.push(slv_port_axi.ar_id, exp_slv_ar);
              incr_expected_tests(6)                                         ;
            end
            // INCR downsize
            else begin
              automatic int unsigned num_beats = (slv_port_axi.ar_len + 1) * conv_ratio(slv_port_axi.ar_size, AxiMstPortMaxSize) - aligned_adjustment(slv_port_axi.ar_addr, slv_port_axi.ar_size);
              // One burst
              if (num_beats <= 256) begin
                exp_slv_ar = '{
                  axi_id   : slv_port_axi.ar_id   ,
                  axi_addr : slv_port_axi.ar_addr ,
                  axi_len  : num_beats - 1        ,
                  axi_burst: slv_port_axi.ar_burst,
                  axi_size : AxiMstPortMaxSize    ,
                  axi_cache: slv_port_axi.ar_cache
                };

                this.exp_mst_port_ar_queue.push(slv_port_axi.ar_id, exp_slv_ar);
                incr_expected_tests(6)                                         ;
              end
              // Need to split the incoming burst into several INCR bursts
              else begin
                automatic axi_addr_t burst_addr;
                automatic len_t burst_len      ;

                // First burst is a "partial" burst
                burst_len  = 255 - aligned_adjustment(slv_port_axi.ar_addr, slv_port_axi.ar_size);
                exp_slv_ar = '{
                  axi_id   : slv_port_axi.ar_id   ,
                  axi_addr : slv_port_axi.ar_addr ,
                  axi_len  : burst_len            ,
                  axi_burst: slv_port_axi.ar_burst,
                  axi_size : AxiMstPortMaxSize    ,
                  axi_cache: slv_port_axi.ar_cache
                }                                                              ;
                this.exp_mst_port_ar_queue.push(slv_port_axi.ar_id, exp_slv_ar);
                incr_expected_tests(6)                                         ;

                // Push the other bursts in a loop
                num_beats  = num_beats - burst_len - 1                                                                                                                         ;
                burst_addr = axi_pkg::beat_addr(axi_pkg::aligned_addr(slv_port_axi.ar_addr, AxiMstPortMaxSize), AxiMstPortMaxSize, burst_len, axi_pkg::BURST_INCR, burst_len+1);
                while (num_beats != 0) begin
                  burst_len  = num_beats >= 256 ? 255 : num_beats - 1;
                  exp_slv_ar = '{
                    axi_id   : slv_port_axi.ar_id   ,
                    axi_addr : burst_addr           ,
                    axi_len  : burst_len            ,
                    axi_burst: slv_port_axi.ar_burst,
                    axi_size : AxiMstPortMaxSize    ,
                    axi_cache: slv_port_axi.ar_cache
                  }                                                              ;
                  this.exp_mst_port_ar_queue.push(slv_port_axi.ar_id, exp_slv_ar);
                  incr_expected_tests(6)                                         ;

                  num_beats  = num_beats - burst_len - 1                                                                     ;
                  burst_addr = axi_pkg::beat_addr(burst_addr, AxiMstPortMaxSize, burst_len, axi_pkg::BURST_INCR, burst_len+1);
                end;
              end
            end
          end
          // Passthrough downsize
          axi_pkg::BURST_FIXED: begin
            // Transaction unchanged
            if (conv_ratio(slv_port_axi.ar_size, AxiMstPortMaxSize) == 1) begin
              exp_slv_ar = '{
                axi_id   : slv_port_axi.ar_id   ,
                axi_addr : slv_port_axi.ar_addr ,
                axi_len  : slv_port_axi.ar_len  ,
                axi_burst: slv_port_axi.ar_burst,
                axi_size : slv_port_axi.ar_size ,
                axi_cache: slv_port_axi.ar_cache
              };

              this.exp_mst_port_ar_queue.push(slv_port_axi.ar_id, exp_slv_ar);
              incr_expected_tests(6)                                         ;
            end
            // Split into master_axi.ar_len + 1 INCR bursts
            else begin
              for (int unsigned j = 0; j <= slv_port_axi.ar_len; j++) begin
                exp_slv_ar.axi_id    = slv_port_axi.ar_id   ;
                exp_slv_ar.axi_addr  = slv_port_axi.ar_addr ;
                exp_slv_ar.axi_burst = axi_pkg::BURST_INCR  ;
                exp_slv_ar.axi_size  = AxiMstPortMaxSize    ;
                exp_slv_ar.axi_cache = slv_port_axi.ar_cache;
                if (conv_ratio(slv_port_axi.ar_size, AxiMstPortMaxSize) >= aligned_adjustment(slv_port_axi.ar_addr, slv_port_axi.ar_size) + 1)
                  exp_slv_ar.axi_len = conv_ratio(slv_port_axi.ar_size, AxiMstPortMaxSize) - aligned_adjustment(slv_port_axi.ar_addr, slv_port_axi.ar_size) - 1;
                else
                  exp_slv_ar.axi_len = 0;

                this.exp_mst_port_ar_queue.push(slv_port_axi.ar_id, exp_slv_ar);
                incr_expected_tests(6)                                         ;
              end
            end
          end
          // WRAP downsize
          axi_pkg::BURST_WRAP: begin
            exp_slv_ar = '0;
            $warning("WRAP bursts are not supported.");
          end
        endcase

        $display("%0tns > Master: AR with ID: %b",
          $time, slv_port_axi.ar_id);
      end
    endtask : mon_slv_port_ar

    virtual task automatic mon_mst_port_r();
      if (mst_port_axi.r_valid && mst_port_axi.r_ready) begin
        // Retrieve the AR requests related to this R beat
        exp_ax_t act_mst_ar = act_mst_port_ar_queue.get(mst_port_axi.r_id);
        exp_ax_t act_slv_ar = act_slv_port_ar_queue.get(mst_port_axi.r_id);
        axi_id_t id         = mst_port_axi.r_id                           ;

        // Calculate the offsets
        shortint unsigned mst_port_lower_byte =
        axi_pkg::beat_lower_byte(act_mst_ar.axi_addr, act_mst_ar.axi_size, act_mst_ar.axi_len, act_mst_ar.axi_burst, AxiMstPortStrbWidth, mst_port_r_cnt[id]);
        shortint unsigned mst_port_upper_byte =
        axi_pkg::beat_upper_byte(act_mst_ar.axi_addr, act_mst_ar.axi_size, act_mst_ar.axi_len, act_mst_ar.axi_burst, AxiMstPortStrbWidth, mst_port_r_cnt[id]);
        shortint unsigned slv_port_lower_byte =
        axi_pkg::beat_lower_byte(act_slv_ar.axi_addr, act_slv_ar.axi_size, act_slv_ar.axi_len, act_slv_ar.axi_burst, AxiSlvPortStrbWidth, slv_port_r_cnt[id]);
        shortint unsigned slv_port_upper_byte =
        axi_pkg::beat_upper_byte(act_slv_ar.axi_addr, act_slv_ar.axi_size, act_slv_ar.axi_len, act_slv_ar.axi_burst, AxiSlvPortStrbWidth, slv_port_r_cnt[id]);

        // Pointer inside the outcoming word
        shortint unsigned bytes_copied = 0;
        if (slv_port_r_pnt[id] == '1)
          slv_port_r_pnt[id] = slv_port_lower_byte;

        slv_port_r[id].axi_id   = id                                      ;
        slv_port_r[id].axi_last = slv_port_r_cnt[id] == act_slv_ar.axi_len;
        for (shortint unsigned b = mst_port_lower_byte; b <= mst_port_upper_byte; b++) begin
          if (b + slv_port_r_pnt[id] - mst_port_lower_byte == AxiSlvPortStrbWidth)
            break;
          slv_port_r[id].axi_data[8*(b + slv_port_r_pnt[id] - mst_port_lower_byte) +: 8] = mst_port_axi.r_data[8*b +: 8];
          bytes_copied++;
        end

        // Increment the len counters
        mst_port_r_cnt[id]++              ;
        slv_port_r_pnt[id] += bytes_copied;

        if (slv_port_r_pnt[id] == slv_port_upper_byte + 1                // Used all bits from the incoming R beat
            || slv_port_r_pnt[id] == AxiSlvPortStrbWidth                 // Filled up an outcoming R beat
            || conv_ratio(act_slv_ar.axi_size, act_mst_ar.axi_size) == 1 // Not downsizing
            || mst_port_axi.r_last                                       // Last beat of an R burst
          ) begin
          // Don't care for the bits outside these accessed by this request
          for (int unsigned b = 0; b < AxiSlvPortStrbWidth; b++)
            if (!(slv_port_lower_byte <= b && b <= slv_port_upper_byte))
              slv_port_r[id].axi_data[8*b +: 8] = {8{1'b?}};

          this.exp_slv_port_r_queue.push(id, slv_port_r[id]);
          incr_expected_tests(3)                            ;

          // Increment the len counter
          slv_port_r_cnt[id]++;

          // Reset R beat
          slv_port_r[id]     = '0;
          slv_port_r_pnt[id] = '1;
        end

        // Pop the AW request from the queues
        if (slv_port_r_cnt[id] == act_slv_ar.axi_len + 1) begin
          void'(act_slv_port_ar_queue.pop_id(id));
          slv_port_r_cnt[id] = 0;
        end
        if (mst_port_r_cnt[id] == act_mst_ar.axi_len + 1) begin
          void'(act_mst_port_ar_queue.pop_id(id));
          mst_port_r_cnt[id] = 0;
        end
      end
    endtask : mon_mst_port_r
  endclass : axi_dw_downsizer_monitor

endpackage: tb_axi_dw_pkg