mirror of
https://github.com/sethm/symon.git
synced 2025-01-16 12:30:30 +00:00
CPU bug fixes and Simulator enhancements.
Bug Fixes: - Fixed several bugs in the CPU that caused processor status flags to be set incorrectly. Instructions affected were: STA, STX, STY, CMP, CPX, CPY, BIT. - Made some internal-use-only methods on the CPU class private. - Fixed incorrect disassembly of (Indirect,X) and (Indirect),Y instructions. Although this didn't affect behavior, it certainly caused me some confusion in debugging. - Added missing "BCS" instruction to instruction table. Enhancements: - Now includes a full version of Lee Davison's Enhanced 6502 BASIC bundled as source code and a ROM image. Get that REAL COMPUTER EXPERIENCE!(tm) - If a file named "rom.bin" exists in the same directory where the simulator is executed, it will be loaded at addresses $d000-$ffff. - Gave the CPU an idle loop to make simulated timing a little more realistic (but this is still an area needing major improvement) - Changed the CPU's toString() method to give better debugging output. - Added a small typeahead buffer to the Console. - Better exception messaging. Misc: - Bumped version to 0.5, updated README.
This commit is contained in:
parent
c1caf8c6b4
commit
795ccfde5d
2
COPYING
2
COPYING
@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2008-2010 Seth J. Morabito <sethm@loomcom.com>
|
||||
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
|
||||
|
34
README.md
34
README.md
@ -1,12 +1,11 @@
|
||||
SYMON - A 6502 System Simulator
|
||||
===============================
|
||||
|
||||
**NOTE: THIS IS ALPHA QUALITY SOFTWARE UNDER ACTIVE DEVELOPMENT. IT IS
|
||||
NOT YET FULLY FUNCTIONAL. IT MAY BE USEFUL, BUT IT IS NOT YET INTENDED
|
||||
TO BE USED BY ANYONE BUT DEVELOPERS. Feedback is welcome!**
|
||||
**NOTE: THIS IS BETA QUALITY SOFTWARE UNDER ACTIVE DEVELOPMENT. Feedback is
|
||||
welcome!**
|
||||
|
||||
**Version:** 0.3
|
||||
**Last Updated:** 14 October, 2012
|
||||
**Version:** 0.5
|
||||
**Last Updated:** 21 October, 2012
|
||||
|
||||
Copyright (c) 2008-2012 Seth J. Morabito <web@loomcom.com>
|
||||
|
||||
@ -70,23 +69,36 @@ for testing.
|
||||
|
||||
- 'hello.prg' will continuously print "Hello, 6502 World!" to the console.
|
||||
|
||||
### 3.3 Running
|
||||
The sample directory also contains a ROM image of Lee Davison's
|
||||
Ehanced 6502 BASIC. For instructions on loading the rom, please see
|
||||
the README file in that directory.
|
||||
|
||||
After loading a program, clicking "Run" will start the simulator
|
||||
### 3.3 ROM files
|
||||
|
||||
Any 12KB file named 'rom.bin' that exists in the same directory where
|
||||
Symon is launched will be loaded at address $d000. If the file is
|
||||
larger than 12KB, loading will fail. This functionality will be
|
||||
improved in a future release!
|
||||
|
||||
### 3.4 Running
|
||||
|
||||
After loading a program or ROM image, clicking "Run" will start the simulator
|
||||
running at address $0300.
|
||||
|
||||
|
||||
## 4.0 To Do
|
||||
|
||||
- Accurate timing (all simulated instructions currently take
|
||||
only one step to execute)
|
||||
- Better debugging tools from the UI, including memory inspection,
|
||||
disassembly, breakpoints, and execution tracing.
|
||||
|
||||
- Better ROM loading (and re-loading)
|
||||
|
||||
- More accurate timing.
|
||||
|
||||
- Interrupt handling!
|
||||
|
||||
- UI needs a ton more polish.
|
||||
|
||||
- Add a simple menu interface for common tasks.
|
||||
|
||||
- More extensive testing.
|
||||
|
||||
- Clean up JavaDoc.
|
||||
|
@ -1,9 +1,39 @@
|
||||
A Sample Program
|
||||
Sample Programs
|
||||
----------------
|
||||
|
||||
When loaded at address $0300, this program will print "Hello, 6502 World!" in
|
||||
infinite loop.
|
||||
1. hello.prg
|
||||
|
||||
Assembled with the Ophis assembler:
|
||||
When loaded at address $0300, this program will print "Hello, 6502 World!" in
|
||||
infinite loop.
|
||||
|
||||
2. echo.prg
|
||||
|
||||
When loaded at address $0300, this program will echo back to the console
|
||||
anything typed.
|
||||
|
||||
|
||||
Both hello.prg and echo.prg were assembled with the Ophis assembler:
|
||||
|
||||
https://hkn.eecs.berkeley.edu/~mcmartin/ophis/
|
||||
|
||||
|
||||
3. ehbasic.rom
|
||||
|
||||
This is Lee Davison's Enhanced 6502 BASIC.
|
||||
|
||||
To use this ROM image, just copy the file 'ehbasic.rom' into the directory
|
||||
where you run Symon. Rename the file to 'rom.bin'. When you start Symon,
|
||||
the ROM file will be automatically loaded at address $d000.
|
||||
|
||||
Click the "Run" button and EhBASIC should automatically start running.
|
||||
|
||||
Type 'C' to do a cold start.
|
||||
|
||||
Then, type $C000 when prompted for the memory size.
|
||||
|
||||
NOTE: EhBASIC only wants upper-case input. This confused me at first!
|
||||
|
||||
More information can be found in the 'ehbasic' directory, and by visiting
|
||||
the EhBASIC web page:
|
||||
|
||||
http://mycorner.no-ip.org/6502/ehbasic/index.html
|
||||
|
@ -27,9 +27,9 @@ getkey: lda iostatus ; Read the ACIA status
|
||||
;; Write the current char in the accumulator to the console
|
||||
|
||||
write: pha ; Save accumulator
|
||||
lda iostatus ; Read the ACIA status
|
||||
writel: lda iostatus ; Read the ACIA status
|
||||
and #$10 ; Is the tx register empty?
|
||||
beq write ; No, wait for it to empty
|
||||
beq writel ; No, wait for it to empty
|
||||
pla ; Otherwise, load saved accumulator
|
||||
sta iobase ; and write to output.
|
||||
|
||||
|
BIN
samples/ehbasic.rom
Normal file
BIN
samples/ehbasic.rom
Normal file
Binary file not shown.
4
samples/ehbasic/.gitignore
vendored
Normal file
4
samples/ehbasic/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*.rom
|
||||
*.lst
|
||||
*.map
|
||||
*.o
|
13
samples/ehbasic/Makefile
Normal file
13
samples/ehbasic/Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
CA=ca65
|
||||
LD=ld65
|
||||
|
||||
all: ehbasic
|
||||
|
||||
ehbasic: ehbasic.o
|
||||
$(LD) -C symon.config -vm -m ehbasic.map -o ehbasic.rom ehbasic.o
|
||||
|
||||
ehbasic.o:
|
||||
$(CA) --listing -o ehbasic.o min_mon.asm
|
||||
|
||||
clean:
|
||||
rm -f *.o *.rom *.map *.lst
|
85
samples/ehbasic/README.txt
Normal file
85
samples/ehbasic/README.txt
Normal file
@ -0,0 +1,85 @@
|
||||
This directoy contains a very slightly modified version of EhBASIC 2.22
|
||||
to support the Symon simulator.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
The pre-assemled ROM image 'ehbasic.rom' in the "samples" directory is all you
|
||||
need unless you want to rebuild this source code.
|
||||
|
||||
Just copy the image 'ehbasic.rom' to the directory where you run Symon, and
|
||||
rename the file to 'rom.bin'. It will be loaded at memory location $D000 when
|
||||
the simulator starts up. Click "Run" and you'll be presented with BASIC.
|
||||
|
||||
At the first prompt, type 'C' for a Cold Start
|
||||
|
||||
When prompted for free memory, type: $C000
|
||||
|
||||
Note that EhBASIC only accepts upper case input, so you'll need to use caps
|
||||
lock (the cruise control for cool) to really make the most of it.
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
|
||||
To build it, you'll need the CC65 tool chain from http://www.cc65.org/
|
||||
and a suitable version of 'make'. Just typing:
|
||||
|
||||
% make
|
||||
|
||||
in this directory should re-build everything. You'll get a listing,
|
||||
some object files, and the ROM image itself.
|
||||
|
||||
|
||||
Changes from the EhBASIC 2.22
|
||||
-----------------------------
|
||||
|
||||
- Minor syntax changes to allow assembly with the cc65 tool chain.
|
||||
|
||||
- Memory map modified for Symon.
|
||||
|
||||
- At reset, configure the 6551 ACIA for 8-N-1, 2400 baud.
|
||||
|
||||
- Monitor routines 'ACIAin' and 'ACIAout' modified for the 6551 ACIA.
|
||||
Specifically, reading and writing will check the 6551 status register for
|
||||
the status of rx and tx registers before receive or transmit.
|
||||
|
||||
EhBASIC is copyright Lee Davison <leeedavison@googlemail.com>
|
||||
|
||||
My changes are so slight that I hesitate to even say this, but for "CYA"
|
||||
reasons:
|
||||
|
||||
Changes are copyright Seth Morabito <web@loomcom.com> and are distributed under
|
||||
the same license as EhBASIC. I claim no commercial interest whatsoever. Any
|
||||
commercial use must be negotiated directly with Lee Davison.
|
||||
|
||||
|
||||
Original EhBASIC 2.22 README
|
||||
----------------------------
|
||||
|
||||
Enhanced BASIC is a BASIC interpreter for the 6502 family microprocessors. It
|
||||
is constructed to be quick and powerful and easily ported between 6502 systems.
|
||||
It requires few resources to run and includes instructions to facilitate easy
|
||||
low level handling of hardware devices. It also retains most of the powerful
|
||||
high level instructions from similar BASICs.
|
||||
|
||||
EhBASIC is free but not copyright free. For non commercial use there is only one
|
||||
restriction, any derivative work should include, in any binary image distributed,
|
||||
the string "Derived from EhBASIC" and in any distribution that includes human
|
||||
readable files a file that includes the above string in a human readable form
|
||||
e.g. not as a comment in an HTML file.
|
||||
|
||||
For commercial use please contact me, Lee Davison, at leeedavison@googlemail.com
|
||||
for conditions.
|
||||
|
||||
For more information on EhBASIC, other versions of EhBASIC and other projects
|
||||
please visit my site at ..
|
||||
|
||||
http://mycorner.no-ip.org/index.html
|
||||
|
||||
|
||||
P.S. c't magazin, henceforth refered to as "those thieving german bastards", are
|
||||
prohibited from using this or any version of EhBASIC for any of their projects
|
||||
or products. The excuse "we don't charge people for it" doesn't wash, it adds
|
||||
value to your product so you owe me.
|
8691
samples/ehbasic/basic.asm
Normal file
8691
samples/ehbasic/basic.asm
Normal file
File diff suppressed because it is too large
Load Diff
152
samples/ehbasic/min_mon.asm
Normal file
152
samples/ehbasic/min_mon.asm
Normal file
@ -0,0 +1,152 @@
|
||||
|
||||
; minimal monitor for EhBASIC and 6502 simulator V1.05
|
||||
|
||||
; To run EhBASIC on the simulator load and assemble [F7] this file, start the simulator
|
||||
; running [F6] then start the code with the RESET [CTRL][SHIFT]R. Just selecting RUN
|
||||
; will do nothing, you'll still have to do a reset to run the code.
|
||||
|
||||
.feature labels_without_colons
|
||||
.include "basic.asm"
|
||||
|
||||
; put the IRQ and MNI code in RAM so that it can be changed
|
||||
|
||||
IRQ_vec = VEC_SV+2 ; IRQ code vector
|
||||
NMI_vec = IRQ_vec+$0A ; NMI code vector
|
||||
|
||||
; setup for the 6502 simulator environment
|
||||
|
||||
IO_AREA = $C000
|
||||
ACIAdata = IO_AREA ; simulated ACIA r/w port
|
||||
ACIAstatus = IO_AREA+1
|
||||
ACIAcommand = IO_AREA+2
|
||||
ACIAcontrol = IO_AREA+3
|
||||
|
||||
; now the code. all this does is set up the vectors and interrupt code
|
||||
; and wait for the user to select [C]old or [W]arm start. nothing else
|
||||
; fits in less than 128 bytes
|
||||
|
||||
.segment "MONITOR"
|
||||
.org $FF00 ; pretend this is in a 1/8K ROM
|
||||
|
||||
; reset vector points here
|
||||
|
||||
RES_vec
|
||||
CLD ; clear decimal mode
|
||||
LDX #$FF ; empty stack
|
||||
TXS ; set the stack
|
||||
|
||||
; Initialize the ACIA
|
||||
ACIA_init
|
||||
LDA #$09
|
||||
STA ACIAcommand
|
||||
LDA #$1A ; Set output for 8-N-1 2400
|
||||
STA ACIAcontrol
|
||||
|
||||
; set up vectors and interrupt code, copy them to page 2
|
||||
|
||||
LDY #END_CODE-LAB_vec ; set index/count
|
||||
LAB_stlp
|
||||
LDA LAB_vec-1,Y ; get byte from interrupt code
|
||||
STA VEC_IN-1,Y ; save to RAM
|
||||
DEY ; decrement index/count
|
||||
BNE LAB_stlp ; loop if more to do
|
||||
|
||||
; now do the signon message, Y = $00 here
|
||||
|
||||
LAB_signon
|
||||
LDA LAB_mess,Y ; get byte from sign on message
|
||||
BEQ LAB_nokey ; exit loop if done
|
||||
|
||||
JSR V_OUTP ; output character
|
||||
INY ; increment index
|
||||
BNE LAB_signon ; loop, branch always
|
||||
|
||||
LAB_nokey
|
||||
JSR V_INPT ; call scan input device
|
||||
BCC LAB_nokey ; loop if no key
|
||||
|
||||
AND #$DF ; mask xx0x xxxx, ensure upper case
|
||||
CMP #'W' ; compare with [W]arm start
|
||||
BEQ LAB_dowarm ; branch if [W]arm start
|
||||
|
||||
CMP #'C' ; compare with [C]old start
|
||||
BNE RES_vec ; loop if not [C]old start
|
||||
|
||||
JMP LAB_COLD ; do EhBASIC cold start
|
||||
|
||||
LAB_dowarm
|
||||
JMP LAB_WARM ; do EhBASIC warm start
|
||||
|
||||
; byte out to simulated ACIA
|
||||
ACIAout
|
||||
PHA ; save accumulator
|
||||
ACIAout_w
|
||||
LDA ACIAstatus ; Read 6551 status
|
||||
AND #$10 ; Is tx buffer full?
|
||||
BEQ ACIAout_w ; if not, loop back
|
||||
PLA ; Otherwise, restore accumulator
|
||||
STA ACIAdata ; write byte to 6551
|
||||
RTS
|
||||
|
||||
; byte in from simulated ACIA
|
||||
|
||||
ACIAin
|
||||
LDA ACIAstatus ; Read 6551 status
|
||||
AND #$08 ;
|
||||
BEQ LAB_nobyw ; If rx buffer empty, no byte
|
||||
|
||||
LDA ACIAdata ; Read byte from 6551
|
||||
SEC ; Flag byte received
|
||||
RTS
|
||||
|
||||
LAB_nobyw
|
||||
CLC ; flag no byte received
|
||||
no_load ; empty load vector for EhBASIC
|
||||
no_save ; empty save vector for EhBASIC
|
||||
RTS
|
||||
|
||||
; vector tables
|
||||
|
||||
LAB_vec
|
||||
.word ACIAin ; byte in from simulated ACIA
|
||||
.word ACIAout ; byte out to simulated ACIA
|
||||
.word no_load ; null load vector for EhBASIC
|
||||
.word no_save ; null save vector for EhBASIC
|
||||
|
||||
; EhBASIC IRQ support
|
||||
|
||||
IRQ_CODE
|
||||
PHA ; save A
|
||||
LDA IrqBase ; get the IRQ flag byte
|
||||
LSR ; shift the set b7 to b6, and on down ...
|
||||
ORA IrqBase ; OR the original back in
|
||||
STA IrqBase ; save the new IRQ flag byte
|
||||
PLA ; restore A
|
||||
RTI
|
||||
|
||||
; EhBASIC NMI support
|
||||
|
||||
NMI_CODE
|
||||
PHA ; save A
|
||||
LDA NmiBase ; get the NMI flag byte
|
||||
LSR ; shift the set b7 to b6, and on down ...
|
||||
ORA NmiBase ; OR the original back in
|
||||
STA NmiBase ; save the new NMI flag byte
|
||||
PLA ; restore A
|
||||
RTI
|
||||
|
||||
END_CODE
|
||||
|
||||
LAB_mess
|
||||
.byte $0D,$0A,"6502 EhBASIC [C]old/[W]arm ?",$00
|
||||
; sign on string
|
||||
|
||||
; system vectors
|
||||
|
||||
.segment "VECTORS"
|
||||
.org $FFFA
|
||||
|
||||
.word 0 ; NMI vector
|
||||
.word RES_vec ; RESET vector
|
||||
.word 0 ; IRQ vector
|
||||
|
18
samples/ehbasic/symon.config
Normal file
18
samples/ehbasic/symon.config
Normal file
@ -0,0 +1,18 @@
|
||||
MEMORY {
|
||||
RAM1: start = $0000, size = $C000;
|
||||
ROM1: start = $D000, size = $2F00, fill = yes;
|
||||
MONITOR: start = $FF00, size = $FA, fill = yes;
|
||||
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
|
||||
}
|
||||
|
||||
SEGMENTS {
|
||||
CODE: load = ROM1, type = ro;
|
||||
DATA: load = ROM1, type = ro;
|
||||
MONITOR: load = MONITOR, type = ro;
|
||||
VECTORS: load = ROMV, type = ro;
|
||||
}
|
||||
|
||||
SYMBOLS {
|
||||
__STACKSIZE__ = $0300;
|
||||
}
|
||||
|
@ -42,9 +42,10 @@ public class Bus {
|
||||
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.");
|
||||
throw new MemoryRangeException("The device being added at " +
|
||||
String.format("$%04X", memRange.startAddress()) +
|
||||
" overlaps with an existing " +
|
||||
"device, '" + d + "'");
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +115,7 @@ public class Bus {
|
||||
return d.read(devAddr);
|
||||
}
|
||||
}
|
||||
throw new MemoryAccessException("Read failed! No device at address.");
|
||||
throw new MemoryAccessException("Bus read failed. No device at address " + String.format("$%04X", address));
|
||||
}
|
||||
|
||||
public void write(int address, int value) throws MemoryAccessException {
|
||||
@ -127,7 +128,7 @@ public class Bus {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new MemoryAccessException("Write failed! No device at address.");
|
||||
throw new MemoryAccessException("Bus write failed. No device at address " + String.format("$%04X", address));
|
||||
}
|
||||
|
||||
public SortedSet<Device> getDevices() {
|
||||
|
@ -29,6 +29,10 @@ public class Cpu implements InstructionTable {
|
||||
public static final int NMI_VECTOR_L = 0xfffe;
|
||||
public static final int NMI_VECTOR_H = 0xffff;
|
||||
|
||||
// The delay in microseconds between steps.
|
||||
// TODO: Make configurable
|
||||
private static final int CLOCK_IN_NS = 1000;
|
||||
|
||||
/* The Bus */
|
||||
private Bus bus;
|
||||
|
||||
@ -41,11 +45,13 @@ public class Cpu implements InstructionTable {
|
||||
private int pc; // Program Counter register
|
||||
private int sp; // Stack Pointer register, offset into page 1
|
||||
private int ir; // Instruction register
|
||||
|
||||
private int lastPc; // The program counter before the most recent step.
|
||||
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 */
|
||||
* 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;
|
||||
@ -435,8 +441,8 @@ public class Cpu implements InstructionTable {
|
||||
/** BIT - Bit Test ******************************************************/
|
||||
case 0x24: // Zero Page
|
||||
case 0x2c: // Absolute
|
||||
tmp = a & bus.read(effectiveAddress);
|
||||
setZeroFlag(tmp == 0);
|
||||
tmp = bus.read(effectiveAddress);
|
||||
setZeroFlag((a & tmp) == 0);
|
||||
setNegativeFlag((tmp & 0x80) != 0);
|
||||
setOverflowFlag((tmp & 0x40) != 0);
|
||||
break;
|
||||
@ -553,7 +559,6 @@ public class Cpu implements InstructionTable {
|
||||
case 0x99: // Absolute,Y
|
||||
case 0x9d: // Absolute,X
|
||||
bus.write(effectiveAddress, a);
|
||||
setArithmeticFlags(a);
|
||||
break;
|
||||
|
||||
|
||||
@ -562,7 +567,6 @@ public class Cpu implements InstructionTable {
|
||||
case 0x8c: // Absolute
|
||||
case 0x94: // Zero Page,X
|
||||
bus.write(effectiveAddress, y);
|
||||
setArithmeticFlags(y);
|
||||
break;
|
||||
|
||||
|
||||
@ -571,7 +575,6 @@ public class Cpu implements InstructionTable {
|
||||
case 0x8e: // Absolute
|
||||
case 0x96: // Zero Page,Y
|
||||
bus.write(effectiveAddress, x);
|
||||
setArithmeticFlags(x);
|
||||
break;
|
||||
|
||||
|
||||
@ -704,6 +707,8 @@ public class Cpu implements InstructionTable {
|
||||
setOpTrap();
|
||||
break;
|
||||
}
|
||||
|
||||
delayLoop(ir);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -715,7 +720,7 @@ public class Cpu implements InstructionTable {
|
||||
* @param operand The operand
|
||||
* @return
|
||||
*/
|
||||
public int adc(int acc, int operand) {
|
||||
private int adc(int acc, int operand) {
|
||||
int result = (operand & 0xff) + (acc & 0xff) + getCarryBit();
|
||||
int carry6 = (operand & 0x7f) + (acc & 0x7f) + getCarryBit();
|
||||
setCarryFlag((result & 0x100) != 0);
|
||||
@ -729,7 +734,7 @@ public class Cpu implements InstructionTable {
|
||||
* Add with Carry (BCD).
|
||||
*/
|
||||
|
||||
public int adcDecimal(int acc, int operand) {
|
||||
private int adcDecimal(int acc, int operand) {
|
||||
int l, h, result;
|
||||
l = (acc & 0x0f) + (operand & 0x0f) + getCarryBit();
|
||||
if ((l & 0xff) > 9) l += 6;
|
||||
@ -748,12 +753,8 @@ public class Cpu implements InstructionTable {
|
||||
* 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) {
|
||||
private int sbc(int acc, int operand) {
|
||||
int result;
|
||||
result = adc(acc, ~operand);
|
||||
setArithmeticFlags(result);
|
||||
@ -762,12 +763,8 @@ public class Cpu implements InstructionTable {
|
||||
|
||||
/**
|
||||
* Subtract with Carry, BCD mode.
|
||||
*
|
||||
* @param acc
|
||||
* @param operand
|
||||
* @return
|
||||
*/
|
||||
public int sbcDecimal(int acc, int operand) {
|
||||
private int sbcDecimal(int acc, int operand) {
|
||||
int l, h, result;
|
||||
l = (acc & 0x0f) - (operand & 0x0f) - (carryFlag ? 0 : 1);
|
||||
if ((l & 0x10) != 0) l -= 6;
|
||||
@ -784,23 +781,19 @@ public class Cpu implements InstructionTable {
|
||||
/**
|
||||
* Compare two values, and set carry, zero, and negative flags
|
||||
* appropriately.
|
||||
*
|
||||
* @param reg
|
||||
* @param operand
|
||||
*/
|
||||
public void cmp(int reg, int operand) {
|
||||
private void cmp(int reg, int operand) {
|
||||
int tmp = (reg - operand) & 0xff;
|
||||
setCarryFlag(reg >= operand);
|
||||
setZeroFlag(reg == operand);
|
||||
setNegativeFlag((reg - operand) > 0);
|
||||
setZeroFlag(tmp == 0);
|
||||
setNegativeFlag((tmp & 0x80) != 0); // Negative bit set
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Negative and Zero flags based on the current value of the
|
||||
* register operand.
|
||||
*
|
||||
* @param reg The register.
|
||||
*/
|
||||
public void setArithmeticFlags(int reg) {
|
||||
private void setArithmeticFlags(int reg) {
|
||||
zeroFlag = (reg == 0);
|
||||
negativeFlag = (reg & 0x80) != 0;
|
||||
}
|
||||
@ -1134,6 +1127,10 @@ public class Cpu implements InstructionTable {
|
||||
this.y = val;
|
||||
}
|
||||
|
||||
public int getLastProgramCounter() {
|
||||
return lastPc;
|
||||
}
|
||||
|
||||
public int getProgramCounter() {
|
||||
return pc;
|
||||
}
|
||||
@ -1285,15 +1282,15 @@ public class Cpu implements InstructionTable {
|
||||
*/
|
||||
public String toString() {
|
||||
String opcode = opcode(ir, args[0], args[1]);
|
||||
StringBuffer sb = new StringBuffer(String.format("$%04X", addr) +
|
||||
" ");
|
||||
StringBuffer sb = new StringBuffer(getInstructionByteStatus());
|
||||
sb.append(" ");
|
||||
sb.append(String.format("%-14s", opcode));
|
||||
sb.append("A=" + getAccumulatorStatus() + " ");
|
||||
sb.append("X=" + getXRegisterStatus() + " ");
|
||||
sb.append("Y=" + getYRegisterStatus() + " ");
|
||||
sb.append("PC=" + getProgramCounterStatus() + " ");
|
||||
sb.append("SP=" + getStackPointerStatus() + " ");
|
||||
sb.append("P=" + getProcessorStatusString());
|
||||
sb.append("A:" + String.format("%02x", a) + " ");
|
||||
sb.append("X:" + String.format("%02x", x) + " ");
|
||||
sb.append("Y:" + String.format("%02x", y) + " ");
|
||||
sb.append("F:" + String.format("%02x", getProcessorStatus()) + " ");
|
||||
sb.append("S:" + String.format("1%02x", sp) + " ");
|
||||
sb.append(getProcessorStatusString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@ -1397,6 +1394,19 @@ public class Cpu implements InstructionTable {
|
||||
bus.write(RST_VECTOR_L, address & 0x00ff);
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform a busy-loop for CLOCK_IN_NS nanoseconds
|
||||
*/
|
||||
private void delayLoop(int opcode) {
|
||||
int clockSteps = Cpu.instructionClocks[0xff & opcode];
|
||||
// Just a precaution. This could be better.
|
||||
if (clockSteps == 0) { clockSteps = 1; }
|
||||
long startTime = System.nanoTime();
|
||||
long stopTime = startTime + (CLOCK_IN_NS * clockSteps);
|
||||
// Busy loop
|
||||
while (System.nanoTime() < stopTime) { ; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an opcode and its operands, return a formatted name.
|
||||
*
|
||||
@ -1430,10 +1440,10 @@ public class Cpu implements InstructionTable {
|
||||
sb.append(String.format(" ($%04X)", address(op1, op2)));
|
||||
break;
|
||||
case XIN:
|
||||
sb.append(String.format(" ($%02X),X", op1));
|
||||
sb.append(String.format(" ($%02X,X)", op1));
|
||||
break;
|
||||
case INY:
|
||||
sb.append(String.format(" ($%02X,Y)", op1));
|
||||
sb.append(String.format(" ($%02X),Y", op1));
|
||||
break;
|
||||
case REL:
|
||||
case ZPG:
|
||||
@ -1449,4 +1459,19 @@ public class Cpu implements InstructionTable {
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public String getInstructionByteStatus() {
|
||||
switch (Cpu.instructionSizes[ir]) {
|
||||
case 0:
|
||||
case 1:
|
||||
return String.format("%04X %02X ", addr, ir);
|
||||
case 2:
|
||||
return String.format("%04X %02X %02X ", addr, ir, args[0]);
|
||||
case 3:
|
||||
return String.format("%04X %02X %02X %02X", addr, ir, args[0], args[1]);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ public interface InstructionTable {
|
||||
"TYA", "STA", "TXS", null, null, "STA", null, null,
|
||||
"LDY", "LDA", "LDX", null, "LDY", "LDA", "LDX", null,
|
||||
"TAY", "LDA", "TAX", null, "LDY", "LDA", "LDX", null,
|
||||
null, "LDA", null, 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,
|
||||
@ -173,13 +173,13 @@ public interface InstructionTable {
|
||||
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.ABX, Mode.IMP, Mode.NUL, // 0x98-0x9b
|
||||
Mode.NUL, Mode.ABY, Mode.NUL, Mode.NUL, // 0x9c-0x9f
|
||||
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.NUL, Mode.INY, Mode.NUL, Mode.NUL, // 0xb0-0xb3
|
||||
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
|
||||
@ -239,11 +239,11 @@ public interface InstructionTable {
|
||||
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,
|
||||
0, 5, 0, 0, 4, 4, 4, 0, 2, 4, 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
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.loomcom.symon;
|
||||
|
||||
import com.loomcom.symon.devices.Acia;
|
||||
import com.loomcom.symon.devices.Memory;
|
||||
import com.loomcom.symon.exceptions.FifoUnderrunException;
|
||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||
import com.loomcom.symon.exceptions.SymonException;
|
||||
@ -14,7 +15,6 @@ import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import java.util.logging.Level;
|
||||
@ -27,16 +27,16 @@ public class Simulator implements ActionListener, Observer {
|
||||
private static final int BUS_BOTTOM = 0x0000;
|
||||
private static final int BUS_TOP = 0xffff;
|
||||
|
||||
// 32K of RAM
|
||||
private static final int MEMORY_BASE = 0x0000;
|
||||
private static final int MEMORY_SIZE = 0xc000; // 48 KB
|
||||
private static final int MEMORY_SIZE = 0xC000;
|
||||
|
||||
private static final int ROM_BASE = 0xe000;
|
||||
private static final int ROM_SIZE = 0x2000; // 8 KB
|
||||
// IO area at $D000
|
||||
private static final int ACIA_BASE = 0xC000;
|
||||
|
||||
private static final int ACIA_BASE = 0xc000;
|
||||
|
||||
// The delay in microseconds between steps.
|
||||
private static final int DELAY_BETWEEN_STEPS_NS = 1000;
|
||||
// 8KB ROM at E000-FFFF
|
||||
private static final int ROM_BASE = 0xD000;
|
||||
private static final int ROM_SIZE = 0x3000;
|
||||
|
||||
// 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
|
||||
@ -84,18 +84,26 @@ public class Simulator implements ActionListener, Observer {
|
||||
private JFileChooser fileChooser;
|
||||
private PreferencesDialog preferences;
|
||||
|
||||
public Simulator() throws MemoryRangeException {
|
||||
public Simulator() throws MemoryRangeException, IOException {
|
||||
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, of course!
|
||||
this.rom = new Memory(ROM_BASE, ROM_SIZE, false);
|
||||
// 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.addCpu(cpu);
|
||||
bus.addDevice(acia);
|
||||
bus.addDevice(ram);
|
||||
bus.addDevice(acia);
|
||||
bus.addDevice(rom);
|
||||
}
|
||||
|
||||
@ -212,7 +220,7 @@ public class Simulator implements ActionListener, Observer {
|
||||
*/
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
if (actionEvent.getSource() == resetButton) {
|
||||
handleReset();
|
||||
coldReset();
|
||||
} else if (actionEvent.getSource() == stepButton) {
|
||||
handleStep();
|
||||
} else if (actionEvent.getSource() == runStopButton) {
|
||||
@ -274,7 +282,7 @@ public class Simulator implements ActionListener, Observer {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReset() {
|
||||
private void coldReset() {
|
||||
if (runLoop != null && runLoop.isRunning()) {
|
||||
runLoop.requestStop();
|
||||
runLoop.interrupt();
|
||||
@ -282,7 +290,7 @@ public class Simulator implements ActionListener, Observer {
|
||||
}
|
||||
|
||||
try {
|
||||
logger.log(Level.INFO, "Reset requested. Resetting CPU and clearing memory.");
|
||||
logger.log(Level.INFO, "Cold reset requested. Resetting CPU and clearing memory.");
|
||||
// Reset and clear memory
|
||||
cpu.reset();
|
||||
ram.fill(0x00);
|
||||
@ -307,6 +315,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() {
|
||||
@ -337,8 +346,6 @@ public class Simulator implements ActionListener, Observer {
|
||||
*/
|
||||
private void step() throws MemoryAccessException {
|
||||
|
||||
delayLoop();
|
||||
|
||||
cpu.step();
|
||||
|
||||
// Read from the ACIA and immediately update the console if there's
|
||||
@ -351,8 +358,12 @@ public class Simulator implements ActionListener, Observer {
|
||||
|
||||
// If a key has been pressed, fill the ACIA.
|
||||
// TODO: Interrupt handling.
|
||||
if (console.hasInput()) {
|
||||
acia.rxWrite((int)console.readInputChar());
|
||||
try {
|
||||
if (console.hasInput()) {
|
||||
acia.rxWrite((int)console.readInputChar());
|
||||
}
|
||||
} catch (FifoUnderrunException ex) {
|
||||
logger.severe("Console type-ahead buffer underrun!");
|
||||
}
|
||||
|
||||
// This is a very expensive update, and we're doing it without
|
||||
@ -374,8 +385,6 @@ public class Simulator implements ActionListener, Observer {
|
||||
* Load a program into memory at the simulatorDidStart address.
|
||||
*/
|
||||
private void loadProgram(byte[] program, int startAddress) throws MemoryAccessException {
|
||||
cpu.setResetVector(startAddress);
|
||||
|
||||
int addr = startAddress, i;
|
||||
for (i = 0; i < program.length; i++) {
|
||||
bus.write(addr++, program[i] & 0xff);
|
||||
@ -388,6 +397,9 @@ public class Simulator implements ActionListener, Observer {
|
||||
// Reset (but don't clear memory, naturally)
|
||||
cpu.reset();
|
||||
|
||||
// Reset the stack program counter
|
||||
cpu.setProgramCounter(preferences.getProgramStartAddress());
|
||||
|
||||
// Immediately update the UI.
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
@ -405,8 +417,8 @@ public class Simulator implements ActionListener, Observer {
|
||||
Simulator simulator = new Simulator();
|
||||
simulator.createAndShowUi();
|
||||
// Reset the simulator.
|
||||
simulator.handleReset();
|
||||
} catch (MemoryRangeException e) {
|
||||
simulator.coldReset();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@ -433,15 +445,6 @@ public class Simulator implements ActionListener, Observer {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform a busy-loop for DELAY_BETWEEN_STEPS_NS nanoseconds
|
||||
*/
|
||||
private void delayLoop() {
|
||||
long startTime = System.nanoTime();
|
||||
long stopTime = startTime + DELAY_BETWEEN_STEPS_NS;
|
||||
// Busy loop
|
||||
while (System.nanoTime() < stopTime) { ; }
|
||||
}
|
||||
|
||||
/**
|
||||
* The main run thread.
|
||||
@ -496,4 +499,4 @@ public class Simulator implements ActionListener, Observer {
|
||||
isRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
package com.loomcom.symon.devices;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import com.loomcom.symon.exceptions.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class Memory extends Device {
|
||||
|
||||
private boolean readOnly;
|
||||
@ -24,6 +27,17 @@ public class Memory extends Device {
|
||||
this(address, size, false);
|
||||
}
|
||||
|
||||
public static Memory makeROM(int address, int size, File f) throws MemoryRangeException, IOException {
|
||||
Memory memory = new Memory(address, size, true);
|
||||
memory.loadFromFile(f);
|
||||
return memory;
|
||||
}
|
||||
|
||||
public static Memory makeRAM(int address, int size) throws MemoryRangeException {
|
||||
Memory memory = new Memory(address, size, false);
|
||||
return memory;
|
||||
}
|
||||
|
||||
public void write(int address, int data) throws MemoryAccessException {
|
||||
if (readOnly) {
|
||||
throw new MemoryAccessException("Cannot write to read-only memory at address " + address);
|
||||
@ -32,6 +46,34 @@ public class Memory extends Device {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the memory from a file.
|
||||
*
|
||||
* @param file The file to read an array of bytes from.
|
||||
* @throws MemoryRangeException if the file and memory size do not match.
|
||||
* @throws IOException if the file read fails.
|
||||
*/
|
||||
public void loadFromFile(File file) throws MemoryRangeException, IOException {
|
||||
if (file.canRead()) {
|
||||
long fileSize = file.length();
|
||||
|
||||
if (fileSize > mem.length) {
|
||||
throw new MemoryRangeException("File will not fit in available memory.");
|
||||
} else {
|
||||
int i = 0;
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
BufferedInputStream bis = new BufferedInputStream(fis);
|
||||
DataInputStream dis = new DataInputStream(bis);
|
||||
while (dis.available() != 0) {
|
||||
mem[i++] = dis.readUnsignedByte();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Cannot open file " + file);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public int read(int address) throws MemoryAccessException {
|
||||
return this.mem[address];
|
||||
}
|
||||
@ -40,7 +82,6 @@ public class Memory extends Device {
|
||||
Arrays.fill(this.mem, val);
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
return "Memory: " + getMemoryRange().toString();
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import java.awt.event.MouseListener;
|
||||
|
||||
import com.grahamedgecombe.jterminal.JTerminal;
|
||||
import com.grahamedgecombe.jterminal.vt100.Vt100TerminalModel;
|
||||
import com.loomcom.symon.exceptions.FifoUnderrunException;
|
||||
import com.loomcom.symon.util.FifoRingBuffer;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.BevelBorder;
|
||||
@ -22,16 +24,22 @@ 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;
|
||||
// If true, send CRLF (0x0d 0x0a) whenever CR is typed
|
||||
private static final boolean SEND_CR_LF_FOR_CR = false;
|
||||
|
||||
private boolean hasInput = false;
|
||||
private char keyBuffer;
|
||||
private FifoRingBuffer typeAheadBuffer;
|
||||
|
||||
public Console() {
|
||||
this(DEFAULT_COLUMNS, DEFAULT_ROWS);
|
||||
}
|
||||
|
||||
public Console(int columns, int rows) {
|
||||
public Console(int columns, int rows) {
|
||||
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);
|
||||
setBorderWidth(DEFAULT_BORDER_WIDTH);
|
||||
addKeyListener(this);
|
||||
addMouseListener(this);
|
||||
@ -39,7 +47,7 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
|
||||
Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.LOWERED);
|
||||
Border compoundBorder = BorderFactory.createCompoundBorder(emptyBorder, bevelBorder);
|
||||
this.setBorder(compoundBorder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the console. This will cause the console to be cleared and the cursor returned to the
|
||||
@ -47,11 +55,11 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
|
||||
*
|
||||
*/
|
||||
public void reset() {
|
||||
typeAheadBuffer.reset();
|
||||
getModel().clear();
|
||||
getModel().setCursorColumn(0);
|
||||
getModel().setCursorRow(0);
|
||||
repaint();
|
||||
this.hasInput = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,7 +68,7 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
|
||||
* @return
|
||||
*/
|
||||
public boolean hasInput() {
|
||||
return hasInput;
|
||||
return !typeAheadBuffer.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,6 +77,24 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
|
||||
* @param keyEvent The key event.
|
||||
*/
|
||||
public void keyTyped(KeyEvent keyEvent) {
|
||||
char keyTyped = keyEvent.getKeyChar();
|
||||
|
||||
if (SWAP_CR_AND_LF) {
|
||||
if (keyTyped == 0x0a) {
|
||||
keyTyped = 0x0d;
|
||||
}
|
||||
else if (keyTyped == 0x0d) {
|
||||
keyTyped = 0x0a;
|
||||
}
|
||||
}
|
||||
|
||||
if (SEND_CR_LF_FOR_CR && keyTyped == 0x0d) {
|
||||
typeAheadBuffer.push(0x0d);
|
||||
typeAheadBuffer.push(0x0a);
|
||||
} else {
|
||||
typeAheadBuffer.push(keyTyped);
|
||||
}
|
||||
|
||||
keyEvent.consume();
|
||||
}
|
||||
|
||||
@ -78,8 +104,6 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
|
||||
* @param keyEvent The key event.
|
||||
*/
|
||||
public void keyPressed(KeyEvent keyEvent) {
|
||||
keyBuffer = keyEvent.getKeyChar();
|
||||
hasInput = true;
|
||||
keyEvent.consume();
|
||||
}
|
||||
|
||||
@ -88,9 +112,8 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
|
||||
*
|
||||
* @return The character typed.
|
||||
*/
|
||||
public char readInputChar() {
|
||||
hasInput = false;
|
||||
return this.keyBuffer;
|
||||
public char readInputChar() throws FifoUnderrunException {
|
||||
return (char)typeAheadBuffer.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -412,7 +412,8 @@ public class CpuAbsoluteYModeTest extends TestCase {
|
||||
bus.loadProgram(0x99, 0x10, 0xab); // STA $ab10,Y
|
||||
cpu.step();
|
||||
assertEquals(0x00, bus.read(0xab40));
|
||||
assertTrue(cpu.getZeroFlag());
|
||||
// STA should have NO effect on flags
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
|
||||
cpu.reset();
|
||||
@ -434,7 +435,7 @@ public class CpuAbsoluteYModeTest extends TestCase {
|
||||
cpu.step();
|
||||
assertEquals(0x80, bus.read(0xab40));
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertTrue(cpu.getNegativeFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
}
|
||||
|
||||
/* LDX - Load X Register - $be */
|
||||
@ -523,7 +524,7 @@ public class CpuAbsoluteYModeTest extends TestCase {
|
||||
cpu.step();
|
||||
assertFalse(cpu.getCarryFlag()); // m < y
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag()); // m - y > 0
|
||||
assertTrue(cpu.getNegativeFlag()); // $80 - $ff = $81
|
||||
}
|
||||
|
||||
/* SBC - Subtract with Carry - $f9 */
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -412,7 +412,7 @@ public class CpuIndirectXModeTest extends TestCase {
|
||||
bus.loadProgram(0x9d, 0x10, 0xab); // STA $ab10,X
|
||||
cpu.step();
|
||||
assertEquals(0x00, bus.read(0xab40));
|
||||
assertTrue(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
|
||||
cpu.reset();
|
||||
@ -434,7 +434,7 @@ public class CpuIndirectXModeTest extends TestCase {
|
||||
cpu.step();
|
||||
assertEquals(0x80, bus.read(0xab40));
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertTrue(cpu.getNegativeFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
}
|
||||
|
||||
/* LDA - Load Accumulator - $bd */
|
||||
@ -494,7 +494,7 @@ public class CpuIndirectXModeTest extends TestCase {
|
||||
cpu.step();
|
||||
assertFalse(cpu.getCarryFlag()); // m < y
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag()); // m - y > 0
|
||||
assertTrue(cpu.getNegativeFlag()); // m - y < 0
|
||||
}
|
||||
|
||||
/* SBC - Subtract with Carry - $fd */
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -709,7 +709,8 @@ public class CpuZeroPageXModeTest extends TestCase {
|
||||
bus.loadProgram(0x94, 0x10); // STY $10,X
|
||||
cpu.step();
|
||||
assertEquals(0x00, bus.read(0x40));
|
||||
assertTrue(cpu.getZeroFlag());
|
||||
// Should have no effect on flags.
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
|
||||
cpu.reset();
|
||||
@ -730,7 +731,7 @@ public class CpuZeroPageXModeTest extends TestCase {
|
||||
cpu.step();
|
||||
assertEquals(0x80, bus.read(0x40));
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertTrue(cpu.getNegativeFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
}
|
||||
|
||||
/* STA - Store Accumulator - $95 */
|
||||
@ -742,7 +743,8 @@ public class CpuZeroPageXModeTest extends TestCase {
|
||||
bus.loadProgram(0x95, 0x10); // STA $10,X
|
||||
cpu.step();
|
||||
assertEquals(0x00, bus.read(0x40));
|
||||
assertTrue(cpu.getZeroFlag());
|
||||
// Should have no effect on flags.
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
|
||||
cpu.reset();
|
||||
@ -763,7 +765,7 @@ public class CpuZeroPageXModeTest extends TestCase {
|
||||
cpu.step();
|
||||
assertEquals(0x80, bus.read(0x40));
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertTrue(cpu.getNegativeFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
}
|
||||
|
||||
/* LDY - Load Y Register - $b4 */
|
||||
@ -852,7 +854,7 @@ public class CpuZeroPageXModeTest extends TestCase {
|
||||
cpu.step();
|
||||
assertFalse(cpu.getCarryFlag()); // m < y
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag()); // m - y > 0
|
||||
assertTrue(cpu.getNegativeFlag()); // m - y < 0
|
||||
}
|
||||
|
||||
/* DEC - Decrement Memory Location - $d6 */
|
||||
|
@ -7,96 +7,97 @@ import junit.framework.TestCase;
|
||||
|
||||
public class CpuZeroPageYModeTest 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, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
|
||||
bus.write(0xfffd, (Cpu.DEFAULT_BASE_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:
|
||||
*
|
||||
* STX - $96
|
||||
* LDX - $b6
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* The following opcodes are tested for correctness in this file:
|
||||
*
|
||||
* STX - $96
|
||||
* LDX - $b6
|
||||
*
|
||||
*/
|
||||
|
||||
/* STX - Store X Register - $96 */
|
||||
/* STX - Store X Register - $96 */
|
||||
|
||||
public void test_STX() throws MemoryAccessException {
|
||||
cpu.setYRegister(0x30);
|
||||
cpu.setXRegister(0x00);
|
||||
bus.loadProgram(0x96, 0x10); // STX $10,Y
|
||||
cpu.step();
|
||||
assertEquals(0x00, bus.read(0x40));
|
||||
assertTrue(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
public void test_STX() throws MemoryAccessException {
|
||||
cpu.setYRegister(0x30);
|
||||
cpu.setXRegister(0x00);
|
||||
bus.loadProgram(0x96, 0x10); // STX $10,Y
|
||||
cpu.step();
|
||||
assertEquals(0x00, bus.read(0x40));
|
||||
// Should have no effect on flags.
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
|
||||
cpu.reset();
|
||||
cpu.setYRegister(0x30);
|
||||
cpu.setXRegister(0x0f);
|
||||
bus.loadProgram(0x96, 0x10); // STX $10,Y
|
||||
cpu.step();
|
||||
assertEquals(0x0f, bus.read(0x40));
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
cpu.reset();
|
||||
cpu.setYRegister(0x30);
|
||||
cpu.setXRegister(0x0f);
|
||||
bus.loadProgram(0x96, 0x10); // STX $10,Y
|
||||
cpu.step();
|
||||
assertEquals(0x0f, bus.read(0x40));
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
|
||||
cpu.reset();
|
||||
cpu.setYRegister(0x30);
|
||||
cpu.setXRegister(0x80);
|
||||
bus.loadProgram(0x96, 0x10); // STX $10,Y
|
||||
cpu.step();
|
||||
assertEquals(0x80, bus.read(0x40));
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertTrue(cpu.getNegativeFlag());
|
||||
}
|
||||
cpu.reset();
|
||||
cpu.setYRegister(0x30);
|
||||
cpu.setXRegister(0x80);
|
||||
bus.loadProgram(0x96, 0x10); // STX $10,Y
|
||||
cpu.step();
|
||||
assertEquals(0x80, bus.read(0x40));
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
}
|
||||
|
||||
/* LDX - Load X Register - $b6 */
|
||||
/* LDX - Load X Register - $b6 */
|
||||
|
||||
public void test_LDX() throws MemoryAccessException {
|
||||
bus.write(0x40, 0x00);
|
||||
bus.write(0x41, 0x0f);
|
||||
bus.write(0x42, 0x80);
|
||||
public void test_LDX() throws MemoryAccessException {
|
||||
bus.write(0x40, 0x00);
|
||||
bus.write(0x41, 0x0f);
|
||||
bus.write(0x42, 0x80);
|
||||
|
||||
bus.loadProgram(0xb6, 0x10,
|
||||
0xb6, 0x11,
|
||||
0xb6, 0x12);
|
||||
bus.loadProgram(0xb6, 0x10,
|
||||
0xb6, 0x11,
|
||||
0xb6, 0x12);
|
||||
|
||||
cpu.setYRegister(0x30);
|
||||
cpu.setYRegister(0x30);
|
||||
|
||||
cpu.step();
|
||||
assertEquals(0x00, cpu.getXRegister());
|
||||
assertTrue(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
cpu.step();
|
||||
assertEquals(0x00, cpu.getXRegister());
|
||||
assertTrue(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
|
||||
cpu.step();
|
||||
assertEquals(0x0f, cpu.getXRegister());
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
cpu.step();
|
||||
assertEquals(0x0f, cpu.getXRegister());
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertFalse(cpu.getNegativeFlag());
|
||||
|
||||
cpu.step();
|
||||
assertEquals(0x80, cpu.getXRegister());
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertTrue(cpu.getNegativeFlag());
|
||||
}
|
||||
cpu.step();
|
||||
assertEquals(0x80, cpu.getXRegister());
|
||||
assertFalse(cpu.getZeroFlag());
|
||||
assertTrue(cpu.getNegativeFlag());
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user