/*********************************************************************
----------------------------------------------------------------------
Purpose     : Ozone disassembly plugin for the AUCU AG32R2X core.
Literature  : [1] AUCU AU3X RISC-V MCU: User Manual
Author      : Weber Wang
E-Mail      : weber.wang@aucuic.com
---------------------------END-OF-HEADER------------------------------
*/

/*********************************************************************
*
* init
*
* Function Description
* Called by Ozone when the script was loaded
* (i.e. when command “Project.SetDisassemblyPlugin” was executed).
*
* Typical usage: executes one or multiple “Debug.enableOverrideInst”
* commands which define the instructions whose default disassembly
* is to be overridden by this plugin.
*
* Return Value
* 0 on success, -1 on error
*/
function init() {
    var aInst = new Array();
    var aMask = new Array();

    aMask[0] = 0x03;
    aMask[1] = 0xE0;

    aInst[0] = 0xFE;
    aInst[1] = 0x3F;
    Debug.enableOverrideInst(aInst, aMask); // Override C.LHU

    aInst[1] = 0xBF;
    Debug.enableOverrideInst(aInst, aMask); // Override C.SH

    aInst[0] = 0xFC;
    aInst[1] = 0x3F;
    Debug.enableOverrideInst(aInst, aMask); // Override C.LBU

    aInst[1] = 0xBF;
    Debug.enableOverrideInst(aInst, aMask); // Override C.SB

    return 0;
}

function printZeroExtInstAsm(Addr, Encoding, Flags)
{
    var opc = (Encoding >> 5) & 0x3;
    var mask = Encoding & 0xFC1F;
    var reg = (Encoding >> 7) & 0x7;

    if (mask != 0x9C01) {
        return undefined;
    }
    if (opc == 0) {
        sInst = "UXTB\t" + regName(reg + 8) + "\t";
    } else if (opc == 1) {
        sInst = "UXTH\t" + regName(reg + 8) + "\t";
    } else {
        return undefined;
    }
    return sInst;
}

function printCompressLoadByteUnsignedInstAsm(Addr, Encoding, Flags)
{
    var rs1 = (Encoding >> 7) & 0x7;
    var rd = (Encoding >> 2) & 0x7;
    var uimm = ((((Encoding >>10) & 0x7) << 2) + ((Encoding >> 5) & 0x3)) << 1;
    return "LBU\t" + regName(rd + 8) + ", " + uimm + "(" + regName(rs1 + 8) + ")";
}

function printCompressStoreInstAsm(Addr, Encoding, Flags)
{
    var rs1 = (Encoding >> 7) & 0x7;
    var rs2 = (Encoding >> 2) & 0x7;
    var uimm = ((((Encoding >>10) & 0x7) << 2) + ((Encoding >> 5) & 0x3)) << 1;
    return "SB\t" + regName(rs2 + 8) + ", " + uimm + "(" + regName(rs1 + 8) + ")";
}

function printCompressLoadStoreHalfInstAsm(Addr, Encoding, Flags)
{
    var func = (Encoding >> 13) & 0x7;
    var rs1 = (Encoding >> 7) & 0x7;
    var rd = (Encoding >> 2) & 0x7;
    var rs2 = rd;

    var uimm = ((((Encoding >> 10) & 0x7) << 2) + ((Encoding >> 5) & 0x3)) << 1;

    if (func == 1) {
        return "LHU\t" + regName(rd + 8) + ", " + uimm + "(" + regName(rs1 + 8) + ")";
    } else if (func == 5) {
        return "SH\t" + regName(rs2 + 8) + ", " + uimm + "(" + regName(rs1 + 8) + ")";
    }
    return undefined;
}

