Merge remote-tracking branch 'upstream/master'
Conflicts: src/main/java/com/loomcom/symon/machines/MulticompMachine.java
This commit is contained in:
commit
e9f662cc17
17 files changed
+9298
-84
No files matched your search
@@ -279,6 +279,10 @@ running.
|
|||||||
- Allow displaying ACIA status and dumping ACIA buffers, for
|
- Allow displaying ACIA status and dumping ACIA buffers, for
|
||||||
debugging.
|
debugging.
|
||||||
|
|
||||||
|
- CRTC emulation is very naive. The whole frame is drawn in one
|
||||||
|
CPU step. This should be improved by drawing scan lines during
|
||||||
|
machine steps to approximate real NTSC/PAL refresh rates.
|
||||||
|
|
||||||
- Symbolic debugging.
|
- Symbolic debugging.
|
||||||
|
|
||||||
## 7.0 Copyright and Acknowledgements
|
## 7.0 Copyright and Acknowledgements
|
||||||
|
|||||||
@@ -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,268 @@
|
|||||||
|
|
||||||
|
; 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 = $8800
|
||||||
|
ACIAdata = IO_AREA ; simulated ACIA r/w port
|
||||||
|
ACIAstatus = IO_AREA+1
|
||||||
|
ACIAcommand = IO_AREA+2
|
||||||
|
ACIAcontrol = IO_AREA+3
|
||||||
|
|
||||||
|
CPOS_L = $E0 ; Cursor address (low)
|
||||||
|
CPOS_H = $E1 ; Cursor address (high)
|
||||||
|
COUTC = $E2 ; Temp storage for char out.
|
||||||
|
|
||||||
|
|
||||||
|
; 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 $FC00 ; 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 CRTC
|
||||||
|
LDA #$00 ; Set cursor start to $7000
|
||||||
|
STA CPOS_L
|
||||||
|
LDA #$70
|
||||||
|
STA CPOS_H
|
||||||
|
|
||||||
|
|
||||||
|
; Initialize the ACIA
|
||||||
|
ACIA_init
|
||||||
|
LDA #$00
|
||||||
|
STA ACIAstatus ; Soft reset
|
||||||
|
LDA #$0B
|
||||||
|
STA ACIAcommand ; Parity disabled, IRQ disabled
|
||||||
|
LDA #$1E
|
||||||
|
STA ACIAcontrol ; Set output for 8-N-1 9600
|
||||||
|
|
||||||
|
; 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
|
||||||
|
|
||||||
|
|
||||||
|
ACIAout:
|
||||||
|
|
||||||
|
PHA
|
||||||
|
@loop: LDA ACIAstatus
|
||||||
|
AND #$10
|
||||||
|
BEQ @loop ; Wait for buffer to empty
|
||||||
|
PLA
|
||||||
|
STA ACIAdata
|
||||||
|
RTS
|
||||||
|
|
||||||
|
;;; Byte out to the CRTC
|
||||||
|
;;;
|
||||||
|
;;; 1. Increment cursor position.
|
||||||
|
;;; 2. Scroll if necessary.
|
||||||
|
;;; 3. Store new cursor position in CRTC.
|
||||||
|
|
||||||
|
CRTCout:
|
||||||
|
STA COUTC ; Store the character going out
|
||||||
|
JSR ACIAout ; Also echo to terminal for debugging.
|
||||||
|
|
||||||
|
;; Is this a CR or LF?
|
||||||
|
CMP #$0D
|
||||||
|
BEQ @dolf
|
||||||
|
CMP #$0A
|
||||||
|
BEQ @docr
|
||||||
|
;; Is this a backspace?
|
||||||
|
CMP #$08
|
||||||
|
BEQ @dobs
|
||||||
|
|
||||||
|
;; Otherwise, this is a normal character
|
||||||
|
;; Do normal character stuff.
|
||||||
|
TYA ; Save Y
|
||||||
|
PHA
|
||||||
|
LDA COUTC ; Restore character going out
|
||||||
|
LDY #$00
|
||||||
|
STA (CPOS_L),Y
|
||||||
|
INC CPOS_L
|
||||||
|
PLA ; Restore Y
|
||||||
|
TAY
|
||||||
|
JMP @done
|
||||||
|
|
||||||
|
|
||||||
|
@dolf:
|
||||||
|
;; Handle a LF
|
||||||
|
;; Increment CRSR_R.
|
||||||
|
;; If CRSR_R > ROW_M, scroll..
|
||||||
|
|
||||||
|
|
||||||
|
@docr:
|
||||||
|
;; Handle a CR
|
||||||
|
;; Set CRSR_C = 0.
|
||||||
|
|
||||||
|
@dobs:
|
||||||
|
;; 2. Is this a backspace?
|
||||||
|
;; IF CRSR_C == 0 && CRSR_R == 0
|
||||||
|
;; Do nothing, already home
|
||||||
|
;; If CRSR_C > 0
|
||||||
|
;; Set CRSR_C = CRSR_C - 1
|
||||||
|
;; Else
|
||||||
|
;; Set CRSR_C = COL_M
|
||||||
|
;; Set CRSR_R = CRSR_R - 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
@done:
|
||||||
|
;; Now new cursor Column and Row have been calculated,
|
||||||
|
;; and we know if we need to scroll.
|
||||||
|
;;
|
||||||
|
;; (CPOS_H) = $70 + (row /
|
||||||
|
|
||||||
|
RTS
|
||||||
|
|
||||||
|
;; Convert CRSR_C, CRSR_R into Cursor Address.
|
||||||
|
;; Result stored in CPOS_L,CPOS_H
|
||||||
|
;;
|
||||||
|
;; C_POS:
|
||||||
|
;; ;; Multiply CRSR_R * $28, store result in CPOS_L,CPOS_H
|
||||||
|
;; LDA CRSR_R ; Copy CRSR_R to CPOS_L
|
||||||
|
;; STA CPOS_L
|
||||||
|
;; LDA #$00
|
||||||
|
;; LDX #$08
|
||||||
|
;; LSR CPOS_L
|
||||||
|
;; @cpos1: BCC @cpos2
|
||||||
|
;; CLC
|
||||||
|
;; ADC #$28
|
||||||
|
;; @cpos2: ROR
|
||||||
|
;; ROR CPOS_L
|
||||||
|
;; DEX
|
||||||
|
;; BNE @cpos1
|
||||||
|
;; STA CPOS_H
|
||||||
|
;; ;; At this point, CPOS_L,CPOS_H holds the product
|
||||||
|
;; ;; of CRSR_R and $28. We need to add CRSR_C with
|
||||||
|
;; ;; an unsigned 16-bit add.
|
||||||
|
;; CLC
|
||||||
|
;; LDA CRSR_C
|
||||||
|
;; ADC CPOS_L
|
||||||
|
;; STA CPOS_L
|
||||||
|
;; BCC @noinc ; If carry wasn't set, no need
|
||||||
|
;; INC CPOS_H ; to increment CPOS_H
|
||||||
|
;; @noinc:
|
||||||
|
|
||||||
|
;
|
||||||
|
; byte in from ACIA. This subroutine will also force
|
||||||
|
; all lowercase letters to be uppercase.
|
||||||
|
;
|
||||||
|
ACIAin
|
||||||
|
LDA ACIAstatus ; Read 6551 status
|
||||||
|
AND #$08 ;
|
||||||
|
BEQ LAB_nobyw ; If rx buffer empty, no byte
|
||||||
|
|
||||||
|
LDA ACIAdata ; Read byte from 6551
|
||||||
|
CMP #'a' ; Is it < 'a'?
|
||||||
|
BCC @done ; Yes, we're done
|
||||||
|
CMP #'{' ; Is it >= '{'?
|
||||||
|
BCS @done ; Yes, we're done
|
||||||
|
AND #$5f ; Otherwise, mask to uppercase
|
||||||
|
@done
|
||||||
|
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 CRTCout ; 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
|
||||||
|
|
||||||
|
; sign on string
|
||||||
|
|
||||||
|
LAB_mess
|
||||||
|
.byte $0D,$0A,"Symon (c) 2008-2014, Seth Morabito"
|
||||||
|
.byte $0D,$0A,"Enhanced 6502 BASIC 2.22 (c) Lee Davison"
|
||||||
|
.byte $0D,$0A,"[C]old/[W]arm ?",$00
|
||||||
|
|
||||||
|
|
||||||
|
; system vectors
|
||||||
|
|
||||||
|
.segment "VECTORS"
|
||||||
|
.org $FFFA
|
||||||
|
|
||||||
|
.word NMI_vec ; NMI vector
|
||||||
|
.word RES_vec ; RESET vector
|
||||||
|
.word IRQ_vec ; IRQ vector
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
MEMORY {
|
||||||
|
RAM1: start = $0000, size = $8000;
|
||||||
|
ROM1: start = $C000, size = $3C00, fill = yes;
|
||||||
|
MONITOR: start = $FC00, size = $3FA, 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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -364,6 +364,9 @@ public class Simulator {
|
|||||||
|
|
||||||
if (videoWindow != null && stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) {
|
if (videoWindow != null && stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) {
|
||||||
stepsSinceLastCrtcRefresh = 0;
|
stepsSinceLastCrtcRefresh = 0;
|
||||||
|
if (videoWindow.isVisible()) {
|
||||||
|
videoWindow.repaint();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a very expensive update, and we're doing it without
|
// This is a very expensive update, and we're doing it without
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ public class Crtc extends Device {
|
|||||||
|
|
||||||
private int currentRegister = 0;
|
private int currentRegister = 0;
|
||||||
|
|
||||||
|
// Status bits
|
||||||
|
private boolean rowColumnAddressing = false;
|
||||||
|
private boolean displayEnableSkew = false;
|
||||||
|
private boolean cursorSkew = false;
|
||||||
|
|
||||||
private Memory memory;
|
private Memory memory;
|
||||||
|
|
||||||
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException, IOException {
|
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException, IOException {
|
||||||
@@ -118,8 +123,9 @@ public class Crtc extends Device {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] getDmaAccess() {
|
public int getCharAtAddress(int address) throws MemoryAccessException {
|
||||||
return memory.getDmaAccess();
|
// TODO: Row/Column addressing
|
||||||
|
return memory.read(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHorizontalDisplayed() {
|
public int getHorizontalDisplayed() {
|
||||||
@@ -162,6 +168,18 @@ public class Crtc extends Device {
|
|||||||
return pageSize;
|
return pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getRowColumnAddressing() {
|
||||||
|
return rowColumnAddressing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getDisplayEnableSkew() {
|
||||||
|
return displayEnableSkew;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getCursorSkew() {
|
||||||
|
return cursorSkew;
|
||||||
|
}
|
||||||
|
|
||||||
private void setCurrentRegister(int registerNumber) {
|
private void setCurrentRegister(int registerNumber) {
|
||||||
this.currentRegister = registerNumber;
|
this.currentRegister = registerNumber;
|
||||||
}
|
}
|
||||||
@@ -180,7 +198,9 @@ public class Crtc extends Device {
|
|||||||
pageSize = horizontalDisplayed * verticalDisplayed;
|
pageSize = horizontalDisplayed * verticalDisplayed;
|
||||||
break;
|
break;
|
||||||
case MODE_CONTROL:
|
case MODE_CONTROL:
|
||||||
// TODO: Implement multiple addressing modes and cursor skew.
|
rowColumnAddressing = (data & 0x04) != 0;
|
||||||
|
displayEnableSkew = (data & 0x10) != 0;
|
||||||
|
cursorSkew = (data & 0x20) != 0;
|
||||||
break;
|
break;
|
||||||
case SCAN_LINE:
|
case SCAN_LINE:
|
||||||
scanLinesPerRow = data;
|
scanLinesPerRow = data;
|
||||||
|
|||||||
+6
-32
@@ -23,45 +23,19 @@
|
|||||||
|
|
||||||
package com.loomcom.symon.devices;
|
package com.loomcom.symon.devices;
|
||||||
|
|
||||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
|
||||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
|
|
||||||
public class Via extends Device {
|
public abstract class Pia extends Device {
|
||||||
public static final int VIA_SIZE = 16;
|
|
||||||
|
|
||||||
private static final int ORB = 0;
|
private final String name;
|
||||||
private static final int ORA = 1;
|
|
||||||
private static final int DDRB = 2;
|
|
||||||
private static final int DDRA = 3;
|
|
||||||
private static final int T1C_L = 4;
|
|
||||||
private static final int T1C_H = 5;
|
|
||||||
private static final int T1L_L = 6;
|
|
||||||
private static final int T1L_H = 7;
|
|
||||||
private static final int T2C_L = 8;
|
|
||||||
private static final int T2C_H = 9;
|
|
||||||
private static final int SR = 10;
|
|
||||||
private static final int ACR = 11;
|
|
||||||
private static final int PCR = 12;
|
|
||||||
private static final int IFR = 13;
|
|
||||||
private static final int IER = 14;
|
|
||||||
private static final int ORA_H = 15;
|
|
||||||
|
|
||||||
public Via(int address) throws MemoryRangeException {
|
public Pia(int startAddress, int endAddress, String name) throws MemoryRangeException {
|
||||||
super(address, address + VIA_SIZE - 1, "VIA");
|
super(startAddress, endAddress, name);
|
||||||
}
|
this.name = name;
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(int address, int data) throws MemoryAccessException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(int address) throws MemoryAccessException {
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return null;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.loomcom.symon.devices;
|
||||||
|
|
||||||
|
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||||
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Very basic implementation of a MOS 6522 VIA.
|
||||||
|
*
|
||||||
|
* TODO: Implement timers as threads.
|
||||||
|
*/
|
||||||
|
public class Via6522 extends Pia {
|
||||||
|
public static final int VIA_SIZE = 16;
|
||||||
|
|
||||||
|
enum Register {
|
||||||
|
ORB, ORA, DDRB, DDRA, T1C_L, T1C_H, T1L_L, T1L_H,
|
||||||
|
T2C_L, T2C_H, SR, ACR, PCR, IFR, IER, ORA_H
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ports A and B
|
||||||
|
private char[] portData = {0, 0};
|
||||||
|
private char[] portDirections = {0, 0};
|
||||||
|
|
||||||
|
public Via6522(int address) throws MemoryRangeException {
|
||||||
|
super(address, address + VIA_SIZE - 1, "MOS 6522 VIA");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int address, int data) throws MemoryAccessException {
|
||||||
|
Register[] registers = Register.values();
|
||||||
|
|
||||||
|
if (address >= registers.length) {
|
||||||
|
throw new MemoryAccessException("Unknown register: " + address);
|
||||||
|
}
|
||||||
|
|
||||||
|
Register r = registers[address];
|
||||||
|
|
||||||
|
switch (r) {
|
||||||
|
case ORA:
|
||||||
|
case ORB:
|
||||||
|
case DDRA:
|
||||||
|
case DDRB:
|
||||||
|
case T1C_L:
|
||||||
|
case T1C_H:
|
||||||
|
case T1L_L:
|
||||||
|
case T1L_H:
|
||||||
|
case T2C_L:
|
||||||
|
case T2C_H:
|
||||||
|
case SR:
|
||||||
|
case ACR:
|
||||||
|
case PCR:
|
||||||
|
case IFR:
|
||||||
|
case IER:
|
||||||
|
case ORA_H:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(int address) throws MemoryAccessException {
|
||||||
|
Register[] registers = Register.values();
|
||||||
|
|
||||||
|
if (address >= registers.length) {
|
||||||
|
throw new MemoryAccessException("Unknown register: " + address);
|
||||||
|
}
|
||||||
|
|
||||||
|
Register r = registers[address];
|
||||||
|
|
||||||
|
switch (r) {
|
||||||
|
case ORA:
|
||||||
|
case ORB:
|
||||||
|
case DDRA:
|
||||||
|
case DDRB:
|
||||||
|
case T1C_L:
|
||||||
|
case T1C_H:
|
||||||
|
case T1L_L:
|
||||||
|
case T1L_H:
|
||||||
|
case T2C_L:
|
||||||
|
case T2C_H:
|
||||||
|
case SR:
|
||||||
|
case ACR:
|
||||||
|
case PCR:
|
||||||
|
case IFR:
|
||||||
|
case IER:
|
||||||
|
case ORA_H:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ import com.loomcom.symon.Cpu;
|
|||||||
import com.loomcom.symon.devices.Acia;
|
import com.loomcom.symon.devices.Acia;
|
||||||
import com.loomcom.symon.devices.Crtc;
|
import com.loomcom.symon.devices.Crtc;
|
||||||
import com.loomcom.symon.devices.Memory;
|
import com.loomcom.symon.devices.Memory;
|
||||||
import com.loomcom.symon.devices.Via;
|
import com.loomcom.symon.devices.Pia;
|
||||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
|
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ public interface Machine {
|
|||||||
|
|
||||||
public Acia getAcia();
|
public Acia getAcia();
|
||||||
|
|
||||||
public Via getVia();
|
public Pia getPia();
|
||||||
|
|
||||||
public Crtc getCrtc();
|
public Crtc getCrtc();
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ import com.loomcom.symon.devices.Acia;
|
|||||||
import com.loomcom.symon.devices.Acia6850;
|
import com.loomcom.symon.devices.Acia6850;
|
||||||
import com.loomcom.symon.devices.Crtc;
|
import com.loomcom.symon.devices.Crtc;
|
||||||
import com.loomcom.symon.devices.Memory;
|
import com.loomcom.symon.devices.Memory;
|
||||||
|
import com.loomcom.symon.devices.Pia;
|
||||||
import com.loomcom.symon.devices.SdController;
|
import com.loomcom.symon.devices.SdController;
|
||||||
import com.loomcom.symon.devices.Via;
|
|
||||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -118,7 +118,7 @@ public class MulticompMachine implements Machine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Via getVia() {
|
public Pia getPia() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import com.loomcom.symon.Cpu;
|
|||||||
import com.loomcom.symon.devices.Acia;
|
import com.loomcom.symon.devices.Acia;
|
||||||
import com.loomcom.symon.devices.Crtc;
|
import com.loomcom.symon.devices.Crtc;
|
||||||
import com.loomcom.symon.devices.Memory;
|
import com.loomcom.symon.devices.Memory;
|
||||||
import com.loomcom.symon.devices.Via;
|
import com.loomcom.symon.devices.Pia;
|
||||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,7 +52,7 @@ public class SimpleMachine implements Machine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Via getVia() {
|
public Pia getPia() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,7 @@ package com.loomcom.symon.machines;
|
|||||||
|
|
||||||
import com.loomcom.symon.Bus;
|
import com.loomcom.symon.Bus;
|
||||||
import com.loomcom.symon.Cpu;
|
import com.loomcom.symon.Cpu;
|
||||||
import com.loomcom.symon.devices.Acia;
|
import com.loomcom.symon.devices.*;
|
||||||
import com.loomcom.symon.devices.Acia6551;
|
|
||||||
import com.loomcom.symon.devices.Crtc;
|
|
||||||
import com.loomcom.symon.devices.Memory;
|
|
||||||
import com.loomcom.symon.devices.Via;
|
|
||||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -48,16 +44,15 @@ public class SymonMachine implements Machine {
|
|||||||
private static final int MEMORY_BASE = 0x0000;
|
private static final int MEMORY_BASE = 0x0000;
|
||||||
private static final int MEMORY_SIZE = 0x8000;
|
private static final int MEMORY_SIZE = 0x8000;
|
||||||
|
|
||||||
// VIA at $8000-$800F
|
// PIA at $8000-$800F
|
||||||
|
|
||||||
private static final int VIA_BASE = 0x8000;
|
private static final int PIA_BASE = 0x8000;
|
||||||
|
|
||||||
// ACIA at $8800-$8803
|
// ACIA at $8800-$8803
|
||||||
private static final int ACIA_BASE = 0x8800;
|
private static final int ACIA_BASE = 0x8800;
|
||||||
|
|
||||||
// CRTC at $9000-$9001
|
// CRTC at $9000-$9001
|
||||||
private static final int CRTC_BASE = 0x9000;
|
private static final int CRTC_BASE = 0x9000;
|
||||||
private static final int VIDEO_RAM_BASE = 0x7000;
|
|
||||||
|
|
||||||
// 16KB ROM at $C000-$FFFF
|
// 16KB ROM at $C000-$FFFF
|
||||||
private static final int ROM_BASE = 0xC000;
|
private static final int ROM_BASE = 0xC000;
|
||||||
@@ -68,7 +63,7 @@ public class SymonMachine implements Machine {
|
|||||||
private final Bus bus;
|
private final Bus bus;
|
||||||
private final Cpu cpu;
|
private final Cpu cpu;
|
||||||
private final Acia acia;
|
private final Acia acia;
|
||||||
private final Via via;
|
private final Pia pia;
|
||||||
private final Crtc crtc;
|
private final Crtc crtc;
|
||||||
private final Memory ram;
|
private final Memory ram;
|
||||||
private Memory rom;
|
private Memory rom;
|
||||||
@@ -78,13 +73,13 @@ public class SymonMachine implements Machine {
|
|||||||
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_BASE + MEMORY_SIZE - 1, false);
|
this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1, false);
|
||||||
this.via = new Via(VIA_BASE);
|
this.pia = new Via6522(PIA_BASE);
|
||||||
this.acia = new Acia6551(ACIA_BASE);
|
this.acia = new Acia6551(ACIA_BASE);
|
||||||
this.crtc = new Crtc(CRTC_BASE, ram);
|
this.crtc = new Crtc(CRTC_BASE, ram);
|
||||||
|
|
||||||
bus.addCpu(cpu);
|
bus.addCpu(cpu);
|
||||||
bus.addDevice(ram);
|
bus.addDevice(ram);
|
||||||
bus.addDevice(via);
|
bus.addDevice(pia);
|
||||||
bus.addDevice(acia);
|
bus.addDevice(acia);
|
||||||
bus.addDevice(crtc);
|
bus.addDevice(crtc);
|
||||||
|
|
||||||
@@ -124,8 +119,8 @@ public class SymonMachine implements Machine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Via getVia() {
|
public Pia getPia() {
|
||||||
return via;
|
return pia;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ package com.loomcom.symon.ui;
|
|||||||
|
|
||||||
import com.loomcom.symon.devices.Crtc;
|
import com.loomcom.symon.devices.Crtc;
|
||||||
import com.loomcom.symon.devices.DeviceChangeListener;
|
import com.loomcom.symon.devices.DeviceChangeListener;
|
||||||
|
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -36,8 +37,11 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.lang.System.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VideoWindow represents a graphics framebuffer backed by a 6545 CRTC.
|
* VideoWindow represents a graphics framebuffer backed by a 6545 CRTC.
|
||||||
* Each time the window's VideoPanel is repainted, the video memory is
|
* Each time the window's VideoPanel is repainted, the video memory is
|
||||||
@@ -65,7 +69,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
|||||||
|
|
||||||
private BufferedImage image;
|
private BufferedImage image;
|
||||||
private int[] charRom;
|
private int[] charRom;
|
||||||
private int[] videoRam;
|
|
||||||
|
|
||||||
private int horizontalDisplayed;
|
private int horizontalDisplayed;
|
||||||
private int verticalDisplayed;
|
private int verticalDisplayed;
|
||||||
@@ -85,17 +88,21 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
|||||||
private class VideoPanel extends JPanel {
|
private class VideoPanel extends JPanel {
|
||||||
@Override
|
@Override
|
||||||
public void paintComponent(Graphics g) {
|
public void paintComponent(Graphics g) {
|
||||||
for (int i = 0; i < crtc.getPageSize(); i++) {
|
try {
|
||||||
int address = crtc.getStartAddress() + i;
|
for (int i = 0; i < crtc.getPageSize(); i++) {
|
||||||
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
|
int address = crtc.getStartAddress() + i;
|
||||||
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
|
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
|
||||||
image.getRaster().setPixels(originX, originY, CHAR_WIDTH, scanLinesPerRow, getGlyph(address));
|
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
|
||||||
|
image.getRaster().setPixels(originX, originY, CHAR_WIDTH, scanLinesPerRow, getGlyph(address));
|
||||||
|
}
|
||||||
|
Graphics2D g2d = (Graphics2D) g;
|
||||||
|
if (shouldScale) {
|
||||||
|
g2d.scale(scaleX, scaleY);
|
||||||
|
}
|
||||||
|
g2d.drawImage(image, 0, 0, null);
|
||||||
|
} catch (MemoryAccessException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Memory Access Exception, can't paint video window! " + ex.getMessage());
|
||||||
}
|
}
|
||||||
Graphics2D g2d = (Graphics2D)g;
|
|
||||||
if (shouldScale) {
|
|
||||||
g2d.scale(scaleX, scaleY);
|
|
||||||
}
|
|
||||||
g2d.drawImage(image, 0, 0, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -131,8 +138,7 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
|||||||
|
|
||||||
this.scheduler = Executors.newSingleThreadScheduledExecutor();
|
this.scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
this.crtc = crtc;
|
this.crtc = crtc;
|
||||||
this.charRom = loadCharRom("/pet.rom");
|
this.charRom = loadCharRom("/cga8.rom");
|
||||||
this.videoRam = crtc.getDmaAccess();
|
|
||||||
this.scaleX = scaleX;
|
this.scaleX = scaleX;
|
||||||
this.scaleY = scaleY;
|
this.scaleY = scaleY;
|
||||||
this.shouldScale = (scaleX > 1 || scaleY > 1);
|
this.shouldScale = (scaleX > 1 || scaleY > 1);
|
||||||
@@ -158,13 +164,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshDisplay() {
|
|
||||||
// TODO: Verify whether this is necessary. Does `repaint()' do anything if the window is not visible?
|
|
||||||
if (isVisible()) {
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the CRTC on state change.
|
* Called by the CRTC on state change.
|
||||||
*/
|
*/
|
||||||
@@ -239,15 +238,13 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
|||||||
* @param address The address of the character being requested.
|
* @param address The address of the character being requested.
|
||||||
* @return An array of integers representing the pixel data.
|
* @return An array of integers representing the pixel data.
|
||||||
*/
|
*/
|
||||||
private int[] getGlyph(int address) {
|
private int[] getGlyph(int address) throws MemoryAccessException {
|
||||||
int chr = videoRam[address];
|
int chr = crtc.getCharAtAddress(address);
|
||||||
int romOffset = (chr & 0xff) * (CHAR_HEIGHT * CHAR_WIDTH);
|
int romOffset = (chr & 0xff) * (CHAR_HEIGHT * CHAR_WIDTH);
|
||||||
int[] glyph = new int[CHAR_WIDTH * scanLinesPerRow];
|
int[] glyph = new int[CHAR_WIDTH * scanLinesPerRow];
|
||||||
|
|
||||||
// Populate the character
|
// Populate the character
|
||||||
for (int i = 0; i < (CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow)); i++) {
|
arraycopy(charRom, romOffset, glyph, 0, CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow));
|
||||||
glyph[i] = charRom[romOffset + i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overlay the cursor
|
// Overlay the cursor
|
||||||
if (!hideCursor && crtc.isCursorEnabled() && crtc.getCursorPosition() == address) {
|
if (!hideCursor && crtc.isCursorEnabled() && crtc.getCursorPosition() == address) {
|
||||||
|
|||||||
@@ -19,19 +19,17 @@ import static org.mockito.Mockito.*;
|
|||||||
public class CrtcTest {
|
public class CrtcTest {
|
||||||
|
|
||||||
Crtc crtc;
|
Crtc crtc;
|
||||||
|
Memory memory;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
DeviceChangeListener changeListener;
|
DeviceChangeListener changeListener;
|
||||||
@Mock
|
|
||||||
Memory memory;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void createDevices() throws Exception {
|
public void createDevices() throws Exception {
|
||||||
|
memory = new Memory(0, 0x7fff);
|
||||||
|
|
||||||
crtc = new Crtc(0x9000, memory);
|
crtc = new Crtc(0x9000, memory);
|
||||||
crtc.registerListener(changeListener);
|
crtc.registerListener(changeListener);
|
||||||
|
|
||||||
when(memory.startAddress()).thenReturn(0);
|
|
||||||
when(memory.endAddress()).thenReturn(0x7fff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -340,4 +338,58 @@ public class CrtcTest {
|
|||||||
crtc.write(1, 0x80); // Can't position cursor
|
crtc.write(1, 0x80); // Can't position cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSetRowColumnAddressing() throws Exception {
|
||||||
|
assertEquals(false, crtc.getRowColumnAddressing());
|
||||||
|
crtc.write(0, 8); // Select mode control register
|
||||||
|
crtc.write(1, 0x04);
|
||||||
|
assertEquals(true, crtc.getRowColumnAddressing());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSetDisplayEnableSkew() throws Exception {
|
||||||
|
assertEquals(false, crtc.getDisplayEnableSkew());
|
||||||
|
crtc.write(0, 8); // Select mode control register
|
||||||
|
crtc.write(1, 0x10);
|
||||||
|
assertEquals(true, crtc.getDisplayEnableSkew());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSetCursorSkew() throws Exception {
|
||||||
|
assertEquals(false, crtc.getCursorSkew());
|
||||||
|
crtc.write(0, 8); // Select mode control register
|
||||||
|
crtc.write(1, 0x20);
|
||||||
|
assertEquals(true, crtc.getCursorSkew());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldDoStraightBinaryAddressing() throws Exception {
|
||||||
|
crtc.write(0, 8);
|
||||||
|
crtc.write(1, 0); // Select straight binary addressing
|
||||||
|
|
||||||
|
// Fill the memory with a repeating pattern
|
||||||
|
int videoMemoryBase = 0x7000;
|
||||||
|
int j = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2048; i++) {
|
||||||
|
memory.write(videoMemoryBase + i, j);
|
||||||
|
if (j == 255) {
|
||||||
|
j = 0;
|
||||||
|
} else {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now verify that straight-binary addressing of the CRTC works
|
||||||
|
j = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2048; i++) {
|
||||||
|
assertEquals(j, crtc.getCharAtAddress(videoMemoryBase + i));
|
||||||
|
if (j == 255) {
|
||||||
|
j = 0;
|
||||||
|
} else {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user