// Copyright 2017 ETH Zurich and University of Bologna.
// Copyright and related rights are licensed under the Solderpad Hardware
// License, Version 0.51 (the “License”); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
// or agreed to in writing, software, hardware and materials distributed under
// this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

////////////////////////////////////////////////////////////////////////////////
// Engineer:       Sven Stucki - svstucki@student.ethz.ch                     //
//                                                                            //
// Additional contributions by:                                               //
//                 Andreas Traber - atraber@iis.ee.ethz.ch                    //
//                 Davide Schiavone - pschiavo@iis.ee.ethz.ch                 //
//                                                                            //
// Design Name:    Control and Status Registers                               //
// Project Name:   zero-riscy                                                 //
// Language:       SystemVerilog                                              //
//                                                                            //
// Description:    Control and Status Registers (CSRs) loosely following the  //
//                 RiscV draft priviledged instruction set spec (v1.9)        //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////

`include "zeroriscy_config.sv"

import zeroriscy_defines::*;

`ifndef PULP_FPGA_EMUL
 `ifdef SYNTHESIS
  `define ASIC_SYNTHESIS
 `endif
`endif

module zeroriscy_cs_registers
  #(
    parameter N_EXT_CNT    = 0
    )
   (
    // Clock and Reset
    input logic                 clk,
    input logic                 rst_n,

    // Core and Cluster ID
    input logic [3:0]           core_id_i,
    input logic [5:0]           cluster_id_i,

    // Used for boot address
    input logic [31:0]          boot_addr_i,

    // Interface to registers (SRAM like)
    input logic                 csr_access_i,
    input logic [11:0]          csr_addr_i,
    input logic [31:0]          csr_wdata_i,
    input logic [1:0]           csr_op_i,
    output logic [31:0]         csr_rdata_o,

    // Interrupts
    output logic                illegal_csr_o,
    output logic                m_irq_enable_o,
    output PrivLvl_t            m_prv_o,
    output logic [31:0]         mtvec_o,
    output logic [31:0]         mepc_o,

    input logic [31:0]          pc_if_i,
    input logic [31:0]          pc_id_i,

    input logic                 csr_save_if_i,
    input logic                 csr_save_id_i,
    input logic                 csr_restore_mret_i,

    input logic [5:0]           csr_cause_i,
    input logic                 csr_save_cause_i,


    // Performance Counters
    input logic                 if_valid_i, // IF stage gives a new instruction
    input logic                 id_valid_i, // ID stage is done
    input logic                 is_compressed_i, // compressed instruction in ID
    input logic                 is_decoding_i, // controller is in DECODE state

    input logic                 imiss_i, // instruction fetch
    input logic                 pc_set_i, // pc was set to a new value
    input logic                 jump_i, // jump instruction seen   (j, jr, jal, jalr)
    input logic                 branch_i, // branch instruction seen (bf, bnf)
    input logic                 branch_taken_i, // branch was taken
    input logic                 mem_load_i, // load from memory in this cycle
    input logic                 mem_store_i, // store to memory in this cycle
    input logic [N_EXT_CNT-1:0] ext_counters_i
    );

   localparam N_PERF_COUNTERS = 11 + N_EXT_CNT;

`ifdef ASIC_SYNTHESIS
   localparam N_PERF_REGS     = 1;
`else
   localparam N_PERF_REGS     = N_PERF_COUNTERS;
