-- ieee packages ------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_misc.all;
use ieee.numeric_std.all;
use std.textio.all;

-- local packages ------------
use work.riscv_klessydra.all;
use work.thread_parameters_klessydra.all;

entity Debug_UNIT is
  port (
    set_branch_condition     : in  std_logic;
    set_except_condition     : in  std_logic;
    set_mret_condition       : in  std_logic;
    branch_condition_pending : in  replicated_bit;
    except_condition_pending : in  replicated_bit;
    mret_condition_pending   : in  replicated_bit;
    served_irq               : in  replicated_bit;
    irq_pending              : in  replicated_bit;
    taken_branch_pc_lat      : in  replicated_32b_reg;
    incremented_pc           : in  replicated_32b_reg;
    MTVEC                    : in  replicated_32b_reg;
    MIP                      : in  replicated_32b_reg;
    MSTATUS                  : in  replicated_32b_reg;
    MCAUSE                   : in  replicated_32b_reg;
    mepc_incremented_pc      : in  replicated_32b_reg;
    mepc_interrupt_pc        : in  replicated_32b_reg;
    regfile                  : in  regfile_replicated_array;
    pc_IE                    : in  std_logic_vector (31 downto 0);
    harc_IE                  : in  harc_range;
    ebreak_instr             : in  std_logic;
    dbg_ack_i                : in  std_logic;
	taken_branch			 : in std_logic;
	taken_branch_pending     : in replicated_bit;
    dbg_req_o                : out std_logic;
    dbg_halted_o             : out std_logic;
    clk_i                    : in  std_logic;
    rst_ni                   : in  std_logic;
    debug_req_i              : in  std_logic;
    debug_gnt_o              : out std_logic;
    debug_rvalid_o           : out std_logic;
    debug_addr_i             : in  std_logic_vector(14 downto 0);
    debug_we_i               : in  std_logic;
    debug_wdata_i            : in  std_logic_vector(31 downto 0);
    debug_rdata_o            : out std_logic_vector(31 downto 0);
    debug_halted_o           : out std_logic;
    debug_halt_i             : in  std_logic;
    debug_resume_i           : in  std_logic
    );
end entity;

architecture DBG of Debug_Unit is



  type fsm_DBU_states is (RUNNING, HALT_REQ, HALT, SINGLE_STEP_REQ, SINGLE_STEP);
  signal state_DBU       : fsm_DBU_states;
  signal nextstate_DBU   : fsm_DBU_states;
  signal dbg_halt_req    : std_logic;
  signal dbg_resume_core : std_logic;
  signal dbg_ssh         : std_logic;
  signal dbg_sse         : std_logic;

  -- wire only signals (For Synopsis Comaptibility)
  signal dbg_halted_o_wire : std_logic;

