// Copyright 2018 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.

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                          //
// Author:                              Francesco Minervini - minervif@student.ethz.ch                      //
//                                                                                                          //
// Additional contributions by:         Davide Schiavone - pschiavo@iis.ee.ethz.ch                          //
//                                      Andrea Bettati - andrea.bettati@studenti.unipr.it                   //
// Design Name:                         Interrupt generator                                                 //
// Project Name:                        RI5CY, Zeroriscy                                                    //
// Language:                            SystemVerilog                                                       //
//                                                                                                          //
// Description:                         Send interrupt request to core choosing one option between:         //
//                                      - Random;                                                           //
//                                      - Standard;                                                         //
//                                      - PC value-triggering                                               //
//                                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

module cv32e40p_random_interrupt_generator
  import cv32e40p_pkg::*;
(
    input  logic        rst_ni,
    input  logic        clk_i,
    input  logic        irq_i,
    input  logic        irq_ack_i,
    output logic [18:0] irq_rnd_lines_o,
    output logic        irq_ack_o,
    input  logic [31:0] irq_mode_i,
    input  logic [31:0] irq_min_cycles_i,
    input  logic [31:0] irq_max_cycles_i,
    input  logic [31:0] irq_min_id_i,
    input  logic [31:0] irq_max_id_i,
    output logic [31:0] irq_act_id_o,
    output logic        irq_id_we_o,
    input  logic [31:0] irq_pc_id_i,
    input  logic [31:0] irq_pc_trig_i,
    // software defined mode i/o
    input  logic [31:0] irq_lines_i
);

  import perturbation_pkg::*;

