// -*- c++ -*-
//
// arm_inst.h:
//
#ifndef	ARM_INST_H
#define	ARM_INST_H

#include <iostream>
#include "sim.h"

using namespace std;

//
// ARM instruction register w/ field extractor
//
class arm_inst {
    sim_inst    IR;
    int         inst_code;      // decoded instruction

    // for easy handling of operand registers
#define OPERAND_NOT_USED        (-1)
    int         A_reg;          // source register 1 (fed into A bus)
    int         B_reg;          // source register 2 (fed into B bus)
    int         C_reg;          // source register 3 (fed into C bus)
    int         D_reg;          // destination register 1 (update w/ ALUOutput)
    int         E_reg;          // destination register 2 (update w/ LMD/ALUOutput2)

public:
    arm_inst(sim_inst ir = 0) {
        IR = ir;
        decode_opcode();
        decode_operand();
    }

    sim_inst raw() { return IR; }
    int code() { return inst_code; }

    int Cond() { return (IR >> 28) & 0xf; }     // Cond field (IR[31:28]) (4bit)
    int Optype() { return (IR >> 25) & 0x7; }   // IR[27:25] (3bit)
    int Opcode() { return (IR >> 21) & 0xf; }   // IR[24:20] (4bit)

    // helper for Data Processing
    int Rn() { return (IR >> 16) & 0xf; }	// Rn (1st operand register)
    int Rd() { return (IR >> 12) & 0xf; }	// Rd (destination register)
    int Rm() { return (IR >>  0) & 0xf; }	// Rm (2nd operand register)
    int Operand2() { return IR & 0xfff; }	// lower 12bit of IR
    int I() { return (IR >> 25) & 0x1; }	// Immediate bit
    int S() { return (IR >> 20) & 0x1; }	// Set condition codes bit
    int shRm() { return (IR >> 4) & 1; }	// shift applied to Rm (0: constant, 1: variable)
    int shType() { return (IR >> 5) & 3; }	// shift type
    int shAmnt() { return (IR >> 7) & 0x1f; }   // shift amount
    int Rs() { return (IR >>  8) & 0xf; }	// Rs (shift register)
    int shImm8() { return (IR >> 8) & 0xf; }	// shift applied to Imm8

#define _unsignedImm(IR, n)\
    (IR & ((1 << (n))-1))
    int UImm8() { return _unsignedImm(IR, 8); }      // unsigned 8bit immediate value
    int UOffset12() { return _unsignedImm(IR, 12); } // unsigned 12bit offset value
    int UOffset24() { return _unsignedImm(IR, 24); } // unsigned 24bit offset value
#undef  _unsignedImm

#define _signedImm(IR, n)\
    (IR & (1 << ((n)-1)))? IR | ~((1 << (n))-1) : IR & ((1 << (n))-1)
    int Imm8() { return _signedImm(IR, 8); }         // signed 8bit immediate value
    int Offset12() { return _signedImm(IR, 12); }    // signed 12bit offset value
    int Offset24() { return _signedImm(IR, 24); }    // signed 24bit offset value
#undef  _signedImm

    // for Single Data Transfer
    int P() { return (IR >> 24) & 1; }	        // Pre/Post indexing bit
    int U() { return (IR >> 23) & 1; }	        // Up/Down bit
    int B() { return (IR >> 22) & 1; }	        // Byte/Word bit
    int W() { return (IR >> 21) & 1; }	        // Write-back bit
    int Ld() { return (IR >> 20) & 1; }	        // Load/Store bit

    // for Block Data Transfer
    int RegList() { return IR & 0xffff; }

    // for Branch/Branch with Link
    int L() { return (IR >> 24) & 1; }          // Link bit

    // for Multiply/Multiply-Accumulate
    int A() { return (IR >> 21) & 1; }          // Accumulate bit
    int SMul() { return (IR >> 22) & 1; }       // Singed Multiply bit

    // for Halfword and Signed Data Transfer
    int SH() { return (IR >> 5) & 3; }		// Sign/Halfword bit
    int UOffset8HL() { return ((IR >> 4) & 0xf0) | (IR & 0xf); }

    // decode instruction
    void decode_opcode();
    void decode_operand();

    bool is_memory_load();
    bool is_memory_store();

    // misc.
    friend ostream &operator<<(ostream &ost, arm_inst &inst) {
	ost << inst.raw();
	return ost;
    }

    bool has_src_operand_1() { return (A_reg != OPERAND_NOT_USED); }
    bool has_src_operand_2() { return (B_reg != OPERAND_NOT_USED); }
    bool has_src_operand_3() { return (C_reg != OPERAND_NOT_USED); }
    bool has_dest_operand_1() { return (D_reg != OPERAND_NOT_USED); }
    bool has_dest_operand_2() { return (E_reg != OPERAND_NOT_USED); }

    int regno_src_operand_1() { return A_reg; }
    int regno_src_operand_2() { return B_reg; }
    int regno_src_operand_3() { return C_reg; }
    int regno_dest_operand_1() { return D_reg; }
    int regno_dest_operand_2() { return E_reg; }

};

