/*
 * Copyright (c) 2005-2020 Imperas Software Ltd., www.imperas.com
 *
 * 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.
 *
 */

// VMI header files
#include "vmi/vmiCxt.h"
#include "vmi/vmiMessage.h"
#include "vmi/vmiMt.h"
#include "vmi/vmiRt.h"

// model header files
#include "riscvBlockState.h"
#include "riscvDecode.h"
#include "riscvExceptions.h"
#include "riscvFunctions.h"
#include "riscvMessage.h"
#include "riscvMode.h"
#include "riscvStructure.h"
#include "riscvUtils.h"
#include "riscvVariant.h"
#include "riscvVM.h"


//
// Write a net port
//
inline static void writeNet(riscvP riscv, Uns32 handle, Uns32 value) {
    if(handle) {
        vmirtWriteNetPort((vmiProcessorP)riscv, handle, value);
    }
}

//
// Update the currently-enabled architecture settings
//
void riscvSetCurrentArch(riscvP riscv) {

    Uns32 MXL   = RD_CSR_FIELD(riscv, misa, MXL);
    Bool  V     = inVMode(riscv);
    Bool  FS_HS = RD_CSR_FIELD(riscv, mstatus,  FS);
    Bool  FS_VS = RD_CSR_FIELD(riscv, vsstatus, FS) || !V;
    Bool  FS    = FS_HS && FS_VS;

    // derive new architecture value based on misa value, preserving rounding
    // mode invalid setting
    riscvArchitecture arch = (
        (riscv->currentArch & ISA_RM_INVALID) |
        RD_CSR_FIELD(riscv, misa, Extensions) |
        (MXL<< XLEN_SHIFT)                    |
        (FS << MSTATUS_FS_SHIFT)
    );

    // mstatus.FS=0 disables floating point extensions
    if(!FS) {
        arch &= ~ISA_DF;
    }

    // mstatus.VS=0 disables vector extensions (if implemented)
    if(arch & ISA_V) {

        Uns32 WM_mstatus_VS = 0;

        // get mask of dirty bits for mstatus.VS in either 0.8 or 0.9 location
        if(riscvVFSupport(riscv, RVVF_VS_STATUS_8)) {
            WM_mstatus_VS = WM_mstatus_VS_8;
        } else if(riscvVFSupport(riscv, RVVF_VS_STATUS_9)) {
            WM_mstatus_VS = WM_mstatus_VS_9;
        }

        if(!WM_mstatus_VS) {
            // no action
        } else if(!(RD_CSR(riscv, mstatus) & WM_mstatus_VS)) {
            arch &= ~ISA_V;
        } else if(!V) {
            // no action
        } else if(!(RD_CSR(riscv, vsstatus) & WM_mstatus_VS)) {
            arch &= ~ISA_V;
        }
    }

    // handle big endian access if required
    if(riscv->checkEndian && riscvGetCurrentDataEndian(riscv)) {
        arch |= ISA_BE;
    }

    if(riscv->currentArch != arch) {

        vmiProcessorP processor = (vmiProcessorP)riscv;

        // update current block mask to match architecture
        vmirtSetBlockMask(processor, arch);

        // update current architecture on processor
        riscv->currentArch = arch;
    }
}

//
// Return the configured XLEN (may not be the current XLEN if dynamic update
// of XLEN is allowed)
//
Uns32 riscvGetXlenArch(riscvP riscv) {

    riscvArchitecture arch   = riscv->configInfo.arch;
    Uns32             result = 0;

    if(arch & ISA_XLEN_64) {
        result = 64;
    } else if(arch & ISA_XLEN_32) {
        result = 32;
    } else {
        VMI_ABORT("invalid XLEN"); // LCOV_EXCL_LINE
    }

    return result;
}

//
// Return the configured FLEN (may not be the current FLEN if dynamic update
// of FLEN is allowed)
//
Uns32 riscvGetFlenArch(riscvP riscv) {

    riscvArchitecture arch   = riscv->configInfo.arch;
    Uns32             result = 0;

    if(arch & ISA_D) {
        result = 64;
    } else if(arch & ISA_F) {
        result = 32;
    }

    return result;
}

//
// Register extension callback block for the id with the base model
//
void riscvRegisterExtCB(riscvP riscv, riscvExtCBP extCB, Uns32 id) {

    riscvExtCBPP tail = &riscv->extCBs;
    riscvExtCBP  this;

    while((this=*tail)) {
        tail = &this->next;
    }

    *tail = extCB;
    extCB->next = 0;
    extCB->id   = id;
}

