module axi_lite_mailbox #(
  parameter int unsigned MailboxDepth = 32'd0,
  parameter bit unsigned IrqEdgeTrig  = 1'b0,
  parameter bit unsigned IrqActHigh   = 1'b1,
  parameter int unsigned AxiAddrWidth = 32'd0,
  parameter int unsigned AxiDataWidth = 32'd0,
  parameter type         req_lite_t   = logic,
  parameter type         resp_lite_t  = logic,
  // DEPENDENT PARAMETERS, DO NOT OVERRIDE!
  parameter type         addr_t       = logic [AxiAddrWidth-1:0]
) (
  input  logic             clk_i,       // Clock
  input  logic             rst_ni,      // Asynchronous reset active low
  input  logic             test_i,      // Testmode enable
  // slave ports [1:0]
  input  req_lite_t  [1:0] slv_reqs_i,
  output resp_lite_t [1:0] slv_resps_o,
  output logic       [1:0] irq_o,       // interrupt output for each port
  input  addr_t      [1:0] base_addr_i  // base address for each port
);
  localparam int unsigned FifoUsageWidth = $clog2(MailboxDepth);
  typedef logic [AxiDataWidth-1:0] data_t;
  // usage type of the mailbox FIFO, also the type of the threshold comparison
  // is one bit wider, MSB is the fifo_full flag of the respective fifo
  typedef logic [FifoUsageWidth:0] usage_t;

  // signal declaration for the mailbox FIFO's, signal index is the port
  logic   [1:0] mbox_full,    mbox_empty;   // index is the instantiated mailbox FIFO
  logic   [1:0] mbox_push,    mbox_pop;     // index is port
  logic   [1:0] w_mbox_flush, r_mbox_flush; // index is port
  data_t  [1:0] mbox_w_data,  mbox_r_data;  // index is port
  usage_t [1:0] mbox_usage;                 // index is the instantiated mailbox FIFO
  // interrupt request from this slave port, level triggered, active high --> convert
  logic   [1:0] slv_irq;
  logic   [1:0] clear_irq;

  axi_lite_mailbox_slave #(
    .MailboxDepth ( MailboxDepth ),
    .AxiAddrWidth ( AxiAddrWidth ),
    .AxiDataWidth ( AxiDataWidth ),
    .req_lite_t   ( req_lite_t   ),
    .resp_lite_t  ( resp_lite_t  ),
    .addr_t       ( addr_t       ),
    .data_t       ( data_t       ),
    .usage_t      ( usage_t      )  // fill pointer from MBOX FIFO
  ) i_slv_port_0 (
    .clk_i,   // Clock
    .rst_ni,  // Asynchronous reset active low
    // slave port
    .slv_req_i      ( slv_reqs_i[0]   ),
    .slv_resp_o     ( slv_resps_o[0]  ),
    .base_addr_i    ( base_addr_i[0]  ), // base address for the slave port
    // write FIFO port
    .mbox_w_data_o  ( mbox_w_data[0]  ),
    .mbox_w_full_i  ( mbox_full[0]    ),
    .mbox_w_push_o  ( mbox_push[0]    ),
    .mbox_w_flush_o ( w_mbox_flush[0] ),
    .mbox_w_usage_i ( mbox_usage[0]   ),
    // read FIFO port
    .mbox_r_data_i  ( mbox_r_data[0]  ),
    .mbox_r_empty_i ( mbox_empty[1]   ),
    .mbox_r_pop_o   ( mbox_pop[0]     ),
    .mbox_r_flush_o ( r_mbox_flush[0] ),
    .mbox_r_usage_i ( mbox_usage[1]   ),
    // interrupt output, level triggered, active high, conversion in top
    .irq_o          ( slv_irq[0]      ),
    .clear_irq_o    ( clear_irq[0]    )
  );

  axi_lite_mailbox_slave #(
    .MailboxDepth ( MailboxDepth ),
    .AxiAddrWidth ( AxiAddrWidth ),
    .AxiDataWidth ( AxiDataWidth ),
    .req_lite_t   ( req_lite_t   ),
    .resp_lite_t  ( resp_lite_t  ),
    .addr_t       ( addr_t       ),
    .data_t       ( data_t       ),
    .usage_t      ( usage_t      )  // fill pointer from MBOX FIFO
  ) i_slv_port_1 (
    .clk_i,   // Clock
    .rst_ni,  // Asynchronous reset active low
    // slave port
    .slv_req_i      ( slv_reqs_i[1]   ),
    .slv_resp_o     ( slv_resps_o[1]  ),
    .base_addr_i    ( base_addr_i[1]  ), // base address for the slave port
    // write FIFO port
    .mbox_w_data_o  ( mbox_w_data[1]  ),
    .mbox_w_full_i  ( mbox_full[1]    ),
    .mbox_w_push_o  ( mbox_push[1]    ),
    .mbox_w_flush_o ( w_mbox_flush[1] ),
    .mbox_w_usage_i ( mbox_usage[1]   ),
    // read FIFO port
    .mbox_r_data_i  ( mbox_r_data[1]  ),
    .mbox_r_empty_i ( mbox_empty[0]   ),
    .mbox_r_pop_o   ( mbox_pop[1]     ),
    .mbox_r_flush_o ( r_mbox_flush[1] ),
    .mbox_r_usage_i ( mbox_usage[0]   ),
    // interrupt output, level triggered, active high, conversion in top
    .irq_o          ( slv_irq[1]      ),
    .clear_irq_o    ( clear_irq[1]    )
  );

  // the usage gets concatinated with the full flag to have consistent threshold detection
  logic [FifoUsageWidth-1:0] mbox_0_to_1_usage, mbox_1_to_0_usage;
  fifo_v3 #(
    .FALL_THROUGH ( 1'b0         ),
    .DEPTH        ( MailboxDepth ),
    .dtype        ( data_t       )
  ) i_mbox_0_to_1 (
    .clk_i,
    .rst_ni,
    .testmode_i( test_i                            ),
    .flush_i   ( w_mbox_flush[0] | r_mbox_flush[1] ),
    .full_o    ( mbox_full[0]                      ),
    .empty_o   ( mbox_empty[0]                     ),
    .usage_o   ( mbox_0_to_1_usage                 ),
    .data_i    ( mbox_w_data[0]                    ),
    .push_i    ( mbox_push[0]                      ),
    .data_o    ( mbox_r_data[1]                    ),
    .pop_i     ( mbox_pop[1]                       )
  );
  // assign the MSB of the FIFO to the correct usage signal
  assign mbox_usage[0] = {mbox_full[0], mbox_0_to_1_usage};

  fifo_v3 #(
    .FALL_THROUGH ( 1'b0         ),
    .DEPTH        ( MailboxDepth ),
    .dtype        ( data_t       )
  ) i_mbox_1_to_0 (
    .clk_i,
    .rst_ni,
    .testmode_i( test_i                            ),
    .flush_i   ( w_mbox_flush[1] | r_mbox_flush[0] ),
    .full_o    ( mbox_full[1]                      ),
    .empty_o   ( mbox_empty[1]                     ),
    .usage_o   ( mbox_1_to_0_usage                 ),
    .data_i    ( mbox_w_data[1]                    ),
    .push_i    ( mbox_push[1]                      ),
    .data_o    ( mbox_r_data[0]                    ),
    .pop_i     ( mbox_pop[0]                       )
  );
  assign mbox_usage[1] = {mbox_full[1], mbox_1_to_0_usage};

  for (genvar i = 0; i < 2; i++) begin : gen_irq_conversion
    if (IrqEdgeTrig) begin : gen_irq_edge
      logic irq_q, irq_d, update_irq;

      always_comb begin
        // default assignments
        irq_d      = irq_q;
        update_irq = 1'b0;
        // init the irq and pulse only on update
        irq_o[i]   = ~IrqActHigh;
        if (clear_irq[i]) begin
          irq_d      = 1'b0;
          update_irq = 1'b1;
        end else if (!irq_q && slv_irq[i]) begin
          irq_d      = 1'b1;
          update_irq = 1'b1;
          irq_o[i]   = IrqActHigh; // on update of the register pulse the irq signal
        end
      end

      `FFLARN(irq_q, irq_d, update_irq, '0, clk_i, rst_ni)
    end else begin : gen_irq_level
      assign irq_o[i] = (IrqActHigh) ? slv_irq[i] : ~slv_irq[i];
    end
  end

  // pragma translate_off
  `ifndef VERILATOR
  initial begin : proc_check_params
    mailbox_depth:  assert (MailboxDepth > 1) else $fatal(1, "MailboxDepth has to be at least 2");
    axi_addr_width: assert (AxiAddrWidth > 0) else $fatal(1, "AxiAddrWidth has to be > 0");
    axi_data_width: assert (AxiDataWidth > 0) else $fatal(1, "AxiDataWidth has to be > 0");
  end
  `endif
  // pragma translate_on
endmodule