diff --git a/.gitignore b/.gitignore index a1be012..a7e6162 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ *~ *# target -.classpath -.project -.settings .DS_Store +.idea +symon.iml diff --git a/pom.xml b/pom.xml index ea4b299..18d1e36 100644 --- a/pom.xml +++ b/pom.xml @@ -1,115 +1,144 @@ - 4.0.0 - com.loomcom.symon - symon - jar - 0.1 - symon - http://www.loomcom.com/symon - - - jline - JLine Project Repository - http://jline.sourceforge.net/m2repo - - - - - junit - junit - 4.7 - test - - - jline - jline - 0.9.9 - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 + com.loomcom.symon + symon + jar + 0.1 + symon + http://www.loomcom.com/symon + + + UTF-8 + + + UTF-8 + + + + + jline + JLine Project Repository + http://jline.sourceforge.net/m2repo + + + + + true + always + fail + + com.loomcom + Loom Communications Maven2 Repository + http://www.loomcom.com/maven2 + default + + + + + junit + junit + 4.7 + test + + + jline + jline + 0.9.9 + + + com.grahamedgecombe.jterminal + jterminal + 1.0.2-SNAPSHOT + + - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - 1.5 - 1.5 - - + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + + 1.5 + 1.5 + + - - - org.apache.maven.plugins - maven-jar-plugin - - - - com.loomcom.symon.Simulator - com.loomcom.symon - - - development - ${pom.url} - - - - + + + org.apache.maven.plugins + maven-jar-plugin + 2.3.1 + + + + com.loomcom.symon.MainWindow + com.loomcom.symon + + + development + ${project.url} + + + + - - - org.codehaus.mojo - cobertura-maven-plugin - - - false - - - com.loomcom.symon.* - 90 - 90 - - - - - - com/loomcom/symon/*.class - - - - - - clean - pre-site - - clean - - - - instrument - site - - instrument - cobertura - check - - - - + + + org.codehaus.mojo + cobertura-maven-plugin + 2.4 + + + false + + + com.loomcom.symon.* + 90 + 90 + + + + + + com/loomcom/symon/*.class + + + + + + clean + pre-site + + clean + + + + instrument + site + + instrument + cobertura + check + + + + - - + + - - - - org.codehaus.mojo - cobertura-maven-plugin - - - + + + + org.codehaus.mojo + cobertura-maven-plugin + 2.4 + + + diff --git a/src/main/java/com/loomcom/symon/Bus.java b/src/main/java/com/loomcom/symon/Bus.java index 6979bb9..201a507 100644 --- a/src/main/java/com/loomcom/symon/Bus.java +++ b/src/main/java/com/loomcom/symon/Bus.java @@ -1,6 +1,7 @@ package com.loomcom.symon; import java.util.*; + import com.loomcom.symon.devices.*; import com.loomcom.symon.exceptions.*; @@ -9,134 +10,140 @@ import com.loomcom.symon.exceptions.*; */ public class Bus { - // By default, our bus starts at 0, and goes up to 64K - private int startAddress = 0x0000; - private int endAddress = 0xffff; - // The CPU - private Cpu cpu; - // Ordered list of IO devices. - private SortedSet devices; + // By default, our bus starts at 0, and goes up to 64K + private int startAddress = 0x0000; + private int endAddress = 0xffff; + // The CPU + private Cpu cpu; + // Ordered list of IO devices. + private SortedSet devices; - public Bus(int size) { - this(0, size - 1); - } + public Bus(int size) { + this(0, size - 1); + } - public Bus(int startAddress, int endAddress) { - this.devices = new TreeSet(); - this.startAddress = startAddress; - this.endAddress = endAddress; - } + public Bus(int startAddress, int endAddress) { + this.devices = new TreeSet(); + this.startAddress = startAddress; + this.endAddress = endAddress; + } - public int startAddress() { - return startAddress; - } + public int startAddress() { + return startAddress; + } - public int endAddress() { - return endAddress; - } + public int endAddress() { + return endAddress; + } - public void addDevice(Device device) + public void addDevice(Device device) throws MemoryRangeException { - // Make sure there's no memory overlap. - MemoryRange memRange = device.getMemoryRange(); - for (Device d : devices) { - if (d.getMemoryRange().overlaps(memRange)) { - throw new MemoryRangeException("The device being added " + - "overlaps with an existing " + - "device."); - } - } - - // Add the device - device.setBus(this); - devices.add(device); - } - - public void addCpu(Cpu cpu) { - cpu.setBus(this); - this.cpu = cpu; - } - - /** - * Returns true if the memory map is full, i.e., there are no - * gaps between any IO devices. All memory locations map to some - * device. - */ - public boolean isComplete() { - // Empty maps cannot be complete. - if (devices.isEmpty()) { return false; } - - // Loop over devices and ensure they are contiguous. - MemoryRange prev = null; - int i = 0; - int length = devices.size(); - for (Device d : devices) { - MemoryRange cur = d.getMemoryRange(); - if (i == 0) { - // If the first entry doesn't start at 'startAddress', return false. - if (cur.startAddress() != startAddress) { return false; } - } - - if (prev != null && i < length - 1) { - // Otherwise, compare previous map's end against this map's - // endAddress. They must be adjacent! - if (cur.startAddress() - 1 != prev.endAddress()) { - return false; + // Make sure there's no memory overlap. + MemoryRange memRange = device.getMemoryRange(); + for (Device d : devices) { + if (d.getMemoryRange().overlaps(memRange)) { + throw new MemoryRangeException("The device being added " + + "overlaps with an existing " + + "device."); + } } - } - if (i == length - 1) { - // If the last entry doesn't end at endAddress, return false; - if (cur.endAddress() != endAddress) { return false; } - } - - i++; - prev = cur; + // Add the device + device.setBus(this); + devices.add(device); } - // Must be complete. - return true; - } - - public int read(int address) throws MemoryAccessException { - for (Device d : devices) { - MemoryRange range = d.getMemoryRange(); - if (range.includes(address)) { - // Compute offset into this device's address space. - int devAddr = address - range.startAddress(); - return d.read(devAddr); - } + public void addCpu(Cpu cpu) { + cpu.setBus(this); + this.cpu = cpu; } - throw new MemoryAccessException("Read failed! No device at address."); - } - public void write(int address, int value) throws MemoryAccessException { - for (Device d : devices) { - MemoryRange range = d.getMemoryRange(); - if (range.includes(address)) { - // Compute offset into this device's address space. - int devAddr = address - range.startAddress(); - d.write(devAddr, value); - return; - } + /** + * Returns true if the memory map is full, i.e., there are no + * gaps between any IO devices. All memory locations map to some + * device. + */ + public boolean isComplete() { + // Empty maps cannot be complete. + if (devices.isEmpty()) { + return false; + } + + // Loop over devices and ensure they are contiguous. + MemoryRange prev = null; + int i = 0; + int length = devices.size(); + for (Device d : devices) { + MemoryRange cur = d.getMemoryRange(); + if (i == 0) { + // If the first entry doesn't start at 'startAddress', return false. + if (cur.startAddress() != startAddress) { + return false; + } + } + + if (prev != null && i < length - 1) { + // Otherwise, compare previous map's end against this map's + // endAddress. They must be adjacent! + if (cur.startAddress() - 1 != prev.endAddress()) { + return false; + } + } + + if (i == length - 1) { + // If the last entry doesn't end at endAddress, return false; + if (cur.endAddress() != endAddress) { + return false; + } + } + + i++; + prev = cur; + } + + // Must be complete. + return true; } - throw new MemoryAccessException("Write failed! No device at address."); - } - public SortedSet getDevices() { - // Expose a copy of the device list, not the original - return new TreeSet(devices); - } - - public Cpu getCpu() { - return cpu; - } - - public void loadProgram(int... program) throws MemoryAccessException { - int address = getCpu().getProgramCounter(); - int i = 0; - for (int d : program) { - write(address + i++, d); + public int read(int address) throws MemoryAccessException { + for (Device d : devices) { + MemoryRange range = d.getMemoryRange(); + if (range.includes(address)) { + // Compute offset into this device's address space. + int devAddr = address - range.startAddress(); + return d.read(devAddr); + } + } + throw new MemoryAccessException("Read failed! No device at address."); + } + + public void write(int address, int value) throws MemoryAccessException { + for (Device d : devices) { + MemoryRange range = d.getMemoryRange(); + if (range.includes(address)) { + // Compute offset into this device's address space. + int devAddr = address - range.startAddress(); + d.write(devAddr, value); + return; + } + } + throw new MemoryAccessException("Write failed! No device at address."); + } + + public SortedSet getDevices() { + // Expose a copy of the device list, not the original + return new TreeSet(devices); + } + + public Cpu getCpu() { + return cpu; + } + + public void loadProgram(int... program) throws MemoryAccessException { + int address = getCpu().getProgramCounter(); + int i = 0; + for (int d : program) { + write(address + i++, d); + } } - } } diff --git a/src/main/java/com/loomcom/symon/Cpu.java b/src/main/java/com/loomcom/symon/Cpu.java index cdf6680..107b0d7 100644 --- a/src/main/java/com/loomcom/symon/Cpu.java +++ b/src/main/java/com/loomcom/symon/Cpu.java @@ -1,6 +1,5 @@ package com.loomcom.symon; -import com.loomcom.symon.InstructionTable; import com.loomcom.symon.exceptions.MemoryAccessException; /** @@ -8,1365 +7,1429 @@ import com.loomcom.symon.exceptions.MemoryAccessException; */ public class Cpu implements InstructionTable { - public static final int DEFAULT_BASE_ADDRESS = 0x200; + public static final int DEFAULT_BASE_ADDRESS = 0x200; - /* Process status register mnemonics */ - public static final int P_CARRY = 0x01; - public static final int P_ZERO = 0x02; - public static final int P_IRQ_DISABLE = 0x04; - public static final int P_DECIMAL = 0x08; - public static final int P_BREAK = 0x10; - // Bit 5 is always '1' - public static final int P_OVERFLOW = 0x40; - public static final int P_NEGATIVE = 0x80; + /* Process status register mnemonics */ + public static final int P_CARRY = 0x01; + public static final int P_ZERO = 0x02; + public static final int P_IRQ_DISABLE = 0x04; + public static final int P_DECIMAL = 0x08; + public static final int P_BREAK = 0x10; + // Bit 5 is always '1' + public static final int P_OVERFLOW = 0x40; + public static final int P_NEGATIVE = 0x80; - // NMI vector - public static final int IRQ_VECTOR_L = 0xfffa; - public static final int IRQ_VECTOR_H = 0xfffb; - // Reset vector - public static final int RST_VECTOR_L = 0xfffc; - public static final int RST_VECTOR_H = 0xfffd; - // IRQ vector - public static final int NMI_VECTOR_L = 0xfffe; - public static final int NMI_VECTOR_H = 0xffff; + // NMI vector + public static final int IRQ_VECTOR_L = 0xfffa; + public static final int IRQ_VECTOR_H = 0xfffb; + // Reset vector + public static final int RST_VECTOR_L = 0xfffc; + public static final int RST_VECTOR_H = 0xfffd; + // IRQ vector + public static final int NMI_VECTOR_L = 0xfffe; + public static final int NMI_VECTOR_H = 0xffff; - /* The Bus */ - private Bus bus; + /* The Bus */ + private Bus bus; - /* User Registers */ - private int a; // Accumulator - private int x; // X index register - private int y; // Y index register + /* User Registers */ + private int a; // Accumulator + private int x; // X index register + private int y; // Y index register - /* Internal Registers */ - private int pc; // Program Counter register - private int sp; // Stack Pointer register, offset into page 1 - private int ir; // Instruction register - private int[] args = new int[3]; // Decoded instruction args - private int instSize; // # of operands for the instruction + /* Internal Registers */ + private int pc; // Program Counter register + private int sp; // Stack Pointer register, offset into page 1 + private int ir; // Instruction register + private int[] args = new int[3]; // Decoded instruction args + private int instSize; // # of operands for the instruction - /* Scratch space for addressing mode and effective address - * calculations */ - private int irAddressMode; // Bits 3-5 of IR: [ | | |X|X|X| | ] - private int irOpMode; // Bits 6-7 of IR: [ | | | | | |X|X] - private int effectiveAddress; + /* Scratch space for addressing mode and effective address + * calculations */ + private int irAddressMode; // Bits 3-5 of IR: [ | | |X|X|X| | ] + private int irOpMode; // Bits 6-7 of IR: [ | | | | | |X|X] + private int effectiveAddress; - /* Internal scratch space */ - private int lo = 0, hi = 0; // Used in address calculation - private int tmp; // Temporary storage + /* Internal scratch space */ + private int lo = 0, hi = 0; // Used in address calculation + private int tmp; // Temporary storage - /* Unimplemented instruction flag */ - private boolean opTrap = false; + /* Unimplemented instruction flag */ + private boolean opTrap = false; - private int addr; // The address the most recent instruction - // was fetched from + private int addr; // The address the most recent instruction + // was fetched from - /* Status Flag Register bits */ - private boolean carryFlag; - private boolean negativeFlag; - private boolean zeroFlag; - private boolean irqDisableFlag; - private boolean decimalModeFlag; - private boolean breakFlag; - private boolean overflowFlag; + /* Status Flag Register bits */ + private boolean carryFlag; + private boolean negativeFlag; + private boolean zeroFlag; + private boolean irqDisableFlag; + private boolean decimalModeFlag; + private boolean breakFlag; + private boolean overflowFlag; - /** - * Construct a new CPU. - */ - public Cpu() {} + /* The number of steps taken since the last reset. */ + private long stepCounter = 0L; - /** - * Set the bus reference for this CPU. - */ - public void setBus(Bus bus) { - this.bus = bus; - } - - /** - * Return the Bus that this CPU is associated with. - */ - public Bus getBus() { - return bus; - } - - /** - * Reset the CPU to known initial values. - */ - public void reset() throws MemoryAccessException { - // Registers - sp = 0xff; - - // Set the PC to the address stored in the reset vector - pc = address(bus.read(RST_VECTOR_L), bus.read(RST_VECTOR_H)); - - // Clear instruction register. - ir = 0; - - // Clear status register bits. - carryFlag = false; - irqDisableFlag = false; - decimalModeFlag = false; - breakFlag = false; - overflowFlag = false; - - // Clear illegal opcode trap. - opTrap = false; - } - - public void step(int num) throws MemoryAccessException { - for (int i = 0; i < num; i++) { - step(); - } - } - - /** - * Performs an individual machine cycle. - */ - public void step() throws MemoryAccessException { - // Store the address from which the IR was read, for debugging - addr = pc; - - // Fetch memory location for this instruction. - ir = bus.read(pc); - irAddressMode = (ir >> 2) & 0x07; - irOpMode = ir & 0x03; - - // Increment PC - incrementPC(); - - // Clear the illegal opcode trap. - clearOpTrap(); - - // Decode the instruction and operands - instSize = Cpu.instructionSizes[ir]; - for (int i = 0; i < instSize-1; i++) { - args[i] = bus.read(pc); - // Increment PC after reading - incrementPC(); + /** + * Construct a new CPU. + */ + public Cpu() { } - // Get the data from the effective address (if any) + /** + * Set the bus reference for this CPU. + */ + public void setBus(Bus bus) { + this.bus = bus; + } - effectiveAddress = 0; + /** + * Return the Bus that this CPU is associated with. + */ + public Bus getBus() { + return bus; + } - switch(irOpMode) { - case 0: - case 2: - switch(irAddressMode) { - case 0: // #Immediate - break; - case 1: // Zero Page - effectiveAddress = args[0]; - break; - case 2: // Accumulator - ignored - break; - case 3: // Absolute - effectiveAddress = address(args[0], args[1]); - break; - case 5: // Zero Page,X / Zero Page,Y - if (ir == 0x96 || ir == 0xb6) { - effectiveAddress = zpyAddress(args[0]); - } else { - effectiveAddress = zpxAddress(args[0]); + /** + * Reset the CPU to known initial values. + */ + public void reset() throws MemoryAccessException { + // Registers + sp = 0xff; + + // Set the PC to the address stored in the reset vector + pc = address(bus.read(RST_VECTOR_L), bus.read(RST_VECTOR_H)); + + // Clear instruction register. + ir = 0; + + // Clear status register bits. + carryFlag = false; + irqDisableFlag = false; + decimalModeFlag = false; + breakFlag = false; + overflowFlag = false; + + // Clear illegal opcode trap. + opTrap = false; + + // Reset step counter + stepCounter = 0L; + } + + public void step(int num) throws MemoryAccessException { + for (int i = 0; i < num; i++) { + step(); } - break; - case 7: // Absolute,X / Absolute,Y - if (ir == 0xbe) { - effectiveAddress = yAddress(args[0], args[1]); - } else { - effectiveAddress = xAddress(args[0], args[1]); + } + + /** + * Performs an individual machine cycle. + */ + public void step() throws MemoryAccessException { + // Store the address from which the IR was read, for debugging + addr = pc; + + // Fetch memory location for this instruction. + ir = bus.read(pc); + irAddressMode = (ir >> 2) & 0x07; + irOpMode = ir & 0x03; + + // Increment PC + incrementPC(); + + // Clear the illegal opcode trap. + clearOpTrap(); + + // Decode the instruction and operands + instSize = Cpu.instructionSizes[ir]; + for (int i = 0; i < instSize - 1; i++) { + args[i] = bus.read(pc); + // Increment PC after reading + incrementPC(); + } + + // Increment step counter + stepCounter++; + + // Get the data from the effective address (if any) + effectiveAddress = 0; + + switch (irOpMode) { + case 0: + case 2: + switch (irAddressMode) { + case 0: // #Immediate + break; + case 1: // Zero Page + effectiveAddress = args[0]; + break; + case 2: // Accumulator - ignored + break; + case 3: // Absolute + effectiveAddress = address(args[0], args[1]); + break; + case 5: // Zero Page,X / Zero Page,Y + if (ir == 0x96 || ir == 0xb6) { + effectiveAddress = zpyAddress(args[0]); + } else { + effectiveAddress = zpxAddress(args[0]); + } + break; + case 7: // Absolute,X / Absolute,Y + if (ir == 0xbe) { + effectiveAddress = yAddress(args[0], args[1]); + } else { + effectiveAddress = xAddress(args[0], args[1]); + } + break; + } + break; + case 1: + switch (irAddressMode) { + case 0: // (Zero Page,X) + tmp = args[0] + getXRegister(); + effectiveAddress = address(bus.read(tmp), bus.read(tmp + 1)); + break; + case 1: // Zero Page + effectiveAddress = args[0]; + break; + case 2: // #Immediate + effectiveAddress = -1; + break; + case 3: // Absolute + effectiveAddress = address(args[0], args[1]); + break; + case 4: // (Zero Page),Y + tmp = address(bus.read(args[0]), + bus.read((args[0] + 1) & 0xff)); + effectiveAddress = (tmp + getYRegister()) & 0xffff; + break; + case 5: // Zero Page,X + effectiveAddress = zpxAddress(args[0]); + break; + case 6: // Absolute, Y + effectiveAddress = yAddress(args[0], args[1]); + break; + case 7: // Absolute, X + effectiveAddress = xAddress(args[0], args[1]); + break; + } + break; + } + + // Execute + switch (ir) { + + /** Single Byte Instructions; Implied and Relative **/ + case 0x00: // BRK - Force Interrupt - Implied + if (!getIrqDisableFlag()) { + // Set the break flag before pushing. + setBreakFlag(); + // Push program counter + 2 onto the stack + stackPush((pc + 2 >> 8) & 0xff); // PC high byte + stackPush(pc + 2 & 0xff); // PC low byte + stackPush(getProcessorStatus()); + // Set the Interrupt Disabled flag. RTI will clear it. + setIrqDisableFlag(); + // Load interrupt vector address into PC + pc = address(bus.read(IRQ_VECTOR_L), bus.read(IRQ_VECTOR_H)); + } + break; + case 0x08: // PHP - Push Processor Status - Implied + stackPush(getProcessorStatus()); + break; + case 0x10: // BPL - Branch if Positive - Relative + if (!getNegativeFlag()) { + pc = relAddress(args[0]); + } + break; + case 0x18: // CLC - Clear Carry Flag - Implied + clearCarryFlag(); + break; + case 0x20: // JSR - Jump to Subroutine - Implied + stackPush((pc - 1 >> 8) & 0xff); // PC high byte + stackPush(pc - 1 & 0xff); // PC low byte + pc = address(args[0], args[1]); + break; + case 0x28: // PLP - Pull Processor Status - Implied + setProcessorStatus(stackPop()); + break; + case 0x30: // BMI - Branch if Minus - Relative + if (getNegativeFlag()) { + pc = relAddress(args[0]); + } + break; + case 0x38: // SEC - Set Carry Flag - Implied + setCarryFlag(); + break; + case 0x40: // RTI - Return from Interrupt - Implied + setProcessorStatus(stackPop()); + lo = stackPop(); + hi = stackPop(); + setProgramCounter(address(lo, hi)); + break; + case 0x48: // PHA - Push Accumulator - Implied + stackPush(a); + break; + case 0x50: // BVC - Branch if Overflow Clear - Relative + if (!getOverflowFlag()) { + pc = relAddress(args[0]); + } + break; + case 0x58: // CLI - Clear Interrupt Disable - Implied + clearIrqDisableFlag(); + break; + case 0x60: // RTS - Return from Subroutine - Implied + lo = stackPop(); + hi = stackPop(); + setProgramCounter((address(lo, hi) + 1) & 0xffff); + break; + case 0x68: // PLA - Pull Accumulator - Implied + a = stackPop(); + setArithmeticFlags(a); + break; + case 0x70: // BVS - Branch if Overflow Set - Relative + if (getOverflowFlag()) { + pc = relAddress(args[0]); + } + break; + case 0x78: // SEI - Set Interrupt Disable - Implied + setIrqDisableFlag(); + break; + case 0x88: // DEY - Decrement Y Register - Implied + y = --y & 0xff; + setArithmeticFlags(y); + break; + case 0x8a: // TXA - Transfer X to Accumulator - Implied + a = x; + setArithmeticFlags(a); + break; + case 0x90: // BCC - Branch if Carry Clear - Relative + if (!getCarryFlag()) { + pc = relAddress(args[0]); + } + break; + case 0x98: // TYA - Transfer Y to Accumulator - Implied + a = y; + setArithmeticFlags(a); + break; + case 0x9a: // TXS - Transfer X to Stack Pointer - Implied + setStackPointer(x); + break; + case 0xa8: // TAY - Transfer Accumulator to Y - Implied + y = a; + setArithmeticFlags(y); + break; + case 0xaa: // TAX - Transfer Accumulator to X - Implied + x = a; + setArithmeticFlags(x); + break; + case 0xb0: // BCS - Branch if Carry Set - Relative + if (getCarryFlag()) { + pc = relAddress(args[0]); + } + break; + case 0xb8: // CLV - Clear Overflow Flag - Implied + clearOverflowFlag(); + break; + case 0xba: // TSX - Transfer Stack Pointer to X - Implied + x = getStackPointer(); + setArithmeticFlags(x); + break; + case 0xc8: // INY - Increment Y Register - Implied + y = ++y & 0xff; + setArithmeticFlags(y); + break; + case 0xca: // DEX - Decrement X Register - Implied + x = --x & 0xff; + setArithmeticFlags(x); + break; + case 0xd0: // BNE - Branch if Not Equal to Zero - Relative + if (!getZeroFlag()) { + pc = relAddress(args[0]); + } + break; + case 0xd8: // CLD - Clear Decimal Mode - Implied + clearDecimalModeFlag(); + break; + case 0xe8: // INX - Increment X Register - Implied + x = ++x & 0xff; + setArithmeticFlags(x); + break; + case 0xea: // NOP + // Do nothing. + break; + case 0xf0: // BEQ - Branch if Equal to Zero - Relative + if (getZeroFlag()) { + pc = relAddress(args[0]); + } + break; + case 0xf8: // SED - Set Decimal Flag - Implied + setDecimalModeFlag(); + break; + + /** JMP *****************************************************************/ + case 0x4c: // JMP - Absolute + pc = address(args[0], args[1]); + break; + case 0x6c: // JMP - Indirect + lo = address(args[0], args[1]); // Address of low byte + hi = lo + 1; // Address of high byte + pc = address(bus.read(lo), bus.read(hi)); + /* TODO: For accuracy, allow a flag to enable broken behavior + * of early 6502s: + * + * "An original 6502 has does not correctly fetch the target + * address if the indirect vector falls on a page boundary + * (e.g. $xxFF where xx is and value from $00 to $FF). In this + * case fetches the LSB from $xxFF as expected but takes the MSB + * from $xx00. This is fixed in some later chips like the 65SC02 + * so for compatibility always ensure the indirect vector is not + * at the end of the page." + * (http://www.obelisk.demon.co.uk/6502/reference.html#JMP) + */ + break; + + + /** ORA - Logical Inclusive Or ******************************************/ + case 0x09: // #Immediate + a |= args[0]; + setArithmeticFlags(a); + break; + case 0x01: // (Zero Page,X) + case 0x05: // Zero Page + case 0x0d: // Absolute + case 0x11: // (Zero Page),Y + case 0x15: // Zero Page,X + case 0x19: // Absolute,Y + case 0x1d: // Absolute,X + a |= bus.read(effectiveAddress); + setArithmeticFlags(a); + break; + + + /** ASL - Arithmetic Shift Left *****************************************/ + case 0x0a: // Accumulator + a = asl(a); + setArithmeticFlags(a); + break; + case 0x06: // Zero Page + case 0x0e: // Absolute + case 0x16: // Zero Page,X + case 0x1e: // Absolute,X + tmp = asl(bus.read(effectiveAddress)); + bus.write(effectiveAddress, tmp); + setArithmeticFlags(tmp); + break; + + + /** BIT - Bit Test ******************************************************/ + case 0x24: // Zero Page + case 0x2c: // Absolute + tmp = a & bus.read(effectiveAddress); + setZeroFlag(tmp == 0); + setNegativeFlag((tmp & 0x80) != 0); + setOverflowFlag((tmp & 0x40) != 0); + break; + + + /** AND - Logical AND ***************************************************/ + case 0x29: // #Immediate + a &= args[0]; + setArithmeticFlags(a); + break; + case 0x21: // (Zero Page,X) + case 0x25: // Zero Page + case 0x2d: // Absolute + case 0x31: // (Zero Page),Y + case 0x35: // Zero Page,X + case 0x39: // Absolute,Y + case 0x3d: // Absolute,X + a &= bus.read(effectiveAddress); + setArithmeticFlags(a); + break; + + + /** ROL - Rotate Left ***************************************************/ + case 0x2a: // Accumulator + a = rol(a); + setArithmeticFlags(a); + break; + case 0x26: // Zero Page + case 0x2e: // Absolute + case 0x36: // Zero Page,X + case 0x3e: // Absolute,X + tmp = rol(bus.read(effectiveAddress)); + bus.write(effectiveAddress, tmp); + setArithmeticFlags(tmp); + break; + + + /** EOR - Exclusive OR **************************************************/ + case 0x49: // #Immediate + a ^= args[0]; + setArithmeticFlags(a); + break; + case 0x41: // (Zero Page,X) + case 0x45: // Zero Page + case 0x4d: // Absolute + case 0x51: // (Zero Page,Y) + case 0x55: // Zero Page,X + case 0x59: // Absolute,Y + case 0x5d: // Absolute,X + a ^= bus.read(effectiveAddress); + setArithmeticFlags(a); + break; + + + /** LSR - Logical Shift Right *******************************************/ + case 0x4a: // Accumulator + a = lsr(a); + setArithmeticFlags(a); + break; + case 0x46: // Zero Page + case 0x4e: // Absolute + case 0x56: // Zero Page,X + case 0x5e: // Absolute,X + tmp = lsr(bus.read(effectiveAddress)); + bus.write(effectiveAddress, tmp); + setArithmeticFlags(tmp); + break; + + + /** ADC - Add with Carry ************************************************/ + case 0x69: // #Immediate + if (decimalModeFlag) { + a = adcDecimal(a, args[0]); + } else { + a = adc(a, args[0]); + } + break; + case 0x61: // (Zero Page,X) + case 0x65: // Zero Page + case 0x6d: // Absolute + case 0x71: // (Zero Page),Y + case 0x75: // Zero Page,X + case 0x79: // Absolute,Y + case 0x7d: // Absolute,X + if (decimalModeFlag) { + a = adcDecimal(a, bus.read(effectiveAddress)); + } else { + a = adc(a, bus.read(effectiveAddress)); + } + break; + + + /** ROR - Rotate Right **************************************************/ + case 0x6a: // Accumulator + a = ror(a); + setArithmeticFlags(a); + break; + case 0x66: // Zero Page + case 0x6e: // Absolute + case 0x76: // Zero Page,X + case 0x7e: // Absolute,X + tmp = ror(bus.read(effectiveAddress)); + bus.write(effectiveAddress, tmp); + setArithmeticFlags(tmp); + break; + + + /** STA - Store Accumulator *********************************************/ + case 0x81: // (Zero Page,X) + case 0x85: // Zero Page + case 0x8d: // Absolute + case 0x91: // (Zero Page),Y + case 0x95: // Zero Page,X + case 0x99: // Absolute,Y + case 0x9d: // Absolute,X + bus.write(effectiveAddress, a); + setArithmeticFlags(a); + break; + + + /** STY - Store Y Register **********************************************/ + case 0x84: // Zero Page + case 0x8c: // Absolute + case 0x94: // Zero Page,X + bus.write(effectiveAddress, y); + setArithmeticFlags(y); + break; + + + /** STX - Store X Register **********************************************/ + case 0x86: // Zero Page + case 0x8e: // Absolute + case 0x96: // Zero Page,Y + bus.write(effectiveAddress, x); + setArithmeticFlags(x); + break; + + + /** LDY - Load Y Register ***********************************************/ + case 0xa0: // #Immediate + y = args[0]; + setArithmeticFlags(y); + break; + case 0xa4: // Zero Page + case 0xac: // Absolute + case 0xb4: // Zero Page,X + case 0xbc: // Absolute,X + y = bus.read(effectiveAddress); + setArithmeticFlags(y); + break; + + + /** LDX - Load X Register ***********************************************/ + case 0xa2: // #Immediate + x = args[0]; + setArithmeticFlags(x); + break; + case 0xa6: // Zero Page + case 0xae: // Absolute + case 0xb6: // Zero Page,Y + case 0xbe: // Absolute,Y + x = bus.read(effectiveAddress); + setArithmeticFlags(x); + break; + + + /** LDA - Load Accumulator **********************************************/ + case 0xa9: // #Immediate + a = args[0]; + setArithmeticFlags(a); + break; + case 0xa1: // (Zero Page,X) + case 0xa5: // Zero Page + case 0xad: // Absolute + case 0xb1: // (Zero Page),Y + case 0xb5: // Zero Page,X + case 0xb9: // Absolute,Y + case 0xbd: // Absolute,X + a = bus.read(effectiveAddress); + setArithmeticFlags(a); + break; + + + /** CPY - Compare Y Register ********************************************/ + case 0xc0: // #Immediate + cmp(y, args[0]); + break; + case 0xc4: // Zero Page + case 0xcc: // Absolute + cmp(y, bus.read(effectiveAddress)); + break; + + + /** CMP - Compare Accumulator *******************************************/ + case 0xc9: // #Immediate + cmp(a, args[0]); + break; + case 0xc1: // (Zero Page,X) + case 0xc5: // Zero Page + case 0xcd: // Absolute + case 0xd1: // (Zero Page),Y + case 0xd5: // Zero Page,X + case 0xd9: // Absolute,Y + case 0xdd: // Absolute,X + cmp(a, bus.read(effectiveAddress)); + break; + + + /** DEC - Decrement Memory **********************************************/ + case 0xc6: // Zero Page + case 0xce: // Absolute + case 0xd6: // Zero Page,X + case 0xde: // Absolute,X + tmp = (bus.read(effectiveAddress) - 1) & 0xff; + bus.write(effectiveAddress, tmp); + setArithmeticFlags(tmp); + break; + + + /** CPX - Compare X Register ********************************************/ + case 0xe0: // #Immediate + cmp(x, args[0]); + break; + case 0xe4: // Zero Page + case 0xec: // Absolute + cmp(x, bus.read(effectiveAddress)); + break; + + + /** SBC - Subtract with Carry (Borrow) **********************************/ + case 0xe9: // #Immediate + if (decimalModeFlag) { + a = sbcDecimal(a, args[0]); + } else { + a = sbc(a, args[0]); + } + break; + case 0xe1: // (Zero Page,X) + case 0xe5: // Zero Page + case 0xed: // Absolute + case 0xf1: // (Zero Page),Y + case 0xf5: // Zero Page,X + case 0xf9: // Absolute,Y + case 0xfd: // Absolute,X + if (decimalModeFlag) { + a = sbcDecimal(a, bus.read(effectiveAddress)); + } else { + a = sbc(a, bus.read(effectiveAddress)); + } + break; + + + /** INC - Increment Memory **********************************************/ + case 0xe6: // Zero Page + case 0xee: // Absolute + case 0xf6: // Zero Page,X + case 0xfe: // Absolute,X + tmp = (bus.read(effectiveAddress) + 1) & 0xff; + bus.write(effectiveAddress, tmp); + setArithmeticFlags(tmp); + break; + + /** Unimplemented Instructions ****************************************/ + default: + setOpTrap(); + break; } - break; - } - break; - case 1: - switch(irAddressMode) { - case 0: // (Zero Page,X) - tmp = args[0] + getXRegister(); - effectiveAddress = address(bus.read(tmp), bus.read(tmp + 1)); - break; - case 1: // Zero Page - effectiveAddress = args[0]; - break; - case 2: // #Immediate - effectiveAddress = -1; - break; - case 3: // Absolute - effectiveAddress = address(args[0], args[1]); - break; - case 4: // (Zero Page),Y - tmp = address(bus.read(args[0]), - bus.read((args[0]+1)&0xff)); - effectiveAddress = (tmp + getYRegister())&0xffff; - break; - case 5: // Zero Page,X - effectiveAddress = zpxAddress(args[0]); - break; - case 6: // Absolute, Y - effectiveAddress = yAddress(args[0], args[1]); - break; - case 7: // Absolute, X - effectiveAddress = xAddress(args[0], args[1]); - break; - } - break; } - // Execute - switch(ir) { - - /** Single Byte Instructions; Implied and Relative **/ - case 0x00: // BRK - Force Interrupt - Implied - if (!getIrqDisableFlag()) { - // Set the break flag before pushing. - setBreakFlag(); - // Push program counter + 2 onto the stack - stackPush((pc+2 >> 8) & 0xff); // PC high byte - stackPush(pc+2 & 0xff); // PC low byte - stackPush(getProcessorStatus()); - // Set the Interrupt Disabled flag. RTI will clear it. - setIrqDisableFlag(); - // Load interrupt vector address into PC - pc = address(bus.read(IRQ_VECTOR_L), bus.read(IRQ_VECTOR_H)); - } - break; - case 0x08: // PHP - Push Processor Status - Implied - stackPush(getProcessorStatus()); - break; - case 0x10: // BPL - Branch if Positive - Relative - if (!getNegativeFlag()) { - pc = relAddress(args[0]); - } - break; - case 0x18: // CLC - Clear Carry Flag - Implied - clearCarryFlag(); - break; - case 0x20: // JSR - Jump to Subroutine - Implied - stackPush((pc-1 >> 8) & 0xff); // PC high byte - stackPush(pc-1 & 0xff); // PC low byte - pc = address(args[0], args[1]); - break; - case 0x28: // PLP - Pull Processor Status - Implied - setProcessorStatus(stackPop()); - break; - case 0x30: // BMI - Branch if Minus - Relative - if (getNegativeFlag()) { - pc = relAddress(args[0]); - } - break; - case 0x38: // SEC - Set Carry Flag - Implied - setCarryFlag(); - break; - case 0x40: // RTI - Return from Interrupt - Implied - setProcessorStatus(stackPop()); - lo = stackPop(); - hi = stackPop(); - setProgramCounter(address(lo, hi)); - break; - case 0x48: // PHA - Push Accumulator - Implied - stackPush(a); - break; - case 0x50: // BVC - Branch if Overflow Clear - Relative - if (!getOverflowFlag()) { - pc = relAddress(args[0]); - } - break; - case 0x58: // CLI - Clear Interrupt Disable - Implied - clearIrqDisableFlag(); - break; - case 0x60: // RTS - Return from Subroutine - Implied - lo = stackPop(); - hi = stackPop(); - setProgramCounter((address(lo, hi) + 1) & 0xffff); - break; - case 0x68: // PLA - Pull Accumulator - Implied - a = stackPop(); - setArithmeticFlags(a); - break; - case 0x70: // BVS - Branch if Overflow Set - Relative - if (getOverflowFlag()) { - pc = relAddress(args[0]); - } - break; - case 0x78: // SEI - Set Interrupt Disable - Implied - setIrqDisableFlag(); - break; - case 0x88: // DEY - Decrement Y Register - Implied - y = --y & 0xff; - setArithmeticFlags(y); - break; - case 0x8a: // TXA - Transfer X to Accumulator - Implied - a = x; - setArithmeticFlags(a); - break; - case 0x90: // BCC - Branch if Carry Clear - Relative - if (!getCarryFlag()) { - pc = relAddress(args[0]); - } - break; - case 0x98: // TYA - Transfer Y to Accumulator - Implied - a = y; - setArithmeticFlags(a); - break; - case 0x9a: // TXS - Transfer X to Stack Pointer - Implied - setStackPointer(x); - break; - case 0xa8: // TAY - Transfer Accumulator to Y - Implied - y = a; - setArithmeticFlags(y); - break; - case 0xaa: // TAX - Transfer Accumulator to X - Implied - x = a; - setArithmeticFlags(x); - break; - case 0xb0: // BCS - Branch if Carry Set - Relative - if (getCarryFlag()) { - pc = relAddress(args[0]); - } - break; - case 0xb8: // CLV - Clear Overflow Flag - Implied - clearOverflowFlag(); - break; - case 0xba: // TSX - Transfer Stack Pointer to X - Implied - x = getStackPointer(); - setArithmeticFlags(x); - break; - case 0xc8: // INY - Increment Y Register - Implied - y = ++y & 0xff; - setArithmeticFlags(y); - break; - case 0xca: // DEX - Decrement X Register - Implied - x = --x & 0xff; - setArithmeticFlags(x); - break; - case 0xd0: // BNE - Branch if Not Equal to Zero - Relative - if (!getZeroFlag()) { - pc = relAddress(args[0]); - } - break; - case 0xd8: // CLD - Clear Decimal Mode - Implied - clearDecimalModeFlag(); - break; - case 0xe8: // INX - Increment X Register - Implied - x = ++x & 0xff; - setArithmeticFlags(x); - break; - case 0xea: // NOP - // Do nothing. - break; - case 0xf0: // BEQ - Branch if Equal to Zero - Relative - if (getZeroFlag()) { - pc = relAddress(args[0]); - } - break; - case 0xf8: // SED - Set Decimal Flag - Implied - setDecimalModeFlag(); - break; - - /** JMP *****************************************************************/ - case 0x4c: // JMP - Absolute - pc = address(args[0], args[1]); - break; - case 0x6c: // JMP - Indirect - lo = address(args[0], args[1]); // Address of low byte - hi = lo+1; // Address of high byte - pc = address(bus.read(lo), bus.read(hi)); - /* TODO: For accuracy, allow a flag to enable broken behavior - * of early 6502s: - * - * "An original 6502 has does not correctly fetch the target - * address if the indirect vector falls on a page boundary - * (e.g. $xxFF where xx is and value from $00 to $FF). In this - * case fetches the LSB from $xxFF as expected but takes the MSB - * from $xx00. This is fixed in some later chips like the 65SC02 - * so for compatibility always ensure the indirect vector is not - * at the end of the page." - * (http://www.obelisk.demon.co.uk/6502/reference.html#JMP) - */ - break; - - - /** ORA - Logical Inclusive Or ******************************************/ - case 0x09: // #Immediate - a |= args[0]; - setArithmeticFlags(a); - break; - case 0x01: // (Zero Page,X) - case 0x05: // Zero Page - case 0x0d: // Absolute - case 0x11: // (Zero Page),Y - case 0x15: // Zero Page,X - case 0x19: // Absolute,Y - case 0x1d: // Absolute,X - a |= bus.read(effectiveAddress); - setArithmeticFlags(a); - break; - - - /** ASL - Arithmetic Shift Left *****************************************/ - case 0x0a: // Accumulator - a = asl(a); - setArithmeticFlags(a); - break; - case 0x06: // Zero Page - case 0x0e: // Absolute - case 0x16: // Zero Page,X - case 0x1e: // Absolute,X - tmp = asl(bus.read(effectiveAddress)); - bus.write(effectiveAddress, tmp); - setArithmeticFlags(tmp); - break; - - - /** BIT - Bit Test ******************************************************/ - case 0x24: // Zero Page - case 0x2c: // Absolute - tmp = a & bus.read(effectiveAddress); - setZeroFlag(tmp == 0); - setNegativeFlag((tmp & 0x80) != 0); - setOverflowFlag((tmp & 0x40) != 0); - break; - - - /** AND - Logical AND ***************************************************/ - case 0x29: // #Immediate - a &= args[0]; - setArithmeticFlags(a); - break; - case 0x21: // (Zero Page,X) - case 0x25: // Zero Page - case 0x2d: // Absolute - case 0x31: // (Zero Page),Y - case 0x35: // Zero Page,X - case 0x39: // Absolute,Y - case 0x3d: // Absolute,X - a &= bus.read(effectiveAddress); - setArithmeticFlags(a); - break; - - - /** ROL - Rotate Left ***************************************************/ - case 0x2a: // Accumulator - a = rol(a); - setArithmeticFlags(a); - break; - case 0x26: // Zero Page - case 0x2e: // Absolute - case 0x36: // Zero Page,X - case 0x3e: // Absolute,X - tmp = rol(bus.read(effectiveAddress)); - bus.write(effectiveAddress, tmp); - setArithmeticFlags(tmp); - break; - - - /** EOR - Exclusive OR **************************************************/ - case 0x49: // #Immediate - a ^= args[0]; - setArithmeticFlags(a); - break; - case 0x41: // (Zero Page,X) - case 0x45: // Zero Page - case 0x4d: // Absolute - case 0x51: // (Zero Page,Y) - case 0x55: // Zero Page,X - case 0x59: // Absolute,Y - case 0x5d: // Absolute,X - a ^= bus.read(effectiveAddress); - setArithmeticFlags(a); - break; - - - /** LSR - Logical Shift Right *******************************************/ - case 0x4a: // Accumulator - a = lsr(a); - setArithmeticFlags(a); - break; - case 0x46: // Zero Page - case 0x4e: // Absolute - case 0x56: // Zero Page,X - case 0x5e: // Absolute,X - tmp = lsr(bus.read(effectiveAddress)); - bus.write(effectiveAddress, tmp); - setArithmeticFlags(tmp); - break; - - - /** ADC - Add with Carry ************************************************/ - case 0x69: // #Immediate - if (decimalModeFlag) { - a = adcDecimal(a, args[0]); - } else { - a = adc(a, args[0]); - } - break; - case 0x61: // (Zero Page,X) - case 0x65: // Zero Page - case 0x6d: // Absolute - case 0x71: // (Zero Page),Y - case 0x75: // Zero Page,X - case 0x79: // Absolute,Y - case 0x7d: // Absolute,X - if (decimalModeFlag) { - a = adcDecimal(a, bus.read(effectiveAddress)); - } else { - a = adc(a, bus.read(effectiveAddress)); - } - break; - - - /** ROR - Rotate Right **************************************************/ - case 0x6a: // Accumulator - a = ror(a); - setArithmeticFlags(a); - break; - case 0x66: // Zero Page - case 0x6e: // Absolute - case 0x76: // Zero Page,X - case 0x7e: // Absolute,X - tmp = ror(bus.read(effectiveAddress)); - bus.write(effectiveAddress, tmp); - setArithmeticFlags(tmp); - break; - - - /** STA - Store Accumulator *********************************************/ - case 0x81: // (Zero Page,X) - case 0x85: // Zero Page - case 0x8d: // Absolute - case 0x91: // (Zero Page),Y - case 0x95: // Zero Page,X - case 0x99: // Absolute,Y - case 0x9d: // Absolute,X - bus.write(effectiveAddress, a); - setArithmeticFlags(a); - break; - - - /** STY - Store Y Register **********************************************/ - case 0x84: // Zero Page - case 0x8c: // Absolute - case 0x94: // Zero Page,X - bus.write(effectiveAddress, y); - setArithmeticFlags(y); - break; - - - /** STX - Store X Register **********************************************/ - case 0x86: // Zero Page - case 0x8e: // Absolute - case 0x96: // Zero Page,Y - bus.write(effectiveAddress, x); - setArithmeticFlags(x); - break; - - - /** LDY - Load Y Register ***********************************************/ - case 0xa0: // #Immediate - y = args[0]; - setArithmeticFlags(y); - break; - case 0xa4: // Zero Page - case 0xac: // Absolute - case 0xb4: // Zero Page,X - case 0xbc: // Absolute,X - y = bus.read(effectiveAddress); - setArithmeticFlags(y); - break; - - - /** LDX - Load X Register ***********************************************/ - case 0xa2: // #Immediate - x = args[0]; - setArithmeticFlags(x); - break; - case 0xa6: // Zero Page - case 0xae: // Absolute - case 0xb6: // Zero Page,Y - case 0xbe: // Absolute,Y - x = bus.read(effectiveAddress); - setArithmeticFlags(x); - break; - - - /** LDA - Load Accumulator **********************************************/ - case 0xa9: // #Immediate - a = args[0]; - setArithmeticFlags(a); - break; - case 0xa1: // (Zero Page,X) - case 0xa5: // Zero Page - case 0xad: // Absolute - case 0xb1: // (Zero Page),Y - case 0xb5: // Zero Page,X - case 0xb9: // Absolute,Y - case 0xbd: // Absolute,X - a = bus.read(effectiveAddress); - setArithmeticFlags(a); - break; - - - /** CPY - Compare Y Register ********************************************/ - case 0xc0: // #Immediate - cmp(y, args[0]); - break; - case 0xc4: // Zero Page - case 0xcc: // Absolute - cmp(y, bus.read(effectiveAddress)); - break; - - - /** CMP - Compare Accumulator *******************************************/ - case 0xc9: // #Immediate - cmp(a, args[0]); - break; - case 0xc1: // (Zero Page,X) - case 0xc5: // Zero Page - case 0xcd: // Absolute - case 0xd1: // (Zero Page),Y - case 0xd5: // Zero Page,X - case 0xd9: // Absolute,Y - case 0xdd: // Absolute,X - cmp(a, bus.read(effectiveAddress)); - break; - - - /** DEC - Decrement Memory **********************************************/ - case 0xc6: // Zero Page - case 0xce: // Absolute - case 0xd6: // Zero Page,X - case 0xde: // Absolute,X - tmp = (bus.read(effectiveAddress) - 1) & 0xff; - bus.write(effectiveAddress, tmp); - setArithmeticFlags(tmp); - break; - - - /** CPX - Compare X Register ********************************************/ - case 0xe0: // #Immediate - cmp(x, args[0]); - break; - case 0xe4: // Zero Page - case 0xec: // Absolute - cmp(x, bus.read(effectiveAddress)); - break; - - - /** SBC - Subtract with Carry (Borrow) **********************************/ - case 0xe9: // #Immediate - if (decimalModeFlag) { - a = sbcDecimal(a, args[0]); - } else { - a = sbc(a, args[0]); - } - break; - case 0xe1: // (Zero Page,X) - case 0xe5: // Zero Page - case 0xed: // Absolute - case 0xf1: // (Zero Page),Y - case 0xf5: // Zero Page,X - case 0xf9: // Absolute,Y - case 0xfd: // Absolute,X - if (decimalModeFlag) { - a = sbcDecimal(a, bus.read(effectiveAddress)); - } else { - a = sbc(a, bus.read(effectiveAddress)); - } - break; - - - /** INC - Increment Memory **********************************************/ - case 0xe6: // Zero Page - case 0xee: // Absolute - case 0xf6: // Zero Page,X - case 0xfe: // Absolute,X - tmp = (bus.read(effectiveAddress) + 1) & 0xff; - bus.write(effectiveAddress, tmp); - setArithmeticFlags(tmp); - break; - - /** Unimplemented Instructions ****************************************/ - default: - setOpTrap(); - break; - } - } - - /** - * Add with Carry, used by all addressing mode implementations of ADC. - * As a side effect, this will set the overflow and carry flags as - * needed. - * - * @param acc The current value of the accumulator - * @param operand The operand - * @return - */ - public int adc(int acc, int operand) { - int result = (operand & 0xff) + (acc & 0xff) + getCarryBit(); - int carry6 = (operand & 0x7f) + (acc & 0x7f) + getCarryBit(); - setCarryFlag((result & 0x100) != 0); - setOverflowFlag(carryFlag ^ ((carry6 & 0x80) != 0)); - result &= 0xff; - setArithmeticFlags(result); - return result; - } - - /** - * Add with Carry (BCD). - */ - - public int adcDecimal(int acc, int operand) { - int l, h, result; - l = (acc & 0x0f) + (operand & 0x0f) + getCarryBit(); - if ((l & 0xff) > 9) l += 6; - h = (acc >> 4) + (operand >> 4) + (l > 15 ? 1 : 0); - if ((h & 0xff) > 9) h += 6; - result = (l & 0x0f) | (h << 4); - result &= 0xff; - setCarryFlag(h > 15); - setZeroFlag(result == 0); - setNegativeFlag(false); // BCD is never negative - setOverflowFlag(false); // BCD never sets overflow flag - return result; - } - - /** - * Common code for Subtract with Carry. Just calls ADC of the - * one's complement of the operand. This lets the N, V, C, and Z - * flags work out nicely without any additional logic. - * - * @param acc - * @param operand - * @return - */ - public int sbc(int acc, int operand) { - int result; - result = adc(acc, ~operand); - setArithmeticFlags(result); - return result; - } - - /** - * Subtract with Carry, BCD mode. - * - * @param acc - * @param operand - * @return - */ - public int sbcDecimal(int acc, int operand) { - int l, h, result; - l = (acc & 0x0f) - (operand & 0x0f) - (carryFlag ? 0 : 1); - if ((l & 0x10) != 0) l -= 6; - h = (acc >> 4) - (operand >> 4) - ((l & 0x10) != 0 ? 1 : 0); - if ((h & 0x10) != 0) h -= 6; - result = (l & 0x0f) | (h << 4); - setCarryFlag((h & 0xff) < 15); - setZeroFlag(result == 0); - setNegativeFlag(false); // BCD is never negative - setOverflowFlag(false); // BCD never sets overflow flag - return (result & 0xff); - } - - /** - * Compare two values, and set carry, zero, and negative flags - * appropriately. - * - * @param reg - * @param operand - */ - public void cmp(int reg, int operand) { - setCarryFlag(reg >= operand); - setZeroFlag(reg == operand); - setNegativeFlag((reg - operand) > 0); - } - - /** - * Set the Negative and Zero flags based on the current value of the - * register operand. - * - * @param reg The register. - */ - public void setArithmeticFlags(int reg) { - zeroFlag = (reg == 0); - negativeFlag = (reg & 0x80) != 0; - } - - /** - * Shifts the given value left by one bit, and sets the carry - * flag to the high bit of the initial value. - * - * @param m The value to shift left. - * @return the left shifted value (m * 2). - */ - private int asl(int m) { - setCarryFlag((m & 0x80) != 0); - return (m << 1) & 0xff; - } - - /** - * Shifts the given value right by one bit, filling with zeros, - * and sets the carry flag to the low bit of the initial value. - */ - private int lsr(int m) { - setCarryFlag((m & 0x01) != 0); - return (m >>> 1) & 0xff; - } - - /** - * Rotates the given value left by one bit, setting bit 0 to the value - * of the carry flag, and setting the carry flag to the original value - * of bit 7. - */ - private int rol(int m) { - int result = ((m << 1) | getCarryBit()) & 0xff; - setCarryFlag((m & 0x80) != 0); - return result; - } - - /** - * Rotates the given value right by one bit, setting bit 7 to the value - * of the carry flag, and setting the carry flag to the original value - * of bit 1. - */ - private int ror(int m) { - int result = ((m >>> 1) | (getCarryBit() << 7)) & 0xff; - setCarryFlag((m & 0x01) != 0); - return result; - } - - /** - * @return the negative flag - */ - public boolean getNegativeFlag() { - return negativeFlag; - } - - /** - * @return 1 if the negative flag is set, 0 if it is clear. - */ - public int getNegativeBit() { - return (negativeFlag ? 1 : 0); - } - - /** - * @param register the register value to test for negativity - */ - public void setNegativeFlag(int register) { - negativeFlag = (register < 0); - } - - /** - * @param negativeFlag the negative flag to set - */ - public void setNegativeFlag(boolean negativeFlag) { - this.negativeFlag = negativeFlag; - } - - public void setNegativeFlag() { - this.negativeFlag = true; - } - - public void clearNegativeFlag() { - this.negativeFlag = false; - } - - /** - * @return the carry flag - */ - public boolean getCarryFlag() { - return carryFlag; - } - - /** - * @return 1 if the carry flag is set, 0 if it is clear. - */ - public int getCarryBit() { - return (carryFlag ? 1 : 0); - } - - /** - * @param carryFlag the carry flag to set - */ - public void setCarryFlag(boolean carryFlag) { - this.carryFlag = carryFlag; - } - - /** - * Sets the Carry Flag - */ - public void setCarryFlag() { - this.carryFlag = true; - } - - /** - * Clears the Carry Flag - */ - public void clearCarryFlag() { - this.carryFlag = false; - } - - /** - * @return the zero flag - */ - public boolean getZeroFlag() { - return zeroFlag; - } - - /** - * @return 1 if the zero flag is set, 0 if it is clear. - */ - public int getZeroBit() { - return (zeroFlag ? 1 : 0); - } - - /** - * @param zeroFlag the zero flag to set - */ - public void setZeroFlag(boolean zeroFlag) { - this.zeroFlag = zeroFlag; - } - - /** - * Sets the Zero Flag - */ - public void setZeroFlag() { - this.zeroFlag = true; - } - - /** - * Clears the Zero Flag - */ - public void clearZeroFlag() { - this.zeroFlag = false; - } - - /** - * @return the irq disable flag - */ - public boolean getIrqDisableFlag() { - return irqDisableFlag; - } - - /** - * @return 1 if the interrupt disable flag is set, 0 if it is clear. - */ - public int getIrqDisableBit() { - return (irqDisableFlag ? 1 : 0); - } - - /** - * @param irqDisableFlag the irq disable flag to set - */ - public void setIrqDisableFlag(boolean irqDisableFlag) { - this.irqDisableFlag = irqDisableFlag; - } - - public void setIrqDisableFlag() { - this.irqDisableFlag = true; - } - - public void clearIrqDisableFlag() { - this.irqDisableFlag = false; - } - - - /** - * @return the decimal mode flag - */ - public boolean getDecimalModeFlag() { - return decimalModeFlag; - } - - /** - * @return 1 if the decimal mode flag is set, 0 if it is clear. - */ - public int getDecimalModeBit() { - return (decimalModeFlag ? 1 : 0); - } - - /** - * @param decimalModeFlag the decimal mode flag to set - */ - public void setDecimalModeFlag(boolean decimalModeFlag) { - this.decimalModeFlag = decimalModeFlag; - } - - /** - * Sets the Decimal Mode Flag to true. - */ - public void setDecimalModeFlag() { - this.decimalModeFlag = true; - } - - /** - * Clears the Decimal Mode Flag. - */ - public void clearDecimalModeFlag() { - this.decimalModeFlag = false; - } - - /** - * @return the break flag - */ - public boolean getBreakFlag() { - return breakFlag; - } - - /** - * @return 1 if the break flag is set, 0 if it is clear. - */ - public int getBreakBit() { - return (carryFlag ? 1 : 0); - } - - /** - * @param breakFlag the break flag to set - */ - public void setBreakFlag(boolean breakFlag) { - this.breakFlag = breakFlag; - } - - /** - * Sets the Break Flag - */ - public void setBreakFlag() { - this.breakFlag = true; - } - - /** - * Clears the Break Flag - */ - public void clearBreakFlag() { - this.breakFlag = false; - } - - /** - * @return the overflow flag - */ - public boolean getOverflowFlag() { - return overflowFlag; - } - - /** - * @return 1 if the overflow flag is set, 0 if it is clear. - */ - public int getOverflowBit() { - return (overflowFlag ? 1 : 0); - } - - /** - * @param overflowFlag the overflow flag to set - */ - public void setOverflowFlag(boolean overflowFlag) { - this.overflowFlag = overflowFlag; - } - - /** - * Sets the Overflow Flag - */ - public void setOverflowFlag() { - this.overflowFlag = true; - } - - /** - * Clears the Overflow Flag - */ - public void clearOverflowFlag() { - this.overflowFlag = false; - } - - /** - * Set the illegal instruction trap. - */ - public void setOpTrap() { - this.opTrap = true; - } - - /** - * Clear the illegal instruction trap. - */ - public void clearOpTrap() { - this.opTrap = false; - } - - /** - * Get the status of the illegal instruction trap. - */ - public boolean getOpTrap() { - return this.opTrap; - } - - public int getAccumulator() { - return a; - } - - public void setAccumulator(int val) { - this.a = val; - } - - public int getXRegister() { - return x; - } - - public void setXRegister(int val) { - this.x = val; - } - - public int getYRegister() { - return y; - } - - public void setYRegister(int val) { - this.y = val; - } - - public int getProgramCounter() { - return pc; - } - - public void setProgramCounter(int addr) { - this.pc = addr; - } - - public int getStackPointer() { - return sp; - } - - public void setStackPointer(int offset) { - this.sp = offset; - } - - /** - * @value The value of the Process Status Register bits to be set. - */ - public void setProcessorStatus(int value) { - if ((value&P_CARRY) != 0) - setCarryFlag(); - else - clearCarryFlag(); - - if ((value&P_ZERO) != 0) - setZeroFlag(); - else - clearZeroFlag(); - - if ((value&P_IRQ_DISABLE) != 0) - setIrqDisableFlag(); - else - clearIrqDisableFlag(); - - if ((value&P_DECIMAL) != 0) - setDecimalModeFlag(); - else - clearDecimalModeFlag(); - - if ((value&P_BREAK) != 0) - setBreakFlag(); - else - clearBreakFlag(); - - if ((value&P_OVERFLOW) != 0) - setOverflowFlag(); - else - clearOverflowFlag(); - - if ((value&P_NEGATIVE) != 0) - setNegativeFlag(); - else - clearNegativeFlag(); - } - - /** - * @returns The value of the Process Status Register, as a byte. - */ - public int getProcessorStatus() { - int status = 0x20; - if (getCarryFlag()) { status |= P_CARRY; } - if (getZeroFlag()) { status |= P_ZERO; } - if (getIrqDisableFlag()) { status |= P_IRQ_DISABLE; } - if (getDecimalModeFlag()) { status |= P_DECIMAL; } - if (getBreakFlag()) { status |= P_BREAK; } - if (getOverflowFlag()) { status |= P_OVERFLOW; } - if (getNegativeFlag()) { status |= P_NEGATIVE; } - return status; - } - - /** - * @return A string representing the current status register state. - */ - public String statusRegisterString() { - StringBuffer sb = new StringBuffer("["); - sb.append(getNegativeFlag() ? 'N' : '.'); // Bit 7 - sb.append(getOverflowFlag() ? 'V' : '.'); // Bit 6 - sb.append("-"); // Bit 5 (always 1) - sb.append(getBreakFlag() ? 'B' : '.'); // Bit 4 - sb.append(getDecimalModeFlag() ? 'D' : '.'); // Bit 3 - sb.append(getIrqDisableFlag() ? 'I' : '.'); // Bit 2 - sb.append(getZeroFlag() ? 'Z' : '.'); // Bit 1 - sb.append(getCarryFlag() ? 'C' : '.'); // Bit 0 - sb.append("]"); - return sb.toString(); - } - - /** - * Returns a string representing the CPU state. - */ - public String toString() { - String opcode = opcode(ir, args[0], args[1]); - StringBuffer sb = new StringBuffer(String.format("$%04X", addr) + - " "); - sb.append(String.format("%-14s", opcode)); - sb.append("A=" + String.format("$%02X", a) + " "); - sb.append("X=" + String.format("$%02X", x) + " "); - sb.append("Y=" + String.format("$%02X", y) + " "); - sb.append("PC=" + String.format("$%04X", pc)+ " "); - sb.append("P=" + statusRegisterString()); - return sb.toString(); - } - - /** - * Push an item onto the stack, and decrement the stack counter. - * Silently fails to push onto the stack if SP is - */ - void stackPush(int data) throws MemoryAccessException { - bus.write(0x100+sp, data); - - if (sp == 0) - sp = 0xff; - else - --sp; - } - - - /** - * Pre-increment the stack pointer, and return the top of the stack. - */ - int stackPop() throws MemoryAccessException { - if (sp == 0xff) - sp = 0x00; - else - ++sp; - - int data = bus.read(0x100+sp); - - return data; - } - - /** - * Peek at the value currently at the top of the stack - */ - int stackPeek() throws MemoryAccessException { - return bus.read(0x100+sp+1); - } - - /* - * Increment the PC, rolling over if necessary. - */ - void incrementPC() { - if (pc == 0xffff) { - pc = 0; - } else { - ++pc; - } - } - - /** - * Given two bytes, return an address. - */ - int address(int lowByte, int hiByte) { - return ((hiByte<<8)|lowByte) & 0xffff; - } - - /** - * Given a hi byte and a low byte, return the Absolute,X - * offset address. - */ - int xAddress(int lowByte, int hiByte) { - return (address(lowByte, hiByte)+getXRegister()) & 0xffff; - } - - /** - * Given a hi byte and a low byte, return the Absolute,Y - * offset address. - */ - int yAddress(int lowByte, int hiByte) { - return (address(lowByte, hiByte)+getYRegister()) & 0xffff; - } - - /** - * Given a single byte, compute the Zero Page,X offset address. - */ - int zpxAddress(int zp) { - return (zp+getXRegister())&0xff; - } - - /** - * Given a single byte, compute the offset address. - */ - int relAddress(int offset) { - // Cast the offset to a signed byte to handle negative offsets - return (pc + (byte)offset) & 0xffff; - } - - /** - * Given a single byte, compute the Zero Page,Y offset address. - */ - int zpyAddress(int zp) { - return (zp+getYRegister())&0xff; - } - - void setResetVector(int address) throws MemoryAccessException { - bus.write(RST_VECTOR_H, (address&0xff00)>>>8); - bus.write(RST_VECTOR_L, address&0x00ff); - } - - /** - * Given an opcode and its operands, return a formatted name. - * - * @param opcode - * @param operands - * @return - */ - String opcode(int opcode, int op1, int op2) { - String opcodeName = Cpu.opcodeNames[opcode]; - if (opcodeName == null) { return "???"; } - - StringBuffer sb = new StringBuffer(opcodeName); - - switch (Cpu.instructionModes[opcode]) { - case ABS: - sb.append(String.format(" $%04X", address(op1, op2))); - break; - case ABX: - sb.append(String.format(" $%04X,X", address(op1, op2))); - break; - case ABY: - sb.append(String.format(" $%04X,Y", address(op1, op2))); - break; - case IMM: - sb.append(String.format(" #$%02X", op1)); - break; - case IND: - sb.append(String.format(" ($%04X)", address(op1, op2))); - break; - case XIN: - sb.append(String.format(" ($%02X),X", op1)); - break; - case INY: - sb.append(String.format(" ($%02X,Y)", op1)); - break; - case REL: - case ZPG: - sb.append(String.format(" $%02X", op1)); - break; - case ZPX: - sb.append(String.format(" $%02X,X", op1)); - break; - case ZPY: - sb.append(String.format(" $%02X,Y", op1)); - break; + /** + * Add with Carry, used by all addressing mode implementations of ADC. + * As a side effect, this will set the overflow and carry flags as + * needed. + * + * @param acc The current value of the accumulator + * @param operand The operand + * @return + */ + public int adc(int acc, int operand) { + int result = (operand & 0xff) + (acc & 0xff) + getCarryBit(); + int carry6 = (operand & 0x7f) + (acc & 0x7f) + getCarryBit(); + setCarryFlag((result & 0x100) != 0); + setOverflowFlag(carryFlag ^ ((carry6 & 0x80) != 0)); + result &= 0xff; + setArithmeticFlags(result); + return result; } - return sb.toString(); - } + /** + * Add with Carry (BCD). + */ + + public int adcDecimal(int acc, int operand) { + int l, h, result; + l = (acc & 0x0f) + (operand & 0x0f) + getCarryBit(); + if ((l & 0xff) > 9) l += 6; + h = (acc >> 4) + (operand >> 4) + (l > 15 ? 1 : 0); + if ((h & 0xff) > 9) h += 6; + result = (l & 0x0f) | (h << 4); + result &= 0xff; + setCarryFlag(h > 15); + setZeroFlag(result == 0); + setNegativeFlag(false); // BCD is never negative + setOverflowFlag(false); // BCD never sets overflow flag + return result; + } + + /** + * Common code for Subtract with Carry. Just calls ADC of the + * one's complement of the operand. This lets the N, V, C, and Z + * flags work out nicely without any additional logic. + * + * @param acc + * @param operand + * @return + */ + public int sbc(int acc, int operand) { + int result; + result = adc(acc, ~operand); + setArithmeticFlags(result); + return result; + } + + /** + * Subtract with Carry, BCD mode. + * + * @param acc + * @param operand + * @return + */ + public int sbcDecimal(int acc, int operand) { + int l, h, result; + l = (acc & 0x0f) - (operand & 0x0f) - (carryFlag ? 0 : 1); + if ((l & 0x10) != 0) l -= 6; + h = (acc >> 4) - (operand >> 4) - ((l & 0x10) != 0 ? 1 : 0); + if ((h & 0x10) != 0) h -= 6; + result = (l & 0x0f) | (h << 4); + setCarryFlag((h & 0xff) < 15); + setZeroFlag(result == 0); + setNegativeFlag(false); // BCD is never negative + setOverflowFlag(false); // BCD never sets overflow flag + return (result & 0xff); + } + + /** + * Compare two values, and set carry, zero, and negative flags + * appropriately. + * + * @param reg + * @param operand + */ + public void cmp(int reg, int operand) { + setCarryFlag(reg >= operand); + setZeroFlag(reg == operand); + setNegativeFlag((reg - operand) > 0); + } + + /** + * Set the Negative and Zero flags based on the current value of the + * register operand. + * + * @param reg The register. + */ + public void setArithmeticFlags(int reg) { + zeroFlag = (reg == 0); + negativeFlag = (reg & 0x80) != 0; + } + + /** + * Shifts the given value left by one bit, and sets the carry + * flag to the high bit of the initial value. + * + * @param m The value to shift left. + * @return the left shifted value (m * 2). + */ + private int asl(int m) { + setCarryFlag((m & 0x80) != 0); + return (m << 1) & 0xff; + } + + /** + * Shifts the given value right by one bit, filling with zeros, + * and sets the carry flag to the low bit of the initial value. + */ + private int lsr(int m) { + setCarryFlag((m & 0x01) != 0); + return (m >>> 1) & 0xff; + } + + /** + * Rotates the given value left by one bit, setting bit 0 to the value + * of the carry flag, and setting the carry flag to the original value + * of bit 7. + */ + private int rol(int m) { + int result = ((m << 1) | getCarryBit()) & 0xff; + setCarryFlag((m & 0x80) != 0); + return result; + } + + /** + * Rotates the given value right by one bit, setting bit 7 to the value + * of the carry flag, and setting the carry flag to the original value + * of bit 1. + */ + private int ror(int m) { + int result = ((m >>> 1) | (getCarryBit() << 7)) & 0xff; + setCarryFlag((m & 0x01) != 0); + return result; + } + + /** + * @return the negative flag + */ + public boolean getNegativeFlag() { + return negativeFlag; + } + + /** + * @return 1 if the negative flag is set, 0 if it is clear. + */ + public int getNegativeBit() { + return (negativeFlag ? 1 : 0); + } + + /** + * @param register the register value to test for negativity + */ + public void setNegativeFlag(int register) { + negativeFlag = (register < 0); + } + + /** + * @param negativeFlag the negative flag to set + */ + public void setNegativeFlag(boolean negativeFlag) { + this.negativeFlag = negativeFlag; + } + + public void setNegativeFlag() { + this.negativeFlag = true; + } + + public void clearNegativeFlag() { + this.negativeFlag = false; + } + + /** + * @return the carry flag + */ + public boolean getCarryFlag() { + return carryFlag; + } + + /** + * @return 1 if the carry flag is set, 0 if it is clear. + */ + public int getCarryBit() { + return (carryFlag ? 1 : 0); + } + + /** + * @param carryFlag the carry flag to set + */ + public void setCarryFlag(boolean carryFlag) { + this.carryFlag = carryFlag; + } + + /** + * Sets the Carry Flag + */ + public void setCarryFlag() { + this.carryFlag = true; + } + + /** + * Clears the Carry Flag + */ + public void clearCarryFlag() { + this.carryFlag = false; + } + + /** + * @return the zero flag + */ + public boolean getZeroFlag() { + return zeroFlag; + } + + /** + * @return 1 if the zero flag is set, 0 if it is clear. + */ + public int getZeroBit() { + return (zeroFlag ? 1 : 0); + } + + /** + * @param zeroFlag the zero flag to set + */ + public void setZeroFlag(boolean zeroFlag) { + this.zeroFlag = zeroFlag; + } + + /** + * Sets the Zero Flag + */ + public void setZeroFlag() { + this.zeroFlag = true; + } + + /** + * Clears the Zero Flag + */ + public void clearZeroFlag() { + this.zeroFlag = false; + } + + /** + * @return the irq disable flag + */ + public boolean getIrqDisableFlag() { + return irqDisableFlag; + } + + /** + * @return 1 if the interrupt disable flag is set, 0 if it is clear. + */ + public int getIrqDisableBit() { + return (irqDisableFlag ? 1 : 0); + } + + /** + * @param irqDisableFlag the irq disable flag to set + */ + public void setIrqDisableFlag(boolean irqDisableFlag) { + this.irqDisableFlag = irqDisableFlag; + } + + public void setIrqDisableFlag() { + this.irqDisableFlag = true; + } + + public void clearIrqDisableFlag() { + this.irqDisableFlag = false; + } + + + /** + * @return the decimal mode flag + */ + public boolean getDecimalModeFlag() { + return decimalModeFlag; + } + + /** + * @return 1 if the decimal mode flag is set, 0 if it is clear. + */ + public int getDecimalModeBit() { + return (decimalModeFlag ? 1 : 0); + } + + /** + * @param decimalModeFlag the decimal mode flag to set + */ + public void setDecimalModeFlag(boolean decimalModeFlag) { + this.decimalModeFlag = decimalModeFlag; + } + + /** + * Sets the Decimal Mode Flag to true. + */ + public void setDecimalModeFlag() { + this.decimalModeFlag = true; + } + + /** + * Clears the Decimal Mode Flag. + */ + public void clearDecimalModeFlag() { + this.decimalModeFlag = false; + } + + /** + * @return the break flag + */ + public boolean getBreakFlag() { + return breakFlag; + } + + /** + * @return 1 if the break flag is set, 0 if it is clear. + */ + public int getBreakBit() { + return (carryFlag ? 1 : 0); + } + + /** + * @param breakFlag the break flag to set + */ + public void setBreakFlag(boolean breakFlag) { + this.breakFlag = breakFlag; + } + + /** + * Sets the Break Flag + */ + public void setBreakFlag() { + this.breakFlag = true; + } + + /** + * Clears the Break Flag + */ + public void clearBreakFlag() { + this.breakFlag = false; + } + + /** + * @return the overflow flag + */ + public boolean getOverflowFlag() { + return overflowFlag; + } + + /** + * @return 1 if the overflow flag is set, 0 if it is clear. + */ + public int getOverflowBit() { + return (overflowFlag ? 1 : 0); + } + + /** + * @param overflowFlag the overflow flag to set + */ + public void setOverflowFlag(boolean overflowFlag) { + this.overflowFlag = overflowFlag; + } + + /** + * Sets the Overflow Flag + */ + public void setOverflowFlag() { + this.overflowFlag = true; + } + + /** + * Clears the Overflow Flag + */ + public void clearOverflowFlag() { + this.overflowFlag = false; + } + + /** + * Set the illegal instruction trap. + */ + public void setOpTrap() { + this.opTrap = true; + } + + /** + * Clear the illegal instruction trap. + */ + public void clearOpTrap() { + this.opTrap = false; + } + + /** + * Get the status of the illegal instruction trap. + */ + public boolean getOpTrap() { + return this.opTrap; + } + + public int getAccumulator() { + return a; + } + + public void setAccumulator(int val) { + this.a = val; + } + + public int getXRegister() { + return x; + } + + public void setXRegister(int val) { + this.x = val; + } + + public int getYRegister() { + return y; + } + + public void setYRegister(int val) { + this.y = val; + } + + public int getProgramCounter() { + return pc; + } + + public void setProgramCounter(int addr) { + this.pc = addr; + } + + public int getStackPointer() { + return sp; + } + + public void setStackPointer(int offset) { + this.sp = offset; + } + + public int getInstructionRegister() { + return this.ir; + } + + public void setInstructionRegister(int op) { + this.ir = op; + } + + public long getStepCounter() { + return stepCounter; + } + + public void setStepCounter(long stepCount) { + this.stepCounter = stepCount; + } + + /** + * @value The value of the Process Status Register bits to be set. + */ + public void setProcessorStatus(int value) { + if ((value & P_CARRY) != 0) + setCarryFlag(); + else + clearCarryFlag(); + + if ((value & P_ZERO) != 0) + setZeroFlag(); + else + clearZeroFlag(); + + if ((value & P_IRQ_DISABLE) != 0) + setIrqDisableFlag(); + else + clearIrqDisableFlag(); + + if ((value & P_DECIMAL) != 0) + setDecimalModeFlag(); + else + clearDecimalModeFlag(); + + if ((value & P_BREAK) != 0) + setBreakFlag(); + else + clearBreakFlag(); + + if ((value & P_OVERFLOW) != 0) + setOverflowFlag(); + else + clearOverflowFlag(); + + if ((value & P_NEGATIVE) != 0) + setNegativeFlag(); + else + clearNegativeFlag(); + } + + /** + * @returns The value of the Process Status Register, as a byte. + */ + public int getProcessorStatus() { + int status = 0x20; + if (getCarryFlag()) { + status |= P_CARRY; + } + if (getZeroFlag()) { + status |= P_ZERO; + } + if (getIrqDisableFlag()) { + status |= P_IRQ_DISABLE; + } + if (getDecimalModeFlag()) { + status |= P_DECIMAL; + } + if (getBreakFlag()) { + status |= P_BREAK; + } + if (getOverflowFlag()) { + status |= P_OVERFLOW; + } + if (getNegativeFlag()) { + status |= P_NEGATIVE; + } + return status; + } + + /** + * @return A string representing the current status register state. + */ + public String getProcessorStatusString() { + StringBuffer sb = new StringBuffer("["); + sb.append(getNegativeFlag() ? 'N' : '.'); // Bit 7 + sb.append(getOverflowFlag() ? 'V' : '.'); // Bit 6 + sb.append("-"); // Bit 5 (always 1) + sb.append(getBreakFlag() ? 'B' : '.'); // Bit 4 + sb.append(getDecimalModeFlag() ? 'D' : '.'); // Bit 3 + sb.append(getIrqDisableFlag() ? 'I' : '.'); // Bit 2 + sb.append(getZeroFlag() ? 'Z' : '.'); // Bit 1 + sb.append(getCarryFlag() ? 'C' : '.'); // Bit 0 + sb.append("]"); + return sb.toString(); + } + + public String getOpcodeStatus() { + return opcode(ir, args[0], args[1]); + } + + public String getAddressStatus() { + return String.format("$%04X", addr); + } + + public String getARegisterStatus() { + return String.format("$%02X", a); + } + + public String getXRegisterStatus() { + return String.format("$%02X", x); + } + + public String getYRegisterStatus() { + return String.format("$%02X", y); + } + + public String getProgramCounterStatus() { + return String.format("$%04X", pc); + } + + /** + * Returns a string representing the CPU state. + */ + public String toString() { + String opcode = opcode(ir, args[0], args[1]); + StringBuffer sb = new StringBuffer(String.format("$%04X", addr) + + " "); + sb.append(String.format("%-14s", opcode)); + sb.append("A=" + String.format("$%02X", a) + " "); + sb.append("X=" + String.format("$%02X", x) + " "); + sb.append("Y=" + String.format("$%02X", y) + " "); + sb.append("PC=" + String.format("$%04X", pc) + " "); + sb.append("P=" + getProcessorStatusString()); + return sb.toString(); + } + + /** + * Push an item onto the stack, and decrement the stack counter. + * Silently fails to push onto the stack if SP is + */ + void stackPush(int data) throws MemoryAccessException { + bus.write(0x100 + sp, data); + + if (sp == 0) + sp = 0xff; + else + --sp; + } + + + /** + * Pre-increment the stack pointer, and return the top of the stack. + */ + int stackPop() throws MemoryAccessException { + if (sp == 0xff) + sp = 0x00; + else + ++sp; + + return bus.read(0x100 + sp); + } + + /** + * Peek at the value currently at the top of the stack + */ + int stackPeek() throws MemoryAccessException { + return bus.read(0x100 + sp + 1); + } + + /* + * Increment the PC, rolling over if necessary. + */ + void incrementPC() { + if (pc == 0xffff) { + pc = 0; + } else { + ++pc; + } + } + + /** + * Given two bytes, return an address. + */ + int address(int lowByte, int hiByte) { + return ((hiByte << 8) | lowByte) & 0xffff; + } + + /** + * Given a hi byte and a low byte, return the Absolute,X + * offset address. + */ + int xAddress(int lowByte, int hiByte) { + return (address(lowByte, hiByte) + getXRegister()) & 0xffff; + } + + /** + * Given a hi byte and a low byte, return the Absolute,Y + * offset address. + */ + int yAddress(int lowByte, int hiByte) { + return (address(lowByte, hiByte) + getYRegister()) & 0xffff; + } + + /** + * Given a single byte, compute the Zero Page,X offset address. + */ + int zpxAddress(int zp) { + return (zp + getXRegister()) & 0xff; + } + + /** + * Given a single byte, compute the offset address. + */ + int relAddress(int offset) { + // Cast the offset to a signed byte to handle negative offsets + return (pc + (byte) offset) & 0xffff; + } + + /** + * Given a single byte, compute the Zero Page,Y offset address. + */ + int zpyAddress(int zp) { + return (zp + getYRegister()) & 0xff; + } + + void setResetVector(int address) throws MemoryAccessException { + bus.write(RST_VECTOR_H, (address & 0xff00) >>> 8); + bus.write(RST_VECTOR_L, address & 0x00ff); + } + + /** + * Given an opcode and its operands, return a formatted name. + * + * @param opcode The opcode + * @param op1 The first operand + * @param op2 The second operand + * @return + */ + String opcode(int opcode, int op1, int op2) { + String opcodeName = Cpu.opcodeNames[opcode]; + if (opcodeName == null) { + return "???"; + } + + StringBuffer sb = new StringBuffer(opcodeName); + + switch (Cpu.instructionModes[opcode]) { + case ABS: + sb.append(String.format(" $%04X", address(op1, op2))); + break; + case ABX: + sb.append(String.format(" $%04X,X", address(op1, op2))); + break; + case ABY: + sb.append(String.format(" $%04X,Y", address(op1, op2))); + break; + case IMM: + sb.append(String.format(" #$%02X", op1)); + break; + case IND: + sb.append(String.format(" ($%04X)", address(op1, op2))); + break; + case XIN: + sb.append(String.format(" ($%02X),X", op1)); + break; + case INY: + sb.append(String.format(" ($%02X,Y)", op1)); + break; + case REL: + case ZPG: + sb.append(String.format(" $%02X", op1)); + break; + case ZPX: + sb.append(String.format(" $%02X,X", op1)); + break; + case ZPY: + sb.append(String.format(" $%02X,Y", op1)); + break; + } + + return sb.toString(); + } } \ No newline at end of file diff --git a/src/main/java/com/loomcom/symon/MainWindow.java b/src/main/java/com/loomcom/symon/MainWindow.java new file mode 100644 index 0000000..dc72578 --- /dev/null +++ b/src/main/java/com/loomcom/symon/MainWindow.java @@ -0,0 +1,247 @@ +package com.loomcom.symon; + +import com.loomcom.symon.exceptions.MemoryAccessException; +import com.loomcom.symon.exceptions.MemoryRangeException; +import com.loomcom.symon.exceptions.SymonException; +import com.loomcom.symon.ui.Console; +import com.loomcom.symon.ui.StatusPane; +import com.loomcom.symon.ui.UiUpdater; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.*; + +public class MainWindow extends JFrame implements ActionListener { + + // Hard coded for now. + public static final int PROGRAM_START_ADDRESS = 0x0300; + + private final static Logger logger = Logger.getLogger(MainWindow.class.getName()); + + private final Simulator simulator; + private Thread simulatorThread; + private final Console console; + private final StatusPane statusPane; + private final JButton loadButton; + private final JButton runButton; + private final JButton stepButton; + private final JButton resetButton; + private final JButton quitButton; + private final JFileChooser fileChooser; + + private final UiUpdater uiUpdater; + + public MainWindow(final Simulator simulator) { + + this.setLayout(new BorderLayout()); + + this.simulator = simulator; + + // UI components used for I/O. + this.console = new Console(); + this.statusPane = new StatusPane(simulator.getCpu()); + + JPanel buttonContainer = new JPanel(); + JPanel controlsContainer = new JPanel(); + + // File Chooser + fileChooser = new JFileChooser(); + + buttonContainer.setLayout(new FlowLayout()); + controlsContainer.setLayout(new BorderLayout()); + + this.loadButton = new JButton("Load"); + this.runButton = new JButton("Run"); + this.stepButton = new JButton("Step"); + this.resetButton = new JButton("Reset"); + this.quitButton = new JButton("Quit"); + + buttonContainer.add(loadButton); + buttonContainer.add(runButton); + buttonContainer.add(stepButton); + buttonContainer.add(resetButton); + buttonContainer.add(quitButton); + + controlsContainer.add(buttonContainer, BorderLayout.PAGE_START); + controlsContainer.add(statusPane, BorderLayout.PAGE_END); + + console.setBorder(BorderFactory.createBevelBorder(1)); + console.setBackground(Color.BLACK); + console.setForeground(Color.WHITE); + console.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 20)); + + getContentPane().add(console, BorderLayout.CENTER); + getContentPane().add(controlsContainer, BorderLayout.PAGE_END); + + quitButton.addActionListener(this); + runButton.addActionListener(this); + stepButton.addActionListener(this); + resetButton.addActionListener(this); + loadButton.addActionListener(this); + + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + console.requestFocus(); + + // Finally create the UI Updater and hand a reference to the Simulator. + this.uiUpdater = new UiUpdater(this); + + // The simulator must always have a reference to the same UI updater. + this.simulator.setUiUpdater(uiUpdater); + } + + + public void actionPerformed(ActionEvent actionEvent) { + if (actionEvent.getSource() == loadButton) { + handleProgramLoad(); + } else if (actionEvent.getSource() == resetButton) { + handleReset(); + } else if (actionEvent.getSource() == stepButton) { + handleStep(); + } else if (actionEvent.getSource() == quitButton) { + handleQuit(); + } else if (actionEvent.getSource() == runButton) { + if (simulator.isRunning()) { + stop(); + } else { + start(); + } + } + } + + private void handleProgramLoad() { + try { + int retVal = fileChooser.showOpenDialog(MainWindow.this); + if (retVal == JFileChooser.APPROVE_OPTION) { + File f = fileChooser.getSelectedFile(); + if (f.canRead()) { + long fileSize = f.length(); + + if (fileSize > simulator.memorySize()) { + throw new IOException("Program will not fit in available memory."); + } else { + byte[] program = new byte[(int) fileSize]; + int i = 0; + FileInputStream fis = new FileInputStream(f); + BufferedInputStream bis = new BufferedInputStream(fis); + DataInputStream dis = new DataInputStream(bis); + while (dis.available() != 0) { + program[i++] = dis.readByte(); + } + + // Now load the program at the starting address. + simulator.loadProgram(program, PROGRAM_START_ADDRESS); + // Reset (but don't clear memory, naturally) + simulator.reset(); + // Update status and clear the screen + console.reset(); + uiUpdater.updateUi(); + } + } + } + } catch (IOException ex) { + logger.log(Level.SEVERE, "Unable to read file: " + ex.getMessage()); + ex.printStackTrace(); + } catch (MemoryAccessException ex) { + logger.log(Level.SEVERE, "Memory access error loading program"); + ex.printStackTrace(); + } + } + + private void handleReset() { + if (simulator.isRunning()) { + stop(); + } + + try { + logger.log(Level.INFO, "Reset requested. Resetting CPU and clearing memory."); + // Reset and clear memory + simulator.reset(); + simulator.clearMemory(); + // Clear the console. + console.reset(); + // Update status. + uiUpdater.updateUi(); + } catch (MemoryAccessException ex) { + logger.log(Level.SEVERE, "Exception during simulator reset: " + ex.getMessage()); + ex.printStackTrace(); + } + } + + private void handleStep() { + try { + simulator.step(); + // The simulator is lazy about updating the UI for + // performance reasons, so always request an immediate update after stepping manually. + uiUpdater.updateUi(); + } catch (SymonException ex) { + logger.log(Level.SEVERE, "Exception during simulator step: " + ex.getMessage()); + ex.printStackTrace(); + } + + } + + private void handleQuit() { + // TODO: Clean up and exit properly + System.exit(0); + } + + private void stop() { + // Allow step while the simulator is stopped + simulator.requestStop(); + if (simulatorThread != null) { + simulatorThread.interrupt(); + simulatorThread = null; + } + } + + private void start() { + simulatorThread = new Thread(simulator); + simulatorThread.start(); + } + + public Console getConsole() { + return this.console; + } + + public StatusPane getStatusPane() { + return statusPane; + } + + public JButton getRunButton() { + return runButton; + } + + public JButton getLoadButton() { + return loadButton; + } + + public JButton getStepButton() { + return stepButton; + } + + public static void main(String args[]) { + try { + // Create the simulated system + Simulator simulator = new Simulator(); + + // Create the main UI window + MainWindow app = new MainWindow(simulator); + + // Pack and display main window + app.pack(); + app.setVisible(true); + + // Reset the simulator. + simulator.reset(); + } catch (MemoryAccessException e) { + e.printStackTrace(); + } catch (MemoryRangeException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java index 88145a7..d145822 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -1,490 +1,157 @@ package com.loomcom.symon; -import java.io.*; -import java.util.StringTokenizer; +import java.util.logging.*; import com.loomcom.symon.devices.*; import com.loomcom.symon.exceptions.*; +import com.loomcom.symon.ui.UiUpdater; + +import javax.swing.*; /** - * Main control class for the J6502 Simulator. + * Main entry point and control for the Symon Simulator. + * This class is responsible for creating the UI and starting + * the IO threads that pass data to and from the simulated + * Bus and Cpu. */ -public class Simulator { +public class Simulator implements Runnable { - /** - * The CPU itself. - */ - private Cpu cpu; + private final static Logger logger = Logger.getLogger(Simulator.class.getName()); - /** - * The Bus responsible for routing memory read/write requests to the - * correct IO devices. - */ - private Bus bus; + private Bus bus; + private Cpu cpu; + private Acia acia; + private Memory ram; + private Memory rom; + private boolean isRunning = false; - /** - * The ACIA, used for charater in/out. - * - * By default, the simulator uses base address c000 for the ACIA. - */ - private Acia acia; + private int updatesRequested = 0; - private BufferedReader in; - private BufferedWriter out; + private UiUpdater uiUpdater; - /* If true, trace execution of the CPU */ - private boolean trace = false; - private int nextExamineAddress = 0; + private static final int BUS_BOTTOM = 0x0000; + private static final int BUS_TOP = 0xffff; - private static final int BUS_BOTTOM = 0x0000; - private static final int BUS_TOP = 0xffff; + private static final int MEMORY_BASE = 0x0000; + private static final int MEMORY_SIZE = 0xc000; // 48 KB - private static final int ACIA_BASE = 0xc000; + private static final int ROM_BASE = 0xe000; + private static final int ROM_SIZE = 0x2000; // 8 KB - private static final int MEMORY_BASE = 0x0000; - private static final int MEMORY_SIZE = 0xc000; // 48 KB + public static final int ACIA_BASE = 0xc000; - private static final int ROM_BASE = 0xe000; - private static final int ROM_SIZE = 0x2000; // 8 KB + private static final int MAX_REQUESTS_BETWEEN_UPDATES = 25000; - public Simulator() throws MemoryRangeException { - this.bus = new Bus(BUS_BOTTOM, BUS_TOP); - this.cpu = new Cpu(); - this.acia = new Acia(ACIA_BASE); + public Simulator() throws MemoryRangeException { + this.acia = new Acia(ACIA_BASE); + this.bus = new Bus(BUS_BOTTOM, BUS_TOP); + this.cpu = new Cpu(); + this.ram = new Memory(MEMORY_BASE, MEMORY_SIZE, false); + // TODO: Load this ROM from a file, naturally! + this.rom = new Memory(ROM_BASE, ROM_SIZE, false); + bus.addCpu(cpu); + bus.addDevice(acia); + bus.addDevice(ram); + bus.addDevice(rom); + } - bus.addCpu(cpu); - bus.addDevice(new Memory(MEMORY_BASE, MEMORY_SIZE, false)); - bus.addDevice(acia); - // TODO: This should be read-only memory. Add a method - // to allow one-time initialization of ROM with a loaded - // ROM binary file. - bus.addDevice(new Memory(ROM_BASE, ROM_SIZE, false)); + public void loadProgram(byte[] program, int startAddress) throws MemoryAccessException { + cpu.setResetVector(startAddress); - this.in = new BufferedReader(new InputStreamReader(System.in)); - this.out = new BufferedWriter(new OutputStreamWriter(System.out)); - } + int addr = startAddress, i; + for (i = 0; i < program.length; i++) { + bus.write(addr++, program[i] & 0xff); + } + + logger.log(Level.INFO, "Loaded " + i + " bytes at address 0x" + + Integer.toString(startAddress, 16)); + } + + public void setUiUpdater(UiUpdater uiUpdater) { + this.uiUpdater = uiUpdater; + } + + public boolean isRunning() { + return isRunning; + } + + public void requestStop() { + isRunning = false; + } + + public void reset() throws MemoryAccessException { + cpu.reset(); + } + + public void clearMemory() { + ram.fill(0x00); + } + + public int getProcessorStatus() { + return cpu.getProcessorStatus(); + } + + public Cpu getCpu() { + return this.cpu; + } + + public long memorySize() { + return MEMORY_SIZE; + } + + public void run() { + logger.log(Level.INFO, "Entering 'run' on main Simulator thread"); + isRunning = true; + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + uiUpdater.simulatorDidStart(); + } + }); - public void run() throws MemoryAccessException, FifoUnderrunException { - try { - greeting(); - prompt(); - String command = null; - while (!shouldQuit(command = readLine())) { try { - dispatch(command); - } catch (CommandFormatException ex) { - writeLine(ex.getMessage()); - } - prompt(); - } - writeLine("\nGoodbye!"); - } catch (IOException ex) { - System.err.println("Error: " + ex.toString()); - System.exit(1); - } - } - - /** - * Dispatch the command. - */ - public void dispatch(String commandLine) throws MemoryAccessException, - IOException, - CommandFormatException, - FifoUnderrunException { - Command c = new Command(commandLine); - String cmd = c.getCommand(); - if (cmd != null) { - if (cmd.startsWith("ste")) { - doStep(c); - } else if (cmd.startsWith("sta")) { - doGetState(); - } else if (cmd.startsWith("se")) { - doSet(c); - } else if (cmd.startsWith("r")) { - doReset(); - } else if (cmd.startsWith("e")) { - doExamine(c); - } else if (cmd.startsWith("d")) { - doDeposit(c); - } else if (cmd.startsWith("g")) { - doGo(c); - } else if (cmd.startsWith("h")) { - doHelp(c); - } else if (cmd.startsWith("t")) { - doToggleTrace(); - } else if (cmd.startsWith("f")) { - doFill(c); - } else if (cmd.startsWith("l")) { - doLoad(c); - } else { - writeLine("? Type h for help"); - } - } - } - - public void doHelp(Command c) throws IOException { - writeLine("Symon 6502 Simulator"); - writeLine(""); - writeLine("All addresses must be in hexadecimal."); - writeLine("Commands may be short or long (e.g. 'e' or 'ex' or 'examine')."); - writeLine("Note that 'go' clears the BREAK processor status flag."); - writeLine(""); - writeLine("h Show this help file."); - writeLine("e [start] [end] Examine memory at PC, start, or start-end."); - writeLine("d
Deposit data into address."); - writeLine("f Fill memory with data."); - writeLine("set {pc,a,x,y} [data] Set register to data value."); - writeLine("load
Load binary file at address."); - writeLine("g [address] [steps] Start running at address, or at PC."); - writeLine("step [address] Step once, optionally starting at address."); - writeLine("stat Show CPU state."); - writeLine("reset Reset simulator."); - writeLine("trace Toggle trace."); - writeLine("q (or Control-D) Quit.\n"); - } - - public void doGetState() throws IOException, MemoryAccessException { - writeLine(cpu.toString()); - writeLine("Trace is " + (trace ? "on" : "off")); - } - - public void doLoad(Command c) throws IOException, - MemoryAccessException, - CommandFormatException { - if (c.numArgs() != 2) { - throw new CommandFormatException("load
"); - } - - File binFile = new File(c.getArg(0)); - int address = stringToWord(c.getArg(1)); - - if (!binFile.exists()) { - throw new CommandFormatException("File '" + binFile + - "' does not exist."); - } - writeLine("Loading file '" + binFile + "' at address " + - String.format("%04x", address) + "..."); - - int bytesLoaded = 0; - - FileInputStream fis = new FileInputStream(binFile); - - try { - int b = 0; - while ((b = fis.read()) > -1 && address <= bus.endAddress()) { - bus.write(address++, b); - bytesLoaded++; - } - } finally { - fis.close(); - } - - writeLine("Loaded " + bytesLoaded + " (" + - String.format("$%04x", bytesLoaded) + ") bytes"); - } - - public void doSet(Command c) throws MemoryAccessException, - CommandFormatException { - if (c.numArgs() != 2) { - throw new CommandFormatException("set {a, x, y, pc} "); - } - try { - String reg = c.getArg(0).toLowerCase(); - String data = c.getArg(1); - if ("a".equals(reg)) { - cpu.setAccumulator(stringToByte(data)); - } else if ("x".equals(reg)) { - cpu.setXRegister(stringToByte(data)); - } else if ("y".equals(reg)) { - cpu.setYRegister(stringToByte(data)); - } else if ("pc".equals(reg)) { - cpu.setProgramCounter(stringToWord(data)); - } else { - throw new CommandFormatException("set {a, x, y, pc} "); - } - } catch (NumberFormatException ex) { - throw new CommandFormatException("Illegal address"); - } - } - - public void doExamine(Command c) throws IOException, - MemoryAccessException, - CommandFormatException { - try { - if (c.numArgs() == 2) { - int startAddress = stringToWord(c.getArgs()[0]); - int endAddress = stringToWord(c.getArgs()[1]); - while (startAddress < endAddress) { - StringBuffer line = new StringBuffer(); - int numBytes = 0; - line.append(String.format("%04x ", startAddress)); - while (numBytes++ < 16 && startAddress <= endAddress) { - line.append(String.format("%02x ", bus.read(startAddress++))); - if (numBytes % 8 == 0) { - line.append(" "); + while (isRunning && !cpu.getBreakFlag()) { + step(); } - } - writeLine(line.toString()); + } catch (SymonException ex) { + logger.log(Level.SEVERE, "Exception in main simulator run thread. Exiting run."); + ex.printStackTrace(); } - nextExamineAddress = endAddress + 1; - } else if (c.numArgs() == 1) { - int address = stringToWord(c.getArgs()[0]); - writeLine(String.format("%04x %02x", address, bus.read(address))); - nextExamineAddress = address + 1; - } else if (c.numArgs() == 0) { - writeLine(String.format("%04x %02x", nextExamineAddress, - bus.read(nextExamineAddress))); - nextExamineAddress++; - } else { - throw new CommandFormatException("e [start [end]]"); - } - } catch (NumberFormatException ex) { - throw new CommandFormatException("Illegal Address"); + + logger.log(Level.INFO, "Exiting 'run'. BREAK=" + cpu.getBreakBit() + "; RUN_FLAG=" + isRunning); + isRunning = false; + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + uiUpdater.simulatorDidStop(); + } + }); + } - } - public void doDeposit(Command c) throws MemoryAccessException, - CommandFormatException { - if (c.numArgs() != 2) { - throw new CommandFormatException("d [address] [data]"); - } - try { - int address = stringToWord(c.getArg(0)); - int data = stringToByte(c.getArg(1)); - bus.write(address, data); - } catch (NumberFormatException ex) { - throw new CommandFormatException("Illegal Address"); - } - } - public void doFill(Command c) throws MemoryAccessException, - CommandFormatException { - if (c.numArgs() != 3) { - throw new CommandFormatException("f [start] [end] [data]"); - } - try { - int start = stringToWord(c.getArg(0)); - int end = stringToWord(c.getArg(1)); - int data = stringToByte(c.getArg(2)); - while (start <= end) { - bus.write(start, data); - start++; - } - } catch (NumberFormatException ex) { - throw new CommandFormatException("Illegal Address"); - } - } + public void step() throws MemoryAccessException, FifoUnderrunException { - public void doStep(Command c) throws IOException, - MemoryAccessException, - FifoUnderrunException, - CommandFormatException { - try { - if (c.numArgs() > 0) { - cpu.setProgramCounter(stringToWord(c.getArg(1))); - } - cpu.step(); - writeLine(cpu.toString()); // Always show status after stepping - } catch (NumberFormatException ex) { - throw new CommandFormatException("Illegal Address"); - } - } - - public void doGo(Command c) throws IOException, - MemoryAccessException, - FifoUnderrunException, - CommandFormatException { - int readChar; - int stepCount = 0; - - if (c.numArgs() > 2) { - throw new CommandFormatException("g [address] [steps]"); - } - try { - int start = 0; - int steps = -1; - - if (c.numArgs() > 0) { - start = stringToWord(c.getArg(0)); - } else { - start = cpu.getProgramCounter(); - } - if (c.numArgs() == 2) { - steps = stringToWord(c.getArg(1)); - } - - // Make a gross assumption: Restarting the CPU clears - // the break flag and the IRQ disable flag. - cpu.clearBreakFlag(); - cpu.clearIrqDisableFlag(); - - cpu.setProgramCounter(start); - outer: - while (!cpu.getBreakFlag() && (steps == -1 || steps-- > 0)) { cpu.step(); - if (trace) { - writeLine(cpu.toString()); + + // TODO: ACIA interrupt handling. For now, poll ACIA on each step. + + // Read from the ACIA and add to the UiUpdater buffer + while (acia.hasTxChar()) { + uiUpdater.consoleWrite(acia.txRead()); } - // Wake up and scan keyboard every 500 steps - if (stepCount++ >= 500) { - // Reset step count - stepCount = 0; - - // - // Do output if available. - // - while (acia.hasTxChar()) { - out.write(acia.txRead()); - out.flush(); - } - - // - // Consume input if available. - // - // NOTE: On UNIX systems, System.in.available() returns 0 - // until Enter is pressed. So to interrupt we must ALWAYS - // type "^E". Sucks hard. But such is life. - if (System.in.available() > 0) { - while ((readChar = in.read()) > -1) { - // Keep consuming unless ^E is found. - // - // TODO: This will probably lead to a lot of spurious keyboard - // entry. Gotta keep an eye on that. - // - if (readChar == 0x05) { - break outer; - } else { - // Buffer keyboard input into the simulated ACIA's - // read buffer. - acia.rxWrite(readChar); - } - } - } + // This is a very expensive update, and we're doing it without + // a delay, so we don't want to overwhelm the Swing event processing thread + // with requests. Limit the number of ui updates that can be performed. + if (updatesRequested++ > MAX_REQUESTS_BETWEEN_UPDATES) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + uiUpdater.updateUi(); + } + }); + updatesRequested = 0; } - } - if (!trace) { - writeLine(cpu.toString()); - } - } catch (NumberFormatException ex) { - throw new CommandFormatException("Illegal Address"); } - } - - public void doToggleTrace() throws IOException { - this.trace = !trace; - writeLine("Trace is now " + (trace ? "on" : "off")); - } - - public void doReset() throws MemoryAccessException { - cpu.reset(); - this.trace = false; - } - - /** - * Main simulator routine. - */ - public static void main(String[] args) throws MemoryAccessException, - FifoUnderrunException { - try { - new Simulator().run(); - } catch (MemoryRangeException ex) { - System.err.println("Error: " + ex.toString()); - } - } - - /******************************************************************* - * Private - *******************************************************************/ - - public void load(int address, int[] data) - throws MemoryAccessException { - int i = 0; - for (int d : data) { - bus.write(address + i++, d); - } - } - - private int stringToWord(String addrString) { - return Integer.parseInt(addrString, 16) & 0xffff; - } - - private int stringToByte(String dataString) { - return Integer.parseInt(dataString, 16) & 0xff; - } - - private void greeting() throws IOException { - writeLine("Welcome to the Symon 6502 Simulator. Type 'h' for help."); - } - - private void prompt() throws IOException { - out.write("symon> "); - out.flush(); - } - - private String readLine() throws IOException { - String line = in.readLine(); - if (line == null) { return null; } - return line.trim(); - } - - private void writeLine(String line) throws IOException { - out.write(line); - out.newLine(); - out.flush(); - } - - /** - * Returns true if the line is a quit. - */ - private boolean shouldQuit(String line) { - return (line == null || "q".equals(line.toLowerCase())); - } - - /** - * Command line tokenizer class. Given a command line, tokenize - * it and give easy access to the command and its arguments. - */ - public static final class Command { - private String command; - private String[] args; - - public Command(String commandLine) { - StringTokenizer st = new StringTokenizer(commandLine); - int numTokens = st.countTokens(); - int idx = 0; - args = new String[numTokens > 1 ? numTokens - 1 : 0]; - while (st.hasMoreTokens()) { - if (command == null) { - command = st.nextToken(); - } else { - args[idx++] = st.nextToken(); - } - } - } - - public String getCommand() { - return command; - } - - public String[] getArgs() { - return args; - } - - public String getArg(int argNum) { - if (argNum > args.length - 1) { - return null; - } else { - return args[argNum]; - } - } - - public int numArgs() { - return args.length; - } - - public boolean hasArgs() { - return args.length > 0; - } - } } + diff --git a/src/main/java/com/loomcom/symon/devices/Memory.java b/src/main/java/com/loomcom/symon/devices/Memory.java index 555a94a..e717dc1 100644 --- a/src/main/java/com/loomcom/symon/devices/Memory.java +++ b/src/main/java/com/loomcom/symon/devices/Memory.java @@ -6,38 +6,43 @@ import com.loomcom.symon.exceptions.*; public class Memory extends Device { - private boolean readOnly; - private int[] mem; + private boolean readOnly; + private int[] mem; - /* Initialize all locations to 0x00 (BRK) */ - private static final int DEFAULT_FILL = 0x00; + /* Initialize all locations to 0x00 (BRK) */ + private static final int DEFAULT_FILL = 0x00; - public Memory(int address, int size, boolean readOnly) - throws MemoryRangeException { - super(address, size, (readOnly ? "RO Memory" : "RW Memory")); - this.readOnly = readOnly; - this.mem = new int[size]; - Arrays.fill(this.mem, DEFAULT_FILL); - } - - public Memory(int address, int size) throws MemoryRangeException { - this(address, size, false); - } - - public void write(int address, int data) throws MemoryAccessException { - if (readOnly) { - throw new MemoryAccessException("Cannot write to read-only memory at address " + address); - } else { - this.mem[address] = data; + public Memory(int address, int size, boolean readOnly) + throws MemoryRangeException { + super(address, size, (readOnly ? "RO Memory" : "RW Memory")); + this.readOnly = readOnly; + this.mem = new int[size]; + this.fill(DEFAULT_FILL); } - } - public int read(int address) throws MemoryAccessException { - return this.mem[address]; - } + public Memory(int address, int size) throws MemoryRangeException { + this(address, size, false); + } - public String toString() { - return "Memory: " + getMemoryRange().toString(); - } + public void write(int address, int data) throws MemoryAccessException { + if (readOnly) { + throw new MemoryAccessException("Cannot write to read-only memory at address " + address); + } else { + this.mem[address] = data; + } + } + + public int read(int address) throws MemoryAccessException { + return this.mem[address]; + } + + public void fill(int val) { + Arrays.fill(this.mem, val); + } + + + public String toString() { + return "Memory: " + getMemoryRange().toString(); + } } \ No newline at end of file diff --git a/src/main/java/com/loomcom/symon/exceptions/CommandFormatException.java b/src/main/java/com/loomcom/symon/exceptions/CommandFormatException.java index 2208a6b..6dbb101 100644 --- a/src/main/java/com/loomcom/symon/exceptions/CommandFormatException.java +++ b/src/main/java/com/loomcom/symon/exceptions/CommandFormatException.java @@ -1,6 +1,6 @@ package com.loomcom.symon.exceptions; -public class CommandFormatException extends Exception { +public class CommandFormatException extends SymonException { public CommandFormatException(String msg) { super(msg); } diff --git a/src/main/java/com/loomcom/symon/exceptions/FifoUnderrunException.java b/src/main/java/com/loomcom/symon/exceptions/FifoUnderrunException.java index 0ffb158..162c077 100644 --- a/src/main/java/com/loomcom/symon/exceptions/FifoUnderrunException.java +++ b/src/main/java/com/loomcom/symon/exceptions/FifoUnderrunException.java @@ -1,6 +1,6 @@ package com.loomcom.symon.exceptions; -public class FifoUnderrunException extends Exception { +public class FifoUnderrunException extends SymonException { public FifoUnderrunException(String msg) { super(msg); } diff --git a/src/main/java/com/loomcom/symon/exceptions/MemoryAccessException.java b/src/main/java/com/loomcom/symon/exceptions/MemoryAccessException.java index 465136f..2f210eb 100644 --- a/src/main/java/com/loomcom/symon/exceptions/MemoryAccessException.java +++ b/src/main/java/com/loomcom/symon/exceptions/MemoryAccessException.java @@ -4,7 +4,7 @@ package com.loomcom.symon.exceptions; * Exception that will be thrown if access to memory or IO cannot be * accessed. */ -public class MemoryAccessException extends Exception { +public class MemoryAccessException extends SymonException { public MemoryAccessException(String msg) { super(msg); } diff --git a/src/main/java/com/loomcom/symon/exceptions/MemoryRangeException.java b/src/main/java/com/loomcom/symon/exceptions/MemoryRangeException.java index 02a109e..571c617 100644 --- a/src/main/java/com/loomcom/symon/exceptions/MemoryRangeException.java +++ b/src/main/java/com/loomcom/symon/exceptions/MemoryRangeException.java @@ -3,7 +3,7 @@ package com.loomcom.symon.exceptions; /** * Exception that will be thrown if devices conflict in the IO map. */ -public class MemoryRangeException extends Exception { +public class MemoryRangeException extends SymonException { public MemoryRangeException(String msg) { super(msg); } diff --git a/src/main/java/com/loomcom/symon/exceptions/SymonException.java b/src/main/java/com/loomcom/symon/exceptions/SymonException.java new file mode 100644 index 0000000..bff91a8 --- /dev/null +++ b/src/main/java/com/loomcom/symon/exceptions/SymonException.java @@ -0,0 +1,14 @@ +package com.loomcom.symon.exceptions; + + +/** + * Superclass for all symon Exceptions. + */ +public class SymonException extends Exception { + public SymonException(String msg) { + super(msg); + } + public SymonException() { + super(); + } +} diff --git a/src/main/java/com/loomcom/symon/ui/Console.java b/src/main/java/com/loomcom/symon/ui/Console.java new file mode 100644 index 0000000..a1870c5 --- /dev/null +++ b/src/main/java/com/loomcom/symon/ui/Console.java @@ -0,0 +1,69 @@ +package com.loomcom.symon.ui; + +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import com.grahamedgecombe.jterminal.JTerminal; + +/** + * The Console is a simulated 80 column x 24 row VT-100 terminal attached to + * the ACIA of the system. It provides basic keyboard I/O to Symon. + */ + +public class Console extends JTerminal implements KeyListener, MouseListener { + + // TODO: Pass in io threads, read and write to ACIA. + public Console() { + super(); + addKeyListener(this); + addMouseListener(this); + } + + public void reset() { + getModel().clear(); + getModel().setCursorColumn(0); + getModel().setCursorRow(0); + repaint(); + } + + public void keyTyped(KeyEvent keyEvent) { + keyEvent.consume(); + } + + public void keyPressed(KeyEvent keyEvent) { + int keyCode = keyEvent.getKeyCode(); + int modifiersMask = keyEvent.getModifiers(); + int modifiersExMask = keyEvent.getModifiersEx(); + + System.out.println("Key Pressed #" + keyEvent.getKeyCode() + " : " + + KeyEvent.getKeyText(keyCode) + " MASK : " + + modifiersMask + " EXT MASK : " + + modifiersExMask + ); + + keyEvent.consume(); + } + + public void keyReleased(KeyEvent keyEvent) { + keyEvent.consume(); + } + + public void mouseClicked(MouseEvent mouseEvent) { + } + + public void mousePressed(MouseEvent mouseEvent) { + requestFocus(); + mouseEvent.consume(); + } + + public void mouseReleased(MouseEvent mouseEvent) { + } + + public void mouseEntered(MouseEvent mouseEvent) { + } + + public void mouseExited(MouseEvent mouseEvent) { + } +} diff --git a/src/main/java/com/loomcom/symon/ui/StatusPane.java b/src/main/java/com/loomcom/symon/ui/StatusPane.java new file mode 100644 index 0000000..4616041 --- /dev/null +++ b/src/main/java/com/loomcom/symon/ui/StatusPane.java @@ -0,0 +1,207 @@ +/** + * + */ +package com.loomcom.symon.ui; + +import com.loomcom.symon.Cpu; + +import javax.swing.*; +import java.awt.*; + +public class StatusPane extends JPanel { + + // The CPU to ask for state information. + private final Cpu cpu; + + private final ImageIcon carryOn; + private final ImageIcon carryOff; + private final ImageIcon zeroOn; + private final ImageIcon zeroOff; + private final ImageIcon irqOn; + private final ImageIcon irqOff; + private final ImageIcon decimalOn; + private final ImageIcon decimalOff; + private final ImageIcon breakOn; + private final ImageIcon breakOff; + private final ImageIcon overflowOn; + private final ImageIcon overflowOff; + private final ImageIcon negativeOn; + private final ImageIcon negativeOff; + + private final JLabel carryFlagLabel; + private final JLabel zeroFlagLabel; + private final JLabel irqDisableFlagLabel; + private final JLabel decimalModeFlagLabel; + private final JLabel breakFlagLabel; + private final JLabel overflowFlagLabel; + private final JLabel negativeFlagLabel; + + private final JLabel opcodeLabel; + private final JLabel pcLabel; + private final JLabel aLabel; + private final JLabel xLabel; + private final JLabel yLabel; + private final JLabel stepCountLabel; + + private static final int WIDTH = 134; + private static final int HEIGHT = 27; + + public StatusPane(Cpu cpu) { + super(); + + this.cpu = cpu; + + Dimension dimensions = new Dimension(WIDTH, HEIGHT); + + setMinimumSize(dimensions); + setPreferredSize(dimensions); + setMaximumSize(dimensions); + + carryOn = new ImageIcon(this.getClass().getResource("images/C_on.png")); + carryOff = new ImageIcon(this.getClass().getResource("images/C_off.png")); + zeroOn = new ImageIcon(this.getClass().getResource("images/Z_on.png")); + zeroOff = new ImageIcon(this.getClass().getResource("images/Z_off.png")); + irqOn = new ImageIcon(this.getClass().getResource("images/I_on.png")); + irqOff = new ImageIcon(this.getClass().getResource("images/I_off.png")); + decimalOn = new ImageIcon(this.getClass().getResource("images/D_on.png")); + decimalOff = new ImageIcon(this.getClass().getResource("images/D_off.png")); + breakOn = new ImageIcon(this.getClass().getResource("images/B_on.png")); + breakOff = new ImageIcon(this.getClass().getResource("images/B_off.png")); + overflowOn = new ImageIcon(this.getClass().getResource("images/O_on.png")); + overflowOff = new ImageIcon(this.getClass().getResource("images/O_off.png")); + negativeOn = new ImageIcon(this.getClass().getResource("images/N_on.png")); + negativeOff = new ImageIcon(this.getClass().getResource("images/N_off.png")); + + // Initialize all to off + carryFlagLabel = new JLabel(carryOff, JLabel.CENTER); + zeroFlagLabel = new JLabel(zeroOff, JLabel.CENTER); + irqDisableFlagLabel = new JLabel(irqOff, JLabel.CENTER); + decimalModeFlagLabel = new JLabel(decimalOff, JLabel.CENTER); + breakFlagLabel = new JLabel(breakOff, JLabel.CENTER); + overflowFlagLabel = new JLabel(overflowOff, JLabel.CENTER); + negativeFlagLabel = new JLabel(negativeOff, JLabel.CENTER); + + // Create and add register and address labels + + this.opcodeLabel = new JLabel(); + this.pcLabel = new JLabel(); + this.aLabel = new JLabel(); + this.xLabel = new JLabel(); + this.yLabel = new JLabel(); + this.stepCountLabel = new JLabel(); + + this.opcodeLabel.setMinimumSize(new Dimension(100, 20)); + this.pcLabel.setMinimumSize(new Dimension(80, 20)); + this.aLabel.setMinimumSize(new Dimension(60, 20)); + this.xLabel.setMinimumSize(new Dimension(60, 20)); + this.yLabel.setMinimumSize(new Dimension(60, 20)); + this.stepCountLabel.setMinimumSize(new Dimension(120, 20)); + + this.opcodeLabel.setPreferredSize(new Dimension(100, 20)); + this.pcLabel.setPreferredSize(new Dimension(80, 20)); + this.aLabel.setPreferredSize(new Dimension(60, 20)); + this.xLabel.setPreferredSize(new Dimension(60, 20)); + this.yLabel.setPreferredSize(new Dimension(60, 20)); + this.stepCountLabel.setPreferredSize(new Dimension(120, 20)); + + this.setLayout(new FlowLayout()); + + this.add(negativeFlagLabel); + this.add(overflowFlagLabel); + this.add(breakFlagLabel); + this.add(decimalModeFlagLabel); + this.add(irqDisableFlagLabel); + this.add(zeroFlagLabel); + this.add(carryFlagLabel); + + this.add(opcodeLabel); + this.add(pcLabel); + this.add(aLabel); + this.add(xLabel); + this.add(yLabel); + this.add(stepCountLabel); + + updateState(); + } + + public void updateState() { + // Update the Processor Status Flag display + int state = this.cpu.getProcessorStatus(); + + carryFlagLabel.setIcon(iconForFlag(state, 0)); + zeroFlagLabel.setIcon(iconForFlag(state, 1)); + irqDisableFlagLabel.setIcon(iconForFlag(state, 2)); + decimalModeFlagLabel.setIcon(iconForFlag(state, 3)); + breakFlagLabel.setIcon(iconForFlag(state, 4)); + overflowFlagLabel.setIcon(iconForFlag(state, 6)); + negativeFlagLabel.setIcon(iconForFlag(state, 7)); + + // Update the register and address displays + opcodeLabel.setText(cpu.getOpcodeStatus()); + pcLabel.setText(cpu.getProgramCounterStatus()); + aLabel.setText(cpu.getARegisterStatus()); + xLabel.setText(cpu.getXRegisterStatus()); + yLabel.setText(cpu.getYRegisterStatus()); + stepCountLabel.setText(Long.toString(cpu.getStepCounter())); + + repaint(); + } + + private ImageIcon iconForFlag(int state, int flagIndex) { + ImageIcon imageIcon = null; + + if ((((state & 0xff) >> flagIndex) & 0x01) == 1) { + switch (flagIndex) { + case 0: + imageIcon = carryOn; + break; + case 1: + imageIcon = zeroOn; + break; + case 2: + imageIcon = irqOn; + break; + case 3: + imageIcon = decimalOn; + break; + case 4: + imageIcon = breakOn; + break; + case 6: + imageIcon = overflowOn; + break; + case 7: + imageIcon = negativeOn; + break; + } + } else { + switch (flagIndex) { + case 0: + imageIcon = carryOff; + break; + case 1: + imageIcon = zeroOff; + break; + case 2: + imageIcon = irqOff; + break; + case 3: + imageIcon = decimalOff; + break; + case 4: + imageIcon = breakOff; + break; + case 6: + imageIcon = overflowOff; + break; + case 7: + imageIcon = negativeOff; + break; + } + + } + + return imageIcon; + } + +} diff --git a/src/main/java/com/loomcom/symon/ui/UiUpdater.java b/src/main/java/com/loomcom/symon/ui/UiUpdater.java new file mode 100644 index 0000000..93f66a2 --- /dev/null +++ b/src/main/java/com/loomcom/symon/ui/UiUpdater.java @@ -0,0 +1,63 @@ +package com.loomcom.symon.ui; + +import com.loomcom.symon.MainWindow; + +/** + * Update the console and status display. All direct manipulation of the + * main UI should go through this class. When not called from the Swing + * event dispatch thread, these methods should be called using + * SwingUtilities.invokeLater. + */ +public class UiUpdater { + private final Console console; + private final MainWindow mainWindow; + private final StatusPane statusPane; + + private final StringBuffer data; + + public UiUpdater(MainWindow mainWindow) { + this.mainWindow = mainWindow; + this.console = mainWindow.getConsole(); + this.statusPane = mainWindow.getStatusPane(); + this.data = new StringBuffer(); + } + + public void consoleWrite(int i) { + data.append((char) i); + } + + /** + * Callback called by the simulator before exiting its run method. + */ + public void simulatorDidStop() { + mainWindow.getStepButton().setEnabled(true); + mainWindow.getLoadButton().setEnabled(true); + mainWindow.getRunButton().setText("Run"); + updateUi(); + } + + /** + * Callback called by the simulator when entering its run method. + */ + public void simulatorDidStart() { + // Don't allow step while the simulator is running + mainWindow.getStepButton().setEnabled(false); + mainWindow.getLoadButton().setEnabled(false); + // Toggle the state of the run button + mainWindow.getRunButton().setText("Stop"); + updateUi(); + } + + public void updateUi() { + // Update the console with any text + if (data.length() > 0) { + console.getModel().print(data.toString()); + console.repaint(); + // Clear the buffer + data.delete(0, data.length()); + } + + // Update the status UI. + statusPane.updateState(); + } +} diff --git a/src/main/java/com/loomcom/symon/ui/images/B_off.png b/src/main/java/com/loomcom/symon/ui/images/B_off.png new file mode 100644 index 0000000..145264f Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/B_off.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/B_on.png b/src/main/java/com/loomcom/symon/ui/images/B_on.png new file mode 100644 index 0000000..3e3da87 Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/B_on.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/C_off.png b/src/main/java/com/loomcom/symon/ui/images/C_off.png new file mode 100644 index 0000000..84986cf Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/C_off.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/C_on.png b/src/main/java/com/loomcom/symon/ui/images/C_on.png new file mode 100644 index 0000000..8c57a38 Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/C_on.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/D_off.png b/src/main/java/com/loomcom/symon/ui/images/D_off.png new file mode 100644 index 0000000..39db75d Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/D_off.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/D_on.png b/src/main/java/com/loomcom/symon/ui/images/D_on.png new file mode 100644 index 0000000..a9308be Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/D_on.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/I_off.png b/src/main/java/com/loomcom/symon/ui/images/I_off.png new file mode 100644 index 0000000..288a487 Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/I_off.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/I_on.png b/src/main/java/com/loomcom/symon/ui/images/I_on.png new file mode 100644 index 0000000..650d081 Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/I_on.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/N_off.png b/src/main/java/com/loomcom/symon/ui/images/N_off.png new file mode 100644 index 0000000..6468771 Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/N_off.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/N_on.png b/src/main/java/com/loomcom/symon/ui/images/N_on.png new file mode 100644 index 0000000..140c2b6 Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/N_on.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/O_off.png b/src/main/java/com/loomcom/symon/ui/images/O_off.png new file mode 100644 index 0000000..51945fb Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/O_off.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/O_on.png b/src/main/java/com/loomcom/symon/ui/images/O_on.png new file mode 100644 index 0000000..fb9bf8b Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/O_on.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/Z_off.png b/src/main/java/com/loomcom/symon/ui/images/Z_off.png new file mode 100644 index 0000000..124dc55 Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/Z_off.png differ diff --git a/src/main/java/com/loomcom/symon/ui/images/Z_on.png b/src/main/java/com/loomcom/symon/ui/images/Z_on.png new file mode 100644 index 0000000..65fe24b Binary files /dev/null and b/src/main/java/com/loomcom/symon/ui/images/Z_on.png differ diff --git a/src/test/java/com/loomcom/symon/CommandTest.java b/src/test/java/com/loomcom/symon/CommandTest.java deleted file mode 100644 index e7b21b6..0000000 --- a/src/test/java/com/loomcom/symon/CommandTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.loomcom.symon; - -import org.junit.*; -import static org.junit.Assert.*; - -public class CommandTest { - - @Test - public void testCommandShouldParseCorrectNumberOfArguments() { - Simulator.Command c; - - c = new Simulator.Command("foo"); - assertEquals("foo", c.getCommand()); - assertEquals(0, c.numArgs()); - - c = new Simulator.Command("foo bar"); - assertEquals("foo", c.getCommand()); - assertEquals(1, c.numArgs()); - assertEquals("bar", c.getArgs()[0]); - - c = new Simulator.Command("foo bar baz quux 0 100"); - assertEquals("foo", c.getCommand()); - assertEquals(5, c.numArgs()); - assertEquals("bar", c.getArgs()[0]); - assertEquals("baz", c.getArgs()[1]); - assertEquals("quux", c.getArgs()[2]); - assertEquals("0", c.getArgs()[3]); - assertEquals("100", c.getArgs()[4]); - } - - @Test - public void testCommandShouldIgnoreWhitespaceBetweenTokens() { - Simulator.Command c; - - c = new Simulator.Command("foo bar baz"); - assertEquals("foo", c.getCommand()); - assertEquals(2, c.numArgs()); - assertEquals("bar", c.getArgs()[0]); - assertEquals("baz", c.getArgs()[1]); - } - - @Test - public void testCommandShouldIgnoreWhitespaceBeforeCommand() { - Simulator.Command c; - - c = new Simulator.Command(" foo bar baz"); - assertEquals("foo", c.getCommand()); - assertEquals(2, c.numArgs()); - assertEquals("bar", c.getArgs()[0]); - assertEquals("baz", c.getArgs()[1]); - } - - @Test - public void testCommandShouldIgnoreWhitespaceAfterCommand() { - Simulator.Command c; - - c = new Simulator.Command("foo bar baz "); - assertEquals("foo", c.getCommand()); - assertEquals(2, c.numArgs()); - assertEquals("bar", c.getArgs()[0]); - assertEquals("baz", c.getArgs()[1]); - } - -} diff --git a/src/test/java/com/loomcom/symon/CpuTest.java b/src/test/java/com/loomcom/symon/CpuTest.java index ea86bf8..7902c1b 100644 --- a/src/test/java/com/loomcom/symon/CpuTest.java +++ b/src/test/java/com/loomcom/symon/CpuTest.java @@ -10,387 +10,409 @@ import com.loomcom.symon.exceptions.*; */ public class CpuTest extends TestCase { - private Cpu cpu; - private Bus bus; - private Memory mem; + private Cpu cpu; + private Bus bus; + private Memory mem; - public CpuTest(String testName) { - super(testName); - } - - public static Test suite() { - return new TestSuite(CpuTest.class); - } - - public void setUp() throws MemoryRangeException, MemoryAccessException { - this.cpu = new Cpu(); - this.bus = new Bus(0x0000, 0xffff); - this.mem = new Memory(0x0000, 0x10000); - bus.addCpu(cpu); - bus.addDevice(mem); - - // All test programs start at 0x0200; - bus.write(0xfffc, 0x00); - bus.write(0xfffd, 0x02); - - cpu.reset(); - } - - public void testReset() { - assertEquals(0, cpu.getAccumulator()); - assertEquals(0, cpu.getXRegister()); - assertEquals(0, cpu.getYRegister()); - assertEquals(0x0200, cpu.getProgramCounter()); - assertFalse(cpu.getCarryFlag()); - assertFalse(cpu.getZeroFlag()); - assertFalse(cpu.getIrqDisableFlag()); - assertFalse(cpu.getDecimalModeFlag()); - assertFalse(cpu.getBreakFlag()); - assertFalse(cpu.getOverflowFlag()); - assertFalse(cpu.getNegativeFlag()); - } - - public void testStack() throws MemoryAccessException { - - cpu.stackPush(0x13); - assertEquals(0x13, cpu.stackPop()); - - cpu.stackPush(0x12); - assertEquals(0x12, cpu.stackPop()); - - for (int i = 0x00; i <= 0xff; i++) { - cpu.stackPush(i); + public CpuTest(String testName) { + super(testName); } - for (int i = 0xff; i >= 0x00; i--) { - assertEquals(i, cpu.stackPop()); + public static Test suite() { + return new TestSuite(CpuTest.class); } - } + public void setUp() throws MemoryRangeException, MemoryAccessException { + this.cpu = new Cpu(); + this.bus = new Bus(0x0000, 0xffff); + this.mem = new Memory(0x0000, 0x10000); + bus.addCpu(cpu); + bus.addDevice(mem); - public void testStackPush() throws MemoryAccessException { - assertEquals(0xff, cpu.getStackPointer()); - assertEquals(0x00, bus.read(0x1ff)); + // All test programs start at 0x0200; + bus.write(0xfffc, 0x00); + bus.write(0xfffd, 0x02); - cpu.stackPush(0x06); - assertEquals(0xfe, cpu.getStackPointer()); - assertEquals(0x06, bus.read(0x1ff)); + cpu.reset(); + } - cpu.stackPush(0x05); - assertEquals(0xfd, cpu.getStackPointer()); - assertEquals(0x06, bus.read(0x1ff)); - assertEquals(0x05, bus.read(0x1fe)); + public void testReset() { + assertEquals(0, cpu.getAccumulator()); + assertEquals(0, cpu.getXRegister()); + assertEquals(0, cpu.getYRegister()); + assertEquals(0x0200, cpu.getProgramCounter()); + assertFalse(cpu.getCarryFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getNegativeFlag()); + } - cpu.stackPush(0x04); - assertEquals(0xfc, cpu.getStackPointer()); - assertEquals(0x06, bus.read(0x1ff)); - assertEquals(0x05, bus.read(0x1fe)); - assertEquals(0x04, bus.read(0x1fd)); + public void testStack() throws MemoryAccessException { - cpu.stackPush(0x03); - assertEquals(0xfb, cpu.getStackPointer()); - assertEquals(0x06, bus.read(0x1ff)); - assertEquals(0x05, bus.read(0x1fe)); - assertEquals(0x04, bus.read(0x1fd)); - assertEquals(0x03, bus.read(0x1fc)); + cpu.stackPush(0x13); + assertEquals(0x13, cpu.stackPop()); - cpu.stackPush(0x02); - assertEquals(0xfa, cpu.getStackPointer()); - assertEquals(0x06, bus.read(0x1ff)); - assertEquals(0x05, bus.read(0x1fe)); - assertEquals(0x04, bus.read(0x1fd)); - assertEquals(0x03, bus.read(0x1fc)); - assertEquals(0x02, bus.read(0x1fb)); + cpu.stackPush(0x12); + assertEquals(0x12, cpu.stackPop()); - cpu.stackPush(0x01); - assertEquals(0xf9, cpu.getStackPointer()); - assertEquals(0x06, bus.read(0x1ff)); - assertEquals(0x05, bus.read(0x1fe)); - assertEquals(0x04, bus.read(0x1fd)); - assertEquals(0x03, bus.read(0x1fc)); - assertEquals(0x02, bus.read(0x1fb)); - assertEquals(0x01, bus.read(0x1fa)); - } + for (int i = 0x00; i <= 0xff; i++) { + cpu.stackPush(i); + } - public void testStackPushWrapsAroundToStackTop() throws MemoryAccessException { - cpu.setStackPointer(0x01); + for (int i = 0xff; i >= 0x00; i--) { + assertEquals(i, cpu.stackPop()); + } - cpu.stackPush(0x01); - assertEquals(0x01, bus.read(0x101)); - assertEquals(0x00, cpu.getStackPointer()); + } - cpu.stackPush(0x02); - assertEquals(0x02, bus.read(0x100)); - assertEquals(0xff, cpu.getStackPointer()); + public void testStackPush() throws MemoryAccessException { + assertEquals(0xff, cpu.getStackPointer()); + assertEquals(0x00, bus.read(0x1ff)); - cpu.stackPush(0x03); - assertEquals(0x03, bus.read(0x1ff)); - assertEquals(0xfe, cpu.getStackPointer()); - } + cpu.stackPush(0x06); + assertEquals(0xfe, cpu.getStackPointer()); + assertEquals(0x06, bus.read(0x1ff)); + + cpu.stackPush(0x05); + assertEquals(0xfd, cpu.getStackPointer()); + assertEquals(0x06, bus.read(0x1ff)); + assertEquals(0x05, bus.read(0x1fe)); + + cpu.stackPush(0x04); + assertEquals(0xfc, cpu.getStackPointer()); + assertEquals(0x06, bus.read(0x1ff)); + assertEquals(0x05, bus.read(0x1fe)); + assertEquals(0x04, bus.read(0x1fd)); + + cpu.stackPush(0x03); + assertEquals(0xfb, cpu.getStackPointer()); + assertEquals(0x06, bus.read(0x1ff)); + assertEquals(0x05, bus.read(0x1fe)); + assertEquals(0x04, bus.read(0x1fd)); + assertEquals(0x03, bus.read(0x1fc)); + + cpu.stackPush(0x02); + assertEquals(0xfa, cpu.getStackPointer()); + assertEquals(0x06, bus.read(0x1ff)); + assertEquals(0x05, bus.read(0x1fe)); + assertEquals(0x04, bus.read(0x1fd)); + assertEquals(0x03, bus.read(0x1fc)); + assertEquals(0x02, bus.read(0x1fb)); + + cpu.stackPush(0x01); + assertEquals(0xf9, cpu.getStackPointer()); + assertEquals(0x06, bus.read(0x1ff)); + assertEquals(0x05, bus.read(0x1fe)); + assertEquals(0x04, bus.read(0x1fd)); + assertEquals(0x03, bus.read(0x1fc)); + assertEquals(0x02, bus.read(0x1fb)); + assertEquals(0x01, bus.read(0x1fa)); + } + + public void testStackPushWrapsAroundToStackTop() throws MemoryAccessException { + cpu.setStackPointer(0x01); + + cpu.stackPush(0x01); + assertEquals(0x01, bus.read(0x101)); + assertEquals(0x00, cpu.getStackPointer()); + + cpu.stackPush(0x02); + assertEquals(0x02, bus.read(0x100)); + assertEquals(0xff, cpu.getStackPointer()); + + cpu.stackPush(0x03); + assertEquals(0x03, bus.read(0x1ff)); + assertEquals(0xfe, cpu.getStackPointer()); + } - public void testStackPop() throws MemoryAccessException { - bus.write(0x1ff, 0x06); - bus.write(0x1fe, 0x05); - bus.write(0x1fd, 0x04); - bus.write(0x1fc, 0x03); - bus.write(0x1fb, 0x02); - bus.write(0x1fa, 0x01); - cpu.setStackPointer(0xf9); + public void testStackPop() throws MemoryAccessException { + bus.write(0x1ff, 0x06); + bus.write(0x1fe, 0x05); + bus.write(0x1fd, 0x04); + bus.write(0x1fc, 0x03); + bus.write(0x1fb, 0x02); + bus.write(0x1fa, 0x01); + cpu.setStackPointer(0xf9); - assertEquals(0x01, cpu.stackPop()); - assertEquals(0xfa, cpu.getStackPointer()); + assertEquals(0x01, cpu.stackPop()); + assertEquals(0xfa, cpu.getStackPointer()); - assertEquals(0x02, cpu.stackPop()); - assertEquals(0xfb, cpu.getStackPointer()); + assertEquals(0x02, cpu.stackPop()); + assertEquals(0xfb, cpu.getStackPointer()); - assertEquals(0x03, cpu.stackPop()); - assertEquals(0xfc, cpu.getStackPointer()); + assertEquals(0x03, cpu.stackPop()); + assertEquals(0xfc, cpu.getStackPointer()); - assertEquals(0x04, cpu.stackPop()); - assertEquals(0xfd, cpu.getStackPointer()); + assertEquals(0x04, cpu.stackPop()); + assertEquals(0xfd, cpu.getStackPointer()); - assertEquals(0x05, cpu.stackPop()); - assertEquals(0xfe, cpu.getStackPointer()); + assertEquals(0x05, cpu.stackPop()); + assertEquals(0xfe, cpu.getStackPointer()); - assertEquals(0x06, cpu.stackPop()); - assertEquals(0xff, cpu.getStackPointer()); - } + assertEquals(0x06, cpu.stackPop()); + assertEquals(0xff, cpu.getStackPointer()); + } - public void testStackPopWrapsAroundToStackBottom() throws MemoryAccessException { - bus.write(0x1ff, 0x0f); // top of stack - bus.write(0x100, 0xf0); // bottom of stack - bus.write(0x101, 0xf1); - bus.write(0x102, 0xf2); + public void testStackPopWrapsAroundToStackBottom() throws MemoryAccessException { + bus.write(0x1ff, 0x0f); // top of stack + bus.write(0x100, 0xf0); // bottom of stack + bus.write(0x101, 0xf1); + bus.write(0x102, 0xf2); - cpu.setStackPointer(0xfe); + cpu.setStackPointer(0xfe); - assertEquals(0x0f, cpu.stackPop()); - assertEquals(0xff, cpu.getStackPointer()); + assertEquals(0x0f, cpu.stackPop()); + assertEquals(0xff, cpu.getStackPointer()); - assertEquals(0xf0, cpu.stackPop()); - assertEquals(0x00, cpu.getStackPointer()); + assertEquals(0xf0, cpu.stackPop()); + assertEquals(0x00, cpu.getStackPointer()); - assertEquals(0xf1, cpu.stackPop()); - assertEquals(0x01, cpu.getStackPointer()); + assertEquals(0xf1, cpu.stackPop()); + assertEquals(0x01, cpu.getStackPointer()); - assertEquals(0xf2, cpu.stackPop()); - assertEquals(0x02, cpu.getStackPointer()); - } + assertEquals(0xf2, cpu.stackPop()); + assertEquals(0x02, cpu.getStackPointer()); + } - public void testStackPeekDoesNotAlterStackPointer() throws MemoryAccessException { - assertEquals(0x00, cpu.stackPeek()); - assertEquals(0xff, cpu.getStackPointer()); + public void testStackPeekDoesNotAlterStackPointer() throws MemoryAccessException { + assertEquals(0x00, cpu.stackPeek()); + assertEquals(0xff, cpu.getStackPointer()); - cpu.stackPush(0x01); - assertEquals(0x01, cpu.stackPeek()); - assertEquals(0xfe, cpu.getStackPointer()); + cpu.stackPush(0x01); + assertEquals(0x01, cpu.stackPeek()); + assertEquals(0xfe, cpu.getStackPointer()); - cpu.stackPush(0x02); - assertEquals(0x02, cpu.stackPeek()); - assertEquals(0xfd, cpu.getStackPointer()); + cpu.stackPush(0x02); + assertEquals(0x02, cpu.stackPeek()); + assertEquals(0xfd, cpu.getStackPointer()); - cpu.stackPush(0x03); - assertEquals(0x03, cpu.stackPeek()); - assertEquals(0xfc, cpu.getStackPointer()); + cpu.stackPush(0x03); + assertEquals(0x03, cpu.stackPeek()); + assertEquals(0xfc, cpu.getStackPointer()); - cpu.stackPush(0x04); - assertEquals(0x04, cpu.stackPeek()); - assertEquals(0xfb, cpu.getStackPointer()); - assertEquals(0x04, cpu.stackPeek()); - assertEquals(0xfb, cpu.getStackPointer()); - assertEquals(0x04, cpu.stackPeek()); - assertEquals(0xfb, cpu.getStackPointer()); - } + cpu.stackPush(0x04); + assertEquals(0x04, cpu.stackPeek()); + assertEquals(0xfb, cpu.getStackPointer()); + assertEquals(0x04, cpu.stackPeek()); + assertEquals(0xfb, cpu.getStackPointer()); + assertEquals(0x04, cpu.stackPeek()); + assertEquals(0xfb, cpu.getStackPointer()); + } - public void testGetProcessorStatus() { - // By default, no flags are set. Remember, bit 5 - // is always '1'. - assertEquals(0x20, cpu.getProcessorStatus()); - cpu.setCarryFlag(); - assertEquals(0x21, cpu.getProcessorStatus()); - cpu.setZeroFlag(); - assertEquals(0x23, cpu.getProcessorStatus()); - cpu.setIrqDisableFlag(); - assertEquals(0x27, cpu.getProcessorStatus()); - cpu.setDecimalModeFlag(); - assertEquals(0x2f, cpu.getProcessorStatus()); - cpu.setBreakFlag(); - assertEquals(0x3f, cpu.getProcessorStatus()); - cpu.setOverflowFlag(); - assertEquals(0x7f, cpu.getProcessorStatus()); - cpu.setNegativeFlag(); - assertEquals(0xff, cpu.getProcessorStatus()); + public void testGetProcessorStatus() { + // By default, no flags are set. Remember, bit 5 + // is always '1'. + assertEquals(0x20, cpu.getProcessorStatus()); + cpu.setCarryFlag(); + assertEquals(0x21, cpu.getProcessorStatus()); + cpu.setZeroFlag(); + assertEquals(0x23, cpu.getProcessorStatus()); + cpu.setIrqDisableFlag(); + assertEquals(0x27, cpu.getProcessorStatus()); + cpu.setDecimalModeFlag(); + assertEquals(0x2f, cpu.getProcessorStatus()); + cpu.setBreakFlag(); + assertEquals(0x3f, cpu.getProcessorStatus()); + cpu.setOverflowFlag(); + assertEquals(0x7f, cpu.getProcessorStatus()); + cpu.setNegativeFlag(); + assertEquals(0xff, cpu.getProcessorStatus()); - cpu.clearCarryFlag(); - assertEquals(0xfe, cpu.getProcessorStatus()); - cpu.clearZeroFlag(); - assertEquals(0xfc, cpu.getProcessorStatus()); - cpu.clearIrqDisableFlag(); - assertEquals(0xf8, cpu.getProcessorStatus()); - cpu.clearDecimalModeFlag(); - assertEquals(0xf0, cpu.getProcessorStatus()); - cpu.clearBreakFlag(); - assertEquals(0xe0, cpu.getProcessorStatus()); - cpu.clearOverflowFlag(); - assertEquals(0xa0, cpu.getProcessorStatus()); - cpu.clearNegativeFlag(); - assertEquals(0x20, cpu.getProcessorStatus()); - } + cpu.clearCarryFlag(); + assertEquals(0xfe, cpu.getProcessorStatus()); + cpu.clearZeroFlag(); + assertEquals(0xfc, cpu.getProcessorStatus()); + cpu.clearIrqDisableFlag(); + assertEquals(0xf8, cpu.getProcessorStatus()); + cpu.clearDecimalModeFlag(); + assertEquals(0xf0, cpu.getProcessorStatus()); + cpu.clearBreakFlag(); + assertEquals(0xe0, cpu.getProcessorStatus()); + cpu.clearOverflowFlag(); + assertEquals(0xa0, cpu.getProcessorStatus()); + cpu.clearNegativeFlag(); + assertEquals(0x20, cpu.getProcessorStatus()); + } - public void testSetProcessorStatus() { - // Default - assertFalse(cpu.getZeroFlag()); - assertFalse(cpu.getZeroFlag()); - assertFalse(cpu.getIrqDisableFlag()); - assertFalse(cpu.getDecimalModeFlag()); - assertFalse(cpu.getBreakFlag()); - assertFalse(cpu.getOverflowFlag()); - assertFalse(cpu.getNegativeFlag()); + public void testSetProcessorStatus() { + // Default + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getNegativeFlag()); - cpu.setProcessorStatus(0x20|Cpu.P_CARRY); + cpu.setProcessorStatus(0x20 | Cpu.P_CARRY); - assertTrue(cpu.getCarryFlag()); - assertFalse(cpu.getZeroFlag()); - assertFalse(cpu.getIrqDisableFlag()); - assertFalse(cpu.getDecimalModeFlag()); - assertFalse(cpu.getBreakFlag()); - assertFalse(cpu.getOverflowFlag()); - assertFalse(cpu.getNegativeFlag()); + assertTrue(cpu.getCarryFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getNegativeFlag()); - cpu.setProcessorStatus(0x20|Cpu.P_CARRY|Cpu.P_NEGATIVE); + cpu.setProcessorStatus(0x20 | Cpu.P_CARRY | Cpu.P_NEGATIVE); - assertTrue(cpu.getCarryFlag()); - assertFalse(cpu.getZeroFlag()); - assertFalse(cpu.getIrqDisableFlag()); - assertFalse(cpu.getDecimalModeFlag()); - assertFalse(cpu.getBreakFlag()); - assertFalse(cpu.getOverflowFlag()); - assertTrue(cpu.getNegativeFlag()); + assertTrue(cpu.getCarryFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertTrue(cpu.getNegativeFlag()); - cpu.setProcessorStatus(0x20|Cpu.P_CARRY|Cpu.P_NEGATIVE|Cpu.P_ZERO); + cpu.setProcessorStatus(0x20 | Cpu.P_CARRY | Cpu.P_NEGATIVE | Cpu.P_ZERO); - assertTrue(cpu.getCarryFlag()); - assertTrue(cpu.getZeroFlag()); - assertFalse(cpu.getIrqDisableFlag()); - assertFalse(cpu.getDecimalModeFlag()); - assertFalse(cpu.getBreakFlag()); - assertFalse(cpu.getOverflowFlag()); - assertTrue(cpu.getNegativeFlag()); + assertTrue(cpu.getCarryFlag()); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertTrue(cpu.getNegativeFlag()); - cpu.setProcessorStatus(0x20|Cpu.P_CARRY|Cpu.P_NEGATIVE|Cpu.P_ZERO| - Cpu.P_OVERFLOW); + cpu.setProcessorStatus(0x20 | Cpu.P_CARRY | Cpu.P_NEGATIVE | Cpu.P_ZERO | + Cpu.P_OVERFLOW); - assertTrue(cpu.getCarryFlag()); - assertTrue(cpu.getZeroFlag()); - assertFalse(cpu.getIrqDisableFlag()); - assertFalse(cpu.getDecimalModeFlag()); - assertFalse(cpu.getBreakFlag()); - assertTrue(cpu.getOverflowFlag()); - assertTrue(cpu.getNegativeFlag()); + assertTrue(cpu.getCarryFlag()); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertTrue(cpu.getOverflowFlag()); + assertTrue(cpu.getNegativeFlag()); - cpu.setProcessorStatus(0x20|Cpu.P_CARRY|Cpu.P_NEGATIVE|Cpu.P_ZERO| - Cpu.P_OVERFLOW|Cpu.P_BREAK); + cpu.setProcessorStatus(0x20 | Cpu.P_CARRY | Cpu.P_NEGATIVE | Cpu.P_ZERO | + Cpu.P_OVERFLOW | Cpu.P_BREAK); - assertTrue(cpu.getCarryFlag()); - assertTrue(cpu.getZeroFlag()); - assertFalse(cpu.getIrqDisableFlag()); - assertFalse(cpu.getDecimalModeFlag()); - assertTrue(cpu.getBreakFlag()); - assertTrue(cpu.getOverflowFlag()); - assertTrue(cpu.getNegativeFlag()); + assertTrue(cpu.getCarryFlag()); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertTrue(cpu.getBreakFlag()); + assertTrue(cpu.getOverflowFlag()); + assertTrue(cpu.getNegativeFlag()); - cpu.setProcessorStatus(0x20|Cpu.P_CARRY|Cpu.P_NEGATIVE|Cpu.P_ZERO| - Cpu.P_OVERFLOW|Cpu.P_BREAK|Cpu.P_DECIMAL); + cpu.setProcessorStatus(0x20 | Cpu.P_CARRY | Cpu.P_NEGATIVE | Cpu.P_ZERO | + Cpu.P_OVERFLOW | Cpu.P_BREAK | Cpu.P_DECIMAL); - assertTrue(cpu.getCarryFlag()); - assertTrue(cpu.getZeroFlag()); - assertFalse(cpu.getIrqDisableFlag()); - assertTrue(cpu.getDecimalModeFlag()); - assertTrue(cpu.getBreakFlag()); - assertTrue(cpu.getOverflowFlag()); - assertTrue(cpu.getNegativeFlag()); + assertTrue(cpu.getCarryFlag()); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertTrue(cpu.getDecimalModeFlag()); + assertTrue(cpu.getBreakFlag()); + assertTrue(cpu.getOverflowFlag()); + assertTrue(cpu.getNegativeFlag()); - cpu.setProcessorStatus(0x20|Cpu.P_CARRY|Cpu.P_NEGATIVE|Cpu.P_ZERO| - Cpu.P_OVERFLOW|Cpu.P_BREAK|Cpu.P_DECIMAL| - Cpu.P_IRQ_DISABLE); + cpu.setProcessorStatus(0x20 | Cpu.P_CARRY | Cpu.P_NEGATIVE | Cpu.P_ZERO | + Cpu.P_OVERFLOW | Cpu.P_BREAK | Cpu.P_DECIMAL | + Cpu.P_IRQ_DISABLE); - assertTrue(cpu.getCarryFlag()); - assertTrue(cpu.getZeroFlag()); - assertTrue(cpu.getIrqDisableFlag()); - assertTrue(cpu.getDecimalModeFlag()); - assertTrue(cpu.getBreakFlag()); - assertTrue(cpu.getOverflowFlag()); - assertTrue(cpu.getNegativeFlag()); + assertTrue(cpu.getCarryFlag()); + assertTrue(cpu.getZeroFlag()); + assertTrue(cpu.getIrqDisableFlag()); + assertTrue(cpu.getDecimalModeFlag()); + assertTrue(cpu.getBreakFlag()); + assertTrue(cpu.getOverflowFlag()); + assertTrue(cpu.getNegativeFlag()); - cpu.setProcessorStatus(0x20); + cpu.setProcessorStatus(0x20); - assertFalse(cpu.getCarryFlag()); - assertFalse(cpu.getZeroFlag()); - assertFalse(cpu.getIrqDisableFlag()); - assertFalse(cpu.getDecimalModeFlag()); - assertFalse(cpu.getBreakFlag()); - assertFalse(cpu.getOverflowFlag()); - assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getCarryFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getNegativeFlag()); - cpu.setProcessorStatus(0x00); + cpu.setProcessorStatus(0x00); - assertFalse(cpu.getCarryFlag()); - assertFalse(cpu.getZeroFlag()); - assertFalse(cpu.getIrqDisableFlag()); - assertFalse(cpu.getDecimalModeFlag()); - assertFalse(cpu.getBreakFlag()); - assertFalse(cpu.getOverflowFlag()); - assertFalse(cpu.getNegativeFlag()); - } + assertFalse(cpu.getCarryFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getNegativeFlag()); + } - public void testAddress() { - assertEquals(0xf1ea, cpu.address(0xea, 0xf1)); - assertEquals(0x00ea, cpu.address(0xea, 0x00)); - assertEquals(0xf100, cpu.address(0x00, 0xf1)); - assertEquals(0x1234, cpu.address(0x34, 0x12)); - assertEquals(0xffff, cpu.address(0xff, 0xff)); - } + public void testAddress() { + assertEquals(0xf1ea, cpu.address(0xea, 0xf1)); + assertEquals(0x00ea, cpu.address(0xea, 0x00)); + assertEquals(0xf100, cpu.address(0x00, 0xf1)); + assertEquals(0x1234, cpu.address(0x34, 0x12)); + assertEquals(0xffff, cpu.address(0xff, 0xff)); + } - public void testZpxAddress() { - cpu.setXRegister(0x00); - assertEquals(0x10, cpu.zpxAddress(0x10)); - cpu.setXRegister(0x10); - assertEquals(0x20, cpu.zpxAddress(0x10)); - cpu.setXRegister(0x25); - assertEquals(0x35, cpu.zpxAddress(0x10)); - cpu.setXRegister(0xf5); - assertEquals(0x05, cpu.zpxAddress(0x10)); + public void testZpxAddress() { + cpu.setXRegister(0x00); + assertEquals(0x10, cpu.zpxAddress(0x10)); + cpu.setXRegister(0x10); + assertEquals(0x20, cpu.zpxAddress(0x10)); + cpu.setXRegister(0x25); + assertEquals(0x35, cpu.zpxAddress(0x10)); + cpu.setXRegister(0xf5); + assertEquals(0x05, cpu.zpxAddress(0x10)); - cpu.setXRegister(0x00); - assertEquals(0x80, cpu.zpxAddress(0x80)); - cpu.setXRegister(0x10); - assertEquals(0x90, cpu.zpxAddress(0x80)); - cpu.setXRegister(0x25); - assertEquals(0xa5, cpu.zpxAddress(0x80)); - cpu.setXRegister(0x95); - assertEquals(0x15, cpu.zpxAddress(0x80)); - } + cpu.setXRegister(0x00); + assertEquals(0x80, cpu.zpxAddress(0x80)); + cpu.setXRegister(0x10); + assertEquals(0x90, cpu.zpxAddress(0x80)); + cpu.setXRegister(0x25); + assertEquals(0xa5, cpu.zpxAddress(0x80)); + cpu.setXRegister(0x95); + assertEquals(0x15, cpu.zpxAddress(0x80)); + } - public void testZpyAddress() { - cpu.setYRegister(0x00); - assertEquals(0x10, cpu.zpyAddress(0x10)); - cpu.setYRegister(0x10); - assertEquals(0x20, cpu.zpyAddress(0x10)); - cpu.setYRegister(0x25); - assertEquals(0x35, cpu.zpyAddress(0x10)); - cpu.setYRegister(0xf5); - assertEquals(0x05, cpu.zpyAddress(0x10)); + public void testZpyAddress() { + cpu.setYRegister(0x00); + assertEquals(0x10, cpu.zpyAddress(0x10)); + cpu.setYRegister(0x10); + assertEquals(0x20, cpu.zpyAddress(0x10)); + cpu.setYRegister(0x25); + assertEquals(0x35, cpu.zpyAddress(0x10)); + cpu.setYRegister(0xf5); + assertEquals(0x05, cpu.zpyAddress(0x10)); - cpu.setYRegister(0x00); - assertEquals(0x80, cpu.zpyAddress(0x80)); - cpu.setYRegister(0x10); - assertEquals(0x90, cpu.zpyAddress(0x80)); - cpu.setYRegister(0x25); - assertEquals(0xa5, cpu.zpyAddress(0x80)); - cpu.setYRegister(0x95); - assertEquals(0x15, cpu.zpyAddress(0x80)); - } + cpu.setYRegister(0x00); + assertEquals(0x80, cpu.zpyAddress(0x80)); + cpu.setYRegister(0x10); + assertEquals(0x90, cpu.zpyAddress(0x80)); + cpu.setYRegister(0x25); + assertEquals(0xa5, cpu.zpyAddress(0x80)); + cpu.setYRegister(0x95); + assertEquals(0x15, cpu.zpyAddress(0x80)); + } + + public void testPcStatus() { + cpu.setProgramCounter(0x03fa); + assertEquals("$03FA", cpu.getProgramCounterStatus()); + } + + public void testOpcodeStatus() throws MemoryAccessException { + // LDA (immediate) + bus.write(0x0200, 0xa9); + bus.write(0x0201, 0xef); + cpu.step(); + assertEquals("LDA #$EF", cpu.getOpcodeStatus()); + + // BRK instruction + bus.write(0x0202, 0x00); + cpu.step(); + assertEquals("BRK", cpu.getOpcodeStatus()); + + // Illegal opcode + cpu.step(); + assertEquals("BRK", cpu.getOpcodeStatus()); + } } \ No newline at end of file