/*
    Small instrcuton set computer emulator
*/

#include <stdio.h>
#include <stdlib.h>
#include <mem.h>
#include <conio.h>
#include "emu.h"

#define debug 0             // if true, display status, information
#define MAXINST 10000       // maximun number id insctrucions to run (for debugging)


/* load program into memory */
int load_program(WORD *memory, char *filename)
{
    FILE *f = fopen(filename, "rb");

    if (!f)
    {
        printf("Unable to open (%s)\n", filename);
        system("pause");
        return(1);
    }

    unsigned int i = 0;

    while (!feof(f))
    {
        unsigned char tmp = fgetc(f); // read MSB

        if (feof(f))
        {
            fclose(f);
            return(0);
        }

        memory[i] = (WORD)tmp << 8;

        tmp = fgetc(f);               // read LSB
        memory[i++] |= tmp;

        if (i >= MEMSIZE)
        {
            printf("program too big\n");
            fclose(f);
            return(1);
        }
    }

    fclose(f);
    return(0);
}
/* end of load program */



/* display a block of memory */
void memory_dump(WORD start, WORD end, WORD *memory)
{
    printf("Memoey Dump %04X-%04X\n", start, end);
    for (WORD base = start; base <= end; base += 8)
    {
        printf("%04x-%04x:  ", base, base+7);

        // display hex values
        for (int i = 0; i < 8; i++)
            printf("%04x ", memory[base + i]);

        printf("   ");

        // display as char data
        for (int i = 0; i < 8; i++)
        {
            char tmp = memory[base+i];
            if (tmp < 32) tmp = 32;

            printf("%c", tmp);

            if (base+i == 0xffff) return;
        }
        printf("\n");
    }
}
/* end if memory_dump */


/* display processor status */
void show_stat(WORD *r, WORD w, WORD *mem)
{
    printf("PC: %04X  SP: %04X  SR: %04X  Word: %04X  Inst: %02X  Src: R[%01X]  Dest: R[%01X]\n",
            r[PC], r[SP], r[SR], w, get_inst(w), get_src(w), get_dest(w));

    for (int i = 0; i < 13; i++)
    {
        printf("R[%01X]: %04x  ", i, r[i]);
        if ((i+1) % 6 == 0) printf("\n");
    }

//    memory_dump(0xFFE0, 0xFFFF, mem);

    printf("\n\n");
}
/* end of display status */


/* push the registers to the stack */
void inline push_regs(WORD *r, WORD *memory)
{
    // push the stack pointer
    memory[r[SP]--] = r[SP];

    // push the rest
    for(int i = 0; i < 15; i++)
        memory[r[SP]--] = r[i];
}
/* end of push_regs */


/* pull the registers from the stack */
void inline pull_regs(WORD *r, WORD *memory)
{
    // pull the registers
    for(int i = 14; i >= 0; i--)
        r[i] = memory[++r[SP]] ;

    // pull, the stack pointer
    r[SP] = memory[++r[SP]];
}
/* end of pull regs */


/* set the status register from a value */
void inline set_stat(WORD *reg, WORD V)
{
    if (V == 0)
        reg[SR] |= Z; // set zero flag
    else
        reg[SR] &= MAXWORD - 1- Z;

    if ((signed short)V < 0)
        reg[SR] |= N; // set zero flag
    else
        reg[SR] &= MAXWORD - 1- N;
}
/* end of set_stat */


// make the code cleaner
#define rdest      (reg[get_dest(W)])       // the destination register
#define rsrc       (reg[get_src(W)])        // the source register
#define next_word  (mem[reg[PC]++])         // the next word in memory


