library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_misc.all;
use ieee.numeric_std.all;
use std.textio.all;

use work.riscv_klessydra.all;
use work.thread_parameters_klessydra.all;

entity CSR_Unit is
  port (
    pc_IE                      : in  std_logic_vector(31 downto 0);
    harc_IE                    : in  harc_range;
    harc_to_csr                : in  harc_range;
    instr_word_IE              : in  std_logic_vector(31 downto 0);
    served_except_condition    : in  replicated_bit;
    served_mret_condition      : in  replicated_bit;
    served_irq                 : in  replicated_bit;
    pc_except_value            : in  replicated_32b_reg;
    dbg_req_o                  : in  std_logic;
    data_addr_internal         : in  std_logic_vector(31 downto 0);
    jump_instr                 : in  std_logic;
    branch_instr               : in  std_logic;
    data_valid_waiting_counter : in  std_logic;
    set_branch_condition       : in  std_logic;
    csr_instr_req              : in  std_logic;
    misaligned_err             : in  std_logic;
    WFI_Instr                  : in  std_logic;
    csr_wdata_i                : in  std_logic_vector (31 downto 0);
    csr_op_i                   : in  std_logic_vector (2 downto 0);
    csr_addr_i                 : in  std_logic_vector (11 downto 0);
    csr_instr_done             : out std_logic;
    csr_access_denied_o        : out std_logic;
    csr_rdata_o                : out std_logic_vector (31 downto 0);
    MSTATUS                    : out replicated_32b_reg;
    MEPC                       : out replicated_32b_reg;
    MCAUSE                     : out replicated_32b_reg;
    MIP                        : out replicated_32b_reg;
    MTVEC                      : out replicated_32b_reg;
    fetch_enable_i             : in  std_logic;
    clk_i                      : in  std_logic;
    rst_ni                     : in  std_logic;
    cluster_id_i               : in  std_logic_vector(5 downto 0);
    instr_rvalid_i             : in  std_logic;
    data_we_o_wire_top         : in  std_logic;
    data_req_o_wire_top        : in  std_logic;
    data_gnt_i                 : in  std_logic;
    irq_i                      : in  std_logic;
    irq_id_i                   : in  std_logic_vector(4 downto 0);
    irq_id_o                   : out std_logic_vector(4 downto 0);
    irq_ack_o                  : out std_logic
    );
end entity;


architecture CSR of CSR_Unit is

  signal pc_IE_replicated	: replicated_32b_reg;	
	
  signal PCCRs       : replicated_32b_reg;
  signal PCER        : replicated_32b_reg;
  signal PCMR        : replicated_32b_reg;
  signal MESTATUS    : replicated_32b_reg;
  signal MCPUID      : replicated_32b_reg;
  signal MIMPID      : replicated_32b_reg;
  signal MHARTID     : replicated_32b_reg;
  signal MIRQ        : replicated_32b_reg;
  signal MBADADDR    : replicated_32b_reg;

  signal MCYCLE        : replicated_32b_reg;
  signal MINSTRET      : replicated_32b_reg;
  signal MHPMCOUNTER3  : replicated_32b_reg;
  signal MHPMCOUNTER6  : replicated_32b_reg;
  signal MHPMCOUNTER7  : replicated_32b_reg;
  signal MHPMCOUNTER8  : replicated_32b_reg;
  signal MHPMCOUNTER9  : replicated_32b_reg;
  signal MHPMCOUNTER10 : replicated_32b_reg;
  signal MHPMCOUNTER11 : replicated_32b_reg;
  signal MCYCLEH       : replicated_32b_reg;
  signal MINSTRETH     : replicated_32b_reg;
  signal MHPMEVENT3    : replicated_bit;
  signal MHPMEVENT6    : replicated_bit;
  signal MHPMEVENT7    : replicated_bit;
  signal MHPMEVENT8    : replicated_bit;
  signal MHPMEVENT9    : replicated_bit;
  signal MHPMEVENT10   : replicated_bit;
  signal MHPMEVENT11   : replicated_bit;
  signal MIP_7         : std_logic;
  signal MIP_11        : std_logic;


  signal csr_instr_req_replicated       : replicated_bit;
  signal csr_instr_done_replicated      : replicated_bit;
  signal csr_access_denied_o_replicated : replicated_bit;
  signal csr_rdata_o_replicated         : replicated_32b_reg;

  signal MSTATUS_internal       : replicated_32b_reg;
  signal MEPC_internal          : replicated_32b_reg;
  signal MCAUSE_internal        : replicated_32b_reg;
  signal MIP_internal           : replicated_32b_reg;
  signal MTVEC_internal         : replicated_32b_reg;
  signal irq_ack_o_internal     : std_logic;
  signal trap_hndlr         	: replicated_bit;
  


