LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
USE work.mips_types.all;

ENTITY e_mips_decoder IS
    PORT (
        i_instr:  IN  std_logic_vector(31 DOWNTO 0);
        o_reg_s:  OUT std_logic_vector( 4 DOWNTO 0);
        o_reg_t:  OUT std_logic_vector( 4 DOWNTO 0);
        o_reg_d:  OUT std_logic_vector( 4 DOWNTO 0);
        o_imm_a:  OUT std_logic_vector( 4 DOWNTO 0);
        o_imm_16: OUT std_logic_vector(15 DOWNTO 0);
        o_imm_26: OUT std_logic_vector(25 DOWNTO 0);
        o_op:     OUT t_op;
        o_link:   OUT t_link;
        o_cmp:    OUT t_cmp;
        o_alu:    OUT t_alu;
        o_imm:    OUT t_imm;
        o_ldst:   OUT t_ldst
    );
END ENTITY e_mips_decoder;

ARCHITECTURE a_mips_decoder OF e_mips_decoder IS

    TYPE t_enc_type IS (enc_reg, enc_imm, enc_jmp);

    SIGNAL s_opcode:   std_logic_vector(5 DOWNTO 0);
    SIGNAL s_ext_op:   std_logic_vector(4 DOWNTO 0);
    SIGNAL s_func:     std_logic_vector(5 DOWNTO 0);
    SIGNAL s_enc_type: t_enc_type;

