import java.io.*;

public class SimpleSimulator {
  /*************************************************
   * Simple N-105 simulator.
   * Daniel Hulme (dh286)
   * July 2004
   * TODO:
   * better way to handle halt detection
   *************************************************/
  /* opcodes ------------------------------------------ */
  public static final byte OR = 0x00;
  public static final byte AND = 0x01;
  public static final byte XOR = 0x02;
  public static final byte NOT = 0x03;

  public static final byte MOV = 0x04;
  public static final byte ADD = 0x05;
  public static final byte SUB = 0x06;
  public static final byte CMP = 0x07;


  public static final byte LSLI = 0x08;
  public static final byte LSRI = 0x09;
  public static final byte ASRI = 0x0a;
  public static final byte ROTI = 0x0b;

  public static final byte MOVI = 0x0c;
  public static final byte ADDI = 0x0d;
  public static final byte SUBI = 0x0e;
  public static final byte CMPI = 0x0f;

  public static final byte LD = 0x10;
  public static final byte ST = 0x14;
  
  public static final byte BR = 0x18;
  public static final byte BSR = 0x19;
  public static final byte RET = 0x1a;
  public static final byte IFS = 0x1f;
  
  /* condition codes -------------------------------- */
  public static final byte NC = 0x0;
  public static final byte C = 0x1;
  public static final byte NZ = 0x2;
  public static final byte Z = 0x3;
  public static final byte PL = 0x4;
  public static final byte MI = 0x5;
  public static final byte LT = 0x6;
  public static final byte GE = 0x7;
  public static final byte GT = 0x8;
  public static final byte LE = 0x9;
  public static final byte NV = 0xa;
  public static final byte V = 0xb;
  public static final byte HI = 0xc;
  public static final byte LA = 0xd;

  static BufferedReader read;
  static short regs[]; // 16-bit signed
  static short pc=0;
  static boolean Nflag = false;
  static boolean Vflag = false;
  static boolean Zflag = false;
  static boolean Cflag = false;
  static short dataMemory[];
  static char instrMemory[];
  static boolean skip=false;
  static boolean delaySlot = false;

  static {
    regs = new short[16];
    dataMemory = new short[2048];
    instrMemory = new char[4096];
    init();
  }
  
  public static void main(String[] argv) {
    System.out.println("N-105 simulator");
    String fileName = "";
    InputStream input = null;
    try {
      if (argv.length==1) { // read binary from file
	input = new FileInputStream(argv[0]);
      } else { // read from stdin
	input = System.in;
      }
      // XXX massage the whole thing into instruction memory
    } catch (FileNotFoundException e) {
      System.err.println("File not found.");
      System.exit(1);
    } catch (IOException e) {
      System.err.println("IOExn");
      System.exit(1);
    } finally {
      try {
	input.close();
      } catch (IOException e) {
	System.err.println("IOExn on close");
	System.exit(1);
      }
    }

    boolean loop = true;
    while (loop) {
      loop = mainLoop();
    }
    System.out.println("Register file contents");
    System.out.println("----------------------");
    for (int i=regs.length - 1; i>=0; i--)
      System.out.println(i + "\t" + regs[i] + "\t" + Integer.toHexString((int)regs[i]));
  }

