mirror of
https://github.com/irmen/ksim65.git
synced 2024-12-12 16:29:07 +00:00
virtual machine
This commit is contained in:
parent
789b1f69a6
commit
d0dfb24172
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,4 +5,6 @@
|
||||
build/
|
||||
|
||||
.idea/workspace.xml
|
||||
.idea/dictionaries/
|
||||
.idea/inspectionProfiles/
|
||||
.attach_pid*
|
||||
|
2
.idea/codeStyles/Project.xml
generated
2
.idea/codeStyles/Project.xml
generated
@ -4,7 +4,7 @@
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<MarkdownNavigatorCodeStyleSettings>
|
||||
<option name="RIGHT_MARGIN" value="72" />
|
||||
<option name="RIGHT_MARGIN" value="80" />
|
||||
</MarkdownNavigatorCodeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
|
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
|
360
src/main/kotlin/razorvine/examplemachine/GUI.kt
Normal file
360
src/main/kotlin/razorvine/examplemachine/GUI.kt
Normal file
@ -0,0 +1,360 @@
|
||||
package razorvine.examplemachine
|
||||
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import java.awt.*
|
||||
import java.awt.image.BufferedImage
|
||||
import java.util.ArrayDeque
|
||||
import javax.imageio.ImageIO
|
||||
import javax.swing.event.MouseInputListener
|
||||
import razorvine.ksim65.IHostInterface
|
||||
import java.awt.event.*
|
||||
import javax.swing.*
|
||||
|
||||
|
||||
/**
|
||||
* Define a monochrome screen that can display 640x480 pixels
|
||||
* and/or 80x30 characters (these are 8x16 pixels).
|
||||
*/
|
||||
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 DISPLAY_PIXEL_SCALING: Double = 1.5
|
||||
val BG_COLOR = Color(0, 10, 20)
|
||||
val FG_COLOR = Color(200, 255, 230)
|
||||
val BORDER_COLOR = Color(20, 30, 40)
|
||||
val Characters = loadCharacters()
|
||||
|
||||
private fun loadCharacters(): Array<BufferedImage> {
|
||||
val img = ImageIO.read(javaClass.getResource("/charset/unscii8x16.png"))
|
||||
val charactersImage = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
|
||||
charactersImage.createGraphics().drawImage(img, 0, 0, null)
|
||||
|
||||
val black = Color(0, 0, 0).rgb
|
||||
val foreground = FG_COLOR.rgb
|
||||
val nopixel = Color(0, 0, 0, 0).rgb
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return charImages.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
private class BitmapScreenPanel : JPanel() {
|
||||
|
||||
private val image = BufferedImage(ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT, BufferedImage.TYPE_INT_ARGB)
|
||||
private val g2d = image.graphics as Graphics2D
|
||||
private var cursorX: Int = 0
|
||||
private var cursorY: Int = 0
|
||||
|
||||
init {
|
||||
val size = Dimension(
|
||||
(image.width * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
|
||||
(image.height * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt()
|
||||
)
|
||||
minimumSize = size
|
||||
maximumSize = size
|
||||
preferredSize = size
|
||||
clearScreen()
|
||||
isFocusable = true
|
||||
requestFocusInWindow()
|
||||
}
|
||||
|
||||
override fun paint(graphics: Graphics?) {
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
fun clearScreen() {
|
||||
g2d.background = ScreenDefs.BG_COLOR
|
||||
g2d.clearRect(0, 0, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT)
|
||||
cursorX = 0
|
||||
cursorY = 0
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
val coloredImage = ScreenDefs.Characters[character.toInt()]
|
||||
g2d.drawImage(coloredImage, 8 * x, 16 * y, null)
|
||||
}
|
||||
|
||||
fun scrollUp() {
|
||||
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)
|
||||
}
|
||||
|
||||
fun mousePixelPosition(): Point? {
|
||||
val pos = mousePosition ?: return null
|
||||
return Point(
|
||||
(pos.x / ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
|
||||
(pos.y / ScreenDefs.DISPLAY_PIXEL_SCALING).toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class DebugWindow(val vm: VirtualMachine) : JFrame("debugger"), ActionListener {
|
||||
val cyclesTf = JTextField("00000000000000")
|
||||
val regAtf = JTextField("000")
|
||||
val regXtf = JTextField("000")
|
||||
val regYtf = JTextField("000")
|
||||
val regPCtf = JTextField("00000")
|
||||
val regSPtf = JTextField("000")
|
||||
val regPtf = JTextField("000000000")
|
||||
val opcodeTf = JTextField("000")
|
||||
val mnemonicTf = JTextField("brk ")
|
||||
private val pauseBt = JButton("Pause").also { it.actionCommand = "pause" }
|
||||
|
||||
init {
|
||||
isFocusable = true
|
||||
val cpuPanel = JPanel(GridBagLayout())
|
||||
cpuPanel.border = BorderFactory.createTitledBorder("CPU: ${vm.cpu.name}")
|
||||
val gc = GridBagConstraints()
|
||||
gc.insets = Insets(4, 4, 4, 4)
|
||||
gc.anchor = GridBagConstraints.EAST
|
||||
gc.gridx = 0
|
||||
gc.gridy = 0
|
||||
val cyclesLb = JLabel("cycles")
|
||||
val regAlb = JLabel("A")
|
||||
val regXlb = JLabel("X")
|
||||
val regYlb = JLabel("Y")
|
||||
val regSPlb = JLabel("SP")
|
||||
val regPClb = JLabel("PC")
|
||||
val dummyLb = JLabel("")
|
||||
val regPlb = JLabel("Status")
|
||||
val opcodeLb = JLabel("opcode")
|
||||
val mnemonicLb = JLabel("mnemonic")
|
||||
listOf(cyclesLb, regAlb, regXlb, regYlb, regSPlb, regPClb, dummyLb, regPlb, opcodeLb, mnemonicLb).forEach {
|
||||
cpuPanel.add(it, gc)
|
||||
gc.gridy++
|
||||
}
|
||||
gc.anchor = GridBagConstraints.WEST
|
||||
gc.gridx = 1
|
||||
gc.gridy = 0
|
||||
val bitsLb = JTextField("NV-BDIZC")
|
||||
listOf(cyclesTf, regAtf, regXtf, regYtf, regSPtf, regPCtf, bitsLb, regPtf, opcodeTf, mnemonicTf).forEach {
|
||||
it.font = Font(Font.MONOSPACED, Font.PLAIN, 16)
|
||||
it.isEditable = false
|
||||
it.columns = it.text.length
|
||||
cpuPanel.add(it, gc)
|
||||
gc.gridy++
|
||||
}
|
||||
add(cpuPanel, BorderLayout.NORTH)
|
||||
|
||||
val buttonPanel = JPanel(FlowLayout())
|
||||
buttonPanel.border = BorderFactory.createTitledBorder("Control")
|
||||
|
||||
val resetBt = JButton("Reset").also { it.actionCommand = "reset" }
|
||||
val cycleBt = JButton("Cycle").also { it.actionCommand = "cycle" }
|
||||
listOf(resetBt, cycleBt, pauseBt).forEach {
|
||||
it.addActionListener(this)
|
||||
buttonPanel.add(it)
|
||||
}
|
||||
|
||||
add(buttonPanel, BorderLayout.CENTER)
|
||||
|
||||
pack()
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
when {
|
||||
e.actionCommand == "reset" -> {
|
||||
vm.bus.reset()
|
||||
updateCpu(vm.cpu)
|
||||
}
|
||||
e.actionCommand == "cycle" -> {
|
||||
vm.bus.clock()
|
||||
updateCpu(vm.cpu)
|
||||
}
|
||||
e.actionCommand == "pause" -> {
|
||||
vm.paused = true
|
||||
pauseBt.actionCommand = "continue"
|
||||
pauseBt.text = "Cont."
|
||||
}
|
||||
e.actionCommand == "continue" -> {
|
||||
vm.paused = false
|
||||
pauseBt.actionCommand = "pause"
|
||||
pauseBt.text = "Pause"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCpu(cpu: Cpu6502) {
|
||||
cyclesTf.text = cpu.totalCycles.toString()
|
||||
regAtf.text = cpu.hexB(cpu.regA)
|
||||
regXtf.text = cpu.hexB(cpu.regX)
|
||||
regYtf.text = cpu.hexB(cpu.regY)
|
||||
regPtf.text = cpu.regP.asByte().toString(2).padStart(8, '0')
|
||||
regPCtf.text = cpu.hexW(cpu.regPC)
|
||||
regSPtf.text = cpu.hexB(cpu.regSP)
|
||||
opcodeTf.text = cpu.hexB(cpu.currentOpcode)
|
||||
mnemonicTf.text = cpu.currentMnemonic
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener, IHostInterface {
|
||||
private val canvas = BitmapScreenPanel()
|
||||
val keyboardBuffer = ArrayDeque<Char>()
|
||||
var mousePos = Point(0, 0)
|
||||
var leftButton = false
|
||||
var rightButton = false
|
||||
var middleButton = false
|
||||
|
||||
init {
|
||||
val borderWidth = 16
|
||||
layout = GridBagLayout()
|
||||
defaultCloseOperation = EXIT_ON_CLOSE
|
||||
isResizable = false
|
||||
isFocusable = true
|
||||
|
||||
// the borders (top, left, right, bottom)
|
||||
val borderTop = JPanel().apply {
|
||||
preferredSize = Dimension(
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * (ScreenDefs.SCREEN_WIDTH + 2 * borderWidth)).toInt(),
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * borderWidth).toInt()
|
||||
)
|
||||
background = ScreenDefs.BORDER_COLOR
|
||||
}
|
||||
val borderBottom = JPanel().apply {
|
||||
preferredSize = Dimension(
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * (ScreenDefs.SCREEN_WIDTH + 2 * borderWidth)).toInt(),
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * borderWidth).toInt()
|
||||
)
|
||||
background = ScreenDefs.BORDER_COLOR
|
||||
}
|
||||
val borderLeft = JPanel().apply {
|
||||
preferredSize = Dimension(
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * borderWidth).toInt(),
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * ScreenDefs.SCREEN_HEIGHT).toInt()
|
||||
)
|
||||
background = ScreenDefs.BORDER_COLOR
|
||||
}
|
||||
val borderRight = JPanel().apply {
|
||||
preferredSize = Dimension(
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * borderWidth).toInt(),
|
||||
(ScreenDefs.DISPLAY_PIXEL_SCALING * ScreenDefs.SCREEN_HEIGHT).toInt()
|
||||
)
|
||||
background = ScreenDefs.BORDER_COLOR
|
||||
}
|
||||
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)
|
||||
addMouseMotionListener(this)
|
||||
addMouseListener(this)
|
||||
pack()
|
||||
requestFocusInWindow()
|
||||
setLocationRelativeTo(null)
|
||||
isVisible = true
|
||||
}
|
||||
|
||||
fun start() {
|
||||
// repaint the screen's back buffer ~60 times per second
|
||||
val repaintTimer = Timer(1000 / 60) { repaint() }
|
||||
repaintTimer.initialDelay = 0
|
||||
repaintTimer.start()
|
||||
}
|
||||
|
||||
// keyboard events:
|
||||
override fun keyTyped(p0: KeyEvent) {
|
||||
println(p0)
|
||||
println("[${p0.keyChar}]")
|
||||
keyboardBuffer.add(p0.keyChar)
|
||||
while (keyboardBuffer.size > 8)
|
||||
keyboardBuffer.pop()
|
||||
}
|
||||
|
||||
override fun keyPressed(p0: KeyEvent) {}
|
||||
override fun keyReleased(p0: KeyEvent) {}
|
||||
|
||||
// mouse events:
|
||||
override fun mousePressed(p0: MouseEvent) {}
|
||||
|
||||
override fun mouseReleased(p0: MouseEvent) {}
|
||||
override fun mouseEntered(p0: MouseEvent) {}
|
||||
override fun mouseExited(p0: MouseEvent) {}
|
||||
override fun mouseDragged(p0: MouseEvent) {}
|
||||
override fun mouseClicked(p0: MouseEvent) {
|
||||
val pos = canvas.mousePixelPosition()
|
||||
if (pos == null)
|
||||
return
|
||||
else {
|
||||
mousePos = pos
|
||||
leftButton = SwingUtilities.isLeftMouseButton(p0)
|
||||
rightButton = SwingUtilities.isRightMouseButton(p0)
|
||||
middleButton = SwingUtilities.isMiddleMouseButton(p0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseMoved(p0: MouseEvent) {
|
||||
val pos = canvas.mousePixelPosition()
|
||||
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)
|
||||
override fun setChar(x: Int, y: Int, character: Char) = canvas.setChar(x, y, character)
|
||||
override fun scrollUp() = canvas.scrollUp()
|
||||
override fun mouse(): IHostInterface.MouseInfo {
|
||||
return IHostInterface.MouseInfo(mousePos.x, mousePos.y, leftButton, rightButton, middleButton)
|
||||
}
|
||||
|
||||
override fun keyboard(): Char? {
|
||||
return if (keyboardBuffer.isEmpty())
|
||||
null
|
||||
else
|
||||
keyboardBuffer.pop()
|
||||
}
|
||||
}
|
84
src/main/kotlin/razorvine/examplemachine/systemMain.kt
Normal file
84
src/main/kotlin/razorvine/examplemachine/systemMain.kt
Normal file
@ -0,0 +1,84 @@
|
||||
package razorvine.examplemachine
|
||||
|
||||
import kotlin.concurrent.scheduleAtFixedRate
|
||||
import java.time.LocalDateTime
|
||||
import razorvine.ksim65.Bus
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.Version
|
||||
import razorvine.ksim65.components.*
|
||||
import razorvine.ksim65.components.Timer
|
||||
|
||||
/**
|
||||
* A virtual computer constructed from the various virtual components
|
||||
*/
|
||||
class VirtualMachine(title: String) {
|
||||
val bus = Bus()
|
||||
val cpu = Cpu6502(false)
|
||||
val ram = Ram(0x0000, 0xffff)
|
||||
private val rtc = RealTimeClock(0xd100, 0xd108)
|
||||
private val timer = Timer(0xd200, 0xd203, cpu)
|
||||
|
||||
private val hostDisplay = MainWindow(title)
|
||||
private val debugWindow = DebugWindow(this)
|
||||
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, 0xd304, hostDisplay)
|
||||
private val keyboard = Keyboard(0xd400, 0xd400, hostDisplay)
|
||||
|
||||
init {
|
||||
ram[Cpu6502.RESET_vector] = 0x00
|
||||
ram[Cpu6502.RESET_vector + 1] = 0x10
|
||||
|
||||
bus += rtc
|
||||
bus += timer
|
||||
bus += display
|
||||
bus += mouse
|
||||
bus += keyboard
|
||||
bus += ram
|
||||
bus += cpu
|
||||
bus.reset()
|
||||
|
||||
hostDisplay.start()
|
||||
|
||||
debugWindow.setLocation(hostDisplay.location.x+hostDisplay.width, hostDisplay.location.y)
|
||||
debugWindow.isVisible = true
|
||||
}
|
||||
|
||||
var paused = false
|
||||
|
||||
fun clock() {
|
||||
if(!paused) {
|
||||
bus.clock()
|
||||
debugWindow.updateCpu(cpu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val machine = VirtualMachine("KSim65 demo virtual machine - using ksim65 v${Version.version}")
|
||||
val v = 0xd000
|
||||
|
||||
machine.bus[v + 0x08] = 20
|
||||
machine.bus[v + 0x09] = 2
|
||||
val text = ">> Hello this is an example text! 1234567890 <<\n" +
|
||||
"next line 1\n" +
|
||||
"next line 2\n" +
|
||||
"next line 3\rnext line 4\rnext line 5\n" +
|
||||
"a mistakk\be\n\n\n\n\n\n\n\n\n\n"
|
||||
text.forEach {
|
||||
machine.bus[v + 0x0a] = it.toShort()
|
||||
}
|
||||
|
||||
repeat(20) {
|
||||
Thread.sleep(100)
|
||||
"time: ${LocalDateTime.now()}\n".forEach { c ->
|
||||
machine.bus[v + 0x0a] = c.toShort()
|
||||
}
|
||||
}
|
||||
|
||||
val timer = java.util.Timer("clock", true)
|
||||
timer.scheduleAtFixedRate(1, 1) {
|
||||
machine.clock()
|
||||
}
|
||||
}
|
@ -27,6 +27,12 @@ class Bus {
|
||||
memComponents.forEach { it.clock() }
|
||||
}
|
||||
|
||||
operator fun plusAssign(memcomponent: MemMappedComponent) = add(memcomponent)
|
||||
operator fun plusAssign(component: BusComponent) = add(component)
|
||||
operator fun get(address: Address): UByte = read(address)
|
||||
operator fun set(address: Address, data: UByte) = write(address, data)
|
||||
|
||||
|
||||
fun add(component: BusComponent) {
|
||||
components.add(component)
|
||||
component.bus = this
|
||||
@ -54,6 +60,7 @@ class Bus {
|
||||
* Any memory mapped component that listens to the address, will receive the data.
|
||||
*/
|
||||
fun write(address: Address, data: UByte) {
|
||||
require(data in 0..255) { "data must be a byte 0..255" }
|
||||
memComponents.forEach {
|
||||
if (address >= it.startAddress && address <= it.endAddress)
|
||||
it[address] = data
|
||||
|
@ -11,6 +11,7 @@ import razorvine.ksim65.components.UByte
|
||||
* TODO: add the optional additional cycles to certain instructions and addressing modes
|
||||
*/
|
||||
open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
open val name = "6502"
|
||||
var tracing: ((state:String) -> Unit)? = null
|
||||
var totalCycles: Long = 0
|
||||
protected set
|
||||
@ -102,6 +103,8 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
protected set
|
||||
|
||||
protected lateinit var currentInstruction: Instruction
|
||||
val currentMnemonic: String
|
||||
get() = currentInstruction.mnemonic
|
||||
|
||||
// has an interrupt been requested?
|
||||
protected var pendingInterrupt: Pair<Boolean, BusComponent>? = null
|
||||
@ -249,6 +252,9 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cpu
|
||||
*/
|
||||
override fun reset() {
|
||||
regSP = 0xfd
|
||||
regPC = readWord(RESET_vector)
|
||||
@ -267,6 +273,9 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
currentInstruction = instructions[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Process once clock cycle in the cpu
|
||||
*/
|
||||
override fun clock() {
|
||||
if (instrCycles == 0) {
|
||||
if (pendingInterrupt != null) {
|
||||
@ -309,11 +318,13 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
totalCycles++
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute one single complete instruction
|
||||
*/
|
||||
open fun step() {
|
||||
// step a whole instruction
|
||||
while (instrCycles > 0) clock() // remaining instruction subcycles from the previous instruction
|
||||
clock() // the actual instruction execution cycle
|
||||
while (instrCycles > 0) clock() // instruction subcycles
|
||||
while (instrCycles > 0) clock()
|
||||
clock()
|
||||
while (instrCycles > 0) clock()
|
||||
}
|
||||
|
||||
fun nmi(source: BusComponent) {
|
||||
|
@ -7,6 +7,7 @@ import razorvine.ksim65.components.Address
|
||||
* TODO: add the optional additional cycles to certain instructions and addressing modes
|
||||
*/
|
||||
class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||
override val name = "65C02"
|
||||
|
||||
enum class Wait {
|
||||
Normal,
|
||||
@ -23,6 +24,9 @@ class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||
const val resetCycles = Cpu6502.resetCycles
|
||||
}
|
||||
|
||||
/**
|
||||
* Process once clock cycle in the cpu
|
||||
*/
|
||||
override fun clock() {
|
||||
when (waiting) {
|
||||
Wait.Normal -> super.clock()
|
||||
@ -42,13 +46,15 @@ class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute one single complete instruction
|
||||
*/
|
||||
override fun step() {
|
||||
// step a whole instruction
|
||||
if (waiting == Wait.Normal) {
|
||||
while (instrCycles > 0) clock() // remaining instruction subcycles from the previous instruction
|
||||
clock() // the actual instruction execution cycle
|
||||
while (instrCycles > 0) clock()
|
||||
clock()
|
||||
if (waiting == Wait.Normal)
|
||||
while (instrCycles > 0) clock() // instruction subcycles
|
||||
while (instrCycles > 0) clock()
|
||||
else {
|
||||
totalCycles += instrCycles
|
||||
instrCycles = 0
|
||||
|
18
src/main/kotlin/razorvine/ksim65/IHostInterface.kt
Normal file
18
src/main/kotlin/razorvine/ksim65/IHostInterface.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package razorvine.ksim65
|
||||
|
||||
/**
|
||||
* The virtual machine uses this to interface with the host system,
|
||||
* to connect to the "real" devices.
|
||||
*/
|
||||
interface IHostInterface {
|
||||
fun clearScreen()
|
||||
fun getPixel(x: Int, y: Int): Boolean
|
||||
fun setPixel(x: Int, y: Int)
|
||||
fun clearPixel(x: Int, y: Int)
|
||||
fun setChar(x: Int, y: Int, character: Char)
|
||||
fun scrollUp()
|
||||
fun mouse(): MouseInfo
|
||||
fun keyboard(): Char?
|
||||
|
||||
class MouseInfo(val x: Int, val y: Int, val left: Boolean, val right: Boolean, val middle: Boolean)
|
||||
}
|
@ -12,7 +12,14 @@ typealias Address = Int
|
||||
abstract class BusComponent {
|
||||
lateinit var bus: Bus
|
||||
|
||||
/**
|
||||
* One clock cycle on the bus
|
||||
*/
|
||||
abstract fun clock()
|
||||
|
||||
/**
|
||||
* Reset all devices on the bus
|
||||
*/
|
||||
abstract fun reset()
|
||||
}
|
||||
|
||||
@ -53,4 +60,8 @@ abstract class MemMappedComponent(val startAddress: Address, val endAddress: Add
|
||||
abstract class MemoryComponent(startAddress: Address, endAddress: Address) :
|
||||
MemMappedComponent(startAddress, endAddress) {
|
||||
abstract fun copyOfMem(): Array<UByte>
|
||||
|
||||
init {
|
||||
require(startAddress and 0xff == 0 && endAddress and 0xff == 0xff) {"address range must span complete page(s)"}
|
||||
}
|
||||
}
|
||||
|
167
src/main/kotlin/razorvine/ksim65/components/Display.kt
Normal file
167
src/main/kotlin/razorvine/ksim65/components/Display.kt
Normal file
@ -0,0 +1,167 @@
|
||||
package razorvine.ksim65.components
|
||||
|
||||
import razorvine.ksim65.IHostInterface
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Text mode and graphics (bitmap) mode display.
|
||||
* Note that the character matrix and pixel matrix are NOT memory mapped,
|
||||
* this display device is controlled by sending char/pixel commands to it.
|
||||
*
|
||||
* Requires a host display to actually view the stuff, obviously.
|
||||
*
|
||||
* reg. value
|
||||
* ---- -----
|
||||
* 00 char X position
|
||||
* 01 char Y position
|
||||
* 02 r/w character at cX,cY (doesn't change cursor position)
|
||||
* 03 pixel X pos (lsb)
|
||||
* 04 pixel X pos (msb)
|
||||
* 05 pixel Y pos (lsb)
|
||||
* 06 pixel Y pos (msb)
|
||||
* 07 read or write pixel value at pX, pY
|
||||
* 08 cursor X position (r/w)
|
||||
* 09 cursor Y position (r/w)
|
||||
* 0a read or write character at cursor pos, updates cursor position, scrolls up if necessary
|
||||
* control characters: 0x08=backspace, 0x09=tab, 0x0a=newline, 0x0c=formfeed(clear screen), 0x0d=carriagereturn
|
||||
*
|
||||
* TODO: cursor blinking, blink speed (0=off)
|
||||
*/
|
||||
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" }
|
||||
}
|
||||
|
||||
private var cursorX = 0
|
||||
private var cursorY = 0
|
||||
private var charposX = 0
|
||||
private var charposY = 0
|
||||
private var pixelX = 0
|
||||
private var pixelY = 0
|
||||
private val charMatrix = Array<ShortArray>(charHeight) { ShortArray(charWidth) } // matrix[y][x] to access
|
||||
|
||||
override fun clock() {
|
||||
// if the system clock is synced to the display refresh,
|
||||
// you *could* add a Vertical Blank interrupt here.
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
charMatrix.forEach { it.fill(' '.toShort()) }
|
||||
cursorX = 0
|
||||
cursorY = 0
|
||||
charposX = 0
|
||||
charposY = 0
|
||||
pixelX = 0
|
||||
pixelY = 0
|
||||
host.clearScreen()
|
||||
}
|
||||
|
||||
override operator fun get(address: Address): UByte {
|
||||
return when(address-startAddress) {
|
||||
0x02 -> {
|
||||
if(charposY in 0 until charHeight && charposX in 0 until charWidth) {
|
||||
charMatrix[charposY][charposX]
|
||||
} else 0xff
|
||||
}
|
||||
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) {
|
||||
charMatrix[cursorY][cursorX]
|
||||
} else 0xff
|
||||
}
|
||||
else -> return 0xff
|
||||
}
|
||||
}
|
||||
|
||||
override operator fun set(address: Address, data: UByte) {
|
||||
when(address-startAddress) {
|
||||
0x00 -> charposX = data.toInt()
|
||||
0x01 -> charposY = data.toInt()
|
||||
0x02 -> {
|
||||
if(charposY in 0 until charHeight && charposX in 0 until charWidth) {
|
||||
charMatrix[charposY][charposX] = data
|
||||
host.setChar(charposX, charposY, data.toChar())
|
||||
}
|
||||
}
|
||||
0x03 -> pixelX = (pixelX and 0xff00) or data.toInt()
|
||||
0x04 -> pixelX = (pixelX and 0x00ff) or (data.toInt() shl 8)
|
||||
0x05 -> pixelY = (pixelY and 0xff00) or data.toInt()
|
||||
0x06 -> pixelY = (pixelY and 0x00ff) or (data.toInt() shl 8)
|
||||
0x07 -> {
|
||||
if(data==0.toShort()) host.clearPixel(pixelX, pixelY)
|
||||
else host.setPixel(pixelX, pixelY)
|
||||
}
|
||||
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()) {
|
||||
0x08 -> {
|
||||
// backspace
|
||||
cursorX--
|
||||
if(cursorX<0) {
|
||||
if(cursorY>0) {
|
||||
cursorY--
|
||||
cursorX = charWidth - 1
|
||||
}
|
||||
}
|
||||
charMatrix[cursorY][cursorX] = ' '.toShort()
|
||||
host.setChar(cursorX, cursorY, ' ')
|
||||
}
|
||||
0x09 -> {
|
||||
// tab
|
||||
cursorX = (cursorX and 248) + 8
|
||||
if(cursorX >= charWidth) {
|
||||
cursorX = 0
|
||||
cursorDown()
|
||||
}
|
||||
}
|
||||
0x0a -> {
|
||||
// newline
|
||||
cursorX = 0
|
||||
cursorDown()
|
||||
}
|
||||
0x0c -> reset() // clear screen
|
||||
0x0d -> cursorX = 0 // carriage return
|
||||
else -> {
|
||||
// set character on screen
|
||||
charMatrix[cursorY][cursorX] = data
|
||||
host.setChar(cursorX, cursorY, data.toChar())
|
||||
cursorX++
|
||||
if(cursorX >= charWidth) {
|
||||
cursorX = 0
|
||||
cursorDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cursorDown() {
|
||||
cursorY++
|
||||
while(cursorY >= charHeight) {
|
||||
// scroll up 1 line
|
||||
for(y in 0 .. charHeight-2) {
|
||||
charMatrix[y+1].copyInto(charMatrix[y])
|
||||
}
|
||||
for(x in 0 until charWidth) {
|
||||
charMatrix[charHeight-1][x] = ' '.toShort()
|
||||
}
|
||||
cursorY--
|
||||
host.scrollUp()
|
||||
}
|
||||
}
|
||||
}
|
34
src/main/kotlin/razorvine/ksim65/components/Keyboard.kt
Normal file
34
src/main/kotlin/razorvine/ksim65/components/Keyboard.kt
Normal file
@ -0,0 +1,34 @@
|
||||
package razorvine.ksim65.components
|
||||
|
||||
import razorvine.ksim65.IHostInterface
|
||||
|
||||
/**
|
||||
* Simple keyboard for text entry.
|
||||
* The keyboard device itself takes care of decoding the keys,
|
||||
* this device simply produces the actual keys pressed.
|
||||
* There's NO support right now to detect keydown/keyup events or the
|
||||
* state of the shift/control/function keys.
|
||||
*
|
||||
* reg. value
|
||||
* ---- ---------
|
||||
* 00 character from keyboard, 0 = no character/key pressed
|
||||
*/
|
||||
class Keyboard(startAddress: Address, endAddress: Address, private val host: IHostInterface) :
|
||||
MemMappedComponent(startAddress, endAddress) {
|
||||
|
||||
init {
|
||||
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) {
|
||||
0x00 -> host.keyboard()?.toShort() ?: 0
|
||||
else -> 0xff
|
||||
}
|
||||
}
|
||||
|
||||
override operator fun set(address: Address, data: UByte) { /* read-only device */ }
|
||||
}
|
44
src/main/kotlin/razorvine/ksim65/components/Mouse.kt
Normal file
44
src/main/kotlin/razorvine/ksim65/components/Mouse.kt
Normal file
@ -0,0 +1,44 @@
|
||||
package razorvine.ksim65.components
|
||||
|
||||
import razorvine.ksim65.IHostInterface
|
||||
|
||||
/**
|
||||
* An analog mouse or paddle input device, with 2 buttons.
|
||||
*
|
||||
* reg. value
|
||||
* ---- ---------
|
||||
* 00 mouse pixel pos X (lsb)
|
||||
* 01 mouse pixel pos X (msb)
|
||||
* 02 mouse pixel pos Y (lsb)
|
||||
* 03 mouse pixel pos Y (msb)
|
||||
* 04 buttons, bit 0 = left button, bit 1 = right button, bit 2 = middle button
|
||||
*/
|
||||
class Mouse(startAddress: Address, endAddress: Address, private val host: IHostInterface) :
|
||||
MemMappedComponent(startAddress, endAddress) {
|
||||
|
||||
init {
|
||||
require(endAddress - startAddress + 1 == 5) { "mouse needs exactly 5 memory bytes" }
|
||||
}
|
||||
|
||||
override fun clock() {}
|
||||
override fun reset() {}
|
||||
|
||||
override operator fun get(address: Address): UByte {
|
||||
val mouse = host.mouse()
|
||||
return when (address - startAddress) {
|
||||
0x00 -> (mouse.x and 0xff).toShort()
|
||||
0x01 -> (mouse.x ushr 8).toShort()
|
||||
0x02 -> (mouse.y and 0xff).toShort()
|
||||
0x03 -> (mouse.y ushr 8).toShort()
|
||||
0x04 -> {
|
||||
val b1 = if (mouse.left) 0b00000001 else 0
|
||||
val b2 = if (mouse.right) 0b00000010 else 0
|
||||
val b3 = if (mouse.middle) 0b00000100 else 0
|
||||
return (b1 or b2 or b3).toShort()
|
||||
}
|
||||
else -> 0xff
|
||||
}
|
||||
}
|
||||
|
||||
override operator fun set(address: Address, data: UByte) { /* read-only device */ }
|
||||
}
|
@ -3,7 +3,7 @@ package razorvine.ksim65.components
|
||||
/**
|
||||
* A simple parallel output device (basically, prints bytes as characters to the console)
|
||||
*
|
||||
* byte value
|
||||
* reg. value
|
||||
* ---- ---------
|
||||
* 00 data (the 8 parallel bits)
|
||||
* 01 control latch (set bit 0 to write the data byte)
|
||||
@ -22,7 +22,7 @@ class ParallelPort(startAddress: Address, endAddress: Address) : MemMappedCompon
|
||||
return if (address == startAddress)
|
||||
dataByte
|
||||
else
|
||||
0
|
||||
0xff
|
||||
}
|
||||
|
||||
override operator fun set(address: Address, data: UByte) {
|
||||
|
@ -8,7 +8,7 @@ import java.time.LocalTime
|
||||
* A real-time time of day clock.
|
||||
* (System timers are elsewhere)
|
||||
*
|
||||
* byte value
|
||||
* reg. value
|
||||
* ---- ----------
|
||||
* 00 year (lsb)
|
||||
* 01 year (msb)
|
||||
@ -36,28 +36,28 @@ class RealTimeClock(startAddress: Address, endAddress: Address) : MemMappedCompo
|
||||
|
||||
override operator fun get(address: Address): UByte {
|
||||
return when (address - startAddress) {
|
||||
0 -> {
|
||||
0x00 -> {
|
||||
val year = LocalDate.now().year
|
||||
(year and 255).toShort()
|
||||
}
|
||||
1 -> {
|
||||
0x01 -> {
|
||||
val year = LocalDate.now().year
|
||||
(year ushr 8).toShort()
|
||||
}
|
||||
2 -> LocalDate.now().monthValue.toShort()
|
||||
3 -> LocalDate.now().dayOfMonth.toShort()
|
||||
4 -> LocalTime.now().hour.toShort()
|
||||
5 -> LocalTime.now().minute.toShort()
|
||||
6 -> LocalTime.now().second.toShort()
|
||||
7 -> {
|
||||
0x02 -> LocalDate.now().monthValue.toShort()
|
||||
0x03 -> LocalDate.now().dayOfMonth.toShort()
|
||||
0x04 -> LocalTime.now().hour.toShort()
|
||||
0x05 -> LocalTime.now().minute.toShort()
|
||||
0x06 -> LocalTime.now().second.toShort()
|
||||
0x07 -> {
|
||||
val ms = LocalTime.now().nano / 1000
|
||||
(ms and 255).toShort()
|
||||
}
|
||||
8 -> {
|
||||
0x08 -> {
|
||||
val ms = LocalTime.now().nano / 1000
|
||||
(ms ushr 8).toShort()
|
||||
}
|
||||
else -> 0
|
||||
else -> 0xff
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,15 +3,14 @@ package razorvine.ksim65.components
|
||||
import razorvine.ksim65.Cpu6502
|
||||
|
||||
/**
|
||||
* A programmable timer. Causes an IRQ or NMI at specified 24-bits intervals.
|
||||
* A programmable timer. Causes an IRQ or NMI at specified 24-bits clock cycle intervals.
|
||||
*
|
||||
* byte value
|
||||
* reg. value
|
||||
* ---- --------------
|
||||
* 00 control register bit 0=enable bit 1=nmi (instead of irq)
|
||||
* 01 24 bits interval value, bits 0-7 (lo)
|
||||
* 02 24 bits interval value, bits 8-15 (mid)
|
||||
* 03 24 bits interval value, bits 16-23 (hi)
|
||||
*
|
||||
*/
|
||||
class Timer(startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress) {
|
||||
private var counter: Int = 0
|
||||
@ -51,40 +50,34 @@ class Timer(startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemM
|
||||
}
|
||||
|
||||
override operator fun get(address: Address): UByte {
|
||||
when (address - startAddress) {
|
||||
0 -> {
|
||||
return when (address - startAddress) {
|
||||
0x00 -> {
|
||||
var data = 0
|
||||
if (enabled) data = data or 0b00000001
|
||||
if (nmi) data = data or 0b00000010
|
||||
return data.toShort()
|
||||
data.toShort()
|
||||
}
|
||||
1 -> {
|
||||
return (counter and 0xff).toShort()
|
||||
}
|
||||
2 -> {
|
||||
return ((counter ushr 8) and 0xff).toShort()
|
||||
}
|
||||
3 -> {
|
||||
return ((counter ushr 16) and 0xff).toShort()
|
||||
}
|
||||
else -> return 0
|
||||
0x01 -> (counter and 0xff).toShort()
|
||||
0x02 -> ((counter ushr 8) and 0xff).toShort()
|
||||
0x03 -> ((counter ushr 16) and 0xff).toShort()
|
||||
else -> 0xff
|
||||
}
|
||||
}
|
||||
|
||||
override operator fun set(address: Address, data: UByte) {
|
||||
when (address - startAddress) {
|
||||
0 -> {
|
||||
0x00 -> {
|
||||
val i = data.toInt()
|
||||
enabled = (i and 0b00000001) != 0
|
||||
nmi = (i and 0b00000010) != 0
|
||||
}
|
||||
1 -> {
|
||||
0x01 -> {
|
||||
interval = (interval and 0x7fffff00) or data.toInt()
|
||||
}
|
||||
2 -> {
|
||||
0x02 -> {
|
||||
interval = (interval and 0x7fff00ff) or (data.toInt() shl 8)
|
||||
}
|
||||
3 -> {
|
||||
0x03 -> {
|
||||
interval = (interval and 0x7f00ffff) or (data.toInt() shl 16)
|
||||
}
|
||||
}
|
||||
|
@ -1,75 +0,0 @@
|
||||
package testmain
|
||||
|
||||
import razorvine.ksim65.Bus
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.Version
|
||||
import razorvine.ksim65.components.*
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
println(Version.copyright)
|
||||
startSimulator(args)
|
||||
}
|
||||
|
||||
|
||||
private fun startSimulator(args: Array<String>) {
|
||||
|
||||
// create a computer system.
|
||||
// note that the order in which components are added to the bus, is important:
|
||||
// it determines the priority of reads and writes.
|
||||
val cpu = Cpu6502(true)
|
||||
val ram = Ram(0, 0xffff)
|
||||
ram[Cpu6502.RESET_vector] = 0x00
|
||||
ram[Cpu6502.RESET_vector + 1] = 0x10
|
||||
ram[Cpu6502.IRQ_vector] = 0x00
|
||||
ram[Cpu6502.IRQ_vector + 1] = 0x20
|
||||
ram[Cpu6502.NMI_vector] = 0x00
|
||||
ram[Cpu6502.NMI_vector + 1] = 0x30
|
||||
|
||||
// // read the RTC and write the date+time to $2000
|
||||
// for(b in listOf(0xa0, 0x00, 0xb9, 0x00, 0xd1, 0x99, 0x00, 0x20, 0xc8, 0xc0, 0x09, 0xd0, 0xf5, 0x00).withIndex()) {
|
||||
// ram[0x1000+b.index] = b.value.toShort()
|
||||
// }
|
||||
|
||||
// set the timer to $22aa00 and enable it on regular irq
|
||||
for(b in listOf(0xa9, 0x00, 0x8d, 0x00, 0xd2, 0xa9, 0x00, 0x8d, 0x01, 0xd2, 0xa9, 0xaa, 0x8d, 0x02,
|
||||
0xd2, 0xa9, 0x22, 0x8d, 0x03, 0xd2, 0xa9, 0x01, 0x8d, 0x00, 0xd2, 0x4c, 0x19, 0x10).withIndex()) {
|
||||
ram[0x1000+b.index] = b.value.toShort()
|
||||
}
|
||||
|
||||
|
||||
// load the irq routine that prints 'irq!' to the parallel port
|
||||
for(b in listOf(0x48, 0xa9, 0x09, 0x8d, 0x00, 0xd0, 0xee, 0x01, 0xd0, 0xa9, 0x12, 0x8d, 0x00, 0xd0,
|
||||
0xee, 0x01, 0xd0, 0xa9, 0x11, 0x8d, 0x00, 0xd0, 0xee, 0x01, 0xd0, 0xa9, 0x21, 0x8d, 0x00, 0xd0,
|
||||
0xee, 0x01, 0xd0, 0x68, 0x40).withIndex()) {
|
||||
ram[0x2000+b.index] = b.value.toShort()
|
||||
}
|
||||
|
||||
val parallel = ParallelPort(0xd000, 0xd001)
|
||||
val clock = RealTimeClock(0xd100, 0xd108)
|
||||
val timer = Timer(0xd200, 0xd203, cpu)
|
||||
|
||||
val bus = Bus()
|
||||
bus.add(cpu)
|
||||
bus.add(parallel)
|
||||
bus.add(clock)
|
||||
bus.add(timer)
|
||||
bus.add(ram)
|
||||
bus.reset()
|
||||
|
||||
cpu.regP.I = false // enable interrupts
|
||||
|
||||
// TODO
|
||||
// try {
|
||||
// while (true) {
|
||||
// bus.clock()
|
||||
// }
|
||||
// } catch (ix: Cpu6502.InstructionError) {
|
||||
// println("Hmmm... $ix")
|
||||
// }
|
||||
|
||||
ram.hexDump(0x1000, 0x1020)
|
||||
val dis = cpu.disassemble(ram, 0x1000, 0x1020)
|
||||
println(dis.joinToString("\n"))
|
||||
ram.hexDump(0x2000, 0x2008)
|
||||
}
|
BIN
src/main/resources/charset/unscii8x16.png
Normal file
BIN
src/main/resources/charset/unscii8x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
@ -48,7 +48,7 @@ class Test6502CpuBasics {
|
||||
@Test
|
||||
fun testCpuPerformance6502() {
|
||||
val cpu = Cpu6502(true)
|
||||
val ram = Ram(0x1000, 0x2000)
|
||||
val ram = Ram(0x1000, 0x1fff)
|
||||
// load a simple program that loops a few instructions
|
||||
for(b in listOf(0xa9, 0x63, 0xaa, 0x86, 0x22, 0x8e, 0x22, 0x22, 0x91, 0x22, 0x6d, 0x33, 0x33, 0xcd, 0x55, 0x55, 0xd0, 0xee, 0xf0, 0xec).withIndex()) {
|
||||
ram[0x1000+b.index] = b.value.toShort()
|
||||
@ -79,7 +79,7 @@ class Test6502CpuBasics {
|
||||
@Test
|
||||
fun testCpuPerformance65C02() {
|
||||
val cpu = Cpu65C02(true)
|
||||
val ram = Ram(0x0000, 0x2000)
|
||||
val ram = Ram(0x0000, 0x1fff)
|
||||
// load a simple program that loops a few instructions
|
||||
for(b in listOf(0xa9, 0x63, 0xaa, 0x86, 0x22, 0x8e, 0x22, 0x22, 0x91, 0x22, 0x6d, 0x33, 0x33, 0xcd, 0x55, 0x55,
|
||||
0xff, 0xff, 0x79, 0x9e, 0x56, 0x34, 0xd0, 0xe8, 0xf0, 0xe6).withIndex()) {
|
||||
|
@ -28,7 +28,7 @@ class TestDisassembler {
|
||||
@Test
|
||||
fun testDisassembleRockwell65C02() {
|
||||
val cpu = Cpu65C02()
|
||||
val memory = Ram(0, 0x1000)
|
||||
val memory = Ram(0, 0x0fff)
|
||||
val source = javaClass.classLoader.getResource("disassem_r65c02.bin")!!
|
||||
memory.load(source, 0x0200)
|
||||
val resultLines = cpu.disassemble(memory, 0x0200, 0x0250)
|
||||
@ -73,7 +73,7 @@ ${'$'}0250 00 brk""", result)
|
||||
@Test
|
||||
fun testDisassembleWDC65C02() {
|
||||
val cpu = Cpu65C02()
|
||||
val memory = Ram(0, 0x1000)
|
||||
val memory = Ram(0, 0x0fff)
|
||||
val source = javaClass.classLoader.getResource("disassem_wdc65c02.bin")!!
|
||||
memory.load(source, 0x200)
|
||||
val resultLines = cpu.disassemble(memory, 0x0200, 0x0215)
|
||||
|
Loading…
Reference in New Issue
Block a user