begin

  MSTATUS       <= MSTATUS_internal;
  MEPC          <= MEPC_internal;
  MCAUSE        <= MCAUSE_internal;
  MIP           <= MIP_internal;
  MTVEC         <= MTVEC_internal;
  irq_ack_o     <= irq_ack_o_internal;

  CSR_updating_logic : for h in harc_range generate

    MCPUID(h) <= std_logic_vector(to_unsigned(256, 32));
    MIMPID(h) <= std_logic_vector(to_unsigned(32768, 32));
    MHARTID(h) <= std_logic_vector(resize(unsigned(cluster_id_i) &
                                          to_unsigned(harc_IE, THREAD_ID_SIZE), 32));

    MIRQ(h) <= "0000000000000000000000000" & irq_id_i & "00";
	pc_IE_replicated(harc_IE) <= pc_IE;
    csr_instr_req_replicated(h) <= '1' when csr_instr_req = '1' and harc_to_csr = h else '0';
    trap_hndlr(h)               <= '1' when pc_IE_replicated(h) = MTVEC_RESET_VALUE(h)  else '0' when (pc_IE_replicated(h) = MEPC_internal(h)) or (pc_IE_replicated(h) = std_logic_vector(unsigned(MEPC_internal(h)) + 4));

    CSR_unit_op : process(clk_i, rst_ni)

      variable MIP_3_wire : replicated_bit;

    begin

      if rst_ni = '0' then
        MSTATUS_internal(h)                   <= MSTATUS_RESET_VALUE;
        MESTATUS(h)                       <= MESTATUS_RESET_VALUE;
        MEPC_internal(h)                      <= MEPC_RESET_VALUE;
        MCAUSE_internal(h)                    <= MCAUSE_RESET_VALUE;
        MTVEC_internal(h)                     <= MTVEC_RESET_VALUE(h);
        PCER(h)                           <= PCER_RESET_VALUE(h);
        MCYCLE(h)                         <= x"00000000";
        MINSTRET(h)                       <= x"00000000";
        MHPMCOUNTER3(h)                   <= x"00000000";
        MHPMCOUNTER6(h)                   <= x"00000000";
        MHPMCOUNTER7(h)                   <= x"00000000";
        MHPMCOUNTER8(h)                   <= x"00000000";
        MHPMCOUNTER9(h)                   <= x"00000000";
        MHPMCOUNTER10(h)                  <= x"00000000";
        MCYCLEH(h)                        <= x"00000000";
        MINSTRETH(h)                      <= x"00000000";
        MHPMEVENT3(h)                     <= PCER_RESET_VALUE(h)(2);
        MHPMEVENT6(h)                     <= PCER_RESET_VALUE(h)(5);
        MHPMEVENT7(h)                     <= PCER_RESET_VALUE(h)(6);
        MHPMEVENT8(h)                     <= PCER_RESET_VALUE(h)(7);
        MHPMEVENT9(h)                     <= PCER_RESET_VALUE(h)(8);
        MHPMEVENT10(h)                    <= PCER_RESET_VALUE(h)(9);
        MIP_internal(h)                       <= MIP_RESET_VALUE;

        csr_instr_done_replicated(h)      <= '0';
        csr_access_denied_o_replicated(h) <= '0';
        csr_rdata_o_replicated(h)         <= (others => '0');

      elsif rising_edge(clk_i) then



        if served_irq(h) = '1' and MIP_internal(h)(11) = '1' then
          MCAUSE_internal(h) <= "1" & std_logic_vector(to_unsigned(11, 31));
          MESTATUS(h)    <= MSTATUS_internal(h);
          if trap_hndlr(h) = '0' then
            MEPC_internal(h) <= pc_IE;
          end if;     
          if WFI_Instr = '1' then
			MCAUSE_internal(h)(30) <= '1';
          else
			MCAUSE_internal(h)(30) <= '0';
          end if;
          MSTATUS_internal(h)(3) <= '0';
          MSTATUS_internal(h)(7) <= MSTATUS_internal(h)(3);
        elsif served_irq(h) = '1' and MIP_internal(h)(3) = '1' then
          MCAUSE_internal(h) <= "1" & std_logic_vector(to_unsigned(3, 31));
          MIP_internal(h)(3) <= '0';
          MESTATUS(h)    <= MSTATUS_internal(h);
          if trap_hndlr(h) = '0' then
            MEPC_internal(h) <= pc_IE;
          end if;     
          if WFI_Instr = '1' then
			MCAUSE_internal(h)(30) <= '1';
          else
			MCAUSE_internal(h)(30) <= '0';
          end if;
          MSTATUS_internal(h)(3) <= '0';
          MSTATUS_internal(h)(7) <= MSTATUS_internal(h)(3);
        elsif served_irq(h) = '1' and MIP_internal(h)(7) = '1' then
          MCAUSE_internal(h) <= "1" & std_logic_vector(to_unsigned(7, 31));
          MESTATUS(h)    <= MSTATUS_internal(h);
          if trap_hndlr(h) = '0' then
            MEPC_internal(h) <= pc_IE;
          end if;   
          if WFI_Instr = '1' then
			MCAUSE_internal(h)(30) <= '1';
          else
			MCAUSE_internal(h)(30) <= '0';
          end if;
          MSTATUS_internal(h)(3) <= '0';
          MSTATUS_internal(h)(7) <= MSTATUS_internal(h)(3);
          
        elsif served_except_condition(h) = '1' then
          MCAUSE_internal(h)     <= csr_wdata_i;
          MESTATUS(h)        <= MSTATUS_internal(h);
          MEPC_internal(h)       <= pc_except_value(h);
          MSTATUS_internal(h)(3) <= '0';
          MSTATUS_internal(h)(7) <= '1';
          if misaligned_err = '1' then
            MBADADDR(h) <= data_addr_internal;
          end if;

        elsif served_mret_condition(h) = '1' then
          MSTATUS_internal(h)(7) <= '1';
          MSTATUS_internal(h)(3) <= MSTATUS_internal(h)(7);

        elsif(csr_instr_done_replicated(h) = '1') then
          csr_instr_done_replicated(h)      <= '0';
          csr_access_denied_o_replicated(h) <= '0';
        elsif csr_instr_req_replicated(h) = '1' then
          csr_instr_done_replicated(h) <= '1';
          if (csr_op_i /= "000" and csr_op_i /= "100") then
            case csr_addr_i is
              when MSTATUS_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= MSTATUS_internal(h);
                    MSTATUS_internal(h)(7)        <= csr_wdata_i(7);
                    MSTATUS_internal(h)(3)        <= csr_wdata_i(3);
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MSTATUS_internal(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MSTATUS_internal(h)(7) <= (MSTATUS_internal(h)(7) or csr_wdata_i(7));
                      MSTATUS_internal(h)(3) <= (MSTATUS_internal(h)(3) or csr_wdata_i(3));
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MSTATUS_internal(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MSTATUS_internal(h)(7) <= MSTATUS_internal(h)(7) and (not csr_wdata_i(7));
                      MSTATUS_internal(h)(3) <= MSTATUS_internal(h)(3) and (not csr_wdata_i(3));
                    end if;
                  when others =>
                    null;
                end case;
              when MIP_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= (11 => MIP_internal(h)(11), 7 => MIP_internal(h)(7), 3 => MIP_internal(h)(3), others => '0');
                    MIP_internal(h)(3)             <= csr_wdata_i(3);
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= (11 => MIP_internal(h)(11), 7 => MIP_internal(h)(7), 3 => MIP_internal(h)(3), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MIP_internal(h)(3) <= (MIP_internal(h)(3) or csr_wdata_i(3));
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= (11 => MIP_internal(h)(11), 7 => MIP_internal(h)(7), 3 => MIP_internal(h)(3), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MIP_internal(h)(3) <= (MIP_internal(h)(3) and (not csr_wdata_i(3)));
                    end if;
                  when others =>
                    null;
                end case;
              when MEPC_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= MEPC_internal(h);
                    MEPC_internal(h)              <= csr_wdata_i;
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MEPC_internal(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MEPC_internal(h) <= (MEPC_internal(h) or csr_wdata_i);
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MEPC_internal(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MEPC_internal(h) <= (MEPC_internal(h) and not(csr_wdata_i));
                    end if;
                  when others =>
                    null;
                end case;
              when MTVEC_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= MTVEC_internal(h);
                    MTVEC_internal(h)         <= csr_wdata_i;
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MTVEC_internal(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MTVEC_internal(h) <= (MTVEC_internal(h) or csr_wdata_i);
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MTVEC_internal(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MTVEC_internal(h) <= (MTVEC_internal(h) and not(csr_wdata_i));
                    end if;
                  when others =>
                    null;
                end case;
              when MCAUSE_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h)  <= MCAUSE_internal(h);
                    MCAUSE_internal(h)(31)         <= csr_wdata_i(31);
                    MCAUSE_internal(h)(4 downto 0) <= csr_wdata_i(4 downto 0);
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MCAUSE_internal(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MCAUSE_internal(h)(31)         <= (MCAUSE_internal(h)(31) or csr_wdata_i(31));
                      MCAUSE_internal(h)(4 downto 0) <= (MCAUSE_internal(h)(4 downto 0) or csr_wdata_i(4 downto 0));
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MCAUSE_internal(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MCAUSE_internal(h)(4 downto 0) <= (MCAUSE_internal(h)(4 downto 0) and not(csr_wdata_i(4 downto 0)));
                      MCAUSE_internal(h)(31)         <= (MCAUSE_internal(h)(31) and not(csr_wdata_i(31)));
                    end if;
                  when others =>
                    null;
                end case;
              when MESTATUS_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= MESTATUS(h);
                    MESTATUS(h)(0)            <= csr_wdata_i(0);
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MESTATUS(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MESTATUS(h)(0) <= (MESTATUS(h)(0) or csr_wdata_i(0));
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MESTATUS(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MESTATUS(h)(0) <= (MESTATUS(h)(0) and (not csr_wdata_i(0)));
                    end if;
                  when others =>
                    null;
                end case;
              when MCPUID_addr =>
                case csr_op_i is
                  when CSRRC|CSRRS|CSRRCI|CSRRSI =>
                    if(rs1(instr_word_IE) = 0) then
                      csr_rdata_o_replicated(h) <= MCPUID(h);
                    else
                      csr_access_denied_o_replicated(h) <= '1';
                    end if;
                  when CSRRW|CSRRWI =>
                    csr_access_denied_o_replicated(h) <= '1';
                  when others =>
                    null;
                end case;
              when MIMPID_addr =>
                case csr_op_i is
                  when CSRRC|CSRRS|CSRRCI|CSRRSI =>
                    if(rs1(instr_word_IE) = 0) then
                      csr_rdata_o_replicated(h) <= MIMPID(h);
                    else
                      csr_access_denied_o_replicated(h) <= '1';
                    end if;
                  when CSRRW|CSRRWI =>
                    csr_access_denied_o_replicated(h) <= '1';
                  when others =>
                    null;
                end case;
              when MHARTID_addr =>
                case csr_op_i is
                  when CSRRC|CSRRS|CSRRCI|CSRRSI =>
                    if(rs1(instr_word_IE) = 0) then
                      csr_rdata_o_replicated(h) <= MHARTID(h);
                    else
                      csr_access_denied_o_replicated(h) <= '1';
                    end if;
                  when CSRRW|CSRRWI =>
                    csr_access_denied_o_replicated(h) <= '1';
                  when others =>
                    null;
                end case;
              when MIRQ_addr =>
                case csr_op_i is
                  when CSRRC|CSRRS|CSRRCI|CSRRSI =>
                    if(rs1(instr_word_IE) = 0) then
                      csr_rdata_o_replicated(h) <= MIRQ(h);
                    else
                      csr_access_denied_o_replicated(h) <= '1';
                    end if;
                  when CSRRW|CSRRWI =>
                    csr_access_denied_o_replicated(h) <= '1';
                  when others =>
                    null;
                end case;
              when BADADDR_addr =>
                case csr_op_i is
                  when CSRRC|CSRRS|CSRRCI|CSRRSI =>
                    if(rs1(instr_word_IE) = 0) then
                      csr_rdata_o_replicated(h) <= MBADADDR(h);
                    else
                      csr_access_denied_o_replicated(h) <= '1';
                    end if;
                  when CSRRW|CSRRWI =>
                    csr_access_denied_o_replicated(h) <= '1';
                  when others =>
                    null;
                end case;

              when MCYCLE_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= MCYCLE(h);
                    MCYCLE(h)                 <= csr_wdata_i;
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MCYCLE(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MCYCLE(h) <= (MCYCLE(h) or csr_wdata_i);
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MCYCLE(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MCYCLE(h) <= (MCYCLE(h) and not(csr_wdata_i));
                    end if;
                  when others =>
                    null;
                end case;

              when MINSTRET_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= std_logic_vector(unsigned(MINSTRET(h))-1);
                    MINSTRET(h)               <= csr_wdata_i;
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= std_logic_vector(unsigned(MINSTRET(h))-1);
                    if(rs1(instr_word_IE) /= 0) then
                      MINSTRET(h) <= (MINSTRET(h) or csr_wdata_i);
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= std_logic_vector(unsigned(MINSTRET(h))-1);
                    if(rs1(instr_word_IE) /= 0) then
                      MINSTRET(h) <= (MINSTRET(h) and not(csr_wdata_i));
                    end if;
                  when others =>
                    null;
                end case;

              when MCYCLEH_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= MCYCLEH(h);
                    MCYCLEH(h)                <= csr_wdata_i;
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MCYCLEH(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MCYCLEH(h) <= (MCYCLEH(h) or csr_wdata_i);
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MCYCLEH(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MCYCLEH(h) <= (MCYCLEH(h) and not(csr_wdata_i));
                    end if;
                  when others =>
                    null;
                end case;

              when MINSTRETH_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    if(MINSTRET(h) = x"00000000" and MINSTRETH(h) /= x"00000000") then
                      csr_rdata_o_replicated(h) <= std_logic_vector(unsigned(MINSTRETH(h))-1);
                    else
                      csr_rdata_o_replicated(h) <= MINSTRETH(h);
                    end if;
                    MINSTRETH(h) <= csr_wdata_i;
                  when CSRRS|CSRRSI =>
                    if(MINSTRET(h) = x"00000000" and MINSTRETH(h) /= x"00000000") then
                      csr_rdata_o_replicated(h) <= std_logic_vector(unsigned(MINSTRETH(h))-1);
                    else
                      csr_rdata_o_replicated(h) <= MINSTRETH(h);
                    end if;
                    if(rs1(instr_word_IE) /= 0) then
                      MINSTRETH(h) <= (MINSTRETH(h) or csr_wdata_i);
                    end if;
                  when CSRRC|CSRRCI =>
                    if(MINSTRET(h) = x"00000000" and MINSTRETH(h) /= x"00000000") then
                      csr_rdata_o_replicated(h) <= std_logic_vector(unsigned(MINSTRETH(h))-1);
                    else
                      csr_rdata_o_replicated(h) <= MINSTRETH(h);
                    end if;
                    if(rs1(instr_word_IE) /= 0) then
                      MINSTRETH(h) <= (MINSTRETH(h) and not(csr_wdata_i));
                    end if;
                  when others =>
                    null;
                end case;

              when MHPMCOUNTER3_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER3(h);
                    MHPMCOUNTER3(h)           <= csr_wdata_i;
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER3(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMCOUNTER3(h) <= (MHPMCOUNTER3(h) or csr_wdata_i);
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER3(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMCOUNTER3(h) <= (MHPMCOUNTER3(h) and not(csr_wdata_i));
                    end if;
                  when others =>
                    null;
                end case;



              when MHPMCOUNTER6_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER6(h);
                    MHPMCOUNTER6(h)           <= csr_wdata_i;
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER6(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMCOUNTER6(h) <= (MHPMCOUNTER6(h) or csr_wdata_i);
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER6(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMCOUNTER6(h) <= (MHPMCOUNTER6(h) and not(csr_wdata_i));
                    end if;
                  when others =>
                    null;
                end case;

              when MHPMCOUNTER7_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER7(h);
                    MHPMCOUNTER7(h)           <= csr_wdata_i;
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER7(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMCOUNTER7(h) <= (MHPMCOUNTER7(h) or csr_wdata_i);
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER7(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMCOUNTER7(h) <= (MHPMCOUNTER7(h) and not(csr_wdata_i));
                    end if;
                  when others =>
                    null;
                end case;

              when MHPMCOUNTER8_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER8(h);
                    MHPMCOUNTER8(h)           <= csr_wdata_i;
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER8(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMCOUNTER8(h) <= (MHPMCOUNTER8(h) or csr_wdata_i);
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER8(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMCOUNTER8(h) <= (MHPMCOUNTER8(h) and not(csr_wdata_i));
                    end if;
                  when others =>
                    null;
                end case;

              when MHPMCOUNTER9_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER9(h);
                    MHPMCOUNTER9(h)           <= csr_wdata_i;
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER9(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMCOUNTER9(h) <= (MHPMCOUNTER9(h) or csr_wdata_i);
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER9(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMCOUNTER9(h) <= (MHPMCOUNTER9(h) and not(csr_wdata_i));
                    end if;
                  when others =>
                    null;
                end case;

              when MHPMCOUNTER10_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER10(h);
                    MHPMCOUNTER10(h)          <= csr_wdata_i;
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER10(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMCOUNTER10(h) <= (MHPMCOUNTER10(h) or csr_wdata_i);
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= MHPMCOUNTER10(h);
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMCOUNTER10(h) <= (MHPMCOUNTER10(h) and not(csr_wdata_i));
                    end if;
                  when others =>
                    null;
                end case;


              when PCER_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= PCER(h);
                    PCER(h)                   <= csr_wdata_i;
                    MHPMEVENT3(h)             <= csr_wdata_i(2);
                    MHPMEVENT6(h)             <= csr_wdata_i(5);
                    MHPMEVENT7(h)             <= csr_wdata_i(6);
                    MHPMEVENT8(h)             <= csr_wdata_i(7);
                    MHPMEVENT9(h)             <= csr_wdata_i(8);
                    MHPMEVENT10(h)            <= csr_wdata_i(9);

                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= PCER(h);
                    if(rs1(instr_word_IE) /= 0) then
                      PCER(h)        <= (PCER(h) or csr_wdata_i);
                      MHPMEVENT3(h)  <= (PCER(h)(2) or csr_wdata_i(2));
                      MHPMEVENT6(h)  <= (PCER(h)(5) or csr_wdata_i(5));
                      MHPMEVENT7(h)  <= (PCER(h)(6) or csr_wdata_i(6));
                      MHPMEVENT8(h)  <= (PCER(h)(7) or csr_wdata_i(7));
                      MHPMEVENT9(h)  <= (PCER(h)(8) or csr_wdata_i(8));
                      MHPMEVENT10(h) <= (PCER(h)(9) or csr_wdata_i(9));

                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= PCER(h);
                    if(rs1(instr_word_IE) /= 0) then
                      PCER(h)        <= (PCER(h) and not(csr_wdata_i));
                      MHPMEVENT3(h)  <= (PCER(h)(2) and not (csr_wdata_i(2)));
                      MHPMEVENT6(h)  <= (PCER(h)(5) and not (csr_wdata_i(5)));
                      MHPMEVENT7(h)  <= (PCER(h)(6) and not (csr_wdata_i(6)));
                      MHPMEVENT8(h)  <= (PCER(h)(7) and not (csr_wdata_i(7)));
                      MHPMEVENT9(h)  <= (PCER(h)(8) and not (csr_wdata_i(8)));
                      MHPMEVENT10(h) <= (PCER(h)(9) and not (csr_wdata_i(9)));
                    end if;
                  when others =>
                    null;
                end case;

              when MHPMEVENT3_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= (2 => MHPMEVENT3(h), others => '0');
                    MHPMEVENT3(h)             <= csr_wdata_i(2);
                    PCER(h)(2)                <= csr_wdata_i(2);
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= (2 => MHPMEVENT3(h), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMEVENT3(h) <= (MHPMEVENT3(h) or csr_wdata_i(2));
                      PCER(h)(2)    <= (PCER(h)(2) or csr_wdata_i(2));
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= (2 => MHPMEVENT3(h), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMEVENT3(h) <= (MHPMEVENT3(h) and not(csr_wdata_i(2)));
                    end if;
                  when others =>
                    null;
                end case;



              when MHPMEVENT6_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= (5 => MHPMEVENT6(h), others => '0');
                    MHPMEVENT6(h)             <= csr_wdata_i(5);
                    PCER(h)(5)                <= csr_wdata_i(5);
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= (5 => MHPMEVENT6(h), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMEVENT6(h) <= (MHPMEVENT6(h) or csr_wdata_i(5));
                      PCER(h)(5)    <= (PCER(h)(5) or csr_wdata_i(5));
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= (5 => MHPMEVENT6(h), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMEVENT6(h) <= (MHPMEVENT6(h) and not(csr_wdata_i(5)));
                    end if;
                  when others =>
                    null;
                end case;

              when MHPMEVENT7_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= (6 => MHPMEVENT7(h), others => '0');
                    MHPMEVENT7(h)             <= csr_wdata_i(6);
                    PCER(h)(6)                <= csr_wdata_i(6);
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= (6 => MHPMEVENT7(h), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMEVENT7(h) <= (MHPMEVENT7(h) or csr_wdata_i(6));
                      PCER(h)(6)    <= (PCER(h)(6) or csr_wdata_i(6));
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= (6 => MHPMEVENT7(h), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMEVENT7(h) <= (MHPMEVENT7(h) and not(csr_wdata_i(6)));
                    end if;
                  when others =>
                    null;
                end case;

              when MHPMEVENT8_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= (7 => MHPMEVENT8(h), others => '0');
                    MHPMEVENT8(h)             <= csr_wdata_i(7);
                    PCER(h)(7)                <= csr_wdata_i(7);
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= (7 => MHPMEVENT8(h), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMEVENT8(h) <= (MHPMEVENT8(h) or csr_wdata_i(7));
                      PCER(h)(7)    <= (PCER(h)(7) or csr_wdata_i(7));
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= (7 => MHPMEVENT8(h), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMEVENT8(h) <= (MHPMEVENT8(h) and not(csr_wdata_i(7)));
                    end if;
                  when others =>
                    null;
                end case;

              when MHPMEVENT9_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= (8 => MHPMEVENT9(h), others => '0');
                    MHPMEVENT9(h)             <= csr_wdata_i(8);
                    PCER(h)(8)                <= csr_wdata_i(8);
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= (8 => MHPMEVENT9(h), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMEVENT9(h) <= (MHPMEVENT9(h) or csr_wdata_i(8));
                      PCER(h)(8)    <= (PCER(h)(8) or csr_wdata_i(8));
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= (8 => MHPMEVENT9(h), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMEVENT9(h) <= (MHPMEVENT9(h) and not(csr_wdata_i(8)));
                    end if;
                  when others =>
                    null;
                end case;

              when MHPMEVENT10_addr =>
                case csr_op_i is
                  when CSRRW|CSRRWI =>
                    csr_rdata_o_replicated(h) <= (9 => MHPMEVENT10(h), others => '0');
                    MHPMEVENT10(h)            <= csr_wdata_i(9);
                    PCER(h)(9)                <= csr_wdata_i(9);
                  when CSRRS|CSRRSI =>
                    csr_rdata_o_replicated(h) <= (9 => MHPMEVENT10(h), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMEVENT10(h) <= (MHPMEVENT10(h) or csr_wdata_i(9));
                      PCER(h)(9)     <= (PCER(h)(9) or csr_wdata_i(9));
                    end if;
                  when CSRRC|CSRRCI =>
                    csr_rdata_o_replicated(h) <= (9 => MHPMEVENT10(h), others => '0');
                    if(rs1(instr_word_IE) /= 0) then
                      MHPMEVENT10(h) <= (MHPMEVENT10(h) and not(csr_wdata_i(9)));
                    end if;
                  when others =>
                    null;
                end case;

              when others =>
                csr_rdata_o_replicated(h) <= (others => '0');
            end case;
          else
            null;
          end if;
        end if;


        if dbg_req_o = '0' then
          if (PCER(h)(0) = '1'
              and not(csr_instr_req = '1'
                      and (csr_addr_i = (MCYCLE_addr)
                           or csr_addr_i = MCYCLEH_addr)
                      and (csr_op_i = CSRRWI
                           or csr_op_i = CSRRW
                           or (csr_op_i = CSRRS and rs1(instr_word_IE) /= 0)
                           or (csr_op_i = CSRRSI and rs1(instr_word_IE) /= 0)
                           or (csr_op_i = CSRRC and rs1(instr_word_IE) /= 0)
                           or (csr_op_i = CSRRCI and rs1(instr_word_IE) /= 0)
                           )
                      )
              )
          then
            if(MCYCLE(h) = x"FFFFFFFF") then
              MCYCLEH(h) <= std_logic_vector(unsigned(MCYCLEH(h))+1);
              MCYCLE(h)  <= x"00000000";
            else
              MCYCLE(h) <= std_logic_vector(unsigned(MCYCLE(h))+1);
            end if;
          end if;

          if (PCER(h)(1) = '1'
              and not(csr_instr_req = '1'
                      and (csr_addr_i = (MINSTRET_addr)
                           or csr_addr_i = MINSTRETH_addr)
                      and (csr_op_i = CSRRWI
                           or csr_op_i = CSRRW
                           or (csr_op_i = CSRRS and rs1(instr_word_IE) /= 0)
                           or (csr_op_i = CSRRSI and rs1(instr_word_IE) /= 0)
                           or (csr_op_i = CSRRC and rs1(instr_word_IE) /= 0)
                           or (csr_op_i = CSRRCI and rs1(instr_word_IE) /= 0)
                           )
                      )
              )
          then
            if(instr_rvalid_i = '1') then
              if (MINSTRET(h) = x"FFFFFFFF") then
                MINSTRETH(h) <= std_logic_vector(unsigned(MINSTRETH(h))+1);
                MINSTRET(h)  <= x"00000000";
              else
                MINSTRET(h) <= std_logic_vector(unsigned(MINSTRET(h))+1);
              end if;
            end if;
          end if;

          if (PCER(h)(2) = '1') then
            if (((data_req_o_wire_top = '1' and data_gnt_i = '0') and data_valid_waiting_counter = '0') or ((not(data_req_o_wire_top = '1' and data_gnt_i = '0')) and data_valid_waiting_counter = '1')) then
              MHPMCOUNTER3(h) <= std_logic_vector(unsigned(MHPMCOUNTER3(h))+1);
            elsif((data_req_o_wire_top = '1' and data_gnt_i = '0') and (data_valid_waiting_counter = '1')) then
              MHPMCOUNTER3(h) <= std_logic_vector(unsigned(MHPMCOUNTER3(h))+2);
            end if;
          end if;


          if(PCER(h)(5) = '1') then
            if (data_req_o_wire_top = '1' and data_gnt_i = '1' and data_we_o_wire_top = '0') then
              MHPMCOUNTER6(h) <= std_logic_vector(unsigned(MHPMCOUNTER6(h))+1);
            end if;
          end if;

          if(PCER(h)(6) = '1') then
            if (data_req_o_wire_top = '1' and data_gnt_i = '1' and data_we_o_wire_top = '1') then
              MHPMCOUNTER7(h) <= std_logic_vector(unsigned(MHPMCOUNTER7(h))+1);
            end if;
          end if;

          if(PCER(h)(7) = '1') then
            if (jump_instr = '1') then
              MHPMCOUNTER8(h) <= std_logic_vector(unsigned(MHPMCOUNTER8(h))+1);
            end if;
          end if;

          if(PCER(h)(8) = '1') then
            if (branch_instr = '1') then
              MHPMCOUNTER9(h) <= std_logic_vector(unsigned(MHPMCOUNTER9(h))+1);
            end if;
          end if;

          if(PCER(h)(9) = '1') then
            if (branch_instr = '1' and set_branch_condition = '1') then
              MHPMCOUNTER10(h) <= std_logic_vector(unsigned(MHPMCOUNTER10(h))+1);
            end if;
          end if;
        end if;
        if h = 0 and unsigned(irq_id_i) >= 28 and irq_i = '1' then
          MIP_internal(h)(7) <= '1';
        else
          MIP_internal(h)(7) <= '0';
        end if;
        if h = 0 and unsigned(irq_id_i) < 28 and irq_i = '1' then
          MIP_internal(h)(11) <= '1';
        else
          MIP_internal(h)(11) <= '0';
        end if;
      end if;
    end process;

  end generate CSR_updating_logic;

  process(all)
    variable wire1, wire2 : std_logic;
  begin
    wire1 := '0'; wire2 := '0';
    for h in harc_range loop
      wire1 := wire1 or csr_instr_done_replicated(h);
      wire2 := wire2 or csr_access_denied_o_replicated(h);
    end loop;
    csr_instr_done      <= wire1;
    csr_access_denied_o <= wire2;
  end process;

  csr_rdata_o <= csr_rdata_o_replicated(harc_IE);


  irq_ack_manager : process(clk_i, rst_ni)
    variable wire1 : std_logic;
  begin
    if rst_ni = '0' then
      irq_ack_o_internal <= '0';
    elsif rising_edge(clk_i) then
      wire1 := '0';
      for h in harc_range loop
        wire1 := wire1 or served_irq(h);
      end loop;
      case irq_ack_o_internal is
        when '0' =>
          if wire1 = '0' then
            irq_ack_o_internal <= '0';
          else
            irq_ack_o_internal <= '1';
            irq_id_o       <= irq_id_i;
          end if;
        when '1' =>
          irq_ack_o_internal <= '0';
        when others => null;
      end case;
    end if;
  end process irq_ack_manager;


end CSR;
