// (c)1998, Carl Burch. This may not be redistributed
import java.util.Vector;

import DLXListener;

public class DLX {
	// number of registers
	public static final int NUM_REGS = 32;

	// the register where the JAL instructions store the PC
	public static final int LINK_REG = 31;

	// whether to use delay slot
	public static final boolean DELAY_SLOT = true;

	// the default number of bytes, and the maximum number of bytes
	public static final int NUM_BYTES_DFLT = 512;
	public static final int NUM_BYTES_MAX = 0x40000000;

	// the default memory value
	public static final byte MEM_DFLT = (byte) 0xFF;

	// which register holds the current program counter
	public static final int PC_REG = 31;

	// bounds for values in halfwords and bytes
	public static final int HALF_MAX =  0x7FFF;
	public static final int HALF_MIN = -0x8000;
	public static final int BYTE_MAX =  0x7F;
	public static final int BYTE_MIN = -0x80;

	// codes for notifying DLXListeners of changes
	public static final int ELT_REG = 0;
	public static final int ELT_MEM = 1;

	// opcodes
	public static final int OP_SPECIAL = 0x00;
	public static final int OP_FPARITH = 0x01;
	public static final int OP_J       = 0x02;
	public static final int OP_JAL     = 0x03;
	public static final int OP_BEQZ    = 0x04;
	public static final int OP_BNEZ    = 0x05;
	public static final int OP_BFPT    = 0x06;
	public static final int OP_BFPF    = 0x07;
	public static final int OP_ADDI    = 0x08;
	public static final int OP_ADDUI   = 0x09;
	public static final int OP_SUBI    = 0x0A;
	public static final int OP_SUBUI   = 0x0B;
	public static final int OP_ANDI    = 0x0C;
	public static final int OP_ORI     = 0x0D;
	public static final int OP_XORI    = 0x0E;
	public static final int OP_LHI     = 0x0F;
	public static final int OP_RFE     = 0x10;
	public static final int OP_TRAP    = 0x11;
	public static final int OP_JR      = 0x12;
	public static final int OP_JALR    = 0x13;
	public static final int OP_SLLI    = 0x14;
	// public static final int OP_     = 0x15;
	public static final int OP_SRLI    = 0x16;
	public static final int OP_SRAI    = 0x17;
	public static final int OP_SEQI    = 0x18;
	public static final int OP_SNEI    = 0x19;
	public static final int OP_SLTI    = 0x1A;
	public static final int OP_SGTI    = 0x1B;
	public static final int OP_SLEI    = 0x1C;
	public static final int OP_SGEI    = 0x1D;
	// public static final int OP_     = 0x1E;
	// public static final int OP_     = 0x1F;
	public static final int OP_LB      = 0x20;
	public static final int OP_LH      = 0x21;
	// public static final int OP_     = 0x22;
	public static final int OP_LW      = 0x23;
	public static final int OP_LBU     = 0x24;
	public static final int OP_LHU     = 0x25;
	public static final int OP_LF      = 0x26;
	public static final int OP_LD      = 0x27;
	public static final int OP_SB      = 0x28;
	public static final int OP_SH      = 0x29;
	// public static final int OP_     = 0x2A;
	public static final int OP_SW      = 0x2B;
	// public static final int OP_     = 0x2C;
	// public static final int OP_     = 0x2D;
	public static final int OP_SF      = 0x2E;
	public static final int OP_SD      = 0x2F;

	// special opcodes
	public static final int SOP_SLL    = 0x04;
	public static final int SOP_SRL    = 0x06;
	public static final int SOP_SRA    = 0x07;
	public static final int SOP_TRAP   = 0x0C;
	public static final int SOP_ADD    = 0x20;
	public static final int SOP_ADDU   = 0x21;
	public static final int SOP_SUB    = 0x22;
	public static final int SOP_SUBU   = 0x23;
	public static final int SOP_AND    = 0x24;
	public static final int SOP_OR     = 0x25;
	public static final int SOP_XOR    = 0x26;
	public static final int SOP_SEQ    = 0x28;
	public static final int SOP_SNE    = 0x29;
	public static final int SOP_SLT    = 0x2A;
	public static final int SOP_SGT    = 0x2B;
	public static final int SOP_SLE    = 0x2C;
	public static final int SOP_SGE    = 0x2D;
	public static final int SOP_MOVI2S = 0x30;
	public static final int SOP_MOVS2I = 0x31;
	public static final int SOP_MOVF   = 0x32;
	public static final int SOP_MOVD   = 0x33;
	public static final int SOP_MOVFP2I = 0x34;
	public static final int SOP_MOVI2FP = 0x35;

