library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library work;
use work.harv_pkg.all;

entity control is
  port (
    -- input ports
    -- processor status
    imem_gnt_i : in std_logic;
    imem_err_i : in std_logic;
    dmem_gnt_i : in std_logic;
    dmem_err_i : in std_logic;
    dmem_sbu_i : in std_logic;
    dmem_dbu_i : in std_logic;

    -- instruction decode
    opcode_i  : in std_logic_vector(6 downto 0);
    funct3_i  : in std_logic_vector(2 downto 0);
    funct7_i  : in std_logic_vector(6 downto 0);
    funct12_i : in std_logic_vector(11 downto 0);

    -- sync
    rstn_i  : in std_logic;
    clk_i   : in std_logic;
    start_i : in std_logic;

    -- output ports
    -- processor status
    imem_req_o : out std_logic;
    dmem_req_o : out std_logic;

    update_pc_o : out std_logic;
    trap_o      : out std_logic;

    -- ALU operations
    aluop_o      : out std_logic_vector(ALUOP_SIZE-1 downto 0);
    alusrc_imm_o : out std_logic;
    -- immediate selector
    imm_shamt_o : out std_logic;
    imm_up_o    : out std_logic;
    -- register file
    regwr_o  : out std_logic;
    -- control transfer
    inv_branch_o : out std_logic;
    branch_o     : out std_logic;
    jump_o       : out std_logic;
    jalr_o       : out std_logic;
    ecall_o      : out std_logic;
    -- mem access
    mem_rd_o   : out std_logic;
    mem_wr_o   : out std_logic;
    mem_ben_o  : out std_logic_vector(1 downto 0);
    mem_usgn_o : out std_logic; -- unsigned data
    -- U type
    load_upimm_o : out std_logic;
    auipc_o      : out std_logic;
    -- csr control
    csr_enable_o     : out std_logic;
    csr_source_imm_o : out std_logic;
    csr_maskop_o     : out std_logic;
    csr_clearop_o    : out std_logic
  );
end entity;

architecture arch of control is
  --------- PROCESSOR STATUS -----------
  subtype proc_status_t is std_logic_vector(2 downto 0);
  constant STAT_IDLE       : proc_status_t := "000";
  constant STAT_REQ_INSTR  : proc_status_t := "001";
  constant STAT_RUN        : proc_status_t := "010";
  constant STAT_DMEM_STALL : proc_status_t := "011";
  constant STAT_UPDATE_PC  : proc_status_t := "100";
  constant STAT_TRAP       : proc_status_t := "101";

  signal proc_status_r      : proc_status_t;
  signal next_proc_status_w : proc_status_t;

  -------- INSTRUCTION DECODE ----------
  constant R        : std_logic_vector(3 downto 0) := x"1";
  constant I_jalr   : std_logic_vector(3 downto 0) := x"2";
  constant I_load   : std_logic_vector(3 downto 0) := x"3";
  constant I_arith  : std_logic_vector(3 downto 0) := x"4";
  constant I_fence  : std_logic_vector(3 downto 0) := x"5";
  constant I_system : std_logic_vector(3 downto 0) := x"6";
  constant S        : std_logic_vector(3 downto 0) := x"7";
  constant B        : std_logic_vector(3 downto 0) := x"8";
  constant U_lui    : std_logic_vector(3 downto 0) := x"9";
  constant U_auipc  : std_logic_vector(3 downto 0) := x"A";
  constant U_jal    : std_logic_vector(3 downto 0) := x"B";

  signal instr_format_w : std_logic_vector(3 downto 0);

  --- CSR TYPES ---
  constant SYS_ECALL  : std_logic_vector(2 downto 0) := "000";
  constant SYS_CSRRW  : std_logic_vector(2 downto 0) := "001"; -- Atomic Read/Write CSR
  constant SYS_CSRRS  : std_logic_vector(2 downto 0) := "010"; -- Atomic Read and Set Bits in CSR
  constant SYS_CSRRC  : std_logic_vector(2 downto 0) := "011"; -- Atomic Read and Clear Bits in CSR
  constant SYS_CSRRWI : std_logic_vector(2 downto 0) := "101";
  constant SYS_CSRRSI : std_logic_vector(2 downto 0) := "110";
  constant SYS_CSRRCI : std_logic_vector(2 downto 0) := "111";

  -- auxiliar signals
  signal mem_wr_w : std_logic;
  signal mem_rd_w : std_logic;
  signal mem_req_w : std_logic;

  -- signal rshift_op_w   : std_logic_vector(ALUOP_SIZE-1 downto 0);
  -- signal add_op_w      : std_logic_vector(ALUOP_SIZE-1 downto 0);
  signal shift_op_w   : std_logic;
  signal add_sub_op_w : std_logic;
  signal aluop_w     : std_logic_vector(ALUOP_SIZE-1 downto 0);
  signal branch_op_w : std_logic_vector(ALUOP_SIZE-1 downto 0);

