/**
 * University of Illinois/NCSA
 * Open Source License
 *
 *  Copyright (c) 2007-2008,The Board of Trustees of the University of
 *  Illinois.  All rights reserved.
 *
 *  Copyright (c) 2009 Sam King
 *
 *  Developed by:
 *
 *  Professor Sam King in the Department of Computer Science
 *  The University of Illinois at Urbana-Champaign
 *      http://www.cs.uiuc.edu/homes/kingst/Research.html
 *
 *       Permission is hereby granted, free of charge, to any person
 *       obtaining a copy of this software and associated
 *       documentation files (the "Software"), to deal with the
 *       Software without restriction, including without limitation
 *       the rights to use, copy, modify, merge, publish, distribute,
 *       sublicense, and/or sell copies of the Software, and to permit
 *       persons to whom the Software is furnished to do so, subject
 *       to the following conditions:
 *
 *          Redistributions of source code must retain the above
 *          copyright notice, this list of conditions and the
 *          following disclaimers.
 *
 *          Redistributions in binary form must reproduce the above
 *          copyright notice, this list of conditions and the
 *          following disclaimers in the documentation and/or other
 *          materials provided with the distribution.
 *
 *          Neither the names of Sam King, the University of Illinois,
 *          nor the names of its contributors may be used to endorse
 *          or promote products derived from this Software without
 *          specific prior written permission.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT.  IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
 *  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 *  DEALINGS WITH THE SOFTWARE.
*/
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <string.h>

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

using namespace std;

#define RAM_SIZE 0x20000
#define NUM_REGS 32

#define OP_FORMAT_1     0x1
#define OP_FORMAT_2     0x0
#define OP_FORMAT_3_ALU 0x2
#define OP_FORMAT_3_MEM 0x3

#define OP2_BRANCH      0x2
#define OP2_SETHI       0x4

#define OP3_ADD         0x0
#define OP3_SUB         0x4
#define OP3_SUBCC       0x14
#define OP3_JMPL        0x38

#define OP3_LD          0x0
#define OP3_ST          0x4

#define COND_BNE        0x9
#define COND_B          0x8
#define COND_BE         0x1

int PRINT_INST = 1;

#define COUT(x) do { if(PRINT_INST) { x; } } while(0)

typedef uint32_t u32;
typedef int32_t i32;

struct CPU {
    u32 regs[NUM_REGS];
    u32 pc;
    bool icc_z;    
    bool icc_n;
};

struct control_signals {
    u32 op;
    u32 rd;
    u32 cond;
    u32 op2;
    u32 op3;
    u32 rs1;
    u32 i;
    u32 asi;
    i32 simm;
    u32 imm;
    u32 disp22;
    u32 disp30;
    u32 rs2;
    u32 raw;
};

static uint8_t *ram = NULL;
static bool userQuit = false;
static time_t startTime;

void ctrlC(int /*signo*/) {
    userQuit = true;
}

u32 fetch(struct CPU *cpu) {
    u32 opcode;
    assert((cpu->pc & 0x3) == 0);
    assert(cpu->pc < RAM_SIZE);
    memcpy(&opcode, ram+cpu->pc, sizeof(opcode));
    return opcode;
}

i32 sign_extend_22(u32 disp22) {
    i32 ret;

    ret = disp22;    
    assert((disp22 & 0xffc00000) == 0);
    if((disp22 & 0x200000) != 0) {
        ret |= 0xffc00000;
    }

    return ret;
}

i32 sign_extend_13(u32 imm) {
    i32 ret;

    ret = imm;    
    assert((imm & 0xffffe000) == 0);
    if((imm & 0x01000) != 0) {
        ret |= 0xffffe000;
    }

    return ret;
}

void decode_control_signals(u32 opcode, struct control_signals *control) {
    control->raw = opcode;
    control->op = (opcode >> 30) & 0x3;
    control->rd = (opcode >> 25) & 0x1f;
    control->op2 = (opcode >> 22) & 0x7;
    control->op3 = (opcode >> 19) & 0x3f;
    control->rs1 = (opcode >> 14) & 0x1f;
    control->i = (opcode >> 13) & 0x1;
    control->asi = (opcode >> 5) & 0xff;
    control->simm = sign_extend_13(opcode & 0x1fff);
    control->imm = opcode & 0x3fffff;
    control->rs2 = opcode & 0x1f;
    control->disp22 = opcode & 0x3fffff;
    control->disp30 = opcode & 0x3fffffff;
    control->cond = (opcode >> 25) & 0xf;
}

void set_icc(struct CPU *cpu, u32 value) {
    cpu->icc_n = (value & 0x80000000) == 0x80000000;
    cpu->icc_z = value == 0;
}