	public static final int SOP_MULT   = 0x17; // this is really floating-point
	public static final int SOP_DIV    = 0x18; // this is really floating-point
	public static final int SOP_MOD    = 0x19; // this really isn't yet defined

	public boolean halted = false;

	// current state of the machine
	protected Register[] reg = new Register[NUM_REGS];
	protected byte[] mem;
	int pc; // program counter
	int next_pc; // this is needed for the delay slot

	// other data
	protected int num_bytes;
	protected Vector listeners = new Vector();

	public DLX() {
		num_bytes = NUM_BYTES_DFLT;
		mem = new byte[num_bytes];
		reset();
	}

	public DLX(int n) {
		if(n > NUM_BYTES_MAX) n = NUM_BYTES_MAX;
		num_bytes = n;
		mem = new byte[num_bytes];
		reset();
	}

	public int numBytes() { return num_bytes; }

	public void reset() {
		halted = false;
		pc = 0;
		next_pc = DELAY_SLOT ? 4 : 0;
		for(int i = 0; i < num_bytes / 2; i++) mem[i] = MEM_DFLT;
		for(int i = 0; i < NUM_REGS; i++) reg[i] = new Register();
	}

	public boolean isHalted() {
		return halted;
	}

	public void addListener(DLXListener l) {
		listeners.addElement(l);
	}
	public void notify(int type, int first, int num) {
		for(int i = 0; i < listeners.size(); i++) {
			DLXListener l = (DLXListener) listeners.elementAt(i);
			l.machineChanged(this, type, first, num);
		}
	}

	public int getPC() { return pc; }

	public int getReg(int which) throws RunException {
		if(which < 0 || which >= NUM_REGS) {
			throw new RunException("Attempt to access register "
				+ Integer.toString(which));
		}
		return reg[which].getSignedInt();
	}
	public void setReg(int which, int val) throws RunException {
		if(which < 0 || which >= NUM_REGS) {
			throw new RunException("Attempt to set register "
				+ Integer.toString(which));
		}
		if(which == 0 && val != 0) {
			throw new RunException("Attempt to set R0 to nonzero value");
		}
		if(reg[which].getSignedInt() != val) {
			reg[which].setToSignedInt(val);
			notify(ELT_REG, which, 1);
		}
	}

	public static int byteToUnsigned(int what) {
		return what >= 0 ? what : (what + 0x100);
	}
	public static byte unsignedToByte(int what) {
		return (byte) (what <= 0x7F ? what : (what - 0x100));
	}
	public void checkAlignedAccess(int which, int length)
			throws RunException {
		if(which < 0 || which + length > num_bytes) {
			throw new RunException("Attempt to access out-of-memory address 0x"
				+ intToHex(which, 8));
		}

		if(which % length != 0) {
			throw new RunException("Unaligned access to address 0x"
				+ intToHex(which, 8));
		}
	}
	protected void setMemBytes(int which, byte[] val) throws RunException {
		if(which < 0 || which + val.length > num_bytes) {
			throw new RunException("Attempt to access out-of-memory address 0x"
				+ intToHex(which, 8));
		}
		for(int i = 0; i < val.length; i++) {
			mem[which + i] = unsignedToByte(val[i]);
		}
		notify(ELT_MEM, which, val.length);
	}
	public int getMemWord(int which) throws RunException {
		checkAlignedAccess(which, 4);
		return (byteToUnsigned(mem[which + 0]) << 24)
			| (byteToUnsigned(mem[which + 1]) << 16)
			| (byteToUnsigned(mem[which + 2]) << 8)
			| byteToUnsigned(mem[which + 3]);
	}
	protected void setMemWord(int which, int val) throws RunException {
		checkAlignedAccess(which, 4);
		mem[which + 0] = unsignedToByte((val >> 24) & 0xFF);
		mem[which + 1] = unsignedToByte((val >> 16) & 0xFF);
		mem[which + 2] = unsignedToByte((val >>  8) & 0xFF);
		mem[which + 3] = unsignedToByte((val      ) & 0xFF);
		notify(ELT_MEM, which, 4);
	}
	public int getMemHalf(int which) throws RunException {
		checkAlignedAccess(which, 2);
		return (byteToUnsigned(mem[which]) << 8)
			| byteToUnsigned(mem[which + 1]);
	}
	protected void setMemHalf(int which, int val) throws RunException {
		checkAlignedAccess(which, 2);
		if(val < HALF_MIN || val > HALF_MAX) {
			throw new RunException("Attempt to set byte 0x"
				+ intToHex(which, 8) + " to out-of-range value "
				+ Integer.toString(val));
		}
		mem[which + 0] = unsignedToByte((val >>  8) & 0xFF);
		mem[which + 1] = unsignedToByte((val      ) & 0xFF);
		notify(ELT_MEM, which, 4);
	}
	public int getMemByte(int which) throws RunException {
		checkAlignedAccess(which, 1);
		return byteToUnsigned(mem[which]);
	}
	protected void setMemByte(int which, int val) throws RunException {
		checkAlignedAccess(which, 1);
		if(val < BYTE_MIN || val > BYTE_MAX) {
			throw new RunException("Attempt to set byte 0x"
				+ intToHex(which, 8) + " to out-of-range value "
				+ Integer.toString(val));
		}
		mem[which + 0] = unsignedToByte((val      ) & 0xFF);
	}