/* the core emulation loop */
void emulate_code(WORD *mem)
{
    WORD reg[16];

    // clear all of the registers
    memset(reg, 0, 16 * sizeof(WORD));

    reg[SP] = MEMSIZE - 1; // init the stack pointer

    unsigned int count = MAXINST; // set the maximun number of instructions

    while(1)//count--)
    {
        WORD W = mem[reg[PC]];

        if (debug) show_stat(reg, W, mem);

        reg[PC]++;

        switch(get_inst(W)) // pull out the instruction
        {
            case HALT: return; break;

            // load immediate into dest
            case LDI: rdest = next_word; break;
            case LDR: rdest = rsrc; break;
            case LDP: rdest = mem[rsrc]; break;
            case LDA: rdest = mem[next_word]; break;

            // store operatoions
            case STA: mem[next_word] = rsrc; break;
            case STP: mem[rdest] = rsrc; break;

            // basic math
            case ADD: rdest += rsrc; set_stat(reg, rdest); break;
            case SUB: rdest -= rsrc; set_stat(reg, rdest); break;
            case MUL: rdest *= rsrc; set_stat(reg, rdest); break;
            case DIV: if (rsrc == 0) { printf("Div by zero\n"); return;}
                      rdest /= rsrc; set_stat(reg, rdest); break;
            case MOD: if (rsrc == 0) { printf("Div by zero\n"); return;}
                      rdest %= rsrc; set_stat(reg, rdest); break;

            // more math
            case SHR: rdest = rdest >> rsrc; set_stat(reg, rdest); break;
            case SHL: rdest = rdest << rsrc; set_stat(reg, rdest); break;
            case AND: rdest = rdest &  rsrc; set_stat(reg, rdest); break;
            case OR:  rdest = rdest |  rsrc; set_stat(reg, rdest); break;
            case XOR: rdest = rdest ^  rsrc; set_stat(reg, rdest); break;

            // basic math
            case ADDI: rdest += next_word; set_stat(reg, rdest); break;
            case SUBI: rdest -= next_word; set_stat(reg, rdest); break;
            case MULI: rdest *= next_word; set_stat(reg, rdest); break;
            case DIVI: { WORD t = next_word; if (t == 0) { printf("Div by zero\n"); return;}
                      rdest /= t; set_stat(reg, rdest);} break;
            case MODI:{ WORD t = next_word; if (t == 0) { printf("Div by zero\n"); return;}
                      rdest %= t; set_stat(reg, rdest); }break;

            // more math
            case SHRI: rdest = rdest >> next_word; set_stat(reg, rdest); break;
            case SHLI: rdest = rdest << next_word; set_stat(reg, rdest); break;
            case ANDI: rdest = rdest &  next_word; set_stat(reg, rdest); break;
            case ORI:  rdest = rdest |  next_word; set_stat(reg, rdest); break;
            case XORI: rdest = rdest ^  next_word; set_stat(reg, rdest); break;

            // function calls
            case CALLA:  push_regs(reg, mem); reg[PC] = next_word ; break;

            case CALLP:  push_regs(reg, mem); reg[PC] = rsrc - 1; break;
            case RET:    pull_regs(reg, mem); reg[PC]+=1; break;

            // stack
            case PUSH: mem[reg[SP]--] = rsrc; break;
            case PULL: rdest = mem[++reg[SP]]; break;

            // branching
            case CMP: set_stat(reg, rdest - rsrc); break;
            case CMPI: set_stat(reg, rdest - next_word); break;
            case BE:  reg[PC] += (reg[SR] & Z) ? (SWORD)(next_word - 2) : 1; break;
            case BL:  reg[PC] += (reg[SR] & N) ? (SWORD)(next_word - 2) : 1; break;
            case BLE: reg[PC] += (reg[SR] & N || reg[SR] & Z) ? (SWORD)(next_word - 2) : 1; break;
            case BG:  reg[PC] += (!(reg[SR] & Z) && !(reg[SR] & N)) ? (SWORD)(next_word - 2) : 1; break;
            case BGE: reg[PC] += (!(reg[SR] & N)) ? (SWORD)(next_word - 2) : 1; break;

            case BAA: reg[PC] = (SWORD)(next_word) - 1; break;
            case BAP: reg[PC] = (SWORD)(rdest) - 1; break;

            // input / output
            case IN:  rdest = getch(); break;
            case OUT: putch(rsrc);  break;
            
      }

        if (debug) getch();

    }
}
/* end of core loop */


/* main function */
int main(int argc, char *argv[])
{
    printf("Small Instruction Set Computer Emulator (C) lifejunkie justinsanemsu@hotmail.com\n");

    if (argc != 2)
    {
        printf("Usage: emu file.o\n\n");
        system("pause");
        return(1);
    }

    WORD memory[MEMSIZE];

    if(load_program(memory, argv[1]))
        return(1);

    memory_dump(0, 100, memory);

    printf("\nProgram start.\n");

    // do the emulation
    emulate_code(memory);

    printf("\nProgram end.\n");

    system("PAUSE");
    return(0);
}
/* end of main */


