----------------------------------------------------------------------------------
-- Project Name: RPU
-- Description: control unit
-- 
----------------------------------------------------------------------------------
-- Copyright 2016,2018,2019,2020 Colin Riley
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the 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.
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.all;

library work;
use work.constants.all;

entity control_unit is
    port (
        I_clk : in STD_LOGIC;
        I_reset : in STD_LOGIC;
        I_halt : in STD_LOGIC;
        I_aluop : in STD_LOGIC_VECTOR (6 downto 0);

        -- interrupts
        I_int_enabled : in std_logic;
        I_int : in STD_LOGIC;
        O_int_ack : out STD_LOGIC;
        I_int_mem_data : in STD_LOGIC_VECTOR(XLENM1 downto 0);
        O_idata : out STD_LOGIC_VECTOR(XLENM1 downto 0);
        O_set_idata : out STD_LOGIC;
        O_set_ipc : out STD_LOGIC;
        O_set_irpc : out STD_LOGIC;
        O_instTick : out STD_LOGIC;
        -- mem controller state and control 
        I_misalignment : in STD_LOGIC;
        I_ready : in STD_LOGIC;
        O_execute : out STD_LOGIC;
        I_dataReady : in STD_LOGIC;

        -- alu stall input
        I_aluWait : in STD_LOGIC;
        I_aluMultiCy : in STD_LOGIC;
        O_state : out STD_LOGIC_VECTOR (6 downto 0)
    );
end control_unit;

architecture Behavioral of control_unit is
    signal s_state : STD_LOGIC_VECTOR(6 downto 0) := "0000001";

    signal mem_ready : std_logic;
    signal mem_execute : std_logic := '0';
    signal mem_dataReady : std_logic;

    signal mem_cycles : integer := 0;

    signal next_s_state : STD_LOGIC_VECTOR(6 downto 0) := "0000001";

    signal interrupt_state : STD_LOGIC_VECTOR(2 downto 0) := "000";
    signal interrupt_ack : STD_LOGIC := '0';
    signal interrupt_was_inactive : STD_LOGIC := '1';
    signal set_idata : STD_LOGIC := '0';
    signal set_ipc : STD_LOGIC := '0';
    signal instTick : STD_LOGIC := '0';
    signal s_hasWaited : STD_LOGIC := '0';

    signal s_check_alignint : integer := 0;