//
// Return the indexed extension's extCB clientData
//
void *riscvGetExtClientData(riscvP riscv, Uns32 id) {

    riscvExtCBP this = riscv->extCBs;

    while(this && (this->id!=id)) {
        this = this->next;
    }

    return this ? this->clientData : 0;
}

//
// Return extension configuration with the given id
//
riscvExtConfigCP riscvGetExtConfig(riscvP riscv, Uns32 id) {

    riscvExtConfigCPP extCfgs = riscv->configInfo.extensionConfigs;
    riscvExtConfigCP  extCfg  = 0;

    if(extCfgs) {
        while((extCfg=*extCfgs) && (extCfg->id!=id)) {
            extCfgs++;
        }
    }

    return extCfg;
}

//
// Return the current XLEN
//
Uns32 riscvGetXlenMode(riscvP riscv) {

    riscvArchitecture arch   = riscv->currentArch;
    Uns32             result = 0;

    if(arch & ISA_XLEN_64) {
        result = 64;
    } else if(arch & ISA_XLEN_32) {
        result = 32;
    } else {
        VMI_ABORT("invalid XLEN"); // LCOV_EXCL_LINE
    }

    return result;
}

//
// Does the processor support configurable endianness?
//
Bool riscvSupportEndian(riscvP riscv) {
    return (RISCV_PRIV_VERSION(riscv)>RVPV_20190405);
}

//
// Return endianness for data access in the given mode
//
memEndian riscvGetDataEndian(riscvP riscv, riscvMode mode) {

    memEndian result = riscv->dendian;

    if(!riscvSupportEndian(riscv)) {
        // no action
    } else if(mode==RISCV_MODE_U) {
        result = RD_CSR_FIELD(riscv, mstatus, UBE);
    } else if(mode==RISCV_MODE_S) {
        result = RD_CSR_FIELD_ALT(riscv, mstatush, mstatus, SBE);
    } else if(mode==RISCV_MODE_M) {
        result = RD_CSR_FIELD_ALT(riscv, mstatush, mstatus, MBE);
    } else if(mode==RISCV_MODE_VU) {
        result = RD_CSR_FIELD(riscv, vsstatus, UBE);
    } else if(mode==RISCV_MODE_VS) {
        result = RD_CSR_FIELD(riscv, hstatus, VSBE);
    } else {
        VMI_ABORT("invalid mode"); // LCOV_EXCL_LINE
    }

    return result;
}

//
// Return endianness for data access in the current mode
//
memEndian riscvGetCurrentDataEndian(riscvP riscv) {
    return riscvGetDataEndian(riscv, riscv->dataMode);
}

//
// Return endianness of access
//
VMI_ENDIAN_FN(riscvGetEndian) {

    riscvP riscv = (riscvP)processor;

    if(isFetch) {
        return riscv->iendian;
    } else {
        return riscvGetCurrentDataEndian(riscv);
    }
}

//
// Return next PC after the given PC
//
VMI_NEXT_PC_FN(riscvNextPC) {

    // calculate nextPC ignoring wrapping
    riscvP riscv  = (riscvP)processor;
    Uns64  nextPC = thisPC + riscvGetInstructionSize(riscv, thisPC);

    // allow for wrapping if XLEN is 32
    if(riscvGetXlenMode(riscv)==32) {
        nextPC &= 0xffffffff;
    }

    return nextPC;
}

//
// Table of processor mode descriptions
//
// NOTE: The CPU Helper depends on the low order 2 bits of the code
//       matching the privilege level encoding for the mstatus.MPP field
//       defined in table 1.1 of RISC-V Privileged Architectures V1.10
//
static const vmiModeInfo modes[] = {

    [RISCV_MODE_U] = {
        .name        = "User",
        .code        = RISCV_MODE_U,
        .description = "User mode"
    },

    [RISCV_MODE_S] = {
        .name        = "Supervisor",
        .code        = RISCV_MODE_S,
        .description = "Supervisor mode"
    },

    [RISCV_MODE_H] = {
        .name        = "Hypervisor",
        .code        = RISCV_MODE_H,
        .description = "Hypervisor mode"
    },

    [RISCV_MODE_M] = {
        .name        = "Machine",
        .code        = RISCV_MODE_M,
        .description = "Machine mode"
    },

    [RISCV_MODE_VU] = {
        .name        = "Virtual User",
        .code        = RISCV_MODE_VU,
        .description = "User mode"
    },

    [RISCV_MODE_VS] = {
        .name        = "Virtual Supervisor",
        .code        = RISCV_MODE_VS,
        .description = "Supervisor mode"
    },

    [RISCV_MODE_D] = {
        .name        = "Debug",
        .code        = RISCV_MODE_D,
        .description = "Debug mode"
    },

    // terminator
    {0}
};

