// Copyright (c) 2016-2020 Bluespec, Inc. All Rights Reserved.

//
//-
// RVFI_DII + CHERI modifications:
//     Copyright (c) 2020 Alexandre Joannou
//     Copyright (c) 2020 Peter Rugg
//     Copyright (c) 2020 Jonathan Woodruff
//     All rights reserved.
//
//     This software was developed by SRI International and the University of
//     Cambridge Computer Laboratory (Department of Computer Science and
//     Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the
//     DARPA SSITH research programme.
//
//     This work was supported by NCSC programme grant 4212611/RFA 15971 ("SafeBet").
//-

package SoC_Top;

// ================================================================
// This package is the SoC "top-level".

// (Note: there will be further layer(s) above this for
//    simulation top-level, FPGA top-level, etc.)

// ================================================================
// Exports

export SoC_Top_IFC (..), mkSoC_Top;

// ================================================================
// BSV library imports

import FIFOF         :: *;
import GetPut        :: *;
import ClientServer  :: *;
import Connectable   :: *;
import Memory        :: *;
import Clocks        :: *;
import Vector        :: *;

// ----------------
// BSV additional libs

import Cur_Cycle   :: *;
import GetPut_Aux  :: *;
import Routable    :: *;
import BlueBasics  :: *;
import BlueAXI4    :: *;

// ================================================================
// Project imports

import Fabric_Defs :: *;
import SoC_Map     :: *;

// SoC components (CPU, mem, and IPs)

import WindCoreInterface :: *;
import CoreW     :: *;
import PLIC      :: *;

import Boot_ROM       :: *;
import Mem_Controller :: *;
import UART_Model     :: *;

`ifdef INCLUDE_CAMERA_MODEL
import Camera_Model   :: *;
`endif

`ifdef INCLUDE_ACCEL0
import AXI4_Accel_IFC :: *;
import AXI4_Accel     :: *;
`endif

`ifdef INCLUDE_TANDEM_VERIF
import TV_Info :: *;
`endif

`ifdef RVFI_DII
import RVFI_DII_Types :: *;
import ProcTypes :: *;
`endif

`ifdef INCLUDE_GDB_CONTROL
import Debug_Module     :: *;
`endif

// ================================================================
// The outermost interface of the SoC

interface SoC_Top_IFC;

`ifdef INCLUDE_GDB_CONTROL
  interface AXI4Lite_Slave #(21, 32, 0, 0, 0, 0, 0) debug_subordinate;
`endif

`ifdef INCLUDE_TANDEM_VERIF
   // To tandem verifier
   interface Get #(Info_CPU_to_Verifier) tv_verifier_info_get;
`elsif RVFI_DII
   interface Toooba_RVFI_DII_Server rvfi_dii_server;
`endif

   // External real memory
   interface MemoryClient #(Bits_per_Raw_Mem_Addr, Bits_per_Raw_Mem_Word)  to_raw_mem;

   // UART0 to external console
   interface Get #(Bit #(8)) get_to_console;
   interface Put #(Bit #(8)) put_from_console;

   // Catch-all status; return-value can identify the origin (0 = none)
   (* always_ready *)
   method Bit #(8) status;

   // Start CPU execution
   // For ISA tests: watch memory writes to <tohost> addr
   method Action start (Fabric_Addr  tohost_addr, Fabric_Addr  fromhost_addr);
endinterface

// ================================================================
// Local types and constants

typedef enum {SOC_START,
              SOC_RESETTING,
              SOC_IDLE} SoC_State
deriving (Bits, Eq, FShow);

// ================================================================
// The module

(* synthesize *)
module mkSoC_Top #(Reset dm_power_on_reset)
                 (SoC_Top_IFC);
   Integer verbosity = 2;    // Normally 0; non-zero for debugging

   Reg #(SoC_State) rg_state <- mkReg (SOC_START);

   // SoC address map specifying base and limit for memories, IPs, etc.
   SoC_Map_IFC soc_map <- mkSoC_Map;

   // Core: CPU + Near_Mem_IO (CLINT) + PLIC + Debug module (optional) + TV (optional)
   // The Debug Module has its own RST_N reset signal (which comes
   // from outside this module as a paramter)
`ifdef RVFI_DII
   match {.core_rvfi_dii_server , .coreifc} <- mkCoreW;
   CoreW_IFC #(N_External_Interrupt_Sources) corew = coreifc;
