mirror of
https://github.com/irmen/ksim65.git
synced 2025-02-07 18:30:48 +00:00
implemented sprites, code reformatting
This commit is contained in:
parent
8a2212e34f
commit
ea4b5239cc
18
.idea/codeStyles/Project.xml
generated
18
.idea/codeStyles/Project.xml
generated
@ -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>
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 -> {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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}")
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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--
|
||||
|
@ -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 */
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user