void add(struct CPU *cpu, u32 rd, u32 rs1, u32 rs2) {
    COUT(cout << "add r" << rs1 << ", r" << rs2 << ", r" << rd << endl);
    cpu->regs[rd] = cpu->regs[rs1] + cpu->regs[rs2];
}

void addi(struct CPU *cpu, u32 rd, u32 rs1, i32 signedImm) {
    COUT(cout << "addi " << "r" << rs1 << ", " << signedImm << ", r" << rd << endl);
    cpu->regs[rd] = cpu->regs[rs1] + signedImm;
}

void subi(struct CPU *cpu, u32 rd, u32 rs1, i32 signedImm) {
    COUT(cout << "subi " << "r" << rs1 << ", " << signedImm << ", r" << rd << endl);
    cpu->regs[rd] = cpu->regs[rs1] - signedImm;
}
void subicc(struct CPU *cpu, u32 rd, u32 rs1, i32 signedImm) {
    COUT(cout << "subicc " << "r" << rs1 << ", " << signedImm << ", r" << rd << endl);
    cpu->regs[rd] = cpu->regs[rs1] - signedImm;
    set_icc(cpu, cpu->regs[rd]);
}

void sethi(struct CPU *cpu, u32 rd, u32 imm) {
    COUT(cout << "sethi " << imm << ", r" << rd << endl);
    cpu->regs[rd] = imm << 10;
}

void ld(struct CPU *cpu, u32 rd, u32 rs1, i32 simm) {
    COUT(cout << "ld [r" << rs1 << "+" << simm << "], r" << rd << endl);
    u32 guestAddr = cpu->regs[rs1] + simm;
    assert(guestAddr < RAM_SIZE);
    u32 *addr = (u32 *) (ram + guestAddr);
    cpu->regs[rd] = *addr;
}

void st(struct CPU *cpu, u32 rd, u32 rs1, i32 simm) {
    COUT(cout << "st r" << rd << ", [r" << rs1 << "+" << simm << "]" << endl);
    u32 guestAddr = cpu->regs[rs1] + simm;
    assert(guestAddr < RAM_SIZE);
    u32 *addr = (u32 *) (ram + guestAddr);
    *addr = cpu->regs[rd];

    if(guestAddr == (RAM_SIZE - 4)) {
        cout << cpu->regs[rd] << endl;
        if(cpu->regs[rd] == 3524578) {
            cout << "totalTime = " << time(NULL) - startTime << endl;
            userQuit = true;
        }
    }
}

void bne(struct CPU *cpu, u32 disp22) {
    COUT(cout << "bne " << disp22 << " " << sign_extend_22(disp22) << endl);
    if(!cpu->icc_z) {
        cpu->pc = cpu->pc + (4 * sign_extend_22(disp22));
    }
}

void be(struct CPU *cpu, u32 disp22) {
    COUT(cout << "be " << disp22 << " " << sign_extend_22(disp22) << endl);
    if(cpu->icc_z) {
        cpu->pc = cpu->pc + (4 * sign_extend_22(disp22));
    }
}

void b(struct CPU *cpu, u32 disp22) {
    COUT(cout << "b " << disp22 << " " << sign_extend_22(disp22) << endl);
    cpu->pc = cpu->pc + (4 * sign_extend_22(disp22));
}

void jmpli(struct CPU *cpu, u32 rd, u32 rs1, i32 simm) {
    COUT(cout << "jmpl r" << rs1 << ", " << simm << ", r" << rd << endl);
    cpu->regs[rd] = cpu->pc;
    cpu->pc = cpu->regs[rs1] + simm;
}

void call(struct CPU *cpu, u32 disp30) {
    COUT(cout << "call " << disp30 << endl);
    cpu->regs[15] = cpu->pc;
    cpu->pc = cpu->pc + (4 * disp30);    
}

void unknown_inst(struct CPU *cpu, struct control_signals *control) {
    cout << "op = 0x" << hex << control->op << endl;
    cout << "op2 = 0x" << hex << control->op2 << endl;
    cout << "op3 = 0x" << hex << control->op3 << endl;
    cout << "pc = 0x" << hex << cpu->pc << endl;
    assert(false);
}

void process_format_1(struct CPU *cpu, struct control_signals *control) {
    assert(control->op == OP_FORMAT_1);
    call(cpu, control->disp30);
}

void process_format_2(struct CPU *cpu, struct control_signals *control) {
    assert(control->op == OP_FORMAT_2);
    switch(control->op2)
    {
        case OP2_BRANCH:
            switch(control->cond)
            {
                case COND_B:
                    b(cpu, control->disp22);
                    break;
                case COND_BE:
                    be(cpu, control->disp22);
                    break;
                case COND_BNE:
                    bne(cpu, control->disp22);
                    break;
                default:
                    unknown_inst(cpu, control);
            }
            break;
        case OP2_SETHI:
            sethi(cpu, control->rd, control->imm);
            break;
        default:
            unknown_inst(cpu, control);
    }
}

