1
0
mirror of https://github.com/irmen/ksim65.git synced 2024-06-01 06:41:34 +00:00

implemented sprites, code reformatting

This commit is contained in:
Irmen de Jong 2019-10-12 12:35:18 +02:00
parent 8a2212e34f
commit ea4b5239cc
26 changed files with 640 additions and 874 deletions

View File

@ -1,6 +1,12 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="RIGHT_MARGIN" value="140" />
<JetCodeStyleSettings>
<option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="true" />
<option name="CONTINUATION_INDENT_IN_ARGUMENT_LISTS" value="true" />
<option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="true" />
<option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="true" />
<option name="CONTINUATION_INDENT_IN_ELVIS" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<MarkdownNavigatorCodeStyleSettings>
@ -8,6 +14,18 @@
</MarkdownNavigatorCodeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="KEEP_LINE_BREAKS" value="false" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
<option name="SPACE_AROUND_ADDITIVE_OPERATORS" value="false" />
<option name="SPACE_AROUND_MULTIPLICATIVE_OPERATORS" value="false" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -22,7 +22,7 @@ Properties of this simulator:
- Aims to implements all 6502 and 65c02 instructions, including the 'illegal' 6502 instructions (not yet done)
- correct BCD mode for adc/sbc instructions on both cpu types
- passes several extensive unit test suites that verify instruction and cpu flags behavior
- provide a few virtual example machines, one of which is a Commodore-64 (character display mode only for now)
- provide a few virtual example machines, one of which is a Commodore-64
## Documentation
@ -39,6 +39,9 @@ There's another one ``ehBasicMain`` that is configured to run the "enhanced 6502
![ehBasic](ehbasic.png)
Finally there is a fairly functional C64 emulator running the actual roms (not included,
but can be easily found elsewhere for example with the Vice emulator):
but can be easily found elsewhere for example with the [Vice emulator](http://vice-emu.sourceforge.net/).
The emulator supports character mode, bitmap mode (hires and multicolor), hardware sprites and
various timers and IRQs. It's not cycle perfect, and the video display is drawn on a per-frame basis,
so raster splits/rasterbars are impossible. But many other things work fine.
![C64 emulation](c64.png)

View File

@ -11,11 +11,8 @@ import java.awt.event.KeyEvent
* Depending on what CIA it is (1 or 2), some registers do different things on the C64.
* This implementation provides a working keyboard matrix, TOD clock, and the essentials of the timer A and B.
*/
class Cia(val number: Int,
startAddress: Address, endAddress: Address,
val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress)
{
private var ramBuffer = Array<UByte>(endAddress - startAddress + 1) { 0 }
class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress) {
private var ramBuffer = Array<UByte>(endAddress-startAddress+1) { 0 }
private var regPRA = 0xff
class TimeOfDay {
@ -34,7 +31,7 @@ class Cia(val number: Int,
if (!running) {
updatedAt = System.currentTimeMillis()
startedAt = updatedAt
userStartTime = hours * 3600 + minutes * 60 + seconds + tenths / 10.0
userStartTime = hours*3600+minutes*60+seconds+tenths/10.0
running = true
}
latch(false)
@ -50,19 +47,18 @@ class Cia(val number: Int,
}
fun update() {
if(!running || latched)
return
if (!running || latched) return
latchedTime = System.currentTimeMillis()
if (updatedAt != latchedTime) {
updatedAt = latchedTime
var elapsedSeconds = (latchedTime.toDouble() - startedAt) / 1000.0 + userStartTime
hours = (elapsedSeconds / 3600).toInt()
elapsedSeconds -= hours * 3600
minutes = (elapsedSeconds / 60).toInt()
elapsedSeconds -= minutes * 60
var elapsedSeconds = (latchedTime.toDouble()-startedAt)/1000.0+userStartTime
hours = (elapsedSeconds/3600).toInt()
elapsedSeconds -= hours*3600
minutes = (elapsedSeconds/60).toInt()
elapsedSeconds -= minutes*60
seconds = (elapsedSeconds).toInt()
elapsedSeconds -= seconds
tenths = (elapsedSeconds * 10).toInt()
tenths = (elapsedSeconds*10).toInt()
}
}
}
@ -76,53 +72,46 @@ class Cia(val number: Int,
private var timerAinterruptEnabled = false
private var timerBinterruptEnabled = false
private data class HostKeyPress(val code: Int, val rightSide: Boolean=false, val numpad: Boolean=false)
private data class HostKeyPress(val code: Int, val rightSide: Boolean = false, val numpad: Boolean = false)
private val hostKeyPresses = mutableSetOf<HostKeyPress>()
init {
require(endAddress - startAddress + 1 == 256) { "cia requires exactly 256 memory bytes (16*16 mirrored)" }
require(endAddress-startAddress+1 == 256) { "cia requires exactly 256 memory bytes (16*16 mirrored)" }
}
override fun clock() {
totalCycles++
if(totalCycles % 20000 == 0) {
if (totalCycles%20000 == 0) {
// TOD resolution is 0.1 second, no need to update it in every cycle
tod.update()
}
if(ramBuffer[0x0e].toInt() and 1 != 0) {
if (ramBuffer[0x0e].toInt() and 1 != 0) {
// timer A is enabled, assume system cycles counting for now
timerAactual--
if(timerAactual==0 && timerAinterruptEnabled) {
if(number==1)
cpu.irq()
else if(number==2)
cpu.nmi()
if (timerAactual == 0 && timerAinterruptEnabled) {
if (number == 1) cpu.irq()
else if (number == 2) cpu.nmi()
}
if(timerAactual<0)
timerAactual = if(ramBuffer[0x0e].toInt() and 0b00001000 != 0) 0 else timerAset
if (timerAactual < 0) timerAactual = if (ramBuffer[0x0e].toInt() and 0b00001000 != 0) 0 else timerAset
}
if(ramBuffer[0x0f].toInt() and 1 != 0) {
if (ramBuffer[0x0f].toInt() and 1 != 0) {
// timer B is enabled
val regCRB = ramBuffer[0x0f].toInt()
if(regCRB and 0b01000000 != 0) {
if (regCRB and 0b01000000 != 0) {
// timer B counts timer A underruns
if(timerAactual==0)
timerBactual--
if (timerAactual == 0) timerBactual--
} else {
// timer B counts just the system cycles
timerBactual--
}
if(timerBactual==0 && timerBinterruptEnabled) {
if(number==1)
cpu.irq()
else if(number==2)
cpu.nmi()
if (timerBactual == 0 && timerBinterruptEnabled) {
if (number == 1) cpu.irq()
else if (number == 2) cpu.nmi()
}
if(timerBactual<0)
timerBactual = if(regCRB and 0b00001000 != 0) 0 else timerBset
if (timerBactual < 0) timerBactual = if (regCRB and 0b00001000 != 0) 0 else timerBset
}
}
@ -147,7 +136,7 @@ class Cia(val number: Int,
return (presses.inv() and 255).toShort()
}
val register = (address - startAddress) and 15
val register = (address-startAddress) and 15
if (number == 1 && register == 0x01) {
// register 1 on CIA#1 is the keyboard data port
// if bit is cleared in PRA, contains keys pressed in that column of the matrix
@ -158,107 +147,54 @@ class Cia(val number: Int,
}
0b11111110 -> {
// read column 0
scanColumn(
HostKeyPress(KeyEvent.VK_DOWN),
HostKeyPress(KeyEvent.VK_F5),
HostKeyPress(KeyEvent.VK_F3),
HostKeyPress(KeyEvent.VK_F1),
HostKeyPress(KeyEvent.VK_F7),
HostKeyPress(KeyEvent.VK_RIGHT),
HostKeyPress(KeyEvent.VK_ENTER),
HostKeyPress(KeyEvent.VK_BACK_SPACE)
)
scanColumn(HostKeyPress(KeyEvent.VK_DOWN), HostKeyPress(KeyEvent.VK_F5), HostKeyPress(KeyEvent.VK_F3),
HostKeyPress(KeyEvent.VK_F1), HostKeyPress(KeyEvent.VK_F7), HostKeyPress(KeyEvent.VK_RIGHT),
HostKeyPress(KeyEvent.VK_ENTER), HostKeyPress(KeyEvent.VK_BACK_SPACE))
}
0b11111101 -> {
// read column 1
scanColumn(
HostKeyPress(KeyEvent.VK_SHIFT), // left shift
HostKeyPress(KeyEvent.VK_E),
HostKeyPress(KeyEvent.VK_S),
HostKeyPress(KeyEvent.VK_Z),
HostKeyPress(KeyEvent.VK_4),
HostKeyPress(KeyEvent.VK_A),
HostKeyPress(KeyEvent.VK_W),
HostKeyPress(KeyEvent.VK_3)
)
scanColumn(HostKeyPress(KeyEvent.VK_SHIFT), // left shift
HostKeyPress(KeyEvent.VK_E), HostKeyPress(KeyEvent.VK_S), HostKeyPress(KeyEvent.VK_Z),
HostKeyPress(KeyEvent.VK_4), HostKeyPress(KeyEvent.VK_A), HostKeyPress(KeyEvent.VK_W),
HostKeyPress(KeyEvent.VK_3))
}
0b11111011 -> {
// read column 2
scanColumn(
HostKeyPress(KeyEvent.VK_X),
HostKeyPress(KeyEvent.VK_T),
HostKeyPress(KeyEvent.VK_F),
HostKeyPress(KeyEvent.VK_C),
HostKeyPress(KeyEvent.VK_6),
HostKeyPress(KeyEvent.VK_D),
HostKeyPress(KeyEvent.VK_R),
HostKeyPress(KeyEvent.VK_5)
)
scanColumn(HostKeyPress(KeyEvent.VK_X), HostKeyPress(KeyEvent.VK_T), HostKeyPress(KeyEvent.VK_F),
HostKeyPress(KeyEvent.VK_C), HostKeyPress(KeyEvent.VK_6), HostKeyPress(KeyEvent.VK_D),
HostKeyPress(KeyEvent.VK_R), HostKeyPress(KeyEvent.VK_5))
}
0b11110111 -> {
// read column 3
scanColumn(
HostKeyPress(KeyEvent.VK_V),
HostKeyPress(KeyEvent.VK_U),
HostKeyPress(KeyEvent.VK_H),
HostKeyPress(KeyEvent.VK_B),
HostKeyPress(KeyEvent.VK_8),
HostKeyPress(KeyEvent.VK_G),
HostKeyPress(KeyEvent.VK_Y),
HostKeyPress(KeyEvent.VK_7)
)
scanColumn(HostKeyPress(KeyEvent.VK_V), HostKeyPress(KeyEvent.VK_U), HostKeyPress(KeyEvent.VK_H),
HostKeyPress(KeyEvent.VK_B), HostKeyPress(KeyEvent.VK_8), HostKeyPress(KeyEvent.VK_G),
HostKeyPress(KeyEvent.VK_Y), HostKeyPress(KeyEvent.VK_7))
}
0b11101111 -> {
// read column 4
scanColumn(
HostKeyPress(KeyEvent.VK_N),
HostKeyPress(KeyEvent.VK_O),
HostKeyPress(KeyEvent.VK_K),
HostKeyPress(KeyEvent.VK_M),
HostKeyPress(KeyEvent.VK_0),
HostKeyPress(KeyEvent.VK_J),
HostKeyPress(KeyEvent.VK_I),
HostKeyPress(KeyEvent.VK_9)
)
scanColumn(HostKeyPress(KeyEvent.VK_N), HostKeyPress(KeyEvent.VK_O), HostKeyPress(KeyEvent.VK_K),
HostKeyPress(KeyEvent.VK_M), HostKeyPress(KeyEvent.VK_0), HostKeyPress(KeyEvent.VK_J),
HostKeyPress(KeyEvent.VK_I), HostKeyPress(KeyEvent.VK_9))
}
0b11011111 -> {
// read column 5
scanColumn(
HostKeyPress(KeyEvent.VK_COMMA),
HostKeyPress(KeyEvent.VK_AT),
HostKeyPress(KeyEvent.VK_COLON),
HostKeyPress(KeyEvent.VK_PERIOD),
HostKeyPress(KeyEvent.VK_MINUS),
HostKeyPress(KeyEvent.VK_L),
HostKeyPress(KeyEvent.VK_P),
HostKeyPress(KeyEvent.VK_PLUS)
)
scanColumn(HostKeyPress(KeyEvent.VK_COMMA), HostKeyPress(KeyEvent.VK_AT), HostKeyPress(KeyEvent.VK_COLON),
HostKeyPress(KeyEvent.VK_PERIOD), HostKeyPress(KeyEvent.VK_MINUS), HostKeyPress(KeyEvent.VK_L),
HostKeyPress(KeyEvent.VK_P), HostKeyPress(KeyEvent.VK_PLUS))
}
0b10111111 -> {
// read column 6
scanColumn(
HostKeyPress(KeyEvent.VK_SLASH),
HostKeyPress(KeyEvent.VK_CIRCUMFLEX),
HostKeyPress(KeyEvent.VK_EQUALS),
HostKeyPress(KeyEvent.VK_SHIFT, rightSide = true), // right shift
HostKeyPress(KeyEvent.VK_HOME),
HostKeyPress(KeyEvent.VK_SEMICOLON),
HostKeyPress(KeyEvent.VK_ASTERISK),
HostKeyPress(KeyEvent.VK_DEAD_TILDE) // pound sign
scanColumn(HostKeyPress(KeyEvent.VK_SLASH), HostKeyPress(KeyEvent.VK_CIRCUMFLEX), HostKeyPress(KeyEvent.VK_EQUALS),
HostKeyPress(KeyEvent.VK_SHIFT, rightSide = true), // right shift
HostKeyPress(KeyEvent.VK_HOME), HostKeyPress(KeyEvent.VK_SEMICOLON), HostKeyPress(KeyEvent.VK_ASTERISK),
HostKeyPress(KeyEvent.VK_DEAD_TILDE) // pound sign
)
}
0b01111111 -> {
// read column 7
scanColumn(
HostKeyPress(KeyEvent.VK_ESCAPE),
HostKeyPress(KeyEvent.VK_Q),
HostKeyPress(KeyEvent.VK_ALT),
HostKeyPress(KeyEvent.VK_SPACE),
HostKeyPress(KeyEvent.VK_2),
HostKeyPress(KeyEvent.VK_CONTROL),
HostKeyPress(KeyEvent.VK_BACK_QUOTE),
HostKeyPress(KeyEvent.VK_1)
)
scanColumn(HostKeyPress(KeyEvent.VK_ESCAPE), HostKeyPress(KeyEvent.VK_Q), HostKeyPress(KeyEvent.VK_ALT),
HostKeyPress(KeyEvent.VK_SPACE), HostKeyPress(KeyEvent.VK_2), HostKeyPress(KeyEvent.VK_CONTROL),
HostKeyPress(KeyEvent.VK_BACK_QUOTE), HostKeyPress(KeyEvent.VK_1))
}
else -> {
// invalid column selection
@ -287,8 +223,8 @@ class Cia(val number: Int,
}
private fun toBCD(data: Int): UByte {
val tens = data / 10
val ones = data - tens * 10
val tens = data/10
val ones = data-tens*10
return ((tens shl 4) or ones).toShort()
}
@ -296,40 +232,39 @@ class Cia(val number: Int,
val ibcd = bcd.toInt()
val tens = ibcd ushr 4
val ones = ibcd and 0x0f
return tens * 10 + ones
return tens*10+ones
}
override fun set(address: Address, data: UByte) {
val register = (address - startAddress) and 15
val register = (address-startAddress) and 15
if (number == 1 && register == 0x00) {
// PRA data port A (select keyboard matrix column)
regPRA = data.toInt()
}
if(register!=0x0d)
ramBuffer[register] = data
if (register != 0x0d) ramBuffer[register] = data
when (register) {
0x04 -> {
if(ramBuffer[0x0e].toInt() and 0b10000000 == 0) {
if (ramBuffer[0x0e].toInt() and 0b10000000 == 0) {
timerAset = (timerAset and 0xff00) or data.toInt()
timerAactual = timerAset
}
}
0x05 -> {
if(ramBuffer[0x0e].toInt() and 0b10000000 == 0) {
if (ramBuffer[0x0e].toInt() and 0b10000000 == 0) {
timerAset = (timerAset and 0x00ff) or (data.toInt() shl 8)
timerAactual = timerAset
}
}
0x06 -> {
if(ramBuffer[0x0f].toInt() and 0b10000000 == 0) {
if (ramBuffer[0x0f].toInt() and 0b10000000 == 0) {
timerBset = (timerBset and 0xff00) or data.toInt()
timerBactual = timerBset
}
}
0x07 -> {
if(ramBuffer[0x0f].toInt() and 0b10000000 == 0) {
if (ramBuffer[0x0f].toInt() and 0b10000000 == 0) {
timerBset = (timerBset and 0x00ff) or (data.toInt() shl 8)
timerBactual = timerBset
}
@ -342,7 +277,7 @@ class Cia(val number: Int,
tod.hours = fromBCD(data)
}
0x0d -> {
if(data.toInt() and 0b10000000 != 0) {
if (data.toInt() and 0b10000000 != 0) {
// set ICR bits
val newICR = ramBuffer[0x0d].toInt() or (data.toInt() and 0b01111111)
timerAinterruptEnabled = newICR and 1 != 0
@ -376,12 +311,8 @@ class Cia(val number: Int,
// to avoid some 'stuck' keys, if we receive a shift/control/alt RELEASE, we wipe the keyboard buffer
// (this can happen because we're changing the key code for some pressed keys below,
// and a released key doesn't always match the pressed key code anymore then)
if (event.id == KeyEvent.KEY_RELEASED && event.keyCode in listOf(
KeyEvent.VK_SHIFT,
KeyEvent.VK_CONTROL,
KeyEvent.VK_ALT,
KeyEvent.VK_ALT_GRAPH
)
if (event.id == KeyEvent.KEY_RELEASED && event.keyCode in listOf(KeyEvent.VK_SHIFT, KeyEvent.VK_CONTROL, KeyEvent.VK_ALT,
KeyEvent.VK_ALT_GRAPH)
) hostKeyPresses.clear()
// try to remap the keys a bit so a modern PC keyboard maps better to the keys of the C64

View File

@ -3,7 +3,9 @@ package razorvine.c64emu
import razorvine.ksim65.Cpu6502
import razorvine.ksim65.components.MemoryComponent
import razorvine.ksim65.components.UByte
import java.awt.*
import java.awt.Color
import java.awt.KeyboardFocusManager
import java.awt.Point
import java.awt.event.KeyEvent
import java.awt.event.KeyListener
import javax.swing.JFrame
@ -17,31 +19,31 @@ import javax.swing.Timer
object ScreenDefs {
const val SCREEN_WIDTH_CHARS = 40
const val SCREEN_HEIGHT_CHARS = 25
const val SCREEN_WIDTH = SCREEN_WIDTH_CHARS * 8
const val SCREEN_HEIGHT = SCREEN_HEIGHT_CHARS * 8
const val SCREEN_WIDTH = SCREEN_WIDTH_CHARS*8
const val SCREEN_HEIGHT = SCREEN_HEIGHT_CHARS*8
const val DISPLAY_PIXEL_SCALING: Double = 3.0
const val BORDER_SIZE = 24
class Palette {
// this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
private val sixteenColors = listOf(
Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
Color(0x813338), // 2 = red
Color(0x75cec8), // 3 = cyan
Color(0x8e3c97), // 4 = purple
Color(0x56ac4d), // 5 = green
Color(0x2e2c9b), // 6 = blue
Color(0xedf171), // 7 = yellow
Color(0x8e5029), // 8 = orange
Color(0x553800), // 9 = brown
Color(0xc46c71), // 10 = light red
Color(0x4a4a4a), // 11 = dark grey
Color(0x7b7b7b), // 12 = medium grey
Color(0xa9ff9f), // 13 = light green
Color(0x706deb), // 14 = light blue
Color(0xb2b2b2) // 15 = light grey
private val sixteenColors = listOf(Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
Color(0x813338), // 2 = red
Color(0x75cec8), // 3 = cyan
Color(0x8e3c97), // 4 = purple
Color(0x56ac4d), // 5 = green
Color(0x2e2c9b), // 6 = blue
Color(0xedf171), // 7 = yellow
Color(0x8e5029), // 8 = orange
Color(0x553800), // 9 = brown
Color(0xc46c71), // 10 = light red
Color(0x4a4a4a), // 11 = dark grey
Color(0x7b7b7b), // 12 = medium grey
Color(0xa9ff9f), // 13 = light green
Color(0x706deb), // 14 = light blue
Color(0xb2b2b2) // 15 = light grey
)
operator fun get(i: Int): Color = sixteenColors[i and 15]
operator fun get(i: UByte): Color = sixteenColors[i.toInt() and 15]
}
@ -49,13 +51,8 @@ object ScreenDefs {
val colorPalette = Palette()
}
class MainC64Window(
title: String,
chargenData: ByteArray,
val ram: MemoryComponent,
val cpu: Cpu6502,
val keypressCia: Cia
) : JFrame(title), KeyListener {
class MainC64Window(title: String, chargenData: ByteArray, val ram: MemoryComponent, val cpu: Cpu6502, val keypressCia: Cia) :
JFrame(title), KeyListener {
init {
defaultCloseOperation = EXIT_ON_CLOSE
isResizable = false
@ -65,7 +62,7 @@ class MainC64Window(
addKeyListener(this)
pack()
setLocationRelativeTo(null)
location = Point(location.x / 2, location.y)
location = Point(location.x/2, location.y)
setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, mutableSetOf())
setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, mutableSetOf())
requestFocusInWindow()
@ -73,7 +70,7 @@ class MainC64Window(
fun start(updateRate: Int) {
// repaint the screen's back buffer
val repaintTimer = Timer(1000 / updateRate) {
val repaintTimer = Timer(1000/updateRate) {
repaint()
}
repaintTimer.initialDelay = 0

View File

@ -24,18 +24,13 @@ internal class Screen(private val chargenData: ByteArray, val ram: MemoryCompone
init {
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
val gd = ge.defaultScreenDevice.defaultConfiguration
fullscreenImage = gd.createCompatibleImage(
ScreenDefs.SCREEN_WIDTH + 2 * ScreenDefs.BORDER_SIZE,
ScreenDefs.SCREEN_HEIGHT + 2 * ScreenDefs.BORDER_SIZE,
Transparency.OPAQUE
)
fullscreenImage = gd.createCompatibleImage(ScreenDefs.SCREEN_WIDTH+2*ScreenDefs.BORDER_SIZE,
ScreenDefs.SCREEN_HEIGHT+2*ScreenDefs.BORDER_SIZE, Transparency.OPAQUE)
fullscreenImage.accelerationPriority = 1.0f
fullscreenG2d = fullscreenImage.graphics as Graphics2D
val size = Dimension(
fullscreenImage.width * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt(),
fullscreenImage.height * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
)
val size = Dimension(fullscreenImage.width*ScreenDefs.DISPLAY_PIXEL_SCALING.toInt(),
fullscreenImage.height*ScreenDefs.DISPLAY_PIXEL_SCALING.toInt())
minimumSize = size
maximumSize = size
preferredSize = size
@ -46,16 +41,14 @@ internal class Screen(private val chargenData: ByteArray, val ram: MemoryCompone
private fun loadCharacters(shifted: Boolean): Array<BufferedImage> {
val chars = Array(256) {
BufferedImage(8, 8, BufferedImage.TYPE_BYTE_BINARY)
.also { it.accelerationPriority = 1.0f }
BufferedImage(8, 8, BufferedImage.TYPE_BYTE_BINARY).also { it.accelerationPriority = 1.0f }
}
val offset = if (shifted) 256 * 8 else 0
val offset = if (shifted) 256*8 else 0
for (char in 0..255) {
for (line in 0..7) {
val charbyte = chargenData[offset + char * 8 + line].toInt()
val charbyte = chargenData[offset+char*8+line].toInt()
for (x in 0..7) {
if (charbyte and (0b10000000 ushr x) != 0)
chars[char].setRGB(x, line, 0xffffff)
if (charbyte and (0b10000000 ushr x) != 0) chars[char].setRGB(x, line, 0xffffff)
}
}
}
@ -66,13 +59,10 @@ internal class Screen(private val chargenData: ByteArray, val ram: MemoryCompone
val windowG2d = graphics as Graphics2D
val vicSCROLY = ram[0xd011].toInt()
if (vicSCROLY and 0b10000 == 0) {
// screen blanked, only display border
// screen blanked, only display border color
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd020]]
fullscreenG2d.clearRect(
0, 0,
ScreenDefs.SCREEN_WIDTH + 2 * ScreenDefs.BORDER_SIZE,
ScreenDefs.SCREEN_HEIGHT + 2 * ScreenDefs.BORDER_SIZE
)
fullscreenG2d.clearRect(0, 0, ScreenDefs.SCREEN_WIDTH+2*ScreenDefs.BORDER_SIZE,
ScreenDefs.SCREEN_HEIGHT+2*ScreenDefs.BORDER_SIZE)
} else {
val vicSCROLX = ram[0xd016].toInt()
val vicVMCSB = ram[0xd018].toInt()
@ -84,66 +74,106 @@ internal class Screen(private val chargenData: ByteArray, val ram: MemoryCompone
else -> 0x0000
}
// draw the screen border
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd020]]
fullscreenG2d.clearRect(
0, 0,
ScreenDefs.SCREEN_WIDTH + 2 * ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE
)
fullscreenG2d.clearRect(
0, ScreenDefs.SCREEN_HEIGHT + ScreenDefs.BORDER_SIZE,
ScreenDefs.SCREEN_WIDTH + 2 * ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE
)
fullscreenG2d.clearRect(
0, ScreenDefs.BORDER_SIZE,
ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_HEIGHT
)
fullscreenG2d.clearRect(
ScreenDefs.SCREEN_WIDTH + ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE,
ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_HEIGHT
)
if (vicSCROLY and 0b00100000 != 0)
renderBitmapMode(vicBank, vicVMCSB, multiColorMode)
if (vicSCROLY and 0b00100000 != 0) renderBitmapMode(vicBank, vicVMCSB, multiColorMode)
else {
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd021]]
fullscreenG2d.clearRect(
ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE,
ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT
)
fullscreenG2d.clearRect(ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT)
renderCharacterMode(vicBank, vicVMCSB, multiColorMode)
}
renderSprites(vicBank)
renderBorder()
}
// scale and draw the image to the window, and simulate a slight scanline effect
windowG2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
windowG2d.drawImage(
fullscreenImage, 0, 0, (fullscreenImage.width * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
(fullscreenImage.height * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), null
)
windowG2d.drawImage(fullscreenImage, 0, 0, (fullscreenImage.width*ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
(fullscreenImage.height*ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), null)
windowG2d.color = Color(0, 0, 0, 40)
windowG2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
val width = fullscreenImage.width * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
val height = fullscreenImage.height * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
val width = fullscreenImage.width*ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
val height = fullscreenImage.height*ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
for (y in 0 until height step ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()) {
windowG2d.drawLine(0, y, width, y)
}
Toolkit.getDefaultToolkit().sync()
}
private fun renderBorder() {
// draw the screen border
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd020]]
fullscreenG2d.clearRect(0, 0, ScreenDefs.SCREEN_WIDTH+2*ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE)
fullscreenG2d.clearRect(0, ScreenDefs.SCREEN_HEIGHT+ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_WIDTH+2*ScreenDefs.BORDER_SIZE,
ScreenDefs.BORDER_SIZE)
fullscreenG2d.clearRect(0, ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_HEIGHT)
fullscreenG2d.clearRect(ScreenDefs.SCREEN_WIDTH+ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE,
ScreenDefs.SCREEN_HEIGHT)
}
private fun renderSprites(vicBank: Address) {
val pixels: IntArray = (fullscreenImage.raster.dataBuffer as DataBufferInt).data
val vicSPENA = ram[0xd015].toInt()
val vicXXPAND = ram[0xd01d].toInt()
val vicYXPAND = ram[0xd017].toInt()
for (i in 7 downTo 0) {
val bit = 1 shl i
if (vicSPENA and bit != 0) renderSprite(pixels, vicBank, i, vicXXPAND and bit != 0, vicYXPAND and bit != 0)
}
}
private fun renderSprite(pixels: IntArray, vicBank: Address, sprite: Int, expandX: Boolean, expandY: Boolean) {
val mx = ram[0xd010].toInt() and (1 shl sprite) != 0
var xstart = ram[0xd000+sprite*2].toInt()+if (mx) 256 else 0
var ystart = ram[0xd001+sprite*2].toInt()
if (xstart == 0 || xstart > 343 || ystart < 30 || ystart > 249) return
ystart -= 50
xstart -= 24
val sprptr = ram[vicBank+2040+sprite]*64
val color = ScreenDefs.colorPalette[ram[0xd027+sprite]].rgb
val offset = ScreenDefs.BORDER_SIZE+ScreenDefs.BORDER_SIZE*fullscreenImage.width+xstart+ystart*fullscreenImage.width
for (i in 0..62) {
val bits = ram[sprptr+i].toInt()
val x = (i%3)*if (expandX) 16 else 8
val y = (i/3)*if (expandY) 2 else 1
val pixOffset = offset+x+y*fullscreenImage.width
if (pixOffset < 0 || pixOffset >= pixels.size-fullscreenImage.width) break
if (expandX) {
for (px in 0..7) {
if (bits and (0b10000000 ushr px) != 0) {
pixels[pixOffset+px*2] = color
pixels[pixOffset+1+px*2] = color
if (expandY) {
pixels[pixOffset+fullscreenImage.width+px*2] = color
pixels[pixOffset+1+fullscreenImage.width+px*2] = color
}
}
}
} else {
for (px in 0..7) {
if (bits and (0b10000000 ushr px) != 0) {
pixels[pixOffset+px] = color
if (expandY) pixels[pixOffset+fullscreenImage.width+px] = color
}
}
}
}
// TODO multicolor sprites
// TODO sprite-background priorities
}
private fun renderCharacterMode(vicBank: Address, vicVMCSB: Int, multiColorMode: Boolean) {
if (multiColorMode) {
TODO("multicolor character mode")
} else {
// normal character mode
val screenAddress = vicBank + (vicVMCSB ushr 4) shl 10
val screenAddress = vicBank+(vicVMCSB ushr 4) shl 10
val charsetAddress = (vicVMCSB and 0b00001110) shl 10
if (charsetAddress == 0x1000 || charsetAddress == 0x1800) {
// use built-in character ROM
for (y in 0 until ScreenDefs.SCREEN_HEIGHT_CHARS) {
for (x in 0 until ScreenDefs.SCREEN_WIDTH_CHARS) {
val char = ram[screenAddress + x + y * ScreenDefs.SCREEN_WIDTH_CHARS].toInt()
val color = ram[0xd800 + x + y * ScreenDefs.SCREEN_WIDTH_CHARS].toInt() // colors always at $d800
val char = ram[screenAddress+x+y*ScreenDefs.SCREEN_WIDTH_CHARS].toInt()
val color = ram[0xd800+x+y*ScreenDefs.SCREEN_WIDTH_CHARS].toInt() // colors always at $d800
drawColoredChar(x, y, char, color, charsetAddress == 0x1800)
}
}
@ -155,8 +185,8 @@ internal class Screen(private val chargenData: ByteArray, val ram: MemoryCompone
}
private fun renderBitmapMode(vicBank: Address, vicVMCSB: Int, multiColorMode: Boolean) {
val bitmap = ram.getPages((vicBank ushr 8) + if (vicVMCSB and 0b00001000 != 0) 32 else 0, 32)
val colorBytes = ram.getPages((vicBank ushr 8) + ((vicVMCSB ushr 4) shl 2), 4)
val bitmap = ram.getPages((vicBank ushr 8)+if (vicVMCSB and 0b00001000 != 0) 32 else 0, 32)
val colorBytes = ram.getPages((vicBank ushr 8)+((vicVMCSB ushr 4) shl 2), 4)
val pixels: IntArray = (fullscreenImage.raster.dataBuffer as DataBufferInt).data
val screenColor = ScreenDefs.colorPalette[ram[0xd021]].rgb
if (multiColorMode) {
@ -165,10 +195,10 @@ internal class Screen(private val chargenData: ByteArray, val ram: MemoryCompone
fourColors[0b00] = screenColor
for (y in 0 until ScreenDefs.SCREEN_HEIGHT) {
for (x in 0 until ScreenDefs.SCREEN_WIDTH/2 step 4) {
val colorIdx = ScreenDefs.SCREEN_WIDTH_CHARS * (y ushr 3) + (x ushr 2)
val colorIdx = ScreenDefs.SCREEN_WIDTH_CHARS*(y ushr 3)+(x ushr 2)
fourColors[0b01] = ScreenDefs.colorPalette[colorBytes[colorIdx].toInt() ushr 4].rgb
fourColors[0b10] = ScreenDefs.colorPalette[colorBytes[colorIdx].toInt()].rgb
fourColors[0b11] = ScreenDefs.colorPalette[ram[0xd800 + colorIdx].toInt()].rgb
fourColors[0b11] = ScreenDefs.colorPalette[ram[0xd800+colorIdx].toInt()].rgb
draw4bitmapPixelsMc(pixels, x, y, bitmap, fourColors)
}
}
@ -176,7 +206,7 @@ internal class Screen(private val chargenData: ByteArray, val ram: MemoryCompone
// bitmap mode 320x200
for (y in 0 until ScreenDefs.SCREEN_HEIGHT) {
for (x in 0 until ScreenDefs.SCREEN_WIDTH step 8) {
val colorIdx = ScreenDefs.SCREEN_WIDTH_CHARS * (y ushr 3) + (x ushr 3)
val colorIdx = ScreenDefs.SCREEN_WIDTH_CHARS*(y ushr 3)+(x ushr 3)
val bgColor = ScreenDefs.colorPalette[colorBytes[colorIdx].toInt()].rgb
val fgColor = ScreenDefs.colorPalette[colorBytes[colorIdx].toInt() ushr 4].rgb
draw8bitmapPixels(pixels, x, y, bitmap, fgColor, bgColor)
@ -185,43 +215,32 @@ internal class Screen(private val chargenData: ByteArray, val ram: MemoryCompone
}
}
private fun draw8bitmapPixels(
pixels: IntArray, xstart: Int, y: Int,
bitmap: Array<UByte>, fgColorRgb: Int, bgColorRgb: Int )
{
val offset = ScreenDefs.BORDER_SIZE + ScreenDefs.BORDER_SIZE * fullscreenImage.width +
xstart + y * fullscreenImage.width
val byte = bitmap[ScreenDefs.SCREEN_WIDTH_CHARS * (y and 248) + (y and 7) + (xstart and 504)].toInt()
private fun draw8bitmapPixels(pixels: IntArray, xstart: Int, y: Int, bitmap: Array<UByte>, fgColorRgb: Int, bgColorRgb: Int) {
val offset = ScreenDefs.BORDER_SIZE+ScreenDefs.BORDER_SIZE*fullscreenImage.width+xstart+y*fullscreenImage.width
val byte = bitmap[ScreenDefs.SCREEN_WIDTH_CHARS*(y and 248)+(y and 7)+(xstart and 504)].toInt()
pixels[offset] = if (byte and 0b10000000 != 0) fgColorRgb else bgColorRgb
pixels[offset + 1] = if (byte and 0b01000000 != 0) fgColorRgb else bgColorRgb
pixels[offset + 2] = if (byte and 0b00100000 != 0) fgColorRgb else bgColorRgb
pixels[offset + 3] = if (byte and 0b00010000 != 0) fgColorRgb else bgColorRgb
pixels[offset + 4] = if (byte and 0b00001000 != 0) fgColorRgb else bgColorRgb
pixels[offset + 5] = if (byte and 0b00000100 != 0) fgColorRgb else bgColorRgb
pixels[offset + 6] = if (byte and 0b00000010 != 0) fgColorRgb else bgColorRgb
pixels[offset + 7] = if (byte and 0b00000001 != 0) fgColorRgb else bgColorRgb
pixels[offset+1] = if (byte and 0b01000000 != 0) fgColorRgb else bgColorRgb
pixels[offset+2] = if (byte and 0b00100000 != 0) fgColorRgb else bgColorRgb
pixels[offset+3] = if (byte and 0b00010000 != 0) fgColorRgb else bgColorRgb
pixels[offset+4] = if (byte and 0b00001000 != 0) fgColorRgb else bgColorRgb
pixels[offset+5] = if (byte and 0b00000100 != 0) fgColorRgb else bgColorRgb
pixels[offset+6] = if (byte and 0b00000010 != 0) fgColorRgb else bgColorRgb
pixels[offset+7] = if (byte and 0b00000001 != 0) fgColorRgb else bgColorRgb
}
private fun draw4bitmapPixelsMc(pixels: IntArray, xstart: Int, y: Int,
bitmap: Array<UByte>, fourColors: IntArray) {
val realx = xstart * 2
val offset = ScreenDefs.BORDER_SIZE + ScreenDefs.BORDER_SIZE * fullscreenImage.width +
realx + y * fullscreenImage.width
val byte = bitmap[ScreenDefs.SCREEN_WIDTH_CHARS * (y and 248) + (y and 7) + (realx and 504)].toInt()
val colors = listOf(
byte and 0b11000000 ushr 6,
byte and 0b00110000 ushr 4,
byte and 0b00001100 ushr 2,
byte and 0b00000011
)
private fun draw4bitmapPixelsMc(pixels: IntArray, xstart: Int, y: Int, bitmap: Array<UByte>, fourColors: IntArray) {
val realx = xstart*2
val offset = ScreenDefs.BORDER_SIZE+ScreenDefs.BORDER_SIZE*fullscreenImage.width+realx+y*fullscreenImage.width
val byte = bitmap[ScreenDefs.SCREEN_WIDTH_CHARS*(y and 248)+(y and 7)+(realx and 504)].toInt()
val colors = listOf(byte and 0b11000000 ushr 6, byte and 0b00110000 ushr 4, byte and 0b00001100 ushr 2, byte and 0b00000011)
pixels[offset] = fourColors[colors[0]]
pixels[offset + 1] = fourColors[colors[0]]
pixels[offset + 2] = fourColors[colors[1]]
pixels[offset + 3] = fourColors[colors[1]]
pixels[offset + 4] = fourColors[colors[2]]
pixels[offset + 5] = fourColors[colors[2]]
pixels[offset + 6] = fourColors[colors[3]]
pixels[offset + 7] = fourColors[colors[3]]
pixels[offset+1] = fourColors[colors[0]]
pixels[offset+2] = fourColors[colors[1]]
pixels[offset+3] = fourColors[colors[1]]
pixels[offset+4] = fourColors[colors[2]]
pixels[offset+5] = fourColors[colors[2]]
pixels[offset+6] = fourColors[colors[3]]
pixels[offset+7] = fourColors[colors[3]]
}
private val coloredCharacters = mutableMapOf<Triple<Int, Int, Boolean>, BufferedImage>()
@ -247,6 +266,6 @@ internal class Screen(private val chargenData: ByteArray, val ram: MemoryCompone
coloredCharacters[Triple(char, color, shifted)] = colored
cached = colored
}
fullscreenG2d.drawImage(cached, x * 8 + ScreenDefs.BORDER_SIZE, y * 8 + ScreenDefs.BORDER_SIZE, null)
fullscreenG2d.drawImage(cached, x*8+ScreenDefs.BORDER_SIZE, y*8+ScreenDefs.BORDER_SIZE, null)
}
}

View File

@ -10,14 +10,14 @@ import razorvine.ksim65.components.UByte
* It only has some logic to keep track of the raster line
* This chip is the PAL version (50 Hz screen refresh, 312 vertical raster lines)
*/
class VicII(startAddress: Address, endAddress: Address, val cpu: Cpu6502): MemMappedComponent(startAddress, endAddress) {
private var ramBuffer = Array<UByte>(endAddress - startAddress + 1) { 0xff }
class VicII(startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress) {
private var ramBuffer = Array<UByte>(endAddress-startAddress+1) { 0xff }
private var rasterIrqLine = 0
private var scanlineClocks = 0
private var interruptStatusRegisterD019 = 0
var currentRasterLine = 1
val vsync: Boolean
get() = currentRasterLine==0
get() = currentRasterLine == 0
companion object {
const val framerate = 50
@ -25,7 +25,7 @@ class VicII(startAddress: Address, endAddress: Address, val cpu: Cpu6502): MemMa
}
init {
require(endAddress - startAddress + 1 == 0x400) { "vic-II requires exactly 1024 memory bytes (64*16 mirrored)" }
require(endAddress-startAddress+1 == 0x400) { "vic-II requires exactly 1024 memory bytes (64*16 mirrored)" }
ramBuffer[0x1a] = 0 // initially, disable IRQs
}
@ -34,16 +34,13 @@ class VicII(startAddress: Address, endAddress: Address, val cpu: Cpu6502): MemMa
if (scanlineClocks == 63) {
scanlineClocks = 0
currentRasterLine++
if (currentRasterLine >= rasterlines)
currentRasterLine = 0
if (currentRasterLine >= rasterlines) currentRasterLine = 0
interruptStatusRegisterD019 = if (currentRasterLine == rasterIrqLine) {
// signal that current raster line is equal to the desired IRQ raster line
// schedule an IRQ as well if the raster interrupt is enabled
if((ramBuffer[0x1a].toInt() and 1) != 0)
cpu.irq()
if ((ramBuffer[0x1a].toInt() and 1) != 0) cpu.irq()
interruptStatusRegisterD019 or 0b10000001
} else
interruptStatusRegisterD019 and 0b11111110
} else interruptStatusRegisterD019 and 0b11111110
}
}
@ -54,7 +51,7 @@ class VicII(startAddress: Address, endAddress: Address, val cpu: Cpu6502): MemMa
}
override fun get(address: Address): UByte {
return when(val register = (address - startAddress) and 63) {
return when (val register = (address-startAddress) and 63) {
0x11 -> (0b00011011 or ((currentRasterLine and 0b100000000) ushr 1)).toShort()
0x12 -> {
(currentRasterLine and 255).toShort()
@ -65,7 +62,7 @@ class VicII(startAddress: Address, endAddress: Address, val cpu: Cpu6502): MemMa
}
override fun set(address: Address, data: UByte) {
val register = (address - startAddress) and 63
val register = (address-startAddress) and 63
ramBuffer[register] = data
when (register) {
0x11 -> {

View File

@ -59,10 +59,10 @@ class C64Machine(title: String) : IVirtualMachine {
hostDisplay.iconImage = ImageIcon(javaClass.getResource("/icon.png")).image
debugWindow.iconImage = hostDisplay.iconImage
debugWindow.setLocation(hostDisplay.location.x + hostDisplay.width, hostDisplay.location.y)
debugWindow.setLocation(hostDisplay.location.x+hostDisplay.width, hostDisplay.location.y)
debugWindow.isVisible = true
hostDisplay.isVisible = true
hostDisplay.start(30)
//hostDisplay.start(30)
}
private fun breakpointKernelLoad(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
@ -70,10 +70,10 @@ class C64Machine(title: String) : IVirtualMachine {
val fnlen = ram[0xb7] // file name length
val fa = ram[0xba] // device number
val sa = ram[0xb9] // secondary address
val txttab = ram[0x2b] + 256 * ram[0x2c] // basic load address ($0801 usually)
val fnaddr = ram[0xbb] + 256 * ram[0xbc] // file name address
val txttab = ram[0x2b]+256*ram[0x2c] // basic load address ($0801 usually)
val fnaddr = ram[0xbb]+256*ram[0xbc] // file name address
return if (fnlen > 0) {
val filename = (0 until fnlen).map { ram[fnaddr + it].toChar() }.joinToString("")
val filename = (0 until fnlen).map { ram[fnaddr+it].toChar() }.joinToString("")
val loadEndAddress = searchAndLoadFile(filename, fa, sa, txttab)
if (loadEndAddress != null) {
ram[0x90] = 0 // status OK
@ -87,16 +87,15 @@ class C64Machine(title: String) : IVirtualMachine {
private fun breakpointKernelSave(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
val fnlen = ram[0xb7] // file name length
// val fa = ram[0xba] // device number
// val sa = ram[0xb9] // secondary address
val fnaddr = ram[0xbb] + 256 * ram[0xbc] // file name address
// val fa = ram[0xba] // device number
// val sa = ram[0xb9] // secondary address
val fnaddr = ram[0xbb]+256*ram[0xbc] // file name address
return if (fnlen > 0) {
val fromAddr = ram[cpu.regA] + 256 * ram[cpu.regA + 1]
val endAddr = cpu.regX + 256 * cpu.regY
val fromAddr = ram[cpu.regA]+256*ram[cpu.regA+1]
val endAddr = cpu.regX+256*cpu.regY
val data = (fromAddr..endAddr).map { ram[it].toByte() }.toByteArray()
var filename = (0 until fnlen).map { ram[fnaddr + it].toChar() }.joinToString("").toLowerCase()
if (!filename.endsWith(".prg"))
filename += ".prg"
var filename = (0 until fnlen).map { ram[fnaddr+it].toChar() }.joinToString("").toLowerCase()
if (!filename.endsWith(".prg")) filename += ".prg"
File(filename).outputStream().use {
it.write(fromAddr and 0xff)
it.write(fromAddr ushr 8)
@ -111,35 +110,25 @@ class C64Machine(title: String) : IVirtualMachine {
throw Cpu6502.InstructionError("BRK instruction hit at $${hexW(pc)}")
}
private fun searchAndLoadFile(filename: String,
device: UByte, secondary: UByte, basicLoadAddress: Address): Address? {
private fun searchAndLoadFile(filename: String, device: UByte, secondary: UByte, basicLoadAddress: Address): Address? {
when (filename) {
"*" -> {
// load the first file in the directory
return searchAndLoadFile(
File(".").listFiles()?.firstOrNull()?.name ?: "",
device,
secondary,
basicLoadAddress
)
return searchAndLoadFile(File(".").listFiles()?.firstOrNull()?.name ?: "", device, secondary, basicLoadAddress)
}
"$" -> {
// load the directory
val files = File(".")
.listFiles(FileFilter { it.isFile })!!
.associate {
val name = it.nameWithoutExtension.toUpperCase()
val ext = it.extension.toUpperCase()
val fileAndSize = Pair(it, it.length())
if (name.isEmpty())
Pair(".$ext", "") to fileAndSize
else
Pair(name, ext) to fileAndSize
}
val files = File(".").listFiles(FileFilter { it.isFile })!!.associate {
val name = it.nameWithoutExtension.toUpperCase()
val ext = it.extension.toUpperCase()
val fileAndSize = Pair(it, it.length())
if (name.isEmpty()) Pair(".$ext", "") to fileAndSize
else Pair(name, ext) to fileAndSize
}
val dirname = File(".").canonicalPath.substringAfterLast(File.separator).toUpperCase()
val dirlisting = makeDirListing(dirname, files, basicLoadAddress)
ram.load(dirlisting, basicLoadAddress)
return basicLoadAddress + dirlisting.size - 1
return basicLoadAddress+dirlisting.size-1
}
else -> {
fun findHostFile(filename: String): String? {
@ -153,10 +142,10 @@ class C64Machine(title: String) : IVirtualMachine {
return try {
return if (secondary == 1.toShort()) {
val (loadAddress, size) = ram.loadPrg(hostFileName, null)
loadAddress + size - 1
loadAddress+size-1
} else {
val (loadAddress, size) = ram.loadPrg(hostFileName, basicLoadAddress)
loadAddress + size - 1
loadAddress+size-1
}
} catch (iox: IOException) {
println("LOAD ERROR $iox")
@ -166,13 +155,12 @@ class C64Machine(title: String) : IVirtualMachine {
}
}
private fun makeDirListing(dirname: String,
files: Map<Pair<String, String>, Pair<File, Long>>, basicLoadAddress: Address): Array<UByte>
{
private fun makeDirListing(dirname: String, files: Map<Pair<String, String>, Pair<File, Long>>,
basicLoadAddress: Address): Array<UByte> {
var address = basicLoadAddress
val listing = mutableListOf<UByte>()
fun addLine(lineNumber: Int, line: String) {
address += line.length + 3
address += line.length+3
listing.add((address and 0xff).toShort())
listing.add((address ushr 8).toShort())
listing.add((lineNumber and 0xff).toShort())
@ -183,14 +171,14 @@ class C64Machine(title: String) : IVirtualMachine {
addLine(0, "\u0012\"${dirname.take(16).padEnd(16)}\" 00 2A")
var totalBlocks = 0
files.forEach {
val blocksize = (it.value.second / 256).toInt()
val blocksize = (it.value.second/256).toInt()
totalBlocks += blocksize
val filename = it.key.first.take(16)
val padding1 = " ".substring(blocksize.toString().length)
val padding2 = " ".substring(filename.length)
addLine(blocksize, "$padding1 \"$filename\" $padding2${it.key.second.take(3).padEnd(3)}")
}
addLine(kotlin.math.max(0, 664 - totalBlocks), "BLOCKS FREE.")
addLine(kotlin.math.max(0, 664-totalBlocks), "BLOCKS FREE.")
listing.add(0)
listing.add(0)
return listing.toTypedArray()
@ -200,26 +188,23 @@ class C64Machine(title: String) : IVirtualMachine {
val candidates = listOf("./roms", "~/roms/c64", "~/roms", "~/.vice/C64")
candidates.forEach {
val path = Paths.get(expandUser(it))
if (path.toFile().isDirectory)
return path
if (path.toFile().isDirectory) return path
}
throw FileNotFoundException("no roms directory found, tried: $candidates")
}
private fun expandUser(path: String): String {
return when {
path.startsWith("~/") -> System.getProperty("user.home") + path.substring(1)
path.startsWith("~" + File.separatorChar) -> System.getProperty("user.home") + path.substring(1)
path.startsWith("~/") -> System.getProperty("user.home")+path.substring(1)
path.startsWith("~"+File.separatorChar) -> System.getProperty("user.home")+path.substring(1)
path.startsWith("~") -> throw UnsupportedOperationException("home dir expansion not implemented for other users")
else -> path
}
}
override fun loadFileInRam(file: File, loadAddress: Address?) {
if (file.extension == "prg" && (loadAddress == null || loadAddress == 0x0801))
ram.loadPrg(file.inputStream(), null)
else
ram.load(file.readBytes(), loadAddress!!)
if (file.extension == "prg" && (loadAddress == null || loadAddress == 0x0801)) ram.loadPrg(file.inputStream(), null)
else ram.load(file.readBytes(), loadAddress!!)
}
override fun getZeroAndStackPages(): Array<UByte> = ram.getPages(0, 2)
@ -243,29 +228,20 @@ class C64Machine(title: String) : IVirtualMachine {
}.start()
val timer = java.util.Timer("cpu-cycle", true)
timer.scheduleAtFixedRate(0, 1000L / VicII.framerate) {
timer.scheduleAtFixedRate(0, 1000L/VicII.framerate) {
if (!paused) {
// we synchronise cpu cycles to the vertical blank of the Vic chip
// this should result in ~1 Mhz cpu speed
try {
while (vic.vsync) step()
while (!vic.vsync) step()
hostDisplay.repaint() // repaint synced with VIC vertical blank
} catch (rx: RuntimeException) {
JOptionPane.showMessageDialog(
hostDisplay,
"Run time error: $rx",
"Error during execution",
JOptionPane.ERROR_MESSAGE
)
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $rx", "Error during execution", JOptionPane.ERROR_MESSAGE)
this.cancel()
throw rx
} catch (ex: Error) {
JOptionPane.showMessageDialog(
hostDisplay,
"Run time error: $ex",
"Error during execution",
JOptionPane.ERROR_MESSAGE
)
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $ex", "Error during execution", JOptionPane.ERROR_MESSAGE)
this.cancel()
throw ex
}

View File

@ -19,8 +19,8 @@ import javax.swing.event.MouseInputListener
object ScreenDefs {
const val SCREEN_WIDTH_CHARS = 80
const val SCREEN_HEIGHT_CHARS = 30
const val SCREEN_WIDTH = SCREEN_WIDTH_CHARS * 8
const val SCREEN_HEIGHT = SCREEN_HEIGHT_CHARS * 16
const val SCREEN_WIDTH = SCREEN_WIDTH_CHARS*8
const val SCREEN_HEIGHT = SCREEN_HEIGHT_CHARS*16
const val DISPLAY_PIXEL_SCALING: Double = 1.5
const val BORDER_SIZE = 32
@ -31,7 +31,7 @@ object ScreenDefs {
private fun loadCharacters(): Array<BufferedImage> {
val img = ImageIO.read(javaClass.getResourceAsStream("/charset/unscii8x16.png"))
val charactersImage = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB).also {it.accelerationPriority=1.0f}
val charactersImage = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB).also { it.accelerationPriority = 1.0f }
charactersImage.createGraphics().drawImage(img, 0, 0, null)
val black = Color(0, 0, 0).rgb
@ -40,18 +40,16 @@ object ScreenDefs {
for (y in 0 until charactersImage.height) {
for (x in 0 until charactersImage.width) {
val col = charactersImage.getRGB(x, y)
if (col == black)
charactersImage.setRGB(x, y, nopixel)
else
charactersImage.setRGB(x, y, foreground)
if (col == black) charactersImage.setRGB(x, y, nopixel)
else charactersImage.setRGB(x, y, foreground)
}
}
val numColumns = charactersImage.width / 8
val numColumns = charactersImage.width/8
val charImages = (0..255).map {
val charX = it % numColumns
val charY = it / numColumns
charactersImage.getSubimage(charX * 8, charY * 16, 8, 16)
val charX = it%numColumns
val charY = it/numColumns
charactersImage.getSubimage(charX*8, charY*16, 8, 16)
}
return charImages.toTypedArray()
@ -72,10 +70,8 @@ private class BitmapScreenPanel : JPanel() {
image = gd.createCompatibleImage(ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT, Transparency.OPAQUE)
g2d = image.graphics as Graphics2D
val size = Dimension(
(image.width * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
(image.height * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt()
)
val size =
Dimension((image.width*ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), (image.height*ScreenDefs.DISPLAY_PIXEL_SCALING).toInt())
minimumSize = size
maximumSize = size
preferredSize = size
@ -88,15 +84,13 @@ private class BitmapScreenPanel : JPanel() {
override fun paint(graphics: Graphics) {
val g2d = graphics as Graphics2D
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
g2d.drawImage(
image, 0, 0, (image.width * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
(image.height * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), null
)
if(cursorState) {
val scx = (cursorX * ScreenDefs.DISPLAY_PIXEL_SCALING * 8).toInt()
val scy = (cursorY * ScreenDefs.DISPLAY_PIXEL_SCALING * 16).toInt()
val scw = (8 * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt()
val sch = (16 * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt()
g2d.drawImage(image, 0, 0, (image.width*ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
(image.height*ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), null)
if (cursorState) {
val scx = (cursorX*ScreenDefs.DISPLAY_PIXEL_SCALING*8).toInt()
val scy = (cursorY*ScreenDefs.DISPLAY_PIXEL_SCALING*16).toInt()
val scw = (8*ScreenDefs.DISPLAY_PIXEL_SCALING).toInt()
val sch = (16*ScreenDefs.DISPLAY_PIXEL_SCALING).toInt()
g2d.setXORMode(Color.CYAN)
g2d.fillRect(scx, scy, scw, sch)
g2d.setPaintMode()
@ -111,37 +105,31 @@ private class BitmapScreenPanel : JPanel() {
}
fun setPixel(x: Int, y: Int, onOff: Boolean) {
if (onOff)
image.setRGB(x, y, ScreenDefs.FG_COLOR.rgb)
else
image.setRGB(x, y, ScreenDefs.BG_COLOR.rgb)
if (onOff) image.setRGB(x, y, ScreenDefs.FG_COLOR.rgb)
else image.setRGB(x, y, ScreenDefs.BG_COLOR.rgb)
}
fun getPixel(x: Int, y: Int) = image.getRGB(x, y) != ScreenDefs.BG_COLOR.rgb
fun setChar(x: Int, y: Int, character: Char) {
g2d.clearRect(8 * x, 16 * y, 8, 16)
g2d.clearRect(8*x, 16*y, 8, 16)
val coloredImage = ScreenDefs.Characters[character.toInt()]
g2d.drawImage(coloredImage, 8 * x, 16 * y, null)
g2d.drawImage(coloredImage, 8*x, 16*y, null)
}
fun scrollUp() {
g2d.copyArea(0, 16, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT - 16, 0, -16)
g2d.copyArea(0, 16, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT-16, 0, -16)
g2d.background = ScreenDefs.BG_COLOR
g2d.clearRect(0, ScreenDefs.SCREEN_HEIGHT - 16, ScreenDefs.SCREEN_WIDTH, 16)
g2d.clearRect(0, ScreenDefs.SCREEN_HEIGHT-16, ScreenDefs.SCREEN_WIDTH, 16)
}
fun mousePixelPosition(): Point? {
val pos = mousePosition ?: return null
return Point(
(pos.x / ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
(pos.y / ScreenDefs.DISPLAY_PIXEL_SCALING).toInt()
)
return Point((pos.x/ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), (pos.y/ScreenDefs.DISPLAY_PIXEL_SCALING).toInt())
}
fun cursorPos(x: Int, y: Int) {
if(x!=cursorX || y!=cursorY)
cursorState=true
if (x != cursorX || y != cursorY) cursorState = true
cursorX = x
cursorY = y
}
@ -151,7 +139,7 @@ private class BitmapScreenPanel : JPanel() {
}
}
private class UnaliasedTextBox(rows: Int, columns: Int): JTextArea(rows, columns) {
private class UnaliasedTextBox(rows: Int, columns: Int) : JTextArea(rows, columns) {
override fun paintComponent(g: Graphics) {
g as Graphics2D
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF)
@ -179,7 +167,7 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("Debugger - ksim65 v
}
private val stackpageTf = UnaliasedTextBox(8, 102).also {
it.border = BorderFactory.createEtchedBorder()
it.isEnabled=false
it.isEnabled = false
it.disabledTextColor = Color.DARK_GRAY
it.font = Font(Font.MONOSPACED, Font.PLAIN, 12)
}
@ -214,11 +202,11 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("Debugger - ksim65 v
it.font = Font(Font.MONOSPACED, Font.PLAIN, 14)
it.disabledTextColor = Color.DARK_GRAY
it.isEnabled = false
if(it is JTextField) {
if (it is JTextField) {
it.columns = it.text.length
} else if(it is JTextArea) {
} else if (it is JTextArea) {
it.border = BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY),
BorderFactory.createEmptyBorder(2,2,2,2))
BorderFactory.createEmptyBorder(2, 2, 2, 2))
}
cpuPanel.add(it, gc)
gc.gridy++
@ -267,16 +255,15 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("Debugger - ksim65 v
output.append("\n")
val command = input.text.trim()
val result = vm.executeMonitorCommand(command)
if(result.echo)
output.append("> $command\n")
if (result.echo) output.append("> $command\n")
output.append(result.output)
input.text = result.prompt
}
monitorPanel.add(input)
gc.gridx=0
gc.gridy=0
gc.fill=GridBagConstraints.BOTH
gc.gridx = 0
gc.gridy = 0
gc.fill = GridBagConstraints.BOTH
contentPane.add(cpuPanel, gc)
gc.gridy++
contentPane.add(zeropagePanel, gc)
@ -288,26 +275,21 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("Debugger - ksim65 v
}
override fun actionPerformed(e: ActionEvent) {
when(e.actionCommand) {
when (e.actionCommand) {
"inject" -> {
val chooser = JFileChooser()
chooser.dialogTitle = "Choose binary program or .prg to load"
chooser.currentDirectory = File(".")
chooser.isMultiSelectionEnabled = false
val result = chooser.showOpenDialog(this)
if(result==JFileChooser.APPROVE_OPTION) {
if(chooser.selectedFile.extension=="prg") {
if (result == JFileChooser.APPROVE_OPTION) {
if (chooser.selectedFile.extension == "prg") {
vm.loadFileInRam(chooser.selectedFile, null)
} else {
val addressStr = JOptionPane.showInputDialog(
this,
"The selected file isn't a .prg.\nSpecify memory load address (hexadecimal) manually.",
"Load address",
JOptionPane.QUESTION_MESSAGE,
null,
null,
"$"
) as String
val addressStr = JOptionPane.showInputDialog(this,
"The selected file isn't a .prg.\nSpecify memory load address (hexadecimal) manually.",
"Load address", JOptionPane.QUESTION_MESSAGE, null, null,
"$") as String
val loadAddress = parseInt(addressStr.removePrefix("$"), 16)
vm.loadFileInRam(chooser.selectedFile, loadAddress)
@ -347,27 +329,25 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("Debugger - ksim65 v
regAtf.text = hexB(state.A)
regXtf.text = hexB(state.X)
regYtf.text = hexB(state.Y)
regPtf.text = "NV-BDIZC\n" + state.P.asInt().toString(2).padStart(8, '0')
regPtf.text = "NV-BDIZC\n"+state.P.asInt().toString(2).padStart(8, '0')
regPCtf.text = hexW(state.PC)
regSPtf.text = hexB(state.SP)
val memory = bus.memoryComponentFor(state.PC)
disassemTf.text = cpu.disassembleOneInstruction(memory.data, state.PC, memory.startAddress).first.substringAfter(' ').trim()
val pages = vm.getZeroAndStackPages()
if(pages.isNotEmpty()) {
if (pages.isNotEmpty()) {
val zpLines = (0..0xff step 32).map { location ->
" ${'$'}${location.toString(16).padStart(2, '0')} " + (
(0..31).joinToString(" ") { lineoffset ->
pages[location + lineoffset].toString(16).padStart(2, '0')
})
" ${'$'}${location.toString(16).padStart(2, '0')} "+((0..31).joinToString(" ") { lineoffset ->
pages[location+lineoffset].toString(16).padStart(2, '0')
})
}
val stackLines = (0x100..0x1ff step 32).map { location ->
"${'$'}${location.toString(16).padStart(2, '0')} " + (
(0..31).joinToString(" ") { lineoffset ->
pages[location + lineoffset].toString(16).padStart(2, '0')
})
"${'$'}${location.toString(16).padStart(2, '0')} "+((0..31).joinToString(" ") { lineoffset ->
pages[location+lineoffset].toString(16).padStart(2, '0')
})
}
zeropageTf.text = zpLines.joinToString ("\n")
stackpageTf.text = stackLines.joinToString ("\n")
zeropageTf.text = zpLines.joinToString("\n")
stackpageTf.text = stackLines.joinToString("\n")
}
speedKhzTf.text = "%.1f".format(cpu.averageSpeedKhzSinceReset)
@ -391,8 +371,8 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener
contentPane.background = ScreenDefs.BORDER_COLOR
val gc = GridBagConstraints()
gc.fill = GridBagConstraints.BOTH
gc.gridx=1
gc.gridy=1
gc.gridx = 1
gc.gridy = 1
gc.insets = Insets(ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE)
contentPane.add(canvas, gc)
addKeyListener(this)
@ -408,9 +388,9 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener
fun start(updateRate: Int) {
// repaint the screen's back buffer
var cursorBlink = 0L
val repaintTimer = javax.swing.Timer(1000 / updateRate) {
val repaintTimer = javax.swing.Timer(1000/updateRate) {
repaint()
if(it.`when` - cursorBlink > 200L) {
if (it.`when`-cursorBlink > 200L) {
cursorBlink = it.`when`
canvas.blinkCursor()
}
@ -422,8 +402,7 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener
// keyboard events:
override fun keyTyped(event: KeyEvent) {
keyboardBuffer.add(event.keyChar)
while (keyboardBuffer.size > 8)
keyboardBuffer.pop()
while (keyboardBuffer.size > 8) keyboardBuffer.pop()
}
override fun keyPressed(event: KeyEvent) {}
@ -432,8 +411,7 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener
// mouse events:
override fun mousePressed(event: MouseEvent) {
val pos = canvas.mousePixelPosition()
if (pos == null)
return
if (pos == null) return
else {
mousePos = pos
leftButton = leftButton or SwingUtilities.isLeftMouseButton(event)
@ -441,10 +419,10 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener
middleButton = middleButton or SwingUtilities.isMiddleMouseButton(event)
}
}
override fun mouseReleased(event: MouseEvent) {
val pos = canvas.mousePixelPosition()
if (pos == null)
return
if (pos == null) return
else {
mousePos = pos
leftButton = leftButton xor SwingUtilities.isLeftMouseButton(event)
@ -452,6 +430,7 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener
middleButton = middleButton xor SwingUtilities.isMiddleMouseButton(event)
}
}
override fun mouseEntered(event: MouseEvent) {}
override fun mouseExited(event: MouseEvent) {}
override fun mouseDragged(event: MouseEvent) = mouseMoved(event)
@ -459,15 +438,14 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener
override fun mouseMoved(event: MouseEvent) {
val pos = canvas.mousePixelPosition()
if (pos == null)
return
else
mousePos = pos
if (pos == null) return
else mousePos = pos
}
// the overrides required for IHostDisplay:
override fun clearScreen() = canvas.clearScreen()
override fun setPixel(x: Int, y: Int) = canvas.setPixel(x, y, true)
override fun clearPixel(x: Int, y: Int) = canvas.setPixel(x, y, false)
override fun getPixel(x: Int, y: Int) = canvas.getPixel(x, y)
@ -477,10 +455,8 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener
override fun mouse() = IHostInterface.MouseInfo(mousePos.x, mousePos.y, leftButton, rightButton, middleButton)
override fun keyboard(): Char? {
return if (keyboardBuffer.isEmpty())
null
else
keyboardBuffer.pop()
return if (keyboardBuffer.isEmpty()) null
else keyboardBuffer.pop()
}
}

View File

@ -22,9 +22,9 @@ class EhBasicMachine(title: String) {
val rom = Rom(0xc000, 0xffff).also { it.load(javaClass.getResourceAsStream("/ehbasic_C000.bin").readBytes()) }
private val hostDisplay = MainWindow(title)
private val display = Display(0xd000, 0xd00a, hostDisplay,
ScreenDefs.SCREEN_WIDTH_CHARS, ScreenDefs.SCREEN_HEIGHT_CHARS,
ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT)
private val display =
Display(0xd000, 0xd00a, hostDisplay, ScreenDefs.SCREEN_WIDTH_CHARS, ScreenDefs.SCREEN_HEIGHT_CHARS, ScreenDefs.SCREEN_WIDTH,
ScreenDefs.SCREEN_HEIGHT)
private val keyboard = Keyboard(0xd400, 0xd400, hostDisplay)
private var paused = false
@ -53,16 +53,16 @@ class EhBasicMachine(title: String) {
val desiredCyclesPerFrame = 500_000L/frameRate // 500 khz
val timer = java.util.Timer("cpu-cycle", true)
timer.scheduleAtFixedRate(500, 1000/frameRate) {
if(!paused) {
if (!paused) {
try {
val prevCycles = cpu.totalCycles
while (cpu.totalCycles - prevCycles < desiredCyclesPerFrame) {
while (cpu.totalCycles-prevCycles < desiredCyclesPerFrame) {
step()
}
} catch(rx: RuntimeException) {
} catch (rx: RuntimeException) {
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $rx", "Error during execution", JOptionPane.ERROR_MESSAGE)
this.cancel()
} catch(ex: Error) {
} catch (ex: Error) {
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $ex", "Error during execution", JOptionPane.ERROR_MESSAGE)
this.cancel()
}

View File

@ -21,18 +21,16 @@ class VirtualMachine(title: String) : IVirtualMachine {
private val monitor = Monitor(bus, cpu)
private val debugWindow = DebugWindow(this)
private val hostDisplay = MainWindow(title)
private val display = Display(
0xd000, 0xd00a, hostDisplay,
ScreenDefs.SCREEN_WIDTH_CHARS, ScreenDefs.SCREEN_HEIGHT_CHARS,
ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT
)
private val display =
Display(0xd000, 0xd00a, hostDisplay, ScreenDefs.SCREEN_WIDTH_CHARS, ScreenDefs.SCREEN_HEIGHT_CHARS, ScreenDefs.SCREEN_WIDTH,
ScreenDefs.SCREEN_HEIGHT)
private val mouse = Mouse(0xd300, 0xd305, hostDisplay)
private val keyboard = Keyboard(0xd400, 0xd400, hostDisplay)
private var paused = false
init {
ram[Cpu6502.RESET_vector] = 0x00
ram[Cpu6502.RESET_vector + 1] = 0x10
ram[Cpu6502.RESET_vector+1] = 0x10
ram.loadPrg(javaClass.getResourceAsStream("/vmdemo.prg"), null)
bus += rtc
@ -46,7 +44,7 @@ class VirtualMachine(title: String) : IVirtualMachine {
hostDisplay.iconImage = ImageIcon(javaClass.getResource("/icon.png")).image
debugWindow.iconImage = hostDisplay.iconImage
debugWindow.setLocation(hostDisplay.location.x + hostDisplay.width, hostDisplay.location.y)
debugWindow.setLocation(hostDisplay.location.x+hostDisplay.width, hostDisplay.location.y)
debugWindow.isVisible = true
hostDisplay.isVisible = true
hostDisplay.start(30)
@ -55,10 +53,8 @@ class VirtualMachine(title: String) : IVirtualMachine {
override fun getZeroAndStackPages(): Array<UByte> = ram.getPages(0, 2)
override fun loadFileInRam(file: File, loadAddress: Address?) {
if (file.extension == "prg" && loadAddress == null)
ram.loadPrg(file.inputStream(), null)
else
ram.load(file.readBytes(), loadAddress!!)
if (file.extension == "prg" && loadAddress == null) ram.loadPrg(file.inputStream(), null)
else ram.load(file.readBytes(), loadAddress!!)
}
override fun pause(paused: Boolean) {
@ -83,16 +79,16 @@ class VirtualMachine(title: String) : IVirtualMachine {
val desiredCyclesPerFrame = 500_000L/frameRate // 500 khz
val timer = java.util.Timer("cpu-cycle", true)
timer.scheduleAtFixedRate(500, 1000/frameRate) {
if(!paused) {
if (!paused) {
try {
val prevCycles = cpu.totalCycles
while (cpu.totalCycles - prevCycles < desiredCyclesPerFrame) {
while (cpu.totalCycles-prevCycles < desiredCyclesPerFrame) {
step()
}
} catch(rx: RuntimeException) {
} catch (rx: RuntimeException) {
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $rx", "Error during execution", JOptionPane.ERROR_MESSAGE)
this.cancel()
} catch(ex: Error) {
} catch (ex: Error) {
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $ex", "Error during execution", JOptionPane.ERROR_MESSAGE)
this.cancel()
}

View File

@ -60,11 +60,10 @@ class Bus {
fun write(address: Address, data: UByte) {
require(data in 0..255) { "data written to address $address must be a byte 0..255" }
memComponents.forEach {
if (address >= it.startAddress && address <= it.endAddress)
it[address] = data
if (address >= it.startAddress && address <= it.endAddress) it[address] = data
}
}
fun memoryComponentFor(address: Address) =
memComponents.first { it is MemoryComponent && address >= it.startAddress && address <= it.endAddress } as MemoryComponent
memComponents.first { it is MemoryComponent && address >= it.startAddress && address <= it.endAddress } as MemoryComponent
}

View File

@ -13,7 +13,7 @@ import razorvine.ksim65.components.UByte
*/
open class Cpu6502 : BusComponent() {
open val name = "6502"
var tracing: ((state:String) -> Unit)? = null
var tracing: ((state: String) -> Unit)? = null
var totalCycles = 0L
protected set
private var resetTime = System.nanoTime()
@ -29,25 +29,10 @@ open class Cpu6502 : BusComponent() {
const val resetCycles = 8
}
class StatusRegister(
var C: Boolean = false,
var Z: Boolean = false,
var I: Boolean = false,
var D: Boolean = false,
var B: Boolean = false,
var V: Boolean = false,
var N: Boolean = false
) {
class StatusRegister(var C: Boolean = false, var Z: Boolean = false, var I: Boolean = false, var D: Boolean = false,
var B: Boolean = false, var V: Boolean = false, var N: Boolean = false) {
fun asInt(): Int {
return (0b00100000 or
(if (N) 0b10000000 else 0) or
(if (V) 0b01000000 else 0) or
(if (B) 0b00010000 else 0) or
(if (D) 0b00001000 else 0) or
(if (I) 0b00000100 else 0) or
(if (Z) 0b00000010 else 0) or
(if (C) 0b00000001 else 0)
)
return (0b00100000 or (if (N) 0b10000000 else 0) or (if (V) 0b01000000 else 0) or (if (B) 0b00010000 else 0) or (if (D) 0b00001000 else 0) or (if (I) 0b00000100 else 0) or (if (Z) 0b00000010 else 0) or (if (C) 0b00000001 else 0))
}
fun fromInt(byte: Int) {
@ -67,8 +52,7 @@ open class Cpu6502 : BusComponent() {
override fun hashCode(): Int = asInt()
override fun equals(other: Any?): Boolean {
if (other !is StatusRegister)
return false
if (other !is StatusRegister) return false
return asInt() == other.asInt()
}
}
@ -85,51 +69,18 @@ open class Cpu6502 : BusComponent() {
* without having to actually have a BRK in the breakpoint's memory location
* (this is the same as changeOpcode=0x00)
*/
class BreakpointResultAction(val changePC: Address? = null,
val changeOpcode: Int? = null,
val causeBRK: Boolean = false)
class BreakpointResultAction(val changePC: Address? = null, val changeOpcode: Int? = null, val causeBRK: Boolean = false)
class State (
val A: UByte,
val X: UByte,
val Y: UByte,
val SP: Address,
val P: StatusRegister,
val PC: Address,
val cycles: Long
) {
class State(val A: UByte, val X: UByte, val Y: UByte, val SP: Address, val P: StatusRegister, val PC: Address, val cycles: Long) {
override fun toString(): String {
return "cycle:$cycles - pc=${hexW(PC)} " +
"A=${hexB(A)} " +
"X=${hexB(X)} " +
"Y=${hexB(Y)} " +
"SP=${hexB(SP)} " +
" n=" + (if (P.N) "1" else "0") +
" v=" + (if (P.V) "1" else "0") +
" b=" + (if (P.B) "1" else "0") +
" d=" + (if (P.D) "1" else "0") +
" i=" + (if (P.I) "1" else "0") +
" z=" + (if (P.Z) "1" else "0") +
" c=" + (if (P.C) "1" else "0")
return "cycle:$cycles - pc=${hexW(PC)} "+"A=${hexB(A)} "+"X=${hexB(X)} "+"Y=${hexB(Y)} "+"SP=${hexB(
SP)} "+" n="+(if (P.N) "1" else "0")+" v="+(if (P.V) "1" else "0")+" b="+(if (P.B) "1" else "0")+" d="+(if (P.D) "1" else "0")+" i="+(if (P.I) "1" else "0")+" z="+(if (P.Z) "1" else "0")+" c="+(if (P.C) "1" else "0")
}
}
enum class AddrMode {
Imp,
Acc,
Imm,
Zp,
Zpr, // special addressing mode used by the 65C02
ZpX,
ZpY,
Rel,
Abs,
AbsX,
AbsY,
Ind,
IzX,
IzY,
Izp, // special addressing mode used by the 65C02
Imp, Acc, Imm, Zp, Zpr, // special addressing mode used by the 65C02
ZpX, ZpY, Rel, Abs, AbsX, AbsY, Ind, IzX, IzY, Izp, // special addressing mode used by the 65C02
IaX, // special addressing mode used by the 65C02
}
@ -151,24 +102,19 @@ open class Cpu6502 : BusComponent() {
get() = currentInstruction.mnemonic
val averageSpeedKhzSinceReset: Double
get() = totalCycles.toDouble() / (System.nanoTime() - resetTime) * 1_000_000
get() = totalCycles.toDouble()/(System.nanoTime()-resetTime)*1_000_000
@Synchronized fun snapshot(): State {
@Synchronized
fun snapshot(): State {
val status = StatusRegister().also { it.fromInt(regP.asInt()) }
return State(regA.toShort(),
regX.toShort(),
regY.toShort(),
regSP,
status,
regPC,
totalCycles)
return State(regA.toShort(), regX.toShort(), regY.toShort(), regSP, status, regPC, totalCycles)
}
// has an interrupt been requested?
protected enum class Interrupt {
IRQ,
NMI
IRQ, NMI
}
protected var pendingInterrupt: Interrupt? = null
// data byte from the instruction (only set when addr.mode is Accumulator, Immediate or Implied)
@ -185,8 +131,7 @@ open class Cpu6502 : BusComponent() {
fun removeBreakpoint(address: Address) = breakpoints.remove(address)
fun disassemble(memory: MemoryComponent, from: Address, to: Address) =
disassemble(memory.data, memory.startAddress, from, to)
fun disassemble(memory: MemoryComponent, from: Address, to: Address) = disassemble(memory.data, memory.startAddress, from, to)
fun disassemble(memory: Array<UByte>, baseAddress: Address, from: Address, to: Address): Pair<List<String>, Address> {
var location = from
@ -211,90 +156,84 @@ open class Cpu6502 : BusComponent() {
val opcode = instructions[byte.toInt()]
return when (opcode.mode) {
AddrMode.Acc -> {
Pair(line + "$spacing1 ${opcode.mnemonic} a", 1)
Pair(line+"$spacing1 ${opcode.mnemonic} a", 1)
}
AddrMode.Imp -> {
Pair(line + "$spacing1 ${opcode.mnemonic}", 1)
Pair(line+"$spacing1 ${opcode.mnemonic}", 1)
}
AddrMode.Imm -> {
val value = memory[location+1]
Pair(line + "${hexB(value)} $spacing2 ${opcode.mnemonic} #\$${hexB(value)}", 2)
Pair(line+"${hexB(value)} $spacing2 ${opcode.mnemonic} #\$${hexB(value)}", 2)
}
AddrMode.Zp -> {
val zpAddr = memory[location+1]
Pair(line + "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)}", 2)
Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)}", 2)
}
AddrMode.Zpr -> {
// addressing mode used by the 65C02, put here for convenience
val zpAddr = memory[location+1]
val rel = memory[location+2]
val target =
if (rel <= 0x7f)
location + 3 + rel + baseAddress
else
location + 3 - (256 - rel) + baseAddress
Pair(line + "${hexB(zpAddr)} ${hexB(rel)} $spacing3 ${opcode.mnemonic} \$${hexB(zpAddr)}, \$${hexW(target, true)}", 3)
val target = if (rel <= 0x7f) location+3+rel+baseAddress
else location+3-(256-rel)+baseAddress
Pair(line+"${hexB(zpAddr)} ${hexB(rel)} $spacing3 ${opcode.mnemonic} \$${hexB(zpAddr)}, \$${hexW(target, true)}", 3)
}
AddrMode.Izp -> {
// addressing mode used by the 65C02, put here for convenience
val zpAddr = memory[location+1]
Pair(line + "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$(${hexB(zpAddr)})", 2)
Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$(${hexB(zpAddr)})", 2)
}
AddrMode.IaX -> {
// addressing mode used by the 65C02, put here for convenience
val lo = memory[location+1]
val hi = memory[location+2]
val absAddr = lo.toInt() or (hi.toInt() shl 8)
Pair(line + "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$(${hexW(absAddr)},x)", 3)
Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$(${hexW(absAddr)},x)", 3)
}
AddrMode.ZpX -> {
val zpAddr = memory[location+1]
Pair(line + "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)},x", 2)
Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)},x", 2)
}
AddrMode.ZpY -> {
val zpAddr = memory[location+1]
Pair(line + "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)},y", 2)
Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)},y", 2)
}
AddrMode.Rel -> {
val rel = memory[location+1]
val target =
if (rel <= 0x7f)
location + 2 + rel + baseAddress
else
location + 2 - (256 - rel) + baseAddress
Pair(line + "${hexB(rel)} $spacing2 ${opcode.mnemonic} \$${hexW(target, true)}", 2)
val target = if (rel <= 0x7f) location+2+rel+baseAddress
else location+2-(256-rel)+baseAddress
Pair(line+"${hexB(rel)} $spacing2 ${opcode.mnemonic} \$${hexW(target, true)}", 2)
}
AddrMode.Abs -> {
val lo = memory[location+1]
val hi = memory[location+2]
val absAddr = lo.toInt() or (hi.toInt() shl 8)
Pair(line + "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)}", 3)
Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)}", 3)
}
AddrMode.AbsX -> {
val lo = memory[location+1]
val hi = memory[location+2]
val absAddr = lo.toInt() or (hi.toInt() shl 8)
Pair(line + "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},x", 3)
Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},x", 3)
}
AddrMode.AbsY -> {
val lo = memory[location+1]
val hi = memory[location+2]
val absAddr = lo.toInt() or (hi.toInt() shl 8)
Pair(line + "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},y", 3)
Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},y", 3)
}
AddrMode.Ind -> {
val lo = memory[location+1]
val hi = memory[location+2]
val indirectAddr = lo.toInt() or (hi.toInt() shl 8)
Pair(line + "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} (\$${hexW(indirectAddr)})", 3)
Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} (\$${hexW(indirectAddr)})", 3)
}
AddrMode.IzX -> {
val zpAddr = memory[location+1]
Pair(line + "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB(zpAddr)},x)", 2)
Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB(zpAddr)},x)", 2)
}
AddrMode.IzY -> {
val zpAddr = memory[location+1]
Pair(line + "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB(zpAddr)}),y", 2)
Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB(zpAddr)}),y", 2)
}
}
}
@ -341,15 +280,12 @@ open class Cpu6502 : BusComponent() {
// tracing and breakpoint handling
tracing?.invoke(snapshot().toString())
breakpoints[regPC]?.let {
if(breakpoint(it))
return
if (breakpoint(it)) return
}
if(currentOpcode==0x00)
breakpointForBRK?.let {
if(breakpoint(it))
return
}
if (currentOpcode == 0x00) breakpointForBRK?.let {
if (breakpoint(it)) return
}
}
regPC++
@ -381,8 +317,7 @@ open class Cpu6502 : BusComponent() {
return if (regPC != oldPC) {
clock()
true
} else
false
} else false
}
/**
@ -402,18 +337,12 @@ open class Cpu6502 : BusComponent() {
}
fun irq() {
if (!regP.I)
pendingInterrupt = Interrupt.IRQ
if (!regP.I) pendingInterrupt = Interrupt.IRQ
}
protected fun getFetched() =
if (currentInstruction.mode == AddrMode.Imm ||
currentInstruction.mode == AddrMode.Acc ||
currentInstruction.mode == AddrMode.Imp
)
fetchedData
else
read(fetchedAddress)
if (currentInstruction.mode == AddrMode.Imm || currentInstruction.mode == AddrMode.Acc || currentInstruction.mode == AddrMode.Imp) fetchedData
else read(fetchedAddress)
protected fun readPc(): Int = bus.read(regPC++).toInt()
@ -430,11 +359,11 @@ open class Cpu6502 : BusComponent() {
protected fun pushStack(data: Int) {
write(regSP or 0x0100, data)
regSP = (regSP - 1) and 0xff
regSP = (regSP-1) and 0xff
}
protected fun popStack(): Int {
regSP = (regSP + 1) and 0xff
regSP = (regSP+1) and 0xff
return read(regSP or 0x0100)
}
@ -445,12 +374,11 @@ open class Cpu6502 : BusComponent() {
}
protected fun read(address: Address): Int = bus.read(address).toInt()
protected fun readWord(address: Address): Int = bus.read(address).toInt() or (bus.read(address + 1).toInt() shl 8)
protected fun readWord(address: Address): Int = bus.read(address).toInt() or (bus.read(address+1).toInt() shl 8)
protected fun write(address: Address, data: Int) = bus.write(address, data.toShort())
// opcodes table from http://www.oxyron.de/html/opcodes02.html
open val instructions: Array<Instruction> =
listOf(
open val instructions: Array<Instruction> = listOf(
/* 00 */ Instruction("brk", AddrMode.Imp, 7),
/* 01 */ Instruction("ora", AddrMode.IzX, 6),
/* 02 */ Instruction("???", AddrMode.Imp, 2),
@ -706,8 +634,7 @@ open class Cpu6502 : BusComponent() {
/* fc */ Instruction("nop", AddrMode.AbsX, 4),
/* fd */ Instruction("sbc", AddrMode.AbsX, 4),
/* fe */ Instruction("inc", AddrMode.AbsX, 7),
/* ff */ Instruction("isc", AddrMode.AbsX, 7)
).toTypedArray()
/* ff */ Instruction("isc", AddrMode.AbsX, 7)).toTypedArray()
protected open fun applyAddressingMode(addrMode: AddrMode) {
when (addrMode) {
@ -722,18 +649,17 @@ open class Cpu6502 : BusComponent() {
}
AddrMode.ZpX -> {
// note: zeropage index will not leave Zp when page boundary is crossed
fetchedAddress = (readPc() + regX) and 0xff
fetchedAddress = (readPc()+regX) and 0xff
}
AddrMode.ZpY -> {
// note: zeropage index will not leave Zp when page boundary is crossed
fetchedAddress = (readPc() + regY) and 0xff
fetchedAddress = (readPc()+regY) and 0xff
}
AddrMode.Rel -> {
val relative = readPc()
fetchedAddress = if (relative >= 0x80) {
regPC - (256 - relative) and 0xffff
} else
regPC + relative and 0xffff
regPC-(256-relative) and 0xffff
} else regPC+relative and 0xffff
}
AddrMode.Abs -> {
val lo = readPc()
@ -743,12 +669,12 @@ open class Cpu6502 : BusComponent() {
AddrMode.AbsX -> {
val lo = readPc()
val hi = readPc()
fetchedAddress = regX + (lo or (hi shl 8)) and 0xffff
fetchedAddress = regX+(lo or (hi shl 8)) and 0xffff
}
AddrMode.AbsY -> {
val lo = readPc()
val hi = readPc()
fetchedAddress = regY + (lo or (hi shl 8)) and 0xffff
fetchedAddress = regY+(lo or (hi shl 8)) and 0xffff
}
AddrMode.Ind -> {
var lo = readPc()
@ -762,23 +688,23 @@ open class Cpu6502 : BusComponent() {
} else {
// normal behavior
lo = read(fetchedAddress)
hi = read(fetchedAddress + 1)
hi = read(fetchedAddress+1)
}
fetchedAddress = lo or (hi shl 8)
}
AddrMode.IzX -> {
// note: not able to fetch an address which crosses the (zero)page boundary
fetchedAddress = readPc()
val lo = read((fetchedAddress + regX) and 0xff)
val hi = read((fetchedAddress + regX + 1) and 0xff)
val lo = read((fetchedAddress+regX) and 0xff)
val hi = read((fetchedAddress+regX+1) and 0xff)
fetchedAddress = lo or (hi shl 8)
}
AddrMode.IzY -> {
// note: not able to fetch an address which crosses the (zero)page boundary
fetchedAddress = readPc()
val lo = read(fetchedAddress)
val hi = read((fetchedAddress + 1) and 0xff)
fetchedAddress = regY + (lo or (hi shl 8)) and 0xffff
val hi = read((fetchedAddress+1) and 0xff)
fetchedAddress = regY+(lo or (hi shl 8)) and 0xffff
}
AddrMode.Zpr, AddrMode.Izp, AddrMode.IaX -> {
// addressing mode used by the 65C02 only
@ -1061,14 +987,14 @@ open class Cpu6502 : BusComponent() {
// and http://nesdev.com/6502.txt
// and https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/6510core.c#l598
// (the implementation below is based on the code used by Vice)
var tmp = (regA and 0xf) + (operand and 0xf) + (if (regP.C) 1 else 0)
var tmp = (regA and 0xf)+(operand and 0xf)+(if (regP.C) 1 else 0)
if (tmp > 9) tmp += 6
tmp = if (tmp <= 0x0f) {
(tmp and 0xf) + (regA and 0xf0) + (operand and 0xf0)
(tmp and 0xf)+(regA and 0xf0)+(operand and 0xf0)
} else {
(tmp and 0xf) + (regA and 0xf0) + (operand and 0xf0) + 0x10
(tmp and 0xf)+(regA and 0xf0)+(operand and 0xf0)+0x10
}
regP.Z = regA + operand + (if (regP.C) 1 else 0) and 0xff == 0
regP.Z = regA+operand+(if (regP.C) 1 else 0) and 0xff == 0
regP.N = tmp and 0b10000000 != 0
regP.V = (regA xor tmp) and 0x80 != 0 && (regA xor operand) and 0b10000000 == 0
if (tmp and 0x1f0 > 0x90) tmp += 0x60
@ -1076,7 +1002,7 @@ open class Cpu6502 : BusComponent() {
regA = tmp and 0xff
} else {
// normal add
val tmp = operand + regA + if (regP.C) 1 else 0
val tmp = operand+regA+if (regP.C) 1 else 0
regP.N = (tmp and 0b10000000) != 0
regP.Z = (tmp and 0xff) == 0
regP.V = (regA xor operand).inv() and (regA xor tmp) and 0b10000000 != 0
@ -1141,7 +1067,7 @@ open class Cpu6502 : BusComponent() {
protected open fun iBrk() {
// handle BRK ('software interrupt') or a real hardware IRQ
if (pendingInterrupt != null) {
pushStackAddr(regPC - 1)
pushStackAddr(regPC-1)
} else {
regPC++
pushStackAddr(regPC)
@ -1150,7 +1076,7 @@ open class Cpu6502 : BusComponent() {
pushStack(regP)
regP.I = true // interrupts are now disabled
// NMOS 6502 doesn't clear the D flag (CMOS 65C02 version does...)
regPC = readWord(if (pendingInterrupt==Interrupt.NMI) NMI_vector else IRQ_vector)
regPC = readWord(if (pendingInterrupt == Interrupt.NMI) NMI_vector else IRQ_vector)
pendingInterrupt = null
}
@ -1182,38 +1108,38 @@ open class Cpu6502 : BusComponent() {
val fetched = getFetched()
regP.C = regA >= fetched
regP.Z = regA == fetched
regP.N = ((regA - fetched) and 0b10000000) != 0
regP.N = ((regA-fetched) and 0b10000000) != 0
}
protected fun iCpx() {
val fetched = getFetched()
regP.C = regX >= fetched
regP.Z = regX == fetched
regP.N = ((regX - fetched) and 0b10000000) != 0
regP.N = ((regX-fetched) and 0b10000000) != 0
}
protected fun iCpy() {
val fetched = getFetched()
regP.C = regY >= fetched
regP.Z = regY == fetched
regP.N = ((regY - fetched) and 0b10000000) != 0
regP.N = ((regY-fetched) and 0b10000000) != 0
}
protected open fun iDec() {
val data = (read(fetchedAddress) - 1) and 0xff
val data = (read(fetchedAddress)-1) and 0xff
write(fetchedAddress, data)
regP.Z = data == 0
regP.N = (data and 0b10000000) != 0
}
protected fun iDex() {
regX = (regX - 1) and 0xff
regX = (regX-1) and 0xff
regP.Z = regX == 0
regP.N = (regX and 0b10000000) != 0
}
protected fun iDey() {
regY = (regY - 1) and 0xff
regY = (regY-1) and 0xff
regP.Z = regY == 0
regP.N = (regY and 0b10000000) != 0
}
@ -1225,20 +1151,20 @@ open class Cpu6502 : BusComponent() {
}
protected open fun iInc() {
val data = (read(fetchedAddress) + 1) and 0xff
val data = (read(fetchedAddress)+1) and 0xff
write(fetchedAddress, data)
regP.Z = data == 0
regP.N = (data and 0b10000000) != 0
}
protected fun iInx() {
regX = (regX + 1) and 0xff
regX = (regX+1) and 0xff
regP.Z = regX == 0
regP.N = (regX and 0b10000000) != 0
}
protected fun iIny() {
regY = (regY + 1) and 0xff
regY = (regY+1) and 0xff
regP.Z = regY == 0
regP.N = (regY and 0b10000000) != 0
}
@ -1248,7 +1174,7 @@ open class Cpu6502 : BusComponent() {
}
protected fun iJsr() {
pushStackAddr(regPC - 1)
pushStackAddr(regPC-1)
regPC = fetchedAddress
}
@ -1358,12 +1284,12 @@ open class Cpu6502 : BusComponent() {
protected fun iRts() {
regPC = popStackAddr()
regPC = (regPC + 1) and 0xffff
regPC = (regPC+1) and 0xffff
}
protected open fun iSbc() {
val operand = getFetched()
val tmp = (regA - operand - if (regP.C) 0 else 1) and 0xffff
val tmp = (regA-operand-if (regP.C) 0 else 1) and 0xffff
regP.V = (regA xor operand) and (regA xor tmp) and 0b10000000 != 0
if (regP.D) {
// BCD subtract
@ -1371,11 +1297,11 @@ open class Cpu6502 : BusComponent() {
// and http://nesdev.com/6502.txt
// and https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/6510core.c#l1396
// (the implementation below is based on the code used by Vice)
var tmpA = ((regA and 0xf) - (operand and 0xf) - if (regP.C) 0 else 1) and 0xffff
var tmpA = ((regA and 0xf)-(operand and 0xf)-if (regP.C) 0 else 1) and 0xffff
tmpA = if ((tmpA and 0x10) != 0) {
((tmpA - 6) and 0xf) or (regA and 0xf0) - (operand and 0xf0) - 0x10
((tmpA-6) and 0xf) or (regA and 0xf0)-(operand and 0xf0)-0x10
} else {
(tmpA and 0xf) or (regA and 0xf0) - (operand and 0xf0)
(tmpA and 0xf) or (regA and 0xf0)-(operand and 0xf0)
}
if ((tmpA and 0x100) != 0) tmpA -= 0x60
regA = tmpA and 0xff
@ -1522,8 +1448,6 @@ open class Cpu6502 : BusComponent() {
// invalid instruction (JAM / KIL)
private fun iInvalid() {
throw InstructionError(
"invalid instruction encountered: opcode=${hexB(currentOpcode)} instr=${currentInstruction.mnemonic}"
)
throw InstructionError("invalid instruction encountered: opcode=${hexB(currentOpcode)} instr=${currentInstruction.mnemonic}")
}
}

View File

@ -10,9 +10,7 @@ class Cpu65C02 : Cpu6502() {
override val name = "65C02"
enum class Wait {
Normal,
Waiting,
Stopped
Normal, Waiting, Stopped
}
var waiting: Wait = Wait.Normal
@ -66,10 +64,7 @@ class Cpu65C02 : Cpu6502() {
override fun applyAddressingMode(addrMode: AddrMode) {
when (addrMode) {
AddrMode.Imp, AddrMode.Acc, AddrMode.Imm,
AddrMode.Zp, AddrMode.ZpX, AddrMode.ZpY,
AddrMode.Rel, AddrMode.Abs, AddrMode.AbsX, AddrMode.AbsY,
AddrMode.IzX, AddrMode.IzY -> {
AddrMode.Imp, AddrMode.Acc, AddrMode.Imm, AddrMode.Zp, AddrMode.ZpX, AddrMode.ZpY, AddrMode.Rel, AddrMode.Abs, AddrMode.AbsX, AddrMode.AbsY, AddrMode.IzX, AddrMode.IzY -> {
super.applyAddressingMode(addrMode)
}
AddrMode.Ind -> {
@ -78,7 +73,7 @@ class Cpu65C02 : Cpu6502() {
fetchedAddress = lo or (hi shl 8)
// 65c02 doesn't have the page bug of the 6502
lo = read(fetchedAddress)
hi = read(fetchedAddress + 1)
hi = read(fetchedAddress+1)
fetchedAddress = lo or (hi shl 8)
}
AddrMode.Zpr -> {
@ -87,15 +82,14 @@ class Cpu65C02 : Cpu6502() {
fetchedAddress = readPc()
val relative = readPc()
fetchedAddressZpr = if (relative >= 0x80) {
regPC - (256 - relative) and 0xffff
} else
regPC + relative and 0xffff
regPC-(256-relative) and 0xffff
} else regPC+relative and 0xffff
}
AddrMode.Izp -> {
// addressing mode used by the 65C02 only
fetchedAddress = readPc()
val lo = read((fetchedAddress) and 0xff)
val hi = read((fetchedAddress + 1) and 0xff)
val hi = read((fetchedAddress+1) and 0xff)
fetchedAddress = lo or (hi shl 8)
}
AddrMode.IaX -> {
@ -103,8 +97,8 @@ class Cpu65C02 : Cpu6502() {
var lo = readPc()
var hi = readPc()
fetchedAddress = lo or (hi shl 8)
lo = read((fetchedAddress + regX) and 0xffff)
hi = read((fetchedAddress + regX + 1) and 0xffff)
lo = read((fetchedAddress+regX) and 0xffff)
hi = read((fetchedAddress+regX+1) and 0xffff)
fetchedAddress = lo or (hi shl 8)
}
}
@ -374,8 +368,7 @@ class Cpu65C02 : Cpu6502() {
}
// opcode list: http://www.oxyron.de/html/opcodesc02.html
override val instructions: Array<Instruction> =
listOf(
override val instructions: Array<Instruction> = listOf(
/* 00 */ Instruction("brk", AddrMode.Imp, 7),
/* 01 */ Instruction("ora", AddrMode.IzX, 6),
/* 02 */ Instruction("nop", AddrMode.Imm, 2),
@ -631,14 +624,13 @@ class Cpu65C02 : Cpu6502() {
/* fc */ Instruction("nop", AddrMode.AbsX, 4),
/* fd */ Instruction("sbc", AddrMode.AbsX, 4),
/* fe */ Instruction("inc", AddrMode.AbsX, 7),
/* ff */ Instruction("bbs7", AddrMode.Zpr, 5)
).toTypedArray()
/* ff */ Instruction("bbs7", AddrMode.Zpr, 5)).toTypedArray()
override fun iBrk() {
// handle BRK ('software interrupt') or a real hardware IRQ
val nmi = pendingInterrupt == Interrupt.NMI
if (pendingInterrupt != null) {
pushStackAddr(regPC - 1)
pushStackAddr(regPC-1)
} else {
regPC++
pushStackAddr(regPC)
@ -667,8 +659,8 @@ class Cpu65C02 : Cpu6502() {
// see http://www.6502.org/tutorials/decimal_mode.html
// and https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/65c02core.c#l542
// (the implementation below is based on the code used by Vice)
var tmp = (regA and 0x0f) + (value and 0x0f) + if (regP.C) 1 else 0
var tmp2 = (regA and 0xf0) + (value and 0xf0)
var tmp = (regA and 0x0f)+(value and 0x0f)+if (regP.C) 1 else 0
var tmp2 = (regA and 0xf0)+(value and 0xf0)
if (tmp > 9) {
tmp2 += 0x10
tmp += 6
@ -676,13 +668,13 @@ class Cpu65C02 : Cpu6502() {
regP.V = (regA xor value).inv() and (regA xor tmp2) and 0b10000000 != 0
if (tmp2 > 0x90) tmp2 += 0x60
regP.C = tmp2 >= 0x100
tmp = (tmp and 0x0f) + (tmp2 and 0xf0)
tmp = (tmp and 0x0f)+(tmp2 and 0xf0)
regP.N = (tmp and 0b10000000) != 0
regP.Z = tmp == 0
regA = tmp and 0xff
} else {
// normal add (identical to 6502)
val tmp = value + regA + if (regP.C) 1 else 0
val tmp = value+regA+if (regP.C) 1 else 0
regP.N = (tmp and 0b10000000) != 0
regP.Z = (tmp and 0xff) == 0
regP.V = (regA xor value).inv() and (regA xor tmp) and 0b10000000 != 0
@ -696,14 +688,14 @@ class Cpu65C02 : Cpu6502() {
// and https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/65c02core.c#l1205
// (the implementation below is based on the code used by Vice)
val value = getFetched()
var tmp = (regA - value - if (regP.C) 0 else 1) and 0xffff
var tmp = (regA-value-if (regP.C) 0 else 1) and 0xffff
regP.V = (regA xor tmp) and (regA xor value) and 0b10000000 != 0
if (regP.D) {
if (tmp > 0xff) tmp = (tmp - 0x60) and 0xffff
val tmp2 = ((regA and 0x0f) - (value and 0x0f) - if (regP.C) 0 else 1) and 0xffff
if (tmp > 0xff) tmp = (tmp-0x60) and 0xffff
val tmp2 = ((regA and 0x0f)-(value and 0x0f)-if (regP.C) 0 else 1) and 0xffff
if (tmp2 > 0xff) tmp -= 6
}
regP.C = (regA - if (regP.C) 0 else 1) >= value
regP.C = (regA-if (regP.C) 0 else 1) >= value
regP.Z = (tmp and 0xff) == 0
regP.N = (tmp and 0b10000000) != 0
regA = tmp and 0xff
@ -711,7 +703,7 @@ class Cpu65C02 : Cpu6502() {
override fun iDec() {
if (currentInstruction.mode == AddrMode.Acc) {
regA = (regA - 1) and 0xff
regA = (regA-1) and 0xff
regP.Z = regA == 0
regP.N = (regA and 0b10000000) != 0
} else super.iDec()
@ -719,7 +711,7 @@ class Cpu65C02 : Cpu6502() {
override fun iInc() {
if (currentInstruction.mode == AddrMode.Acc) {
regA = (regA + 1) and 0xff
regA = (regA+1) and 0xff
regP.Z = regA == 0
regP.N = (regA and 0b10000000) != 0
} else super.iInc()
@ -776,98 +768,82 @@ class Cpu65C02 : Cpu6502() {
private fun iBbr0() {
val data = getFetched()
if (data and 1 == 0)
regPC = fetchedAddressZpr
if (data and 1 == 0) regPC = fetchedAddressZpr
}
private fun iBbr1() {
val data = getFetched()
if (data and 2 == 0)
regPC = fetchedAddressZpr
if (data and 2 == 0) regPC = fetchedAddressZpr
}
private fun iBbr2() {
val data = getFetched()
if (data and 4 == 0)
regPC = fetchedAddressZpr
if (data and 4 == 0) regPC = fetchedAddressZpr
}
private fun iBbr3() {
val data = getFetched()
if (data and 8 == 0)
regPC = fetchedAddressZpr
if (data and 8 == 0) regPC = fetchedAddressZpr
}
private fun iBbr4() {
val data = getFetched()
if (data and 16 == 0)
regPC = fetchedAddressZpr
if (data and 16 == 0) regPC = fetchedAddressZpr
}
private fun iBbr5() {
val data = getFetched()
if (data and 32 == 0)
regPC = fetchedAddressZpr
if (data and 32 == 0) regPC = fetchedAddressZpr
}
private fun iBbr6() {
val data = getFetched()
if (data and 64 == 0)
regPC = fetchedAddressZpr
if (data and 64 == 0) regPC = fetchedAddressZpr
}
private fun iBbr7() {
val data = getFetched()
if (data and 128 == 0)
regPC = fetchedAddressZpr
if (data and 128 == 0) regPC = fetchedAddressZpr
}
private fun iBbs0() {
val data = getFetched()
if (data and 1 != 0)
regPC = fetchedAddressZpr
if (data and 1 != 0) regPC = fetchedAddressZpr
}
private fun iBbs1() {
val data = getFetched()
if (data and 2 != 0)
regPC = fetchedAddressZpr
if (data and 2 != 0) regPC = fetchedAddressZpr
}
private fun iBbs2() {
val data = getFetched()
if (data and 4 != 0)
regPC = fetchedAddressZpr
if (data and 4 != 0) regPC = fetchedAddressZpr
}
private fun iBbs3() {
val data = getFetched()
if (data and 8 != 0)
regPC = fetchedAddressZpr
if (data and 8 != 0) regPC = fetchedAddressZpr
}
private fun iBbs4() {
val data = getFetched()
if (data and 16 != 0)
regPC = fetchedAddressZpr
if (data and 16 != 0) regPC = fetchedAddressZpr
}
private fun iBbs5() {
val data = getFetched()
if (data and 32 != 0)
regPC = fetchedAddressZpr
if (data and 32 != 0) regPC = fetchedAddressZpr
}
private fun iBbs6() {
val data = getFetched()
if (data and 64 != 0)
regPC = fetchedAddressZpr
if (data and 64 != 0) regPC = fetchedAddressZpr
}
private fun iBbs7() {
val data = getFetched()
if (data and 128 != 0)
regPC = fetchedAddressZpr
if (data and 128 != 0) regPC = fetchedAddressZpr
}
private fun iSmb0() {

View File

@ -11,6 +11,7 @@ interface IVirtualMachine {
fun loadFileInRam(file: File, loadAddress: Address?)
class MonitorCmdResult(val output: String, val prompt: String, val echo: Boolean)
fun executeMonitorCommand(command: String): MonitorCmdResult
val cpu: Cpu6502

View File

@ -11,23 +11,21 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
}
fun command(command: String): IVirtualMachine.MonitorCmdResult {
if(command.isEmpty())
return IVirtualMachine.MonitorCmdResult("", "", false)
if (command.isEmpty()) return IVirtualMachine.MonitorCmdResult("", "", false)
return when(command[0]) {
return when (command[0]) {
'h', '?' -> {
val text="h)elp m)emory i)nspect f)ill p)oke g)o a)ssemble d)isassemble\n$ # % for hex, dec, bin number"
val text = "h)elp m)emory i)nspect f)ill p)oke g)o a)ssemble d)isassemble\n$ # % for hex, dec, bin number"
IVirtualMachine.MonitorCmdResult(text, "", false)
}
'f' -> {
val parts = command.substring(1).trim().split(' ')
if(parts.size!=3)
IVirtualMachine.MonitorCmdResult("?syntax error", command, false)
if (parts.size != 3) IVirtualMachine.MonitorCmdResult("?syntax error", command, false)
else {
val start = parseNumber(parts[0])
val end = parseNumber(parts[1])
val value = parseNumber(parts[2]).toShort()
for(addr in start..end) {
for (addr in start..end) {
bus.write(addr, value)
}
IVirtualMachine.MonitorCmdResult("ok", "", true)
@ -36,20 +34,14 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
'm' -> {
val addresses = command.substring(1).trim().split(' ')
val start = parseNumber(addresses[0])
val end = if(addresses.size>1) parseNumber(addresses[1]) else start+1
val end = if (addresses.size > 1) parseNumber(addresses[1]) else start+1
val result = mutableListOf<String>()
for(addr in start until end step 16) {
result.add(
"m$${hexW(addr)} " +
(0..15).joinToString(" ") { hexB(bus.read(addr + it)) } + " " +
(0..15).joinToString("") {
val chr = bus.read(addr+it).toChar()
if(chr.isLetterOrDigit())
chr.toString()
else
"."
}
)
for (addr in start until end step 16) {
result.add("m$${hexW(addr)} "+(0..15).joinToString(" ") { hexB(bus.read(addr+it)) }+" "+(0..15).joinToString("") {
val chr = bus.read(addr+it).toChar()
if (chr.isLetterOrDigit()) chr.toString()
else "."
})
}
IVirtualMachine.MonitorCmdResult(result.joinToString("\n"), "", true)
}
@ -63,19 +55,14 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
'i' -> {
val addresses = command.substring(1).trim().split(' ')
val start = parseNumber(addresses[0])
val end = if(addresses.size>1) parseNumber(addresses[1]) else start+1
val end = if (addresses.size > 1) parseNumber(addresses[1]) else start+1
val result = mutableListOf<String>()
for(addr in start until end step 64) {
result.add(
"i$${hexW(addr)} " +
(0..63).joinToString("") {
val chr = bus.read(addr+it).toChar()
if(chr.isLetterOrDigit())
chr.toString()
else
"."
}
)
for (addr in start until end step 64) {
result.add("i$${hexW(addr)} "+(0..63).joinToString("") {
val chr = bus.read(addr+it).toChar()
if (chr.isLetterOrDigit()) chr.toString()
else "."
})
}
IVirtualMachine.MonitorCmdResult(result.joinToString("\n"), "", true)
}
@ -106,7 +93,7 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
'd' -> {
val addresses = command.substring(1).trim().split(' ')
val start = parseNumber(addresses[0])
val end = if(addresses.size>1) parseNumber(addresses[1]) else start
val end = if (addresses.size > 1) parseNumber(addresses[1]) else start
val disassem = cpu.disassemble(bus.memoryComponentFor(start), start, end)
IVirtualMachine.MonitorCmdResult(disassem.first.joinToString("\n") { "d$it" }, "d$${hexW(disassem.second)}", false)
}
@ -117,28 +104,25 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
}
private fun assemble(command: String, parts: List<String>): IVirtualMachine.MonitorCmdResult {
if(parts.size<2)
return IVirtualMachine.MonitorCmdResult("done", "", false)
if (parts.size < 2) return IVirtualMachine.MonitorCmdResult("done", "", false)
val address = parseNumber(parts[0])
val mnemonic = parts[1].toLowerCase()
when {
parts.size==2 -> {
parts.size == 2 -> {
// implied or acc
var instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Imp)]
if(instruction==null)
instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Acc)]
if(instruction==null)
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
if (instruction == null) instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Acc)]
if (instruction == null) return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
bus.write(address, instruction.toShort())
}
parts.size==3 -> {
parts.size == 3 -> {
val arg = parts[2]
when {
arg.startsWith('#') -> {
// immediate
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Imm)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Imm)] ?: return IVirtualMachine.MonitorCmdResult(
"?invalid instruction", command, false)
bus.write(address, instruction.toShort())
bus.write(address+1, parseNumber(arg.substring(1), true).toShort())
}
@ -146,11 +130,11 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
// indirect X
val indAddress = try {
parseNumber(arg.substring(1, arg.length-3))
} catch(x: NumberFormatException) {
} catch (x: NumberFormatException) {
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
}
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.IzX)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.IzX)] ?: return IVirtualMachine.MonitorCmdResult(
"?invalid instruction", command, false)
bus.write(address, instruction.toShort())
bus.write(address+1, indAddress.toShort())
}
@ -158,11 +142,11 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
// indirect Y
val indAddress = try {
parseNumber(arg.substring(1, arg.length-3))
} catch(x: NumberFormatException) {
} catch (x: NumberFormatException) {
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
}
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.IzY)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.IzY)] ?: return IVirtualMachine.MonitorCmdResult(
"?invalid instruction", command, false)
bus.write(address, instruction.toShort())
bus.write(address+1, indAddress.toShort())
}
@ -170,62 +154,64 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
// indexed X or zpIndexed X
val indAddress = try {
parseNumber(arg.substring(1, arg.length-2))
} catch(x: NumberFormatException) {
} catch (x: NumberFormatException) {
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
}
if(indAddress<=255) {
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.ZpX)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
if (indAddress <= 255) {
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.ZpX)] ?: return IVirtualMachine.MonitorCmdResult(
"?invalid instruction", command, false)
bus.write(address, instruction.toShort())
bus.write(address+1, indAddress.toShort())
} else {
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.AbsX)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
val instruction =
instructions[Pair(mnemonic, Cpu6502.AddrMode.AbsX)] ?: return IVirtualMachine.MonitorCmdResult(
"?invalid instruction", command, false)
bus.write(address, instruction.toShort())
bus.write(address + 1, (indAddress and 255).toShort())
bus.write(address + 2, (indAddress ushr 8).toShort())
bus.write(address+1, (indAddress and 255).toShort())
bus.write(address+2, (indAddress ushr 8).toShort())
}
}
arg.endsWith(",y") -> {
// indexed Y or zpIndexed Y
val indAddress = try {
parseNumber(arg.substring(1, arg.length - 2))
} catch(x: NumberFormatException) {
parseNumber(arg.substring(1, arg.length-2))
} catch (x: NumberFormatException) {
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
}
if(indAddress<=255) {
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.ZpY)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
if (indAddress <= 255) {
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.ZpY)] ?: return IVirtualMachine.MonitorCmdResult(
"?invalid instruction", command, false)
bus.write(address, instruction.toShort())
bus.write(address+1, indAddress.toShort())
} else {
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.AbsY)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
val instruction =
instructions[Pair(mnemonic, Cpu6502.AddrMode.AbsY)] ?: return IVirtualMachine.MonitorCmdResult(
"?invalid instruction", command, false)
bus.write(address, instruction.toShort())
bus.write(address + 1, (indAddress and 255).toShort())
bus.write(address + 2, (indAddress ushr 8).toShort())
bus.write(address+1, (indAddress and 255).toShort())
bus.write(address+2, (indAddress ushr 8).toShort())
}
}
arg.endsWith(")") -> {
// indirect (jmp)
val indAddress = try {
parseNumber(arg.substring(1, arg.length - 1))
} catch(x: NumberFormatException) {
parseNumber(arg.substring(1, arg.length-1))
} catch (x: NumberFormatException) {
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
}
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Ind)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Ind)] ?: return IVirtualMachine.MonitorCmdResult(
"?invalid instruction", command, false)
bus.write(address, instruction.toShort())
bus.write(address + 1, (indAddress and 255).toShort())
bus.write(address + 2, (indAddress ushr 8).toShort())
bus.write(address+1, (indAddress and 255).toShort())
bus.write(address+2, (indAddress ushr 8).toShort())
}
else -> {
val instr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Rel)]
if(instr!=null) {
if (instr != null) {
// relative address
val rel = try {
parseNumber(arg)
} catch(x: NumberFormatException) {
} catch (x: NumberFormatException) {
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
}
bus.write(address, instr.toShort())
@ -234,20 +220,21 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
// absolute or absZp
val absAddress = try {
parseNumber(arg)
} catch(x: NumberFormatException) {
} catch (x: NumberFormatException) {
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
}
if (absAddress <= 255) {
val absInstr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Zp)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
val absInstr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Zp)] ?: return IVirtualMachine.MonitorCmdResult(
"?invalid instruction", command, false)
bus.write(address, absInstr.toShort())
bus.write(address + 1, absAddress.toShort())
bus.write(address+1, absAddress.toShort())
} else {
val absInstr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Abs)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
val absInstr =
instructions[Pair(mnemonic, Cpu6502.AddrMode.Abs)] ?: return IVirtualMachine.MonitorCmdResult(
"?invalid instruction", command, false)
bus.write(address, absInstr.toShort())
bus.write(address + 1, (absAddress and 255).toShort())
bus.write(address + 2, (absAddress ushr 8).toShort())
bus.write(address+1, (absAddress and 255).toShort())
bus.write(address+2, (absAddress ushr 8).toShort())
}
}
}
@ -261,12 +248,10 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
}
private fun parseNumber(number: String, decimalFirst: Boolean = false): Int {
val num=number.trim()
if(num.isBlank())
return 0
if(decimalFirst && num[0].isDigit())
return num.toInt(10)
return when(num[0]) {
val num = number.trim()
if (num.isBlank()) return 0
if (decimalFirst && num[0].isDigit()) return num.toInt(10)
return when (num[0]) {
'$' -> num.substring(1).trimStart().toInt(16)
'#' -> num.substring(1).trimStart().toInt(10)
'%' -> num.substring(1).trimStart().toInt(2)

View File

@ -5,10 +5,8 @@ import razorvine.ksim65.components.Address
fun hexW(number: Address, allowSingleByte: Boolean = false): String {
val msb = number ushr 8
val lsb = number and 0xff
return if (msb == 0 && allowSingleByte)
hexB(lsb)
else
hexB(msb) + hexB(lsb)
return if (msb == 0 && allowSingleByte) hexB(lsb)
else hexB(msb)+hexB(lsb)
}
fun hexB(number: Short): String = hexB(number.toInt())
@ -17,7 +15,7 @@ fun hexB(number: Int): String {
val hexdigits = "0123456789abcdef"
val loNibble = number and 15
val hiNibble = number ushr 4
return hexdigits[hiNibble].toString() + hexdigits[loNibble]
return hexdigits[hiNibble].toString()+hexdigits[loNibble]
}
typealias BreakpointHandler = (cpu: Cpu6502, pc: Address) -> Cpu6502.BreakpointResultAction

View File

@ -10,7 +10,6 @@ object Version {
}
val copyright: String by lazy {
"KSim65 6502 cpu simulator v$version by Irmen de Jong (irmen@razorvine.net)" +
"\nThis software is free and licensed under the MIT open-source license\n"
"KSim65 6502 cpu simulator v$version by Irmen de Jong (irmen@razorvine.net)"+"\nThis software is free and licensed under the MIT open-source license\n"
}
}

View File

@ -36,19 +36,16 @@ abstract class MemMappedComponent(val startAddress: Address, val endAddress: Add
require(startAddress >= 0 && endAddress <= 0xffff) { "can only have 16-bit address space" }
}
fun hexDump(from: Address, to: Address, charmapper: ((Short)->Char)? = null) {
fun hexDump(from: Address, to: Address, charmapper: ((Short) -> Char)? = null) {
(from..to).chunked(16).forEach {
print("\$${it.first().toString(16).padStart(4, '0')} ")
val bytes = it.map { address -> get(address) }
bytes.forEach { byte ->
print(byte.toString(16).padStart(2, '0') + " ")
print(byte.toString(16).padStart(2, '0')+" ")
}
print(" ")
val chars =
if(charmapper!=null)
bytes.map { b -> charmapper(b) }
else
bytes.map { b -> if(b in 32..255) b.toChar() else '.' }
val chars = if (charmapper != null) bytes.map { b -> charmapper(b) }
else bytes.map { b -> if (b in 32..255) b.toChar() else '.' }
println(chars.joinToString(""))
}
}
@ -57,13 +54,12 @@ abstract class MemMappedComponent(val startAddress: Address, val endAddress: Add
/**
* Base class for components that actually contain memory (RAM or ROM chips).
*/
abstract class MemoryComponent(startAddress: Address, endAddress: Address) :
MemMappedComponent(startAddress, endAddress) {
abstract class MemoryComponent(startAddress: Address, endAddress: Address) : MemMappedComponent(startAddress, endAddress) {
abstract val data: Array<UByte>
init {
require(startAddress and 0xff == 0 && endAddress and 0xff == 0xff) {"address range must span complete page(s)"}
require(startAddress and 0xff == 0 && endAddress and 0xff == 0xff) { "address range must span complete page(s)" }
}
fun getPages(page: Int, numPages: Int): Array<UByte> = data.copyOfRange(page*256, (page+numPages)*256)

View File

@ -28,18 +28,13 @@ import kotlin.math.min
* 0a r/w character at cursor pos, updates cursor position, scrolls up if necessary
* control chars: 8=backspace, 9=tab, 10=newline, 12=form feed (clear screen), 13=carriage return
*/
class Display(
startAddress: Address, endAddress: Address,
private val host: IHostInterface,
private val charWidth: Int,
private val charHeight: Int,
private val pixelWidth: Int,
private val pixelHeight: Int
) : MemMappedComponent(startAddress, endAddress) {
class Display(startAddress: Address, endAddress: Address, private val host: IHostInterface, private val charWidth: Int,
private val charHeight: Int, private val pixelWidth: Int, private val pixelHeight: Int) :
MemMappedComponent(startAddress, endAddress) {
init {
require(endAddress - startAddress + 1 == 11) { "display needs exactly 11 memory bytes" }
require(endAddress-startAddress+1 == 11) { "display needs exactly 11 memory bytes" }
}
private var cursorX = 0
@ -69,11 +64,11 @@ class Display(
}
override operator fun get(address: Address): UByte {
return when(address-startAddress) {
return when (address-startAddress) {
0x00 -> charposX.toShort()
0x01 -> charposY.toShort()
0x02 -> {
if(charposY in 0 until charHeight && charposX in 0 until charWidth) {
if (charposY in 0 until charHeight && charposX in 0 until charWidth) {
charMatrix[charposY][charposX]
} else 0xff
}
@ -81,11 +76,11 @@ class Display(
0x04 -> (pixelX ushr 8).toShort()
0x05 -> (pixelY and 0xff).toShort()
0x06 -> (pixelY ushr 8).toShort()
0x07 -> if(host.getPixel(pixelX, pixelY)) 1 else 0
0x07 -> if (host.getPixel(pixelX, pixelY)) 1 else 0
0x08 -> cursorX.toShort()
0x09 -> cursorY.toShort()
0x0a -> {
if(cursorY in 0 until charHeight && cursorX in 0 until charWidth) {
if (cursorY in 0 until charHeight && cursorX in 0 until charWidth) {
charMatrix[cursorY][cursorX]
} else 0xff
}
@ -94,11 +89,11 @@ class Display(
}
override operator fun set(address: Address, data: UByte) {
when(address-startAddress) {
when (address-startAddress) {
0x00 -> charposX = data.toInt()
0x01 -> charposY = data.toInt()
0x02 -> {
if(charposY in 0 until charHeight && charposX in 0 until charWidth) {
if (charposY in 0 until charHeight && charposX in 0 until charWidth) {
charMatrix[charposY][charposX] = data
host.setChar(charposX, charposY, data.toChar())
}
@ -108,7 +103,7 @@ class Display(
0x05 -> pixelY = (pixelY and 0xff00) or data.toInt()
0x06 -> pixelY = (pixelY and 0x00ff) or (data.toInt() shl 8)
0x07 -> {
if(pixelX in 0 until ScreenDefs.SCREEN_WIDTH && pixelY in 0 until ScreenDefs.SCREEN_HEIGHT) {
if (pixelX in 0 until ScreenDefs.SCREEN_WIDTH && pixelY in 0 until ScreenDefs.SCREEN_HEIGHT) {
if (data == 0.toShort()) host.clearPixel(pixelX, pixelY)
else host.setPixel(pixelX, pixelY)
}
@ -116,15 +111,15 @@ class Display(
0x08 -> cursorX = min(data.toInt() and 65535, charWidth-1)
0x09 -> cursorY = min(data.toInt() and 65535, charHeight-1)
0x0a -> {
if(cursorY in 0 until charHeight && cursorX in 0 until charWidth) {
when(data.toInt()) {
if (cursorY in 0 until charHeight && cursorX in 0 until charWidth) {
when (data.toInt()) {
0x08 -> {
// backspace
cursorX--
if(cursorX<0) {
if(cursorY>0) {
if (cursorX < 0) {
if (cursorY > 0) {
cursorY--
cursorX = charWidth - 1
cursorX = charWidth-1
}
}
charMatrix[cursorY][cursorX] = ' '.toShort()
@ -132,8 +127,8 @@ class Display(
}
0x09 -> {
// tab
cursorX = (cursorX and 0b11111000) + 8
if(cursorX >= charWidth) {
cursorX = (cursorX and 0b11111000)+8
if (cursorX >= charWidth) {
cursorX = 0
cursorDown()
}
@ -150,7 +145,7 @@ class Display(
charMatrix[cursorY][cursorX] = data
host.setChar(cursorX, cursorY, data.toChar())
cursorX++
if(cursorX >= charWidth) {
if (cursorX >= charWidth) {
cursorX = 0
cursorDown()
}
@ -164,12 +159,12 @@ class Display(
private fun cursorDown() {
cursorY++
while(cursorY >= charHeight) {
while (cursorY >= charHeight) {
// scroll up 1 line
for(y in 0 .. charHeight-2) {
for (y in 0..charHeight-2) {
charMatrix[y+1].copyInto(charMatrix[y])
}
for(x in 0 until charWidth) {
for (x in 0 until charWidth) {
charMatrix[charHeight-1][x] = ' '.toShort()
}
cursorY--

View File

@ -14,21 +14,22 @@ import razorvine.ksim65.IHostInterface
* 00 character from keyboard, 0 = no character/key pressed
*/
class Keyboard(startAddress: Address, endAddress: Address, private val host: IHostInterface) :
MemMappedComponent(startAddress, endAddress) {
MemMappedComponent(startAddress, endAddress) {
init {
require(endAddress - startAddress + 1 == 1) { "keyboard needs exactly 1 memory byte" }
require(endAddress-startAddress+1 == 1) { "keyboard needs exactly 1 memory byte" }
}
override fun clock() {}
override fun reset() {}
override operator fun get(address: Address): UByte {
return when(address-startAddress) {
return when (address-startAddress) {
0x00 -> host.keyboard()?.toShort() ?: 0
else -> 0xff
}
}
override operator fun set(address: Address, data: UByte) { /* read-only device */ }
override operator fun set(address: Address, data: UByte) { /* read-only device */
}
}

View File

@ -14,11 +14,10 @@ import razorvine.ksim65.IHostInterface
* 04 buttons, bit 0 = left button, bit 1 = right button, bit 2 = middle button
* 05 latch: when written, samples the current mouse position and button state.
*/
class Mouse(startAddress: Address, endAddress: Address, private val host: IHostInterface) :
MemMappedComponent(startAddress, endAddress) {
class Mouse(startAddress: Address, endAddress: Address, private val host: IHostInterface) : MemMappedComponent(startAddress, endAddress) {
init {
require(endAddress - startAddress + 1 == 6) { "mouse needs exactly 6 memory bytes" }
require(endAddress-startAddress+1 == 6) { "mouse needs exactly 6 memory bytes" }
}
override fun clock() {}
@ -27,7 +26,7 @@ class Mouse(startAddress: Address, endAddress: Address, private val host: IHostI
private var mouse = host.mouse()
override operator fun get(address: Address): UByte {
return when (address - startAddress) {
return when (address-startAddress) {
0x00 -> (mouse.x and 0xff).toShort()
0x01 -> (mouse.x ushr 8).toShort()
0x02 -> (mouse.y and 0xff).toShort()
@ -43,7 +42,6 @@ class Mouse(startAddress: Address, endAddress: Address, private val host: IHostI
}
override operator fun set(address: Address, data: UByte) {
if (address - startAddress == 0x05)
mouse = host.mouse()
if (address-startAddress == 0x05) mouse = host.mouse()
}
}

View File

@ -12,22 +12,19 @@ class ParallelPort(startAddress: Address, endAddress: Address) : MemMappedCompon
private var dataByte: UByte = 0
init {
require(endAddress - startAddress + 1 == 2) { "parallel needs exactly 2 memory bytes (data + control)" }
require(endAddress-startAddress+1 == 2) { "parallel needs exactly 2 memory bytes (data + control)" }
}
override fun clock() {}
override fun reset() {}
override operator fun get(address: Address): UByte {
return if (address == startAddress)
dataByte
else
0xff
return if (address == startAddress) dataByte
else 0xff
}
override operator fun set(address: Address, data: UByte) {
if (address == startAddress)
dataByte = data
if (address == startAddress) dataByte = data
else if (address == endAddress) {
if ((data.toInt() and 1) == 1) {
val char = dataByte.toChar()

View File

@ -8,12 +8,12 @@ import java.io.InputStream
* A RAM chip with read/write memory.
*/
class Ram(startAddress: Address, endAddress: Address) : MemoryComponent(startAddress, endAddress) {
override val data = Array<UByte>(endAddress - startAddress + 1) { 0 }
override val data = Array<UByte>(endAddress-startAddress+1) { 0 }
override operator fun get(address: Address): UByte = data[address - startAddress]
override operator fun get(address: Address): UByte = data[address-startAddress]
override operator fun set(address: Address, data: UByte) {
this.data[address - startAddress] = data
this.data[address-startAddress] = data
}
override fun clock() {}
@ -27,24 +27,19 @@ class Ram(startAddress: Address, endAddress: Address) : MemoryComponent(startAdd
/**
* Load a c64-style prg program. This file type has the load address as the first two bytes.
*/
fun loadPrg(filename: String, overrideLoadAddress: Address?)
= loadPrg(File(filename).inputStream(), overrideLoadAddress)
fun loadPrg(filename: String, overrideLoadAddress: Address?) = loadPrg(File(filename).inputStream(), overrideLoadAddress)
/**
* Load a c64-style prg program. This file type has the load address as the first two bytes.
*/
fun loadPrg(stream: InputStream, overrideLoadAddress: Address?): Pair<Address, Int> {
val bytes = stream.readBytes()
if(bytes.size > 0xffff)
throw IOException("file too large")
val loadAddress = overrideLoadAddress ?: bytes[0] + 256* bytes[1]
val baseAddress = loadAddress - startAddress
if (bytes.size > 0xffff) throw IOException("file too large")
val loadAddress = overrideLoadAddress ?: bytes[0]+256*bytes[1]
val baseAddress = loadAddress-startAddress
bytes.drop(2).forEachIndexed { index, byte ->
data[baseAddress + index] =
if (byte >= 0)
byte.toShort()
else
(256 + byte).toShort()
data[baseAddress+index] = if (byte >= 0) byte.toShort()
else (256+byte).toShort()
}
return Pair(baseAddress, bytes.size-2)
}
@ -57,19 +52,14 @@ class Ram(startAddress: Address, endAddress: Address) : MemoryComponent(startAdd
load(bytes, address)
}
fun load(data: Array<UByte>, address: Address) =
data.forEachIndexed { index, byte ->
val baseAddress = address - startAddress
this.data[baseAddress + index] = byte
}
fun load(data: Array<UByte>, address: Address) = data.forEachIndexed { index, byte ->
val baseAddress = address-startAddress
this.data[baseAddress+index] = byte
}
fun load(data: ByteArray, address: Address) =
data.forEachIndexed { index, byte ->
val baseAddress = address - startAddress
this.data[baseAddress + index] =
if (byte >= 0)
byte.toShort()
else
(256 + byte).toShort()
}
fun load(data: ByteArray, address: Address) = data.forEachIndexed { index, byte ->
val baseAddress = address-startAddress
this.data[baseAddress+index] = if (byte >= 0) byte.toShort()
else (256+byte).toShort()
}
}

View File

@ -23,7 +23,7 @@ import java.time.LocalTime
class RealTimeClock(startAddress: Address, endAddress: Address) : MemMappedComponent(startAddress, endAddress) {
init {
require(endAddress - startAddress + 1 == 9) { "rtc needs exactly 9 memory bytes" }
require(endAddress-startAddress+1 == 9) { "rtc needs exactly 9 memory bytes" }
}
override fun clock() {
@ -35,7 +35,7 @@ class RealTimeClock(startAddress: Address, endAddress: Address) : MemMappedCompo
}
override operator fun get(address: Address): UByte {
return when (address - startAddress) {
return when (address-startAddress) {
0x00 -> {
val year = LocalDate.now().year
(year and 255).toShort()
@ -50,11 +50,11 @@ class RealTimeClock(startAddress: Address, endAddress: Address) : MemMappedCompo
0x05 -> LocalTime.now().minute.toShort()
0x06 -> LocalTime.now().second.toShort()
0x07 -> {
val ms = LocalTime.now().nano / 1000000
val ms = LocalTime.now().nano/1000000
(ms and 255).toShort()
}
0x08 -> {
val ms = LocalTime.now().nano / 1000000
val ms = LocalTime.now().nano/1000000
(ms ushr 8).toShort()
}
else -> 0xff

View File

@ -6,15 +6,16 @@ import java.io.File
* A ROM chip (read-only memory).
*/
class Rom(startAddress: Address, endAddress: Address, initialData: Array<UByte>? = null) : MemoryComponent(startAddress, endAddress) {
override val data: Array<UByte> =
initialData?.copyOf() ?: Array<UByte>(endAddress - startAddress + 1) { 0 }
override val data: Array<UByte> = initialData?.copyOf() ?: Array<UByte>(endAddress-startAddress+1) { 0 }
init {
require(endAddress - startAddress + 1 == data.size) { "rom address range doesn't match size of data bytes" }
require(endAddress-startAddress+1 == data.size) { "rom address range doesn't match size of data bytes" }
}
override operator fun get(address: Address): UByte = data[address-startAddress]
override operator fun set(address: Address, data: UByte) { /* read-only */
}
override operator fun get(address: Address): UByte = data[address - startAddress]
override operator fun set(address: Address, data: UByte) { /* read-only */ }
override fun clock() {}
override fun reset() {}
@ -26,17 +27,12 @@ class Rom(startAddress: Address, endAddress: Address, initialData: Array<UByte>?
load(bytes)
}
fun load(data: Array<UByte>) =
data.forEachIndexed { index, byte ->
this.data[index] = byte
}
fun load(data: Array<UByte>) = data.forEachIndexed { index, byte ->
this.data[index] = byte
}
fun load(data: ByteArray) =
data.forEachIndexed { index, byte ->
this.data[index] =
if (byte >= 0)
byte.toShort()
else
(256 + byte).toShort()
}
fun load(data: ByteArray) = data.forEachIndexed { index, byte ->
this.data[index] = if (byte >= 0) byte.toShort()
else (256+byte).toShort()
}
}

View File

@ -26,17 +26,15 @@ class Timer(startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemM
}
init {
require(endAddress - startAddress + 1 == 4) { "timer needs exactly 4 memory bytes" }
require(endAddress-startAddress+1 == 4) { "timer needs exactly 4 memory bytes" }
}
override fun clock() {
if (enabled && interval > 0) {
counter++
if (counter == interval) {
if (nmi)
cpu.nmi()
else
cpu.irq()
if (nmi) cpu.nmi()
else cpu.irq()
counter = 0
}
}
@ -50,7 +48,7 @@ class Timer(startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemM
}
override operator fun get(address: Address): UByte {
return when (address - startAddress) {
return when (address-startAddress) {
0x00 -> {
var data = 0
if (enabled) data = data or 0b00000001
@ -65,7 +63,7 @@ class Timer(startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemM
}
override operator fun set(address: Address, data: UByte) {
when (address - startAddress) {
when (address-startAddress) {
0x00 -> {
val i = data.toInt()
enabled = (i and 0b00000001) != 0