mirror of
https://github.com/irmen/ksim65.git
synced 2024-12-13 22:30:27 +00:00
added Commodore-64 emulator
This commit is contained in:
parent
8aad9795f7
commit
e13e86f33f
20
README.md
20
README.md
@ -24,12 +24,20 @@ Properties of this simulator:
|
||||
- passes several extensive unit test suites that verify instruction and cpu flags behavior
|
||||
- maximum simulated performance is a 6502 running at ~100 Mhz (on my machine)
|
||||
|
||||
## Virtual machine examples
|
||||
|
||||
Two virtual example machines are included.
|
||||
The default one starts with ``gradle run`` or run the ``ksim64vm`` command.
|
||||
There's another one ``ehBasicMain`` that is configured to run the "enhanced 6502 basic" ROM.
|
||||
|
||||
## Documentation
|
||||
|
||||
Still to be written. For now, use the source ;-)
|
||||
|
||||
|
||||
## Virtual machine examples
|
||||
|
||||
Three virtual example machines are included.
|
||||
The default one starts with ``gradle run`` or run the ``ksim64vm`` command.
|
||||
There's another one ``ehBasicMain`` that is configured to run the "enhanced 6502 basic" ROM:
|
||||
|
||||
![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):
|
||||
|
||||
![C64 emulation](c64.png)
|
||||
|
BIN
ehbasic.png
Normal file
BIN
ehbasic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 179 KiB |
8791
src/ehbasic/basic-patched.asm
Normal file
8791
src/ehbasic/basic-patched.asm
Normal file
File diff suppressed because it is too large
Load Diff
133
src/ehbasic/main.asm
Normal file
133
src/ehbasic/main.asm
Normal file
@ -0,0 +1,133 @@
|
||||
; minimal monitor for EhBASIC and 6502 simulator V1.05
|
||||
; tabs converted to space, tabwidth=6
|
||||
|
||||
; To run EhBASIC on the simulator load and assemble [F7] this file, start the simulator
|
||||
; running [F6] then start the code with the RESET [CTRL][SHIFT]R. Just selecting RUN
|
||||
; will do nothing, you'll still have to do a reset to run the code.
|
||||
|
||||
.include "basic-patched.asm"
|
||||
|
||||
; put the IRQ and MNI code in RAM so that it can be changed
|
||||
|
||||
IRQ_vec = VEC_SV+2 ; IRQ code vector
|
||||
NMI_vec = IRQ_vec+$0A ; NMI code vector
|
||||
|
||||
; setup for the 6502 simulator environment
|
||||
|
||||
IO_AREA = $F000 ; set I/O area for this monitor
|
||||
|
||||
ACIAsimwr = $d00a ; simulated ACIA write port (display char out)
|
||||
ACIAsimrd = $d400 ; simulated ACIA read port (keyboard input char)
|
||||
|
||||
; now the code. all this does is set up the vectors and interrupt code
|
||||
; and wait for the user to select [C]old or [W]arm start. nothing else
|
||||
; fits in less than 128 bytes
|
||||
|
||||
*= $FF80 ; pretend this is in a 1/8K ROM
|
||||
|
||||
; reset vector points here
|
||||
|
||||
RES_vec
|
||||
CLD ; clear decimal mode
|
||||
LDX #$FF ; empty stack
|
||||
TXS ; set the stack
|
||||
|
||||
; set up vectors and interrupt code, copy them to page 2
|
||||
|
||||
LDY #END_CODE-LAB_vec ; set index/count
|
||||
LAB_stlp
|
||||
LDA LAB_vec-1,Y ; get byte from interrupt code
|
||||
STA VEC_IN-1,Y ; save to RAM
|
||||
DEY ; decrement index/count
|
||||
BNE LAB_stlp ; loop if more to do
|
||||
|
||||
; now do the signon message, Y = $00 here
|
||||
|
||||
LAB_signon
|
||||
LDA LAB_mess,Y ; get byte from sign on message
|
||||
BEQ LAB_nokey ; exit loop if done
|
||||
|
||||
JSR V_OUTP ; output character
|
||||
INY ; increment index
|
||||
BNE LAB_signon ; loop, branch always
|
||||
|
||||
LAB_nokey
|
||||
JSR V_INPT ; call scan input device
|
||||
BCC LAB_nokey ; loop if no key
|
||||
|
||||
AND #$DF ; mask xx0x xxxx, ensure upper case
|
||||
CMP #'W' ; compare with [W]arm start
|
||||
BEQ LAB_dowarm ; branch if [W]arm start
|
||||
|
||||
CMP #'C' ; compare with [C]old start
|
||||
BNE RES_vec ; loop if not [C]old start
|
||||
|
||||
JMP LAB_COLD ; do EhBASIC cold start
|
||||
|
||||
LAB_dowarm
|
||||
JMP LAB_WARM ; do EhBASIC warm start
|
||||
|
||||
; byte out to simulated ACIA
|
||||
|
||||
ACIAout
|
||||
STA ACIAsimwr ; save byte to simulated ACIA
|
||||
RTS
|
||||
|
||||
; byte in from simulated ACIA
|
||||
|
||||
ACIAin
|
||||
LDA ACIAsimrd ; get byte from simulated ACIA
|
||||
BEQ LAB_nobyw ; branch if no byte waiting
|
||||
|
||||
SEC ; flag byte received
|
||||
RTS
|
||||
|
||||
LAB_nobyw
|
||||
CLC ; flag no byte received
|
||||
no_load ; empty load vector for EhBASIC
|
||||
no_save ; empty save vector for EhBASIC
|
||||
RTS
|
||||
|
||||
; vector tables
|
||||
|
||||
LAB_vec
|
||||
.word ACIAin ; byte in from simulated ACIA
|
||||
.word ACIAout ; byte out to simulated ACIA
|
||||
.word no_load ; null load vector for EhBASIC
|
||||
.word no_save ; null save vector for EhBASIC
|
||||
|
||||
; EhBASIC IRQ support
|
||||
|
||||
IRQ_CODE
|
||||
PHA ; save A
|
||||
LDA IrqBase ; get the IRQ flag byte
|
||||
LSR ; shift the set b7 to b6, and on down ...
|
||||
ORA IrqBase ; OR the original back in
|
||||
STA IrqBase ; save the new IRQ flag byte
|
||||
PLA ; restore A
|
||||
RTI
|
||||
|
||||
; EhBASIC NMI support
|
||||
|
||||
NMI_CODE
|
||||
PHA ; save A
|
||||
LDA NmiBase ; get the NMI flag byte
|
||||
LSR ; shift the set b7 to b6, and on down ...
|
||||
ORA NmiBase ; OR the original back in
|
||||
STA NmiBase ; save the new NMI flag byte
|
||||
PLA ; restore A
|
||||
RTI
|
||||
|
||||
END_CODE
|
||||
|
||||
LAB_mess
|
||||
.text $0D,$0A,"6502 EhBASIC [C]old/[W]arm ?",$00
|
||||
; sign on string
|
||||
|
||||
; system vectors
|
||||
|
||||
*= $FFFA
|
||||
|
||||
.word NMI_vec ; NMI vector
|
||||
.word RES_vec ; RESET vector
|
||||
.word IRQ_vec ; IRQ vector
|
247
src/main/kotlin/razorvine/c64emu/GUI.kt
Normal file
247
src/main/kotlin/razorvine/c64emu/GUI.kt
Normal file
@ -0,0 +1,247 @@
|
||||
package razorvine.c64emu
|
||||
|
||||
import razorvine.ksim65.components.MemoryComponent
|
||||
import java.awt.*
|
||||
import java.awt.image.BufferedImage
|
||||
import java.awt.event.*
|
||||
import java.awt.image.ByteLookupTable
|
||||
import java.awt.image.LookupOp
|
||||
import java.io.CharConversionException
|
||||
import java.util.*
|
||||
import javax.swing.*
|
||||
import javax.swing.Timer
|
||||
|
||||
|
||||
/**
|
||||
* Define the C64 character screen matrix: 320x200 pixels,
|
||||
* 40x25 characters (of 8x8 pixels), and a colored border.
|
||||
*/
|
||||
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 DISPLAY_PIXEL_SCALING: Double = 2.0
|
||||
|
||||
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
||||
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 class BitmapScreenPanel(val chargenData: ByteArray, val ram: MemoryComponent) : JPanel() {
|
||||
|
||||
private val image = BufferedImage(ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT, BufferedImage.TYPE_INT_ARGB)
|
||||
private val g2d = image.graphics as Graphics2D
|
||||
private val normalCharacters = loadCharacters(false)
|
||||
|
||||
init {
|
||||
val size = Dimension(
|
||||
(image.width * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
|
||||
(image.height * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt()
|
||||
)
|
||||
minimumSize = size
|
||||
maximumSize = size
|
||||
preferredSize = size
|
||||
isFocusable = true
|
||||
requestFocusInWindow()
|
||||
}
|
||||
|
||||
private fun loadCharacters(shifted: Boolean): Array<BufferedImage> {
|
||||
val chars = Array(256) { BufferedImage(8, 8, BufferedImage.TYPE_BYTE_BINARY) }
|
||||
val offset = if(shifted) 256*8 else 0
|
||||
// val color = ScreenDefs.colorPalette[14].rgb
|
||||
for(char in 0..255) {
|
||||
for(line in 0..7) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
return chars
|
||||
}
|
||||
|
||||
override fun paint(graphics: Graphics?) {
|
||||
redrawCharacters()
|
||||
val g2d = graphics as Graphics2D?
|
||||
g2d!!.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF)
|
||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE)
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC)
|
||||
g2d.drawImage(
|
||||
image, 0, 0, (image.width * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
|
||||
(image.height * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), null
|
||||
)
|
||||
}
|
||||
|
||||
private fun redrawCharacters() {
|
||||
val screen = 0x0400
|
||||
val colors = 0xd800
|
||||
g2d.background = ScreenDefs.colorPalette[ram[0xd021].toInt()]
|
||||
g2d.clearRect(0, 0, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT)
|
||||
for(y in 0 until ScreenDefs.SCREEN_HEIGHT_CHARS) {
|
||||
for(x in 0 until ScreenDefs.SCREEN_WIDTH_CHARS) {
|
||||
val char = ram[screen + x + y*ScreenDefs.SCREEN_WIDTH_CHARS].toInt()
|
||||
val color = ram[colors + x + y*ScreenDefs.SCREEN_WIDTH_CHARS].toInt()
|
||||
drawColoredChar(x, y, char, color and 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val coloredCharacters = mutableMapOf<Pair<Int, Int>, BufferedImage>()
|
||||
|
||||
private fun drawColoredChar(x: Int, y: Int, char: Int, color: Int) {
|
||||
var cached = coloredCharacters[Pair(char, color)]
|
||||
if(cached==null) {
|
||||
cached = normalCharacters.get(char)
|
||||
val colored = g2d.deviceConfiguration.createCompatibleImage(8, 8, BufferedImage.BITMASK)
|
||||
val sourceRaster = cached.raster
|
||||
val coloredRaster = colored.raster
|
||||
val pixelArray = IntArray(4)
|
||||
val javaColor = ScreenDefs.colorPalette[color]
|
||||
val coloredPixel = listOf(javaColor.red, javaColor.green, javaColor.blue, javaColor.alpha).toIntArray()
|
||||
for(y in 0..7) {
|
||||
for(x in 0..7) {
|
||||
val source = sourceRaster.getPixel(x, y, pixelArray)
|
||||
if(source[0]!=0) {
|
||||
coloredRaster.setPixel(x, y, coloredPixel)
|
||||
}
|
||||
}
|
||||
}
|
||||
coloredCharacters[Pair(char, color)] = colored
|
||||
cached = colored
|
||||
}
|
||||
g2d.drawImage(cached, x * 8, y * 8, null)
|
||||
}
|
||||
}
|
||||
|
||||
class MainWindow(title: String, chargenData: ByteArray, val ram: MemoryComponent) : JFrame(title), KeyListener {
|
||||
private val canvas = BitmapScreenPanel(chargenData, ram)
|
||||
val keyboardBuffer = ArrayDeque<Char>()
|
||||
private var borderTop: JPanel
|
||||
private var borderBottom: JPanel
|
||||
private var borderLeft: JPanel
|
||||
private var borderRight: JPanel
|
||||
|
||||
init {
|
||||
val borderWidth = 24
|
||||
layout = GridBagLayout()
|
||||
defaultCloseOperation = EXIT_ON_CLOSE
|
||||
isResizable = false
|
||||
isFocusable = true
|
||||
|
||||
// the borders (top, left, right, bottom)
|
||||
borderTop = JPanel().apply {
|
||||
preferredSize = Dimension(
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * (ScreenDefs.SCREEN_WIDTH + 2 * borderWidth)).toInt(),
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * borderWidth).toInt()
|
||||
)
|
||||
background = ScreenDefs.colorPalette[14]
|
||||
}
|
||||
borderBottom = JPanel().apply {
|
||||
preferredSize = Dimension(
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * (ScreenDefs.SCREEN_WIDTH + 2 * borderWidth)).toInt(),
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * borderWidth).toInt()
|
||||
)
|
||||
background = ScreenDefs.colorPalette[14]
|
||||
}
|
||||
borderLeft = JPanel().apply {
|
||||
preferredSize = Dimension(
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * borderWidth).toInt(),
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * ScreenDefs.SCREEN_HEIGHT).toInt()
|
||||
)
|
||||
background = ScreenDefs.colorPalette[14]
|
||||
}
|
||||
borderRight = JPanel().apply {
|
||||
preferredSize = Dimension(
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * borderWidth).toInt(),
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * ScreenDefs.SCREEN_HEIGHT).toInt()
|
||||
)
|
||||
background = ScreenDefs.colorPalette[14]
|
||||
}
|
||||
var c = GridBagConstraints()
|
||||
c.gridx = 0; c.gridy = 1; c.gridwidth = 3
|
||||
add(borderTop, c)
|
||||
c = GridBagConstraints()
|
||||
c.gridx = 0; c.gridy = 2
|
||||
add(borderLeft, c)
|
||||
c = GridBagConstraints()
|
||||
c.gridx = 2; c.gridy = 2
|
||||
add(borderRight, c)
|
||||
c = GridBagConstraints()
|
||||
c.gridx = 0; c.gridy = 3; c.gridwidth = 3
|
||||
add(borderBottom, c)
|
||||
// the screen canvas(bitmap)
|
||||
c = GridBagConstraints()
|
||||
c.gridx = 1; c.gridy = 2
|
||||
add(canvas, c)
|
||||
addKeyListener(this)
|
||||
pack()
|
||||
setLocationRelativeTo(null)
|
||||
setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, mutableSetOf())
|
||||
setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, mutableSetOf())
|
||||
requestFocusInWindow()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
// repaint the screen's back buffer ~60 times per second
|
||||
val repaintTimer = Timer(1000 / 60) {
|
||||
repaint()
|
||||
borderTop.background = ScreenDefs.colorPalette[ram[0xd020].toInt() and 15]
|
||||
borderBottom.background = ScreenDefs.colorPalette[ram[0xd020].toInt() and 15]
|
||||
borderLeft.background = ScreenDefs.colorPalette[ram[0xd020].toInt() and 15]
|
||||
borderRight.background = ScreenDefs.colorPalette[ram[0xd020].toInt() and 15]
|
||||
|
||||
if(keyboardBuffer.isNotEmpty()) {
|
||||
// inject keystrokes directly into the c64's keyboard buffer (translate to petscii first)
|
||||
var kbbLen = ram[0xc6]
|
||||
while(kbbLen<=10 && keyboardBuffer.isNotEmpty()) {
|
||||
try {
|
||||
val char = keyboardBuffer.pop()
|
||||
// print("CHAR: '$char' ${char.toShort()} -> ")
|
||||
val petscii = when(char) {
|
||||
'\n' -> 13 // enter
|
||||
'\b' -> 20 // backspace ('delete')
|
||||
else -> Petscii.encodePetscii(char.toString(), true)[0]
|
||||
}
|
||||
// println("$petscii")
|
||||
ram[0x277 + kbbLen] = petscii
|
||||
kbbLen++
|
||||
} catch(ccx: CharConversionException) {
|
||||
// ignore character
|
||||
// println("ignored")
|
||||
}
|
||||
}
|
||||
ram[0xc6] = kbbLen
|
||||
}
|
||||
}
|
||||
repaintTimer.initialDelay = 0
|
||||
repaintTimer.start()
|
||||
}
|
||||
|
||||
// keyboard events:
|
||||
override fun keyTyped(event: KeyEvent) {
|
||||
// println("KEY TYPED: $event '${event.keyChar}'")
|
||||
keyboardBuffer.add(event.keyChar)
|
||||
while (keyboardBuffer.size > 8)
|
||||
keyboardBuffer.pop()
|
||||
}
|
||||
|
||||
override fun keyPressed(event: KeyEvent) {}
|
||||
override fun keyReleased(event: KeyEvent) {}
|
||||
}
|
1121
src/main/kotlin/razorvine/c64emu/Petscii.kt
Normal file
1121
src/main/kotlin/razorvine/c64emu/Petscii.kt
Normal file
File diff suppressed because it is too large
Load Diff
61
src/main/kotlin/razorvine/c64emu/VicII.kt
Normal file
61
src/main/kotlin/razorvine/c64emu/VicII.kt
Normal file
@ -0,0 +1,61 @@
|
||||
package razorvine.c64emu
|
||||
|
||||
import razorvine.ksim65.components.Address
|
||||
import razorvine.ksim65.components.MemMappedComponent
|
||||
import razorvine.ksim65.components.UByte
|
||||
|
||||
class VicII(startAddress: Address, endAddress: Address): MemMappedComponent(startAddress, endAddress) {
|
||||
private var ramBuffer = Array<UByte>(endAddress - startAddress + 1) { 0 }
|
||||
private var rasterIrqLine = 0
|
||||
var currentRasterLine = 1
|
||||
private var totalClocks = 0L
|
||||
private var interruptStatusRegisterD019 = 0
|
||||
|
||||
override fun clock() {
|
||||
totalClocks++
|
||||
if(totalClocks % 63L == 0L) {
|
||||
currentRasterLine++
|
||||
if(currentRasterLine >= 312)
|
||||
currentRasterLine = 0
|
||||
interruptStatusRegisterD019 = if(currentRasterLine == rasterIrqLine) {
|
||||
// signal that current raster line is equal to the desired IRQ raster line
|
||||
interruptStatusRegisterD019 or 0b00000001
|
||||
} else
|
||||
interruptStatusRegisterD019 and 0b11111110
|
||||
}
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
rasterIrqLine = 0
|
||||
currentRasterLine = 1
|
||||
totalClocks = 0L
|
||||
interruptStatusRegisterD019 = 0
|
||||
}
|
||||
|
||||
override fun get(address: Address): UByte {
|
||||
val register = (address - startAddress) and 63
|
||||
// println("VIC GET ${register.toString(16)}")
|
||||
return when(register) {
|
||||
0x11 -> (0b00011011 or ((currentRasterLine and 0b100000000) ushr 1)).toShort()
|
||||
0x12 -> {
|
||||
// println(" read raster: $currentRasterLine")
|
||||
(currentRasterLine and 255).toShort()
|
||||
}
|
||||
0x19 -> interruptStatusRegisterD019.toShort()
|
||||
else -> ramBuffer[register]
|
||||
}
|
||||
}
|
||||
|
||||
override fun set(address: Address, data: UByte) {
|
||||
val register = (address - startAddress) and 63
|
||||
ramBuffer[register] = data
|
||||
when(register) {
|
||||
0x11 -> {
|
||||
val rasterHigh = (data.toInt() ushr 7) shl 8
|
||||
rasterIrqLine = (rasterIrqLine and 0x00ff) or rasterHigh
|
||||
}
|
||||
0x12 -> rasterIrqLine = (rasterIrqLine and 0xff00) or data.toInt()
|
||||
}
|
||||
// println("VIC SET ${register.toString(16)} = ${data.toString(16)} (rasterIrqLine= $rasterIrqLine)")
|
||||
}
|
||||
}
|
92
src/main/kotlin/razorvine/c64emu/c64Main.kt
Normal file
92
src/main/kotlin/razorvine/c64emu/c64Main.kt
Normal file
@ -0,0 +1,92 @@
|
||||
package razorvine.c64emu
|
||||
|
||||
import razorvine.examplemachine.DebugWindow
|
||||
import kotlin.concurrent.scheduleAtFixedRate
|
||||
import razorvine.ksim65.Bus
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.IVirtualMachine
|
||||
import razorvine.ksim65.Version
|
||||
import razorvine.ksim65.components.*
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
import javax.swing.ImageIcon
|
||||
|
||||
/**
|
||||
* The virtual representation of the Commodore-64
|
||||
*/
|
||||
class C64Machine(title: String) : IVirtualMachine {
|
||||
|
||||
private val romsPath = Paths.get(expandUser("~/.vice/C64"))
|
||||
val chargenData = romsPath.resolve("chargen").toFile().readBytes()
|
||||
val basicData = romsPath.resolve("basic").toFile().readBytes()
|
||||
val kernalData = romsPath.resolve("kernal").toFile().readBytes()
|
||||
|
||||
override val bus = Bus()
|
||||
override val cpu = Cpu6502(false)
|
||||
val ram = Ram(0x0000, 0xffff)
|
||||
val vic = VicII(0xd000, 0xd3ff)
|
||||
val basicRom = Rom(0xa000, 0xbfff).also { it.load(basicData) }
|
||||
val kernalRom = Rom(0xe000, 0xffff).also { it.load(kernalData) }
|
||||
|
||||
private val debugWindow = DebugWindow(this)
|
||||
private val hostDisplay = MainWindow(title, chargenData, ram)
|
||||
|
||||
init {
|
||||
hostDisplay.iconImage = ImageIcon(javaClass.getResource("/icon.png")).image
|
||||
debugWindow.iconImage = hostDisplay.iconImage
|
||||
debugWindow.setLocation(hostDisplay.location.x+hostDisplay.width, hostDisplay.location.y)
|
||||
|
||||
bus += basicRom
|
||||
bus += kernalRom
|
||||
bus += vic
|
||||
bus += ram
|
||||
bus += cpu
|
||||
bus.reset()
|
||||
|
||||
debugWindow.isVisible = true
|
||||
hostDisplay.isVisible = true
|
||||
hostDisplay.start()
|
||||
}
|
||||
|
||||
private fun expandUser(path: String): String {
|
||||
if(path.startsWith("~" + File.separator)) {
|
||||
return System.getProperty("user.home") + path.substring(1);
|
||||
} else {
|
||||
throw UnsupportedOperationException("home dir expansion not implemented for other users")
|
||||
}
|
||||
}
|
||||
|
||||
override var paused = false
|
||||
|
||||
override fun stepInstruction() {
|
||||
while (cpu.instrCycles > 0) bus.clock()
|
||||
bus.clock()
|
||||
while (cpu.instrCycles > 0) bus.clock()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
val timer = java.util.Timer("clock", true)
|
||||
val startTime = System.currentTimeMillis()
|
||||
timer.scheduleAtFixedRate(1, 1) {
|
||||
if(!paused) {
|
||||
repeat(400) {
|
||||
stepInstruction()
|
||||
if(vic.currentRasterLine == 255) {
|
||||
// we force an irq here ourselves rather than fully emulating the VIC-II's raster IRQ
|
||||
// or the CIA timer IRQ/NMI.
|
||||
cpu.irq()
|
||||
}
|
||||
}
|
||||
debugWindow.updateCpu(cpu, bus)
|
||||
val duration = System.currentTimeMillis() - startTime
|
||||
val speedKhz = cpu.totalCycles.toDouble() / duration
|
||||
debugWindow.speedKhzTf.text = "%.1f".format(speedKhz)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val machine = C64Machine("virtual Commodore-64 - using KSim65 v${Version.version}")
|
||||
machine.start()
|
||||
}
|
@ -7,6 +7,7 @@ import java.awt.image.BufferedImage
|
||||
import javax.imageio.ImageIO
|
||||
import javax.swing.event.MouseInputListener
|
||||
import razorvine.ksim65.IHostInterface
|
||||
import razorvine.ksim65.IVirtualMachine
|
||||
import java.awt.event.*
|
||||
import java.util.*
|
||||
import javax.swing.*
|
||||
@ -146,7 +147,7 @@ private class BitmapScreenPanel : JPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
class DebugWindow(val vm: VirtualMachine) : JFrame("debugger"), ActionListener {
|
||||
class DebugWindow(val vm: IVirtualMachine) : JFrame("debugger"), ActionListener {
|
||||
val cyclesTf = JTextField("00000000000000")
|
||||
val speedKhzTf = JTextField("0000000")
|
||||
val regAtf = JTextField("000")
|
||||
@ -160,7 +161,7 @@ class DebugWindow(val vm: VirtualMachine) : JFrame("debugger"), ActionListener {
|
||||
|
||||
init {
|
||||
defaultCloseOperation = EXIT_ON_CLOSE
|
||||
preferredSize = Dimension(350, 600)
|
||||
preferredSize = Dimension(350, 500)
|
||||
val cpuPanel = JPanel(GridBagLayout())
|
||||
cpuPanel.border = BorderFactory.createTitledBorder("CPU: ${vm.cpu.name}")
|
||||
val gc = GridBagConstraints()
|
||||
@ -323,12 +324,9 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener
|
||||
addMouseMotionListener(this)
|
||||
addMouseListener(this)
|
||||
pack()
|
||||
requestFocusInWindow()
|
||||
setLocationRelativeTo(null)
|
||||
setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, mutableSetOf())
|
||||
isVisible = true
|
||||
toFront()
|
||||
requestFocus()
|
||||
requestFocusInWindow()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
|
@ -33,8 +33,8 @@ class EhBasicMachine(title: String) {
|
||||
bus += cpu
|
||||
bus.reset()
|
||||
|
||||
hostDisplay.isVisible = true
|
||||
hostDisplay.start()
|
||||
hostDisplay.requestFocus()
|
||||
}
|
||||
|
||||
var paused = false
|
||||
|
@ -3,6 +3,7 @@ package razorvine.examplemachine
|
||||
import kotlin.concurrent.scheduleAtFixedRate
|
||||
import razorvine.ksim65.Bus
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.IVirtualMachine
|
||||
import razorvine.ksim65.Version
|
||||
import razorvine.ksim65.components.*
|
||||
import razorvine.ksim65.components.Timer
|
||||
@ -11,9 +12,9 @@ import javax.swing.ImageIcon
|
||||
/**
|
||||
* A virtual computer constructed from the various virtual components
|
||||
*/
|
||||
class VirtualMachine(title: String) {
|
||||
val bus = Bus()
|
||||
val cpu = Cpu6502(false)
|
||||
class VirtualMachine(title: String) : IVirtualMachine {
|
||||
override val bus = Bus()
|
||||
override val cpu = Cpu6502(false)
|
||||
val ram = Ram(0x0000, 0xffff)
|
||||
private val rtc = RealTimeClock(0xd100, 0xd108)
|
||||
private val timer = Timer(0xd200, 0xd203, cpu)
|
||||
@ -29,6 +30,7 @@ class VirtualMachine(title: String) {
|
||||
init {
|
||||
hostDisplay.iconImage = ImageIcon(javaClass.getResource("/icon.png")).image
|
||||
debugWindow.iconImage = hostDisplay.iconImage
|
||||
debugWindow.setLocation(hostDisplay.location.x+hostDisplay.width, hostDisplay.location.y)
|
||||
|
||||
ram[Cpu6502.RESET_vector] = 0x00
|
||||
ram[Cpu6502.RESET_vector + 1] = 0x10
|
||||
@ -43,16 +45,14 @@ class VirtualMachine(title: String) {
|
||||
bus += cpu
|
||||
bus.reset()
|
||||
|
||||
hostDisplay.start()
|
||||
|
||||
debugWindow.setLocation(hostDisplay.location.x+hostDisplay.width, hostDisplay.location.y)
|
||||
debugWindow.isVisible = true
|
||||
hostDisplay.requestFocus()
|
||||
hostDisplay.isVisible = true
|
||||
hostDisplay.start()
|
||||
}
|
||||
|
||||
var paused = false
|
||||
override var paused = false
|
||||
|
||||
fun stepInstruction() {
|
||||
override fun stepInstruction() {
|
||||
while (cpu.instrCycles > 0) bus.clock()
|
||||
bus.clock()
|
||||
while (cpu.instrCycles > 0) bus.clock()
|
||||
|
9
src/main/kotlin/razorvine/ksim65/IVirtualMachine.kt
Normal file
9
src/main/kotlin/razorvine/ksim65/IVirtualMachine.kt
Normal file
@ -0,0 +1,9 @@
|
||||
package razorvine.ksim65
|
||||
|
||||
interface IVirtualMachine {
|
||||
fun stepInstruction()
|
||||
|
||||
var paused: Boolean
|
||||
val cpu: Cpu6502
|
||||
val bus: Bus
|
||||
}
|
Loading…
Reference in New Issue
Block a user