  public static boolean mainLoop() {
    /** @return true iff we aren't halting */
    if (pc >= instrMemory.length) {
      System.out.println("instruction memory exhausted");
      return false;
    }
    char instr = instrMemory[pc];
    Bus instrBits = new Bus(16);
    instrBits.write(instr);
    byte opcode = instrBits.extract(0,5).getByte();
    byte rega = instrBits.subscript(15,12).getByte();
    byte regb = instrBits.subscript(11,8).getByte();
    char imm11 = instrBits.extract(5,11).getChar();
    short imm11s = instrBits.extract(5,11).getShort(); // signed, needed for branches
    byte imm7 = instrBits.extract(5,7).getByte();
    short imm7s = instrBits.extract(5,7).getShort(); // signed, needed for movi and cmpi
    byte imm4 = regb;
    int grr;

    pc++;
    if (skip) {
      skip = false;
      return true;
    }
    switch(opcode) {
      case OR:
	regs[rega] = (short)(regs[rega] | regs[regb]);
	Nflag = regs[rega] < 0;
	Zflag = regs[rega] == 0;
	Vflag = false; Cflag = false; // are undef in the spec
        if (rega!=0 || regb!=0)
            System.out.println("OR\tr"+rega+", r"+regb+"\t;"+regs[rega]);
	break;
      case AND:
	regs[rega] = (short)(regs[rega] & regs[regb]);
	Nflag = regs[rega] < 0;
	Zflag = regs[rega] == 0;
	Vflag = false; Cflag = false; // are undef in the spec
        System.out.println("AND\tr"+rega+", r"+regb+"\t;"+regs[rega]);
	break;
      case XOR:
	regs[rega] = (short)(regs[rega] ^ regs[regb]);
	Nflag = regs[rega] < 0;
	Zflag = regs[rega] == 0;
	Vflag = false; Cflag = false; // are undef in the spec
        System.out.println("XOR\tr"+rega+", r"+regb+"\t;"+regs[rega]);
	break;

      case NOT:
	regs[rega] = (short)(~regs[rega]);
        System.out.println("NOT\tr"+rega+"\t;"+regs[rega]);
	break;

      case MOV:
	regs[rega] = regs[regb];
        System.out.println("MOV\tr"+rega+", r"+regb);
	break;
      case ADD:
	grr = regs[rega] + regs[regb]; // big enough to hold it even if it overflows
        Cflag = (char)regs[rega]>(char)grr;
	regs[rega] = (short)grr; // truncate
	Nflag = regs[rega] < 0;
	Vflag = grr!=regs[rega];
	Zflag = regs[rega] == 0;
        System.out.println("ADD\tr"+rega+", r"+regb+"\t;"+regs[rega]);
	break;

      case SUB:
	grr = regs[rega] - regs[regb]; // big enough to hold it even if it overflows
        Cflag = (char)regs[rega]<(char)grr;
	regs[rega] = (short)grr; // truncate
	Nflag = regs[rega] < 0;
	Vflag = grr!=regs[rega];
	Zflag = regs[rega] == 0;
        System.out.println("SUB\tr"+rega+", r"+regb+"\t;"+regs[rega]);
	break;
      case CMP:
	// just set the condition flags
	grr = regs[rega] - regs[regb]; // big enough to hold it even if it overflows
        Cflag = (char) regs[rega]<(char)grr;
	Nflag = (short)grr < 0;
	Vflag = grr!=(short)grr;
	Zflag = grr == 0;
        System.out.println("CMP\tr"+rega+", r"+regb);
	break;

      case LSLI:
        if (imm4!=0)
          regs[rega] = (short)(regs[rega] << (16-imm4));
        System.out.println("LSLI\tr"+rega+", "+imm4+"\t;"+regs[rega]);
	break;
      case LSRI:
	regs[rega] = (short)((((int)regs[rega])&0xffff) >>> imm4);
        System.out.println("LSRI\tr"+rega+", "+imm4+"\t;"+regs[rega]);
	break;
      case ASRI:
	regs[rega] = (short)(regs[rega] >> imm4);
        System.out.println("ASRI\tr"+rega+", "+imm4+"\t;"+regs[rega]);
	break;
      case ROTI: // this is the only non-trivial one
	grr = (regs[rega]&0xffff) | ((int)regs[rega] << 16);
        regs[rega] = (short)((grr >> (imm4)) & 0xffff);
        System.out.println("ROTI\tr"+rega+", "+imm4+"\t;"+regs[rega]);
	break;
	
      case MOVI:
	regs[rega] = imm7s;
	System.out.println("MOVI\tr" + rega + ", " + imm7s);
	break;
      case ADDI:
	grr = regs[rega] + imm7; // big enough to hold it even if it overflows
        Cflag = (char)regs[rega]>(char)grr;
	regs[rega] = (short)grr; // truncate
	Nflag = regs[rega] < 0;
	Vflag = grr!=regs[rega];
	Zflag = regs[rega] == 0;
	System.out.println("ADDI\tr" + rega + ", " + imm7s+"\t;"+regs[rega]);
	break;
      case SUBI:
	grr = regs[rega] - imm7; // big enough to hold it even if it overflows
        Cflag = (char)regs[rega]<(char)grr;
	regs[rega] = (short)grr; // truncate
	Nflag = regs[rega] < 0;
	Vflag = grr!=regs[rega];
	Zflag = regs[rega] == 0;
	System.out.println("SUBI\tr" + rega + ", " + imm7s+"\t;"+regs[rega]);
	break;
      case CMPI:
	// just set the condition flags
	grr = regs[rega] - imm7s; // big enough to hold it even if it overflows
        Cflag = (char) regs[rega]<(char)grr;
	Nflag = (short)grr < 0;
	Vflag = grr!=(short)grr;
	Zflag = grr == 0;
	System.out.println("CMPI\tr" + rega + ", " + imm7s);
	break;

      case LD:
	regs[rega] = readData((char)regs[regb]);
	break;
      case ST:
	writeData((char)regs[regb], regs[rega]);
	break;

      case BR:
	if (delaySlot) return false;
	if (imm11s == -1) return false; // because we are looping on the spot i.e. halting
	delaySlot = true;
	mainLoop();
	delaySlot = false;
	pc += imm11s - 1;
	System.out.println("BR\t" + pc);
	break;
      case BSR:
	if (delaySlot) return false;
	delaySlot = true;
	mainLoop();
	delaySlot = false;
	regs[15] = pc;
	pc += imm11s - 1;
	System.out.println("BSR\t" + pc);
	break;
      case RET:
	if (delaySlot) return false;
	delaySlot = true;
	mainLoop();
	delaySlot = false;
	pc = regs[15];
	System.out.println("RET\t\t;" + pc);
	break;
      case IFS:
	if (delaySlot) return false;
        System.out.print("IFS\t");
	switch(imm4) {
	  /* remember skip is true if we *aren't* doing the next instruction i.e. if the condition is false */
	  case NC:
	    skip = Cflag;
            System.out.print("NC");
	    break;
	  case C:
	    skip = !Cflag;
            System.out.print("C");
	    break;
	  case NZ:
	    skip = Zflag;
            System.out.print("NZ");
	    break;
	  case Z:
	    skip = !Zflag;
            System.out.print("Z");
	    break;
	  case PL:
	    skip = Nflag;
            System.out.print("PL");
	    break;
	  case MI:
	    skip = !Nflag;
            System.out.print("MI");
	    break;
	  case LT:
	    skip = !(Nflag ^ Vflag);
            System.out.print("LT");
	    break;
	  case GE:
	    skip = Nflag ^ Vflag;
            System.out.print("GE");
	    break;
	  case GT:
	    skip = Zflag || (Nflag ^ Vflag);
            System.out.print("GT");
	    break;
	  case LE:
	    skip = !(Zflag || (Nflag ^ Vflag));
            System.out.print("LE");
	    break;
	  case NV:
	    skip = Vflag;
            System.out.print("NV");
	    break;
	  case V:
	    skip = !Vflag;
            System.out.print("V");
	    break;
	  case HI:
	    skip = Cflag || Zflag;
            System.out.print("HI");
	    break;
	  case LA:
	    skip = !(Cflag || Zflag);
            System.out.print("LA");
	    break;
	  default:
	    System.err.println("illegal condition code: " + imm4);
	    return false;
	}
        if (skip)
          System.out.println("\t;condition false");
        else
          System.out.println("\t;condition true");
	break;
      default:
	System.err.println("Illegal instruction: " + opcode);
	return false;
    }
    return true;
  }