begin

  dbg_halted_o <= dbg_halted_o_wire;

  --DEBUG_UNIT--
  --There are two processes, one handle the minterface between the external and the core, and one memorize the debug state.

  DBU_interface_handler : process(clk_i, rst_ni)
  begin
    if(rst_ni = '0') then
      debug_rvalid_o  <= '0';
      debug_rdata_o   <= (others => 'Z');
      dbg_halt_req    <= '0';
      dbg_sse         <= '0';
      dbg_ssh         <= '1';
      dbg_resume_core <= '0';
    elsif rising_edge(clk_i) then
      dbg_ssh <= '1';
      if(debug_req_i = '1') then
        debug_rvalid_o <= '1';
        if(debug_we_i = '0') then       --read access
          case debug_addr_i(13 downto 8) is
            when "000000" =>            --debug register always accessible
              case debug_addr_i(6 downto 2) is
                when "00000" =>
                  debug_rdata_o <= (0      => dbg_halted_o_wire,
                                    16     => dbg_sse,
                                    others => '0');
                when "00001" =>
                  debug_rdata_o <= (0 => dbg_ssh, others => '0');
                when others =>
                  null;
              end case;
            when "100000" =>  --debug regster accessible only when core is halted, that's why there is a condition on dbg_halted_o_wire
              if dbg_halted_o_wire = '1' then
                case debug_addr_i(2) is
                  when '0' =>           --previous pc 
                    debug_rdata_o <= pc_ie;
                  when '1' =>
                    if served_irq(harc_IE) = '1' then  --next program counter, there rows came form the PC handler, but the destination is debug bus and not PC
                      debug_rdata_o <= MTVEC(harc_IE);
                    elsif (not taken_branch = '1' and not taken_branch_pending(harc_IE) = '1')
                    then
                      debug_rdata_o <= incremented_pc(harc_IE);
                    elsif set_branch_condition = '1' or branch_condition_pending(harc_IE) = '1' then
                      debug_rdata_o <= taken_branch_pc_lat(harc_IE);
                    elsif irq_pending(harc_IE) = '1' then
                      debug_rdata_o <= MTVEC(harc_IE);
                    elsif (set_except_condition or except_condition_pending(harc_IE)) = '1' then
                      debug_rdata_o <= MTVEC(harc_IE);
                    elsif (set_mret_condition or mret_condition_pending(harc_IE)) = '1' and MCAUSE(harc_IE)(31) = '0' then
                      debug_rdata_o <= mepc_incremented_pc(harc_IE);
                    elsif (set_mret_condition or mret_condition_pending(harc_IE)) = '1' and MCAUSE(harc_IE)(31) = '1' then
                      debug_rdata_o <= mepc_interrupt_pc(harc_IE);
                    end if;
                  when others =>
                    null;
                end case;
              end if;
            when "000100" =>            --GPR
              debug_rdata_o <= regfile(harc_IE)(to_integer(unsigned(debug_addr_i(6 downto 2))));
            when others =>
              null;
          end case;
        else                            --WRITE ACCESS
          debug_rvalid_o <= '0';
          case debug_addr_i(13 downto 8) is
            when "000000" =>            --debug register always accessible
              case debug_addr_i(6 downto 2) is
                when "00000" =>
                  if (debug_wdata_i(16) = '1') then
                    if dbg_halted_o_wire = '0' then
                      dbg_halt_req <= '1';
                    else
                      dbg_halt_req <= '0';
                    end if;
                  else
                    if dbg_halted_o_wire = '1' then
                      dbg_resume_core <= '1';
                      dbg_halt_req    <= '0';
                    end if;
                  end if;
                  if(debug_wdata_i(0) = '1') then
                    dbg_sse <= '1';
                  else
                    dbg_sse <= '0';
                  end if;
                when "00001" =>
                  if (debug_wdata_i(0) = '0') then
                    if dbg_sse = '1' and dbg_halted_o_wire = '1' then
                      dbg_ssh <= '0';
                    end if;
                  end if;
                when others =>
                  null;
              end case;
            when others =>
              null;
          end case;
        end if;
      end if;
    end if;
  end process;

  DBU_combinatory_access : process (all)
  begin
    if (debug_req_i = '1') then
      debug_gnt_o <= '1';
    else
      debug_gnt_o <= '0';
    end if;
  end process;

  --DEBUG_UNIT_NEXTSTATE
  fsm_Debug_Unit_nextstate : process(all)

  begin
    if rst_ni = '0' then
      nextstate_DBU     <= RUNNING;
      dbg_req_o         <= '0';
      dbg_halted_o_wire <= '0';
    else
      case state_DBU is
        when RUNNING =>
          dbg_halted_o_wire <= '0';
          if ebreak_instr = '1' then
            dbg_req_o <= '0';
            if dbg_sse = '1' then
              nextstate_DBU <= SINGLE_STEP;
            else
              nextstate_DBU <= HALT;
            end if;
          elsif dbg_halt_req = '1' then
            dbg_req_o <= '1';
            if dbg_sse = '0' then
              nextstate_DBU <= HALT_REQ;
            else
              nextstate_DBU <= SINGLE_STEP_REQ;
            end if;
          else
            dbg_req_o     <= '0';
            nextstate_DBU <= RUNNING;
          end if;
        when HALT_REQ =>
          dbg_halted_o_wire <= '0';
          dbg_req_o         <= '1';
          if dbg_ack_i = '1' then
            nextstate_DBU <= HALT;
          else
            if dbg_resume_core = '1' then
              nextstate_DBU <= RUNNING;
            else
              if dbg_sse = '1' then
                nextstate_DBU <= SINGLE_STEP_REQ;
              else
                nextstate_DBU <= HALT_REQ;
              end if;
            end if;
          end if;  --dbg_ack_i 
        when HALT =>
          dbg_req_o         <= '1';
          dbg_halted_o_wire <= '1';
          if dbg_resume_core = '1' then
            nextstate_DBU <= RUNNING;
          else
            if dbg_sse = '1' then
              nextstate_DBU <= SINGLE_STEP;
            else
              nextstate_DBU <= HALT;
            end if;
          end if;
        when SINGLE_STEP_REQ =>
          dbg_req_o         <= '1';
          dbg_halted_o_wire <= '0';
          if dbg_ack_i = '1' then
            nextstate_DBU <= SINGLE_STEP;
          else
            if dbg_sse = '0' then
              nextstate_DBU <= HALT_REQ;
            else
              nextstate_DBU <= SINGLE_STEP_REQ;
            end if;
          end if;
        when SINGLE_STEP =>
          if dbg_sse = '0' then
            if dbg_resume_core = '1' then
              nextstate_DBU     <= RUNNING;
              dbg_req_o         <= '0';
              dbg_halted_o_wire <= '0';
            else
              dbg_req_o         <= '1';
              dbg_halted_o_wire <= '1';
              nextstate_DBU     <= HALT;
            end if;
          elsif dbg_ssh = '0' then
            dbg_req_o         <= '0';
            dbg_halted_o_wire <= '0';
            nextstate_DBU     <= SINGLE_STEP_REQ;
          else
            dbg_req_o         <= '1';
            dbg_halted_o_wire <= '1';
            nextstate_DBU     <= SINGLE_STEP;
          end if;
        when others =>
          nextstate_DBU     <= RUNNING;
          dbg_req_o         <= '0';
          dbg_halted_o_wire <= '0';
      end case;
    end if;
  end process;


  fsm_DBU_register_state : process(clk_i, rst_ni)
  begin
    if rst_ni = '0' then
      state_DBU <= RUNNING;
    elsif rising_edge(clk_i) then
      state_DBU <= nextstate_DBU;
    end if;
  end process;

end DBG;