//
// Get mode name for the indexed mode
//
const char *riscvGetModeName(riscvMode mode) {
    return modes[mode].name;
}

//
// Get current mode
//
VMI_GET_MODE_FN(riscvGetMode) {

    riscvP riscv = (riscvP)processor;

    return &modes[inDebugMode(riscv) ? RISCV_MODE_D : getCurrentMode5(riscv)];
}

//
// Change processor mode
//
void riscvSetMode(riscvP riscv, riscvMode mode) {

    riscvDMode dMode = modeToDMode(mode);
    Bool       V     = modeIsVirtual(mode);

    // consolidate floating point flags on CSR view (in case of switch between
    // normal and virtual modes)
    riscvConsolidateFPFlags(riscv);

    // if executing in supervisor or user mode, include VM-enabled indication
    if((mode!=RISCV_MODE_M) && (RD_CSR_FIELD_V(riscv, satp, V, MODE))) {
        dMode |= RISCV_DMODE_VM;
    }

    // update mode if it has changed
    if(riscv->mode != dMode) {
        riscv->mode = dMode;
        vmirtSetMode((vmiProcessorP)riscv, dMode);
    }

    // refresh current data domain (may be modified by mstatus.MPRV, and may
    // have changed while taking an exception even if mode has not changed)
    riscvVMRefreshMPRVDomain(riscv);

    // set step breakpoint if required
    riscvSetStepBreakpoint(riscv);

    // update active mode output signal (external CLIC)
    writeNet(riscv, riscv->sec_lvl_Handle, mode);
}

//
// Return the minimum supported processor mode
//
riscvMode riscvGetMinMode(riscvP riscv) {

    if(riscv->configInfo.arch & ISA_U) {
        return RISCV_MODE_U;
    } else if(riscv->configInfo.arch & ISA_S) {
        return RISCV_MODE_S;
    } else {
        return RISCV_MODE_M;
    }
}

//
// Does the processor implement the given mode?
//
Bool riscvHasMode(riscvP riscv, riscvMode mode) {

    switch(mode) {
        case RISCV_MODE_U:
            return riscv->configInfo.arch & ISA_U;
        case RISCV_MODE_S:
            return riscv->configInfo.arch & ISA_S;
        case RISCV_MODE_VU:
            return (riscv->configInfo.arch & ISA_VU) == ISA_VU;
        case RISCV_MODE_VS:
            return (riscv->configInfo.arch & ISA_VS) == ISA_VS;
        case RISCV_MODE_M:
            return True;
        case RISCV_MODE_D:
            return riscv->configInfo.debug_mode;
        default:
            return False;
    }
}

//
// Iterate processor modes
//
VMI_MODE_INFO_FN(riscvModeInfo) {

    riscvP riscv = (riscvP)processor;

    // on the first call, start with the first member of the table
    if(!prev) {
        prev = modes-1;
    }

    // get the next member
    vmiModeInfoCP this = prev+1;

    // skip to the next implemented mode
    while((this->name) && !riscvHasMode(riscv, this->code)) {
        this++;
    }

    // return the next member, or NULL if at the end of the list
    return (this->name) ? this : 0;
}

//
// Return the indexed X register name
//
const char *riscvGetXRegName(Uns32 index) {

    static const char *map[32] = {
        [0] = "zero",
        [1] = "ra",
        [2] = "sp",
        [3] = "gp",
        [4] = "tp",
        [5] = "t0",
        [6] = "t1",
        [7] = "t2",
        [8] = "s0",
        [9] = "s1",
        [10] = "a0",
        [11] = "a1",
        [12] = "a2",
        [13] = "a3",
        [14] = "a4",
        [15] = "a5",
        [16] = "a6",
        [17] = "a7",
        [18] = "s2",
        [19] = "s3",
        [20] = "s4",
        [21] = "s5",
        [22] = "s6",
        [23] = "s7",
        [24] = "s8",
        [25] = "s9",
        [26] = "s10",
        [27] = "s11",
        [28] = "t3",
        [29] = "t4",
        [30] = "t5",
        [31] = "t6",
    };

    // sanity check index is in range
    VMI_ASSERT(index<32, "Illegal index %u", index);

    return map[index];
}

