mirror of https://github.com/irmen/ksim65.git
virtual machine
This commit is contained in:
parent
789b1f69a6
commit
d0dfb24172
|
@ -5,4 +5,6 @@
|
||||||
build/
|
build/
|
||||||
|
|
||||||
.idea/workspace.xml
|
.idea/workspace.xml
|
||||||
|
.idea/dictionaries/
|
||||||
|
.idea/inspectionProfiles/
|
||||||
.attach_pid*
|
.attach_pid*
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
</JetCodeStyleSettings>
|
</JetCodeStyleSettings>
|
||||||
<MarkdownNavigatorCodeStyleSettings>
|
<MarkdownNavigatorCodeStyleSettings>
|
||||||
<option name="RIGHT_MARGIN" value="72" />
|
<option name="RIGHT_MARGIN" value="80" />
|
||||||
</MarkdownNavigatorCodeStyleSettings>
|
</MarkdownNavigatorCodeStyleSettings>
|
||||||
<codeStyleSettings language="kotlin">
|
<codeStyleSettings language="kotlin">
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() }
|
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) {
|
fun add(component: BusComponent) {
|
||||||
components.add(component)
|
components.add(component)
|
||||||
component.bus = this
|
component.bus = this
|
||||||
|
@ -54,6 +60,7 @@ class Bus {
|
||||||
* Any memory mapped component that listens to the address, will receive the data.
|
* Any memory mapped component that listens to the address, will receive the data.
|
||||||
*/
|
*/
|
||||||
fun write(address: Address, data: UByte) {
|
fun write(address: Address, data: UByte) {
|
||||||
|
require(data in 0..255) { "data must be a byte 0..255" }
|
||||||
memComponents.forEach {
|
memComponents.forEach {
|
||||||
if (address >= it.startAddress && address <= it.endAddress)
|
if (address >= it.startAddress && address <= it.endAddress)
|
||||||
it[address] = data
|
it[address] = data
|
||||||
|
|
|
@ -11,6 +11,7 @@ import razorvine.ksim65.components.UByte
|
||||||
* TODO: add the optional additional cycles to certain instructions and addressing modes
|
* TODO: add the optional additional cycles to certain instructions and addressing modes
|
||||||
*/
|
*/
|
||||||
open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||||
|
open val name = "6502"
|
||||||
var tracing: ((state:String) -> Unit)? = null
|
var tracing: ((state:String) -> Unit)? = null
|
||||||
var totalCycles: Long = 0
|
var totalCycles: Long = 0
|
||||||
protected set
|
protected set
|
||||||
|
@ -102,6 +103,8 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
protected lateinit var currentInstruction: Instruction
|
protected lateinit var currentInstruction: Instruction
|
||||||
|
val currentMnemonic: String
|
||||||
|
get() = currentInstruction.mnemonic
|
||||||
|
|
||||||
// has an interrupt been requested?
|
// has an interrupt been requested?
|
||||||
protected var pendingInterrupt: Pair<Boolean, BusComponent>? = null
|
protected var pendingInterrupt: Pair<Boolean, BusComponent>? = null
|
||||||
|
@ -249,6 +252,9 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the cpu
|
||||||
|
*/
|
||||||
override fun reset() {
|
override fun reset() {
|
||||||
regSP = 0xfd
|
regSP = 0xfd
|
||||||
regPC = readWord(RESET_vector)
|
regPC = readWord(RESET_vector)
|
||||||
|
@ -267,6 +273,9 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||||
currentInstruction = instructions[0]
|
currentInstruction = instructions[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process once clock cycle in the cpu
|
||||||
|
*/
|
||||||
override fun clock() {
|
override fun clock() {
|
||||||
if (instrCycles == 0) {
|
if (instrCycles == 0) {
|
||||||
if (pendingInterrupt != null) {
|
if (pendingInterrupt != null) {
|
||||||
|
@ -309,11 +318,13 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||||
totalCycles++
|
totalCycles++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute one single complete instruction
|
||||||
|
*/
|
||||||
open fun step() {
|
open fun step() {
|
||||||
// step a whole instruction
|
while (instrCycles > 0) clock()
|
||||||
while (instrCycles > 0) clock() // remaining instruction subcycles from the previous instruction
|
clock()
|
||||||
clock() // the actual instruction execution cycle
|
while (instrCycles > 0) clock()
|
||||||
while (instrCycles > 0) clock() // instruction subcycles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nmi(source: BusComponent) {
|
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
|
* TODO: add the optional additional cycles to certain instructions and addressing modes
|
||||||
*/
|
*/
|
||||||
class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||||
|
override val name = "65C02"
|
||||||
|
|
||||||
enum class Wait {
|
enum class Wait {
|
||||||
Normal,
|
Normal,
|
||||||
|
@ -23,6 +24,9 @@ class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||||
const val resetCycles = Cpu6502.resetCycles
|
const val resetCycles = Cpu6502.resetCycles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process once clock cycle in the cpu
|
||||||
|
*/
|
||||||
override fun clock() {
|
override fun clock() {
|
||||||
when (waiting) {
|
when (waiting) {
|
||||||
Wait.Normal -> super.clock()
|
Wait.Normal -> super.clock()
|
||||||
|
@ -42,13 +46,15 @@ class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute one single complete instruction
|
||||||
|
*/
|
||||||
override fun step() {
|
override fun step() {
|
||||||
// step a whole instruction
|
|
||||||
if (waiting == Wait.Normal) {
|
if (waiting == Wait.Normal) {
|
||||||
while (instrCycles > 0) clock() // remaining instruction subcycles from the previous instruction
|
while (instrCycles > 0) clock()
|
||||||
clock() // the actual instruction execution cycle
|
clock()
|
||||||
if (waiting == Wait.Normal)
|
if (waiting == Wait.Normal)
|
||||||
while (instrCycles > 0) clock() // instruction subcycles
|
while (instrCycles > 0) clock()
|
||||||
else {
|
else {
|
||||||
totalCycles += instrCycles
|
totalCycles += instrCycles
|
||||||
instrCycles = 0
|
instrCycles = 0
|
||||||
|
|
|
@ -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 {
|
abstract class BusComponent {
|
||||||
lateinit var bus: Bus
|
lateinit var bus: Bus
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One clock cycle on the bus
|
||||||
|
*/
|
||||||
abstract fun clock()
|
abstract fun clock()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all devices on the bus
|
||||||
|
*/
|
||||||
abstract fun reset()
|
abstract fun reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,4 +60,8 @@ abstract class MemMappedComponent(val startAddress: Address, val endAddress: Add
|
||||||
abstract class MemoryComponent(startAddress: Address, endAddress: Address) :
|
abstract class MemoryComponent(startAddress: Address, endAddress: Address) :
|
||||||
MemMappedComponent(startAddress, endAddress) {
|
MemMappedComponent(startAddress, endAddress) {
|
||||||
abstract fun copyOfMem(): Array<UByte>
|
abstract fun copyOfMem(): Array<UByte>
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(startAddress and 0xff == 0 && endAddress and 0xff == 0xff) {"address range must span complete page(s)"}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 */ }
|
||||||
|
}
|
|
@ -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)
|
* A simple parallel output device (basically, prints bytes as characters to the console)
|
||||||
*
|
*
|
||||||
* byte value
|
* reg. value
|
||||||
* ---- ---------
|
* ---- ---------
|
||||||
* 00 data (the 8 parallel bits)
|
* 00 data (the 8 parallel bits)
|
||||||
* 01 control latch (set bit 0 to write the data byte)
|
* 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)
|
return if (address == startAddress)
|
||||||
dataByte
|
dataByte
|
||||||
else
|
else
|
||||||
0
|
0xff
|
||||||
}
|
}
|
||||||
|
|
||||||
override operator fun set(address: Address, data: UByte) {
|
override operator fun set(address: Address, data: UByte) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import java.time.LocalTime
|
||||||
* A real-time time of day clock.
|
* A real-time time of day clock.
|
||||||
* (System timers are elsewhere)
|
* (System timers are elsewhere)
|
||||||
*
|
*
|
||||||
* byte value
|
* reg. value
|
||||||
* ---- ----------
|
* ---- ----------
|
||||||
* 00 year (lsb)
|
* 00 year (lsb)
|
||||||
* 01 year (msb)
|
* 01 year (msb)
|
||||||
|
@ -36,28 +36,28 @@ class RealTimeClock(startAddress: Address, endAddress: Address) : MemMappedCompo
|
||||||
|
|
||||||
override operator fun get(address: Address): UByte {
|
override operator fun get(address: Address): UByte {
|
||||||
return when (address - startAddress) {
|
return when (address - startAddress) {
|
||||||
0 -> {
|
0x00 -> {
|
||||||
val year = LocalDate.now().year
|
val year = LocalDate.now().year
|
||||||
(year and 255).toShort()
|
(year and 255).toShort()
|
||||||
}
|
}
|
||||||
1 -> {
|
0x01 -> {
|
||||||
val year = LocalDate.now().year
|
val year = LocalDate.now().year
|
||||||
(year ushr 8).toShort()
|
(year ushr 8).toShort()
|
||||||
}
|
}
|
||||||
2 -> LocalDate.now().monthValue.toShort()
|
0x02 -> LocalDate.now().monthValue.toShort()
|
||||||
3 -> LocalDate.now().dayOfMonth.toShort()
|
0x03 -> LocalDate.now().dayOfMonth.toShort()
|
||||||
4 -> LocalTime.now().hour.toShort()
|
0x04 -> LocalTime.now().hour.toShort()
|
||||||
5 -> LocalTime.now().minute.toShort()
|
0x05 -> LocalTime.now().minute.toShort()
|
||||||
6 -> LocalTime.now().second.toShort()
|
0x06 -> LocalTime.now().second.toShort()
|
||||||
7 -> {
|
0x07 -> {
|
||||||
val ms = LocalTime.now().nano / 1000
|
val ms = LocalTime.now().nano / 1000
|
||||||
(ms and 255).toShort()
|
(ms and 255).toShort()
|
||||||
}
|
}
|
||||||
8 -> {
|
0x08 -> {
|
||||||
val ms = LocalTime.now().nano / 1000
|
val ms = LocalTime.now().nano / 1000
|
||||||
(ms ushr 8).toShort()
|
(ms ushr 8).toShort()
|
||||||
}
|
}
|
||||||
else -> 0
|
else -> 0xff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,15 +3,14 @@ package razorvine.ksim65.components
|
||||||
import razorvine.ksim65.Cpu6502
|
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)
|
* 00 control register bit 0=enable bit 1=nmi (instead of irq)
|
||||||
* 01 24 bits interval value, bits 0-7 (lo)
|
* 01 24 bits interval value, bits 0-7 (lo)
|
||||||
* 02 24 bits interval value, bits 8-15 (mid)
|
* 02 24 bits interval value, bits 8-15 (mid)
|
||||||
* 03 24 bits interval value, bits 16-23 (hi)
|
* 03 24 bits interval value, bits 16-23 (hi)
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
class Timer(startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress) {
|
class Timer(startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress) {
|
||||||
private var counter: Int = 0
|
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 {
|
override operator fun get(address: Address): UByte {
|
||||||
when (address - startAddress) {
|
return when (address - startAddress) {
|
||||||
0 -> {
|
0x00 -> {
|
||||||
var data = 0
|
var data = 0
|
||||||
if (enabled) data = data or 0b00000001
|
if (enabled) data = data or 0b00000001
|
||||||
if (nmi) data = data or 0b00000010
|
if (nmi) data = data or 0b00000010
|
||||||
return data.toShort()
|
data.toShort()
|
||||||
}
|
}
|
||||||
1 -> {
|
0x01 -> (counter and 0xff).toShort()
|
||||||
return (counter and 0xff).toShort()
|
0x02 -> ((counter ushr 8) and 0xff).toShort()
|
||||||
}
|
0x03 -> ((counter ushr 16) and 0xff).toShort()
|
||||||
2 -> {
|
else -> 0xff
|
||||||
return ((counter ushr 8) and 0xff).toShort()
|
|
||||||
}
|
|
||||||
3 -> {
|
|
||||||
return ((counter ushr 16) and 0xff).toShort()
|
|
||||||
}
|
|
||||||
else -> return 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override operator fun set(address: Address, data: UByte) {
|
override operator fun set(address: Address, data: UByte) {
|
||||||
when (address - startAddress) {
|
when (address - startAddress) {
|
||||||
0 -> {
|
0x00 -> {
|
||||||
val i = data.toInt()
|
val i = data.toInt()
|
||||||
enabled = (i and 0b00000001) != 0
|
enabled = (i and 0b00000001) != 0
|
||||||
nmi = (i and 0b00000010) != 0
|
nmi = (i and 0b00000010) != 0
|
||||||
}
|
}
|
||||||
1 -> {
|
0x01 -> {
|
||||||
interval = (interval and 0x7fffff00) or data.toInt()
|
interval = (interval and 0x7fffff00) or data.toInt()
|
||||||
}
|
}
|
||||||
2 -> {
|
0x02 -> {
|
||||||
interval = (interval and 0x7fff00ff) or (data.toInt() shl 8)
|
interval = (interval and 0x7fff00ff) or (data.toInt() shl 8)
|
||||||
}
|
}
|
||||||
3 -> {
|
0x03 -> {
|
||||||
interval = (interval and 0x7f00ffff) or (data.toInt() shl 16)
|
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)
|
|
||||||
}
|
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -48,7 +48,7 @@ class Test6502CpuBasics {
|
||||||
@Test
|
@Test
|
||||||
fun testCpuPerformance6502() {
|
fun testCpuPerformance6502() {
|
||||||
val cpu = Cpu6502(true)
|
val cpu = Cpu6502(true)
|
||||||
val ram = Ram(0x1000, 0x2000)
|
val ram = Ram(0x1000, 0x1fff)
|
||||||
// load a simple program that loops a few instructions
|
// 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()) {
|
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()
|
ram[0x1000+b.index] = b.value.toShort()
|
||||||
|
@ -79,7 +79,7 @@ class Test6502CpuBasics {
|
||||||
@Test
|
@Test
|
||||||
fun testCpuPerformance65C02() {
|
fun testCpuPerformance65C02() {
|
||||||
val cpu = Cpu65C02(true)
|
val cpu = Cpu65C02(true)
|
||||||
val ram = Ram(0x0000, 0x2000)
|
val ram = Ram(0x0000, 0x1fff)
|
||||||
// load a simple program that loops a few instructions
|
// 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,
|
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()) {
|
0xff, 0xff, 0x79, 0x9e, 0x56, 0x34, 0xd0, 0xe8, 0xf0, 0xe6).withIndex()) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ class TestDisassembler {
|
||||||
@Test
|
@Test
|
||||||
fun testDisassembleRockwell65C02() {
|
fun testDisassembleRockwell65C02() {
|
||||||
val cpu = Cpu65C02()
|
val cpu = Cpu65C02()
|
||||||
val memory = Ram(0, 0x1000)
|
val memory = Ram(0, 0x0fff)
|
||||||
val source = javaClass.classLoader.getResource("disassem_r65c02.bin")!!
|
val source = javaClass.classLoader.getResource("disassem_r65c02.bin")!!
|
||||||
memory.load(source, 0x0200)
|
memory.load(source, 0x0200)
|
||||||
val resultLines = cpu.disassemble(memory, 0x0200, 0x0250)
|
val resultLines = cpu.disassemble(memory, 0x0200, 0x0250)
|
||||||
|
@ -73,7 +73,7 @@ ${'$'}0250 00 brk""", result)
|
||||||
@Test
|
@Test
|
||||||
fun testDisassembleWDC65C02() {
|
fun testDisassembleWDC65C02() {
|
||||||
val cpu = Cpu65C02()
|
val cpu = Cpu65C02()
|
||||||
val memory = Ram(0, 0x1000)
|
val memory = Ram(0, 0x0fff)
|
||||||
val source = javaClass.classLoader.getResource("disassem_wdc65c02.bin")!!
|
val source = javaClass.classLoader.getResource("disassem_wdc65c02.bin")!!
|
||||||
memory.load(source, 0x200)
|
memory.load(source, 0x200)
|
||||||
val resultLines = cpu.disassemble(memory, 0x0200, 0x0215)
|
val resultLines = cpu.disassemble(memory, 0x0200, 0x0215)
|
||||||
|
|
Loading…
Reference in New Issue