void process_format_3_alu(struct CPU *cpu, struct control_signals *control) {
    assert(control->op == OP_FORMAT_3_ALU);
    switch(control->op3)
    {
        case OP3_ADD:
            if(control->i == 0) {
                add(cpu, control->rd, control->rs1, control->rs2);
            }
            else {
                addi(cpu, control->rd, control->rs1, control->simm);
            }
            break;
        case OP3_SUB:
            assert(control->i == 1);
            subi(cpu, control->rd, control->rs1, control->simm);
            break;
        case OP3_SUBCC:
            assert(control->i == 1);
            subicc(cpu, control->rd, control->rs1, control->simm);
            break;
        case OP3_JMPL:
            assert(control->i == 1);
            jmpli(cpu, control->rd, control->rs1, control->simm);
            break;
        default:
            unknown_inst(cpu, control);
    }
}

void process_format_3_mem(struct CPU *cpu, struct control_signals *control) {
    assert(control->op == OP_FORMAT_3_MEM);
    switch(control->op3)
    {
        case OP3_LD:
            assert(control->i == 1);
            ld(cpu, control->rd, control->rs1, control->simm);
            break;
        case OP3_ST:
            assert(control->i == 1);
            st(cpu, control->rd, control->rs1, control->simm);
            break;
        default:
            unknown_inst(cpu, control);
    }
}

bool execute(struct CPU *cpu, struct control_signals *control) {
    u32 savedPc = cpu->pc;

    COUT(cout << "0x" << hex << cpu->pc << dec << " ");

    // writes to reg 0 are ignored and reads should always be 0
    cpu->regs[0] = 0;
    switch (control->op) {
        case OP_FORMAT_1:
            process_format_1(cpu, control);
            break;
        case OP_FORMAT_2:
            process_format_2(cpu, control);
            break;
        case OP_FORMAT_3_ALU:
            process_format_3_alu(cpu, control);
            break;
        case OP_FORMAT_3_MEM:
            process_format_3_mem(cpu, control);
            break;
        default:
            unknown_inst(cpu, control);
    }

    // the pc is modified in the loop after the branch instruction
    if(cpu->pc == savedPc) {
        cpu->pc += sizeof(cpu->pc);
        if(cpu->pc < savedPc) {
            return false;
        }
    }

    return true;
}

string prompt(const char *str) {
    string ret;
    cout << str;
    cout.flush();
    cin >> ret;
    return ret;
}

void fillState(char *fileName, void *buf, int size) {
    int ret, fd;

    assert(size > 0);

    fd = open(fileName, O_RDONLY);
    assert(fd >= 0);

    ret = read(fd, buf, size);
    assert((ret == size) && (ret>0));

    close(fd);
}

void saveState(const char *fileName, void *buf, int size) {
    int ret, fd;

    assert(size > 0);

    fd = open(fileName, O_WRONLY | O_TRUNC | O_CREAT, 0644);
    assert(fd >= 0);

    ret = write(fd, buf, size);
    assert(ret == size);

    close(fd);
}

void cpu_exec(struct CPU *cpu) {
    struct control_signals *control = new struct control_signals;
    u32 opcode;
    bool runSimulation = true;

    while(runSimulation && !userQuit) {
        opcode = fetch(cpu);
        decode_control_signals(opcode, control);

        // second part of decode and write back also
        runSimulation = execute(cpu, control);
    }

    if(userQuit) {
        if(prompt("would you like to save your system state (y/n)?: ") == "y") {
            string memFileName = prompt("mem file name: ");
            string cpuFileName = prompt("cpu file name: ");
            saveState(memFileName.c_str(), ram, RAM_SIZE);
            saveState(cpuFileName.c_str(), cpu, sizeof(struct CPU));
        }
    }
}

int main(int argc, char *argv[]) {
    ifstream infile;
    struct CPU *cpu;

    if(argc < 2) {
        cerr << "Usage: " << argv[0] << " memory_file [cpu_file]" << endl;
        return -1;
    }

    signal(SIGINT, ctrlC);

    // initialize our state
    ram = new uint8_t[RAM_SIZE];
    cpu = new struct CPU;
    for(int idx = 0; idx < NUM_REGS; idx++) {
        cpu->regs[idx] = 0;
    }
    cpu->pc = 0;
    // setup the stack pointer
    cpu->regs[14] = RAM_SIZE-4-120;

    // setup the fib program
    cpu->regs[8] = RAM_SIZE-4;

    // fetch our memory image and cpu state (if set)
    fillState(argv[1], ram, RAM_SIZE);
    if(argc >= 3) {
        fillState(argv[2], cpu, sizeof(struct CPU));
    }

    startTime = time(NULL);
    cpu_exec(cpu);

    return 0;
}