`ifndef VERILATOR
  class rand_irq_cycles;
    rand int n;
  endclass : rand_irq_cycles

  class rand_irq_id;
    rand int n;
    rand bit [31:0] rand_word;
  endclass : rand_irq_id

  logic [31:0] irq_mode_q;
  logic        irq_random;
  logic        irq_ack_random;
  logic        irq_monitor;
  logic [ 4:0] irq_id_monitor;
  logic        irq_ack_monitor;

  logic        irq_sd;
  logic [31:0] irq_lines;
  logic        ack_flag;
  logic        irq_lines_changed;


  // struct irq_lines
  typedef struct packed {
    logic irq_software;
    logic irq_timer;
    logic irq_external;
    logic [15:0] irq_fast;
  } Interrupts_tb_t;

  Interrupts_tb_t irq_lines_q, irq_lines_n;
  Interrupts_tb_t irq_rnd_lines, irq_pc_trig_lines, irq_sd_lines;


  assign irq_ack_o       = irq_ack_i;
  assign irq_rnd_lines_o = irq_lines_q;

  always_ff @(posedge clk_i or negedge rst_ni) begin
    if (~rst_ni) begin
      irq_mode_q  <= 0;
      irq_lines_q <= '0;
    end else begin
      irq_mode_q  <= irq_mode_i;
      irq_lines_q <= irq_lines_n;
    end
  end

  always_comb begin
    unique case (irq_mode_q)
      RANDOM: begin
        // UPDATE LOGIC:
        // random irq word id stored and updated every time
        // an interrupt is taken by the core

        irq_lines_n = irq_rnd_lines;
      end

      PC_TRIG: begin
        // TODO generate individual irq lines
        irq_lines_n = irq_pc_trig_lines;
      end

      SOFTWARE_DEFINED: begin
        // UPDATE LOGIC:
        irq_lines_n = irq_sd_lines;
      end

      default: begin
        irq_lines_n = '0;
      end
    endcase
  end

  // RANDOM INTERRUPTS PROCESS
  // Generates a random word irq_rnd_lines [18:0] at a random time.
  // The generated word is stored in irq_lines_q.

  initial begin
    automatic rand_irq_cycles wait_cycles = new();
    automatic rand_irq_id value = new();
    int temp, i, min_irq_cycles, max_irq_cycles, min_irq_id, max_irq_id;
    irq_random    = 1'b0;
    irq_rnd_lines = '0;
    while (1) begin
      @(posedge clk_i);

      wait(irq_mode_q == RANDOM);
      min_irq_id = irq_min_id_i;
      max_irq_id = irq_max_id_i;
      min_irq_cycles = irq_min_cycles_i;
      max_irq_cycles = irq_max_cycles_i;

      // generate random word and mask it with lower/upper bounds
      do begin
        temp = value.randomize();

        // These lines are not used by the processor
        value.rand_word[2:0] = '0;
        value.rand_word[6:4] = '0;
        value.rand_word[10:8] = '0;
        value.rand_word[15:12] = '0;

        for (int i = min_irq_id - 1; i >= 0; i--) begin
          value.rand_word[i] = 0;
        end

        for (int i = max_irq_id + 1; i <= 31; i++) begin
          value.rand_word[i] = 0;
        end
        // Randomize again if value.rand_word == '0, because irq_line should be one-hot
      end while (!(|value.rand_word));

      temp = wait_cycles.randomize() with{
n >= min_irq_cycles;
n <= max_irq_cycles;
};

      irq_random    = 1'b1;
      irq_act_id_o  = value.n;
      @(posedge clk_i);
      irq_random = 1'b0;

      // random irq word: update logic
      while (wait_cycles.n) begin
        @(posedge clk_i) irq_lines <= irq_lines_i;
        wait_cycles.n--;
      end

      // map random irq word to physical lines
      irq_rnd_lines.irq_software = value.rand_word[3];
      irq_rnd_lines.irq_timer    = value.rand_word[7];
      irq_rnd_lines.irq_external = value.rand_word[11];
      irq_rnd_lines.irq_fast     = value.rand_word[31:16];

      //clear interrupt request
      wait(irq_mode_q == STANDARD);
      irq_rnd_lines = '0;

    end
  end

  // check if signal changed @posedge
  assign irq_lines_changed = (irq_lines != irq_lines_i) ? 1 : 0;


  // SOFTWARE DEFINED INTERRUPTS PROCESS
  // Samples irq lines (word) as defined by software

  initial begin
    irq_sd_lines = 32'b0;
    while (1) begin
      irq_sd   = 1'b0;
      ack_flag = 1'b0;

      wait(irq_mode_q == SOFTWARE_DEFINED);

      // blocking wait for a valid sd_id
      while (irq_lines_i != 0) begin
        @(posedge clk_i);
        // sample input lines
        irq_sd_lines.irq_software = irq_lines_i[3];
        irq_sd_lines.irq_timer    = irq_lines_i[7];
        irq_sd_lines.irq_external = irq_lines_i[11];
        irq_sd_lines.irq_fast     = irq_lines_i[31:16];
        irq_sd                    = 1'b1;
      end

      @(posedge clk_i);

    end
  end

  //PC Trig Process
  initial begin
    int pc_value;
    irq_monitor    = 1'b0;
    pc_value = 0;
    irq_pc_trig_lines = '0;
    wait(irq_mode_q == PC_TRIG);
    wait(irq_pc_id_i == irq_pc_trig_i);
    irq_monitor                    = 1'b1;

    // build irq monitor word
    irq_pc_trig_lines.irq_software = 1'b1;
    irq_pc_trig_lines.irq_timer    = irq_lines_i[7];
    irq_pc_trig_lines.irq_external = irq_lines_i[11];
    irq_pc_trig_lines.irq_fast     = irq_lines_i[31:16];

    irq_monitor                    = 1'b0;
    irq_id_monitor                 = '0;
  end

  initial begin
    while (1) begin
      irq_id_we_o = 1'b0;
      wait(irq_ack_i == 1'b1);
      irq_id_we_o = 1'b1;  //Give the write enable to store the core response
      @(posedge clk_i);
    end
  end

`endif
endmodule