`else
   CoreW_IFC #(N_External_Interrupt_Sources) corew <- mkCoreW;
`endif

   // SoC Boot ROM
   Boot_ROM_IFC  boot_rom <- mkBoot_ROM;
   // AXI4 Deburster in front of Boot_ROM
   AXI4_Shim#(Wd_SId, Wd_Addr, Wd_Data_Periph, 0, 0, 0, 0, 0)
      boot_rom_axi4_deburster <- mkBurstToNoBurst;

   // SoC Memory
   Mem_Controller_IFC  mem0_controller <- mkMem_Controller;
   // AXI4 Deburster in front of SoC Memory
   AXI4_Shim#(Wd_SId, Wd_Addr, Wd_Data_Periph, 0, 0, 0, 0, 0)
      mem0_controller_axi4_deburster <- mkBurstToNoBurst;

   // AXI4 Narrower Master in front of cached memory master
   NumProxy #(4) proxyInDepth = error ("don't look inside a proxy");
   NumProxy #(4) proxyOutDepth = error ("don't look inside a proxy");
   Tuple2 #( AXI4_Slave #(TAdd#(Wd_MId,1), Wd_Addr, Wd_Data, 0, 0, 0, 0, 0)
           , AXI4_Master #(TAdd#(Wd_MId,1), Wd_Addr, Wd_Data_Periph, 0, 0, 0, 0, 0) )
     wideS_narrowM <- mkAXI4DataWidthShim_WideToNarrow (proxyInDepth, proxyOutDepth);
   match {.wideS, .narrowM} = wideS_narrowM;
   AXI4_Shim#(TAdd #(Wd_MId, 1), Wd_Addr, Wd_Data, 0, 0, 0, 0, 0)
       manager_0_deburster <- mkBurstToNoBurst;
   mkConnection(corew.manager_0, manager_0_deburster.slave);
   mkConnection(manager_0_deburster.master, wideS);

   // SoC IPs
   UART_IFC   uart0  <- mkUART;

`ifdef INCLUDE_ACCEL0
   // Accel0 master to fabric
   AXI4_Accel_IFC  accel0 <- mkAXI4_Accel;
`endif

   // ----------------
   // SoC fabric master connections
   // Note: see 'SoC_Map' for 'master_num' definitions

   Vector#(Num_Masters, AXI4_Master #(TAdd#(Wd_MId,1), Wd_Addr, Wd_Data_Periph,
                                      0, 0, 0, 0, 0))
      master_vector = newVector;

   // CPU IMem master to fabric
   master_vector[imem_master_num] = narrowM;

   // CPU DMem master to fabric
   master_vector[dmem_master_num] = corew.manager_1;

   // ----------------
   // SoC fabric slave connections
   // Note: see 'SoC_Map' for 'slave_num' definitions

   Vector#(Num_Slaves, AXI4_Slave #(Wd_SId, Wd_Addr, Wd_Data_Periph,
                                    0, 0, 0, 0, 0))
      slave_vector = newVector;
   Vector#(Num_Slaves, Range#(Wd_Addr)) route_vector = newVector;

   // Fabric to Boot ROM
   mkConnection(boot_rom_axi4_deburster.master, boot_rom.slave);
   slave_vector[boot_rom_slave_num] = boot_rom_axi4_deburster.slave;
   route_vector[boot_rom_slave_num] = soc_map.m_boot_rom_addr_range;

   // Fabric to Mem Controller
   mkConnection(mem0_controller_axi4_deburster.master, mem0_controller.slave);
   slave_vector[mem0_controller_slave_num] = mem0_controller_axi4_deburster.slave;
   route_vector[mem0_controller_slave_num] = soc_map.m_mem0_controller_addr_range;

   // Fabric to UART0
   slave_vector[uart0_slave_num] = zero_AXI4_Slave_user(uart0.slave);
   route_vector[uart0_slave_num] = soc_map.m_uart0_addr_range;

`ifdef INCLUDE_ACCEL0
   // Fabric to accel0
   slave_vector[accel0_slave_num] = zero_AXI4_Slave_user(accel0.slave);
   route_vector[accel0_slave_num] = soc_map.m_accel0_addr_range;
`endif

`ifdef HTIF_MEMORY
   AXI4_Slave_IFC#(Wd_Id, Wd_Addr, Wd_Data, Wd_User) htif <- mkAxi4LRegFile(bytes_per_htif);

   slave_vector[htif_slave_num] = htif;
   route_vector[htif_slave_num] = soc_map.m_htif_addr_range;
`endif

   // SoC Fabric
   let bus <- mkAXI4Bus (routeFromMappingTable(route_vector),
                         master_vector, slave_vector);

   // ----------------
   // Connect interrupt sources for CPU external interrupt request inputs.

   (* fire_when_enabled, no_implicit_conditions *)
   rule rl_connect_external_interrupt_requests;
      Bool intr = uart0.intr;

      // UART
      corew.irq [irq_num_uart0].put (intr);
      Integer last_irq_num = irq_num_uart0;

