module mem_to_banks #(
  /// Input address width.
  parameter int unsigned AddrWidth = 32'd0,
  /// Input data width, must be a power of two.
  parameter int unsigned DataWidth = 32'd0,
  /// Number of banks at output, must evenly divide `DataWidth`.
  parameter int unsigned NumBanks  = 32'd0,
  /// Dependent parameter, do not override! Address type.
  localparam type addr_t     = logic [AddrWidth-1:0],
  /// Dependent parameter, do not override! Input data type.
  localparam type inp_data_t = logic [DataWidth-1:0],
  /// Dependent parameter, do not override! Input write strobe type.
  localparam type inp_strb_t = logic [DataWidth/8-1:0],
  /// Dependent parameter, do not override! Output data type.
  localparam type oup_data_t = logic [DataWidth/NumBanks-1:0],
  /// Dependent parameter, do not override! Output write strobe type.
  localparam type oup_strb_t = logic [DataWidth/NumBanks/8-1:0]
) (
  /// Clock input.
  input  logic                      clk_i,
  /// Asynchronous reset, active low.
  input  logic                      rst_ni,
  /// Memory request to split, request is valid.
  input  logic                      req_i,
  /// Memory request to split, request can be granted.
  output logic                      gnt_o,
  /// Memory request to split, request address, byte-wise.
  input  addr_t                     addr_i,
  /// Memory request to split, request write data.
  input  inp_data_t                 wdata_i,
  /// Memory request to split, request write strobe.
  input  inp_strb_t                 strb_i,
  /// Memory request to split, request Atomic signal from AXI4+ATOP.
  input  axi_pkg::atop_t            atop_i,
  /// Memory request to split, request write enable, active high.
  input  logic                      we_i,
  /// Memory request to split, response is valid. Required for read and write requests
  output logic                      rvalid_o,
  /// Memory request to split, response read data.
  output inp_data_t                 rdata_o,
  /// Memory bank request, request is valid.
  output logic           [NumBanks-1:0]  bank_req_o,
  /// Memory bank request, request can be granted.
  input  logic           [NumBanks-1:0]  bank_gnt_i,
  /// Memory bank request, request address, byte-wise. Will be different for each bank.
  output addr_t          [NumBanks-1:0]  bank_addr_o,
  /// Memory bank request, request write data.
  output oup_data_t      [NumBanks-1:0]  bank_wdata_o,
  /// Memory bank request, request write strobe.
  output oup_strb_t      [NumBanks-1:0]  bank_strb_o,
  /// Memory bank request, request Atomic signal from AXI4+ATOP.
  output axi_pkg::atop_t [NumBanks-1:0]  bank_atop_o,
  /// Memory bank request, request write enable, active high.
  output logic           [NumBanks-1:0]  bank_we_o,
  /// Memory bank request, response is valid. Required for read and write requests
  input  logic           [NumBanks-1:0]  bank_rvalid_i,
  /// Memory bank request, response read data.
  input  oup_data_t      [NumBanks-1:0]  bank_rdata_i
);

  localparam DataBytes    = $bits(inp_strb_t);
  localparam BitsPerBank  = $bits(oup_data_t);
  localparam BytesPerBank = $bits(oup_strb_t);

  typedef struct packed {
    addr_t          addr;
    oup_data_t      wdata;
    oup_strb_t      strb;
    axi_pkg::atop_t atop;
    logic           we;
  } req_t;

  logic                 req_valid;
  logic [NumBanks-1:0]              req_ready,
                        resp_valid, resp_ready;
  req_t [NumBanks-1:0]  bank_req,
                        bank_oup;

  function automatic addr_t align_addr(input addr_t addr);
    return (addr >> $clog2(DataBytes)) << $clog2(DataBytes);
  endfunction

  // Handle requests.
  assign req_valid = req_i & gnt_o;
  for (genvar i = 0; unsigned'(i) < NumBanks; i++) begin : gen_reqs
    assign bank_req[i].addr  = align_addr(addr_i) + i * BytesPerBank;
    assign bank_req[i].wdata = wdata_i[i*BitsPerBank+:BitsPerBank];
    assign bank_req[i].strb  = strb_i[i*BytesPerBank+:BytesPerBank];
    assign bank_req[i].atop  = atop_i;
    assign bank_req[i].we    = we_i;
    fall_through_register #(
      .T ( req_t )
    ) i_ft_reg (
      .clk_i,
      .rst_ni,
      .clr_i      ( 1'b0          ),
      .testmode_i ( 1'b0          ),
      .valid_i    ( req_valid     ),
      .ready_o    ( req_ready[i]  ),
      .data_i     ( bank_req[i]   ),
      .valid_o    ( bank_req_o[i] ),
      .ready_i    ( bank_gnt_i[i] ),
      .data_o     ( bank_oup[i]   )
    );
    assign bank_addr_o[i]  = bank_oup[i].addr;
    assign bank_wdata_o[i] = bank_oup[i].wdata;
    assign bank_strb_o[i]  = bank_oup[i].strb;
    assign bank_atop_o[i]  = bank_oup[i].atop;
    assign bank_we_o[i]    = bank_oup[i].we;
  end

  // Grant output if all our requests have been granted.
  assign gnt_o = (&req_ready) & (&resp_ready);

  // Handle responses.
  for (genvar i = 0; unsigned'(i) < NumBanks; i++) begin : gen_resp_regs
    fall_through_register #(
      .T ( oup_data_t )
    ) i_ft_reg (
      .clk_i,
      .rst_ni,
      .clr_i      ( 1'b0                                ),
      .testmode_i ( 1'b0                                ),
      .valid_i    ( bank_rvalid_i[i]                    ),
      .ready_o    ( resp_ready[i]                       ),
      .data_i     ( bank_rdata_i[i]                     ),
      .data_o     ( rdata_o[i*BitsPerBank+:BitsPerBank] ),
      .ready_i    ( rvalid_o                            ),
      .valid_o    ( resp_valid[i]                       )
    );
  end
  assign rvalid_o = &resp_valid;

  // Assertions
  // pragma translate_off
  `ifndef VERILATOR
    initial begin
      assume (DataWidth != 0 && (DataWidth & (DataWidth - 1)) == 0)
        else $fatal(1, "Data width must be a power of two!");
      assume (DataWidth % NumBanks == 0)
        else $fatal(1, "Data width must be evenly divisible over banks!");
      assume ((DataWidth / NumBanks) % 8 == 0)
        else $fatal(1, "Data width of each bank must be divisible into 8-bit bytes!");
    end
  `endif
  // pragma translate_on
endmodule