begin

    O_execute <= mem_execute;
    mem_ready <= I_ready;
    mem_dataReady <= I_dataReady;
    O_int_ack <= interrupt_ack;
    O_set_idata <= set_idata;
    O_set_irpc <= set_idata;
    O_set_ipc <= set_ipc;
    O_instTick <= instTick;

    process (I_clk)
    begin
        if rising_edge(I_clk) and I_halt = '0' then

            if I_reset = '1' then
                s_state <= "0000001";
                next_s_state <= "0000001";
                mem_cycles <= 0;
                mem_execute <= '0';
                interrupt_was_inactive <= '1';
                interrupt_ack <= '0';
                interrupt_state <= "000";
                set_ipc <= '0';
                O_idata <= X"00000000";
                set_idata <= '0';
                instTick <= '0';
            else
                case s_state is
                    ---------------------------
                    -- FETCH     
                    when "0000001" => -- fetch

                        if s_check_alignint /= 0 then
                            -- If we've seen an alignment hint we need to stall here for s_check_alignint
                            -- cycles, checking for an interrupt each time. When it's 0 we give up.
                            if I_int_enabled = '1' and interrupt_was_inactive = '1' and I_int = '1' then
                                interrupt_ack <= '1';
                                interrupt_was_inactive <= '0';
                                interrupt_state <= "001";
                                next_s_state <= "0000001"; --F
                                s_state <= "1000000"; --S
                                s_check_alignint <= 0;
                            else
                                s_check_alignint <= s_check_alignint - 1;
                            end if;
                        else

                            if I_int = '0' then
                                interrupt_was_inactive <= '1';
                            end if;
                            instTick <= '0';
                            if mem_cycles = 0 and mem_ready = '1' then
                                mem_execute <= '1';
                                mem_cycles <= 1;

                            elsif mem_cycles = 1 then
                                mem_execute <= '0';
                                mem_cycles <= 2;

                            elsif mem_cycles = 2 then
                                mem_execute <= '0';
                                if mem_dataReady = '1' then
                                    mem_cycles <= 0;
                                    s_state <= "0000010";
                                end if;
                            end if;

                        end if;

                    ---------------------------
                    -- DECODE                        
                    when "0000010" => --- decode
                        if I_int = '0' then
                            interrupt_was_inactive <= '1';
                        end if;
                        s_hasWaited <= '0';
                        s_state <= "0001000"; --E "0000100"; --R
                        
                    
                    ---------------------------
                    -- EXECUTE
                    when "0001000" => -- execute
                        if I_int = '0' then
                            interrupt_was_inactive <= '1';
                        end if;
                        --MEM/WB
                        -- if it's not a memory alu op, goto writeback
                        if (I_aluop(6 downto 2) = OPCODE_LOAD or
                            I_aluop(6 downto 2) = OPCODE_STORE) then
                            s_state <= "0010000"; -- MEM

                            --  -- mem load short cut
                            --  ISSUE - this fails to take into account the type of request, sizing, address correctly
                            --          and therefore needs removed for compliance to pass.
                            --  if I_misalignment = '0' and mem_cycles = 0 and mem_ready = '1' then
                            --      mem_execute <= '1';
                            --      mem_cycles <= 1;
                            --  end if;

                        else
                            if I_aluWait = '0' then
                                if I_aluMultiCy = '1' then
                                    if s_hasWaited = '1' then
                                        s_state <= "0100000"; -- WB
                                    end if;
                                else
                                    s_state <= "0100000"; -- WB
                                end if;
                                s_hasWaited <= '1';
                            end if;
                        end if;

                    ---------------------------
                    -- MEMORY
                    when "0010000" => -- mem
                        if I_int = '0' then
                            interrupt_was_inactive <= '1';
                        end if;

                        -- alignment traps here are tricky.
                        -- if we see the misalignment hint, wait 6 cycles and then test interrupt stall.
                        -- if no interrupt we need to re-run the stage.

                        if I_misalignment = '1' and s_check_alignint = 0 then
                            s_check_alignint <= 6;
                        elsif s_check_alignint > 0 then
                            if I_int_enabled = '1' and interrupt_was_inactive = '1' and I_int = '1' then
                                interrupt_ack <= '1';
                                interrupt_was_inactive <= '0';
                                interrupt_state <= "001";
                                next_s_state <= "0000001"; --F
                                s_state <= "1000000"; --F
                                s_check_alignint <= 0;
                            else
                                s_check_alignint <= s_check_alignint - 1;
                            end if;

                        else
                            if mem_cycles = 0 and mem_ready = '1' then

                                mem_execute <= '1';
                                mem_cycles <= 1;

                            elsif mem_cycles = 1 then
                                mem_execute <= '0';
                                -- if it's a write, go through
                                if I_aluop(6 downto 2) = OPCODE_STORE then
                                    mem_cycles <= 0;
                                    s_state <= "0100000"; -- WB
                                elsif mem_dataReady = '1' then
                                    -- if read, wait for data
                                    mem_cycles <= 0;
                                    s_state <= "0100000"; -- WB
                                end if;
                            end if;
                        end if;


                    ---------------------------
                    -- WRITEBACK
                    when "0100000" => -- writeback
                        -- check interrupt?
                        if I_int_enabled = '1' and interrupt_was_inactive = '1' and I_int = '1' then
                            interrupt_ack <= '1';
                            interrupt_was_inactive <= '0';
                            interrupt_state <= "001";
                            next_s_state <= "0000001"; --F
                            s_state <= "1000000"; --F
                        else
                            if I_int = '0' then
                                interrupt_was_inactive <= '1';
                            end if;
                            if I_misalignment = '1' and s_check_alignint = 0 then
                                s_check_alignint <= 3;
                            end if;

                            -- misalign interrupts take a while to propagate
                            -- this signal short cuts to ensure we can catch any misalignments before fetch.

                            s_state <= "0000001"; --F

                            -- if the mem system is ready, shortcut the fetch
                            -- at this point, the next PC/Branch should be set.
                            -- need to ensure the sizing is correct.
                            if I_misalignment = '0' and mem_cycles = 0 and mem_ready = '1' then -- shortcut
                                mem_execute <= '1'; -- shortcut
                                mem_cycles <= 2;    -- shortcut 
                            end if;                 -- shortcut

                        end if;
                        instTick <= '1';
                    when "1000000" => --  stalls
                        if I_int = '0' then
                            interrupt_was_inactive <= '1';
                        end if;
                        instTick <= '0';
                        -- interrupt stall
                        if interrupt_state = "001" then
                            -- give a cycle of latency
                            -- set PC to interrupt vector.

                            set_ipc <= '1';
                            interrupt_state <= "101";

                        elsif interrupt_state = "101" then
                            set_ipc <= '0';
                            interrupt_ack <= '0';
                            interrupt_state <= "111";
                        elsif interrupt_state = "111" then
                            interrupt_state <= "000";
                            s_state <= "0000001"; --F
                        end if;
                    when "1001000" =>
                        -- alu 1 cycle stall
                        s_state <= "0100000"; -- WB
                    when others =>
                        s_state <= "0000001";
                end case;
            end if;
        end if;
    end process;

    O_state <= s_state;
end Behavioral;