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

Merge remote-tracking branch 'upstream/master'

Conflicts:
	src/main/java/com/loomcom/symon/machines/MulticompMachine.java
This commit is contained in:
Maik Merten 2014-08-15 20:11:33 +02:00
commit e9f662cc17
17 changed files with 9298 additions and 84 deletions

View File

@ -279,6 +279,10 @@ running.
- Allow displaying ACIA status and dumping ACIA buffers, for
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.
## 7.0 Copyright and Acknowledgements

4
samples/ehbasic_crtc/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.rom
*.lst
*.map
*.o

View File

@ -0,0 +1,13 @@
CA=ca65
LD=ld65
all: ehbasic
ehbasic: ehbasic.o
$(LD) -C symon.config -vm -m ehbasic.map -o ehbasic.rom ehbasic.o
ehbasic.o:
$(CA) --listing -o ehbasic.o min_mon.asm
clean:
rm -f *.o *.rom *.map *.lst

View File

@ -0,0 +1,85 @@
This directoy contains a very slightly modified version of EhBASIC 2.22
to support the Symon simulator.
Usage
-----
The pre-assemled ROM image 'ehbasic.rom' in the "samples" directory is all you
need unless you want to rebuild this source code.
Just copy the image 'ehbasic.rom' to the directory where you run Symon, and
rename the file to 'rom.bin'. It will be loaded at memory location $D000 when
the simulator starts up. Click "Run" and you'll be presented with BASIC.
At the first prompt, type 'C' for a Cold Start
When prompted for free memory, type: $C000
Note that EhBASIC only accepts upper case input, so you'll need to use caps
lock (the cruise control for cool) to really make the most of it.
Building
--------
To build it, you'll need the CC65 tool chain from http://www.cc65.org/
and a suitable version of 'make'. Just typing:
% make
in this directory should re-build everything. You'll get a listing,
some object files, and the ROM image itself.
Changes from the EhBASIC 2.22
-----------------------------
- Minor syntax changes to allow assembly with the cc65 tool chain.
- Memory map modified for Symon.
- At reset, configure the 6551 ACIA for 8-N-1, 2400 baud.
- Monitor routines 'ACIAin' and 'ACIAout' modified for the 6551 ACIA.
Specifically, reading and writing will check the 6551 status register for
the status of rx and tx registers before receive or transmit.
EhBASIC is copyright Lee Davison <leeedavison@googlemail.com>
My changes are so slight that I hesitate to even say this, but for "CYA"
reasons:
Changes are copyright Seth Morabito <web@loomcom.com> and are distributed under
the same license as EhBASIC. I claim no commercial interest whatsoever. Any
commercial use must be negotiated directly with Lee Davison.
Original EhBASIC 2.22 README
----------------------------
Enhanced BASIC is a BASIC interpreter for the 6502 family microprocessors. It
is constructed to be quick and powerful and easily ported between 6502 systems.
It requires few resources to run and includes instructions to facilitate easy
low level handling of hardware devices. It also retains most of the powerful
high level instructions from similar BASICs.
EhBASIC is free but not copyright free. For non commercial use there is only one
restriction, any derivative work should include, in any binary image distributed,
the string "Derived from EhBASIC" and in any distribution that includes human
readable files a file that includes the above string in a human readable form
e.g. not as a comment in an HTML file.
For commercial use please contact me, Lee Davison, at leeedavison@googlemail.com
for conditions.
For more information on EhBASIC, other versions of EhBASIC and other projects
please visit my site at ..
http://mycorner.no-ip.org/index.html
P.S. c't magazin, henceforth refered to as "those thieving german bastards", are
prohibited from using this or any version of EhBASIC for any of their projects
or products. The excuse "we don't charge people for it" doesn't wash, it adds
value to your product so you owe me.

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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;
}

View File

