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 Program_Counter is
  port (
    absolute_jump                     : in  std_logic;
    data_we_o_lat                     : in  std_logic;
    PC_offset                         : in  replicated_32b_reg;
    taken_branch                      : in  std_logic;
    set_branch_condition              : in  std_logic;
    set_except_condition              : in  std_logic;
    set_mret_condition                : in  std_logic;
    set_wfi_condition                 : in  std_logic;
    harc_ID                           : in  harc_range;
    harc_IE                           : in  harc_range;
    instr_rvalid_IE                   : in  std_logic;
    pc_IE                             : in  std_logic_vector(31 downto 0);
    MIP, MEPC, MSTATUS, MCAUSE, MTVEC : in  replicated_32b_reg;
    instr_word_IE                     : in  std_logic_vector(31 downto 0);
    reset_state                       : in  std_logic;
    pc_IF                             : out std_logic_vector(31 downto 0);
    harc_IF                           : out harc_range;
    served_except_condition           : out replicated_bit;
    served_mret_condition             : out replicated_bit;
    served_irq                        : in  replicated_bit;
	taken_branch_pending              : out replicated_bit; 
    taken_branch_pc_lat               : out replicated_32b_reg;
    incremented_pc                    : out replicated_32b_reg;
    mepc_incremented_pc               : out replicated_32b_reg;
    mepc_interrupt_pc                 : out replicated_32b_reg;
    irq_pending                       : out replicated_bit;
    branch_condition_pending          : out replicated_bit;
    except_condition_pending          : out replicated_bit;
    mret_condition_pending            : out replicated_bit;
    clk_i                             : in  std_logic;
    rst_ni                            : in  std_logic;
    irq_i                             : in  std_logic;
    fetch_enable_i                    : in  std_logic;
    boot_addr_i                       : in  std_logic_vector(31 downto 0);
    instr_gnt_i                       : in  std_logic
    );
end entity;



architecture PC of Program_counter is

  signal served_except_condition_lat     : std_logic;
  signal pc_update_enable                : replicated_bit;
  signal wfi_condition_pending           : replicated_bit;
  signal taken_branch_replicated         : replicated_bit;
  signal set_branch_condition_replicated : replicated_bit;
  signal set_wfi_condition_replicated    : replicated_bit;
  signal set_except_condition_replicated : replicated_bit;
  signal set_mret_condition_replicated   : replicated_bit;
  signal relative_to_PC                  : replicated_32b_reg;
  signal pc                              : replicated_32b_reg;
  signal boot_pc                         : std_logic_vector(31 downto 0);

  signal harc_IF_internal                  : harc_range;
  signal harc_IF_comb                      : harc_range;
  signal mret_condition_pending_internal   : replicated_bit;
  signal mepc_incremented_pc_internal      : replicated_32b_reg;
  signal incremented_pc_internal           : replicated_32b_reg;
  signal mepc_interrupt_pc_internal        : replicated_32b_reg;
  signal taken_branch_pc_lat_internal      : replicated_32b_reg;
  signal taken_branch_pending_internal     : replicated_bit;
  signal except_condition_pending_internal : replicated_bit;
  signal irq_pending_internal              : replicated_bit;

  procedure pc_update(
    signal MTVEC                     : in    std_logic_vector(31 downto 0);
    signal instr_gnt_i, taken_branch : in    std_logic;
    signal wfi_condition_pending     : inout std_logic;
    signal set_wfi_condition         : in    std_logic;
    signal taken_branch_pending      : inout std_logic;
    signal irq_pending               : in    std_logic;
    signal set_except_condition      : in    std_logic;
    signal set_mret_condition        : in    std_logic;
    signal pc                        : inout std_logic_vector(31 downto 0);
    signal taken_branch_pc_lat       : in    std_logic_vector(31 downto 0);
    signal incremented_pc            : in    std_logic_vector(31 downto 0);
    signal boot_pc                   : in    std_logic_vector(31 downto 0);
    signal pc_update_enable          : in    std_logic;
    signal served_except_condition   : out   std_logic;
    signal served_mret_condition     : out   std_logic) is
  begin
    if pc_update_enable = '1' then

      if not taken_branch = '1' and not taken_branch_pending = '1'
      then
        pc                      <= incremented_pc;
        served_except_condition <= '0';
        served_mret_condition   <= '0';
      elsif taken_branch = '1' or taken_branch_pending = '1' then
        pc                      <= taken_branch_pc_lat;
        taken_branch_pending    <= '0';
        served_except_condition <= '1' when set_except_condition = '1' else '0';
        served_mret_condition   <= '1' when set_mret_condition = '1' else '0';
      else
        pc <= boot_pc;
      end if;
    else
      served_except_condition <= '0';
      served_mret_condition   <= '0';
      if taken_branch = '1' then
        taken_branch_pending <= '1';
      end if;
      if set_except_condition = '1' then
        served_except_condition <= '1';
      end if;
      if set_mret_condition = '1' then
        served_mret_condition <= '1';
      end if;
      if set_wfi_condition = '1' then
         wfi_condition_pending <= '1';
      end if;
    end if;
  end pc_update;

