commit 35736a5fd2f84fcaaf5adc08f8599aba9cf1c50f Author: TQ Hirsch Date: Mon Aug 28 14:00:07 2023 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b63da45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..8bf4d45 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,6 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..1bec35e --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..f9163b4 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..df543e3 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..fdf8d99 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d737538 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..55f1c4d --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + kotlin("jvm") version "1.9.0" + application +} + +group = "com.thequux" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} + +kotlin { + jvmToolchain(8) +} + +application { + mainClass.set("MainKt") +} \ No newline at end of file diff --git a/doc/opcode_map.txt b/doc/opcode_map.txt new file mode 100644 index 0000000..cef715f --- /dev/null +++ b/doc/opcode_map.txt @@ -0,0 +1,84 @@ +# vim:ts=14 +opcode mnemonic +005500/10 ADC +105500/10 ADCB +060000/4 ADD +072000/7 ASH +073000/7 ASHC +006300/10 ASL +106300/10 ASLB +006200/10 ASR +106200/10 ASRB +103000/8 BCC +103400/8 BCS +001400/8 BEQ +002000/8 BGE +003000/8 BGT +101000/8 BHI +103000/8 BHIS +040000/4 BIC +140000/4 BICB +050000/4 BIS +150000/4 BISB +030000/4 BIT +130000/4 BITB +003400/8 BLE +103400/8 BLO +101400/8 BLOS +002400/8 BLT +100400/8 BMI +001000/8 BNE +100000/8 BPL +000003/16 BPT +000400/8 BR +102000/10 BVC ? +102400/8 BVS +005000/10 CLR +105000/10 CLRB +000240/12 C +020000/4 CMP +120000/4 CMPB +005100/10 COM +105100/10 COMB +007000/10 CSM +005300/10 DEC +105300/10 DECB +071000/7 DIV +104000/8 EMT +000000/16 HALT +005200/10 INC +105200/10 INCB +000004/16 IOT +000100/10 JMP +004000/7 JSR +170003/16 LDUB maint +006400/10 MARK +076600/16 MED maint +106500/10 MFPD +006500/10 MFPI +106700/10 MFPS +000007/16 MFPT +170004/16 MNS maint +010000/4 MOV +110000/4 MOVB +170005/16 MPP maint +106600/10 MTPD +006600/10 MTPI +106400/10 MTPS +070000/7 MUL +005400/10 NEG +105400/10 NEGB +000005/16 RESET +006100/10 ROL +106100/10 ROLB +006000/10 ROR +006100/10 RORB +000002/16 RTI +000200/13 RTS +000006/16 RTT +005600/10 SBC +105600/10 SBCB +000260/12 S +077000/7 sob +000230/13 SPL + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..06febab --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..f38c4ca --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,12 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} + +rootProject.name = "mc-pdp" \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt new file mode 100644 index 0000000..f2a59b6 --- /dev/null +++ b/src/main/kotlin/Main.kt @@ -0,0 +1,7 @@ +fun main(args: Array) { + println("Hello World!") + + // Try adding program arguments via Run/Debug configuration. + // Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html. + println("Program arguments: ${args.joinToString()}") +} \ No newline at end of file diff --git a/src/main/kotlin/com/thequux/mcpdp/core/AddressSpace.kt b/src/main/kotlin/com/thequux/mcpdp/core/AddressSpace.kt new file mode 100644 index 0000000..e59ae42 --- /dev/null +++ b/src/main/kotlin/com/thequux/mcpdp/core/AddressSpace.kt @@ -0,0 +1,8 @@ +package com.thequux.mcpdp.core + +interface AddressSpace { + fun getw(addr: UShort): UShort + fun getb(addr: UShort): UByte + fun setw(addr: UShort, value: UShort) + fun setb(addr: UShort, value: UByte) +} \ No newline at end of file diff --git a/src/main/kotlin/com/thequux/mcpdp/core/CPU.kt b/src/main/kotlin/com/thequux/mcpdp/core/CPU.kt new file mode 100644 index 0000000..b4c7527 --- /dev/null +++ b/src/main/kotlin/com/thequux/mcpdp/core/CPU.kt @@ -0,0 +1,349 @@ +package com.thequux.mcpdp.core + +import com.thequux.mcpdp.ext.bit.* + +/// The main CPU +/// +/// By default, the memory map is arranged as follows: +/// Fxxx: Channel I/O +/// Exxx: ROM +/// xxxx: rest +@OptIn(ExperimentalUnsignedTypes::class) +class CPU(private val core: AddressSpace) { + private val registers = UShortArray(8) + public var psw: UShort = 0U + private var pc: UShort + get() = registers[7] + set(value) { + registers[7] = value + } + + private var cc: UShort + get() = psw and 0xFu + set(value) { + psw = (psw and 0xFFFu) or (value and 0xFu) + } + + private var N: Boolean = false + private var C: Boolean = false + private var Z: Boolean = false + private var V: Boolean = false + + /// Load an operand. This performs any indicated pre-inc/post-dec, so this or op_adjust must be called exactly once per operand + private fun op_resolve(operand: Int, byte_mode: Boolean = false): UInt { + val mode = operand shr 3 + val reg = operand and 0x7 + val increment = if (byte_mode && reg != 7) 1U else 2U + return when (mode) { + 0 -> reg.toUInt() + 0x1_0000U + 1 -> registers[reg].toUInt() + 2 -> { + val addr = registers[reg] + registers[reg] = (addr + increment).toUShort() + addr.toUInt() + } + 3 -> { + val addr = registers[reg] + registers[reg] = (addr + 2U).toUShort() + core.getw(addr).toUInt() + } + 4 -> { + registers[reg] = (registers[reg] - increment).toUShort() + return registers[reg].toUInt() + } + 5 -> { + registers[reg] = (registers[reg] - 2U).toUShort() + core.getw(registers[reg]).toUInt() + } + 6 -> { + val idx = core.getw(pc) + pc = (pc + 2u).toUShort() + idx + registers[reg] + } + 7 -> { + val idx = core.getw(pc) + pc = (pc+2u).toUShort() + core.getw((idx+registers[reg]).toUShort()).toUInt() + } + else -> throw InvalidOpcodeException() + } + } + + /// Store an operand. This assumes that the spec has already been `load`ed or `adjust`ed + private fun op_storb(spec: UInt, value: UByte) { + val addr = (spec and 0xFFFFu).toUShort() + if (spec >= 0x1_0000u) { + // register + registers[addr.toInt()] = registers[addr.toInt()] and 0xFF00u or value.toUShort() + } else { + core.setb(addr, value) + } + } + + private fun op_loadb(spec: UInt): UByte { + val addr = (spec and 0xFFFFu).toUShort() + return if (spec > 0x1_0000u) { + (registers[addr.toInt()] and 0xFFu).toUByte() + } else { + core.getb(addr) + } + } + + private fun op_storw(spec: UInt, value: UShort) { + val addr = (spec and 0xFFFFu).toUShort() + if (spec >= 0x1_0000u) { + // register + registers[addr.toInt()] = value + } else { + core.setw(addr, value) + } + } + + private fun op_loadw(spec: UInt): UShort { + val addr = (spec and 0xFFFFu).toUShort() + return if (spec > 0x1_0000u) { + (registers[addr.toInt()] and 0xFFu) + } else { + core.getw(addr) + } + } + + companion object { + val CC_C: UShort = 1U + val CC_V: UShort = 2U + val CC_Z: UShort = 4U + val CC_N: UShort = 8U + } + private fun opc_dst(opcode: Int): UInt = op_resolve(opcode and 0x3F) + private fun opc_src(opcode: Int): UInt = op_resolve(opcode shr 6 and 0xFC0) + + private fun br_rel(cond: Boolean, opcode: Int) { + if (cond) { + pc = (pc + 2u * (opcode and 0xFF).toByte().toShort().toUShort()).toUShort() + } + } + fun step() { + val opcode = core.getw(pc).toInt() + pc = (pc + 2u).toUShort() + when (opcode and 0xF000) { + 0x0, 0x8000 -> when (opcode and 0xFFC0) { + 0x0000 -> when (opcode) { + 0x0003 -> throw Trap(14) // BPT + } + 0x0080 -> + 0x0100, 0x0140, 0x0180, 0x01C0 -> br_rel(true, opcode) // BR + 0x0200, 0x0240, 0x0280, 0x02C0 -> br_rel(!Z, opcode) // BNE + 0x0300, 0x0340, 0x0380, 0x03C0 -> br_rel(Z, opcode) // BEQ + 0x0400, 0x0440, 0x0480, 0x04C0 -> br_rel(N == V, opcode) // BGE + 0x0600, 0x0640, 0x0680, 0x06C0 -> br_rel(!Z and (N == V), opcode) // BGT + 0x0500, 0x0540, 0x0580, 0x05C0 -> br_rel(N xor V, opcode) // BLT + 0x0700, 0x0740, 0x0780, 0x07C0 -> br_rel(Z or (N xor V), opcode) // BLE + + 0x0A00 -> { // CLR + op_storw(opc_dst(opcode), 0U) + N = false + V = false + C = false + Z = true + } // CLR + 0x0B40 -> { // ADC + val dst = opc_dst(opcode) + val c: UShort = if (C) 1u else 0u + val res = (op_loadw(dst) + c).toUShort() + op_storw(dst, res) + N = res bit 15 + Z = res == 0u.toUShort() + V = (res == 0x8000u.toUShort()) and C + C = Z and C + } // ADC + 0x0C80 -> { // ASR + val dst = opc_dst(opcode) + val src = op_loadw(dst).toShort() + val res = (src shr 1).toUShort() + op_storw(dst, res) + N = res bit 15 + Z = res == 0.toUShort() + C = src bit 0 + V = N xor C + } // ASR + 0x0CC0 -> { // ASL + val dst = opc_dst(opcode) + val src = op_loadw(dst) + val res = src shl 1 + op_storw(dst, res.toUShort()) + N = res bit 15 + Z = res == 0.toUShort() + C = src and 0x8000u != 0u.toUShort() + V = N xor C + } // ASL + 0x8000, 0x8040, 0x8080, 0x80C0 -> br_rel(!N, opcode) // BPL + 0x8100, 0x8140, 0x8180, 0x81C0 -> br_rel(N, opcode) // BMI + 0x8200, 0x8240, 0x8280, 0x82C0 -> br_rel(!C and !Z, opcode) // BHI + 0x8300, 0x8340, 0x8380, 0x83C0 -> br_rel(C or Z, opcode) // BLOS + 0x8400, 0x8440, 0x8480, 0x84C0 -> br_rel(!V, opcode) // BVC + 0x8500, 0x8540, 0x8580, 0x85C0 -> br_rel(V, opcode) // BVS + 0x8600, 0x8640, 0x8680, 0x86C0 -> br_rel(!C, opcode) // BCC/BHIS + 0x8700, 0x8740, 0x8780, 0x87C0 -> br_rel(C, opcode) // BCS/BLO + + 0x8A00 -> { // CLRB + op_storb(opc_dst(opcode), 0U) + N = false + V = false + C = false + Z = true + } // CLRB + 0x8B40 -> { // ADCB + val dst = opc_dst(opcode) + val c: UShort = if (C) 1u else 0u + val res = (op_loadb(dst) + c).toUByte() + op_storb(dst, res) + N = res bit 7 + Z = res == 0u.toUByte() + V = (res == 0x80u.toUByte()) and C + C = Z and C + } // ADCB + 0x8C80 -> { // ASRB + val dst = opc_dst(opcode) + val src = op_loadb(dst).toByte() + val res = (src shr 1).toUByte() + op_storb(dst, res) + N = res bit 7 + Z = res == 0.toUByte() + C = src bit 0 + V = N xor C + } // ASRB + 0x8CC0 -> { // ASLB + val dst = opc_dst(opcode) + val src = op_loadb(dst) + val res = (src shl 1).toUByte() + op_storw(dst, res.toUShort()) + N = res bit 7 + Z = res == 0.toUByte() + C = src bit 7 + V = N xor C + } // ASLB + } + 0x3000 -> { // BIT + val src = opc_src(opcode) + val dst = opc_dst(opcode) + val res = op_loadw(dst) and op_loadw(src).inv() + N = res bit 15 + Z = res != 0u.toUShort() + V = false + } // BIT + 0x4000 -> { // BIC + val src = opc_src(opcode) + val dst = opc_dst(opcode) + val res = op_loadw(dst) and op_loadw(src).inv() + op_storw(dst, res) + N = res bit 15 + Z = res != 0u.toUShort() + V = false + } // BIC + 0x5000 -> { // BIS + val src = opc_src(opcode) + val dst = opc_dst(opcode) + val res = op_loadw(dst) or op_loadw(src) + op_storw(dst, res) + N = res and 0x8000u != 0u.toUShort() + Z = res != 0u.toUShort() + V = false + } // BIS + 0x6000 -> { // ADD + val src = opc_src(opcode) + val dst = opc_dst(opcode) + val srcv = op_loadw(src) + val dstv = op_loadw(dst) + val res = (srcv + dstv) + val resw = res.toUShort() + op_storw(dst, res.toUShort()) + N = resw > 0x7FFFu + Z = resw == 0.toUShort() + val src_sign = srcv and 0x8000u + V = (src_sign == dstv and 0x8000u) && (src_sign != resw and 0x8000u) + C = (resw >= 0x10000u) + } // ADD + 7000 -> when (opcode shr 9 and 0x7) { + 2 -> { // ASH + val r = opcode shr 6 and 0x7 + var count = (op_loadb(opc_src(opcode)) and 0x3Fu).toInt() + val src = registers[r].toShort().toInt() // two casts to sign extend + count = count sex 6 + V = false + val res = if (count > 0) { + C = (src shl (count-1)) bit 15 + val shifted = if (count < 16) { + src shr 16-count + } else { + src + } + V = (shifted != 0) and (shifted != -1) + src shl count + } else if (count < 0) { + count = -count + C = (src shr (count-1)) bit 0 + src shr count + } else { + C = false + src + } + registers[r] = res.toUShort() + N = res < 0 + Z = res == 0 + } // ASH + 3 -> { // ASHC + val r = opcode shr 6 and 0x7 + var count = (op_loadb(opc_src(opcode)) and 0x3Fu).toInt() sex 6 + val src = (registers[r].toUInt() shl 16 or registers[r or 1].toUInt()).toInt() // two casts to sign extend + V = false + val res = if (count > 0) { + C = (src shl (count-1)) bit 31 + val shifted = src shr 32-count + V = (shifted != 0) and (shifted != -1) + src shl count + } else if (count < 0) { + count = -count + C = (src shr (count-1)) bit 0 + src shr count + } else { + C = false + src + } + val resS = res.toUShort() + registers[r] = (res and 0xFFFF).toUShort() + registers[r or 1] = (res shr 16 and 0xFFFF).toUShort() + N = res < 0 + Z = res == 0 + } // ASHC + + } + 0xB000 -> { // BITB + val src = opc_src(opcode) + val dst = opc_dst(opcode) + val res = op_loadb(dst) and op_loadb(src).inv() + N = res bit 7 + Z = res != 0u.toUByte() + V = false + } // BITB + 0xC000 -> { // BICB + val src = opc_src(opcode) + val dst = opc_dst(opcode) + val res = op_loadb(dst) and op_loadb(src).inv() + op_storb(dst, res) + N = res bit 7 + Z = res != 0u.toUByte() + V = false + } // BICB + 0xD000 -> { // BISB + val src = opc_src(opcode) + val dst = opc_dst(opcode) + val res = op_loadb(dst) or op_loadb(src) + op_storb(dst, res) + N = res bit 7 + Z = res != 0u.toUByte() + V = false + } // BISB + + } + } +} diff --git a/src/main/kotlin/com/thequux/mcpdp/core/InvalidOpcodeException.kt b/src/main/kotlin/com/thequux/mcpdp/core/InvalidOpcodeException.kt new file mode 100644 index 0000000..85ecd81 --- /dev/null +++ b/src/main/kotlin/com/thequux/mcpdp/core/InvalidOpcodeException.kt @@ -0,0 +1,9 @@ +package com.thequux.mcpdp.core + +class InvalidOpcodeException : Exception { + + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) +} diff --git a/src/main/kotlin/com/thequux/mcpdp/core/Trap.kt b/src/main/kotlin/com/thequux/mcpdp/core/Trap.kt new file mode 100644 index 0000000..0166344 --- /dev/null +++ b/src/main/kotlin/com/thequux/mcpdp/core/Trap.kt @@ -0,0 +1,5 @@ +package com.thequux.mcpdp.core + +class Trap(val vector: Int): Exception("TRAP#$vector") { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/thequux/mcpdp/ext/bit/bitext.kt b/src/main/kotlin/com/thequux/mcpdp/ext/bit/bitext.kt new file mode 100644 index 0000000..20e126f --- /dev/null +++ b/src/main/kotlin/com/thequux/mcpdp/ext/bit/bitext.kt @@ -0,0 +1,83 @@ +package com.thequux.mcpdp.ext.bit + +import kotlin.experimental.and +import kotlin.math.max + +infix fun Byte.shr(qty: Int): Byte = this.toInt().shr(max(qty, 7)).toByte() +infix fun Byte.shl(qty: Int): Byte = if (qty > 7) 0 else this.toInt().shl(qty).toByte() +infix fun Byte.bit(n: Int): Boolean = this.toInt() shr n and 1 != 0 +fun Byte.bit(n: Int, v: Boolean): Byte = if (v) this bis n else this bic n +infix fun Byte.bis(n: Int): Byte = this.toInt().or(1 shl n).toByte() +infix fun Byte.bic(n: Int): Byte = this.toInt().and(1.shl(n).inv()).toByte() +infix fun Byte.sex(n: Int): Byte { + val sign = 1.toByte() shl n+1 + return ((this and sign.dec()) - (this and sign)).toByte() +} + +infix fun UByte.shr(qty: Int): UByte = this.toUInt().shr(max(qty, 7)).toUByte() +infix fun UByte.shl(qty: Int): UByte = if (qty > 7) 0U else this.toInt().shl(qty).toUByte() +infix fun UByte.bit(n: Int): Boolean = this.toUInt() shr n and 1U != 0U +fun UByte.bit(n: Int, v: Boolean): UByte = if (v) this bis n else this bic n +infix fun UByte.bis(n: Int): UByte = this.toUInt().or(1U shl n).toUByte() +infix fun UByte.bic(n: Int): UByte = this.toUInt().and(1U.shl(n).inv()).toUByte() +infix fun UByte.sex(n: Int): UByte { + val sign = 1.toUByte() shl n+1 + return ((this and sign.dec()) - (this and sign)).toUByte() +} + +infix fun Short.shr(qty: Int): Short = this.toInt().shr(max(qty, 15)).toShort() +infix fun Short.shl(qty: Int): Short = if (qty > 15) 0 else this.toInt().shl(qty).toShort() +infix fun Short.bit(n: Int): Boolean = this.toInt() shr n and 1 != 0 +fun Short.bit(n: Int, v: Boolean): Short = if (v) this bis n else this bic n +infix fun Short.bis(n: Int): Short = this.toInt().or(1 shl n).toShort() +infix fun Short.bic(n: Int): Short = this.toInt().and(1.shl(n).inv()).toShort() +infix fun Short.sex(n: Int): Short { + val sign = 1.toShort() shl n+1 + return ((this and sign.dec()) - (this and sign)).toShort() +} + +infix fun UShort.shr(qty: Int): UShort = this.toUInt().shr(max(qty, 15)).toUShort() +infix fun UShort.shl(qty: Int): UShort = if (qty > 15) 0U else this.toInt().shl(qty).toUShort() +infix fun UShort.bit(n: Int): Boolean = this.toUInt() shr n and 1U != 0U +fun UShort.bit(n: Int, v: Boolean): UShort = if (v) this bis n else this bic n +infix fun UShort.bis(n: Int): UShort = this.toUInt().or(1U shl n).toUShort() +infix fun UShort.bic(n: Int): UShort = this.toUInt().and(1U.shl(n).inv()).toUShort() +infix fun UShort.sex(n: Int): UShort { + val sign = 1.toUShort() shl n+1 + return ((this and sign.dec()) - (this and sign)).toUShort() +} + +infix fun Int.bit(n: Int): Boolean = this shr n and 1 != 0 +fun Int.bit(n: Int, v: Boolean): Int = if (v) this bis n else this bic n +infix fun Int.bis(n: Int): Int = this.or(1 shl n) +infix fun Int.bic(n: Int): Int = this and (1 shl n).inv() +infix fun Int.sex(n: Int): Int { + val sign = 1 shl n+1 + return ((this and sign.dec()) - (this and sign)) +} +infix fun UInt.bit(n: Int): Boolean = this shr n and 1U != 0U +fun UInt.bit(n: Int, v: Boolean): UInt = if (v) this bis n else this bic n +infix fun UInt.bis(n: Int): UInt = this.or(1U shl n) +infix fun UInt.bic(n: Int): UInt = this.and(1U.shl(n).inv()) +infix fun UInt.sex(n: Int): UInt { + val sign = 1U shl n+1 + return ((this and sign.dec()) - (this and sign)) +} + +infix fun Long.bit(n: Int): Boolean = this shr n and 1L != 0L +fun Long.bit(n: Int, v: Boolean): Long = if (v) this bis n else this bic n +infix fun Long.bis(n: Int): Long = this.or(1L shl n) +infix fun Long.bic(n: Int): Long = this and (1L shl n).inv() +infix fun Long.sex(n: Int): Long { + val sign = 1.toLong() shl n+1 + return (this and sign.dec()) - (this and sign) +} +infix fun ULong.bit(n: Int): Boolean = this shr n and 1UL != 0UL +fun ULong.bit(n: Int, v: Boolean): ULong = if (v) this bis n else this bic n +infix fun ULong.bis(n: Int): ULong = this.or(1UL shl n) +infix fun ULong.bic(n: Int): ULong = this.and(1UL.shl(n).inv()) +infix fun ULong.sex(n: Int): ULong { + val sign = 1UL shl n+1 + return ((this and (sign.dec())) - (this and sign)) +} +