`endif

`define MSTATUS_UIE_BITS        0
`define MSTATUS_SIE_BITS        1
`define MSTATUS_MIE_BITS        3
`define MSTATUS_UPIE_BITS       4
`define MSTATUS_SPIE_BITS       5
`define MSTATUS_MPIE_BITS       7
`define MSTATUS_SPP_BITS        8
`define MSTATUS_MPP_BITS    12:11

   typedef struct               packed {
      logic                     uie;
      // logic sie;      - unimplemented, hardwired to '0
      // logic hie;      - unimplemented, hardwired to '0
      logic                     mie;
      logic                     upie;
      // logic spie;     - unimplemented, hardwired to '0
      // logic hpie;     - unimplemented, hardwired to '0
      logic                     mpie;
      // logic spp;      - unimplemented, hardwired to '0
      // logic[1:0] hpp; - unimplemented, hardwired to '0
      PrivLvl_t mpp;
      PrivLvl_t prv;
   } Status_t;

   // Performance Counter Signals
   logic                        id_valid_q;
   logic [N_PERF_COUNTERS-1:0]  PCCR_in;  // input signals for each counter category
   logic [N_PERF_COUNTERS-1:0]  PCCR_inc, PCCR_inc_q; // should the counter be increased?

   logic [N_PERF_REGS-1:0] [31:0] PCCR_q, PCCR_n; // performance counters counter register
   logic [1:0]                    PCMR_n, PCMR_q; // mode register, controls saturation and global enable
   logic [N_PERF_COUNTERS-1:0]    PCER_n, PCER_q; // selected counter input

   logic [31:0]                   perf_rdata;
   logic [4:0]                    pccr_index;
   logic                          pccr_all_sel;
   logic                          is_pccr;
   logic                          is_pcer;
   logic                          is_pcmr;

   // CSR update logic
   logic [31:0]                   csr_wdata_int;
   logic [31:0]                   csr_rdata_int;
   logic                          csr_we_int;

   // Interrupt control signals
   logic [31:0]                   mtvec_q, mtvec_n;
   logic [31:0]                   mscratch_q, mscratch_n;
   logic [31:0]                   mepc_q, mepc_n;
   logic [ 5:0]                   mcause_q, mcause_n;
   Status_t mstatus_q, mstatus_n;

   ////////////////////////////////////////////
   //   ____ ____  ____    ____              //
   //  / ___/ ___||  _ \  |  _ \ ___  __ _   //
   // | |   \___ \| |_) | | |_) / _ \/ _` |  //   
   // | |___ ___) |  _ <  |  _ <  __/ (_| |  //
   //  \____|____/|_| \_\ |_| \_\___|\__, |  //
   //                                |___/   //
   ////////////////////////////////////////////

   // illegal logic
   always_comb
     begin
        illegal_csr_o = 1'b0;
        if(csr_access_i)begin
           case (csr_addr_i)
             // mstatus: always M-mode, contains IE bit
             12'h300: ;
             // misa: Machine ISA
             12'h301: ;
             // mtvec: machine trap-handler base address
             12'h305: ;
             // mscratch: Machine Scratch
             12'h340: ;
             // mepc: exception program counter
             12'h341: ;
             // mcause: exception cause
             12'h342: ;
             // tselecr: trigger select
             12'h7a0: ;
             // Machine xx IDs
             12'hF11: ;
             12'hF12: ;
             12'hF13: ;
             // mhartid: unique hardware thread id
             12'hF14: ;
		     default: illegal_csr_o = 1'b1;
           endcase
        end
     end


   // read logic
   always_comb
     begin
	    csr_rdata_int = '0;
        case (csr_addr_i)

          // mstatus: always M-mode, contains IE bit
          12'h300: csr_rdata_int = {
                                    19'b0,
                                    mstatus_q.mpp,
                                    3'b0,
                                    mstatus_q.mpie,
                                    2'h0,
                                    mstatus_q.upie,
                                    mstatus_q.mie,
                                    2'h0,
                                    mstatus_q.uie
                                    };
          // misa: Machine ISA
          12'h301: csr_rdata_int = 32'h40001104;
          // mtvec: machine trap-handler base address
          12'h305: csr_rdata_int = mtvec_q;
          // mscratch: Machine Scratch
          12'h340: csr_rdata_int = mscratch_q;
          // mepc: exception program counter
          12'h341: csr_rdata_int = mepc_q;
          // mcause: exception cause
          12'h342: csr_rdata_int = {mcause_q[5], 26'b0, mcause_q[4:0]};

          // tselecr: trigger select
          12'h7a0: csr_rdata_int = -1;
          // Machine xx IDs
          12'hF11: csr_rdata_int = '0;
          12'hF12: csr_rdata_int = '0;
          12'hF13: csr_rdata_int = '0;
          // mhartid: unique hardware thread id
          12'hF14: csr_rdata_int = {21'b0, cluster_id_i[5:0], 1'b0, core_id_i[3:0]};

		  default: ;
        endcase
     end


   // write logic
   always_comb
     begin
        mtvec_n      = mtvec_q;
        mscratch_n   = mscratch_q;
        mepc_n       = mepc_q;
        mstatus_n    = mstatus_q;
        mcause_n     = mcause_q;

        case (csr_addr_i)
          // mstatus: IE bit
          12'h300: if (csr_we_int) begin
             mstatus_n = '{
                           uie:  csr_wdata_int[`MSTATUS_UIE_BITS],
                           mie:  csr_wdata_int[`MSTATUS_MIE_BITS],
                           upie: csr_wdata_int[`MSTATUS_UPIE_BITS],
                           mpie: csr_wdata_int[`MSTATUS_MPIE_BITS],