function printPushPopInstAsm(Addr, Encoding, Flags)
{
    var opc = (Encoding >> 2) & 0x3;
    var rcount = (Encoding >> 4) & 0xF;
    var sp16imm = ((Encoding >> 8) & 0x1F) << 4;

    if (opc >= 3) {
        return undefined;
    }
    if (rcount == 0) {
        return undefined;
    }

    var sOpc = new Array("POP", "POPRET", "PUSH");
    var sRcount = new Array("reserved", "ra", "ra,s0", "ra,s0-s1", "ra,s0-s2", "ra,s0-s3", "ra,s0-s4", "ra,s0-s5",
        "ra,s0-s6", "ra,s0-s7", "ra,s0-s8", "ra,s0-s9", "ra,s0-s10", "ra,s0-s11", "ra,s0-s11,a0", "ra,s0-s11,a0-a1");
    if (opc == 2) {
        var imm = (sp16imm + 1) * -16;
    } else {
        var imm = (sp16imm + 1) * 16;
    }
    return sOpc[opc] + "\t{" + sRcount[rcount] + "}," + imm;
}

function sext10(offset)
{
    var ofst = offset;

    if (offset > 510) {
        ofst |= 0xFFFFFFFF - 0x3FF;
        ofst = ofst << 0;
    }
    return ofst;
}

function sext8(offset)
{
    var ofst = offset;

    if (offset > 127) {
        ofst |= 0xFFFFFFFF - 0xFF;
        ofst = ofst << 0;
    }
    return ofst;
}

function printPrefInstAsm(Addr, Encoding, Flags)
{
    var opc = (Encoding >> 12) & 0x7;
    var imm = (Encoding >> 25) & 0x7F;
    var mask = (0x1F << 20) + (0x1F << 7);
    var rs1 = (Encoding >> 15) & 0x1F;

    if ((Encoding & mask) != 0) {
        return undefined;
    }
    var sOpc;
    if (opc == 2) {
        sOpc = "PREFI";
    } else if (opc == 3) {
        sOpc = "PREFD";
    } else {
        return undefined;
    }
    return sOpc + "\t" + imm + "(" + regName(rs1) + ")";
}

function GetRegisterListName(callee, mask, begin, end)
{
    var find = 0;
    var beginIdx = 0;
    var endIdx = 0;
    for (i = begin; i < end; ++i) {
        if (mask & (1 << i)) {
            if (find == 0) {
                find = 1;
                beginIdx = i;
                endIdx = i;
            } else {
                endIdx = i;
            }
        }
    }
    if (find == 1) {
        if (beginIdx == endIdx) {
            return callee[beginIdx];
        } else {
            regList = callee[beginIdx] + "-" + callee[endIdx];
            return regList;
        }
    }
    return undefined;
}

function GetRegisterListForCallee(gprMask)
{
    var regList;
    var mask = gprMask;
    var a = (mask & 0xC) >> 2;
    var b = (mask & 0x30) >> 4;
    var i;

    mask = (mask & ~0x3C);
    mask = mask | (a << 4) | (b << 2);

    var callee = new Array("ra", "sp", "a0", "a1", "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
        "s8", "s9", "s10", "s11");
    var resultArray = new Array();
    for (i = 0; i < 2; ++i) {
        if (mask & (1 << i)) {
            resultArray.push(callee[i]);
        }
    }
    var tmpMask = (mask >> 2) & 0x3;
    switch (tmpMask) {
        case 1:
            resultArray.push(callee[2]);
            break;
        case 2:
            resultArray.push(callee[3]);
            break;
        case 3:
            regList = callee[2] + "-" + callee[3];
            resultArray.push(regList);
            break;
        case 0:
        default:
            break;
    }

    var regList = GetRegisterListName(callee, mask, 4, 16);
    if (regList != undefined) {
        resultArray.push(regList);
    }

    return resultArray;
}

function GetRegisterListForCaller(gprMask)
{
    var mask = gprMask;
    var a = (mask & 0xE) >> 1;
    var b = (mask & 0xFF0) >> 4;
    mask = (mask & ~0xFFE);
    mask = mask | (a << 9) | (b << 1);

    var caller = new Array("ra","a0","a1","a2","a3","a4","a5","a6","a7","t0","t1","t2","t3","t4","t5","t6");
    var resultArray = new Array();
    if (mask & (1 << 0)) {
        resultArray.push(caller[0]);
    }

    var regList = GetRegisterListName(caller, mask, 9, 16);
    if (regList != undefined) {
        resultArray.push(regList);
    }
    var regList = GetRegisterListName(caller, mask, 1, 9);
    if (regList != undefined) {
        resultArray.push(regList);
    }

    return resultArray;
}