@ -364,6 +364,9 @@ public class Simulator {
if (videoWindow != null && stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) {
stepsSinceLastCrtcRefresh = 0;
if (videoWindow.isVisible()) {
videoWindow.repaint();
}
}
// This is a very expensive update, and we're doing it without

View File

@ -65,6 +65,11 @@ public class Crtc extends Device {
private int currentRegister = 0;
// Status bits
private boolean rowColumnAddressing = false;
private boolean displayEnableSkew = false;
private boolean cursorSkew = false;
private Memory memory;
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException, IOException {
@ -118,8 +123,9 @@ public class Crtc extends Device {
return null;
}
public int[] getDmaAccess() {
return memory.getDmaAccess();
public int getCharAtAddress(int address) throws MemoryAccessException {
// TODO: Row/Column addressing
return memory.read(address);
}
public int getHorizontalDisplayed() {
@ -162,6 +168,18 @@ public class Crtc extends Device {
return pageSize;
}
public boolean getRowColumnAddressing() {
return rowColumnAddressing;
}
public boolean getDisplayEnableSkew() {
return displayEnableSkew;
}
public boolean getCursorSkew() {
return cursorSkew;
}
private void setCurrentRegister(int registerNumber) {
this.currentRegister = registerNumber;
}
@ -180,7 +198,9 @@ public class Crtc extends Device {
pageSize = horizontalDisplayed * verticalDisplayed;
break;
case MODE_CONTROL:
// TODO: Implement multiple addressing modes and cursor skew.
rowColumnAddressing = (data & 0x04) != 0;
displayEnableSkew = (data & 0x10) != 0;
cursorSkew = (data & 0x20) != 0;
break;
case SCAN_LINE:
scanLinesPerRow = data;

View File

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

View File

@ -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;
}
}

View File

@ -29,7 +29,7 @@ import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.exceptions.MemoryRangeException;
@ -43,7 +43,7 @@ public interface Machine {
public Acia getAcia();
public Via getVia();
public Pia getPia();
public Crtc getCrtc();

View File

@ -30,8 +30,8 @@ import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Acia6850;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.devices.SdController;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.File;
import java.util.logging.Logger;
@ -118,7 +118,7 @@ public class MulticompMachine implements Machine {
}
@Override
public Via getVia() {
public Pia getPia() {
return null;
}

View File

@ -5,7 +5,7 @@ import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.devices.Via;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.exceptions.MemoryRangeException;
/**
@ -52,7 +52,7 @@ public class SimpleMachine implements Machine {
}
@Override
public Via getVia() {
public Pia getPia() {
return null;
}

View File

@ -26,11 +26,7 @@ package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.Acia;
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.devices.*;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.io.File;
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_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
private static final int ACIA_BASE = 0x8800;
// CRTC at $9000-$9001
private static final int CRTC_BASE = 0x9000;
private static final int VIDEO_RAM_BASE = 0x7000;
// 16KB ROM at $C000-$FFFF
private static final int ROM_BASE = 0xC000;
@ -68,7 +63,7 @@ public class SymonMachine implements Machine {
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Via via;
private final Pia pia;
private final Crtc crtc;
private final Memory ram;
private Memory rom;
@ -78,13 +73,13 @@ public class SymonMachine implements Machine {
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.cpu = new Cpu();
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.crtc = new Crtc(CRTC_BASE, ram);
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(via);
bus.addDevice(pia);
bus.addDevice(acia);
bus.addDevice(crtc);
@ -124,8 +119,8 @@ public class SymonMachine implements Machine {
}
@Override
public Via getVia() {
return via;
public Pia getPia() {
return pia;
}
@Override

View File

@ -25,6 +25,7 @@ package com.loomcom.symon.ui;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.DeviceChangeListener;
import com.loomcom.symon.exceptions.MemoryAccessException;
import javax.swing.*;
import java.awt.*;
@ -36,8 +37,11 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.lang.System.*;
/**
* VideoWindow represents a graphics framebuffer backed by a 6545 CRTC.
* 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 int[] charRom;
private int[] videoRam;
private int horizontalDisplayed;
private int verticalDisplayed;
@ -85,17 +88,21 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
private class VideoPanel extends JPanel {
@Override
public void paintComponent(Graphics g) {
for (int i = 0; i < crtc.getPageSize(); i++) {
int address = crtc.getStartAddress() + i;
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
image.getRaster().setPixels(originX, originY, CHAR_WIDTH, scanLinesPerRow, getGlyph(address));
try {
for (int i = 0; i < crtc.getPageSize(); i++) {
int address = crtc.getStartAddress() + i;
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
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
@ -131,8 +138,7 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
this.scheduler = Executors.newSingleThreadScheduledExecutor();
this.crtc = crtc;
this.charRom = loadCharRom("/pet.rom");
this.videoRam = crtc.getDmaAccess();
this.charRom = loadCharRom("/cga8.rom");
this.scaleX = scaleX;
this.scaleY = scaleY;
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.
*/
@ -239,15 +238,13 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
* @param address The address of the character being requested.
* @return An array of integers representing the pixel data.
*/
private int[] getGlyph(int address) {
int chr = videoRam[address];
private int[] getGlyph(int address) throws MemoryAccessException {
int chr = crtc.getCharAtAddress(address);
int romOffset = (chr & 0xff) * (CHAR_HEIGHT * CHAR_WIDTH);
int[] glyph = new int[CHAR_WIDTH * scanLinesPerRow];
// Populate the character
for (int i = 0; i < (CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow)); i++) {
glyph[i] = charRom[romOffset + i];
}
arraycopy(charRom, romOffset, glyph, 0, CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow));
// Overlay the cursor
if (!hideCursor && crtc.isCursorEnabled() && crtc.getCursorPosition() == address) {

View File

@ -19,19 +19,17 @@ import static org.mockito.Mockito.*;
public class CrtcTest {
Crtc crtc;
Memory memory;
@Mock
DeviceChangeListener changeListener;
@Mock
Memory memory;
@Before
public void createDevices() throws Exception {
memory = new Memory(0, 0x7fff);
crtc = new Crtc(0x9000, memory);
crtc.registerListener(changeListener);
when(memory.startAddress()).thenReturn(0);
when(memory.endAddress()).thenReturn(0x7fff);
}
@Test
@ -340,4 +338,58 @@ public class CrtcTest {
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++;
}
}
}
}