begin

  harc_IF                  <= harc_IF_internal;
  mret_condition_pending   <= mret_condition_pending_internal;
  mepc_incremented_pc      <= mepc_incremented_pc_internal;
  mepc_interrupt_pc        <= mepc_interrupt_pc_internal;
  taken_branch_pc_lat      <= taken_branch_pc_lat_internal;
  incremented_pc           <= incremented_pc_internal;
  taken_branch_pending     <= taken_branch_pending_internal;
  except_condition_pending <= except_condition_pending_internal;
  irq_pending              <= irq_pending_internal;

  hardware_context_counter : process(all)
  begin
    if rst_ni = '0' then
      harc_IF_internal <= THREAD_POOL_SIZE -1;
    elsif rising_edge(clk_i) then
      if instr_gnt_i = '1' then
        harc_IF_internal <= harc_IF_internal - 1 when harc_IF_internal > 0 else THREAD_POOL_SIZE -1;
      end if;
    end if;
  end process hardware_context_counter;

  pc_IF <= pc(harc_IF_internal);

  boot_pc                               <= boot_addr_i(31 downto 8) & std_logic_vector(to_unsigned(128, 8));
  mepc_incremented_pc_internal(harc_IE) <= MEPC(harc_IE);
  mepc_interrupt_pc_internal(harc_IE)   <= MEPC(harc_IE) when MCAUSE(harc_IE)(30) = '0' else std_logic_vector(unsigned(MEPC(harc_IE)) + 4);
  pc_update_logic : for h in harc_range generate

    relative_to_PC(h) <= std_logic_vector(to_unsigned(0, 32)) when (absolute_jump = '1')
                         else pc_IE;
    incremented_pc_internal(h) <= std_logic_vector(unsigned(pc(h))+4);
    irq_pending_internal(h)    <= ((MIP(h)(11) or MIP(h)(7) or MIP(h)(3)) and MSTATUS(h)(3));

    set_wfi_condition_replicated(h) <= '1' when set_wfi_condition = '1' and (harc_IE = h)
                                       else '0';
    taken_branch_replicated(h) <= '1' when taken_branch = '1' and (harc_IE = h)
                                  else '0';
    set_branch_condition_replicated(h) <= '1' when set_branch_condition = '1' and (harc_IE = h)
                                          else '0';
    set_except_condition_replicated(h) <= '1' when set_except_condition = '1' and (harc_IE = h)
                                          else '0';
    set_mret_condition_replicated(h) <= '1' when set_mret_condition = '1' and (harc_IE = h)
                                        else '0';


    taken_branch_pc_lat_internal(h) <=
      std_logic_vector(signed(relative_to_PC(h))+signed(PC_offset(h))) when set_branch_condition_replicated(h) = '1'                       else
      std_logic_vector(signed(relative_to_PC(h)))                      when set_wfi_condition_replicated(h) = '1'                           else
      MTVEC(h)                                                         when set_except_condition_replicated(h) = '1'                       else
      mepc_incremented_pc_internal(h)                                  when set_mret_condition_replicated(h) = '1' and MCAUSE(h)(31) = '0' else
      mepc_interrupt_pc_internal(h)                                    when set_mret_condition_replicated(h) = '1' and MCAUSE(h)(31) = '1' else
      MTVEC(h)                                                         when served_irq(h);


    pc_update_enable(h) <= '1' when instr_gnt_i = '1'
                           and (harc_IF_internal = h
                                or taken_branch_replicated(h) = '1'
                                or set_wfi_condition_replicated(h) = '1'
                                or taken_branch_pending_internal(h) = '1'
                                or wfi_condition_pending(h) = '1')
                           else '0';

    pc_updater : process(clk_i, rst_ni, boot_pc)
    begin
      if rst_ni = '0' then
        pc(h)                            <= (others => '0');
        taken_branch_pending_internal(h) <= '0';
        wfi_condition_pending(h)         <= '0';
        served_except_condition(h)       <= '0';
        served_mret_condition(h)         <= '0';
        served_except_condition_lat      <= '0';
      elsif rising_edge(clk_i) then
        served_except_condition_lat <= served_except_condition(h);
        if reset_state = '1' then
          pc(h) <= boot_pc;
        else
          pc_update(MTVEC(h), instr_gnt_i, taken_branch_replicated(h), wfi_condition_pending(h), set_wfi_condition_replicated(h), taken_branch_pending_internal(h),
                    irq_pending_internal(h), set_except_condition_replicated(h), set_mret_condition_replicated(h), pc(h),
                    taken_branch_pc_lat_internal(h), incremented_pc_internal(h), boot_pc, pc_update_enable(h), served_except_condition(h),
                    served_mret_condition(h));
        end if;
      end if;
    end process;


  end generate pc_update_logic;



end PC;