function printMiaInstAsm(Addr, Encoding, Flags) {
    var Encoding = (aInst[3] << 24) | (aInst[2] << 16) | (aInst[1] << 8) | aInst[0];
    var gprMask  = ((Encoding >> 7)  & 0x1F) | (((Encoding >> 20) & 0x7FF) << 5);
    var func     = (Encoding >> 12) & 0x7;
    var rs1      = (Encoding >> 15) & 0x1F;
    var eFlag = (Encoding >> 31) & 1;

    var sOp = "LDMIA";
    if (func == 1) {
        sOp = "STMIA";
    }

    var regList;
    if (eFlag == 0) {
        regList = GetRegisterListForCallee(gprMask);
    } else {
        regList = GetRegisterListForCaller(gprMask);
    }
    return sOp + "\t" + "{" + regList + "}," + "(" + regName(rs1) + ")";
}

function printPreShiftDataProcInstAsm(Addr, Encoding, Flags)
{
    var shType = (Encoding >> 30) & 0x3;
    var shamt = (Encoding >> 25) & 0x1F;
    var opc = (Encoding >> 12) & 0x7;
    var rs2 = (Encoding >> 20) & 0x1F;
    var rs1 = (Encoding >> 15) & 0x1F;
    var rd = (Encoding >> 7) & 0x1F;
    var sInst;

    if (opc >= 5) {
        return undefined;
    }
    if (shamt == 0) {
        return undefined;
    }

    var sOpc = new Array("ADDSHF", "SUBSHF", "ORSHF", "XORSHF", "ANDSHF");
    var sShType = new Array("SLL", "SRL", "SRA", "ROR");
    sInst = sOpc[opc] + "\t" + regName(rd) + ", " + regName(rs1) + ", " + regName(rs2) + ", " + sShType[shType] + ", " + shamt;
    return sInst;
}

function printConditionalBranchWithImmInstAsm(Addr, Encoding, Flags) {
    var cmpimm = (Encoding >> 24) & 0xFF;
    var offset = ((((Encoding >> 20) & 0xF) << 5) + ((Encoding >> 7) & 0x1F)) << 1;
    var opc = (Encoding >> 12) & 0x7;
    var rs1 = (Encoding >> 15) & 0x1F;
    var sInst;

    if ((opc == 2) || (opc == 3)) {
        return undefined;
    }

    var signedOfst = sext10(offset);
    var signedImm = sext8(cmpimm);

    var sOpc = new Array("BEQI", "BNEI", "Reserved", "Reserved", "BLTI", "BGEI", "BLTUI", "BGEUI");

    var jmpAddr = Addr + signedOfst;
    var sJmpAddr = jmpAddr.toString(16).toUpperCase();
    if ((opc == 4) || (opc == 5)) { // signed operation
        sInst = sOpc[opc] + "\t" + regName(rs1) + ", " + signedImm + ", 0x" + sJmpAddr;
    } else {
        sInst = sOpc[opc] + "\t" + regName(rs1) + ", " + cmpimm + ", 0x" + sJmpAddr;
    }
    var sSymbol = Debug.getSymbol(Addr + signedOfst);
    return sInst + "\t;" + sSymbol;
}

function printMulitAddInstAsm(Addr, Encoding, Flags)
{
    var uimm = ((Encoding >> 25) & 0x7F) << 1;
    var rs2 = (Encoding >> 20) & 0x1F;
    var rs1 = (Encoding >> 15) & 0x1F;;
    var opc = (Encoding >> 12) & 0x3;
    var rd = (Encoding >> 7) & 0x1F;

    if (opc != 1) {
        return undefined;
    }
    return "MULIADD\t" + regName(rd) + "," + regName(rs1) + "," + regName(rs2) + "," +uimm;
}