//                           mpp:  csr_wdata_int[`MSTATUS_MPP_BITS],
                           mpp:  PrivLvl_t'({2{|csr_wdata_int[`MSTATUS_MPP_BITS]}}),
                           prv:  mstatus_q.prv
                           };
          end
          // mtvec: machine trap-handler base address
          12'h305: if (csr_we_int) mtvec_n = csr_wdata_int;
          // mscratch: Machine Scratch
          12'h340: if (csr_we_int) mscratch_n = csr_wdata_int;
          // mepc: exception program counter
          12'h341: if (csr_we_int) mepc_n = csr_wdata_int;
          // mcause
          12'h342: if (csr_we_int) mcause_n = {csr_wdata_int[31], csr_wdata_int[4:0]};
		  default: ;
        endcase

        // exception controller gets priority over other writes
        unique case (1'b1)

          csr_save_cause_i: begin
             unique case (1'b1)
               csr_save_if_i:
                 mepc_n = pc_if_i;
               csr_save_id_i:
                 mepc_n = pc_id_i;
               default:;
             endcase
             mstatus_n.mpp = mstatus_q.prv;
             if(mstatus_q.prv == PrivLvl_t'(PRIV_LVL_M))begin
                mstatus_n.mpie = mstatus_q.mie;
             end else begin
                mstatus_n.mpie = mstatus_q.uie;
             end
             mstatus_n.mie  = 1'b0;
             mstatus_n.prv = PrivLvl_t'(PRIV_LVL_M);
             mcause_n       = csr_cause_i;
          end //csr_save_cause_i

          csr_restore_mret_i: begin //MRET
             mstatus_n.mpp = PrivLvl_t'(PRIV_LVL_U);
             mstatus_n.prv = mstatus_q.mpp;
             if(mstatus_q.prv == PrivLvl_t'(PRIV_LVL_M))begin
                mstatus_n.mie  = mstatus_q.mpie;
             end else begin
                mstatus_n.mie  = mstatus_q.upie;
             end
             mstatus_n.mpie = 1'b1;
          end //csr_restore_mret_i

          default:;

        endcase

     end


   // CSR operation logic
   always_comb
     begin
        csr_wdata_int = csr_wdata_i;
        csr_we_int    = 1'b1;

        unique case (csr_op_i)
          CSR_OP_WRITE: csr_wdata_int = csr_wdata_i;
          CSR_OP_SET:   csr_wdata_int = csr_wdata_i | csr_rdata_o;
          CSR_OP_CLEAR: csr_wdata_int = (~csr_wdata_i) & csr_rdata_o;

          CSR_OP_NONE: begin
             csr_wdata_int = csr_wdata_i;
             csr_we_int    = 1'b0;
          end

          default:;
        endcase
     end


   // output mux
   always_comb
     begin
        csr_rdata_o = csr_rdata_int;

        // performance counters
        if (is_pccr || is_pcer || is_pcmr)
          csr_rdata_o = perf_rdata;
     end


   // directly output some registers
   assign m_irq_enable_o  = mstatus_q.mie;
   assign m_prv_o         = mstatus_q.prv;
   assign mtvec_o         = mtvec_q;
   assign mepc_o          = mepc_q;


   // actual registers
   always_ff @(posedge clk, negedge rst_n)
     begin
        if (rst_n == 1'b0)
          begin
             mstatus_q  <= '{
                             uie:  1'b0,
                             mie:  1'b0,
                             upie: 1'b0,
                             mpie: 1'b0,
                             mpp:  PRIV_LVL_U,
                             prv:  PRIV_LVL_M
                             };
             mtvec_q    <= boot_addr_i;
             mscratch_q <= '0;
             mepc_q     <= '0;
             mcause_q   <= '0;
          end
        else
          begin
             // update CSRs
             mstatus_q  <= '{
                             uie:  mstatus_n.uie,
                             mie:  mstatus_n.mie,
                             upie: mstatus_n.upie,
                             mpie: mstatus_n.mpie,
                             mpp:  mstatus_n.mpp,
                             prv:  mstatus_n.prv
                             };
             mtvec_q    <= mtvec_n;
             mscratch_q <= mscratch_n;
             mepc_q     <= mepc_n;
             mcause_q   <= mcause_n;
          end
     end

   /////////////////////////////////////////////////////////////////
   //   ____            __     ____                  _            //
   // |  _ \ ___ _ __ / _|   / ___|___  _   _ _ __ | |_ ___ _ __  //
   // | |_) / _ \ '__| |_   | |   / _ \| | | | '_ \| __/ _ \ '__| //
   // |  __/  __/ |  |  _|  | |__| (_) | |_| | | | | ||  __/ |    //
   // |_|   \___|_|  |_|(_)  \____\___/ \__,_|_| |_|\__\___|_|    //
   //                                                             //
   /////////////////////////////////////////////////////////////////

   assign PCCR_in[0]  = 1'b1;                          // cycle counter
   assign PCCR_in[1]  = if_valid_i;                    // instruction counter
   assign PCCR_in[2]  = 1'b0;                          // Reserved
   assign PCCR_in[3]  = 1'b0;                          // Reserved
   assign PCCR_in[4]  = imiss_i & (~pc_set_i);         // cycles waiting for instruction fetches, excluding jumps and branches
   assign PCCR_in[5]  = mem_load_i;                    // nr of loads
   assign PCCR_in[6]  = mem_store_i;                   // nr of stores
   assign PCCR_in[7]  = jump_i;                        // nr of jumps (unconditional)
   assign PCCR_in[8]  = branch_i;                      // nr of branches (conditional)
   assign PCCR_in[9]  = branch_taken_i;                // nr of taken branches (conditional)
   assign PCCR_in[10] = id_valid_i & is_decoding_i & is_compressed_i; // compressed instruction counter

   // assign external performance counters
   generate
      genvar i;
      for (i = 0; i < N_EXT_CNT; i++) 
        begin : g_extcounters
           assign PCCR_in[N_PERF_COUNTERS - N_EXT_CNT + i] = ext_counters_i[i];
        end
   endgenerate

   // address decoder for performance counter registers
   always_comb
     begin
        is_pccr      = 1'b0;
        is_pcmr      = 1'b0;
        is_pcer      = 1'b0;
        pccr_all_sel = 1'b0;
        pccr_index   = '0;
        perf_rdata   = '0;

        // only perform csr access if we actually care about the read data
        if (csr_access_i) begin
           unique case (csr_addr_i)
             12'h790: begin
                is_pcer = 1'b1;
                perf_rdata[15:0] = PCER_q;
             end
             12'h791: begin
                is_pcmr = 1'b1;
                perf_rdata[1:0] = PCMR_q;
             end
             12'h78F: begin
                is_pccr = 1'b1;
                pccr_all_sel = 1'b1;
             end
             default:;
           endcase

           // look for 780 to 79F, Performance Counter Counter Registers
           if (csr_addr_i[11:5] == 7'b0111100) begin
              is_pccr     = 1'b1;

              pccr_index = csr_addr_i[4:0];
`ifdef  ASIC_SYNTHESIS
              perf_rdata = PCCR_q[0];
`else
              perf_rdata = PCCR_q[csr_addr_i[4:0]];
`endif
           end
        end
     end


   // performance counter counter update logic
`ifdef ASIC_SYNTHESIS
   // for synthesis we just have one performance counter register
   assign PCCR_inc[0] = (|(PCCR_in & PCER_q)) & PCMR_q[0];

   always_comb
     begin
        PCCR_n[0]   = PCCR_q[0];

        if ((PCCR_inc_q[0] == 1'b1) && ((PCCR_q[0] != 32'hFFFFFFFF) || (PCMR_q[1] == 1'b0)))
          PCCR_n[0] = PCCR_q[0] + 1;

        if (is_pccr == 1'b1) begin
           unique case (csr_op_i)
             CSR_OP_NONE:   ;
             CSR_OP_WRITE:  PCCR_n[0] = csr_wdata_i;
             CSR_OP_SET:    PCCR_n[0] = csr_wdata_i | PCCR_q[0];
             CSR_OP_CLEAR:  PCCR_n[0] = csr_wdata_i & ~(PCCR_q[0]);
           endcase
        end
     end
`else
   always_comb
     begin
        for(int i = 0; i < N_PERF_COUNTERS; i++)
          begin : PERF_CNT_INC
             PCCR_inc[i] = PCCR_in[i] & PCER_q[i] & PCMR_q[0];

             PCCR_n[i]   = PCCR_q[i];

             if ((PCCR_inc_q[i] == 1'b1) && ((PCCR_q[i] != 32'hFFFFFFFF) || (PCMR_q[1] == 1'b0)))
               PCCR_n[i] = PCCR_q[i] + 1;

             if (is_pccr == 1'b1 && (pccr_all_sel == 1'b1 || pccr_index == i)) begin
                unique case (csr_op_i)
                  CSR_OP_NONE:   ;
                  CSR_OP_WRITE:  PCCR_n[i] = csr_wdata_i;
                  CSR_OP_SET:    PCCR_n[i] = csr_wdata_i | PCCR_q[i];
                  CSR_OP_CLEAR:  PCCR_n[i] = csr_wdata_i & ~(PCCR_q[i]);
                endcase
             end
          end
     end
`endif

   // update PCMR and PCER
   always_comb
     begin
        PCMR_n = PCMR_q;
        PCER_n = PCER_q;

        if (is_pcmr) begin
           unique case (csr_op_i)
             CSR_OP_NONE:   ;
             CSR_OP_WRITE:  PCMR_n = csr_wdata_i[1:0];
             CSR_OP_SET:    PCMR_n = csr_wdata_i[1:0] | PCMR_q;
             CSR_OP_CLEAR:  PCMR_n = csr_wdata_i[1:0] & ~(PCMR_q);
           endcase
        end

        if (is_pcer) begin
           unique case (csr_op_i)
             CSR_OP_NONE:   ;
             CSR_OP_WRITE:  PCER_n = csr_wdata_i[N_PERF_COUNTERS-1:0];
             CSR_OP_SET:    PCER_n = csr_wdata_i[N_PERF_COUNTERS-1:0] | PCER_q;
             CSR_OP_CLEAR:  PCER_n = csr_wdata_i[N_PERF_COUNTERS-1:0] & ~(PCER_q);
           endcase
        end
     end

   // Performance Counter Registers
   always_ff @(posedge clk, negedge rst_n)
     begin
        if (rst_n == 1'b0)
          begin
             id_valid_q <= 1'b0;

             PCER_q <= '0;
             PCMR_q <= 2'h3;

             for(int i = 0; i < N_PERF_REGS; i++)
               begin
                  PCCR_q[i]     <= '0;
                  PCCR_inc_q[i] <= '0;
               end
          end
        else
          begin
             id_valid_q <= id_valid_i;

             PCER_q <= PCER_n;
             PCMR_q <= PCMR_n;

             for(int i = 0; i < N_PERF_REGS; i++)
               begin
                  PCCR_q[i]     <= PCCR_n[i];
                  PCCR_inc_q[i] <= PCCR_inc[i];
               end

          end
     end

endmodule