//
// Return the indexed F register name
//
const char *riscvGetFRegName(Uns32 index) {

    static const char *map[32] = {
        [0] = "ft0",
        [1] = "ft1",
        [2] = "ft2",
        [3] = "ft3",
        [4] = "ft4",
        [5] = "ft5",
        [6] = "ft6",
        [7] = "ft7",
        [8] = "fs0",
        [9] = "fs1",
        [10] = "fa0",
        [11] = "fa1",
        [12] = "fa2",
        [13] = "fa3",
        [14] = "fa4",
        [15] = "fa5",
        [16] = "fa6",
        [17] = "fa7",
        [18] = "fs2",
        [19] = "fs3",
        [20] = "fs4",
        [21] = "fs5",
        [22] = "fs6",
        [23] = "fs7",
        [24] = "fs8",
        [25] = "fs9",
        [26] = "fs10",
        [27] = "fs11",
        [28] = "ft8",
        [29] = "ft9",
        [30] = "ft10",
        [31] = "ft11",
    };

    // sanity check index is in range
    VMI_ASSERT(index<32, "Illegal index %u", index);

    return map[index];
}

//
// Return the indexed V register name
//
const char *riscvGetVRegName(Uns32 index) {

    static const char *map[32] = {
        [0] = "v0",
        [1] = "v1",
        [2] = "v2",
        [3] = "v3",
        [4] = "v4",
        [5] = "v5",
        [6] = "v6",
        [7] = "v7",
        [8] = "v8",
        [9] = "v9",
        [10] = "v10",
        [11] = "v11",
        [12] = "v12",
        [13] = "v13",
        [14] = "v14",
        [15] = "v15",
        [16] = "v16",
        [17] = "v17",
        [18] = "v18",
        [19] = "v19",
        [20] = "v20",
        [21] = "v21",
        [22] = "v22",
        [23] = "v23",
        [24] = "v24",
        [25] = "v25",
        [26] = "v26",
        [27] = "v27",
        [28] = "v28",
        [29] = "v29",
        [30] = "v30",
        [31] = "v31",
    };

    // sanity check index is in range
    VMI_ASSERT(index<32, "Illegal index %u", index);

    return map[index];
}

//
// Utility function returning a vmiReg object to access the indexed vector
// register
//
vmiReg riscvGetVReg(riscvP riscv, Uns32 index) {

    void *value = &riscv->v[index*riscv->configInfo.VLEN/32];

    return vmimtGetExtReg((vmiProcessorP)riscv, value);
}

//
// Return index for the first feature identified by the given feature id
//
static Uns32 getFeatureIndex(riscvArchitecture feature) {

    Uns32 index = -1;

    // select first indicated feature
    feature = feature & -feature;

    // convert to index
    while(feature) {
        feature >>= 1;
        index++;
    }

    return index;
}

//
// Get character identifier for the first feature identified by the given
// feature id
//
char riscvGetFeatureChar(riscvArchitecture feature) {

    return getFeatureIndex(feature)+'A';
}

//
// Get description for the first feature identified by the given feature id
//
const char *riscvGetFeatureName(riscvArchitecture feature) {

    // table mapping to feature descriptions
    static const char *featureDescs[32] = {
        [RISCV_FEATURE_INDEX(XLEN32_CHAR)] = "32-bit XLEN",
        [RISCV_FEATURE_INDEX(XLEN64_CHAR)] = "64-bit XLEN",
        [RISCV_FEATURE_INDEX('A')]         = "extension A (atomic instructions)",
        [RISCV_FEATURE_INDEX('B')]         = "extension B (bit manipulation extension)",
        [RISCV_FEATURE_INDEX('C')]         = "extension C (compressed instructions)",
        [RISCV_FEATURE_INDEX('E')]         = "RV32E base ISA",
        [RISCV_FEATURE_INDEX('D')]         = "extension D (double-precision floating point)",
        [RISCV_FEATURE_INDEX('F')]         = "extension F (single-precision floating point)",
        [RISCV_FEATURE_INDEX('H')]         = "extension H (hypervisor)",
        [RISCV_FEATURE_INDEX('I')]         = "RV32I/64I/128I base ISA",
        [RISCV_FEATURE_INDEX('M')]         = "extension M (integer multiply/divide instructions)",
        [RISCV_FEATURE_INDEX('N')]         = "extension N (user-level interrupts)",
        [RISCV_FEATURE_INDEX('S')]         = "extension S (Supervisor mode)",
        [RISCV_FEATURE_INDEX('U')]         = "extension U (User mode)",
        [RISCV_FEATURE_INDEX('V')]         = "extension V (vector extension)",
        [RISCV_FEATURE_INDEX('X')]         = "extension X (non-standard extensions present)"
    };

    // get feature description
    return featureDescs[getFeatureIndex(feature)];
}