function printJAndJalImmInstAsm(Addr, Encoding, Flags)
{
    var imm = ((Encoding >> 8) & 0xF) << 21;
    var opc = (Encoding >> 7) & 0x1;
    var sOpc;
    imm += (Encoding >> 31) << 20;
    imm += ((Encoding >> 21) & 0x3FF) << 1;
    imm += ((Encoding >> 20) & 0x1) << 11;
    imm += ((Encoding >> 12) & 0xFF) << 12;

    if (opc == 0) {
        sOpc = "JAL16";
    } else {
        sOpc = "J16";
    }

    Addr += imm;
    var sSymbol = Debug.getSymbol(Addr);
    return sOpc + "\t" + "0x" + Addr.toString(16).toUpperCase() + "\t;" + sSymbol;
}

function printNormalInstAsm(Addr, aInst, Flags)
{
    var Encoding = (aInst[3] << 24) | (aInst[2] << 16) | (aInst[1] << 8) | aInst[0];
    var opcode = Encoding & 0x7F;

    switch (opcode) {
        case 0xB:
            var func = (Encoding >> 12) & 0x7;
            if ((func == 0) || (func == 1)){
                return printMiaInstAsm(Addr, Encoding, Flags);
            }
            if ((func == 2) || (func == 3)) {
                return printPrefInstAsm(Addr, Encoding, Flags);
            }
            break;

        case 0x1B:
            return printPreShiftDataProcInstAsm(Addr, Encoding, Flags);
        case 0x3B:
            return printConditionalBranchWithImmInstAsm(Addr, Encoding, Flags);
        case 0x5B:
            return printMulitAddInstAsm(Addr, Encoding, Flags);
        case 0x7B:
            return printJAndJalImmInstAsm(Addr, Encoding, Flags);
    }
    return sInst;
}

function printShortInstAsm(Addr, aInst, Flags)
{
    var Encoding = (aInst[1] << 8) | aInst[0];
    var opc = (Encoding & 0x3);
    var func = (Encoding >> 13) & 0x7;
    switch (opc) {
        case 0:
            switch (func) {
                case 1:
                    return printCompressLoadByteUnsignedInstAsm(Addr, Encoding, Flags);
                case 4:
                    return printPushPopInstAsm(Addr, Encoding, Flags);
                case 5:
                    return printCompressStoreInstAsm(Addr, Encoding, Flags);
                default:
                    break;
            }

        case 1:
            return printZeroExtInstAsm(Addr, Encoding, Flags);

        case 2:
            return printCompressLoadStoreHalfInstAsm(Addr, Encoding, Flags);

        default:
            break;
    }
    return undefined;
}

function printLoadImmInstAsm(Addr, Encoding, high, Flags)
{
    var rd = (Encoding >> 7) & 0x1F;
    var imm = (Encoding >> 16) & 0xFFFF;
    var rsv = (Encoding >> 12) & 0xF;

    if (rsv != 0) {
        return undefined;
    }
    return "L.LI\t" + regName(rd) + ", " + "0x" + high.toString(16).toUpperCase() + imm.toString(16).toUpperCase();
}

function printLongInstAsm(Addr, aInst, Flags)
{
    var Encoding = (aInst[3] << 24) | (aInst[2] << 16) | (aInst[1] << 8) | aInst[0];
    var high = (aInst[5] << 8) | aInst[4];
    var opc = Encoding & 0x7F;

    switch (opc) {
        case 0x1F:
            return printLoadImmInstAsm(Addr, Encoding, high, Flags);
    }
    return undefined;
}

/*********************************************************************
*
*       printInstAsm
*
*  Function Description
*    Prints the assembly code of an instruction.
*
*  Function Parameters
*    Addr     instruction address (type: integer)
*    aInst    instruction data bytes (type: byte array)
*    Flags    basic information about the instruction required for analysis.
*
*  Return Value
*    undefined:   if the input instruction is not supported by this plugin or an error occurred
*    string:      assembly code string containing a single tab after the instruction mnemonic and a single tab before a possible trailing comment
*/
function printInstAsm(Addr, aInst, Flags) {
    switch (aInst.length) {
        case 2:
            return printShortInstAsm(Addr, aInst, Flags);
        case 4:
            return printNormalInstAsm(Addr, aInst, Flags);
        case 6:
            return printLongInstAsm(Addr, aInst, Flags);
        default:
            break;
    }

  return undefined;
}

