1
0
mirror of https://github.com/sethm/symon.git synced 2024-06-01 08:41:32 +00:00

Work In Progress: CPU behavior, UI changes

This is something of a "Work in Progress" checkpoint of several features
that are all half baked:

1. Allow loading of 16KB ROM files at address $C000 at run-time, not
   just at startup. See the "Load ROM..." File menu item.

2. Introduces the notion of "CPU Behaviors", so the core 6502 CPU
   implementation can match the behavior of either an early NMOS 6502, late
   NMOS 6502, or CMOS 65C02. Very little of this is actually implemented so
   far.

3. Adds a completely bogus implementation of the 6522 VIA (it
   does absolutely nothing right now).

4. Changes the address of the ACIA in the simulated system to match a
   real hardware implementation I put together.
This commit is contained in:
Seth Morabito 2012-11-25 22:49:21 -08:00
parent 795ccfde5d
commit 2ebdd254b3
33 changed files with 822 additions and 443 deletions

View File

@ -4,8 +4,8 @@ SYMON - A 6502 System Simulator
**NOTE: THIS IS BETA QUALITY SOFTWARE UNDER ACTIVE DEVELOPMENT. Feedback is
welcome!**
**Version:** 0.5
**Last Updated:** 21 October, 2012
**Version:** 0.6
**Last Updated:** 5 November, 2012
Copyright (c) 2008-2012 Seth J. Morabito <web@loomcom.com>
@ -47,7 +47,7 @@ Maven will build Symon, run unit tests, and produce a jar file in the
Symon is meant to be invoked directly from the jar file. To run with
Java 1.5 or greater, just type:
$ java -jar symon-0.3-jar-with-dependencies.jar
$ java -jar symon-0.6-jar-with-dependencies.jar
When Symon is running, you should be presented with a simple graphical
interface.

View File

@ -4,7 +4,7 @@
<groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId>
<packaging>jar</packaging>
<version>0.3</version>
<version>0.6-snapshot</version>
<name>symon</name>
<url>http://www.loomcom.com/symon</url>
<properties>
@ -44,7 +44,7 @@
<dependency>
<groupId>com.grahamedgecombe.jterminal</groupId>
<artifactId>jterminal</artifactId>
<version>1.0.2-loomcom</version>
<version>1.0.2.1-loomcom</version>
</dependency>
</dependencies>
@ -79,6 +79,7 @@
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifest>
<mainClass>com.loomcom.symon.Simulator</mainClass>

View File

@ -3,7 +3,7 @@
;;
.alias iobase $c000
.alias iobase $8000
.alias iostatus [iobase + 1]
.alias iocmd [iobase + 2]
.alias ioctrl [iobase + 3]

Binary file not shown.

Binary file not shown.

View File

@ -441,7 +441,7 @@ Ram_top = $C000 ; end of user RAM+1 (set as needed, should be page aligned)
; This start can be changed to suit your system
.org $D000
.org $C000
; BASIC cold start entry point

View File

@ -15,7 +15,7 @@ NMI_vec = IRQ_vec+$0A ; NMI code vector
; setup for the 6502 simulator environment
IO_AREA = $C000
IO_AREA = $8800
ACIAdata = IO_AREA ; simulated ACIA r/w port
ACIAstatus = IO_AREA+1
ACIAcommand = IO_AREA+2

View File

@ -1,6 +1,6 @@
MEMORY {
RAM1: start = $0000, size = $C000;
ROM1: start = $D000, size = $2F00, fill = yes;
RAM1: start = $0000, size = $8000;
ROM1: start = $C000, size = $3F00, fill = yes;
MONITOR: start = $FF00, size = $FA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
}

View File

@ -3,7 +3,7 @@
;;
.alias iobase $c000
.alias iobase $8000
.alias iostatus [iobase + 1]
.alias iocmd [iobase + 2]
.alias ioctrl [iobase + 3]

Binary file not shown.

View File