`ifdef INCLUDE_ACCEL0
      Bool intr_accel0 = accel0.interrupt_req;
      corew.irq [irq_num_accel0].put (intr_accel0);
      last_irq_num = irq_num_accel0;
`endif

      // Tie off remaining interrupt request lines (1..N)
      for (Integer j = last_irq_num + 1; j < valueOf (N_External_Interrupt_Sources); j = j + 1)
         corew.irq [j].put (False);

      // Non-maskable interrupt request. [Tie-off; TODO: connect to genuine sources]
      corew.nmirq.put (False);
   endrule

   // ================================================================
   // MODULE INITIALIZATIONS

   function Action fa_reset_start_actions;
      action
         mem0_controller.server_reset.request.put (?);
         uart0.server_reset.request.put (?);
      endaction
   endfunction

   function Action fa_reset_complete_actions;
      action
         let mem0_controller_rsp <- mem0_controller.server_reset.response.get;
         let uart0_rsp           <- uart0.server_reset.response.get;
         // Initialize address maps of slave IPs
         boot_rom.set_addr_map (rangeBase(soc_map.m_boot_rom_addr_range),
                                rangeTop(soc_map.m_boot_rom_addr_range));

         mem0_controller.set_addr_map (rangeBase(soc_map.m_mem0_controller_addr_range),
                                       rangeTop(soc_map.m_mem0_controller_addr_range));

         uart0.set_addr_map (rangeBase(soc_map.m_uart0_addr_range),
                             rangeTop(soc_map.m_uart0_addr_range));

`ifdef INCLUDE_ACCEL0
         accel0.init (fabric_default_id,
                      soc_map.m_accel0_addr_range.base,
                      rangeTop(soc_map.m_accel0_addr_range));
`endif

         if (verbosity != 0) begin
            $display ("  SoC address map:");
            $display ("  Boot ROM:        0x%0h .. 0x%0h",
                      rangeBase(soc_map.m_boot_rom_addr_range),
                      rangeTop(soc_map.m_boot_rom_addr_range));
            $display ("  Mem0 Controller: 0x%0h .. 0x%0h",
                      rangeBase(soc_map.m_mem0_controller_addr_range),
                      rangeTop(soc_map.m_mem0_controller_addr_range));
            $display ("  UART0:           0x%0h .. 0x%0h",
                      rangeBase(soc_map.m_uart0_addr_range),
                      rangeTop(soc_map.m_uart0_addr_range));
         end
      endaction
   endfunction

   // ----------------
   // Initial reset

   rule rl_reset_start_initial (rg_state == SOC_START);
      fa_reset_start_actions;
      rg_state <= SOC_RESETTING;

      $display ("%0d: %m.rl_reset_start_initial ...", cur_cycle);
   endrule

   rule rl_reset_complete_initial (rg_state == SOC_RESETTING);
      fa_reset_complete_actions;
      rg_state <= SOC_IDLE;

      $display ("%0d: %m.rl_reset_complete_initial", cur_cycle);
   endrule

   // ================================================================
   // INTERFACE

   // To external controller (E.g., GDB)
`ifdef INCLUDE_GDB_CONTROL
  interface debug_subordinate = corew.debug_subordinate;
`endif

`ifdef INCLUDE_TANDEM_VERIF
   // To tandem verifier
   interface tv_verifier_info_get = corew.tv_verifier_info_get;
`elsif RVFI_DII
   interface rvfi_dii_server = core_rvfi_dii_server;
`endif

   // External real memory
   interface to_raw_mem = mem0_controller.to_raw_mem;

   // UART to external console
   interface get_to_console   = uart0.get_to_console;
   interface put_from_console = uart0.put_from_console;

   // Catch-all status; return-value can identify the origin (0 = none)
   method Bit #(8) status;
      return mem0_controller.status;
   endmethod

   // Start CPU execution
   // For ISA tests: watch memory writes to <tohost> addr
   method Action start (Fabric_Addr tohost_addr, Fabric_Addr fromhost_addr)
      if (rg_state == SOC_IDLE);
      Bool watch_tohost = (tohost_addr != 0);
      mem0_controller.set_watch_tohost (watch_tohost, tohost_addr);
      Bool is_running = True;
      corew.controlStatusServer.request.put
        (ReleaseAndSetToHostAddrReq (tohost_addr));
      $display ("%0d: %m.method start (tohost %0h, fromhost %0h)",
                cur_cycle, tohost_addr, fromhost_addr);
   endmethod
endmodule: mkSoC_Top

// ================================================================

endpackage