//
// Parse the extensions string
//
riscvArchitecture riscvParseExtensions(const char *extensions) {

    riscvArchitecture result = 0;

    if(extensions) {

        const char *tail = extensions;
        Bool        ok   = True;
        char        extension;

        while(ok && (extension=*tail++)) {

            ok = (extension>='A') && (extension<='Z');

            if(!ok) {
                vmiMessage("E", CPU_PREFIX"_ILLEXT",
                    "Illegal extension string \"%s\" - letters A-Z required",
                    extensions
                );
            } else {
                result |= (1<<(extension-'A'));
            }
        }
    }

    return result;
}


////////////////////////////////////////////////////////////////////////////////
// PROCESSOR RUN STATE TRANSITION HANDLING
////////////////////////////////////////////////////////////////////////////////

//
// If this memory access callback is triggered, abort any active load linked
//
static VMI_MEM_WATCH_FN(abortEA) {
    if(processor) {
        riscvAbortExclusiveAccess((riscvP)userData);
    }
}

//
// Install or remove the exclusive access monitor callback
//
static void updateExclusiveAccessCallback(riscvP riscv, Bool install) {

    memDomainP domain  = vmirtGetProcessorDataDomain((vmiProcessorP)riscv);
    Uns32      bits    = vmirtGetDomainAddressBits(domain);
    Uns64      mask    = (bits==64) ? -1 : ((1ULL<<bits)-1);
    Uns64      simLow  = mask & riscv->exclusiveTag;
    Uns64      simHigh = mask & (simLow + ~riscv->exclusiveTagMask);

    // install or remove a watchpoint on the current exclusive access address
    if(install) {
        vmirtAddWriteCallback(domain, 0, simLow, simHigh, abortEA, riscv);
    } else {
        vmirtRemoveWriteCallback(domain, 0, simLow, simHigh, abortEA, riscv);
    }
}

//
// Abort any active exclusive access
//
void riscvAbortExclusiveAccess(riscvP riscv) {

    if(riscv->exclusiveTag != RISCV_NO_TAG) {

        // remove callback on exclusive access monitor region
        updateExclusiveAccessCallback(riscv, False);

        // clear exclusive tag (AFTER updateExclusiveAccessCallback)
        riscv->exclusiveTag = RISCV_NO_TAG;
    }
}

//
// Install or remove the exclusive access monitor callback if required
//
void riscvUpdateExclusiveAccessCallback(riscvP riscv, Bool install) {
    if(riscv->exclusiveTag != RISCV_NO_TAG) {
        updateExclusiveAccessCallback(riscv, install);
    }
}

//
// This is called on simulator context switch (when this processor is either
// about to start or about to stop simulation)
//
VMI_IASSWITCH_FN(riscvContextSwitchCB) {

    riscvP      riscv = (riscvP)processor;
    riscvExtCBP extCB;

    riscvUpdateExclusiveAccessCallback(riscv, state==RS_SUSPEND);

    // call derived model context switch function if required
    for(extCB=riscv->extCBs; extCB; extCB=extCB->next) {
        if(extCB->switchCB) {
            extCB->switchCB(riscv, state, extCB->clientData);
        }
    }
}


////////////////////////////////////////////////////////////////////////////////
// TRANSACTION MODE
////////////////////////////////////////////////////////////////////////////////

//
// Enable or disable transaction mode
//
RISCV_SET_TMODE_FN(riscvSetTMode) {

    // flush dictionaries the first time transaction mode is enabled
    if(enable && !riscv->useTMode) {

        riscv->useTMode = True;

        vmirtFlushAllDicts((vmiProcessorP)riscv);
    }

    // enable mode using polymorphic key
    if(enable) {
        riscv->pmKey |= PMK_TRANSACTION;
    } else {
        riscv->pmKey &= ~PMK_TRANSACTION;
    }
}

//
// Return true if in transaction mode
//
RISCV_GET_TMODE_FN(riscvGetTMode) {
    return (riscv->pmKey & PMK_TRANSACTION) != 0;
}