begin
  ------------------------- PROCESSOR STATUS ------------------------------
  -- STAT_REQ_INSTR
  imem_req_o <= '1' when proc_status_r = STAT_REQ_INSTR else '0';

  -- STAT_DMEM_STALL
  mem_req_w <= mem_rd_w or mem_wr_w;
  dmem_req_o <= '1' when proc_status_r = STAT_DMEM_STALL else '0';

  -- STAT_UPDATE_PC
  update_pc_o <= '1' when proc_status_r = STAT_UPDATE_PC else
                 '1' when proc_status_r = STAT_TRAP      else
                 '0';
  trap_o      <= '1' when proc_status_r = STAT_TRAP else '0';


  PROC_CURRENT_STATUS : process(clk_i, rstn_i)
  begin
    if rstn_i = '0' then
      proc_status_r <= STAT_IDLE;
    elsif rising_edge(clk_i) then
      proc_status_r <= next_proc_status_w;
    end if;
  end process;

  PROC_NEXT_STATUS : process(proc_status_r, start_i, imem_gnt_i, imem_err_i, dmem_err_i, mem_req_w, dmem_gnt_i)
  begin
    case proc_status_r is

      when STAT_IDLE =>
        if start_i = '1' then
          next_proc_status_w <= STAT_REQ_INSTR;
        else
          next_proc_status_w <= STAT_IDLE;
        end if;

      when STAT_REQ_INSTR =>
        if imem_err_i = '1' then
          next_proc_status_w <= STAT_IDLE;

        elsif imem_gnt_i = '1' then
          next_proc_status_w <= STAT_RUN;

        else
          next_proc_status_w <= STAT_REQ_INSTR;
        end if;

      when STAT_RUN =>
        if mem_req_w = '1' then
          next_proc_status_w <= STAT_DMEM_STALL;
        else
          next_proc_status_w <= STAT_UPDATE_PC;
        end if;

      when STAT_DMEM_STALL =>
        if dmem_err_i = '1' then -- or dmem_sbu_i = '1' or dmem_dbu_i = '1' then
          next_proc_status_w <= STAT_TRAP;
        elsif dmem_gnt_i = '1' then
          next_proc_status_w <= STAT_UPDATE_PC;
        else
          next_proc_status_w <= STAT_DMEM_STALL;
        end if;

      when STAT_UPDATE_PC =>
        next_proc_status_w <= STAT_REQ_INSTR;

      when STAT_TRAP =>
        next_proc_status_w <= STAT_REQ_INSTR;

      when others =>
        next_proc_status_w <= STAT_TRAP;

    end case;
  end process;

  ------------------------ INSTRUCTIONS DECODE ----------------------------
  with opcode_i select instr_format_w <=
    R               when "0110011",
    I_jalr          when "1100111",
    I_load          when "0000011",
    I_arith         when "0010011",
    I_fence         when "0001111",
    I_system        when "1110011",
    S               when "0100011",
    B               when "1100011",
    U_lui           when "0110111",
    U_auipc         when "0010111",
    U_jal           when "1101111",
    (others => '0') when others;
  --------------------------------- ALU -----------------------------------

  with funct3_i(2 downto 1) select branch_op_w <=
    ALU_XOR_OP  when "00", -- beq  or bne
    ALU_SLT_OP  when "10", -- blt  or bge
    ALU_SLTU_OP when "11", -- bltu or bgeu
    ALU_XOR_OP  when others; -- not in specification

  shift_op_w   <= '1' when funct3_i(1 downto 0) = "01"  else '0';
  add_sub_op_w <= '1' when funct3_i             = "000" else '0';

  aluop_w <= (funct7_i(5) and shift_op_w)                   & funct3_i when instr_format_w = I_arith else
             (funct7_i(5) and (shift_op_w or add_sub_op_w)) & funct3_i when instr_format_w = R       else
             branch_op_w                                               when instr_format_w = B       else
             ALU_ADD_OP; -- when instr_format_w = U_auipc, I_load, S
  aluop_o <= aluop_w;
  alusrc_imm_o <= '1' when instr_format_w /= R and instr_format_w /= B else '0';

  ------------------------ IMMEDIATE SELECTOR ------------------------------
  -- instr[24:20]
  imm_shamt_o <= '1' when (instr_format_w = I_arith or instr_format_w = R) and shift_op_w = '1'  else '0';
  -- instr[31:12] -> imm[31:12]
  imm_up_o <= '1' when instr_format_w = U_lui   or
                       instr_format_w = U_auipc else '0';

  ----------------------------- REGISTER FILE -------------------------------
  regwr_o <= '1' when proc_status_r = STAT_UPDATE_PC and not (
                      instr_format_w  = I_fence                            or
                      (instr_format_w = I_system and funct3_i = SYS_ECALL) or
                      instr_format_w  = S or instr_format_w  = I_load      or
                      instr_format_w  = B)                                 else
             '1' when proc_status_r  = STAT_DMEM_STALL and
                      instr_format_w = I_load          and
                      dmem_gnt_i     = '1'             else
             '0';

  --------------------------- CONTROL TRANSFER ------------------------------
  inv_branch_o <= funct3_i(2) xor funct3_i(0);
  branch_o <= '1' when instr_format_w = B else '0';
  jump_o   <= '1' when instr_format_w = U_jal or instr_format_w = I_jalr else '0';
  jalr_o   <= '1' when instr_format_w = I_jalr else '0';

  ecall_o  <= '0'; -- '1' when instr_format_w = I_system else

  ------------------------------ MEM ACCESS ---------------------------------
  mem_rd_w   <= '1' when instr_format_w = I_load else '0';
  mem_rd_o   <= mem_rd_w;
  mem_wr_w   <= '1' when instr_format_w = S else '0';
  mem_wr_o   <= mem_wr_w;
  mem_ben_o  <= funct3_i(1 downto 0) when funct3_i(1) = '0' else "11"; -- byte or halfword -- else word
  mem_usgn_o <= funct3_i(2);

  -------------------------------- U type -----------------------------------
  load_upimm_o <= '1' when instr_format_w = U_lui else '0';
  auipc_o      <= '1' when instr_format_w = U_auipc else '0';

  -------------------------- CSR instructions  ------------------------------
  csr_enable_o     <= '1' when instr_format_w = I_system and funct3_i /= SYS_ECALL else '0';
  csr_source_imm_o <= funct3_i(2); -- select source to write the CSR (immediate or register)
  csr_maskop_o     <= funct3_i(1); -- write operation based on mask
  csr_clearop_o    <= funct3_i(0); -- operation is CLEAR, if not, operation is SET

end architecture;