/*********************************************************************
*
*       getInstInfo
*
*  Function Description
*    Returns information about an instruction.
*
*    Used by Ozone to generate timeline stacks and call-graphs,
*    among other applications.
*
*  Function Parameters
*    Addr     instruction address (type: integer)
*    aInst    instruction data bytes (type: byte array)
*    Flags    basic information about the instruction required for analysis.
*
*  Return Value
*    undefined:   if the input instruction is not supported by this plugin or an error occurred
*    InstInfo:    an object corresponding to the following C structure:
*
*    struct INST_INFO {
*      U32 Mode;                      // instruction execution mode (for ex. THUMB or ARM)
*      U32 Size;                      // instruction byte size
*      U64 AccessAddress;             // branch address or memory access address
*      int StackAdjust;               // Difference of SP before and after instruction execution
*      struct {
*        U32 IsValid           : 1;   // all fields initialized
*        U32 IsControlTransfer : 1;   // Instruction possibly alters the PC (synchronously or asynchronously) (WFI, SVC, POP PC, LDR PC, ...)
*        U32 IsSoftIRQ         : 1;   // Instruction is a software interrupt request
*        U32 IsBranch          : 1;   // Instruction is a simple branch (B, JMP, ...)
*        U32 IsCall            : 1;   // Instruction is a function call (Branch with Link, BL, CALL, ...)
*        U32 IsReturn          : 1;   // Dedicated return instruction or return-style branch (e.g. POP PC)
*        U32 IsMemAccess       : 1;   // Instruction reads from or writes to memory
*        U32 IsFixedAddress    : 1;   // Branch or access address is fixed (absolute or PC-relative)
*        U32 IsBP              : 1;   // Instruction is a SW Breakpoint
*        U32 IsSemiHosting     : 1;   // Instruction could be a semihosting instruction (BKPT 0xAB or SVC 0xAB or SVC 0x123456)
*        U32 IsNOP             : 1;   // Instruction is a NOP
*        U32 IsConditional     : 1;   // Instruction is conditionally executed
*        U32 Condition         : 4;   // Condition if conditionally executed
*      } Flags;
*    }
*/
function getInstInfo(Addr, aInst, Flags) {
    if (aInst.length == 4) {
        //
        // convert byte array “aInst” to integer “Encoding”
        //
        var Encoding = (aInst[3]<<24) | (aInst[2]<<16) | (aInst[1]<<8) | aInst[0];
        if ((Encoding & 0x707F) == 0x100B) { // opcode == “P.BEQIMM” ?
            var InstInfo;
            InstInfo = new Object();
            InstInfo.Size = 4;
            InstInfo.Mode = 0;
            InstInfo.StackAdjust = 0;
            InstInfo.AccessAddr = Addr + 100;
            InstInfo.Flags = 0x0888; // IsBranch | IsConditional | IsFixedAddress
            return InstInfo;
        } // if opcode == “P.BEQIMM”

    } // if aInst.length == 4
    return undefined;

}

/*********************************************************************
*
*       regName
*
*  Function Description
*    Helper function. Returns the ABI name of a RISCV-register.
*/
function regName(r)
{

    if (r > 31) {
        return "?";
    }
    var sReg = new Array("zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "fp", "s1", "a0", "a1", "a2", "a3", "a4",
        "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6");
    return sReg[r];
}

/*********************************************************************
*
*       immName
*
*  Function Description
*    Helper function. Returns an unsigned immediate value as string
*/

function immName(Imm) {

  var sImm;

  if (Imm < 0x20) { // print small immediates in decimal
    sImm = Imm.toString(10);
  } else { // print large immediates in hexadecimal
    sImm = Imm.toString(16);
  }
  return sImm;
}

/*************************** End of file ****************************/