  public static short readData(char addr) {
    short tmp;

    tmp = dataMemory[addr];
    System.out.println("read " + tmp + " from address " + (int)addr);
    return tmp;
  }

  static public void writeData(char addr, short data) {
    dataMemory[addr] = data;
    System.out.println("wrote " + data + " to address " + (int) addr);
  }

  static String regToHex(int r) {
    /* Returns out a register as a hex value */
    Bus v = new Bus(16);
    v.write((char)(regs[r]));
    return v.toHexString();
  }
    

  static void init() //initialises the instruction memory - ignore for your own sanity!!!
    {
	char [] instruction_buffer = new char [32768];
	int [] int_buffer = new int [4];
	try
	    {
		BufferedReader in = new BufferedReader(new FileReader("..\\N_105_system_sim\\onchip_rom_lane0.dat"));
		//this file is the same one read by the hardware simulation and generated by the assembler (probably!)
		//char [] stuff = new char [15]; //crap at the beginning of the file
		//in.read(stuff,0,15);
		
		int file_length = in.read(instruction_buffer,0,32768);
		//System.out.println(instruction_buffer);
		int k = 10;
		for(int i = 0; i<(file_length/4); i++)
		    {
			for(int j = 0; (j<4 && k<file_length); k++)
			    {
				int_buffer[j] = (int)instruction_buffer[k];
				
				if (int_buffer[j] < 58)
				    int_buffer[j] -= 48;
				else
				    int_buffer[j] -= 87;

				if(int_buffer[j] >= 0 && int_buffer[j] < 16)    
				    j++;
			    }
			instrMemory[i] = (char)((int_buffer[0] << 12) + (int_buffer[1] << 8) + (int_buffer[2] << 4) + (int_buffer[3]));
			//System.out.println((int)instruction_mem[i]);
		    }
		in.close();
	    }
	catch(Exception e)
	    {
		System.out.println(e);
	    }
    }

}
