// Copyright 2021 ETH Zurich and University of Bologna.
// Solderpad Hardware License, Version 0.51, see LICENSE for details.
// SPDX-License-Identifier: SHL-0.51

`include "axi/assign.svh"
`include "common_cells/registers.svh"

module mempool_system
  import mempool_pkg::*;
#(
  // TCDM
  parameter addr_t       TCDMBaseAddr  = 32'h0000_0000,
  // Boot address
  parameter addr_t       BootAddr      = 32'h0000_0000
) (
  input logic                clk_i,
  input logic                rst_ni,

  input  logic               fetch_en_i,
  output logic               eoc_valid_o,
  output logic               busy_o,

  output axi_system_req_t    mst_req_o,
  input  axi_system_resp_t   mst_resp_i,

  input  axi_system_req_t    slv_req_i,
  output axi_system_resp_t   slv_resp_o
);

  import axi_pkg::xbar_cfg_t;
  import axi_pkg::xbar_rule_32_t;
  import axi_pkg::atop_t;
  localparam TCDMSize = NumBanks * TCDMSizePerBank;

  /*********
   *  AXI  *
   *********/

  localparam NumAXIMasters = NumGroups + 1; // +1 because the external host is also a master
  localparam NumAXISlaves  = 4;            // control regs, l2 memory, bootrom and the external mst ports
  localparam NumRules      = NumAXISlaves - 1;

  typedef enum logic [$clog2(NumAXISlaves) - 1:0] {
    CtrlRegisters,
    L2Memory,
    Bootrom,
    External
  } axi_slave_target;

  axi_tile_req_t    [NumAXIMasters - 1:0] axi_mst_req;
  axi_tile_resp_t   [NumAXIMasters - 1:0] axi_mst_resp;
  axi_system_req_t  [NumAXISlaves - 1:0]  axi_mem_req;
  axi_system_resp_t [NumAXISlaves - 1:0]  axi_mem_resp;
  logic             [NumCores - 1:0]      wake_up;
  logic             [DataWidth - 1:0]     eoc;

  localparam xbar_cfg_t XBarCfg = '{
    NoSlvPorts         : NumAXIMasters,
    NoMstPorts         : NumAXISlaves,
    MaxMstTrans        : 4,
    MaxSlvTrans        : 4,
    FallThrough        : 1'b0,
    LatencyMode        : axi_pkg::CUT_MST_PORTS,
    AxiIdWidthSlvPorts : AxiTileIdWidth,
    AxiIdUsedSlvPorts  : AxiTileIdWidth,
    UniqueIds          : 0,
    AxiAddrWidth       : AddrWidth,
    AxiDataWidth       : AxiDataWidth,
    NoAddrRules        : NumRules
  };

  /*********************
   *  MemPool Cluster  *
   ********************/

  mempool_cluster #(
    .TCDMBaseAddr(TCDMBaseAddr),
    .BootAddr    (BootAddr    )
  ) i_mempool_cluster (
    .clk_i         (clk_i                          ),
    .rst_ni        (rst_ni                         ),
    .wake_up_i     (wake_up                        ),
    .testmode_i    (1'b0                           ),
    .scan_enable_i (1'b0                           ),
    .scan_data_i   (1'b0                           ),
    .scan_data_o   (/* Unused */                   ),
    .axi_mst_req_o (axi_mst_req[NumAXIMasters-2:0] ),
    .axi_mst_resp_i(axi_mst_resp[NumAXIMasters-2:0])
  );

  /**********************
   *  AXI Interconnect  *
   **********************/

  localparam addr_t CtrlRegistersBaseAddr = 32'h4000_0000;
  localparam addr_t CtrlRegistersEndAddr  = 32'h4000_FFFF;
  localparam addr_t L2MemoryBaseAddr      = `ifdef L2_BASE `L2_BASE `else 32'h8000_0000 `endif;
  localparam addr_t L2MemoryEndAddr       = L2MemoryBaseAddr + L2Size;
  localparam addr_t BootromBaseAddr       = 32'hA000_0000;
  localparam addr_t BootromEndAddr        = 32'hA000_FFFF;

  xbar_rule_32_t [NumRules - 1:0] xbar_routing_rules;
  assign xbar_routing_rules = '{
    '{idx: CtrlRegisters, start_addr: CtrlRegistersBaseAddr, end_addr: CtrlRegistersEndAddr},
    '{idx: L2Memory, start_addr: L2MemoryBaseAddr, end_addr: L2MemoryEndAddr},
    '{idx: Bootrom, start_addr: BootromBaseAddr, end_addr: BootromEndAddr}
  };

  axi_xbar #(
    .Cfg          (XBarCfg          ),
    .slv_aw_chan_t(axi_tile_aw_t    ),
    .mst_aw_chan_t(axi_system_aw_t  ),
    .w_chan_t     (axi_tile_w_t     ),
    .slv_b_chan_t (axi_tile_b_t     ),
    .mst_b_chan_t (axi_system_b_t   ),
    .slv_ar_chan_t(axi_tile_ar_t    ),
    .mst_ar_chan_t(axi_system_ar_t  ),
    .slv_r_chan_t (axi_tile_r_t     ),
    .mst_r_chan_t (axi_system_r_t   ),
    .slv_req_t    (axi_tile_req_t   ),
    .slv_resp_t   (axi_tile_resp_t  ),
    .mst_req_t    (axi_system_req_t ),
    .mst_resp_t   (axi_system_resp_t),
    .rule_t       (xbar_rule_32_t   )
  ) i_xbar (
    .clk_i                (clk_i                    ),
    .rst_ni               (rst_ni                   ),
    .test_i               (1'b0                     ),
    .slv_ports_req_i      (axi_mst_req              ),
    .slv_ports_resp_o     (axi_mst_resp             ),
    .mst_ports_req_o      (axi_mem_req              ),
    .mst_ports_resp_i     (axi_mem_resp             ),
    .addr_map_i           (xbar_routing_rules       ),
    .en_default_mst_port_i('1                       ), // default all slave ports to master port External
    .default_mst_port_i   ({NumAXIMasters{External}})
  );

  /********
   *  L2  *
   ********/
  localparam L2NumWords  = L2Size / L2BeWidth;
  localparam L2AddrWidth = $clog2(L2NumWords);

  // Memory
  logic      mem_req;
  logic      mem_rvalid;
  addr_t     mem_addr;
  axi_data_t mem_wdata;
  axi_strb_t mem_strb;
  logic      mem_we;
  axi_data_t mem_rdata;

  axi2mem #(
    .axi_req_t  (axi_system_req_t ),
    .axi_resp_t (axi_system_resp_t),
    .AddrWidth  (AddrWidth),
    .DataWidth  (AxiDataWidth),
    .IdWidth    (AxiSystemIdWidth),
    .NumBanks   (1),
    .BufDepth   (2)
  ) i_axi2mem_l2mem (
    .clk_i        (clk_i),
    .rst_ni       (rst_ni),

    .busy_o       (/*unsused*/),

    .axi_req_i    (axi_mem_req[L2Memory] ),
    .axi_resp_o   (axi_mem_resp[L2Memory]),

    .mem_req_o    (mem_req),
    .mem_gnt_i    (mem_req),
    .mem_addr_o   (mem_addr),
    .mem_wdata_o  (mem_wdata),
    .mem_strb_o   (mem_strb),
    .mem_atop_o   (/*unused*/),
    .mem_we_o     (mem_we),
    .mem_rvalid_i (mem_rvalid),
    .mem_rdata_i  (mem_rdata)
  );

  `FF(mem_rvalid, mem_req, 1'b0, clk_i, rst_ni)

  tc_sram #(
    .DataWidth(AxiDataWidth),
    .NumWords (L2NumWords  ),
    .NumPorts (1           )
  ) l2_mem (
    .clk_i  (clk_i                                ),
    .rst_ni (rst_ni                               ),
    .req_i  (mem_req                              ),
    .we_i   (mem_we                               ),
    .addr_i (mem_addr[L2ByteOffset +: L2AddrWidth]),
    .wdata_i(mem_wdata                            ),
    .be_i   (mem_strb                             ),
    .rdata_o(mem_rdata                            )
  );

  /*************
   *  Bootrom  *
   *************/

  // Memory
  logic      bootrom_req;
  logic      bootrom_rvalid;
  addr_t     bootrom_addr;
  axi_data_t bootrom_rdata;

  axi2mem #(
    .axi_req_t  (axi_system_req_t ),
    .axi_resp_t (axi_system_resp_t),
    .AddrWidth  (AddrWidth),
    .DataWidth  (AxiDataWidth),
    .IdWidth    (AxiSystemIdWidth),
    .NumBanks   (1),
    .BufDepth   (2)
  ) i_axi2mem_bootrom (
    .clk_i        (clk_i),
    .rst_ni       (rst_ni),

    .busy_o       (/*unsused*/),

    .axi_req_i    (axi_mem_req[Bootrom] ),
    .axi_resp_o   (axi_mem_resp[Bootrom]),

    .mem_req_o    (bootrom_req),
    .mem_gnt_i    (bootrom_req),
    .mem_addr_o   (bootrom_addr),
    .mem_wdata_o  (/*unused*/),
    .mem_strb_o   (/*unused*/),
    .mem_atop_o   (/*unused*/),
    .mem_we_o     (/*unused*/),
    .mem_rvalid_i (bootrom_rvalid),
    .mem_rdata_i  (bootrom_rdata)
  );

  `FF(bootrom_rvalid, bootrom_req, 1'b0, clk_i, rst_ni)

  bootrom i_bootrom (
    .clk_i  (clk_i        ),
    .req_i  (bootrom_req  ),
    .addr_i (bootrom_addr ),
    .rdata_o(bootrom_rdata)
  );

  /***********************
   *  Control Registers  *
   ***********************/

  axi_ctrl_req_t    axi_ctrl_req;
  axi_ctrl_resp_t   axi_ctrl_resp;
  axi_lite_slv_req_t  axi_lite_ctrl_registers_req;
  axi_lite_slv_resp_t axi_lite_ctrl_registers_resp;

  axi_dw_converter #(
    .AxiMaxReads         (1                ), // Number of outstanding reads
    .AxiSlvPortDataWidth (AxiDataWidth     ), // Data width of the slv port
    .AxiMstPortDataWidth (AxiLiteDataWidth ), // Data width of the mst port
    .AxiAddrWidth        (AddrWidth        ), // Address width
    .AxiIdWidth          (AxiSystemIdWidth ), // ID width
    .aw_chan_t           (axi_system_aw_t  ), // AW Channel Type
    .mst_w_chan_t        (axi_ctrl_w_t     ), //  W Channel Type for the mst port
    .slv_w_chan_t        (axi_system_w_t   ), //  W Channel Type for the slv port
    .b_chan_t            (axi_system_b_t   ), //  B Channel Type
    .ar_chan_t           (axi_system_ar_t  ), // AR Channel Type
    .mst_r_chan_t        (axi_ctrl_r_t     ), //  R Channel Type for the mst port
    .slv_r_chan_t        (axi_system_r_t   ), //  R Channel Type for the slv port
    .axi_mst_req_t       (axi_ctrl_req_t   ), // AXI Request Type for mst ports
    .axi_mst_resp_t      (axi_ctrl_resp_t  ), // AXI Response Type for mst ports
    .axi_slv_req_t       (axi_system_req_t ), // AXI Request Type for slv ports
    .axi_slv_resp_t      (axi_system_resp_t)  // AXI Response Type for slv ports
  ) i_axi_dw_converter_ctrl (
    .clk_i      (clk_i                      ),
    .rst_ni     (rst_ni                     ),
    // Slave interface
    .slv_req_i  (axi_mem_req[CtrlRegisters] ),
    .slv_resp_o (axi_mem_resp[CtrlRegisters]),
    // Master interface
    .mst_req_o  (axi_ctrl_req               ),
    .mst_resp_i (axi_ctrl_resp              )
  );

  axi_to_axi_lite #(
    .AxiAddrWidth   (AddrWidth          ),
    .AxiDataWidth   (AxiLiteDataWidth   ),
    .AxiIdWidth     (AxiSystemIdWidth   ),
    .AxiUserWidth   (1                  ),
    .AxiMaxReadTxns (1                  ),
    .AxiMaxWriteTxns(1                  ),
    .FallThrough    (1'b0               ),
    .full_req_t     (axi_ctrl_req_t     ),
    .full_resp_t    (axi_ctrl_resp_t    ),
    .lite_req_t     (axi_lite_slv_req_t ),
    .lite_resp_t    (axi_lite_slv_resp_t)
  ) i_axi_to_axi_lite (
    .clk_i     (clk_i                       ),
    .rst_ni    (rst_ni                      ),
    .test_i    (1'b0                        ),
    .slv_req_i (axi_ctrl_req                ),
    .slv_resp_o(axi_ctrl_resp               ),
    .mst_req_o (axi_lite_ctrl_registers_req ),
    .mst_resp_i(axi_lite_ctrl_registers_resp)
  );

  ctrl_registers #(
    .NumRegs        (5                  ),
    .TCDMBaseAddr   (TCDMBaseAddr       ),
    .TCDMSize       (TCDMSize           ),
    .NumCores       (NumCores           ),
    .axi_lite_req_t (axi_lite_slv_req_t ),
    .axi_lite_resp_t(axi_lite_slv_resp_t)
  ) i_ctrl_registers (
    .clk_i                (clk_i                       ),
    .rst_ni               (rst_ni                      ),
    .axi_lite_slave_req_i (axi_lite_ctrl_registers_req ),
    .axi_lite_slave_resp_o(axi_lite_ctrl_registers_resp),
    .tcdm_start_address_o (/* Unused */                ),
    .tcdm_end_address_o   (/* Unused */                ),
    .num_cores_o          (/* Unused */                ),
    .wake_up_o            (wake_up                     ),
    .eoc_o                (/* Unused */                ),
    .eoc_valid_o          (eoc_valid_o                 )
  );

  assign busy_o = 1'b0;

  // From MemPool to the Host
  assign mst_req_o              = axi_mem_req[External];
  assign axi_mem_resp[External] = mst_resp_i;
  // From the Host to MemPool
  axi_id_remap #(
    .AxiSlvPortIdWidth   (AxiSystemIdWidth ),
    .AxiSlvPortMaxUniqIds(1                ),
    .AxiMaxTxnsPerId     (1                ),
    .AxiMstPortIdWidth   (AxiTileIdWidth   ),
    .slv_req_t           (axi_system_req_t ),
    .slv_resp_t          (axi_system_resp_t),
    .mst_req_t           (axi_tile_req_t   ),
    .mst_resp_t          (axi_tile_resp_t  )
  ) i_axi_id_remap (
    .clk_i     (clk_i                        ),
    .rst_ni    (rst_ni                       ),
    .slv_req_i (slv_req_i                    ),
    .slv_resp_o(slv_resp_o                   ),
    .mst_req_o (axi_mst_req[NumAXIMasters-1] ),
    .mst_resp_i(axi_mst_resp[NumAXIMasters-1])
  );

endmodule : mempool_system