@ -10,11 +10,16 @@ import com.loomcom.symon.exceptions.*;
*/
public class Bus {
// The default address at which to load programs
public static int DEFAULT_LOAD_ADDRESS = 0x0200;
// 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<Device> devices;
@ -36,8 +41,13 @@ public class Bus {
return endAddress;
}
public void addDevice(Device device)
throws MemoryRangeException {
/**
* Add a device to the bus. Throws a MemoryRangeException if the device overlaps with any others.
*
* @param device
* @throws MemoryRangeException
*/
public void addDevice(Device device) throws MemoryRangeException {
// Make sure there's no memory overlap.
MemoryRange memRange = device.getMemoryRange();
for (Device d : devices) {
@ -54,9 +64,20 @@ public class Bus {
devices.add(device);
}
/**
* Remove a device from the bus.
*
* @param device
*/
public void removeDevice(Device device) {
if (devices.contains(device)) {
devices.remove(device);
}
}
public void addCpu(Cpu cpu) {
cpu.setBus(this);
this.cpu = cpu;
cpu.setBus(this);
}
/**

View File

@ -1,14 +1,37 @@
/*
* Copyright (c) 2008-2012 Seth J. Morabito <sethm@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon;
import com.loomcom.symon.exceptions.MemoryAccessException;
/**
* Main 6502 CPU Simulation.
* This class provides a simulation of the MOS 6502 CPU's state machine.
* A simple interface allows this 6502 to read and write to a simulated bus,
* and exposes some of the internal state for inspection and debugging.
*/
public class Cpu implements InstructionTable {
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;
@ -33,6 +56,9 @@ public class Cpu implements InstructionTable {
// TODO: Make configurable
private static final int CLOCK_IN_NS = 1000;
/* Simulated behavior */
private static CpuBehavior behavior;
/* The Bus */
private Bus bus;
@ -82,6 +108,11 @@ public class Cpu implements InstructionTable {
* Construct a new CPU.
*/
public Cpu() {
this(CpuBehavior.NMOS_WITH_INDIRECT_JMP_BUG);
}
public Cpu(CpuBehavior behavior) {
this.behavior = behavior;
}
/**
@ -98,6 +129,14 @@ public class Cpu implements InstructionTable {
return bus;
}
public void setBehavior(CpuBehavior behavior) {
this.behavior = behavior;
}
public CpuBehavior getBehavior() {
return behavior;
}
/**
* Reset the CPU to known initial values.
*/
@ -389,7 +428,15 @@ public class Cpu implements InstructionTable {
break;
case 0x6c: // JMP - Indirect
lo = address(args[0], args[1]); // Address of low byte
hi = lo + 1; // Address of high byte
if (args[0] == 0xff &&
(behavior == CpuBehavior.NMOS_WITH_INDIRECT_JMP_BUG ||
behavior == CpuBehavior.NMOS_WITH_ROR_BUG)) {
hi = address(0x00, args[1]);
} else {
hi = lo + 1;
}
pc = address(bus.read(lo), bus.read(hi));
/* TODO: For accuracy, allow a flag to enable broken behavior
* of early 6502s:
@ -1400,11 +1447,15 @@ public class Cpu implements InstructionTable {
private void delayLoop(int opcode) {
int clockSteps = Cpu.instructionClocks[0xff & opcode];
// Just a precaution. This could be better.
if (clockSteps == 0) { clockSteps = 1; }
if (clockSteps == 0) {
clockSteps = 1;
}
long startTime = System.nanoTime();
long stopTime = startTime + (CLOCK_IN_NS * clockSteps);
// Busy loop
while (System.nanoTime() < stopTime) { ; }
while (System.nanoTime() < stopTime) {
;
}
}
/**
@ -1459,7 +1510,7 @@ public class Cpu implements InstructionTable {
return sb.toString();
}
public String getInstructionByteStatus() {
switch (Cpu.instructionSizes[ir]) {
case 0:

View File

@ -2,248 +2,285 @@ package com.loomcom.symon;
public interface InstructionTable {
/**
* Enumeration of Addressing Modes.
*/
public enum Mode {
ACC {
public String toString() {
return "Accumulator";
}
},
/**
* Enumeration of valid CPU behaviors. These determine what behavior and instruction
* set will be simulated, depending on desired version of 6502.
*
* TODO: As of version 0.6, this is still not used! All CPUs are "idealized" NMOS 6502 only.
*/
public enum CpuBehavior {
/**
* The earliest NMOS 6502 includes a bug that causes the ROR instruction
* to behave like an ASL that does not affect the carry bit. This version
* is very rare in the wild.
*
* NB: Does NOT implement "unimplemented" NMOS instructions.
*/
NMOS_WITH_ROR_BUG,
ABS {
public String toString() {
return "Absolute";
}
},
/**
* All NMOS 6502's have a bug with the indirect JMP instruction. If the
*
* NB: Does NOT implement "unimplemented" NMOS instructions.
*/
NMOS_WITH_INDIRECT_JMP_BUG,
ABX {
public String toString() {
return "Absolute, X-indexed";
}
},
/**
* Emulate an NMOS 6502 without the indirect JMP bug. This type of 6502
* does not actually exist in the wild.
*
* NB: Does NOT implement "unimplemented" NMOS instructions.
*/
NMOS_WITHOUT_INDIRECT_JMP_BUG,
ABY {
public String toString() {
return "Absolute, Y-indexed";
}
},
IMM {
public String toString() {
return "Immediate";
}
},
IMP {
public String toString() {
return "Implied";
}
},
IND {
public String toString() {
return "Indirect";
}
},
XIN {
public String toString() {
return "X-indexed Indirect";
}
},
INY {
public String toString() {
return "Indirect, Y-indexed";
}
},
REL {
public String toString() {
return "Relative";
}
},
ZPG {
public String toString() {
return "Zeropage";
}
},
ZPX {
public String toString() {
return "Zeropage, X-indexed";
}
},
ZPY {
public String toString() {
return "Zeropage, Y-indexed";
}
},
NUL {
public String toString() {
return "NULL";
}
/**
* Emulate a CMOS 65C02, with all CMOS instructions and addressing modes.
*/
CMOS
}
}
// 6502 opcodes. No 65C02 opcodes implemented.
/**
* Enumeration of Addressing Modes.
*/
public enum Mode {
ACC {
public String toString() {
return "Accumulator";
}
},
/**
* Instruction opcode names.
*/
public static final String[] opcodeNames = {
"BRK", "ORA", null, null, null, "ORA", "ASL", null,
"PHP", "ORA", "ASL", null, null, "ORA", "ASL", null,
"BPL", "ORA", null, null, null, "ORA", "ASL", null,
"CLC", "ORA", null, null, null, "ORA", "ASL", null,
"JSR", "AND", null, null, "BIT", "AND", "ROL", null,
"PLP", "AND", "ROL", null, "BIT", "AND", "ROL", null,
"BMI", "AND", null, null, null, "AND", "ROL", null,
"SEC", "AND", null, null, null, "AND", "ROL", null,
"RTI", "EOR", null, null, null, "EOR", "LSR", null,
"PHA", "EOR", "LSR", null, "JMP", "EOR", "LSR", null,
"BVC", "EOR", null, null, null, "EOR", "LSR", null,
"CLI", "EOR", null, null, null, "EOR", "LSR", null,
"RTS", "ADC", null, null, null, "ADC", "ROR", null,
"PLA", "ADC", "ROR", null, "JMP", "ADC", "ROR", null,
"BVS", "ADC", null, null, null, "ADC", "ROR", null,
"SEI", "ADC", null, null, null, "ADC", "ROR", null,
"BCS", "STA", null, null, "STY", "STA", "STX", null,
"DEY", null, "TXA", null, "STY", "STA", "STX", null,
"BCC", "STA", null, null, "STY", "STA", "STX", null,
"TYA", "STA", "TXS", null, null, "STA", null, null,
"LDY", "LDA", "LDX", null, "LDY", "LDA", "LDX", null,
"TAY", "LDA", "TAX", null, "LDY", "LDA", "LDX", null,
"BCS", "LDA", null, null, "LDY", "LDA", "LDX", null,
"CLV", "LDA", "TSX", null, "LDY", "LDA", "LDX", null,
"CPY", "CMP", null, null, "CPY", "CMP", "DEC", null,
"INY", "CMP", "DEX", null, "CPY", "CMP", "DEC", null,
"BNE", "CMP", null, null, null, "CMP", "DEC", null,
"CLD", "CMP", null, null, null, "CMP", "DEC", null,
"CPX", "SBC", null, null, "CPX", "SBC", "INC", null,
"INX", "SBC", "NOP", null, "CPX", "SBC", "INC", null,
"BEQ", "SBC", null, null, null, "SBC", "INC", null,
"SED", "SBC", null, null, null, "SBC", "INC", null
};
ABS {
public String toString() {
return "Absolute";
}
},
/**
* Instruction addressing modes.
*/
public static final Mode[] instructionModes = {
Mode.IMP, Mode.XIN, Mode.NUL, Mode.NUL, // 0x00-0x03
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x04-0x07
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x08-0x0b
Mode.NUL, Mode.ABS, Mode.ABS, Mode.NUL, // 0x0c-0x0f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x10-0x13
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x14-0x17
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x18-0x1b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x1c-0x1f
Mode.ABS, Mode.XIN, Mode.NUL, Mode.NUL, // 0x20-0x23
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x24-0x27
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x28-0x2b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x2c-0x2f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x30-0x33
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x34-0x37
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x38-0x3b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x3c-0x3f
Mode.IMP, Mode.XIN, Mode.NUL, Mode.NUL, // 0x40-0x43
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x44-0x47
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x48-0x4b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x4c-0x4f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x50-0x53
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x54-0x57
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x58-0x5b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x5c-0x5f
Mode.IMP, Mode.XIN, Mode.NUL, Mode.NUL, // 0x60-0x63
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x64-0x67
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x68-0x6b
Mode.IND, Mode.ABS, Mode.ABS, Mode.NUL, // 0x6c-0x6f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x70-0x73
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x74-0x77
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x78-0x7b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x7c-0x7f
Mode.REL, Mode.XIN, Mode.NUL, Mode.NUL, // 0x80-0x83
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x84-0x87
Mode.IMP, Mode.NUL, Mode.IMP, Mode.NUL, // 0x88-0x8b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x8c-0x8f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x90-0x93
Mode.ZPX, Mode.ZPX, Mode.ZPY, Mode.NUL, // 0x94-0x97
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0x98-0x9b
Mode.NUL, Mode.ABX, Mode.NUL, Mode.NUL, // 0x9c-0x9f
Mode.IMM, Mode.XIN, Mode.IMM, Mode.NUL, // 0xa0-0xa3
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xa4-0xa7
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xa8-0xab
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xac-0xaf
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0xb0-0xb3
Mode.ZPX, Mode.ZPX, Mode.ZPY, Mode.NUL, // 0xb4-0xb7
Mode.IMP, Mode.ABX, Mode.IMP, Mode.NUL, // 0xb8-0xbb
Mode.ABX, Mode.ABY, Mode.ABY, Mode.NUL, // 0xbc-0xbf
Mode.IMM, Mode.XIN, Mode.NUL, Mode.NUL, // 0xc0-0xc3
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xc4-0xc7
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xc8-0xcb
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xcc-0xcf
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0xd0-0xd3
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0xd4-0xd7
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0xd8-0xdb
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0xdc-0xdf
Mode.IMM, Mode.XIN, Mode.NUL, Mode.NUL, // 0xe0-0xe3
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xe4-0xe7
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xe8-0xeb
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xec-0xef
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0xf0-0xf3
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0xf4-0xf7
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0xf8-0xfb
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL // 0xfc-0xff
};
ABX {
public String toString() {
return "Absolute, X-indexed";
}
},
ABY {
public String toString() {
return "Absolute, Y-indexed";
}
},
IMM {
public String toString() {
return "Immediate";
}
},
IMP {
public String toString() {
return "Implied";
}
},
IND {
public String toString() {
return "Indirect";
}
},
XIN {
public String toString() {
return "X-indexed Indirect";
}
},
INY {
public String toString() {
return "Indirect, Y-indexed";
}
},
REL {
public String toString() {
return "Relative";
}
},
ZPG {
public String toString() {
return "Zeropage";
}
},
ZPX {
public String toString() {
return "Zeropage, X-indexed";
}
},
ZPY {
public String toString() {
return "Zeropage, Y-indexed";
}
},
NUL {
public String toString() {
return "NULL";
}
}
}
// 6502 opcodes. No 65C02 opcodes implemented.
/**
* Instruction opcode names.
*/
public static final String[] opcodeNames = {
"BRK", "ORA", null, null, null, "ORA", "ASL", null,
"PHP", "ORA", "ASL", null, null, "ORA", "ASL", null,
"BPL", "ORA", null, null, null, "ORA", "ASL", null,
"CLC", "ORA", null, null, null, "ORA", "ASL", null,
"JSR", "AND", null, null, "BIT", "AND", "ROL", null,
"PLP", "AND", "ROL", null, "BIT", "AND", "ROL", null,
"BMI", "AND", null, null, null, "AND", "ROL", null,
"SEC", "AND", null, null, null, "AND", "ROL", null,
"RTI", "EOR", null, null, null, "EOR", "LSR", null,
"PHA", "EOR", "LSR", null, "JMP", "EOR", "LSR", null,
"BVC", "EOR", null, null, null, "EOR", "LSR", null,
"CLI", "EOR", null, null, null, "EOR", "LSR", null,
"RTS", "ADC", null, null, null, "ADC", "ROR", null,
"PLA", "ADC", "ROR", null, "JMP", "ADC", "ROR", null,
"BVS", "ADC", null, null, null, "ADC", "ROR", null,
"SEI", "ADC", null, null, null, "ADC", "ROR", null,
"BCS", "STA", null, null, "STY", "STA", "STX", null,
"DEY", null, "TXA", null, "STY", "STA", "STX", null,
"BCC", "STA", null, null, "STY", "STA", "STX", null,
"TYA", "STA", "TXS", null, null, "STA", null, null,
"LDY", "LDA", "LDX", null, "LDY", "LDA", "LDX", null,
"TAY", "LDA", "TAX", null, "LDY", "LDA", "LDX", null,
"BCS", "LDA", null, null, "LDY", "LDA", "LDX", null,
"CLV", "LDA", "TSX", null, "LDY", "LDA", "LDX", null,
"CPY", "CMP", null, null, "CPY", "CMP", "DEC", null,
"INY", "CMP", "DEX", null, "CPY", "CMP", "DEC", null,
"BNE", "CMP", null, null, null, "CMP", "DEC", null,
"CLD", "CMP", null, null, null, "CMP", "DEC", null,
"CPX", "SBC", null, null, "CPX", "SBC", "INC", null,
"INX", "SBC", "NOP", null, "CPX", "SBC", "INC", null,
"BEQ", "SBC", null, null, null, "SBC", "INC", null,
"SED", "SBC", null, null, null, "SBC", "INC", null
};
/**
* Instruction addressing modes.
*/
public static final Mode[] instructionModes = {
Mode.IMP, Mode.XIN, Mode.NUL, Mode.NUL, // 0x00-0x03
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x04-0x07
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x08-0x0b
Mode.NUL, Mode.ABS, Mode.ABS, Mode.NUL, // 0x0c-0x0f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x10-0x13
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x14-0x17
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x18-0x1b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x1c-0x1f
Mode.ABS, Mode.XIN, Mode.NUL, Mode.NUL, // 0x20-0x23
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x24-0x27
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x28-0x2b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x2c-0x2f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x30-0x33
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x34-0x37
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x38-0x3b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x3c-0x3f
Mode.IMP, Mode.XIN, Mode.NUL, Mode.NUL, // 0x40-0x43
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x44-0x47
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x48-0x4b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x4c-0x4f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x50-0x53
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x54-0x57
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x58-0x5b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x5c-0x5f
Mode.IMP, Mode.XIN, Mode.NUL, Mode.NUL, // 0x60-0x63
Mode.NUL, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x64-0x67
Mode.IMP, Mode.IMM, Mode.ACC, Mode.NUL, // 0x68-0x6b
Mode.IND, Mode.ABS, Mode.ABS, Mode.NUL, // 0x6c-0x6f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x70-0x73
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0x74-0x77
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0x78-0x7b
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0x7c-0x7f
Mode.REL, Mode.XIN, Mode.NUL, Mode.NUL, // 0x80-0x83
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0x84-0x87
Mode.IMP, Mode.NUL, Mode.IMP, Mode.NUL, // 0x88-0x8b
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x8c-0x8f
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x90-0x93
Mode.ZPX, Mode.ZPX, Mode.ZPY, Mode.NUL, // 0x94-0x97
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0x98-0x9b
Mode.NUL, Mode.ABX, Mode.NUL, Mode.NUL, // 0x9c-0x9f
Mode.IMM, Mode.XIN, Mode.IMM, Mode.NUL, // 0xa0-0xa3
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xa4-0xa7
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xa8-0xab
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xac-0xaf
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0xb0-0xb3
Mode.ZPX, Mode.ZPX, Mode.ZPY, Mode.NUL, // 0xb4-0xb7
Mode.IMP, Mode.ABX, Mode.IMP, Mode.NUL, // 0xb8-0xbb
Mode.ABX, Mode.ABY, Mode.ABY, Mode.NUL, // 0xbc-0xbf
Mode.IMM, Mode.XIN, Mode.NUL, Mode.NUL, // 0xc0-0xc3
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xc4-0xc7
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xc8-0xcb
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xcc-0xcf
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0xd0-0xd3
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0xd4-0xd7
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0xd8-0xdb
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL, // 0xdc-0xdf
Mode.IMM, Mode.XIN, Mode.NUL, Mode.NUL, // 0xe0-0xe3
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xe4-0xe7
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xe8-0xeb
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xec-0xef
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0xf0-0xf3
Mode.NUL, Mode.ZPX, Mode.ZPX, Mode.NUL, // 0xf4-0xf7
Mode.IMP, Mode.ABY, Mode.NUL, Mode.NUL, // 0xf8-0xfb
Mode.NUL, Mode.ABX, Mode.ABX, Mode.NUL // 0xfc-0xff
};
/**
* Size, in bytes, required for each instruction.
*/
public static final int[] instructionSizes = {
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 0, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0,
2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0
};
/**
* Size, in bytes, required for each instruction.
*/
public static final int[] instructionSizes = {
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 0, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0,
2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0
};
/**
* Number of clock cycles required for each instruction
*/
public static final int[] instructionClocks = {
7, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 0, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 3, 3, 5, 0, 4, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 3, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 0, 3, 5, 0, 4, 2, 2, 0, 5, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
2, 6, 0, 0, 3, 3, 3, 0, 2, 0, 2, 0, 4, 4, 4, 0,
2, 6, 0, 0, 4, 4, 4, 0, 2, 5, 2, 0, 0, 5, 0, 0,
2, 6, 2, 0, 3, 3, 3, 0, 2, 2, 2, 0, 4, 4, 4, 0,
2, 5, 0, 0, 4, 4, 4, 0, 2, 4, 2, 0, 4, 4, 4, 0,
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0
};
/**
* Number of clock cycles required for each instruction
*/
public static final int[] instructionClocks = {
7, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 0, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 3, 3, 5, 0, 4, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 0, 3, 5, 0, 3, 2, 2, 0, 3, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
6, 6, 0, 0, 0, 3, 5, 0, 4, 2, 2, 0, 5, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
2, 6, 0, 0, 3, 3, 3, 0, 2, 0, 2, 0, 4, 4, 4, 0,
2, 6, 0, 0, 4, 4, 4, 0, 2, 5, 2, 0, 0, 5, 0, 0,
2, 6, 2, 0, 3, 3, 3, 0, 2, 2, 2, 0, 4, 4, 4, 0,
2, 5, 0, 0, 4, 4, 4, 0, 2, 4, 2, 0, 4, 4, 4, 0,
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0,
2, 6, 0, 0, 3, 3, 5, 0, 2, 2, 2, 0, 4, 4, 6, 0,
2, 5, 0, 0, 0, 4, 6, 0, 2, 4, 0, 0, 0, 4, 7, 0
};
}

View File

@ -1,15 +1,41 @@
/*
* Copyright (c) 2008-2012 Seth J. Morabito <sethm@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.loomcom.symon;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.exceptions.FifoUnderrunException;
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.PreferencesDialog;
import com.loomcom.symon.ui.StatusPanel;
import com.loomcom.symon.ui.Console;
import sun.rmi.rmic.iiop.DirectoryLoader;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@ -19,24 +45,35 @@ import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
/**
* Symon Simulator Interface and Control.
*
* This class provides a control and I/O system for the simulated 6502 system.
* It includes the simulated CPU itself, as well as 32KB of RAM, 16KB of ROM,
* and a simulated ACIA for serial I/O. The ACIA is attached to a dumb terminal
* with a basic 80x25 character display.
*
*/
public class Simulator implements ActionListener, Observer {
// Constants used by the simulated system. These define the memory map.
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
// 32K of RAM
// 32K of RAM from $0000 - $7FFF
private static final int MEMORY_BASE = 0x0000;
private static final int MEMORY_SIZE = 0xC000;
private static final int MEMORY_SIZE = 0x8000;
// IO area at $D000
private static final int ACIA_BASE = 0xC000;
// VIA at $8000-$800F
private static final int VIA_BASE = 0x8000;
// 8KB ROM at E000-FFFF
private static final int ROM_BASE = 0xD000;
private static final int ROM_SIZE = 0x3000;
// ACIA at $8800-$8803
private static final int ACIA_BASE = 0x8800;
// 16KB ROM at $C000-$FFFF
private static final int ROM_BASE = 0xC000;
private static final int ROM_SIZE = 0x4000;
// Since it is very expensive to update the UI with Swing's Event Dispatch Thread, we can't afford
// to refresh the view on every simulated clock cycle. Instead, we will only refresh the view after this
@ -55,8 +92,9 @@ public class Simulator implements ActionListener, Observer {
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Via via;
private final Memory ram;
private final Memory rom;
private Memory rom;
// A counter to keep track of the number of UI updates that have been
// requested
@ -74,37 +112,36 @@ public class Simulator implements ActionListener, Observer {
// The most recently read key code
private char keyBuffer;
// TODO: loadMenuItem seriously violates encapsulation!
// TODO: loadProgramItem seriously violates encapsulation!
// A far better solution would be to extend JMenu and add callback
// methods to enable and disable menus as required.
// Menu Items
private JMenuItem loadMenuItem;
private JMenuItem loadProgramItem;
private JMenuItem loadRomItem;
private JFileChooser fileChooser;
private PreferencesDialog preferences;
public Simulator() throws MemoryRangeException, IOException {
this.acia = new Acia(ACIA_BASE);
this.via = new Via(VIA_BASE);
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.cpu = new Cpu();
this.ram = new Memory(MEMORY_BASE, MEMORY_SIZE, false);
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(via);
bus.addDevice(acia);
// TODO: Make this configurable, of course.
File romImage = new File("rom.bin");
if (romImage.canRead()) {
logger.info("Loading ROM image from file " + romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_SIZE, romImage);
} else {
logger.info("No ROM file 'rom.bin' found. ROM image will be empty R/W memory.");
// Just make it a normal RAM image.
this.rom = Memory.makeRAM(ROM_BASE, ROM_SIZE);
bus.addDevice(rom);
}
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(acia);
bus.addDevice(rom);
}
/**
@ -124,7 +161,7 @@ public class Simulator implements ActionListener, Observer {
this.statusPane = new StatusPanel();
// File Chooser
fileChooser = new JFileChooser();
fileChooser = new JFileChooser(System.getProperty("user.dir"));
preferences = new PreferencesDialog(mainWindow, true);
preferences.addObserver(this);
@ -177,8 +214,11 @@ public class Simulator implements ActionListener, Observer {
menuBar.add(fileMenu);
loadMenuItem = new JMenuItem("Load Program");
loadMenuItem.setMnemonic(KeyEvent.VK_L);
loadProgramItem = new JMenuItem("Load Program");
loadProgramItem.setMnemonic(KeyEvent.VK_L);
loadRomItem = new JMenuItem("Load ROM...");
loadRomItem.setMnemonic(KeyEvent.VK_R);
JMenuItem prefsItem = new JMenuItem("Preferences...");
prefsItem.setMnemonic(KeyEvent.VK_P);
@ -186,12 +226,18 @@ public class Simulator implements ActionListener, Observer {
JMenuItem quitItem = new JMenuItem("Quit");
quitItem.setMnemonic(KeyEvent.VK_Q);
loadMenuItem.addActionListener(new ActionListener() {
loadProgramItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
handleProgramLoad();
}
});
loadRomItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
handleRomLoad();
}
});
prefsItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
showAndUpdatePreferences();
@ -204,7 +250,8 @@ public class Simulator implements ActionListener, Observer {
}
});
fileMenu.add(loadMenuItem);
fileMenu.add(loadProgramItem);
fileMenu.add(loadRomItem);
fileMenu.add(prefsItem);
fileMenu.add(quitItem);
@ -224,20 +271,79 @@ public class Simulator implements ActionListener, Observer {
} else if (actionEvent.getSource() == stepButton) {
handleStep();
} else if (actionEvent.getSource() == runStopButton) {
// Shift focus to the console.
console.requestFocus();
if (runLoop != null && runLoop.isRunning()) {
runLoop.requestStop();
runLoop.interrupt();
runLoop = null;
handleStop();
} else {
// Spin up the new run loop
runLoop = new RunLoop();
runLoop.start();
handleStart();
}
}
}
private void handleStart() {
// Shift focus to the console.
console.requestFocus();
// Spin up the new run loop
runLoop = new RunLoop();
runLoop.start();
}
private void handleStop() {
runLoop.requestStop();
runLoop.interrupt();
runLoop = null;
simulatorDidStop();
}
/*
* Handle post-stop actions.
*/
private void simulatorDidStop() {
// The simulator is lazy about updating the UI for performance reasons, so always request an
// immediate update after stopping.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState(cpu);
}
});
// TODO: Write to Log window, if frame is visible.
// TODO: Update memory window, if frame is visible.
}
// TODO: Alert user of errors.
private void handleRomLoad() {
try {
int retVal = fileChooser.showOpenDialog(mainWindow);
if (retVal == JFileChooser.APPROVE_OPTION) {
File romFile = fileChooser.getSelectedFile();
if (romFile.canRead()) {
long fileSize = romFile.length();
if (fileSize != ROM_SIZE) {
throw new IOException("ROM file must be exactly " + String.valueOf(fileSize) + " bytes.");
} else {
if (rom != null) {
// Unload the existing ROM image.
bus.removeDevice(rom);
}
// Load the new ROM image
bus.addDevice(Memory.makeROM(ROM_BASE, ROM_SIZE, romFile));
logger.log(Level.INFO, "ROM File `" + romFile.getName() + "' loaded at " +
String.format("0x%04X", ROM_BASE));
}
}
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Unable to read file: " + ex.getMessage());
ex.printStackTrace();
} catch (MemoryRangeException ex) {
logger.log(Level.SEVERE, "Memory range error loading ROM");
ex.printStackTrace();
}
}
/**
* Display a file chooser prompting the user to load a binary program.
* After the user selects a file, read it in starting at PROGRAM_START_ADDRESS.
@ -315,15 +421,7 @@ public class Simulator implements ActionListener, Observer {
private void handleStep() {
try {
step();
// The simulator is lazy about updating the UI for performance reasons, so always request an
// immediate update after stepping manually.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now update the state
statusPane.updateState(cpu);
}
});
simulatorDidStop();
} catch (SymonException ex) {
logger.log(Level.SEVERE, "Exception during simulator step: " + ex.getMessage());
ex.printStackTrace();
@ -466,9 +564,12 @@ public class Simulator implements ActionListener, Observer {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Tell the console to start handling key presses
console.startListening();
// Don't allow step while the simulator is running
stepButton.setEnabled(false);
loadMenuItem.setEnabled(false);
loadProgramItem.setEnabled(false);
loadRomItem.setEnabled(false);
// Toggle the state of the run button
runStopButton.setText("Stop");
}
@ -486,9 +587,11 @@ public class Simulator implements ActionListener, Observer {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
console.stopListening();
// Allow step while the simulator is stopped
stepButton.setEnabled(true);
loadMenuItem.setEnabled(true);
loadProgramItem.setEnabled(true);
loadRomItem.setEnabled(true);
runStopButton.setText("Run");
// Now update the state
statusPane.updateState(cpu);

View File

@ -0,0 +1,44 @@
package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
public class Via extends Device {
public static final int VIA_SIZE = 16;
private static final int ORB = 0;
private static final int ORA = 1;
private static final int DDRB = 2;
private static final int DDRA = 3;
private static final int T1C_L = 4;
private static final int T1C_H = 5;
private static final int T1L_L = 6;
private static final int T1L_H = 7;
private static final int T2C_L = 8;
private static final int T2C_H = 9;
private static final int SR = 10;
private static final int ACR = 11;
private static final int PCR = 12;
private static final int IFR = 13;
private static final int IER = 14;
private static final int ORA_H = 15;
public Via(int address) throws MemoryRangeException {
super(address, VIA_SIZE, "VIA");
}
@Override
public void write(int address, int data) throws MemoryAccessException {
}
@Override
public int read(int address) throws MemoryAccessException {
return 0;
}
@Override
public String toString() {
return null;
}
}

View File

@ -4,6 +4,8 @@ import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.grahamedgecombe.jterminal.JTerminal;
import com.grahamedgecombe.jterminal.vt100.Vt100TerminalModel;
@ -21,15 +23,18 @@ import javax.swing.border.Border;
public class Console extends JTerminal implements KeyListener, MouseListener {
private static final int DEFAULT_COLUMNS = 80;
private static final int DEFAULT_ROWS = 24;
private static final int DEFAULT_BORDER_WIDTH = 10;
// If true, swap CR and LF characters
private static final boolean SWAP_CR_AND_LF = true;
private static final int DEFAULT_COLUMNS = 80;
private static final int DEFAULT_ROWS = 24;
private static final int DEFAULT_BORDER_WIDTH = 10;
// If true, swap CR and LF characters.
private static final boolean SWAP_CR_AND_LF = true;
// If true, send CRLF (0x0d 0x0a) whenever CR is typed
private static final boolean SEND_CR_LF_FOR_CR = false;
private static final boolean SEND_CR_LF_FOR_CR = false;
private FifoRingBuffer typeAheadBuffer;
// If true, the console is actively listening for key input.
private boolean isListening;
private FifoRingBuffer<Character> typeAheadBuffer;
public Console() {
this(DEFAULT_COLUMNS, DEFAULT_ROWS);
@ -39,7 +44,8 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
super(new Vt100TerminalModel(columns, rows));
// A small type-ahead buffer, as might be found in any real
// VT100-style serial terminal.
this.typeAheadBuffer = new FifoRingBuffer(128);
this.typeAheadBuffer = new FifoRingBuffer<Character>(128);
this.isListening = false;
setBorderWidth(DEFAULT_BORDER_WIDTH);
addKeyListener(this);
addMouseListener(this);
@ -52,7 +58,6 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
/**
* Reset the console. This will cause the console to be cleared and the cursor returned to the
* home position.
*
*/
public void reset() {
typeAheadBuffer.reset();
@ -62,6 +67,14 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
repaint();
}
public void startListening() {
this.isListening = true;
}
public void stopListening() {
this.isListening = false;
}
/**
* Returns true if a key has been pressed since the last time input was read.
*
@ -77,22 +90,23 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
* @param keyEvent The key event.
*/
public void keyTyped(KeyEvent keyEvent) {
char keyTyped = keyEvent.getKeyChar();
if (isListening) {
char keyTyped = keyEvent.getKeyChar();
if (SWAP_CR_AND_LF) {
if (keyTyped == 0x0a) {
keyTyped = 0x0d;
if (SWAP_CR_AND_LF) {
if (keyTyped == 0x0a) {
keyTyped = 0x0d;
} else if (keyTyped == 0x0d) {
keyTyped = 0x0a;
}
}
else if (keyTyped == 0x0d) {
keyTyped = 0x0a;
}
}
if (SEND_CR_LF_FOR_CR && keyTyped == 0x0d) {
typeAheadBuffer.push(0x0d);
typeAheadBuffer.push(0x0a);
} else {
typeAheadBuffer.push(keyTyped);
if (SEND_CR_LF_FOR_CR && keyTyped == 0x0d) {
typeAheadBuffer.push((char) 0x0d);
typeAheadBuffer.push((char) 0x0a);
} else {
typeAheadBuffer.push(keyTyped);
}
}
keyEvent.consume();
@ -113,7 +127,7 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
* @return The character typed.
*/
public char readInputChar() throws FifoUnderrunException {
return (char)typeAheadBuffer.pop();
return typeAheadBuffer.pop();
}
/**

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2008-2012 Seth J. Morabito <sethm@loomcom.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.loomcom.symon.ui;
import com.loomcom.symon.util.FifoRingBuffer;
import javax.swing.*;
/**
* This frame displays a trace of CPU execution. The most recent TRACE_LENGTH lines
* are captured in a buffer and rendered to the JFrame's main text area upon request.
*/
public class TraceLog extends JFrame {
private FifoRingBuffer<String> traceLog;
}

View File

@ -2,77 +2,57 @@ package com.loomcom.symon.util;
import com.loomcom.symon.exceptions.*;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
/**
* A very simple and efficient FIFO ring buffer implementation backed
* by an array. It can only hold only integers.
* A FIFO buffer with a bounded maximum size.
*/
public class FifoRingBuffer {
public class FifoRingBuffer<E> implements Iterable<E> {
private int[] fifoBuffer;
private int readPtr = 0;
private int writePtr = 0;
private int size = 0;
private Queue<E> fifoBuffer;
private int maxLength;
public FifoRingBuffer(int size) {
if (size <= 0) {
throw new RuntimeException("Cannot create a FifoRingBuffer with size <= 0.");
public FifoRingBuffer(int maxLength) {
this.fifoBuffer = new LinkedList<E>();
this.maxLength = maxLength;
}
this.size = size;
fifoBuffer = new int[size];
}
public int peek() throws FifoUnderrunException {
if (isEmpty()) {
throw new FifoUnderrunException("Buffer Underrun");
public E pop() throws FifoUnderrunException {
return fifoBuffer.remove();
}
return fifoBuffer[readPtr];
}
public int pop() throws FifoUnderrunException {
if (isEmpty()) {
throw new FifoUnderrunException("Buffer Underrun");
public boolean isEmpty() {
return fifoBuffer.isEmpty();
}
int val = fifoBuffer[readPtr];
incrementReadPointer();
return val;
}
public boolean isEmpty() {
return(readPtr == writePtr);
}
public boolean isFull() {
return((readPtr == 0 && writePtr == (size - 1)) ||
writePtr == (readPtr - 1));
}
public void push(int val) {
fifoBuffer[writePtr] = val;
incrementWritePointer();
}
public void reset() {
readPtr = 0;
writePtr = 0;
}
private void incrementWritePointer() {
if (++writePtr == size) {
writePtr = 0;
public void push(E val) {
if (fifoBuffer.size() == maxLength) {
// Delete the oldest element.
fifoBuffer.remove();
}
fifoBuffer.offer(val);
}
if (writePtr == readPtr) {
incrementReadPointer();
}
}
private void incrementReadPointer() {
if (++readPtr == size) {
readPtr = 0;
public E peek() {
return fifoBuffer.peek();
}
}
public String toString() {
return "[FifoRingBuffer: size=" + size + "]";
}
public void reset() {
fifoBuffer.clear();
}
public int length() {
return fifoBuffer.size();
}
public String toString() {
return "[FifoRingBuffer: size=" + fifoBuffer.size() + "]";
}
public Iterator<E> iterator() {
return fifoBuffer.iterator();
}
}

View File

@ -19,8 +19,8 @@ public class CpuAbsoluteModeTest extends TestCase {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00) >>> 8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
// Assert initial state

View File

@ -19,8 +19,8 @@ public class CpuAbsoluteXModeTest extends TestCase {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00) >>> 8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
// Assert initial state

View File

@ -19,8 +19,8 @@ public class CpuAbsoluteYModeTest extends TestCase {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00) >>> 8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
// Assert initial state

View File

@ -19,8 +19,8 @@ public class CpuAccumulatorModeTest extends TestCase {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00)>>>8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00)>>>8);
cpu.reset();

View File

@ -19,8 +19,8 @@ public class CpuImmediateModeTest extends TestCase {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00) >>> 8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
// Assert initial state

View File

@ -19,8 +19,8 @@ public class CpuImpliedModeTest extends TestCase {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00)>>>8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00)>>>8);
cpu.reset();

View File

@ -20,8 +20,8 @@ public class CpuIndexedIndirectModeTest {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00)>>>8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00)>>>8);
cpu.reset();
// Assert initial state

View File

@ -20,8 +20,8 @@ public class CpuIndirectIndexedModeTest {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00)>>>8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00)>>>8);
cpu.reset();
// Assert initial state

View File

@ -2,53 +2,102 @@ package com.loomcom.symon;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.InstructionTable;
import junit.framework.TestCase;
public class CpuIndirectModeTest extends TestCase {
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
protected Cpu cpu;
protected Bus bus;
protected Memory mem;
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
bus.addCpu(cpu);
bus.addDevice(mem);
protected void setUp() throws Exception {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000);
bus.addCpu(cpu);
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00)>>>8);
// Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
// Assert initial state
assertEquals(0, cpu.getAccumulator());
assertEquals(0, cpu.getXRegister());
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
}
cpu.reset();
// Assert initial state
assertEquals(0, cpu.getAccumulator());
assertEquals(0, cpu.getXRegister());
assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus());
}
/*
* The following opcodes are tested for correctness in this file:
*
* JMP - $6c
*
*/
/*
* The following opcodes are tested for correctness in this file:
*
* JMP - $6c
*
*/
/* JMP - Jump - $6c */
/* JMP - Jump - $6c */
public void test_JMP() throws MemoryAccessException {
bus.write(0x3400, 0x00);
bus.write(0x3401, 0x54);
bus.loadProgram(0x6c, 0x00, 0x34);
cpu.step();
assertEquals(0x5400, cpu.getProgramCounter());
// No change to status flags.
assertEquals(0x20, cpu.getProcessorStatus());
}
public void test_JMP_notOnPageBoundary() throws MemoryAccessException {
bus.write(0x3400, 0x00);
bus.write(0x3401, 0x54);
bus.loadProgram(0x6c, 0x00, 0x34);
cpu.step();
assertEquals(0x5400, cpu.getProgramCounter());
// No change to status flags.
assertEquals(0x20, cpu.getProcessorStatus());
}
public void test_JMP_with_ROR_Bug() throws MemoryAccessException {
cpu.setBehavior(Cpu.CpuBehavior.NMOS_WITH_ROR_BUG);
bus.write(0x3400, 0x22);
bus.write(0x34ff, 0x00);
bus.write(0x3500, 0x54);
bus.loadProgram(0x6c, 0xff, 0x34);
cpu.step();
assertEquals(0x2200, cpu.getProgramCounter());
// No change to status flags.
assertEquals(0x20, cpu.getProcessorStatus());
}
public void test_JMP_withIndirectBug() throws MemoryAccessException {
cpu.setBehavior(Cpu.CpuBehavior.NMOS_WITH_INDIRECT_JMP_BUG);
bus.write(0x3400, 0x22);
bus.write(0x34ff, 0x00);
bus.write(0x3500, 0x54);
bus.loadProgram(0x6c, 0xff, 0x34);
cpu.step();
assertEquals(0x2200, cpu.getProgramCounter());
// No change to status flags.
assertEquals(0x20, cpu.getProcessorStatus());
}
public void test_JMP_withOutIndirectBug() throws MemoryAccessException {
cpu.setBehavior(Cpu.CpuBehavior.NMOS_WITHOUT_INDIRECT_JMP_BUG);
bus.write(0x3400, 0x22);
bus.write(0x34ff, 0x00);
bus.write(0x3500, 0x54);
bus.loadProgram(0x6c, 0xff, 0x34);
cpu.step();
assertEquals(0x5400, cpu.getProgramCounter());
// No change to status flags.
assertEquals(0x20, cpu.getProcessorStatus());
}
public void test_JMP_cmos() throws MemoryAccessException {
cpu.setBehavior(Cpu.CpuBehavior.CMOS);
bus.write(0x3400, 0x22);
bus.write(0x34ff, 0x00);
bus.write(0x3500, 0x54);
bus.loadProgram(0x6c, 0xff, 0x34);
cpu.step();
assertEquals(0x5400, cpu.getProgramCounter());
// No change to status flags.
assertEquals(0x20, cpu.getProcessorStatus());
}
}

View File

@ -18,8 +18,8 @@ public class CpuIndirectXModeTest extends TestCase {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00) >>> 8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
// Assert initial state

View File

@ -19,8 +19,8 @@ public class CpuRelativeModeTest extends TestCase {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00)>>>8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00)>>>8);
cpu.reset();
// Assert initial state

View File

@ -19,8 +19,8 @@ public class CpuZeroPageModeTest extends TestCase {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00) >>> 8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
// Assert initial state

View File

@ -19,8 +19,8 @@ public class CpuZeroPageXModeTest extends TestCase {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00) >>> 8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
// Assert initial state

View File

@ -19,8 +19,8 @@ public class CpuZeroPageYModeTest extends TestCase {
bus.addDevice(mem);
// Load the reset vector.
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00) >>> 8);
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset();
// Assert initial state

View File

@ -0,0 +1,39 @@
package com.loomcom.symon;
import com.loomcom.symon.util.FifoRingBuffer;
import junit.framework.TestCase;
import java.lang.Character;
/**
* Created with IntelliJ IDEA.
* User: seth
* Date: 11/22/12
* Time: 11:28 PM
* To change this template use File | Settings | File Templates.
*/
public class FifoRingBufferTest extends TestCase {
public void testRingBufferShouldDeleteOldestItemIfAtMaximum() {
FifoRingBuffer<Character> buffer = new FifoRingBuffer<Character>(3);
assertEquals(0, buffer.length());
buffer.push('a');
assertEquals(1, buffer.length());
assertTrue('a' == buffer.peek());
buffer.push('b');
assertEquals(2, buffer.length());
assertTrue('a' == buffer.peek());
buffer.push('c');
assertEquals(3, buffer.length());
assertTrue('a' == buffer.peek());
buffer.push('d');
assertEquals(3, buffer.length());
assertTrue('b' == buffer.peek());
buffer.push('e');
assertEquals(3, buffer.length());
assertTrue('c' == buffer.peek());
}
}