// Copyright (c) 2019 Bluespec, Inc.  All Rights Reserved
//
//-
// RVFI_DII + CHERI modifications:
//     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 Test_PLIC;

// ================================================================
// Standalone unit-test testbench for PLIC.
//
// ================================================================
// Bluespec lib imports

import  ConfigReg    :: *;
import  Vector       :: *;
import  FIFOF        :: *;
import  GetPut       :: *;
import  ClientServer :: *;
import  Connectable  :: *;
import  Assert       :: *;
import  StmtFSM      :: *;

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

import  Cur_Cycle  :: *;
import  GetPut_Aux :: *;
import  Semi_FIFOF :: *;

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

import AXI4_Types   :: *;
import AXI4_Fabric  :: *;
import Fabric_Defs  :: *;    // for Wd_Id, Wd_Addr, Wd_Data, Wd_User
import SoC_Map      :: *;

import PLIC                :: *;
import PLIC_16_CoreNumX2_7 :: *;

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

Integer n_external_interrupt_sources = valueOf (N_External_Interrupt_Sources);
typedef TAdd #(1, N_External_Interrupt_Sources) N_Interrupt_Sources;
typedef Bit #(TLog #(N_Interrupt_Sources))  Source_Id;

Integer plic_n_targets               = valueOf (PLIC_N_Targets);
Integer target_0 = 0;
Integer target_1 = 1;

Integer plic_max_priority            = valueOf (PLIC_Max_Priority);
typedef Bit #(TLog #(TAdd #(1, PLIC_Max_Priority)))  Priority;

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