	public void executeStep() throws RunException {
		int instr; // this will be the instruction to execute

		if(isHalted()) return;

		// fetch & increment (the order changes if we use delay slots)
		if(DELAY_SLOT) {
			instr = getMemWord(pc);
			pc = next_pc;
			next_pc = pc + 4;
		} else {
			pc = next_pc;
			instr = getMemWord(pc);
			pc = pc + 4;
			next_pc = pc;
		}

		int opcode = (instr >> 26) & 0x3F;

		// handle R-type instructions
		if(opcode == OP_SPECIAL) {
			int func = instr & 0x7FF;
			Register ri = reg[(instr >> 21) & 0x1F];
			Register rj = reg[(instr >> 16) & 0x1F];
			int rd_num  =     (instr >> 11) & 0x1F;
			Register rd = reg[rd_num];

			rd.setChanged(false);
			switch(func) {
			case SOP_ADD:  rd.setToSum(ri, rj); break;
			case SOP_SUB:  rd.setToDifference(ri, rj); break;
			case SOP_MULT: rd.setToProduct(ri, rj); break;
			case SOP_DIV:  rd.setToQuotient(ri, rj); break;
			case SOP_MOD:  rd.setToModulo(ri, rj); break;
			case SOP_AND:  rd.setToAnd(ri, rj); break;
			case SOP_OR:   rd.setToOr(ri, rj); break;
			case SOP_XOR:  rd.setToXor(ri, rj); break;
			case SOP_SLL:  rd.setToShiftLeft(ri, rj.getSignedInt()); break;
			case SOP_SRL:  rd.setToShiftRightLogical(ri, rj.getSignedInt()); break;
			case SOP_SRA:  rd.setToShiftRightArithmetic(ri, rj.getSignedInt()); break;
			case SOP_SEQ:  rd.setToSignedInt(ri.compare(rj) == 0 ? 1 : 0); break;
			case SOP_SNE:  rd.setToSignedInt(ri.compare(rj) != 0 ? 1 : 0); break;
			case SOP_SLT:  rd.setToSignedInt(ri.compare(rj) < 0 ? 1 : 0); break;
			case SOP_SGT:  rd.setToSignedInt(ri.compare(rj) > 0 ? 1 : 0); break;
			case SOP_SLE:  rd.setToSignedInt(ri.compare(rj) <= 0 ? 1 : 0); break;
			case SOP_SGE:  rd.setToSignedInt(ri.compare(rj) >= 0 ? 1 : 0); break;
			case SOP_TRAP: halted = true; break;
			case SOP_ADDU:
			case SOP_SUBU:
			case SOP_MOVI2S:
			case SOP_MOVS2I:
			case SOP_MOVF:
			case SOP_MOVD:
			case SOP_MOVFP2I:
			case SOP_MOVI2FP:
				throw new RunException("Unimplemented special func 0x"
					+ intToHex(func, 3));
			default:
				throw new RunException("Unrecognized special func 0x"
					+ intToHex(func, 3));
			}
			if(rd.isChanged()) notify(ELT_REG, rd_num, 1);
			return;
		}

		// handle J-type instructions
		int offs = instr & 0x03FFFFFF;

		if(offs > 0x01FFFFFF) offs -= 0x04000000;
		boolean handled = true; // will be false if not a J instruction
		switch(opcode) {
		case OP_J:
			next_pc = pc + offs;
			break;
		case OP_JAL:
			reg[LINK_REG].setToSignedInt(pc);
			notify(ELT_REG, LINK_REG, 1);
			next_pc = pc + offs;
			break;
		case OP_TRAP:
			halted = true;
			break;
		case OP_RFE:
			throw new RunException("Unimplememented operation 0x"
				+ intToHex(opcode, 2));
		default:
			// it's an I-type instruction; these we tackle next
			handled = false;
		}
		if(handled) return;

		// handle I-type instructions
		int immed_val = instr & 0x0000FFFF;
		if(immed_val > 0x00007FFF) immed_val -= 0x00010000;
		Register immed = new Register(immed_val);
		Register rs = reg[(instr >> 21) & 0x1F];
		int  rd_num =     (instr >> 16) & 0x1F;
		Register rd = reg[rd_num];

		rd.setChanged(false);
		switch(opcode) {
		case OP_JR:   next_pc = rd.getSignedInt(); break;
		case OP_JALR:
			reg[LINK_REG].setToSignedInt(pc);
			notify(ELT_REG, LINK_REG, 1);
			next_pc = rd.getSignedInt();
			break;
		case OP_BEQZ: if(rd.isZero()) next_pc = pc + immed_val; break;
		case OP_BNEZ: if(!rd.isZero()) next_pc = pc + immed_val; break;
		case OP_ADDI: rd.setToSum(rs, immed); break;
		case OP_SUBI: rd.setToDifference(rs, immed); break;
		case OP_LHI:  immed.setToShiftLeft(immed, 16); rd.setToOr(rd, immed); break;
		case OP_ANDI: rd.setToAnd(rs, immed); break;
		case OP_ORI:  rd.setToOr(rs, immed); break;
		case OP_XORI: rd.setToXor(rs, immed); break;
		case OP_SLLI: rd.setToShiftLeft(rs, immed.getSignedInt()); break;
		case OP_SRLI: rd.setToShiftRightLogical(rs, immed.getSignedInt()); break;
		case OP_SRAI: rd.setToShiftRightArithmetic(rs, immed.getSignedInt()); break;
		case OP_SEQI: rd.setToSignedInt(rs.compare(immed) == 0 ? 1 : 0); break;
		case OP_SNEI: rd.setToSignedInt(rs.compare(immed) != 0 ? 1 : 0); break;
		case OP_SLTI: rd.setToSignedInt(rs.compare(immed) < 0 ? 1 : 0); break;
		case OP_SGTI: rd.setToSignedInt(rs.compare(immed) > 0 ? 1 : 0); break;
		case OP_SLEI: rd.setToSignedInt(rs.compare(immed) <= 0 ? 1 : 0); break;
		case OP_SGEI: rd.setToSignedInt(rs.compare(immed) >= 0 ? 1 : 0); break;
		case OP_LB:   rd.setToSignedInt(getMemByte(rs.getSignedInt() + immed_val)); rd.signExtend(8); break;
		case OP_LBU:  rd.setToSignedInt(getMemByte(rs.getSignedInt() + immed_val)); rd.signExtend(16); break;
		case OP_LHU:  rd.setToSignedInt(getMemHalf(rs.getSignedInt() + immed_val)); break;
		case OP_LW:   rd.setToSignedInt(getMemWord(rs.getSignedInt() + immed_val)); break;
		case OP_SB:   setMemByte(rs.getSignedInt() + immed_val, rd.getSignedInt()); break;
		case OP_SH:   setMemHalf(rs.getSignedInt() + immed_val, rd.getSignedInt()); break;
		case OP_SW:   setMemWord(rs.getSignedInt() + immed_val, rd.getSignedInt()); break;
		case OP_FPARITH:
		case OP_BFPT:
		case OP_BFPF:
		case OP_ADDUI:
		case OP_SUBUI:
		case OP_LF:
		case OP_LD:
		case OP_SF:
		case OP_SD:
			throw new RunException("Unimplemented operation 0x"
				+ intToHex(opcode, 2));
		default:
			throw new RunException("Unrecognized operation 0x"
				+ intToHex(opcode, 2));
		}

		if(rd.isChanged()) notify(ELT_REG, rd_num, 1);
		return;
	}

	public static String intToHex(long what, int len) {
		char[] ret = new char[len];
		long x = what;
		for(int i = 0; i < len; i++) {
			ret[len - 1 - i] = Character.forDigit((int) (x & 0xF), 16);
			x >>= 4;
		}
		return new String(ret);
	}
}
