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:
1 parent
c1caf8c6b4
commit
795ccfde5d
25 files changed
+13622
-4508
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
The MIT License
|
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
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
a copy of this software and associated documentation files (the
|
a copy of this software and associated documentation files (the
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
SYMON - A 6502 System Simulator
|
SYMON - A 6502 System Simulator
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
**NOTE: THIS IS ALPHA QUALITY SOFTWARE UNDER ACTIVE DEVELOPMENT. IT IS
|
**NOTE: THIS IS BETA QUALITY SOFTWARE UNDER ACTIVE DEVELOPMENT. Feedback is
|
||||||
NOT YET FULLY FUNCTIONAL. IT MAY BE USEFUL, BUT IT IS NOT YET INTENDED
|
welcome!**
|
||||||
TO BE USED BY ANYONE BUT DEVELOPERS. Feedback is welcome!**
|
|
||||||
|
|
||||||
**Version:** 0.3
|
**Version:** 0.5
|
||||||
**Last Updated:** 14 October, 2012
|
**Last Updated:** 21 October, 2012
|
||||||
|
|
||||||
Copyright (c) 2008-2012 Seth J. Morabito <web@loomcom.com>
|
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.
|
- '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.
|
running at address $0300.
|
||||||
|
|
||||||
|
|
||||||
## 4.0 To Do
|
## 4.0 To Do
|
||||||
|
|
||||||
- Accurate timing (all simulated instructions currently take
|
- Better debugging tools from the UI, including memory inspection,
|
||||||
only one step to execute)
|
disassembly, breakpoints, and execution tracing.
|
||||||
|
|
||||||
|
- Better ROM loading (and re-loading)
|
||||||
|
|
||||||
|
- More accurate timing.
|
||||||
|
|
||||||
- Interrupt handling!
|
- Interrupt handling!
|
||||||
|
|
||||||
- UI needs a ton more polish.
|
- UI needs a ton more polish.
|
||||||
|
|
||||||
- Add a simple menu interface for common tasks.
|
|
||||||
|
|
||||||
- More extensive testing.
|
- More extensive testing.
|
||||||
|
|
||||||
- Clean up JavaDoc.
|
- Clean up JavaDoc.
|
||||||
|
|||||||
+34
-4
@@ -1,9 +1,39 @@
|
|||||||
A Sample Program
|
Sample Programs
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
When loaded at address $0300, this program will print "Hello, 6502 World!" in
|
1. hello.prg
|
||||||
infinite loop.
|
|
||||||
|
|
||||||
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/
|
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
|
||||||
+2
-2
@@ -27,9 +27,9 @@ getkey: lda iostatus ; Read the ACIA status
|
|||||||
;; Write the current char in the accumulator to the console
|
;; Write the current char in the accumulator to the console
|
||||||
|
|
||||||
write: pha ; Save accumulator
|
write: pha ; Save accumulator
|
||||||
lda iostatus ; Read the ACIA status
|
writel: lda iostatus ; Read the ACIA status
|
||||||
and #$10 ; Is the tx register empty?
|
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
|
pla ; Otherwise, load saved accumulator
|
||||||
sta iobase ; and write to output.
|
sta iobase ; and write to output.
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,4 @@
|
|||||||
|
*.rom
|
||||||
|
*.lst
|
||||||
|
*.map
|
||||||
|
*.o
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||||
|
|
||||||
@@ -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();
|
MemoryRange memRange = device.getMemoryRange();
|
||||||
for (Device d : devices) {
|
for (Device d : devices) {
|
||||||
if (d.getMemoryRange().overlaps(memRange)) {
|
if (d.getMemoryRange().overlaps(memRange)) {
|
||||||
throw new MemoryRangeException("The device being added " +
|
throw new MemoryRangeException("The device being added at " +
|
||||||
"overlaps with an existing " +
|
String.format("$%04X", memRange.startAddress()) +
|
||||||
"device.");
|
" overlaps with an existing " +
|
||||||
|
"device, '" + d + "'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +115,7 @@ public class Bus {
|
|||||||
return d.read(devAddr);
|
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 {
|
public void write(int address, int value) throws MemoryAccessException {
|
||||||
@@ -127,7 +128,7 @@ public class Bus {
|
|||||||
return;
|
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() {
|
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_L = 0xfffe;
|
||||||
public static final int NMI_VECTOR_H = 0xffff;
|
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 */
|
/* The Bus */
|
||||||
private Bus bus;
|
private Bus bus;
|
||||||
|
|
||||||
@@ -41,11 +45,13 @@ public class Cpu implements InstructionTable {
|
|||||||
private int pc; // Program Counter register
|
private int pc; // Program Counter register
|
||||||
private int sp; // Stack Pointer register, offset into page 1
|
private int sp; // Stack Pointer register, offset into page 1
|
||||||
private int ir; // Instruction register
|
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[] args = new int[3]; // Decoded instruction args
|
||||||
private int instSize; // # of operands for the instruction
|
private int instSize; // # of operands for the instruction
|
||||||
|
|
||||||
/* Scratch space for addressing mode and effective address
|
/* Scratch space for addressing mode and effective address
|
||||||
* calculations */
|
* calculations */
|
||||||
private int irAddressMode; // Bits 3-5 of IR: [ | | |X|X|X| | ]
|
private int irAddressMode; // Bits 3-5 of IR: [ | | |X|X|X| | ]
|
||||||
private int irOpMode; // Bits 6-7 of IR: [ | | | | | |X|X]
|
private int irOpMode; // Bits 6-7 of IR: [ | | | | | |X|X]
|
||||||
private int effectiveAddress;
|
private int effectiveAddress;
|
||||||
@@ -435,8 +441,8 @@ public class Cpu implements InstructionTable {
|
|||||||
/** BIT - Bit Test ******************************************************/
|
/** BIT - Bit Test ******************************************************/
|
||||||
case 0x24: // Zero Page
|
case 0x24: // Zero Page
|
||||||
case 0x2c: // Absolute
|
case 0x2c: // Absolute
|
||||||
tmp = a & bus.read(effectiveAddress);
|
tmp = bus.read(effectiveAddress);
|
||||||
setZeroFlag(tmp == 0);
|
setZeroFlag((a & tmp) == 0);
|
||||||
setNegativeFlag((tmp & 0x80) != 0);
|
setNegativeFlag((tmp & 0x80) != 0);
|
||||||
setOverflowFlag((tmp & 0x40) != 0);
|
setOverflowFlag((tmp & 0x40) != 0);
|
||||||
break;
|
break;
|
||||||
@@ -553,7 +559,6 @@ public class Cpu implements InstructionTable {
|
|||||||
case 0x99: // Absolute,Y
|
case 0x99: // Absolute,Y
|
||||||
case 0x9d: // Absolute,X
|
case 0x9d: // Absolute,X
|
||||||
bus.write(effectiveAddress, a);
|
bus.write(effectiveAddress, a);
|
||||||
setArithmeticFlags(a);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
@@ -562,7 +567,6 @@ public class Cpu implements InstructionTable {
|
|||||||
case 0x8c: // Absolute
|
case 0x8c: // Absolute
|
||||||
case 0x94: // Zero Page,X
|
case 0x94: // Zero Page,X
|
||||||
bus.write(effectiveAddress, y);
|
bus.write(effectiveAddress, y);
|
||||||
setArithmeticFlags(y);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
@@ -571,7 +575,6 @@ public class Cpu implements InstructionTable {
|
|||||||
case 0x8e: // Absolute
|
case 0x8e: // Absolute
|
||||||
case 0x96: // Zero Page,Y
|
case 0x96: // Zero Page,Y
|
||||||
bus.write(effectiveAddress, x);
|
bus.write(effectiveAddress, x);
|
||||||
setArithmeticFlags(x);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
@@ -704,6 +707,8 @@ public class Cpu implements InstructionTable {
|
|||||||
setOpTrap();
|
setOpTrap();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delayLoop(ir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -715,7 +720,7 @@ public class Cpu implements InstructionTable {
|
|||||||
* @param operand The operand
|
* @param operand The operand
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public int adc(int acc, int operand) {
|
private int adc(int acc, int operand) {
|
||||||
int result = (operand & 0xff) + (acc & 0xff) + getCarryBit();
|
int result = (operand & 0xff) + (acc & 0xff) + getCarryBit();
|
||||||
int carry6 = (operand & 0x7f) + (acc & 0x7f) + getCarryBit();
|
int carry6 = (operand & 0x7f) + (acc & 0x7f) + getCarryBit();
|
||||||
setCarryFlag((result & 0x100) != 0);
|
setCarryFlag((result & 0x100) != 0);
|
||||||
@@ -729,7 +734,7 @@ public class Cpu implements InstructionTable {
|
|||||||
* Add with Carry (BCD).
|
* Add with Carry (BCD).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public int adcDecimal(int acc, int operand) {
|
private int adcDecimal(int acc, int operand) {
|
||||||
int l, h, result;
|
int l, h, result;
|
||||||
l = (acc & 0x0f) + (operand & 0x0f) + getCarryBit();
|
l = (acc & 0x0f) + (operand & 0x0f) + getCarryBit();
|
||||||
if ((l & 0xff) > 9) l += 6;
|
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
|
* 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
|
* one's complement of the operand. This lets the N, V, C, and Z
|
||||||
* flags work out nicely without any additional logic.
|
* 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;
|
int result;
|
||||||
result = adc(acc, ~operand);
|
result = adc(acc, ~operand);
|
||||||
setArithmeticFlags(result);
|
setArithmeticFlags(result);
|
||||||
@@ -762,12 +763,8 @@ public class Cpu implements InstructionTable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Subtract with Carry, BCD mode.
|
* 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;
|
int l, h, result;
|
||||||
l = (acc & 0x0f) - (operand & 0x0f) - (carryFlag ? 0 : 1);
|
l = (acc & 0x0f) - (operand & 0x0f) - (carryFlag ? 0 : 1);
|
||||||
if ((l & 0x10) != 0) l -= 6;
|
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
|
* Compare two values, and set carry, zero, and negative flags
|
||||||
* appropriately.
|
* 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);
|
setCarryFlag(reg >= operand);
|
||||||
setZeroFlag(reg == operand);
|
setZeroFlag(tmp == 0);
|
||||||
setNegativeFlag((reg - operand) > 0);
|
setNegativeFlag((tmp & 0x80) != 0); // Negative bit set
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the Negative and Zero flags based on the current value of the
|
* Set the Negative and Zero flags based on the current value of the
|
||||||
* register operand.
|
* register operand.
|
||||||
*
|
|
||||||
* @param reg The register.
|
|
||||||
*/
|
*/
|
||||||
public void setArithmeticFlags(int reg) {
|
private void setArithmeticFlags(int reg) {
|
||||||
zeroFlag = (reg == 0);
|
zeroFlag = (reg == 0);
|
||||||
negativeFlag = (reg & 0x80) != 0;
|
negativeFlag = (reg & 0x80) != 0;
|
||||||
}
|
}
|
||||||
@@ -1134,6 +1127,10 @@ public class Cpu implements InstructionTable {
|
|||||||
this.y = val;
|
this.y = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getLastProgramCounter() {
|
||||||
|
return lastPc;
|
||||||
|
}
|
||||||
|
|
||||||
public int getProgramCounter() {
|
public int getProgramCounter() {
|
||||||
return pc;
|
return pc;
|
||||||
}
|
}
|
||||||
@@ -1285,15 +1282,15 @@ public class Cpu implements InstructionTable {
|
|||||||
*/
|
*/
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String opcode = opcode(ir, args[0], args[1]);
|
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(String.format("%-14s", opcode));
|
||||||
sb.append("A=" + getAccumulatorStatus() + " ");
|
sb.append("A:" + String.format("%02x", a) + " ");
|
||||||
sb.append("X=" + getXRegisterStatus() + " ");
|
sb.append("X:" + String.format("%02x", x) + " ");
|
||||||
sb.append("Y=" + getYRegisterStatus() + " ");
|
sb.append("Y:" + String.format("%02x", y) + " ");
|
||||||
sb.append("PC=" + getProgramCounterStatus() + " ");
|
sb.append("F:" + String.format("%02x", getProcessorStatus()) + " ");
|
||||||
sb.append("SP=" + getStackPointerStatus() + " ");
|
sb.append("S:" + String.format("1%02x", sp) + " ");
|
||||||
sb.append("P=" + getProcessorStatusString());
|
sb.append(getProcessorStatusString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1397,6 +1394,19 @@ public class Cpu implements InstructionTable {
|
|||||||
bus.write(RST_VECTOR_L, address & 0x00ff);
|
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.
|
* 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)));
|
sb.append(String.format(" ($%04X)", address(op1, op2)));
|
||||||
break;
|
break;
|
||||||
case XIN:
|
case XIN:
|
||||||
sb.append(String.format(" ($%02X),X", op1));
|
sb.append(String.format(" ($%02X,X)", op1));
|
||||||
break;
|
break;
|
||||||
case INY:
|
case INY:
|
||||||
sb.append(String.format(" ($%02X,Y)", op1));
|
sb.append(String.format(" ($%02X),Y", op1));
|
||||||
break;
|
break;
|
||||||
case REL:
|
case REL:
|
||||||
case ZPG:
|
case ZPG:
|
||||||
@@ -1449,4 +1459,19 @@ public class Cpu implements InstructionTable {
|
|||||||
|
|
||||||
return sb.toString();
|
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,
|
"TYA", "STA", "TXS", null, null, "STA", null, null,
|
||||||
"LDY", "LDA", "LDX", null, "LDY", "LDA", "LDX", null,
|
"LDY", "LDA", "LDX", null, "LDY", "LDA", "LDX", null,
|
||||||
"TAY", "LDA", "TAX", 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,
|
"CLV", "LDA", "TSX", null, "LDY", "LDA", "LDX", null,
|
||||||
"CPY", "CMP", null, null, "CPY", "CMP", "DEC", null,
|
"CPY", "CMP", null, null, "CPY", "CMP", "DEC", null,
|
||||||
"INY", "CMP", "DEX", 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.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0x8c-0x8f
|
||||||
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x90-0x93
|
Mode.REL, Mode.INY, Mode.NUL, Mode.NUL, // 0x90-0x93
|
||||||
Mode.ZPX, Mode.ZPX, Mode.ZPY, Mode.NUL, // 0x94-0x97
|
Mode.ZPX, Mode.ZPX, Mode.ZPY, Mode.NUL, // 0x94-0x97
|
||||||
Mode.IMP, Mode.ABX, Mode.IMP, Mode.NUL, // 0x98-0x9b
|
Mode.IMP, Mode.ABY, Mode.IMP, Mode.NUL, // 0x98-0x9b
|
||||||
Mode.NUL, Mode.ABY, Mode.NUL, Mode.NUL, // 0x9c-0x9f
|
Mode.NUL, Mode.ABX, Mode.NUL, Mode.NUL, // 0x9c-0x9f
|
||||||
Mode.IMM, Mode.XIN, Mode.IMM, Mode.NUL, // 0xa0-0xa3
|
Mode.IMM, Mode.XIN, Mode.IMM, Mode.NUL, // 0xa0-0xa3
|
||||||
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xa4-0xa7
|
Mode.ZPG, Mode.ZPG, Mode.ZPG, Mode.NUL, // 0xa4-0xa7
|
||||||
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xa8-0xab
|
Mode.IMP, Mode.IMM, Mode.IMP, Mode.NUL, // 0xa8-0xab
|
||||||
Mode.ABS, Mode.ABS, Mode.ABS, Mode.NUL, // 0xac-0xaf
|
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.ZPX, Mode.ZPX, Mode.ZPY, Mode.NUL, // 0xb4-0xb7
|
||||||
Mode.IMP, Mode.ABX, Mode.IMP, Mode.NUL, // 0xb8-0xbb
|
Mode.IMP, Mode.ABX, Mode.IMP, Mode.NUL, // 0xb8-0xbb
|
||||||
Mode.ABX, Mode.ABY, Mode.ABY, Mode.NUL, // 0xbc-0xbf
|
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, 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, 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, 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, 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, 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, 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, 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.Acia;
|
||||||
import com.loomcom.symon.devices.Memory;
|
import com.loomcom.symon.devices.Memory;
|
||||||
|
import com.loomcom.symon.exceptions.FifoUnderrunException;
|
||||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
import com.loomcom.symon.exceptions.SymonException;
|
import com.loomcom.symon.exceptions.SymonException;
|
||||||
@@ -14,7 +15,6 @@ import java.awt.event.ActionEvent;
|
|||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.Observable;
|
import java.util.Observable;
|
||||||
import java.util.Observer;
|
import java.util.Observer;
|
||||||
import java.util.logging.Level;
|
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_BOTTOM = 0x0000;
|
||||||
private static final int BUS_TOP = 0xffff;
|
private static final int BUS_TOP = 0xffff;
|
||||||
|
|
||||||
|
// 32K of RAM
|
||||||
private static final int MEMORY_BASE = 0x0000;
|
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;
|
// IO area at $D000
|
||||||
private static final int ROM_SIZE = 0x2000; // 8 KB
|
private static final int ACIA_BASE = 0xC000;
|
||||||
|
|
||||||
private static final int ACIA_BASE = 0xc000;
|
// 8KB ROM at E000-FFFF
|
||||||
|
private static final int ROM_BASE = 0xD000;
|
||||||
// The delay in microseconds between steps.
|
private static final int ROM_SIZE = 0x3000;
|
||||||
private static final int DELAY_BETWEEN_STEPS_NS = 1000;
|
|
||||||
|
|
||||||
// Since it is very expensive to update the UI with Swing's Event Dispatch Thread, we can't afford
|
// 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
|
// 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 JFileChooser fileChooser;
|
||||||
private PreferencesDialog preferences;
|
private PreferencesDialog preferences;
|
||||||
|
|
||||||
public Simulator() throws MemoryRangeException {
|
public Simulator() throws MemoryRangeException, IOException {
|
||||||
this.acia = new Acia(ACIA_BASE);
|
this.acia = new Acia(ACIA_BASE);
|
||||||
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
||||||
this.cpu = new Cpu();
|
this.cpu = new Cpu();
|
||||||
this.ram = new Memory(MEMORY_BASE, MEMORY_SIZE, false);
|
this.ram = new Memory(MEMORY_BASE, MEMORY_SIZE, false);
|
||||||
|
|
||||||
// TODO: Load this ROM from a file, of course!
|
// TODO: Make this configurable, of course.
|
||||||
this.rom = new Memory(ROM_BASE, ROM_SIZE, false);
|
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.addCpu(cpu);
|
||||||
bus.addDevice(acia);
|
|
||||||
bus.addDevice(ram);
|
bus.addDevice(ram);
|
||||||
|
bus.addDevice(acia);
|
||||||
bus.addDevice(rom);
|
bus.addDevice(rom);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +220,7 @@ public class Simulator implements ActionListener, Observer {
|
|||||||
*/
|
*/
|
||||||
public void actionPerformed(ActionEvent actionEvent) {
|
public void actionPerformed(ActionEvent actionEvent) {
|
||||||
if (actionEvent.getSource() == resetButton) {
|
if (actionEvent.getSource() == resetButton) {
|
||||||
handleReset();
|
coldReset();
|
||||||
} else if (actionEvent.getSource() == stepButton) {
|
} else if (actionEvent.getSource() == stepButton) {
|
||||||
handleStep();
|
handleStep();
|
||||||
} else if (actionEvent.getSource() == runStopButton) {
|
} 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()) {
|
if (runLoop != null && runLoop.isRunning()) {
|
||||||
runLoop.requestStop();
|
runLoop.requestStop();
|
||||||
runLoop.interrupt();
|
runLoop.interrupt();
|
||||||
@@ -282,7 +290,7 @@ public class Simulator implements ActionListener, Observer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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
|
// Reset and clear memory
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
ram.fill(0x00);
|
ram.fill(0x00);
|
||||||
@@ -307,6 +315,7 @@ public class Simulator implements ActionListener, Observer {
|
|||||||
private void handleStep() {
|
private void handleStep() {
|
||||||
try {
|
try {
|
||||||
step();
|
step();
|
||||||
|
|
||||||
// The simulator is lazy about updating the UI for performance reasons, so always request an
|
// The simulator is lazy about updating the UI for performance reasons, so always request an
|
||||||
// immediate update after stepping manually.
|
// immediate update after stepping manually.
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
@@ -337,8 +346,6 @@ public class Simulator implements ActionListener, Observer {
|
|||||||
*/
|
*/
|
||||||
private void step() throws MemoryAccessException {
|
private void step() throws MemoryAccessException {
|
||||||
|
|
||||||
delayLoop();
|
|
||||||
|
|
||||||
cpu.step();
|
cpu.step();
|
||||||
|
|
||||||
// Read from the ACIA and immediately update the console if there's
|
// 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.
|
// If a key has been pressed, fill the ACIA.
|
||||||
// TODO: Interrupt handling.
|
// TODO: Interrupt handling.
|
||||||
if (console.hasInput()) {
|
try {
|
||||||
acia.rxWrite((int)console.readInputChar());
|
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
|
// 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.
|
* Load a program into memory at the simulatorDidStart address.
|
||||||
*/
|
*/
|
||||||
private void loadProgram(byte[] program, int startAddress) throws MemoryAccessException {
|
private void loadProgram(byte[] program, int startAddress) throws MemoryAccessException {
|
||||||
cpu.setResetVector(startAddress);
|
|
||||||
|
|
||||||
int addr = startAddress, i;
|
int addr = startAddress, i;
|
||||||
for (i = 0; i < program.length; i++) {
|
for (i = 0; i < program.length; i++) {
|
||||||
bus.write(addr++, program[i] & 0xff);
|
bus.write(addr++, program[i] & 0xff);
|
||||||
@@ -388,6 +397,9 @@ public class Simulator implements ActionListener, Observer {
|
|||||||
// Reset (but don't clear memory, naturally)
|
// Reset (but don't clear memory, naturally)
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
|
|
||||||
|
// Reset the stack program counter
|
||||||
|
cpu.setProgramCounter(preferences.getProgramStartAddress());
|
||||||
|
|
||||||
// Immediately update the UI.
|
// Immediately update the UI.
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -405,8 +417,8 @@ public class Simulator implements ActionListener, Observer {
|
|||||||
Simulator simulator = new Simulator();
|
Simulator simulator = new Simulator();
|
||||||
simulator.createAndShowUi();
|
simulator.createAndShowUi();
|
||||||
// Reset the simulator.
|
// Reset the simulator.
|
||||||
simulator.handleReset();
|
simulator.coldReset();
|
||||||
} catch (MemoryRangeException e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
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.
|
* The main run thread.
|
||||||
@@ -496,4 +499,4 @@ public class Simulator implements ActionListener, Observer {
|
|||||||
isRunning = false;
|
isRunning = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package com.loomcom.symon.devices;
|
package com.loomcom.symon.devices;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import com.loomcom.symon.exceptions.*;
|
import com.loomcom.symon.exceptions.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
public class Memory extends Device {
|
public class Memory extends Device {
|
||||||
|
|
||||||
private boolean readOnly;
|
private boolean readOnly;
|
||||||
@@ -24,6 +27,17 @@ public class Memory extends Device {
|
|||||||
this(address, size, false);
|
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 {
|
public void write(int address, int data) throws MemoryAccessException {
|
||||||
if (readOnly) {
|
if (readOnly) {
|
||||||
throw new MemoryAccessException("Cannot write to read-only memory at address " + address);
|
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 {
|
public int read(int address) throws MemoryAccessException {
|
||||||
return this.mem[address];
|
return this.mem[address];
|
||||||
}
|
}
|
||||||
@@ -40,7 +82,6 @@ public class Memory extends Device {
|
|||||||
Arrays.fill(this.mem, val);
|
Arrays.fill(this.mem, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Memory: " + getMemoryRange().toString();
|
return "Memory: " + getMemoryRange().toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import java.awt.event.MouseListener;
|
|||||||
|
|
||||||
import com.grahamedgecombe.jterminal.JTerminal;
|
import com.grahamedgecombe.jterminal.JTerminal;
|
||||||
import com.grahamedgecombe.jterminal.vt100.Vt100TerminalModel;
|
import com.grahamedgecombe.jterminal.vt100.Vt100TerminalModel;
|
||||||
|
import com.loomcom.symon.exceptions.FifoUnderrunException;
|
||||||
|
import com.loomcom.symon.util.FifoRingBuffer;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.BevelBorder;
|
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_COLUMNS = 80;
|
||||||
private static final int DEFAULT_ROWS = 24;
|
private static final int DEFAULT_ROWS = 24;
|
||||||
private static final int DEFAULT_BORDER_WIDTH = 10;
|
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 FifoRingBuffer typeAheadBuffer;
|
||||||
private char keyBuffer;
|
|
||||||
|
|
||||||
public Console() {
|
public Console() {
|
||||||
this(DEFAULT_COLUMNS, DEFAULT_ROWS);
|
this(DEFAULT_COLUMNS, DEFAULT_ROWS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Console(int columns, int rows) {
|
public Console(int columns, int rows) {
|
||||||
super(new Vt100TerminalModel(columns, 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);
|
setBorderWidth(DEFAULT_BORDER_WIDTH);
|
||||||
addKeyListener(this);
|
addKeyListener(this);
|
||||||
addMouseListener(this);
|
addMouseListener(this);
|
||||||
@@ -39,7 +47,7 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
|
|||||||
Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.LOWERED);
|
Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.LOWERED);
|
||||||
Border compoundBorder = BorderFactory.createCompoundBorder(emptyBorder, bevelBorder);
|
Border compoundBorder = BorderFactory.createCompoundBorder(emptyBorder, bevelBorder);
|
||||||
this.setBorder(compoundBorder);
|
this.setBorder(compoundBorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the console. This will cause the console to be cleared and the cursor returned to the
|
* 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() {
|
public void reset() {
|
||||||
|
typeAheadBuffer.reset();
|
||||||
getModel().clear();
|
getModel().clear();
|
||||||
getModel().setCursorColumn(0);
|
getModel().setCursorColumn(0);
|
||||||
getModel().setCursorRow(0);
|
getModel().setCursorRow(0);
|
||||||
repaint();
|
repaint();
|
||||||
this.hasInput = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +68,7 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public boolean hasInput() {
|
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.
|
* @param keyEvent The key event.
|
||||||
*/
|
*/
|
||||||
public void keyTyped(KeyEvent keyEvent) {
|
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();
|
keyEvent.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +104,6 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
|
|||||||
* @param keyEvent The key event.
|
* @param keyEvent The key event.
|
||||||
*/
|
*/
|
||||||
public void keyPressed(KeyEvent keyEvent) {
|
public void keyPressed(KeyEvent keyEvent) {
|
||||||
keyBuffer = keyEvent.getKeyChar();
|
|
||||||
hasInput = true;
|
|
||||||
keyEvent.consume();
|
keyEvent.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,9 +112,8 @@ public class Console extends JTerminal implements KeyListener, MouseListener {
|
|||||||
*
|
*
|
||||||
* @return The character typed.
|
* @return The character typed.
|
||||||
*/
|
*/
|
||||||
public char readInputChar() {
|
public char readInputChar() throws FifoUnderrunException {
|
||||||
hasInput = false;
|
return (char)typeAheadBuffer.pop();
|
||||||
return this.keyBuffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
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
|
bus.loadProgram(0x99, 0x10, 0xab); // STA $ab10,Y
|
||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x00, bus.read(0xab40));
|
assertEquals(0x00, bus.read(0xab40));
|
||||||
assertTrue(cpu.getZeroFlag());
|
// STA should have NO effect on flags
|
||||||
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertFalse(cpu.getNegativeFlag());
|
assertFalse(cpu.getNegativeFlag());
|
||||||
|
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
@@ -434,7 +435,7 @@ public class CpuAbsoluteYModeTest extends TestCase {
|
|||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x80, bus.read(0xab40));
|
assertEquals(0x80, bus.read(0xab40));
|
||||||
assertFalse(cpu.getZeroFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertTrue(cpu.getNegativeFlag());
|
assertFalse(cpu.getNegativeFlag());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LDX - Load X Register - $be */
|
/* LDX - Load X Register - $be */
|
||||||
@@ -523,7 +524,7 @@ public class CpuAbsoluteYModeTest extends TestCase {
|
|||||||
cpu.step();
|
cpu.step();
|
||||||
assertFalse(cpu.getCarryFlag()); // m < y
|
assertFalse(cpu.getCarryFlag()); // m < y
|
||||||
assertFalse(cpu.getZeroFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertFalse(cpu.getNegativeFlag()); // m - y > 0
|
assertTrue(cpu.getNegativeFlag()); // $80 - $ff = $81
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SBC - Subtract with Carry - $f9 */
|
/* 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
|
bus.loadProgram(0x9d, 0x10, 0xab); // STA $ab10,X
|
||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x00, bus.read(0xab40));
|
assertEquals(0x00, bus.read(0xab40));
|
||||||
assertTrue(cpu.getZeroFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertFalse(cpu.getNegativeFlag());
|
assertFalse(cpu.getNegativeFlag());
|
||||||
|
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
@@ -434,7 +434,7 @@ public class CpuIndirectXModeTest extends TestCase {
|
|||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x80, bus.read(0xab40));
|
assertEquals(0x80, bus.read(0xab40));
|
||||||
assertFalse(cpu.getZeroFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertTrue(cpu.getNegativeFlag());
|
assertFalse(cpu.getNegativeFlag());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LDA - Load Accumulator - $bd */
|
/* LDA - Load Accumulator - $bd */
|
||||||
@@ -494,7 +494,7 @@ public class CpuIndirectXModeTest extends TestCase {
|
|||||||
cpu.step();
|
cpu.step();
|
||||||
assertFalse(cpu.getCarryFlag()); // m < y
|
assertFalse(cpu.getCarryFlag()); // m < y
|
||||||
assertFalse(cpu.getZeroFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertFalse(cpu.getNegativeFlag()); // m - y > 0
|
assertTrue(cpu.getNegativeFlag()); // m - y < 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SBC - Subtract with Carry - $fd */
|
/* 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
|
bus.loadProgram(0x94, 0x10); // STY $10,X
|
||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x00, bus.read(0x40));
|
assertEquals(0x00, bus.read(0x40));
|
||||||
assertTrue(cpu.getZeroFlag());
|
// Should have no effect on flags.
|
||||||
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertFalse(cpu.getNegativeFlag());
|
assertFalse(cpu.getNegativeFlag());
|
||||||
|
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
@@ -730,7 +731,7 @@ public class CpuZeroPageXModeTest extends TestCase {
|
|||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x80, bus.read(0x40));
|
assertEquals(0x80, bus.read(0x40));
|
||||||
assertFalse(cpu.getZeroFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertTrue(cpu.getNegativeFlag());
|
assertFalse(cpu.getNegativeFlag());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* STA - Store Accumulator - $95 */
|
/* STA - Store Accumulator - $95 */
|
||||||
@@ -742,7 +743,8 @@ public class CpuZeroPageXModeTest extends TestCase {
|
|||||||
bus.loadProgram(0x95, 0x10); // STA $10,X
|
bus.loadProgram(0x95, 0x10); // STA $10,X
|
||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x00, bus.read(0x40));
|
assertEquals(0x00, bus.read(0x40));
|
||||||
assertTrue(cpu.getZeroFlag());
|
// Should have no effect on flags.
|
||||||
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertFalse(cpu.getNegativeFlag());
|
assertFalse(cpu.getNegativeFlag());
|
||||||
|
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
@@ -763,7 +765,7 @@ public class CpuZeroPageXModeTest extends TestCase {
|
|||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x80, bus.read(0x40));
|
assertEquals(0x80, bus.read(0x40));
|
||||||
assertFalse(cpu.getZeroFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertTrue(cpu.getNegativeFlag());
|
assertFalse(cpu.getNegativeFlag());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LDY - Load Y Register - $b4 */
|
/* LDY - Load Y Register - $b4 */
|
||||||
@@ -852,7 +854,7 @@ public class CpuZeroPageXModeTest extends TestCase {
|
|||||||
cpu.step();
|
cpu.step();
|
||||||
assertFalse(cpu.getCarryFlag()); // m < y
|
assertFalse(cpu.getCarryFlag()); // m < y
|
||||||
assertFalse(cpu.getZeroFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertFalse(cpu.getNegativeFlag()); // m - y > 0
|
assertTrue(cpu.getNegativeFlag()); // m - y < 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DEC - Decrement Memory Location - $d6 */
|
/* DEC - Decrement Memory Location - $d6 */
|
||||||
|
|||||||
@@ -7,96 +7,97 @@ import junit.framework.TestCase;
|
|||||||
|
|
||||||
public class CpuZeroPageYModeTest extends TestCase {
|
public class CpuZeroPageYModeTest extends TestCase {
|
||||||
|
|
||||||
protected Cpu cpu;
|
protected Cpu cpu;
|
||||||
protected Bus bus;
|
protected Bus bus;
|
||||||
protected Memory mem;
|
protected Memory mem;
|
||||||
|
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
this.cpu = new Cpu();
|
this.cpu = new Cpu();
|
||||||
this.bus = new Bus(0x0000, 0xffff);
|
this.bus = new Bus(0x0000, 0xffff);
|
||||||
this.mem = new Memory(0x0000, 0x10000);
|
this.mem = new Memory(0x0000, 0x10000);
|
||||||
bus.addCpu(cpu);
|
bus.addCpu(cpu);
|
||||||
bus.addDevice(mem);
|
bus.addDevice(mem);
|
||||||
|
|
||||||
// Load the reset vector.
|
// Load the reset vector.
|
||||||
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
|
bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff);
|
||||||
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00)>>>8);
|
bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00) >>> 8);
|
||||||
|
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
// Assert initial state
|
// Assert initial state
|
||||||
assertEquals(0, cpu.getAccumulator());
|
assertEquals(0, cpu.getAccumulator());
|
||||||
assertEquals(0, cpu.getXRegister());
|
assertEquals(0, cpu.getXRegister());
|
||||||
assertEquals(0, cpu.getYRegister());
|
assertEquals(0, cpu.getYRegister());
|
||||||
assertEquals(0x200, cpu.getProgramCounter());
|
assertEquals(0x200, cpu.getProgramCounter());
|
||||||
assertEquals(0xff, cpu.getStackPointer());
|
assertEquals(0xff, cpu.getStackPointer());
|
||||||
assertEquals(0x20, cpu.getProcessorStatus());
|
assertEquals(0x20, cpu.getProcessorStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The following opcodes are tested for correctness in this file:
|
* The following opcodes are tested for correctness in this file:
|
||||||
*
|
*
|
||||||
* STX - $96
|
* STX - $96
|
||||||
* LDX - $b6
|
* LDX - $b6
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* STX - Store X Register - $96 */
|
/* STX - Store X Register - $96 */
|
||||||
|
|
||||||
public void test_STX() throws MemoryAccessException {
|
public void test_STX() throws MemoryAccessException {
|
||||||
cpu.setYRegister(0x30);
|
cpu.setYRegister(0x30);
|
||||||
cpu.setXRegister(0x00);
|
cpu.setXRegister(0x00);
|
||||||
bus.loadProgram(0x96, 0x10); // STX $10,Y
|
bus.loadProgram(0x96, 0x10); // STX $10,Y
|
||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x00, bus.read(0x40));
|
assertEquals(0x00, bus.read(0x40));
|
||||||
assertTrue(cpu.getZeroFlag());
|
// Should have no effect on flags.
|
||||||
assertFalse(cpu.getNegativeFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
|
assertFalse(cpu.getNegativeFlag());
|
||||||
|
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
cpu.setYRegister(0x30);
|
cpu.setYRegister(0x30);
|
||||||
cpu.setXRegister(0x0f);
|
cpu.setXRegister(0x0f);
|
||||||
bus.loadProgram(0x96, 0x10); // STX $10,Y
|
bus.loadProgram(0x96, 0x10); // STX $10,Y
|
||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x0f, bus.read(0x40));
|
assertEquals(0x0f, bus.read(0x40));
|
||||||
assertFalse(cpu.getZeroFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertFalse(cpu.getNegativeFlag());
|
assertFalse(cpu.getNegativeFlag());
|
||||||
|
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
cpu.setYRegister(0x30);
|
cpu.setYRegister(0x30);
|
||||||
cpu.setXRegister(0x80);
|
cpu.setXRegister(0x80);
|
||||||
bus.loadProgram(0x96, 0x10); // STX $10,Y
|
bus.loadProgram(0x96, 0x10); // STX $10,Y
|
||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x80, bus.read(0x40));
|
assertEquals(0x80, bus.read(0x40));
|
||||||
assertFalse(cpu.getZeroFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertTrue(cpu.getNegativeFlag());
|
assertFalse(cpu.getNegativeFlag());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LDX - Load X Register - $b6 */
|
/* LDX - Load X Register - $b6 */
|
||||||
|
|
||||||
public void test_LDX() throws MemoryAccessException {
|
public void test_LDX() throws MemoryAccessException {
|
||||||
bus.write(0x40, 0x00);
|
bus.write(0x40, 0x00);
|
||||||
bus.write(0x41, 0x0f);
|
bus.write(0x41, 0x0f);
|
||||||
bus.write(0x42, 0x80);
|
bus.write(0x42, 0x80);
|
||||||
|
|
||||||
bus.loadProgram(0xb6, 0x10,
|
bus.loadProgram(0xb6, 0x10,
|
||||||
0xb6, 0x11,
|
0xb6, 0x11,
|
||||||
0xb6, 0x12);
|
0xb6, 0x12);
|
||||||
|
|
||||||
cpu.setYRegister(0x30);
|
cpu.setYRegister(0x30);
|
||||||
|
|
||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x00, cpu.getXRegister());
|
assertEquals(0x00, cpu.getXRegister());
|
||||||
assertTrue(cpu.getZeroFlag());
|
assertTrue(cpu.getZeroFlag());
|
||||||
assertFalse(cpu.getNegativeFlag());
|
assertFalse(cpu.getNegativeFlag());
|
||||||
|
|
||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x0f, cpu.getXRegister());
|
assertEquals(0x0f, cpu.getXRegister());
|
||||||
assertFalse(cpu.getZeroFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertFalse(cpu.getNegativeFlag());
|
assertFalse(cpu.getNegativeFlag());
|
||||||
|
|
||||||
cpu.step();
|
cpu.step();
|
||||||
assertEquals(0x80, cpu.getXRegister());
|
assertEquals(0x80, cpu.getXRegister());
|
||||||
assertFalse(cpu.getZeroFlag());
|
assertFalse(cpu.getZeroFlag());
|
||||||
assertTrue(cpu.getNegativeFlag());
|
assertTrue(cpu.getNegativeFlag());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user