(* synthesize *)
module mkTest_PLIC (Empty);

   // System address map
   SoC_Map_IFC  soc_map  <- mkSoC_Map;

   // PLIC (Platform-Level Interrupt Controller)
   PLIC_IFC_16_CoreNumX2_7  plic <- mkPLIC_16_CoreNumX2_7;

   // Master transactor through which to read/write PLIC regs
   AXI4_Master_Xactor_IFC #(Wd_Id, Wd_Addr, Wd_Data, Wd_User) master_xactor <- mkAXI4_Master_Xactor;

   Vector #(N_Interrupt_Sources, Reg #(Bool)) vrg_irqs <- replicateM (mkReg (False));

   // ================================================================
   // AXI4 interactions

   FIFOF #(Fabric_Addr) f_read_addr <- mkFIFOF;
   FIFOF #(Fabric_Data) f_read_data <- mkFIFOF;

   function Action fa_read_req (Integer addr);
      action
         let fabric_addr = soc_map.m_plic_addr_base + fromInteger (addr);

         let mem_req_rd_addr = AXI4_Rd_Addr {arid:     fabric_default_id,
                                             araddr:   fabric_addr,
                                             arlen:    0,           // burst len = arlen+1
                                             arsize:   zeroExtend (axsize_4),
                                             arburst:  fabric_default_burst,
                                             arlock:   fabric_default_lock,
                                             arcache:  fabric_default_arcache,
                                             arprot:   fabric_default_prot,
                                             arqos:    fabric_default_qos,
                                             arregion: fabric_default_region,
                                             aruser:   fabric_default_user};
         master_xactor.i_rd_addr.enq (mem_req_rd_addr);
         f_read_addr.enq (fabric_addr);
      endaction
   endfunction

   function Action fa_read_rsp;
      action
         let fabric_addr <- pop (f_read_addr);
         let rd_data <- pop_o (master_xactor.o_rd_data);
         if (rd_data.rresp != axi4_resp_okay) begin
            $display ("ERROR: fa_read_rsp: fabric response error");
            $display ("    ", fshow (rd_data));
         end
         let x = (((valueOf (Wd_Data) == 64) && ((fabric_addr & 'h7) == 'h4))
                  ? (rd_data.rdata >> 32)
                  : rd_data.rdata);
         f_read_data.enq (x);
      endaction
   endfunction

   function Action fa_write_req (Integer addr, Fabric_Data data);
      action
         let fabric_addr = soc_map.m_plic_addr_base + fromInteger (addr);

         let mem_req_wr_addr = AXI4_Wr_Addr {awid:     fabric_default_id,
                                             awaddr:   fabric_addr,
                                             awlen:    0,           // burst len = awlen+1
                                             awsize:   zeroExtend (axsize_4),
                                             awburst:  fabric_default_burst,
                                             awlock:   fabric_default_lock,
                                             awcache:  fabric_default_awcache,
                                             awprot:   fabric_default_prot,
                                             awqos:    fabric_default_qos,
                                             awregion: fabric_default_region,
                                             awuser:   fabric_default_user};

         let x = (((valueOf (Wd_Data) == 64) && ((fabric_addr & 'h7) == 'h4))
                  ? (data << 32)
                  : data);
         let mem_req_wr_data = AXI4_Wr_Data {wid:    fabric_default_id,
                                             wdata:  x,
                                             wstrb:  '1,
                                             wlast:  True,
                                             wuser:  fabric_default_user};
         master_xactor.i_wr_addr.enq (mem_req_wr_addr);
         master_xactor.i_wr_data.enq (mem_req_wr_data);
      endaction
   endfunction

   function Action fa_write_rsp;
      action
         let wr_resp <- pop_o (master_xactor.o_wr_resp);

         if (wr_resp.bresp != axi4_resp_okay) begin
            $display ("ERROR: rl_discard_write_rsp: fabric response error");
            $display ("    ", fshow (wr_resp));
         end
      endaction
   endfunction

   // ================================================================
   // Help functions to interact with PLIC at an "API" level

   function Action fa_print_plic_eips ();
      action
         $write ("PLIC.v_target  eip =");
         $write (" ", fshow (plic.v_targets [0].m_eip));
         $write (" ", fshow (plic.v_targets [1].m_eip));
         $display ("");
      endaction
   endfunction

   function Stmt fstmt_set_source_priority (Integer src, Priority prio);
      return seq
                fa_write_req (src * 4, zeroExtend (prio));
                fa_write_rsp;
             endseq;
   endfunction

   function Stmt fstmt_set_target_ies (Integer target, Bit #(32) ies);
      return seq
                fa_write_req ('h2000 + (target * 'h80), zeroExtend (ies));
                fa_write_rsp;
             endseq;
   endfunction

   function Stmt fstmt_set_target_threshold (Integer target, Priority threshold);
      return seq
                fa_write_req ('h20_0000 + (target * 'h1000), zeroExtend (threshold));
                fa_write_rsp;
             endseq;
   endfunction

   function Stmt fstmt_claim (Integer target);
      return seq
                fa_read_req ('h20_0004 + (target * 'h1000));
                fa_read_rsp;
                action
                   let x <- pop (f_read_data);
                   $display ("fstmt_claim: PLIC returned %0d", x);
                endaction
             endseq;
   endfunction

   function Stmt fstmt_complete (Integer target, Source_Id source_id);
      return seq
                fa_write_req ('h20_0004 + (target * 'h1000), zeroExtend (source_id));
                fa_write_rsp;
             endseq;
   endfunction

   // ================================================================
   // BEHAVIOR

   mkConnection (master_xactor.axi_side,  plic.axi4_slave);

   // Drive all interrupt requests from local regs
   for (Integer j = 0; j < n_external_interrupt_sources; j = j + 1)
      rule rl_drive_irq;
         plic.v_sources [j].m_interrupt_req (vrg_irqs [j]);
      endrule

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

   Stmt init = seq
                  action
                     $display ("Initializing PLIC");
                     plic.server_reset.request.put (?);
                  endaction
                  action
                     let rsp <- plic.server_reset.response.get;
                     plic.set_addr_map (zeroExtend (soc_map.m_plic_addr_base),
                                       zeroExtend (soc_map.m_plic_addr_lim));
                  endaction
                  fstmt_set_source_priority (1,  0);
                  fstmt_set_source_priority (2,  0);
                  fstmt_set_source_priority (3,  0);
                  fstmt_set_source_priority (4,  0);

                  fstmt_set_source_priority (5,  0);
                  fstmt_set_source_priority (6,  0);
                  fstmt_set_source_priority (7,  0);
                  fstmt_set_source_priority (8,  0);

                  fstmt_set_source_priority (9,  0);
                  fstmt_set_source_priority (10, 0);
                  fstmt_set_source_priority (11, 0);
                  fstmt_set_source_priority (12, 0);

                  fstmt_set_source_priority (13, 0);
                  fstmt_set_source_priority (14, 0);
                  fstmt_set_source_priority (15, 0);
                  fstmt_set_source_priority (16, 0);

                  fstmt_set_target_ies (target_0, 0);
                  fstmt_set_target_ies (target_1, 0);

                  fstmt_set_target_threshold (target_0, 7);
                  fstmt_set_target_threshold (target_1, 7);
                  delay (5);
                  $display ("Finished Initializing PLIC");
               endseq;

   Stmt test1 = seq
                   $display (">---------------- TEST 1");
                   plic.set_verbosity (1);
                   fstmt_set_source_priority (5, 4);

                   fstmt_set_target_ies (target_0, 'b10_0000);    // bit 5
                   fstmt_set_target_threshold (target_0, 4);

                   fstmt_set_target_ies (target_1, 'b10_0000);    // bit 5
                   fstmt_set_target_threshold (target_1, 2);

                   plic.show_PLIC_state;
                   fa_print_plic_eips;

                   vrg_irqs [5] <= True;
                   delay (2);
                   plic.show_PLIC_state;
                   fa_print_plic_eips;

                   fstmt_set_target_threshold (target_0, 3);
                   plic.show_PLIC_state;
                   fa_print_plic_eips;

                   fstmt_claim (target_1);
                   plic.show_PLIC_state;
                   fa_print_plic_eips;

                   fstmt_complete (target_1, 5);
                   plic.show_PLIC_state;
                   fa_print_plic_eips;
                endseq;

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

   mkAutoFSM (seq
                 init;
                 test1;
                 $finish (0);
              endseq);


endmodule

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

endpackage