//
// ARM decoded instruction types
//
enum {
    ARM_INST_UNDEF = -1,
    ARM_INST_NOP = 0,

    // branch/control transfer
    ARM_INST_BR_TYPE            = 0x10,
    ARM_INST_BX,                // branch and exchange
    ARM_INST_B,                 // branch
    ARM_INST_BL,                // branch with link

    // data processing (register-register)
    ARM_INST_ALU_TYPE           = 0x20,
    ARM_INST_AND,               // bitwise AND
    ARM_INST_EOR,               // bitwise Exculsive-OR
    ARM_INST_SUB,               // subtract
    ARM_INST_RSB,               // subtract reverse
    ARM_INST_ADD,               // add
    ARM_INST_ADC,               // add with carry
    ARM_INST_SBC,               // subtract with carry
    ARM_INST_RSC,               // subtract reverse with carry
    ARM_INST_TST,               // test (bitwise AND)
    ARM_INST_TEQ,               // test (bitwise EOR)
    ARM_INST_CMP,               // compare
    ARM_INST_CMN,               // compare negative
    ARM_INST_ORR,               // bitwise OR
    ARM_INST_MOV,               // move
    ARM_INST_BIC,               // bit clear
    ARM_INST_MVN,               // move with negate

    // data processing (register-immediate)
    ARM_INST_ALU_I_TYPE         = 0x40,
    ARM_INST_AND_I,             // bitwise AND
    ARM_INST_EOR_I,             // bitwise Exculsive-OR
    ARM_INST_SUB_I,             // subtract
    ARM_INST_RSB_I,             // subtract reverse
    ARM_INST_ADD_I,             // add
    ARM_INST_ADC_I,             // add with carry
    ARM_INST_SBC_I,             // subtract with carry
    ARM_INST_RSC_I,             // subtract reverse with carry
    ARM_INST_TST_I,             // test (bitwise AND)
    ARM_INST_TEQ_I,             // test (bitwise EOR)
    ARM_INST_CMP_I,             // compare
    ARM_INST_CMN_I,             // compare negative
    ARM_INST_ORR_I,             // bitwise OR
    ARM_INST_MOV_I,             // move
    ARM_INST_BIC_I,             // bit clear
    ARM_INST_MVN_I,             // move with negate

    // single data transfer (base+offset)
    ARM_INST_LD_TYPE            = 0x60,
    ARM_INST_LDR,               // load word
    ARM_INST_LDRB,              // load byte
    ARM_INST_LDRSB,             // load byte (signed)
    ARM_INST_LDRH,              // load halfword
    ARM_INST_LDRSH,             // load halfword (signed)

    ARM_INST_ST_TYPE            = 0x70,
    ARM_INST_STR,               // store word
    ARM_INST_STRB,              // store byte
    ARM_INST_STRSB,             // store byte (signed)
    ARM_INST_STRH,              // store halfword
    ARM_INST_STRSH,             // store halfword (signed)

    // single data transfer (base+index)
    ARM_INST_LD_RR_TYPE         = 0x80,
    ARM_INST_LDR_RR,            // load word
    ARM_INST_LDRB_RR,           // load byte
    ARM_INST_LDRSB_RR,          // load byte (signed)
    ARM_INST_LDRH_RR,           // load halfword
    ARM_INST_LDRSH_RR,          // load halfword (signed)

    ARM_INST_ST_RR_TYPE         = 0x90,
    ARM_INST_STR_RR,            // store word
    ARM_INST_STRB_RR,           // store byte
    ARM_INST_STRSB_RR,          // store byte (signed)
    ARM_INST_STRH_RR,           // store halfword
    ARM_INST_STRSH_RR,          // store halfword (signed)

    // multiple data transfer
    ARM_INST_LDSTM_TYPE         = 0xa0,
    ARM_INST_LDM,               // load multiple
    ARM_INST_STM,               // store multiple

    // swap
    ARM_INST_SWAP_TYPE          = 0xb0,
    ARM_INST_SWP,               // swap word
    ARM_INST_SWPB,              // swap byte

    // multiplication
    ARM_INST_MULT_TYPE          = 0xc0,
    ARM_INST_MUL,               // multiply
    ARM_INST_MULL,              // multiply long

    // coprocessor
    ARM_INST_COP_TYPE           = 0xd0,
    ARM_INST_CDP,               // coprocessor data processing
    ARM_INST_LDC,               // coprocessor load data
    ARM_INST_STC,               // coprocessor store data
    ARM_INST_MCR,               // coprocessor move from register
    ARM_INST_MRC,               // coprocessor move to register

    // PSR transfer
    ARM_INST_PSR_TYPE           = 0xe0,
    ARM_INST_MRS,               // move PSR to register
    ARM_INST_MSR,               // move PSR from register

    // software interrupt/system call
    ARM_INST_TRAP_TYPE          = 0xf0,
    ARM_INST_SWI,               // software interrupt

    // max number of ARM_INST_*
    ARM_INST_MAXVAL,
};
        
#endif
// end of arm_inst.h