BEGIN

    s_opcode <= i_instr(31 DOWNTO 26);
    s_ext_op <= i_instr(20 DOWNTO 16);
    s_func   <= i_instr( 5 DOWNTO  0);

    p_enc_type: PROCESS(s_opcode)
    BEGIN
        CASE s_opcode IS
            WHEN "000000" => s_enc_type <= enc_reg;
            WHEN "000010" => s_enc_type <= enc_jmp;
            WHEN "011010" => s_enc_type <= enc_jmp;
            WHEN OTHERS   => s_enc_type <= enc_imm;
        END CASE;
    END PROCESS p_enc_type;

    p_reg_s: PROCESS(i_instr, s_enc_type)
    BEGIN
        CASE s_enc_type IS
            WHEN enc_reg => o_reg_s <= i_instr(25 DOWNTO 21);
            WHEN enc_imm => o_reg_s <= i_instr(25 DOWNTO 21);
            WHEN OTHERS  => o_reg_s <= "00000";
        END CASE;
    END PROCESS p_reg_s;

    p_reg_t: PROCESS(i_instr, s_enc_type)
    BEGIN
        CASE s_enc_type IS
            WHEN enc_reg => o_reg_t <= i_instr(20 DOWNTO 16);
            WHEN enc_imm => o_reg_t <= i_instr(20 DOWNTO 16);
            WHEN OTHERS  => o_reg_t <= "00000";
        END CASE;
    END PROCESS p_reg_t;

    p_reg_d: PROCESS(i_instr, s_enc_type)
    BEGIN
        CASE s_enc_type IS
            WHEN enc_reg => o_reg_d <= i_instr(15 DOWNTO 11);
            WHEN OTHERS  => o_reg_d <= "00000";
        END CASE;
    END PROCESS p_reg_d;

    p_imm_a: PROCESS(i_instr, s_enc_type)
    BEGIN
        CASE s_enc_type IS
            WHEN enc_reg => o_imm_a <= i_instr(10 DOWNTO 6);
            WHEN OTHERS  => o_imm_a <= "00000";
        END CASE;
    END PROCESS p_imm_a;

    p_imm_16: PROCESS(i_instr, s_enc_type)
    BEGIN
        CASE s_enc_type IS
            WHEN enc_imm => o_imm_16 <= i_instr(15 DOWNTO 0);
            WHEN OTHERS  => o_imm_16 <= X"0000";
        END CASE;
    END PROCESS p_imm_16;

    p_imm_26: PROCESS(i_instr, s_enc_type)
    BEGIN
        CASE s_enc_type IS
            WHEN enc_jmp => o_imm_26 <= i_instr(25 DOWNTO 0);
            WHEN OTHERS  => o_imm_26 <= X"000000" & "00";
        END CASE;
    END PROCESS p_imm_26;

    p_op: PROCESS(s_opcode, s_ext_op, s_func)
    BEGIN
        o_op   <= op_none;
        o_link <= link_none;
        o_cmp  <= cmp_none;
        o_alu  <= alu_none;
        o_imm  <= imm_none;
        o_ldst <= ldst_none;
        CASE s_opcode IS
            WHEN "000000" =>
                CASE s_func IS
                    WHEN "000000" => o_op <= op_alu; o_alu <= alu_sll; o_imm <= imm_a;
                    WHEN "000010" => o_op <= op_alu; o_alu <= alu_srl; o_imm <= imm_a;
                    WHEN "000011" => o_op <= op_alu; o_alu <= alu_sra; o_imm <= imm_a;
                    WHEN "000100" => o_op <= op_alu; o_alu <= alu_sll;
                    WHEN "000110" => o_op <= op_alu; o_alu <= alu_srl;
                    WHEN "000111" => o_op <= op_alu; o_alu <= alu_sra;
                    WHEN "001000" => o_op <= op_j;
                    WHEN "001001" => o_op <= op_j; o_link <= link_link;
                    WHEN "010000" => o_op <= op_mfhi;
                    WHEN "010001" => o_op <= op_mtlo;
                    WHEN "010010" => o_op <= op_mfhi;
                    WHEN "010011" => o_op <= op_mtlo;
                    WHEN "011000" => o_op <= op_mult;
                    WHEN "011001" => o_op <= op_multu;
                    WHEN "011010" => o_op <= op_div;
                    WHEN "011011" => o_op <= op_divu;
                    WHEN "100000" => o_op <= op_alu; o_alu <= alu_add;
                    WHEN "100001" => o_op <= op_alu; o_alu <= alu_add;
                    WHEN "100010" => o_op <= op_alu; o_alu <= alu_sub;
                    WHEN "100011" => o_op <= op_alu; o_alu <= alu_sub;
                    WHEN "100100" => o_op <= op_alu; o_alu <= alu_and;
                    WHEN "100101" => o_op <= op_alu; o_alu <= alu_or;
                    WHEN "100110" => o_op <= op_alu; o_alu <= alu_xor;
                    WHEN "100111" => o_op <= op_alu; o_alu <= alu_nor;
                    WHEN "101010" => o_op <= op_alu; o_alu <= alu_slt;
                    WHEN "101011" => o_op <= op_alu; o_alu <= alu_sltu;
                    WHEN OTHERS => NULL;
                END CASE;
            WHEN "000001" => o_op <= op_j; o_imm <= imm_16se;
                             IF s_ext_op(0) = '1' THEN o_cmp <= cmp_gez;
                                                  ELSE o_cmp <= cmp_ltz;
                                                  END IF;
                             IF s_ext_op(4) = '1' THEN o_link <= link_link;
                                                  ELSE o_link <= link_none;
                                                  END IF;
            WHEN "000010" => o_op <= op_j; o_imm <= imm_26;
            WHEN "000011" => o_op <= op_j; o_link <= link_link; o_imm <= imm_26;
            WHEN "000100" => o_op <= op_j; o_cmp <= cmp_eq; o_imm <= imm_16se;
            WHEN "000101" => o_op <= op_j; o_cmp <= cmp_ne; o_imm <= imm_16se;
            WHEN "000110" => o_op <= op_j; o_cmp <= cmp_lez; o_imm <= imm_16se;
            WHEN "000111" => o_op <= op_j; o_cmp <= cmp_gtz; o_imm <= imm_16se;
            WHEN "001000" => o_op <= op_alu; o_alu <= alu_add; o_imm <= imm_16se;
            WHEN "001001" => o_op <= op_alu; o_alu <= alu_add; o_imm <= imm_16se;
            WHEN "001010" => o_op <= op_alu; o_alu <= alu_slt; o_imm <= imm_16se;
            WHEN "001011" => o_op <= op_alu; o_alu <= alu_sltu; o_imm <= imm_16se;
            WHEN "001100" => o_op <= op_alu; o_alu <= alu_and; o_imm <= imm_16ze;
            WHEN "001101" => o_op <= op_alu; o_alu <= alu_or; o_imm <= imm_16ze;
            WHEN "001110" => o_op <= op_alu; o_alu <= alu_xor; o_imm <= imm_16ze;
            WHEN "001111" => o_op <= op_alu; o_alu <= alu_up; o_imm <= imm_16ze;
            WHEN "100000" => o_op <= op_l; o_imm <= imm_16se; o_ldst <= ldst_b;
            WHEN "100001" => o_op <= op_l; o_imm <= imm_16se; o_ldst <= ldst_h;
            WHEN "100010" => o_op <= op_l; o_imm <= imm_16se; o_ldst <= ldst_wl;
            WHEN "100011" => o_op <= op_l; o_imm <= imm_16se; o_ldst <= ldst_w;
            WHEN "100100" => o_op <= op_l; o_imm <= imm_16se; o_ldst <= ldst_bu;
            WHEN "100101" => o_op <= op_l; o_imm <= imm_16se; o_ldst <= ldst_hu;
            WHEN "100110" => o_op <= op_l; o_imm <= imm_16se; o_ldst <= ldst_wr;
            WHEN "101000" => o_op <= op_s; o_imm <= imm_16se; o_ldst <= ldst_b;
            WHEN "101001" => o_op <= op_s; o_imm <= imm_16se; o_ldst <= ldst_h;
            WHEN "101010" => o_op <= op_s; o_imm <= imm_16se; o_ldst <= ldst_wl;
            WHEN "101011" => o_op <= op_s; o_imm <= imm_16se; o_ldst <= ldst_w;
            WHEN "101110" => o_op <= op_s; o_imm <= imm_16se; o_ldst <= ldst_wr;
            WHEN OTHERS => NULL;
        END CASE;
    END PROCESS p_op;

END ARCHITECTURE a_mips_decoder;