diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e10e727
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/.metadata/
diff --git a/playing-coffee - Copy/.classpath b/playing-coffee - Copy/.classpath
new file mode 100644
index 0000000..271ce0c
--- /dev/null
+++ b/playing-coffee - Copy/.classpath
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/playing-coffee - Copy/.gitignore b/playing-coffee - Copy/.gitignore
new file mode 100644
index 0000000..b83d222
--- /dev/null
+++ b/playing-coffee - Copy/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/playing-coffee - Copy/.project b/playing-coffee - Copy/.project
new file mode 100644
index 0000000..8e61026
--- /dev/null
+++ b/playing-coffee - Copy/.project
@@ -0,0 +1,23 @@
+
+
+ playing-coffee
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/playing-coffee - Copy/.settings/org.eclipse.jdt.core.prefs b/playing-coffee - Copy/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..cac0df4
--- /dev/null
+++ b/playing-coffee - Copy/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/playing-coffee - Copy/.settings/org.eclipse.m2e.core.prefs b/playing-coffee - Copy/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/playing-coffee - Copy/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/playing-coffee - Copy/dmg_boot.bin b/playing-coffee - Copy/dmg_boot.bin
new file mode 100644
index 0000000..afa0ee4
Binary files /dev/null and b/playing-coffee - Copy/dmg_boot.bin differ
diff --git a/playing-coffee - Copy/gb-test-roms-master.zip b/playing-coffee - Copy/gb-test-roms-master.zip
new file mode 100644
index 0000000..b66c88f
Binary files /dev/null and b/playing-coffee - Copy/gb-test-roms-master.zip differ
diff --git a/playing-coffee - Copy/log.txt b/playing-coffee - Copy/log.txt
new file mode 100644
index 0000000..9fd27a9
--- /dev/null
+++ b/playing-coffee - Copy/log.txt
@@ -0,0 +1,646 @@
+[19:39:15.741] Info: Initialized logger
+[19:39:15.830] Info: Pushing 0x 2b to the stack.
+[19:39:15.831] Info: Pushing 0x 4ce to the stack.
+[19:39:15.832] Info: Pushing 0x 39d to the stack.
+[19:39:15.832] Info: Pushing 0x 23a to the stack.
+[19:39:15.832] Info: Pushing 0x 175 to the stack.
+[19:39:15.832] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.832] Info: Pushing 0x 2e to the stack.
+[19:39:15.833] Info: Pushing 0x 4eb to the stack.
+[19:39:15.833] Info: Pushing 0x 3d7 to the stack.
+[19:39:15.833] Info: Pushing 0x 2af to the stack.
+[19:39:15.833] Info: Pushing 0x 15e to the stack.
+[19:39:15.833] Info: Returning from 0x a8 to 0x 2e
+[19:39:15.834] Info: Pushing 0x 2b to the stack.
+[19:39:15.834] Info: Pushing 0x 4ed to the stack.
+[19:39:15.834] Info: Pushing 0x 3db to the stack.
+[19:39:15.834] Info: Pushing 0x 2b7 to the stack.
+[19:39:15.835] Info: Pushing 0x 16f to the stack.
+[19:39:15.835] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.835] Info: Pushing 0x 2e to the stack.
+[19:39:15.835] Info: Pushing 0x 4de to the stack.
+[19:39:15.835] Info: Pushing 0x 3bd to the stack.
+[19:39:15.835] Info: Pushing 0x 27b to the stack.
+[19:39:15.835] Info: Pushing 0x 1f7 to the stack.
+[19:39:15.835] Info: Returning from 0x a8 to 0x 2e
+[19:39:15.836] Info: Pushing 0x 2b to the stack.
+[19:39:15.836] Info: Pushing 0x 466 to the stack.
+[19:39:15.836] Info: Pushing 0x 3cc to the stack.
+[19:39:15.836] Info: Pushing 0x 299 to the stack.
+[19:39:15.836] Info: Pushing 0x 132 to the stack.
+[19:39:15.836] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.836] Info: Pushing 0x 2e to the stack.
+[19:39:15.836] Info: Pushing 0x 465 to the stack.
+[19:39:15.836] Info: Pushing 0x 3ca to the stack.
+[19:39:15.837] Info: Pushing 0x 295 to the stack.
+[19:39:15.837] Info: Pushing 0x 12b to the stack.
+[19:39:15.837] Info: Returning from 0x a8 to 0x 2e
+[19:39:15.837] Info: Pushing 0x 2b to the stack.
+[19:39:15.837] Info: Pushing 0x 466 to the stack.
+[19:39:15.837] Info: Pushing 0x 3cc to the stack.
+[19:39:15.837] Info: Pushing 0x 299 to the stack.
+[19:39:15.837] Info: Pushing 0x 132 to the stack.
+[19:39:15.838] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.838] Info: Pushing 0x 2e to the stack.
+[19:39:15.838] Info: Pushing 0x 465 to the stack.
+[19:39:15.838] Info: Pushing 0x 3ca to the stack.
+[19:39:15.838] Info: Pushing 0x 295 to the stack.
+[19:39:15.838] Info: Pushing 0x 12b to the stack.
+[19:39:15.838] Info: Returning from 0x a8 to 0x 2e
+[19:39:15.838] Info: Pushing 0x 2b to the stack.
+[19:39:15.839] Info: Pushing 0x 4cc to the stack.
+[19:39:15.839] Info: Pushing 0x 399 to the stack.
+[19:39:15.839] Info: Pushing 0x 232 to the stack.
+[19:39:15.839] Info: Pushing 0x 165 to the stack.
+[19:39:15.839] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.839] Info: Pushing 0x 2e to the stack.
+[19:39:15.839] Info: Pushing 0x 4ca to the stack.
+[19:39:15.839] Info: Pushing 0x 395 to the stack.
+[19:39:15.840] Info: Pushing 0x 22b to the stack.
+[19:39:15.840] Info: Pushing 0x 156 to the stack.
+[19:39:15.840] Info: Returning from 0x a8 to 0x 2e
+[19:39:15.840] Info: Pushing 0x 2b to the stack.
+[19:39:15.840] Info: Pushing 0x 40d to the stack.
+[19:39:15.840] Info: Pushing 0x 31a to the stack.
+[19:39:15.840] Info: Pushing 0x 234 to the stack.
+[19:39:15.840] Info: Pushing 0x 169 to the stack.
+[19:39:15.841] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.841] Info: Pushing 0x 2e to the stack.
+[19:39:15.841] Info: Pushing 0x 4d2 to the stack.
+[19:39:15.841] Info: Pushing 0x 3a4 to the stack.
+[19:39:15.841] Info: Pushing 0x 248 to the stack.
+[19:39:15.841] Info: Pushing 0x 190 to the stack.
+[19:39:15.841] Info: Returning from 0x a8 to 0x 2e
+[19:39:15.841] Info: Pushing 0x 2b to the stack.
+[19:39:15.841] Info: Pushing 0x 400 to the stack.
+[19:39:15.842] Info: Pushing 0x 300 to the stack.
+[19:39:15.842] Info: Pushing 0x 200 to the stack.
+[19:39:15.842] Info: Pushing 0x 100 to the stack.
+[19:39:15.842] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.842] Info: Pushing 0x 2e to the stack.
+[19:39:15.842] Info: Pushing 0x 400 to the stack.
+[19:39:17.652] Info: Pushing 0x 300 to the stack.
+[19:39:17.652] Info: Pushing 0x 200 to the stack.
+[19:39:17.652] Info: Pushing 0x 100 to the stack.
+[19:39:17.652] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.652] Info: Pushing 0x 2b to the stack.
+[19:39:17.652] Info: Pushing 0x 40b to the stack.
+[19:39:17.652] Info: Pushing 0x 316 to the stack.
+[19:39:17.653] Info: Pushing 0x 22c to the stack.
+[19:39:17.653] Info: Pushing 0x 159 to the stack.
+[19:39:17.653] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.653] Info: Pushing 0x 2e to the stack.
+[19:39:17.653] Info: Pushing 0x 4b3 to the stack.
+[19:39:17.653] Info: Pushing 0x 366 to the stack.
+[19:39:17.653] Info: Pushing 0x 2cc to the stack.
+[19:39:17.653] Info: Pushing 0x 198 to the stack.
+[19:39:17.653] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.654] Info: Pushing 0x 2b to the stack.
+[19:39:17.654] Info: Pushing 0x 403 to the stack.
+[19:39:17.654] Info: Pushing 0x 306 to the stack.
+[19:39:17.654] Info: Pushing 0x 20c to the stack.
+[19:39:17.654] Info: Pushing 0x 118 to the stack.
+[19:39:17.654] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.654] Info: Pushing 0x 2e to the stack.
+[19:39:17.654] Info: Pushing 0x 431 to the stack.
+[19:39:17.655] Info: Pushing 0x 362 to the stack.
+[19:39:17.655] Info: Pushing 0x 2c4 to the stack.
+[19:39:17.655] Info: Pushing 0x 188 to the stack.
+[19:39:17.655] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.655] Info: Pushing 0x 2b to the stack.
+[19:39:17.655] Info: Pushing 0x 473 to the stack.
+[19:39:17.655] Info: Pushing 0x 3e6 to the stack.
+[19:39:17.655] Info: Pushing 0x 2cd to the stack.
+[19:39:17.655] Info: Pushing 0x 19a to the stack.
+[19:39:17.655] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.655] Info: Pushing 0x 2e to the stack.
+[19:39:17.656] Info: Pushing 0x 435 to the stack.
+[19:39:17.656] Info: Pushing 0x 36a to the stack.
+[19:39:17.656] Info: Pushing 0x 2d5 to the stack.
+[19:39:17.656] Info: Pushing 0x 1ab to the stack.
+[19:39:17.656] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.656] Info: Pushing 0x 2b to the stack.
+[19:39:17.656] Info: Pushing 0x 400 to the stack.
+[19:39:17.657] Info: Pushing 0x 300 to the stack.
+[19:39:17.657] Info: Pushing 0x 200 to the stack.
+[19:39:17.657] Info: Pushing 0x 100 to the stack.
+[19:39:17.657] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.658] Info: Pushing 0x 2e to the stack.
+[19:39:17.658] Info: Pushing 0x 400 to the stack.
+[19:39:17.658] Info: Pushing 0x 300 to the stack.
+[19:39:17.658] Info: Pushing 0x 200 to the stack.
+[19:39:17.658] Info: Pushing 0x 100 to the stack.
+[19:39:17.658] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.658] Info: Pushing 0x 2b to the stack.
+[19:39:17.658] Info: Pushing 0x 483 to the stack.
+[19:39:17.658] Info: Pushing 0x 307 to the stack.
+[19:39:17.658] Info: Pushing 0x 20e to the stack.
+[19:39:17.660] Info: Pushing 0x 11c to the stack.
+[19:39:17.660] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.660] Info: Pushing 0x 2e to the stack.
+[19:39:17.660] Info: Pushing 0x 439 to the stack.
+[19:39:17.667] Info: Pushing 0x 373 to the stack.
+[19:39:17.668] Info: Pushing 0x 2e6 to the stack.
+[19:39:17.668] Info: Pushing 0x 1cc to the stack.
+[19:39:17.668] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.668] Info: Pushing 0x 2b to the stack.
+[19:39:17.668] Info: Pushing 0x 400 to the stack.
+[19:39:17.668] Info: Pushing 0x 300 to the stack.
+[19:39:17.668] Info: Pushing 0x 200 to the stack.
+[19:39:17.668] Info: Pushing 0x 100 to the stack.
+[19:39:17.668] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.668] Info: Pushing 0x 2e to the stack.
+[19:39:17.668] Info: Pushing 0x 400 to the stack.
+[19:39:17.669] Info: Pushing 0x 300 to the stack.
+[19:39:17.669] Info: Pushing 0x 200 to the stack.
+[19:39:17.669] Info: Pushing 0x 100 to the stack.
+[19:39:17.669] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.669] Info: Pushing 0x 2b to the stack.
+[19:39:17.669] Info: Pushing 0x 40c to the stack.
+[19:39:17.669] Info: Pushing 0x 318 to the stack.
+[19:39:17.669] Info: Pushing 0x 230 to the stack.
+[19:39:17.669] Info: Pushing 0x 161 to the stack.
+[19:39:17.669] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.670] Info: Pushing 0x 2e to the stack.
+[19:39:17.670] Info: Pushing 0x 4c2 to the stack.
+[19:39:17.670] Info: Pushing 0x 384 to the stack.
+[19:39:17.670] Info: Pushing 0x 208 to the stack.
+[19:39:17.670] Info: Pushing 0x 110 to the stack.
+[19:39:17.670] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.670] Info: Pushing 0x 2b to the stack.
+[19:39:17.670] Info: Pushing 0x 400 to the stack.
+[19:39:17.670] Info: Pushing 0x 300 to the stack.
+[19:39:17.670] Info: Pushing 0x 200 to the stack.
+[19:39:17.670] Info: Pushing 0x 100 to the stack.
+[19:39:17.671] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.671] Info: Pushing 0x 2e to the stack.
+[19:39:17.671] Info: Pushing 0x 400 to the stack.
+[19:39:17.671] Info: Pushing 0x 300 to the stack.
+[19:39:17.671] Info: Pushing 0x 200 to the stack.
+[19:39:17.671] Info: Pushing 0x 100 to the stack.
+[19:39:17.671] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.671] Info: Pushing 0x 2b to the stack.
+[19:39:17.671] Info: Pushing 0x 40d to the stack.
+[19:39:17.671] Info: Pushing 0x 31a to the stack.
+[19:39:17.671] Info: Pushing 0x 234 to the stack.
+[19:39:17.671] Info: Pushing 0x 169 to the stack.
+[19:39:17.671] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.671] Info: Pushing 0x 2e to the stack.
+[19:39:17.672] Info: Pushing 0x 4d2 to the stack.
+[19:39:17.672] Info: Pushing 0x 3a4 to the stack.
+[19:39:17.672] Info: Pushing 0x 248 to the stack.
+[19:39:17.672] Info: Pushing 0x 190 to the stack.
+[19:39:17.672] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.672] Info: Pushing 0x 2b to the stack.
+[19:39:17.672] Info: Pushing 0x 400 to the stack.
+[19:39:17.672] Info: Pushing 0x 300 to the stack.
+[19:39:17.672] Info: Pushing 0x 200 to the stack.
+[19:39:17.672] Info: Pushing 0x 100 to the stack.
+[19:39:17.672] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.672] Info: Pushing 0x 2e to the stack.
+[19:39:17.673] Info: Pushing 0x 400 to the stack.
+[19:39:17.673] Info: Pushing 0x 300 to the stack.
+[19:39:17.673] Info: Pushing 0x 200 to the stack.
+[19:39:17.673] Info: Pushing 0x 100 to the stack.
+[19:39:17.673] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.673] Info: Pushing 0x 2b to the stack.
+[19:39:17.673] Info: Pushing 0x 408 to the stack.
+[19:39:17.673] Info: Pushing 0x 310 to the stack.
+[19:39:17.673] Info: Pushing 0x 220 to the stack.
+[19:39:17.673] Info: Pushing 0x 141 to the stack.
+[19:39:17.673] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.673] Info: Pushing 0x 2e to the stack.
+[19:39:17.673] Info: Pushing 0x 482 to the stack.
+[19:39:17.673] Info: Pushing 0x 304 to the stack.
+[19:39:17.674] Info: Pushing 0x 208 to the stack.
+[19:39:17.674] Info: Pushing 0x 110 to the stack.
+[19:39:17.674] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.674] Info: Pushing 0x 2b to the stack.
+[19:39:17.674] Info: Pushing 0x 411 to the stack.
+[19:39:17.674] Info: Pushing 0x 322 to the stack.
+[19:39:17.674] Info: Pushing 0x 244 to the stack.
+[19:39:17.674] Info: Pushing 0x 188 to the stack.
+[19:39:17.674] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.674] Info: Pushing 0x 2e to the stack.
+[19:39:17.674] Info: Pushing 0x 410 to the stack.
+[19:39:17.675] Info: Pushing 0x 320 to the stack.
+[19:39:17.675] Info: Pushing 0x 240 to the stack.
+[19:39:17.675] Info: Pushing 0x 180 to the stack.
+[19:39:17.675] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.675] Info: Pushing 0x 2b to the stack.
+[19:39:17.675] Info: Pushing 0x 41f to the stack.
+[19:39:17.675] Info: Pushing 0x 33e to the stack.
+[19:39:17.675] Info: Pushing 0x 27c to the stack.
+[19:39:17.675] Info: Pushing 0x 1f9 to the stack.
+[19:39:17.675] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.675] Info: Pushing 0x 2e to the stack.
+[19:39:17.675] Info: Pushing 0x 4f3 to the stack.
+[19:39:17.675] Info: Pushing 0x 3e6 to the stack.
+[19:39:17.676] Info: Pushing 0x 2cc to the stack.
+[19:39:17.676] Info: Pushing 0x 198 to the stack.
+[19:39:17.676] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.676] Info: Pushing 0x 2b to the stack.
+[19:39:17.676] Info: Pushing 0x 488 to the stack.
+[19:39:17.676] Info: Pushing 0x 311 to the stack.
+[19:39:17.676] Info: Pushing 0x 222 to the stack.
+[19:39:17.676] Info: Pushing 0x 145 to the stack.
+[19:39:17.676] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.676] Info: Pushing 0x 2e to the stack.
+[19:39:17.676] Info: Pushing 0x 48a to the stack.
+[19:39:17.676] Info: Pushing 0x 315 to the stack.
+[19:39:17.676] Info: Pushing 0x 22a to the stack.
+[19:39:17.677] Info: Pushing 0x 154 to the stack.
+[19:39:17.677] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.677] Info: Pushing 0x 2b to the stack.
+[19:39:17.677] Info: Pushing 0x 489 to the stack.
+[19:39:17.677] Info: Pushing 0x 313 to the stack.
+[19:39:17.677] Info: Pushing 0x 226 to the stack.
+[19:39:17.677] Info: Pushing 0x 14d to the stack.
+[19:39:17.677] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.677] Info: Pushing 0x 2e to the stack.
+[19:39:17.677] Info: Pushing 0x 49a to the stack.
+[19:39:17.678] Info: Pushing 0x 335 to the stack.
+[19:39:17.678] Info: Pushing 0x 26a to the stack.
+[19:39:17.678] Info: Pushing 0x 1d4 to the stack.
+[19:39:17.678] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.678] Info: Pushing 0x 2b to the stack.
+[19:39:17.678] Info: Pushing 0x 400 to the stack.
+[19:39:17.678] Info: Pushing 0x 300 to the stack.
+[19:39:17.678] Info: Pushing 0x 200 to the stack.
+[19:39:17.678] Info: Pushing 0x 100 to the stack.
+[19:39:17.678] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.678] Info: Pushing 0x 2e to the stack.
+[19:39:17.678] Info: Pushing 0x 400 to the stack.
+[19:39:17.678] Info: Pushing 0x 300 to the stack.
+[19:39:17.678] Info: Pushing 0x 200 to the stack.
+[19:39:17.680] Info: Pushing 0x 100 to the stack.
+[19:39:17.680] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.680] Info: Pushing 0x 2b to the stack.
+[19:39:17.680] Info: Pushing 0x 40e to the stack.
+[19:39:17.680] Info: Pushing 0x 31c to the stack.
+[19:39:17.680] Info: Pushing 0x 238 to the stack.
+[19:39:17.680] Info: Pushing 0x 171 to the stack.
+[19:39:17.680] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.680] Info: Pushing 0x 2e to the stack.
+[19:39:17.681] Info: Pushing 0x 4e3 to the stack.
+[19:39:17.681] Info: Pushing 0x 3c6 to the stack.
+[19:39:17.681] Info: Pushing 0x 28c to the stack.
+[19:39:17.681] Info: Pushing 0x 118 to the stack.
+[19:39:17.681] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.681] Info: Pushing 0x 2b to the stack.
+[19:39:17.681] Info: Pushing 0x 4dc to the stack.
+[19:39:17.681] Info: Pushing 0x 3b9 to the stack.
+[19:39:17.681] Info: Pushing 0x 272 to the stack.
+[19:39:17.682] Info: Pushing 0x 1e5 to the stack.
+[19:39:17.682] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.682] Info: Pushing 0x 2e to the stack.
+[19:39:17.682] Info: Pushing 0x 4ca to the stack.
+[19:39:17.682] Info: Pushing 0x 395 to the stack.
+[19:39:17.682] Info: Pushing 0x 22b to the stack.
+[19:39:17.682] Info: Pushing 0x 156 to the stack.
+[19:39:17.682] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.682] Info: Pushing 0x 2b to the stack.
+[19:39:17.682] Info: Pushing 0x 4cc to the stack.
+[19:39:17.682] Info: Pushing 0x 399 to the stack.
+[19:39:17.682] Info: Pushing 0x 232 to the stack.
+[19:39:17.682] Info: Pushing 0x 165 to the stack.
+[19:39:17.683] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.683] Info: Pushing 0x 2e to the stack.
+[19:39:17.683] Info: Pushing 0x 4ca to the stack.
+[19:39:17.683] Info: Pushing 0x 395 to the stack.
+[19:39:17.683] Info: Pushing 0x 22b to the stack.
+[19:39:17.683] Info: Pushing 0x 156 to the stack.
+[19:39:17.683] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.683] Info: Pushing 0x 2b to the stack.
+[19:39:17.683] Info: Pushing 0x 46e to the stack.
+[19:39:17.683] Info: Pushing 0x 3dc to the stack.
+[19:39:17.683] Info: Pushing 0x 2b9 to the stack.
+[19:39:17.683] Info: Pushing 0x 173 to the stack.
+[19:39:17.683] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.683] Info: Pushing 0x 2e to the stack.
+[19:39:17.684] Info: Pushing 0x 4e7 to the stack.
+[19:39:17.684] Info: Pushing 0x 3ce to the stack.
+[19:39:17.684] Info: Pushing 0x 29d to the stack.
+[19:39:17.684] Info: Pushing 0x 13b to the stack.
+[19:39:17.684] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.684] Info: Pushing 0x 2b to the stack.
+[19:39:17.684] Info: Pushing 0x 4e6 to the stack.
+[19:39:17.684] Info: Pushing 0x 3cd to the stack.
+[19:39:17.684] Info: Pushing 0x 29b to the stack.
+[19:39:17.684] Info: Pushing 0x 136 to the stack.
+[19:39:17.684] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.684] Info: Pushing 0x 2e to the stack.
+[19:39:17.684] Info: Pushing 0x 46d to the stack.
+[19:39:17.684] Info: Pushing 0x 3db to the stack.
+[19:39:17.685] Info: Pushing 0x 2b7 to the stack.
+[19:39:17.685] Info: Pushing 0x 16f to the stack.
+[19:39:17.685] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.685] Info: Pushing 0x 2b to the stack.
+[19:39:17.685] Info: Pushing 0x 4dd to the stack.
+[19:39:17.685] Info: Pushing 0x 3bb to the stack.
+[19:39:17.685] Info: Pushing 0x 276 to the stack.
+[19:39:17.685] Info: Pushing 0x 1ed to the stack.
+[19:39:17.686] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.686] Info: Pushing 0x 2e to the stack.
+[19:39:17.686] Info: Pushing 0x 4da to the stack.
+[19:39:17.686] Info: Pushing 0x 3b5 to the stack.
+[19:39:17.686] Info: Pushing 0x 26b to the stack.
+[19:39:17.686] Info: Pushing 0x 1d6 to the stack.
+[19:39:17.686] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.686] Info: Pushing 0x 2b to the stack.
+[19:39:17.686] Info: Pushing 0x 4dd to the stack.
+[19:39:17.686] Info: Pushing 0x 3bb to the stack.
+[19:39:17.686] Info: Pushing 0x 276 to the stack.
+[19:39:17.686] Info: Pushing 0x 1ed to the stack.
+[19:39:17.686] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.686] Info: Pushing 0x 2e to the stack.
+[19:39:17.688] Info: Pushing 0x 4da to the stack.
+[19:39:17.688] Info: Pushing 0x 3b5 to the stack.
+[19:39:17.688] Info: Pushing 0x 26b to the stack.
+[19:39:17.688] Info: Pushing 0x 1d6 to the stack.
+[19:39:17.689] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.689] Info: Pushing 0x 2b to the stack.
+[19:39:17.689] Info: Pushing 0x 4d9 to the stack.
+[19:39:17.689] Info: Pushing 0x 3b3 to the stack.
+[19:39:17.689] Info: Pushing 0x 266 to the stack.
+[19:39:17.689] Info: Pushing 0x 1cd to the stack.
+[19:39:17.689] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.689] Info: Pushing 0x 2e to the stack.
+[19:39:17.689] Info: Pushing 0x 49a to the stack.
+[19:39:17.689] Info: Pushing 0x 335 to the stack.
+[19:39:17.689] Info: Pushing 0x 26b to the stack.
+[19:39:17.689] Info: Pushing 0x 1d6 to the stack.
+[19:39:17.689] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.689] Info: Pushing 0x 2b to the stack.
+[19:39:17.689] Info: Pushing 0x 499 to the stack.
+[19:39:17.689] Info: Pushing 0x 333 to the stack.
+[19:39:17.689] Info: Pushing 0x 266 to the stack.
+[19:39:17.690] Info: Pushing 0x 1cd to the stack.
+[19:39:17.690] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.690] Info: Pushing 0x 2e to the stack.
+[19:39:17.690] Info: Pushing 0x 49a to the stack.
+[19:39:17.690] Info: Pushing 0x 335 to the stack.
+[19:39:17.690] Info: Pushing 0x 26a to the stack.
+[19:39:17.690] Info: Pushing 0x 1d4 to the stack.
+[19:39:17.690] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.690] Info: Pushing 0x 2b to the stack.
+[19:39:17.690] Info: Pushing 0x 4bb to the stack.
+[19:39:17.690] Info: Pushing 0x 377 to the stack.
+[19:39:17.690] Info: Pushing 0x 2ef to the stack.
+[19:39:17.690] Info: Pushing 0x 1df to the stack.
+[19:39:17.690] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.690] Info: Pushing 0x 2e to the stack.
+[19:39:17.690] Info: Pushing 0x 4bf to the stack.
+[19:39:17.691] Info: Pushing 0x 37f to the stack.
+[19:39:17.691] Info: Pushing 0x 2fe to the stack.
+[19:39:17.691] Info: Pushing 0x 1fd to the stack.
+[19:39:17.691] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.691] Info: Pushing 0x 2b to the stack.
+[19:39:17.691] Info: Pushing 0x 4bb to the stack.
+[19:39:17.691] Info: Pushing 0x 377 to the stack.
+[19:39:17.691] Info: Pushing 0x 2ef to the stack.
+[19:39:17.691] Info: Pushing 0x 1df to the stack.
+[19:39:17.691] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.691] Info: Pushing 0x 2e to the stack.
+[19:39:17.691] Info: Pushing 0x 4bf to the stack.
+[19:39:17.691] Info: Pushing 0x 37f to the stack.
+[19:39:17.691] Info: Pushing 0x 2fe to the stack.
+[19:39:17.691] Info: Pushing 0x 1fd to the stack.
+[19:39:17.691] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.691] Info: Pushing 0x 2b to the stack.
+[19:39:17.691] Info: Pushing 0x 467 to the stack.
+[19:39:17.692] Info: Pushing 0x 3ce to the stack.
+[19:39:17.692] Info: Pushing 0x 29d to the stack.
+[19:39:17.694] Info: Pushing 0x 13a to the stack.
+[19:39:17.694] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.694] Info: Pushing 0x 2e to the stack.
+[19:39:17.694] Info: Pushing 0x 475 to the stack.
+[19:39:17.694] Info: Pushing 0x 3ea to the stack.
+[19:39:17.694] Info: Pushing 0x 2d5 to the stack.
+[19:39:17.694] Info: Pushing 0x 1ab to the stack.
+[19:39:17.694] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.694] Info: Pushing 0x 2b to the stack.
+[19:39:17.694] Info: Pushing 0x 463 to the stack.
+[19:39:17.694] Info: Pushing 0x 3c6 to the stack.
+[19:39:17.694] Info: Pushing 0x 28d to the stack.
+[19:39:17.694] Info: Pushing 0x 11a to the stack.
+[19:39:17.694] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.694] Info: Pushing 0x 2e to the stack.
+[19:39:17.694] Info: Pushing 0x 435 to the stack.
+[19:39:17.694] Info: Pushing 0x 36a to the stack.
+[19:39:17.694] Info: Pushing 0x 2d5 to the stack.
+[19:39:17.695] Info: Pushing 0x 1ab to the stack.
+[19:39:17.695] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.695] Info: Pushing 0x 2b to the stack.
+[19:39:17.695] Info: Pushing 0x 46e to the stack.
+[19:39:17.695] Info: Pushing 0x 3dc to the stack.
+[19:39:17.695] Info: Pushing 0x 2b9 to the stack.
+[19:39:17.695] Info: Pushing 0x 173 to the stack.
+[19:39:17.695] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.695] Info: Pushing 0x 2e to the stack.
+[19:39:17.695] Info: Pushing 0x 4e7 to the stack.
+[19:39:17.695] Info: Pushing 0x 3ce to the stack.
+[19:39:17.695] Info: Pushing 0x 29d to the stack.
+[19:39:17.695] Info: Pushing 0x 13b to the stack.
+[19:39:17.695] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.696] Info: Pushing 0x 2b to the stack.
+[19:39:17.696] Info: Pushing 0x 40e to the stack.
+[19:39:17.696] Info: Pushing 0x 31c to the stack.
+[19:39:17.696] Info: Pushing 0x 238 to the stack.
+[19:39:17.696] Info: Pushing 0x 171 to the stack.
+[19:39:17.696] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.696] Info: Pushing 0x 2e to the stack.
+[19:39:17.696] Info: Pushing 0x 4e3 to the stack.
+[19:39:17.696] Info: Pushing 0x 3c6 to the stack.
+[19:39:17.696] Info: Pushing 0x 28c to the stack.
+[19:39:17.696] Info: Pushing 0x 118 to the stack.
+[19:39:17.696] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.696] Info: Pushing 0x 2b to the stack.
+[19:39:17.696] Info: Pushing 0x 4ec to the stack.
+[19:39:17.696] Info: Pushing 0x 3d9 to the stack.
+[19:39:17.696] Info: Pushing 0x 2b3 to the stack.
+[19:39:17.696] Info: Pushing 0x 167 to the stack.
+[19:39:17.696] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.696] Info: Pushing 0x 2e to the stack.
+[19:39:17.696] Info: Pushing 0x 4ce to the stack.
+[19:39:17.697] Info: Pushing 0x 39d to the stack.
+[19:39:17.697] Info: Pushing 0x 23b to the stack.
+[19:39:17.697] Info: Pushing 0x 177 to the stack.
+[19:39:17.697] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.697] Info: Pushing 0x 2b to the stack.
+[19:39:17.698] Info: Pushing 0x 4cc to the stack.
+[19:39:17.698] Info: Pushing 0x 399 to the stack.
+[19:39:17.698] Info: Pushing 0x 232 to the stack.
+[19:39:17.698] Info: Pushing 0x 165 to the stack.
+[19:39:17.698] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.698] Info: Pushing 0x 2e to the stack.
+[19:39:17.698] Info: Pushing 0x 4ca to the stack.
+[19:39:17.698] Info: Pushing 0x 395 to the stack.
+[19:39:17.698] Info: Pushing 0x 22b to the stack.
+[19:39:17.698] Info: Pushing 0x 156 to the stack.
+[19:39:17.699] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.699] Info: Pushing 0x 2b to the stack.
+[19:39:17.699] Info: Pushing 0x 4dd to the stack.
+[19:39:17.699] Info: Pushing 0x 3bb to the stack.
+[19:39:17.699] Info: Pushing 0x 276 to the stack.
+[19:39:17.699] Info: Pushing 0x 1ed to the stack.
+[19:39:17.699] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.699] Info: Pushing 0x 2e to the stack.
+[19:39:17.699] Info: Pushing 0x 4da to the stack.
+[19:39:17.699] Info: Pushing 0x 3b5 to the stack.
+[19:39:17.699] Info: Pushing 0x 26b to the stack.
+[19:39:17.699] Info: Pushing 0x 1d6 to the stack.
+[19:39:17.699] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.699] Info: Pushing 0x 2b to the stack.
+[19:39:17.699] Info: Pushing 0x 4dc to the stack.
+[19:39:17.699] Info: Pushing 0x 3b9 to the stack.
+[19:39:17.699] Info: Pushing 0x 272 to the stack.
+[19:39:17.700] Info: Pushing 0x 1e5 to the stack.
+[19:39:17.700] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.700] Info: Pushing 0x 2e to the stack.
+[19:39:17.700] Info: Pushing 0x 4ca to the stack.
+[19:39:17.700] Info: Pushing 0x 395 to the stack.
+[19:39:17.700] Info: Pushing 0x 22b to the stack.
+[19:39:17.700] Info: Pushing 0x 156 to the stack.
+[19:39:17.700] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.700] Info: Pushing 0x 2b to the stack.
+[19:39:17.700] Info: Pushing 0x 499 to the stack.
+[19:39:17.700] Info: Pushing 0x 333 to the stack.
+[19:39:17.700] Info: Pushing 0x 266 to the stack.
+[19:39:17.700] Info: Pushing 0x 1cd to the stack.
+[19:39:17.700] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.700] Info: Pushing 0x 2e to the stack.
+[19:39:17.700] Info: Pushing 0x 49a to the stack.
+[19:39:17.700] Info: Pushing 0x 335 to the stack.
+[19:39:17.700] Info: Pushing 0x 26a to the stack.
+[19:39:17.701] Info: Pushing 0x 1d4 to the stack.
+[19:39:17.701] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.702] Info: Pushing 0x 2b to the stack.
+[19:39:17.702] Info: Pushing 0x 49f to the stack.
+[19:39:17.702] Info: Pushing 0x 33f to the stack.
+[19:39:17.702] Info: Pushing 0x 27e to the stack.
+[19:39:17.702] Info: Pushing 0x 1fd to the stack.
+[19:39:17.702] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.702] Info: Pushing 0x 2e to the stack.
+[19:39:17.702] Info: Pushing 0x 4fb to the stack.
+[19:39:17.702] Info: Pushing 0x 3f7 to the stack.
+[19:39:17.702] Info: Pushing 0x 2ee to the stack.
+[19:39:17.702] Info: Pushing 0x 1dc to the stack.
+[19:39:17.702] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.702] Info: Pushing 0x 2b to the stack.
+[19:39:17.702] Info: Pushing 0x 4bb to the stack.
+[19:39:17.702] Info: Pushing 0x 377 to the stack.
+[19:39:17.702] Info: Pushing 0x 2ef to the stack.
+[19:39:17.703] Info: Pushing 0x 1df to the stack.
+[19:39:17.703] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.703] Info: Pushing 0x 2e to the stack.
+[19:39:17.703] Info: Pushing 0x 4bf to the stack.
+[19:39:17.703] Info: Pushing 0x 37f to the stack.
+[19:39:17.703] Info: Pushing 0x 2fe to the stack.
+[19:39:17.703] Info: Pushing 0x 1fd to the stack.
+[19:39:17.703] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.703] Info: Pushing 0x 2b to the stack.
+[19:39:17.703] Info: Pushing 0x 4b9 to the stack.
+[19:39:17.703] Info: Pushing 0x 373 to the stack.
+[19:39:17.703] Info: Pushing 0x 2e7 to the stack.
+[19:39:17.703] Info: Pushing 0x 1cf to the stack.
+[19:39:17.703] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.703] Info: Pushing 0x 2e to the stack.
+[19:39:17.703] Info: Pushing 0x 49e to the stack.
+[19:39:17.703] Info: Pushing 0x 33d to the stack.
+[19:39:17.703] Info: Pushing 0x 27a to the stack.
+[19:39:17.705] Info: Pushing 0x 1f5 to the stack.
+[19:39:17.705] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.705] Info: Pushing 0x 2b to the stack.
+[19:39:17.705] Info: Pushing 0x 433 to the stack.
+[19:39:17.705] Info: Pushing 0x 366 to the stack.
+[19:39:17.705] Info: Pushing 0x 2cd to the stack.
+[19:39:17.705] Info: Pushing 0x 19a to the stack.
+[19:39:17.705] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.705] Info: Pushing 0x 2e to the stack.
+[19:39:17.705] Info: Pushing 0x 435 to the stack.
+[19:39:17.705] Info: Pushing 0x 36a to the stack.
+[19:39:17.705] Info: Pushing 0x 2d4 to the stack.
+[19:39:17.705] Info: Pushing 0x 1a9 to the stack.
+[19:39:17.705] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.705] Info: Pushing 0x 2b to the stack.
+[19:39:17.705] Info: Pushing 0x 43e to the stack.
+[19:39:17.705] Info: Pushing 0x 37c to the stack.
+[19:39:17.705] Info: Pushing 0x 2f9 to the stack.
+[19:39:17.705] Info: Pushing 0x 1f3 to the stack.
+[19:39:17.705] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.705] Info: Pushing 0x 2e to the stack.
+[19:39:17.705] Info: Pushing 0x 4e7 to the stack.
+[19:39:17.706] Info: Pushing 0x 3ce to the stack.
+[19:39:17.706] Info: Pushing 0x 29c to the stack.
+[19:39:17.706] Info: Pushing 0x 139 to the stack.
+[19:39:17.706] Info: Returning from 0x a8 to 0x 2e
+[19:39:22.038] Info: Pushing 0x 153 to the stack.
+[19:39:22.042] Info: Returning from 0x2c20 to 0x 153
+[19:39:22.042] Info: Pushing 0x 159 to the stack.
+[19:39:22.042] Info: Returning from 0x2b94 to 0x 159
+[19:39:22.042] Info: Pushing 0x 15e to the stack.
+[19:39:22.056] Info: Returning from 0x2c47 to 0x 15e
+[19:39:22.136] Info: Pushing 0x 19a to the stack.
+[19:39:22.136] Info: Returning from 0x71de to 0x 19a
+[19:39:22.136] Info: Pushing 0x 1c5 to the stack.
+[19:39:22.136] Info: Pushing 0x1f8c to the stack.
+[19:39:22.136] Info: Pushing 0x 80 to the stack.
+[19:39:22.136] Info: Pushing 0x ff to the stack.
+[19:39:22.136] Info: Pushing 0xf810 to the stack.
+[19:39:22.136] Info: Pushing 0xc000 to the stack.
+[19:39:22.139] Info: Returning from 0x2c38 to 0x1f8c
+[19:39:22.139] Info: Returning from 0x1f8d to 0x 1c5
+[19:39:22.139] Info: Pushing 0x 1c8 to the stack.
+[19:39:22.139] Info: Pushing 0x 203 to the stack.
+[19:39:22.139] Info: Pushing 0x 80 to the stack.
+[19:39:22.139] Info: Pushing 0x 168 to the stack.
+[19:39:22.139] Info: Pushing 0xf810 to the stack.
+[19:39:22.140] Info: Pushing 0xc100 to the stack.
+[19:39:22.144] Info: Returning from 0x2c38 to 0x 203
+[19:39:22.144] Info: Pushing 0x 206 to the stack.
+[19:39:22.144] Info: Pushing 0x 168 to the stack.
+[19:39:22.144] Info: Pushing 0x1b92 to the stack.
+[19:39:22.144] Info: Pushing 0x 168 to the stack.
+[19:39:22.144] Info: Pushing 0xf810 to the stack.
+[19:39:22.144] Info: Pushing 0xc100 to the stack.
+[19:39:22.144] Info: Pushing 0x 0 to the stack.
+[19:39:22.144] Info: Pushing 0x3eff to the stack.
+[19:39:22.144] Info: Returning from 0x27bd to 0x1b92
+[19:39:22.144] Info: Pushing 0xffa0 to the stack.
+[19:39:22.144] Info: Pushing 0x1b96 to the stack.
+[19:39:22.144] Info: Pushing 0xc100 to the stack.
+[19:39:22.144] Info: Pushing 0x 168 to the stack.
+[19:39:22.144] Info: Pushing 0x1bae to the stack.
+[19:39:22.144] Info: Pushing 0x 168 to the stack.
+[19:39:22.144] Info: Pushing 0xf810 to the stack.
+[19:39:22.145] Info: Returning from 0x1f4d to 0x1bae
+[19:39:22.145] Info: Returning from 0x1bc4 to 0x1b96
+[19:39:22.145] Info: Pushing 0x1b99 to the stack.
+[19:39:22.145] Info: Returning from 0x1f7f to 0x1b99
+[19:39:22.145] Warning: Attempting to write to ROM at address: 0x 0.
+[19:39:22.145] Info: Pushing 0x 167 to the stack.
+[19:39:22.145] Info: Pushing 0x1b92 to the stack.
+[19:39:22.145] Info: Pushing 0x 167 to the stack.
+[19:39:22.145] Info: Pushing 0x 0 to the stack.
+[19:39:22.145] Info: Pushing 0xc101 to the stack.
+[19:39:22.145] Info: Pushing 0x f to the stack.
+[19:39:22.145] Info: Pushing 0x3eff to the stack.
+[19:39:22.145] Info: Returning from 0x27bd to 0x1b92
+[19:39:22.145] Info: Pushing 0xffa0 to the stack.
+[19:39:22.145] Info: Pushing 0x1b96 to the stack.
+[19:39:22.145] Info: Pushing 0xc101 to the stack.
+[19:39:22.145] Info: Pushing 0x 167 to the stack.
+[19:39:22.145] Info: Pushing 0x1bae to the stack.
+[19:39:22.145] Info: Pushing 0x 167 to the stack.
+[19:39:22.145] Info: Pushing 0x 0 to the stack.
+[19:39:22.146] Info: Returning from 0x1f4d to 0x1bae
+[19:39:22.147] Info: Returning from 0x1bc4 to 0x1b96
+[19:39:22.147] Info: Pushing 0x1b99 to the stack.
+[19:39:22.147] Info: Returning from 0x1f7f to 0xffa0
+[19:39:22.147] Info: Pushing 0xfbc3 to the stack.
+[19:39:22.148] Info: Returning from 0xff8a to 0x 0
+[19:39:22.148] Info: Pushing 0xfbc3 to the stack.
+[19:39:22.152] Info: Returning from 0xff47 to 0x 0
+[19:39:22.152] Info: Pushing 0xfbc3 to the stack.
+[19:39:22.152] Error: Unimplemented opcode 0xfd at 0xfbc4!
diff --git a/playing-coffee - Copy/pom.xml b/playing-coffee - Copy/pom.xml
new file mode 100644
index 0000000..beaa1be
--- /dev/null
+++ b/playing-coffee - Copy/pom.xml
@@ -0,0 +1,15 @@
+
+ 4.0.0
+ sixtenhugosson
+ playing-coffee
+ 0.0.1-SNAPSHOT
+ playing-coffee
+ A GameBoy emulator written in Java.
+
+
+ junit
+ junit
+ 4.13.1
+
+
+
\ No newline at end of file
diff --git a/playing-coffee - Copy/roms/cgb_sound/cgb_sound.gb b/playing-coffee - Copy/roms/cgb_sound/cgb_sound.gb
new file mode 100644
index 0000000..dc50471
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/cgb_sound.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/readme.txt b/playing-coffee - Copy/roms/cgb_sound/readme.txt
new file mode 100644
index 0000000..5d8d188
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/readme.txt
@@ -0,0 +1,86 @@
+Game Boy Sound Hardware Tests
+-----------------------------
+These tests verify aspects of the sound hardware that the CPU can
+observe. The ROMs and GBSs are either for DMG or CGB hardware, as there
+are several differences.
+
+
+Multi-ROM
+---------
+In the main directory is a single ROM/GBS which runs all the tests. It
+prints a test's number, runs the test, then "ok" if it passes, otherwise
+a failure code. Once all tests have completed it either reports that all
+tests passed, or reports the number of the first failed test as the
+result code (1 = first). Finally, it makes several beeps. If a test
+fails, it can be run on its own by finding the corresponding ROM/GBS in
+the singles directories.
+
+Ths compact format on screen is to avoid having the results scroll off
+the top, so the test can be started and allowed to run without having to
+constantly monitor the display.
+
+
+Failure information
+-------------------
+For more information about a failure code or information printed, see
+the test's source code in source/. To find failure code N, search for
+"set_test N", which will usually be before the subtest which failed.
+
+
+Flashes, clicks, other glitches
+-------------------------------
+Some tests might need to turn the screen off and on, or cause slight
+audio clicks. This does not indicate failure, and should be ignored.
+Only the test result reported at the end is important, unless stated
+otherwise.
+
+
+LCD support
+-----------
+Tests generally print information on screen. The tests will work fine if
+run on an emulator with NO LCD support, or as an GBS which has no
+inherent screen; in particular, the VBL wait routine has a timeout in
+case LY doesn't reflect the current LCD line. The text printing will
+also work if the LCD doesn't support scrolling.
+
+
+Output to memory
+----------------
+Text output and the final result are also written to memory at $A000,
+allowing testing a very minimal emulator that supports little more than
+CPU and RAM. To reliably indicate that the data is from a test and not
+random data, $A001-$A003 are written with a signature: $DE,$B0,$61. If
+this is present, then the text string and final result status are valid.
+
+$A000 holds the overall status. If the test is still running, it holds
+$80, otherwise it holds the final result code.
+
+All text output is appended to a zero-terminated string at $A004. An
+emulator could regularly check this string for any additional
+characters, and output them, allowing real-time text output, rather than
+just printing the final output at the end.
+
+
+GBS versions
+------------
+Many GBS-based tests require that the GBS player either not interrupt
+the init routine with the play routine, or if they do, not interrupt the
+play routine again if it hasn't returned yet. This is because many tests
+need to run for a while without returning.
+
+In addition to the other text output methods described above, GBS builds
+report essential information bytes audibly, including the final result.
+A byte is reported as a series of tones. The code is in binary, with a
+low tone for 0 and a high tone for 1. The first tone is always a zero. A
+final code of 0 means passed, 1 means failure, and 2 or higher indicates
+a specific reason as listed in the source code by the corresponding
+set_code line. Examples:
+
+Tones Binary Decimal Meaning
+- - - - - - - - - - - - - - - - - - - -
+low 0 0 passed
+low high 01 1 failed
+low high low 010 2 error 2
+
+--
+Shay Green
diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/01-registers.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/01-registers.gb
new file mode 100644
index 0000000..be180ba
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/rom_singles/01-registers.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/02-len ctr.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/02-len ctr.gb
new file mode 100644
index 0000000..eb5ca82
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/rom_singles/02-len ctr.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/03-trigger.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/03-trigger.gb
new file mode 100644
index 0000000..28b3586
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/rom_singles/03-trigger.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/04-sweep.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/04-sweep.gb
new file mode 100644
index 0000000..75d6366
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/rom_singles/04-sweep.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/05-sweep details.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/05-sweep details.gb
new file mode 100644
index 0000000..383e0c1
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/rom_singles/05-sweep details.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/06-overflow on trigger.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/06-overflow on trigger.gb
new file mode 100644
index 0000000..b2d5c75
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/rom_singles/06-overflow on trigger.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/07-len sweep period sync.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/07-len sweep period sync.gb
new file mode 100644
index 0000000..31cce89
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/rom_singles/07-len sweep period sync.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/08-len ctr during power.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/08-len ctr during power.gb
new file mode 100644
index 0000000..a3120eb
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/rom_singles/08-len ctr during power.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/09-wave read while on.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/09-wave read while on.gb
new file mode 100644
index 0000000..1e4a78c
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/rom_singles/09-wave read while on.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/10-wave trigger while on.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/10-wave trigger while on.gb
new file mode 100644
index 0000000..56f0e90
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/rom_singles/10-wave trigger while on.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/11-regs after power.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/11-regs after power.gb
new file mode 100644
index 0000000..f6c84b4
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/rom_singles/11-regs after power.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/12-wave.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/12-wave.gb
new file mode 100644
index 0000000..e488e5e
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/rom_singles/12-wave.gb differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/01-registers.s b/playing-coffee - Copy/roms/cgb_sound/source/01-registers.s
new file mode 100644
index 0000000..ea9f5d5
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/01-registers.s
@@ -0,0 +1,117 @@
+; - APU registers always have some bits set when read back.
+; - Wave memory can be read back freely.
+; - When powered off, registers are cleared, except high bit of NR52.
+; - While off, register writes are ignored, but not reads.
+; - Wave RAM is always readable and writable, and unaffected by power.
+
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ set_test 2,"NR10-NR51 and wave RAM write/read"
+ ld d,0
+- call test_rw
+ inc d
+ jr nz,-
+
+ set_test 3,"NR52 write/read"
+ wreg NR52,$00
+ lda NR52
+ cp $70
+ jp nz,test_failed
+ wreg NR52,$FF
+ lda NR52
+ cp $F0
+ jp nz,test_failed
+
+ set_test 4,"Powering APU shouldn't affect wave"
+ ld a,$37
+ call fill_wave
+ wreg NR52,$00
+
+ ; Verify that wave RAM is unchanged
+ ld hl,WAVE
+- ld a,(hl+)
+ cp $37
+ jp nz,test_failed
+ ld a,l
+ cp $40
+ jr nz,-
+ wreg NR52,$80 ; on
+
+ set_test 5,"Powering APU off should write 0 to all regs"
+ ld a,$FF
+ call fill_apu_regs
+ wreg NR52,$00
+ wreg NR52,$80
+ call regs_should_be_clear
+
+ set_test 6,"When off, should ignore writes to registers"
+ wreg NR52,$00
+ ld a,$FF
+ call fill_apu_regs
+ wreg NR52,$80
+ call regs_should_be_clear
+ wreg NR52,$80
+
+ set_test 7,"When off, should allow normal register reads"
+ wreg NR52,$00
+ call regs_should_be_clear
+ wreg NR52,$80
+
+ jp tests_passed
+
+regs_should_be_clear:
+ ld bc,masks
+ ld hl,NR10
+- ld a,(bc)
+ cp (hl)
+ jp nz,test_failed
+ inc bc
+ inc l
+ ld a,l
+ cp disabled clocks as well
+ ld a,1
+ call end
+
+ call begin
+ wchn 4,$40 ; enable length
+ wchn 1,-2 ; length = 2
+ wchn 4,$40 ; enabled -> enabled doesn't clock
+ wchn 4,$00 ; enabled -> disabled doesn't clock
+ ld a,2
+ call end
+.else
+ set_test 4,"Anything besides enabling shouldnt't clock"
+ call begin
+ wchn 4,$40 ; enable length
+ wchn 1,-2 ; length = 2
+ wchn 4,$40 ; enabled -> enabled doesn't clock
+ wchn 4,$00 ; enabled -> disabled doesn't clock
+ wchn 4,$00 ; disabled -> disabled doesn't clock
+ ld a,2
+ call end
+.endif
+
+ set_test 5,"If clock makes length zero, should disable chan"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ lda chan_mask
+ ld b,a
+ lda NR52 ; channel now disabled
+ and b
+ jp nz,test_failed
+
+ set_test 6,"If length already reached zero, shouldn't clock"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,0
+ wchn 4,$40 ; no clock; length still 0
+ wchn 4,0
+ wchn 4,$40 ; no clock; length still 0
+ lda chan_maxlen; end triggers channel, which loads it with max length
+ call end
+
+ set_test 7,"Trigger should un-freeze length that reached zero"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,$00 ; disable
+ wchn 4,$80 ; trigger unfreezes length, so it takes on maximum value
+ delay_clocks 8192
+ wchn 4,$40 ; enable
+ delay_apu 2 ; clock length by 2
+ lda chan_maxlen
+ sub 2
+ call end_nodelay
+
+ set_test 8,"Trigger that un-freezes enabled length should clock it"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,$00 ; disable
+ wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it
+ lda chan_maxlen
+ dec a
+ call end_nodelay
+
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it
+ lda chan_maxlen
+ dec a
+ call end_nodelay
+
+ set_test 9,"Triggering that clocks length of 1 ","should clock twice and shouldn't freeze"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$C0 ; trigger and enable
+ ; First length counter is enabled, which clocks it to 0 and freezes it
+ ; Trigger unfreezes length counter, which clocks it AGAIN
+ ; The result is the same as the previous test, which enables separately
+ lda chan_maxlen
+ dec a
+ call end_nodelay
+
+ set_test 10,"Trigger shouldn't otherwise affect length"
+ call begin
+ wchn 1,0 ; length = max
+ delay_clocks 8192
+ wchn 4,$80 ; trigger
+ lda chan_maxlen
+ call end_nodelay
+
+.ifndef CGB_02
+ call begin
+ wchn 1,0 ; length = max
+ wchn 4,$80 ; trigger
+ lda chan_maxlen
+ call end
+
+ call begin
+ wchn 1,-2 ; length = 2
+ wchn 4,$80 ; trigger
+ ld a,2
+ call end
+.endif
+
+ set_test 11,"Disabled DAC shouldn't stop other trigger effects"
+ call begin
+ wchn 0,$00 ; disable wave DAC
+ wchn 2,$07 ; disable square/noise DAC
+ wchn 1,-1
+ wchn 4,$C0 ; clocks length, which becomes max
+ wchn 0,$80 ; enable wave DAC
+ wchn 2,$08 ; enable square/noise DAC
+ wchn 4,$80 ; trigger
+ lda chan_maxlen
+ dec a
+ call end
+
+ set_test 12,"Other trigger effects should still occur when disabled"
+ call sync_apu
+ wchn 0,0
+ wchn 4,0
+ wchn 1,-1
+ wchn 4,$40 ; len = 0
+ wchn 4,0
+ wchn 4,$40 ; len = 0
+ wchn 4,$80 ; len = max
+ wchn 4,$40 ; len = max-1
+ wchn 4,0
+ wchn 4,$40 ; len = max-2
+ wchn 0,$80 ; enable now
+ wchn 4,$C0
+ lda chan_maxlen
+ sub 3
+ call delay_apu_cycles
+ lda chan_mask
+ ld b,a
+ lda NR52
+ and b
+ jp z,test_failed
+ delay_apu 1
+ lda NR52
+ and b
+ jp nz,test_failed
+
+ ret
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/04-sweep.s b/playing-coffee - Copy/roms/cgb_sound/source/04-sweep.s
new file mode 100644
index 0000000..17ac4e4
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/04-sweep.s
@@ -0,0 +1,120 @@
+; Calc = calculation of new frequency and check for > $7FF
+; Update = modification of frequency with new calculated value
+.include "shell.inc"
+.include "apu.s"
+
+begin:
+ call sync_sweep
+ wreg NR14,$40
+ wreg NR11,-$21
+ wreg NR12,$08
+ ret
+
+should_be_almost_off:
+ lda NR52
+ and $01
+ jp z,test_failed
+ delay_apu 1
+should_be_off:
+ lda NR52
+ and $01
+ jp nz,test_failed
+ ret
+
+main:
+ set_test 2,"If shift>0, calculates on trigger"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$FF
+ wreg NR14,$C7
+ call should_be_off
+ call begin
+ wreg NR10,$11
+ wreg NR13,$FF
+ wreg NR14,$C7
+ call should_be_off
+
+ set_test 3,"If shift=0, doesn't calculate on trigger"
+ call begin
+ wreg NR10,$10
+ wreg NR13,$FF
+ wreg NR14,$C7
+ delay_apu 1
+ call should_be_almost_off
+
+ set_test 4,"If period=0, doesn't calculate"
+ call begin
+ wreg NR10,$00
+ wreg NR13,$FF
+ wreg NR14,$C7
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 5,"After updating frequency, calculates a second time"
+ call begin
+ wreg NR10,$11
+ wreg NR13,$00
+ wreg NR14,$C5
+ delay_apu 1
+ call should_be_almost_off
+
+ set_test 6,"If calculation>$7FF, disables channel"
+ call begin
+ wreg NR10,$02
+ wreg NR13,$67
+ wreg NR14,$C6
+ call should_be_off
+
+ set_test 7,"If calculation<=$7FF, doesn't disable channel"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$55
+ wreg NR14,$C5
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 8,"If shift=0 and period>0, trigger enables"
+ call begin
+ wreg NR10,$10
+ wreg NR13,$FF
+ wreg NR14,$C3
+ delay_apu 2
+ wreg NR10,$11
+ delay_apu 1
+ call should_be_almost_off
+
+ set_test 9,"If shift>0 and period=0, trigger enables"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$FF
+ wreg NR14,$C3
+ delay_apu 15
+ wreg NR10,$11
+ call should_be_almost_off
+
+ set_test 10,"If shift=0 and period=0, trigger disables"
+ call begin
+ wreg NR10,$08
+ wreg NR13,$FF
+ wreg NR14,$C3
+ wreg NR10,$11
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 11,"If shift=0, doesn't update"
+ call begin
+ wreg NR10,$10
+ wreg NR13,$FF
+ wreg NR14,$C3
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 12,"If period=0, doesn't update"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$00
+ wreg NR14,$C5
+ delay_apu $20
+ call should_be_almost_off
+
+ jp tests_passed
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/05-sweep details.s b/playing-coffee - Copy/roms/cgb_sound/source/05-sweep details.s
new file mode 100644
index 0000000..1519b68
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/05-sweep details.s
@@ -0,0 +1,121 @@
+; Calc = calculation of new frequency and check for > $7FF
+; Update = modification of frequency with new calculated value
+.include "shell.inc"
+.include "apu.s"
+
+begin:
+ call sync_sweep
+ wreg NR14,$40
+ wreg NR11,-$20
+ wreg NR12,$08
+ ret
+
+should_be_almost_off:
+ lda NR52
+ and $01
+ jp z,test_failed
+ delay_apu 1
+should_be_off:
+ lda NR52
+ and $01
+ jp nz,test_failed
+ ret
+
+main:
+ set_test 2,"Timer treats period 0 as 8"
+ call begin
+ wreg NR10,$11
+ wreg NR13,$00
+ wreg NR14,$C2
+ delay_apu 1
+ wreg NR10,$01 ; sweep enabled
+ delay_apu 3
+ wreg NR10,$11 ; non-zero period so calc will occur when timer reloads
+ delay_apu $11
+ call should_be_almost_off
+
+ set_test 3,"Makes private copy of frequency on trigger"
+ call begin
+ wreg NR10,$12
+ wreg NR13,$04
+ wreg NR14,$80
+ wreg NR13,$00
+ delay_apu $39
+ call should_be_almost_off
+
+ set_test 4,"Exiting negate mode after calculation disables channel"
+ call begin
+ wreg NR10,$09 ; since shift > 0, calculates sweep value at init
+ wreg NR13,$00
+ wreg NR14,$C0
+ delay_apu 2
+ wreg NR10,$10 ; neg->pos, so disables channel
+ call should_be_off
+
+ set_test 5,"Ending negate after it maybe changed freq disables chan"
+ call begin
+ wreg NR10,$10 ; enable sweep
+ wreg NR13,$00
+ wreg NR14,$C0
+ delay_apu 2
+ wreg NR10,$18 ; negate mode
+ delay_apu 2
+ wreg NR10,$10 ; neg->pos, so disables channel
+ call should_be_off
+
+ set_test 6,"Ending negate mode any other way doesn't disable channel"
+ call begin
+ wreg NR10,$1F ; use negate mode once
+ wreg NR14,$C0
+ delay_apu 2
+ wreg NR10,$18 ; since period > 0, doesn't calculate at init
+ wreg NR13,$00
+ wreg NR14,$C0
+ delay_apu 1 ; no sweep clock here
+ wreg NR10,$10 ; pos mode before neg mode ever used
+ delay_apu 1 ; sweep clock occurs here
+ wreg NR10,$0F ; now let neg mode be seen once, but period = 0 so no calculation is made
+ delay_apu 2 ; sweep clock occurs here
+ wreg NR10,$10 ; doesn't affect channel
+ delay_apu 2 ; sweep clock occurs here
+ wreg NR10,$1F ; let neg mode get used
+ delay_apu 18
+ wreg NR10,$79 ; period and shift can be changed without channel disabling
+ delay_apu 5
+ call should_be_almost_off
+
+ set_test 7,"Subtract mode uses two's complement"
+ call begin
+ delay 2048 ; avoids extra length clocking on CGB-02
+ wreg NR10,$1C
+ wreg NR13,$B0
+ wreg NR14,$85
+ delay_apu 2
+ wreg NR10,$01
+ wreg NR14,$C5
+ delay_apu $1F
+ call should_be_almost_off
+
+ set_test 8,"Subtract mode uses two's complement (upper bound)"
+ call begin
+ wreg NR10,$1C
+ wreg NR13,$B1
+ wreg NR14,$85
+ delay_apu 2
+ wreg NR10,$01
+ wreg NR14,$C5
+ call should_be_off
+
+ set_test 9,"Update channel frequency only when period is reloaded"
+ call begin
+ wreg NR10,$74
+ wreg NR13,$06
+ wreg NR14,$85
+ delay_apu 14 ; just reloaded
+ wreg NR13,$06
+ delay_apu 13 ; if 14, fails
+ wreg NR10,$11
+ wreg NR14,$85 ; just before next reload, so freq is still $506
+ call should_be_almost_off
+
+ jp tests_passed
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/06-overflow on trigger.s b/playing-coffee - Copy/roms/cgb_sound/source/06-overflow on trigger.s
new file mode 100644
index 0000000..2ebc236
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/06-overflow on trigger.s
@@ -0,0 +1,64 @@
+; Finds highest and lowest frequencies that don't overflow
+; immediately on trigger, for NR10 values of $00-$07
+
+.include "shell.inc"
+.include "apu.s"
+
+main:
+
+ ; DMG-06:
+ ; 0555 0666 071C 0787 07C1 07E0 07F0
+
+ wreg NR12,8
+ ld d,$01
+shift_loop:
+ ld a,d
+ sta NR10
+ ld bc,$87FF
+- ld a,c
+ sta NR13
+ ld a,b
+ sta NR14
+ delay_clocks 40
+ lda NR52
+ and 1
+ jr nz,+
+ dec bc
+ bit 6,b
+ jr z,-
++ res 7,b
+ call print_bc
+ inc d
+ bit 3,d
+ jr z,shift_loop
+ call print_newline
+ check_crc $F604603B
+
+ ; DMG-05, DMG-06, DMG-09, CGB-04, CGB-05:
+ ; 0556 0667 071D 0788 07C2 07E1 07F1
+
+ wreg NR12,8
+ ld d,$01
+shift_loop2:
+ ld a,d
+ sta NR10
+ ld bc,$8000
+- ld a,c
+ sta NR13
+ ld a,b
+ sta NR14
+ delay_clocks 40
+ lda NR52
+ and 1
+ jr z,+
+ inc bc
+ bit 6,b
+ jr z,-
++ res 7,b
+ call print_bc
+ inc d
+ bit 3,d
+ jr z,shift_loop2
+ check_crc $5A1697EE
+
+ jp tests_passed
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/07-len sweep period sync.s b/playing-coffee - Copy/roms/cgb_sound/source/07-len sweep period sync.s
new file mode 100644
index 0000000..6b3ed7e
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/07-len sweep period sync.s
@@ -0,0 +1,106 @@
+; Tests length and sweep periods, and synchronization between the two
+.include "shell.inc"
+.include "apu.s"
+
+test_timing:
+ ; Time how long until next length clock
+- inc de
+ ld a,(NR52)
+ and $01
+ jr nz,-
+
+ ;call print_de
+
+ ; Error if not in 0...4 range
+ ld a,d
+ cp 0
+ jp nz,test_failed
+ ld a,e
+ cp 5
+ jp nc,test_failed
+ ret
+
+main:
+
+ set_test 2,"Length period is wrong"
+ call sync_apu
+ wreg NR14,$40 ; avoids extra length clock
+ wreg NR11,$3F ; length = $01
+ wreg NR12,$08 ; silent without disabling channel
+ wreg NR14,$C0 ; start length
+ ld de,-$170
+ call test_timing
+
+ set_test 3,"Sweep period is wrong"
+ call sync_sweep
+ wreg NR10,$10 ; sweep period = 1
+ wreg NR12,$08 ; silent without disabling channel
+ wreg NR13,$FF ; max freq
+ wreg NR14,$87 ; start
+ ld de,-$2E4
+ call test_timing
+
+ set_test 4,"Sweep clock is synchronized with length"
+ call sync_sweep
+ wreg NR14,$40 ; avoids extra length clock
+ wreg NR11,$3F ; length = $01
+ wreg NR12,$08 ; silent without disabling channel
+ wreg NR14,$C0 ; start length
+ ld de,-$170
+ call test_timing
+
+ set_test 5,"Powering up APU MODs next frame time with 8192"
+ call sync_apu
+ ld de,-$16F
+ call test_power
+
+ call sync_apu
+ ld de,-$B5
+ call test_power_off
+
+ call sync_apu
+ delay_clocks 8192
+ ld de,-$B5
+ call test_power
+
+ call sync_apu
+ delay_clocks 8192
+ ld de,-$B5
+ call test_power_off
+
+ call sync_apu
+ ld de,-$B5
+ wreg NR52,$00 ; power off
+ delay_clocks 8192
+ call test_power
+
+ set_test 6,"Powering up APU resets 128 Hz sweep divider"
+ call sync_sweep
+ ld de,-$229
+ call test_power2
+
+ call sync_sweep
+ delay_apu 1
+ ld de,-$229
+ call test_power2
+
+ jp tests_passed
+
+test_power_off:
+ wreg NR52,$00 ; power off
+test_power:
+ wreg NR52,$80 ; power on
+ wreg NR14,$40
+ wreg NR11,-1 ; length = 1
+ wreg NR12,8
+ wreg NR14,$C0
+ jp test_timing
+
+test_power2:
+ wreg NR52,$00 ; power off
+ wreg NR52,$80 ; power on
+ wreg NR10,$11
+ wreg NR12,8
+ wreg NR13,$00
+ wreg NR14,$84
+ jp test_timing
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/08-len ctr during power.s b/playing-coffee - Copy/roms/cgb_sound/source/08-len ctr during power.s
new file mode 100644
index 0000000..1ea218a
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/08-len ctr during power.s
@@ -0,0 +1,84 @@
+; On CGB, length counters are reset when powered up.
+; On DMG, they are unaffected, and not clocked.
+
+;.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+enable_len_ctrs:
+ wreg NR22,8
+ wreg NR24,$C0
+ wreg NR12,8
+ wreg NR14,$C0
+ wreg NR30,$80
+ wreg NR34,$C0
+ wreg NR42,8
+ wreg NR44,$C0
+ ret
+
+main:
+ call sync_apu
+
+ ld a,0
+ call fill_apu_regs
+
+ ; Load length counters
+ wreg NR41,-$33
+ wreg NR31,-$44
+ wreg NR11,-$11
+ wreg NR21,-$22
+
+ delay_clocks 8192
+ call enable_len_ctrs
+
+ ; Power down. Comment out to see what would
+ ; happen if length counters did run.
+ wreg NR52,$00
+
+ ; Try to enable length counters
+ call enable_len_ctrs
+
+ ; Give plenty of time for them to be clocked
+ delay_msec 250
+
+ ; Power back on and wait a bit longer
+ wreg NR52,$80
+ ;call enable_len_ctrs ; can't do this here
+ delay_clocks 2048
+
+ ; Get values from length counters
+ wreg NR22,8
+ wreg NR24,$C0
+ ld a,$02
+ call get_len_a
+ push af
+
+ wreg NR12,8
+ wreg NR14,$C0
+ ld a,$01
+ call get_len_a
+ push af
+
+ wreg NR30,$80
+ wreg NR34,$C0
+ ld a,$04
+ call get_len_a
+ push af
+
+ wreg NR42,8
+ wreg NR44,$C0
+ ld a,$08
+ call get_len_a
+
+ ; Print them
+ call print_a
+ pop af
+ call print_a
+ pop af
+ call print_a
+ pop af
+ call print_a
+
+ check_crc_dmg_cgb $32F0CFBB,$3CF589B4
+ jp tests_passed
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/09-wave read while on.s b/playing-coffee - Copy/roms/cgb_sound/source/09-wave read while on.s
new file mode 100644
index 0000000..2d06a77
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/09-wave read while on.s
@@ -0,0 +1,41 @@
+; Reads from wave RAM while playing, each time 2
+; clocks later.
+
+;.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ wreg NR51,0 ; mute sound
+ loop_n_times test,69
+ check_crc_dmg_cgb $118A3620,$270DA9A3
+ jp tests_passed
+
+test:
+ add $99
+ ld b,a
+
+ ; Reload wave and have its first
+ ; sample read occur 2 clocks earlier
+ ; each loop iteration
+ ld hl,wave
+ call load_wave
+ wreg NR30,$80 ; enable
+ wreg NR32,$00 ; silent
+ ld a,b
+ sta NR33 ; period
+ wreg NR34,$87 ; start
+
+ ; Read from wave
+ wreg NR33,-2 ; period = 4
+ delay_clocks 176
+ lda WAVE
+
+ call print_a
+
+ ret
+
+wave:
+ .byte $00,$11,$22,$33,$44,$55,$66,$77
+ .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/10-wave trigger while on.s b/playing-coffee - Copy/roms/cgb_sound/source/10-wave trigger while on.s
new file mode 100644
index 0000000..d7ec063
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/10-wave trigger while on.s
@@ -0,0 +1,49 @@
+; Retriggers wave without stopping first
+
+;.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ wreg NR51,0 ; mute sound
+ loop_n_times test,69
+ check_crc_dmg_cgb $533D6D4D,$8130733A
+ jp tests_passed
+
+test:
+ add $99
+ ld b,a
+
+ ; Reload wave and have its first
+ ; sample read occur 2 clocks earlier
+ ; each loop iteration
+ ld hl,wave
+ call load_wave
+ wreg NR30,$80 ; enable
+ wreg NR32,$00 ; silent
+ ld a,b
+ sta NR33 ; period
+ wreg NR34,$87 ; start
+
+ ; Retrigger wave
+ wreg NR33,-2 ; period = 4
+ delay_clocks 168
+ wreg NR34,$87 ; restart
+ delay_clocks 40
+
+ ; Print wave RAM
+ wreg NR30,0
+ ld c,$30
+- ld a,($FF00+c)
+ call print_a
+ inc c
+ bit 6,c
+ jr z,-
+ call print_newline
+
+ ret
+
+wave:
+ .byte $00,$11,$22,$33,$44,$55,$66,$77
+ .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/11-regs after power.s b/playing-coffee - Copy/roms/cgb_sound/source/11-regs after power.s
new file mode 100644
index 0000000..6379ad7
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/11-regs after power.s
@@ -0,0 +1,51 @@
+; After powering sound off then on, NR12, NR14, and NR44
+; are clear.
+.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ call sync_apu
+
+ ld a,$FF
+ call fill_apu_regs
+
+ ; Power down for a moment
+ wreg NR52,$00
+ wreg NR41,-$12
+ wreg NR12,$F0
+ delay_msec 100
+ wreg NR52,$80
+
+ set_test 2,"Powering off should clear NR12"
+ call sync_apu
+ wreg NR14,$80
+ lda NR52
+ and $01
+ jp nz,test_failed
+
+ set_test 3,"Powering off should clear NR13"
+ call sync_apu
+ wreg NR10,$11
+ wreg NR12,$08
+ wreg NR14,$80
+ delay_apu 20
+ lda NR52
+ and $01
+ jp z,test_failed
+
+ set_test 4,"Powering off should clear NR41"
+ call sync_apu
+ delay_clocks 8192 ; avoids extra length clocking
+ wreg NR42,$08
+ wreg NR44,$C0
+ delay_apu 63
+ lda NR52
+ and $08
+ jp z,test_failed
+ delay_apu 1
+ lda NR52
+ and $08
+ jp nz,test_failed
+
+ jp tests_passed
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/12-wave.s b/playing-coffee - Copy/roms/cgb_sound/source/12-wave.s
new file mode 100644
index 0000000..ba3185d
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/12-wave.s
@@ -0,0 +1,112 @@
+; Tests wave channel timer reload and phase rest on trigger,
+; and access to wave RAM while playing.
+.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ ld hl,wave
+ call load_wave
+ wreg NR32,0
+
+ set_test 2,"Timer period or phase resetting is wrong"
+ wreg NR30,$80
+ wreg NR33,$00
+ wreg NR34,$80
+ delay_clocks 1024
+ wreg NR34,$80
+ ld c,$31
+ ld de,-$FE
+ call test_wave
+
+ set_test 3,"Current byte readable at any wave addr"
+ wreg NR30,$80
+ wreg NR33,$00
+ wreg NR34,$80
+ ld c,$3C
+ ld de,-$FE
+ call test_wave
+
+ set_test 5,"Normal access when chan disabled"
+ wreg NR30,$80
+ wreg NR33,$00
+ wreg NR34,$80
+ wreg NR30,$00 ; disable chan
+ wreg NR30,$80 ; DAC on
+ ld c,$31
+ ld de,0
+ call test_wave
+
+ set_test 6,"Write test"
+ wreg NR30,$80
+ wreg NR33,$F0
+ wreg NR34,$87
+ delay_clocks 256
+ wreg $FF30,$BC
+ wreg NR30,0
+ ld a,($FF34)
+ cp $BC
+ jp nz,test_failed
+
+ set_test 7,"Timer period change"
+ wreg NR30,$80
+ wreg NR33,$00
+ wreg NR34,$87
+ wreg NR33,$F0
+ ld c,$30
+ ld de,-$E
+ call test_wave
+
+ set_test 8,"Frequency 0 is valid"
+ wreg NR30,$80
+ wreg NR33,$00
+ wreg NR34,$80
+ ld c,$30
+ ld de,-$FE
+ call test_wave
+
+ set_test 9,"Maintains phase properly when vol = 0"
+ wreg NR30,$80
+ wreg NR32,0
+ wreg NR33,$00
+ wreg NR34,$87
+ ld c,$30
+ ld de,-$1E
+ call test_wave
+
+ set_test 10,"Maintains phase properly when stereo = 0"
+ wreg NR51,$00
+ wreg NR30,$80
+ wreg NR33,$00
+ wreg NR34,$87
+ ld c,$30
+ ld de,-$1E
+ call test_wave
+
+ jp tests_passed
+
+test_wave:
+- inc de ; 8
+ ld a,($FF00+c) ; 8
+ or a ; 4
+ jr z,- ; 12
+
+ ;call print_a
+ ;call print_de
+
+ cp $11
+ jp nz,test_failed
+
+
+ ; Error if not in 0...4 range
+ ld a,d
+ cp 0
+ jp nz,test_failed
+ ld a,e
+ cp 5
+ jp nc,test_failed
+ ret
+
+wave:
+ .byte $00,$11,$22,$33,$44,$55,$66,$77
+ .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/build_gbs.s b/playing-coffee - Copy/roms/cgb_sound/source/common/build_gbs.s
new file mode 100644
index 0000000..877754f
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/common/build_gbs.s
@@ -0,0 +1,139 @@
+; Build as GBS music file
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $2000 size $2000
+ slot 1 $C000 size $2000
+.endMe
+
+.romBankSize $2000
+.romBanks 2
+
+.define RST_OFFSET $70
+
+.ifndef GBS_TMA
+ .define GBS_TMA 0
+.endif
+
+.ifndef GBS_TAC
+ .define GBS_TAC 0
+.endif
+
+;;;; GBS music file header
+
+.ifndef CUSTOM_HEADER
+ .byte "GBS"
+ .byte 1,1,1 ; vers, song count, first song
+ .word load_addr, reset, gbs_play_, std_stack
+ .byte GBS_TMA,GBS_TAC ; timer
+.endif
+ .org $10
+ .ds $60,0
+load_addr:
+ .org RST_OFFSET+$70 ; space for RST vectors
+ .ds $148-RST_OFFSET-$70,0
+ .org $150 ; wla insists on generating GB header
+
+gbs_play_:
+ jp gbs_play ; GBS spec disallows having gbs_play in RAM
+
+;;;; Shell
+
+.include "shell.s"
+
+.define gbs_idle nv_ram
+.redefine nv_ram nv_ram+2
+
+init_runtime:
+ ; Identify as DMG hardware
+ ld a,$01
+ ld (gb_id),a
+
+ ; Save return address
+ pop hl
+ ld a,l
+ ld (gbs_idle),a
+ ld a,h
+ ld (gbs_idle+1),a
+
+ ; Delay 1/4 second to give time
+ ; for GBS player to interrupt with
+ ; play, if it's going to do so
+ delay_msec 250
+
+.ifndef CUSTOM_PLAY
+gbs_play:
+.endif
+ ; Get return address
+ ld a,(gbs_idle)
+ ld l,a
+ ld a,(gbs_idle+1)
+ ld h,a
+
+ ; If zero, then play interrupted init
+ ; call, or another play call, and we
+ ; can't run the program properly.
+ or l
+ jp z,internal_error
+
+ setw gbs_idle,0
+ jp hl
+
+
+; Reports A in binary as high and low tones, with
+; leading low tone for reference. Omits leading
+; zeroes.
+; Preserved: AF, BC, DE, HL
+play_byte:
+ push af
+ push hl
+
+ ; HL = (A << 1) | 1
+ scf
+ rla
+ ld l,a
+ ld h,0
+ rl h
+
+ ; Shift left until next-to-top bit is 1
+- add hl,hl
+ bit 6,h
+ jr z,-
+
+ ; Reset sound
+ delay_msec 400
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+- add hl,hl
+
+ ; Low or high pitch based on bit shifted out
+ ; of HL
+ ld a,0
+ jr nc,+
+ ld a,$FF
++ sta NR23
+
+ ; Play short tone
+ wreg NR21,$A0
+ wreg NR22,$F0
+ wreg NR24,$86
+ delay_msec 75
+ wreg NR22,0
+ wreg NR23,$F8
+ wreg NR24,$87
+ delay_msec 200
+
+ ; Loop until HL = $8000
+ ld a,h
+ xor $80
+ or l
+ jr nz,-
+
+ pop hl
+ pop af
+ ret
+
+.ends
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/build_rom.s b/playing-coffee - Copy/roms/cgb_sound/source/common/build_rom.s
new file mode 100644
index 0000000..f369b92
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/common/build_rom.s
@@ -0,0 +1,70 @@
+; Build as GB ROM
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $0000 size $4000
+ slot 1 $C000 size $4000
+.endMe
+
+.romBankSize $4000
+.romBanks 2
+
+.cartridgeType 2 ; MBC1+RAM
+.ramsize 02 ; 8K
+.computeChecksum
+.computeComplementCheck
+
+;;;; GB ROM header
+
+ ; Reserve space for RST handlers
+ .org $70
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .ds $90,0
+
+ ; GB header read by bootrom
+ .org $100
+ nop
+ jp reset
+
+ ; Nintendo logo required for proper boot
+ .byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
+ .byte $03,$73,$00,$83,$00,$0C,$00,$0D
+ .byte $00,$08,$11,$1F,$88,$89,$00,$0E
+ .byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
+ .byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
+ .byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
+
+ ; Internal name
+ .ifdef ROM_NAME
+ .byte ROM_NAME
+ .endif
+
+ ; CGB/DMG requirements
+ .org $143
+ .ifdef REQUIRE_CGB
+ .byte $C0
+ .else
+ .ifndef REQUIRE_DMG
+ .byte $80
+ .endif
+ .endif
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .org $150
+ .ds $2150-$150,0
+
+;;;; Shell
+
+.define NEED_CONSOLE 1
+.include "shell.s"
+
+init_runtime:
+ ret
+
+play_byte:
+ ret
+
+.ends
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/console.bin b/playing-coffee - Copy/roms/cgb_sound/source/common/console.bin
new file mode 100644
index 0000000..b02f2d3
Binary files /dev/null and b/playing-coffee - Copy/roms/cgb_sound/source/common/console.bin differ
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/console.s b/playing-coffee - Copy/roms/cgb_sound/source/common/console.s
new file mode 100644
index 0000000..403fa5c
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/common/console.s
@@ -0,0 +1,285 @@
+; Scrolling text console
+
+; Console is 20x18 characters. Buffers lines, so
+; output doesn't appear until a newline or flush.
+; If scrolling isn't supported (i.e. SCY is treated
+; as if always zero), the first 18 lines will
+; still print properly). Also works properly if
+; LY isn't supported (always reads back as the same
+; value).
+
+.define console_width 20
+
+.define console_buf bss+0
+.define console_pos bss+console_width
+.define console_mode bss+console_width+1
+.define console_scroll bss+console_width+2
+.redefine bss bss+console_width+3
+
+
+; Waits for start of LCD blanking period
+; Preserved: BC, DE, HL
+console_wait_vbl:
+ push bc
+
+ ; Wait for start of vblank, with
+ ; timeout in case LY doesn't work
+ ; or LCD is disabled.
+ ld bc,-1250
+- inc bc
+ ld a,b
+ or c
+ jr z,@timeout
+ lda LY
+ cp 144
+ jr nz,-
+@timeout:
+
+ pop bc
+ ret
+
+
+; Initializes text console
+console_init:
+ call console_hide
+
+ ; CGB-specific inits
+ ld a,(gb_id)
+ and gb_id_cgb
+ call nz,@init_cgb
+
+ ; Clear nametable
+ ld a,' '
+ call @fill_nametable
+
+ ; Load tiles
+ ld hl,TILES+$200
+ ld c,0
+ call @load_tiles
+ ld hl,TILES+$A00
+ ld c,$FF
+ call @load_tiles
+
+ ; Init state
+ setb console_pos,console_width
+ setb console_mode,0
+ setb console_scroll,-8
+ call console_scroll_up_
+ jr console_show
+
+@fill_nametable:
+ ld hl,BGMAP0
+ ld b,4
+- ld (hl),a
+ inc l
+ jr nz,-
+ inc h
+ dec b
+ jr nz,-
+ ret
+
+@init_cgb:
+ ; Clear palette
+ wreg $FF68,$80
+ ld b,16
+- wreg $FF69,$FF
+ wreg $FF69,$7F
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ dec b
+ jr nz,-
+
+ ; Clear attributes
+ wreg VBK,1
+ ld a,0
+ call @fill_nametable
+
+ wreg VBK,0
+ ret
+
+@load_tiles:
+ ld de,ASCII
+ ld b,96
+-- push bc
+ ld b,8
+- ld a,(de)
+ inc de
+ xor c
+ ldi (hl),a
+ ldi (hl),a
+ dec b
+ jr nz,-
+ pop bc
+ dec b
+ jr nz,--
+ ret
+
+
+; Shows console display
+; Preserved: AF, BC, DE, HL
+console_show:
+ push af
+
+ ; Enable LCD
+ call console_wait_vbl
+ wreg LCDC,$91
+ wreg SCX,0
+ wreg BGP,$E4
+
+ jp console_apply_scroll_
+
+
+; Hides console display by turning LCD off
+; Preserved: AF, BC, DE, HL
+console_hide:
+ push af
+
+ ; LCD off
+ call console_wait_vbl
+ wreg LCDC,$11
+
+ pop af
+ ret
+
+
+; Changes to normal text mode
+; Preserved: BC, DE, HL
+console_normal:
+ xor a
+ jr console_set_mode
+
+; Changes to inverse text mode
+; Preserved: BC, DE, HL
+console_inverse:
+ ld a,$80
+
+; Changes console mode to A.
+; 0: Normal, $80: Inverse
+; Preserved: BC, DE, HL
+console_set_mode:
+ and $80
+ ld (console_mode),a
+ ret
+
+
+; Prints char A to console. Will not appear until
+; a newline or flush occurs.
+; Preserved: AF, BC, DE, HL
+console_print:
+ push af
+
+ cp 10
+ jr z,console_newline_
+
+ push hl
+ push af
+ ld hl,console_pos
+ ldi a,(hl)
+ cp BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/delay.s b/playing-coffee - Copy/roms/cgb_sound/source/common/delay.s
new file mode 100644
index 0000000..cbcdcf3
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+.endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+.endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+.endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/gb.inc b/playing-coffee - Copy/roms/cgb_sound/source/common/gb.inc
new file mode 100644
index 0000000..2d0118d
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/common/gb.inc
@@ -0,0 +1,81 @@
+; Game Boy hardware addresses
+
+; $0000-$3FFF Fixed ROM bank
+; $4000-$7FFF Switchable bank
+; $8000-$9FFF VRAM
+; $A000-$BFFF optional cartridge RAM
+; $C000-$DFFF RAM
+; $E000-$FDFF RAM mirror
+; $FE00-$FE9F OAM
+; $FEA0-$FEFF unused
+; $FF00-$FF7F registers
+; $FF80-$FFFE RAM
+; $FFFF register
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define BRAM $A000 ; cart memory
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+; Registers
+
+.define RAMEN $0000 ; cartridge WRAM control
+.define BANK $2000 ; bank select
+.define P1 $FF00 ; controller
+
+; Game link I/O
+.define SB $FF01 ; serial buffer
+.define SC $FF02 ; serial control
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/macros.inc b/playing-coffee - Copy/roms/cgb_sound/source/common/macros.inc
new file mode 100644
index 0000000..9d7cf44
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/common/macros.inc
@@ -0,0 +1,91 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ; addr
+ ldh a,(\1 - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ; addr
+ ldh (\1 - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Writes byte to addr
+; Preserved: F, BC, DE, HL
+; Time: 6 cycles
+.macro setb ; addr, data
+ ld a,\2
+ ld (\1),a
+.endm
+
+; Writes word to addr
+; Preserved: F, BC, DE, HL
+; Time: 12 cycles
+.macro setw ; addr, data
+ ld a,<\2
+ ld (\1),a
+ ld a,>\2
+ ld (\1+1),a
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/numbers.s b/playing-coffee - Copy/roms/cgb_sound/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/printing.s b/playing-coffee - Copy/roms/cgb_sound/source/common/printing.s
new file mode 100644
index 0000000..bb9389b
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/common/printing.s
@@ -0,0 +1,77 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+;print_char_nocrc (defined by user)
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/shell.s b/playing-coffee - Copy/roms/cgb_sound/source/common/shell.s
new file mode 100644
index 0000000..3e588c7
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/common/shell.s
@@ -0,0 +1,261 @@
+; Common routines and runtime
+
+.define RUNTIME_INCLUDED 1
+
+; A few bytes of RAM that aren't cleared
+.define nv_ram_base $D800
+.define nv_ram nv_ram_base
+
+; Address of next normal variable
+.define bss_base nv_ram_base+$80
+.define bss bss_base
+
+; Address of next direct-page ($FFxx) variable
+.define dp_base $FF80
+.define dp dp_base
+
+; Top of stack
+.define std_stack $DFFF+1
+
+; Final exit result byte is written here
+.define final_result $A000
+
+; Text output is written here as zero-terminated string
+.define text_out_base $A004
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+.define gb_id nv_ram
+.redefine nv_ram nv_ram+1
+
+; Copies C*$100 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+- ld a,(hl+)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+
+.ifndef RST_OFFSET
+ .define RST_OFFSET 0
+.endif
+
+.ifndef CUSTOM_RESET
+ reset:
+ di
+
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ ld c,$14
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org 0
+ jp std_reset
+.endif
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
+
+ ; Catch user code running off end
+ jp internal_error
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld (gb_id),a
+
+ ; Clear memory except very top of stack
+ ld bc,std_stack-bss_base - 2
+ ld hl,bss_base
+ call clear_mem
+ ld bc,$FFFF-dp_base
+ ld hl,dp_base
+ call clear_mem
+
+ ; Init hardware
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ call init_runtime
+ call init_text_out
+ call console_init
+ call init_testing
+
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ call console_show
+ pop af
+ call play_byte
+ jp post_exit
+
++ push af
+ call print_newline
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+
+; Clears BC bytes starting at HL
+clear_mem:
+ ; If C>0, increment B
+ dec bc
+ inc c
+ inc b
+
+ ld a,0
+- ld (hl+),a
+ dec c
+ jr nz,-
+ dec b
+ jr nz,-
+ ret
+
+
+; Reports internal error and exits with code 255
+internal_error:
+ print_str "Internal error"
+ ld a,255
+ jp exit
+
+
+; build_devcart and build_multi customize this
+.ifndef CUSTOM_PRINT
+ .define text_out_addr bss+0
+ .redefine bss bss+2
+
+ ; Initializes text output to cartridge RAM
+ init_text_out:
+ ; Enable cartridge RAM and set text output pointer
+ setb RAMEN,$0A
+ setw text_out_addr,text_out_base
+ setb text_out_base-3,$DE
+ setb text_out_base-2,$B0
+ setb text_out_base-1,$61
+ setb text_out_base,0
+ setb final_result,$80
+ ret
+
+
+ ; Appends character to text output string
+ ; Preserved: AF, BC, DE, HL
+ write_text_out:
+ push hl
+ push af
+ ld a,(text_out_addr)
+ ld l,a
+ ld a,(text_out_addr+1)
+ ld h,a
+ inc hl
+ ld (hl),0
+ ld a,l
+ ld (text_out_addr),a
+ ld a,h
+ ld (text_out_addr+1),a
+ dec hl
+ pop af
+ ld (hl),a
+ pop hl
+ ret
+
+ print_char_nocrc:
+ call write_text_out
+ jp console_print
+.endif
+
+
+; only build_rom uses console
+.ifdef NEED_CONSOLE
+ .include "console.s"
+.else
+ console_init:
+ console_print:
+ console_flush:
+ console_normal:
+ console_inverse:
+ console_show:
+ console_set_mode:
+ ret
+.endif
+
+
+; build_devcart and build_multi need to customize this
+.ifndef CUSTOM_EXIT
+ post_exit:
+ ld (final_result),a
+ forever:
+ wreg NR52,0 ; sound off
+- jr -
+.endif
+
+
+.macro def_rst ARGS addr
+ .bank 0 slot 0
+ .org addr+RST_OFFSET
+.endm
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/testing.s b/playing-coffee - Copy/roms/cgb_sound/source/common/testing.s
new file mode 100644
index 0000000..5dd63ce
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/common/testing.s
@@ -0,0 +1,195 @@
+; Diagnostic and testing utilities
+
+.define test_code bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text.
+; Takes multiple strings due to wla's idiotic
+; default limit of 63 chars per string.
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2[,text3]]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .if NARGS > 3
+ .byte \4
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (test_code),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(test_code)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(test_code)
+ cp $FF ; if a = $FF then a = 1
+ jr nz,+
+ ld a,1
++ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+; Checks CRC, differing based on DMG or CGB build
+.macro check_crc_dmg_cgb ARGS dmg, cgb
+ .ifdef REQUIRE_DMG
+ check_crc dmg
+ .else
+ .ifdef REQUIRE_CGB
+ check_crc cgb
+ .else
+ .printt "CGB or DMG must be specified"
+ .fail
+ .endif
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/linkfile b/playing-coffee - Copy/roms/cgb_sound/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/readme.txt b/playing-coffee - Copy/roms/cgb_sound/source/readme.txt
new file mode 100644
index 0000000..a0269c1
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/readme.txt
@@ -0,0 +1,82 @@
+Game Boy Tests Source Code
+--------------------------
+
+Building with wla-dx
+--------------------
+To assemble a test ROM with wla-dx, use the following commands:
+
+ wla -o source_filename_here.s test.o
+ wlalink linkfile test.gb
+
+To assemble as a GBS music file:
+
+ wla -o source_filename_here.s test.o -DBUILD_GBS
+ wlalink linkfile test.gbs
+
+Note that some tests might only work when built as a ROM or GBS file,
+but not both.
+
+Some tests might include a ROM/GBS that has all the tests combined.
+Building such a multi-test is complex and the necessary files aren't
+included.
+
+
+Framework
+---------
+Each test is in a single source file, and makes use of several library
+source files from common/. This framework provides common services and
+reduces code to only that which performs the actual test. Virtually all
+tests include "shell.inc" at the beginning, which sets things up and
+includes all the appropriate library files.
+
+The reset handler does minimal GB hardware initialization, clears RAM,
+sets up the text console, then runs main. Main can exit by returning or
+jumping to "exit" with an error code in A. Exit reports the code then
+goes into an infinite loop. If the code is 0, it doesn't do anything,
+otherwise it reports the code. Code 1 is reported as "Failed", and the
+rest as "Error ".
+
+The default is to build a ROM. Defining BUILD_GBS will build as an GBS.
+The other build types aren't supported due to their complexity. I load
+the code into RAM at $C000 since my devcart requires it, and I don't
+want the normal ROM to differ in any way from what I've tested. This
+also allows easy self-modifying code.
+
+Several routines are available to print values and text to the console.
+Most update a running CRC-32 checksum which can be checked with
+check_crc, allowing ALL the output to be checked very easily. If the
+checksum doesn't match, it is printed, so you can run the code on a GB
+and paste the correct checksum into your code.
+
+
+Macros
+------
+Some macros are used to make common operations more convenient. The left
+is equivalent to the right:
+
+ Macro Equivalent
+ -------------------------------------
+ lda addr ldh a,(addr-$FF00)
+
+ sta addr ldh (addr-$FF00),a
+
+ wreg addr,data ld a,data
+ ldh (addr-$FF00),a
+
+ setb ld a,data
+ ld (addr),a
+
+ setw setb addr+0,data
+
+ for_loop routine,begin,end,step
+ calls routine with A set to successive values
+
+ loop_n_times routine,count
+ calls routine with A from 0 to count-1
+
+ print_str "str" prints string
+
+
+--
+Shay Green
diff --git a/playing-coffee - Copy/roms/cgb_sound/source/shell.inc b/playing-coffee - Copy/roms/cgb_sound/source/shell.inc
new file mode 100644
index 0000000..0d43faa
--- /dev/null
+++ b/playing-coffee - Copy/roms/cgb_sound/source/shell.inc
@@ -0,0 +1,27 @@
+; Included at beginning of all programs
+; that use standard shell
+
+; Get include files from common/
+.incdir "common"
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.else
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
+
+.endif ; .ifdef BUILD_MULTI
diff --git a/playing-coffee - Copy/roms/cpu_instrs.gb b/playing-coffee - Copy/roms/cpu_instrs.gb
new file mode 100644
index 0000000..7b06221
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs.gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/cpu_instrs.gb b/playing-coffee - Copy/roms/cpu_instrs/cpu_instrs.gb
new file mode 100644
index 0000000..7b06221
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs/cpu_instrs.gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/01-special.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/01-special.gb
new file mode 100644
index 0000000..ad3e998
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs/individual/01-special.gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/02-interrupts.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/02-interrupts.gb
new file mode 100644
index 0000000..2089594
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs/individual/02-interrupts.gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/03-op sp,hl.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/03-op sp,hl.gb
new file mode 100644
index 0000000..50b3cc7
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs/individual/03-op sp,hl.gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/04-op r,imm.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/04-op r,imm.gb
new file mode 100644
index 0000000..58ca7b8
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs/individual/04-op r,imm.gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/05-op rp.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/05-op rp.gb
new file mode 100644
index 0000000..1c19d92
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs/individual/05-op rp.gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/06-ld r,r.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/06-ld r,r.gb
new file mode 100644
index 0000000..d497bfd
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs/individual/06-ld r,r.gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb
new file mode 100644
index 0000000..5c8d20b
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/08-misc instrs.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/08-misc instrs.gb
new file mode 100644
index 0000000..4da139b
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs/individual/08-misc instrs.gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/09-op r,r.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/09-op r,r.gb
new file mode 100644
index 0000000..e30e6ec
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs/individual/09-op r,r.gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/10-bit ops.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/10-bit ops.gb
new file mode 100644
index 0000000..8988458
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs/individual/10-bit ops.gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/11-op a,(hl).gb b/playing-coffee - Copy/roms/cpu_instrs/individual/11-op a,(hl).gb
new file mode 100644
index 0000000..0634b7f
Binary files /dev/null and b/playing-coffee - Copy/roms/cpu_instrs/individual/11-op a,(hl).gb differ
diff --git a/playing-coffee - Copy/roms/cpu_instrs/readme.txt b/playing-coffee - Copy/roms/cpu_instrs/readme.txt
new file mode 100644
index 0000000..6f94955
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/readme.txt
@@ -0,0 +1,119 @@
+Game Boy CPU Instruction Behavior Test
+--------------------------------------
+This ROM tests the behavior of all CPU instructions except STOP and the
+11 illegal opcodes. The tests are fairly thorough, running instructions
+with boundary data and verifying both the result and that other
+registers are not modified. Instructions which perform the same
+operation on different registers are each tested just as thoroughly, in
+case an emulator implements each independently. Some sub-tests take half
+minute to complete.
+
+Failed instructions are listed as
+
+ [CB] opcode
+
+Some errors cannot of course be diagnosed properly, since the test
+framework itself relies on basic instruction behavior being correct.
+
+
+Internal operation
+------------------
+The main tests use a framework that runs each instruction in a loop,
+varying the register values on input and examining them on output.
+Rather than keep a table of correct values, it simply calculates a
+CRC-32 checksum of all the output, then compares this with the correct
+value. Instructions are divided into several groups, each with a
+different set of input values suited for their behavior; for example,
+the bit test instructions are fed $01, $02, $04 ... $40, $80, to ensure
+each bit is handled properly, while the arithmetic instructions are fed
+$01, $0F, $10, $7F, $FF, to exercise carry and half-carry. A few
+instructions require a custom test due to their uniqueness.
+
+
+Multi-ROM
+---------
+In the main directory is a single ROM which runs all the tests. It
+prints a test's number, runs the test, then "ok" if it passes, otherwise
+a failure code. Once all tests have completed it either reports that all
+tests passed, or prints the number of failed tests. Finally, it makes
+several beeps. If a test fails, it can be run on its own by finding the
+corresponding ROM in individual/.
+
+Ths compact format on screen is to avoid having the results scroll off
+the top, so the test can be started and allowed to run without having to
+constantly monitor the display.
+
+Currently there is no well-defined way for an emulator test rig to
+programatically find the result of the test; contact me if you're trying
+to do completely automated testing of your emulator. One simple approach
+is to take a screenshot after all tests have run, or even just a
+checksum of one, and compare this with a previous run.
+
+
+Failure codes
+-------------
+Failed tests may print a failure code, and also short description of the
+problem. For more information about a failure code, look in the
+corresponding source file in source/; the point in the code where
+"set_test n" occurs is where that failure code will be generated.
+Failure code 1 is a general failure of the test; any further information
+will be printed.
+
+Note that once a sub-test fails, no further tests for that file are run.
+
+
+Console output
+--------------
+Information is printed on screen in a way that needs only minimum LCD
+support, and won't hang if LCD output isn't supported at all.
+Specifically, while polling LY to wait for vblank, it will time out if
+it takes too long, so LY always reading back as the same value won't
+hang the test. It's also OK if scrolling isn't supported; in this case,
+text will appear starting at the top of the screen.
+
+Everything printed on screen is also sent to the game link port by
+writing the character to SB, then writing $81 to SC. This is useful for
+tests which print lots of information that scrolls off screen.
+
+
+Source code
+-----------
+Source code is included for all tests, in source/. It can be used to
+build the individual test ROMs. Code for the multi test isn't included
+due to the complexity of putting everything together.
+
+Code is written for the wla-dx assembler. To assemble a particular test,
+execute
+
+ wla -o "source_filename.s" test.o
+ wlalink linkfile test.gb
+
+Test code uses a common shell framework contained in common/.
+
+
+Internal framework operation
+----------------------------
+Tests use a common framework for setting things up, reporting results,
+and ending. All files first include "shell.inc", which sets up the ROM
+header and shell code, and includes other commonly-used modules.
+
+One oddity is that test code is first copied to internal RAM at $D000,
+then executed there. This allows self-modification, and ensures the code
+is executed the same way it is on my devcart, which doesn't have a
+rewritable ROM as most do.
+
+Some macros are used to simplify common tasks:
+
+ Macro Behavior
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ wreg addr,data Writes data to addr using LDH
+ lda addr Loads byte from addr into A using LDH
+ sta addr Stores A at addr using LDH
+ delay n Delays n cycles, where NOP = 1 cycle
+ delay_msec n Delays n milliseconds
+ set_test n,"Cause" Sets failure code and optional string
+
+Routines and macros are documented where they are defined.
+
+--
+Shay Green
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/01-special.s b/playing-coffee - Copy/roms/cpu_instrs/source/01-special.s
new file mode 100644
index 0000000..776d685
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/01-special.s
@@ -0,0 +1,78 @@
+; Tests instructions that don't fit template
+
+.include "shell.inc"
+
+main:
+ set_test 2,"JR negative"
+ ld a,0
+ jp jr_neg
+ inc a
+- inc a
+ inc a
+ cp 2
+ jp nz,test_failed
+ jp +
+jr_neg:
+ jr -
++
+
+ set_test 3,"JR positive"
+ ld a,0
+ jr +
+ inc a
++ inc a
+ inc a
+ cp 2
+ jp nz,test_failed
+
+
+ set_test 4,"LD PC,HL"
+ ld hl,+
+ ld a,0
+ ld pc,hl
+ inc a
++ inc a
+ inc a
+ cp 2
+ jp nz,test_failed
+
+
+ set_test 5,"POP AF"
+ ld bc,$1200
+- push bc
+ pop af
+ push af
+ pop de
+ ld a,c
+ and $F0
+ cp e
+ jp nz,test_failed
+ inc b
+ inc c
+ jr nz,-
+
+
+ set_test 6,"DAA"
+ ; Test all combinations of A and flags (256*16 total)
+ ld de,0
+- push de
+ pop af
+ daa
+
+ push af
+ call update_crc
+ pop hl
+ ld a,l
+ call update_crc
+
+ inc d
+ jr nz,-
+
+ ld a,e
+ add $10
+ ld e,a
+ jr nz,-
+
+ check_crc $6A9F8D8A
+
+ jp tests_passed
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/02-interrupts.s b/playing-coffee - Copy/roms/cpu_instrs/source/02-interrupts.s
new file mode 100644
index 0000000..de18b34
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/02-interrupts.s
@@ -0,0 +1,73 @@
+; Tests DI, EI, and HALT (STOP proved untestable)
+
+.include "shell.inc"
+
+main:
+ wreg IE,$04
+
+ set_test 2,"EI"
+ ei
+ ld bc,0
+ push bc
+ pop bc
+ inc b
+ wreg IF,$04
+interrupt_addr:
+ dec b
+ jp nz,test_failed
+ ld hl,sp-2
+ ldi a,(hl)
+ cp interrupt_addr
+ jp nz,test_failed
+ lda IF
+ and $04
+ jp nz,test_failed
+
+ set_test 3,"DI"
+ di
+ ld bc,0
+ push bc
+ pop bc
+ wreg IF,$04
+ ld hl,sp-2
+ ldi a,(hl)
+ or (hl)
+ jp nz,test_failed
+ lda IF
+ and $04
+ jp z,test_failed
+
+ set_test 4,"Timer doesn't work"
+ wreg TAC,$05
+ wreg TIMA,0
+ wreg IF,0
+ delay 500
+ lda IF
+ delay 500
+ and $04
+ jp nz,test_failed
+ delay 500
+ lda IF
+ and $04
+ jp z,test_failed
+ pop af
+
+ set_test 5,"HALT"
+ wreg TAC,$05
+ wreg TIMA,0
+ wreg IF,0
+ halt ; timer interrupt will exit halt
+ nop ; avoids DMG bug
+ lda IF
+ and $04
+ jp z,test_failed
+
+ jp tests_passed
+
+.bank 0 slot 0
+.org $50
+ inc a
+ ret
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/03-op sp,hl.s b/playing-coffee - Copy/roms/cpu_instrs/source/03-op sp,hl.s
new file mode 100644
index 0000000..9531d51
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/03-op sp,hl.s
@@ -0,0 +1,102 @@
+; Tests SP/HL instructions
+
+;.define PRINT_CHECKSUMS 1
+.include "shell.inc"
+.include "instr_test.s"
+
+instrs:
+ .byte $33,0,0 ; INC SP
+ .byte $3B,0,0 ; DEC SP
+ .byte $39,0,0 ; ADD HL,SP
+ .byte $F9,0,0 ; LD SP,HL
+ .byte $E8,$01,0 ; ADD SP,1
+ .byte $E8,$FF,0 ; ADD SP,-1
+ .byte $F8,$01,0 ; LD HL,SP+1
+ .byte $F8,$FF,0 ; LD HL,SP-1
+instrs_end:
+
+test_instr:
+ ; C = flags register
+ ld c,$00
+ call test
+ ld c,$F0
+ call test
+ ret
+
+test:
+ ; Go through each value for HL
+ ld hl,values
+hl_loop:
+ ld e,(hl)
+ inc hl
+ ld d,(hl)
+ inc hl
+ push hl
+
+ ; Go through each value for SP
+ ld hl,values
+values_loop:
+ push bc
+ push de
+ push hl
+
+ push bc
+ pop af
+
+ ; Switch stack
+ ld (temp),sp
+ ld a,(hl+)
+ ld h,(hl)
+ ld l,a
+; call print_regs
+ ld sp,hl
+
+ ; Set registers
+ ld h,d
+ ld l,e
+ ld a,$12
+ ld bc,$5691
+ ld de,$9ABC
+
+ jp instr
+instr_done:
+ ; Save new SP and switch to yet another stack
+ ld (temp+2),sp
+ ld sp,$DF70
+
+ call checksum_af_bc_de_hl
+
+ ; Checksum SP
+ ld a,(temp+2)
+ call update_crc_fast
+ ld a,(temp+3)
+ call update_crc_fast
+
+ ldsp temp
+
+ pop hl
+ pop de
+ pop bc
+ inc hl
+ inc hl
+ ld a,l
+ cp taken ; JP NZ,taken
+ .byte $C3,taken ; JP taken
+ .byte $CA,taken ; JP Z,taken
+ .byte $D2,taken ; JP NC,taken
+ .byte $DA,taken ; JP C,taken
+
+ .byte $C4,taken ; CALL NZ,taken
+ .byte $CC,taken ; CALL Z,taken
+ .byte $CD,taken ; CALL taken
+ .byte $D4,taken ; CALL NC,taken
+ .byte $DC,taken ; CALL C,taken
+
+ ; RET cond
+ ; INC A
+ .byte $C0,$3C,0 ; RET NZ
+ .byte $C8,$3C,0 ; RET Z
+ .byte $C9,$3C,0 ; RET
+ .byte $D0,$3C,0 ; RET NC
+ .byte $D8,$3C,0 ; RET C
+ .byte $D9,$3C,0 ; RETI
+
+ ; RST
+ ; can only easily test this one on devcart
+ .byte $C7,0,0 ; RST $00
+.ifndef BUILD_DEVCART
+ .byte $CF,0,0 ; RST $08
+ .byte $D7,0,0 ; RST $10
+ .byte $DF,0,0 ; RST $18
+ .byte $E7,0,0 ; RST $20
+ .byte $EF,0,0 ; RST $28
+ .byte $F7,0,0 ; RST $30
+ .byte $FF,0,0 ; RST $38
+.endif
+
+instrs_end:
+
+test_instr:
+ wreg IE,0 ; disable interrupts, since RETI does EI
+
+ ; Go through all 16 combinations of flags
+ ld bc,$1200
+-
+ ; Fill 4 bytes of new stack
+ ld a,$12
+ ld ($DF80-2),a
+ ld a,$34
+ ld ($DF80-3),a
+ ld a,$56
+ ld ($DF80-4),a
+ ld a,$78
+ ld ($DF80-5),a
+
+ ; Set AF
+ push bc
+ pop af
+
+ ; Switch to new stack
+ ld (temp),sp
+ ld sp,$DF80
+
+ ; Set return address
+ ld de,instr+3
+ push de
+
+ jp instr
+instr_done:
+ inc a
+taken:
+ di ; RETI enables interrupts
+
+ ; Save new SP and switch to yet another stack
+ ld (temp+2),sp
+ ld sp,$DF70
+
+ ; Checksum A and SP
+ call update_crc_fast
+ ld a,(temp+2)
+ call update_crc_fast
+ ld a,(temp+3)
+ call update_crc_fast
+
+ ; Checksum 4 bytes of stack
+ ld a,($DF80-2)
+ call update_crc_fast
+ ld a,($DF80-3)
+ call update_crc_fast
+ ld a,($DF80-4)
+ call update_crc_fast
+ ld a,($DF80-5)
+ call update_crc_fast
+
+ ldsp temp
+
+ ld a,c
+ add $10
+ ld c,a
+ jr nz,-
+
+ ret
+
+checksums:
+ .byte $EC,$A4,$94,$79,$C4,$00,$96,$2C,$C4,$64,$90,$33,$77,$C7,$0A,$D4
+ .byte $77,$A3,$0C,$CB,$79,$E7,$7E,$AE,$DA,$DC,$03,$F7,$4F,$9F,$E9,$20
+ .byte $72,$12,$DA,$01,$44,$6A,$4D,$8F,$D1,$79,$30,$4C,$AA,$37,$F2,$6A
+ .byte $97,$EA,$56,$5F,$32,$28,$C7,$D1,$49,$66,$05,$F7,$80,$0F,$BA,$8E
+ .byte $41,$E2,$A4,$9A,$2D,$2D,$8C,$72,$A5,$13,$76,$A8,$64,$FE,$68,$BC
+ .byte $2D,$2D,$8C,$72,$50,$96,$24,$27,$50,$96,$24,$27,$50,$96,$24,$27
+ .byte $50,$96,$24,$27,$50,$96,$24,$27,$50,$96,$24,$27,$50,$96,$24,$27
+ .byte $50,$96,$24,$27
+
+.include "multi_custom.s"
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/08-misc instrs.s b/playing-coffee - Copy/roms/cpu_instrs/source/08-misc instrs.s
new file mode 100644
index 0000000..5c11c8e
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/08-misc instrs.s
@@ -0,0 +1,110 @@
+; Tests miscellaneous instructions
+
+;.define PRINT_CHECKSUMS 1
+.include "shell.inc"
+.include "instr_test.s"
+
+instrs:
+ .byte $F0,$91,0 ; LDH A,($91)
+ .byte $E0,$91,0 ; LDH ($91),A
+ .byte $F2,$00,0 ; LDH A,(C)
+ .byte $E2,$00,0 ; LDH (C),A
+ .byte $FA,$91,$FF ; LD A,($FF91)
+ .byte $EA,$91,$FF ; LD ($FF91),A
+ .byte $08,$91,$FF ; LD ($FF91),SP
+ .byte $01,$23,$01 ; LD BC,$0123
+ .byte $11,$23,$01 ; LD DE,$0123
+ .byte $21,$23,$01 ; LD HL,$0123
+ .byte $31,$23,$01 ; LD SP,$0123
+ .byte $F5,0,0 ; PUSH AF
+ .byte $C5,0,0 ; PUSH BC
+ .byte $D5,0,0 ; PUSH DE
+ .byte $E5,0,0 ; PUSH HL
+ .byte $F1,0,0 ; POP AF
+ .byte $C1,0,0 ; POP BC
+ .byte $D1,0,0 ; POP DE
+ .byte $E1,0,0 ; POP HL
+instrs_end:
+
+test_instr:
+ ; C = flags register
+ ld c,$00
+ call test
+ ld c,$10
+ call test
+ ld c,$E0
+ call test
+ ld c,$F0
+ call test
+ ret
+
+test:
+ ; Fill RAM
+ ld a,$FE
+ ld ($FF90),a
+ ld a,$DC
+ ld ($FF91),a
+ ld a,$BA
+ ld ($FF92),a
+
+ ; Fill stack
+ ld a,$13
+ ld ($DF80),a
+ ld a,$57
+ ld ($DF80-1),a
+ ld a,$9B
+ ld ($DF80-2),a
+ ld a,$DF
+ ld ($DF80-3),a
+
+ ; Set registers
+ ld b,$12
+ push bc
+ ld bc,$5691
+ ld de,$9ABC
+ ld hl,$DEF0
+ pop af
+
+ ; Switch stack
+ ld (temp),sp
+ ld sp,$DF80-2
+
+ jp instr
+instr_done:
+ ; Save new SP and switch to another stack
+ ld (temp+2),sp
+ ld sp,$DF70
+
+ call checksum_af_bc_de_hl
+
+ ; Checksum SP
+ ld a,(temp+2)
+ call update_crc_fast
+ ld a,(temp+3)
+ call update_crc_fast
+
+ ; Checksum RAM
+ ld a,($FF90)
+ call update_crc_fast
+ ld a,($FF91)
+ call update_crc_fast
+ ld a,($FF92)
+ call update_crc_fast
+
+ ; Checksum stack
+ ld a,($DF80)
+ call update_crc_fast
+ ld a,($DF80-1)
+ call update_crc_fast
+ ld a,($DF80-2)
+ call update_crc_fast
+ ld a,($DF80-3)
+ call update_crc_fast
+
+ ; Restore SP
+ ldsp temp
+
+ ret
+
+checksums:
+ .byte $4D,$FF,$15,$97,$6D,$A7,$35,$65,$4D,$FF,$15,$97,$6D,$A7,$35,$65,$4D,$FF,$15,$97,$6D,$A7,$35,$65,$AD,$FA,$5E,$41,$D0,$78,$79,$C1,$AF,$66,$99,$34,$0D,$E1,$97,$99,$6F,$D0,$6F,$5D,$C3,$1F,$A3,$8A,$C2,$F1,$9C,$F3,$C1,$C3,$DC,$78,$C0,$2D,$E3,$01,$8F,$C4,$0F,$44,$95,$22,$6A,$39,$61,$C5,$AB,$55,$FB,$DF,$2C,$52,
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/09-op r,r.s b/playing-coffee - Copy/roms/cpu_instrs/source/09-op r,r.s
new file mode 100644
index 0000000..432c4e7
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/09-op r,r.s
@@ -0,0 +1,269 @@
+; Tests most register instructions.
+; Takes 10 seconds.
+
+;.define PRINT_CHECKSUMS 1
+.include "shell.inc"
+.include "instr_test.s"
+
+instrs:
+ .byte $00,0,0 ; NOP
+ .byte $2F,0,0 ; CPL
+ .byte $37,0,0 ; SCF
+ .byte $3F,0,0 ; CCF
+
+ .byte $B0,0,0 ; OR B
+ .byte $B1,0,0 ; OR C
+ .byte $B2,0,0 ; OR D
+ .byte $B3,0,0 ; OR E
+ .byte $B4,0,0 ; OR H
+ .byte $B5,0,0 ; OR L
+ .byte $B7,0,0 ; OR A
+
+ .byte $B8,0,0 ; CP B
+ .byte $B9,0,0 ; CP C
+ .byte $BA,0,0 ; CP D
+ .byte $BB,0,0 ; CP E
+ .byte $BC,0,0 ; CP H
+ .byte $BD,0,0 ; CP L
+ .byte $BF,0,0 ; CP A
+
+ .byte $80,0,0 ; ADD B
+ .byte $81,0,0 ; ADD C
+ .byte $82,0,0 ; ADD D
+ .byte $83,0,0 ; ADD E
+ .byte $84,0,0 ; ADD H
+ .byte $85,0,0 ; ADD L
+ .byte $87,0,0 ; ADD A
+
+ .byte $88,0,0 ; ADC B
+ .byte $89,0,0 ; ADC C
+ .byte $8A,0,0 ; ADC D
+ .byte $8B,0,0 ; ADC E
+ .byte $8C,0,0 ; ADC H
+ .byte $8D,0,0 ; ADC L
+ .byte $8F,0,0 ; ADC A
+
+ .byte $90,0,0 ; SUB B
+ .byte $91,0,0 ; SUB C
+ .byte $92,0,0 ; SUB D
+ .byte $93,0,0 ; SUB E
+ .byte $94,0,0 ; SUB H
+ .byte $95,0,0 ; SUB L
+ .byte $97,0,0 ; SUB A
+
+ .byte $98,0,0 ; SBC B
+ .byte $99,0,0 ; SBC C
+ .byte $9A,0,0 ; SBC D
+ .byte $9B,0,0 ; SBC E
+ .byte $9C,0,0 ; SBC H
+ .byte $9D,0,0 ; SBC L
+ .byte $9F,0,0 ; SBC A
+
+ .byte $A0,0,0 ; AND B
+ .byte $A1,0,0 ; AND C
+ .byte $A2,0,0 ; AND D
+ .byte $A3,0,0 ; AND E
+ .byte $A4,0,0 ; AND H
+ .byte $A5,0,0 ; AND L
+ .byte $A7,0,0 ; AND A
+
+ .byte $A8,0,0 ; XOR B
+ .byte $A9,0,0 ; XOR C
+ .byte $AA,0,0 ; XOR D
+ .byte $AB,0,0 ; XOR E
+ .byte $AC,0,0 ; XOR H
+ .byte $AD,0,0 ; XOR L
+ .byte $AF,0,0 ; XOR A
+
+ .byte $05,0,0 ; DEC B
+ .byte $0D,0,0 ; DEC C
+ .byte $15,0,0 ; DEC D
+ .byte $1D,0,0 ; DEC E
+ .byte $25,0,0 ; DEC H
+ .byte $2D,0,0 ; DEC L
+ .byte $3D,0,0 ; DEC A
+
+ .byte $04,0,0 ; INC B
+ .byte $0C,0,0 ; INC C
+ .byte $14,0,0 ; INC D
+ .byte $1C,0,0 ; INC E
+ .byte $24,0,0 ; INC H
+ .byte $2C,0,0 ; INC L
+ .byte $3C,0,0 ; INC A
+
+ .byte $07,0,0 ; RLCA
+ .byte $17,0,0 ; RLA
+ .byte $0F,0,0 ; RRCA
+ .byte $1F,0,0 ; RRA
+
+ .byte $CB,$00,0 ; RLC B
+ .byte $CB,$01,0 ; RLC C
+ .byte $CB,$02,0 ; RLC D
+ .byte $CB,$03,0 ; RLC E
+ .byte $CB,$04,0 ; RLC H
+ .byte $CB,$05,0 ; RLC L
+ .byte $CB,$07,0 ; RLC A
+
+ .byte $CB,$08,0 ; RRC B
+ .byte $CB,$09,0 ; RRC C
+ .byte $CB,$0A,0 ; RRC D
+ .byte $CB,$0B,0 ; RRC E
+ .byte $CB,$0C,0 ; RRC H
+ .byte $CB,$0D,0 ; RRC L
+ .byte $CB,$0F,0 ; RRC A
+
+ .byte $CB,$10,0 ; RL B
+ .byte $CB,$11,0 ; RL C
+ .byte $CB,$12,0 ; RL D
+ .byte $CB,$13,0 ; RL E
+ .byte $CB,$14,0 ; RL H
+ .byte $CB,$15,0 ; RL L
+ .byte $CB,$17,0 ; RL A
+
+ .byte $CB,$18,0 ; RR B
+ .byte $CB,$19,0 ; RR C
+ .byte $CB,$1A,0 ; RR D
+ .byte $CB,$1B,0 ; RR E
+ .byte $CB,$1C,0 ; RR H
+ .byte $CB,$1D,0 ; RR L
+ .byte $CB,$1F,0 ; RR A
+
+ .byte $CB,$20,0 ; SLA B
+ .byte $CB,$21,0 ; SLA C
+ .byte $CB,$22,0 ; SLA D
+ .byte $CB,$23,0 ; SLA E
+ .byte $CB,$24,0 ; SLA H
+ .byte $CB,$25,0 ; SLA L
+ .byte $CB,$27,0 ; SLA A
+
+ .byte $CB,$28,0 ; SRA B
+ .byte $CB,$29,0 ; SRA C
+ .byte $CB,$2A,0 ; SRA D
+ .byte $CB,$2B,0 ; SRA E
+ .byte $CB,$2C,0 ; SRA H
+ .byte $CB,$2D,0 ; SRA L
+ .byte $CB,$2F,0 ; SRA A
+
+ .byte $CB,$30,0 ; SWAP B
+ .byte $CB,$31,0 ; SWAP C
+ .byte $CB,$32,0 ; SWAP D
+ .byte $CB,$33,0 ; SWAP E
+ .byte $CB,$34,0 ; SWAP H
+ .byte $CB,$35,0 ; SWAP L
+ .byte $CB,$37,0 ; SWAP A
+
+ .byte $CB,$38,0 ; SRL B
+ .byte $CB,$39,0 ; SRL C
+ .byte $CB,$3A,0 ; SRL D
+ .byte $CB,$3B,0 ; SRL E
+ .byte $CB,$3C,0 ; SRL H
+ .byte $CB,$3D,0 ; SRL L
+ .byte $CB,$3F,0 ; SRL A
+instrs_end:
+
+test_instr:
+ ld c,$00
+ call test
+ ld c,$F0
+ call test
+ ret
+
+test:
+ ; Go through each value for A
+ ld hl,values
+a_loop:
+ ld b,(hl)
+ push hl
+
+ ; Go through each value for other registers
+ ld hl,values
+values_loop:
+ push bc
+ push hl
+
+ push bc
+
+ ; BC
+ ld a,(hl+)
+ ld b,a
+ ld a,(hl+)
+ ld c,a
+
+ ; HL
+ ld a,(hl+)
+ ld d,a
+ ld a,(hl+)
+ ld e,a
+ push de
+
+ ; DE
+ ld a,(hl+)
+ ld d,a
+ ld a,(hl+)
+ ld e,a
+
+ pop hl
+ pop af
+
+; call print_regs
+ jp instr
+instr_done:
+
+ ; Checksum registers
+ call checksum_af_bc_de_hl
+
+ pop hl
+ pop bc
+ inc hl
+ ld a,l
+ cp checksums
+ ld (next_checksum+1),a
+ ret
+
+; Compares current checksum with next checksum in
+; list. Z if they match, NZ if not.
+; Preserved: BC, DE, HL
+checksums_compare:
+.ifdef PRINT_CHECKSUMS
+ lda checksum+3
+ push af
+ lda checksum+2
+ push af
+ lda checksum+1
+ push af
+ lda checksum+0
+ push af
+
+ ld a,(next_checksum)
+ inc a
+ ld (next_checksum),a
+ sub BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/crc_fast.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/crc_fast.s
new file mode 100644
index 0000000..d1088b0
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/crc_fast.s
@@ -0,0 +1,88 @@
+; Fast table-based CRC-32
+
+.define crc_tables (bss+$FF)&$FF00 ; 256-byte aligned
+.redefine bss crc_tables+$400
+
+
+; Initializes fast CRC tables and resets checksum.
+; Time: 47 msec
+init_crc_fast:
+ ld l,0
+@next:
+ xor a
+ ld c,a
+ ld d,a
+ ld e,l
+
+ ld h,8
+- rra
+ rr c
+ rr d
+ rr e
+ jr nc,+
+ xor $ED
+ ld b,a
+ ld a,c
+ xor $B8
+ ld c,a
+ ld a,d
+ xor $83
+ ld d,a
+ ld a,e
+ xor $20
+ ld e,a
+ ld a,b
+
++ dec h
+ jr nz,-
+
+ ld h,>crc_tables
+ ld (hl),e
+ inc h
+ ld (hl),d
+ inc h
+ ld (hl),c
+ inc h
+ ld (hl),a
+
+ inc l
+ jr nz,@next
+
+ jp init_crc
+
+
+; Faster version of update_crc
+; Preserved: BC, DE
+; Time: 50 cycles (including CALL)
+update_crc_fast:
+
+; Fastest inline macro version of update_crc_fast
+; Time: 40 cycles
+; Size: 28 bytes
+.macro update_crc_fast
+ ld l,a ; 1
+ lda checksum ; 3
+ xor l ; 1
+ ld l,a ; 1
+ ld h,>crc_tables ; 2
+
+ lda checksum+1 ; 3
+ xor (hl) ; 2
+ inc h ; 1
+ sta checksum ; 3
+
+ lda checksum+2 ; 3
+ xor (hl) ; 2
+ inc h ; 1
+ sta checksum+1 ; 3
+
+ lda checksum+3 ; 3
+ xor (hl) ; 2
+ inc h ; 1
+ sta checksum+2 ; 3
+
+ ld a,(hl) ; 2
+ sta checksum+3 ; 3
+.endm
+ update_crc_fast
+ ret
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/delay.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/delay.s
new file mode 100644
index 0000000..65eb9c0
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+ .endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+ .endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+ .endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/gb.inc b/playing-coffee - Copy/roms/cpu_instrs/source/common/gb.inc
new file mode 100644
index 0000000..31bbf14
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/gb.inc
@@ -0,0 +1,64 @@
+; Game Boy hardware addresses
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+.define P1 $FF00
+
+; Game link I/O
+.define SB $FF01
+.define SC $FF02
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/instr_test.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/instr_test.s
new file mode 100644
index 0000000..5ed6e2c
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/instr_test.s
@@ -0,0 +1,105 @@
+; Framework for CPU instruction tests
+
+; Calls test_instr with each instruction copied
+; to instr, with a JP instr_done after it.
+; Verifies checksum after testing instruction and
+; prints opcode if it's wrong.
+
+.include "checksums.s"
+.include "cpu_speed.s"
+.include "apu.s"
+.include "crc_fast.s"
+
+.define instr $DEF8
+.define rp_temp (instr-4)
+
+.define temp bss
+
+; Sets SP to word at addr
+; Preserved: BC, DE
+.macro ldsp ; addr
+ ld a,(\1)
+ ld l,a
+ ld a,((\1)+1)
+ ld h,a
+ ld sp,hl
+.endm
+
+main:
+ call cpu_fast
+ call init_crc_fast
+ call checksums_init
+ set_test 0
+
+ ld hl,instrs
+- ; Copy instruction
+ ld a,(hl+)
+ ld (instr),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Put JP instr_done after it
+ ld a,$C3
+ ld (instr+3),a
+ ld a,instr_done
+ ld (instr+5),a
+
+ call reset_crc
+ call test_instr
+
+ call checksums_compare
+ jr z,passed
+
+ set_test 1
+ ld a,(instr)
+ call print_a
+ cp $CB
+ jr nz,+
+ ld a,(instr+1)
+ call print_a
++
+
+passed:
+ ; Next instruction
+ pop hl
+ ld a,l
+ cp instrs_end
+ jr nz,-
+
+ jp tests_done
+
+
+; Updates checksum with AF, BC, DE, and HL
+checksum_af_bc_de_hl:
+ push hl
+
+ push af
+ update_crc_fast
+ pop hl
+ ld a,l
+ update_crc_fast
+
+ ld a,b
+ update_crc_fast
+ ld a,c
+ update_crc_fast
+
+ ld a,d
+ update_crc_fast
+ ld a,e
+ update_crc_fast
+
+ pop de
+ ld a,d
+ update_crc_fast
+ ld a,e
+ update_crc_fast
+ ret
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/macros.inc b/playing-coffee - Copy/roms/cpu_instrs/source/common/macros.inc
new file mode 100644
index 0000000..c413bd5
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/macros.inc
@@ -0,0 +1,73 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ARGS addr
+ ldh a,(addr - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ARGS addr
+ ldh (addr - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/multi_custom.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/multi_custom.s
new file mode 100644
index 0000000..4dbae9d
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/multi_custom.s
@@ -0,0 +1,38 @@
+; RST handlers
+.bank 0 slot 0
+.org 0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/numbers.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/printing.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/printing.s
new file mode 100644
index 0000000..ad9d811
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/printing.s
@@ -0,0 +1,98 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+.define print_char_nocrc bss
+.redefine bss bss+3
+
+
+; Initializes printing. HL = print routine
+init_printing:
+ ld a,l
+ ld (print_char_nocrc+1),a
+ ld a,h
+ ld (print_char_nocrc+2),a
+ jr show_printing
+
+
+; Hides/shows further printing
+; Preserved: BC, DE, HL
+hide_printing:
+ ld a,$C9 ; RET
+ jr +
+show_printing:
+ ld a,$C3 ; JP (nn)
++ ld (print_char_nocrc),a
+ ret
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/runtime.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/runtime.s
new file mode 100644
index 0000000..90cd24f
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/runtime.s
@@ -0,0 +1,142 @@
+; Common routines and runtime
+
+; Must be defined by target-specific runtime:
+;
+; init_runtime: ; target-specific inits
+; std_print: ; default routine to print char A
+; post_exit: ; called at end of std_exit
+; report_byte: ; report A to user
+
+.define RUNTIME_INCLUDED 1
+
+.ifndef bss
+ ; address of next normal variable
+ .define bss $D800
+.endif
+
+.ifndef dp
+ ; address of next direct-page ($FFxx) variable
+ .define dp $FF80
+.endif
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+
+.define gb_id bss
+.redefine bss bss+1
+
+; Stack is normally here
+.define std_stack $DFFF
+
+; Copies $1000 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+ ld c,$10
+- ldi a,(hl)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+.ifndef CUSTOM_RESET
+ reset:
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org $0 ; otherwise wla pads with lots of zeroes
+ jp std_reset
+.endif
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld (gb_id),a
+
+ ; Init hardware
+ .ifndef BUILD_GBS
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+ .endif
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ ; TODO: clear all memory?
+
+ ld hl,std_print
+ call init_printing
+ call init_testing
+ call init_runtime
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ pop af
+ jp post_exit
+
++ push af
+ call print_newline
+ call show_printing
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/testing.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/testing.s
new file mode 100644
index 0000000..c102f78
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/testing.s
@@ -0,0 +1,176 @@
+; Diagnostic and testing utilities
+
+.define result bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (result),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(result)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(result)
+ cp 1 ; if a = 0 then a = 1
+ adc 0
+ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call show_printing
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/linkfile b/playing-coffee - Copy/roms/cpu_instrs/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/shell.inc b/playing-coffee - Copy/roms/cpu_instrs/source/shell.inc
new file mode 100644
index 0000000..277a9b7
--- /dev/null
+++ b/playing-coffee - Copy/roms/cpu_instrs/source/shell.inc
@@ -0,0 +1,21 @@
+.incdir "common"
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
diff --git a/playing-coffee - Copy/roms/dmg_sound/dmg_sound.gb b/playing-coffee - Copy/roms/dmg_sound/dmg_sound.gb
new file mode 100644
index 0000000..fe91310
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/dmg_sound.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/readme.txt b/playing-coffee - Copy/roms/dmg_sound/readme.txt
new file mode 100644
index 0000000..5d8d188
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/readme.txt
@@ -0,0 +1,86 @@
+Game Boy Sound Hardware Tests
+-----------------------------
+These tests verify aspects of the sound hardware that the CPU can
+observe. The ROMs and GBSs are either for DMG or CGB hardware, as there
+are several differences.
+
+
+Multi-ROM
+---------
+In the main directory is a single ROM/GBS which runs all the tests. It
+prints a test's number, runs the test, then "ok" if it passes, otherwise
+a failure code. Once all tests have completed it either reports that all
+tests passed, or reports the number of the first failed test as the
+result code (1 = first). Finally, it makes several beeps. If a test
+fails, it can be run on its own by finding the corresponding ROM/GBS in
+the singles directories.
+
+Ths compact format on screen is to avoid having the results scroll off
+the top, so the test can be started and allowed to run without having to
+constantly monitor the display.
+
+
+Failure information
+-------------------
+For more information about a failure code or information printed, see
+the test's source code in source/. To find failure code N, search for
+"set_test N", which will usually be before the subtest which failed.
+
+
+Flashes, clicks, other glitches
+-------------------------------
+Some tests might need to turn the screen off and on, or cause slight
+audio clicks. This does not indicate failure, and should be ignored.
+Only the test result reported at the end is important, unless stated
+otherwise.
+
+
+LCD support
+-----------
+Tests generally print information on screen. The tests will work fine if
+run on an emulator with NO LCD support, or as an GBS which has no
+inherent screen; in particular, the VBL wait routine has a timeout in
+case LY doesn't reflect the current LCD line. The text printing will
+also work if the LCD doesn't support scrolling.
+
+
+Output to memory
+----------------
+Text output and the final result are also written to memory at $A000,
+allowing testing a very minimal emulator that supports little more than
+CPU and RAM. To reliably indicate that the data is from a test and not
+random data, $A001-$A003 are written with a signature: $DE,$B0,$61. If
+this is present, then the text string and final result status are valid.
+
+$A000 holds the overall status. If the test is still running, it holds
+$80, otherwise it holds the final result code.
+
+All text output is appended to a zero-terminated string at $A004. An
+emulator could regularly check this string for any additional
+characters, and output them, allowing real-time text output, rather than
+just printing the final output at the end.
+
+
+GBS versions
+------------
+Many GBS-based tests require that the GBS player either not interrupt
+the init routine with the play routine, or if they do, not interrupt the
+play routine again if it hasn't returned yet. This is because many tests
+need to run for a while without returning.
+
+In addition to the other text output methods described above, GBS builds
+report essential information bytes audibly, including the final result.
+A byte is reported as a series of tones. The code is in binary, with a
+low tone for 0 and a high tone for 1. The first tone is always a zero. A
+final code of 0 means passed, 1 means failure, and 2 or higher indicates
+a specific reason as listed in the source code by the corresponding
+set_code line. Examples:
+
+Tones Binary Decimal Meaning
+- - - - - - - - - - - - - - - - - - - -
+low 0 0 passed
+low high 01 1 failed
+low high low 010 2 error 2
+
+--
+Shay Green
diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/01-registers.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/01-registers.gb
new file mode 100644
index 0000000..c1fa6c5
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/rom_singles/01-registers.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/02-len ctr.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/02-len ctr.gb
new file mode 100644
index 0000000..d940c7a
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/rom_singles/02-len ctr.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/03-trigger.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/03-trigger.gb
new file mode 100644
index 0000000..1b0f032
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/rom_singles/03-trigger.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/04-sweep.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/04-sweep.gb
new file mode 100644
index 0000000..746b78a
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/rom_singles/04-sweep.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/05-sweep details.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/05-sweep details.gb
new file mode 100644
index 0000000..55351a3
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/rom_singles/05-sweep details.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/06-overflow on trigger.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/06-overflow on trigger.gb
new file mode 100644
index 0000000..d3fbe3b
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/rom_singles/06-overflow on trigger.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/07-len sweep period sync.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/07-len sweep period sync.gb
new file mode 100644
index 0000000..3da3bd7
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/rom_singles/07-len sweep period sync.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/08-len ctr during power.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/08-len ctr during power.gb
new file mode 100644
index 0000000..c72bbf2
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/rom_singles/08-len ctr during power.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/09-wave read while on.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/09-wave read while on.gb
new file mode 100644
index 0000000..b4a79ad
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/rom_singles/09-wave read while on.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/10-wave trigger while on.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/10-wave trigger while on.gb
new file mode 100644
index 0000000..a7c7ddc
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/rom_singles/10-wave trigger while on.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/11-regs after power.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/11-regs after power.gb
new file mode 100644
index 0000000..773e01e
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/rom_singles/11-regs after power.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/12-wave write while on.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/12-wave write while on.gb
new file mode 100644
index 0000000..e0119b9
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/rom_singles/12-wave write while on.gb differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/01-registers.s b/playing-coffee - Copy/roms/dmg_sound/source/01-registers.s
new file mode 100644
index 0000000..ea9f5d5
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/01-registers.s
@@ -0,0 +1,117 @@
+; - APU registers always have some bits set when read back.
+; - Wave memory can be read back freely.
+; - When powered off, registers are cleared, except high bit of NR52.
+; - While off, register writes are ignored, but not reads.
+; - Wave RAM is always readable and writable, and unaffected by power.
+
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ set_test 2,"NR10-NR51 and wave RAM write/read"
+ ld d,0
+- call test_rw
+ inc d
+ jr nz,-
+
+ set_test 3,"NR52 write/read"
+ wreg NR52,$00
+ lda NR52
+ cp $70
+ jp nz,test_failed
+ wreg NR52,$FF
+ lda NR52
+ cp $F0
+ jp nz,test_failed
+
+ set_test 4,"Powering APU shouldn't affect wave"
+ ld a,$37
+ call fill_wave
+ wreg NR52,$00
+
+ ; Verify that wave RAM is unchanged
+ ld hl,WAVE
+- ld a,(hl+)
+ cp $37
+ jp nz,test_failed
+ ld a,l
+ cp $40
+ jr nz,-
+ wreg NR52,$80 ; on
+
+ set_test 5,"Powering APU off should write 0 to all regs"
+ ld a,$FF
+ call fill_apu_regs
+ wreg NR52,$00
+ wreg NR52,$80
+ call regs_should_be_clear
+
+ set_test 6,"When off, should ignore writes to registers"
+ wreg NR52,$00
+ ld a,$FF
+ call fill_apu_regs
+ wreg NR52,$80
+ call regs_should_be_clear
+ wreg NR52,$80
+
+ set_test 7,"When off, should allow normal register reads"
+ wreg NR52,$00
+ call regs_should_be_clear
+ wreg NR52,$80
+
+ jp tests_passed
+
+regs_should_be_clear:
+ ld bc,masks
+ ld hl,NR10
+- ld a,(bc)
+ cp (hl)
+ jp nz,test_failed
+ inc bc
+ inc l
+ ld a,l
+ cp disabled clocks as well
+ ld a,1
+ call end
+
+ call begin
+ wchn 4,$40 ; enable length
+ wchn 1,-2 ; length = 2
+ wchn 4,$40 ; enabled -> enabled doesn't clock
+ wchn 4,$00 ; enabled -> disabled doesn't clock
+ ld a,2
+ call end
+.else
+ set_test 4,"Anything besides enabling shouldnt't clock"
+ call begin
+ wchn 4,$40 ; enable length
+ wchn 1,-2 ; length = 2
+ wchn 4,$40 ; enabled -> enabled doesn't clock
+ wchn 4,$00 ; enabled -> disabled doesn't clock
+ wchn 4,$00 ; disabled -> disabled doesn't clock
+ ld a,2
+ call end
+.endif
+
+ set_test 5,"If clock makes length zero, should disable chan"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ lda chan_mask
+ ld b,a
+ lda NR52 ; channel now disabled
+ and b
+ jp nz,test_failed
+
+ set_test 6,"If length already reached zero, shouldn't clock"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,0
+ wchn 4,$40 ; no clock; length still 0
+ wchn 4,0
+ wchn 4,$40 ; no clock; length still 0
+ lda chan_maxlen; end triggers channel, which loads it with max length
+ call end
+
+ set_test 7,"Trigger should un-freeze length that reached zero"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,$00 ; disable
+ wchn 4,$80 ; trigger unfreezes length, so it takes on maximum value
+ delay_clocks 8192
+ wchn 4,$40 ; enable
+ delay_apu 2 ; clock length by 2
+ lda chan_maxlen
+ sub 2
+ call end_nodelay
+
+ set_test 8,"Trigger that un-freezes enabled length should clock it"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,$00 ; disable
+ wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it
+ lda chan_maxlen
+ dec a
+ call end_nodelay
+
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it
+ lda chan_maxlen
+ dec a
+ call end_nodelay
+
+ set_test 9,"Triggering that clocks length of 1 ","should clock twice and shouldn't freeze"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$C0 ; trigger and enable
+ ; First length counter is enabled, which clocks it to 0 and freezes it
+ ; Trigger unfreezes length counter, which clocks it AGAIN
+ ; The result is the same as the previous test, which enables separately
+ lda chan_maxlen
+ dec a
+ call end_nodelay
+
+ set_test 10,"Trigger shouldn't otherwise affect length"
+ call begin
+ wchn 1,0 ; length = max
+ delay_clocks 8192
+ wchn 4,$80 ; trigger
+ lda chan_maxlen
+ call end_nodelay
+
+.ifndef CGB_02
+ call begin
+ wchn 1,0 ; length = max
+ wchn 4,$80 ; trigger
+ lda chan_maxlen
+ call end
+
+ call begin
+ wchn 1,-2 ; length = 2
+ wchn 4,$80 ; trigger
+ ld a,2
+ call end
+.endif
+
+ set_test 11,"Disabled DAC shouldn't stop other trigger effects"
+ call begin
+ wchn 0,$00 ; disable wave DAC
+ wchn 2,$07 ; disable square/noise DAC
+ wchn 1,-1
+ wchn 4,$C0 ; clocks length, which becomes max
+ wchn 0,$80 ; enable wave DAC
+ wchn 2,$08 ; enable square/noise DAC
+ wchn 4,$80 ; trigger
+ lda chan_maxlen
+ dec a
+ call end
+
+ set_test 12,"Other trigger effects should still occur when disabled"
+ call sync_apu
+ wchn 0,0
+ wchn 4,0
+ wchn 1,-1
+ wchn 4,$40 ; len = 0
+ wchn 4,0
+ wchn 4,$40 ; len = 0
+ wchn 4,$80 ; len = max
+ wchn 4,$40 ; len = max-1
+ wchn 4,0
+ wchn 4,$40 ; len = max-2
+ wchn 0,$80 ; enable now
+ wchn 4,$C0
+ lda chan_maxlen
+ sub 3
+ call delay_apu_cycles
+ lda chan_mask
+ ld b,a
+ lda NR52
+ and b
+ jp z,test_failed
+ delay_apu 1
+ lda NR52
+ and b
+ jp nz,test_failed
+
+ ret
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/04-sweep.s b/playing-coffee - Copy/roms/dmg_sound/source/04-sweep.s
new file mode 100644
index 0000000..17ac4e4
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/04-sweep.s
@@ -0,0 +1,120 @@
+; Calc = calculation of new frequency and check for > $7FF
+; Update = modification of frequency with new calculated value
+.include "shell.inc"
+.include "apu.s"
+
+begin:
+ call sync_sweep
+ wreg NR14,$40
+ wreg NR11,-$21
+ wreg NR12,$08
+ ret
+
+should_be_almost_off:
+ lda NR52
+ and $01
+ jp z,test_failed
+ delay_apu 1
+should_be_off:
+ lda NR52
+ and $01
+ jp nz,test_failed
+ ret
+
+main:
+ set_test 2,"If shift>0, calculates on trigger"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$FF
+ wreg NR14,$C7
+ call should_be_off
+ call begin
+ wreg NR10,$11
+ wreg NR13,$FF
+ wreg NR14,$C7
+ call should_be_off
+
+ set_test 3,"If shift=0, doesn't calculate on trigger"
+ call begin
+ wreg NR10,$10
+ wreg NR13,$FF
+ wreg NR14,$C7
+ delay_apu 1
+ call should_be_almost_off
+
+ set_test 4,"If period=0, doesn't calculate"
+ call begin
+ wreg NR10,$00
+ wreg NR13,$FF
+ wreg NR14,$C7
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 5,"After updating frequency, calculates a second time"
+ call begin
+ wreg NR10,$11
+ wreg NR13,$00
+ wreg NR14,$C5
+ delay_apu 1
+ call should_be_almost_off
+
+ set_test 6,"If calculation>$7FF, disables channel"
+ call begin
+ wreg NR10,$02
+ wreg NR13,$67
+ wreg NR14,$C6
+ call should_be_off
+
+ set_test 7,"If calculation<=$7FF, doesn't disable channel"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$55
+ wreg NR14,$C5
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 8,"If shift=0 and period>0, trigger enables"
+ call begin
+ wreg NR10,$10
+ wreg NR13,$FF
+ wreg NR14,$C3
+ delay_apu 2
+ wreg NR10,$11
+ delay_apu 1
+ call should_be_almost_off
+
+ set_test 9,"If shift>0 and period=0, trigger enables"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$FF
+ wreg NR14,$C3
+ delay_apu 15
+ wreg NR10,$11
+ call should_be_almost_off
+
+ set_test 10,"If shift=0 and period=0, trigger disables"
+ call begin
+ wreg NR10,$08
+ wreg NR13,$FF
+ wreg NR14,$C3
+ wreg NR10,$11
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 11,"If shift=0, doesn't update"
+ call begin
+ wreg NR10,$10
+ wreg NR13,$FF
+ wreg NR14,$C3
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 12,"If period=0, doesn't update"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$00
+ wreg NR14,$C5
+ delay_apu $20
+ call should_be_almost_off
+
+ jp tests_passed
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/05-sweep details.s b/playing-coffee - Copy/roms/dmg_sound/source/05-sweep details.s
new file mode 100644
index 0000000..1519b68
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/05-sweep details.s
@@ -0,0 +1,121 @@
+; Calc = calculation of new frequency and check for > $7FF
+; Update = modification of frequency with new calculated value
+.include "shell.inc"
+.include "apu.s"
+
+begin:
+ call sync_sweep
+ wreg NR14,$40
+ wreg NR11,-$20
+ wreg NR12,$08
+ ret
+
+should_be_almost_off:
+ lda NR52
+ and $01
+ jp z,test_failed
+ delay_apu 1
+should_be_off:
+ lda NR52
+ and $01
+ jp nz,test_failed
+ ret
+
+main:
+ set_test 2,"Timer treats period 0 as 8"
+ call begin
+ wreg NR10,$11
+ wreg NR13,$00
+ wreg NR14,$C2
+ delay_apu 1
+ wreg NR10,$01 ; sweep enabled
+ delay_apu 3
+ wreg NR10,$11 ; non-zero period so calc will occur when timer reloads
+ delay_apu $11
+ call should_be_almost_off
+
+ set_test 3,"Makes private copy of frequency on trigger"
+ call begin
+ wreg NR10,$12
+ wreg NR13,$04
+ wreg NR14,$80
+ wreg NR13,$00
+ delay_apu $39
+ call should_be_almost_off
+
+ set_test 4,"Exiting negate mode after calculation disables channel"
+ call begin
+ wreg NR10,$09 ; since shift > 0, calculates sweep value at init
+ wreg NR13,$00
+ wreg NR14,$C0
+ delay_apu 2
+ wreg NR10,$10 ; neg->pos, so disables channel
+ call should_be_off
+
+ set_test 5,"Ending negate after it maybe changed freq disables chan"
+ call begin
+ wreg NR10,$10 ; enable sweep
+ wreg NR13,$00
+ wreg NR14,$C0
+ delay_apu 2
+ wreg NR10,$18 ; negate mode
+ delay_apu 2
+ wreg NR10,$10 ; neg->pos, so disables channel
+ call should_be_off
+
+ set_test 6,"Ending negate mode any other way doesn't disable channel"
+ call begin
+ wreg NR10,$1F ; use negate mode once
+ wreg NR14,$C0
+ delay_apu 2
+ wreg NR10,$18 ; since period > 0, doesn't calculate at init
+ wreg NR13,$00
+ wreg NR14,$C0
+ delay_apu 1 ; no sweep clock here
+ wreg NR10,$10 ; pos mode before neg mode ever used
+ delay_apu 1 ; sweep clock occurs here
+ wreg NR10,$0F ; now let neg mode be seen once, but period = 0 so no calculation is made
+ delay_apu 2 ; sweep clock occurs here
+ wreg NR10,$10 ; doesn't affect channel
+ delay_apu 2 ; sweep clock occurs here
+ wreg NR10,$1F ; let neg mode get used
+ delay_apu 18
+ wreg NR10,$79 ; period and shift can be changed without channel disabling
+ delay_apu 5
+ call should_be_almost_off
+
+ set_test 7,"Subtract mode uses two's complement"
+ call begin
+ delay 2048 ; avoids extra length clocking on CGB-02
+ wreg NR10,$1C
+ wreg NR13,$B0
+ wreg NR14,$85
+ delay_apu 2
+ wreg NR10,$01
+ wreg NR14,$C5
+ delay_apu $1F
+ call should_be_almost_off
+
+ set_test 8,"Subtract mode uses two's complement (upper bound)"
+ call begin
+ wreg NR10,$1C
+ wreg NR13,$B1
+ wreg NR14,$85
+ delay_apu 2
+ wreg NR10,$01
+ wreg NR14,$C5
+ call should_be_off
+
+ set_test 9,"Update channel frequency only when period is reloaded"
+ call begin
+ wreg NR10,$74
+ wreg NR13,$06
+ wreg NR14,$85
+ delay_apu 14 ; just reloaded
+ wreg NR13,$06
+ delay_apu 13 ; if 14, fails
+ wreg NR10,$11
+ wreg NR14,$85 ; just before next reload, so freq is still $506
+ call should_be_almost_off
+
+ jp tests_passed
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/06-overflow on trigger.s b/playing-coffee - Copy/roms/dmg_sound/source/06-overflow on trigger.s
new file mode 100644
index 0000000..2ebc236
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/06-overflow on trigger.s
@@ -0,0 +1,64 @@
+; Finds highest and lowest frequencies that don't overflow
+; immediately on trigger, for NR10 values of $00-$07
+
+.include "shell.inc"
+.include "apu.s"
+
+main:
+
+ ; DMG-06:
+ ; 0555 0666 071C 0787 07C1 07E0 07F0
+
+ wreg NR12,8
+ ld d,$01
+shift_loop:
+ ld a,d
+ sta NR10
+ ld bc,$87FF
+- ld a,c
+ sta NR13
+ ld a,b
+ sta NR14
+ delay_clocks 40
+ lda NR52
+ and 1
+ jr nz,+
+ dec bc
+ bit 6,b
+ jr z,-
++ res 7,b
+ call print_bc
+ inc d
+ bit 3,d
+ jr z,shift_loop
+ call print_newline
+ check_crc $F604603B
+
+ ; DMG-05, DMG-06, DMG-09, CGB-04, CGB-05:
+ ; 0556 0667 071D 0788 07C2 07E1 07F1
+
+ wreg NR12,8
+ ld d,$01
+shift_loop2:
+ ld a,d
+ sta NR10
+ ld bc,$8000
+- ld a,c
+ sta NR13
+ ld a,b
+ sta NR14
+ delay_clocks 40
+ lda NR52
+ and 1
+ jr z,+
+ inc bc
+ bit 6,b
+ jr z,-
++ res 7,b
+ call print_bc
+ inc d
+ bit 3,d
+ jr z,shift_loop2
+ check_crc $5A1697EE
+
+ jp tests_passed
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/07-len sweep period sync.s b/playing-coffee - Copy/roms/dmg_sound/source/07-len sweep period sync.s
new file mode 100644
index 0000000..6b3ed7e
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/07-len sweep period sync.s
@@ -0,0 +1,106 @@
+; Tests length and sweep periods, and synchronization between the two
+.include "shell.inc"
+.include "apu.s"
+
+test_timing:
+ ; Time how long until next length clock
+- inc de
+ ld a,(NR52)
+ and $01
+ jr nz,-
+
+ ;call print_de
+
+ ; Error if not in 0...4 range
+ ld a,d
+ cp 0
+ jp nz,test_failed
+ ld a,e
+ cp 5
+ jp nc,test_failed
+ ret
+
+main:
+
+ set_test 2,"Length period is wrong"
+ call sync_apu
+ wreg NR14,$40 ; avoids extra length clock
+ wreg NR11,$3F ; length = $01
+ wreg NR12,$08 ; silent without disabling channel
+ wreg NR14,$C0 ; start length
+ ld de,-$170
+ call test_timing
+
+ set_test 3,"Sweep period is wrong"
+ call sync_sweep
+ wreg NR10,$10 ; sweep period = 1
+ wreg NR12,$08 ; silent without disabling channel
+ wreg NR13,$FF ; max freq
+ wreg NR14,$87 ; start
+ ld de,-$2E4
+ call test_timing
+
+ set_test 4,"Sweep clock is synchronized with length"
+ call sync_sweep
+ wreg NR14,$40 ; avoids extra length clock
+ wreg NR11,$3F ; length = $01
+ wreg NR12,$08 ; silent without disabling channel
+ wreg NR14,$C0 ; start length
+ ld de,-$170
+ call test_timing
+
+ set_test 5,"Powering up APU MODs next frame time with 8192"
+ call sync_apu
+ ld de,-$16F
+ call test_power
+
+ call sync_apu
+ ld de,-$B5
+ call test_power_off
+
+ call sync_apu
+ delay_clocks 8192
+ ld de,-$B5
+ call test_power
+
+ call sync_apu
+ delay_clocks 8192
+ ld de,-$B5
+ call test_power_off
+
+ call sync_apu
+ ld de,-$B5
+ wreg NR52,$00 ; power off
+ delay_clocks 8192
+ call test_power
+
+ set_test 6,"Powering up APU resets 128 Hz sweep divider"
+ call sync_sweep
+ ld de,-$229
+ call test_power2
+
+ call sync_sweep
+ delay_apu 1
+ ld de,-$229
+ call test_power2
+
+ jp tests_passed
+
+test_power_off:
+ wreg NR52,$00 ; power off
+test_power:
+ wreg NR52,$80 ; power on
+ wreg NR14,$40
+ wreg NR11,-1 ; length = 1
+ wreg NR12,8
+ wreg NR14,$C0
+ jp test_timing
+
+test_power2:
+ wreg NR52,$00 ; power off
+ wreg NR52,$80 ; power on
+ wreg NR10,$11
+ wreg NR12,8
+ wreg NR13,$00
+ wreg NR14,$84
+ jp test_timing
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/08-len ctr during power.s b/playing-coffee - Copy/roms/dmg_sound/source/08-len ctr during power.s
new file mode 100644
index 0000000..1ea218a
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/08-len ctr during power.s
@@ -0,0 +1,84 @@
+; On CGB, length counters are reset when powered up.
+; On DMG, they are unaffected, and not clocked.
+
+;.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+enable_len_ctrs:
+ wreg NR22,8
+ wreg NR24,$C0
+ wreg NR12,8
+ wreg NR14,$C0
+ wreg NR30,$80
+ wreg NR34,$C0
+ wreg NR42,8
+ wreg NR44,$C0
+ ret
+
+main:
+ call sync_apu
+
+ ld a,0
+ call fill_apu_regs
+
+ ; Load length counters
+ wreg NR41,-$33
+ wreg NR31,-$44
+ wreg NR11,-$11
+ wreg NR21,-$22
+
+ delay_clocks 8192
+ call enable_len_ctrs
+
+ ; Power down. Comment out to see what would
+ ; happen if length counters did run.
+ wreg NR52,$00
+
+ ; Try to enable length counters
+ call enable_len_ctrs
+
+ ; Give plenty of time for them to be clocked
+ delay_msec 250
+
+ ; Power back on and wait a bit longer
+ wreg NR52,$80
+ ;call enable_len_ctrs ; can't do this here
+ delay_clocks 2048
+
+ ; Get values from length counters
+ wreg NR22,8
+ wreg NR24,$C0
+ ld a,$02
+ call get_len_a
+ push af
+
+ wreg NR12,8
+ wreg NR14,$C0
+ ld a,$01
+ call get_len_a
+ push af
+
+ wreg NR30,$80
+ wreg NR34,$C0
+ ld a,$04
+ call get_len_a
+ push af
+
+ wreg NR42,8
+ wreg NR44,$C0
+ ld a,$08
+ call get_len_a
+
+ ; Print them
+ call print_a
+ pop af
+ call print_a
+ pop af
+ call print_a
+ pop af
+ call print_a
+
+ check_crc_dmg_cgb $32F0CFBB,$3CF589B4
+ jp tests_passed
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/09-wave read while on.s b/playing-coffee - Copy/roms/dmg_sound/source/09-wave read while on.s
new file mode 100644
index 0000000..2d06a77
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/09-wave read while on.s
@@ -0,0 +1,41 @@
+; Reads from wave RAM while playing, each time 2
+; clocks later.
+
+;.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ wreg NR51,0 ; mute sound
+ loop_n_times test,69
+ check_crc_dmg_cgb $118A3620,$270DA9A3
+ jp tests_passed
+
+test:
+ add $99
+ ld b,a
+
+ ; Reload wave and have its first
+ ; sample read occur 2 clocks earlier
+ ; each loop iteration
+ ld hl,wave
+ call load_wave
+ wreg NR30,$80 ; enable
+ wreg NR32,$00 ; silent
+ ld a,b
+ sta NR33 ; period
+ wreg NR34,$87 ; start
+
+ ; Read from wave
+ wreg NR33,-2 ; period = 4
+ delay_clocks 176
+ lda WAVE
+
+ call print_a
+
+ ret
+
+wave:
+ .byte $00,$11,$22,$33,$44,$55,$66,$77
+ .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/10-wave trigger while on.s b/playing-coffee - Copy/roms/dmg_sound/source/10-wave trigger while on.s
new file mode 100644
index 0000000..d7ec063
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/10-wave trigger while on.s
@@ -0,0 +1,49 @@
+; Retriggers wave without stopping first
+
+;.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ wreg NR51,0 ; mute sound
+ loop_n_times test,69
+ check_crc_dmg_cgb $533D6D4D,$8130733A
+ jp tests_passed
+
+test:
+ add $99
+ ld b,a
+
+ ; Reload wave and have its first
+ ; sample read occur 2 clocks earlier
+ ; each loop iteration
+ ld hl,wave
+ call load_wave
+ wreg NR30,$80 ; enable
+ wreg NR32,$00 ; silent
+ ld a,b
+ sta NR33 ; period
+ wreg NR34,$87 ; start
+
+ ; Retrigger wave
+ wreg NR33,-2 ; period = 4
+ delay_clocks 168
+ wreg NR34,$87 ; restart
+ delay_clocks 40
+
+ ; Print wave RAM
+ wreg NR30,0
+ ld c,$30
+- ld a,($FF00+c)
+ call print_a
+ inc c
+ bit 6,c
+ jr z,-
+ call print_newline
+
+ ret
+
+wave:
+ .byte $00,$11,$22,$33,$44,$55,$66,$77
+ .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/11-regs after power.s b/playing-coffee - Copy/roms/dmg_sound/source/11-regs after power.s
new file mode 100644
index 0000000..56506bb
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/11-regs after power.s
@@ -0,0 +1,52 @@
+; After powering sound off then on, NR12 and NR14 are 0
+; and NR44 is unchanged. While powered off, writes to
+; NR41 are NOT ignored.
+
+.define REQUIRE_DMG 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ call sync_apu
+ ld a,$FF
+ call fill_apu_regs
+
+ ; Power down for a moment
+ wreg NR52,$00
+ wreg NR41,-$12
+ wreg NR12,$F0
+ delay_msec 100
+ wreg NR52,$80
+
+ set_test 2,"Powering off should clear NR12"
+ call sync_apu
+ wreg NR14,$80
+ lda NR52
+ and $01
+ jp nz,test_failed
+
+ set_test 3,"Powering off should clear NR13"
+ call sync_apu
+ wreg NR10,$11
+ wreg NR12,$08
+ wreg NR14,$80
+ delay_apu 20
+ lda NR52
+ and $01
+ jp z,test_failed
+
+ set_test 4,"Powering off shouldn't affect NR41"
+ call sync_apu
+ delay_clocks 8192 ; avoids extra length clocking
+ wreg NR42,$08
+ wreg NR44,$C0
+ delay_apu $11
+ lda NR52
+ and $08
+ jp z,test_failed
+ delay_apu 1
+ lda NR52
+ and $08
+ jp nz,test_failed
+
+ jp tests_passed
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/12-wave write while on.s b/playing-coffee - Copy/roms/dmg_sound/source/12-wave write while on.s
new file mode 100644
index 0000000..de4df7e
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/12-wave write while on.s
@@ -0,0 +1,49 @@
+; Writes to wave RAM while playing
+
+.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ loop_n_times test,69
+
+ ; CGB behaves erratically, so DMG-only for now
+ check_crc_dmg_cgb $3B4538A9,$2B27544E
+ jp tests_passed
+
+test:
+ add $99
+ ld b,a
+
+ ; Reload wave and have its first
+ ; sample read occur 2 clocks earlier
+ ; each loop iteration
+ ld hl,wave
+ call load_wave
+ wreg NR30,$80 ; enable
+ wreg NR32,$00 ; silent
+ ld a,b
+ sta NR33 ; period
+ wreg NR34,$87 ; start
+
+ ; Write to wave
+ wreg NR33,-2 ; period = 4
+ delay_clocks 168
+ wreg WAVE,$F7
+
+ ; Print wave RAM
+ wreg NR30,0
+ delay 1000
+ ld hl,WAVE
+- ld a,(hl+)
+ call print_a
+ bit 6,l
+ jr z,-
+ call print_newline
+
+ ret
+
+wave:
+ .byte $00,$11,$22,$33,$44,$55,$66,$77
+ .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/build_gbs.s b/playing-coffee - Copy/roms/dmg_sound/source/common/build_gbs.s
new file mode 100644
index 0000000..877754f
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/common/build_gbs.s
@@ -0,0 +1,139 @@
+; Build as GBS music file
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $2000 size $2000
+ slot 1 $C000 size $2000
+.endMe
+
+.romBankSize $2000
+.romBanks 2
+
+.define RST_OFFSET $70
+
+.ifndef GBS_TMA
+ .define GBS_TMA 0
+.endif
+
+.ifndef GBS_TAC
+ .define GBS_TAC 0
+.endif
+
+;;;; GBS music file header
+
+.ifndef CUSTOM_HEADER
+ .byte "GBS"
+ .byte 1,1,1 ; vers, song count, first song
+ .word load_addr, reset, gbs_play_, std_stack
+ .byte GBS_TMA,GBS_TAC ; timer
+.endif
+ .org $10
+ .ds $60,0
+load_addr:
+ .org RST_OFFSET+$70 ; space for RST vectors
+ .ds $148-RST_OFFSET-$70,0
+ .org $150 ; wla insists on generating GB header
+
+gbs_play_:
+ jp gbs_play ; GBS spec disallows having gbs_play in RAM
+
+;;;; Shell
+
+.include "shell.s"
+
+.define gbs_idle nv_ram
+.redefine nv_ram nv_ram+2
+
+init_runtime:
+ ; Identify as DMG hardware
+ ld a,$01
+ ld (gb_id),a
+
+ ; Save return address
+ pop hl
+ ld a,l
+ ld (gbs_idle),a
+ ld a,h
+ ld (gbs_idle+1),a
+
+ ; Delay 1/4 second to give time
+ ; for GBS player to interrupt with
+ ; play, if it's going to do so
+ delay_msec 250
+
+.ifndef CUSTOM_PLAY
+gbs_play:
+.endif
+ ; Get return address
+ ld a,(gbs_idle)
+ ld l,a
+ ld a,(gbs_idle+1)
+ ld h,a
+
+ ; If zero, then play interrupted init
+ ; call, or another play call, and we
+ ; can't run the program properly.
+ or l
+ jp z,internal_error
+
+ setw gbs_idle,0
+ jp hl
+
+
+; Reports A in binary as high and low tones, with
+; leading low tone for reference. Omits leading
+; zeroes.
+; Preserved: AF, BC, DE, HL
+play_byte:
+ push af
+ push hl
+
+ ; HL = (A << 1) | 1
+ scf
+ rla
+ ld l,a
+ ld h,0
+ rl h
+
+ ; Shift left until next-to-top bit is 1
+- add hl,hl
+ bit 6,h
+ jr z,-
+
+ ; Reset sound
+ delay_msec 400
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+- add hl,hl
+
+ ; Low or high pitch based on bit shifted out
+ ; of HL
+ ld a,0
+ jr nc,+
+ ld a,$FF
++ sta NR23
+
+ ; Play short tone
+ wreg NR21,$A0
+ wreg NR22,$F0
+ wreg NR24,$86
+ delay_msec 75
+ wreg NR22,0
+ wreg NR23,$F8
+ wreg NR24,$87
+ delay_msec 200
+
+ ; Loop until HL = $8000
+ ld a,h
+ xor $80
+ or l
+ jr nz,-
+
+ pop hl
+ pop af
+ ret
+
+.ends
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/build_rom.s b/playing-coffee - Copy/roms/dmg_sound/source/common/build_rom.s
new file mode 100644
index 0000000..c1595b7
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/common/build_rom.s
@@ -0,0 +1,70 @@
+; Build as GB ROM
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $0000 size $4000
+ slot 1 $C000 size $4000
+.endMe
+
+.romBankSize $4000
+.romBanks 2
+
+.cartridgeType 3 ; MBC1+RAM+battery
+.ramsize 02 ; 8K
+.computeChecksum
+.computeComplementCheck
+
+;;;; GB ROM header
+
+ ; Reserve space for RST handlers
+ .org $70
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .ds $90,0
+
+ ; GB header read by bootrom
+ .org $100
+ nop
+ jp reset
+
+ ; Nintendo logo required for proper boot
+ .byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
+ .byte $03,$73,$00,$83,$00,$0C,$00,$0D
+ .byte $00,$08,$11,$1F,$88,$89,$00,$0E
+ .byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
+ .byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
+ .byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
+
+ ; Internal name
+ .ifdef ROM_NAME
+ .byte ROM_NAME
+ .endif
+
+ ; CGB/DMG requirements
+ .org $143
+ .ifdef REQUIRE_CGB
+ .byte $C0
+ .else
+ .ifndef REQUIRE_DMG
+ .byte $80
+ .endif
+ .endif
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .org $150
+ .ds $2150-$150,0
+
+;;;; Shell
+
+.define NEED_CONSOLE 1
+.include "shell.s"
+
+init_runtime:
+ ret
+
+play_byte:
+ ret
+
+.ends
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/console.bin b/playing-coffee - Copy/roms/dmg_sound/source/common/console.bin
new file mode 100644
index 0000000..b02f2d3
Binary files /dev/null and b/playing-coffee - Copy/roms/dmg_sound/source/common/console.bin differ
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/console.s b/playing-coffee - Copy/roms/dmg_sound/source/common/console.s
new file mode 100644
index 0000000..403fa5c
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/common/console.s
@@ -0,0 +1,285 @@
+; Scrolling text console
+
+; Console is 20x18 characters. Buffers lines, so
+; output doesn't appear until a newline or flush.
+; If scrolling isn't supported (i.e. SCY is treated
+; as if always zero), the first 18 lines will
+; still print properly). Also works properly if
+; LY isn't supported (always reads back as the same
+; value).
+
+.define console_width 20
+
+.define console_buf bss+0
+.define console_pos bss+console_width
+.define console_mode bss+console_width+1
+.define console_scroll bss+console_width+2
+.redefine bss bss+console_width+3
+
+
+; Waits for start of LCD blanking period
+; Preserved: BC, DE, HL
+console_wait_vbl:
+ push bc
+
+ ; Wait for start of vblank, with
+ ; timeout in case LY doesn't work
+ ; or LCD is disabled.
+ ld bc,-1250
+- inc bc
+ ld a,b
+ or c
+ jr z,@timeout
+ lda LY
+ cp 144
+ jr nz,-
+@timeout:
+
+ pop bc
+ ret
+
+
+; Initializes text console
+console_init:
+ call console_hide
+
+ ; CGB-specific inits
+ ld a,(gb_id)
+ and gb_id_cgb
+ call nz,@init_cgb
+
+ ; Clear nametable
+ ld a,' '
+ call @fill_nametable
+
+ ; Load tiles
+ ld hl,TILES+$200
+ ld c,0
+ call @load_tiles
+ ld hl,TILES+$A00
+ ld c,$FF
+ call @load_tiles
+
+ ; Init state
+ setb console_pos,console_width
+ setb console_mode,0
+ setb console_scroll,-8
+ call console_scroll_up_
+ jr console_show
+
+@fill_nametable:
+ ld hl,BGMAP0
+ ld b,4
+- ld (hl),a
+ inc l
+ jr nz,-
+ inc h
+ dec b
+ jr nz,-
+ ret
+
+@init_cgb:
+ ; Clear palette
+ wreg $FF68,$80
+ ld b,16
+- wreg $FF69,$FF
+ wreg $FF69,$7F
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ dec b
+ jr nz,-
+
+ ; Clear attributes
+ wreg VBK,1
+ ld a,0
+ call @fill_nametable
+
+ wreg VBK,0
+ ret
+
+@load_tiles:
+ ld de,ASCII
+ ld b,96
+-- push bc
+ ld b,8
+- ld a,(de)
+ inc de
+ xor c
+ ldi (hl),a
+ ldi (hl),a
+ dec b
+ jr nz,-
+ pop bc
+ dec b
+ jr nz,--
+ ret
+
+
+; Shows console display
+; Preserved: AF, BC, DE, HL
+console_show:
+ push af
+
+ ; Enable LCD
+ call console_wait_vbl
+ wreg LCDC,$91
+ wreg SCX,0
+ wreg BGP,$E4
+
+ jp console_apply_scroll_
+
+
+; Hides console display by turning LCD off
+; Preserved: AF, BC, DE, HL
+console_hide:
+ push af
+
+ ; LCD off
+ call console_wait_vbl
+ wreg LCDC,$11
+
+ pop af
+ ret
+
+
+; Changes to normal text mode
+; Preserved: BC, DE, HL
+console_normal:
+ xor a
+ jr console_set_mode
+
+; Changes to inverse text mode
+; Preserved: BC, DE, HL
+console_inverse:
+ ld a,$80
+
+; Changes console mode to A.
+; 0: Normal, $80: Inverse
+; Preserved: BC, DE, HL
+console_set_mode:
+ and $80
+ ld (console_mode),a
+ ret
+
+
+; Prints char A to console. Will not appear until
+; a newline or flush occurs.
+; Preserved: AF, BC, DE, HL
+console_print:
+ push af
+
+ cp 10
+ jr z,console_newline_
+
+ push hl
+ push af
+ ld hl,console_pos
+ ldi a,(hl)
+ cp BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/delay.s b/playing-coffee - Copy/roms/dmg_sound/source/common/delay.s
new file mode 100644
index 0000000..cbcdcf3
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+.endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+.endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+.endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/gb.inc b/playing-coffee - Copy/roms/dmg_sound/source/common/gb.inc
new file mode 100644
index 0000000..2d0118d
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/common/gb.inc
@@ -0,0 +1,81 @@
+; Game Boy hardware addresses
+
+; $0000-$3FFF Fixed ROM bank
+; $4000-$7FFF Switchable bank
+; $8000-$9FFF VRAM
+; $A000-$BFFF optional cartridge RAM
+; $C000-$DFFF RAM
+; $E000-$FDFF RAM mirror
+; $FE00-$FE9F OAM
+; $FEA0-$FEFF unused
+; $FF00-$FF7F registers
+; $FF80-$FFFE RAM
+; $FFFF register
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define BRAM $A000 ; cart memory
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+; Registers
+
+.define RAMEN $0000 ; cartridge WRAM control
+.define BANK $2000 ; bank select
+.define P1 $FF00 ; controller
+
+; Game link I/O
+.define SB $FF01 ; serial buffer
+.define SC $FF02 ; serial control
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/macros.inc b/playing-coffee - Copy/roms/dmg_sound/source/common/macros.inc
new file mode 100644
index 0000000..9d7cf44
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/common/macros.inc
@@ -0,0 +1,91 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ; addr
+ ldh a,(\1 - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ; addr
+ ldh (\1 - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Writes byte to addr
+; Preserved: F, BC, DE, HL
+; Time: 6 cycles
+.macro setb ; addr, data
+ ld a,\2
+ ld (\1),a
+.endm
+
+; Writes word to addr
+; Preserved: F, BC, DE, HL
+; Time: 12 cycles
+.macro setw ; addr, data
+ ld a,<\2
+ ld (\1),a
+ ld a,>\2
+ ld (\1+1),a
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/numbers.s b/playing-coffee - Copy/roms/dmg_sound/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/printing.s b/playing-coffee - Copy/roms/dmg_sound/source/common/printing.s
new file mode 100644
index 0000000..bb9389b
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/common/printing.s
@@ -0,0 +1,77 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+;print_char_nocrc (defined by user)
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/shell.s b/playing-coffee - Copy/roms/dmg_sound/source/common/shell.s
new file mode 100644
index 0000000..3e588c7
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/common/shell.s
@@ -0,0 +1,261 @@
+; Common routines and runtime
+
+.define RUNTIME_INCLUDED 1
+
+; A few bytes of RAM that aren't cleared
+.define nv_ram_base $D800
+.define nv_ram nv_ram_base
+
+; Address of next normal variable
+.define bss_base nv_ram_base+$80
+.define bss bss_base
+
+; Address of next direct-page ($FFxx) variable
+.define dp_base $FF80
+.define dp dp_base
+
+; Top of stack
+.define std_stack $DFFF+1
+
+; Final exit result byte is written here
+.define final_result $A000
+
+; Text output is written here as zero-terminated string
+.define text_out_base $A004
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+.define gb_id nv_ram
+.redefine nv_ram nv_ram+1
+
+; Copies C*$100 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+- ld a,(hl+)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+
+.ifndef RST_OFFSET
+ .define RST_OFFSET 0
+.endif
+
+.ifndef CUSTOM_RESET
+ reset:
+ di
+
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ ld c,$14
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org 0
+ jp std_reset
+.endif
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
+
+ ; Catch user code running off end
+ jp internal_error
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld (gb_id),a
+
+ ; Clear memory except very top of stack
+ ld bc,std_stack-bss_base - 2
+ ld hl,bss_base
+ call clear_mem
+ ld bc,$FFFF-dp_base
+ ld hl,dp_base
+ call clear_mem
+
+ ; Init hardware
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ call init_runtime
+ call init_text_out
+ call console_init
+ call init_testing
+
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ call console_show
+ pop af
+ call play_byte
+ jp post_exit
+
++ push af
+ call print_newline
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+
+; Clears BC bytes starting at HL
+clear_mem:
+ ; If C>0, increment B
+ dec bc
+ inc c
+ inc b
+
+ ld a,0
+- ld (hl+),a
+ dec c
+ jr nz,-
+ dec b
+ jr nz,-
+ ret
+
+
+; Reports internal error and exits with code 255
+internal_error:
+ print_str "Internal error"
+ ld a,255
+ jp exit
+
+
+; build_devcart and build_multi customize this
+.ifndef CUSTOM_PRINT
+ .define text_out_addr bss+0
+ .redefine bss bss+2
+
+ ; Initializes text output to cartridge RAM
+ init_text_out:
+ ; Enable cartridge RAM and set text output pointer
+ setb RAMEN,$0A
+ setw text_out_addr,text_out_base
+ setb text_out_base-3,$DE
+ setb text_out_base-2,$B0
+ setb text_out_base-1,$61
+ setb text_out_base,0
+ setb final_result,$80
+ ret
+
+
+ ; Appends character to text output string
+ ; Preserved: AF, BC, DE, HL
+ write_text_out:
+ push hl
+ push af
+ ld a,(text_out_addr)
+ ld l,a
+ ld a,(text_out_addr+1)
+ ld h,a
+ inc hl
+ ld (hl),0
+ ld a,l
+ ld (text_out_addr),a
+ ld a,h
+ ld (text_out_addr+1),a
+ dec hl
+ pop af
+ ld (hl),a
+ pop hl
+ ret
+
+ print_char_nocrc:
+ call write_text_out
+ jp console_print
+.endif
+
+
+; only build_rom uses console
+.ifdef NEED_CONSOLE
+ .include "console.s"
+.else
+ console_init:
+ console_print:
+ console_flush:
+ console_normal:
+ console_inverse:
+ console_show:
+ console_set_mode:
+ ret
+.endif
+
+
+; build_devcart and build_multi need to customize this
+.ifndef CUSTOM_EXIT
+ post_exit:
+ ld (final_result),a
+ forever:
+ wreg NR52,0 ; sound off
+- jr -
+.endif
+
+
+.macro def_rst ARGS addr
+ .bank 0 slot 0
+ .org addr+RST_OFFSET
+.endm
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/testing.s b/playing-coffee - Copy/roms/dmg_sound/source/common/testing.s
new file mode 100644
index 0000000..5dd63ce
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/common/testing.s
@@ -0,0 +1,195 @@
+; Diagnostic and testing utilities
+
+.define test_code bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text.
+; Takes multiple strings due to wla's idiotic
+; default limit of 63 chars per string.
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2[,text3]]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .if NARGS > 3
+ .byte \4
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (test_code),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(test_code)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(test_code)
+ cp $FF ; if a = $FF then a = 1
+ jr nz,+
+ ld a,1
++ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+; Checks CRC, differing based on DMG or CGB build
+.macro check_crc_dmg_cgb ARGS dmg, cgb
+ .ifdef REQUIRE_DMG
+ check_crc dmg
+ .else
+ .ifdef REQUIRE_CGB
+ check_crc cgb
+ .else
+ .printt "CGB or DMG must be specified"
+ .fail
+ .endif
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/linkfile b/playing-coffee - Copy/roms/dmg_sound/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/readme.txt b/playing-coffee - Copy/roms/dmg_sound/source/readme.txt
new file mode 100644
index 0000000..a0269c1
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/readme.txt
@@ -0,0 +1,82 @@
+Game Boy Tests Source Code
+--------------------------
+
+Building with wla-dx
+--------------------
+To assemble a test ROM with wla-dx, use the following commands:
+
+ wla -o source_filename_here.s test.o
+ wlalink linkfile test.gb
+
+To assemble as a GBS music file:
+
+ wla -o source_filename_here.s test.o -DBUILD_GBS
+ wlalink linkfile test.gbs
+
+Note that some tests might only work when built as a ROM or GBS file,
+but not both.
+
+Some tests might include a ROM/GBS that has all the tests combined.
+Building such a multi-test is complex and the necessary files aren't
+included.
+
+
+Framework
+---------
+Each test is in a single source file, and makes use of several library
+source files from common/. This framework provides common services and
+reduces code to only that which performs the actual test. Virtually all
+tests include "shell.inc" at the beginning, which sets things up and
+includes all the appropriate library files.
+
+The reset handler does minimal GB hardware initialization, clears RAM,
+sets up the text console, then runs main. Main can exit by returning or
+jumping to "exit" with an error code in A. Exit reports the code then
+goes into an infinite loop. If the code is 0, it doesn't do anything,
+otherwise it reports the code. Code 1 is reported as "Failed", and the
+rest as "Error ".
+
+The default is to build a ROM. Defining BUILD_GBS will build as an GBS.
+The other build types aren't supported due to their complexity. I load
+the code into RAM at $C000 since my devcart requires it, and I don't
+want the normal ROM to differ in any way from what I've tested. This
+also allows easy self-modifying code.
+
+Several routines are available to print values and text to the console.
+Most update a running CRC-32 checksum which can be checked with
+check_crc, allowing ALL the output to be checked very easily. If the
+checksum doesn't match, it is printed, so you can run the code on a GB
+and paste the correct checksum into your code.
+
+
+Macros
+------
+Some macros are used to make common operations more convenient. The left
+is equivalent to the right:
+
+ Macro Equivalent
+ -------------------------------------
+ lda addr ldh a,(addr-$FF00)
+
+ sta addr ldh (addr-$FF00),a
+
+ wreg addr,data ld a,data
+ ldh (addr-$FF00),a
+
+ setb ld a,data
+ ld (addr),a
+
+ setw setb addr+0,data
+
+ for_loop routine,begin,end,step
+ calls routine with A set to successive values
+
+ loop_n_times routine,count
+ calls routine with A from 0 to count-1
+
+ print_str "str" prints string
+
+
+--
+Shay Green
diff --git a/playing-coffee - Copy/roms/dmg_sound/source/shell.inc b/playing-coffee - Copy/roms/dmg_sound/source/shell.inc
new file mode 100644
index 0000000..0d43faa
--- /dev/null
+++ b/playing-coffee - Copy/roms/dmg_sound/source/shell.inc
@@ -0,0 +1,27 @@
+; Included at beginning of all programs
+; that use standard shell
+
+; Get include files from common/
+.incdir "common"
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.else
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
+
+.endif ; .ifdef BUILD_MULTI
diff --git a/playing-coffee - Copy/roms/drmario.gb b/playing-coffee - Copy/roms/drmario.gb
new file mode 100644
index 0000000..2f3dc69
Binary files /dev/null and b/playing-coffee - Copy/roms/drmario.gb differ
diff --git a/playing-coffee - Copy/roms/halt_bug.gb b/playing-coffee - Copy/roms/halt_bug.gb
new file mode 100644
index 0000000..38e3662
Binary files /dev/null and b/playing-coffee - Copy/roms/halt_bug.gb differ
diff --git a/playing-coffee - Copy/roms/instr_timing/instr_timing.gb b/playing-coffee - Copy/roms/instr_timing/instr_timing.gb
new file mode 100644
index 0000000..61d2b20
Binary files /dev/null and b/playing-coffee - Copy/roms/instr_timing/instr_timing.gb differ
diff --git a/playing-coffee - Copy/roms/instr_timing/readme.txt b/playing-coffee - Copy/roms/instr_timing/readme.txt
new file mode 100644
index 0000000..b64024f
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/readme.txt
@@ -0,0 +1,139 @@
+Game Boy CPU Instruction Timing Test
+------------------------------------
+This ROM tests the timings of all CPU instructions except HALT, STOP,
+and the 11 illegal opcodes. For conditional instructions, it tests taken
+and not taken timings. This test requires proper timer operation (TAC,
+TIMA, TMA).
+
+Failed instructions are listed as
+
+ [CB] opcode:measured time-correct time
+
+Times are in terms of instruction cycles, where NOP takes one cycle.
+
+
+Verified cycle timing tables
+----------------------------
+The test internally uses a table of proper cycle times, which can be
+used in an emulator to ensure proper timing. The only changes below are
+removal of the .byte prefixes, and addition of commas at the ends, so
+that they can be used without changes in most programming languages. For
+added correctness assurance, the original tables can be found at the end
+of the source code.
+
+Normal instructions:
+
+ 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1,
+ 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1,
+ 2,3,2,2,1,1,2,1,2,2,2,2,1,1,2,1,
+ 2,3,2,2,3,3,3,1,2,2,2,2,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 2,3,3,4,3,4,2,4,2,4,3,0,3,6,2,4,
+ 2,3,3,0,3,4,2,4,2,4,3,0,3,0,2,4,
+ 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4,
+ 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4
+
+CB-prefixed instructions:
+
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2,
+ 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2,
+ 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2,
+ 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+
+
+Internal operation
+------------------
+Before each instruction is executed, the test sets up registers and
+memory in such a way that the instruction will cleanly execute and then
+end up at a common destination, without trashing anything important. The
+timing itself is done by first synchronizing to the timer via a loop,
+executing the instruction, then using a similar loop to determine how
+many clocks elapsed.
+
+
+Failure codes
+-------------
+Failed tests may print a failure code, and also short description of the
+problem. For more information about a failure code, look in the
+corresponding source file in source/; the point in the code where
+"set_test n" occurs is where that failure code will be generated.
+Failure code 1 is a general failure of the test; any further information
+will be printed.
+
+Note that once a sub-test fails, no further tests for that file are run.
+
+
+Console output
+--------------
+Information is printed on screen in a way that needs only minimum LCD
+support, and won't hang if LCD output isn't supported at all.
+Specifically, while polling LY to wait for vblank, it will time out if
+it takes too long, so LY always reading back as the same value won't
+hang the test. It's also OK if scrolling isn't supported; in this case,
+text will appear starting at the top of the screen.
+
+Everything printed on screen is also sent to the game link port by
+writing the character to SB, then writing $81 to SC. This is useful for
+tests which print lots of information that scrolls off screen.
+
+
+Source code
+-----------
+Source code is included for all tests, in source/. It can be used to
+build the individual test ROMs. Code for the multi test isn't included
+due to the complexity of putting everything together.
+
+Code is written for the wla-dx assembler. To assemble a particular test,
+execute
+
+ wla -o "source_filename.s" test.o
+ wlalink linkfile test.gb
+
+Test code uses a common shell framework contained in common/.
+
+
+Internal framework operation
+----------------------------
+Tests use a common framework for setting things up, reporting results,
+and ending. All files first include "shell.inc", which sets up the ROM
+header and shell code, and includes other commonly-used modules.
+
+One oddity is that test code is first copied to internal RAM at $D000,
+then executed there. This allows self-modification, and ensures the code
+is executed the same way it is on my devcart, which doesn't have a
+rewritable ROM as most do.
+
+Some macros are used to simplify common tasks:
+
+ Macro Behavior
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ wreg addr,data Writes data to addr using LDH
+ lda addr Loads byte from addr into A using LDH
+ sta addr Stores A at addr using LDH
+ delay n Delays n cycles, where NOP = 1 cycle
+ delay_msec n Delays n milliseconds
+ set_test n,"Cause" Sets failure code and optional string
+
+Routines and macros are documented where they are defined.
+
+--
+Shay Green
diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/build_gbs.s b/playing-coffee - Copy/roms/instr_timing/source/common/build_gbs.s
new file mode 100644
index 0000000..7ac8d3c
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/common/build_gbs.s
@@ -0,0 +1,121 @@
+; Build as GBS music file
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $3000 size $1000
+ slot 1 $C000 size $1000
+.endMe
+
+.romBankSize $1000
+.romBanks 2
+
+
+;;;; GBS music file header
+
+.byte "GBS"
+.byte 1 ; vers
+.byte 1 ; songs
+.byte 1 ; first song
+.word load_addr
+.word reset
+.word gbs_play
+.word std_stack
+.byte 0,0 ; timer
+.ds $60,0
+load_addr:
+
+; WLA assumes we're building ROM and messes
+; with bytes at the beginning, so skip them.
+.ds $100,0
+
+
+;;;; Shell
+
+.include "runtime.s"
+
+init_runtime:
+ ld a,$01 ; Identify as DMG hardware
+ ld (gb_id),a
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+ ret
+
+std_print:
+ sta SB
+ wreg SC,$81
+ delay 2304
+ ret
+
+post_exit:
+ call play_byte
+forever:
+ wreg NR52,0 ; sound off
+- jp -
+
+.ifndef CUSTOM_RESET
+ gbs_play:
+.endif
+console_flush:
+console_normal:
+console_inverse:
+console_set_mode:
+ ret
+
+; Reports A in binary as high and low tones, with
+; leading low tone for reference. Omits leading
+; zeroes.
+; Preserved: AF, BC, DE, HL
+play_byte:
+ push af
+ push hl
+
+ ; HL = (A << 1) | 1
+ scf
+ rla
+ ld l,a
+ ld h,0
+ rl h
+
+ ; Shift left until next-to-top bit is 1
+- add hl,hl
+ bit 6,h
+ jr z,-
+
+ ; Reset sound
+ delay_msec 400
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+- add hl,hl
+
+ ; Low or high pitch based on bit shifted out
+ ; of HL
+ ld a,0
+ jr nc,+
+ ld a,$FF
++ sta NR23
+
+ ; Play short tone
+ wreg NR21,$A0
+ wreg NR22,$F0
+ wreg NR24,$86
+ delay_msec 75
+ wreg NR22,0
+ wreg NR23,$F8
+ wreg NR24,$87
+ delay_msec 200
+
+ ; Loop until HL = $8000
+ ld a,h
+ xor $80
+ or l
+ jr nz,-
+
+ pop hl
+ pop af
+ ret
+
+.ends
diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/build_rom.s b/playing-coffee - Copy/roms/instr_timing/source/common/build_rom.s
new file mode 100644
index 0000000..e1e220f
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/common/build_rom.s
@@ -0,0 +1,80 @@
+; Build as GB ROM
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $0000 size $4000
+ slot 1 $C000 size $4000
+.endMe
+
+.romBankSize $4000 ; generates $8000 byte ROM
+.romBanks 2
+
+.cartridgeType 1 ; MBC1
+.computeChecksum
+.computeComplementCheck
+
+
+;;;; GB ROM header
+
+; GB header read by bootrom
+.org $100
+ nop
+ jp reset
+
+; Nintendo logo required for proper boot
+.byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
+.byte $03,$73,$00,$83,$00,$0C,$00,$0D
+.byte $00,$08,$11,$1F,$88,$89,$00,$0E
+.byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
+.byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
+.byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
+
+; Internal name
+.ifdef ROM_NAME
+ .byte ROM_NAME
+.endif
+
+; CGB/DMG requirements
+.org $143
+ .ifdef REQUIRE_CGB
+ .byte $C0
+ .else
+ .ifndef REQUIRE_DMG
+ .byte $80
+ .endif
+ .endif
+
+.org $200
+
+
+;;;; Shell
+
+.include "runtime.s"
+.include "console.s"
+
+init_runtime:
+ call console_init
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+ ret
+
+std_print:
+ push af
+ sta SB
+ wreg SC,$81
+ delay 2304
+ pop af
+ jp console_print
+
+post_exit:
+ call console_show
+ call play_byte
+forever:
+ wreg NR52,0 ; sound off
+- jr -
+
+play_byte:
+ ret
+
+.ends
diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/console.bin b/playing-coffee - Copy/roms/instr_timing/source/common/console.bin
new file mode 100644
index 0000000..b02f2d3
Binary files /dev/null and b/playing-coffee - Copy/roms/instr_timing/source/common/console.bin differ
diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/console.s b/playing-coffee - Copy/roms/instr_timing/source/common/console.s
new file mode 100644
index 0000000..5307f6b
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/common/console.s
@@ -0,0 +1,291 @@
+; Scrolling text console
+
+; Console is 20x18 characters. Buffers lines, so
+; output doesn't appear until a newline or flush.
+; If scrolling isn't supported (i.e. SCY is treated
+; as if always zero), the first 18 lines will
+; still print properly). Also works properly if
+; LY isn't supported (always reads back as the same
+; value).
+
+.define console_width 20
+
+.define console_buf bss+0
+.define console_pos bss+console_width
+.define console_mode bss+console_width+1
+.define console_scroll bss+console_width+2
+.redefine bss bss+console_width+3
+
+
+; Waits for start of LCD blanking period
+; Preserved: BC, DE, HL
+console_wait_vbl:
+ push bc
+
+ ; Wait for start of vblank, with
+ ; timeout in case LY doesn't work
+ ; or LCD is disabled.
+ ld bc,-1250
+- inc bc
+ ld a,b
+ or c
+ jr z,@timeout
+ lda LY
+ cp 144
+ jr nz,-
+@timeout:
+
+ pop bc
+ ret
+
+
+; Initializes text console
+console_init:
+ call console_hide
+
+ ; CGB-specific inits
+ ld a,(gb_id)
+ and gb_id_cgb
+ call nz,@init_cgb
+
+ ; Clear nametable
+ ld a,' '
+ call @fill_nametable
+
+ ; Load tiles
+ ld hl,TILES+$200
+ ld c,0
+ call @load_tiles
+ ld hl,TILES+$A00
+ ld c,$FF
+ call @load_tiles
+
+ ; Init state
+ ld a,console_width
+ ld (console_pos),a
+ ld a,0
+ ld (console_mode),a
+ ld a,-8
+ ld (console_scroll),a
+ call console_scroll_up_
+ jr console_show
+
+@fill_nametable:
+ ld hl,BGMAP0
+ ld b,4
+- ld (hl),a
+ inc l
+ jr nz,-
+ inc h
+ dec b
+ jr nz,-
+ ret
+
+@init_cgb:
+ ; Clear palette
+ wreg $FF68,$80
+ ld b,16
+- wreg $FF69,$FF
+ wreg $FF69,$7F
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ dec b
+ jr nz,-
+
+ ; Clear attributes
+ ld a,1
+ ld (VBK),a
+ ld a,0
+ call @fill_nametable
+
+ ld a,0
+ ld (VBK),a
+ ret
+
+@load_tiles:
+ ld de,ASCII
+ ld b,96
+-- push bc
+ ld b,8
+- ld a,(de)
+ inc de
+ xor c
+ ldi (hl),a
+ ldi (hl),a
+ dec b
+ jr nz,-
+ pop bc
+ dec b
+ jr nz,--
+ ret
+
+
+; Shows console display
+; Preserved: AF, BC, DE, HL
+console_show:
+ push af
+
+ ; Enable LCD
+ call console_wait_vbl
+ wreg LCDC,$91
+ wreg SCX,0
+ wreg BGP,$E4
+
+ jp console_apply_scroll_
+
+
+; Hides console display by turning LCD off
+; Preserved: AF, BC, DE, HL
+console_hide:
+ push af
+
+ ; LCD off
+ call console_wait_vbl
+ wreg LCDC,$11
+
+ pop af
+ ret
+
+
+; Changes to normal text mode
+; Preserved: BC, DE, HL
+console_normal:
+ xor a
+ jr console_set_mode
+
+; Changes to inverse text mode
+; Preserved: BC, DE, HL
+console_inverse:
+ ld a,$80
+
+; Changes console mode to A.
+; 0: Normal, $80: Inverse
+; Preserved: BC, DE, HL
+console_set_mode:
+ and $80
+ ld (console_mode),a
+ ret
+
+
+; Prints char A to console. Will not appear until
+; a newline or flush occurs.
+; Preserved: AF, BC, DE, HL
+console_print:
+ push af
+
+ cp 10
+ jr z,console_newline_
+
+ push hl
+ push af
+ ld hl,console_pos
+ ldi a,(hl)
+ cp BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/delay.s b/playing-coffee - Copy/roms/instr_timing/source/common/delay.s
new file mode 100644
index 0000000..65eb9c0
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+ .endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+ .endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+ .endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/gb.inc b/playing-coffee - Copy/roms/instr_timing/source/common/gb.inc
new file mode 100644
index 0000000..31bbf14
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/common/gb.inc
@@ -0,0 +1,64 @@
+; Game Boy hardware addresses
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+.define P1 $FF00
+
+; Game link I/O
+.define SB $FF01
+.define SC $FF02
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/macros.inc b/playing-coffee - Copy/roms/instr_timing/source/common/macros.inc
new file mode 100644
index 0000000..c413bd5
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/common/macros.inc
@@ -0,0 +1,73 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ARGS addr
+ ldh a,(addr - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ARGS addr
+ ldh (addr - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/numbers.s b/playing-coffee - Copy/roms/instr_timing/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/printing.s b/playing-coffee - Copy/roms/instr_timing/source/common/printing.s
new file mode 100644
index 0000000..ad9d811
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/common/printing.s
@@ -0,0 +1,98 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+.define print_char_nocrc bss
+.redefine bss bss+3
+
+
+; Initializes printing. HL = print routine
+init_printing:
+ ld a,l
+ ld (print_char_nocrc+1),a
+ ld a,h
+ ld (print_char_nocrc+2),a
+ jr show_printing
+
+
+; Hides/shows further printing
+; Preserved: BC, DE, HL
+hide_printing:
+ ld a,$C9 ; RET
+ jr +
+show_printing:
+ ld a,$C3 ; JP (nn)
++ ld (print_char_nocrc),a
+ ret
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/runtime.s b/playing-coffee - Copy/roms/instr_timing/source/common/runtime.s
new file mode 100644
index 0000000..90cd24f
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/common/runtime.s
@@ -0,0 +1,142 @@
+; Common routines and runtime
+
+; Must be defined by target-specific runtime:
+;
+; init_runtime: ; target-specific inits
+; std_print: ; default routine to print char A
+; post_exit: ; called at end of std_exit
+; report_byte: ; report A to user
+
+.define RUNTIME_INCLUDED 1
+
+.ifndef bss
+ ; address of next normal variable
+ .define bss $D800
+.endif
+
+.ifndef dp
+ ; address of next direct-page ($FFxx) variable
+ .define dp $FF80
+.endif
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+
+.define gb_id bss
+.redefine bss bss+1
+
+; Stack is normally here
+.define std_stack $DFFF
+
+; Copies $1000 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+ ld c,$10
+- ldi a,(hl)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+.ifndef CUSTOM_RESET
+ reset:
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org $0 ; otherwise wla pads with lots of zeroes
+ jp std_reset
+.endif
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld (gb_id),a
+
+ ; Init hardware
+ .ifndef BUILD_GBS
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+ .endif
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ ; TODO: clear all memory?
+
+ ld hl,std_print
+ call init_printing
+ call init_testing
+ call init_runtime
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ pop af
+ jp post_exit
+
++ push af
+ call print_newline
+ call show_printing
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/testing.s b/playing-coffee - Copy/roms/instr_timing/source/common/testing.s
new file mode 100644
index 0000000..c102f78
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/common/testing.s
@@ -0,0 +1,176 @@
+; Diagnostic and testing utilities
+
+.define result bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (result),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(result)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(result)
+ cp 1 ; if a = 0 then a = 1
+ adc 0
+ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call show_printing
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/timer.s b/playing-coffee - Copy/roms/instr_timing/source/common/timer.s
new file mode 100644
index 0000000..32c713e
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/common/timer.s
@@ -0,0 +1,88 @@
+; Cycle-accurate timer
+
+; TIMA is incremented every 4 cycles. Loops
+; check for increment within 3-cycle window,
+; so when it occurs outside this, loop is
+; exactly synchronized. Loop iterations are
+; one more or less than 12 cycles, so they
+; will never run more than 4 times.
+
+; Initializes timer
+; Preserved: AF, BC, DE, HL
+init_timer:
+ push af
+ di
+ lda IE ; disable timer interrupt
+ and ~$04
+ sta IE
+ wreg TMA,0 ; max period
+ wreg TAC,$05 ; 262144 Hz
+
+ ; Be sure timer doesn't expire
+ ; immediately or take too long
+ wreg IF,0
+ wreg TIMA,-20
+ delay 70
+ lda IF
+ and $04
+ jp nz,test_failed
+ lda IF
+ and $04
+ jp z,test_failed
+
+ pop af
+ ret
+
+
+; Starts timer
+; Preserved: AF, BC, DE, HL
+start_timer:
+ push af
+
+- xor a ; 1
+ sta TIMA ; 3
+ lda TIMA ; 3
+ or a ; 1
+ jr nz,- ; 3
+
+ pop af
+ ret
+
+
+; Stops timer and determines cycles since
+; it was started. A = cycles (0 to 255).
+; Preserved: BC, DE, HL
+stop_timer:
+ push de
+ call stop_timer_word
+ ld a,e
+ sub 10
+ pop de
+ ret
+
+
+; Same as stop_timer, but with greater range.
+; DE = cycles (0 to 1019).
+; Preserved: BC, HL
+stop_timer_word:
+
+ ld d,0
+
+ ; Get main count (TIMA*4)
+ lda TIMA
+ sub 5
+ add a
+ rl d
+ add a
+ rl d
+ ld e,a
+
+ ; One iteration per remaining cycle
+- xor a ; 1
+ sta TIMA ; 3
+ lda TIMA ; 3
+ dec de ; 2
+ or a ; 1
+ jr nz,- ; 3
+
+ ret
diff --git a/playing-coffee - Copy/roms/instr_timing/source/instr_timing.s b/playing-coffee - Copy/roms/instr_timing/source/instr_timing.s
new file mode 100644
index 0000000..b6b7338
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/instr_timing.s
@@ -0,0 +1,336 @@
+; Tests number of cycles taken by instructions
+; except STOP, HALT, and illegals.
+
+.include "shell.inc"
+.include "timer.s"
+
+.define saved_sp bss+0
+.define instr bss+2 ; 3-byte instr + JP instr_end
+.define instr_addr bss+8 ; JP instr_end
+.redefine bss bss+11
+
+main:
+ call init_timer
+ call test_timer
+ set_test 0
+ call test_main_ops
+ call test_cb_ops
+ jp tests_done
+
+
+; Ensures timer works
+test_timer:
+ call start_timer
+ call stop_timer
+ or a
+ ret z
+ set_test 2,"Timer doesn't work properly"
+ jp test_failed
+
+
+; Tests main opcodes
+test_main_ops:
+ ld l,0
+- ld h,>op_times
+ ld a,(hl)
+ cp 0
+ call nz,@test_op
+ inc l
+ jr nz,-
+ ret
+
+@test_op:
+ ; Can't test the 8 RST instructions on devcart
+ ld a,l
+ cpl
+ and $C7
+ jr nz,+
+ ld a,(gb_id)
+ and gb_id_devcart
+ ret nz
++
+ ; Test with flags set so that branches are
+ ; not taken
+ ld a,l ; e = (l & 0x08 ? 0 : 0xFF)
+ and $08
+ add $F8
+ ld e,a
+ call @copy_and_exec
+ ld d,0
+ cp (hl)
+ jr z,+
+ ld d,a
+ call print_failed_opcode
++
+
+ ; Time with branches not taken
+ ld a,e
+ cpl
+ ld e,a
+ call @copy_and_exec
+ ld h,>op_times_taken
+ cp (hl)
+ ret z
+
+ ; If opcode already failed and timed the
+ ; same again, avoid re-reporting.
+ cp d
+ ret z
+
+ call print_failed_opcode
+ ret
+
+@copy_and_exec:
+ push de
+ push hl
+
+ ld h,>op_lens
+ ld c,(hl)
+ ld a,l
+ ld hl,instr
+ ld (hl+),a
+ dec c
+ jr z,@one_byte
+ ld a,0
+ dec c
+ jr z,@two_bytes
+ ld a,instr_addr
+@two_bytes:
+ ld (hl+),a
+@one_byte:
+ ld a,e
+ call time_instruction
+
+ pop hl
+ pop de
+ ret
+
+
+; Tests CB opcodes
+test_cb_ops:
+ ld hl,cb_op_times
+- ld a,(hl)
+ cp 0
+ call nz,@test_op_cb
+ inc l
+ jr nz,-
+ ret
+
+@test_op_cb:
+ ; Test with flags clear
+ ld e,$00
+ call @copy_and_exec_cb
+ cp (hl)
+ jr nz,+
+
+ ; Test with flags set
+ ld e,$FF
+ call @copy_and_exec_cb
+ cp (hl)
+ jr nz,+
+
+ ret
++ print_str "CB "
+ call print_failed_opcode
+ ret
+
+@copy_and_exec_cb:
+ push hl
+
+ ; Copy instr to exec space
+ ld a,l
+ ld hl,instr+1
+ ld (hl+),a
+ ld a,$CB
+ ld (instr),a
+ call time_instruction
+
+ pop hl
+ ret
+
+
+; Reports failed opcode
+; L -> opcode
+; A -> cycles it took
+; (HL) -> cycles it should have taken
+; Preserved: HL
+print_failed_opcode:
+ ; Print opcode
+ push af
+ ld a,l
+ call print_hex
+ ld a,':'
+ call print_char
+ pop af
+
+ ; Print actual and correct times
+ call print_dec
+ ld a,'-'
+ call print_char
+ ld a,(hl)
+ call print_dec
+ ld a,' '
+ call print_char
+
+ ; Remember that failure occurred
+ set_test 1
+
+ ret
+
+
+; Times instruction.
+; HL -> address of byte just after instruction
+; A -> flags when executing instruction
+; A <- number of cycles instruction took
+time_instruction:
+ ld c,a
+
+ ; Write JP instr_end to HL and instr_addr
+ ld a,$C3 ; JP
+ ld (hl+),a
+ ld (instr_addr),a
+
+ ld a,instr_end
+ ld (instr_addr+2),a
+ ld (hl),a
+
+ ; Save sp
+ ld (saved_sp),sp
+
+ ; Set regs and stack contents
+ push bc
+ ld bc,instr_addr
+ ld de,instr_addr
+ ld hl,instr_addr
+ call start_timer
+ pop af
+ push hl
+
+ ; Environment instruction executes in:
+ ; 1 byte: OP
+ ; 2 byte: OP 00
+ ; 3 byte: OP instr_addr
+ ; BC,DE,HL = instr_addr
+ ; Stack has instr_addr pushed on it.
+ ; Stack pointer can be trashed by instr.
+ ; instr_addr contains JP instr_end, that
+ ; can be trashed. Instructions which trash
+ ; this don't execute it.
+
+ jp instr
+instr_end: ; instruction jumps here when done
+ di
+
+ ; Restore sp
+ ld sp,saved_sp
+ pop hl
+ ld sp,hl
+
+ call stop_timer
+ sub 24
+ ret
+
+.section "page_aligned" align 256
+
+; Instruction lengths of opcodes.
+; 0 for instructions not timed.
+op_lens:
+ .byte 1,3,1,1,1,1,2,1,3,1,1,1,1,1,2,1 ; 0
+ .byte 0,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1 ; 1
+ .byte 2,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1 ; 2
+ .byte 2,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1 ; 3
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 4
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 5
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 6
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 7
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 8
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 9
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; A
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; B
+ .byte 1,1,3,3,3,1,2,1,1,1,3,0,3,3,2,1 ; C
+ .byte 1,1,3,0,3,1,2,1,1,1,3,0,3,0,2,1 ; D
+ .byte 2,1,1,0,0,1,2,1,2,1,3,0,0,0,2,1 ; E
+ .byte 2,1,1,1,0,1,2,1,2,1,3,1,0,0,2,1 ; F
+
+; Timings for main opcodes
+op_times:
+ .byte 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1
+ .byte 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1
+ .byte 2,3,2,2,1,1,2,1,2,2,2,2,1,1,2,1
+ .byte 2,3,2,2,3,3,3,1,2,2,2,2,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 2,3,3,4,3,4,2,4,2,4,3,0,3,6,2,4
+ .byte 2,3,3,0,3,4,2,4,2,4,3,0,3,0,2,4
+ .byte 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4
+ .byte 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4
+
+; Timings when conditionals are taken
+op_times_taken:
+ .byte 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1
+ .byte 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1
+ .byte 3,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1
+ .byte 3,3,2,2,3,3,3,1,3,2,2,2,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 5,3,4,4,6,4,2,4,5,4,4,0,6,6,2,4
+ .byte 5,3,4,0,6,4,2,4,5,4,4,0,6,0,2,4
+ .byte 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4
+ .byte 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4
+
+; Timings for CB-prefixed opcodes
+cb_op_times:
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2
+ .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2
+ .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2
+ .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+.ends
+
+; RST handlers
+.bank 0 slot 0
+.org $00
+ jp instr_end
+.org $08
+ jp instr_end
+.org $10
+ jp instr_end
+.org $18
+ jp instr_end
+.org $20
+ jp instr_end
+.org $28
+ jp instr_end
+.org $30
+ jp instr_end
+.org $38
+ jp instr_end
diff --git a/playing-coffee - Copy/roms/instr_timing/source/linkfile b/playing-coffee - Copy/roms/instr_timing/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee - Copy/roms/instr_timing/source/shell.inc b/playing-coffee - Copy/roms/instr_timing/source/shell.inc
new file mode 100644
index 0000000..277a9b7
--- /dev/null
+++ b/playing-coffee - Copy/roms/instr_timing/source/shell.inc
@@ -0,0 +1,21 @@
+.incdir "common"
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
diff --git a/playing-coffee - Copy/roms/interrupt_time/interrupt_time.gb b/playing-coffee - Copy/roms/interrupt_time/interrupt_time.gb
new file mode 100644
index 0000000..1b17845
Binary files /dev/null and b/playing-coffee - Copy/roms/interrupt_time/interrupt_time.gb differ
diff --git a/playing-coffee - Copy/roms/interrupt_time/interrupt_time.s b/playing-coffee - Copy/roms/interrupt_time/interrupt_time.s
new file mode 100644
index 0000000..ae29d8a
--- /dev/null
+++ b/playing-coffee - Copy/roms/interrupt_time/interrupt_time.s
@@ -0,0 +1,57 @@
+; Tests interrupt handling time for slow and fast
+; CPU. First value is CPU speed (0=slow, 1=fast),
+; second is number of cycles taken by interrupt.
+; Should take 13 cycles at either speed.
+.define REQUIRE_CGB 1
+
+.include "shell.inc"
+.include "cpu_speed.s"
+.include "timer.s"
+.include "apu.s"
+
+; $58: JP $DEC3
+; $DEC3: RET
+.define sint $DEC3
+
+main:
+ call init_timer
+
+ ld d,0
+ call test_interrupt
+ ld d,8
+ call test_interrupt
+ call cpu_fast
+ ld d,0
+ call test_interrupt
+ ld d,8
+ call test_interrupt
+
+ check_crc $C86CC74D
+ jp tests_passed
+
+test_interrupt:
+ call get_cpu_speed
+ call print_a
+ call print_d
+
+ ld a,$C9 ; RET
+ ld (sint),a
+
+ wreg IE,$08
+ wreg IF,$00
+ call start_timer
+ ei
+ ld a,d
+ ld (IF),a ; $00 = 0 clocks, $08 = 13 clocks
+ di
+ call stop_timer
+ sub 3+4 ; instruction overhead
+ call print_a
+ call print_newline
+
+ ret
+
+; RST handler that matches the one in my devcart
+.bank 0 slot 0
+.org $58
+ jp $DEC3
diff --git a/playing-coffee - Copy/roms/kwirk.gb b/playing-coffee - Copy/roms/kwirk.gb
new file mode 100644
index 0000000..c25d3c7
Binary files /dev/null and b/playing-coffee - Copy/roms/kwirk.gb differ
diff --git a/playing-coffee - Copy/roms/mem_timing-2/mem_timing.gb b/playing-coffee - Copy/roms/mem_timing-2/mem_timing.gb
new file mode 100644
index 0000000..2665aa2
Binary files /dev/null and b/playing-coffee - Copy/roms/mem_timing-2/mem_timing.gb differ
diff --git a/playing-coffee - Copy/roms/mem_timing-2/readme.txt b/playing-coffee - Copy/roms/mem_timing-2/readme.txt
new file mode 100644
index 0000000..f6577c1
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/readme.txt
@@ -0,0 +1,114 @@
+Game Boy CPU Memory Access Timing Test
+--------------------------------------
+These tests verify the timing of memory reads and writes made by
+instructions, except stack and program counter accesses. These tests
+require correct instruction timing and proper timer operation (TAC,
+TIMA, TMA).
+
+The read and write tests list failing instructions as
+
+ [CB] opcode:tested-correct
+
+The read-modify-write test lists failing instructions as
+
+ [CB] opcode:tested read/tested write-correct read/correct write
+
+The values after the opcode refer to which instruction cycle the access
+occurs on, with 1 being the first. If a time couldn't be determined due
+to some other problem, it prints 0.
+
+For instructions which either read or write, but not both, the CPU makes
+the access on the last cycle. For instructions which read, modify, then
+write back, the CPU reads on the next-to-last cycle, and writes on the
+last cycle.
+
+
+Internal operation
+------------------
+The tests have the timer increment TIMA every 64 cycles, synchronize
+with this, delay a variable amount, then have the instruction under test
+access the timer. By varying the delay in one-cycle increments, the
+memory access made by the instruction can be made to fall before and
+after a TIMA increment. By then examining the registers and value in
+TIMA, it can be determined which occurred.
+
+
+Multi-ROM
+---------
+In the main directory is a single ROM/GBS which runs all the tests. It
+prints a test's number, runs the test, then "ok" if it passes, otherwise
+a failure code. Once all tests have completed it either reports that all
+tests passed, or reports the number of the first failed test as the
+result code (1 = first). Finally, it makes several beeps. If a test
+fails, it can be run on its own by finding the corresponding ROM/GBS in
+the singles directories.
+
+Ths compact format on screen is to avoid having the results scroll off
+the top, so the test can be started and allowed to run without having to
+constantly monitor the display.
+
+
+Failure information
+-------------------
+For more information about a failure code or information printed, see
+the test's source code in source/. To find failure code N, search for
+"set_test N", which will usually be before the subtest which failed.
+
+
+Flashes, clicks, other glitches
+-------------------------------
+Some tests might need to turn the screen off and on, or cause slight
+audio clicks. This does not indicate failure, and should be ignored.
+Only the test result reported at the end is important, unless stated
+otherwise.
+
+
+LCD support
+-----------
+Tests generally print information on screen. The tests will work fine if
+run on an emulator with NO LCD support, or as an GBS which has no
+inherent screen; in particular, the VBL wait routine has a timeout in
+case LY doesn't reflect the current LCD line. The text printing will
+also work if the LCD doesn't support scrolling.
+
+
+Output to memory
+----------------
+Text output and the final result are also written to memory at $A000,
+allowing testing a very minimal emulator that supports little more than
+CPU and RAM. To reliably indicate that the data is from a test and not
+random data, $A001-$A003 are written with a signature: $DE,$B0,$61. If
+this is present, then the text string and final result status are valid.
+
+$A000 holds the overall status. If the test is still running, it holds
+$80, otherwise it holds the final result code.
+
+All text output is appended to a zero-terminated string at $A004. An
+emulator could regularly check this string for any additional
+characters, and output them, allowing real-time text output, rather than
+just printing the final output at the end.
+
+
+GBS versions
+------------
+Many GBS-based tests require that the GBS player either not interrupt
+the init routine with the play routine, or if they do, not interrupt the
+play routine again if it hasn't returned yet. This is because many tests
+need to run for a while without returning.
+
+In addition to the other text output methods described above, GBS builds
+report essential information bytes audibly, including the final result.
+A byte is reported as a series of tones. The code is in binary, with a
+low tone for 0 and a high tone for 1. The first tone is always a zero. A
+final code of 0 means passed, 1 means failure, and 2 or higher indicates
+a specific reason as listed in the source code by the corresponding
+set_code line. Examples:
+
+Tones Binary Decimal Meaning
+- - - - - - - - - - - - - - - - - - - -
+low 0 0 passed
+low high 01 1 failed
+low high low 010 2 error 2
+
+--
+Shay Green
diff --git a/playing-coffee - Copy/roms/mem_timing-2/rom_singles/01-read_timing.gb b/playing-coffee - Copy/roms/mem_timing-2/rom_singles/01-read_timing.gb
new file mode 100644
index 0000000..660298b
Binary files /dev/null and b/playing-coffee - Copy/roms/mem_timing-2/rom_singles/01-read_timing.gb differ
diff --git a/playing-coffee - Copy/roms/mem_timing-2/rom_singles/02-write_timing.gb b/playing-coffee - Copy/roms/mem_timing-2/rom_singles/02-write_timing.gb
new file mode 100644
index 0000000..e92f553
Binary files /dev/null and b/playing-coffee - Copy/roms/mem_timing-2/rom_singles/02-write_timing.gb differ
diff --git a/playing-coffee - Copy/roms/mem_timing-2/rom_singles/03-modify_timing.gb b/playing-coffee - Copy/roms/mem_timing-2/rom_singles/03-modify_timing.gb
new file mode 100644
index 0000000..7579c42
Binary files /dev/null and b/playing-coffee - Copy/roms/mem_timing-2/rom_singles/03-modify_timing.gb differ
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/01-read_timing.s b/playing-coffee - Copy/roms/mem_timing-2/source/01-read_timing.s
new file mode 100644
index 0000000..5084d4b
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/01-read_timing.s
@@ -0,0 +1,150 @@
+; Tests timing of accesses made by
+; memory read instructions
+
+.define ROM_NAME "foo"
+
+.include "shell.inc"
+.include "tima_64.s"
+
+instructions:
+ ; last value is time of read
+ .byte $B6,$00,$00,2 ; OR (HL)
+ .byte $BE,$00,$00,2 ; CP (HL)
+ .byte $86,$00,$00,2 ; ADD (HL)
+ .byte $8E,$00,$00,2 ; ADC (HL)
+ .byte $96,$00,$00,2 ; SUB (HL)
+ .byte $9E,$00,$00,2 ; SBC (HL)
+ .byte $A6,$00,$00,2 ; AND (HL)
+ .byte $AE,$00,$00,2 ; XOR (HL)
+ .byte $46,$00,$00,2 ; LD B,(HL)
+ .byte $4E,$00,$00,2 ; LD C,(HL)
+ .byte $56,$00,$00,2 ; LD D,(HL)
+ .byte $5E,$00,$00,2 ; LD E,(HL)
+ .byte $66,$00,$00,2 ; LD H,(HL)
+ .byte $6E,$00,$00,2 ; LD L,(HL)
+ .byte $7E,$00,$00,2 ; LD A,(HL)
+ .byte $F2,$00,$00,2 ; LDH A,(C)
+ .byte $0A,$00,$00,2 ; LD A,(BC)
+ .byte $1A,$00,$00,2 ; LD A,(DE)
+ .byte $2A,$00,$00,2 ; LD A,(HL+)
+ .byte $3A,$00,$00,2 ; LD A,(HL-)
+ .byte $F0,tima_64,4 ; LD A,($0000)
+
+ .byte $CB,$46,$00,3 ; BIT 0,(HL)
+ .byte $CB,$4E,$00,3 ; BIT 1,(HL)
+ .byte $CB,$56,$00,3 ; BIT 2,(HL)
+ .byte $CB,$5E,$00,3 ; BIT 3,(HL)
+ .byte $CB,$66,$00,3 ; BIT 4,(HL)
+ .byte $CB,$6E,$00,3 ; BIT 5,(HL)
+ .byte $CB,$76,$00,3 ; BIT 6,(HL)
+ .byte $CB,$7E,$00,3 ; BIT 7,(HL)
+instructions_end:
+
+main:
+ call init_tima_64
+ set_test 0
+
+ ; Test instructions
+ ld hl,instructions
+- call @time_instr
+ cp (hl)
+ call nz,@print_failed
+ inc hl
+ ld a,l
+ cp 3-byte instruction
+; HL <- HL + 3
+@time_instr:
+ ; Copy instr
+ ld a,(hl+)
+ ld (instr+0),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Find result when access doesn't occur
+ ld b,0
+ call @time_access
+ ld c,a
+
+ ; Test for accesses on each cycle
+ ld b,0
+- push bc
+ call @time_access
+ pop bc
+ cp c
+ jr nz,@found
+ inc b
+ ld a,b
+ cp 10
+ jr nz,-
+ ld b,0
+
+@found:
+ ld a,b
+ pop hl
+ ret
+
+; Tests for read
+; B -> which cycle to test
+; A <- timer value after test
+@time_access:
+ call sync_tima_64
+ ld a,9
+ sub b
+ call delay_a_20_cycles
+ xor a ; clear flags
+ ld hl,tima_64
+ ld (hl),$7F
+ ld bc,tima_64
+ ld de,tima_64
+ ld a,$7F
+instr:
+ nop
+ nop
+ nop
+
+ ; Add all registers together to yield
+ ; unique value that differs based on
+ ; read occurring before or after tima_64
+ ; increments.
+ push af
+ add hl,bc
+ add hl,de
+ pop de
+ add hl,de
+ ld a,h
+ add l
+ ret
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/02-write_timing.s b/playing-coffee - Copy/roms/mem_timing-2/source/02-write_timing.s
new file mode 100644
index 0000000..acd6d9d
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/02-write_timing.s
@@ -0,0 +1,115 @@
+; Tests timing of accesses made by
+; memory write instructions
+
+.include "shell.inc"
+.include "tima_64.s"
+
+instructions:
+ ; last value is time of write
+ .byte $36,$FF,$00,3 ; LD (HL),n
+ .byte $70,$00,$00,2 ; LD (HL),B
+ .byte $71,$00,$00,2 ; LD (HL),C
+ .byte $72,$00,$00,2 ; LD (HL),D
+ .byte $73,$00,$00,2 ; LD (HL),E
+ .byte $74,$00,$00,2 ; LD (HL),H
+ .byte $75,$00,$00,2 ; LD (HL),L
+ .byte $77,$00,$00,2 ; LD (HL),A
+ .byte $02,$00,$00,2 ; LD (BC),A
+ .byte $12,$00,$00,2 ; LD (DE),A
+ .byte $22,$00,$00,2 ; LD (HL+),A
+ .byte $32,$00,$00,2 ; LD (HL-),A
+ .byte $E2,$00,$00,2 ; LDH (C),A
+ .byte $E0,tima_64,4 ; LD (nn),A
+instructions_end:
+
+main:
+ call init_tima_64
+ set_test 0
+
+ ; Test instructions
+ ld hl,instructions
+- call @test_instr
+ cp (hl)
+ call nz,@print_failed
+ inc hl
+ ld a,l
+ cp 3-byte instruction
+; HL <- HL + 3
+@test_instr:
+ ; Copy instr
+ ld a,(hl+)
+ ld (instr+0),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Test for writes on each cycle
+ ld b,0
+- push bc
+ call @time_write
+ pop bc
+ cp tima_64
+ jr z,@no_write
+ jr @found
+@no_write:
+ inc b
+ ld a,b
+ cp 10
+ jr nz,-
+ ld b,0
+
+@found:
+ ld a,b
+ pop hl
+ ret
+
+; Tests for write
+; B -> which cycle to test
+; A <- timer value after test
+@time_write:
+ call sync_tima_64
+ ld a,13
+ sub b
+ call delay_a_20_cycles
+ ld hl,tima_64
+ ld bc,tima_64
+ ld de,tima_64
+ ld a, 3-byte instruction
+; HL <- HL + 3
+@time_instr:
+ ; Copy instr
+ ld a,(hl+)
+ ld (instr+0),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Find result when access doesn't occur
+ ld b,0
+ call @time_access
+
+ ; Find first access
+ call @find_next_access
+ ld d,b
+
+ ; Find second access
+ call @find_next_access
+ ld e,b
+
+ pop hl
+ ret
+
+; A -> current timer result
+; B -> starting clock
+; B <- clock next access occurs on
+; A <- new timer result
+@find_next_access:
+ ld c,a
+- call @time_access
+ cp c
+ ret nz
+ inc b
+ ld a,b
+ cp 10
+ jr c,-
+
+ ; Couldn't find time, so return 0/0
+ ld a,c
+ ld b,0
+ ld d,b
+ ret
+
+; Tests for access
+; B -> which cycle to test
+; A <- timer value after test
+@time_access:
+ call sync_tima_64
+ ld hl,tima_64
+ ld (hl),$7F
+ ld a,17
+ sub b
+ call delay_a_20_cycles
+ xor a ; clear flags
+instr:
+ nop
+ nop
+ nop
+ delay 32
+ ld a,(tima_64)
+ ret
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/build_gbs.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/build_gbs.s
new file mode 100644
index 0000000..877754f
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/build_gbs.s
@@ -0,0 +1,139 @@
+; Build as GBS music file
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $2000 size $2000
+ slot 1 $C000 size $2000
+.endMe
+
+.romBankSize $2000
+.romBanks 2
+
+.define RST_OFFSET $70
+
+.ifndef GBS_TMA
+ .define GBS_TMA 0
+.endif
+
+.ifndef GBS_TAC
+ .define GBS_TAC 0
+.endif
+
+;;;; GBS music file header
+
+.ifndef CUSTOM_HEADER
+ .byte "GBS"
+ .byte 1,1,1 ; vers, song count, first song
+ .word load_addr, reset, gbs_play_, std_stack
+ .byte GBS_TMA,GBS_TAC ; timer
+.endif
+ .org $10
+ .ds $60,0
+load_addr:
+ .org RST_OFFSET+$70 ; space for RST vectors
+ .ds $148-RST_OFFSET-$70,0
+ .org $150 ; wla insists on generating GB header
+
+gbs_play_:
+ jp gbs_play ; GBS spec disallows having gbs_play in RAM
+
+;;;; Shell
+
+.include "shell.s"
+
+.define gbs_idle nv_ram
+.redefine nv_ram nv_ram+2
+
+init_runtime:
+ ; Identify as DMG hardware
+ ld a,$01
+ ld (gb_id),a
+
+ ; Save return address
+ pop hl
+ ld a,l
+ ld (gbs_idle),a
+ ld a,h
+ ld (gbs_idle+1),a
+
+ ; Delay 1/4 second to give time
+ ; for GBS player to interrupt with
+ ; play, if it's going to do so
+ delay_msec 250
+
+.ifndef CUSTOM_PLAY
+gbs_play:
+.endif
+ ; Get return address
+ ld a,(gbs_idle)
+ ld l,a
+ ld a,(gbs_idle+1)
+ ld h,a
+
+ ; If zero, then play interrupted init
+ ; call, or another play call, and we
+ ; can't run the program properly.
+ or l
+ jp z,internal_error
+
+ setw gbs_idle,0
+ jp hl
+
+
+; Reports A in binary as high and low tones, with
+; leading low tone for reference. Omits leading
+; zeroes.
+; Preserved: AF, BC, DE, HL
+play_byte:
+ push af
+ push hl
+
+ ; HL = (A << 1) | 1
+ scf
+ rla
+ ld l,a
+ ld h,0
+ rl h
+
+ ; Shift left until next-to-top bit is 1
+- add hl,hl
+ bit 6,h
+ jr z,-
+
+ ; Reset sound
+ delay_msec 400
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+- add hl,hl
+
+ ; Low or high pitch based on bit shifted out
+ ; of HL
+ ld a,0
+ jr nc,+
+ ld a,$FF
++ sta NR23
+
+ ; Play short tone
+ wreg NR21,$A0
+ wreg NR22,$F0
+ wreg NR24,$86
+ delay_msec 75
+ wreg NR22,0
+ wreg NR23,$F8
+ wreg NR24,$87
+ delay_msec 200
+
+ ; Loop until HL = $8000
+ ld a,h
+ xor $80
+ or l
+ jr nz,-
+
+ pop hl
+ pop af
+ ret
+
+.ends
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/build_rom.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/build_rom.s
new file mode 100644
index 0000000..0f090aa
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/build_rom.s
@@ -0,0 +1,84 @@
+; Build as GB ROM
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $0000 size $4000
+ slot 1 $C000 size $4000
+.endMe
+
+.romBankSize $4000
+.romBanks 2
+
+.cartridgeType 3 ; MBC1+RAM+battery
+.ramsize 02 ; 8K
+.computeChecksum
+.computeComplementCheck
+.emptyfill $FF
+
+;;;; GB ROM header
+
+.org $134
+ .ds 15,0
+
+ ; Reserve space for RST handlers
+ .org $70
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .ds $90,0
+
+ ; GB header read by bootrom
+ .org $100
+ nop
+ jp reset
+
+ ; Nintendo logo required for proper boot
+ .byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
+ .byte $03,$73,$00,$83,$00,$0C,$00,$0D
+ .byte $00,$08,$11,$1F,$88,$89,$00,$0E
+ .byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
+ .byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
+ .byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
+
+.section "HEADER" OVERWRITE
+ ; Internal name
+ .ifdef ROM_NAME
+ .byte ROM_NAME
+ .else
+ .ifdef ROM_NAME_DEFAULT
+ .byte ROM_NAME_DEFAULT
+ .endif
+ .endif
+.ends
+
+ ; CGB/DMG requirements
+ .org $143
+ .ifdef REQUIRE_CGB
+ .byte $C0
+ .else
+ .ifndef REQUIRE_DMG
+ .byte $80
+ .endif
+ .endif
+ .byte 0,0,0
+
+ .org $14A
+ .byte 0,0,0
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .org $150
+ .ds $2150-$150,$FF
+
+;;;; Shell
+
+.define NEED_CONSOLE 1
+.include "shell.s"
+
+init_runtime:
+ ret
+
+play_byte:
+ ret
+
+.ends
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/console.bin b/playing-coffee - Copy/roms/mem_timing-2/source/common/console.bin
new file mode 100644
index 0000000..b02f2d3
Binary files /dev/null and b/playing-coffee - Copy/roms/mem_timing-2/source/common/console.bin differ
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/console.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/console.s
new file mode 100644
index 0000000..403fa5c
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/console.s
@@ -0,0 +1,285 @@
+; Scrolling text console
+
+; Console is 20x18 characters. Buffers lines, so
+; output doesn't appear until a newline or flush.
+; If scrolling isn't supported (i.e. SCY is treated
+; as if always zero), the first 18 lines will
+; still print properly). Also works properly if
+; LY isn't supported (always reads back as the same
+; value).
+
+.define console_width 20
+
+.define console_buf bss+0
+.define console_pos bss+console_width
+.define console_mode bss+console_width+1
+.define console_scroll bss+console_width+2
+.redefine bss bss+console_width+3
+
+
+; Waits for start of LCD blanking period
+; Preserved: BC, DE, HL
+console_wait_vbl:
+ push bc
+
+ ; Wait for start of vblank, with
+ ; timeout in case LY doesn't work
+ ; or LCD is disabled.
+ ld bc,-1250
+- inc bc
+ ld a,b
+ or c
+ jr z,@timeout
+ lda LY
+ cp 144
+ jr nz,-
+@timeout:
+
+ pop bc
+ ret
+
+
+; Initializes text console
+console_init:
+ call console_hide
+
+ ; CGB-specific inits
+ ld a,(gb_id)
+ and gb_id_cgb
+ call nz,@init_cgb
+
+ ; Clear nametable
+ ld a,' '
+ call @fill_nametable
+
+ ; Load tiles
+ ld hl,TILES+$200
+ ld c,0
+ call @load_tiles
+ ld hl,TILES+$A00
+ ld c,$FF
+ call @load_tiles
+
+ ; Init state
+ setb console_pos,console_width
+ setb console_mode,0
+ setb console_scroll,-8
+ call console_scroll_up_
+ jr console_show
+
+@fill_nametable:
+ ld hl,BGMAP0
+ ld b,4
+- ld (hl),a
+ inc l
+ jr nz,-
+ inc h
+ dec b
+ jr nz,-
+ ret
+
+@init_cgb:
+ ; Clear palette
+ wreg $FF68,$80
+ ld b,16
+- wreg $FF69,$FF
+ wreg $FF69,$7F
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ dec b
+ jr nz,-
+
+ ; Clear attributes
+ wreg VBK,1
+ ld a,0
+ call @fill_nametable
+
+ wreg VBK,0
+ ret
+
+@load_tiles:
+ ld de,ASCII
+ ld b,96
+-- push bc
+ ld b,8
+- ld a,(de)
+ inc de
+ xor c
+ ldi (hl),a
+ ldi (hl),a
+ dec b
+ jr nz,-
+ pop bc
+ dec b
+ jr nz,--
+ ret
+
+
+; Shows console display
+; Preserved: AF, BC, DE, HL
+console_show:
+ push af
+
+ ; Enable LCD
+ call console_wait_vbl
+ wreg LCDC,$91
+ wreg SCX,0
+ wreg BGP,$E4
+
+ jp console_apply_scroll_
+
+
+; Hides console display by turning LCD off
+; Preserved: AF, BC, DE, HL
+console_hide:
+ push af
+
+ ; LCD off
+ call console_wait_vbl
+ wreg LCDC,$11
+
+ pop af
+ ret
+
+
+; Changes to normal text mode
+; Preserved: BC, DE, HL
+console_normal:
+ xor a
+ jr console_set_mode
+
+; Changes to inverse text mode
+; Preserved: BC, DE, HL
+console_inverse:
+ ld a,$80
+
+; Changes console mode to A.
+; 0: Normal, $80: Inverse
+; Preserved: BC, DE, HL
+console_set_mode:
+ and $80
+ ld (console_mode),a
+ ret
+
+
+; Prints char A to console. Will not appear until
+; a newline or flush occurs.
+; Preserved: AF, BC, DE, HL
+console_print:
+ push af
+
+ cp 10
+ jr z,console_newline_
+
+ push hl
+ push af
+ ld hl,console_pos
+ ldi a,(hl)
+ cp BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/delay.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/delay.s
new file mode 100644
index 0000000..cbcdcf3
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+.endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+.endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+.endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/gb.inc b/playing-coffee - Copy/roms/mem_timing-2/source/common/gb.inc
new file mode 100644
index 0000000..2d0118d
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/gb.inc
@@ -0,0 +1,81 @@
+; Game Boy hardware addresses
+
+; $0000-$3FFF Fixed ROM bank
+; $4000-$7FFF Switchable bank
+; $8000-$9FFF VRAM
+; $A000-$BFFF optional cartridge RAM
+; $C000-$DFFF RAM
+; $E000-$FDFF RAM mirror
+; $FE00-$FE9F OAM
+; $FEA0-$FEFF unused
+; $FF00-$FF7F registers
+; $FF80-$FFFE RAM
+; $FFFF register
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define BRAM $A000 ; cart memory
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+; Registers
+
+.define RAMEN $0000 ; cartridge WRAM control
+.define BANK $2000 ; bank select
+.define P1 $FF00 ; controller
+
+; Game link I/O
+.define SB $FF01 ; serial buffer
+.define SC $FF02 ; serial control
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/macros.inc b/playing-coffee - Copy/roms/mem_timing-2/source/common/macros.inc
new file mode 100644
index 0000000..9d7cf44
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/macros.inc
@@ -0,0 +1,91 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ; addr
+ ldh a,(\1 - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ; addr
+ ldh (\1 - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Writes byte to addr
+; Preserved: F, BC, DE, HL
+; Time: 6 cycles
+.macro setb ; addr, data
+ ld a,\2
+ ld (\1),a
+.endm
+
+; Writes word to addr
+; Preserved: F, BC, DE, HL
+; Time: 12 cycles
+.macro setw ; addr, data
+ ld a,<\2
+ ld (\1),a
+ ld a,>\2
+ ld (\1+1),a
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/numbers.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/printing.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/printing.s
new file mode 100644
index 0000000..bb9389b
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/printing.s
@@ -0,0 +1,77 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+;print_char_nocrc (defined by user)
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/shell.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/shell.s
new file mode 100644
index 0000000..a045121
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/shell.s
@@ -0,0 +1,266 @@
+; Common routines and runtime
+
+.define RUNTIME_INCLUDED 1
+
+; A few bytes of RAM that aren't cleared
+.define nv_ram_base $D800
+.define nv_ram nv_ram_base
+
+; Address of next normal variable
+.define bss_base nv_ram_base+$80
+.define bss bss_base
+
+; Address of next direct-page ($FFxx) variable
+.define dp_base $FF80
+.define dp dp_base
+
+; Top of stack
+.define std_stack $DFFF+1
+
+; Final exit result byte is written here
+.define final_result $A000
+
+; Text output is written here as zero-terminated string
+.define text_out_base $A004
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+.define gb_id nv_ram
+.redefine nv_ram nv_ram+1
+
+; Copies C*$100 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+- ld a,(hl+)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+
+.ifndef RST_OFFSET
+ .define RST_OFFSET 0
+.endif
+
+.ifndef CUSTOM_RESET
+ reset:
+ di
+
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ ld c,$14
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org 0
+ jp std_reset
+.endif
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
+
+ ; Catch user code running off end
+ jp internal_error
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld b,a
+ and $EA
+ jr z,+
+ ld b,0
++ ld a,b
+ ld (gb_id),a
+
+ ; Clear memory except very top of stack
+ ld bc,std_stack-bss_base - 2
+ ld hl,bss_base
+ call clear_mem
+ ld bc,$FFFF-dp_base
+ ld hl,dp_base
+ call clear_mem
+
+ ; Init hardware
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ call init_runtime
+ call init_text_out
+ call console_init
+ call init_testing
+
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ call console_show
+ pop af
+ call play_byte
+ jp post_exit
+
++ push af
+ call print_newline
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+
+; Clears BC bytes starting at HL
+clear_mem:
+ ; If C>0, increment B
+ dec bc
+ inc c
+ inc b
+
+ ld a,0
+- ld (hl+),a
+ dec c
+ jr nz,-
+ dec b
+ jr nz,-
+ ret
+
+
+; Reports internal error and exits with code 255
+internal_error:
+ print_str "Internal error"
+ ld a,255
+ jp exit
+
+
+; build_devcart and build_multi customize this
+.ifndef CUSTOM_PRINT
+ .define text_out_addr bss+0
+ .redefine bss bss+2
+
+ ; Initializes text output to cartridge RAM
+ init_text_out:
+ ; Enable cartridge RAM and set text output pointer
+ setb RAMEN,$0A
+ setw text_out_addr,text_out_base
+ setb text_out_base-3,$DE
+ setb text_out_base-2,$B0
+ setb text_out_base-1,$61
+ setb text_out_base,0
+ setb final_result,$80
+ ret
+
+
+ ; Appends character to text output string
+ ; Preserved: AF, BC, DE, HL
+ write_text_out:
+ push hl
+ push af
+ ld a,(text_out_addr)
+ ld l,a
+ ld a,(text_out_addr+1)
+ ld h,a
+ inc hl
+ ld (hl),0
+ ld a,l
+ ld (text_out_addr),a
+ ld a,h
+ ld (text_out_addr+1),a
+ dec hl
+ pop af
+ ld (hl),a
+ pop hl
+ ret
+
+ print_char_nocrc:
+ call write_text_out
+ jp console_print
+.endif
+
+
+; only build_rom uses console
+.ifdef NEED_CONSOLE
+ .include "console.s"
+.else
+ console_init:
+ console_print:
+ console_flush:
+ console_normal:
+ console_inverse:
+ console_show:
+ console_set_mode:
+ ret
+.endif
+
+
+; build_devcart and build_multi need to customize this
+.ifndef CUSTOM_EXIT
+ post_exit:
+ ld (final_result),a
+ forever:
+ wreg NR52,0 ; sound off
+- jr -
+.endif
+
+
+.macro def_rst ARGS addr
+ .bank 0 slot 0
+ .org addr+RST_OFFSET
+.endm
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/testing.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/testing.s
new file mode 100644
index 0000000..5dd63ce
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/testing.s
@@ -0,0 +1,195 @@
+; Diagnostic and testing utilities
+
+.define test_code bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text.
+; Takes multiple strings due to wla's idiotic
+; default limit of 63 chars per string.
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2[,text3]]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .if NARGS > 3
+ .byte \4
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (test_code),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(test_code)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(test_code)
+ cp $FF ; if a = $FF then a = 1
+ jr nz,+
+ ld a,1
++ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+; Checks CRC, differing based on DMG or CGB build
+.macro check_crc_dmg_cgb ARGS dmg, cgb
+ .ifdef REQUIRE_DMG
+ check_crc dmg
+ .else
+ .ifdef REQUIRE_CGB
+ check_crc cgb
+ .else
+ .printt "CGB or DMG must be specified"
+ .fail
+ .endif
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/tima_64.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/tima_64.s
new file mode 100644
index 0000000..5b6c3a1
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/tima_64.s
@@ -0,0 +1,36 @@
+; Timer that's incremented every 64 cycles
+
+; Initializes timer for use by sync_tima_64
+init_tima_64:
+ wreg TMA,0
+ wreg TAC,$07
+ ret
+
+; Synchronizes to timer
+; Preserved: AF, BC, DE, HL
+sync_tima_64:
+ push af
+ push hl
+
+ ; Coarse
+ ld hl,TIMA
+ ld a,0
+ ld (hl),a
+- or (hl)
+ jr z,-
+
+ ; Fine
+- delay 65-12
+ xor a
+ ld (hl),a
+ or (hl)
+ delay 4
+ jr z,-
+
+ pop hl
+ pop af
+ ret
+
+; Read from this to get count that's incremented
+; every 64 cycles.
+.define tima_64 TIMA
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/linkfile b/playing-coffee - Copy/roms/mem_timing-2/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/readme.txt b/playing-coffee - Copy/roms/mem_timing-2/source/readme.txt
new file mode 100644
index 0000000..a0269c1
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/readme.txt
@@ -0,0 +1,82 @@
+Game Boy Tests Source Code
+--------------------------
+
+Building with wla-dx
+--------------------
+To assemble a test ROM with wla-dx, use the following commands:
+
+ wla -o source_filename_here.s test.o
+ wlalink linkfile test.gb
+
+To assemble as a GBS music file:
+
+ wla -o source_filename_here.s test.o -DBUILD_GBS
+ wlalink linkfile test.gbs
+
+Note that some tests might only work when built as a ROM or GBS file,
+but not both.
+
+Some tests might include a ROM/GBS that has all the tests combined.
+Building such a multi-test is complex and the necessary files aren't
+included.
+
+
+Framework
+---------
+Each test is in a single source file, and makes use of several library
+source files from common/. This framework provides common services and
+reduces code to only that which performs the actual test. Virtually all
+tests include "shell.inc" at the beginning, which sets things up and
+includes all the appropriate library files.
+
+The reset handler does minimal GB hardware initialization, clears RAM,
+sets up the text console, then runs main. Main can exit by returning or
+jumping to "exit" with an error code in A. Exit reports the code then
+goes into an infinite loop. If the code is 0, it doesn't do anything,
+otherwise it reports the code. Code 1 is reported as "Failed", and the
+rest as "Error ".
+
+The default is to build a ROM. Defining BUILD_GBS will build as an GBS.
+The other build types aren't supported due to their complexity. I load
+the code into RAM at $C000 since my devcart requires it, and I don't
+want the normal ROM to differ in any way from what I've tested. This
+also allows easy self-modifying code.
+
+Several routines are available to print values and text to the console.
+Most update a running CRC-32 checksum which can be checked with
+check_crc, allowing ALL the output to be checked very easily. If the
+checksum doesn't match, it is printed, so you can run the code on a GB
+and paste the correct checksum into your code.
+
+
+Macros
+------
+Some macros are used to make common operations more convenient. The left
+is equivalent to the right:
+
+ Macro Equivalent
+ -------------------------------------
+ lda addr ldh a,(addr-$FF00)
+
+ sta addr ldh (addr-$FF00),a
+
+ wreg addr,data ld a,data
+ ldh (addr-$FF00),a
+
+ setb ld a,data
+ ld (addr),a
+
+ setw setb addr+0,data
+
+ for_loop routine,begin,end,step
+ calls routine with A set to successive values
+
+ loop_n_times routine,count
+ calls routine with A from 0 to count-1
+
+ print_str "str" prints string
+
+
+--
+Shay Green
diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/shell.inc b/playing-coffee - Copy/roms/mem_timing-2/source/shell.inc
new file mode 100644
index 0000000..0d43faa
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing-2/source/shell.inc
@@ -0,0 +1,27 @@
+; Included at beginning of all programs
+; that use standard shell
+
+; Get include files from common/
+.incdir "common"
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.else
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
+
+.endif ; .ifdef BUILD_MULTI
diff --git a/playing-coffee - Copy/roms/mem_timing/individual/01-read_timing.gb b/playing-coffee - Copy/roms/mem_timing/individual/01-read_timing.gb
new file mode 100644
index 0000000..d0836aa
Binary files /dev/null and b/playing-coffee - Copy/roms/mem_timing/individual/01-read_timing.gb differ
diff --git a/playing-coffee - Copy/roms/mem_timing/individual/02-write_timing.gb b/playing-coffee - Copy/roms/mem_timing/individual/02-write_timing.gb
new file mode 100644
index 0000000..cfe7c0f
Binary files /dev/null and b/playing-coffee - Copy/roms/mem_timing/individual/02-write_timing.gb differ
diff --git a/playing-coffee - Copy/roms/mem_timing/individual/03-modify_timing.gb b/playing-coffee - Copy/roms/mem_timing/individual/03-modify_timing.gb
new file mode 100644
index 0000000..28f1ae6
Binary files /dev/null and b/playing-coffee - Copy/roms/mem_timing/individual/03-modify_timing.gb differ
diff --git a/playing-coffee - Copy/roms/mem_timing/mem_timing.gb b/playing-coffee - Copy/roms/mem_timing/mem_timing.gb
new file mode 100644
index 0000000..78766b5
Binary files /dev/null and b/playing-coffee - Copy/roms/mem_timing/mem_timing.gb differ
diff --git a/playing-coffee - Copy/roms/mem_timing/readme.txt b/playing-coffee - Copy/roms/mem_timing/readme.txt
new file mode 100644
index 0000000..98d76e2
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/readme.txt
@@ -0,0 +1,122 @@
+Game Boy CPU Memory Access Timing Test
+--------------------------------------
+This ROM tests the timing of memory reads and writes made by
+instructions, except stack and program counter accesses. These tests
+require correct instruction timing and proper timer operation (TAC,
+TIMA, TMA).
+
+The read and write tests list failing instructions as
+
+ [CB] opcode:tested-correct
+
+The read-modify-write test lists failing instructions as
+
+ [CB] opcode:tested read/tested write-correct read/correct write
+
+The values after the opcode refer to which instruction cycle the access
+occurs on, with 1 being the first. If a time couldn't be determined due
+to some other problem, it prints 0.
+
+For instructions which either read or write, but not both, the CPU makes
+the access on the last cycle. For instructions which read, modify, then
+write back, the CPU reads on the next-to-last cycle, and writes on the
+last cycle.
+
+
+Internal operation
+------------------
+The tests have the timer increment TIMA every 64 cycles, synchronize
+with this, delay a variable amount, then have the instruction under test
+access the timer. By varying the delay in one-cycle increments, the
+memory access made by the instruction can be made to fall before and
+after a TIMA increment. By then examining the registers and value in
+TIMA, it can be determined which occurred.
+
+
+Multi-ROM
+---------
+In the main directory is a single ROM which runs all the tests. It
+prints a test's number, runs the test, then "ok" if it passes, otherwise
+a failure code. Once all tests have completed it either reports that all
+tests passed, or prints the number of failed tests. Finally, it makes
+several beeps. If a test fails, it can be run on its own by finding the
+corresponding ROM in individual/.
+
+Ths compact format on screen is to avoid having the results scroll off
+the top, so the test can be started and allowed to run without having to
+constantly monitor the display.
+
+Currently there is no well-defined way for an emulator test rig to
+programatically find the result of the test; contact me if you're trying
+to do completely automated testing of your emulator. One simple approach
+is to take a screenshot after all tests have run, or even just a
+checksum of one, and compare this with a previous run.
+
+
+Failure codes
+-------------
+Failed tests may print a failure code, and also short description of the
+problem. For more information about a failure code, look in the
+corresponding source file in source/; the point in the code where
+"set_test n" occurs is where that failure code will be generated.
+Failure code 1 is a general failure of the test; any further information
+will be printed.
+
+Note that once a sub-test fails, no further tests for that file are run.
+
+
+Console output
+--------------
+Information is printed on screen in a way that needs only minimum LCD
+support, and won't hang if LCD output isn't supported at all.
+Specifically, while polling LY to wait for vblank, it will time out if
+it takes too long, so LY always reading back as the same value won't
+hang the test. It's also OK if scrolling isn't supported; in this case,
+text will appear starting at the top of the screen.
+
+Everything printed on screen is also sent to the game link port by
+writing the character to SB, then writing $81 to SC. This is useful for
+tests which print lots of information that scrolls off screen.
+
+
+Source code
+-----------
+Source code is included for all tests, in source/. It can be used to
+build the individual test ROMs. Code for the multi test isn't included
+due to the complexity of putting everything together.
+
+Code is written for the wla-dx assembler. To assemble a particular test,
+execute
+
+ wla -o "source_filename.s" test.o
+ wlalink linkfile test.gb
+
+Test code uses a common shell framework contained in common/.
+
+
+Internal framework operation
+----------------------------
+Tests use a common framework for setting things up, reporting results,
+and ending. All files first include "shell.inc", which sets up the ROM
+header and shell code, and includes other commonly-used modules.
+
+One oddity is that test code is first copied to internal RAM at $D000,
+then executed there. This allows self-modification, and ensures the code
+is executed the same way it is on my devcart, which doesn't have a
+rewritable ROM as most do.
+
+Some macros are used to simplify common tasks:
+
+ Macro Behavior
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ wreg addr,data Writes data to addr using LDH
+ lda addr Loads byte from addr into A using LDH
+ sta addr Stores A at addr using LDH
+ delay n Delays n cycles, where NOP = 1 cycle
+ delay_msec n Delays n milliseconds
+ set_test n,"Cause" Sets failure code and optional string
+
+Routines and macros are documented where they are defined.
+
+--
+Shay Green
diff --git a/playing-coffee - Copy/roms/mem_timing/source/01-read_timing.s b/playing-coffee - Copy/roms/mem_timing/source/01-read_timing.s
new file mode 100644
index 0000000..e119615
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/01-read_timing.s
@@ -0,0 +1,148 @@
+; Tests timing of accesses made by
+; memory read instructions
+
+.include "shell.inc"
+.include "tima_64.s"
+
+instructions:
+ ; last value is time of read
+ .byte $B6,$00,$00,2 ; OR (HL)
+ .byte $BE,$00,$00,2 ; CP (HL)
+ .byte $86,$00,$00,2 ; ADD (HL)
+ .byte $8E,$00,$00,2 ; ADC (HL)
+ .byte $96,$00,$00,2 ; SUB (HL)
+ .byte $9E,$00,$00,2 ; SBC (HL)
+ .byte $A6,$00,$00,2 ; AND (HL)
+ .byte $AE,$00,$00,2 ; XOR (HL)
+ .byte $46,$00,$00,2 ; LD B,(HL)
+ .byte $4E,$00,$00,2 ; LD C,(HL)
+ .byte $56,$00,$00,2 ; LD D,(HL)
+ .byte $5E,$00,$00,2 ; LD E,(HL)
+ .byte $66,$00,$00,2 ; LD H,(HL)
+ .byte $6E,$00,$00,2 ; LD L,(HL)
+ .byte $7E,$00,$00,2 ; LD A,(HL)
+ .byte $F2,$00,$00,2 ; LDH A,(C)
+ .byte $0A,$00,$00,2 ; LD A,(BC)
+ .byte $1A,$00,$00,2 ; LD A,(DE)
+ .byte $2A,$00,$00,2 ; LD A,(HL+)
+ .byte $3A,$00,$00,2 ; LD A,(HL-)
+ .byte $F0,tima_64,4 ; LD A,($0000)
+
+ .byte $CB,$46,$00,3 ; BIT 0,(HL)
+ .byte $CB,$4E,$00,3 ; BIT 1,(HL)
+ .byte $CB,$56,$00,3 ; BIT 2,(HL)
+ .byte $CB,$5E,$00,3 ; BIT 3,(HL)
+ .byte $CB,$66,$00,3 ; BIT 4,(HL)
+ .byte $CB,$6E,$00,3 ; BIT 5,(HL)
+ .byte $CB,$76,$00,3 ; BIT 6,(HL)
+ .byte $CB,$7E,$00,3 ; BIT 7,(HL)
+instructions_end:
+
+main:
+ call init_tima_64
+ set_test 0
+
+ ; Test instructions
+ ld hl,instructions
+- call @time_instr
+ cp (hl)
+ call nz,@print_failed
+ inc hl
+ ld a,l
+ cp 3-byte instruction
+; HL <- HL + 3
+@time_instr:
+ ; Copy instr
+ ld a,(hl+)
+ ld (instr+0),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Find result when access doesn't occur
+ ld b,0
+ call @time_access
+ ld c,a
+
+ ; Test for accesses on each cycle
+ ld b,0
+- push bc
+ call @time_access
+ pop bc
+ cp c
+ jr nz,@found
+ inc b
+ ld a,b
+ cp 10
+ jr nz,-
+ ld b,0
+
+@found:
+ ld a,b
+ pop hl
+ ret
+
+; Tests for read
+; B -> which cycle to test
+; A <- timer value after test
+@time_access:
+ call sync_tima_64
+ ld a,9
+ sub b
+ call delay_a_20_cycles
+ xor a ; clear flags
+ ld hl,tima_64
+ ld (hl),$7F
+ ld bc,tima_64
+ ld de,tima_64
+ ld a,$7F
+instr:
+ nop
+ nop
+ nop
+
+ ; Add all registers together to yield
+ ; unique value that differs based on
+ ; read occurring before or after tima_64
+ ; increments.
+ push af
+ add hl,bc
+ add hl,de
+ pop de
+ add hl,de
+ ld a,h
+ add l
+ ret
diff --git a/playing-coffee - Copy/roms/mem_timing/source/02-write_timing.s b/playing-coffee - Copy/roms/mem_timing/source/02-write_timing.s
new file mode 100644
index 0000000..acd6d9d
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/02-write_timing.s
@@ -0,0 +1,115 @@
+; Tests timing of accesses made by
+; memory write instructions
+
+.include "shell.inc"
+.include "tima_64.s"
+
+instructions:
+ ; last value is time of write
+ .byte $36,$FF,$00,3 ; LD (HL),n
+ .byte $70,$00,$00,2 ; LD (HL),B
+ .byte $71,$00,$00,2 ; LD (HL),C
+ .byte $72,$00,$00,2 ; LD (HL),D
+ .byte $73,$00,$00,2 ; LD (HL),E
+ .byte $74,$00,$00,2 ; LD (HL),H
+ .byte $75,$00,$00,2 ; LD (HL),L
+ .byte $77,$00,$00,2 ; LD (HL),A
+ .byte $02,$00,$00,2 ; LD (BC),A
+ .byte $12,$00,$00,2 ; LD (DE),A
+ .byte $22,$00,$00,2 ; LD (HL+),A
+ .byte $32,$00,$00,2 ; LD (HL-),A
+ .byte $E2,$00,$00,2 ; LDH (C),A
+ .byte $E0,tima_64,4 ; LD (nn),A
+instructions_end:
+
+main:
+ call init_tima_64
+ set_test 0
+
+ ; Test instructions
+ ld hl,instructions
+- call @test_instr
+ cp (hl)
+ call nz,@print_failed
+ inc hl
+ ld a,l
+ cp 3-byte instruction
+; HL <- HL + 3
+@test_instr:
+ ; Copy instr
+ ld a,(hl+)
+ ld (instr+0),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Test for writes on each cycle
+ ld b,0
+- push bc
+ call @time_write
+ pop bc
+ cp tima_64
+ jr z,@no_write
+ jr @found
+@no_write:
+ inc b
+ ld a,b
+ cp 10
+ jr nz,-
+ ld b,0
+
+@found:
+ ld a,b
+ pop hl
+ ret
+
+; Tests for write
+; B -> which cycle to test
+; A <- timer value after test
+@time_write:
+ call sync_tima_64
+ ld a,13
+ sub b
+ call delay_a_20_cycles
+ ld hl,tima_64
+ ld bc,tima_64
+ ld de,tima_64
+ ld a, 3-byte instruction
+; HL <- HL + 3
+@time_instr:
+ ; Copy instr
+ ld a,(hl+)
+ ld (instr+0),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Find result when access doesn't occur
+ ld b,0
+ call @time_access
+
+ ; Find first access
+ call @find_next_access
+ ld d,b
+
+ ; Find second access
+ call @find_next_access
+ ld e,b
+
+ pop hl
+ ret
+
+; A -> current timer result
+; B -> starting clock
+; B <- clock next access occurs on
+; A <- new timer result
+@find_next_access:
+ ld c,a
+- call @time_access
+ cp c
+ ret nz
+ inc b
+ ld a,b
+ cp 10
+ jr c,-
+
+ ; Couldn't find time, so return 0/0
+ ld a,c
+ ld b,0
+ ld d,b
+ ret
+
+; Tests for access
+; B -> which cycle to test
+; A <- timer value after test
+@time_access:
+ call sync_tima_64
+ ld hl,tima_64
+ ld (hl),$7F
+ ld a,17
+ sub b
+ call delay_a_20_cycles
+ xor a ; clear flags
+instr:
+ nop
+ nop
+ nop
+ delay 32
+ ld a,(tima_64)
+ ret
diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/build_gbs.s b/playing-coffee - Copy/roms/mem_timing/source/common/build_gbs.s
new file mode 100644
index 0000000..7ac8d3c
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/common/build_gbs.s
@@ -0,0 +1,121 @@
+; Build as GBS music file
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $3000 size $1000
+ slot 1 $C000 size $1000
+.endMe
+
+.romBankSize $1000
+.romBanks 2
+
+
+;;;; GBS music file header
+
+.byte "GBS"
+.byte 1 ; vers
+.byte 1 ; songs
+.byte 1 ; first song
+.word load_addr
+.word reset
+.word gbs_play
+.word std_stack
+.byte 0,0 ; timer
+.ds $60,0
+load_addr:
+
+; WLA assumes we're building ROM and messes
+; with bytes at the beginning, so skip them.
+.ds $100,0
+
+
+;;;; Shell
+
+.include "runtime.s"
+
+init_runtime:
+ ld a,$01 ; Identify as DMG hardware
+ ld (gb_id),a
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+ ret
+
+std_print:
+ sta SB
+ wreg SC,$81
+ delay 2304
+ ret
+
+post_exit:
+ call play_byte
+forever:
+ wreg NR52,0 ; sound off
+- jp -
+
+.ifndef CUSTOM_RESET
+ gbs_play:
+.endif
+console_flush:
+console_normal:
+console_inverse:
+console_set_mode:
+ ret
+
+; Reports A in binary as high and low tones, with
+; leading low tone for reference. Omits leading
+; zeroes.
+; Preserved: AF, BC, DE, HL
+play_byte:
+ push af
+ push hl
+
+ ; HL = (A << 1) | 1
+ scf
+ rla
+ ld l,a
+ ld h,0
+ rl h
+
+ ; Shift left until next-to-top bit is 1
+- add hl,hl
+ bit 6,h
+ jr z,-
+
+ ; Reset sound
+ delay_msec 400
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+- add hl,hl
+
+ ; Low or high pitch based on bit shifted out
+ ; of HL
+ ld a,0
+ jr nc,+
+ ld a,$FF
++ sta NR23
+
+ ; Play short tone
+ wreg NR21,$A0
+ wreg NR22,$F0
+ wreg NR24,$86
+ delay_msec 75
+ wreg NR22,0
+ wreg NR23,$F8
+ wreg NR24,$87
+ delay_msec 200
+
+ ; Loop until HL = $8000
+ ld a,h
+ xor $80
+ or l
+ jr nz,-
+
+ pop hl
+ pop af
+ ret
+
+.ends
diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/build_rom.s b/playing-coffee - Copy/roms/mem_timing/source/common/build_rom.s
new file mode 100644
index 0000000..e1e220f
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/common/build_rom.s
@@ -0,0 +1,80 @@
+; Build as GB ROM
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $0000 size $4000
+ slot 1 $C000 size $4000
+.endMe
+
+.romBankSize $4000 ; generates $8000 byte ROM
+.romBanks 2
+
+.cartridgeType 1 ; MBC1
+.computeChecksum
+.computeComplementCheck
+
+
+;;;; GB ROM header
+
+; GB header read by bootrom
+.org $100
+ nop
+ jp reset
+
+; Nintendo logo required for proper boot
+.byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
+.byte $03,$73,$00,$83,$00,$0C,$00,$0D
+.byte $00,$08,$11,$1F,$88,$89,$00,$0E
+.byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
+.byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
+.byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
+
+; Internal name
+.ifdef ROM_NAME
+ .byte ROM_NAME
+.endif
+
+; CGB/DMG requirements
+.org $143
+ .ifdef REQUIRE_CGB
+ .byte $C0
+ .else
+ .ifndef REQUIRE_DMG
+ .byte $80
+ .endif
+ .endif
+
+.org $200
+
+
+;;;; Shell
+
+.include "runtime.s"
+.include "console.s"
+
+init_runtime:
+ call console_init
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+ ret
+
+std_print:
+ push af
+ sta SB
+ wreg SC,$81
+ delay 2304
+ pop af
+ jp console_print
+
+post_exit:
+ call console_show
+ call play_byte
+forever:
+ wreg NR52,0 ; sound off
+- jr -
+
+play_byte:
+ ret
+
+.ends
diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/console.bin b/playing-coffee - Copy/roms/mem_timing/source/common/console.bin
new file mode 100644
index 0000000..b02f2d3
Binary files /dev/null and b/playing-coffee - Copy/roms/mem_timing/source/common/console.bin differ
diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/console.s b/playing-coffee - Copy/roms/mem_timing/source/common/console.s
new file mode 100644
index 0000000..5307f6b
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/common/console.s
@@ -0,0 +1,291 @@
+; Scrolling text console
+
+; Console is 20x18 characters. Buffers lines, so
+; output doesn't appear until a newline or flush.
+; If scrolling isn't supported (i.e. SCY is treated
+; as if always zero), the first 18 lines will
+; still print properly). Also works properly if
+; LY isn't supported (always reads back as the same
+; value).
+
+.define console_width 20
+
+.define console_buf bss+0
+.define console_pos bss+console_width
+.define console_mode bss+console_width+1
+.define console_scroll bss+console_width+2
+.redefine bss bss+console_width+3
+
+
+; Waits for start of LCD blanking period
+; Preserved: BC, DE, HL
+console_wait_vbl:
+ push bc
+
+ ; Wait for start of vblank, with
+ ; timeout in case LY doesn't work
+ ; or LCD is disabled.
+ ld bc,-1250
+- inc bc
+ ld a,b
+ or c
+ jr z,@timeout
+ lda LY
+ cp 144
+ jr nz,-
+@timeout:
+
+ pop bc
+ ret
+
+
+; Initializes text console
+console_init:
+ call console_hide
+
+ ; CGB-specific inits
+ ld a,(gb_id)
+ and gb_id_cgb
+ call nz,@init_cgb
+
+ ; Clear nametable
+ ld a,' '
+ call @fill_nametable
+
+ ; Load tiles
+ ld hl,TILES+$200
+ ld c,0
+ call @load_tiles
+ ld hl,TILES+$A00
+ ld c,$FF
+ call @load_tiles
+
+ ; Init state
+ ld a,console_width
+ ld (console_pos),a
+ ld a,0
+ ld (console_mode),a
+ ld a,-8
+ ld (console_scroll),a
+ call console_scroll_up_
+ jr console_show
+
+@fill_nametable:
+ ld hl,BGMAP0
+ ld b,4
+- ld (hl),a
+ inc l
+ jr nz,-
+ inc h
+ dec b
+ jr nz,-
+ ret
+
+@init_cgb:
+ ; Clear palette
+ wreg $FF68,$80
+ ld b,16
+- wreg $FF69,$FF
+ wreg $FF69,$7F
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ dec b
+ jr nz,-
+
+ ; Clear attributes
+ ld a,1
+ ld (VBK),a
+ ld a,0
+ call @fill_nametable
+
+ ld a,0
+ ld (VBK),a
+ ret
+
+@load_tiles:
+ ld de,ASCII
+ ld b,96
+-- push bc
+ ld b,8
+- ld a,(de)
+ inc de
+ xor c
+ ldi (hl),a
+ ldi (hl),a
+ dec b
+ jr nz,-
+ pop bc
+ dec b
+ jr nz,--
+ ret
+
+
+; Shows console display
+; Preserved: AF, BC, DE, HL
+console_show:
+ push af
+
+ ; Enable LCD
+ call console_wait_vbl
+ wreg LCDC,$91
+ wreg SCX,0
+ wreg BGP,$E4
+
+ jp console_apply_scroll_
+
+
+; Hides console display by turning LCD off
+; Preserved: AF, BC, DE, HL
+console_hide:
+ push af
+
+ ; LCD off
+ call console_wait_vbl
+ wreg LCDC,$11
+
+ pop af
+ ret
+
+
+; Changes to normal text mode
+; Preserved: BC, DE, HL
+console_normal:
+ xor a
+ jr console_set_mode
+
+; Changes to inverse text mode
+; Preserved: BC, DE, HL
+console_inverse:
+ ld a,$80
+
+; Changes console mode to A.
+; 0: Normal, $80: Inverse
+; Preserved: BC, DE, HL
+console_set_mode:
+ and $80
+ ld (console_mode),a
+ ret
+
+
+; Prints char A to console. Will not appear until
+; a newline or flush occurs.
+; Preserved: AF, BC, DE, HL
+console_print:
+ push af
+
+ cp 10
+ jr z,console_newline_
+
+ push hl
+ push af
+ ld hl,console_pos
+ ldi a,(hl)
+ cp BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/delay.s b/playing-coffee - Copy/roms/mem_timing/source/common/delay.s
new file mode 100644
index 0000000..65eb9c0
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+ .endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+ .endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+ .endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/gb.inc b/playing-coffee - Copy/roms/mem_timing/source/common/gb.inc
new file mode 100644
index 0000000..31bbf14
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/common/gb.inc
@@ -0,0 +1,64 @@
+; Game Boy hardware addresses
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+.define P1 $FF00
+
+; Game link I/O
+.define SB $FF01
+.define SC $FF02
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/macros.inc b/playing-coffee - Copy/roms/mem_timing/source/common/macros.inc
new file mode 100644
index 0000000..c413bd5
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/common/macros.inc
@@ -0,0 +1,73 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ARGS addr
+ ldh a,(addr - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ARGS addr
+ ldh (addr - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/numbers.s b/playing-coffee - Copy/roms/mem_timing/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/printing.s b/playing-coffee - Copy/roms/mem_timing/source/common/printing.s
new file mode 100644
index 0000000..ad9d811
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/common/printing.s
@@ -0,0 +1,98 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+.define print_char_nocrc bss
+.redefine bss bss+3
+
+
+; Initializes printing. HL = print routine
+init_printing:
+ ld a,l
+ ld (print_char_nocrc+1),a
+ ld a,h
+ ld (print_char_nocrc+2),a
+ jr show_printing
+
+
+; Hides/shows further printing
+; Preserved: BC, DE, HL
+hide_printing:
+ ld a,$C9 ; RET
+ jr +
+show_printing:
+ ld a,$C3 ; JP (nn)
++ ld (print_char_nocrc),a
+ ret
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/runtime.s b/playing-coffee - Copy/roms/mem_timing/source/common/runtime.s
new file mode 100644
index 0000000..90cd24f
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/common/runtime.s
@@ -0,0 +1,142 @@
+; Common routines and runtime
+
+; Must be defined by target-specific runtime:
+;
+; init_runtime: ; target-specific inits
+; std_print: ; default routine to print char A
+; post_exit: ; called at end of std_exit
+; report_byte: ; report A to user
+
+.define RUNTIME_INCLUDED 1
+
+.ifndef bss
+ ; address of next normal variable
+ .define bss $D800
+.endif
+
+.ifndef dp
+ ; address of next direct-page ($FFxx) variable
+ .define dp $FF80
+.endif
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+
+.define gb_id bss
+.redefine bss bss+1
+
+; Stack is normally here
+.define std_stack $DFFF
+
+; Copies $1000 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+ ld c,$10
+- ldi a,(hl)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+.ifndef CUSTOM_RESET
+ reset:
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org $0 ; otherwise wla pads with lots of zeroes
+ jp std_reset
+.endif
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld (gb_id),a
+
+ ; Init hardware
+ .ifndef BUILD_GBS
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+ .endif
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ ; TODO: clear all memory?
+
+ ld hl,std_print
+ call init_printing
+ call init_testing
+ call init_runtime
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ pop af
+ jp post_exit
+
++ push af
+ call print_newline
+ call show_printing
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/testing.s b/playing-coffee - Copy/roms/mem_timing/source/common/testing.s
new file mode 100644
index 0000000..c102f78
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/common/testing.s
@@ -0,0 +1,176 @@
+; Diagnostic and testing utilities
+
+.define result bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (result),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(result)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(result)
+ cp 1 ; if a = 0 then a = 1
+ adc 0
+ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call show_printing
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/tima_64.s b/playing-coffee - Copy/roms/mem_timing/source/common/tima_64.s
new file mode 100644
index 0000000..1db7211
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/common/tima_64.s
@@ -0,0 +1,33 @@
+; Timer that's incremented every 64 cycles
+
+; Initializes timer for use by sync_tima_64
+init_tima_64:
+ wreg TMA,0
+ wreg TAC,$07
+ ret
+
+; Synchronizes to timer
+sync_tima_64:
+ push af
+ push hl
+
+ ld a,0
+ ld hl,TIMA
+ ld (hl),a
+- or (hl)
+ jr z,-
+
+- delay 65-12
+ xor a
+ ld (hl),a
+ or (hl)
+ delay 4
+ jr z,-
+
+ pop hl
+ pop af
+ ret
+
+; Read from this to get count that's incremented
+; every 64 cycles.
+.define tima_64 TIMA
diff --git a/playing-coffee - Copy/roms/mem_timing/source/linkfile b/playing-coffee - Copy/roms/mem_timing/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee - Copy/roms/mem_timing/source/shell.inc b/playing-coffee - Copy/roms/mem_timing/source/shell.inc
new file mode 100644
index 0000000..277a9b7
--- /dev/null
+++ b/playing-coffee - Copy/roms/mem_timing/source/shell.inc
@@ -0,0 +1,21 @@
+.incdir "common"
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
diff --git a/playing-coffee - Copy/roms/oam_bug/oam_bug.gb b/playing-coffee - Copy/roms/oam_bug/oam_bug.gb
new file mode 100644
index 0000000..42ff9ca
Binary files /dev/null and b/playing-coffee - Copy/roms/oam_bug/oam_bug.gb differ
diff --git a/playing-coffee - Copy/roms/oam_bug/readme.txt b/playing-coffee - Copy/roms/oam_bug/readme.txt
new file mode 100644
index 0000000..cf59776
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/readme.txt
@@ -0,0 +1,109 @@
+OAM Corruption Test
+-------------------
+* Verifies OAM corruption bug on DMG.
+
+* Occurs when 16-bit increment/decrement is made of value in range $FE00
+to $FEFF, during around the first 20 cycles of a visible scanline while
+LCD is on, where 114 cycles = 1 scanline.
+
+* Causes several bytes of OAM to be copied from one place to another.
+
+* Occurs with instructions that do increment:
+ INC rp (including SP)
+ DEC rp
+ POP rp counts as two increments
+ PUSH rp counts as two increments
+ LD A,(HL+)
+ LD A,(HL-)
+
+* Doesn't occur with instructions that do 16-bit add:
+ LD HL,SP+n
+ ADD HL,rp
+ ADD SP,n
+
+* Doesn't occur anytime during the 10 vblank scanlines.
+
+* Doesn't occur when LCD is off, no matter when it happens.
+
+* Corruption depends on when it occurs.
+
+
+Multi-ROM
+---------
+In the main directory is a single ROM/GBS which runs all the tests. It
+prints a test's number, runs the test, then "ok" if it passes, otherwise
+a failure code. Once all tests have completed it either reports that all
+tests passed, or reports the number of the first failed test as the
+result code (1 = first). Finally, it makes several beeps. If a test
+fails, it can be run on its own by finding the corresponding ROM/GBS in
+the singles directories.
+
+Ths compact format on screen is to avoid having the results scroll off
+the top, so the test can be started and allowed to run without having to
+constantly monitor the display.
+
+
+Failure information
+-------------------
+For more information about a failure code or information printed, see
+the test's source code in source/. To find failure code N, search for
+"set_test N", which will usually be before the subtest which failed.
+
+
+Flashes, clicks, other glitches
+-------------------------------
+Some tests might need to turn the screen off and on, or cause slight
+audio clicks. This does not indicate failure, and should be ignored.
+Only the test result reported at the end is important, unless stated
+otherwise.
+
+
+LCD support
+-----------
+Tests generally print information on screen. The tests will work fine if
+run on an emulator with NO LCD support, or as an GBS which has no
+inherent screen; in particular, the VBL wait routine has a timeout in
+case LY doesn't reflect the current LCD line. The text printing will
+also work if the LCD doesn't support scrolling.
+
+
+Output to memory
+----------------
+Text output and the final result are also written to memory at $A000,
+allowing testing a very minimal emulator that supports little more than
+CPU and RAM. To reliably indicate that the data is from a test and not
+random data, $A001-$A003 are written with a signature: $DE,$B0,$61. If
+this is present, then the text string and final result status are valid.
+
+$A000 holds the overall status. If the test is still running, it holds
+$80, otherwise it holds the final result code.
+
+All text output is appended to a zero-terminated string at $A004. An
+emulator could regularly check this string for any additional
+characters, and output them, allowing real-time text output, rather than
+just printing the final output at the end.
+
+
+GBS versions
+------------
+Many GBS-based tests require that the GBS player either not interrupt
+the init routine with the play routine, or if they do, not interrupt the
+play routine again if it hasn't returned yet. This is because many tests
+need to run for a while without returning.
+
+In addition to the other text output methods described above, GBS builds
+report essential information bytes audibly, including the final result.
+A byte is reported as a series of tones. The code is in binary, with a
+low tone for 0 and a high tone for 1. The first tone is always a zero. A
+final code of 0 means passed, 1 means failure, and 2 or higher indicates
+a specific reason as listed in the source code by the corresponding
+set_code line. Examples:
+
+Tones Binary Decimal Meaning
+- - - - - - - - - - - - - - - - - - - -
+low 0 0 passed
+low high 01 1 failed
+low high low 010 2 error 2
+
+--
+Shay Green
diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/1-lcd_sync.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/1-lcd_sync.gb
new file mode 100644
index 0000000..8f3d856
Binary files /dev/null and b/playing-coffee - Copy/roms/oam_bug/rom_singles/1-lcd_sync.gb differ
diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/2-causes.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/2-causes.gb
new file mode 100644
index 0000000..b997cda
Binary files /dev/null and b/playing-coffee - Copy/roms/oam_bug/rom_singles/2-causes.gb differ
diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/3-non_causes.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/3-non_causes.gb
new file mode 100644
index 0000000..d545e83
Binary files /dev/null and b/playing-coffee - Copy/roms/oam_bug/rom_singles/3-non_causes.gb differ
diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/4-scanline_timing.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/4-scanline_timing.gb
new file mode 100644
index 0000000..dc38b26
Binary files /dev/null and b/playing-coffee - Copy/roms/oam_bug/rom_singles/4-scanline_timing.gb differ
diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/5-timing_bug.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/5-timing_bug.gb
new file mode 100644
index 0000000..47a5c02
Binary files /dev/null and b/playing-coffee - Copy/roms/oam_bug/rom_singles/5-timing_bug.gb differ
diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/6-timing_no_bug.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/6-timing_no_bug.gb
new file mode 100644
index 0000000..b4e67e9
Binary files /dev/null and b/playing-coffee - Copy/roms/oam_bug/rom_singles/6-timing_no_bug.gb differ
diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/7-timing_effect.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/7-timing_effect.gb
new file mode 100644
index 0000000..2212779
Binary files /dev/null and b/playing-coffee - Copy/roms/oam_bug/rom_singles/7-timing_effect.gb differ
diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/8-instr_effect.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/8-instr_effect.gb
new file mode 100644
index 0000000..464a5f4
Binary files /dev/null and b/playing-coffee - Copy/roms/oam_bug/rom_singles/8-instr_effect.gb differ
diff --git a/playing-coffee - Copy/roms/oam_bug/source/1-lcd_sync.s b/playing-coffee - Copy/roms/oam_bug/source/1-lcd_sync.s
new file mode 100644
index 0000000..3c4f5e5
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/1-lcd_sync.s
@@ -0,0 +1,28 @@
+; Verifies LCD timing when turning on. Necessary for
+; timing tests to work right.
+
+; With LCD off, turning it on synchronizes to beginning
+; of first visible scanline
+
+.include "oam_bug.inc"
+
+main:
+ set_test 2,"Turning LCD on starts too late in scanline"
+ call disable_lcd
+ ld a,$81
+ ldh (LCDC-$FF00),a ; LCD on
+ delay 109
+ ldh a,(LY-$FF00) ; just before LY increments
+ cp 0
+ jp nz,test_failed
+
+ set_test 3,"Turning LCD on starts too early in scanline"
+ call disable_lcd
+ ld a,$81
+ ldh (LCDC-$FF00),a ; LCD on
+ delay 110
+ ldh a,(LY-$FF00) ; just after LY increments
+ cp 1
+ jp nz,test_failed
+
+ jp tests_passed
diff --git a/playing-coffee - Copy/roms/oam_bug/source/2-causes.s b/playing-coffee - Copy/roms/oam_bug/source/2-causes.s
new file mode 100644
index 0000000..15b725c
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/2-causes.s
@@ -0,0 +1,86 @@
+; Things that cause corruption
+
+.include "oam_bug.inc"
+
+main:
+ save_sp
+
+ set_test 2,"LD DE,$FE00 : INC DE"
+ call begin
+ ld de,$FE00
+ inc de
+ call end
+
+ set_test 3,"LD DE,$FE00 : DEC DE"
+ call begin
+ ld de,$FE00
+ dec de
+ call end
+
+ set_test 4,"LD DE,$FEFF : INC DE"
+ call begin
+ ld de,$FEFF
+ inc de
+ call end
+
+ set_test 5,"LD BC,$FE00 : INC BC"
+ call begin
+ ld bc,$FE00
+ inc bc
+ call end
+
+ set_test 6,"LD HL,$FE00 : INC HL"
+ call begin
+ ld hl,$FE00
+ inc hl
+ call end
+
+ set_test 7,"LD SP,$FE00 : INC SP"
+ call begin
+ ld sp,$FE00
+ inc sp
+ restore_sp
+ call end
+
+ set_test 8,"LD SP,$FDFF : POP BC"
+ call begin
+ ld sp,$FDFF
+ pop bc
+ restore_sp
+ call end
+
+ set_test 9,"LD SP,$FE00 : PUSH BC"
+ call begin
+ ld sp,$FE00
+ push bc
+ restore_sp
+ call end
+
+ set_test 10,"LD HL,$FE00 : LD A,(HL+)"
+ call begin
+ ld hl,$FE00
+ ld a,(hl+)
+ restore_sp
+ call end
+
+ set_test 11,"LD HL,$FE00 : LD A,(HL-)"
+ call begin
+ ld hl,$FE00
+ ld a,(hl-)
+ restore_sp
+ call end
+
+ jp tests_passed
+
+begin:
+ call disable_lcd
+ call fill_oam
+ wreg LCDC,$81
+ delay 70224 - 6
+ ret
+
+end:
+ call disable_lcd
+ call cp_oam
+ jp z,test_failed
+ ret
diff --git a/playing-coffee - Copy/roms/oam_bug/source/3-non_causes.s b/playing-coffee - Copy/roms/oam_bug/source/3-non_causes.s
new file mode 100644
index 0000000..c161f97
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/3-non_causes.s
@@ -0,0 +1,86 @@
+; Things that don't cause corruption
+
+.include "oam_bug.inc"
+
+main:
+ save_sp
+
+ set_test 2,"When LCD is off"
+ call disable_lcd
+ call fill_oam
+ ld bc,2 * 70224/11 ; a couple of frames
+ ld de,$FE00
+- inc de
+ dec bc
+ dec de
+ ld a,b
+ or c
+ jr nz,-
+ call end
+
+ set_test 3,"LD DE,$FF00 : DEC DE"
+ call begin
+ ld de,$FF00
+ dec de
+ call end
+
+ set_test 4,"LD DE,$FDFF : INC DE"
+ call begin
+ ld de,$FDFF
+ inc de
+ call end
+
+ set_test 5,"LD DE,$7E00 : INC DE"
+ call begin
+ ld de,$7E00
+ inc de
+ call end
+
+ set_test 6,"LD DE,$FE00 : INC E"
+ call begin
+ ld de,$FE00
+ inc e
+ call end
+
+ set_test 7,"LD SP,$FDFE : POP BC"
+ call begin
+ ld sp,$FDFE
+ pop bc
+ restore_sp
+ call end
+
+ set_test 8,"LD SP,$FE00 : LD HL,SP+1"
+ call begin
+ ld sp,$FE00
+ ld hl,sp+1
+ restore_sp
+ call end
+
+ set_test 9,"LD HL,$FE00 : LD BC,$0001 : ADD HL,BC"
+ call begin
+ ld hl,$FE00
+ ld bc,$0001
+ add hl,bc
+ call end
+
+ set_test 10,"LD SP,$FE00 : ADD SP,1"
+ call begin
+ ld sp,$FE00
+ add sp,1
+ restore_sp
+ call end
+
+ jp tests_passed
+
+begin:
+ call disable_lcd
+ call fill_oam
+ wreg LCDC,$81
+ delay 70224 - 6
+ ret
+
+end:
+ call disable_lcd
+ call cp_oam
+ jp nz,test_failed
+ ret
diff --git a/playing-coffee - Copy/roms/oam_bug/source/4-scanline_timing.s b/playing-coffee - Copy/roms/oam_bug/source/4-scanline_timing.s
new file mode 100644
index 0000000..e5136f2
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/4-scanline_timing.s
@@ -0,0 +1,62 @@
+; Demonstrates exact timing for first scanline
+
+; With LCD off, turning it on synchronizes to beginning
+; of first visible scanline
+
+.include "oam_bug.inc"
+
+main:
+ set_test 2,"INC DE just before first corruption"
+ call begin
+ ld de,$FE00
+ ld a,$81
+ ldh (LCDC-$FF00),a ; LCD on
+ delay 70224-3
+ inc de
+ call should_not_corrupt
+
+ set_test 3,"INC DE at first corruption"
+ call begin
+ ld de,$FE00
+ ld a,$81
+ ldh (LCDC-$FF00),a ; LCD on
+ delay 70224-2
+ inc de
+ call should_corrupt
+
+ set_test 4,"INC DE at last corruption"
+ call begin
+ ld de,$FE00
+ ld a,$81
+ ldh (LCDC-$FF00),a ; LCD on
+ delay 70224-2+18
+ inc de
+ call should_corrupt
+
+ set_test 5,"INC DE just after last corruption"
+ call begin
+ ld de,$FE00
+ ld a,$81
+ ldh (LCDC-$FF00),a ; LCD on
+ delay 70224-2+19
+ inc de
+ call should_not_corrupt
+
+ jp tests_passed
+
+begin:
+ call disable_lcd
+ call fill_oam
+ ret
+
+should_not_corrupt:
+ call disable_lcd
+ call cp_oam
+ jp nz,test_failed
+ ret
+
+should_corrupt:
+ call disable_lcd
+ call cp_oam
+ jp z,test_failed
+ ret
diff --git a/playing-coffee - Copy/roms/oam_bug/source/5-timing_bug.s b/playing-coffee - Copy/roms/oam_bug/source/5-timing_bug.s
new file mode 100644
index 0000000..cdaa10a
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/5-timing_bug.s
@@ -0,0 +1,43 @@
+; Verifies corruption at timing edges:
+; * Beginning of first scanline, and 18 cycles later
+; * Beginning of second scanline
+; * End of last scanline
+
+.include "oam_bug.inc"
+
+main:
+ set_test 2,"Should corrupt at beginning of first scanline"
+ call begin
+ call end
+
+ set_test 3,"Should corrupt at +18 of first scanline"
+ call begin
+ delay 18
+ call end
+
+ set_test 4,"Should corrupt at beginning of second scanline"
+ call begin
+ delay 114
+ call end
+
+ set_test 5,"Should corrupt at +18 of last scanline"
+ call begin
+ delay 114*143+18
+ call end
+
+ jp tests_passed
+
+begin:
+ call disable_lcd
+ call fill_oam
+ wreg LCDC,$81
+ delay 70224 - 15
+ ret
+
+end:
+ ld de,$FE00
+ inc de
+ call disable_lcd
+ call cp_oam
+ jp z,test_failed
+ ret
diff --git a/playing-coffee - Copy/roms/oam_bug/source/6-timing_no_bug.s b/playing-coffee - Copy/roms/oam_bug/source/6-timing_no_bug.s
new file mode 100644
index 0000000..12979cf
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/6-timing_no_bug.s
@@ -0,0 +1,57 @@
+; Verifies no corruption when done at "safe" times
+
+.include "oam_bug.inc"
+
+main:
+ call disable_lcd
+ call fill_oam
+
+ call test
+
+ call disable_lcd
+ call cp_oam
+
+ call print_oam
+ check_crc $7BB4F198
+ jp tests_passed
+
+test:
+ ld de,$FE00
+ wreg LCDC,$81
+
+ ; Have first INC DE at scanline 0 beginning
+ delay 70224 - 7
+
+ ; Run for several frames
+ ld h,10
+@loop:
+
+ ; Try to trigger just before and just after
+ ; window where corruption occurs, for every
+ ; scanline
+ ld b,144
+- inc de
+ delay 18
+ dec de
+ delay 114-22-4
+ dec b
+ jr nz,-
+
+ ; Try to trigger constantly during vblank
+ ld b,10
+ jr +
+-- delay 13
++ ld c,12
+- inc de
+ dec c
+ dec de
+ jr nz,-
+ dec b
+ jr nz,--
+
+ delay 4
+
+ dec h
+ jr nz,@loop
+
+ ret
diff --git a/playing-coffee - Copy/roms/oam_bug/source/7-timing_effect.s b/playing-coffee - Copy/roms/oam_bug/source/7-timing_effect.s
new file mode 100644
index 0000000..2bc307b
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/7-timing_effect.s
@@ -0,0 +1,38 @@
+; Verifies corruption for each timing
+
+.include "oam_bug.inc"
+
+main:
+ loop_n_times test,116
+ check_crc $7D792E7C
+ jp tests_passed
+
+test:
+ inc a
+ ld b,a
+ push bc
+ call disable_lcd
+ call fill_oam
+ call corrupt_oam
+ call disable_lcd
+ call cp_oam
+ pop bc
+
+ jr z,+
+ call print_b
+ call print_newline
+ call print_oam
+ call print_newline
+
++ ret
+
+corrupt_oam:
+ wreg LCDC,$81
+ ld a,b
+ call delay_a_20_cycles
+ delay 86
+
+ ld de,$FE00
+ inc de
+
+ ret
diff --git a/playing-coffee - Copy/roms/oam_bug/source/8-instr_effect.s b/playing-coffee - Copy/roms/oam_bug/source/8-instr_effect.s
new file mode 100644
index 0000000..3d50112
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/8-instr_effect.s
@@ -0,0 +1,63 @@
+; Verifies corruption pattern for each instruction
+
+.include "oam_bug.inc"
+
+main:
+ save_sp
+
+ set_test 2,"INC/DEC rp pattern is wrong"
+ call begin
+ ld de,$FE00
+ inc de
+ call end
+
+ ld de,$FE00
+ dec de
+ call end
+ check_crc $EF0C266A
+
+ set_test 3,"POP rp pattern is wrong"
+ call begin
+ ld sp,$FEF0
+ pop bc
+ restore_sp
+ call end
+ check_crc $8C62EE7D
+
+ set_test 4,"PUSH rp pattern is wrong"
+ call begin
+ ld sp,$FEF0
+ push bc
+ restore_sp
+ call end
+ check_crc $B3693CEE
+
+ set_test 5,"LD A,(HL+/-) pattern is wrong"
+ call begin
+ ld hl,$FEF0
+ ld a,(hl+)
+ call end
+
+ ld hl,$FEF0
+ ld a,(hl-)
+ call end
+ check_crc $06BE41A4
+
+ jp tests_passed
+
+begin:
+ call disable_lcd
+ call fill_oam
+ wreg LCDC,$81
+ delay 70224 - 4
+ ret
+
+end:
+ call disable_lcd
+ call cp_oam
+ jr z,+
+ call print_oam
+ call print_newline
++
+ call begin
+ ret
diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/build_gbs.s b/playing-coffee - Copy/roms/oam_bug/source/common/build_gbs.s
new file mode 100644
index 0000000..877754f
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/common/build_gbs.s
@@ -0,0 +1,139 @@
+; Build as GBS music file
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $2000 size $2000
+ slot 1 $C000 size $2000
+.endMe
+
+.romBankSize $2000
+.romBanks 2
+
+.define RST_OFFSET $70
+
+.ifndef GBS_TMA
+ .define GBS_TMA 0
+.endif
+
+.ifndef GBS_TAC
+ .define GBS_TAC 0
+.endif
+
+;;;; GBS music file header
+
+.ifndef CUSTOM_HEADER
+ .byte "GBS"
+ .byte 1,1,1 ; vers, song count, first song
+ .word load_addr, reset, gbs_play_, std_stack
+ .byte GBS_TMA,GBS_TAC ; timer
+.endif
+ .org $10
+ .ds $60,0
+load_addr:
+ .org RST_OFFSET+$70 ; space for RST vectors
+ .ds $148-RST_OFFSET-$70,0
+ .org $150 ; wla insists on generating GB header
+
+gbs_play_:
+ jp gbs_play ; GBS spec disallows having gbs_play in RAM
+
+;;;; Shell
+
+.include "shell.s"
+
+.define gbs_idle nv_ram
+.redefine nv_ram nv_ram+2
+
+init_runtime:
+ ; Identify as DMG hardware
+ ld a,$01
+ ld (gb_id),a
+
+ ; Save return address
+ pop hl
+ ld a,l
+ ld (gbs_idle),a
+ ld a,h
+ ld (gbs_idle+1),a
+
+ ; Delay 1/4 second to give time
+ ; for GBS player to interrupt with
+ ; play, if it's going to do so
+ delay_msec 250
+
+.ifndef CUSTOM_PLAY
+gbs_play:
+.endif
+ ; Get return address
+ ld a,(gbs_idle)
+ ld l,a
+ ld a,(gbs_idle+1)
+ ld h,a
+
+ ; If zero, then play interrupted init
+ ; call, or another play call, and we
+ ; can't run the program properly.
+ or l
+ jp z,internal_error
+
+ setw gbs_idle,0
+ jp hl
+
+
+; Reports A in binary as high and low tones, with
+; leading low tone for reference. Omits leading
+; zeroes.
+; Preserved: AF, BC, DE, HL
+play_byte:
+ push af
+ push hl
+
+ ; HL = (A << 1) | 1
+ scf
+ rla
+ ld l,a
+ ld h,0
+ rl h
+
+ ; Shift left until next-to-top bit is 1
+- add hl,hl
+ bit 6,h
+ jr z,-
+
+ ; Reset sound
+ delay_msec 400
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+- add hl,hl
+
+ ; Low or high pitch based on bit shifted out
+ ; of HL
+ ld a,0
+ jr nc,+
+ ld a,$FF
++ sta NR23
+
+ ; Play short tone
+ wreg NR21,$A0
+ wreg NR22,$F0
+ wreg NR24,$86
+ delay_msec 75
+ wreg NR22,0
+ wreg NR23,$F8
+ wreg NR24,$87
+ delay_msec 200
+
+ ; Loop until HL = $8000
+ ld a,h
+ xor $80
+ or l
+ jr nz,-
+
+ pop hl
+ pop af
+ ret
+
+.ends
diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/build_rom.s b/playing-coffee - Copy/roms/oam_bug/source/common/build_rom.s
new file mode 100644
index 0000000..c1595b7
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/common/build_rom.s
@@ -0,0 +1,70 @@
+; Build as GB ROM
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $0000 size $4000
+ slot 1 $C000 size $4000
+.endMe
+
+.romBankSize $4000
+.romBanks 2
+
+.cartridgeType 3 ; MBC1+RAM+battery
+.ramsize 02 ; 8K
+.computeChecksum
+.computeComplementCheck
+
+;;;; GB ROM header
+
+ ; Reserve space for RST handlers
+ .org $70
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .ds $90,0
+
+ ; GB header read by bootrom
+ .org $100
+ nop
+ jp reset
+
+ ; Nintendo logo required for proper boot
+ .byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
+ .byte $03,$73,$00,$83,$00,$0C,$00,$0D
+ .byte $00,$08,$11,$1F,$88,$89,$00,$0E
+ .byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
+ .byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
+ .byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
+
+ ; Internal name
+ .ifdef ROM_NAME
+ .byte ROM_NAME
+ .endif
+
+ ; CGB/DMG requirements
+ .org $143
+ .ifdef REQUIRE_CGB
+ .byte $C0
+ .else
+ .ifndef REQUIRE_DMG
+ .byte $80
+ .endif
+ .endif
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .org $150
+ .ds $2150-$150,0
+
+;;;; Shell
+
+.define NEED_CONSOLE 1
+.include "shell.s"
+
+init_runtime:
+ ret
+
+play_byte:
+ ret
+
+.ends
diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/console.bin b/playing-coffee - Copy/roms/oam_bug/source/common/console.bin
new file mode 100644
index 0000000..b02f2d3
Binary files /dev/null and b/playing-coffee - Copy/roms/oam_bug/source/common/console.bin differ
diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/console.s b/playing-coffee - Copy/roms/oam_bug/source/common/console.s
new file mode 100644
index 0000000..403fa5c
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/common/console.s
@@ -0,0 +1,285 @@
+; Scrolling text console
+
+; Console is 20x18 characters. Buffers lines, so
+; output doesn't appear until a newline or flush.
+; If scrolling isn't supported (i.e. SCY is treated
+; as if always zero), the first 18 lines will
+; still print properly). Also works properly if
+; LY isn't supported (always reads back as the same
+; value).
+
+.define console_width 20
+
+.define console_buf bss+0
+.define console_pos bss+console_width
+.define console_mode bss+console_width+1
+.define console_scroll bss+console_width+2
+.redefine bss bss+console_width+3
+
+
+; Waits for start of LCD blanking period
+; Preserved: BC, DE, HL
+console_wait_vbl:
+ push bc
+
+ ; Wait for start of vblank, with
+ ; timeout in case LY doesn't work
+ ; or LCD is disabled.
+ ld bc,-1250
+- inc bc
+ ld a,b
+ or c
+ jr z,@timeout
+ lda LY
+ cp 144
+ jr nz,-
+@timeout:
+
+ pop bc
+ ret
+
+
+; Initializes text console
+console_init:
+ call console_hide
+
+ ; CGB-specific inits
+ ld a,(gb_id)
+ and gb_id_cgb
+ call nz,@init_cgb
+
+ ; Clear nametable
+ ld a,' '
+ call @fill_nametable
+
+ ; Load tiles
+ ld hl,TILES+$200
+ ld c,0
+ call @load_tiles
+ ld hl,TILES+$A00
+ ld c,$FF
+ call @load_tiles
+
+ ; Init state
+ setb console_pos,console_width
+ setb console_mode,0
+ setb console_scroll,-8
+ call console_scroll_up_
+ jr console_show
+
+@fill_nametable:
+ ld hl,BGMAP0
+ ld b,4
+- ld (hl),a
+ inc l
+ jr nz,-
+ inc h
+ dec b
+ jr nz,-
+ ret
+
+@init_cgb:
+ ; Clear palette
+ wreg $FF68,$80
+ ld b,16
+- wreg $FF69,$FF
+ wreg $FF69,$7F
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ dec b
+ jr nz,-
+
+ ; Clear attributes
+ wreg VBK,1
+ ld a,0
+ call @fill_nametable
+
+ wreg VBK,0
+ ret
+
+@load_tiles:
+ ld de,ASCII
+ ld b,96
+-- push bc
+ ld b,8
+- ld a,(de)
+ inc de
+ xor c
+ ldi (hl),a
+ ldi (hl),a
+ dec b
+ jr nz,-
+ pop bc
+ dec b
+ jr nz,--
+ ret
+
+
+; Shows console display
+; Preserved: AF, BC, DE, HL
+console_show:
+ push af
+
+ ; Enable LCD
+ call console_wait_vbl
+ wreg LCDC,$91
+ wreg SCX,0
+ wreg BGP,$E4
+
+ jp console_apply_scroll_
+
+
+; Hides console display by turning LCD off
+; Preserved: AF, BC, DE, HL
+console_hide:
+ push af
+
+ ; LCD off
+ call console_wait_vbl
+ wreg LCDC,$11
+
+ pop af
+ ret
+
+
+; Changes to normal text mode
+; Preserved: BC, DE, HL
+console_normal:
+ xor a
+ jr console_set_mode
+
+; Changes to inverse text mode
+; Preserved: BC, DE, HL
+console_inverse:
+ ld a,$80
+
+; Changes console mode to A.
+; 0: Normal, $80: Inverse
+; Preserved: BC, DE, HL
+console_set_mode:
+ and $80
+ ld (console_mode),a
+ ret
+
+
+; Prints char A to console. Will not appear until
+; a newline or flush occurs.
+; Preserved: AF, BC, DE, HL
+console_print:
+ push af
+
+ cp 10
+ jr z,console_newline_
+
+ push hl
+ push af
+ ld hl,console_pos
+ ldi a,(hl)
+ cp BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/delay.s b/playing-coffee - Copy/roms/oam_bug/source/common/delay.s
new file mode 100644
index 0000000..cbcdcf3
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+.endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+.endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+.endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/gb.inc b/playing-coffee - Copy/roms/oam_bug/source/common/gb.inc
new file mode 100644
index 0000000..2d0118d
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/common/gb.inc
@@ -0,0 +1,81 @@
+; Game Boy hardware addresses
+
+; $0000-$3FFF Fixed ROM bank
+; $4000-$7FFF Switchable bank
+; $8000-$9FFF VRAM
+; $A000-$BFFF optional cartridge RAM
+; $C000-$DFFF RAM
+; $E000-$FDFF RAM mirror
+; $FE00-$FE9F OAM
+; $FEA0-$FEFF unused
+; $FF00-$FF7F registers
+; $FF80-$FFFE RAM
+; $FFFF register
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define BRAM $A000 ; cart memory
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+; Registers
+
+.define RAMEN $0000 ; cartridge WRAM control
+.define BANK $2000 ; bank select
+.define P1 $FF00 ; controller
+
+; Game link I/O
+.define SB $FF01 ; serial buffer
+.define SC $FF02 ; serial control
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/macros.inc b/playing-coffee - Copy/roms/oam_bug/source/common/macros.inc
new file mode 100644
index 0000000..9d7cf44
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/common/macros.inc
@@ -0,0 +1,91 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ; addr
+ ldh a,(\1 - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ; addr
+ ldh (\1 - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Writes byte to addr
+; Preserved: F, BC, DE, HL
+; Time: 6 cycles
+.macro setb ; addr, data
+ ld a,\2
+ ld (\1),a
+.endm
+
+; Writes word to addr
+; Preserved: F, BC, DE, HL
+; Time: 12 cycles
+.macro setw ; addr, data
+ ld a,<\2
+ ld (\1),a
+ ld a,>\2
+ ld (\1+1),a
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/numbers.s b/playing-coffee - Copy/roms/oam_bug/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/ppu.s b/playing-coffee - Copy/roms/oam_bug/source/common/ppu.s
new file mode 100644
index 0000000..e1d2a6a
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/common/ppu.s
@@ -0,0 +1,14 @@
+; Waits for LCD blanking period
+; Preserved: BC, DE, HL
+wait_vbl:
+ ; Return if off
+ lda LCDC
+ rla
+ ret nc
+
+ ; Wait for start of vblank
+- lda LY
+ cp 144
+ jr nz,-
+
+ ret
diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/printing.s b/playing-coffee - Copy/roms/oam_bug/source/common/printing.s
new file mode 100644
index 0000000..bb9389b
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/common/printing.s
@@ -0,0 +1,77 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+;print_char_nocrc (defined by user)
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/shell.s b/playing-coffee - Copy/roms/oam_bug/source/common/shell.s
new file mode 100644
index 0000000..3e588c7
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/common/shell.s
@@ -0,0 +1,261 @@
+; Common routines and runtime
+
+.define RUNTIME_INCLUDED 1
+
+; A few bytes of RAM that aren't cleared
+.define nv_ram_base $D800
+.define nv_ram nv_ram_base
+
+; Address of next normal variable
+.define bss_base nv_ram_base+$80
+.define bss bss_base
+
+; Address of next direct-page ($FFxx) variable
+.define dp_base $FF80
+.define dp dp_base
+
+; Top of stack
+.define std_stack $DFFF+1
+
+; Final exit result byte is written here
+.define final_result $A000
+
+; Text output is written here as zero-terminated string
+.define text_out_base $A004
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+.define gb_id nv_ram
+.redefine nv_ram nv_ram+1
+
+; Copies C*$100 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+- ld a,(hl+)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+
+.ifndef RST_OFFSET
+ .define RST_OFFSET 0
+.endif
+
+.ifndef CUSTOM_RESET
+ reset:
+ di
+
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ ld c,$14
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org 0
+ jp std_reset
+.endif
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
+
+ ; Catch user code running off end
+ jp internal_error
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld (gb_id),a
+
+ ; Clear memory except very top of stack
+ ld bc,std_stack-bss_base - 2
+ ld hl,bss_base
+ call clear_mem
+ ld bc,$FFFF-dp_base
+ ld hl,dp_base
+ call clear_mem
+
+ ; Init hardware
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ call init_runtime
+ call init_text_out
+ call console_init
+ call init_testing
+
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ call console_show
+ pop af
+ call play_byte
+ jp post_exit
+
++ push af
+ call print_newline
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+
+; Clears BC bytes starting at HL
+clear_mem:
+ ; If C>0, increment B
+ dec bc
+ inc c
+ inc b
+
+ ld a,0
+- ld (hl+),a
+ dec c
+ jr nz,-
+ dec b
+ jr nz,-
+ ret
+
+
+; Reports internal error and exits with code 255
+internal_error:
+ print_str "Internal error"
+ ld a,255
+ jp exit
+
+
+; build_devcart and build_multi customize this
+.ifndef CUSTOM_PRINT
+ .define text_out_addr bss+0
+ .redefine bss bss+2
+
+ ; Initializes text output to cartridge RAM
+ init_text_out:
+ ; Enable cartridge RAM and set text output pointer
+ setb RAMEN,$0A
+ setw text_out_addr,text_out_base
+ setb text_out_base-3,$DE
+ setb text_out_base-2,$B0
+ setb text_out_base-1,$61
+ setb text_out_base,0
+ setb final_result,$80
+ ret
+
+
+ ; Appends character to text output string
+ ; Preserved: AF, BC, DE, HL
+ write_text_out:
+ push hl
+ push af
+ ld a,(text_out_addr)
+ ld l,a
+ ld a,(text_out_addr+1)
+ ld h,a
+ inc hl
+ ld (hl),0
+ ld a,l
+ ld (text_out_addr),a
+ ld a,h
+ ld (text_out_addr+1),a
+ dec hl
+ pop af
+ ld (hl),a
+ pop hl
+ ret
+
+ print_char_nocrc:
+ call write_text_out
+ jp console_print
+.endif
+
+
+; only build_rom uses console
+.ifdef NEED_CONSOLE
+ .include "console.s"
+.else
+ console_init:
+ console_print:
+ console_flush:
+ console_normal:
+ console_inverse:
+ console_show:
+ console_set_mode:
+ ret
+.endif
+
+
+; build_devcart and build_multi need to customize this
+.ifndef CUSTOM_EXIT
+ post_exit:
+ ld (final_result),a
+ forever:
+ wreg NR52,0 ; sound off
+- jr -
+.endif
+
+
+.macro def_rst ARGS addr
+ .bank 0 slot 0
+ .org addr+RST_OFFSET
+.endm
diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/testing.s b/playing-coffee - Copy/roms/oam_bug/source/common/testing.s
new file mode 100644
index 0000000..5dd63ce
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/common/testing.s
@@ -0,0 +1,195 @@
+; Diagnostic and testing utilities
+
+.define test_code bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text.
+; Takes multiple strings due to wla's idiotic
+; default limit of 63 chars per string.
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2[,text3]]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .if NARGS > 3
+ .byte \4
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (test_code),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(test_code)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(test_code)
+ cp $FF ; if a = $FF then a = 1
+ jr nz,+
+ ld a,1
++ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+; Checks CRC, differing based on DMG or CGB build
+.macro check_crc_dmg_cgb ARGS dmg, cgb
+ .ifdef REQUIRE_DMG
+ check_crc dmg
+ .else
+ .ifdef REQUIRE_CGB
+ check_crc cgb
+ .else
+ .printt "CGB or DMG must be specified"
+ .fail
+ .endif
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee - Copy/roms/oam_bug/source/linkfile b/playing-coffee - Copy/roms/oam_bug/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee - Copy/roms/oam_bug/source/readme.txt b/playing-coffee - Copy/roms/oam_bug/source/readme.txt
new file mode 100644
index 0000000..a0269c1
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/readme.txt
@@ -0,0 +1,82 @@
+Game Boy Tests Source Code
+--------------------------
+
+Building with wla-dx
+--------------------
+To assemble a test ROM with wla-dx, use the following commands:
+
+ wla -o source_filename_here.s test.o
+ wlalink linkfile test.gb
+
+To assemble as a GBS music file:
+
+ wla -o source_filename_here.s test.o -DBUILD_GBS
+ wlalink linkfile test.gbs
+
+Note that some tests might only work when built as a ROM or GBS file,
+but not both.
+
+Some tests might include a ROM/GBS that has all the tests combined.
+Building such a multi-test is complex and the necessary files aren't
+included.
+
+
+Framework
+---------
+Each test is in a single source file, and makes use of several library
+source files from common/. This framework provides common services and
+reduces code to only that which performs the actual test. Virtually all
+tests include "shell.inc" at the beginning, which sets things up and
+includes all the appropriate library files.
+
+The reset handler does minimal GB hardware initialization, clears RAM,
+sets up the text console, then runs main. Main can exit by returning or
+jumping to "exit" with an error code in A. Exit reports the code then
+goes into an infinite loop. If the code is 0, it doesn't do anything,
+otherwise it reports the code. Code 1 is reported as "Failed", and the
+rest as "Error ".
+
+The default is to build a ROM. Defining BUILD_GBS will build as an GBS.
+The other build types aren't supported due to their complexity. I load
+the code into RAM at $C000 since my devcart requires it, and I don't
+want the normal ROM to differ in any way from what I've tested. This
+also allows easy self-modifying code.
+
+Several routines are available to print values and text to the console.
+Most update a running CRC-32 checksum which can be checked with
+check_crc, allowing ALL the output to be checked very easily. If the
+checksum doesn't match, it is printed, so you can run the code on a GB
+and paste the correct checksum into your code.
+
+
+Macros
+------
+Some macros are used to make common operations more convenient. The left
+is equivalent to the right:
+
+ Macro Equivalent
+ -------------------------------------
+ lda addr ldh a,(addr-$FF00)
+
+ sta addr ldh (addr-$FF00),a
+
+ wreg addr,data ld a,data
+ ldh (addr-$FF00),a
+
+ setb ld a,data
+ ld (addr),a
+
+ setw setb addr+0,data
+
+ for_loop routine,begin,end,step
+ calls routine with A set to successive values
+
+ loop_n_times routine,count
+ calls routine with A from 0 to count-1
+
+ print_str "str" prints string
+
+
+--
+Shay Green
diff --git a/playing-coffee - Copy/roms/oam_bug/source/shell.inc b/playing-coffee - Copy/roms/oam_bug/source/shell.inc
new file mode 100644
index 0000000..0d43faa
--- /dev/null
+++ b/playing-coffee - Copy/roms/oam_bug/source/shell.inc
@@ -0,0 +1,27 @@
+; Included at beginning of all programs
+; that use standard shell
+
+; Get include files from common/
+.incdir "common"
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.else
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
+
+.endif ; .ifdef BUILD_MULTI
diff --git a/playing-coffee - Copy/roms/readme.txt b/playing-coffee - Copy/roms/readme.txt
new file mode 100644
index 0000000..2c40e27
--- /dev/null
+++ b/playing-coffee - Copy/roms/readme.txt
@@ -0,0 +1,4 @@
+Blargg's Gameboy hardware test ROMs. Originally hosted at http://blargg.parodius.com/gb-tests/
+before parodious.com went down.
+New official location: http://blargg.8bitalley.com/parodius/gb-tests/
+
diff --git a/playing-coffee - Copy/roms/solitaire.gb b/playing-coffee - Copy/roms/solitaire.gb
new file mode 100644
index 0000000..8c5b8c6
Binary files /dev/null and b/playing-coffee - Copy/roms/solitaire.gb differ
diff --git a/playing-coffee - Copy/roms/supermarioland.gb b/playing-coffee - Copy/roms/supermarioland.gb
new file mode 100644
index 0000000..e5d7125
Binary files /dev/null and b/playing-coffee - Copy/roms/supermarioland.gb differ
diff --git a/playing-coffee - Copy/roms/tetris.gb b/playing-coffee - Copy/roms/tetris.gb
new file mode 100644
index 0000000..fbcef42
Binary files /dev/null and b/playing-coffee - Copy/roms/tetris.gb differ
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/application/Application.java b/playing-coffee - Copy/src/main/java/playingcoffee/application/Application.java
new file mode 100644
index 0000000..37b7ab4
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/application/Application.java
@@ -0,0 +1,111 @@
+package playingcoffee.application;
+
+import java.awt.Canvas;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.image.BufferStrategy;
+
+import javax.swing.JFrame;
+
+public class Application extends Canvas implements Runnable {
+
+ private static final long serialVersionUID = 1L;
+
+ private boolean running;
+ private Thread thread;
+
+ private GameBoy gameBoy;
+
+ public Application() {
+ setPreferredSize(new Dimension(320, 288));
+
+ gameBoy = new GameBoy();
+ gameBoy.start();
+ }
+
+ public void renderGameBoy() {
+ BufferStrategy bs = getBufferStrategy();
+ if (bs == null) {
+ createBufferStrategy(2);
+ return;
+ }
+
+ Graphics g = bs.getDrawGraphics();
+
+ g.setColor(Color.WHITE);
+ g.fillRect(0, 0, getWidth(), getHeight());
+
+ int lcdControl = (gameBoy.getMMU().read(0xFF40));
+
+ int tileDataOffset = ((lcdControl & 0x10) != 0) ? 0x8000 : 0x8800;
+
+ for (int tile = 0; tile < 512; tile++) {
+ int tileIndex = tile;
+ for (int y = 0; y < 8; y++) {
+ for (int n = 0; n < 8; n++) {
+ int value = (((gameBoy.getMMU().read(0x8000 + y * 2 + tileIndex * 16 + 1) >> (7 - n)) & 1) << 1) |
+ (((gameBoy.getMMU().read(0x8000 + y * 2 + tileIndex * 16) >> (7 - n)) & 1));
+
+ g.setColor(new Color(value * 64, value * 64, value * 64));
+ if (value != 0x0)
+ g.fillRect((n + (tile % 16) * 8), (y + (tile / 16) * 8), 1, 1);
+ }
+ }
+ }
+
+ if ((lcdControl & 0x80) != 0) {
+ for (int tileY = 0; tileY < 32; tileY++) {
+ for (int tileX = 0; tileX < 32; tileX++) {
+ int tileIndex = gameBoy.getMMU().read(0x9800 + (tileX + tileY * 32));
+ for (int y = 0; y < 8; y++) {
+ for (int n = 0; n < 8; n++) {
+ int value = (((gameBoy.getMMU().read(tileDataOffset + y * 2 + tileIndex * 16 + 1) >> (7 - n)) & 1) << 1) |
+ (((gameBoy.getMMU().read(tileDataOffset + y * 2 + tileIndex * 16) >> (7 - n)) & 1));
+
+ g.setColor(new Color(value * 64, value * 64, value * 64));
+ if (value != 0x0)
+ g.fillRect((n + (tileX) * 8 - gameBoy.getPPU().getRegisters().scrollX) * 2, (y + (tileY) * 8 - gameBoy.getPPU().getRegisters().scrollY) * 2, 2, 2);
+ }
+ }
+ }
+ }
+ }
+
+ g.dispose();
+
+ bs.show();
+ }
+
+ @Override
+ public void run() {
+ while (running) {
+ renderGameBoy();
+ }
+
+ gameBoy.stop();
+ }
+
+ public void start() {
+ if (running) return;
+
+ running = true;
+
+ thread = new Thread(this);
+ thread.start();
+ }
+
+ public static void main(String[] args) {
+ Application application = new Application();
+
+ JFrame frame = new JFrame("playing-coffee");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.setResizable(false);
+ frame.add(application);
+ frame.pack();
+ frame.setLocationRelativeTo(null);
+ frame.setVisible(true);
+
+ application.start();
+ }
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/application/GameBoy.java b/playing-coffee - Copy/src/main/java/playingcoffee/application/GameBoy.java
new file mode 100644
index 0000000..25480f7
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/application/GameBoy.java
@@ -0,0 +1,85 @@
+package playingcoffee.application;
+
+import playingcoffee.core.Cartridge;
+import playingcoffee.core.InterruptManager;
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.CPU;
+import playingcoffee.log.Log;
+import playingcoffee.ppu.PPU;
+
+public class GameBoy implements Runnable {
+
+ private Thread thread;
+ private boolean running = false;
+
+ private InterruptManager interruptManager;
+ private PPU ppu;
+ private MMU mmu;
+ private CPU cpu;
+
+ public GameBoy() {
+ interruptManager = new InterruptManager();
+ mmu = new MMU();
+
+ ppu = new PPU(mmu, interruptManager);
+ cpu = new CPU(mmu, interruptManager);
+
+ mmu.connectMemorySpace(interruptManager);
+ mmu.connectMemorySpace(new Cartridge("roms/kwirk.gb"));
+ }
+
+ public void start() {
+ if (running)
+ return;
+
+ running = true;
+ thread = new Thread(this);
+ thread.start();
+ }
+
+ public void stop() {
+ if (!running)
+ return;
+
+ running = false;
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void run() {
+ Log.init();
+
+ while (running) {
+ for (int i = 0; i < 8192; i++) {
+ ppu.clock();
+ cpu.clock();
+ interruptManager.clock();
+ }
+
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ Log.close();
+ }
+
+ public PPU getPPU() {
+ return ppu;
+ }
+
+ public MMU getMMU() {
+ return mmu;
+ }
+
+ public CPU getCPU() {
+ return cpu;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/Cartridge.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/Cartridge.java
new file mode 100644
index 0000000..21c9dea
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/Cartridge.java
@@ -0,0 +1,44 @@
+package playingcoffee.core;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import playingcoffee.log.Log;
+
+public class Cartridge implements MemorySpace {
+
+ private int[] rom;
+
+ public Cartridge(String rom) {
+ byte[] bin = null;
+
+ try {
+ bin = Files.readAllBytes(Paths.get(rom));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ this.rom = new int[bin.length];
+
+ for (int i = 0; i < bin.length; i++) {
+ this.rom[i] = Byte.toUnsignedInt(bin[i]);
+ }
+ }
+
+ @Override
+ public int read(int address) {
+ return rom[address];
+ }
+
+ @Override
+ public void write(int value, int address) {
+ Log.warn("Attempting to write to ROM at address: 0x%4x.", address);
+ }
+
+ @Override
+ public boolean inMemorySpace(int address) {
+ return address >= 0x0000 && address <= 0x7FFF;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptListener.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptListener.java
new file mode 100644
index 0000000..088c107
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptListener.java
@@ -0,0 +1,7 @@
+package playingcoffee.core;
+
+public interface InterruptListener {
+
+ public void interruptOccured(int type);
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptManager.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptManager.java
new file mode 100644
index 0000000..5bd5d61
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptManager.java
@@ -0,0 +1,76 @@
+package playingcoffee.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class InterruptManager implements MemorySpace {
+
+ private boolean enabled;
+
+ private int interruptEnable;
+ private int interruptFlag;
+
+ private List listeners;
+
+ public static final int VBLANK = 1 << 0;
+ public static final int LCD_STAT = 1 << 1;
+ public static final int TIMER = 1 << 2;
+ public static final int SERIAL = 1 << 3;
+ public static final int JOYPAD = 1 << 4;
+
+ public InterruptManager() {
+ listeners = new ArrayList();
+ }
+
+ public void addListener(InterruptListener listener) {
+ listeners.add(listener);
+ }
+
+ public void enable() { enabled = true; }
+ public void disable() { enabled = false; }
+
+ public boolean isEnabled() { return enabled; }
+
+ public void requestInterrupt(int type) {
+ interruptFlag |= type;
+ }
+
+ public void clock() {
+ if (enabled) {
+ int toOccur = interruptFlag & interruptEnable;
+
+ //System.out.println(toOccur);
+
+ if (toOccur != 0) {
+ for (int i = 0; i < 8; i++) {
+ if ((toOccur & (1 << i)) != 0) {
+ for (InterruptListener listener : listeners) {
+ listener.interruptOccured(1 << i);
+ }
+ interruptFlag &= ~(1 << i);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public int read(int address) {
+ if (address == 0xFFFF) return interruptEnable;
+ if (address == 0xFF0F) return interruptFlag;
+
+ return 0;
+ }
+
+ @Override
+ public void write(int value, int address) {
+ if (address == 0xFFFF) interruptEnable = value;
+ if (address == 0xFF0F) interruptFlag = value;
+ }
+
+ @Override
+ public boolean inMemorySpace(int address) {
+ return address == 0xFFFF || address == 0xFF0F;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/MMU.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/MMU.java
new file mode 100644
index 0000000..fecec2e
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/MMU.java
@@ -0,0 +1,93 @@
+package playingcoffee.core;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MMU {
+
+ private int[] memory;
+ private int[] bootRom;
+
+ private List memorySpaces;
+
+ public MMU() {
+ memory = new int[0x10000];
+
+ memorySpaces = new ArrayList();
+
+ loadBootROM();
+ }
+
+ public void connectMemorySpace(MemorySpace memorySpace) {
+ memorySpaces.add(memorySpace);
+ }
+
+ // TODO: Move the ROM into a separate file.
+
+ public void loadROM(String rom) {
+ byte[] bin = null;
+
+ try {
+ bin = Files.readAllBytes(Paths.get(rom));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ for (int i = 0; i < bin.length; i++) {
+ memory[i] = Byte.toUnsignedInt(bin[i]);
+ }
+ }
+
+ // TODO: Move the boot ROM to a separate file.
+ private void loadBootROM() {
+ bootRom = new int[0x100];
+
+ byte[] bin = null;
+
+ try {
+ bin = Files.readAllBytes(Paths.get("dmg_boot.bin"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ for (int i = 0; i < 0x100; i++) {
+ bootRom[i] = Byte.toUnsignedInt(bin[i]);
+ }
+ }
+
+ public int read(int address) {
+ if (address == 0xFF00) return 0xF; // TODO: Remove
+
+ if (address >= 0x00 && address <= 0xFF && read(0xFF50) == 0)
+ return bootRom[address];
+
+ for (MemorySpace memorySpace : memorySpaces) {
+ if (memorySpace.inMemorySpace(address)) {
+ return memorySpace.read(address);
+ }
+ }
+
+ return memory[address];
+ }
+
+ public void write(int value, int address) {
+ if (address == 0xFF00) return; // TODO: Remove
+
+ if (address >= 0x00 && address <= 0xFF && read(0xFF50) == 0)
+ bootRom[address] = value & 0xFF;
+
+ for (MemorySpace memorySpace : memorySpaces) {
+ if (memorySpace.inMemorySpace(address)) {
+ memorySpace.write(value & 0xFF, address);
+ }
+ }
+ memory[address] = value & 0xFF;
+ }
+
+ public int[] getMemory() {
+ return memory;
+ }
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/MemorySpace.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/MemorySpace.java
new file mode 100644
index 0000000..c2ce195
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/MemorySpace.java
@@ -0,0 +1,9 @@
+package playingcoffee.core;
+
+public interface MemorySpace {
+
+ public int read(int address);
+ public void write(int value, int address);
+ public boolean inMemorySpace(int address);
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/CPU.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/CPU.java
new file mode 100644
index 0000000..2c25bf1
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/CPU.java
@@ -0,0 +1,343 @@
+package playingcoffee.core.cpu;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import playingcoffee.core.InterruptListener;
+import playingcoffee.core.InterruptManager;
+import playingcoffee.core.MMU;
+import playingcoffee.core.opcode.ALU16Opcode;
+import playingcoffee.core.opcode.ALU16Opcode.ALU16Type;
+import playingcoffee.core.opcode.ALUOpcode;
+import playingcoffee.core.opcode.ALUOpcode.ALUType;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.CallOpcode;
+import playingcoffee.core.opcode.ComplementOpcode;
+import playingcoffee.core.opcode.FlipCarryOpcode;
+import playingcoffee.core.opcode.InterruptOpcode;
+import playingcoffee.core.opcode.JumpOpcode;
+import playingcoffee.core.opcode.JumpRelativeOpcode;
+import playingcoffee.core.opcode.LoadOpcode;
+import playingcoffee.core.opcode.Opcode;
+import playingcoffee.core.opcode.PopOpcode;
+import playingcoffee.core.opcode.PushOpcode;
+import playingcoffee.core.opcode.RestartOpcode;
+import playingcoffee.core.opcode.ReturnOpcode;
+import playingcoffee.core.opcode.RotateAOpcode;
+import playingcoffee.core.opcode.SetCarryOpcode;
+import playingcoffee.core.opcode.prefixed.BitOpcode;
+import playingcoffee.core.opcode.prefixed.ResetBitOpcode;
+import playingcoffee.core.opcode.prefixed.RotateOpcode;
+import playingcoffee.core.opcode.prefixed.SetBitOpcode;
+import playingcoffee.core.opcode.prefixed.ShiftOpcode;
+import playingcoffee.core.opcode.prefixed.ShiftOpcode.ShiftType;
+import playingcoffee.core.opcode.prefixed.SwapOpcode;
+import playingcoffee.log.Log;
+
+public class CPU implements InterruptListener {
+
+ private final MMU mmu;
+ private final InterruptManager interruptManager;
+
+ private Registers registers;
+
+ private int cycles;
+
+ Opcode[] opcodes;
+ Opcode[] prefixedOpcodes;
+
+ public CPU(final MMU mmu, final InterruptManager interruptManager) {
+ this.mmu = mmu;
+ this.interruptManager = interruptManager;
+
+ registers = new Registers();
+
+ opcodes = new Opcode[0x100];
+ prefixedOpcodes = new Opcode[0x100];
+
+ loadOpcodes();
+ loadPrefixedOpcodes();
+
+ interruptManager.addListener(this);
+ }
+
+ private void loadOpcodes() {
+ // NOP
+ opcodes[0x00] = new Opcode() {
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "NOP";
+ }
+ };
+
+ // Load Opcodes
+ for (Entry val : indexedList(0x01, 0x10, Argument.BC, Argument.DE, Argument.HL, Argument.SP)) {
+ opcodes[val.getKey()] = new LoadOpcode(Argument.D16, val.getValue());
+ }
+
+ for (Entry val : indexedList(0x02, 0x10, Argument._BC, Argument._DE, Argument._HL_INC, Argument._HL_DEC)) {
+ opcodes[val.getKey()] = new LoadOpcode(Argument.A, val.getValue());
+ }
+ for (Entry val : indexedList(0x06, 0x10, Argument.B, Argument.D, Argument.H, Argument._HL)) {
+ opcodes[val.getKey()] = new LoadOpcode(Argument.D8, val.getValue());
+ }
+ for (Entry val : indexedList(0x0A, 0x10, Argument._BC, Argument._DE, Argument._HL_INC, Argument._HL_DEC)) {
+ opcodes[val.getKey()] = new LoadOpcode(val.getValue(), Argument.A);
+ }
+ for (Entry val : indexedList(0x0E, 0x10, Argument.C, Argument.E, Argument.L, Argument.A)) {
+ opcodes[val.getKey()] = new LoadOpcode(Argument.D8, val.getValue());
+ }
+
+ for (Entry val : indexedList(0x40, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ for (Entry row : indexedList(val.getKey(), 0x10, Argument.B, Argument.D, Argument.H, Argument._HL)) {
+ opcodes[row.getKey()] = new LoadOpcode(val.getValue(), row.getValue());
+ }
+ }
+ for (Entry val : indexedList(0x48, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ for (Entry row : indexedList(val.getKey(), 0x10, Argument.C, Argument.E, Argument.L, Argument.A)) {
+ opcodes[row.getKey()] = new LoadOpcode(val.getValue(), row.getValue());
+ }
+ }
+
+ opcodes[0x08] = new LoadOpcode(Argument.SP, Argument._D16_SHORT);
+
+ opcodes[0xE0] = new LoadOpcode(Argument.A, Argument._D8);
+ opcodes[0xF0] = new LoadOpcode(Argument._D8, Argument.A);
+
+ opcodes[0xE2] = new LoadOpcode(Argument.A, Argument._C);
+ opcodes[0xF2] = new LoadOpcode(Argument._C, Argument.A);
+
+ opcodes[0xF9] = new LoadOpcode(Argument.HL, Argument.SP);
+
+ opcodes[0xEA] = new LoadOpcode(Argument.A, Argument._D16);
+ opcodes[0xFA] = new LoadOpcode(Argument._D16, Argument.A);
+
+ // POP and PUSH
+ for (Entry val : indexedList(0xC1, 0x10, Argument.BC, Argument.DE, Argument.HL, Argument.AF)) {
+ opcodes[val.getKey()] = new PopOpcode(val.getValue());
+ }
+ for (Entry val : indexedList(0xC5, 0x10, Argument.BC, Argument.DE, Argument.HL, Argument.AF)) {
+ opcodes[val.getKey()] = new PushOpcode(val.getValue());
+ }
+
+ // TODO: Override 0x76 with HALT
+
+ // Jumps, Calls and Returns
+ opcodes[0x20] = new JumpRelativeOpcode(Flags.ZERO, Argument.I8, true);
+ opcodes[0x30] = new JumpRelativeOpcode(Flags.CARRY, Argument.I8, true);
+
+ opcodes[0x18] = new JumpRelativeOpcode(0, Argument.I8, false);
+ opcodes[0x28] = new JumpRelativeOpcode(Flags.ZERO, Argument.I8, false);
+ opcodes[0x38] = new JumpRelativeOpcode(Flags.CARRY, Argument.I8, false);
+
+ opcodes[0xC3] = new JumpOpcode(0, Argument.D16, false);
+ opcodes[0xC2] = new JumpOpcode(Flags.ZERO, Argument.D16, true);
+ opcodes[0xD2] = new JumpOpcode(Flags.CARRY, Argument.D16, true);
+
+ opcodes[0xE9] = new JumpOpcode(0, Argument.HL, false);
+
+ opcodes[0xCA] = new JumpOpcode(Flags.ZERO, Argument.D16, false);
+ opcodes[0xDA] = new JumpOpcode(Flags.CARRY, Argument.D16, false);
+
+ opcodes[0xC4] = new CallOpcode(Flags.ZERO, Argument.D16, true);
+ opcodes[0xD4] = new CallOpcode(Flags.CARRY, Argument.D16, true);
+
+ opcodes[0xCC] = new CallOpcode(Flags.ZERO, Argument.D16, false);
+ opcodes[0xDC] = new CallOpcode(Flags.CARRY, Argument.D16, false);
+
+ opcodes[0xCD] = new CallOpcode(0, Argument.D16, false);
+
+ opcodes[0xC0] = new ReturnOpcode(Flags.ZERO, true, false, interruptManager);
+ opcodes[0xD0] = new ReturnOpcode(Flags.CARRY, true, false, interruptManager);
+
+ opcodes[0xC8] = new ReturnOpcode(Flags.ZERO, false, false, interruptManager);
+ opcodes[0xD8] = new ReturnOpcode(Flags.CARRY, false, false, interruptManager);
+
+ opcodes[0xC9] = new ReturnOpcode(0, false, false, interruptManager);
+ opcodes[0xD9] = new ReturnOpcode(0, false, true, interruptManager);
+
+ // Bit Operations
+
+ opcodes[0x07] = new RotateAOpcode(true, RotateAOpcode.LEFT);
+ opcodes[0x17] = new RotateAOpcode(false, RotateAOpcode.LEFT);
+ opcodes[0x0F] = new RotateAOpcode(true, RotateAOpcode.RIGHT);
+ opcodes[0x1F] = new RotateAOpcode(false, RotateAOpcode.RIGHT);
+
+ // 16-bit ALU Opcodes
+ for (Entry val : indexedList(0x03, 0x10, Argument.BC, Argument.DE,Argument.HL, Argument.SP)) {
+ opcodes[val.getKey()] = new ALU16Opcode(ALU16Type.INC, val.getValue());
+ opcodes[val.getKey() + 8] = new ALU16Opcode(ALU16Type.DEC, val.getValue());
+ }
+ for (Entry val : indexedList(0x09, 0x10, Argument.BC, Argument.DE,Argument.HL, Argument.SP)) {
+ opcodes[val.getKey()] = new ALU16Opcode(ALU16Type.ADD, val.getValue());
+ }
+
+ // ALU Opcodes
+ for (Entry val : indexedList(0x04, 8, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ opcodes[val.getKey()] = new ALUOpcode(ALUType.INC, val.getValue());
+ opcodes[val.getKey() + 1] = new ALUOpcode(ALUType.DEC, val.getValue());
+ }
+
+ for (Entry val : indexedList(0x80, 8, ALUType.ADD, ALUType.ADC, ALUType.SUB, ALUType.SBC, ALUType.AND, ALUType.XOR, ALUType.OR, ALUType.CP)) {
+ for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ opcodes[row.getKey()] = new ALUOpcode(val.getValue(), row.getValue());
+ }
+ }
+ for (Entry val : indexedList(0xC6, 8, ALUType.ADD, ALUType.ADC, ALUType.SUB, ALUType.SBC, ALUType.AND, ALUType.XOR, ALUType.OR, ALUType.CP)) {
+ opcodes[val.getKey()] = new ALUOpcode(val.getValue(), Argument.D8);
+ }
+
+ // Restarts
+ for (Entry val : indexedList(0xC7, 8, 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38)) {
+ opcodes[val.getKey()] = new RestartOpcode(val.getValue());
+ }
+
+ // Interrupts
+ opcodes[0xF3] = new InterruptOpcode(false, interruptManager);
+ opcodes[0xFB] = new InterruptOpcode(true, interruptManager);
+
+ // Misc
+ opcodes[0x2F] = new ComplementOpcode();
+ opcodes[0x37] = new SetCarryOpcode();
+ opcodes[0x3F] = new FlipCarryOpcode();
+ }
+
+ public void loadPrefixedOpcodes() {
+ for (Entry val : indexedList(0x00, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[val.getKey() + 0x00] = new RotateOpcode(true, val.getValue(), RotateOpcode.LEFT);
+ prefixedOpcodes[val.getKey() + 0x08] = new RotateOpcode(true, val.getValue(), RotateOpcode.RIGHT);
+ prefixedOpcodes[val.getKey() + 0x10] = new RotateOpcode(false, val.getValue(), RotateOpcode.LEFT);
+ prefixedOpcodes[val.getKey() + 0x18] = new RotateOpcode(false, val.getValue(), RotateOpcode.RIGHT);
+ }
+
+ for (Entry val : indexedList(0x40, 8, 0, 1, 2, 3, 4, 5, 6, 7)) {
+ for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[row.getKey()] = new BitOpcode(val.getValue(), row.getValue());
+ }
+ }
+
+ for (Entry val : indexedList(0x30, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[val.getKey()] = new SwapOpcode(val.getValue());
+ }
+
+ // Reset Bit
+ for (Entry val : indexedList(0x80, 8, 0, 1, 2, 3, 4, 5, 6, 7)) {
+ for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[row.getKey()] = new ResetBitOpcode(val.getValue(), row.getValue());
+ }
+ }
+
+ // Set Bit
+ for (Entry val : indexedList(0xC0, 8, 0, 1, 2, 3, 4, 5, 6, 7)) {
+ for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[row.getKey()] = new SetBitOpcode(val.getValue(), row.getValue());
+ }
+ }
+
+ for (Entry val : indexedList(0x20, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[val.getKey()] = new ShiftOpcode(ShiftType.SLA, val.getValue());
+ }
+ for (Entry val : indexedList(0x28, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[val.getKey()] = new ShiftOpcode(ShiftType.SRA, val.getValue());
+ }
+ for (Entry val : indexedList(0x38, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[val.getKey()] = new ShiftOpcode(ShiftType.SRL, val.getValue());
+ }
+ }
+
+ @SafeVarargs
+ private static Iterable> indexedList(int start, int step, T... values) {
+ Map map = new LinkedHashMap<>();
+ int i = start;
+ for (T e : values) {
+ map.put(i, e);
+ i += step;
+ }
+ return map.entrySet();
+ }
+
+ public void clock() {
+ if (cycles == 0) {
+ //Log.info("PC: 0x%4x", registers.getPC());
+
+ if (registers.getPC() == 0x2EFC) {
+ Log.info("Breakpoint.");
+ }
+
+ int opcodeValue = mmu.read(registers.getPC());
+ registers.incPC();
+
+ if (opcodeValue == 0xCB) {
+ int prefixedValue = mmu.read(registers.getPC());
+ registers.incPC();
+
+ runPrefixedOpcode(prefixedValue);
+
+ } else {
+ runOpcode(opcodeValue);
+ }
+ }
+
+ cycles--;
+ }
+
+ private void runOpcode(int opcodeValue) {
+ Opcode opcode = opcodes[opcodeValue];
+
+ if (opcode == null) {
+ Log.error("Unimplemented opcode 0x%2x at 0x%4x!", opcodeValue, registers.getPC());
+
+ Log.close();
+
+ throw new IllegalStateException();
+
+ //System.exit(0);
+ }
+
+ //Log.info("Executing opcode: 0x%2x (%s)", opcodeValue, opcode.toString());
+ cycles += opcode.run(registers, mmu) + 4; // Adding 4 because we fetch the instruction.
+ }
+
+ private void runPrefixedOpcode(int opcodeValue) {
+ Opcode opcode = prefixedOpcodes[opcodeValue];
+
+ if (opcode == null) {
+ Log.error("Unimplemented prefixed opcode 0x%2x!", opcodeValue);
+
+ Log.close();
+
+ throw new IllegalStateException();
+
+ //System.exit(0);
+ }
+
+ //Log.info("Executing prefixed opcode: 0x%2x (%s)", opcodeValue, opcode.toString());
+ cycles += opcode.run(registers, mmu) + 8; // Adding 8 because we fetch the 0xCB prefix and the instruction.
+ }
+
+ @Override
+ public void interruptOccured(int types) {
+ /*if ((types & InterruptManager.VBLANK) != 0) {
+ interruptManager.disable();
+
+ registers.decSP();
+ mmu.write(registers.getPC() >> 8, registers.getSP());
+ registers.decSP();
+ mmu.write(registers.getPC(), registers.getSP());
+
+ Log.info("Pushing 0x%4x to the stack.", registers.getPC());
+
+ registers.setPC(0x40);
+
+ cycles += 12;
+ }*/
+ }
+
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Flags.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Flags.java
new file mode 100644
index 0000000..c4e121a
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Flags.java
@@ -0,0 +1,29 @@
+package playingcoffee.core.cpu;
+
+public class Flags {
+
+ private int f; // Technically the "correct" notation
+
+ public static final int ZERO = 1 << 7;
+ public static final int NEGATIVE = 1 << 6;
+ public static final int HALF_CARRY = 1 << 5;
+ public static final int CARRY = 1 << 4;
+
+ public void set(int value) {
+ f = value & 0xFF;
+ }
+
+ public int get() {
+ return f;
+ }
+
+ public void set(int flag, boolean value) {
+ if (value) f |= flag;
+ else f &= ~flag;
+ }
+
+ public boolean get(int flag) {
+ return (f & flag) != 0;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Registers.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Registers.java
new file mode 100644
index 0000000..82c97f6
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Registers.java
@@ -0,0 +1,57 @@
+package playingcoffee.core.cpu;
+
+public class Registers {
+
+ private int a, b, c, d, e, h, l;
+
+ private int pc, sp;
+
+ private Flags flags;
+
+ public Registers() {
+ flags = new Flags();
+ }
+
+ public int getA() { return a; }
+ public int getB() { return b; }
+ public int getC() { return c; }
+ public int getD() { return d; }
+ public int getE() { return e; }
+ public int getH() { return h; }
+ public int getL() { return l; }
+
+ public void setA(int value) { a = value & 0xFF; }
+ public void setB(int value) { b = value & 0xFF; }
+ public void setC(int value) { c = value & 0xFF; }
+ public void setD(int value) { d = value & 0xFF; }
+ public void setE(int value) { e = value & 0xFF; }
+ public void setH(int value) { h = value & 0xFF; }
+ public void setL(int value) { l = value & 0xFF; }
+
+ public Flags getFlags() { return flags; }
+
+ public int getAF() { return (a << 8) | flags.get(); }
+ public int getBC() { return (b << 8) | c; }
+ public int getDE() { return (d << 8) | e; }
+ public int getHL() { return (h << 8) | l; }
+
+ public int getPC() { return pc; }
+ public int getSP() { return sp; }
+
+ public void setAF(int value) { flags.set(value >> 8); setA(value); }
+ public void setBC(int value) { setB(value >> 8); setC(value); }
+ public void setDE(int value) { setD(value >> 8); setE(value); }
+ public void setHL(int value) { setH(value >> 8); setL(value); }
+
+ public void setPC(int value) { pc = value & 0xFFFF; }
+ public void setSP(int value) { sp = value & 0xFFFF; }
+
+ public void addPC(int value) { setPC(getPC() + value); };
+ public void addSP(int value) { setSP(getSP() + value); };
+
+ public void incPC() { addPC(1); }
+ public void incSP() { addSP(1); }
+
+ public void decPC() { addPC(-1); }
+ public void decSP() { addSP(-1); }
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java
new file mode 100644
index 0000000..b4fef41
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java
@@ -0,0 +1,57 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.log.Log;
+
+public class ALU16Opcode implements Opcode {
+
+ private final ALU16Type type;
+ private final Argument register;
+
+ public ALU16Opcode(ALU16Type type, Argument register) {
+ this.type = type;
+ this.register = register;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int result = 0;
+
+ switch (type) {
+ case INC:
+ register.write(register.read(registers, mmu) + 1, registers, mmu);
+ break;
+ case DEC:
+ register.write(register.read(registers, mmu) - 1, registers, mmu);
+ break;
+ case ADD:
+ int value = register.read(registers, mmu);
+ result = registers.getHL() + value;
+
+ registers.getFlags().set(Flags.NEGATIVE, false);
+ registers.getFlags().set(Flags.HALF_CARRY, ((value & 0xF) + (registers.getHL() & 0xF) > 0xF));
+ registers.getFlags().set(Flags.CARRY, (result & 0xFFFF0000) != 0);
+
+ register.write(result, registers, mmu);
+
+ break;
+ default:
+ Log.error("wtf!? how did we get here?!?!?");
+ break;
+ }
+
+ return register.getCycles() + 4;
+ }
+
+ public enum ALU16Type {
+ INC, DEC, ADD
+ }
+
+ @Override
+ public String toString() {
+ return type.name() + " " + register.getName();
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALUOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALUOpcode.java
new file mode 100644
index 0000000..8cfa92a
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALUOpcode.java
@@ -0,0 +1,134 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.log.Log;
+
+public class ALUOpcode implements Opcode {
+
+ private final ALUType type;
+ private final Argument register;
+
+ public ALUOpcode(ALUType type, Argument register) {
+ this.type = type;
+ this.register = register;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int result = 0;
+ int registerValue = register.read(registers, mmu);
+
+ switch (type) {
+ case ADD:
+ result = registers.getA() + registerValue;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE, false);
+ registers.getFlags().set(Flags.HALF_CARRY, (registers.getA() & 0xF) + (registerValue & 0xF) > 0xF);
+ registers.getFlags().set(Flags.CARRY, (result & 0xFF00) > 0);
+
+ break;
+ case ADC:
+ int value = registerValue + (registers.getFlags().get(Flags.CARRY) ? 1 : 0);
+
+ result = registers.getA() + value;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE, false);
+ registers.getFlags().set(Flags.HALF_CARRY, (registers.getA() & 0xF) + (value & 0xF) > 0xF);
+ registers.getFlags().set(Flags.CARRY, (result & 0xFF00) > 0);
+
+ break;
+ case AND:
+ result = registers.getA() & registerValue;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE, false);
+ registers.getFlags().set(Flags.HALF_CARRY, true);
+ registers.getFlags().set(Flags.CARRY, false);
+
+ break;
+ case CP:
+ result = registers.getA();
+
+ registers.getFlags().set(Flags.ZERO, registers.getA() == registerValue);
+ registers.getFlags().set(Flags.NEGATIVE, true);
+ registers.getFlags().set(Flags.HALF_CARRY, (registerValue & 0xF) > (registers.getA() & 0xF));
+ registers.getFlags().set(Flags.CARRY, registerValue > registers.getA());
+
+ break;
+ case OR:
+ result = registers.getA() | registerValue;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY | Flags.CARRY, false);
+
+ break;
+ case SBC:
+ value = registerValue + (registers.getFlags().get(Flags.CARRY) ? 1 : 0);
+
+ registers.getFlags().set(Flags.ZERO, value == registers.getA());
+ registers.getFlags().set(Flags.NEGATIVE, true);
+ registers.getFlags().set(Flags.HALF_CARRY, (value & 0xF) > (registers.getA() & 0xF));
+ registers.getFlags().set(Flags.CARRY, value > registers.getA());
+
+ result = registers.getA() - value;
+
+ break;
+ case SUB:
+ registers.getFlags().set(Flags.ZERO, registerValue == registers.getA());
+ registers.getFlags().set(Flags.NEGATIVE, true);
+ registers.getFlags().set(Flags.HALF_CARRY, registerValue > (registers.getA() & 0xF));
+ registers.getFlags().set(Flags.CARRY, registerValue > registers.getA());
+
+ result = registers.getA() - registerValue;
+
+ break;
+ case XOR:
+ result = registers.getA() ^ registerValue;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY | Flags.CARRY, false);
+
+ break;
+ case INC:
+ result = registerValue + 1;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE, false);
+ registers.getFlags().set(Flags.HALF_CARRY, (result & 0x10) != 0);
+
+ register.write(result, registers, mmu);
+ break;
+ case DEC:
+ result = registerValue - 1;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE, true);
+ registers.getFlags().set(Flags.HALF_CARRY, (registerValue & 0x0F) == 0x0F);
+
+ register.write(result, registers, mmu);
+ break;
+ default:
+ Log.error("wtf!? how did we get here?!?!?");
+ break;
+ }
+
+ if (type != ALUType.INC && type != ALUType.DEC)
+ registers.setA(result);
+
+ return 0;
+ }
+
+ public enum ALUType {
+ ADD, ADC, SUB, SBC, AND, XOR, OR, CP, INC, DEC
+ }
+
+ @Override
+ public String toString() {
+ return type.name() + " A, " + register.getName();
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Argument.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Argument.java
new file mode 100644
index 0000000..6250bad
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Argument.java
@@ -0,0 +1,348 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.log.Log;
+
+public enum Argument {
+
+ // Note: The '_' prefix, specifies the value located at the memory address the register is pointing to.
+ // Example: _HL <=> memory[HL]
+
+ A {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getA();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setA(value);
+ }
+ }, B {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getB();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setB(value);
+ }
+ }, C {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getC();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setC(value);
+ }
+ }, D {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getD();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setD(value);
+ }
+ }, E {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getE();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setE(value);
+ }
+ }, H {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getH();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setH(value);
+ }
+ }, L {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getL();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setL(value);
+ }
+ }, AF {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getAF();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setAF(value);
+ }
+ }, BC {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getBC();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setBC(value);
+ }
+ }, DE {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getDE();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setDE(value);
+ }
+ }, HL {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getHL();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setHL(value);
+ }
+ }, SP {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getSP();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setSP(value);
+ }
+ }, _BC("(BC)", 4) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return mmu.read(registers.getBC());
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ mmu.write(value, registers.getBC());
+ }
+ }, _DE("(DE)", 4) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return mmu.read(registers.getDE());
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ mmu.write(value, registers.getDE());
+ }
+ }, _HL("(HL)", 4) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return mmu.read(registers.getHL());
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ mmu.write(value, registers.getHL());
+ }
+ }, _HL_INC("(HL+)", 4) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int value = mmu.read(registers.getHL());
+ registers.setHL(registers.getHL() + 1);
+ return value;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ int address = registers.getHL();
+ registers.setHL(registers.getHL() + 1);
+ mmu.write(value, address);
+ }
+ }, _HL_DEC("(HL-)", 4) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int value = mmu.read(registers.getHL());
+ registers.setHL(registers.getHL() - 1);
+ return value;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ int address = registers.getHL();
+ registers.setHL(registers.getHL() - 1);
+ mmu.write(value, address);
+ }
+ }, D8(4) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int value = mmu.read(registers.getPC());
+ registers.incPC();
+
+ return value;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ Log.warn("Why are you writing to this argument?");
+ }
+ }, D16(8) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int value = mmu.read(registers.getPC());
+
+ registers.incPC();
+
+ value |= mmu.read(registers.getPC()) << 8;
+
+ registers.incPC();
+
+ return value;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ Log.warn("Why are you writing to this argument?");
+ }
+ }, I8(4) {
+
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ byte relativeAddress = (byte) mmu.read(registers.getPC());
+
+ registers.incPC();
+
+ return relativeAddress;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ Log.warn("Why are you writing to this argument?");
+ }
+
+ }, _C("(C)", 4) { // memory[0xFF00 + C]
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int value = mmu.read(0xFF00 + registers.getC());
+
+ return value;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ mmu.write(value, 0xFF00 + registers.getC());
+ }
+ }, _D8("(D8)", 8) { // memory[0xFF00 + memory[PC++]]
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int value = mmu.read(0xFF00 + mmu.read(registers.getPC()));
+
+ registers.incPC();
+
+ return value;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ mmu.write(value, 0xFF00 + mmu.read(registers.getPC()));
+
+ registers.incPC();
+ }
+ }, _D16("(D16)", 12) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int address = mmu.read(registers.getPC());
+
+ registers.incPC();
+
+ address |= mmu.read(registers.getPC()) << 8;
+
+ registers.incPC();
+
+ return mmu.read(address);
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ int address = mmu.read(registers.getPC());
+
+ registers.incPC();
+
+ address |= mmu.read(registers.getPC()) << 8;
+
+ registers.incPC();
+
+ mmu.write(value, address);
+ }
+ }, _D16_SHORT("(D16 (16 bits))", 16) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int address = mmu.read(registers.getPC());
+
+ registers.incPC();
+
+ address |= mmu.read(registers.getPC()) << 8;
+
+ registers.incPC();
+
+ return (mmu.read(address) << 8) | mmu.read(address);
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ int address = mmu.read(registers.getPC());
+
+ registers.incPC();
+
+ address |= mmu.read(registers.getPC()) << 8;
+
+ registers.incPC();
+
+ mmu.write(value >> 8, address);
+ mmu.write(value, address + 1);
+ }
+ };
+
+ private String name;
+ private int cycles;
+
+ Argument(String name, int cycles) {
+ this.name = name;
+ this.cycles = cycles;
+ }
+
+ Argument(int cycles) {
+ this.name = name();
+ this.cycles = cycles;
+ }
+
+ Argument() {
+ this.name = name();
+ this.cycles = 0;
+ }
+
+ public abstract int read(Registers registers, MMU mmu);
+ public abstract void write(int value, Registers registers, MMU mmu);
+
+ public String getName() {
+ return name;
+ }
+
+ public int getCycles() {
+ return cycles;
+ }
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/CallOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/CallOpcode.java
new file mode 100644
index 0000000..2a0e2ad
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/CallOpcode.java
@@ -0,0 +1,60 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.log.Log;
+
+public class CallOpcode implements Opcode {
+
+ private int conditionFlag;
+ private Argument address;
+ private boolean not;
+
+ public CallOpcode(int conditionFlag, Argument address, boolean not) {
+ this.conditionFlag = conditionFlag;
+ this.address = address;
+ this.not = not;
+ }
+
+ public boolean canExecute(Registers registers) {
+ return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not);
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int addressToJump = address.read(registers, mmu);
+
+ if (canExecute(registers)) {
+ registers.decSP();
+ mmu.write(registers.getPC() >> 8, registers.getSP());
+ registers.decSP();
+ mmu.write(registers.getPC(), registers.getSP());
+
+ Log.info("Pushing 0x%4x to the stack.", registers.getPC());
+
+ registers.setPC(addressToJump);
+
+ return 12 + address.getCycles(); // 2 memory writes and additional cycle
+ }
+
+ return address.getCycles();
+ }
+
+ @Override
+ public String toString() {
+ if (conditionFlag == 0)
+ return "CALL " + address.getName();
+
+ char flag = ' ';
+ switch (conditionFlag) {
+ case Flags.ZERO: flag = 'Z'; break;
+ case Flags.NEGATIVE: flag = 'N'; break;
+ case Flags.HALF_CARRY: flag = 'H'; break;
+ case Flags.CARRY: flag = 'C'; break;
+ }
+
+ return "CALL " + (not ? "" : "N") + flag + ", " + address.getName();
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java
new file mode 100644
index 0000000..ece82c6
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java
@@ -0,0 +1,18 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+
+public class ComplementOpcode implements Opcode {
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ registers.setA(~registers.getA());
+
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+
+ return 0;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java
new file mode 100644
index 0000000..0b32d4a
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java
@@ -0,0 +1,17 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+
+public class FlipCarryOpcode implements Opcode {
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, !registers.getFlags().get(Flags.CARRY));
+
+ return 0;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java
new file mode 100644
index 0000000..9c398f6
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java
@@ -0,0 +1,29 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.InterruptManager;
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+
+public class InterruptOpcode implements Opcode {
+
+ private boolean enable;
+ private InterruptManager interruptManager;
+
+ public InterruptOpcode(boolean enable, InterruptManager interruptManager) {
+ this.enable = enable;
+ this.interruptManager = interruptManager;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ if (enable)
+ interruptManager.enable();
+ else
+ interruptManager.disable();
+
+ return 0;
+ }
+
+
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpOpcode.java
new file mode 100644
index 0000000..5444f41
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpOpcode.java
@@ -0,0 +1,50 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+
+public class JumpOpcode implements Opcode {
+
+ private int conditionFlag;
+ private Argument address;
+ private boolean not;
+
+ public JumpOpcode(int conditionFlag, Argument address, boolean not) {
+ this.conditionFlag = conditionFlag;
+ this.address = address;
+ this.not = not;
+ }
+
+ public boolean canExecute(Registers registers) {
+ return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not);
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int addressToJump = address.read(registers, mmu);
+
+ if (canExecute(registers)) {
+ registers.setPC(addressToJump);
+ return address.getCycles() + 4;
+ }
+
+ return address.getCycles();
+ }
+
+ @Override
+ public String toString() {
+ if (conditionFlag == 0)
+ return "JP " + address.getName();
+
+ char flag = ' ';
+ switch (conditionFlag) {
+ case Flags.ZERO: flag = 'Z'; break;
+ case Flags.NEGATIVE: flag = 'N'; break;
+ case Flags.HALF_CARRY: flag = 'H'; break;
+ case Flags.CARRY: flag = 'C'; break;
+ }
+
+ return "JP " + (not ? "" : "N") + flag + ", " + address.getName();
+ }
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java
new file mode 100644
index 0000000..3a804a8
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java
@@ -0,0 +1,51 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+
+public class JumpRelativeOpcode implements Opcode {
+
+ private int conditionFlag;
+ private Argument address;
+ private boolean not;
+
+ public JumpRelativeOpcode(int conditionFlag, Argument address, boolean not) {
+ this.conditionFlag = conditionFlag;
+ this.address = address;
+ this.not = not;
+ }
+
+ public boolean canExecute(Registers registers) {
+ return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not);
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int relativeValue = address.read(registers, mmu);
+
+ int addressToJump = registers.getPC() + relativeValue;
+ if (canExecute(registers)) {
+ registers.setPC(addressToJump);
+ return address.getCycles() + 4;
+ }
+
+ return address.getCycles();
+ }
+
+ @Override
+ public String toString() {
+ if (conditionFlag == 0)
+ return "JR " + address.getName();
+
+ char flag = ' ';
+ switch (conditionFlag) {
+ case Flags.ZERO: flag = 'Z'; break;
+ case Flags.NEGATIVE: flag = 'N'; break;
+ case Flags.HALF_CARRY: flag = 'H'; break;
+ case Flags.CARRY: flag = 'C'; break;
+ }
+
+ return "JR " + (not ? "" : "N") + flag + ", " + address.getName();
+ }
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/LoadOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/LoadOpcode.java
new file mode 100644
index 0000000..a782b65
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/LoadOpcode.java
@@ -0,0 +1,26 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+
+public class LoadOpcode implements Opcode {
+
+ private Argument from, to;
+
+ public LoadOpcode(Argument from, Argument to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ to.write(from.read(registers, mmu), registers, mmu);
+
+ return to.getCycles() + from.getCycles();
+ }
+
+ @Override
+ public String toString() {
+ return "LD " + to.getName() + ", " + from.getName();
+ }
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Opcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Opcode.java
new file mode 100644
index 0000000..71cdaa5
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Opcode.java
@@ -0,0 +1,10 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+
+public interface Opcode {
+
+ public int run(Registers registers, MMU mmu);
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PopOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PopOpcode.java
new file mode 100644
index 0000000..ce4daca
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PopOpcode.java
@@ -0,0 +1,32 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+
+public class PopOpcode implements Opcode {
+
+ private final Argument register;
+
+ public PopOpcode(Argument register) {
+ this.register = register;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int value = mmu.read(registers.getSP());
+ registers.incSP();
+
+ value |= mmu.read(registers.getSP()) << 8;
+ registers.incSP();
+
+ register.write(value, registers, mmu);
+
+ return 8;
+ }
+
+ @Override
+ public String toString() {
+ return "POP " + register.getName();
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PushOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PushOpcode.java
new file mode 100644
index 0000000..fee0abf
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PushOpcode.java
@@ -0,0 +1,35 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.log.Log;
+
+public class PushOpcode implements Opcode {
+
+ private final Argument register;
+
+ public PushOpcode(Argument register) {
+ this.register = register;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int value = register.read(registers, mmu);
+
+ registers.decSP();
+ mmu.write(value >> 8, registers.getSP());
+
+ registers.decSP();
+ mmu.write(value, registers.getSP());
+
+ Log.info("Pushing 0x%4x to the stack.", value);
+
+ return 8;
+ }
+
+ @Override
+ public String toString() {
+ return "PUSH " + register.getName();
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RestartOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RestartOpcode.java
new file mode 100644
index 0000000..8ce0964
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RestartOpcode.java
@@ -0,0 +1,26 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+
+public class RestartOpcode implements Opcode {
+
+ private int address;
+
+ public RestartOpcode(int address) {
+ this.address = address;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ registers.decSP();
+ mmu.write(registers.getPC() >> 8, registers.getSP());
+ registers.decSP();
+ mmu.write(registers.getPC(), registers.getSP());
+
+ registers.setPC(address);
+
+ return 12;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java
new file mode 100644
index 0000000..5c4d903
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java
@@ -0,0 +1,67 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.InterruptManager;
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.log.Log;
+
+public class ReturnOpcode implements Opcode {
+
+ private int conditionFlag;
+ private boolean not;
+ private boolean fromInterupt;
+
+ private final InterruptManager interruptManager;
+
+ public ReturnOpcode(int conditionFlag, boolean not, boolean fromInterupt, final InterruptManager interruptManager) {
+ this.conditionFlag = conditionFlag;
+ this.not = not;
+ this.fromInterupt = fromInterupt;
+ this.interruptManager = interruptManager;
+ }
+
+ public boolean canExecute(Registers registers) {
+ return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not);
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int addressToJump = mmu.read(registers.getSP());
+ registers.incSP();
+
+ addressToJump |= mmu.read(registers.getSP()) << 8;
+ registers.incSP();
+
+ if (canExecute(registers)) {
+ Log.info("Returning from 0x%4x to 0x%4x", registers.getPC(), addressToJump);
+
+ registers.setPC(addressToJump);
+
+ if (fromInterupt) {
+ interruptManager.enable();
+ }
+
+ return 16;
+ }
+
+ return 4;
+ }
+
+ @Override
+ public String toString() {
+ if (conditionFlag == 0)
+ return "RET" + (fromInterupt ? "I" : "");
+
+ char flag = ' ';
+ switch (conditionFlag) {
+ case Flags.ZERO: flag = 'Z'; break;
+ case Flags.NEGATIVE: flag = 'N'; break;
+ case Flags.HALF_CARRY: flag = 'H'; break;
+ case Flags.CARRY: flag = 'C'; break;
+ }
+
+ return "RET" + (fromInterupt ? "I" : "") + " " + (not ? "" : "N") + flag;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java
new file mode 100644
index 0000000..f74c024
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java
@@ -0,0 +1,68 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+
+public class RotateAOpcode implements Opcode {
+
+ private boolean withCarry;
+ private int direction;
+
+ public static final int LEFT = -1;
+ public static final int RIGHT = 1;
+
+ public RotateAOpcode(boolean withCarry, int direction) {
+ this.withCarry = withCarry;
+ this.direction = direction;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ if (direction == LEFT) {
+ if (withCarry) {
+ int value = registers.getA();
+ int result = (value << 1) | ((value >> 7) & 1);
+
+ registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0);
+
+ registers.setA(result);
+ } else {
+ int value = registers.getA();
+ int result = (value << 1) | (registers.getFlags().get(Flags.CARRY) ? 1 : 0);
+
+ registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0);
+
+ registers.setA(result);
+ }
+ } else {
+ if (withCarry) {
+ int value = registers.getA();
+ int result = (value >> 1) | ((value & 1) << 7);
+
+ registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 1) != 0);
+
+ registers.setA(result);
+ } else {
+ int value = registers.getA();
+ int result = (value >> 1) | ((registers.getFlags().get(Flags.CARRY) ? 1 : 0) << 7);
+
+ registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 1) != 0);
+
+ registers.setA(result);
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "R" + (direction == LEFT ? "L" : "R") + (withCarry ? "C" : "" + "A");
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java
new file mode 100644
index 0000000..9b570b6
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java
@@ -0,0 +1,17 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+
+public class SetCarryOpcode implements Opcode {
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, true);
+
+ return 0;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java
new file mode 100644
index 0000000..2053d6a
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java
@@ -0,0 +1,33 @@
+package playingcoffee.core.opcode.prefixed;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.Opcode;
+
+public class BitOpcode implements Opcode {
+
+ private int bit;
+ private Argument argument;
+
+ public BitOpcode(int bit, Argument argument) {
+ this.bit = bit;
+ this.argument = argument;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ registers.getFlags().set(Flags.ZERO, (argument.read(registers, mmu) & (1 << bit)) == 0);
+ registers.getFlags().set(Flags.NEGATIVE, false);
+ registers.getFlags().set(Flags.HALF_CARRY, true);
+
+ return argument.getCycles();
+ }
+
+ @Override
+ public String toString() {
+ return "BIT " + bit + ", " + argument.getName();
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java
new file mode 100644
index 0000000..525480e
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java
@@ -0,0 +1,25 @@
+package playingcoffee.core.opcode.prefixed;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.Opcode;
+
+public class ResetBitOpcode implements Opcode {
+
+ private int bit;
+ private Argument argument;
+
+ public ResetBitOpcode(int bit, Argument argument) {
+ this.bit = bit;
+ this.argument = argument;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ argument.write(argument.read(registers, mmu) & (~(1 << bit)), registers, mmu);
+
+ return argument.getCycles() * 2;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java
new file mode 100644
index 0000000..b13a21a
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java
@@ -0,0 +1,54 @@
+package playingcoffee.core.opcode.prefixed;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.Opcode;
+
+public class RotateOpcode implements Opcode {
+
+ private boolean withCarry;
+ private Argument register;
+ private int direction;
+
+ public static final int LEFT = -1;
+ public static final int RIGHT = 1;
+
+ public RotateOpcode(boolean withCarry, Argument register, int direction) {
+ this.withCarry = withCarry;
+ this.register = register;
+ this.direction = direction;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ if (direction == LEFT) {
+ int value = register.read(registers, mmu);
+ int result = (value << 1) | (!withCarry ? (registers.getFlags().get(Flags.CARRY) ? 1 : 0) : (value >> 7) & 1);
+
+ registers.getFlags().set(Flags.ZERO, result == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0);
+
+ register.write(result, registers, mmu);
+
+ } else {
+ int value = register.read(registers, mmu);
+ int result = (value >> 1) | ((!withCarry ? (registers.getFlags().get(Flags.CARRY) ? 1 : 0) : (value & 0x1)) << 7);
+
+ registers.getFlags().set(Flags.ZERO, result == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 1) != 0);
+
+ register.write(result, registers, mmu);
+ }
+
+ return register.getCycles();
+ }
+
+ @Override
+ public String toString() {
+ return "R" + (direction == LEFT ? "L" : "R") + (withCarry ? "C " : " " + register.getName());
+ }
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java
new file mode 100644
index 0000000..f53b6c5
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java
@@ -0,0 +1,25 @@
+package playingcoffee.core.opcode.prefixed;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.Opcode;
+
+public class SetBitOpcode implements Opcode {
+
+ private int bit;
+ private Argument argument;
+
+ public SetBitOpcode(int bit, Argument argument) {
+ this.bit = bit;
+ this.argument = argument;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ argument.write(argument.read(registers, mmu) | (1 << bit), registers, mmu);
+
+ return argument.getCycles() * 2;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java
new file mode 100644
index 0000000..f75e9bd
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java
@@ -0,0 +1,57 @@
+package playingcoffee.core.opcode.prefixed;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.Opcode;
+
+public class ShiftOpcode implements Opcode {
+
+ private ShiftType type;
+ private Argument register;
+
+ public ShiftOpcode(ShiftType type, Argument register) {
+ this.type = type;
+ this.register = register;
+ }
+
+ public enum ShiftType {
+ SLA, SRA, SRL
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int value, result;
+
+ switch (type) {
+ case SLA:
+ value = register.read(registers, mmu);
+ result = value << 1;
+
+ registers.getFlags().set(Flags.ZERO, result == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0);
+ break;
+ case SRA:
+ value = register.read(registers, mmu);
+ result = (value >> 1) | (value & 0x80);
+
+ registers.getFlags().set(Flags.ZERO, result == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 0x1) != 0);
+ break;
+ case SRL:
+ value = register.read(registers, mmu);
+ result = (value >> 1);
+
+ registers.getFlags().set(Flags.ZERO, result == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 0x1) != 0);
+ break;
+ }
+
+ return register.getCycles() * 2;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java
new file mode 100644
index 0000000..b31cac8
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java
@@ -0,0 +1,25 @@
+package playingcoffee.core.opcode.prefixed;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.Opcode;
+
+public class SwapOpcode implements Opcode {
+
+ private Argument register;
+
+ public SwapOpcode(Argument register) {
+ this.register = register;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int value = register.read(registers, mmu);
+
+ register.write(((value & 0xF0) >> 4) | ((value & 0xF) << 4), registers, mmu);
+
+ return register.getCycles() * 2;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/log/Log.java b/playing-coffee - Copy/src/main/java/playingcoffee/log/Log.java
new file mode 100644
index 0000000..1bb6478
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/log/Log.java
@@ -0,0 +1,73 @@
+package playingcoffee.log;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.time.LocalTime;
+
+public class Log {
+
+ public static final String ANSI_RESET = "\033[0m";
+ public static final String ANSI_BLACK = "\u001B[30m";
+ public static final String ANSI_RED = "\u001B[31m";
+ public static final String ANSI_GREEN = "\u001B[32m";
+ public static final String ANSI_YELLOW = "\u001B[33m";
+ public static final String ANSI_BLUE = "\u001B[34m";
+ public static final String ANSI_PURPLE = "\u001B[35m";
+ public static final String ANSI_CYAN = "\u001B[36m";
+ public static final String ANSI_WHITE = "\u001B[37m";
+
+ private static FileWriter fileWriter;
+
+ private static boolean useColors = false;
+
+ public static void init() {
+ try {
+ File file = new File("log.txt");
+ file.createNewFile();
+
+ fileWriter = new FileWriter(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.exit(-1);
+ }
+ info("Initialized logger");
+ }
+
+ public static void close() {
+ try {
+ fileWriter.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void info(String format, Object... args) {
+ log("Info", ANSI_RESET, format, args);
+ }
+
+ public static void warn(String format, Object... args) {
+ log("Warning", ANSI_YELLOW, format, args);
+ }
+
+ public static void error(String format, Object... args) {
+ log("Error", ANSI_RED, format, args);
+ }
+
+ public static void fatal(String format, Object... args) {
+ log("Fatal", ANSI_RED, format, args);
+ }
+
+ private static void log(String type, String ansiColor, String format, Object... args) {
+ String finalMessage = "[" + LocalTime.now() + "] " + type + ": " + String.format(format, args);
+
+ ansiColor = useColors ? ansiColor : "";
+
+ System.out.println(ansiColor + finalMessage);
+ try {
+ fileWriter.write(finalMessage + "\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/ppu/OAM.java b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/OAM.java
new file mode 100644
index 0000000..8ef0dcd
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/OAM.java
@@ -0,0 +1,52 @@
+package playingcoffee.ppu;
+
+import playingcoffee.core.MemorySpace;
+import playingcoffee.log.Log;
+
+public class OAM implements MemorySpace {
+
+ public OAMEntry entries[];
+
+ public OAM() {
+ entries = new OAMEntry[40];
+ }
+
+ public int read(int address) {
+ int entry = (address - 0xFE00) / 4;
+
+ switch (address % 4) {
+ case 0: return entries[entry].x;
+ case 1: return entries[entry].y;
+ case 2: return entries[entry].tileNumber;
+ case 3: return entries[entry].flags;
+ }
+
+ Log.error("how in pete's holy christmas tree did we get here?");
+ throw new IllegalArgumentException("Invalid address.");
+ }
+
+ public void write(int value, int address) {
+ int entry = (address - 0xFE00) / 4;
+
+ switch (address % 4) {
+ case 0: entries[entry].x = value;
+ case 1: entries[entry].y = value;
+ case 2: entries[entry].tileNumber = value;
+ case 3: entries[entry].flags = value;
+ }
+
+ Log.error("how in pete's holy christmas tree did we get here?");
+ throw new IllegalArgumentException("Invalid address.");
+ }
+
+ @Override
+ public boolean inMemorySpace(int address) {
+ return (address >= 0xFE00 && address <= 0xFE9F);
+ }
+
+ public class OAMEntry {
+ public int x, y;
+ public int tileNumber;
+ public int flags;
+ }
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPU.java b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPU.java
new file mode 100644
index 0000000..1a071a7
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPU.java
@@ -0,0 +1,73 @@
+package playingcoffee.ppu;
+
+import playingcoffee.core.InterruptManager;
+import playingcoffee.core.MMU;
+
+public class PPU {
+
+ private final MMU mmu;
+ private final InterruptManager interruptManager;
+
+ private PPURegisters registers;
+ private VRAM vram;
+
+ private int clockCount = 0;
+
+ public PPU(final MMU mmu, final InterruptManager interruptManager) {
+ this.mmu = mmu;
+ this.interruptManager = interruptManager;
+
+ registers = new PPURegisters();
+ vram = new VRAM();
+
+ this.mmu.connectMemorySpace(registers);
+ this.mmu.connectMemorySpace(vram);
+ }
+
+ public void OAMSeach() {
+ registers.setLCDCMode(2);
+ }
+
+ public void pixelTransfer() {
+ registers.setLCDCMode(3);
+ }
+
+ public void HBlank() {
+ registers.setLCDCMode(0);
+ }
+
+ public void VBlank() {
+ registers.setLCDCMode(1);
+ }
+
+ public void clock() {
+ registers.lcdcYCoord = clockCount / 114;
+
+ if (clockCount == 114 * 144)
+ interruptManager.requestInterrupt(InterruptManager.VBLANK);
+
+ if (clockCount < 114 * 144) {
+ if (clockCount % 114 < 20) OAMSeach();
+ else if (clockCount % 114 < 43) pixelTransfer();
+ else HBlank();
+
+ } else {
+ VBlank();
+ }
+
+ clockCount++;
+
+ if (clockCount == 17556) {
+ clockCount = 0;
+ }
+ }
+
+ public PPURegisters getRegisters() {
+ return registers;
+ }
+
+ public VRAM getVram() {
+ return vram;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPURegisters.java b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPURegisters.java
new file mode 100644
index 0000000..54695a8
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPURegisters.java
@@ -0,0 +1,61 @@
+package playingcoffee.ppu;
+
+import playingcoffee.core.MemorySpace;
+
+public class PPURegisters implements MemorySpace {
+
+ public int lcdControl; // 0xFF40
+ public int lcdcStatus; // 0xFF41
+ public int scrollY, scrollX; // 0xFF42 - 0xFF43
+ public int lcdcYCoord; // 0xFF44
+ public int lyCompare; // 0xFF45
+ public int dmaTransferStart; // 0xFF46
+ public int bgPalette; // 0xFF47
+ public int objPalette0, objPalette1; // 0xFF48 - 0xFF49
+ public int windowY, windowX; // 0xFF4A - 0xFF4B
+
+ public void setLCDCMode(int mode) {
+ lcdcStatus = (lcdcStatus & 0xFC) | mode;
+ }
+
+ public int read(int address) {
+ switch (address) {
+ case 0xFF40: return lcdControl;
+ case 0xFF41: return lcdcStatus;
+ case 0xFF42: return scrollY;
+ case 0xFF43: return scrollX;
+ case 0xFF44: return lcdcYCoord;
+ case 0xFF45: return lyCompare;
+ case 0xFF46: return dmaTransferStart; // TODO: Do DMA (I have no idea how to do it though...)
+ case 0xFF47: return bgPalette;
+ case 0xFF48: return objPalette0;
+ case 0xFF49: return objPalette1;
+ case 0xFF4A: return windowY;
+ case 0xFF4B: return windowX;
+ }
+
+ throw new IllegalArgumentException("Invalid address");
+ }
+
+ public void write(int value, int address) {
+ switch (address) {
+ case 0xFF40: lcdControl = value; return;
+ case 0xFF41: lcdcStatus = value; return;
+ case 0xFF42: scrollY = value; return;
+ case 0xFF43: scrollX = value; return;
+ case 0xFF45: lyCompare = value; return;
+ case 0xFF46: dmaTransferStart = value; return; // TODO: Do DMA (I have no idea how to do it though...)
+ case 0xFF47: bgPalette = value; return;
+ case 0xFF48: objPalette0 = value; return;
+ case 0xFF49: objPalette1 = value; return;
+ case 0xFF4A: windowY = value; return;
+ case 0xFF4B: windowX = value; return;
+ }
+
+ throw new IllegalArgumentException("Invalid address");
+ }
+
+ public boolean inMemorySpace(int address) {
+ return (address >= 0xFF40 && address <= 0xFF4B);
+ }
+}
diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/ppu/VRAM.java b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/VRAM.java
new file mode 100644
index 0000000..c314446
--- /dev/null
+++ b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/VRAM.java
@@ -0,0 +1,35 @@
+package playingcoffee.ppu;
+
+import playingcoffee.core.MemorySpace;
+
+public class VRAM implements MemorySpace {
+
+ public int[] background0;
+ public int[] background1;
+
+ public VRAM() {
+ background0 = new int[32 * 32];
+ background1 = new int[32 * 32];
+ }
+
+ @Override
+ public int read(int address) {
+ if (address >= 0x9800 && address <= 0x9BFF) return background0[address % (32 * 32)];
+ if (address >= 0x9C00 && address <= 0x9FFF) return background1[address % (32 * 32)];
+
+ return 0;
+ }
+
+ @Override
+ public void write(int value, int address) {
+ if (address >= 0x9800 && address <= 0x9BFF) background0[address % (32 * 32)] = value;
+ if (address >= 0x9C00 && address <= 0x9FFF) background1[address % (32 * 32)] = value;
+
+ }
+
+ @Override
+ public boolean inMemorySpace(int address) {
+ return address >= 0x9800 && address <= 0x9FFF;
+ }
+
+}
diff --git a/playing-coffee - Copy/src/test/java/playingcoffee/test/GameBoyTest.java b/playing-coffee - Copy/src/test/java/playingcoffee/test/GameBoyTest.java
new file mode 100644
index 0000000..8f2adce
--- /dev/null
+++ b/playing-coffee - Copy/src/test/java/playingcoffee/test/GameBoyTest.java
@@ -0,0 +1,26 @@
+package playingcoffee.test;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+public class GameBoyTest {
+
+ @Test
+ public void testCPU() throws IOException {
+ /*Log.init();
+
+
+ byte[] bootRom = Files.readAllBytes(Paths.get("dmg_boot.bin"));
+
+ MMU mmu = new MMU();
+ CPU cpu = new CPU(mmu);
+
+ for (int i = 0; i < 0x100; i++) {
+ mmu.write(Byte.toUnsignedInt(bootRom[i]), i);
+ }
+
+ while (true) cpu.clock();*/
+ }
+
+}
diff --git a/playing-coffee old/.classpath b/playing-coffee old/.classpath
new file mode 100644
index 0000000..51a8bba
--- /dev/null
+++ b/playing-coffee old/.classpath
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/playing-coffee old/.project b/playing-coffee old/.project
new file mode 100644
index 0000000..e526655
--- /dev/null
+++ b/playing-coffee old/.project
@@ -0,0 +1,17 @@
+
+
+ playing-coffee
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/playing-coffee old/.settings/org.eclipse.jdt.core.prefs b/playing-coffee old/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..3a21537
--- /dev/null
+++ b/playing-coffee old/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/playing-coffee old/DMG_ROM.bin b/playing-coffee old/DMG_ROM.bin
new file mode 100644
index 0000000..afa0ee4
Binary files /dev/null and b/playing-coffee old/DMG_ROM.bin differ
diff --git a/playing-coffee old/bin/playingcoffee/core/CPU.class b/playing-coffee old/bin/playingcoffee/core/CPU.class
new file mode 100644
index 0000000..b006975
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/core/CPU.class differ
diff --git a/playing-coffee old/bin/playingcoffee/core/MMU.class b/playing-coffee old/bin/playingcoffee/core/MMU.class
new file mode 100644
index 0000000..1df082d
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/core/MMU.class differ
diff --git a/playing-coffee old/bin/playingcoffee/core/Registers.class b/playing-coffee old/bin/playingcoffee/core/Registers.class
new file mode 100644
index 0000000..60292d5
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/core/Registers.class differ
diff --git a/playing-coffee old/bin/playingcoffee/core/cpu/Argument$1.class b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$1.class
new file mode 100644
index 0000000..eaba3c8
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$1.class differ
diff --git a/playing-coffee old/bin/playingcoffee/core/cpu/Argument$2.class b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$2.class
new file mode 100644
index 0000000..d8e55fc
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$2.class differ
diff --git a/playing-coffee old/bin/playingcoffee/core/cpu/Argument$3.class b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$3.class
new file mode 100644
index 0000000..6dc78e4
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$3.class differ
diff --git a/playing-coffee old/bin/playingcoffee/core/cpu/Argument$4.class b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$4.class
new file mode 100644
index 0000000..cb8caf9
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$4.class differ
diff --git a/playing-coffee old/bin/playingcoffee/core/cpu/Argument$5.class b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$5.class
new file mode 100644
index 0000000..32ba433
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$5.class differ
diff --git a/playing-coffee old/bin/playingcoffee/core/cpu/Argument.class b/playing-coffee old/bin/playingcoffee/core/cpu/Argument.class
new file mode 100644
index 0000000..e29cc3d
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/core/cpu/Argument.class differ
diff --git a/playing-coffee old/bin/playingcoffee/core/cpu/Registers.class b/playing-coffee old/bin/playingcoffee/core/cpu/Registers.class
new file mode 100644
index 0000000..865bc0c
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/core/cpu/Registers.class differ
diff --git a/playing-coffee old/bin/playingcoffee/ui/CPUDebugger$1.class b/playing-coffee old/bin/playingcoffee/ui/CPUDebugger$1.class
new file mode 100644
index 0000000..431e33d
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/ui/CPUDebugger$1.class differ
diff --git a/playing-coffee old/bin/playingcoffee/ui/CPUDebugger$2.class b/playing-coffee old/bin/playingcoffee/ui/CPUDebugger$2.class
new file mode 100644
index 0000000..de14b9a
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/ui/CPUDebugger$2.class differ
diff --git a/playing-coffee old/bin/playingcoffee/ui/CPUDebugger.class b/playing-coffee old/bin/playingcoffee/ui/CPUDebugger.class
new file mode 100644
index 0000000..db6a71a
Binary files /dev/null and b/playing-coffee old/bin/playingcoffee/ui/CPUDebugger.class differ
diff --git a/playing-coffee old/src/playingcoffee/core/CPU.java b/playing-coffee old/src/playingcoffee/core/CPU.java
new file mode 100644
index 0000000..ac2ab94
--- /dev/null
+++ b/playing-coffee old/src/playingcoffee/core/CPU.java
@@ -0,0 +1,112 @@
+// Basic CPU implementation.
+// This will be changed in the future.
+
+package playingcoffee.core;
+
+public class CPU {
+
+ private final MMU mmu;
+
+ private Registers registers;
+
+ private int cycles = 0;
+
+ public static final int[] INSTRUCTION_CYCLE_COUNT = {
+ 4, 12, 8, 8, 4, 4, 8, 4, 20, 8, 8, 8, 4, 4, 8, 4,
+ 4, 12, 8, 8, 4, 4, 8, 4, 12, 8, 8, 8, 4, 4, 8, 4,
+ 8, 12, 8, 8, 12, 12, 12, 4, 8, 8, 8, 8, 4, 4, 8, 4,
+ 8, 12, 8, 8, 12, 12, 12, 4, 8, 8, 8, 8, 4, 4, 8, 4,
+ 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,
+ 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,
+ 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 4,
+ 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,
+ 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,
+ 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,
+ 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,
+ 8, 12, 12, 16, 12, 16, 8, 16, 8, 16, 12, 4, 12, 24, 8, 16,
+ 8, 12, 12, -1, 12, 16, 8, 16, 8, 16, 12, -1, 12, -1, 8, 16,
+ 12, 12, 8, -1, -1, 16, 8, 16, 16, 4, 16, -1, -1, -1, 8, 16,
+ 12, 12, 8, 4, -1, 16, 8, 16, 12, 8, 16, 4, -1, -1, 8, 16
+ };
+
+ public static final int[] PREFIXED_CYCLE_COUNT = {
+ 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8,
+ };
+
+ public CPU(final MMU mmu) {
+ this.mmu = mmu;
+
+ registers = new Registers();
+ }
+
+ public Registers getRegisters() {
+ return registers;
+ }
+
+ public int getCycles() {
+ return cycles;
+ }
+
+ public void cycle() {
+ if (cycles == 0) {
+
+ int opcode = mmu.read(registers.pc++);
+ System.out.printf("Fetched opcode: 0x%02x\n", opcode);
+
+ decodeOpcode(opcode);
+ }
+ cycles--;
+ }
+
+ public void decodeOpcode(int opcode) {
+ // Apply cycle count
+ cycles += INSTRUCTION_CYCLE_COUNT[opcode];
+
+ switch (opcode) {
+ case 0x00: // NOP
+ break;
+
+ case 0x21: // LD HL, d16
+ registers.writeL(mmu.read(registers.pc++));
+ registers.writeH(mmu.read(registers.pc++));
+ break;
+
+ case 0x31: // LD SP, d16
+ registers.sp = mmu.read(registers.pc++) | (mmu.read(registers.pc++) << 8);
+ break;
+
+ case 0x32: // LD (HL-), A
+ mmu.write(registers.readA(), registers.readHL());
+ registers.writeHL(registers.readHL() - 1);
+ break;
+
+ case 0xAF: // XOR A
+ registers.writeA(registers.readA() ^ registers.readA());
+ registers.setFlag(Registers.FLAG_Z, registers.readA() == 0);
+ break;
+
+ case 0xCB: // CB - prefix
+ decodePrefixedOpcode(mmu.read(registers.pc++));
+ break;
+
+ default:
+ System.err.printf("Unknown opcode: 0x%2x\n", opcode);
+ }
+ }
+
+ public void decodePrefixedOpcode(int opcode) {
+ cycles += INSTRUCTION_CYCLE_COUNT[opcode & 0xF];
+
+ switch (opcode) {
+ case 0x7C:
+ registers.setFlag(Registers.FLAG_Z, (registers.readH() & (1 << 7)) == 0);
+ registers.setFlag(Registers.FLAG_N, false);
+ registers.setFlag(Registers.FLAG_H, true);
+ break;
+
+ default:
+ System.err.printf("Unknown opcode: 0xcb%2x\n", opcode);
+ }
+ }
+}
diff --git a/playing-coffee old/src/playingcoffee/core/MMU.java b/playing-coffee old/src/playingcoffee/core/MMU.java
new file mode 100644
index 0000000..e7b98a8
--- /dev/null
+++ b/playing-coffee old/src/playingcoffee/core/MMU.java
@@ -0,0 +1,34 @@
+package playingcoffee.core;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+public class MMU {
+
+ private int ram[];
+
+ public void loadBootRom() {
+ byte[] data;
+ try {
+ data = Files.readAllBytes(Paths.get("DMG_ROM.bin"));
+ for (int i = 0; i < data.length; i++) {
+ ram[i] = Byte.toUnsignedInt(data[i]);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public MMU() {
+ ram = new int[0x10000];
+ }
+
+ public void write(int value, int address) {
+ ram[address] = value;
+ }
+
+ public int read(int address) {
+ return ram[address];
+ }
+}
diff --git a/playing-coffee old/src/playingcoffee/core/Registers.java b/playing-coffee old/src/playingcoffee/core/Registers.java
new file mode 100644
index 0000000..1554b58
--- /dev/null
+++ b/playing-coffee old/src/playingcoffee/core/Registers.java
@@ -0,0 +1,129 @@
+package playingcoffee.core;
+
+public class Registers {
+
+ private int a, f;
+ private int b, c;
+ private int d, e;
+ private int h, l;
+
+ public int pc;
+ public int sp;
+
+ public static final int FLAG_Z = 1 << 7;
+ public static final int FLAG_N = 1 << 6;
+ public static final int FLAG_H = 1 << 5;
+ public static final int FLAG_C = 1 << 4;
+
+ public void setFlag(int flag, boolean value) {
+ if (value)
+ f |= flag;
+ else
+ f &= ~flag;
+ }
+
+ public boolean getFlag(int flag) {
+ return (f & flag) != 0;
+ }
+
+ public int readAF() {
+ return a << 8 | f;
+ }
+
+ public int readBC() {
+ return b << 8 | c;
+ }
+
+ public int readDE() {
+ return d << 8 | e;
+ }
+
+ public int readHL() {
+ return h << 8 | l;
+ }
+
+ public void writeAF(int af) {
+ a = (af >> 8) & 0xFF;
+ f = af & 0xFF;
+ }
+
+ public void writeBC(int bc) {
+ b = (bc >> 8) & 0xFF;
+ c = bc & 0xFF;
+ }
+
+ public void writeDE(int de) {
+ d = (de >> 8) & 0xFF;
+ e = de & 0xFF;
+ }
+
+ public void writeHL(int hl) {
+ h = (hl >> 8) & 0xFF;
+ l = hl & 0xFF;
+ }
+
+ public int readA() {
+ return a;
+ }
+
+ public void writeA(int a) {
+ this.a = a;
+ }
+
+ public int readF() {
+ return f;
+ }
+
+ public void writeF(int f) {
+ this.f = f;
+ }
+
+ public int readB() {
+ return b;
+ }
+
+ public void writeB(int b) {
+ this.b = b;
+ }
+
+ public int readC() {
+ return c;
+ }
+
+ public void writeC(int c) {
+ this.c = c;
+ }
+
+ public int readD() {
+ return d;
+ }
+
+ public void writeD(int d) {
+ this.d = d;
+ }
+
+ public int readE() {
+ return e;
+ }
+
+ public void writeE(int e) {
+ this.e = e;
+ }
+
+ public int readH() {
+ return h;
+ }
+
+ public void writeH(int h) {
+ this.h = h;
+ }
+
+ public int readL() {
+ return l;
+ }
+
+ public void writeL(int l) {
+ this.l = l;
+ }
+
+}
diff --git a/playing-coffee old/src/playingcoffee/core/cpu/Argument.java b/playing-coffee old/src/playingcoffee/core/cpu/Argument.java
new file mode 100644
index 0000000..6d12ee3
--- /dev/null
+++ b/playing-coffee old/src/playingcoffee/core/cpu/Argument.java
@@ -0,0 +1,72 @@
+package playingcoffee.core.cpu;
+
+import playingcoffee.core.MMU;
+
+public enum Argument {
+
+ A {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getA();
+ }
+
+ @Override
+ public void write(Registers registers, MMU mmu, int value) {
+ registers.setA(value);
+ }
+ }, B {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getB();
+ }
+
+ @Override
+ public void write(Registers registers, MMU mmu, int value) {
+ registers.setB(value);
+ }
+ }, C {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getC();
+ }
+
+ @Override
+ public void write(Registers registers, MMU mmu, int value) {
+ registers.setC(value);
+ }
+ }, D {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getD();
+ }
+
+ @Override
+ public void write(Registers registers, MMU mmu, int value) {
+ registers.setD(value);
+ }
+ }, E {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getE();
+ }
+
+ @Override
+ public void write(Registers registers, MMU mmu, int value) {
+ registers.setE(value);
+ }
+ };
+
+ private String label;
+
+ public abstract int read(Registers registers, MMU mmu);
+ public abstract void write(Registers registers, MMU mmu, int value);
+
+ public Argument parse(String argument) {
+ for (Argument arg : values())
+ if (arg.label.equals(argument))
+ return arg;
+
+ throw new IllegalArgumentException(String.format("Invalid argument: %s", argument));
+ }
+
+}
diff --git a/playing-coffee old/src/playingcoffee/core/cpu/Registers.java b/playing-coffee old/src/playingcoffee/core/cpu/Registers.java
new file mode 100644
index 0000000..adc77b7
--- /dev/null
+++ b/playing-coffee old/src/playingcoffee/core/cpu/Registers.java
@@ -0,0 +1,33 @@
+package playingcoffee.core.cpu;
+
+public class Registers {
+
+ private int a, b, c, d, e, h, l;
+
+ private int pc, sp;
+
+ public int getA() { return a & 0xFF; }
+ public int getB() { return b & 0xFF; }
+ public int getC() { return c & 0xFF; }
+ public int getD() { return d & 0xFF; }
+ public int getE() { return e & 0xFF; }
+ public int getH() { return h & 0xFF; }
+ public int getL() { return l & 0xFF; }
+
+ public void setA(int value) { a = value & 0xFF; }
+ public void setB(int value) { b = value & 0xFF; }
+ public void setC(int value) { c = value & 0xFF; }
+ public void setD(int value) { d = value & 0xFF; }
+ public void setE(int value) { e = value & 0xFF; }
+ public void setH(int value) { h = value & 0xFF; }
+ public void setL(int value) { l = value & 0xFF; }
+
+ public int getPC() { return pc & 0xFFFF; }
+ public int getSP() { return sp & 0xFFFF; }
+
+ public void setPC(int value) { pc = value & 0xFFFF; }
+ public void setSP(int value) { sp = value & 0xFFFF; }
+
+ public void incPC(int value) { setPC(pc + value); }
+ public void incSP(int value) { setSP(sp + value); }
+}
diff --git a/playing-coffee old/src/playingcoffee/ui/CPUDebugger.java b/playing-coffee old/src/playingcoffee/ui/CPUDebugger.java
new file mode 100644
index 0000000..c9a71da
--- /dev/null
+++ b/playing-coffee old/src/playingcoffee/ui/CPUDebugger.java
@@ -0,0 +1,158 @@
+package playingcoffee.ui;
+
+import java.awt.EventQueue;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.DefaultListModel;
+import javax.swing.GroupLayout;
+import javax.swing.GroupLayout.Alignment;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.LayoutStyle.ComponentPlacement;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+
+import playingcoffee.core.CPU;
+import playingcoffee.core.MMU;
+import playingcoffee.core.Registers;
+
+import javax.swing.JCheckBox;
+
+public class CPUDebugger {
+
+ private JFrame frmCpuDebugger;
+ private JList registerList;
+
+ private JCheckBox chckbxZero;
+ private JCheckBox chckbxNegative;
+ private JCheckBox chckbxHalfCarry;
+ private JCheckBox chckbxCarry;
+
+ private MMU mmu;
+ private CPU cpu;
+
+ public static void main(String[] args) {
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ try {
+ CPUDebugger window = new CPUDebugger();
+ window.frmCpuDebugger.setVisible(true);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ public CPUDebugger() {
+ initialize();
+
+ mmu = new MMU();
+ cpu = new CPU(mmu);
+
+ mmu.loadBootRom();
+
+ updateValues();
+ }
+
+ private void updateValues() {
+ DefaultListModel model = new DefaultListModel();
+ model.addElement("AF: 0x" + Integer.toHexString(cpu.getRegisters().readAF()));
+ model.addElement("BC: 0x" + Integer.toHexString(cpu.getRegisters().readBC()));
+ model.addElement("DE: 0x" + Integer.toHexString(cpu.getRegisters().readDE()));
+ model.addElement("HL: 0x" + Integer.toHexString(cpu.getRegisters().readHL()));
+ model.addElement("PC: 0x" + Integer.toHexString(cpu.getRegisters().pc));
+ model.addElement("SP: 0x" + Integer.toHexString(cpu.getRegisters().sp));
+
+ chckbxZero.setSelected(cpu.getRegisters().getFlag(Registers.FLAG_Z));
+ chckbxNegative.setSelected(cpu.getRegisters().getFlag(Registers.FLAG_N));
+ chckbxHalfCarry.setSelected(cpu.getRegisters().getFlag(Registers.FLAG_H));
+ chckbxCarry.setSelected(cpu.getRegisters().getFlag(Registers.FLAG_C));
+
+ registerList.setModel(model);
+ }
+
+ private void initialize() {
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+ | UnsupportedLookAndFeelException e) {
+ e.printStackTrace();
+ }
+
+ frmCpuDebugger = new JFrame();
+ frmCpuDebugger.setTitle("CPU Debugger");
+ frmCpuDebugger.setBounds(100, 100, 350, 350);
+ frmCpuDebugger.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ JButton btnStep = new JButton("Step");
+ btnStep.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ do { cpu.cycle(); } while (cpu.getCycles() != 0);
+ updateValues();
+ }
+ });
+
+ JButton btnReset = new JButton("Reset");
+
+ JLabel lblRegisters = new JLabel("Registers:");
+
+ registerList = new JList();
+
+ chckbxZero = new JCheckBox("Zero");
+ chckbxNegative = new JCheckBox("Negative");
+ chckbxHalfCarry = new JCheckBox("Half Carry");
+ chckbxCarry = new JCheckBox("Carry");
+
+ GroupLayout groupLayout = new GroupLayout(frmCpuDebugger.getContentPane());
+ groupLayout.setHorizontalGroup(
+ groupLayout.createParallelGroup(Alignment.LEADING)
+ .addGroup(groupLayout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+ .addComponent(lblRegisters)
+ .addComponent(btnStep)
+ .addComponent(registerList, GroupLayout.PREFERRED_SIZE, 148, GroupLayout.PREFERRED_SIZE))
+ .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+ .addGroup(groupLayout.createSequentialGroup()
+ .addPreferredGap(ComponentPlacement.RELATED, 52, Short.MAX_VALUE)
+ .addComponent(btnReset)
+ .addContainerGap())
+ .addGroup(groupLayout.createSequentialGroup()
+ .addPreferredGap(ComponentPlacement.UNRELATED)
+ .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+ .addComponent(chckbxZero)
+ .addComponent(chckbxNegative)
+ .addComponent(chckbxHalfCarry)
+ .addComponent(chckbxCarry))
+ .addGap(73))))
+ );
+ groupLayout.setVerticalGroup(
+ groupLayout.createParallelGroup(Alignment.LEADING)
+ .addGroup(groupLayout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(lblRegisters)
+ .addPreferredGap(ComponentPlacement.RELATED)
+ .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+ .addGroup(groupLayout.createSequentialGroup()
+ .addComponent(registerList, GroupLayout.PREFERRED_SIZE, 195, GroupLayout.PREFERRED_SIZE)
+ .addGap(51)
+ .addGroup(groupLayout.createParallelGroup(Alignment.BASELINE)
+ .addComponent(btnReset)
+ .addComponent(btnStep)))
+ .addGroup(groupLayout.createSequentialGroup()
+ .addComponent(chckbxZero)
+ .addPreferredGap(ComponentPlacement.UNRELATED)
+ .addComponent(chckbxNegative)
+ .addPreferredGap(ComponentPlacement.UNRELATED)
+ .addComponent(chckbxHalfCarry)
+ .addPreferredGap(ComponentPlacement.UNRELATED)
+ .addComponent(chckbxCarry)))
+ .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+ frmCpuDebugger.getContentPane().setLayout(groupLayout);
+ }
+}
diff --git a/playing-coffee/.gitignore b/playing-coffee/.gitignore
new file mode 100644
index 0000000..b83d222
--- /dev/null
+++ b/playing-coffee/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/playing-coffee/.settings/org.eclipse.m2e.core.prefs b/playing-coffee/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/playing-coffee/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/playing-coffee/dmg_boot.bin b/playing-coffee/dmg_boot.bin
new file mode 100644
index 0000000..afa0ee4
Binary files /dev/null and b/playing-coffee/dmg_boot.bin differ
diff --git a/playing-coffee/gb-test-roms-master.zip b/playing-coffee/gb-test-roms-master.zip
new file mode 100644
index 0000000..b66c88f
Binary files /dev/null and b/playing-coffee/gb-test-roms-master.zip differ
diff --git a/playing-coffee/log.txt b/playing-coffee/log.txt
new file mode 100644
index 0000000..9fd27a9
--- /dev/null
+++ b/playing-coffee/log.txt
@@ -0,0 +1,646 @@
+[19:39:15.741] Info: Initialized logger
+[19:39:15.830] Info: Pushing 0x 2b to the stack.
+[19:39:15.831] Info: Pushing 0x 4ce to the stack.
+[19:39:15.832] Info: Pushing 0x 39d to the stack.
+[19:39:15.832] Info: Pushing 0x 23a to the stack.
+[19:39:15.832] Info: Pushing 0x 175 to the stack.
+[19:39:15.832] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.832] Info: Pushing 0x 2e to the stack.
+[19:39:15.833] Info: Pushing 0x 4eb to the stack.
+[19:39:15.833] Info: Pushing 0x 3d7 to the stack.
+[19:39:15.833] Info: Pushing 0x 2af to the stack.
+[19:39:15.833] Info: Pushing 0x 15e to the stack.
+[19:39:15.833] Info: Returning from 0x a8 to 0x 2e
+[19:39:15.834] Info: Pushing 0x 2b to the stack.
+[19:39:15.834] Info: Pushing 0x 4ed to the stack.
+[19:39:15.834] Info: Pushing 0x 3db to the stack.
+[19:39:15.834] Info: Pushing 0x 2b7 to the stack.
+[19:39:15.835] Info: Pushing 0x 16f to the stack.
+[19:39:15.835] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.835] Info: Pushing 0x 2e to the stack.
+[19:39:15.835] Info: Pushing 0x 4de to the stack.
+[19:39:15.835] Info: Pushing 0x 3bd to the stack.
+[19:39:15.835] Info: Pushing 0x 27b to the stack.
+[19:39:15.835] Info: Pushing 0x 1f7 to the stack.
+[19:39:15.835] Info: Returning from 0x a8 to 0x 2e
+[19:39:15.836] Info: Pushing 0x 2b to the stack.
+[19:39:15.836] Info: Pushing 0x 466 to the stack.
+[19:39:15.836] Info: Pushing 0x 3cc to the stack.
+[19:39:15.836] Info: Pushing 0x 299 to the stack.
+[19:39:15.836] Info: Pushing 0x 132 to the stack.
+[19:39:15.836] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.836] Info: Pushing 0x 2e to the stack.
+[19:39:15.836] Info: Pushing 0x 465 to the stack.
+[19:39:15.836] Info: Pushing 0x 3ca to the stack.
+[19:39:15.837] Info: Pushing 0x 295 to the stack.
+[19:39:15.837] Info: Pushing 0x 12b to the stack.
+[19:39:15.837] Info: Returning from 0x a8 to 0x 2e
+[19:39:15.837] Info: Pushing 0x 2b to the stack.
+[19:39:15.837] Info: Pushing 0x 466 to the stack.
+[19:39:15.837] Info: Pushing 0x 3cc to the stack.
+[19:39:15.837] Info: Pushing 0x 299 to the stack.
+[19:39:15.837] Info: Pushing 0x 132 to the stack.
+[19:39:15.838] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.838] Info: Pushing 0x 2e to the stack.
+[19:39:15.838] Info: Pushing 0x 465 to the stack.
+[19:39:15.838] Info: Pushing 0x 3ca to the stack.
+[19:39:15.838] Info: Pushing 0x 295 to the stack.
+[19:39:15.838] Info: Pushing 0x 12b to the stack.
+[19:39:15.838] Info: Returning from 0x a8 to 0x 2e
+[19:39:15.838] Info: Pushing 0x 2b to the stack.
+[19:39:15.839] Info: Pushing 0x 4cc to the stack.
+[19:39:15.839] Info: Pushing 0x 399 to the stack.
+[19:39:15.839] Info: Pushing 0x 232 to the stack.
+[19:39:15.839] Info: Pushing 0x 165 to the stack.
+[19:39:15.839] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.839] Info: Pushing 0x 2e to the stack.
+[19:39:15.839] Info: Pushing 0x 4ca to the stack.
+[19:39:15.839] Info: Pushing 0x 395 to the stack.
+[19:39:15.840] Info: Pushing 0x 22b to the stack.
+[19:39:15.840] Info: Pushing 0x 156 to the stack.
+[19:39:15.840] Info: Returning from 0x a8 to 0x 2e
+[19:39:15.840] Info: Pushing 0x 2b to the stack.
+[19:39:15.840] Info: Pushing 0x 40d to the stack.
+[19:39:15.840] Info: Pushing 0x 31a to the stack.
+[19:39:15.840] Info: Pushing 0x 234 to the stack.
+[19:39:15.840] Info: Pushing 0x 169 to the stack.
+[19:39:15.841] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.841] Info: Pushing 0x 2e to the stack.
+[19:39:15.841] Info: Pushing 0x 4d2 to the stack.
+[19:39:15.841] Info: Pushing 0x 3a4 to the stack.
+[19:39:15.841] Info: Pushing 0x 248 to the stack.
+[19:39:15.841] Info: Pushing 0x 190 to the stack.
+[19:39:15.841] Info: Returning from 0x a8 to 0x 2e
+[19:39:15.841] Info: Pushing 0x 2b to the stack.
+[19:39:15.841] Info: Pushing 0x 400 to the stack.
+[19:39:15.842] Info: Pushing 0x 300 to the stack.
+[19:39:15.842] Info: Pushing 0x 200 to the stack.
+[19:39:15.842] Info: Pushing 0x 100 to the stack.
+[19:39:15.842] Info: Returning from 0x a8 to 0x 2b
+[19:39:15.842] Info: Pushing 0x 2e to the stack.
+[19:39:15.842] Info: Pushing 0x 400 to the stack.
+[19:39:17.652] Info: Pushing 0x 300 to the stack.
+[19:39:17.652] Info: Pushing 0x 200 to the stack.
+[19:39:17.652] Info: Pushing 0x 100 to the stack.
+[19:39:17.652] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.652] Info: Pushing 0x 2b to the stack.
+[19:39:17.652] Info: Pushing 0x 40b to the stack.
+[19:39:17.652] Info: Pushing 0x 316 to the stack.
+[19:39:17.653] Info: Pushing 0x 22c to the stack.
+[19:39:17.653] Info: Pushing 0x 159 to the stack.
+[19:39:17.653] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.653] Info: Pushing 0x 2e to the stack.
+[19:39:17.653] Info: Pushing 0x 4b3 to the stack.
+[19:39:17.653] Info: Pushing 0x 366 to the stack.
+[19:39:17.653] Info: Pushing 0x 2cc to the stack.
+[19:39:17.653] Info: Pushing 0x 198 to the stack.
+[19:39:17.653] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.654] Info: Pushing 0x 2b to the stack.
+[19:39:17.654] Info: Pushing 0x 403 to the stack.
+[19:39:17.654] Info: Pushing 0x 306 to the stack.
+[19:39:17.654] Info: Pushing 0x 20c to the stack.
+[19:39:17.654] Info: Pushing 0x 118 to the stack.
+[19:39:17.654] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.654] Info: Pushing 0x 2e to the stack.
+[19:39:17.654] Info: Pushing 0x 431 to the stack.
+[19:39:17.655] Info: Pushing 0x 362 to the stack.
+[19:39:17.655] Info: Pushing 0x 2c4 to the stack.
+[19:39:17.655] Info: Pushing 0x 188 to the stack.
+[19:39:17.655] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.655] Info: Pushing 0x 2b to the stack.
+[19:39:17.655] Info: Pushing 0x 473 to the stack.
+[19:39:17.655] Info: Pushing 0x 3e6 to the stack.
+[19:39:17.655] Info: Pushing 0x 2cd to the stack.
+[19:39:17.655] Info: Pushing 0x 19a to the stack.
+[19:39:17.655] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.655] Info: Pushing 0x 2e to the stack.
+[19:39:17.656] Info: Pushing 0x 435 to the stack.
+[19:39:17.656] Info: Pushing 0x 36a to the stack.
+[19:39:17.656] Info: Pushing 0x 2d5 to the stack.
+[19:39:17.656] Info: Pushing 0x 1ab to the stack.
+[19:39:17.656] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.656] Info: Pushing 0x 2b to the stack.
+[19:39:17.656] Info: Pushing 0x 400 to the stack.
+[19:39:17.657] Info: Pushing 0x 300 to the stack.
+[19:39:17.657] Info: Pushing 0x 200 to the stack.
+[19:39:17.657] Info: Pushing 0x 100 to the stack.
+[19:39:17.657] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.658] Info: Pushing 0x 2e to the stack.
+[19:39:17.658] Info: Pushing 0x 400 to the stack.
+[19:39:17.658] Info: Pushing 0x 300 to the stack.
+[19:39:17.658] Info: Pushing 0x 200 to the stack.
+[19:39:17.658] Info: Pushing 0x 100 to the stack.
+[19:39:17.658] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.658] Info: Pushing 0x 2b to the stack.
+[19:39:17.658] Info: Pushing 0x 483 to the stack.
+[19:39:17.658] Info: Pushing 0x 307 to the stack.
+[19:39:17.658] Info: Pushing 0x 20e to the stack.
+[19:39:17.660] Info: Pushing 0x 11c to the stack.
+[19:39:17.660] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.660] Info: Pushing 0x 2e to the stack.
+[19:39:17.660] Info: Pushing 0x 439 to the stack.
+[19:39:17.667] Info: Pushing 0x 373 to the stack.
+[19:39:17.668] Info: Pushing 0x 2e6 to the stack.
+[19:39:17.668] Info: Pushing 0x 1cc to the stack.
+[19:39:17.668] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.668] Info: Pushing 0x 2b to the stack.
+[19:39:17.668] Info: Pushing 0x 400 to the stack.
+[19:39:17.668] Info: Pushing 0x 300 to the stack.
+[19:39:17.668] Info: Pushing 0x 200 to the stack.
+[19:39:17.668] Info: Pushing 0x 100 to the stack.
+[19:39:17.668] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.668] Info: Pushing 0x 2e to the stack.
+[19:39:17.668] Info: Pushing 0x 400 to the stack.
+[19:39:17.669] Info: Pushing 0x 300 to the stack.
+[19:39:17.669] Info: Pushing 0x 200 to the stack.
+[19:39:17.669] Info: Pushing 0x 100 to the stack.
+[19:39:17.669] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.669] Info: Pushing 0x 2b to the stack.
+[19:39:17.669] Info: Pushing 0x 40c to the stack.
+[19:39:17.669] Info: Pushing 0x 318 to the stack.
+[19:39:17.669] Info: Pushing 0x 230 to the stack.
+[19:39:17.669] Info: Pushing 0x 161 to the stack.
+[19:39:17.669] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.670] Info: Pushing 0x 2e to the stack.
+[19:39:17.670] Info: Pushing 0x 4c2 to the stack.
+[19:39:17.670] Info: Pushing 0x 384 to the stack.
+[19:39:17.670] Info: Pushing 0x 208 to the stack.
+[19:39:17.670] Info: Pushing 0x 110 to the stack.
+[19:39:17.670] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.670] Info: Pushing 0x 2b to the stack.
+[19:39:17.670] Info: Pushing 0x 400 to the stack.
+[19:39:17.670] Info: Pushing 0x 300 to the stack.
+[19:39:17.670] Info: Pushing 0x 200 to the stack.
+[19:39:17.670] Info: Pushing 0x 100 to the stack.
+[19:39:17.671] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.671] Info: Pushing 0x 2e to the stack.
+[19:39:17.671] Info: Pushing 0x 400 to the stack.
+[19:39:17.671] Info: Pushing 0x 300 to the stack.
+[19:39:17.671] Info: Pushing 0x 200 to the stack.
+[19:39:17.671] Info: Pushing 0x 100 to the stack.
+[19:39:17.671] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.671] Info: Pushing 0x 2b to the stack.
+[19:39:17.671] Info: Pushing 0x 40d to the stack.
+[19:39:17.671] Info: Pushing 0x 31a to the stack.
+[19:39:17.671] Info: Pushing 0x 234 to the stack.
+[19:39:17.671] Info: Pushing 0x 169 to the stack.
+[19:39:17.671] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.671] Info: Pushing 0x 2e to the stack.
+[19:39:17.672] Info: Pushing 0x 4d2 to the stack.
+[19:39:17.672] Info: Pushing 0x 3a4 to the stack.
+[19:39:17.672] Info: Pushing 0x 248 to the stack.
+[19:39:17.672] Info: Pushing 0x 190 to the stack.
+[19:39:17.672] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.672] Info: Pushing 0x 2b to the stack.
+[19:39:17.672] Info: Pushing 0x 400 to the stack.
+[19:39:17.672] Info: Pushing 0x 300 to the stack.
+[19:39:17.672] Info: Pushing 0x 200 to the stack.
+[19:39:17.672] Info: Pushing 0x 100 to the stack.
+[19:39:17.672] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.672] Info: Pushing 0x 2e to the stack.
+[19:39:17.673] Info: Pushing 0x 400 to the stack.
+[19:39:17.673] Info: Pushing 0x 300 to the stack.
+[19:39:17.673] Info: Pushing 0x 200 to the stack.
+[19:39:17.673] Info: Pushing 0x 100 to the stack.
+[19:39:17.673] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.673] Info: Pushing 0x 2b to the stack.
+[19:39:17.673] Info: Pushing 0x 408 to the stack.
+[19:39:17.673] Info: Pushing 0x 310 to the stack.
+[19:39:17.673] Info: Pushing 0x 220 to the stack.
+[19:39:17.673] Info: Pushing 0x 141 to the stack.
+[19:39:17.673] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.673] Info: Pushing 0x 2e to the stack.
+[19:39:17.673] Info: Pushing 0x 482 to the stack.
+[19:39:17.673] Info: Pushing 0x 304 to the stack.
+[19:39:17.674] Info: Pushing 0x 208 to the stack.
+[19:39:17.674] Info: Pushing 0x 110 to the stack.
+[19:39:17.674] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.674] Info: Pushing 0x 2b to the stack.
+[19:39:17.674] Info: Pushing 0x 411 to the stack.
+[19:39:17.674] Info: Pushing 0x 322 to the stack.
+[19:39:17.674] Info: Pushing 0x 244 to the stack.
+[19:39:17.674] Info: Pushing 0x 188 to the stack.
+[19:39:17.674] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.674] Info: Pushing 0x 2e to the stack.
+[19:39:17.674] Info: Pushing 0x 410 to the stack.
+[19:39:17.675] Info: Pushing 0x 320 to the stack.
+[19:39:17.675] Info: Pushing 0x 240 to the stack.
+[19:39:17.675] Info: Pushing 0x 180 to the stack.
+[19:39:17.675] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.675] Info: Pushing 0x 2b to the stack.
+[19:39:17.675] Info: Pushing 0x 41f to the stack.
+[19:39:17.675] Info: Pushing 0x 33e to the stack.
+[19:39:17.675] Info: Pushing 0x 27c to the stack.
+[19:39:17.675] Info: Pushing 0x 1f9 to the stack.
+[19:39:17.675] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.675] Info: Pushing 0x 2e to the stack.
+[19:39:17.675] Info: Pushing 0x 4f3 to the stack.
+[19:39:17.675] Info: Pushing 0x 3e6 to the stack.
+[19:39:17.676] Info: Pushing 0x 2cc to the stack.
+[19:39:17.676] Info: Pushing 0x 198 to the stack.
+[19:39:17.676] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.676] Info: Pushing 0x 2b to the stack.
+[19:39:17.676] Info: Pushing 0x 488 to the stack.
+[19:39:17.676] Info: Pushing 0x 311 to the stack.
+[19:39:17.676] Info: Pushing 0x 222 to the stack.
+[19:39:17.676] Info: Pushing 0x 145 to the stack.
+[19:39:17.676] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.676] Info: Pushing 0x 2e to the stack.
+[19:39:17.676] Info: Pushing 0x 48a to the stack.
+[19:39:17.676] Info: Pushing 0x 315 to the stack.
+[19:39:17.676] Info: Pushing 0x 22a to the stack.
+[19:39:17.677] Info: Pushing 0x 154 to the stack.
+[19:39:17.677] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.677] Info: Pushing 0x 2b to the stack.
+[19:39:17.677] Info: Pushing 0x 489 to the stack.
+[19:39:17.677] Info: Pushing 0x 313 to the stack.
+[19:39:17.677] Info: Pushing 0x 226 to the stack.
+[19:39:17.677] Info: Pushing 0x 14d to the stack.
+[19:39:17.677] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.677] Info: Pushing 0x 2e to the stack.
+[19:39:17.677] Info: Pushing 0x 49a to the stack.
+[19:39:17.678] Info: Pushing 0x 335 to the stack.
+[19:39:17.678] Info: Pushing 0x 26a to the stack.
+[19:39:17.678] Info: Pushing 0x 1d4 to the stack.
+[19:39:17.678] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.678] Info: Pushing 0x 2b to the stack.
+[19:39:17.678] Info: Pushing 0x 400 to the stack.
+[19:39:17.678] Info: Pushing 0x 300 to the stack.
+[19:39:17.678] Info: Pushing 0x 200 to the stack.
+[19:39:17.678] Info: Pushing 0x 100 to the stack.
+[19:39:17.678] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.678] Info: Pushing 0x 2e to the stack.
+[19:39:17.678] Info: Pushing 0x 400 to the stack.
+[19:39:17.678] Info: Pushing 0x 300 to the stack.
+[19:39:17.678] Info: Pushing 0x 200 to the stack.
+[19:39:17.680] Info: Pushing 0x 100 to the stack.
+[19:39:17.680] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.680] Info: Pushing 0x 2b to the stack.
+[19:39:17.680] Info: Pushing 0x 40e to the stack.
+[19:39:17.680] Info: Pushing 0x 31c to the stack.
+[19:39:17.680] Info: Pushing 0x 238 to the stack.
+[19:39:17.680] Info: Pushing 0x 171 to the stack.
+[19:39:17.680] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.680] Info: Pushing 0x 2e to the stack.
+[19:39:17.681] Info: Pushing 0x 4e3 to the stack.
+[19:39:17.681] Info: Pushing 0x 3c6 to the stack.
+[19:39:17.681] Info: Pushing 0x 28c to the stack.
+[19:39:17.681] Info: Pushing 0x 118 to the stack.
+[19:39:17.681] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.681] Info: Pushing 0x 2b to the stack.
+[19:39:17.681] Info: Pushing 0x 4dc to the stack.
+[19:39:17.681] Info: Pushing 0x 3b9 to the stack.
+[19:39:17.681] Info: Pushing 0x 272 to the stack.
+[19:39:17.682] Info: Pushing 0x 1e5 to the stack.
+[19:39:17.682] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.682] Info: Pushing 0x 2e to the stack.
+[19:39:17.682] Info: Pushing 0x 4ca to the stack.
+[19:39:17.682] Info: Pushing 0x 395 to the stack.
+[19:39:17.682] Info: Pushing 0x 22b to the stack.
+[19:39:17.682] Info: Pushing 0x 156 to the stack.
+[19:39:17.682] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.682] Info: Pushing 0x 2b to the stack.
+[19:39:17.682] Info: Pushing 0x 4cc to the stack.
+[19:39:17.682] Info: Pushing 0x 399 to the stack.
+[19:39:17.682] Info: Pushing 0x 232 to the stack.
+[19:39:17.682] Info: Pushing 0x 165 to the stack.
+[19:39:17.683] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.683] Info: Pushing 0x 2e to the stack.
+[19:39:17.683] Info: Pushing 0x 4ca to the stack.
+[19:39:17.683] Info: Pushing 0x 395 to the stack.
+[19:39:17.683] Info: Pushing 0x 22b to the stack.
+[19:39:17.683] Info: Pushing 0x 156 to the stack.
+[19:39:17.683] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.683] Info: Pushing 0x 2b to the stack.
+[19:39:17.683] Info: Pushing 0x 46e to the stack.
+[19:39:17.683] Info: Pushing 0x 3dc to the stack.
+[19:39:17.683] Info: Pushing 0x 2b9 to the stack.
+[19:39:17.683] Info: Pushing 0x 173 to the stack.
+[19:39:17.683] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.683] Info: Pushing 0x 2e to the stack.
+[19:39:17.684] Info: Pushing 0x 4e7 to the stack.
+[19:39:17.684] Info: Pushing 0x 3ce to the stack.
+[19:39:17.684] Info: Pushing 0x 29d to the stack.
+[19:39:17.684] Info: Pushing 0x 13b to the stack.
+[19:39:17.684] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.684] Info: Pushing 0x 2b to the stack.
+[19:39:17.684] Info: Pushing 0x 4e6 to the stack.
+[19:39:17.684] Info: Pushing 0x 3cd to the stack.
+[19:39:17.684] Info: Pushing 0x 29b to the stack.
+[19:39:17.684] Info: Pushing 0x 136 to the stack.
+[19:39:17.684] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.684] Info: Pushing 0x 2e to the stack.
+[19:39:17.684] Info: Pushing 0x 46d to the stack.
+[19:39:17.684] Info: Pushing 0x 3db to the stack.
+[19:39:17.685] Info: Pushing 0x 2b7 to the stack.
+[19:39:17.685] Info: Pushing 0x 16f to the stack.
+[19:39:17.685] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.685] Info: Pushing 0x 2b to the stack.
+[19:39:17.685] Info: Pushing 0x 4dd to the stack.
+[19:39:17.685] Info: Pushing 0x 3bb to the stack.
+[19:39:17.685] Info: Pushing 0x 276 to the stack.
+[19:39:17.685] Info: Pushing 0x 1ed to the stack.
+[19:39:17.686] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.686] Info: Pushing 0x 2e to the stack.
+[19:39:17.686] Info: Pushing 0x 4da to the stack.
+[19:39:17.686] Info: Pushing 0x 3b5 to the stack.
+[19:39:17.686] Info: Pushing 0x 26b to the stack.
+[19:39:17.686] Info: Pushing 0x 1d6 to the stack.
+[19:39:17.686] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.686] Info: Pushing 0x 2b to the stack.
+[19:39:17.686] Info: Pushing 0x 4dd to the stack.
+[19:39:17.686] Info: Pushing 0x 3bb to the stack.
+[19:39:17.686] Info: Pushing 0x 276 to the stack.
+[19:39:17.686] Info: Pushing 0x 1ed to the stack.
+[19:39:17.686] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.686] Info: Pushing 0x 2e to the stack.
+[19:39:17.688] Info: Pushing 0x 4da to the stack.
+[19:39:17.688] Info: Pushing 0x 3b5 to the stack.
+[19:39:17.688] Info: Pushing 0x 26b to the stack.
+[19:39:17.688] Info: Pushing 0x 1d6 to the stack.
+[19:39:17.689] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.689] Info: Pushing 0x 2b to the stack.
+[19:39:17.689] Info: Pushing 0x 4d9 to the stack.
+[19:39:17.689] Info: Pushing 0x 3b3 to the stack.
+[19:39:17.689] Info: Pushing 0x 266 to the stack.
+[19:39:17.689] Info: Pushing 0x 1cd to the stack.
+[19:39:17.689] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.689] Info: Pushing 0x 2e to the stack.
+[19:39:17.689] Info: Pushing 0x 49a to the stack.
+[19:39:17.689] Info: Pushing 0x 335 to the stack.
+[19:39:17.689] Info: Pushing 0x 26b to the stack.
+[19:39:17.689] Info: Pushing 0x 1d6 to the stack.
+[19:39:17.689] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.689] Info: Pushing 0x 2b to the stack.
+[19:39:17.689] Info: Pushing 0x 499 to the stack.
+[19:39:17.689] Info: Pushing 0x 333 to the stack.
+[19:39:17.689] Info: Pushing 0x 266 to the stack.
+[19:39:17.690] Info: Pushing 0x 1cd to the stack.
+[19:39:17.690] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.690] Info: Pushing 0x 2e to the stack.
+[19:39:17.690] Info: Pushing 0x 49a to the stack.
+[19:39:17.690] Info: Pushing 0x 335 to the stack.
+[19:39:17.690] Info: Pushing 0x 26a to the stack.
+[19:39:17.690] Info: Pushing 0x 1d4 to the stack.
+[19:39:17.690] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.690] Info: Pushing 0x 2b to the stack.
+[19:39:17.690] Info: Pushing 0x 4bb to the stack.
+[19:39:17.690] Info: Pushing 0x 377 to the stack.
+[19:39:17.690] Info: Pushing 0x 2ef to the stack.
+[19:39:17.690] Info: Pushing 0x 1df to the stack.
+[19:39:17.690] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.690] Info: Pushing 0x 2e to the stack.
+[19:39:17.690] Info: Pushing 0x 4bf to the stack.
+[19:39:17.691] Info: Pushing 0x 37f to the stack.
+[19:39:17.691] Info: Pushing 0x 2fe to the stack.
+[19:39:17.691] Info: Pushing 0x 1fd to the stack.
+[19:39:17.691] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.691] Info: Pushing 0x 2b to the stack.
+[19:39:17.691] Info: Pushing 0x 4bb to the stack.
+[19:39:17.691] Info: Pushing 0x 377 to the stack.
+[19:39:17.691] Info: Pushing 0x 2ef to the stack.
+[19:39:17.691] Info: Pushing 0x 1df to the stack.
+[19:39:17.691] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.691] Info: Pushing 0x 2e to the stack.
+[19:39:17.691] Info: Pushing 0x 4bf to the stack.
+[19:39:17.691] Info: Pushing 0x 37f to the stack.
+[19:39:17.691] Info: Pushing 0x 2fe to the stack.
+[19:39:17.691] Info: Pushing 0x 1fd to the stack.
+[19:39:17.691] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.691] Info: Pushing 0x 2b to the stack.
+[19:39:17.691] Info: Pushing 0x 467 to the stack.
+[19:39:17.692] Info: Pushing 0x 3ce to the stack.
+[19:39:17.692] Info: Pushing 0x 29d to the stack.
+[19:39:17.694] Info: Pushing 0x 13a to the stack.
+[19:39:17.694] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.694] Info: Pushing 0x 2e to the stack.
+[19:39:17.694] Info: Pushing 0x 475 to the stack.
+[19:39:17.694] Info: Pushing 0x 3ea to the stack.
+[19:39:17.694] Info: Pushing 0x 2d5 to the stack.
+[19:39:17.694] Info: Pushing 0x 1ab to the stack.
+[19:39:17.694] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.694] Info: Pushing 0x 2b to the stack.
+[19:39:17.694] Info: Pushing 0x 463 to the stack.
+[19:39:17.694] Info: Pushing 0x 3c6 to the stack.
+[19:39:17.694] Info: Pushing 0x 28d to the stack.
+[19:39:17.694] Info: Pushing 0x 11a to the stack.
+[19:39:17.694] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.694] Info: Pushing 0x 2e to the stack.
+[19:39:17.694] Info: Pushing 0x 435 to the stack.
+[19:39:17.694] Info: Pushing 0x 36a to the stack.
+[19:39:17.694] Info: Pushing 0x 2d5 to the stack.
+[19:39:17.695] Info: Pushing 0x 1ab to the stack.
+[19:39:17.695] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.695] Info: Pushing 0x 2b to the stack.
+[19:39:17.695] Info: Pushing 0x 46e to the stack.
+[19:39:17.695] Info: Pushing 0x 3dc to the stack.
+[19:39:17.695] Info: Pushing 0x 2b9 to the stack.
+[19:39:17.695] Info: Pushing 0x 173 to the stack.
+[19:39:17.695] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.695] Info: Pushing 0x 2e to the stack.
+[19:39:17.695] Info: Pushing 0x 4e7 to the stack.
+[19:39:17.695] Info: Pushing 0x 3ce to the stack.
+[19:39:17.695] Info: Pushing 0x 29d to the stack.
+[19:39:17.695] Info: Pushing 0x 13b to the stack.
+[19:39:17.695] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.696] Info: Pushing 0x 2b to the stack.
+[19:39:17.696] Info: Pushing 0x 40e to the stack.
+[19:39:17.696] Info: Pushing 0x 31c to the stack.
+[19:39:17.696] Info: Pushing 0x 238 to the stack.
+[19:39:17.696] Info: Pushing 0x 171 to the stack.
+[19:39:17.696] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.696] Info: Pushing 0x 2e to the stack.
+[19:39:17.696] Info: Pushing 0x 4e3 to the stack.
+[19:39:17.696] Info: Pushing 0x 3c6 to the stack.
+[19:39:17.696] Info: Pushing 0x 28c to the stack.
+[19:39:17.696] Info: Pushing 0x 118 to the stack.
+[19:39:17.696] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.696] Info: Pushing 0x 2b to the stack.
+[19:39:17.696] Info: Pushing 0x 4ec to the stack.
+[19:39:17.696] Info: Pushing 0x 3d9 to the stack.
+[19:39:17.696] Info: Pushing 0x 2b3 to the stack.
+[19:39:17.696] Info: Pushing 0x 167 to the stack.
+[19:39:17.696] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.696] Info: Pushing 0x 2e to the stack.
+[19:39:17.696] Info: Pushing 0x 4ce to the stack.
+[19:39:17.697] Info: Pushing 0x 39d to the stack.
+[19:39:17.697] Info: Pushing 0x 23b to the stack.
+[19:39:17.697] Info: Pushing 0x 177 to the stack.
+[19:39:17.697] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.697] Info: Pushing 0x 2b to the stack.
+[19:39:17.698] Info: Pushing 0x 4cc to the stack.
+[19:39:17.698] Info: Pushing 0x 399 to the stack.
+[19:39:17.698] Info: Pushing 0x 232 to the stack.
+[19:39:17.698] Info: Pushing 0x 165 to the stack.
+[19:39:17.698] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.698] Info: Pushing 0x 2e to the stack.
+[19:39:17.698] Info: Pushing 0x 4ca to the stack.
+[19:39:17.698] Info: Pushing 0x 395 to the stack.
+[19:39:17.698] Info: Pushing 0x 22b to the stack.
+[19:39:17.698] Info: Pushing 0x 156 to the stack.
+[19:39:17.699] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.699] Info: Pushing 0x 2b to the stack.
+[19:39:17.699] Info: Pushing 0x 4dd to the stack.
+[19:39:17.699] Info: Pushing 0x 3bb to the stack.
+[19:39:17.699] Info: Pushing 0x 276 to the stack.
+[19:39:17.699] Info: Pushing 0x 1ed to the stack.
+[19:39:17.699] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.699] Info: Pushing 0x 2e to the stack.
+[19:39:17.699] Info: Pushing 0x 4da to the stack.
+[19:39:17.699] Info: Pushing 0x 3b5 to the stack.
+[19:39:17.699] Info: Pushing 0x 26b to the stack.
+[19:39:17.699] Info: Pushing 0x 1d6 to the stack.
+[19:39:17.699] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.699] Info: Pushing 0x 2b to the stack.
+[19:39:17.699] Info: Pushing 0x 4dc to the stack.
+[19:39:17.699] Info: Pushing 0x 3b9 to the stack.
+[19:39:17.699] Info: Pushing 0x 272 to the stack.
+[19:39:17.700] Info: Pushing 0x 1e5 to the stack.
+[19:39:17.700] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.700] Info: Pushing 0x 2e to the stack.
+[19:39:17.700] Info: Pushing 0x 4ca to the stack.
+[19:39:17.700] Info: Pushing 0x 395 to the stack.
+[19:39:17.700] Info: Pushing 0x 22b to the stack.
+[19:39:17.700] Info: Pushing 0x 156 to the stack.
+[19:39:17.700] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.700] Info: Pushing 0x 2b to the stack.
+[19:39:17.700] Info: Pushing 0x 499 to the stack.
+[19:39:17.700] Info: Pushing 0x 333 to the stack.
+[19:39:17.700] Info: Pushing 0x 266 to the stack.
+[19:39:17.700] Info: Pushing 0x 1cd to the stack.
+[19:39:17.700] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.700] Info: Pushing 0x 2e to the stack.
+[19:39:17.700] Info: Pushing 0x 49a to the stack.
+[19:39:17.700] Info: Pushing 0x 335 to the stack.
+[19:39:17.700] Info: Pushing 0x 26a to the stack.
+[19:39:17.701] Info: Pushing 0x 1d4 to the stack.
+[19:39:17.701] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.702] Info: Pushing 0x 2b to the stack.
+[19:39:17.702] Info: Pushing 0x 49f to the stack.
+[19:39:17.702] Info: Pushing 0x 33f to the stack.
+[19:39:17.702] Info: Pushing 0x 27e to the stack.
+[19:39:17.702] Info: Pushing 0x 1fd to the stack.
+[19:39:17.702] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.702] Info: Pushing 0x 2e to the stack.
+[19:39:17.702] Info: Pushing 0x 4fb to the stack.
+[19:39:17.702] Info: Pushing 0x 3f7 to the stack.
+[19:39:17.702] Info: Pushing 0x 2ee to the stack.
+[19:39:17.702] Info: Pushing 0x 1dc to the stack.
+[19:39:17.702] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.702] Info: Pushing 0x 2b to the stack.
+[19:39:17.702] Info: Pushing 0x 4bb to the stack.
+[19:39:17.702] Info: Pushing 0x 377 to the stack.
+[19:39:17.702] Info: Pushing 0x 2ef to the stack.
+[19:39:17.703] Info: Pushing 0x 1df to the stack.
+[19:39:17.703] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.703] Info: Pushing 0x 2e to the stack.
+[19:39:17.703] Info: Pushing 0x 4bf to the stack.
+[19:39:17.703] Info: Pushing 0x 37f to the stack.
+[19:39:17.703] Info: Pushing 0x 2fe to the stack.
+[19:39:17.703] Info: Pushing 0x 1fd to the stack.
+[19:39:17.703] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.703] Info: Pushing 0x 2b to the stack.
+[19:39:17.703] Info: Pushing 0x 4b9 to the stack.
+[19:39:17.703] Info: Pushing 0x 373 to the stack.
+[19:39:17.703] Info: Pushing 0x 2e7 to the stack.
+[19:39:17.703] Info: Pushing 0x 1cf to the stack.
+[19:39:17.703] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.703] Info: Pushing 0x 2e to the stack.
+[19:39:17.703] Info: Pushing 0x 49e to the stack.
+[19:39:17.703] Info: Pushing 0x 33d to the stack.
+[19:39:17.703] Info: Pushing 0x 27a to the stack.
+[19:39:17.705] Info: Pushing 0x 1f5 to the stack.
+[19:39:17.705] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.705] Info: Pushing 0x 2b to the stack.
+[19:39:17.705] Info: Pushing 0x 433 to the stack.
+[19:39:17.705] Info: Pushing 0x 366 to the stack.
+[19:39:17.705] Info: Pushing 0x 2cd to the stack.
+[19:39:17.705] Info: Pushing 0x 19a to the stack.
+[19:39:17.705] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.705] Info: Pushing 0x 2e to the stack.
+[19:39:17.705] Info: Pushing 0x 435 to the stack.
+[19:39:17.705] Info: Pushing 0x 36a to the stack.
+[19:39:17.705] Info: Pushing 0x 2d4 to the stack.
+[19:39:17.705] Info: Pushing 0x 1a9 to the stack.
+[19:39:17.705] Info: Returning from 0x a8 to 0x 2e
+[19:39:17.705] Info: Pushing 0x 2b to the stack.
+[19:39:17.705] Info: Pushing 0x 43e to the stack.
+[19:39:17.705] Info: Pushing 0x 37c to the stack.
+[19:39:17.705] Info: Pushing 0x 2f9 to the stack.
+[19:39:17.705] Info: Pushing 0x 1f3 to the stack.
+[19:39:17.705] Info: Returning from 0x a8 to 0x 2b
+[19:39:17.705] Info: Pushing 0x 2e to the stack.
+[19:39:17.705] Info: Pushing 0x 4e7 to the stack.
+[19:39:17.706] Info: Pushing 0x 3ce to the stack.
+[19:39:17.706] Info: Pushing 0x 29c to the stack.
+[19:39:17.706] Info: Pushing 0x 139 to the stack.
+[19:39:17.706] Info: Returning from 0x a8 to 0x 2e
+[19:39:22.038] Info: Pushing 0x 153 to the stack.
+[19:39:22.042] Info: Returning from 0x2c20 to 0x 153
+[19:39:22.042] Info: Pushing 0x 159 to the stack.
+[19:39:22.042] Info: Returning from 0x2b94 to 0x 159
+[19:39:22.042] Info: Pushing 0x 15e to the stack.
+[19:39:22.056] Info: Returning from 0x2c47 to 0x 15e
+[19:39:22.136] Info: Pushing 0x 19a to the stack.
+[19:39:22.136] Info: Returning from 0x71de to 0x 19a
+[19:39:22.136] Info: Pushing 0x 1c5 to the stack.
+[19:39:22.136] Info: Pushing 0x1f8c to the stack.
+[19:39:22.136] Info: Pushing 0x 80 to the stack.
+[19:39:22.136] Info: Pushing 0x ff to the stack.
+[19:39:22.136] Info: Pushing 0xf810 to the stack.
+[19:39:22.136] Info: Pushing 0xc000 to the stack.
+[19:39:22.139] Info: Returning from 0x2c38 to 0x1f8c
+[19:39:22.139] Info: Returning from 0x1f8d to 0x 1c5
+[19:39:22.139] Info: Pushing 0x 1c8 to the stack.
+[19:39:22.139] Info: Pushing 0x 203 to the stack.
+[19:39:22.139] Info: Pushing 0x 80 to the stack.
+[19:39:22.139] Info: Pushing 0x 168 to the stack.
+[19:39:22.139] Info: Pushing 0xf810 to the stack.
+[19:39:22.140] Info: Pushing 0xc100 to the stack.
+[19:39:22.144] Info: Returning from 0x2c38 to 0x 203
+[19:39:22.144] Info: Pushing 0x 206 to the stack.
+[19:39:22.144] Info: Pushing 0x 168 to the stack.
+[19:39:22.144] Info: Pushing 0x1b92 to the stack.
+[19:39:22.144] Info: Pushing 0x 168 to the stack.
+[19:39:22.144] Info: Pushing 0xf810 to the stack.
+[19:39:22.144] Info: Pushing 0xc100 to the stack.
+[19:39:22.144] Info: Pushing 0x 0 to the stack.
+[19:39:22.144] Info: Pushing 0x3eff to the stack.
+[19:39:22.144] Info: Returning from 0x27bd to 0x1b92
+[19:39:22.144] Info: Pushing 0xffa0 to the stack.
+[19:39:22.144] Info: Pushing 0x1b96 to the stack.
+[19:39:22.144] Info: Pushing 0xc100 to the stack.
+[19:39:22.144] Info: Pushing 0x 168 to the stack.
+[19:39:22.144] Info: Pushing 0x1bae to the stack.
+[19:39:22.144] Info: Pushing 0x 168 to the stack.
+[19:39:22.144] Info: Pushing 0xf810 to the stack.
+[19:39:22.145] Info: Returning from 0x1f4d to 0x1bae
+[19:39:22.145] Info: Returning from 0x1bc4 to 0x1b96
+[19:39:22.145] Info: Pushing 0x1b99 to the stack.
+[19:39:22.145] Info: Returning from 0x1f7f to 0x1b99
+[19:39:22.145] Warning: Attempting to write to ROM at address: 0x 0.
+[19:39:22.145] Info: Pushing 0x 167 to the stack.
+[19:39:22.145] Info: Pushing 0x1b92 to the stack.
+[19:39:22.145] Info: Pushing 0x 167 to the stack.
+[19:39:22.145] Info: Pushing 0x 0 to the stack.
+[19:39:22.145] Info: Pushing 0xc101 to the stack.
+[19:39:22.145] Info: Pushing 0x f to the stack.
+[19:39:22.145] Info: Pushing 0x3eff to the stack.
+[19:39:22.145] Info: Returning from 0x27bd to 0x1b92
+[19:39:22.145] Info: Pushing 0xffa0 to the stack.
+[19:39:22.145] Info: Pushing 0x1b96 to the stack.
+[19:39:22.145] Info: Pushing 0xc101 to the stack.
+[19:39:22.145] Info: Pushing 0x 167 to the stack.
+[19:39:22.145] Info: Pushing 0x1bae to the stack.
+[19:39:22.145] Info: Pushing 0x 167 to the stack.
+[19:39:22.145] Info: Pushing 0x 0 to the stack.
+[19:39:22.146] Info: Returning from 0x1f4d to 0x1bae
+[19:39:22.147] Info: Returning from 0x1bc4 to 0x1b96
+[19:39:22.147] Info: Pushing 0x1b99 to the stack.
+[19:39:22.147] Info: Returning from 0x1f7f to 0xffa0
+[19:39:22.147] Info: Pushing 0xfbc3 to the stack.
+[19:39:22.148] Info: Returning from 0xff8a to 0x 0
+[19:39:22.148] Info: Pushing 0xfbc3 to the stack.
+[19:39:22.152] Info: Returning from 0xff47 to 0x 0
+[19:39:22.152] Info: Pushing 0xfbc3 to the stack.
+[19:39:22.152] Error: Unimplemented opcode 0xfd at 0xfbc4!
diff --git a/playing-coffee/pom.xml b/playing-coffee/pom.xml
new file mode 100644
index 0000000..beaa1be
--- /dev/null
+++ b/playing-coffee/pom.xml
@@ -0,0 +1,15 @@
+
+ 4.0.0
+ sixtenhugosson
+ playing-coffee
+ 0.0.1-SNAPSHOT
+ playing-coffee
+ A GameBoy emulator written in Java.
+
+
+ junit
+ junit
+ 4.13.1
+
+
+
\ No newline at end of file
diff --git a/playing-coffee/roms/cgb_sound/cgb_sound.gb b/playing-coffee/roms/cgb_sound/cgb_sound.gb
new file mode 100644
index 0000000..dc50471
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/cgb_sound.gb differ
diff --git a/playing-coffee/roms/cgb_sound/readme.txt b/playing-coffee/roms/cgb_sound/readme.txt
new file mode 100644
index 0000000..5d8d188
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/readme.txt
@@ -0,0 +1,86 @@
+Game Boy Sound Hardware Tests
+-----------------------------
+These tests verify aspects of the sound hardware that the CPU can
+observe. The ROMs and GBSs are either for DMG or CGB hardware, as there
+are several differences.
+
+
+Multi-ROM
+---------
+In the main directory is a single ROM/GBS which runs all the tests. It
+prints a test's number, runs the test, then "ok" if it passes, otherwise
+a failure code. Once all tests have completed it either reports that all
+tests passed, or reports the number of the first failed test as the
+result code (1 = first). Finally, it makes several beeps. If a test
+fails, it can be run on its own by finding the corresponding ROM/GBS in
+the singles directories.
+
+Ths compact format on screen is to avoid having the results scroll off
+the top, so the test can be started and allowed to run without having to
+constantly monitor the display.
+
+
+Failure information
+-------------------
+For more information about a failure code or information printed, see
+the test's source code in source/. To find failure code N, search for
+"set_test N", which will usually be before the subtest which failed.
+
+
+Flashes, clicks, other glitches
+-------------------------------
+Some tests might need to turn the screen off and on, or cause slight
+audio clicks. This does not indicate failure, and should be ignored.
+Only the test result reported at the end is important, unless stated
+otherwise.
+
+
+LCD support
+-----------
+Tests generally print information on screen. The tests will work fine if
+run on an emulator with NO LCD support, or as an GBS which has no
+inherent screen; in particular, the VBL wait routine has a timeout in
+case LY doesn't reflect the current LCD line. The text printing will
+also work if the LCD doesn't support scrolling.
+
+
+Output to memory
+----------------
+Text output and the final result are also written to memory at $A000,
+allowing testing a very minimal emulator that supports little more than
+CPU and RAM. To reliably indicate that the data is from a test and not
+random data, $A001-$A003 are written with a signature: $DE,$B0,$61. If
+this is present, then the text string and final result status are valid.
+
+$A000 holds the overall status. If the test is still running, it holds
+$80, otherwise it holds the final result code.
+
+All text output is appended to a zero-terminated string at $A004. An
+emulator could regularly check this string for any additional
+characters, and output them, allowing real-time text output, rather than
+just printing the final output at the end.
+
+
+GBS versions
+------------
+Many GBS-based tests require that the GBS player either not interrupt
+the init routine with the play routine, or if they do, not interrupt the
+play routine again if it hasn't returned yet. This is because many tests
+need to run for a while without returning.
+
+In addition to the other text output methods described above, GBS builds
+report essential information bytes audibly, including the final result.
+A byte is reported as a series of tones. The code is in binary, with a
+low tone for 0 and a high tone for 1. The first tone is always a zero. A
+final code of 0 means passed, 1 means failure, and 2 or higher indicates
+a specific reason as listed in the source code by the corresponding
+set_code line. Examples:
+
+Tones Binary Decimal Meaning
+- - - - - - - - - - - - - - - - - - - -
+low 0 0 passed
+low high 01 1 failed
+low high low 010 2 error 2
+
+--
+Shay Green
diff --git a/playing-coffee/roms/cgb_sound/rom_singles/01-registers.gb b/playing-coffee/roms/cgb_sound/rom_singles/01-registers.gb
new file mode 100644
index 0000000..be180ba
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/rom_singles/01-registers.gb differ
diff --git a/playing-coffee/roms/cgb_sound/rom_singles/02-len ctr.gb b/playing-coffee/roms/cgb_sound/rom_singles/02-len ctr.gb
new file mode 100644
index 0000000..eb5ca82
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/rom_singles/02-len ctr.gb differ
diff --git a/playing-coffee/roms/cgb_sound/rom_singles/03-trigger.gb b/playing-coffee/roms/cgb_sound/rom_singles/03-trigger.gb
new file mode 100644
index 0000000..28b3586
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/rom_singles/03-trigger.gb differ
diff --git a/playing-coffee/roms/cgb_sound/rom_singles/04-sweep.gb b/playing-coffee/roms/cgb_sound/rom_singles/04-sweep.gb
new file mode 100644
index 0000000..75d6366
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/rom_singles/04-sweep.gb differ
diff --git a/playing-coffee/roms/cgb_sound/rom_singles/05-sweep details.gb b/playing-coffee/roms/cgb_sound/rom_singles/05-sweep details.gb
new file mode 100644
index 0000000..383e0c1
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/rom_singles/05-sweep details.gb differ
diff --git a/playing-coffee/roms/cgb_sound/rom_singles/06-overflow on trigger.gb b/playing-coffee/roms/cgb_sound/rom_singles/06-overflow on trigger.gb
new file mode 100644
index 0000000..b2d5c75
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/rom_singles/06-overflow on trigger.gb differ
diff --git a/playing-coffee/roms/cgb_sound/rom_singles/07-len sweep period sync.gb b/playing-coffee/roms/cgb_sound/rom_singles/07-len sweep period sync.gb
new file mode 100644
index 0000000..31cce89
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/rom_singles/07-len sweep period sync.gb differ
diff --git a/playing-coffee/roms/cgb_sound/rom_singles/08-len ctr during power.gb b/playing-coffee/roms/cgb_sound/rom_singles/08-len ctr during power.gb
new file mode 100644
index 0000000..a3120eb
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/rom_singles/08-len ctr during power.gb differ
diff --git a/playing-coffee/roms/cgb_sound/rom_singles/09-wave read while on.gb b/playing-coffee/roms/cgb_sound/rom_singles/09-wave read while on.gb
new file mode 100644
index 0000000..1e4a78c
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/rom_singles/09-wave read while on.gb differ
diff --git a/playing-coffee/roms/cgb_sound/rom_singles/10-wave trigger while on.gb b/playing-coffee/roms/cgb_sound/rom_singles/10-wave trigger while on.gb
new file mode 100644
index 0000000..56f0e90
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/rom_singles/10-wave trigger while on.gb differ
diff --git a/playing-coffee/roms/cgb_sound/rom_singles/11-regs after power.gb b/playing-coffee/roms/cgb_sound/rom_singles/11-regs after power.gb
new file mode 100644
index 0000000..f6c84b4
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/rom_singles/11-regs after power.gb differ
diff --git a/playing-coffee/roms/cgb_sound/rom_singles/12-wave.gb b/playing-coffee/roms/cgb_sound/rom_singles/12-wave.gb
new file mode 100644
index 0000000..e488e5e
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/rom_singles/12-wave.gb differ
diff --git a/playing-coffee/roms/cgb_sound/source/01-registers.s b/playing-coffee/roms/cgb_sound/source/01-registers.s
new file mode 100644
index 0000000..ea9f5d5
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/01-registers.s
@@ -0,0 +1,117 @@
+; - APU registers always have some bits set when read back.
+; - Wave memory can be read back freely.
+; - When powered off, registers are cleared, except high bit of NR52.
+; - While off, register writes are ignored, but not reads.
+; - Wave RAM is always readable and writable, and unaffected by power.
+
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ set_test 2,"NR10-NR51 and wave RAM write/read"
+ ld d,0
+- call test_rw
+ inc d
+ jr nz,-
+
+ set_test 3,"NR52 write/read"
+ wreg NR52,$00
+ lda NR52
+ cp $70
+ jp nz,test_failed
+ wreg NR52,$FF
+ lda NR52
+ cp $F0
+ jp nz,test_failed
+
+ set_test 4,"Powering APU shouldn't affect wave"
+ ld a,$37
+ call fill_wave
+ wreg NR52,$00
+
+ ; Verify that wave RAM is unchanged
+ ld hl,WAVE
+- ld a,(hl+)
+ cp $37
+ jp nz,test_failed
+ ld a,l
+ cp $40
+ jr nz,-
+ wreg NR52,$80 ; on
+
+ set_test 5,"Powering APU off should write 0 to all regs"
+ ld a,$FF
+ call fill_apu_regs
+ wreg NR52,$00
+ wreg NR52,$80
+ call regs_should_be_clear
+
+ set_test 6,"When off, should ignore writes to registers"
+ wreg NR52,$00
+ ld a,$FF
+ call fill_apu_regs
+ wreg NR52,$80
+ call regs_should_be_clear
+ wreg NR52,$80
+
+ set_test 7,"When off, should allow normal register reads"
+ wreg NR52,$00
+ call regs_should_be_clear
+ wreg NR52,$80
+
+ jp tests_passed
+
+regs_should_be_clear:
+ ld bc,masks
+ ld hl,NR10
+- ld a,(bc)
+ cp (hl)
+ jp nz,test_failed
+ inc bc
+ inc l
+ ld a,l
+ cp disabled clocks as well
+ ld a,1
+ call end
+
+ call begin
+ wchn 4,$40 ; enable length
+ wchn 1,-2 ; length = 2
+ wchn 4,$40 ; enabled -> enabled doesn't clock
+ wchn 4,$00 ; enabled -> disabled doesn't clock
+ ld a,2
+ call end
+.else
+ set_test 4,"Anything besides enabling shouldnt't clock"
+ call begin
+ wchn 4,$40 ; enable length
+ wchn 1,-2 ; length = 2
+ wchn 4,$40 ; enabled -> enabled doesn't clock
+ wchn 4,$00 ; enabled -> disabled doesn't clock
+ wchn 4,$00 ; disabled -> disabled doesn't clock
+ ld a,2
+ call end
+.endif
+
+ set_test 5,"If clock makes length zero, should disable chan"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ lda chan_mask
+ ld b,a
+ lda NR52 ; channel now disabled
+ and b
+ jp nz,test_failed
+
+ set_test 6,"If length already reached zero, shouldn't clock"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,0
+ wchn 4,$40 ; no clock; length still 0
+ wchn 4,0
+ wchn 4,$40 ; no clock; length still 0
+ lda chan_maxlen; end triggers channel, which loads it with max length
+ call end
+
+ set_test 7,"Trigger should un-freeze length that reached zero"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,$00 ; disable
+ wchn 4,$80 ; trigger unfreezes length, so it takes on maximum value
+ delay_clocks 8192
+ wchn 4,$40 ; enable
+ delay_apu 2 ; clock length by 2
+ lda chan_maxlen
+ sub 2
+ call end_nodelay
+
+ set_test 8,"Trigger that un-freezes enabled length should clock it"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,$00 ; disable
+ wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it
+ lda chan_maxlen
+ dec a
+ call end_nodelay
+
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it
+ lda chan_maxlen
+ dec a
+ call end_nodelay
+
+ set_test 9,"Triggering that clocks length of 1 ","should clock twice and shouldn't freeze"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$C0 ; trigger and enable
+ ; First length counter is enabled, which clocks it to 0 and freezes it
+ ; Trigger unfreezes length counter, which clocks it AGAIN
+ ; The result is the same as the previous test, which enables separately
+ lda chan_maxlen
+ dec a
+ call end_nodelay
+
+ set_test 10,"Trigger shouldn't otherwise affect length"
+ call begin
+ wchn 1,0 ; length = max
+ delay_clocks 8192
+ wchn 4,$80 ; trigger
+ lda chan_maxlen
+ call end_nodelay
+
+.ifndef CGB_02
+ call begin
+ wchn 1,0 ; length = max
+ wchn 4,$80 ; trigger
+ lda chan_maxlen
+ call end
+
+ call begin
+ wchn 1,-2 ; length = 2
+ wchn 4,$80 ; trigger
+ ld a,2
+ call end
+.endif
+
+ set_test 11,"Disabled DAC shouldn't stop other trigger effects"
+ call begin
+ wchn 0,$00 ; disable wave DAC
+ wchn 2,$07 ; disable square/noise DAC
+ wchn 1,-1
+ wchn 4,$C0 ; clocks length, which becomes max
+ wchn 0,$80 ; enable wave DAC
+ wchn 2,$08 ; enable square/noise DAC
+ wchn 4,$80 ; trigger
+ lda chan_maxlen
+ dec a
+ call end
+
+ set_test 12,"Other trigger effects should still occur when disabled"
+ call sync_apu
+ wchn 0,0
+ wchn 4,0
+ wchn 1,-1
+ wchn 4,$40 ; len = 0
+ wchn 4,0
+ wchn 4,$40 ; len = 0
+ wchn 4,$80 ; len = max
+ wchn 4,$40 ; len = max-1
+ wchn 4,0
+ wchn 4,$40 ; len = max-2
+ wchn 0,$80 ; enable now
+ wchn 4,$C0
+ lda chan_maxlen
+ sub 3
+ call delay_apu_cycles
+ lda chan_mask
+ ld b,a
+ lda NR52
+ and b
+ jp z,test_failed
+ delay_apu 1
+ lda NR52
+ and b
+ jp nz,test_failed
+
+ ret
diff --git a/playing-coffee/roms/cgb_sound/source/04-sweep.s b/playing-coffee/roms/cgb_sound/source/04-sweep.s
new file mode 100644
index 0000000..17ac4e4
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/04-sweep.s
@@ -0,0 +1,120 @@
+; Calc = calculation of new frequency and check for > $7FF
+; Update = modification of frequency with new calculated value
+.include "shell.inc"
+.include "apu.s"
+
+begin:
+ call sync_sweep
+ wreg NR14,$40
+ wreg NR11,-$21
+ wreg NR12,$08
+ ret
+
+should_be_almost_off:
+ lda NR52
+ and $01
+ jp z,test_failed
+ delay_apu 1
+should_be_off:
+ lda NR52
+ and $01
+ jp nz,test_failed
+ ret
+
+main:
+ set_test 2,"If shift>0, calculates on trigger"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$FF
+ wreg NR14,$C7
+ call should_be_off
+ call begin
+ wreg NR10,$11
+ wreg NR13,$FF
+ wreg NR14,$C7
+ call should_be_off
+
+ set_test 3,"If shift=0, doesn't calculate on trigger"
+ call begin
+ wreg NR10,$10
+ wreg NR13,$FF
+ wreg NR14,$C7
+ delay_apu 1
+ call should_be_almost_off
+
+ set_test 4,"If period=0, doesn't calculate"
+ call begin
+ wreg NR10,$00
+ wreg NR13,$FF
+ wreg NR14,$C7
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 5,"After updating frequency, calculates a second time"
+ call begin
+ wreg NR10,$11
+ wreg NR13,$00
+ wreg NR14,$C5
+ delay_apu 1
+ call should_be_almost_off
+
+ set_test 6,"If calculation>$7FF, disables channel"
+ call begin
+ wreg NR10,$02
+ wreg NR13,$67
+ wreg NR14,$C6
+ call should_be_off
+
+ set_test 7,"If calculation<=$7FF, doesn't disable channel"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$55
+ wreg NR14,$C5
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 8,"If shift=0 and period>0, trigger enables"
+ call begin
+ wreg NR10,$10
+ wreg NR13,$FF
+ wreg NR14,$C3
+ delay_apu 2
+ wreg NR10,$11
+ delay_apu 1
+ call should_be_almost_off
+
+ set_test 9,"If shift>0 and period=0, trigger enables"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$FF
+ wreg NR14,$C3
+ delay_apu 15
+ wreg NR10,$11
+ call should_be_almost_off
+
+ set_test 10,"If shift=0 and period=0, trigger disables"
+ call begin
+ wreg NR10,$08
+ wreg NR13,$FF
+ wreg NR14,$C3
+ wreg NR10,$11
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 11,"If shift=0, doesn't update"
+ call begin
+ wreg NR10,$10
+ wreg NR13,$FF
+ wreg NR14,$C3
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 12,"If period=0, doesn't update"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$00
+ wreg NR14,$C5
+ delay_apu $20
+ call should_be_almost_off
+
+ jp tests_passed
diff --git a/playing-coffee/roms/cgb_sound/source/05-sweep details.s b/playing-coffee/roms/cgb_sound/source/05-sweep details.s
new file mode 100644
index 0000000..1519b68
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/05-sweep details.s
@@ -0,0 +1,121 @@
+; Calc = calculation of new frequency and check for > $7FF
+; Update = modification of frequency with new calculated value
+.include "shell.inc"
+.include "apu.s"
+
+begin:
+ call sync_sweep
+ wreg NR14,$40
+ wreg NR11,-$20
+ wreg NR12,$08
+ ret
+
+should_be_almost_off:
+ lda NR52
+ and $01
+ jp z,test_failed
+ delay_apu 1
+should_be_off:
+ lda NR52
+ and $01
+ jp nz,test_failed
+ ret
+
+main:
+ set_test 2,"Timer treats period 0 as 8"
+ call begin
+ wreg NR10,$11
+ wreg NR13,$00
+ wreg NR14,$C2
+ delay_apu 1
+ wreg NR10,$01 ; sweep enabled
+ delay_apu 3
+ wreg NR10,$11 ; non-zero period so calc will occur when timer reloads
+ delay_apu $11
+ call should_be_almost_off
+
+ set_test 3,"Makes private copy of frequency on trigger"
+ call begin
+ wreg NR10,$12
+ wreg NR13,$04
+ wreg NR14,$80
+ wreg NR13,$00
+ delay_apu $39
+ call should_be_almost_off
+
+ set_test 4,"Exiting negate mode after calculation disables channel"
+ call begin
+ wreg NR10,$09 ; since shift > 0, calculates sweep value at init
+ wreg NR13,$00
+ wreg NR14,$C0
+ delay_apu 2
+ wreg NR10,$10 ; neg->pos, so disables channel
+ call should_be_off
+
+ set_test 5,"Ending negate after it maybe changed freq disables chan"
+ call begin
+ wreg NR10,$10 ; enable sweep
+ wreg NR13,$00
+ wreg NR14,$C0
+ delay_apu 2
+ wreg NR10,$18 ; negate mode
+ delay_apu 2
+ wreg NR10,$10 ; neg->pos, so disables channel
+ call should_be_off
+
+ set_test 6,"Ending negate mode any other way doesn't disable channel"
+ call begin
+ wreg NR10,$1F ; use negate mode once
+ wreg NR14,$C0
+ delay_apu 2
+ wreg NR10,$18 ; since period > 0, doesn't calculate at init
+ wreg NR13,$00
+ wreg NR14,$C0
+ delay_apu 1 ; no sweep clock here
+ wreg NR10,$10 ; pos mode before neg mode ever used
+ delay_apu 1 ; sweep clock occurs here
+ wreg NR10,$0F ; now let neg mode be seen once, but period = 0 so no calculation is made
+ delay_apu 2 ; sweep clock occurs here
+ wreg NR10,$10 ; doesn't affect channel
+ delay_apu 2 ; sweep clock occurs here
+ wreg NR10,$1F ; let neg mode get used
+ delay_apu 18
+ wreg NR10,$79 ; period and shift can be changed without channel disabling
+ delay_apu 5
+ call should_be_almost_off
+
+ set_test 7,"Subtract mode uses two's complement"
+ call begin
+ delay 2048 ; avoids extra length clocking on CGB-02
+ wreg NR10,$1C
+ wreg NR13,$B0
+ wreg NR14,$85
+ delay_apu 2
+ wreg NR10,$01
+ wreg NR14,$C5
+ delay_apu $1F
+ call should_be_almost_off
+
+ set_test 8,"Subtract mode uses two's complement (upper bound)"
+ call begin
+ wreg NR10,$1C
+ wreg NR13,$B1
+ wreg NR14,$85
+ delay_apu 2
+ wreg NR10,$01
+ wreg NR14,$C5
+ call should_be_off
+
+ set_test 9,"Update channel frequency only when period is reloaded"
+ call begin
+ wreg NR10,$74
+ wreg NR13,$06
+ wreg NR14,$85
+ delay_apu 14 ; just reloaded
+ wreg NR13,$06
+ delay_apu 13 ; if 14, fails
+ wreg NR10,$11
+ wreg NR14,$85 ; just before next reload, so freq is still $506
+ call should_be_almost_off
+
+ jp tests_passed
diff --git a/playing-coffee/roms/cgb_sound/source/06-overflow on trigger.s b/playing-coffee/roms/cgb_sound/source/06-overflow on trigger.s
new file mode 100644
index 0000000..2ebc236
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/06-overflow on trigger.s
@@ -0,0 +1,64 @@
+; Finds highest and lowest frequencies that don't overflow
+; immediately on trigger, for NR10 values of $00-$07
+
+.include "shell.inc"
+.include "apu.s"
+
+main:
+
+ ; DMG-06:
+ ; 0555 0666 071C 0787 07C1 07E0 07F0
+
+ wreg NR12,8
+ ld d,$01
+shift_loop:
+ ld a,d
+ sta NR10
+ ld bc,$87FF
+- ld a,c
+ sta NR13
+ ld a,b
+ sta NR14
+ delay_clocks 40
+ lda NR52
+ and 1
+ jr nz,+
+ dec bc
+ bit 6,b
+ jr z,-
++ res 7,b
+ call print_bc
+ inc d
+ bit 3,d
+ jr z,shift_loop
+ call print_newline
+ check_crc $F604603B
+
+ ; DMG-05, DMG-06, DMG-09, CGB-04, CGB-05:
+ ; 0556 0667 071D 0788 07C2 07E1 07F1
+
+ wreg NR12,8
+ ld d,$01
+shift_loop2:
+ ld a,d
+ sta NR10
+ ld bc,$8000
+- ld a,c
+ sta NR13
+ ld a,b
+ sta NR14
+ delay_clocks 40
+ lda NR52
+ and 1
+ jr z,+
+ inc bc
+ bit 6,b
+ jr z,-
++ res 7,b
+ call print_bc
+ inc d
+ bit 3,d
+ jr z,shift_loop2
+ check_crc $5A1697EE
+
+ jp tests_passed
diff --git a/playing-coffee/roms/cgb_sound/source/07-len sweep period sync.s b/playing-coffee/roms/cgb_sound/source/07-len sweep period sync.s
new file mode 100644
index 0000000..6b3ed7e
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/07-len sweep period sync.s
@@ -0,0 +1,106 @@
+; Tests length and sweep periods, and synchronization between the two
+.include "shell.inc"
+.include "apu.s"
+
+test_timing:
+ ; Time how long until next length clock
+- inc de
+ ld a,(NR52)
+ and $01
+ jr nz,-
+
+ ;call print_de
+
+ ; Error if not in 0...4 range
+ ld a,d
+ cp 0
+ jp nz,test_failed
+ ld a,e
+ cp 5
+ jp nc,test_failed
+ ret
+
+main:
+
+ set_test 2,"Length period is wrong"
+ call sync_apu
+ wreg NR14,$40 ; avoids extra length clock
+ wreg NR11,$3F ; length = $01
+ wreg NR12,$08 ; silent without disabling channel
+ wreg NR14,$C0 ; start length
+ ld de,-$170
+ call test_timing
+
+ set_test 3,"Sweep period is wrong"
+ call sync_sweep
+ wreg NR10,$10 ; sweep period = 1
+ wreg NR12,$08 ; silent without disabling channel
+ wreg NR13,$FF ; max freq
+ wreg NR14,$87 ; start
+ ld de,-$2E4
+ call test_timing
+
+ set_test 4,"Sweep clock is synchronized with length"
+ call sync_sweep
+ wreg NR14,$40 ; avoids extra length clock
+ wreg NR11,$3F ; length = $01
+ wreg NR12,$08 ; silent without disabling channel
+ wreg NR14,$C0 ; start length
+ ld de,-$170
+ call test_timing
+
+ set_test 5,"Powering up APU MODs next frame time with 8192"
+ call sync_apu
+ ld de,-$16F
+ call test_power
+
+ call sync_apu
+ ld de,-$B5
+ call test_power_off
+
+ call sync_apu
+ delay_clocks 8192
+ ld de,-$B5
+ call test_power
+
+ call sync_apu
+ delay_clocks 8192
+ ld de,-$B5
+ call test_power_off
+
+ call sync_apu
+ ld de,-$B5
+ wreg NR52,$00 ; power off
+ delay_clocks 8192
+ call test_power
+
+ set_test 6,"Powering up APU resets 128 Hz sweep divider"
+ call sync_sweep
+ ld de,-$229
+ call test_power2
+
+ call sync_sweep
+ delay_apu 1
+ ld de,-$229
+ call test_power2
+
+ jp tests_passed
+
+test_power_off:
+ wreg NR52,$00 ; power off
+test_power:
+ wreg NR52,$80 ; power on
+ wreg NR14,$40
+ wreg NR11,-1 ; length = 1
+ wreg NR12,8
+ wreg NR14,$C0
+ jp test_timing
+
+test_power2:
+ wreg NR52,$00 ; power off
+ wreg NR52,$80 ; power on
+ wreg NR10,$11
+ wreg NR12,8
+ wreg NR13,$00
+ wreg NR14,$84
+ jp test_timing
diff --git a/playing-coffee/roms/cgb_sound/source/08-len ctr during power.s b/playing-coffee/roms/cgb_sound/source/08-len ctr during power.s
new file mode 100644
index 0000000..1ea218a
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/08-len ctr during power.s
@@ -0,0 +1,84 @@
+; On CGB, length counters are reset when powered up.
+; On DMG, they are unaffected, and not clocked.
+
+;.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+enable_len_ctrs:
+ wreg NR22,8
+ wreg NR24,$C0
+ wreg NR12,8
+ wreg NR14,$C0
+ wreg NR30,$80
+ wreg NR34,$C0
+ wreg NR42,8
+ wreg NR44,$C0
+ ret
+
+main:
+ call sync_apu
+
+ ld a,0
+ call fill_apu_regs
+
+ ; Load length counters
+ wreg NR41,-$33
+ wreg NR31,-$44
+ wreg NR11,-$11
+ wreg NR21,-$22
+
+ delay_clocks 8192
+ call enable_len_ctrs
+
+ ; Power down. Comment out to see what would
+ ; happen if length counters did run.
+ wreg NR52,$00
+
+ ; Try to enable length counters
+ call enable_len_ctrs
+
+ ; Give plenty of time for them to be clocked
+ delay_msec 250
+
+ ; Power back on and wait a bit longer
+ wreg NR52,$80
+ ;call enable_len_ctrs ; can't do this here
+ delay_clocks 2048
+
+ ; Get values from length counters
+ wreg NR22,8
+ wreg NR24,$C0
+ ld a,$02
+ call get_len_a
+ push af
+
+ wreg NR12,8
+ wreg NR14,$C0
+ ld a,$01
+ call get_len_a
+ push af
+
+ wreg NR30,$80
+ wreg NR34,$C0
+ ld a,$04
+ call get_len_a
+ push af
+
+ wreg NR42,8
+ wreg NR44,$C0
+ ld a,$08
+ call get_len_a
+
+ ; Print them
+ call print_a
+ pop af
+ call print_a
+ pop af
+ call print_a
+ pop af
+ call print_a
+
+ check_crc_dmg_cgb $32F0CFBB,$3CF589B4
+ jp tests_passed
diff --git a/playing-coffee/roms/cgb_sound/source/09-wave read while on.s b/playing-coffee/roms/cgb_sound/source/09-wave read while on.s
new file mode 100644
index 0000000..2d06a77
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/09-wave read while on.s
@@ -0,0 +1,41 @@
+; Reads from wave RAM while playing, each time 2
+; clocks later.
+
+;.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ wreg NR51,0 ; mute sound
+ loop_n_times test,69
+ check_crc_dmg_cgb $118A3620,$270DA9A3
+ jp tests_passed
+
+test:
+ add $99
+ ld b,a
+
+ ; Reload wave and have its first
+ ; sample read occur 2 clocks earlier
+ ; each loop iteration
+ ld hl,wave
+ call load_wave
+ wreg NR30,$80 ; enable
+ wreg NR32,$00 ; silent
+ ld a,b
+ sta NR33 ; period
+ wreg NR34,$87 ; start
+
+ ; Read from wave
+ wreg NR33,-2 ; period = 4
+ delay_clocks 176
+ lda WAVE
+
+ call print_a
+
+ ret
+
+wave:
+ .byte $00,$11,$22,$33,$44,$55,$66,$77
+ .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
diff --git a/playing-coffee/roms/cgb_sound/source/10-wave trigger while on.s b/playing-coffee/roms/cgb_sound/source/10-wave trigger while on.s
new file mode 100644
index 0000000..d7ec063
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/10-wave trigger while on.s
@@ -0,0 +1,49 @@
+; Retriggers wave without stopping first
+
+;.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ wreg NR51,0 ; mute sound
+ loop_n_times test,69
+ check_crc_dmg_cgb $533D6D4D,$8130733A
+ jp tests_passed
+
+test:
+ add $99
+ ld b,a
+
+ ; Reload wave and have its first
+ ; sample read occur 2 clocks earlier
+ ; each loop iteration
+ ld hl,wave
+ call load_wave
+ wreg NR30,$80 ; enable
+ wreg NR32,$00 ; silent
+ ld a,b
+ sta NR33 ; period
+ wreg NR34,$87 ; start
+
+ ; Retrigger wave
+ wreg NR33,-2 ; period = 4
+ delay_clocks 168
+ wreg NR34,$87 ; restart
+ delay_clocks 40
+
+ ; Print wave RAM
+ wreg NR30,0
+ ld c,$30
+- ld a,($FF00+c)
+ call print_a
+ inc c
+ bit 6,c
+ jr z,-
+ call print_newline
+
+ ret
+
+wave:
+ .byte $00,$11,$22,$33,$44,$55,$66,$77
+ .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
diff --git a/playing-coffee/roms/cgb_sound/source/11-regs after power.s b/playing-coffee/roms/cgb_sound/source/11-regs after power.s
new file mode 100644
index 0000000..6379ad7
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/11-regs after power.s
@@ -0,0 +1,51 @@
+; After powering sound off then on, NR12, NR14, and NR44
+; are clear.
+.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ call sync_apu
+
+ ld a,$FF
+ call fill_apu_regs
+
+ ; Power down for a moment
+ wreg NR52,$00
+ wreg NR41,-$12
+ wreg NR12,$F0
+ delay_msec 100
+ wreg NR52,$80
+
+ set_test 2,"Powering off should clear NR12"
+ call sync_apu
+ wreg NR14,$80
+ lda NR52
+ and $01
+ jp nz,test_failed
+
+ set_test 3,"Powering off should clear NR13"
+ call sync_apu
+ wreg NR10,$11
+ wreg NR12,$08
+ wreg NR14,$80
+ delay_apu 20
+ lda NR52
+ and $01
+ jp z,test_failed
+
+ set_test 4,"Powering off should clear NR41"
+ call sync_apu
+ delay_clocks 8192 ; avoids extra length clocking
+ wreg NR42,$08
+ wreg NR44,$C0
+ delay_apu 63
+ lda NR52
+ and $08
+ jp z,test_failed
+ delay_apu 1
+ lda NR52
+ and $08
+ jp nz,test_failed
+
+ jp tests_passed
diff --git a/playing-coffee/roms/cgb_sound/source/12-wave.s b/playing-coffee/roms/cgb_sound/source/12-wave.s
new file mode 100644
index 0000000..ba3185d
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/12-wave.s
@@ -0,0 +1,112 @@
+; Tests wave channel timer reload and phase rest on trigger,
+; and access to wave RAM while playing.
+.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ ld hl,wave
+ call load_wave
+ wreg NR32,0
+
+ set_test 2,"Timer period or phase resetting is wrong"
+ wreg NR30,$80
+ wreg NR33,$00
+ wreg NR34,$80
+ delay_clocks 1024
+ wreg NR34,$80
+ ld c,$31
+ ld de,-$FE
+ call test_wave
+
+ set_test 3,"Current byte readable at any wave addr"
+ wreg NR30,$80
+ wreg NR33,$00
+ wreg NR34,$80
+ ld c,$3C
+ ld de,-$FE
+ call test_wave
+
+ set_test 5,"Normal access when chan disabled"
+ wreg NR30,$80
+ wreg NR33,$00
+ wreg NR34,$80
+ wreg NR30,$00 ; disable chan
+ wreg NR30,$80 ; DAC on
+ ld c,$31
+ ld de,0
+ call test_wave
+
+ set_test 6,"Write test"
+ wreg NR30,$80
+ wreg NR33,$F0
+ wreg NR34,$87
+ delay_clocks 256
+ wreg $FF30,$BC
+ wreg NR30,0
+ ld a,($FF34)
+ cp $BC
+ jp nz,test_failed
+
+ set_test 7,"Timer period change"
+ wreg NR30,$80
+ wreg NR33,$00
+ wreg NR34,$87
+ wreg NR33,$F0
+ ld c,$30
+ ld de,-$E
+ call test_wave
+
+ set_test 8,"Frequency 0 is valid"
+ wreg NR30,$80
+ wreg NR33,$00
+ wreg NR34,$80
+ ld c,$30
+ ld de,-$FE
+ call test_wave
+
+ set_test 9,"Maintains phase properly when vol = 0"
+ wreg NR30,$80
+ wreg NR32,0
+ wreg NR33,$00
+ wreg NR34,$87
+ ld c,$30
+ ld de,-$1E
+ call test_wave
+
+ set_test 10,"Maintains phase properly when stereo = 0"
+ wreg NR51,$00
+ wreg NR30,$80
+ wreg NR33,$00
+ wreg NR34,$87
+ ld c,$30
+ ld de,-$1E
+ call test_wave
+
+ jp tests_passed
+
+test_wave:
+- inc de ; 8
+ ld a,($FF00+c) ; 8
+ or a ; 4
+ jr z,- ; 12
+
+ ;call print_a
+ ;call print_de
+
+ cp $11
+ jp nz,test_failed
+
+
+ ; Error if not in 0...4 range
+ ld a,d
+ cp 0
+ jp nz,test_failed
+ ld a,e
+ cp 5
+ jp nc,test_failed
+ ret
+
+wave:
+ .byte $00,$11,$22,$33,$44,$55,$66,$77
+ .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
diff --git a/playing-coffee/roms/cgb_sound/source/common/build_gbs.s b/playing-coffee/roms/cgb_sound/source/common/build_gbs.s
new file mode 100644
index 0000000..877754f
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/common/build_gbs.s
@@ -0,0 +1,139 @@
+; Build as GBS music file
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $2000 size $2000
+ slot 1 $C000 size $2000
+.endMe
+
+.romBankSize $2000
+.romBanks 2
+
+.define RST_OFFSET $70
+
+.ifndef GBS_TMA
+ .define GBS_TMA 0
+.endif
+
+.ifndef GBS_TAC
+ .define GBS_TAC 0
+.endif
+
+;;;; GBS music file header
+
+.ifndef CUSTOM_HEADER
+ .byte "GBS"
+ .byte 1,1,1 ; vers, song count, first song
+ .word load_addr, reset, gbs_play_, std_stack
+ .byte GBS_TMA,GBS_TAC ; timer
+.endif
+ .org $10
+ .ds $60,0
+load_addr:
+ .org RST_OFFSET+$70 ; space for RST vectors
+ .ds $148-RST_OFFSET-$70,0
+ .org $150 ; wla insists on generating GB header
+
+gbs_play_:
+ jp gbs_play ; GBS spec disallows having gbs_play in RAM
+
+;;;; Shell
+
+.include "shell.s"
+
+.define gbs_idle nv_ram
+.redefine nv_ram nv_ram+2
+
+init_runtime:
+ ; Identify as DMG hardware
+ ld a,$01
+ ld (gb_id),a
+
+ ; Save return address
+ pop hl
+ ld a,l
+ ld (gbs_idle),a
+ ld a,h
+ ld (gbs_idle+1),a
+
+ ; Delay 1/4 second to give time
+ ; for GBS player to interrupt with
+ ; play, if it's going to do so
+ delay_msec 250
+
+.ifndef CUSTOM_PLAY
+gbs_play:
+.endif
+ ; Get return address
+ ld a,(gbs_idle)
+ ld l,a
+ ld a,(gbs_idle+1)
+ ld h,a
+
+ ; If zero, then play interrupted init
+ ; call, or another play call, and we
+ ; can't run the program properly.
+ or l
+ jp z,internal_error
+
+ setw gbs_idle,0
+ jp hl
+
+
+; Reports A in binary as high and low tones, with
+; leading low tone for reference. Omits leading
+; zeroes.
+; Preserved: AF, BC, DE, HL
+play_byte:
+ push af
+ push hl
+
+ ; HL = (A << 1) | 1
+ scf
+ rla
+ ld l,a
+ ld h,0
+ rl h
+
+ ; Shift left until next-to-top bit is 1
+- add hl,hl
+ bit 6,h
+ jr z,-
+
+ ; Reset sound
+ delay_msec 400
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+- add hl,hl
+
+ ; Low or high pitch based on bit shifted out
+ ; of HL
+ ld a,0
+ jr nc,+
+ ld a,$FF
++ sta NR23
+
+ ; Play short tone
+ wreg NR21,$A0
+ wreg NR22,$F0
+ wreg NR24,$86
+ delay_msec 75
+ wreg NR22,0
+ wreg NR23,$F8
+ wreg NR24,$87
+ delay_msec 200
+
+ ; Loop until HL = $8000
+ ld a,h
+ xor $80
+ or l
+ jr nz,-
+
+ pop hl
+ pop af
+ ret
+
+.ends
diff --git a/playing-coffee/roms/cgb_sound/source/common/build_rom.s b/playing-coffee/roms/cgb_sound/source/common/build_rom.s
new file mode 100644
index 0000000..f369b92
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/common/build_rom.s
@@ -0,0 +1,70 @@
+; Build as GB ROM
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $0000 size $4000
+ slot 1 $C000 size $4000
+.endMe
+
+.romBankSize $4000
+.romBanks 2
+
+.cartridgeType 2 ; MBC1+RAM
+.ramsize 02 ; 8K
+.computeChecksum
+.computeComplementCheck
+
+;;;; GB ROM header
+
+ ; Reserve space for RST handlers
+ .org $70
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .ds $90,0
+
+ ; GB header read by bootrom
+ .org $100
+ nop
+ jp reset
+
+ ; Nintendo logo required for proper boot
+ .byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
+ .byte $03,$73,$00,$83,$00,$0C,$00,$0D
+ .byte $00,$08,$11,$1F,$88,$89,$00,$0E
+ .byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
+ .byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
+ .byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
+
+ ; Internal name
+ .ifdef ROM_NAME
+ .byte ROM_NAME
+ .endif
+
+ ; CGB/DMG requirements
+ .org $143
+ .ifdef REQUIRE_CGB
+ .byte $C0
+ .else
+ .ifndef REQUIRE_DMG
+ .byte $80
+ .endif
+ .endif
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .org $150
+ .ds $2150-$150,0
+
+;;;; Shell
+
+.define NEED_CONSOLE 1
+.include "shell.s"
+
+init_runtime:
+ ret
+
+play_byte:
+ ret
+
+.ends
diff --git a/playing-coffee/roms/cgb_sound/source/common/console.bin b/playing-coffee/roms/cgb_sound/source/common/console.bin
new file mode 100644
index 0000000..b02f2d3
Binary files /dev/null and b/playing-coffee/roms/cgb_sound/source/common/console.bin differ
diff --git a/playing-coffee/roms/cgb_sound/source/common/console.s b/playing-coffee/roms/cgb_sound/source/common/console.s
new file mode 100644
index 0000000..403fa5c
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/common/console.s
@@ -0,0 +1,285 @@
+; Scrolling text console
+
+; Console is 20x18 characters. Buffers lines, so
+; output doesn't appear until a newline or flush.
+; If scrolling isn't supported (i.e. SCY is treated
+; as if always zero), the first 18 lines will
+; still print properly). Also works properly if
+; LY isn't supported (always reads back as the same
+; value).
+
+.define console_width 20
+
+.define console_buf bss+0
+.define console_pos bss+console_width
+.define console_mode bss+console_width+1
+.define console_scroll bss+console_width+2
+.redefine bss bss+console_width+3
+
+
+; Waits for start of LCD blanking period
+; Preserved: BC, DE, HL
+console_wait_vbl:
+ push bc
+
+ ; Wait for start of vblank, with
+ ; timeout in case LY doesn't work
+ ; or LCD is disabled.
+ ld bc,-1250
+- inc bc
+ ld a,b
+ or c
+ jr z,@timeout
+ lda LY
+ cp 144
+ jr nz,-
+@timeout:
+
+ pop bc
+ ret
+
+
+; Initializes text console
+console_init:
+ call console_hide
+
+ ; CGB-specific inits
+ ld a,(gb_id)
+ and gb_id_cgb
+ call nz,@init_cgb
+
+ ; Clear nametable
+ ld a,' '
+ call @fill_nametable
+
+ ; Load tiles
+ ld hl,TILES+$200
+ ld c,0
+ call @load_tiles
+ ld hl,TILES+$A00
+ ld c,$FF
+ call @load_tiles
+
+ ; Init state
+ setb console_pos,console_width
+ setb console_mode,0
+ setb console_scroll,-8
+ call console_scroll_up_
+ jr console_show
+
+@fill_nametable:
+ ld hl,BGMAP0
+ ld b,4
+- ld (hl),a
+ inc l
+ jr nz,-
+ inc h
+ dec b
+ jr nz,-
+ ret
+
+@init_cgb:
+ ; Clear palette
+ wreg $FF68,$80
+ ld b,16
+- wreg $FF69,$FF
+ wreg $FF69,$7F
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ dec b
+ jr nz,-
+
+ ; Clear attributes
+ wreg VBK,1
+ ld a,0
+ call @fill_nametable
+
+ wreg VBK,0
+ ret
+
+@load_tiles:
+ ld de,ASCII
+ ld b,96
+-- push bc
+ ld b,8
+- ld a,(de)
+ inc de
+ xor c
+ ldi (hl),a
+ ldi (hl),a
+ dec b
+ jr nz,-
+ pop bc
+ dec b
+ jr nz,--
+ ret
+
+
+; Shows console display
+; Preserved: AF, BC, DE, HL
+console_show:
+ push af
+
+ ; Enable LCD
+ call console_wait_vbl
+ wreg LCDC,$91
+ wreg SCX,0
+ wreg BGP,$E4
+
+ jp console_apply_scroll_
+
+
+; Hides console display by turning LCD off
+; Preserved: AF, BC, DE, HL
+console_hide:
+ push af
+
+ ; LCD off
+ call console_wait_vbl
+ wreg LCDC,$11
+
+ pop af
+ ret
+
+
+; Changes to normal text mode
+; Preserved: BC, DE, HL
+console_normal:
+ xor a
+ jr console_set_mode
+
+; Changes to inverse text mode
+; Preserved: BC, DE, HL
+console_inverse:
+ ld a,$80
+
+; Changes console mode to A.
+; 0: Normal, $80: Inverse
+; Preserved: BC, DE, HL
+console_set_mode:
+ and $80
+ ld (console_mode),a
+ ret
+
+
+; Prints char A to console. Will not appear until
+; a newline or flush occurs.
+; Preserved: AF, BC, DE, HL
+console_print:
+ push af
+
+ cp 10
+ jr z,console_newline_
+
+ push hl
+ push af
+ ld hl,console_pos
+ ldi a,(hl)
+ cp BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee/roms/cgb_sound/source/common/delay.s b/playing-coffee/roms/cgb_sound/source/common/delay.s
new file mode 100644
index 0000000..cbcdcf3
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+.endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+.endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+.endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee/roms/cgb_sound/source/common/gb.inc b/playing-coffee/roms/cgb_sound/source/common/gb.inc
new file mode 100644
index 0000000..2d0118d
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/common/gb.inc
@@ -0,0 +1,81 @@
+; Game Boy hardware addresses
+
+; $0000-$3FFF Fixed ROM bank
+; $4000-$7FFF Switchable bank
+; $8000-$9FFF VRAM
+; $A000-$BFFF optional cartridge RAM
+; $C000-$DFFF RAM
+; $E000-$FDFF RAM mirror
+; $FE00-$FE9F OAM
+; $FEA0-$FEFF unused
+; $FF00-$FF7F registers
+; $FF80-$FFFE RAM
+; $FFFF register
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define BRAM $A000 ; cart memory
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+; Registers
+
+.define RAMEN $0000 ; cartridge WRAM control
+.define BANK $2000 ; bank select
+.define P1 $FF00 ; controller
+
+; Game link I/O
+.define SB $FF01 ; serial buffer
+.define SC $FF02 ; serial control
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee/roms/cgb_sound/source/common/macros.inc b/playing-coffee/roms/cgb_sound/source/common/macros.inc
new file mode 100644
index 0000000..9d7cf44
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/common/macros.inc
@@ -0,0 +1,91 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ; addr
+ ldh a,(\1 - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ; addr
+ ldh (\1 - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Writes byte to addr
+; Preserved: F, BC, DE, HL
+; Time: 6 cycles
+.macro setb ; addr, data
+ ld a,\2
+ ld (\1),a
+.endm
+
+; Writes word to addr
+; Preserved: F, BC, DE, HL
+; Time: 12 cycles
+.macro setw ; addr, data
+ ld a,<\2
+ ld (\1),a
+ ld a,>\2
+ ld (\1+1),a
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee/roms/cgb_sound/source/common/numbers.s b/playing-coffee/roms/cgb_sound/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee/roms/cgb_sound/source/common/printing.s b/playing-coffee/roms/cgb_sound/source/common/printing.s
new file mode 100644
index 0000000..bb9389b
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/common/printing.s
@@ -0,0 +1,77 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+;print_char_nocrc (defined by user)
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee/roms/cgb_sound/source/common/shell.s b/playing-coffee/roms/cgb_sound/source/common/shell.s
new file mode 100644
index 0000000..3e588c7
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/common/shell.s
@@ -0,0 +1,261 @@
+; Common routines and runtime
+
+.define RUNTIME_INCLUDED 1
+
+; A few bytes of RAM that aren't cleared
+.define nv_ram_base $D800
+.define nv_ram nv_ram_base
+
+; Address of next normal variable
+.define bss_base nv_ram_base+$80
+.define bss bss_base
+
+; Address of next direct-page ($FFxx) variable
+.define dp_base $FF80
+.define dp dp_base
+
+; Top of stack
+.define std_stack $DFFF+1
+
+; Final exit result byte is written here
+.define final_result $A000
+
+; Text output is written here as zero-terminated string
+.define text_out_base $A004
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+.define gb_id nv_ram
+.redefine nv_ram nv_ram+1
+
+; Copies C*$100 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+- ld a,(hl+)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+
+.ifndef RST_OFFSET
+ .define RST_OFFSET 0
+.endif
+
+.ifndef CUSTOM_RESET
+ reset:
+ di
+
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ ld c,$14
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org 0
+ jp std_reset
+.endif
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
+
+ ; Catch user code running off end
+ jp internal_error
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld (gb_id),a
+
+ ; Clear memory except very top of stack
+ ld bc,std_stack-bss_base - 2
+ ld hl,bss_base
+ call clear_mem
+ ld bc,$FFFF-dp_base
+ ld hl,dp_base
+ call clear_mem
+
+ ; Init hardware
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ call init_runtime
+ call init_text_out
+ call console_init
+ call init_testing
+
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ call console_show
+ pop af
+ call play_byte
+ jp post_exit
+
++ push af
+ call print_newline
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+
+; Clears BC bytes starting at HL
+clear_mem:
+ ; If C>0, increment B
+ dec bc
+ inc c
+ inc b
+
+ ld a,0
+- ld (hl+),a
+ dec c
+ jr nz,-
+ dec b
+ jr nz,-
+ ret
+
+
+; Reports internal error and exits with code 255
+internal_error:
+ print_str "Internal error"
+ ld a,255
+ jp exit
+
+
+; build_devcart and build_multi customize this
+.ifndef CUSTOM_PRINT
+ .define text_out_addr bss+0
+ .redefine bss bss+2
+
+ ; Initializes text output to cartridge RAM
+ init_text_out:
+ ; Enable cartridge RAM and set text output pointer
+ setb RAMEN,$0A
+ setw text_out_addr,text_out_base
+ setb text_out_base-3,$DE
+ setb text_out_base-2,$B0
+ setb text_out_base-1,$61
+ setb text_out_base,0
+ setb final_result,$80
+ ret
+
+
+ ; Appends character to text output string
+ ; Preserved: AF, BC, DE, HL
+ write_text_out:
+ push hl
+ push af
+ ld a,(text_out_addr)
+ ld l,a
+ ld a,(text_out_addr+1)
+ ld h,a
+ inc hl
+ ld (hl),0
+ ld a,l
+ ld (text_out_addr),a
+ ld a,h
+ ld (text_out_addr+1),a
+ dec hl
+ pop af
+ ld (hl),a
+ pop hl
+ ret
+
+ print_char_nocrc:
+ call write_text_out
+ jp console_print
+.endif
+
+
+; only build_rom uses console
+.ifdef NEED_CONSOLE
+ .include "console.s"
+.else
+ console_init:
+ console_print:
+ console_flush:
+ console_normal:
+ console_inverse:
+ console_show:
+ console_set_mode:
+ ret
+.endif
+
+
+; build_devcart and build_multi need to customize this
+.ifndef CUSTOM_EXIT
+ post_exit:
+ ld (final_result),a
+ forever:
+ wreg NR52,0 ; sound off
+- jr -
+.endif
+
+
+.macro def_rst ARGS addr
+ .bank 0 slot 0
+ .org addr+RST_OFFSET
+.endm
diff --git a/playing-coffee/roms/cgb_sound/source/common/testing.s b/playing-coffee/roms/cgb_sound/source/common/testing.s
new file mode 100644
index 0000000..5dd63ce
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/common/testing.s
@@ -0,0 +1,195 @@
+; Diagnostic and testing utilities
+
+.define test_code bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text.
+; Takes multiple strings due to wla's idiotic
+; default limit of 63 chars per string.
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2[,text3]]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .if NARGS > 3
+ .byte \4
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (test_code),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(test_code)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(test_code)
+ cp $FF ; if a = $FF then a = 1
+ jr nz,+
+ ld a,1
++ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+; Checks CRC, differing based on DMG or CGB build
+.macro check_crc_dmg_cgb ARGS dmg, cgb
+ .ifdef REQUIRE_DMG
+ check_crc dmg
+ .else
+ .ifdef REQUIRE_CGB
+ check_crc cgb
+ .else
+ .printt "CGB or DMG must be specified"
+ .fail
+ .endif
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee/roms/cgb_sound/source/linkfile b/playing-coffee/roms/cgb_sound/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee/roms/cgb_sound/source/readme.txt b/playing-coffee/roms/cgb_sound/source/readme.txt
new file mode 100644
index 0000000..a0269c1
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/readme.txt
@@ -0,0 +1,82 @@
+Game Boy Tests Source Code
+--------------------------
+
+Building with wla-dx
+--------------------
+To assemble a test ROM with wla-dx, use the following commands:
+
+ wla -o source_filename_here.s test.o
+ wlalink linkfile test.gb
+
+To assemble as a GBS music file:
+
+ wla -o source_filename_here.s test.o -DBUILD_GBS
+ wlalink linkfile test.gbs
+
+Note that some tests might only work when built as a ROM or GBS file,
+but not both.
+
+Some tests might include a ROM/GBS that has all the tests combined.
+Building such a multi-test is complex and the necessary files aren't
+included.
+
+
+Framework
+---------
+Each test is in a single source file, and makes use of several library
+source files from common/. This framework provides common services and
+reduces code to only that which performs the actual test. Virtually all
+tests include "shell.inc" at the beginning, which sets things up and
+includes all the appropriate library files.
+
+The reset handler does minimal GB hardware initialization, clears RAM,
+sets up the text console, then runs main. Main can exit by returning or
+jumping to "exit" with an error code in A. Exit reports the code then
+goes into an infinite loop. If the code is 0, it doesn't do anything,
+otherwise it reports the code. Code 1 is reported as "Failed", and the
+rest as "Error ".
+
+The default is to build a ROM. Defining BUILD_GBS will build as an GBS.
+The other build types aren't supported due to their complexity. I load
+the code into RAM at $C000 since my devcart requires it, and I don't
+want the normal ROM to differ in any way from what I've tested. This
+also allows easy self-modifying code.
+
+Several routines are available to print values and text to the console.
+Most update a running CRC-32 checksum which can be checked with
+check_crc, allowing ALL the output to be checked very easily. If the
+checksum doesn't match, it is printed, so you can run the code on a GB
+and paste the correct checksum into your code.
+
+
+Macros
+------
+Some macros are used to make common operations more convenient. The left
+is equivalent to the right:
+
+ Macro Equivalent
+ -------------------------------------
+ lda addr ldh a,(addr-$FF00)
+
+ sta addr ldh (addr-$FF00),a
+
+ wreg addr,data ld a,data
+ ldh (addr-$FF00),a
+
+ setb ld a,data
+ ld (addr),a
+
+ setw setb addr+0,data
+
+ for_loop routine,begin,end,step
+ calls routine with A set to successive values
+
+ loop_n_times routine,count
+ calls routine with A from 0 to count-1
+
+ print_str "str" prints string
+
+
+--
+Shay Green
diff --git a/playing-coffee/roms/cgb_sound/source/shell.inc b/playing-coffee/roms/cgb_sound/source/shell.inc
new file mode 100644
index 0000000..0d43faa
--- /dev/null
+++ b/playing-coffee/roms/cgb_sound/source/shell.inc
@@ -0,0 +1,27 @@
+; Included at beginning of all programs
+; that use standard shell
+
+; Get include files from common/
+.incdir "common"
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.else
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
+
+.endif ; .ifdef BUILD_MULTI
diff --git a/playing-coffee/roms/cpu_instrs.gb b/playing-coffee/roms/cpu_instrs.gb
new file mode 100644
index 0000000..7b06221
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs.gb differ
diff --git a/playing-coffee/roms/cpu_instrs/cpu_instrs.gb b/playing-coffee/roms/cpu_instrs/cpu_instrs.gb
new file mode 100644
index 0000000..7b06221
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs/cpu_instrs.gb differ
diff --git a/playing-coffee/roms/cpu_instrs/individual/01-special.gb b/playing-coffee/roms/cpu_instrs/individual/01-special.gb
new file mode 100644
index 0000000..ad3e998
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs/individual/01-special.gb differ
diff --git a/playing-coffee/roms/cpu_instrs/individual/02-interrupts.gb b/playing-coffee/roms/cpu_instrs/individual/02-interrupts.gb
new file mode 100644
index 0000000..2089594
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs/individual/02-interrupts.gb differ
diff --git a/playing-coffee/roms/cpu_instrs/individual/03-op sp,hl.gb b/playing-coffee/roms/cpu_instrs/individual/03-op sp,hl.gb
new file mode 100644
index 0000000..50b3cc7
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs/individual/03-op sp,hl.gb differ
diff --git a/playing-coffee/roms/cpu_instrs/individual/04-op r,imm.gb b/playing-coffee/roms/cpu_instrs/individual/04-op r,imm.gb
new file mode 100644
index 0000000..58ca7b8
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs/individual/04-op r,imm.gb differ
diff --git a/playing-coffee/roms/cpu_instrs/individual/05-op rp.gb b/playing-coffee/roms/cpu_instrs/individual/05-op rp.gb
new file mode 100644
index 0000000..1c19d92
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs/individual/05-op rp.gb differ
diff --git a/playing-coffee/roms/cpu_instrs/individual/06-ld r,r.gb b/playing-coffee/roms/cpu_instrs/individual/06-ld r,r.gb
new file mode 100644
index 0000000..d497bfd
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs/individual/06-ld r,r.gb differ
diff --git a/playing-coffee/roms/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb b/playing-coffee/roms/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb
new file mode 100644
index 0000000..5c8d20b
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb differ
diff --git a/playing-coffee/roms/cpu_instrs/individual/08-misc instrs.gb b/playing-coffee/roms/cpu_instrs/individual/08-misc instrs.gb
new file mode 100644
index 0000000..4da139b
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs/individual/08-misc instrs.gb differ
diff --git a/playing-coffee/roms/cpu_instrs/individual/09-op r,r.gb b/playing-coffee/roms/cpu_instrs/individual/09-op r,r.gb
new file mode 100644
index 0000000..e30e6ec
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs/individual/09-op r,r.gb differ
diff --git a/playing-coffee/roms/cpu_instrs/individual/10-bit ops.gb b/playing-coffee/roms/cpu_instrs/individual/10-bit ops.gb
new file mode 100644
index 0000000..8988458
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs/individual/10-bit ops.gb differ
diff --git a/playing-coffee/roms/cpu_instrs/individual/11-op a,(hl).gb b/playing-coffee/roms/cpu_instrs/individual/11-op a,(hl).gb
new file mode 100644
index 0000000..0634b7f
Binary files /dev/null and b/playing-coffee/roms/cpu_instrs/individual/11-op a,(hl).gb differ
diff --git a/playing-coffee/roms/cpu_instrs/readme.txt b/playing-coffee/roms/cpu_instrs/readme.txt
new file mode 100644
index 0000000..6f94955
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/readme.txt
@@ -0,0 +1,119 @@
+Game Boy CPU Instruction Behavior Test
+--------------------------------------
+This ROM tests the behavior of all CPU instructions except STOP and the
+11 illegal opcodes. The tests are fairly thorough, running instructions
+with boundary data and verifying both the result and that other
+registers are not modified. Instructions which perform the same
+operation on different registers are each tested just as thoroughly, in
+case an emulator implements each independently. Some sub-tests take half
+minute to complete.
+
+Failed instructions are listed as
+
+ [CB] opcode
+
+Some errors cannot of course be diagnosed properly, since the test
+framework itself relies on basic instruction behavior being correct.
+
+
+Internal operation
+------------------
+The main tests use a framework that runs each instruction in a loop,
+varying the register values on input and examining them on output.
+Rather than keep a table of correct values, it simply calculates a
+CRC-32 checksum of all the output, then compares this with the correct
+value. Instructions are divided into several groups, each with a
+different set of input values suited for their behavior; for example,
+the bit test instructions are fed $01, $02, $04 ... $40, $80, to ensure
+each bit is handled properly, while the arithmetic instructions are fed
+$01, $0F, $10, $7F, $FF, to exercise carry and half-carry. A few
+instructions require a custom test due to their uniqueness.
+
+
+Multi-ROM
+---------
+In the main directory is a single ROM which runs all the tests. It
+prints a test's number, runs the test, then "ok" if it passes, otherwise
+a failure code. Once all tests have completed it either reports that all
+tests passed, or prints the number of failed tests. Finally, it makes
+several beeps. If a test fails, it can be run on its own by finding the
+corresponding ROM in individual/.
+
+Ths compact format on screen is to avoid having the results scroll off
+the top, so the test can be started and allowed to run without having to
+constantly monitor the display.
+
+Currently there is no well-defined way for an emulator test rig to
+programatically find the result of the test; contact me if you're trying
+to do completely automated testing of your emulator. One simple approach
+is to take a screenshot after all tests have run, or even just a
+checksum of one, and compare this with a previous run.
+
+
+Failure codes
+-------------
+Failed tests may print a failure code, and also short description of the
+problem. For more information about a failure code, look in the
+corresponding source file in source/; the point in the code where
+"set_test n" occurs is where that failure code will be generated.
+Failure code 1 is a general failure of the test; any further information
+will be printed.
+
+Note that once a sub-test fails, no further tests for that file are run.
+
+
+Console output
+--------------
+Information is printed on screen in a way that needs only minimum LCD
+support, and won't hang if LCD output isn't supported at all.
+Specifically, while polling LY to wait for vblank, it will time out if
+it takes too long, so LY always reading back as the same value won't
+hang the test. It's also OK if scrolling isn't supported; in this case,
+text will appear starting at the top of the screen.
+
+Everything printed on screen is also sent to the game link port by
+writing the character to SB, then writing $81 to SC. This is useful for
+tests which print lots of information that scrolls off screen.
+
+
+Source code
+-----------
+Source code is included for all tests, in source/. It can be used to
+build the individual test ROMs. Code for the multi test isn't included
+due to the complexity of putting everything together.
+
+Code is written for the wla-dx assembler. To assemble a particular test,
+execute
+
+ wla -o "source_filename.s" test.o
+ wlalink linkfile test.gb
+
+Test code uses a common shell framework contained in common/.
+
+
+Internal framework operation
+----------------------------
+Tests use a common framework for setting things up, reporting results,
+and ending. All files first include "shell.inc", which sets up the ROM
+header and shell code, and includes other commonly-used modules.
+
+One oddity is that test code is first copied to internal RAM at $D000,
+then executed there. This allows self-modification, and ensures the code
+is executed the same way it is on my devcart, which doesn't have a
+rewritable ROM as most do.
+
+Some macros are used to simplify common tasks:
+
+ Macro Behavior
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ wreg addr,data Writes data to addr using LDH
+ lda addr Loads byte from addr into A using LDH
+ sta addr Stores A at addr using LDH
+ delay n Delays n cycles, where NOP = 1 cycle
+ delay_msec n Delays n milliseconds
+ set_test n,"Cause" Sets failure code and optional string
+
+Routines and macros are documented where they are defined.
+
+--
+Shay Green
diff --git a/playing-coffee/roms/cpu_instrs/source/01-special.s b/playing-coffee/roms/cpu_instrs/source/01-special.s
new file mode 100644
index 0000000..776d685
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/01-special.s
@@ -0,0 +1,78 @@
+; Tests instructions that don't fit template
+
+.include "shell.inc"
+
+main:
+ set_test 2,"JR negative"
+ ld a,0
+ jp jr_neg
+ inc a
+- inc a
+ inc a
+ cp 2
+ jp nz,test_failed
+ jp +
+jr_neg:
+ jr -
++
+
+ set_test 3,"JR positive"
+ ld a,0
+ jr +
+ inc a
++ inc a
+ inc a
+ cp 2
+ jp nz,test_failed
+
+
+ set_test 4,"LD PC,HL"
+ ld hl,+
+ ld a,0
+ ld pc,hl
+ inc a
++ inc a
+ inc a
+ cp 2
+ jp nz,test_failed
+
+
+ set_test 5,"POP AF"
+ ld bc,$1200
+- push bc
+ pop af
+ push af
+ pop de
+ ld a,c
+ and $F0
+ cp e
+ jp nz,test_failed
+ inc b
+ inc c
+ jr nz,-
+
+
+ set_test 6,"DAA"
+ ; Test all combinations of A and flags (256*16 total)
+ ld de,0
+- push de
+ pop af
+ daa
+
+ push af
+ call update_crc
+ pop hl
+ ld a,l
+ call update_crc
+
+ inc d
+ jr nz,-
+
+ ld a,e
+ add $10
+ ld e,a
+ jr nz,-
+
+ check_crc $6A9F8D8A
+
+ jp tests_passed
diff --git a/playing-coffee/roms/cpu_instrs/source/02-interrupts.s b/playing-coffee/roms/cpu_instrs/source/02-interrupts.s
new file mode 100644
index 0000000..de18b34
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/02-interrupts.s
@@ -0,0 +1,73 @@
+; Tests DI, EI, and HALT (STOP proved untestable)
+
+.include "shell.inc"
+
+main:
+ wreg IE,$04
+
+ set_test 2,"EI"
+ ei
+ ld bc,0
+ push bc
+ pop bc
+ inc b
+ wreg IF,$04
+interrupt_addr:
+ dec b
+ jp nz,test_failed
+ ld hl,sp-2
+ ldi a,(hl)
+ cp interrupt_addr
+ jp nz,test_failed
+ lda IF
+ and $04
+ jp nz,test_failed
+
+ set_test 3,"DI"
+ di
+ ld bc,0
+ push bc
+ pop bc
+ wreg IF,$04
+ ld hl,sp-2
+ ldi a,(hl)
+ or (hl)
+ jp nz,test_failed
+ lda IF
+ and $04
+ jp z,test_failed
+
+ set_test 4,"Timer doesn't work"
+ wreg TAC,$05
+ wreg TIMA,0
+ wreg IF,0
+ delay 500
+ lda IF
+ delay 500
+ and $04
+ jp nz,test_failed
+ delay 500
+ lda IF
+ and $04
+ jp z,test_failed
+ pop af
+
+ set_test 5,"HALT"
+ wreg TAC,$05
+ wreg TIMA,0
+ wreg IF,0
+ halt ; timer interrupt will exit halt
+ nop ; avoids DMG bug
+ lda IF
+ and $04
+ jp z,test_failed
+
+ jp tests_passed
+
+.bank 0 slot 0
+.org $50
+ inc a
+ ret
diff --git a/playing-coffee/roms/cpu_instrs/source/03-op sp,hl.s b/playing-coffee/roms/cpu_instrs/source/03-op sp,hl.s
new file mode 100644
index 0000000..9531d51
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/03-op sp,hl.s
@@ -0,0 +1,102 @@
+; Tests SP/HL instructions
+
+;.define PRINT_CHECKSUMS 1
+.include "shell.inc"
+.include "instr_test.s"
+
+instrs:
+ .byte $33,0,0 ; INC SP
+ .byte $3B,0,0 ; DEC SP
+ .byte $39,0,0 ; ADD HL,SP
+ .byte $F9,0,0 ; LD SP,HL
+ .byte $E8,$01,0 ; ADD SP,1
+ .byte $E8,$FF,0 ; ADD SP,-1
+ .byte $F8,$01,0 ; LD HL,SP+1
+ .byte $F8,$FF,0 ; LD HL,SP-1
+instrs_end:
+
+test_instr:
+ ; C = flags register
+ ld c,$00
+ call test
+ ld c,$F0
+ call test
+ ret
+
+test:
+ ; Go through each value for HL
+ ld hl,values
+hl_loop:
+ ld e,(hl)
+ inc hl
+ ld d,(hl)
+ inc hl
+ push hl
+
+ ; Go through each value for SP
+ ld hl,values
+values_loop:
+ push bc
+ push de
+ push hl
+
+ push bc
+ pop af
+
+ ; Switch stack
+ ld (temp),sp
+ ld a,(hl+)
+ ld h,(hl)
+ ld l,a
+; call print_regs
+ ld sp,hl
+
+ ; Set registers
+ ld h,d
+ ld l,e
+ ld a,$12
+ ld bc,$5691
+ ld de,$9ABC
+
+ jp instr
+instr_done:
+ ; Save new SP and switch to yet another stack
+ ld (temp+2),sp
+ ld sp,$DF70
+
+ call checksum_af_bc_de_hl
+
+ ; Checksum SP
+ ld a,(temp+2)
+ call update_crc_fast
+ ld a,(temp+3)
+ call update_crc_fast
+
+ ldsp temp
+
+ pop hl
+ pop de
+ pop bc
+ inc hl
+ inc hl
+ ld a,l
+ cp taken ; JP NZ,taken
+ .byte $C3,taken ; JP taken
+ .byte $CA,taken ; JP Z,taken
+ .byte $D2,taken ; JP NC,taken
+ .byte $DA,taken ; JP C,taken
+
+ .byte $C4,taken ; CALL NZ,taken
+ .byte $CC,taken ; CALL Z,taken
+ .byte $CD,taken ; CALL taken
+ .byte $D4,taken ; CALL NC,taken
+ .byte $DC,taken ; CALL C,taken
+
+ ; RET cond
+ ; INC A
+ .byte $C0,$3C,0 ; RET NZ
+ .byte $C8,$3C,0 ; RET Z
+ .byte $C9,$3C,0 ; RET
+ .byte $D0,$3C,0 ; RET NC
+ .byte $D8,$3C,0 ; RET C
+ .byte $D9,$3C,0 ; RETI
+
+ ; RST
+ ; can only easily test this one on devcart
+ .byte $C7,0,0 ; RST $00
+.ifndef BUILD_DEVCART
+ .byte $CF,0,0 ; RST $08
+ .byte $D7,0,0 ; RST $10
+ .byte $DF,0,0 ; RST $18
+ .byte $E7,0,0 ; RST $20
+ .byte $EF,0,0 ; RST $28
+ .byte $F7,0,0 ; RST $30
+ .byte $FF,0,0 ; RST $38
+.endif
+
+instrs_end:
+
+test_instr:
+ wreg IE,0 ; disable interrupts, since RETI does EI
+
+ ; Go through all 16 combinations of flags
+ ld bc,$1200
+-
+ ; Fill 4 bytes of new stack
+ ld a,$12
+ ld ($DF80-2),a
+ ld a,$34
+ ld ($DF80-3),a
+ ld a,$56
+ ld ($DF80-4),a
+ ld a,$78
+ ld ($DF80-5),a
+
+ ; Set AF
+ push bc
+ pop af
+
+ ; Switch to new stack
+ ld (temp),sp
+ ld sp,$DF80
+
+ ; Set return address
+ ld de,instr+3
+ push de
+
+ jp instr
+instr_done:
+ inc a
+taken:
+ di ; RETI enables interrupts
+
+ ; Save new SP and switch to yet another stack
+ ld (temp+2),sp
+ ld sp,$DF70
+
+ ; Checksum A and SP
+ call update_crc_fast
+ ld a,(temp+2)
+ call update_crc_fast
+ ld a,(temp+3)
+ call update_crc_fast
+
+ ; Checksum 4 bytes of stack
+ ld a,($DF80-2)
+ call update_crc_fast
+ ld a,($DF80-3)
+ call update_crc_fast
+ ld a,($DF80-4)
+ call update_crc_fast
+ ld a,($DF80-5)
+ call update_crc_fast
+
+ ldsp temp
+
+ ld a,c
+ add $10
+ ld c,a
+ jr nz,-
+
+ ret
+
+checksums:
+ .byte $EC,$A4,$94,$79,$C4,$00,$96,$2C,$C4,$64,$90,$33,$77,$C7,$0A,$D4
+ .byte $77,$A3,$0C,$CB,$79,$E7,$7E,$AE,$DA,$DC,$03,$F7,$4F,$9F,$E9,$20
+ .byte $72,$12,$DA,$01,$44,$6A,$4D,$8F,$D1,$79,$30,$4C,$AA,$37,$F2,$6A
+ .byte $97,$EA,$56,$5F,$32,$28,$C7,$D1,$49,$66,$05,$F7,$80,$0F,$BA,$8E
+ .byte $41,$E2,$A4,$9A,$2D,$2D,$8C,$72,$A5,$13,$76,$A8,$64,$FE,$68,$BC
+ .byte $2D,$2D,$8C,$72,$50,$96,$24,$27,$50,$96,$24,$27,$50,$96,$24,$27
+ .byte $50,$96,$24,$27,$50,$96,$24,$27,$50,$96,$24,$27,$50,$96,$24,$27
+ .byte $50,$96,$24,$27
+
+.include "multi_custom.s"
diff --git a/playing-coffee/roms/cpu_instrs/source/08-misc instrs.s b/playing-coffee/roms/cpu_instrs/source/08-misc instrs.s
new file mode 100644
index 0000000..5c11c8e
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/08-misc instrs.s
@@ -0,0 +1,110 @@
+; Tests miscellaneous instructions
+
+;.define PRINT_CHECKSUMS 1
+.include "shell.inc"
+.include "instr_test.s"
+
+instrs:
+ .byte $F0,$91,0 ; LDH A,($91)
+ .byte $E0,$91,0 ; LDH ($91),A
+ .byte $F2,$00,0 ; LDH A,(C)
+ .byte $E2,$00,0 ; LDH (C),A
+ .byte $FA,$91,$FF ; LD A,($FF91)
+ .byte $EA,$91,$FF ; LD ($FF91),A
+ .byte $08,$91,$FF ; LD ($FF91),SP
+ .byte $01,$23,$01 ; LD BC,$0123
+ .byte $11,$23,$01 ; LD DE,$0123
+ .byte $21,$23,$01 ; LD HL,$0123
+ .byte $31,$23,$01 ; LD SP,$0123
+ .byte $F5,0,0 ; PUSH AF
+ .byte $C5,0,0 ; PUSH BC
+ .byte $D5,0,0 ; PUSH DE
+ .byte $E5,0,0 ; PUSH HL
+ .byte $F1,0,0 ; POP AF
+ .byte $C1,0,0 ; POP BC
+ .byte $D1,0,0 ; POP DE
+ .byte $E1,0,0 ; POP HL
+instrs_end:
+
+test_instr:
+ ; C = flags register
+ ld c,$00
+ call test
+ ld c,$10
+ call test
+ ld c,$E0
+ call test
+ ld c,$F0
+ call test
+ ret
+
+test:
+ ; Fill RAM
+ ld a,$FE
+ ld ($FF90),a
+ ld a,$DC
+ ld ($FF91),a
+ ld a,$BA
+ ld ($FF92),a
+
+ ; Fill stack
+ ld a,$13
+ ld ($DF80),a
+ ld a,$57
+ ld ($DF80-1),a
+ ld a,$9B
+ ld ($DF80-2),a
+ ld a,$DF
+ ld ($DF80-3),a
+
+ ; Set registers
+ ld b,$12
+ push bc
+ ld bc,$5691
+ ld de,$9ABC
+ ld hl,$DEF0
+ pop af
+
+ ; Switch stack
+ ld (temp),sp
+ ld sp,$DF80-2
+
+ jp instr
+instr_done:
+ ; Save new SP and switch to another stack
+ ld (temp+2),sp
+ ld sp,$DF70
+
+ call checksum_af_bc_de_hl
+
+ ; Checksum SP
+ ld a,(temp+2)
+ call update_crc_fast
+ ld a,(temp+3)
+ call update_crc_fast
+
+ ; Checksum RAM
+ ld a,($FF90)
+ call update_crc_fast
+ ld a,($FF91)
+ call update_crc_fast
+ ld a,($FF92)
+ call update_crc_fast
+
+ ; Checksum stack
+ ld a,($DF80)
+ call update_crc_fast
+ ld a,($DF80-1)
+ call update_crc_fast
+ ld a,($DF80-2)
+ call update_crc_fast
+ ld a,($DF80-3)
+ call update_crc_fast
+
+ ; Restore SP
+ ldsp temp
+
+ ret
+
+checksums:
+ .byte $4D,$FF,$15,$97,$6D,$A7,$35,$65,$4D,$FF,$15,$97,$6D,$A7,$35,$65,$4D,$FF,$15,$97,$6D,$A7,$35,$65,$AD,$FA,$5E,$41,$D0,$78,$79,$C1,$AF,$66,$99,$34,$0D,$E1,$97,$99,$6F,$D0,$6F,$5D,$C3,$1F,$A3,$8A,$C2,$F1,$9C,$F3,$C1,$C3,$DC,$78,$C0,$2D,$E3,$01,$8F,$C4,$0F,$44,$95,$22,$6A,$39,$61,$C5,$AB,$55,$FB,$DF,$2C,$52,
diff --git a/playing-coffee/roms/cpu_instrs/source/09-op r,r.s b/playing-coffee/roms/cpu_instrs/source/09-op r,r.s
new file mode 100644
index 0000000..432c4e7
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/09-op r,r.s
@@ -0,0 +1,269 @@
+; Tests most register instructions.
+; Takes 10 seconds.
+
+;.define PRINT_CHECKSUMS 1
+.include "shell.inc"
+.include "instr_test.s"
+
+instrs:
+ .byte $00,0,0 ; NOP
+ .byte $2F,0,0 ; CPL
+ .byte $37,0,0 ; SCF
+ .byte $3F,0,0 ; CCF
+
+ .byte $B0,0,0 ; OR B
+ .byte $B1,0,0 ; OR C
+ .byte $B2,0,0 ; OR D
+ .byte $B3,0,0 ; OR E
+ .byte $B4,0,0 ; OR H
+ .byte $B5,0,0 ; OR L
+ .byte $B7,0,0 ; OR A
+
+ .byte $B8,0,0 ; CP B
+ .byte $B9,0,0 ; CP C
+ .byte $BA,0,0 ; CP D
+ .byte $BB,0,0 ; CP E
+ .byte $BC,0,0 ; CP H
+ .byte $BD,0,0 ; CP L
+ .byte $BF,0,0 ; CP A
+
+ .byte $80,0,0 ; ADD B
+ .byte $81,0,0 ; ADD C
+ .byte $82,0,0 ; ADD D
+ .byte $83,0,0 ; ADD E
+ .byte $84,0,0 ; ADD H
+ .byte $85,0,0 ; ADD L
+ .byte $87,0,0 ; ADD A
+
+ .byte $88,0,0 ; ADC B
+ .byte $89,0,0 ; ADC C
+ .byte $8A,0,0 ; ADC D
+ .byte $8B,0,0 ; ADC E
+ .byte $8C,0,0 ; ADC H
+ .byte $8D,0,0 ; ADC L
+ .byte $8F,0,0 ; ADC A
+
+ .byte $90,0,0 ; SUB B
+ .byte $91,0,0 ; SUB C
+ .byte $92,0,0 ; SUB D
+ .byte $93,0,0 ; SUB E
+ .byte $94,0,0 ; SUB H
+ .byte $95,0,0 ; SUB L
+ .byte $97,0,0 ; SUB A
+
+ .byte $98,0,0 ; SBC B
+ .byte $99,0,0 ; SBC C
+ .byte $9A,0,0 ; SBC D
+ .byte $9B,0,0 ; SBC E
+ .byte $9C,0,0 ; SBC H
+ .byte $9D,0,0 ; SBC L
+ .byte $9F,0,0 ; SBC A
+
+ .byte $A0,0,0 ; AND B
+ .byte $A1,0,0 ; AND C
+ .byte $A2,0,0 ; AND D
+ .byte $A3,0,0 ; AND E
+ .byte $A4,0,0 ; AND H
+ .byte $A5,0,0 ; AND L
+ .byte $A7,0,0 ; AND A
+
+ .byte $A8,0,0 ; XOR B
+ .byte $A9,0,0 ; XOR C
+ .byte $AA,0,0 ; XOR D
+ .byte $AB,0,0 ; XOR E
+ .byte $AC,0,0 ; XOR H
+ .byte $AD,0,0 ; XOR L
+ .byte $AF,0,0 ; XOR A
+
+ .byte $05,0,0 ; DEC B
+ .byte $0D,0,0 ; DEC C
+ .byte $15,0,0 ; DEC D
+ .byte $1D,0,0 ; DEC E
+ .byte $25,0,0 ; DEC H
+ .byte $2D,0,0 ; DEC L
+ .byte $3D,0,0 ; DEC A
+
+ .byte $04,0,0 ; INC B
+ .byte $0C,0,0 ; INC C
+ .byte $14,0,0 ; INC D
+ .byte $1C,0,0 ; INC E
+ .byte $24,0,0 ; INC H
+ .byte $2C,0,0 ; INC L
+ .byte $3C,0,0 ; INC A
+
+ .byte $07,0,0 ; RLCA
+ .byte $17,0,0 ; RLA
+ .byte $0F,0,0 ; RRCA
+ .byte $1F,0,0 ; RRA
+
+ .byte $CB,$00,0 ; RLC B
+ .byte $CB,$01,0 ; RLC C
+ .byte $CB,$02,0 ; RLC D
+ .byte $CB,$03,0 ; RLC E
+ .byte $CB,$04,0 ; RLC H
+ .byte $CB,$05,0 ; RLC L
+ .byte $CB,$07,0 ; RLC A
+
+ .byte $CB,$08,0 ; RRC B
+ .byte $CB,$09,0 ; RRC C
+ .byte $CB,$0A,0 ; RRC D
+ .byte $CB,$0B,0 ; RRC E
+ .byte $CB,$0C,0 ; RRC H
+ .byte $CB,$0D,0 ; RRC L
+ .byte $CB,$0F,0 ; RRC A
+
+ .byte $CB,$10,0 ; RL B
+ .byte $CB,$11,0 ; RL C
+ .byte $CB,$12,0 ; RL D
+ .byte $CB,$13,0 ; RL E
+ .byte $CB,$14,0 ; RL H
+ .byte $CB,$15,0 ; RL L
+ .byte $CB,$17,0 ; RL A
+
+ .byte $CB,$18,0 ; RR B
+ .byte $CB,$19,0 ; RR C
+ .byte $CB,$1A,0 ; RR D
+ .byte $CB,$1B,0 ; RR E
+ .byte $CB,$1C,0 ; RR H
+ .byte $CB,$1D,0 ; RR L
+ .byte $CB,$1F,0 ; RR A
+
+ .byte $CB,$20,0 ; SLA B
+ .byte $CB,$21,0 ; SLA C
+ .byte $CB,$22,0 ; SLA D
+ .byte $CB,$23,0 ; SLA E
+ .byte $CB,$24,0 ; SLA H
+ .byte $CB,$25,0 ; SLA L
+ .byte $CB,$27,0 ; SLA A
+
+ .byte $CB,$28,0 ; SRA B
+ .byte $CB,$29,0 ; SRA C
+ .byte $CB,$2A,0 ; SRA D
+ .byte $CB,$2B,0 ; SRA E
+ .byte $CB,$2C,0 ; SRA H
+ .byte $CB,$2D,0 ; SRA L
+ .byte $CB,$2F,0 ; SRA A
+
+ .byte $CB,$30,0 ; SWAP B
+ .byte $CB,$31,0 ; SWAP C
+ .byte $CB,$32,0 ; SWAP D
+ .byte $CB,$33,0 ; SWAP E
+ .byte $CB,$34,0 ; SWAP H
+ .byte $CB,$35,0 ; SWAP L
+ .byte $CB,$37,0 ; SWAP A
+
+ .byte $CB,$38,0 ; SRL B
+ .byte $CB,$39,0 ; SRL C
+ .byte $CB,$3A,0 ; SRL D
+ .byte $CB,$3B,0 ; SRL E
+ .byte $CB,$3C,0 ; SRL H
+ .byte $CB,$3D,0 ; SRL L
+ .byte $CB,$3F,0 ; SRL A
+instrs_end:
+
+test_instr:
+ ld c,$00
+ call test
+ ld c,$F0
+ call test
+ ret
+
+test:
+ ; Go through each value for A
+ ld hl,values
+a_loop:
+ ld b,(hl)
+ push hl
+
+ ; Go through each value for other registers
+ ld hl,values
+values_loop:
+ push bc
+ push hl
+
+ push bc
+
+ ; BC
+ ld a,(hl+)
+ ld b,a
+ ld a,(hl+)
+ ld c,a
+
+ ; HL
+ ld a,(hl+)
+ ld d,a
+ ld a,(hl+)
+ ld e,a
+ push de
+
+ ; DE
+ ld a,(hl+)
+ ld d,a
+ ld a,(hl+)
+ ld e,a
+
+ pop hl
+ pop af
+
+; call print_regs
+ jp instr
+instr_done:
+
+ ; Checksum registers
+ call checksum_af_bc_de_hl
+
+ pop hl
+ pop bc
+ inc hl
+ ld a,l
+ cp checksums
+ ld (next_checksum+1),a
+ ret
+
+; Compares current checksum with next checksum in
+; list. Z if they match, NZ if not.
+; Preserved: BC, DE, HL
+checksums_compare:
+.ifdef PRINT_CHECKSUMS
+ lda checksum+3
+ push af
+ lda checksum+2
+ push af
+ lda checksum+1
+ push af
+ lda checksum+0
+ push af
+
+ ld a,(next_checksum)
+ inc a
+ ld (next_checksum),a
+ sub BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee/roms/cpu_instrs/source/common/crc_fast.s b/playing-coffee/roms/cpu_instrs/source/common/crc_fast.s
new file mode 100644
index 0000000..d1088b0
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/common/crc_fast.s
@@ -0,0 +1,88 @@
+; Fast table-based CRC-32
+
+.define crc_tables (bss+$FF)&$FF00 ; 256-byte aligned
+.redefine bss crc_tables+$400
+
+
+; Initializes fast CRC tables and resets checksum.
+; Time: 47 msec
+init_crc_fast:
+ ld l,0
+@next:
+ xor a
+ ld c,a
+ ld d,a
+ ld e,l
+
+ ld h,8
+- rra
+ rr c
+ rr d
+ rr e
+ jr nc,+
+ xor $ED
+ ld b,a
+ ld a,c
+ xor $B8
+ ld c,a
+ ld a,d
+ xor $83
+ ld d,a
+ ld a,e
+ xor $20
+ ld e,a
+ ld a,b
+
++ dec h
+ jr nz,-
+
+ ld h,>crc_tables
+ ld (hl),e
+ inc h
+ ld (hl),d
+ inc h
+ ld (hl),c
+ inc h
+ ld (hl),a
+
+ inc l
+ jr nz,@next
+
+ jp init_crc
+
+
+; Faster version of update_crc
+; Preserved: BC, DE
+; Time: 50 cycles (including CALL)
+update_crc_fast:
+
+; Fastest inline macro version of update_crc_fast
+; Time: 40 cycles
+; Size: 28 bytes
+.macro update_crc_fast
+ ld l,a ; 1
+ lda checksum ; 3
+ xor l ; 1
+ ld l,a ; 1
+ ld h,>crc_tables ; 2
+
+ lda checksum+1 ; 3
+ xor (hl) ; 2
+ inc h ; 1
+ sta checksum ; 3
+
+ lda checksum+2 ; 3
+ xor (hl) ; 2
+ inc h ; 1
+ sta checksum+1 ; 3
+
+ lda checksum+3 ; 3
+ xor (hl) ; 2
+ inc h ; 1
+ sta checksum+2 ; 3
+
+ ld a,(hl) ; 2
+ sta checksum+3 ; 3
+.endm
+ update_crc_fast
+ ret
diff --git a/playing-coffee/roms/cpu_instrs/source/common/delay.s b/playing-coffee/roms/cpu_instrs/source/common/delay.s
new file mode 100644
index 0000000..65eb9c0
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+ .endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+ .endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+ .endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee/roms/cpu_instrs/source/common/gb.inc b/playing-coffee/roms/cpu_instrs/source/common/gb.inc
new file mode 100644
index 0000000..31bbf14
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/common/gb.inc
@@ -0,0 +1,64 @@
+; Game Boy hardware addresses
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+.define P1 $FF00
+
+; Game link I/O
+.define SB $FF01
+.define SC $FF02
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee/roms/cpu_instrs/source/common/instr_test.s b/playing-coffee/roms/cpu_instrs/source/common/instr_test.s
new file mode 100644
index 0000000..5ed6e2c
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/common/instr_test.s
@@ -0,0 +1,105 @@
+; Framework for CPU instruction tests
+
+; Calls test_instr with each instruction copied
+; to instr, with a JP instr_done after it.
+; Verifies checksum after testing instruction and
+; prints opcode if it's wrong.
+
+.include "checksums.s"
+.include "cpu_speed.s"
+.include "apu.s"
+.include "crc_fast.s"
+
+.define instr $DEF8
+.define rp_temp (instr-4)
+
+.define temp bss
+
+; Sets SP to word at addr
+; Preserved: BC, DE
+.macro ldsp ; addr
+ ld a,(\1)
+ ld l,a
+ ld a,((\1)+1)
+ ld h,a
+ ld sp,hl
+.endm
+
+main:
+ call cpu_fast
+ call init_crc_fast
+ call checksums_init
+ set_test 0
+
+ ld hl,instrs
+- ; Copy instruction
+ ld a,(hl+)
+ ld (instr),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Put JP instr_done after it
+ ld a,$C3
+ ld (instr+3),a
+ ld a,instr_done
+ ld (instr+5),a
+
+ call reset_crc
+ call test_instr
+
+ call checksums_compare
+ jr z,passed
+
+ set_test 1
+ ld a,(instr)
+ call print_a
+ cp $CB
+ jr nz,+
+ ld a,(instr+1)
+ call print_a
++
+
+passed:
+ ; Next instruction
+ pop hl
+ ld a,l
+ cp instrs_end
+ jr nz,-
+
+ jp tests_done
+
+
+; Updates checksum with AF, BC, DE, and HL
+checksum_af_bc_de_hl:
+ push hl
+
+ push af
+ update_crc_fast
+ pop hl
+ ld a,l
+ update_crc_fast
+
+ ld a,b
+ update_crc_fast
+ ld a,c
+ update_crc_fast
+
+ ld a,d
+ update_crc_fast
+ ld a,e
+ update_crc_fast
+
+ pop de
+ ld a,d
+ update_crc_fast
+ ld a,e
+ update_crc_fast
+ ret
diff --git a/playing-coffee/roms/cpu_instrs/source/common/macros.inc b/playing-coffee/roms/cpu_instrs/source/common/macros.inc
new file mode 100644
index 0000000..c413bd5
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/common/macros.inc
@@ -0,0 +1,73 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ARGS addr
+ ldh a,(addr - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ARGS addr
+ ldh (addr - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee/roms/cpu_instrs/source/common/multi_custom.s b/playing-coffee/roms/cpu_instrs/source/common/multi_custom.s
new file mode 100644
index 0000000..4dbae9d
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/common/multi_custom.s
@@ -0,0 +1,38 @@
+; RST handlers
+.bank 0 slot 0
+.org 0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
+ .ds 6,0
+ inc a
+ ret
diff --git a/playing-coffee/roms/cpu_instrs/source/common/numbers.s b/playing-coffee/roms/cpu_instrs/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee/roms/cpu_instrs/source/common/printing.s b/playing-coffee/roms/cpu_instrs/source/common/printing.s
new file mode 100644
index 0000000..ad9d811
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/common/printing.s
@@ -0,0 +1,98 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+.define print_char_nocrc bss
+.redefine bss bss+3
+
+
+; Initializes printing. HL = print routine
+init_printing:
+ ld a,l
+ ld (print_char_nocrc+1),a
+ ld a,h
+ ld (print_char_nocrc+2),a
+ jr show_printing
+
+
+; Hides/shows further printing
+; Preserved: BC, DE, HL
+hide_printing:
+ ld a,$C9 ; RET
+ jr +
+show_printing:
+ ld a,$C3 ; JP (nn)
++ ld (print_char_nocrc),a
+ ret
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee/roms/cpu_instrs/source/common/runtime.s b/playing-coffee/roms/cpu_instrs/source/common/runtime.s
new file mode 100644
index 0000000..90cd24f
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/common/runtime.s
@@ -0,0 +1,142 @@
+; Common routines and runtime
+
+; Must be defined by target-specific runtime:
+;
+; init_runtime: ; target-specific inits
+; std_print: ; default routine to print char A
+; post_exit: ; called at end of std_exit
+; report_byte: ; report A to user
+
+.define RUNTIME_INCLUDED 1
+
+.ifndef bss
+ ; address of next normal variable
+ .define bss $D800
+.endif
+
+.ifndef dp
+ ; address of next direct-page ($FFxx) variable
+ .define dp $FF80
+.endif
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+
+.define gb_id bss
+.redefine bss bss+1
+
+; Stack is normally here
+.define std_stack $DFFF
+
+; Copies $1000 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+ ld c,$10
+- ldi a,(hl)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+.ifndef CUSTOM_RESET
+ reset:
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org $0 ; otherwise wla pads with lots of zeroes
+ jp std_reset
+.endif
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld (gb_id),a
+
+ ; Init hardware
+ .ifndef BUILD_GBS
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+ .endif
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ ; TODO: clear all memory?
+
+ ld hl,std_print
+ call init_printing
+ call init_testing
+ call init_runtime
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ pop af
+ jp post_exit
+
++ push af
+ call print_newline
+ call show_printing
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
diff --git a/playing-coffee/roms/cpu_instrs/source/common/testing.s b/playing-coffee/roms/cpu_instrs/source/common/testing.s
new file mode 100644
index 0000000..c102f78
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/common/testing.s
@@ -0,0 +1,176 @@
+; Diagnostic and testing utilities
+
+.define result bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (result),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(result)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(result)
+ cp 1 ; if a = 0 then a = 1
+ adc 0
+ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call show_printing
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee/roms/cpu_instrs/source/linkfile b/playing-coffee/roms/cpu_instrs/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee/roms/cpu_instrs/source/shell.inc b/playing-coffee/roms/cpu_instrs/source/shell.inc
new file mode 100644
index 0000000..277a9b7
--- /dev/null
+++ b/playing-coffee/roms/cpu_instrs/source/shell.inc
@@ -0,0 +1,21 @@
+.incdir "common"
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
diff --git a/playing-coffee/roms/dmg_sound/dmg_sound.gb b/playing-coffee/roms/dmg_sound/dmg_sound.gb
new file mode 100644
index 0000000..fe91310
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/dmg_sound.gb differ
diff --git a/playing-coffee/roms/dmg_sound/readme.txt b/playing-coffee/roms/dmg_sound/readme.txt
new file mode 100644
index 0000000..5d8d188
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/readme.txt
@@ -0,0 +1,86 @@
+Game Boy Sound Hardware Tests
+-----------------------------
+These tests verify aspects of the sound hardware that the CPU can
+observe. The ROMs and GBSs are either for DMG or CGB hardware, as there
+are several differences.
+
+
+Multi-ROM
+---------
+In the main directory is a single ROM/GBS which runs all the tests. It
+prints a test's number, runs the test, then "ok" if it passes, otherwise
+a failure code. Once all tests have completed it either reports that all
+tests passed, or reports the number of the first failed test as the
+result code (1 = first). Finally, it makes several beeps. If a test
+fails, it can be run on its own by finding the corresponding ROM/GBS in
+the singles directories.
+
+Ths compact format on screen is to avoid having the results scroll off
+the top, so the test can be started and allowed to run without having to
+constantly monitor the display.
+
+
+Failure information
+-------------------
+For more information about a failure code or information printed, see
+the test's source code in source/. To find failure code N, search for
+"set_test N", which will usually be before the subtest which failed.
+
+
+Flashes, clicks, other glitches
+-------------------------------
+Some tests might need to turn the screen off and on, or cause slight
+audio clicks. This does not indicate failure, and should be ignored.
+Only the test result reported at the end is important, unless stated
+otherwise.
+
+
+LCD support
+-----------
+Tests generally print information on screen. The tests will work fine if
+run on an emulator with NO LCD support, or as an GBS which has no
+inherent screen; in particular, the VBL wait routine has a timeout in
+case LY doesn't reflect the current LCD line. The text printing will
+also work if the LCD doesn't support scrolling.
+
+
+Output to memory
+----------------
+Text output and the final result are also written to memory at $A000,
+allowing testing a very minimal emulator that supports little more than
+CPU and RAM. To reliably indicate that the data is from a test and not
+random data, $A001-$A003 are written with a signature: $DE,$B0,$61. If
+this is present, then the text string and final result status are valid.
+
+$A000 holds the overall status. If the test is still running, it holds
+$80, otherwise it holds the final result code.
+
+All text output is appended to a zero-terminated string at $A004. An
+emulator could regularly check this string for any additional
+characters, and output them, allowing real-time text output, rather than
+just printing the final output at the end.
+
+
+GBS versions
+------------
+Many GBS-based tests require that the GBS player either not interrupt
+the init routine with the play routine, or if they do, not interrupt the
+play routine again if it hasn't returned yet. This is because many tests
+need to run for a while without returning.
+
+In addition to the other text output methods described above, GBS builds
+report essential information bytes audibly, including the final result.
+A byte is reported as a series of tones. The code is in binary, with a
+low tone for 0 and a high tone for 1. The first tone is always a zero. A
+final code of 0 means passed, 1 means failure, and 2 or higher indicates
+a specific reason as listed in the source code by the corresponding
+set_code line. Examples:
+
+Tones Binary Decimal Meaning
+- - - - - - - - - - - - - - - - - - - -
+low 0 0 passed
+low high 01 1 failed
+low high low 010 2 error 2
+
+--
+Shay Green
diff --git a/playing-coffee/roms/dmg_sound/rom_singles/01-registers.gb b/playing-coffee/roms/dmg_sound/rom_singles/01-registers.gb
new file mode 100644
index 0000000..c1fa6c5
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/rom_singles/01-registers.gb differ
diff --git a/playing-coffee/roms/dmg_sound/rom_singles/02-len ctr.gb b/playing-coffee/roms/dmg_sound/rom_singles/02-len ctr.gb
new file mode 100644
index 0000000..d940c7a
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/rom_singles/02-len ctr.gb differ
diff --git a/playing-coffee/roms/dmg_sound/rom_singles/03-trigger.gb b/playing-coffee/roms/dmg_sound/rom_singles/03-trigger.gb
new file mode 100644
index 0000000..1b0f032
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/rom_singles/03-trigger.gb differ
diff --git a/playing-coffee/roms/dmg_sound/rom_singles/04-sweep.gb b/playing-coffee/roms/dmg_sound/rom_singles/04-sweep.gb
new file mode 100644
index 0000000..746b78a
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/rom_singles/04-sweep.gb differ
diff --git a/playing-coffee/roms/dmg_sound/rom_singles/05-sweep details.gb b/playing-coffee/roms/dmg_sound/rom_singles/05-sweep details.gb
new file mode 100644
index 0000000..55351a3
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/rom_singles/05-sweep details.gb differ
diff --git a/playing-coffee/roms/dmg_sound/rom_singles/06-overflow on trigger.gb b/playing-coffee/roms/dmg_sound/rom_singles/06-overflow on trigger.gb
new file mode 100644
index 0000000..d3fbe3b
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/rom_singles/06-overflow on trigger.gb differ
diff --git a/playing-coffee/roms/dmg_sound/rom_singles/07-len sweep period sync.gb b/playing-coffee/roms/dmg_sound/rom_singles/07-len sweep period sync.gb
new file mode 100644
index 0000000..3da3bd7
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/rom_singles/07-len sweep period sync.gb differ
diff --git a/playing-coffee/roms/dmg_sound/rom_singles/08-len ctr during power.gb b/playing-coffee/roms/dmg_sound/rom_singles/08-len ctr during power.gb
new file mode 100644
index 0000000..c72bbf2
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/rom_singles/08-len ctr during power.gb differ
diff --git a/playing-coffee/roms/dmg_sound/rom_singles/09-wave read while on.gb b/playing-coffee/roms/dmg_sound/rom_singles/09-wave read while on.gb
new file mode 100644
index 0000000..b4a79ad
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/rom_singles/09-wave read while on.gb differ
diff --git a/playing-coffee/roms/dmg_sound/rom_singles/10-wave trigger while on.gb b/playing-coffee/roms/dmg_sound/rom_singles/10-wave trigger while on.gb
new file mode 100644
index 0000000..a7c7ddc
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/rom_singles/10-wave trigger while on.gb differ
diff --git a/playing-coffee/roms/dmg_sound/rom_singles/11-regs after power.gb b/playing-coffee/roms/dmg_sound/rom_singles/11-regs after power.gb
new file mode 100644
index 0000000..773e01e
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/rom_singles/11-regs after power.gb differ
diff --git a/playing-coffee/roms/dmg_sound/rom_singles/12-wave write while on.gb b/playing-coffee/roms/dmg_sound/rom_singles/12-wave write while on.gb
new file mode 100644
index 0000000..e0119b9
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/rom_singles/12-wave write while on.gb differ
diff --git a/playing-coffee/roms/dmg_sound/source/01-registers.s b/playing-coffee/roms/dmg_sound/source/01-registers.s
new file mode 100644
index 0000000..ea9f5d5
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/01-registers.s
@@ -0,0 +1,117 @@
+; - APU registers always have some bits set when read back.
+; - Wave memory can be read back freely.
+; - When powered off, registers are cleared, except high bit of NR52.
+; - While off, register writes are ignored, but not reads.
+; - Wave RAM is always readable and writable, and unaffected by power.
+
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ set_test 2,"NR10-NR51 and wave RAM write/read"
+ ld d,0
+- call test_rw
+ inc d
+ jr nz,-
+
+ set_test 3,"NR52 write/read"
+ wreg NR52,$00
+ lda NR52
+ cp $70
+ jp nz,test_failed
+ wreg NR52,$FF
+ lda NR52
+ cp $F0
+ jp nz,test_failed
+
+ set_test 4,"Powering APU shouldn't affect wave"
+ ld a,$37
+ call fill_wave
+ wreg NR52,$00
+
+ ; Verify that wave RAM is unchanged
+ ld hl,WAVE
+- ld a,(hl+)
+ cp $37
+ jp nz,test_failed
+ ld a,l
+ cp $40
+ jr nz,-
+ wreg NR52,$80 ; on
+
+ set_test 5,"Powering APU off should write 0 to all regs"
+ ld a,$FF
+ call fill_apu_regs
+ wreg NR52,$00
+ wreg NR52,$80
+ call regs_should_be_clear
+
+ set_test 6,"When off, should ignore writes to registers"
+ wreg NR52,$00
+ ld a,$FF
+ call fill_apu_regs
+ wreg NR52,$80
+ call regs_should_be_clear
+ wreg NR52,$80
+
+ set_test 7,"When off, should allow normal register reads"
+ wreg NR52,$00
+ call regs_should_be_clear
+ wreg NR52,$80
+
+ jp tests_passed
+
+regs_should_be_clear:
+ ld bc,masks
+ ld hl,NR10
+- ld a,(bc)
+ cp (hl)
+ jp nz,test_failed
+ inc bc
+ inc l
+ ld a,l
+ cp disabled clocks as well
+ ld a,1
+ call end
+
+ call begin
+ wchn 4,$40 ; enable length
+ wchn 1,-2 ; length = 2
+ wchn 4,$40 ; enabled -> enabled doesn't clock
+ wchn 4,$00 ; enabled -> disabled doesn't clock
+ ld a,2
+ call end
+.else
+ set_test 4,"Anything besides enabling shouldnt't clock"
+ call begin
+ wchn 4,$40 ; enable length
+ wchn 1,-2 ; length = 2
+ wchn 4,$40 ; enabled -> enabled doesn't clock
+ wchn 4,$00 ; enabled -> disabled doesn't clock
+ wchn 4,$00 ; disabled -> disabled doesn't clock
+ ld a,2
+ call end
+.endif
+
+ set_test 5,"If clock makes length zero, should disable chan"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ lda chan_mask
+ ld b,a
+ lda NR52 ; channel now disabled
+ and b
+ jp nz,test_failed
+
+ set_test 6,"If length already reached zero, shouldn't clock"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,0
+ wchn 4,$40 ; no clock; length still 0
+ wchn 4,0
+ wchn 4,$40 ; no clock; length still 0
+ lda chan_maxlen; end triggers channel, which loads it with max length
+ call end
+
+ set_test 7,"Trigger should un-freeze length that reached zero"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,$00 ; disable
+ wchn 4,$80 ; trigger unfreezes length, so it takes on maximum value
+ delay_clocks 8192
+ wchn 4,$40 ; enable
+ delay_apu 2 ; clock length by 2
+ lda chan_maxlen
+ sub 2
+ call end_nodelay
+
+ set_test 8,"Trigger that un-freezes enabled length should clock it"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,$00 ; disable
+ wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it
+ lda chan_maxlen
+ dec a
+ call end_nodelay
+
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$40 ; enable, causing clock to zero
+ wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it
+ lda chan_maxlen
+ dec a
+ call end_nodelay
+
+ set_test 9,"Triggering that clocks length of 1 ","should clock twice and shouldn't freeze"
+ call begin
+ wchn 1,-1 ; length = 1
+ wchn 4,$C0 ; trigger and enable
+ ; First length counter is enabled, which clocks it to 0 and freezes it
+ ; Trigger unfreezes length counter, which clocks it AGAIN
+ ; The result is the same as the previous test, which enables separately
+ lda chan_maxlen
+ dec a
+ call end_nodelay
+
+ set_test 10,"Trigger shouldn't otherwise affect length"
+ call begin
+ wchn 1,0 ; length = max
+ delay_clocks 8192
+ wchn 4,$80 ; trigger
+ lda chan_maxlen
+ call end_nodelay
+
+.ifndef CGB_02
+ call begin
+ wchn 1,0 ; length = max
+ wchn 4,$80 ; trigger
+ lda chan_maxlen
+ call end
+
+ call begin
+ wchn 1,-2 ; length = 2
+ wchn 4,$80 ; trigger
+ ld a,2
+ call end
+.endif
+
+ set_test 11,"Disabled DAC shouldn't stop other trigger effects"
+ call begin
+ wchn 0,$00 ; disable wave DAC
+ wchn 2,$07 ; disable square/noise DAC
+ wchn 1,-1
+ wchn 4,$C0 ; clocks length, which becomes max
+ wchn 0,$80 ; enable wave DAC
+ wchn 2,$08 ; enable square/noise DAC
+ wchn 4,$80 ; trigger
+ lda chan_maxlen
+ dec a
+ call end
+
+ set_test 12,"Other trigger effects should still occur when disabled"
+ call sync_apu
+ wchn 0,0
+ wchn 4,0
+ wchn 1,-1
+ wchn 4,$40 ; len = 0
+ wchn 4,0
+ wchn 4,$40 ; len = 0
+ wchn 4,$80 ; len = max
+ wchn 4,$40 ; len = max-1
+ wchn 4,0
+ wchn 4,$40 ; len = max-2
+ wchn 0,$80 ; enable now
+ wchn 4,$C0
+ lda chan_maxlen
+ sub 3
+ call delay_apu_cycles
+ lda chan_mask
+ ld b,a
+ lda NR52
+ and b
+ jp z,test_failed
+ delay_apu 1
+ lda NR52
+ and b
+ jp nz,test_failed
+
+ ret
diff --git a/playing-coffee/roms/dmg_sound/source/04-sweep.s b/playing-coffee/roms/dmg_sound/source/04-sweep.s
new file mode 100644
index 0000000..17ac4e4
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/04-sweep.s
@@ -0,0 +1,120 @@
+; Calc = calculation of new frequency and check for > $7FF
+; Update = modification of frequency with new calculated value
+.include "shell.inc"
+.include "apu.s"
+
+begin:
+ call sync_sweep
+ wreg NR14,$40
+ wreg NR11,-$21
+ wreg NR12,$08
+ ret
+
+should_be_almost_off:
+ lda NR52
+ and $01
+ jp z,test_failed
+ delay_apu 1
+should_be_off:
+ lda NR52
+ and $01
+ jp nz,test_failed
+ ret
+
+main:
+ set_test 2,"If shift>0, calculates on trigger"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$FF
+ wreg NR14,$C7
+ call should_be_off
+ call begin
+ wreg NR10,$11
+ wreg NR13,$FF
+ wreg NR14,$C7
+ call should_be_off
+
+ set_test 3,"If shift=0, doesn't calculate on trigger"
+ call begin
+ wreg NR10,$10
+ wreg NR13,$FF
+ wreg NR14,$C7
+ delay_apu 1
+ call should_be_almost_off
+
+ set_test 4,"If period=0, doesn't calculate"
+ call begin
+ wreg NR10,$00
+ wreg NR13,$FF
+ wreg NR14,$C7
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 5,"After updating frequency, calculates a second time"
+ call begin
+ wreg NR10,$11
+ wreg NR13,$00
+ wreg NR14,$C5
+ delay_apu 1
+ call should_be_almost_off
+
+ set_test 6,"If calculation>$7FF, disables channel"
+ call begin
+ wreg NR10,$02
+ wreg NR13,$67
+ wreg NR14,$C6
+ call should_be_off
+
+ set_test 7,"If calculation<=$7FF, doesn't disable channel"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$55
+ wreg NR14,$C5
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 8,"If shift=0 and period>0, trigger enables"
+ call begin
+ wreg NR10,$10
+ wreg NR13,$FF
+ wreg NR14,$C3
+ delay_apu 2
+ wreg NR10,$11
+ delay_apu 1
+ call should_be_almost_off
+
+ set_test 9,"If shift>0 and period=0, trigger enables"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$FF
+ wreg NR14,$C3
+ delay_apu 15
+ wreg NR10,$11
+ call should_be_almost_off
+
+ set_test 10,"If shift=0 and period=0, trigger disables"
+ call begin
+ wreg NR10,$08
+ wreg NR13,$FF
+ wreg NR14,$C3
+ wreg NR10,$11
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 11,"If shift=0, doesn't update"
+ call begin
+ wreg NR10,$10
+ wreg NR13,$FF
+ wreg NR14,$C3
+ delay_apu $20
+ call should_be_almost_off
+
+ set_test 12,"If period=0, doesn't update"
+ call begin
+ wreg NR10,$01
+ wreg NR13,$00
+ wreg NR14,$C5
+ delay_apu $20
+ call should_be_almost_off
+
+ jp tests_passed
diff --git a/playing-coffee/roms/dmg_sound/source/05-sweep details.s b/playing-coffee/roms/dmg_sound/source/05-sweep details.s
new file mode 100644
index 0000000..1519b68
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/05-sweep details.s
@@ -0,0 +1,121 @@
+; Calc = calculation of new frequency and check for > $7FF
+; Update = modification of frequency with new calculated value
+.include "shell.inc"
+.include "apu.s"
+
+begin:
+ call sync_sweep
+ wreg NR14,$40
+ wreg NR11,-$20
+ wreg NR12,$08
+ ret
+
+should_be_almost_off:
+ lda NR52
+ and $01
+ jp z,test_failed
+ delay_apu 1
+should_be_off:
+ lda NR52
+ and $01
+ jp nz,test_failed
+ ret
+
+main:
+ set_test 2,"Timer treats period 0 as 8"
+ call begin
+ wreg NR10,$11
+ wreg NR13,$00
+ wreg NR14,$C2
+ delay_apu 1
+ wreg NR10,$01 ; sweep enabled
+ delay_apu 3
+ wreg NR10,$11 ; non-zero period so calc will occur when timer reloads
+ delay_apu $11
+ call should_be_almost_off
+
+ set_test 3,"Makes private copy of frequency on trigger"
+ call begin
+ wreg NR10,$12
+ wreg NR13,$04
+ wreg NR14,$80
+ wreg NR13,$00
+ delay_apu $39
+ call should_be_almost_off
+
+ set_test 4,"Exiting negate mode after calculation disables channel"
+ call begin
+ wreg NR10,$09 ; since shift > 0, calculates sweep value at init
+ wreg NR13,$00
+ wreg NR14,$C0
+ delay_apu 2
+ wreg NR10,$10 ; neg->pos, so disables channel
+ call should_be_off
+
+ set_test 5,"Ending negate after it maybe changed freq disables chan"
+ call begin
+ wreg NR10,$10 ; enable sweep
+ wreg NR13,$00
+ wreg NR14,$C0
+ delay_apu 2
+ wreg NR10,$18 ; negate mode
+ delay_apu 2
+ wreg NR10,$10 ; neg->pos, so disables channel
+ call should_be_off
+
+ set_test 6,"Ending negate mode any other way doesn't disable channel"
+ call begin
+ wreg NR10,$1F ; use negate mode once
+ wreg NR14,$C0
+ delay_apu 2
+ wreg NR10,$18 ; since period > 0, doesn't calculate at init
+ wreg NR13,$00
+ wreg NR14,$C0
+ delay_apu 1 ; no sweep clock here
+ wreg NR10,$10 ; pos mode before neg mode ever used
+ delay_apu 1 ; sweep clock occurs here
+ wreg NR10,$0F ; now let neg mode be seen once, but period = 0 so no calculation is made
+ delay_apu 2 ; sweep clock occurs here
+ wreg NR10,$10 ; doesn't affect channel
+ delay_apu 2 ; sweep clock occurs here
+ wreg NR10,$1F ; let neg mode get used
+ delay_apu 18
+ wreg NR10,$79 ; period and shift can be changed without channel disabling
+ delay_apu 5
+ call should_be_almost_off
+
+ set_test 7,"Subtract mode uses two's complement"
+ call begin
+ delay 2048 ; avoids extra length clocking on CGB-02
+ wreg NR10,$1C
+ wreg NR13,$B0
+ wreg NR14,$85
+ delay_apu 2
+ wreg NR10,$01
+ wreg NR14,$C5
+ delay_apu $1F
+ call should_be_almost_off
+
+ set_test 8,"Subtract mode uses two's complement (upper bound)"
+ call begin
+ wreg NR10,$1C
+ wreg NR13,$B1
+ wreg NR14,$85
+ delay_apu 2
+ wreg NR10,$01
+ wreg NR14,$C5
+ call should_be_off
+
+ set_test 9,"Update channel frequency only when period is reloaded"
+ call begin
+ wreg NR10,$74
+ wreg NR13,$06
+ wreg NR14,$85
+ delay_apu 14 ; just reloaded
+ wreg NR13,$06
+ delay_apu 13 ; if 14, fails
+ wreg NR10,$11
+ wreg NR14,$85 ; just before next reload, so freq is still $506
+ call should_be_almost_off
+
+ jp tests_passed
diff --git a/playing-coffee/roms/dmg_sound/source/06-overflow on trigger.s b/playing-coffee/roms/dmg_sound/source/06-overflow on trigger.s
new file mode 100644
index 0000000..2ebc236
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/06-overflow on trigger.s
@@ -0,0 +1,64 @@
+; Finds highest and lowest frequencies that don't overflow
+; immediately on trigger, for NR10 values of $00-$07
+
+.include "shell.inc"
+.include "apu.s"
+
+main:
+
+ ; DMG-06:
+ ; 0555 0666 071C 0787 07C1 07E0 07F0
+
+ wreg NR12,8
+ ld d,$01
+shift_loop:
+ ld a,d
+ sta NR10
+ ld bc,$87FF
+- ld a,c
+ sta NR13
+ ld a,b
+ sta NR14
+ delay_clocks 40
+ lda NR52
+ and 1
+ jr nz,+
+ dec bc
+ bit 6,b
+ jr z,-
++ res 7,b
+ call print_bc
+ inc d
+ bit 3,d
+ jr z,shift_loop
+ call print_newline
+ check_crc $F604603B
+
+ ; DMG-05, DMG-06, DMG-09, CGB-04, CGB-05:
+ ; 0556 0667 071D 0788 07C2 07E1 07F1
+
+ wreg NR12,8
+ ld d,$01
+shift_loop2:
+ ld a,d
+ sta NR10
+ ld bc,$8000
+- ld a,c
+ sta NR13
+ ld a,b
+ sta NR14
+ delay_clocks 40
+ lda NR52
+ and 1
+ jr z,+
+ inc bc
+ bit 6,b
+ jr z,-
++ res 7,b
+ call print_bc
+ inc d
+ bit 3,d
+ jr z,shift_loop2
+ check_crc $5A1697EE
+
+ jp tests_passed
diff --git a/playing-coffee/roms/dmg_sound/source/07-len sweep period sync.s b/playing-coffee/roms/dmg_sound/source/07-len sweep period sync.s
new file mode 100644
index 0000000..6b3ed7e
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/07-len sweep period sync.s
@@ -0,0 +1,106 @@
+; Tests length and sweep periods, and synchronization between the two
+.include "shell.inc"
+.include "apu.s"
+
+test_timing:
+ ; Time how long until next length clock
+- inc de
+ ld a,(NR52)
+ and $01
+ jr nz,-
+
+ ;call print_de
+
+ ; Error if not in 0...4 range
+ ld a,d
+ cp 0
+ jp nz,test_failed
+ ld a,e
+ cp 5
+ jp nc,test_failed
+ ret
+
+main:
+
+ set_test 2,"Length period is wrong"
+ call sync_apu
+ wreg NR14,$40 ; avoids extra length clock
+ wreg NR11,$3F ; length = $01
+ wreg NR12,$08 ; silent without disabling channel
+ wreg NR14,$C0 ; start length
+ ld de,-$170
+ call test_timing
+
+ set_test 3,"Sweep period is wrong"
+ call sync_sweep
+ wreg NR10,$10 ; sweep period = 1
+ wreg NR12,$08 ; silent without disabling channel
+ wreg NR13,$FF ; max freq
+ wreg NR14,$87 ; start
+ ld de,-$2E4
+ call test_timing
+
+ set_test 4,"Sweep clock is synchronized with length"
+ call sync_sweep
+ wreg NR14,$40 ; avoids extra length clock
+ wreg NR11,$3F ; length = $01
+ wreg NR12,$08 ; silent without disabling channel
+ wreg NR14,$C0 ; start length
+ ld de,-$170
+ call test_timing
+
+ set_test 5,"Powering up APU MODs next frame time with 8192"
+ call sync_apu
+ ld de,-$16F
+ call test_power
+
+ call sync_apu
+ ld de,-$B5
+ call test_power_off
+
+ call sync_apu
+ delay_clocks 8192
+ ld de,-$B5
+ call test_power
+
+ call sync_apu
+ delay_clocks 8192
+ ld de,-$B5
+ call test_power_off
+
+ call sync_apu
+ ld de,-$B5
+ wreg NR52,$00 ; power off
+ delay_clocks 8192
+ call test_power
+
+ set_test 6,"Powering up APU resets 128 Hz sweep divider"
+ call sync_sweep
+ ld de,-$229
+ call test_power2
+
+ call sync_sweep
+ delay_apu 1
+ ld de,-$229
+ call test_power2
+
+ jp tests_passed
+
+test_power_off:
+ wreg NR52,$00 ; power off
+test_power:
+ wreg NR52,$80 ; power on
+ wreg NR14,$40
+ wreg NR11,-1 ; length = 1
+ wreg NR12,8
+ wreg NR14,$C0
+ jp test_timing
+
+test_power2:
+ wreg NR52,$00 ; power off
+ wreg NR52,$80 ; power on
+ wreg NR10,$11
+ wreg NR12,8
+ wreg NR13,$00
+ wreg NR14,$84
+ jp test_timing
diff --git a/playing-coffee/roms/dmg_sound/source/08-len ctr during power.s b/playing-coffee/roms/dmg_sound/source/08-len ctr during power.s
new file mode 100644
index 0000000..1ea218a
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/08-len ctr during power.s
@@ -0,0 +1,84 @@
+; On CGB, length counters are reset when powered up.
+; On DMG, they are unaffected, and not clocked.
+
+;.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+enable_len_ctrs:
+ wreg NR22,8
+ wreg NR24,$C0
+ wreg NR12,8
+ wreg NR14,$C0
+ wreg NR30,$80
+ wreg NR34,$C0
+ wreg NR42,8
+ wreg NR44,$C0
+ ret
+
+main:
+ call sync_apu
+
+ ld a,0
+ call fill_apu_regs
+
+ ; Load length counters
+ wreg NR41,-$33
+ wreg NR31,-$44
+ wreg NR11,-$11
+ wreg NR21,-$22
+
+ delay_clocks 8192
+ call enable_len_ctrs
+
+ ; Power down. Comment out to see what would
+ ; happen if length counters did run.
+ wreg NR52,$00
+
+ ; Try to enable length counters
+ call enable_len_ctrs
+
+ ; Give plenty of time for them to be clocked
+ delay_msec 250
+
+ ; Power back on and wait a bit longer
+ wreg NR52,$80
+ ;call enable_len_ctrs ; can't do this here
+ delay_clocks 2048
+
+ ; Get values from length counters
+ wreg NR22,8
+ wreg NR24,$C0
+ ld a,$02
+ call get_len_a
+ push af
+
+ wreg NR12,8
+ wreg NR14,$C0
+ ld a,$01
+ call get_len_a
+ push af
+
+ wreg NR30,$80
+ wreg NR34,$C0
+ ld a,$04
+ call get_len_a
+ push af
+
+ wreg NR42,8
+ wreg NR44,$C0
+ ld a,$08
+ call get_len_a
+
+ ; Print them
+ call print_a
+ pop af
+ call print_a
+ pop af
+ call print_a
+ pop af
+ call print_a
+
+ check_crc_dmg_cgb $32F0CFBB,$3CF589B4
+ jp tests_passed
diff --git a/playing-coffee/roms/dmg_sound/source/09-wave read while on.s b/playing-coffee/roms/dmg_sound/source/09-wave read while on.s
new file mode 100644
index 0000000..2d06a77
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/09-wave read while on.s
@@ -0,0 +1,41 @@
+; Reads from wave RAM while playing, each time 2
+; clocks later.
+
+;.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ wreg NR51,0 ; mute sound
+ loop_n_times test,69
+ check_crc_dmg_cgb $118A3620,$270DA9A3
+ jp tests_passed
+
+test:
+ add $99
+ ld b,a
+
+ ; Reload wave and have its first
+ ; sample read occur 2 clocks earlier
+ ; each loop iteration
+ ld hl,wave
+ call load_wave
+ wreg NR30,$80 ; enable
+ wreg NR32,$00 ; silent
+ ld a,b
+ sta NR33 ; period
+ wreg NR34,$87 ; start
+
+ ; Read from wave
+ wreg NR33,-2 ; period = 4
+ delay_clocks 176
+ lda WAVE
+
+ call print_a
+
+ ret
+
+wave:
+ .byte $00,$11,$22,$33,$44,$55,$66,$77
+ .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
diff --git a/playing-coffee/roms/dmg_sound/source/10-wave trigger while on.s b/playing-coffee/roms/dmg_sound/source/10-wave trigger while on.s
new file mode 100644
index 0000000..d7ec063
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/10-wave trigger while on.s
@@ -0,0 +1,49 @@
+; Retriggers wave without stopping first
+
+;.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ wreg NR51,0 ; mute sound
+ loop_n_times test,69
+ check_crc_dmg_cgb $533D6D4D,$8130733A
+ jp tests_passed
+
+test:
+ add $99
+ ld b,a
+
+ ; Reload wave and have its first
+ ; sample read occur 2 clocks earlier
+ ; each loop iteration
+ ld hl,wave
+ call load_wave
+ wreg NR30,$80 ; enable
+ wreg NR32,$00 ; silent
+ ld a,b
+ sta NR33 ; period
+ wreg NR34,$87 ; start
+
+ ; Retrigger wave
+ wreg NR33,-2 ; period = 4
+ delay_clocks 168
+ wreg NR34,$87 ; restart
+ delay_clocks 40
+
+ ; Print wave RAM
+ wreg NR30,0
+ ld c,$30
+- ld a,($FF00+c)
+ call print_a
+ inc c
+ bit 6,c
+ jr z,-
+ call print_newline
+
+ ret
+
+wave:
+ .byte $00,$11,$22,$33,$44,$55,$66,$77
+ .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
diff --git a/playing-coffee/roms/dmg_sound/source/11-regs after power.s b/playing-coffee/roms/dmg_sound/source/11-regs after power.s
new file mode 100644
index 0000000..56506bb
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/11-regs after power.s
@@ -0,0 +1,52 @@
+; After powering sound off then on, NR12 and NR14 are 0
+; and NR44 is unchanged. While powered off, writes to
+; NR41 are NOT ignored.
+
+.define REQUIRE_DMG 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ call sync_apu
+ ld a,$FF
+ call fill_apu_regs
+
+ ; Power down for a moment
+ wreg NR52,$00
+ wreg NR41,-$12
+ wreg NR12,$F0
+ delay_msec 100
+ wreg NR52,$80
+
+ set_test 2,"Powering off should clear NR12"
+ call sync_apu
+ wreg NR14,$80
+ lda NR52
+ and $01
+ jp nz,test_failed
+
+ set_test 3,"Powering off should clear NR13"
+ call sync_apu
+ wreg NR10,$11
+ wreg NR12,$08
+ wreg NR14,$80
+ delay_apu 20
+ lda NR52
+ and $01
+ jp z,test_failed
+
+ set_test 4,"Powering off shouldn't affect NR41"
+ call sync_apu
+ delay_clocks 8192 ; avoids extra length clocking
+ wreg NR42,$08
+ wreg NR44,$C0
+ delay_apu $11
+ lda NR52
+ and $08
+ jp z,test_failed
+ delay_apu 1
+ lda NR52
+ and $08
+ jp nz,test_failed
+
+ jp tests_passed
diff --git a/playing-coffee/roms/dmg_sound/source/12-wave write while on.s b/playing-coffee/roms/dmg_sound/source/12-wave write while on.s
new file mode 100644
index 0000000..de4df7e
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/12-wave write while on.s
@@ -0,0 +1,49 @@
+; Writes to wave RAM while playing
+
+.define REQUIRE_DMG 1
+;.define REQUIRE_CGB 1
+.include "shell.inc"
+.include "apu.s"
+
+main:
+ loop_n_times test,69
+
+ ; CGB behaves erratically, so DMG-only for now
+ check_crc_dmg_cgb $3B4538A9,$2B27544E
+ jp tests_passed
+
+test:
+ add $99
+ ld b,a
+
+ ; Reload wave and have its first
+ ; sample read occur 2 clocks earlier
+ ; each loop iteration
+ ld hl,wave
+ call load_wave
+ wreg NR30,$80 ; enable
+ wreg NR32,$00 ; silent
+ ld a,b
+ sta NR33 ; period
+ wreg NR34,$87 ; start
+
+ ; Write to wave
+ wreg NR33,-2 ; period = 4
+ delay_clocks 168
+ wreg WAVE,$F7
+
+ ; Print wave RAM
+ wreg NR30,0
+ delay 1000
+ ld hl,WAVE
+- ld a,(hl+)
+ call print_a
+ bit 6,l
+ jr z,-
+ call print_newline
+
+ ret
+
+wave:
+ .byte $00,$11,$22,$33,$44,$55,$66,$77
+ .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF
diff --git a/playing-coffee/roms/dmg_sound/source/common/build_gbs.s b/playing-coffee/roms/dmg_sound/source/common/build_gbs.s
new file mode 100644
index 0000000..877754f
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/common/build_gbs.s
@@ -0,0 +1,139 @@
+; Build as GBS music file
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $2000 size $2000
+ slot 1 $C000 size $2000
+.endMe
+
+.romBankSize $2000
+.romBanks 2
+
+.define RST_OFFSET $70
+
+.ifndef GBS_TMA
+ .define GBS_TMA 0
+.endif
+
+.ifndef GBS_TAC
+ .define GBS_TAC 0
+.endif
+
+;;;; GBS music file header
+
+.ifndef CUSTOM_HEADER
+ .byte "GBS"
+ .byte 1,1,1 ; vers, song count, first song
+ .word load_addr, reset, gbs_play_, std_stack
+ .byte GBS_TMA,GBS_TAC ; timer
+.endif
+ .org $10
+ .ds $60,0
+load_addr:
+ .org RST_OFFSET+$70 ; space for RST vectors
+ .ds $148-RST_OFFSET-$70,0
+ .org $150 ; wla insists on generating GB header
+
+gbs_play_:
+ jp gbs_play ; GBS spec disallows having gbs_play in RAM
+
+;;;; Shell
+
+.include "shell.s"
+
+.define gbs_idle nv_ram
+.redefine nv_ram nv_ram+2
+
+init_runtime:
+ ; Identify as DMG hardware
+ ld a,$01
+ ld (gb_id),a
+
+ ; Save return address
+ pop hl
+ ld a,l
+ ld (gbs_idle),a
+ ld a,h
+ ld (gbs_idle+1),a
+
+ ; Delay 1/4 second to give time
+ ; for GBS player to interrupt with
+ ; play, if it's going to do so
+ delay_msec 250
+
+.ifndef CUSTOM_PLAY
+gbs_play:
+.endif
+ ; Get return address
+ ld a,(gbs_idle)
+ ld l,a
+ ld a,(gbs_idle+1)
+ ld h,a
+
+ ; If zero, then play interrupted init
+ ; call, or another play call, and we
+ ; can't run the program properly.
+ or l
+ jp z,internal_error
+
+ setw gbs_idle,0
+ jp hl
+
+
+; Reports A in binary as high and low tones, with
+; leading low tone for reference. Omits leading
+; zeroes.
+; Preserved: AF, BC, DE, HL
+play_byte:
+ push af
+ push hl
+
+ ; HL = (A << 1) | 1
+ scf
+ rla
+ ld l,a
+ ld h,0
+ rl h
+
+ ; Shift left until next-to-top bit is 1
+- add hl,hl
+ bit 6,h
+ jr z,-
+
+ ; Reset sound
+ delay_msec 400
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+- add hl,hl
+
+ ; Low or high pitch based on bit shifted out
+ ; of HL
+ ld a,0
+ jr nc,+
+ ld a,$FF
++ sta NR23
+
+ ; Play short tone
+ wreg NR21,$A0
+ wreg NR22,$F0
+ wreg NR24,$86
+ delay_msec 75
+ wreg NR22,0
+ wreg NR23,$F8
+ wreg NR24,$87
+ delay_msec 200
+
+ ; Loop until HL = $8000
+ ld a,h
+ xor $80
+ or l
+ jr nz,-
+
+ pop hl
+ pop af
+ ret
+
+.ends
diff --git a/playing-coffee/roms/dmg_sound/source/common/build_rom.s b/playing-coffee/roms/dmg_sound/source/common/build_rom.s
new file mode 100644
index 0000000..c1595b7
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/common/build_rom.s
@@ -0,0 +1,70 @@
+; Build as GB ROM
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $0000 size $4000
+ slot 1 $C000 size $4000
+.endMe
+
+.romBankSize $4000
+.romBanks 2
+
+.cartridgeType 3 ; MBC1+RAM+battery
+.ramsize 02 ; 8K
+.computeChecksum
+.computeComplementCheck
+
+;;;; GB ROM header
+
+ ; Reserve space for RST handlers
+ .org $70
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .ds $90,0
+
+ ; GB header read by bootrom
+ .org $100
+ nop
+ jp reset
+
+ ; Nintendo logo required for proper boot
+ .byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
+ .byte $03,$73,$00,$83,$00,$0C,$00,$0D
+ .byte $00,$08,$11,$1F,$88,$89,$00,$0E
+ .byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
+ .byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
+ .byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
+
+ ; Internal name
+ .ifdef ROM_NAME
+ .byte ROM_NAME
+ .endif
+
+ ; CGB/DMG requirements
+ .org $143
+ .ifdef REQUIRE_CGB
+ .byte $C0
+ .else
+ .ifndef REQUIRE_DMG
+ .byte $80
+ .endif
+ .endif
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .org $150
+ .ds $2150-$150,0
+
+;;;; Shell
+
+.define NEED_CONSOLE 1
+.include "shell.s"
+
+init_runtime:
+ ret
+
+play_byte:
+ ret
+
+.ends
diff --git a/playing-coffee/roms/dmg_sound/source/common/console.bin b/playing-coffee/roms/dmg_sound/source/common/console.bin
new file mode 100644
index 0000000..b02f2d3
Binary files /dev/null and b/playing-coffee/roms/dmg_sound/source/common/console.bin differ
diff --git a/playing-coffee/roms/dmg_sound/source/common/console.s b/playing-coffee/roms/dmg_sound/source/common/console.s
new file mode 100644
index 0000000..403fa5c
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/common/console.s
@@ -0,0 +1,285 @@
+; Scrolling text console
+
+; Console is 20x18 characters. Buffers lines, so
+; output doesn't appear until a newline or flush.
+; If scrolling isn't supported (i.e. SCY is treated
+; as if always zero), the first 18 lines will
+; still print properly). Also works properly if
+; LY isn't supported (always reads back as the same
+; value).
+
+.define console_width 20
+
+.define console_buf bss+0
+.define console_pos bss+console_width
+.define console_mode bss+console_width+1
+.define console_scroll bss+console_width+2
+.redefine bss bss+console_width+3
+
+
+; Waits for start of LCD blanking period
+; Preserved: BC, DE, HL
+console_wait_vbl:
+ push bc
+
+ ; Wait for start of vblank, with
+ ; timeout in case LY doesn't work
+ ; or LCD is disabled.
+ ld bc,-1250
+- inc bc
+ ld a,b
+ or c
+ jr z,@timeout
+ lda LY
+ cp 144
+ jr nz,-
+@timeout:
+
+ pop bc
+ ret
+
+
+; Initializes text console
+console_init:
+ call console_hide
+
+ ; CGB-specific inits
+ ld a,(gb_id)
+ and gb_id_cgb
+ call nz,@init_cgb
+
+ ; Clear nametable
+ ld a,' '
+ call @fill_nametable
+
+ ; Load tiles
+ ld hl,TILES+$200
+ ld c,0
+ call @load_tiles
+ ld hl,TILES+$A00
+ ld c,$FF
+ call @load_tiles
+
+ ; Init state
+ setb console_pos,console_width
+ setb console_mode,0
+ setb console_scroll,-8
+ call console_scroll_up_
+ jr console_show
+
+@fill_nametable:
+ ld hl,BGMAP0
+ ld b,4
+- ld (hl),a
+ inc l
+ jr nz,-
+ inc h
+ dec b
+ jr nz,-
+ ret
+
+@init_cgb:
+ ; Clear palette
+ wreg $FF68,$80
+ ld b,16
+- wreg $FF69,$FF
+ wreg $FF69,$7F
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ dec b
+ jr nz,-
+
+ ; Clear attributes
+ wreg VBK,1
+ ld a,0
+ call @fill_nametable
+
+ wreg VBK,0
+ ret
+
+@load_tiles:
+ ld de,ASCII
+ ld b,96
+-- push bc
+ ld b,8
+- ld a,(de)
+ inc de
+ xor c
+ ldi (hl),a
+ ldi (hl),a
+ dec b
+ jr nz,-
+ pop bc
+ dec b
+ jr nz,--
+ ret
+
+
+; Shows console display
+; Preserved: AF, BC, DE, HL
+console_show:
+ push af
+
+ ; Enable LCD
+ call console_wait_vbl
+ wreg LCDC,$91
+ wreg SCX,0
+ wreg BGP,$E4
+
+ jp console_apply_scroll_
+
+
+; Hides console display by turning LCD off
+; Preserved: AF, BC, DE, HL
+console_hide:
+ push af
+
+ ; LCD off
+ call console_wait_vbl
+ wreg LCDC,$11
+
+ pop af
+ ret
+
+
+; Changes to normal text mode
+; Preserved: BC, DE, HL
+console_normal:
+ xor a
+ jr console_set_mode
+
+; Changes to inverse text mode
+; Preserved: BC, DE, HL
+console_inverse:
+ ld a,$80
+
+; Changes console mode to A.
+; 0: Normal, $80: Inverse
+; Preserved: BC, DE, HL
+console_set_mode:
+ and $80
+ ld (console_mode),a
+ ret
+
+
+; Prints char A to console. Will not appear until
+; a newline or flush occurs.
+; Preserved: AF, BC, DE, HL
+console_print:
+ push af
+
+ cp 10
+ jr z,console_newline_
+
+ push hl
+ push af
+ ld hl,console_pos
+ ldi a,(hl)
+ cp BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee/roms/dmg_sound/source/common/delay.s b/playing-coffee/roms/dmg_sound/source/common/delay.s
new file mode 100644
index 0000000..cbcdcf3
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+.endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+.endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+.endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee/roms/dmg_sound/source/common/gb.inc b/playing-coffee/roms/dmg_sound/source/common/gb.inc
new file mode 100644
index 0000000..2d0118d
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/common/gb.inc
@@ -0,0 +1,81 @@
+; Game Boy hardware addresses
+
+; $0000-$3FFF Fixed ROM bank
+; $4000-$7FFF Switchable bank
+; $8000-$9FFF VRAM
+; $A000-$BFFF optional cartridge RAM
+; $C000-$DFFF RAM
+; $E000-$FDFF RAM mirror
+; $FE00-$FE9F OAM
+; $FEA0-$FEFF unused
+; $FF00-$FF7F registers
+; $FF80-$FFFE RAM
+; $FFFF register
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define BRAM $A000 ; cart memory
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+; Registers
+
+.define RAMEN $0000 ; cartridge WRAM control
+.define BANK $2000 ; bank select
+.define P1 $FF00 ; controller
+
+; Game link I/O
+.define SB $FF01 ; serial buffer
+.define SC $FF02 ; serial control
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee/roms/dmg_sound/source/common/macros.inc b/playing-coffee/roms/dmg_sound/source/common/macros.inc
new file mode 100644
index 0000000..9d7cf44
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/common/macros.inc
@@ -0,0 +1,91 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ; addr
+ ldh a,(\1 - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ; addr
+ ldh (\1 - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Writes byte to addr
+; Preserved: F, BC, DE, HL
+; Time: 6 cycles
+.macro setb ; addr, data
+ ld a,\2
+ ld (\1),a
+.endm
+
+; Writes word to addr
+; Preserved: F, BC, DE, HL
+; Time: 12 cycles
+.macro setw ; addr, data
+ ld a,<\2
+ ld (\1),a
+ ld a,>\2
+ ld (\1+1),a
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee/roms/dmg_sound/source/common/numbers.s b/playing-coffee/roms/dmg_sound/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee/roms/dmg_sound/source/common/printing.s b/playing-coffee/roms/dmg_sound/source/common/printing.s
new file mode 100644
index 0000000..bb9389b
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/common/printing.s
@@ -0,0 +1,77 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+;print_char_nocrc (defined by user)
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee/roms/dmg_sound/source/common/shell.s b/playing-coffee/roms/dmg_sound/source/common/shell.s
new file mode 100644
index 0000000..3e588c7
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/common/shell.s
@@ -0,0 +1,261 @@
+; Common routines and runtime
+
+.define RUNTIME_INCLUDED 1
+
+; A few bytes of RAM that aren't cleared
+.define nv_ram_base $D800
+.define nv_ram nv_ram_base
+
+; Address of next normal variable
+.define bss_base nv_ram_base+$80
+.define bss bss_base
+
+; Address of next direct-page ($FFxx) variable
+.define dp_base $FF80
+.define dp dp_base
+
+; Top of stack
+.define std_stack $DFFF+1
+
+; Final exit result byte is written here
+.define final_result $A000
+
+; Text output is written here as zero-terminated string
+.define text_out_base $A004
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+.define gb_id nv_ram
+.redefine nv_ram nv_ram+1
+
+; Copies C*$100 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+- ld a,(hl+)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+
+.ifndef RST_OFFSET
+ .define RST_OFFSET 0
+.endif
+
+.ifndef CUSTOM_RESET
+ reset:
+ di
+
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ ld c,$14
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org 0
+ jp std_reset
+.endif
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
+
+ ; Catch user code running off end
+ jp internal_error
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld (gb_id),a
+
+ ; Clear memory except very top of stack
+ ld bc,std_stack-bss_base - 2
+ ld hl,bss_base
+ call clear_mem
+ ld bc,$FFFF-dp_base
+ ld hl,dp_base
+ call clear_mem
+
+ ; Init hardware
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ call init_runtime
+ call init_text_out
+ call console_init
+ call init_testing
+
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ call console_show
+ pop af
+ call play_byte
+ jp post_exit
+
++ push af
+ call print_newline
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+
+; Clears BC bytes starting at HL
+clear_mem:
+ ; If C>0, increment B
+ dec bc
+ inc c
+ inc b
+
+ ld a,0
+- ld (hl+),a
+ dec c
+ jr nz,-
+ dec b
+ jr nz,-
+ ret
+
+
+; Reports internal error and exits with code 255
+internal_error:
+ print_str "Internal error"
+ ld a,255
+ jp exit
+
+
+; build_devcart and build_multi customize this
+.ifndef CUSTOM_PRINT
+ .define text_out_addr bss+0
+ .redefine bss bss+2
+
+ ; Initializes text output to cartridge RAM
+ init_text_out:
+ ; Enable cartridge RAM and set text output pointer
+ setb RAMEN,$0A
+ setw text_out_addr,text_out_base
+ setb text_out_base-3,$DE
+ setb text_out_base-2,$B0
+ setb text_out_base-1,$61
+ setb text_out_base,0
+ setb final_result,$80
+ ret
+
+
+ ; Appends character to text output string
+ ; Preserved: AF, BC, DE, HL
+ write_text_out:
+ push hl
+ push af
+ ld a,(text_out_addr)
+ ld l,a
+ ld a,(text_out_addr+1)
+ ld h,a
+ inc hl
+ ld (hl),0
+ ld a,l
+ ld (text_out_addr),a
+ ld a,h
+ ld (text_out_addr+1),a
+ dec hl
+ pop af
+ ld (hl),a
+ pop hl
+ ret
+
+ print_char_nocrc:
+ call write_text_out
+ jp console_print
+.endif
+
+
+; only build_rom uses console
+.ifdef NEED_CONSOLE
+ .include "console.s"
+.else
+ console_init:
+ console_print:
+ console_flush:
+ console_normal:
+ console_inverse:
+ console_show:
+ console_set_mode:
+ ret
+.endif
+
+
+; build_devcart and build_multi need to customize this
+.ifndef CUSTOM_EXIT
+ post_exit:
+ ld (final_result),a
+ forever:
+ wreg NR52,0 ; sound off
+- jr -
+.endif
+
+
+.macro def_rst ARGS addr
+ .bank 0 slot 0
+ .org addr+RST_OFFSET
+.endm
diff --git a/playing-coffee/roms/dmg_sound/source/common/testing.s b/playing-coffee/roms/dmg_sound/source/common/testing.s
new file mode 100644
index 0000000..5dd63ce
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/common/testing.s
@@ -0,0 +1,195 @@
+; Diagnostic and testing utilities
+
+.define test_code bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text.
+; Takes multiple strings due to wla's idiotic
+; default limit of 63 chars per string.
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2[,text3]]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .if NARGS > 3
+ .byte \4
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (test_code),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(test_code)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(test_code)
+ cp $FF ; if a = $FF then a = 1
+ jr nz,+
+ ld a,1
++ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+; Checks CRC, differing based on DMG or CGB build
+.macro check_crc_dmg_cgb ARGS dmg, cgb
+ .ifdef REQUIRE_DMG
+ check_crc dmg
+ .else
+ .ifdef REQUIRE_CGB
+ check_crc cgb
+ .else
+ .printt "CGB or DMG must be specified"
+ .fail
+ .endif
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee/roms/dmg_sound/source/linkfile b/playing-coffee/roms/dmg_sound/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee/roms/dmg_sound/source/readme.txt b/playing-coffee/roms/dmg_sound/source/readme.txt
new file mode 100644
index 0000000..a0269c1
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/readme.txt
@@ -0,0 +1,82 @@
+Game Boy Tests Source Code
+--------------------------
+
+Building with wla-dx
+--------------------
+To assemble a test ROM with wla-dx, use the following commands:
+
+ wla -o source_filename_here.s test.o
+ wlalink linkfile test.gb
+
+To assemble as a GBS music file:
+
+ wla -o source_filename_here.s test.o -DBUILD_GBS
+ wlalink linkfile test.gbs
+
+Note that some tests might only work when built as a ROM or GBS file,
+but not both.
+
+Some tests might include a ROM/GBS that has all the tests combined.
+Building such a multi-test is complex and the necessary files aren't
+included.
+
+
+Framework
+---------
+Each test is in a single source file, and makes use of several library
+source files from common/. This framework provides common services and
+reduces code to only that which performs the actual test. Virtually all
+tests include "shell.inc" at the beginning, which sets things up and
+includes all the appropriate library files.
+
+The reset handler does minimal GB hardware initialization, clears RAM,
+sets up the text console, then runs main. Main can exit by returning or
+jumping to "exit" with an error code in A. Exit reports the code then
+goes into an infinite loop. If the code is 0, it doesn't do anything,
+otherwise it reports the code. Code 1 is reported as "Failed", and the
+rest as "Error ".
+
+The default is to build a ROM. Defining BUILD_GBS will build as an GBS.
+The other build types aren't supported due to their complexity. I load
+the code into RAM at $C000 since my devcart requires it, and I don't
+want the normal ROM to differ in any way from what I've tested. This
+also allows easy self-modifying code.
+
+Several routines are available to print values and text to the console.
+Most update a running CRC-32 checksum which can be checked with
+check_crc, allowing ALL the output to be checked very easily. If the
+checksum doesn't match, it is printed, so you can run the code on a GB
+and paste the correct checksum into your code.
+
+
+Macros
+------
+Some macros are used to make common operations more convenient. The left
+is equivalent to the right:
+
+ Macro Equivalent
+ -------------------------------------
+ lda addr ldh a,(addr-$FF00)
+
+ sta addr ldh (addr-$FF00),a
+
+ wreg addr,data ld a,data
+ ldh (addr-$FF00),a
+
+ setb ld a,data
+ ld (addr),a
+
+ setw setb addr+0,data
+
+ for_loop routine,begin,end,step
+ calls routine with A set to successive values
+
+ loop_n_times routine,count
+ calls routine with A from 0 to count-1
+
+ print_str "str" prints string
+
+
+--
+Shay Green
diff --git a/playing-coffee/roms/dmg_sound/source/shell.inc b/playing-coffee/roms/dmg_sound/source/shell.inc
new file mode 100644
index 0000000..0d43faa
--- /dev/null
+++ b/playing-coffee/roms/dmg_sound/source/shell.inc
@@ -0,0 +1,27 @@
+; Included at beginning of all programs
+; that use standard shell
+
+; Get include files from common/
+.incdir "common"
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.else
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
+
+.endif ; .ifdef BUILD_MULTI
diff --git a/playing-coffee/roms/drmario.gb b/playing-coffee/roms/drmario.gb
new file mode 100644
index 0000000..2f3dc69
Binary files /dev/null and b/playing-coffee/roms/drmario.gb differ
diff --git a/playing-coffee/roms/halt_bug.gb b/playing-coffee/roms/halt_bug.gb
new file mode 100644
index 0000000..38e3662
Binary files /dev/null and b/playing-coffee/roms/halt_bug.gb differ
diff --git a/playing-coffee/roms/instr_timing/instr_timing.gb b/playing-coffee/roms/instr_timing/instr_timing.gb
new file mode 100644
index 0000000..61d2b20
Binary files /dev/null and b/playing-coffee/roms/instr_timing/instr_timing.gb differ
diff --git a/playing-coffee/roms/instr_timing/readme.txt b/playing-coffee/roms/instr_timing/readme.txt
new file mode 100644
index 0000000..b64024f
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/readme.txt
@@ -0,0 +1,139 @@
+Game Boy CPU Instruction Timing Test
+------------------------------------
+This ROM tests the timings of all CPU instructions except HALT, STOP,
+and the 11 illegal opcodes. For conditional instructions, it tests taken
+and not taken timings. This test requires proper timer operation (TAC,
+TIMA, TMA).
+
+Failed instructions are listed as
+
+ [CB] opcode:measured time-correct time
+
+Times are in terms of instruction cycles, where NOP takes one cycle.
+
+
+Verified cycle timing tables
+----------------------------
+The test internally uses a table of proper cycle times, which can be
+used in an emulator to ensure proper timing. The only changes below are
+removal of the .byte prefixes, and addition of commas at the ends, so
+that they can be used without changes in most programming languages. For
+added correctness assurance, the original tables can be found at the end
+of the source code.
+
+Normal instructions:
+
+ 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1,
+ 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1,
+ 2,3,2,2,1,1,2,1,2,2,2,2,1,1,2,1,
+ 2,3,2,2,3,3,3,1,2,2,2,2,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,
+ 2,3,3,4,3,4,2,4,2,4,3,0,3,6,2,4,
+ 2,3,3,0,3,4,2,4,2,4,3,0,3,0,2,4,
+ 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4,
+ 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4
+
+CB-prefixed instructions:
+
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2,
+ 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2,
+ 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2,
+ 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,
+ 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+
+
+Internal operation
+------------------
+Before each instruction is executed, the test sets up registers and
+memory in such a way that the instruction will cleanly execute and then
+end up at a common destination, without trashing anything important. The
+timing itself is done by first synchronizing to the timer via a loop,
+executing the instruction, then using a similar loop to determine how
+many clocks elapsed.
+
+
+Failure codes
+-------------
+Failed tests may print a failure code, and also short description of the
+problem. For more information about a failure code, look in the
+corresponding source file in source/; the point in the code where
+"set_test n" occurs is where that failure code will be generated.
+Failure code 1 is a general failure of the test; any further information
+will be printed.
+
+Note that once a sub-test fails, no further tests for that file are run.
+
+
+Console output
+--------------
+Information is printed on screen in a way that needs only minimum LCD
+support, and won't hang if LCD output isn't supported at all.
+Specifically, while polling LY to wait for vblank, it will time out if
+it takes too long, so LY always reading back as the same value won't
+hang the test. It's also OK if scrolling isn't supported; in this case,
+text will appear starting at the top of the screen.
+
+Everything printed on screen is also sent to the game link port by
+writing the character to SB, then writing $81 to SC. This is useful for
+tests which print lots of information that scrolls off screen.
+
+
+Source code
+-----------
+Source code is included for all tests, in source/. It can be used to
+build the individual test ROMs. Code for the multi test isn't included
+due to the complexity of putting everything together.
+
+Code is written for the wla-dx assembler. To assemble a particular test,
+execute
+
+ wla -o "source_filename.s" test.o
+ wlalink linkfile test.gb
+
+Test code uses a common shell framework contained in common/.
+
+
+Internal framework operation
+----------------------------
+Tests use a common framework for setting things up, reporting results,
+and ending. All files first include "shell.inc", which sets up the ROM
+header and shell code, and includes other commonly-used modules.
+
+One oddity is that test code is first copied to internal RAM at $D000,
+then executed there. This allows self-modification, and ensures the code
+is executed the same way it is on my devcart, which doesn't have a
+rewritable ROM as most do.
+
+Some macros are used to simplify common tasks:
+
+ Macro Behavior
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ wreg addr,data Writes data to addr using LDH
+ lda addr Loads byte from addr into A using LDH
+ sta addr Stores A at addr using LDH
+ delay n Delays n cycles, where NOP = 1 cycle
+ delay_msec n Delays n milliseconds
+ set_test n,"Cause" Sets failure code and optional string
+
+Routines and macros are documented where they are defined.
+
+--
+Shay Green
diff --git a/playing-coffee/roms/instr_timing/source/common/build_gbs.s b/playing-coffee/roms/instr_timing/source/common/build_gbs.s
new file mode 100644
index 0000000..7ac8d3c
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/common/build_gbs.s
@@ -0,0 +1,121 @@
+; Build as GBS music file
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $3000 size $1000
+ slot 1 $C000 size $1000
+.endMe
+
+.romBankSize $1000
+.romBanks 2
+
+
+;;;; GBS music file header
+
+.byte "GBS"
+.byte 1 ; vers
+.byte 1 ; songs
+.byte 1 ; first song
+.word load_addr
+.word reset
+.word gbs_play
+.word std_stack
+.byte 0,0 ; timer
+.ds $60,0
+load_addr:
+
+; WLA assumes we're building ROM and messes
+; with bytes at the beginning, so skip them.
+.ds $100,0
+
+
+;;;; Shell
+
+.include "runtime.s"
+
+init_runtime:
+ ld a,$01 ; Identify as DMG hardware
+ ld (gb_id),a
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+ ret
+
+std_print:
+ sta SB
+ wreg SC,$81
+ delay 2304
+ ret
+
+post_exit:
+ call play_byte
+forever:
+ wreg NR52,0 ; sound off
+- jp -
+
+.ifndef CUSTOM_RESET
+ gbs_play:
+.endif
+console_flush:
+console_normal:
+console_inverse:
+console_set_mode:
+ ret
+
+; Reports A in binary as high and low tones, with
+; leading low tone for reference. Omits leading
+; zeroes.
+; Preserved: AF, BC, DE, HL
+play_byte:
+ push af
+ push hl
+
+ ; HL = (A << 1) | 1
+ scf
+ rla
+ ld l,a
+ ld h,0
+ rl h
+
+ ; Shift left until next-to-top bit is 1
+- add hl,hl
+ bit 6,h
+ jr z,-
+
+ ; Reset sound
+ delay_msec 400
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+- add hl,hl
+
+ ; Low or high pitch based on bit shifted out
+ ; of HL
+ ld a,0
+ jr nc,+
+ ld a,$FF
++ sta NR23
+
+ ; Play short tone
+ wreg NR21,$A0
+ wreg NR22,$F0
+ wreg NR24,$86
+ delay_msec 75
+ wreg NR22,0
+ wreg NR23,$F8
+ wreg NR24,$87
+ delay_msec 200
+
+ ; Loop until HL = $8000
+ ld a,h
+ xor $80
+ or l
+ jr nz,-
+
+ pop hl
+ pop af
+ ret
+
+.ends
diff --git a/playing-coffee/roms/instr_timing/source/common/build_rom.s b/playing-coffee/roms/instr_timing/source/common/build_rom.s
new file mode 100644
index 0000000..e1e220f
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/common/build_rom.s
@@ -0,0 +1,80 @@
+; Build as GB ROM
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $0000 size $4000
+ slot 1 $C000 size $4000
+.endMe
+
+.romBankSize $4000 ; generates $8000 byte ROM
+.romBanks 2
+
+.cartridgeType 1 ; MBC1
+.computeChecksum
+.computeComplementCheck
+
+
+;;;; GB ROM header
+
+; GB header read by bootrom
+.org $100
+ nop
+ jp reset
+
+; Nintendo logo required for proper boot
+.byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
+.byte $03,$73,$00,$83,$00,$0C,$00,$0D
+.byte $00,$08,$11,$1F,$88,$89,$00,$0E
+.byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
+.byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
+.byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
+
+; Internal name
+.ifdef ROM_NAME
+ .byte ROM_NAME
+.endif
+
+; CGB/DMG requirements
+.org $143
+ .ifdef REQUIRE_CGB
+ .byte $C0
+ .else
+ .ifndef REQUIRE_DMG
+ .byte $80
+ .endif
+ .endif
+
+.org $200
+
+
+;;;; Shell
+
+.include "runtime.s"
+.include "console.s"
+
+init_runtime:
+ call console_init
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+ ret
+
+std_print:
+ push af
+ sta SB
+ wreg SC,$81
+ delay 2304
+ pop af
+ jp console_print
+
+post_exit:
+ call console_show
+ call play_byte
+forever:
+ wreg NR52,0 ; sound off
+- jr -
+
+play_byte:
+ ret
+
+.ends
diff --git a/playing-coffee/roms/instr_timing/source/common/console.bin b/playing-coffee/roms/instr_timing/source/common/console.bin
new file mode 100644
index 0000000..b02f2d3
Binary files /dev/null and b/playing-coffee/roms/instr_timing/source/common/console.bin differ
diff --git a/playing-coffee/roms/instr_timing/source/common/console.s b/playing-coffee/roms/instr_timing/source/common/console.s
new file mode 100644
index 0000000..5307f6b
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/common/console.s
@@ -0,0 +1,291 @@
+; Scrolling text console
+
+; Console is 20x18 characters. Buffers lines, so
+; output doesn't appear until a newline or flush.
+; If scrolling isn't supported (i.e. SCY is treated
+; as if always zero), the first 18 lines will
+; still print properly). Also works properly if
+; LY isn't supported (always reads back as the same
+; value).
+
+.define console_width 20
+
+.define console_buf bss+0
+.define console_pos bss+console_width
+.define console_mode bss+console_width+1
+.define console_scroll bss+console_width+2
+.redefine bss bss+console_width+3
+
+
+; Waits for start of LCD blanking period
+; Preserved: BC, DE, HL
+console_wait_vbl:
+ push bc
+
+ ; Wait for start of vblank, with
+ ; timeout in case LY doesn't work
+ ; or LCD is disabled.
+ ld bc,-1250
+- inc bc
+ ld a,b
+ or c
+ jr z,@timeout
+ lda LY
+ cp 144
+ jr nz,-
+@timeout:
+
+ pop bc
+ ret
+
+
+; Initializes text console
+console_init:
+ call console_hide
+
+ ; CGB-specific inits
+ ld a,(gb_id)
+ and gb_id_cgb
+ call nz,@init_cgb
+
+ ; Clear nametable
+ ld a,' '
+ call @fill_nametable
+
+ ; Load tiles
+ ld hl,TILES+$200
+ ld c,0
+ call @load_tiles
+ ld hl,TILES+$A00
+ ld c,$FF
+ call @load_tiles
+
+ ; Init state
+ ld a,console_width
+ ld (console_pos),a
+ ld a,0
+ ld (console_mode),a
+ ld a,-8
+ ld (console_scroll),a
+ call console_scroll_up_
+ jr console_show
+
+@fill_nametable:
+ ld hl,BGMAP0
+ ld b,4
+- ld (hl),a
+ inc l
+ jr nz,-
+ inc h
+ dec b
+ jr nz,-
+ ret
+
+@init_cgb:
+ ; Clear palette
+ wreg $FF68,$80
+ ld b,16
+- wreg $FF69,$FF
+ wreg $FF69,$7F
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ dec b
+ jr nz,-
+
+ ; Clear attributes
+ ld a,1
+ ld (VBK),a
+ ld a,0
+ call @fill_nametable
+
+ ld a,0
+ ld (VBK),a
+ ret
+
+@load_tiles:
+ ld de,ASCII
+ ld b,96
+-- push bc
+ ld b,8
+- ld a,(de)
+ inc de
+ xor c
+ ldi (hl),a
+ ldi (hl),a
+ dec b
+ jr nz,-
+ pop bc
+ dec b
+ jr nz,--
+ ret
+
+
+; Shows console display
+; Preserved: AF, BC, DE, HL
+console_show:
+ push af
+
+ ; Enable LCD
+ call console_wait_vbl
+ wreg LCDC,$91
+ wreg SCX,0
+ wreg BGP,$E4
+
+ jp console_apply_scroll_
+
+
+; Hides console display by turning LCD off
+; Preserved: AF, BC, DE, HL
+console_hide:
+ push af
+
+ ; LCD off
+ call console_wait_vbl
+ wreg LCDC,$11
+
+ pop af
+ ret
+
+
+; Changes to normal text mode
+; Preserved: BC, DE, HL
+console_normal:
+ xor a
+ jr console_set_mode
+
+; Changes to inverse text mode
+; Preserved: BC, DE, HL
+console_inverse:
+ ld a,$80
+
+; Changes console mode to A.
+; 0: Normal, $80: Inverse
+; Preserved: BC, DE, HL
+console_set_mode:
+ and $80
+ ld (console_mode),a
+ ret
+
+
+; Prints char A to console. Will not appear until
+; a newline or flush occurs.
+; Preserved: AF, BC, DE, HL
+console_print:
+ push af
+
+ cp 10
+ jr z,console_newline_
+
+ push hl
+ push af
+ ld hl,console_pos
+ ldi a,(hl)
+ cp BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee/roms/instr_timing/source/common/delay.s b/playing-coffee/roms/instr_timing/source/common/delay.s
new file mode 100644
index 0000000..65eb9c0
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+ .endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+ .endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+ .endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee/roms/instr_timing/source/common/gb.inc b/playing-coffee/roms/instr_timing/source/common/gb.inc
new file mode 100644
index 0000000..31bbf14
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/common/gb.inc
@@ -0,0 +1,64 @@
+; Game Boy hardware addresses
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+.define P1 $FF00
+
+; Game link I/O
+.define SB $FF01
+.define SC $FF02
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee/roms/instr_timing/source/common/macros.inc b/playing-coffee/roms/instr_timing/source/common/macros.inc
new file mode 100644
index 0000000..c413bd5
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/common/macros.inc
@@ -0,0 +1,73 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ARGS addr
+ ldh a,(addr - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ARGS addr
+ ldh (addr - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee/roms/instr_timing/source/common/numbers.s b/playing-coffee/roms/instr_timing/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee/roms/instr_timing/source/common/printing.s b/playing-coffee/roms/instr_timing/source/common/printing.s
new file mode 100644
index 0000000..ad9d811
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/common/printing.s
@@ -0,0 +1,98 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+.define print_char_nocrc bss
+.redefine bss bss+3
+
+
+; Initializes printing. HL = print routine
+init_printing:
+ ld a,l
+ ld (print_char_nocrc+1),a
+ ld a,h
+ ld (print_char_nocrc+2),a
+ jr show_printing
+
+
+; Hides/shows further printing
+; Preserved: BC, DE, HL
+hide_printing:
+ ld a,$C9 ; RET
+ jr +
+show_printing:
+ ld a,$C3 ; JP (nn)
++ ld (print_char_nocrc),a
+ ret
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee/roms/instr_timing/source/common/runtime.s b/playing-coffee/roms/instr_timing/source/common/runtime.s
new file mode 100644
index 0000000..90cd24f
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/common/runtime.s
@@ -0,0 +1,142 @@
+; Common routines and runtime
+
+; Must be defined by target-specific runtime:
+;
+; init_runtime: ; target-specific inits
+; std_print: ; default routine to print char A
+; post_exit: ; called at end of std_exit
+; report_byte: ; report A to user
+
+.define RUNTIME_INCLUDED 1
+
+.ifndef bss
+ ; address of next normal variable
+ .define bss $D800
+.endif
+
+.ifndef dp
+ ; address of next direct-page ($FFxx) variable
+ .define dp $FF80
+.endif
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+
+.define gb_id bss
+.redefine bss bss+1
+
+; Stack is normally here
+.define std_stack $DFFF
+
+; Copies $1000 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+ ld c,$10
+- ldi a,(hl)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+.ifndef CUSTOM_RESET
+ reset:
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org $0 ; otherwise wla pads with lots of zeroes
+ jp std_reset
+.endif
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld (gb_id),a
+
+ ; Init hardware
+ .ifndef BUILD_GBS
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+ .endif
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ ; TODO: clear all memory?
+
+ ld hl,std_print
+ call init_printing
+ call init_testing
+ call init_runtime
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ pop af
+ jp post_exit
+
++ push af
+ call print_newline
+ call show_printing
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
diff --git a/playing-coffee/roms/instr_timing/source/common/testing.s b/playing-coffee/roms/instr_timing/source/common/testing.s
new file mode 100644
index 0000000..c102f78
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/common/testing.s
@@ -0,0 +1,176 @@
+; Diagnostic and testing utilities
+
+.define result bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (result),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(result)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(result)
+ cp 1 ; if a = 0 then a = 1
+ adc 0
+ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call show_printing
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee/roms/instr_timing/source/common/timer.s b/playing-coffee/roms/instr_timing/source/common/timer.s
new file mode 100644
index 0000000..32c713e
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/common/timer.s
@@ -0,0 +1,88 @@
+; Cycle-accurate timer
+
+; TIMA is incremented every 4 cycles. Loops
+; check for increment within 3-cycle window,
+; so when it occurs outside this, loop is
+; exactly synchronized. Loop iterations are
+; one more or less than 12 cycles, so they
+; will never run more than 4 times.
+
+; Initializes timer
+; Preserved: AF, BC, DE, HL
+init_timer:
+ push af
+ di
+ lda IE ; disable timer interrupt
+ and ~$04
+ sta IE
+ wreg TMA,0 ; max period
+ wreg TAC,$05 ; 262144 Hz
+
+ ; Be sure timer doesn't expire
+ ; immediately or take too long
+ wreg IF,0
+ wreg TIMA,-20
+ delay 70
+ lda IF
+ and $04
+ jp nz,test_failed
+ lda IF
+ and $04
+ jp z,test_failed
+
+ pop af
+ ret
+
+
+; Starts timer
+; Preserved: AF, BC, DE, HL
+start_timer:
+ push af
+
+- xor a ; 1
+ sta TIMA ; 3
+ lda TIMA ; 3
+ or a ; 1
+ jr nz,- ; 3
+
+ pop af
+ ret
+
+
+; Stops timer and determines cycles since
+; it was started. A = cycles (0 to 255).
+; Preserved: BC, DE, HL
+stop_timer:
+ push de
+ call stop_timer_word
+ ld a,e
+ sub 10
+ pop de
+ ret
+
+
+; Same as stop_timer, but with greater range.
+; DE = cycles (0 to 1019).
+; Preserved: BC, HL
+stop_timer_word:
+
+ ld d,0
+
+ ; Get main count (TIMA*4)
+ lda TIMA
+ sub 5
+ add a
+ rl d
+ add a
+ rl d
+ ld e,a
+
+ ; One iteration per remaining cycle
+- xor a ; 1
+ sta TIMA ; 3
+ lda TIMA ; 3
+ dec de ; 2
+ or a ; 1
+ jr nz,- ; 3
+
+ ret
diff --git a/playing-coffee/roms/instr_timing/source/instr_timing.s b/playing-coffee/roms/instr_timing/source/instr_timing.s
new file mode 100644
index 0000000..b6b7338
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/instr_timing.s
@@ -0,0 +1,336 @@
+; Tests number of cycles taken by instructions
+; except STOP, HALT, and illegals.
+
+.include "shell.inc"
+.include "timer.s"
+
+.define saved_sp bss+0
+.define instr bss+2 ; 3-byte instr + JP instr_end
+.define instr_addr bss+8 ; JP instr_end
+.redefine bss bss+11
+
+main:
+ call init_timer
+ call test_timer
+ set_test 0
+ call test_main_ops
+ call test_cb_ops
+ jp tests_done
+
+
+; Ensures timer works
+test_timer:
+ call start_timer
+ call stop_timer
+ or a
+ ret z
+ set_test 2,"Timer doesn't work properly"
+ jp test_failed
+
+
+; Tests main opcodes
+test_main_ops:
+ ld l,0
+- ld h,>op_times
+ ld a,(hl)
+ cp 0
+ call nz,@test_op
+ inc l
+ jr nz,-
+ ret
+
+@test_op:
+ ; Can't test the 8 RST instructions on devcart
+ ld a,l
+ cpl
+ and $C7
+ jr nz,+
+ ld a,(gb_id)
+ and gb_id_devcart
+ ret nz
++
+ ; Test with flags set so that branches are
+ ; not taken
+ ld a,l ; e = (l & 0x08 ? 0 : 0xFF)
+ and $08
+ add $F8
+ ld e,a
+ call @copy_and_exec
+ ld d,0
+ cp (hl)
+ jr z,+
+ ld d,a
+ call print_failed_opcode
++
+
+ ; Time with branches not taken
+ ld a,e
+ cpl
+ ld e,a
+ call @copy_and_exec
+ ld h,>op_times_taken
+ cp (hl)
+ ret z
+
+ ; If opcode already failed and timed the
+ ; same again, avoid re-reporting.
+ cp d
+ ret z
+
+ call print_failed_opcode
+ ret
+
+@copy_and_exec:
+ push de
+ push hl
+
+ ld h,>op_lens
+ ld c,(hl)
+ ld a,l
+ ld hl,instr
+ ld (hl+),a
+ dec c
+ jr z,@one_byte
+ ld a,0
+ dec c
+ jr z,@two_bytes
+ ld a,instr_addr
+@two_bytes:
+ ld (hl+),a
+@one_byte:
+ ld a,e
+ call time_instruction
+
+ pop hl
+ pop de
+ ret
+
+
+; Tests CB opcodes
+test_cb_ops:
+ ld hl,cb_op_times
+- ld a,(hl)
+ cp 0
+ call nz,@test_op_cb
+ inc l
+ jr nz,-
+ ret
+
+@test_op_cb:
+ ; Test with flags clear
+ ld e,$00
+ call @copy_and_exec_cb
+ cp (hl)
+ jr nz,+
+
+ ; Test with flags set
+ ld e,$FF
+ call @copy_and_exec_cb
+ cp (hl)
+ jr nz,+
+
+ ret
++ print_str "CB "
+ call print_failed_opcode
+ ret
+
+@copy_and_exec_cb:
+ push hl
+
+ ; Copy instr to exec space
+ ld a,l
+ ld hl,instr+1
+ ld (hl+),a
+ ld a,$CB
+ ld (instr),a
+ call time_instruction
+
+ pop hl
+ ret
+
+
+; Reports failed opcode
+; L -> opcode
+; A -> cycles it took
+; (HL) -> cycles it should have taken
+; Preserved: HL
+print_failed_opcode:
+ ; Print opcode
+ push af
+ ld a,l
+ call print_hex
+ ld a,':'
+ call print_char
+ pop af
+
+ ; Print actual and correct times
+ call print_dec
+ ld a,'-'
+ call print_char
+ ld a,(hl)
+ call print_dec
+ ld a,' '
+ call print_char
+
+ ; Remember that failure occurred
+ set_test 1
+
+ ret
+
+
+; Times instruction.
+; HL -> address of byte just after instruction
+; A -> flags when executing instruction
+; A <- number of cycles instruction took
+time_instruction:
+ ld c,a
+
+ ; Write JP instr_end to HL and instr_addr
+ ld a,$C3 ; JP
+ ld (hl+),a
+ ld (instr_addr),a
+
+ ld a,instr_end
+ ld (instr_addr+2),a
+ ld (hl),a
+
+ ; Save sp
+ ld (saved_sp),sp
+
+ ; Set regs and stack contents
+ push bc
+ ld bc,instr_addr
+ ld de,instr_addr
+ ld hl,instr_addr
+ call start_timer
+ pop af
+ push hl
+
+ ; Environment instruction executes in:
+ ; 1 byte: OP
+ ; 2 byte: OP 00
+ ; 3 byte: OP instr_addr
+ ; BC,DE,HL = instr_addr
+ ; Stack has instr_addr pushed on it.
+ ; Stack pointer can be trashed by instr.
+ ; instr_addr contains JP instr_end, that
+ ; can be trashed. Instructions which trash
+ ; this don't execute it.
+
+ jp instr
+instr_end: ; instruction jumps here when done
+ di
+
+ ; Restore sp
+ ld sp,saved_sp
+ pop hl
+ ld sp,hl
+
+ call stop_timer
+ sub 24
+ ret
+
+.section "page_aligned" align 256
+
+; Instruction lengths of opcodes.
+; 0 for instructions not timed.
+op_lens:
+ .byte 1,3,1,1,1,1,2,1,3,1,1,1,1,1,2,1 ; 0
+ .byte 0,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1 ; 1
+ .byte 2,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1 ; 2
+ .byte 2,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1 ; 3
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 4
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 5
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 6
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 7
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 8
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 9
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; A
+ .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; B
+ .byte 1,1,3,3,3,1,2,1,1,1,3,0,3,3,2,1 ; C
+ .byte 1,1,3,0,3,1,2,1,1,1,3,0,3,0,2,1 ; D
+ .byte 2,1,1,0,0,1,2,1,2,1,3,0,0,0,2,1 ; E
+ .byte 2,1,1,1,0,1,2,1,2,1,3,1,0,0,2,1 ; F
+
+; Timings for main opcodes
+op_times:
+ .byte 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1
+ .byte 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1
+ .byte 2,3,2,2,1,1,2,1,2,2,2,2,1,1,2,1
+ .byte 2,3,2,2,3,3,3,1,2,2,2,2,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 2,3,3,4,3,4,2,4,2,4,3,0,3,6,2,4
+ .byte 2,3,3,0,3,4,2,4,2,4,3,0,3,0,2,4
+ .byte 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4
+ .byte 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4
+
+; Timings when conditionals are taken
+op_times_taken:
+ .byte 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1
+ .byte 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1
+ .byte 3,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1
+ .byte 3,3,2,2,3,3,3,1,3,2,2,2,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1
+ .byte 5,3,4,4,6,4,2,4,5,4,4,0,6,6,2,4
+ .byte 5,3,4,0,6,4,2,4,5,4,4,0,6,0,2,4
+ .byte 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4
+ .byte 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4
+
+; Timings for CB-prefixed opcodes
+cb_op_times:
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2
+ .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2
+ .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2
+ .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+ .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2
+.ends
+
+; RST handlers
+.bank 0 slot 0
+.org $00
+ jp instr_end
+.org $08
+ jp instr_end
+.org $10
+ jp instr_end
+.org $18
+ jp instr_end
+.org $20
+ jp instr_end
+.org $28
+ jp instr_end
+.org $30
+ jp instr_end
+.org $38
+ jp instr_end
diff --git a/playing-coffee/roms/instr_timing/source/linkfile b/playing-coffee/roms/instr_timing/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee/roms/instr_timing/source/shell.inc b/playing-coffee/roms/instr_timing/source/shell.inc
new file mode 100644
index 0000000..277a9b7
--- /dev/null
+++ b/playing-coffee/roms/instr_timing/source/shell.inc
@@ -0,0 +1,21 @@
+.incdir "common"
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
diff --git a/playing-coffee/roms/interrupt_time/interrupt_time.gb b/playing-coffee/roms/interrupt_time/interrupt_time.gb
new file mode 100644
index 0000000..1b17845
Binary files /dev/null and b/playing-coffee/roms/interrupt_time/interrupt_time.gb differ
diff --git a/playing-coffee/roms/interrupt_time/interrupt_time.s b/playing-coffee/roms/interrupt_time/interrupt_time.s
new file mode 100644
index 0000000..ae29d8a
--- /dev/null
+++ b/playing-coffee/roms/interrupt_time/interrupt_time.s
@@ -0,0 +1,57 @@
+; Tests interrupt handling time for slow and fast
+; CPU. First value is CPU speed (0=slow, 1=fast),
+; second is number of cycles taken by interrupt.
+; Should take 13 cycles at either speed.
+.define REQUIRE_CGB 1
+
+.include "shell.inc"
+.include "cpu_speed.s"
+.include "timer.s"
+.include "apu.s"
+
+; $58: JP $DEC3
+; $DEC3: RET
+.define sint $DEC3
+
+main:
+ call init_timer
+
+ ld d,0
+ call test_interrupt
+ ld d,8
+ call test_interrupt
+ call cpu_fast
+ ld d,0
+ call test_interrupt
+ ld d,8
+ call test_interrupt
+
+ check_crc $C86CC74D
+ jp tests_passed
+
+test_interrupt:
+ call get_cpu_speed
+ call print_a
+ call print_d
+
+ ld a,$C9 ; RET
+ ld (sint),a
+
+ wreg IE,$08
+ wreg IF,$00
+ call start_timer
+ ei
+ ld a,d
+ ld (IF),a ; $00 = 0 clocks, $08 = 13 clocks
+ di
+ call stop_timer
+ sub 3+4 ; instruction overhead
+ call print_a
+ call print_newline
+
+ ret
+
+; RST handler that matches the one in my devcart
+.bank 0 slot 0
+.org $58
+ jp $DEC3
diff --git a/playing-coffee/roms/kwirk.gb b/playing-coffee/roms/kwirk.gb
new file mode 100644
index 0000000..c25d3c7
Binary files /dev/null and b/playing-coffee/roms/kwirk.gb differ
diff --git a/playing-coffee/roms/mem_timing-2/mem_timing.gb b/playing-coffee/roms/mem_timing-2/mem_timing.gb
new file mode 100644
index 0000000..2665aa2
Binary files /dev/null and b/playing-coffee/roms/mem_timing-2/mem_timing.gb differ
diff --git a/playing-coffee/roms/mem_timing-2/readme.txt b/playing-coffee/roms/mem_timing-2/readme.txt
new file mode 100644
index 0000000..f6577c1
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/readme.txt
@@ -0,0 +1,114 @@
+Game Boy CPU Memory Access Timing Test
+--------------------------------------
+These tests verify the timing of memory reads and writes made by
+instructions, except stack and program counter accesses. These tests
+require correct instruction timing and proper timer operation (TAC,
+TIMA, TMA).
+
+The read and write tests list failing instructions as
+
+ [CB] opcode:tested-correct
+
+The read-modify-write test lists failing instructions as
+
+ [CB] opcode:tested read/tested write-correct read/correct write
+
+The values after the opcode refer to which instruction cycle the access
+occurs on, with 1 being the first. If a time couldn't be determined due
+to some other problem, it prints 0.
+
+For instructions which either read or write, but not both, the CPU makes
+the access on the last cycle. For instructions which read, modify, then
+write back, the CPU reads on the next-to-last cycle, and writes on the
+last cycle.
+
+
+Internal operation
+------------------
+The tests have the timer increment TIMA every 64 cycles, synchronize
+with this, delay a variable amount, then have the instruction under test
+access the timer. By varying the delay in one-cycle increments, the
+memory access made by the instruction can be made to fall before and
+after a TIMA increment. By then examining the registers and value in
+TIMA, it can be determined which occurred.
+
+
+Multi-ROM
+---------
+In the main directory is a single ROM/GBS which runs all the tests. It
+prints a test's number, runs the test, then "ok" if it passes, otherwise
+a failure code. Once all tests have completed it either reports that all
+tests passed, or reports the number of the first failed test as the
+result code (1 = first). Finally, it makes several beeps. If a test
+fails, it can be run on its own by finding the corresponding ROM/GBS in
+the singles directories.
+
+Ths compact format on screen is to avoid having the results scroll off
+the top, so the test can be started and allowed to run without having to
+constantly monitor the display.
+
+
+Failure information
+-------------------
+For more information about a failure code or information printed, see
+the test's source code in source/. To find failure code N, search for
+"set_test N", which will usually be before the subtest which failed.
+
+
+Flashes, clicks, other glitches
+-------------------------------
+Some tests might need to turn the screen off and on, or cause slight
+audio clicks. This does not indicate failure, and should be ignored.
+Only the test result reported at the end is important, unless stated
+otherwise.
+
+
+LCD support
+-----------
+Tests generally print information on screen. The tests will work fine if
+run on an emulator with NO LCD support, or as an GBS which has no
+inherent screen; in particular, the VBL wait routine has a timeout in
+case LY doesn't reflect the current LCD line. The text printing will
+also work if the LCD doesn't support scrolling.
+
+
+Output to memory
+----------------
+Text output and the final result are also written to memory at $A000,
+allowing testing a very minimal emulator that supports little more than
+CPU and RAM. To reliably indicate that the data is from a test and not
+random data, $A001-$A003 are written with a signature: $DE,$B0,$61. If
+this is present, then the text string and final result status are valid.
+
+$A000 holds the overall status. If the test is still running, it holds
+$80, otherwise it holds the final result code.
+
+All text output is appended to a zero-terminated string at $A004. An
+emulator could regularly check this string for any additional
+characters, and output them, allowing real-time text output, rather than
+just printing the final output at the end.
+
+
+GBS versions
+------------
+Many GBS-based tests require that the GBS player either not interrupt
+the init routine with the play routine, or if they do, not interrupt the
+play routine again if it hasn't returned yet. This is because many tests
+need to run for a while without returning.
+
+In addition to the other text output methods described above, GBS builds
+report essential information bytes audibly, including the final result.
+A byte is reported as a series of tones. The code is in binary, with a
+low tone for 0 and a high tone for 1. The first tone is always a zero. A
+final code of 0 means passed, 1 means failure, and 2 or higher indicates
+a specific reason as listed in the source code by the corresponding
+set_code line. Examples:
+
+Tones Binary Decimal Meaning
+- - - - - - - - - - - - - - - - - - - -
+low 0 0 passed
+low high 01 1 failed
+low high low 010 2 error 2
+
+--
+Shay Green
diff --git a/playing-coffee/roms/mem_timing-2/rom_singles/01-read_timing.gb b/playing-coffee/roms/mem_timing-2/rom_singles/01-read_timing.gb
new file mode 100644
index 0000000..660298b
Binary files /dev/null and b/playing-coffee/roms/mem_timing-2/rom_singles/01-read_timing.gb differ
diff --git a/playing-coffee/roms/mem_timing-2/rom_singles/02-write_timing.gb b/playing-coffee/roms/mem_timing-2/rom_singles/02-write_timing.gb
new file mode 100644
index 0000000..e92f553
Binary files /dev/null and b/playing-coffee/roms/mem_timing-2/rom_singles/02-write_timing.gb differ
diff --git a/playing-coffee/roms/mem_timing-2/rom_singles/03-modify_timing.gb b/playing-coffee/roms/mem_timing-2/rom_singles/03-modify_timing.gb
new file mode 100644
index 0000000..7579c42
Binary files /dev/null and b/playing-coffee/roms/mem_timing-2/rom_singles/03-modify_timing.gb differ
diff --git a/playing-coffee/roms/mem_timing-2/source/01-read_timing.s b/playing-coffee/roms/mem_timing-2/source/01-read_timing.s
new file mode 100644
index 0000000..5084d4b
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/01-read_timing.s
@@ -0,0 +1,150 @@
+; Tests timing of accesses made by
+; memory read instructions
+
+.define ROM_NAME "foo"
+
+.include "shell.inc"
+.include "tima_64.s"
+
+instructions:
+ ; last value is time of read
+ .byte $B6,$00,$00,2 ; OR (HL)
+ .byte $BE,$00,$00,2 ; CP (HL)
+ .byte $86,$00,$00,2 ; ADD (HL)
+ .byte $8E,$00,$00,2 ; ADC (HL)
+ .byte $96,$00,$00,2 ; SUB (HL)
+ .byte $9E,$00,$00,2 ; SBC (HL)
+ .byte $A6,$00,$00,2 ; AND (HL)
+ .byte $AE,$00,$00,2 ; XOR (HL)
+ .byte $46,$00,$00,2 ; LD B,(HL)
+ .byte $4E,$00,$00,2 ; LD C,(HL)
+ .byte $56,$00,$00,2 ; LD D,(HL)
+ .byte $5E,$00,$00,2 ; LD E,(HL)
+ .byte $66,$00,$00,2 ; LD H,(HL)
+ .byte $6E,$00,$00,2 ; LD L,(HL)
+ .byte $7E,$00,$00,2 ; LD A,(HL)
+ .byte $F2,$00,$00,2 ; LDH A,(C)
+ .byte $0A,$00,$00,2 ; LD A,(BC)
+ .byte $1A,$00,$00,2 ; LD A,(DE)
+ .byte $2A,$00,$00,2 ; LD A,(HL+)
+ .byte $3A,$00,$00,2 ; LD A,(HL-)
+ .byte $F0,tima_64,4 ; LD A,($0000)
+
+ .byte $CB,$46,$00,3 ; BIT 0,(HL)
+ .byte $CB,$4E,$00,3 ; BIT 1,(HL)
+ .byte $CB,$56,$00,3 ; BIT 2,(HL)
+ .byte $CB,$5E,$00,3 ; BIT 3,(HL)
+ .byte $CB,$66,$00,3 ; BIT 4,(HL)
+ .byte $CB,$6E,$00,3 ; BIT 5,(HL)
+ .byte $CB,$76,$00,3 ; BIT 6,(HL)
+ .byte $CB,$7E,$00,3 ; BIT 7,(HL)
+instructions_end:
+
+main:
+ call init_tima_64
+ set_test 0
+
+ ; Test instructions
+ ld hl,instructions
+- call @time_instr
+ cp (hl)
+ call nz,@print_failed
+ inc hl
+ ld a,l
+ cp 3-byte instruction
+; HL <- HL + 3
+@time_instr:
+ ; Copy instr
+ ld a,(hl+)
+ ld (instr+0),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Find result when access doesn't occur
+ ld b,0
+ call @time_access
+ ld c,a
+
+ ; Test for accesses on each cycle
+ ld b,0
+- push bc
+ call @time_access
+ pop bc
+ cp c
+ jr nz,@found
+ inc b
+ ld a,b
+ cp 10
+ jr nz,-
+ ld b,0
+
+@found:
+ ld a,b
+ pop hl
+ ret
+
+; Tests for read
+; B -> which cycle to test
+; A <- timer value after test
+@time_access:
+ call sync_tima_64
+ ld a,9
+ sub b
+ call delay_a_20_cycles
+ xor a ; clear flags
+ ld hl,tima_64
+ ld (hl),$7F
+ ld bc,tima_64
+ ld de,tima_64
+ ld a,$7F
+instr:
+ nop
+ nop
+ nop
+
+ ; Add all registers together to yield
+ ; unique value that differs based on
+ ; read occurring before or after tima_64
+ ; increments.
+ push af
+ add hl,bc
+ add hl,de
+ pop de
+ add hl,de
+ ld a,h
+ add l
+ ret
diff --git a/playing-coffee/roms/mem_timing-2/source/02-write_timing.s b/playing-coffee/roms/mem_timing-2/source/02-write_timing.s
new file mode 100644
index 0000000..acd6d9d
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/02-write_timing.s
@@ -0,0 +1,115 @@
+; Tests timing of accesses made by
+; memory write instructions
+
+.include "shell.inc"
+.include "tima_64.s"
+
+instructions:
+ ; last value is time of write
+ .byte $36,$FF,$00,3 ; LD (HL),n
+ .byte $70,$00,$00,2 ; LD (HL),B
+ .byte $71,$00,$00,2 ; LD (HL),C
+ .byte $72,$00,$00,2 ; LD (HL),D
+ .byte $73,$00,$00,2 ; LD (HL),E
+ .byte $74,$00,$00,2 ; LD (HL),H
+ .byte $75,$00,$00,2 ; LD (HL),L
+ .byte $77,$00,$00,2 ; LD (HL),A
+ .byte $02,$00,$00,2 ; LD (BC),A
+ .byte $12,$00,$00,2 ; LD (DE),A
+ .byte $22,$00,$00,2 ; LD (HL+),A
+ .byte $32,$00,$00,2 ; LD (HL-),A
+ .byte $E2,$00,$00,2 ; LDH (C),A
+ .byte $E0,tima_64,4 ; LD (nn),A
+instructions_end:
+
+main:
+ call init_tima_64
+ set_test 0
+
+ ; Test instructions
+ ld hl,instructions
+- call @test_instr
+ cp (hl)
+ call nz,@print_failed
+ inc hl
+ ld a,l
+ cp 3-byte instruction
+; HL <- HL + 3
+@test_instr:
+ ; Copy instr
+ ld a,(hl+)
+ ld (instr+0),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Test for writes on each cycle
+ ld b,0
+- push bc
+ call @time_write
+ pop bc
+ cp tima_64
+ jr z,@no_write
+ jr @found
+@no_write:
+ inc b
+ ld a,b
+ cp 10
+ jr nz,-
+ ld b,0
+
+@found:
+ ld a,b
+ pop hl
+ ret
+
+; Tests for write
+; B -> which cycle to test
+; A <- timer value after test
+@time_write:
+ call sync_tima_64
+ ld a,13
+ sub b
+ call delay_a_20_cycles
+ ld hl,tima_64
+ ld bc,tima_64
+ ld de,tima_64
+ ld a, 3-byte instruction
+; HL <- HL + 3
+@time_instr:
+ ; Copy instr
+ ld a,(hl+)
+ ld (instr+0),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Find result when access doesn't occur
+ ld b,0
+ call @time_access
+
+ ; Find first access
+ call @find_next_access
+ ld d,b
+
+ ; Find second access
+ call @find_next_access
+ ld e,b
+
+ pop hl
+ ret
+
+; A -> current timer result
+; B -> starting clock
+; B <- clock next access occurs on
+; A <- new timer result
+@find_next_access:
+ ld c,a
+- call @time_access
+ cp c
+ ret nz
+ inc b
+ ld a,b
+ cp 10
+ jr c,-
+
+ ; Couldn't find time, so return 0/0
+ ld a,c
+ ld b,0
+ ld d,b
+ ret
+
+; Tests for access
+; B -> which cycle to test
+; A <- timer value after test
+@time_access:
+ call sync_tima_64
+ ld hl,tima_64
+ ld (hl),$7F
+ ld a,17
+ sub b
+ call delay_a_20_cycles
+ xor a ; clear flags
+instr:
+ nop
+ nop
+ nop
+ delay 32
+ ld a,(tima_64)
+ ret
diff --git a/playing-coffee/roms/mem_timing-2/source/common/build_gbs.s b/playing-coffee/roms/mem_timing-2/source/common/build_gbs.s
new file mode 100644
index 0000000..877754f
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/common/build_gbs.s
@@ -0,0 +1,139 @@
+; Build as GBS music file
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $2000 size $2000
+ slot 1 $C000 size $2000
+.endMe
+
+.romBankSize $2000
+.romBanks 2
+
+.define RST_OFFSET $70
+
+.ifndef GBS_TMA
+ .define GBS_TMA 0
+.endif
+
+.ifndef GBS_TAC
+ .define GBS_TAC 0
+.endif
+
+;;;; GBS music file header
+
+.ifndef CUSTOM_HEADER
+ .byte "GBS"
+ .byte 1,1,1 ; vers, song count, first song
+ .word load_addr, reset, gbs_play_, std_stack
+ .byte GBS_TMA,GBS_TAC ; timer
+.endif
+ .org $10
+ .ds $60,0
+load_addr:
+ .org RST_OFFSET+$70 ; space for RST vectors
+ .ds $148-RST_OFFSET-$70,0
+ .org $150 ; wla insists on generating GB header
+
+gbs_play_:
+ jp gbs_play ; GBS spec disallows having gbs_play in RAM
+
+;;;; Shell
+
+.include "shell.s"
+
+.define gbs_idle nv_ram
+.redefine nv_ram nv_ram+2
+
+init_runtime:
+ ; Identify as DMG hardware
+ ld a,$01
+ ld (gb_id),a
+
+ ; Save return address
+ pop hl
+ ld a,l
+ ld (gbs_idle),a
+ ld a,h
+ ld (gbs_idle+1),a
+
+ ; Delay 1/4 second to give time
+ ; for GBS player to interrupt with
+ ; play, if it's going to do so
+ delay_msec 250
+
+.ifndef CUSTOM_PLAY
+gbs_play:
+.endif
+ ; Get return address
+ ld a,(gbs_idle)
+ ld l,a
+ ld a,(gbs_idle+1)
+ ld h,a
+
+ ; If zero, then play interrupted init
+ ; call, or another play call, and we
+ ; can't run the program properly.
+ or l
+ jp z,internal_error
+
+ setw gbs_idle,0
+ jp hl
+
+
+; Reports A in binary as high and low tones, with
+; leading low tone for reference. Omits leading
+; zeroes.
+; Preserved: AF, BC, DE, HL
+play_byte:
+ push af
+ push hl
+
+ ; HL = (A << 1) | 1
+ scf
+ rla
+ ld l,a
+ ld h,0
+ rl h
+
+ ; Shift left until next-to-top bit is 1
+- add hl,hl
+ bit 6,h
+ jr z,-
+
+ ; Reset sound
+ delay_msec 400
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+- add hl,hl
+
+ ; Low or high pitch based on bit shifted out
+ ; of HL
+ ld a,0
+ jr nc,+
+ ld a,$FF
++ sta NR23
+
+ ; Play short tone
+ wreg NR21,$A0
+ wreg NR22,$F0
+ wreg NR24,$86
+ delay_msec 75
+ wreg NR22,0
+ wreg NR23,$F8
+ wreg NR24,$87
+ delay_msec 200
+
+ ; Loop until HL = $8000
+ ld a,h
+ xor $80
+ or l
+ jr nz,-
+
+ pop hl
+ pop af
+ ret
+
+.ends
diff --git a/playing-coffee/roms/mem_timing-2/source/common/build_rom.s b/playing-coffee/roms/mem_timing-2/source/common/build_rom.s
new file mode 100644
index 0000000..0f090aa
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/common/build_rom.s
@@ -0,0 +1,84 @@
+; Build as GB ROM
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $0000 size $4000
+ slot 1 $C000 size $4000
+.endMe
+
+.romBankSize $4000
+.romBanks 2
+
+.cartridgeType 3 ; MBC1+RAM+battery
+.ramsize 02 ; 8K
+.computeChecksum
+.computeComplementCheck
+.emptyfill $FF
+
+;;;; GB ROM header
+
+.org $134
+ .ds 15,0
+
+ ; Reserve space for RST handlers
+ .org $70
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .ds $90,0
+
+ ; GB header read by bootrom
+ .org $100
+ nop
+ jp reset
+
+ ; Nintendo logo required for proper boot
+ .byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
+ .byte $03,$73,$00,$83,$00,$0C,$00,$0D
+ .byte $00,$08,$11,$1F,$88,$89,$00,$0E
+ .byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
+ .byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
+ .byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
+
+.section "HEADER" OVERWRITE
+ ; Internal name
+ .ifdef ROM_NAME
+ .byte ROM_NAME
+ .else
+ .ifdef ROM_NAME_DEFAULT
+ .byte ROM_NAME_DEFAULT
+ .endif
+ .endif
+.ends
+
+ ; CGB/DMG requirements
+ .org $143
+ .ifdef REQUIRE_CGB
+ .byte $C0
+ .else
+ .ifndef REQUIRE_DMG
+ .byte $80
+ .endif
+ .endif
+ .byte 0,0,0
+
+ .org $14A
+ .byte 0,0,0
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .org $150
+ .ds $2150-$150,$FF
+
+;;;; Shell
+
+.define NEED_CONSOLE 1
+.include "shell.s"
+
+init_runtime:
+ ret
+
+play_byte:
+ ret
+
+.ends
diff --git a/playing-coffee/roms/mem_timing-2/source/common/console.bin b/playing-coffee/roms/mem_timing-2/source/common/console.bin
new file mode 100644
index 0000000..b02f2d3
Binary files /dev/null and b/playing-coffee/roms/mem_timing-2/source/common/console.bin differ
diff --git a/playing-coffee/roms/mem_timing-2/source/common/console.s b/playing-coffee/roms/mem_timing-2/source/common/console.s
new file mode 100644
index 0000000..403fa5c
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/common/console.s
@@ -0,0 +1,285 @@
+; Scrolling text console
+
+; Console is 20x18 characters. Buffers lines, so
+; output doesn't appear until a newline or flush.
+; If scrolling isn't supported (i.e. SCY is treated
+; as if always zero), the first 18 lines will
+; still print properly). Also works properly if
+; LY isn't supported (always reads back as the same
+; value).
+
+.define console_width 20
+
+.define console_buf bss+0
+.define console_pos bss+console_width
+.define console_mode bss+console_width+1
+.define console_scroll bss+console_width+2
+.redefine bss bss+console_width+3
+
+
+; Waits for start of LCD blanking period
+; Preserved: BC, DE, HL
+console_wait_vbl:
+ push bc
+
+ ; Wait for start of vblank, with
+ ; timeout in case LY doesn't work
+ ; or LCD is disabled.
+ ld bc,-1250
+- inc bc
+ ld a,b
+ or c
+ jr z,@timeout
+ lda LY
+ cp 144
+ jr nz,-
+@timeout:
+
+ pop bc
+ ret
+
+
+; Initializes text console
+console_init:
+ call console_hide
+
+ ; CGB-specific inits
+ ld a,(gb_id)
+ and gb_id_cgb
+ call nz,@init_cgb
+
+ ; Clear nametable
+ ld a,' '
+ call @fill_nametable
+
+ ; Load tiles
+ ld hl,TILES+$200
+ ld c,0
+ call @load_tiles
+ ld hl,TILES+$A00
+ ld c,$FF
+ call @load_tiles
+
+ ; Init state
+ setb console_pos,console_width
+ setb console_mode,0
+ setb console_scroll,-8
+ call console_scroll_up_
+ jr console_show
+
+@fill_nametable:
+ ld hl,BGMAP0
+ ld b,4
+- ld (hl),a
+ inc l
+ jr nz,-
+ inc h
+ dec b
+ jr nz,-
+ ret
+
+@init_cgb:
+ ; Clear palette
+ wreg $FF68,$80
+ ld b,16
+- wreg $FF69,$FF
+ wreg $FF69,$7F
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ dec b
+ jr nz,-
+
+ ; Clear attributes
+ wreg VBK,1
+ ld a,0
+ call @fill_nametable
+
+ wreg VBK,0
+ ret
+
+@load_tiles:
+ ld de,ASCII
+ ld b,96
+-- push bc
+ ld b,8
+- ld a,(de)
+ inc de
+ xor c
+ ldi (hl),a
+ ldi (hl),a
+ dec b
+ jr nz,-
+ pop bc
+ dec b
+ jr nz,--
+ ret
+
+
+; Shows console display
+; Preserved: AF, BC, DE, HL
+console_show:
+ push af
+
+ ; Enable LCD
+ call console_wait_vbl
+ wreg LCDC,$91
+ wreg SCX,0
+ wreg BGP,$E4
+
+ jp console_apply_scroll_
+
+
+; Hides console display by turning LCD off
+; Preserved: AF, BC, DE, HL
+console_hide:
+ push af
+
+ ; LCD off
+ call console_wait_vbl
+ wreg LCDC,$11
+
+ pop af
+ ret
+
+
+; Changes to normal text mode
+; Preserved: BC, DE, HL
+console_normal:
+ xor a
+ jr console_set_mode
+
+; Changes to inverse text mode
+; Preserved: BC, DE, HL
+console_inverse:
+ ld a,$80
+
+; Changes console mode to A.
+; 0: Normal, $80: Inverse
+; Preserved: BC, DE, HL
+console_set_mode:
+ and $80
+ ld (console_mode),a
+ ret
+
+
+; Prints char A to console. Will not appear until
+; a newline or flush occurs.
+; Preserved: AF, BC, DE, HL
+console_print:
+ push af
+
+ cp 10
+ jr z,console_newline_
+
+ push hl
+ push af
+ ld hl,console_pos
+ ldi a,(hl)
+ cp BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee/roms/mem_timing-2/source/common/delay.s b/playing-coffee/roms/mem_timing-2/source/common/delay.s
new file mode 100644
index 0000000..cbcdcf3
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+.endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+.endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+.endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee/roms/mem_timing-2/source/common/gb.inc b/playing-coffee/roms/mem_timing-2/source/common/gb.inc
new file mode 100644
index 0000000..2d0118d
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/common/gb.inc
@@ -0,0 +1,81 @@
+; Game Boy hardware addresses
+
+; $0000-$3FFF Fixed ROM bank
+; $4000-$7FFF Switchable bank
+; $8000-$9FFF VRAM
+; $A000-$BFFF optional cartridge RAM
+; $C000-$DFFF RAM
+; $E000-$FDFF RAM mirror
+; $FE00-$FE9F OAM
+; $FEA0-$FEFF unused
+; $FF00-$FF7F registers
+; $FF80-$FFFE RAM
+; $FFFF register
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define BRAM $A000 ; cart memory
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+; Registers
+
+.define RAMEN $0000 ; cartridge WRAM control
+.define BANK $2000 ; bank select
+.define P1 $FF00 ; controller
+
+; Game link I/O
+.define SB $FF01 ; serial buffer
+.define SC $FF02 ; serial control
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee/roms/mem_timing-2/source/common/macros.inc b/playing-coffee/roms/mem_timing-2/source/common/macros.inc
new file mode 100644
index 0000000..9d7cf44
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/common/macros.inc
@@ -0,0 +1,91 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ; addr
+ ldh a,(\1 - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ; addr
+ ldh (\1 - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Writes byte to addr
+; Preserved: F, BC, DE, HL
+; Time: 6 cycles
+.macro setb ; addr, data
+ ld a,\2
+ ld (\1),a
+.endm
+
+; Writes word to addr
+; Preserved: F, BC, DE, HL
+; Time: 12 cycles
+.macro setw ; addr, data
+ ld a,<\2
+ ld (\1),a
+ ld a,>\2
+ ld (\1+1),a
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee/roms/mem_timing-2/source/common/numbers.s b/playing-coffee/roms/mem_timing-2/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee/roms/mem_timing-2/source/common/printing.s b/playing-coffee/roms/mem_timing-2/source/common/printing.s
new file mode 100644
index 0000000..bb9389b
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/common/printing.s
@@ -0,0 +1,77 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+;print_char_nocrc (defined by user)
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee/roms/mem_timing-2/source/common/shell.s b/playing-coffee/roms/mem_timing-2/source/common/shell.s
new file mode 100644
index 0000000..a045121
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/common/shell.s
@@ -0,0 +1,266 @@
+; Common routines and runtime
+
+.define RUNTIME_INCLUDED 1
+
+; A few bytes of RAM that aren't cleared
+.define nv_ram_base $D800
+.define nv_ram nv_ram_base
+
+; Address of next normal variable
+.define bss_base nv_ram_base+$80
+.define bss bss_base
+
+; Address of next direct-page ($FFxx) variable
+.define dp_base $FF80
+.define dp dp_base
+
+; Top of stack
+.define std_stack $DFFF+1
+
+; Final exit result byte is written here
+.define final_result $A000
+
+; Text output is written here as zero-terminated string
+.define text_out_base $A004
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+.define gb_id nv_ram
+.redefine nv_ram nv_ram+1
+
+; Copies C*$100 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+- ld a,(hl+)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+
+.ifndef RST_OFFSET
+ .define RST_OFFSET 0
+.endif
+
+.ifndef CUSTOM_RESET
+ reset:
+ di
+
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ ld c,$14
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org 0
+ jp std_reset
+.endif
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
+
+ ; Catch user code running off end
+ jp internal_error
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld b,a
+ and $EA
+ jr z,+
+ ld b,0
++ ld a,b
+ ld (gb_id),a
+
+ ; Clear memory except very top of stack
+ ld bc,std_stack-bss_base - 2
+ ld hl,bss_base
+ call clear_mem
+ ld bc,$FFFF-dp_base
+ ld hl,dp_base
+ call clear_mem
+
+ ; Init hardware
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ call init_runtime
+ call init_text_out
+ call console_init
+ call init_testing
+
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ call console_show
+ pop af
+ call play_byte
+ jp post_exit
+
++ push af
+ call print_newline
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+
+; Clears BC bytes starting at HL
+clear_mem:
+ ; If C>0, increment B
+ dec bc
+ inc c
+ inc b
+
+ ld a,0
+- ld (hl+),a
+ dec c
+ jr nz,-
+ dec b
+ jr nz,-
+ ret
+
+
+; Reports internal error and exits with code 255
+internal_error:
+ print_str "Internal error"
+ ld a,255
+ jp exit
+
+
+; build_devcart and build_multi customize this
+.ifndef CUSTOM_PRINT
+ .define text_out_addr bss+0
+ .redefine bss bss+2
+
+ ; Initializes text output to cartridge RAM
+ init_text_out:
+ ; Enable cartridge RAM and set text output pointer
+ setb RAMEN,$0A
+ setw text_out_addr,text_out_base
+ setb text_out_base-3,$DE
+ setb text_out_base-2,$B0
+ setb text_out_base-1,$61
+ setb text_out_base,0
+ setb final_result,$80
+ ret
+
+
+ ; Appends character to text output string
+ ; Preserved: AF, BC, DE, HL
+ write_text_out:
+ push hl
+ push af
+ ld a,(text_out_addr)
+ ld l,a
+ ld a,(text_out_addr+1)
+ ld h,a
+ inc hl
+ ld (hl),0
+ ld a,l
+ ld (text_out_addr),a
+ ld a,h
+ ld (text_out_addr+1),a
+ dec hl
+ pop af
+ ld (hl),a
+ pop hl
+ ret
+
+ print_char_nocrc:
+ call write_text_out
+ jp console_print
+.endif
+
+
+; only build_rom uses console
+.ifdef NEED_CONSOLE
+ .include "console.s"
+.else
+ console_init:
+ console_print:
+ console_flush:
+ console_normal:
+ console_inverse:
+ console_show:
+ console_set_mode:
+ ret
+.endif
+
+
+; build_devcart and build_multi need to customize this
+.ifndef CUSTOM_EXIT
+ post_exit:
+ ld (final_result),a
+ forever:
+ wreg NR52,0 ; sound off
+- jr -
+.endif
+
+
+.macro def_rst ARGS addr
+ .bank 0 slot 0
+ .org addr+RST_OFFSET
+.endm
diff --git a/playing-coffee/roms/mem_timing-2/source/common/testing.s b/playing-coffee/roms/mem_timing-2/source/common/testing.s
new file mode 100644
index 0000000..5dd63ce
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/common/testing.s
@@ -0,0 +1,195 @@
+; Diagnostic and testing utilities
+
+.define test_code bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text.
+; Takes multiple strings due to wla's idiotic
+; default limit of 63 chars per string.
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2[,text3]]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .if NARGS > 3
+ .byte \4
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (test_code),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(test_code)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(test_code)
+ cp $FF ; if a = $FF then a = 1
+ jr nz,+
+ ld a,1
++ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+; Checks CRC, differing based on DMG or CGB build
+.macro check_crc_dmg_cgb ARGS dmg, cgb
+ .ifdef REQUIRE_DMG
+ check_crc dmg
+ .else
+ .ifdef REQUIRE_CGB
+ check_crc cgb
+ .else
+ .printt "CGB or DMG must be specified"
+ .fail
+ .endif
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee/roms/mem_timing-2/source/common/tima_64.s b/playing-coffee/roms/mem_timing-2/source/common/tima_64.s
new file mode 100644
index 0000000..5b6c3a1
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/common/tima_64.s
@@ -0,0 +1,36 @@
+; Timer that's incremented every 64 cycles
+
+; Initializes timer for use by sync_tima_64
+init_tima_64:
+ wreg TMA,0
+ wreg TAC,$07
+ ret
+
+; Synchronizes to timer
+; Preserved: AF, BC, DE, HL
+sync_tima_64:
+ push af
+ push hl
+
+ ; Coarse
+ ld hl,TIMA
+ ld a,0
+ ld (hl),a
+- or (hl)
+ jr z,-
+
+ ; Fine
+- delay 65-12
+ xor a
+ ld (hl),a
+ or (hl)
+ delay 4
+ jr z,-
+
+ pop hl
+ pop af
+ ret
+
+; Read from this to get count that's incremented
+; every 64 cycles.
+.define tima_64 TIMA
diff --git a/playing-coffee/roms/mem_timing-2/source/linkfile b/playing-coffee/roms/mem_timing-2/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee/roms/mem_timing-2/source/readme.txt b/playing-coffee/roms/mem_timing-2/source/readme.txt
new file mode 100644
index 0000000..a0269c1
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/readme.txt
@@ -0,0 +1,82 @@
+Game Boy Tests Source Code
+--------------------------
+
+Building with wla-dx
+--------------------
+To assemble a test ROM with wla-dx, use the following commands:
+
+ wla -o source_filename_here.s test.o
+ wlalink linkfile test.gb
+
+To assemble as a GBS music file:
+
+ wla -o source_filename_here.s test.o -DBUILD_GBS
+ wlalink linkfile test.gbs
+
+Note that some tests might only work when built as a ROM or GBS file,
+but not both.
+
+Some tests might include a ROM/GBS that has all the tests combined.
+Building such a multi-test is complex and the necessary files aren't
+included.
+
+
+Framework
+---------
+Each test is in a single source file, and makes use of several library
+source files from common/. This framework provides common services and
+reduces code to only that which performs the actual test. Virtually all
+tests include "shell.inc" at the beginning, which sets things up and
+includes all the appropriate library files.
+
+The reset handler does minimal GB hardware initialization, clears RAM,
+sets up the text console, then runs main. Main can exit by returning or
+jumping to "exit" with an error code in A. Exit reports the code then
+goes into an infinite loop. If the code is 0, it doesn't do anything,
+otherwise it reports the code. Code 1 is reported as "Failed", and the
+rest as "Error ".
+
+The default is to build a ROM. Defining BUILD_GBS will build as an GBS.
+The other build types aren't supported due to their complexity. I load
+the code into RAM at $C000 since my devcart requires it, and I don't
+want the normal ROM to differ in any way from what I've tested. This
+also allows easy self-modifying code.
+
+Several routines are available to print values and text to the console.
+Most update a running CRC-32 checksum which can be checked with
+check_crc, allowing ALL the output to be checked very easily. If the
+checksum doesn't match, it is printed, so you can run the code on a GB
+and paste the correct checksum into your code.
+
+
+Macros
+------
+Some macros are used to make common operations more convenient. The left
+is equivalent to the right:
+
+ Macro Equivalent
+ -------------------------------------
+ lda addr ldh a,(addr-$FF00)
+
+ sta addr ldh (addr-$FF00),a
+
+ wreg addr,data ld a,data
+ ldh (addr-$FF00),a
+
+ setb ld a,data
+ ld (addr),a
+
+ setw setb addr+0,data
+
+ for_loop routine,begin,end,step
+ calls routine with A set to successive values
+
+ loop_n_times routine,count
+ calls routine with A from 0 to count-1
+
+ print_str "str" prints string
+
+
+--
+Shay Green
diff --git a/playing-coffee/roms/mem_timing-2/source/shell.inc b/playing-coffee/roms/mem_timing-2/source/shell.inc
new file mode 100644
index 0000000..0d43faa
--- /dev/null
+++ b/playing-coffee/roms/mem_timing-2/source/shell.inc
@@ -0,0 +1,27 @@
+; Included at beginning of all programs
+; that use standard shell
+
+; Get include files from common/
+.incdir "common"
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.else
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
+
+.endif ; .ifdef BUILD_MULTI
diff --git a/playing-coffee/roms/mem_timing/individual/01-read_timing.gb b/playing-coffee/roms/mem_timing/individual/01-read_timing.gb
new file mode 100644
index 0000000..d0836aa
Binary files /dev/null and b/playing-coffee/roms/mem_timing/individual/01-read_timing.gb differ
diff --git a/playing-coffee/roms/mem_timing/individual/02-write_timing.gb b/playing-coffee/roms/mem_timing/individual/02-write_timing.gb
new file mode 100644
index 0000000..cfe7c0f
Binary files /dev/null and b/playing-coffee/roms/mem_timing/individual/02-write_timing.gb differ
diff --git a/playing-coffee/roms/mem_timing/individual/03-modify_timing.gb b/playing-coffee/roms/mem_timing/individual/03-modify_timing.gb
new file mode 100644
index 0000000..28f1ae6
Binary files /dev/null and b/playing-coffee/roms/mem_timing/individual/03-modify_timing.gb differ
diff --git a/playing-coffee/roms/mem_timing/mem_timing.gb b/playing-coffee/roms/mem_timing/mem_timing.gb
new file mode 100644
index 0000000..78766b5
Binary files /dev/null and b/playing-coffee/roms/mem_timing/mem_timing.gb differ
diff --git a/playing-coffee/roms/mem_timing/readme.txt b/playing-coffee/roms/mem_timing/readme.txt
new file mode 100644
index 0000000..98d76e2
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/readme.txt
@@ -0,0 +1,122 @@
+Game Boy CPU Memory Access Timing Test
+--------------------------------------
+This ROM tests the timing of memory reads and writes made by
+instructions, except stack and program counter accesses. These tests
+require correct instruction timing and proper timer operation (TAC,
+TIMA, TMA).
+
+The read and write tests list failing instructions as
+
+ [CB] opcode:tested-correct
+
+The read-modify-write test lists failing instructions as
+
+ [CB] opcode:tested read/tested write-correct read/correct write
+
+The values after the opcode refer to which instruction cycle the access
+occurs on, with 1 being the first. If a time couldn't be determined due
+to some other problem, it prints 0.
+
+For instructions which either read or write, but not both, the CPU makes
+the access on the last cycle. For instructions which read, modify, then
+write back, the CPU reads on the next-to-last cycle, and writes on the
+last cycle.
+
+
+Internal operation
+------------------
+The tests have the timer increment TIMA every 64 cycles, synchronize
+with this, delay a variable amount, then have the instruction under test
+access the timer. By varying the delay in one-cycle increments, the
+memory access made by the instruction can be made to fall before and
+after a TIMA increment. By then examining the registers and value in
+TIMA, it can be determined which occurred.
+
+
+Multi-ROM
+---------
+In the main directory is a single ROM which runs all the tests. It
+prints a test's number, runs the test, then "ok" if it passes, otherwise
+a failure code. Once all tests have completed it either reports that all
+tests passed, or prints the number of failed tests. Finally, it makes
+several beeps. If a test fails, it can be run on its own by finding the
+corresponding ROM in individual/.
+
+Ths compact format on screen is to avoid having the results scroll off
+the top, so the test can be started and allowed to run without having to
+constantly monitor the display.
+
+Currently there is no well-defined way for an emulator test rig to
+programatically find the result of the test; contact me if you're trying
+to do completely automated testing of your emulator. One simple approach
+is to take a screenshot after all tests have run, or even just a
+checksum of one, and compare this with a previous run.
+
+
+Failure codes
+-------------
+Failed tests may print a failure code, and also short description of the
+problem. For more information about a failure code, look in the
+corresponding source file in source/; the point in the code where
+"set_test n" occurs is where that failure code will be generated.
+Failure code 1 is a general failure of the test; any further information
+will be printed.
+
+Note that once a sub-test fails, no further tests for that file are run.
+
+
+Console output
+--------------
+Information is printed on screen in a way that needs only minimum LCD
+support, and won't hang if LCD output isn't supported at all.
+Specifically, while polling LY to wait for vblank, it will time out if
+it takes too long, so LY always reading back as the same value won't
+hang the test. It's also OK if scrolling isn't supported; in this case,
+text will appear starting at the top of the screen.
+
+Everything printed on screen is also sent to the game link port by
+writing the character to SB, then writing $81 to SC. This is useful for
+tests which print lots of information that scrolls off screen.
+
+
+Source code
+-----------
+Source code is included for all tests, in source/. It can be used to
+build the individual test ROMs. Code for the multi test isn't included
+due to the complexity of putting everything together.
+
+Code is written for the wla-dx assembler. To assemble a particular test,
+execute
+
+ wla -o "source_filename.s" test.o
+ wlalink linkfile test.gb
+
+Test code uses a common shell framework contained in common/.
+
+
+Internal framework operation
+----------------------------
+Tests use a common framework for setting things up, reporting results,
+and ending. All files first include "shell.inc", which sets up the ROM
+header and shell code, and includes other commonly-used modules.
+
+One oddity is that test code is first copied to internal RAM at $D000,
+then executed there. This allows self-modification, and ensures the code
+is executed the same way it is on my devcart, which doesn't have a
+rewritable ROM as most do.
+
+Some macros are used to simplify common tasks:
+
+ Macro Behavior
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ wreg addr,data Writes data to addr using LDH
+ lda addr Loads byte from addr into A using LDH
+ sta addr Stores A at addr using LDH
+ delay n Delays n cycles, where NOP = 1 cycle
+ delay_msec n Delays n milliseconds
+ set_test n,"Cause" Sets failure code and optional string
+
+Routines and macros are documented where they are defined.
+
+--
+Shay Green
diff --git a/playing-coffee/roms/mem_timing/source/01-read_timing.s b/playing-coffee/roms/mem_timing/source/01-read_timing.s
new file mode 100644
index 0000000..e119615
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/01-read_timing.s
@@ -0,0 +1,148 @@
+; Tests timing of accesses made by
+; memory read instructions
+
+.include "shell.inc"
+.include "tima_64.s"
+
+instructions:
+ ; last value is time of read
+ .byte $B6,$00,$00,2 ; OR (HL)
+ .byte $BE,$00,$00,2 ; CP (HL)
+ .byte $86,$00,$00,2 ; ADD (HL)
+ .byte $8E,$00,$00,2 ; ADC (HL)
+ .byte $96,$00,$00,2 ; SUB (HL)
+ .byte $9E,$00,$00,2 ; SBC (HL)
+ .byte $A6,$00,$00,2 ; AND (HL)
+ .byte $AE,$00,$00,2 ; XOR (HL)
+ .byte $46,$00,$00,2 ; LD B,(HL)
+ .byte $4E,$00,$00,2 ; LD C,(HL)
+ .byte $56,$00,$00,2 ; LD D,(HL)
+ .byte $5E,$00,$00,2 ; LD E,(HL)
+ .byte $66,$00,$00,2 ; LD H,(HL)
+ .byte $6E,$00,$00,2 ; LD L,(HL)
+ .byte $7E,$00,$00,2 ; LD A,(HL)
+ .byte $F2,$00,$00,2 ; LDH A,(C)
+ .byte $0A,$00,$00,2 ; LD A,(BC)
+ .byte $1A,$00,$00,2 ; LD A,(DE)
+ .byte $2A,$00,$00,2 ; LD A,(HL+)
+ .byte $3A,$00,$00,2 ; LD A,(HL-)
+ .byte $F0,tima_64,4 ; LD A,($0000)
+
+ .byte $CB,$46,$00,3 ; BIT 0,(HL)
+ .byte $CB,$4E,$00,3 ; BIT 1,(HL)
+ .byte $CB,$56,$00,3 ; BIT 2,(HL)
+ .byte $CB,$5E,$00,3 ; BIT 3,(HL)
+ .byte $CB,$66,$00,3 ; BIT 4,(HL)
+ .byte $CB,$6E,$00,3 ; BIT 5,(HL)
+ .byte $CB,$76,$00,3 ; BIT 6,(HL)
+ .byte $CB,$7E,$00,3 ; BIT 7,(HL)
+instructions_end:
+
+main:
+ call init_tima_64
+ set_test 0
+
+ ; Test instructions
+ ld hl,instructions
+- call @time_instr
+ cp (hl)
+ call nz,@print_failed
+ inc hl
+ ld a,l
+ cp 3-byte instruction
+; HL <- HL + 3
+@time_instr:
+ ; Copy instr
+ ld a,(hl+)
+ ld (instr+0),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Find result when access doesn't occur
+ ld b,0
+ call @time_access
+ ld c,a
+
+ ; Test for accesses on each cycle
+ ld b,0
+- push bc
+ call @time_access
+ pop bc
+ cp c
+ jr nz,@found
+ inc b
+ ld a,b
+ cp 10
+ jr nz,-
+ ld b,0
+
+@found:
+ ld a,b
+ pop hl
+ ret
+
+; Tests for read
+; B -> which cycle to test
+; A <- timer value after test
+@time_access:
+ call sync_tima_64
+ ld a,9
+ sub b
+ call delay_a_20_cycles
+ xor a ; clear flags
+ ld hl,tima_64
+ ld (hl),$7F
+ ld bc,tima_64
+ ld de,tima_64
+ ld a,$7F
+instr:
+ nop
+ nop
+ nop
+
+ ; Add all registers together to yield
+ ; unique value that differs based on
+ ; read occurring before or after tima_64
+ ; increments.
+ push af
+ add hl,bc
+ add hl,de
+ pop de
+ add hl,de
+ ld a,h
+ add l
+ ret
diff --git a/playing-coffee/roms/mem_timing/source/02-write_timing.s b/playing-coffee/roms/mem_timing/source/02-write_timing.s
new file mode 100644
index 0000000..acd6d9d
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/02-write_timing.s
@@ -0,0 +1,115 @@
+; Tests timing of accesses made by
+; memory write instructions
+
+.include "shell.inc"
+.include "tima_64.s"
+
+instructions:
+ ; last value is time of write
+ .byte $36,$FF,$00,3 ; LD (HL),n
+ .byte $70,$00,$00,2 ; LD (HL),B
+ .byte $71,$00,$00,2 ; LD (HL),C
+ .byte $72,$00,$00,2 ; LD (HL),D
+ .byte $73,$00,$00,2 ; LD (HL),E
+ .byte $74,$00,$00,2 ; LD (HL),H
+ .byte $75,$00,$00,2 ; LD (HL),L
+ .byte $77,$00,$00,2 ; LD (HL),A
+ .byte $02,$00,$00,2 ; LD (BC),A
+ .byte $12,$00,$00,2 ; LD (DE),A
+ .byte $22,$00,$00,2 ; LD (HL+),A
+ .byte $32,$00,$00,2 ; LD (HL-),A
+ .byte $E2,$00,$00,2 ; LDH (C),A
+ .byte $E0,tima_64,4 ; LD (nn),A
+instructions_end:
+
+main:
+ call init_tima_64
+ set_test 0
+
+ ; Test instructions
+ ld hl,instructions
+- call @test_instr
+ cp (hl)
+ call nz,@print_failed
+ inc hl
+ ld a,l
+ cp 3-byte instruction
+; HL <- HL + 3
+@test_instr:
+ ; Copy instr
+ ld a,(hl+)
+ ld (instr+0),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Test for writes on each cycle
+ ld b,0
+- push bc
+ call @time_write
+ pop bc
+ cp tima_64
+ jr z,@no_write
+ jr @found
+@no_write:
+ inc b
+ ld a,b
+ cp 10
+ jr nz,-
+ ld b,0
+
+@found:
+ ld a,b
+ pop hl
+ ret
+
+; Tests for write
+; B -> which cycle to test
+; A <- timer value after test
+@time_write:
+ call sync_tima_64
+ ld a,13
+ sub b
+ call delay_a_20_cycles
+ ld hl,tima_64
+ ld bc,tima_64
+ ld de,tima_64
+ ld a, 3-byte instruction
+; HL <- HL + 3
+@time_instr:
+ ; Copy instr
+ ld a,(hl+)
+ ld (instr+0),a
+ ld a,(hl+)
+ ld (instr+1),a
+ ld a,(hl+)
+ ld (instr+2),a
+ push hl
+
+ ; Find result when access doesn't occur
+ ld b,0
+ call @time_access
+
+ ; Find first access
+ call @find_next_access
+ ld d,b
+
+ ; Find second access
+ call @find_next_access
+ ld e,b
+
+ pop hl
+ ret
+
+; A -> current timer result
+; B -> starting clock
+; B <- clock next access occurs on
+; A <- new timer result
+@find_next_access:
+ ld c,a
+- call @time_access
+ cp c
+ ret nz
+ inc b
+ ld a,b
+ cp 10
+ jr c,-
+
+ ; Couldn't find time, so return 0/0
+ ld a,c
+ ld b,0
+ ld d,b
+ ret
+
+; Tests for access
+; B -> which cycle to test
+; A <- timer value after test
+@time_access:
+ call sync_tima_64
+ ld hl,tima_64
+ ld (hl),$7F
+ ld a,17
+ sub b
+ call delay_a_20_cycles
+ xor a ; clear flags
+instr:
+ nop
+ nop
+ nop
+ delay 32
+ ld a,(tima_64)
+ ret
diff --git a/playing-coffee/roms/mem_timing/source/common/build_gbs.s b/playing-coffee/roms/mem_timing/source/common/build_gbs.s
new file mode 100644
index 0000000..7ac8d3c
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/common/build_gbs.s
@@ -0,0 +1,121 @@
+; Build as GBS music file
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $3000 size $1000
+ slot 1 $C000 size $1000
+.endMe
+
+.romBankSize $1000
+.romBanks 2
+
+
+;;;; GBS music file header
+
+.byte "GBS"
+.byte 1 ; vers
+.byte 1 ; songs
+.byte 1 ; first song
+.word load_addr
+.word reset
+.word gbs_play
+.word std_stack
+.byte 0,0 ; timer
+.ds $60,0
+load_addr:
+
+; WLA assumes we're building ROM and messes
+; with bytes at the beginning, so skip them.
+.ds $100,0
+
+
+;;;; Shell
+
+.include "runtime.s"
+
+init_runtime:
+ ld a,$01 ; Identify as DMG hardware
+ ld (gb_id),a
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+ ret
+
+std_print:
+ sta SB
+ wreg SC,$81
+ delay 2304
+ ret
+
+post_exit:
+ call play_byte
+forever:
+ wreg NR52,0 ; sound off
+- jp -
+
+.ifndef CUSTOM_RESET
+ gbs_play:
+.endif
+console_flush:
+console_normal:
+console_inverse:
+console_set_mode:
+ ret
+
+; Reports A in binary as high and low tones, with
+; leading low tone for reference. Omits leading
+; zeroes.
+; Preserved: AF, BC, DE, HL
+play_byte:
+ push af
+ push hl
+
+ ; HL = (A << 1) | 1
+ scf
+ rla
+ ld l,a
+ ld h,0
+ rl h
+
+ ; Shift left until next-to-top bit is 1
+- add hl,hl
+ bit 6,h
+ jr z,-
+
+ ; Reset sound
+ delay_msec 400
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+- add hl,hl
+
+ ; Low or high pitch based on bit shifted out
+ ; of HL
+ ld a,0
+ jr nc,+
+ ld a,$FF
++ sta NR23
+
+ ; Play short tone
+ wreg NR21,$A0
+ wreg NR22,$F0
+ wreg NR24,$86
+ delay_msec 75
+ wreg NR22,0
+ wreg NR23,$F8
+ wreg NR24,$87
+ delay_msec 200
+
+ ; Loop until HL = $8000
+ ld a,h
+ xor $80
+ or l
+ jr nz,-
+
+ pop hl
+ pop af
+ ret
+
+.ends
diff --git a/playing-coffee/roms/mem_timing/source/common/build_rom.s b/playing-coffee/roms/mem_timing/source/common/build_rom.s
new file mode 100644
index 0000000..e1e220f
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/common/build_rom.s
@@ -0,0 +1,80 @@
+; Build as GB ROM
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $0000 size $4000
+ slot 1 $C000 size $4000
+.endMe
+
+.romBankSize $4000 ; generates $8000 byte ROM
+.romBanks 2
+
+.cartridgeType 1 ; MBC1
+.computeChecksum
+.computeComplementCheck
+
+
+;;;; GB ROM header
+
+; GB header read by bootrom
+.org $100
+ nop
+ jp reset
+
+; Nintendo logo required for proper boot
+.byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
+.byte $03,$73,$00,$83,$00,$0C,$00,$0D
+.byte $00,$08,$11,$1F,$88,$89,$00,$0E
+.byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
+.byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
+.byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
+
+; Internal name
+.ifdef ROM_NAME
+ .byte ROM_NAME
+.endif
+
+; CGB/DMG requirements
+.org $143
+ .ifdef REQUIRE_CGB
+ .byte $C0
+ .else
+ .ifndef REQUIRE_DMG
+ .byte $80
+ .endif
+ .endif
+
+.org $200
+
+
+;;;; Shell
+
+.include "runtime.s"
+.include "console.s"
+
+init_runtime:
+ call console_init
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+ ret
+
+std_print:
+ push af
+ sta SB
+ wreg SC,$81
+ delay 2304
+ pop af
+ jp console_print
+
+post_exit:
+ call console_show
+ call play_byte
+forever:
+ wreg NR52,0 ; sound off
+- jr -
+
+play_byte:
+ ret
+
+.ends
diff --git a/playing-coffee/roms/mem_timing/source/common/console.bin b/playing-coffee/roms/mem_timing/source/common/console.bin
new file mode 100644
index 0000000..b02f2d3
Binary files /dev/null and b/playing-coffee/roms/mem_timing/source/common/console.bin differ
diff --git a/playing-coffee/roms/mem_timing/source/common/console.s b/playing-coffee/roms/mem_timing/source/common/console.s
new file mode 100644
index 0000000..5307f6b
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/common/console.s
@@ -0,0 +1,291 @@
+; Scrolling text console
+
+; Console is 20x18 characters. Buffers lines, so
+; output doesn't appear until a newline or flush.
+; If scrolling isn't supported (i.e. SCY is treated
+; as if always zero), the first 18 lines will
+; still print properly). Also works properly if
+; LY isn't supported (always reads back as the same
+; value).
+
+.define console_width 20
+
+.define console_buf bss+0
+.define console_pos bss+console_width
+.define console_mode bss+console_width+1
+.define console_scroll bss+console_width+2
+.redefine bss bss+console_width+3
+
+
+; Waits for start of LCD blanking period
+; Preserved: BC, DE, HL
+console_wait_vbl:
+ push bc
+
+ ; Wait for start of vblank, with
+ ; timeout in case LY doesn't work
+ ; or LCD is disabled.
+ ld bc,-1250
+- inc bc
+ ld a,b
+ or c
+ jr z,@timeout
+ lda LY
+ cp 144
+ jr nz,-
+@timeout:
+
+ pop bc
+ ret
+
+
+; Initializes text console
+console_init:
+ call console_hide
+
+ ; CGB-specific inits
+ ld a,(gb_id)
+ and gb_id_cgb
+ call nz,@init_cgb
+
+ ; Clear nametable
+ ld a,' '
+ call @fill_nametable
+
+ ; Load tiles
+ ld hl,TILES+$200
+ ld c,0
+ call @load_tiles
+ ld hl,TILES+$A00
+ ld c,$FF
+ call @load_tiles
+
+ ; Init state
+ ld a,console_width
+ ld (console_pos),a
+ ld a,0
+ ld (console_mode),a
+ ld a,-8
+ ld (console_scroll),a
+ call console_scroll_up_
+ jr console_show
+
+@fill_nametable:
+ ld hl,BGMAP0
+ ld b,4
+- ld (hl),a
+ inc l
+ jr nz,-
+ inc h
+ dec b
+ jr nz,-
+ ret
+
+@init_cgb:
+ ; Clear palette
+ wreg $FF68,$80
+ ld b,16
+- wreg $FF69,$FF
+ wreg $FF69,$7F
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ dec b
+ jr nz,-
+
+ ; Clear attributes
+ ld a,1
+ ld (VBK),a
+ ld a,0
+ call @fill_nametable
+
+ ld a,0
+ ld (VBK),a
+ ret
+
+@load_tiles:
+ ld de,ASCII
+ ld b,96
+-- push bc
+ ld b,8
+- ld a,(de)
+ inc de
+ xor c
+ ldi (hl),a
+ ldi (hl),a
+ dec b
+ jr nz,-
+ pop bc
+ dec b
+ jr nz,--
+ ret
+
+
+; Shows console display
+; Preserved: AF, BC, DE, HL
+console_show:
+ push af
+
+ ; Enable LCD
+ call console_wait_vbl
+ wreg LCDC,$91
+ wreg SCX,0
+ wreg BGP,$E4
+
+ jp console_apply_scroll_
+
+
+; Hides console display by turning LCD off
+; Preserved: AF, BC, DE, HL
+console_hide:
+ push af
+
+ ; LCD off
+ call console_wait_vbl
+ wreg LCDC,$11
+
+ pop af
+ ret
+
+
+; Changes to normal text mode
+; Preserved: BC, DE, HL
+console_normal:
+ xor a
+ jr console_set_mode
+
+; Changes to inverse text mode
+; Preserved: BC, DE, HL
+console_inverse:
+ ld a,$80
+
+; Changes console mode to A.
+; 0: Normal, $80: Inverse
+; Preserved: BC, DE, HL
+console_set_mode:
+ and $80
+ ld (console_mode),a
+ ret
+
+
+; Prints char A to console. Will not appear until
+; a newline or flush occurs.
+; Preserved: AF, BC, DE, HL
+console_print:
+ push af
+
+ cp 10
+ jr z,console_newline_
+
+ push hl
+ push af
+ ld hl,console_pos
+ ldi a,(hl)
+ cp BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee/roms/mem_timing/source/common/delay.s b/playing-coffee/roms/mem_timing/source/common/delay.s
new file mode 100644
index 0000000..65eb9c0
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+ .endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+ .endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+ .endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee/roms/mem_timing/source/common/gb.inc b/playing-coffee/roms/mem_timing/source/common/gb.inc
new file mode 100644
index 0000000..31bbf14
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/common/gb.inc
@@ -0,0 +1,64 @@
+; Game Boy hardware addresses
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+.define P1 $FF00
+
+; Game link I/O
+.define SB $FF01
+.define SC $FF02
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee/roms/mem_timing/source/common/macros.inc b/playing-coffee/roms/mem_timing/source/common/macros.inc
new file mode 100644
index 0000000..c413bd5
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/common/macros.inc
@@ -0,0 +1,73 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ARGS addr
+ ldh a,(addr - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ARGS addr
+ ldh (addr - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee/roms/mem_timing/source/common/numbers.s b/playing-coffee/roms/mem_timing/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee/roms/mem_timing/source/common/printing.s b/playing-coffee/roms/mem_timing/source/common/printing.s
new file mode 100644
index 0000000..ad9d811
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/common/printing.s
@@ -0,0 +1,98 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+.define print_char_nocrc bss
+.redefine bss bss+3
+
+
+; Initializes printing. HL = print routine
+init_printing:
+ ld a,l
+ ld (print_char_nocrc+1),a
+ ld a,h
+ ld (print_char_nocrc+2),a
+ jr show_printing
+
+
+; Hides/shows further printing
+; Preserved: BC, DE, HL
+hide_printing:
+ ld a,$C9 ; RET
+ jr +
+show_printing:
+ ld a,$C3 ; JP (nn)
++ ld (print_char_nocrc),a
+ ret
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee/roms/mem_timing/source/common/runtime.s b/playing-coffee/roms/mem_timing/source/common/runtime.s
new file mode 100644
index 0000000..90cd24f
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/common/runtime.s
@@ -0,0 +1,142 @@
+; Common routines and runtime
+
+; Must be defined by target-specific runtime:
+;
+; init_runtime: ; target-specific inits
+; std_print: ; default routine to print char A
+; post_exit: ; called at end of std_exit
+; report_byte: ; report A to user
+
+.define RUNTIME_INCLUDED 1
+
+.ifndef bss
+ ; address of next normal variable
+ .define bss $D800
+.endif
+
+.ifndef dp
+ ; address of next direct-page ($FFxx) variable
+ .define dp $FF80
+.endif
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+
+.define gb_id bss
+.redefine bss bss+1
+
+; Stack is normally here
+.define std_stack $DFFF
+
+; Copies $1000 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+ ld c,$10
+- ldi a,(hl)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+.ifndef CUSTOM_RESET
+ reset:
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org $0 ; otherwise wla pads with lots of zeroes
+ jp std_reset
+.endif
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld (gb_id),a
+
+ ; Init hardware
+ .ifndef BUILD_GBS
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+ .endif
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ ; TODO: clear all memory?
+
+ ld hl,std_print
+ call init_printing
+ call init_testing
+ call init_runtime
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ pop af
+ jp post_exit
+
++ push af
+ call print_newline
+ call show_printing
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
diff --git a/playing-coffee/roms/mem_timing/source/common/testing.s b/playing-coffee/roms/mem_timing/source/common/testing.s
new file mode 100644
index 0000000..c102f78
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/common/testing.s
@@ -0,0 +1,176 @@
+; Diagnostic and testing utilities
+
+.define result bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (result),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(result)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(result)
+ cp 1 ; if a = 0 then a = 1
+ adc 0
+ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call show_printing
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee/roms/mem_timing/source/common/tima_64.s b/playing-coffee/roms/mem_timing/source/common/tima_64.s
new file mode 100644
index 0000000..1db7211
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/common/tima_64.s
@@ -0,0 +1,33 @@
+; Timer that's incremented every 64 cycles
+
+; Initializes timer for use by sync_tima_64
+init_tima_64:
+ wreg TMA,0
+ wreg TAC,$07
+ ret
+
+; Synchronizes to timer
+sync_tima_64:
+ push af
+ push hl
+
+ ld a,0
+ ld hl,TIMA
+ ld (hl),a
+- or (hl)
+ jr z,-
+
+- delay 65-12
+ xor a
+ ld (hl),a
+ or (hl)
+ delay 4
+ jr z,-
+
+ pop hl
+ pop af
+ ret
+
+; Read from this to get count that's incremented
+; every 64 cycles.
+.define tima_64 TIMA
diff --git a/playing-coffee/roms/mem_timing/source/linkfile b/playing-coffee/roms/mem_timing/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee/roms/mem_timing/source/shell.inc b/playing-coffee/roms/mem_timing/source/shell.inc
new file mode 100644
index 0000000..277a9b7
--- /dev/null
+++ b/playing-coffee/roms/mem_timing/source/shell.inc
@@ -0,0 +1,21 @@
+.incdir "common"
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
diff --git a/playing-coffee/roms/oam_bug/oam_bug.gb b/playing-coffee/roms/oam_bug/oam_bug.gb
new file mode 100644
index 0000000..42ff9ca
Binary files /dev/null and b/playing-coffee/roms/oam_bug/oam_bug.gb differ
diff --git a/playing-coffee/roms/oam_bug/readme.txt b/playing-coffee/roms/oam_bug/readme.txt
new file mode 100644
index 0000000..cf59776
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/readme.txt
@@ -0,0 +1,109 @@
+OAM Corruption Test
+-------------------
+* Verifies OAM corruption bug on DMG.
+
+* Occurs when 16-bit increment/decrement is made of value in range $FE00
+to $FEFF, during around the first 20 cycles of a visible scanline while
+LCD is on, where 114 cycles = 1 scanline.
+
+* Causes several bytes of OAM to be copied from one place to another.
+
+* Occurs with instructions that do increment:
+ INC rp (including SP)
+ DEC rp
+ POP rp counts as two increments
+ PUSH rp counts as two increments
+ LD A,(HL+)
+ LD A,(HL-)
+
+* Doesn't occur with instructions that do 16-bit add:
+ LD HL,SP+n
+ ADD HL,rp
+ ADD SP,n
+
+* Doesn't occur anytime during the 10 vblank scanlines.
+
+* Doesn't occur when LCD is off, no matter when it happens.
+
+* Corruption depends on when it occurs.
+
+
+Multi-ROM
+---------
+In the main directory is a single ROM/GBS which runs all the tests. It
+prints a test's number, runs the test, then "ok" if it passes, otherwise
+a failure code. Once all tests have completed it either reports that all
+tests passed, or reports the number of the first failed test as the
+result code (1 = first). Finally, it makes several beeps. If a test
+fails, it can be run on its own by finding the corresponding ROM/GBS in
+the singles directories.
+
+Ths compact format on screen is to avoid having the results scroll off
+the top, so the test can be started and allowed to run without having to
+constantly monitor the display.
+
+
+Failure information
+-------------------
+For more information about a failure code or information printed, see
+the test's source code in source/. To find failure code N, search for
+"set_test N", which will usually be before the subtest which failed.
+
+
+Flashes, clicks, other glitches
+-------------------------------
+Some tests might need to turn the screen off and on, or cause slight
+audio clicks. This does not indicate failure, and should be ignored.
+Only the test result reported at the end is important, unless stated
+otherwise.
+
+
+LCD support
+-----------
+Tests generally print information on screen. The tests will work fine if
+run on an emulator with NO LCD support, or as an GBS which has no
+inherent screen; in particular, the VBL wait routine has a timeout in
+case LY doesn't reflect the current LCD line. The text printing will
+also work if the LCD doesn't support scrolling.
+
+
+Output to memory
+----------------
+Text output and the final result are also written to memory at $A000,
+allowing testing a very minimal emulator that supports little more than
+CPU and RAM. To reliably indicate that the data is from a test and not
+random data, $A001-$A003 are written with a signature: $DE,$B0,$61. If
+this is present, then the text string and final result status are valid.
+
+$A000 holds the overall status. If the test is still running, it holds
+$80, otherwise it holds the final result code.
+
+All text output is appended to a zero-terminated string at $A004. An
+emulator could regularly check this string for any additional
+characters, and output them, allowing real-time text output, rather than
+just printing the final output at the end.
+
+
+GBS versions
+------------
+Many GBS-based tests require that the GBS player either not interrupt
+the init routine with the play routine, or if they do, not interrupt the
+play routine again if it hasn't returned yet. This is because many tests
+need to run for a while without returning.
+
+In addition to the other text output methods described above, GBS builds
+report essential information bytes audibly, including the final result.
+A byte is reported as a series of tones. The code is in binary, with a
+low tone for 0 and a high tone for 1. The first tone is always a zero. A
+final code of 0 means passed, 1 means failure, and 2 or higher indicates
+a specific reason as listed in the source code by the corresponding
+set_code line. Examples:
+
+Tones Binary Decimal Meaning
+- - - - - - - - - - - - - - - - - - - -
+low 0 0 passed
+low high 01 1 failed
+low high low 010 2 error 2
+
+--
+Shay Green
diff --git a/playing-coffee/roms/oam_bug/rom_singles/1-lcd_sync.gb b/playing-coffee/roms/oam_bug/rom_singles/1-lcd_sync.gb
new file mode 100644
index 0000000..8f3d856
Binary files /dev/null and b/playing-coffee/roms/oam_bug/rom_singles/1-lcd_sync.gb differ
diff --git a/playing-coffee/roms/oam_bug/rom_singles/2-causes.gb b/playing-coffee/roms/oam_bug/rom_singles/2-causes.gb
new file mode 100644
index 0000000..b997cda
Binary files /dev/null and b/playing-coffee/roms/oam_bug/rom_singles/2-causes.gb differ
diff --git a/playing-coffee/roms/oam_bug/rom_singles/3-non_causes.gb b/playing-coffee/roms/oam_bug/rom_singles/3-non_causes.gb
new file mode 100644
index 0000000..d545e83
Binary files /dev/null and b/playing-coffee/roms/oam_bug/rom_singles/3-non_causes.gb differ
diff --git a/playing-coffee/roms/oam_bug/rom_singles/4-scanline_timing.gb b/playing-coffee/roms/oam_bug/rom_singles/4-scanline_timing.gb
new file mode 100644
index 0000000..dc38b26
Binary files /dev/null and b/playing-coffee/roms/oam_bug/rom_singles/4-scanline_timing.gb differ
diff --git a/playing-coffee/roms/oam_bug/rom_singles/5-timing_bug.gb b/playing-coffee/roms/oam_bug/rom_singles/5-timing_bug.gb
new file mode 100644
index 0000000..47a5c02
Binary files /dev/null and b/playing-coffee/roms/oam_bug/rom_singles/5-timing_bug.gb differ
diff --git a/playing-coffee/roms/oam_bug/rom_singles/6-timing_no_bug.gb b/playing-coffee/roms/oam_bug/rom_singles/6-timing_no_bug.gb
new file mode 100644
index 0000000..b4e67e9
Binary files /dev/null and b/playing-coffee/roms/oam_bug/rom_singles/6-timing_no_bug.gb differ
diff --git a/playing-coffee/roms/oam_bug/rom_singles/7-timing_effect.gb b/playing-coffee/roms/oam_bug/rom_singles/7-timing_effect.gb
new file mode 100644
index 0000000..2212779
Binary files /dev/null and b/playing-coffee/roms/oam_bug/rom_singles/7-timing_effect.gb differ
diff --git a/playing-coffee/roms/oam_bug/rom_singles/8-instr_effect.gb b/playing-coffee/roms/oam_bug/rom_singles/8-instr_effect.gb
new file mode 100644
index 0000000..464a5f4
Binary files /dev/null and b/playing-coffee/roms/oam_bug/rom_singles/8-instr_effect.gb differ
diff --git a/playing-coffee/roms/oam_bug/source/1-lcd_sync.s b/playing-coffee/roms/oam_bug/source/1-lcd_sync.s
new file mode 100644
index 0000000..3c4f5e5
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/1-lcd_sync.s
@@ -0,0 +1,28 @@
+; Verifies LCD timing when turning on. Necessary for
+; timing tests to work right.
+
+; With LCD off, turning it on synchronizes to beginning
+; of first visible scanline
+
+.include "oam_bug.inc"
+
+main:
+ set_test 2,"Turning LCD on starts too late in scanline"
+ call disable_lcd
+ ld a,$81
+ ldh (LCDC-$FF00),a ; LCD on
+ delay 109
+ ldh a,(LY-$FF00) ; just before LY increments
+ cp 0
+ jp nz,test_failed
+
+ set_test 3,"Turning LCD on starts too early in scanline"
+ call disable_lcd
+ ld a,$81
+ ldh (LCDC-$FF00),a ; LCD on
+ delay 110
+ ldh a,(LY-$FF00) ; just after LY increments
+ cp 1
+ jp nz,test_failed
+
+ jp tests_passed
diff --git a/playing-coffee/roms/oam_bug/source/2-causes.s b/playing-coffee/roms/oam_bug/source/2-causes.s
new file mode 100644
index 0000000..15b725c
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/2-causes.s
@@ -0,0 +1,86 @@
+; Things that cause corruption
+
+.include "oam_bug.inc"
+
+main:
+ save_sp
+
+ set_test 2,"LD DE,$FE00 : INC DE"
+ call begin
+ ld de,$FE00
+ inc de
+ call end
+
+ set_test 3,"LD DE,$FE00 : DEC DE"
+ call begin
+ ld de,$FE00
+ dec de
+ call end
+
+ set_test 4,"LD DE,$FEFF : INC DE"
+ call begin
+ ld de,$FEFF
+ inc de
+ call end
+
+ set_test 5,"LD BC,$FE00 : INC BC"
+ call begin
+ ld bc,$FE00
+ inc bc
+ call end
+
+ set_test 6,"LD HL,$FE00 : INC HL"
+ call begin
+ ld hl,$FE00
+ inc hl
+ call end
+
+ set_test 7,"LD SP,$FE00 : INC SP"
+ call begin
+ ld sp,$FE00
+ inc sp
+ restore_sp
+ call end
+
+ set_test 8,"LD SP,$FDFF : POP BC"
+ call begin
+ ld sp,$FDFF
+ pop bc
+ restore_sp
+ call end
+
+ set_test 9,"LD SP,$FE00 : PUSH BC"
+ call begin
+ ld sp,$FE00
+ push bc
+ restore_sp
+ call end
+
+ set_test 10,"LD HL,$FE00 : LD A,(HL+)"
+ call begin
+ ld hl,$FE00
+ ld a,(hl+)
+ restore_sp
+ call end
+
+ set_test 11,"LD HL,$FE00 : LD A,(HL-)"
+ call begin
+ ld hl,$FE00
+ ld a,(hl-)
+ restore_sp
+ call end
+
+ jp tests_passed
+
+begin:
+ call disable_lcd
+ call fill_oam
+ wreg LCDC,$81
+ delay 70224 - 6
+ ret
+
+end:
+ call disable_lcd
+ call cp_oam
+ jp z,test_failed
+ ret
diff --git a/playing-coffee/roms/oam_bug/source/3-non_causes.s b/playing-coffee/roms/oam_bug/source/3-non_causes.s
new file mode 100644
index 0000000..c161f97
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/3-non_causes.s
@@ -0,0 +1,86 @@
+; Things that don't cause corruption
+
+.include "oam_bug.inc"
+
+main:
+ save_sp
+
+ set_test 2,"When LCD is off"
+ call disable_lcd
+ call fill_oam
+ ld bc,2 * 70224/11 ; a couple of frames
+ ld de,$FE00
+- inc de
+ dec bc
+ dec de
+ ld a,b
+ or c
+ jr nz,-
+ call end
+
+ set_test 3,"LD DE,$FF00 : DEC DE"
+ call begin
+ ld de,$FF00
+ dec de
+ call end
+
+ set_test 4,"LD DE,$FDFF : INC DE"
+ call begin
+ ld de,$FDFF
+ inc de
+ call end
+
+ set_test 5,"LD DE,$7E00 : INC DE"
+ call begin
+ ld de,$7E00
+ inc de
+ call end
+
+ set_test 6,"LD DE,$FE00 : INC E"
+ call begin
+ ld de,$FE00
+ inc e
+ call end
+
+ set_test 7,"LD SP,$FDFE : POP BC"
+ call begin
+ ld sp,$FDFE
+ pop bc
+ restore_sp
+ call end
+
+ set_test 8,"LD SP,$FE00 : LD HL,SP+1"
+ call begin
+ ld sp,$FE00
+ ld hl,sp+1
+ restore_sp
+ call end
+
+ set_test 9,"LD HL,$FE00 : LD BC,$0001 : ADD HL,BC"
+ call begin
+ ld hl,$FE00
+ ld bc,$0001
+ add hl,bc
+ call end
+
+ set_test 10,"LD SP,$FE00 : ADD SP,1"
+ call begin
+ ld sp,$FE00
+ add sp,1
+ restore_sp
+ call end
+
+ jp tests_passed
+
+begin:
+ call disable_lcd
+ call fill_oam
+ wreg LCDC,$81
+ delay 70224 - 6
+ ret
+
+end:
+ call disable_lcd
+ call cp_oam
+ jp nz,test_failed
+ ret
diff --git a/playing-coffee/roms/oam_bug/source/4-scanline_timing.s b/playing-coffee/roms/oam_bug/source/4-scanline_timing.s
new file mode 100644
index 0000000..e5136f2
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/4-scanline_timing.s
@@ -0,0 +1,62 @@
+; Demonstrates exact timing for first scanline
+
+; With LCD off, turning it on synchronizes to beginning
+; of first visible scanline
+
+.include "oam_bug.inc"
+
+main:
+ set_test 2,"INC DE just before first corruption"
+ call begin
+ ld de,$FE00
+ ld a,$81
+ ldh (LCDC-$FF00),a ; LCD on
+ delay 70224-3
+ inc de
+ call should_not_corrupt
+
+ set_test 3,"INC DE at first corruption"
+ call begin
+ ld de,$FE00
+ ld a,$81
+ ldh (LCDC-$FF00),a ; LCD on
+ delay 70224-2
+ inc de
+ call should_corrupt
+
+ set_test 4,"INC DE at last corruption"
+ call begin
+ ld de,$FE00
+ ld a,$81
+ ldh (LCDC-$FF00),a ; LCD on
+ delay 70224-2+18
+ inc de
+ call should_corrupt
+
+ set_test 5,"INC DE just after last corruption"
+ call begin
+ ld de,$FE00
+ ld a,$81
+ ldh (LCDC-$FF00),a ; LCD on
+ delay 70224-2+19
+ inc de
+ call should_not_corrupt
+
+ jp tests_passed
+
+begin:
+ call disable_lcd
+ call fill_oam
+ ret
+
+should_not_corrupt:
+ call disable_lcd
+ call cp_oam
+ jp nz,test_failed
+ ret
+
+should_corrupt:
+ call disable_lcd
+ call cp_oam
+ jp z,test_failed
+ ret
diff --git a/playing-coffee/roms/oam_bug/source/5-timing_bug.s b/playing-coffee/roms/oam_bug/source/5-timing_bug.s
new file mode 100644
index 0000000..cdaa10a
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/5-timing_bug.s
@@ -0,0 +1,43 @@
+; Verifies corruption at timing edges:
+; * Beginning of first scanline, and 18 cycles later
+; * Beginning of second scanline
+; * End of last scanline
+
+.include "oam_bug.inc"
+
+main:
+ set_test 2,"Should corrupt at beginning of first scanline"
+ call begin
+ call end
+
+ set_test 3,"Should corrupt at +18 of first scanline"
+ call begin
+ delay 18
+ call end
+
+ set_test 4,"Should corrupt at beginning of second scanline"
+ call begin
+ delay 114
+ call end
+
+ set_test 5,"Should corrupt at +18 of last scanline"
+ call begin
+ delay 114*143+18
+ call end
+
+ jp tests_passed
+
+begin:
+ call disable_lcd
+ call fill_oam
+ wreg LCDC,$81
+ delay 70224 - 15
+ ret
+
+end:
+ ld de,$FE00
+ inc de
+ call disable_lcd
+ call cp_oam
+ jp z,test_failed
+ ret
diff --git a/playing-coffee/roms/oam_bug/source/6-timing_no_bug.s b/playing-coffee/roms/oam_bug/source/6-timing_no_bug.s
new file mode 100644
index 0000000..12979cf
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/6-timing_no_bug.s
@@ -0,0 +1,57 @@
+; Verifies no corruption when done at "safe" times
+
+.include "oam_bug.inc"
+
+main:
+ call disable_lcd
+ call fill_oam
+
+ call test
+
+ call disable_lcd
+ call cp_oam
+
+ call print_oam
+ check_crc $7BB4F198
+ jp tests_passed
+
+test:
+ ld de,$FE00
+ wreg LCDC,$81
+
+ ; Have first INC DE at scanline 0 beginning
+ delay 70224 - 7
+
+ ; Run for several frames
+ ld h,10
+@loop:
+
+ ; Try to trigger just before and just after
+ ; window where corruption occurs, for every
+ ; scanline
+ ld b,144
+- inc de
+ delay 18
+ dec de
+ delay 114-22-4
+ dec b
+ jr nz,-
+
+ ; Try to trigger constantly during vblank
+ ld b,10
+ jr +
+-- delay 13
++ ld c,12
+- inc de
+ dec c
+ dec de
+ jr nz,-
+ dec b
+ jr nz,--
+
+ delay 4
+
+ dec h
+ jr nz,@loop
+
+ ret
diff --git a/playing-coffee/roms/oam_bug/source/7-timing_effect.s b/playing-coffee/roms/oam_bug/source/7-timing_effect.s
new file mode 100644
index 0000000..2bc307b
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/7-timing_effect.s
@@ -0,0 +1,38 @@
+; Verifies corruption for each timing
+
+.include "oam_bug.inc"
+
+main:
+ loop_n_times test,116
+ check_crc $7D792E7C
+ jp tests_passed
+
+test:
+ inc a
+ ld b,a
+ push bc
+ call disable_lcd
+ call fill_oam
+ call corrupt_oam
+ call disable_lcd
+ call cp_oam
+ pop bc
+
+ jr z,+
+ call print_b
+ call print_newline
+ call print_oam
+ call print_newline
+
++ ret
+
+corrupt_oam:
+ wreg LCDC,$81
+ ld a,b
+ call delay_a_20_cycles
+ delay 86
+
+ ld de,$FE00
+ inc de
+
+ ret
diff --git a/playing-coffee/roms/oam_bug/source/8-instr_effect.s b/playing-coffee/roms/oam_bug/source/8-instr_effect.s
new file mode 100644
index 0000000..3d50112
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/8-instr_effect.s
@@ -0,0 +1,63 @@
+; Verifies corruption pattern for each instruction
+
+.include "oam_bug.inc"
+
+main:
+ save_sp
+
+ set_test 2,"INC/DEC rp pattern is wrong"
+ call begin
+ ld de,$FE00
+ inc de
+ call end
+
+ ld de,$FE00
+ dec de
+ call end
+ check_crc $EF0C266A
+
+ set_test 3,"POP rp pattern is wrong"
+ call begin
+ ld sp,$FEF0
+ pop bc
+ restore_sp
+ call end
+ check_crc $8C62EE7D
+
+ set_test 4,"PUSH rp pattern is wrong"
+ call begin
+ ld sp,$FEF0
+ push bc
+ restore_sp
+ call end
+ check_crc $B3693CEE
+
+ set_test 5,"LD A,(HL+/-) pattern is wrong"
+ call begin
+ ld hl,$FEF0
+ ld a,(hl+)
+ call end
+
+ ld hl,$FEF0
+ ld a,(hl-)
+ call end
+ check_crc $06BE41A4
+
+ jp tests_passed
+
+begin:
+ call disable_lcd
+ call fill_oam
+ wreg LCDC,$81
+ delay 70224 - 4
+ ret
+
+end:
+ call disable_lcd
+ call cp_oam
+ jr z,+
+ call print_oam
+ call print_newline
++
+ call begin
+ ret
diff --git a/playing-coffee/roms/oam_bug/source/common/build_gbs.s b/playing-coffee/roms/oam_bug/source/common/build_gbs.s
new file mode 100644
index 0000000..877754f
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/common/build_gbs.s
@@ -0,0 +1,139 @@
+; Build as GBS music file
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $2000 size $2000
+ slot 1 $C000 size $2000
+.endMe
+
+.romBankSize $2000
+.romBanks 2
+
+.define RST_OFFSET $70
+
+.ifndef GBS_TMA
+ .define GBS_TMA 0
+.endif
+
+.ifndef GBS_TAC
+ .define GBS_TAC 0
+.endif
+
+;;;; GBS music file header
+
+.ifndef CUSTOM_HEADER
+ .byte "GBS"
+ .byte 1,1,1 ; vers, song count, first song
+ .word load_addr, reset, gbs_play_, std_stack
+ .byte GBS_TMA,GBS_TAC ; timer
+.endif
+ .org $10
+ .ds $60,0
+load_addr:
+ .org RST_OFFSET+$70 ; space for RST vectors
+ .ds $148-RST_OFFSET-$70,0
+ .org $150 ; wla insists on generating GB header
+
+gbs_play_:
+ jp gbs_play ; GBS spec disallows having gbs_play in RAM
+
+;;;; Shell
+
+.include "shell.s"
+
+.define gbs_idle nv_ram
+.redefine nv_ram nv_ram+2
+
+init_runtime:
+ ; Identify as DMG hardware
+ ld a,$01
+ ld (gb_id),a
+
+ ; Save return address
+ pop hl
+ ld a,l
+ ld (gbs_idle),a
+ ld a,h
+ ld (gbs_idle+1),a
+
+ ; Delay 1/4 second to give time
+ ; for GBS player to interrupt with
+ ; play, if it's going to do so
+ delay_msec 250
+
+.ifndef CUSTOM_PLAY
+gbs_play:
+.endif
+ ; Get return address
+ ld a,(gbs_idle)
+ ld l,a
+ ld a,(gbs_idle+1)
+ ld h,a
+
+ ; If zero, then play interrupted init
+ ; call, or another play call, and we
+ ; can't run the program properly.
+ or l
+ jp z,internal_error
+
+ setw gbs_idle,0
+ jp hl
+
+
+; Reports A in binary as high and low tones, with
+; leading low tone for reference. Omits leading
+; zeroes.
+; Preserved: AF, BC, DE, HL
+play_byte:
+ push af
+ push hl
+
+ ; HL = (A << 1) | 1
+ scf
+ rla
+ ld l,a
+ ld h,0
+ rl h
+
+ ; Shift left until next-to-top bit is 1
+- add hl,hl
+ bit 6,h
+ jr z,-
+
+ ; Reset sound
+ delay_msec 400
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+- add hl,hl
+
+ ; Low or high pitch based on bit shifted out
+ ; of HL
+ ld a,0
+ jr nc,+
+ ld a,$FF
++ sta NR23
+
+ ; Play short tone
+ wreg NR21,$A0
+ wreg NR22,$F0
+ wreg NR24,$86
+ delay_msec 75
+ wreg NR22,0
+ wreg NR23,$F8
+ wreg NR24,$87
+ delay_msec 200
+
+ ; Loop until HL = $8000
+ ld a,h
+ xor $80
+ or l
+ jr nz,-
+
+ pop hl
+ pop af
+ ret
+
+.ends
diff --git a/playing-coffee/roms/oam_bug/source/common/build_rom.s b/playing-coffee/roms/oam_bug/source/common/build_rom.s
new file mode 100644
index 0000000..c1595b7
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/common/build_rom.s
@@ -0,0 +1,70 @@
+; Build as GB ROM
+
+.memoryMap
+ defaultSlot 0
+ slot 0 $0000 size $4000
+ slot 1 $C000 size $4000
+.endMe
+
+.romBankSize $4000
+.romBanks 2
+
+.cartridgeType 3 ; MBC1+RAM+battery
+.ramsize 02 ; 8K
+.computeChecksum
+.computeComplementCheck
+
+;;;; GB ROM header
+
+ ; Reserve space for RST handlers
+ .org $70
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .ds $90,0
+
+ ; GB header read by bootrom
+ .org $100
+ nop
+ jp reset
+
+ ; Nintendo logo required for proper boot
+ .byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
+ .byte $03,$73,$00,$83,$00,$0C,$00,$0D
+ .byte $00,$08,$11,$1F,$88,$89,$00,$0E
+ .byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
+ .byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
+ .byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
+
+ ; Internal name
+ .ifdef ROM_NAME
+ .byte ROM_NAME
+ .endif
+
+ ; CGB/DMG requirements
+ .org $143
+ .ifdef REQUIRE_CGB
+ .byte $C0
+ .else
+ .ifndef REQUIRE_DMG
+ .byte $80
+ .endif
+ .endif
+
+ ; Keep unused space filled, otherwise
+ ; wla moves code here
+ .org $150
+ .ds $2150-$150,0
+
+;;;; Shell
+
+.define NEED_CONSOLE 1
+.include "shell.s"
+
+init_runtime:
+ ret
+
+play_byte:
+ ret
+
+.ends
diff --git a/playing-coffee/roms/oam_bug/source/common/console.bin b/playing-coffee/roms/oam_bug/source/common/console.bin
new file mode 100644
index 0000000..b02f2d3
Binary files /dev/null and b/playing-coffee/roms/oam_bug/source/common/console.bin differ
diff --git a/playing-coffee/roms/oam_bug/source/common/console.s b/playing-coffee/roms/oam_bug/source/common/console.s
new file mode 100644
index 0000000..403fa5c
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/common/console.s
@@ -0,0 +1,285 @@
+; Scrolling text console
+
+; Console is 20x18 characters. Buffers lines, so
+; output doesn't appear until a newline or flush.
+; If scrolling isn't supported (i.e. SCY is treated
+; as if always zero), the first 18 lines will
+; still print properly). Also works properly if
+; LY isn't supported (always reads back as the same
+; value).
+
+.define console_width 20
+
+.define console_buf bss+0
+.define console_pos bss+console_width
+.define console_mode bss+console_width+1
+.define console_scroll bss+console_width+2
+.redefine bss bss+console_width+3
+
+
+; Waits for start of LCD blanking period
+; Preserved: BC, DE, HL
+console_wait_vbl:
+ push bc
+
+ ; Wait for start of vblank, with
+ ; timeout in case LY doesn't work
+ ; or LCD is disabled.
+ ld bc,-1250
+- inc bc
+ ld a,b
+ or c
+ jr z,@timeout
+ lda LY
+ cp 144
+ jr nz,-
+@timeout:
+
+ pop bc
+ ret
+
+
+; Initializes text console
+console_init:
+ call console_hide
+
+ ; CGB-specific inits
+ ld a,(gb_id)
+ and gb_id_cgb
+ call nz,@init_cgb
+
+ ; Clear nametable
+ ld a,' '
+ call @fill_nametable
+
+ ; Load tiles
+ ld hl,TILES+$200
+ ld c,0
+ call @load_tiles
+ ld hl,TILES+$A00
+ ld c,$FF
+ call @load_tiles
+
+ ; Init state
+ setb console_pos,console_width
+ setb console_mode,0
+ setb console_scroll,-8
+ call console_scroll_up_
+ jr console_show
+
+@fill_nametable:
+ ld hl,BGMAP0
+ ld b,4
+- ld (hl),a
+ inc l
+ jr nz,-
+ inc h
+ dec b
+ jr nz,-
+ ret
+
+@init_cgb:
+ ; Clear palette
+ wreg $FF68,$80
+ ld b,16
+- wreg $FF69,$FF
+ wreg $FF69,$7F
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ wreg $FF69,$00
+ dec b
+ jr nz,-
+
+ ; Clear attributes
+ wreg VBK,1
+ ld a,0
+ call @fill_nametable
+
+ wreg VBK,0
+ ret
+
+@load_tiles:
+ ld de,ASCII
+ ld b,96
+-- push bc
+ ld b,8
+- ld a,(de)
+ inc de
+ xor c
+ ldi (hl),a
+ ldi (hl),a
+ dec b
+ jr nz,-
+ pop bc
+ dec b
+ jr nz,--
+ ret
+
+
+; Shows console display
+; Preserved: AF, BC, DE, HL
+console_show:
+ push af
+
+ ; Enable LCD
+ call console_wait_vbl
+ wreg LCDC,$91
+ wreg SCX,0
+ wreg BGP,$E4
+
+ jp console_apply_scroll_
+
+
+; Hides console display by turning LCD off
+; Preserved: AF, BC, DE, HL
+console_hide:
+ push af
+
+ ; LCD off
+ call console_wait_vbl
+ wreg LCDC,$11
+
+ pop af
+ ret
+
+
+; Changes to normal text mode
+; Preserved: BC, DE, HL
+console_normal:
+ xor a
+ jr console_set_mode
+
+; Changes to inverse text mode
+; Preserved: BC, DE, HL
+console_inverse:
+ ld a,$80
+
+; Changes console mode to A.
+; 0: Normal, $80: Inverse
+; Preserved: BC, DE, HL
+console_set_mode:
+ and $80
+ ld (console_mode),a
+ ret
+
+
+; Prints char A to console. Will not appear until
+; a newline or flush occurs.
+; Preserved: AF, BC, DE, HL
+console_print:
+ push af
+
+ cp 10
+ jr z,console_newline_
+
+ push hl
+ push af
+ ld hl,console_pos
+ ldi a,(hl)
+ cp BGMAP0) >> 2
+ add hl,hl
+ add hl,hl
+
+ ; Copy line
+ ld de,console_buf + console_width
+- dec e
+ ld a,(de)
+ ldi (hl),a
+ ld a,e
+ cp checksum
+ ldi (hl),a
+ ld (hl),d
+ inc l
+ ld (hl),c
+ inc l
+ ld (hl),b
+
+ pop hl
+ pop de
+ pop bc
+ pop af
+ ret
diff --git a/playing-coffee/roms/oam_bug/source/common/delay.s b/playing-coffee/roms/oam_bug/source/common/delay.s
new file mode 100644
index 0000000..cbcdcf3
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/common/delay.s
@@ -0,0 +1,220 @@
+; Delays in cycles, milliseconds, etc.
+
+; All routines are re-entrant (no global data). Routines never
+; touch BC, DE, or HL registers. These ASSUME CPU is at normal
+; speed. If running at double speed, msec/usec delays are half advertised.
+
+; Delays n cycles, from 0 to 16777215
+; Preserved: AF, BC, DE, HL
+.macro delay ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 16777215
+ .printt "Delay must be < 16777216"
+ .fail
+ .endif
+ delay_ n&$FFFF, n>>16
+.endm
+
+; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
+; Preserved: AF, BC, DE, HL
+.macro delay_clocks ARGS n
+ .if n # 4 != 0
+ .printt "Delay must be a multiple of 4"
+ .fail
+ .endif
+ delay_ (n/4)&$FFFF,(n/4)>>16
+.endm
+
+; Delays n microseconds (1/1000000 second)
+; n can range from 0 to 4000 usec.
+; Preserved: AF, BC, DE, HL
+.macro delay_usec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 4000
+ .printt "Delay must be <= 4000 usec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
+.endm
+
+; Delays n milliseconds (1/1000 second)
+; n can range from 0 to 10000 msec.
+; Preserved: AF, BC, DE, HL
+.macro delay_msec ARGS n
+ .if n < 0
+ .printt "Delay must be >= 0"
+ .fail
+ .endif
+ .if n > 10000
+ .printt "Delay must be <= 10000 msec"
+ .fail
+ .endif
+ delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
+.endm
+
+ ; All the low/high quantities are to deal wla-dx's asinine
+ ; restriction full expressions must evaluate to a 16-bit
+ ; value. If the author ever rectifies this, all "high"
+ ; arguments can be treated as zero and removed. Better yet,
+ ; I'll just find an assembler that didn't crawl out of
+ ; the sewer (this is one of too many bugs I've wasted
+ ; hours working around).
+
+ .define max_short_delay 28
+
+ .macro delay_long_ ARGS n, high
+ ; 0+ to avoid assembler treating as memory read
+ ld a,0+(((high<<16)+n) - 11) >> 16
+ call delay_65536a_9_cycles_
+ delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
+.endm
+
+ ; Doesn't save AF, allowing minimization of AF save/restore
+ .macro delay_nosave_ ARGS n, high
+ ; 65536+11 = maximum delay using delay_256a_9_cycles_
+ ; 255+22 = maximum delay using delay_a_20_cycles
+ ; 22 = minimum delay using delay_a_20_cycles
+ .if high > 1
+ delay_long_ n, high
+ .else
+ .if high*n > 11
+ delay_long_ n, high
+ .else
+ .if (high*(255+22+1))|n > 255+22
+ ld a,>(((high<<16)+n) - 11)
+ call delay_256a_9_cycles_
+ delay_nosave_ <(((high<<16)+n) - 11), 0
+ .else
+ .if n >= 22
+ ld a,n - 22
+ call delay_a_20_cycles
+ .else
+ delay_short_ n
+ .endif
+ .endif
+ .endif
+ .endif
+.endm
+
+ .macro delay_ ARGS low, high
+ .if (high*(max_short_delay+1))|low > max_short_delay
+ push af
+ delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
+ pop af
+ .else
+ delay_short_ low
+ .endif
+.endm
+
+
+; Delays A cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A+20 cycles (including CALL)
+delay_a_20_cycles:
+- sub 5 ; 2
+ jr nc,- ;3/2 do multiples of 5
+ rra ; 1
+ jr nc,+ ;3/2 bit 0
++ adc 1 ; 2
+ ret nc ;5/2 -1: 0 cycles
+ ret z ;5/2 0: 2 cycles
+ nop ; 1 1: 4 cycles
+ ret ; 4 (thanks to dclxvi for original algorithm)
+
+; Delays A*256 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*256+12 cycles (including CALL)
+delay_256a_12_cycles:
+ or a ; 1
+ ret z ; 5/2
+delay_256a_9_cycles_:
+- delay 256-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays A*65536 cycles + overhead
+; Preserved: BC, DE, HL
+; Time: A*65536+12 cycles (including CALL)
+delay_65536a_12_cycles:
+ or a ; 1
+ ret z ;5/2
+delay_65536a_9_cycles_:
+- delay 65536-4
+ dec a ; 1
+ jr nz,- ;3/2
+ ret ; 4
+
+; Delays H*256+L cycles + overhead
+; Preserved: AF, BC, DE, HL
+; Time: H*256+L+51 cycles
+delay_hl_51_cycles:
+ push af
+ ld a,h
+ call delay_256a_12_cycles
+ ld a,l
+ call delay_a_20_cycles
+ pop af
+ ret
+
+ ; delay_short_ macro calls into these
+ .ds max_short_delay-10,$00 ; NOP repeated several times
+delay_unrolled_:
+ ret
+
+.macro delay_short_ ARGS n
+ .if n < 0
+ .fail
+ .endif
+ .if n > max_short_delay
+ .fail
+ .endif
+
+ .if n == 1
+ nop
+ .endif
+ .if n == 2
+ nop
+ nop
+ .endif
+ .if n == 3
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 4
+ .byte $18,$00 ; JR +0
+ nop
+ .endif
+ .if n == 5
+ .byte $18,$00 ; JR +0
+ nop
+ nop
+ .endif
+ .if n == 6
+ .byte $18,$00 ; JR +0
+ .byte $18,$00 ; JR +0
+ .endif
+ .if n == 7
+ push af
+ pop af
+ .endif
+ .if n == 8
+ push af
+ pop af
+ nop
+ .endif
+ .if n == 9
+ push af
+ pop af
+ nop
+ nop
+ .endif
+ .if n >= 10
+ call delay_unrolled_ + 10 - n
+ .endif
+.endm
diff --git a/playing-coffee/roms/oam_bug/source/common/gb.inc b/playing-coffee/roms/oam_bug/source/common/gb.inc
new file mode 100644
index 0000000..2d0118d
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/common/gb.inc
@@ -0,0 +1,81 @@
+; Game Boy hardware addresses
+
+; $0000-$3FFF Fixed ROM bank
+; $4000-$7FFF Switchable bank
+; $8000-$9FFF VRAM
+; $A000-$BFFF optional cartridge RAM
+; $C000-$DFFF RAM
+; $E000-$FDFF RAM mirror
+; $FE00-$FE9F OAM
+; $FEA0-$FEFF unused
+; $FF00-$FF7F registers
+; $FF80-$FFFE RAM
+; $FFFF register
+
+; Memory
+.define VRAM $8000 ; video memory
+.define TILES $8000 ; tile images
+.define BGMAP0 $9800 ; first 32x32 tilemap
+.define BGMAP1 $9C00 ; second 32x32 tilemap
+.define BRAM $A000 ; cart memory
+.define WRAM $C000 ; internal memory
+.define OAM $FE00 ; sprite memory
+.define HRAM $FF80 ; fast memory for LDH
+
+; Registers
+
+.define RAMEN $0000 ; cartridge WRAM control
+.define BANK $2000 ; bank select
+.define P1 $FF00 ; controller
+
+; Game link I/O
+.define SB $FF01 ; serial buffer
+.define SC $FF02 ; serial control
+
+; Interrupts
+.define DIV $FF04
+.define TIMA $FF05
+.define TMA $FF06
+.define TAC $FF07
+.define IF $FF0F
+.define IE $FFFF
+
+; LCD registers
+.define LCDC $FF40 ; control
+.define STAT $FF41 ; status
+.define SCY $FF42 ; scroll Y
+.define SCX $FF43 ; scroll X
+.define LY $FF44 ; current Y being rendered
+.define BGP $FF47
+
+.define KEY1 $FF4D ; for changing CPU speed
+.define VBK $FF4F
+
+; Sound registers
+.define NR10 $FF10
+.define NR11 $FF11
+.define NR12 $FF12
+.define NR13 $FF13
+.define NR14 $FF14
+
+.define NR21 $FF16
+.define NR22 $FF17
+.define NR23 $FF18
+.define NR24 $FF19
+
+.define NR30 $FF1A
+.define NR31 $FF1B
+.define NR32 $FF1C
+.define NR33 $FF1D
+.define NR34 $FF1E
+
+.define NR41 $FF20
+.define NR42 $FF21
+.define NR43 $FF22
+.define NR44 $FF23
+
+.define NR50 $FF24
+.define NR51 $FF25
+.define NR52 $FF26
+
+.define WAVE $FF30
diff --git a/playing-coffee/roms/oam_bug/source/common/macros.inc b/playing-coffee/roms/oam_bug/source/common/macros.inc
new file mode 100644
index 0000000..9d7cf44
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/common/macros.inc
@@ -0,0 +1,91 @@
+; General macros
+
+; Reads A from addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 3 cycles
+.macro lda ; addr
+ ldh a,(\1 - $FF00)
+.endm
+
+; Writes A to addr, from $FF00 to $FFFF
+; Preserved: AF, BC, DE, HL
+; Time: 3 cycles
+.macro sta ; addr
+ ldh (\1 - $FF00),a
+.endm
+
+; Writes immediate data to addr, from $FF00 to $FFFF
+; Preserved: F, BC, DE, HL
+; Time: 5 cycles
+.macro wreg ARGS addr, data
+ ld a,data
+ sta addr
+.endm
+
+; Writes byte to addr
+; Preserved: F, BC, DE, HL
+; Time: 6 cycles
+.macro setb ; addr, data
+ ld a,\2
+ ld (\1),a
+.endm
+
+; Writes word to addr
+; Preserved: F, BC, DE, HL
+; Time: 12 cycles
+.macro setw ; addr, data
+ ld a,<\2
+ ld (\1),a
+ ld a,>\2
+ ld (\1+1),a
+.endm
+
+; Calls routine multiple times, with A having the
+; value 'start' the first time, 'start+step' the
+; second time, up to 'end' for the last time.
+; Preserved: BC, DE, HL
+.macro for_loop ; routine,start,end,step
+ ld a,\2
+
+for_loop\@:
+ push af
+ call \1
+ pop af
+
+ add \4
+ cp <(\3 + \4)
+ jr nz,for_loop\@
+.endm
+
+; Calls routine n times. The value of A in the routine
+; counts from 0 to n-1.
+; Preserved: BC, DE, HL
+.macro loop_n_times ; routine,n
+ for_loop \1,0,\2 - 1,+1
+.endm
+
+; Same as for_loop, but counts with 16-bit value in BC.
+; Preserved: DE, HL
+.macro for_loop16 ; routine,start,end,step
+ ld bc,\2
+
+for_loop16\@:
+ push bc
+ call \1
+ pop bc
+
+ ld a,c
+ add <\4
+ ld c,a
+
+ ld a,b
+ adc >\4
+ ld b,a
+
+ cp >(\3+\4)
+ jr nz,for_loop16\@
+
+ ld a,c
+ cp <(\3+\4)
+ jr nz,for_loop16\@
+.endm
diff --git a/playing-coffee/roms/oam_bug/source/common/numbers.s b/playing-coffee/roms/oam_bug/source/common/numbers.s
new file mode 100644
index 0000000..6d6faf8
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/common/numbers.s
@@ -0,0 +1,177 @@
+; Printing of numeric values
+
+; Prints value of indicated register/pair
+; as 2/4 hex digits, followed by a space.
+; Updates checksum with printed values.
+; Preserved: AF, BC, DE, HL
+
+print_regs:
+ call print_af
+ call print_bc
+ call print_de
+ call print_hl
+ call print_newline
+ ret
+
+print_a:
+ push af
+print_a_:
+ call print_hex
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+print_af:
+ push af
+ call print_hex
+ pop af
+print_f:
+ push bc
+ push af
+ pop bc
+ call print_c
+ pop bc
+ ret
+
+print_b:
+ push af
+ ld a,b
+ jr print_a_
+
+print_c:
+ push af
+ ld a,c
+ jr print_a_
+
+print_d:
+ push af
+ ld a,d
+ jr print_a_
+
+print_e:
+ push af
+ ld a,e
+ jr print_a_
+
+print_h:
+ push af
+ ld a,h
+ jr print_a_
+
+print_l:
+ push af
+ ld a,l
+ jr print_a_
+
+print_bc:
+ push af
+ push bc
+print_bc_:
+ ld a,b
+ call print_hex
+ ld a,c
+ pop bc
+ jr print_a_
+
+print_de:
+ push af
+ push bc
+ ld b,d
+ ld c,e
+ jr print_bc_
+
+print_hl:
+ push af
+ push bc
+ ld b,h
+ ld c,l
+ jr print_bc_
+
+
+; Prints A as two hex chars and updates checksum
+; Preserved: BC, DE, HL
+print_hex:
+ call update_crc
+print_hex_nocrc:
+ push af
+ swap a
+ call +
+ pop af
+
++ and $0F
+ cp 10
+ jr c,+
+ add 7
++ add '0'
+ jp print_char_nocrc
+
+
+; Prints char_nz if Z flag is clear,
+; char_z if Z flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nz ARGS char_nz, char_z
+ push af
+ ld a,char_nz
+ jr nz,print_nz\@
+ ld a,char_z
+print_nz\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints char_nc if C flag is clear,
+; char_c if C flag is set.
+; Preserved: AF, BC, DE, HL
+.macro print_nc ARGS char_nc, char_c
+ push af
+ ld a,char_nc
+ jr nz,print_nc\@
+ ld a,char_c
+print_nc\@:
+ call print_char
+ pop af
+.endm
+
+
+; Prints A as 2 decimal digits
+; Preserved: AF, BC, DE, HL
+print_dec2:
+ push af
+ push bc
+ jr +
+
+
+; Prints A as 1-3 digit decimal value
+; Preserved: AF, BC, DE, HL
+print_dec:
+ push af
+ push bc
+
+ cp 10
+ jr c,++
+ ld c,100
+ cp c
+ call nc,@digit
++ ld c,10
+ call @digit
+++ add '0'
+ call print_char
+
+ pop bc
+ pop af
+ ret
+
+@digit:
+ ld b,'0'-1
+- inc b
+ sub c
+ jr nc,-
+ add c
+
+ ld c,a
+ ld a,b
+ call print_char
+ ld a,c
+ ret
diff --git a/playing-coffee/roms/oam_bug/source/common/ppu.s b/playing-coffee/roms/oam_bug/source/common/ppu.s
new file mode 100644
index 0000000..e1d2a6a
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/common/ppu.s
@@ -0,0 +1,14 @@
+; Waits for LCD blanking period
+; Preserved: BC, DE, HL
+wait_vbl:
+ ; Return if off
+ lda LCDC
+ rla
+ ret nc
+
+ ; Wait for start of vblank
+- lda LY
+ cp 144
+ jr nz,-
+
+ ret
diff --git a/playing-coffee/roms/oam_bug/source/common/printing.s b/playing-coffee/roms/oam_bug/source/common/printing.s
new file mode 100644
index 0000000..bb9389b
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/common/printing.s
@@ -0,0 +1,77 @@
+; Main printing routine that checksums and
+; prints to output device
+
+; Character that does equivalent of print_newline
+.define newline 10
+
+; Prints char without updating checksum
+; Preserved: BC, DE, HL
+;print_char_nocrc (defined by user)
+
+
+; Prints character and updates checksum UNLESS
+; it's a newline.
+; Preserved: AF, BC, DE, HL
+print_char:
+ push af
+ cp newline
+ call nz,update_crc
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints space. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_space:
+ push af
+ ld a,' '
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Advances to next line. Does NOT update checksum.
+; Preserved: AF, BC, DE, HL
+print_newline:
+ push af
+ ld a,newline
+ call print_char_nocrc
+ pop af
+ ret
+
+
+; Prints immediate string
+; Preserved: AF, BC, DE, HL
+.macro print_str ; string,string2
+ push hl
+ call print_str_
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .byte 0
+ pop hl
+.endm
+
+print_str_:
+ pop hl
+ call print_str_hl
+ jp hl
+
+
+; Prints zero-terminated string pointed to by HL.
+; On return, HL points to byte AFTER zero terminator.
+; Preserved: AF, BC, DE
+print_str_hl:
+ push af
+ jr +
+- call print_char
++ ldi a,(hl)
+ or a
+ jr nz,-
+ pop af
+ ret
diff --git a/playing-coffee/roms/oam_bug/source/common/shell.s b/playing-coffee/roms/oam_bug/source/common/shell.s
new file mode 100644
index 0000000..3e588c7
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/common/shell.s
@@ -0,0 +1,261 @@
+; Common routines and runtime
+
+.define RUNTIME_INCLUDED 1
+
+; A few bytes of RAM that aren't cleared
+.define nv_ram_base $D800
+.define nv_ram nv_ram_base
+
+; Address of next normal variable
+.define bss_base nv_ram_base+$80
+.define bss bss_base
+
+; Address of next direct-page ($FFxx) variable
+.define dp_base $FF80
+.define dp dp_base
+
+; Top of stack
+.define std_stack $DFFF+1
+
+; Final exit result byte is written here
+.define final_result $A000
+
+; Text output is written here as zero-terminated string
+.define text_out_base $A004
+
+; DMG/CGB hardware identifier
+.define gb_id_cgb $10 ; mask for testing CGB bit
+.define gb_id_devcart $04 ; mask for testing "on devcart" bit
+.define gb_id nv_ram
+.redefine nv_ram nv_ram+1
+
+; Copies C*$100 bytes from HL to $C000, then jumps to it.
+; A is preserved for jumped-to code.
+copy_to_wram_then_run:
+ ld b,a
+
+ ld de,$C000
+- ld a,(hl+)
+ ld (de),a
+ inc e
+ jr nz,-
+ inc d
+ dec c
+ jr nz,-
+
+ ld a,b
+ jp $C000
+
+
+.ifndef RST_OFFSET
+ .define RST_OFFSET 0
+.endif
+
+.ifndef CUSTOM_RESET
+ reset:
+ di
+
+ ; Run code from $C000, as is done on devcart. This
+ ; ensures minimal difference in how it behaves.
+ ld hl,$4000
+ ld c,$14
+ jp copy_to_wram_then_run
+
+ .bank 1 slot 1
+ .org 0
+ jp std_reset
+.endif
+
+; returnOrg puts this code AFTER user code.
+.section "runtime" returnOrg
+
+ ; Catch user code running off end
+ jp internal_error
+
+; Common routines
+.include "gb.inc"
+.include "macros.inc"
+.include "delay.s"
+.include "crc.s"
+.include "printing.s"
+.include "numbers.s"
+.include "testing.s"
+
+; Sets up hardware and runs main
+std_reset:
+
+ ; Init hardware
+ di
+ ld sp,std_stack
+
+ ; Save DMG/CGB id
+ ld (gb_id),a
+
+ ; Clear memory except very top of stack
+ ld bc,std_stack-bss_base - 2
+ ld hl,bss_base
+ call clear_mem
+ ld bc,$FFFF-dp_base
+ ld hl,dp_base
+ call clear_mem
+
+ ; Init hardware
+ wreg TAC,$00
+ wreg IF,$00
+ wreg IE,$00
+
+ wreg NR52,0 ; sound off
+ wreg NR52,$80 ; sound on
+ wreg NR51,$FF ; mono
+ wreg NR50,$77 ; volume
+
+ call init_runtime
+ call init_text_out
+ call console_init
+ call init_testing
+
+ .ifdef TEST_NAME
+ print_str TEST_NAME,newline,newline
+ .endif
+
+ call reset_crc ; in case init_runtime prints anything
+
+ delay_msec 250
+
+ ; Run user code
+ call main
+
+ ; Default is to successful exit
+ ld a,0
+ jp exit
+
+
+; Exits code and reports value of A
+exit:
+ ld sp,std_stack
+ push af
+ call +
+ call console_show
+ pop af
+ call play_byte
+ jp post_exit
+
++ push af
+ call print_newline
+ pop af
+
+ ; Report exit status
+ cp 1
+
+ ; 0: ""
+ ret c
+
+ ; 1: "Failed"
+ jr nz,+
+ print_str "Failed",newline
+ ret
+
+ ; n: "Failed #n"
++ print_str "Failed #"
+ call print_dec
+ call print_newline
+ ret
+
+
+; Clears BC bytes starting at HL
+clear_mem:
+ ; If C>0, increment B
+ dec bc
+ inc c
+ inc b
+
+ ld a,0
+- ld (hl+),a
+ dec c
+ jr nz,-
+ dec b
+ jr nz,-
+ ret
+
+
+; Reports internal error and exits with code 255
+internal_error:
+ print_str "Internal error"
+ ld a,255
+ jp exit
+
+
+; build_devcart and build_multi customize this
+.ifndef CUSTOM_PRINT
+ .define text_out_addr bss+0
+ .redefine bss bss+2
+
+ ; Initializes text output to cartridge RAM
+ init_text_out:
+ ; Enable cartridge RAM and set text output pointer
+ setb RAMEN,$0A
+ setw text_out_addr,text_out_base
+ setb text_out_base-3,$DE
+ setb text_out_base-2,$B0
+ setb text_out_base-1,$61
+ setb text_out_base,0
+ setb final_result,$80
+ ret
+
+
+ ; Appends character to text output string
+ ; Preserved: AF, BC, DE, HL
+ write_text_out:
+ push hl
+ push af
+ ld a,(text_out_addr)
+ ld l,a
+ ld a,(text_out_addr+1)
+ ld h,a
+ inc hl
+ ld (hl),0
+ ld a,l
+ ld (text_out_addr),a
+ ld a,h
+ ld (text_out_addr+1),a
+ dec hl
+ pop af
+ ld (hl),a
+ pop hl
+ ret
+
+ print_char_nocrc:
+ call write_text_out
+ jp console_print
+.endif
+
+
+; only build_rom uses console
+.ifdef NEED_CONSOLE
+ .include "console.s"
+.else
+ console_init:
+ console_print:
+ console_flush:
+ console_normal:
+ console_inverse:
+ console_show:
+ console_set_mode:
+ ret
+.endif
+
+
+; build_devcart and build_multi need to customize this
+.ifndef CUSTOM_EXIT
+ post_exit:
+ ld (final_result),a
+ forever:
+ wreg NR52,0 ; sound off
+- jr -
+.endif
+
+
+.macro def_rst ARGS addr
+ .bank 0 slot 0
+ .org addr+RST_OFFSET
+.endm
diff --git a/playing-coffee/roms/oam_bug/source/common/testing.s b/playing-coffee/roms/oam_bug/source/common/testing.s
new file mode 100644
index 0000000..5dd63ce
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/common/testing.s
@@ -0,0 +1,195 @@
+; Diagnostic and testing utilities
+
+.define test_code bss+0
+.define test_name bss+1
+.redefine bss bss+3
+
+
+; Sets test code and optional error text.
+; Takes multiple strings due to wla's idiotic
+; default limit of 63 chars per string.
+; Preserved: AF, BC, DE, HL
+.macro set_test ; code[,text[,text2[,text3]]]
+ push hl
+ call set_test_
+ jr @set_test\@
+ .byte \1
+ .if NARGS > 1
+ .byte \2
+ .endif
+ .if NARGS > 2
+ .byte \3
+ .endif
+ .if NARGS > 3
+ .byte \4
+ .endif
+ .byte 0
+@set_test\@:
+ pop hl
+.endm
+
+set_test_:
+ pop hl
+ push hl
+ push af
+ inc hl
+ inc hl
+ ldi a,(hl)
+ ld (test_code),a
+ ld a,l
+ ld (test_name),a
+ ld a,h
+ ld (test_name+1),a
+ pop af
+ ret
+
+
+; Initializes testing module
+init_testing:
+ set_test $FF
+ call init_crc
+ ret
+
+
+; Reports "Passed", then exits with code 0
+tests_passed:
+ call print_newline
+ print_str "Passed"
+ ld a,0
+ jp exit
+
+
+; Reports "Done" if set_test has never been used,
+; "Passed" if set_test 0 was last used, or
+; failure if set_test n was last used.
+tests_done:
+ ld a,(test_code)
+ inc a
+ jr z,+
+ dec a
+ jr z,tests_passed
+ jr test_failed
++ print_str "Done"
+ ld a,0
+ jp exit
+
+
+; Reports current error text and exits with result code
+test_failed:
+ ld a,(test_name)
+ ld l,a
+ ld a,(test_name+1)
+ ld h,a
+ ld a,(hl)
+ or a
+ jr z,+
+ call print_newline
+ call print_str_hl
+ call print_newline
++
+ ld a,(test_code)
+ cp $FF ; if a = $FF then a = 1
+ jr nz,+
+ ld a,1
++ jp exit
+
+
+; Prints checksum as 8-character hex value
+; Preserved: AF, BC, DE, HL
+print_crc:
+ push af
+
+ ; Must read checksum entirely before printing,
+ ; since printing updates it.
+ lda checksum
+ cpl
+ push af
+
+ lda checksum+1
+ cpl
+ push af
+
+ lda checksum+2
+ cpl
+ push af
+
+ lda checksum+3
+ cpl
+
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_hex
+ pop af
+ call print_a
+
+ pop af
+ ret
+
+
+; If checksum doesn't match expected, reports failed test.
+; Passing 0 just prints checksum. Clears checksum afterwards.
+.macro check_crc ARGS crc
+ .if crc == 0
+ call print_newline
+ call print_crc
+ .else
+ ld bc,(crc >> 16) ~ $FFFF
+ ld de,(crc & $FFFF) ~ $FFFF
+ call check_crc_
+ .endif
+.endm
+
+; Checks CRC, differing based on DMG or CGB build
+.macro check_crc_dmg_cgb ARGS dmg, cgb
+ .ifdef REQUIRE_DMG
+ check_crc dmg
+ .else
+ .ifdef REQUIRE_CGB
+ check_crc cgb
+ .else
+ .printt "CGB or DMG must be specified"
+ .fail
+ .endif
+ .endif
+.endm
+
+check_crc_:
+ lda checksum+0
+ cp e
+ jr nz,+
+
+ lda checksum+1
+ cp d
+ jr nz,+
+
+ lda checksum+2
+ cp c
+ jr nz,+
+
+ lda checksum+3
+ cp b
+ jr nz,+
+
+ jp reset_crc
+
++ call print_crc
+ jp test_failed
+
+
+; Updates checksum with bytes from addr to addr+size-1
+.macro checksum_mem ARGS addr,size
+ ld hl,addr
+ ld bc,size
+ call checksum_mem_
+.endm
+
+checksum_mem_:
+- ldi a,(hl)
+ call update_crc
+ dec bc
+ ld a,b
+ or c
+ jr nz,-
+ ret
diff --git a/playing-coffee/roms/oam_bug/source/linkfile b/playing-coffee/roms/oam_bug/source/linkfile
new file mode 100644
index 0000000..02a5a2e
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/linkfile
@@ -0,0 +1,2 @@
+[objects]
+test.o
diff --git a/playing-coffee/roms/oam_bug/source/readme.txt b/playing-coffee/roms/oam_bug/source/readme.txt
new file mode 100644
index 0000000..a0269c1
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/readme.txt
@@ -0,0 +1,82 @@
+Game Boy Tests Source Code
+--------------------------
+
+Building with wla-dx
+--------------------
+To assemble a test ROM with wla-dx, use the following commands:
+
+ wla -o source_filename_here.s test.o
+ wlalink linkfile test.gb
+
+To assemble as a GBS music file:
+
+ wla -o source_filename_here.s test.o -DBUILD_GBS
+ wlalink linkfile test.gbs
+
+Note that some tests might only work when built as a ROM or GBS file,
+but not both.
+
+Some tests might include a ROM/GBS that has all the tests combined.
+Building such a multi-test is complex and the necessary files aren't
+included.
+
+
+Framework
+---------
+Each test is in a single source file, and makes use of several library
+source files from common/. This framework provides common services and
+reduces code to only that which performs the actual test. Virtually all
+tests include "shell.inc" at the beginning, which sets things up and
+includes all the appropriate library files.
+
+The reset handler does minimal GB hardware initialization, clears RAM,
+sets up the text console, then runs main. Main can exit by returning or
+jumping to "exit" with an error code in A. Exit reports the code then
+goes into an infinite loop. If the code is 0, it doesn't do anything,
+otherwise it reports the code. Code 1 is reported as "Failed", and the
+rest as "Error ".
+
+The default is to build a ROM. Defining BUILD_GBS will build as an GBS.
+The other build types aren't supported due to their complexity. I load
+the code into RAM at $C000 since my devcart requires it, and I don't
+want the normal ROM to differ in any way from what I've tested. This
+also allows easy self-modifying code.
+
+Several routines are available to print values and text to the console.
+Most update a running CRC-32 checksum which can be checked with
+check_crc, allowing ALL the output to be checked very easily. If the
+checksum doesn't match, it is printed, so you can run the code on a GB
+and paste the correct checksum into your code.
+
+
+Macros
+------
+Some macros are used to make common operations more convenient. The left
+is equivalent to the right:
+
+ Macro Equivalent
+ -------------------------------------
+ lda addr ldh a,(addr-$FF00)
+
+ sta addr ldh (addr-$FF00),a
+
+ wreg addr,data ld a,data
+ ldh (addr-$FF00),a
+
+ setb ld a,data
+ ld (addr),a
+
+ setw setb addr+0,data
+
+ for_loop routine,begin,end,step
+ calls routine with A set to successive values
+
+ loop_n_times routine,count
+ calls routine with A from 0 to count-1
+
+ print_str "str" prints string
+
+
+--
+Shay Green
diff --git a/playing-coffee/roms/oam_bug/source/shell.inc b/playing-coffee/roms/oam_bug/source/shell.inc
new file mode 100644
index 0000000..0d43faa
--- /dev/null
+++ b/playing-coffee/roms/oam_bug/source/shell.inc
@@ -0,0 +1,27 @@
+; Included at beginning of all programs
+; that use standard shell
+
+; Get include files from common/
+.incdir "common"
+
+; Sub-test in a multi-test ROM
+.ifdef BUILD_MULTI
+ .include "build_multi.s"
+.else
+
+; GBS music file
+.ifdef BUILD_GBS
+ .include "build_gbs.s"
+.endif
+
+; Devcart
+.ifdef BUILD_DEVCART
+ .include "build_devcart.s"
+.endif
+
+; GB ROM (default)
+.ifndef RUNTIME_INCLUDED
+ .include "build_rom.s"
+.endif
+
+.endif ; .ifdef BUILD_MULTI
diff --git a/playing-coffee/roms/readme.txt b/playing-coffee/roms/readme.txt
new file mode 100644
index 0000000..2c40e27
--- /dev/null
+++ b/playing-coffee/roms/readme.txt
@@ -0,0 +1,4 @@
+Blargg's Gameboy hardware test ROMs. Originally hosted at http://blargg.parodius.com/gb-tests/
+before parodious.com went down.
+New official location: http://blargg.8bitalley.com/parodius/gb-tests/
+
diff --git a/playing-coffee/roms/solitaire.gb b/playing-coffee/roms/solitaire.gb
new file mode 100644
index 0000000..8c5b8c6
Binary files /dev/null and b/playing-coffee/roms/solitaire.gb differ
diff --git a/playing-coffee/roms/supermarioland.gb b/playing-coffee/roms/supermarioland.gb
new file mode 100644
index 0000000..e5d7125
Binary files /dev/null and b/playing-coffee/roms/supermarioland.gb differ
diff --git a/playing-coffee/roms/tetris.gb b/playing-coffee/roms/tetris.gb
new file mode 100644
index 0000000..fbcef42
Binary files /dev/null and b/playing-coffee/roms/tetris.gb differ
diff --git a/playing-coffee/src/main/java/playingcoffee/application/Application.java b/playing-coffee/src/main/java/playingcoffee/application/Application.java
new file mode 100644
index 0000000..37b7ab4
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/application/Application.java
@@ -0,0 +1,111 @@
+package playingcoffee.application;
+
+import java.awt.Canvas;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.image.BufferStrategy;
+
+import javax.swing.JFrame;
+
+public class Application extends Canvas implements Runnable {
+
+ private static final long serialVersionUID = 1L;
+
+ private boolean running;
+ private Thread thread;
+
+ private GameBoy gameBoy;
+
+ public Application() {
+ setPreferredSize(new Dimension(320, 288));
+
+ gameBoy = new GameBoy();
+ gameBoy.start();
+ }
+
+ public void renderGameBoy() {
+ BufferStrategy bs = getBufferStrategy();
+ if (bs == null) {
+ createBufferStrategy(2);
+ return;
+ }
+
+ Graphics g = bs.getDrawGraphics();
+
+ g.setColor(Color.WHITE);
+ g.fillRect(0, 0, getWidth(), getHeight());
+
+ int lcdControl = (gameBoy.getMMU().read(0xFF40));
+
+ int tileDataOffset = ((lcdControl & 0x10) != 0) ? 0x8000 : 0x8800;
+
+ for (int tile = 0; tile < 512; tile++) {
+ int tileIndex = tile;
+ for (int y = 0; y < 8; y++) {
+ for (int n = 0; n < 8; n++) {
+ int value = (((gameBoy.getMMU().read(0x8000 + y * 2 + tileIndex * 16 + 1) >> (7 - n)) & 1) << 1) |
+ (((gameBoy.getMMU().read(0x8000 + y * 2 + tileIndex * 16) >> (7 - n)) & 1));
+
+ g.setColor(new Color(value * 64, value * 64, value * 64));
+ if (value != 0x0)
+ g.fillRect((n + (tile % 16) * 8), (y + (tile / 16) * 8), 1, 1);
+ }
+ }
+ }
+
+ if ((lcdControl & 0x80) != 0) {
+ for (int tileY = 0; tileY < 32; tileY++) {
+ for (int tileX = 0; tileX < 32; tileX++) {
+ int tileIndex = gameBoy.getMMU().read(0x9800 + (tileX + tileY * 32));
+ for (int y = 0; y < 8; y++) {
+ for (int n = 0; n < 8; n++) {
+ int value = (((gameBoy.getMMU().read(tileDataOffset + y * 2 + tileIndex * 16 + 1) >> (7 - n)) & 1) << 1) |
+ (((gameBoy.getMMU().read(tileDataOffset + y * 2 + tileIndex * 16) >> (7 - n)) & 1));
+
+ g.setColor(new Color(value * 64, value * 64, value * 64));
+ if (value != 0x0)
+ g.fillRect((n + (tileX) * 8 - gameBoy.getPPU().getRegisters().scrollX) * 2, (y + (tileY) * 8 - gameBoy.getPPU().getRegisters().scrollY) * 2, 2, 2);
+ }
+ }
+ }
+ }
+ }
+
+ g.dispose();
+
+ bs.show();
+ }
+
+ @Override
+ public void run() {
+ while (running) {
+ renderGameBoy();
+ }
+
+ gameBoy.stop();
+ }
+
+ public void start() {
+ if (running) return;
+
+ running = true;
+
+ thread = new Thread(this);
+ thread.start();
+ }
+
+ public static void main(String[] args) {
+ Application application = new Application();
+
+ JFrame frame = new JFrame("playing-coffee");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.setResizable(false);
+ frame.add(application);
+ frame.pack();
+ frame.setLocationRelativeTo(null);
+ frame.setVisible(true);
+
+ application.start();
+ }
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/application/GameBoy.java b/playing-coffee/src/main/java/playingcoffee/application/GameBoy.java
new file mode 100644
index 0000000..25480f7
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/application/GameBoy.java
@@ -0,0 +1,85 @@
+package playingcoffee.application;
+
+import playingcoffee.core.Cartridge;
+import playingcoffee.core.InterruptManager;
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.CPU;
+import playingcoffee.log.Log;
+import playingcoffee.ppu.PPU;
+
+public class GameBoy implements Runnable {
+
+ private Thread thread;
+ private boolean running = false;
+
+ private InterruptManager interruptManager;
+ private PPU ppu;
+ private MMU mmu;
+ private CPU cpu;
+
+ public GameBoy() {
+ interruptManager = new InterruptManager();
+ mmu = new MMU();
+
+ ppu = new PPU(mmu, interruptManager);
+ cpu = new CPU(mmu, interruptManager);
+
+ mmu.connectMemorySpace(interruptManager);
+ mmu.connectMemorySpace(new Cartridge("roms/kwirk.gb"));
+ }
+
+ public void start() {
+ if (running)
+ return;
+
+ running = true;
+ thread = new Thread(this);
+ thread.start();
+ }
+
+ public void stop() {
+ if (!running)
+ return;
+
+ running = false;
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void run() {
+ Log.init();
+
+ while (running) {
+ for (int i = 0; i < 8192; i++) {
+ ppu.clock();
+ cpu.clock();
+ interruptManager.clock();
+ }
+
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ Log.close();
+ }
+
+ public PPU getPPU() {
+ return ppu;
+ }
+
+ public MMU getMMU() {
+ return mmu;
+ }
+
+ public CPU getCPU() {
+ return cpu;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/Cartridge.java b/playing-coffee/src/main/java/playingcoffee/core/Cartridge.java
new file mode 100644
index 0000000..21c9dea
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/Cartridge.java
@@ -0,0 +1,44 @@
+package playingcoffee.core;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import playingcoffee.log.Log;
+
+public class Cartridge implements MemorySpace {
+
+ private int[] rom;
+
+ public Cartridge(String rom) {
+ byte[] bin = null;
+
+ try {
+ bin = Files.readAllBytes(Paths.get(rom));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ this.rom = new int[bin.length];
+
+ for (int i = 0; i < bin.length; i++) {
+ this.rom[i] = Byte.toUnsignedInt(bin[i]);
+ }
+ }
+
+ @Override
+ public int read(int address) {
+ return rom[address];
+ }
+
+ @Override
+ public void write(int value, int address) {
+ Log.warn("Attempting to write to ROM at address: 0x%4x.", address);
+ }
+
+ @Override
+ public boolean inMemorySpace(int address) {
+ return address >= 0x0000 && address <= 0x7FFF;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/InterruptListener.java b/playing-coffee/src/main/java/playingcoffee/core/InterruptListener.java
new file mode 100644
index 0000000..088c107
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/InterruptListener.java
@@ -0,0 +1,7 @@
+package playingcoffee.core;
+
+public interface InterruptListener {
+
+ public void interruptOccured(int type);
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/InterruptManager.java b/playing-coffee/src/main/java/playingcoffee/core/InterruptManager.java
new file mode 100644
index 0000000..5bd5d61
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/InterruptManager.java
@@ -0,0 +1,76 @@
+package playingcoffee.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class InterruptManager implements MemorySpace {
+
+ private boolean enabled;
+
+ private int interruptEnable;
+ private int interruptFlag;
+
+ private List listeners;
+
+ public static final int VBLANK = 1 << 0;
+ public static final int LCD_STAT = 1 << 1;
+ public static final int TIMER = 1 << 2;
+ public static final int SERIAL = 1 << 3;
+ public static final int JOYPAD = 1 << 4;
+
+ public InterruptManager() {
+ listeners = new ArrayList();
+ }
+
+ public void addListener(InterruptListener listener) {
+ listeners.add(listener);
+ }
+
+ public void enable() { enabled = true; }
+ public void disable() { enabled = false; }
+
+ public boolean isEnabled() { return enabled; }
+
+ public void requestInterrupt(int type) {
+ interruptFlag |= type;
+ }
+
+ public void clock() {
+ if (enabled) {
+ int toOccur = interruptFlag & interruptEnable;
+
+ //System.out.println(toOccur);
+
+ if (toOccur != 0) {
+ for (int i = 0; i < 8; i++) {
+ if ((toOccur & (1 << i)) != 0) {
+ for (InterruptListener listener : listeners) {
+ listener.interruptOccured(1 << i);
+ }
+ interruptFlag &= ~(1 << i);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public int read(int address) {
+ if (address == 0xFFFF) return interruptEnable;
+ if (address == 0xFF0F) return interruptFlag;
+
+ return 0;
+ }
+
+ @Override
+ public void write(int value, int address) {
+ if (address == 0xFFFF) interruptEnable = value;
+ if (address == 0xFF0F) interruptFlag = value;
+ }
+
+ @Override
+ public boolean inMemorySpace(int address) {
+ return address == 0xFFFF || address == 0xFF0F;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/MMU.java b/playing-coffee/src/main/java/playingcoffee/core/MMU.java
new file mode 100644
index 0000000..fecec2e
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/MMU.java
@@ -0,0 +1,93 @@
+package playingcoffee.core;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MMU {
+
+ private int[] memory;
+ private int[] bootRom;
+
+ private List memorySpaces;
+
+ public MMU() {
+ memory = new int[0x10000];
+
+ memorySpaces = new ArrayList();
+
+ loadBootROM();
+ }
+
+ public void connectMemorySpace(MemorySpace memorySpace) {
+ memorySpaces.add(memorySpace);
+ }
+
+ // TODO: Move the ROM into a separate file.
+
+ public void loadROM(String rom) {
+ byte[] bin = null;
+
+ try {
+ bin = Files.readAllBytes(Paths.get(rom));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ for (int i = 0; i < bin.length; i++) {
+ memory[i] = Byte.toUnsignedInt(bin[i]);
+ }
+ }
+
+ // TODO: Move the boot ROM to a separate file.
+ private void loadBootROM() {
+ bootRom = new int[0x100];
+
+ byte[] bin = null;
+
+ try {
+ bin = Files.readAllBytes(Paths.get("dmg_boot.bin"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ for (int i = 0; i < 0x100; i++) {
+ bootRom[i] = Byte.toUnsignedInt(bin[i]);
+ }
+ }
+
+ public int read(int address) {
+ if (address == 0xFF00) return 0xF; // TODO: Remove
+
+ if (address >= 0x00 && address <= 0xFF && read(0xFF50) == 0)
+ return bootRom[address];
+
+ for (MemorySpace memorySpace : memorySpaces) {
+ if (memorySpace.inMemorySpace(address)) {
+ return memorySpace.read(address);
+ }
+ }
+
+ return memory[address];
+ }
+
+ public void write(int value, int address) {
+ if (address == 0xFF00) return; // TODO: Remove
+
+ if (address >= 0x00 && address <= 0xFF && read(0xFF50) == 0)
+ bootRom[address] = value & 0xFF;
+
+ for (MemorySpace memorySpace : memorySpaces) {
+ if (memorySpace.inMemorySpace(address)) {
+ memorySpace.write(value & 0xFF, address);
+ }
+ }
+ memory[address] = value & 0xFF;
+ }
+
+ public int[] getMemory() {
+ return memory;
+ }
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/MemorySpace.java b/playing-coffee/src/main/java/playingcoffee/core/MemorySpace.java
new file mode 100644
index 0000000..c2ce195
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/MemorySpace.java
@@ -0,0 +1,9 @@
+package playingcoffee.core;
+
+public interface MemorySpace {
+
+ public int read(int address);
+ public void write(int value, int address);
+ public boolean inMemorySpace(int address);
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/cpu/CPU.java b/playing-coffee/src/main/java/playingcoffee/core/cpu/CPU.java
new file mode 100644
index 0000000..2c25bf1
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/cpu/CPU.java
@@ -0,0 +1,343 @@
+package playingcoffee.core.cpu;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import playingcoffee.core.InterruptListener;
+import playingcoffee.core.InterruptManager;
+import playingcoffee.core.MMU;
+import playingcoffee.core.opcode.ALU16Opcode;
+import playingcoffee.core.opcode.ALU16Opcode.ALU16Type;
+import playingcoffee.core.opcode.ALUOpcode;
+import playingcoffee.core.opcode.ALUOpcode.ALUType;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.CallOpcode;
+import playingcoffee.core.opcode.ComplementOpcode;
+import playingcoffee.core.opcode.FlipCarryOpcode;
+import playingcoffee.core.opcode.InterruptOpcode;
+import playingcoffee.core.opcode.JumpOpcode;
+import playingcoffee.core.opcode.JumpRelativeOpcode;
+import playingcoffee.core.opcode.LoadOpcode;
+import playingcoffee.core.opcode.Opcode;
+import playingcoffee.core.opcode.PopOpcode;
+import playingcoffee.core.opcode.PushOpcode;
+import playingcoffee.core.opcode.RestartOpcode;
+import playingcoffee.core.opcode.ReturnOpcode;
+import playingcoffee.core.opcode.RotateAOpcode;
+import playingcoffee.core.opcode.SetCarryOpcode;
+import playingcoffee.core.opcode.prefixed.BitOpcode;
+import playingcoffee.core.opcode.prefixed.ResetBitOpcode;
+import playingcoffee.core.opcode.prefixed.RotateOpcode;
+import playingcoffee.core.opcode.prefixed.SetBitOpcode;
+import playingcoffee.core.opcode.prefixed.ShiftOpcode;
+import playingcoffee.core.opcode.prefixed.ShiftOpcode.ShiftType;
+import playingcoffee.core.opcode.prefixed.SwapOpcode;
+import playingcoffee.log.Log;
+
+public class CPU implements InterruptListener {
+
+ private final MMU mmu;
+ private final InterruptManager interruptManager;
+
+ private Registers registers;
+
+ private int cycles;
+
+ Opcode[] opcodes;
+ Opcode[] prefixedOpcodes;
+
+ public CPU(final MMU mmu, final InterruptManager interruptManager) {
+ this.mmu = mmu;
+ this.interruptManager = interruptManager;
+
+ registers = new Registers();
+
+ opcodes = new Opcode[0x100];
+ prefixedOpcodes = new Opcode[0x100];
+
+ loadOpcodes();
+ loadPrefixedOpcodes();
+
+ interruptManager.addListener(this);
+ }
+
+ private void loadOpcodes() {
+ // NOP
+ opcodes[0x00] = new Opcode() {
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "NOP";
+ }
+ };
+
+ // Load Opcodes
+ for (Entry val : indexedList(0x01, 0x10, Argument.BC, Argument.DE, Argument.HL, Argument.SP)) {
+ opcodes[val.getKey()] = new LoadOpcode(Argument.D16, val.getValue());
+ }
+
+ for (Entry val : indexedList(0x02, 0x10, Argument._BC, Argument._DE, Argument._HL_INC, Argument._HL_DEC)) {
+ opcodes[val.getKey()] = new LoadOpcode(Argument.A, val.getValue());
+ }
+ for (Entry val : indexedList(0x06, 0x10, Argument.B, Argument.D, Argument.H, Argument._HL)) {
+ opcodes[val.getKey()] = new LoadOpcode(Argument.D8, val.getValue());
+ }
+ for (Entry val : indexedList(0x0A, 0x10, Argument._BC, Argument._DE, Argument._HL_INC, Argument._HL_DEC)) {
+ opcodes[val.getKey()] = new LoadOpcode(val.getValue(), Argument.A);
+ }
+ for (Entry val : indexedList(0x0E, 0x10, Argument.C, Argument.E, Argument.L, Argument.A)) {
+ opcodes[val.getKey()] = new LoadOpcode(Argument.D8, val.getValue());
+ }
+
+ for (Entry val : indexedList(0x40, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ for (Entry row : indexedList(val.getKey(), 0x10, Argument.B, Argument.D, Argument.H, Argument._HL)) {
+ opcodes[row.getKey()] = new LoadOpcode(val.getValue(), row.getValue());
+ }
+ }
+ for (Entry val : indexedList(0x48, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ for (Entry row : indexedList(val.getKey(), 0x10, Argument.C, Argument.E, Argument.L, Argument.A)) {
+ opcodes[row.getKey()] = new LoadOpcode(val.getValue(), row.getValue());
+ }
+ }
+
+ opcodes[0x08] = new LoadOpcode(Argument.SP, Argument._D16_SHORT);
+
+ opcodes[0xE0] = new LoadOpcode(Argument.A, Argument._D8);
+ opcodes[0xF0] = new LoadOpcode(Argument._D8, Argument.A);
+
+ opcodes[0xE2] = new LoadOpcode(Argument.A, Argument._C);
+ opcodes[0xF2] = new LoadOpcode(Argument._C, Argument.A);
+
+ opcodes[0xF9] = new LoadOpcode(Argument.HL, Argument.SP);
+
+ opcodes[0xEA] = new LoadOpcode(Argument.A, Argument._D16);
+ opcodes[0xFA] = new LoadOpcode(Argument._D16, Argument.A);
+
+ // POP and PUSH
+ for (Entry val : indexedList(0xC1, 0x10, Argument.BC, Argument.DE, Argument.HL, Argument.AF)) {
+ opcodes[val.getKey()] = new PopOpcode(val.getValue());
+ }
+ for (Entry val : indexedList(0xC5, 0x10, Argument.BC, Argument.DE, Argument.HL, Argument.AF)) {
+ opcodes[val.getKey()] = new PushOpcode(val.getValue());
+ }
+
+ // TODO: Override 0x76 with HALT
+
+ // Jumps, Calls and Returns
+ opcodes[0x20] = new JumpRelativeOpcode(Flags.ZERO, Argument.I8, true);
+ opcodes[0x30] = new JumpRelativeOpcode(Flags.CARRY, Argument.I8, true);
+
+ opcodes[0x18] = new JumpRelativeOpcode(0, Argument.I8, false);
+ opcodes[0x28] = new JumpRelativeOpcode(Flags.ZERO, Argument.I8, false);
+ opcodes[0x38] = new JumpRelativeOpcode(Flags.CARRY, Argument.I8, false);
+
+ opcodes[0xC3] = new JumpOpcode(0, Argument.D16, false);
+ opcodes[0xC2] = new JumpOpcode(Flags.ZERO, Argument.D16, true);
+ opcodes[0xD2] = new JumpOpcode(Flags.CARRY, Argument.D16, true);
+
+ opcodes[0xE9] = new JumpOpcode(0, Argument.HL, false);
+
+ opcodes[0xCA] = new JumpOpcode(Flags.ZERO, Argument.D16, false);
+ opcodes[0xDA] = new JumpOpcode(Flags.CARRY, Argument.D16, false);
+
+ opcodes[0xC4] = new CallOpcode(Flags.ZERO, Argument.D16, true);
+ opcodes[0xD4] = new CallOpcode(Flags.CARRY, Argument.D16, true);
+
+ opcodes[0xCC] = new CallOpcode(Flags.ZERO, Argument.D16, false);
+ opcodes[0xDC] = new CallOpcode(Flags.CARRY, Argument.D16, false);
+
+ opcodes[0xCD] = new CallOpcode(0, Argument.D16, false);
+
+ opcodes[0xC0] = new ReturnOpcode(Flags.ZERO, true, false, interruptManager);
+ opcodes[0xD0] = new ReturnOpcode(Flags.CARRY, true, false, interruptManager);
+
+ opcodes[0xC8] = new ReturnOpcode(Flags.ZERO, false, false, interruptManager);
+ opcodes[0xD8] = new ReturnOpcode(Flags.CARRY, false, false, interruptManager);
+
+ opcodes[0xC9] = new ReturnOpcode(0, false, false, interruptManager);
+ opcodes[0xD9] = new ReturnOpcode(0, false, true, interruptManager);
+
+ // Bit Operations
+
+ opcodes[0x07] = new RotateAOpcode(true, RotateAOpcode.LEFT);
+ opcodes[0x17] = new RotateAOpcode(false, RotateAOpcode.LEFT);
+ opcodes[0x0F] = new RotateAOpcode(true, RotateAOpcode.RIGHT);
+ opcodes[0x1F] = new RotateAOpcode(false, RotateAOpcode.RIGHT);
+
+ // 16-bit ALU Opcodes
+ for (Entry val : indexedList(0x03, 0x10, Argument.BC, Argument.DE,Argument.HL, Argument.SP)) {
+ opcodes[val.getKey()] = new ALU16Opcode(ALU16Type.INC, val.getValue());
+ opcodes[val.getKey() + 8] = new ALU16Opcode(ALU16Type.DEC, val.getValue());
+ }
+ for (Entry val : indexedList(0x09, 0x10, Argument.BC, Argument.DE,Argument.HL, Argument.SP)) {
+ opcodes[val.getKey()] = new ALU16Opcode(ALU16Type.ADD, val.getValue());
+ }
+
+ // ALU Opcodes
+ for (Entry val : indexedList(0x04, 8, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ opcodes[val.getKey()] = new ALUOpcode(ALUType.INC, val.getValue());
+ opcodes[val.getKey() + 1] = new ALUOpcode(ALUType.DEC, val.getValue());
+ }
+
+ for (Entry val : indexedList(0x80, 8, ALUType.ADD, ALUType.ADC, ALUType.SUB, ALUType.SBC, ALUType.AND, ALUType.XOR, ALUType.OR, ALUType.CP)) {
+ for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ opcodes[row.getKey()] = new ALUOpcode(val.getValue(), row.getValue());
+ }
+ }
+ for (Entry val : indexedList(0xC6, 8, ALUType.ADD, ALUType.ADC, ALUType.SUB, ALUType.SBC, ALUType.AND, ALUType.XOR, ALUType.OR, ALUType.CP)) {
+ opcodes[val.getKey()] = new ALUOpcode(val.getValue(), Argument.D8);
+ }
+
+ // Restarts
+ for (Entry val : indexedList(0xC7, 8, 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38)) {
+ opcodes[val.getKey()] = new RestartOpcode(val.getValue());
+ }
+
+ // Interrupts
+ opcodes[0xF3] = new InterruptOpcode(false, interruptManager);
+ opcodes[0xFB] = new InterruptOpcode(true, interruptManager);
+
+ // Misc
+ opcodes[0x2F] = new ComplementOpcode();
+ opcodes[0x37] = new SetCarryOpcode();
+ opcodes[0x3F] = new FlipCarryOpcode();
+ }
+
+ public void loadPrefixedOpcodes() {
+ for (Entry val : indexedList(0x00, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[val.getKey() + 0x00] = new RotateOpcode(true, val.getValue(), RotateOpcode.LEFT);
+ prefixedOpcodes[val.getKey() + 0x08] = new RotateOpcode(true, val.getValue(), RotateOpcode.RIGHT);
+ prefixedOpcodes[val.getKey() + 0x10] = new RotateOpcode(false, val.getValue(), RotateOpcode.LEFT);
+ prefixedOpcodes[val.getKey() + 0x18] = new RotateOpcode(false, val.getValue(), RotateOpcode.RIGHT);
+ }
+
+ for (Entry val : indexedList(0x40, 8, 0, 1, 2, 3, 4, 5, 6, 7)) {
+ for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[row.getKey()] = new BitOpcode(val.getValue(), row.getValue());
+ }
+ }
+
+ for (Entry val : indexedList(0x30, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[val.getKey()] = new SwapOpcode(val.getValue());
+ }
+
+ // Reset Bit
+ for (Entry val : indexedList(0x80, 8, 0, 1, 2, 3, 4, 5, 6, 7)) {
+ for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[row.getKey()] = new ResetBitOpcode(val.getValue(), row.getValue());
+ }
+ }
+
+ // Set Bit
+ for (Entry val : indexedList(0xC0, 8, 0, 1, 2, 3, 4, 5, 6, 7)) {
+ for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[row.getKey()] = new SetBitOpcode(val.getValue(), row.getValue());
+ }
+ }
+
+ for (Entry val : indexedList(0x20, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[val.getKey()] = new ShiftOpcode(ShiftType.SLA, val.getValue());
+ }
+ for (Entry val : indexedList(0x28, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[val.getKey()] = new ShiftOpcode(ShiftType.SRA, val.getValue());
+ }
+ for (Entry val : indexedList(0x38, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) {
+ prefixedOpcodes[val.getKey()] = new ShiftOpcode(ShiftType.SRL, val.getValue());
+ }
+ }
+
+ @SafeVarargs
+ private static Iterable> indexedList(int start, int step, T... values) {
+ Map map = new LinkedHashMap<>();
+ int i = start;
+ for (T e : values) {
+ map.put(i, e);
+ i += step;
+ }
+ return map.entrySet();
+ }
+
+ public void clock() {
+ if (cycles == 0) {
+ //Log.info("PC: 0x%4x", registers.getPC());
+
+ if (registers.getPC() == 0x2EFC) {
+ Log.info("Breakpoint.");
+ }
+
+ int opcodeValue = mmu.read(registers.getPC());
+ registers.incPC();
+
+ if (opcodeValue == 0xCB) {
+ int prefixedValue = mmu.read(registers.getPC());
+ registers.incPC();
+
+ runPrefixedOpcode(prefixedValue);
+
+ } else {
+ runOpcode(opcodeValue);
+ }
+ }
+
+ cycles--;
+ }
+
+ private void runOpcode(int opcodeValue) {
+ Opcode opcode = opcodes[opcodeValue];
+
+ if (opcode == null) {
+ Log.error("Unimplemented opcode 0x%2x at 0x%4x!", opcodeValue, registers.getPC());
+
+ Log.close();
+
+ throw new IllegalStateException();
+
+ //System.exit(0);
+ }
+
+ //Log.info("Executing opcode: 0x%2x (%s)", opcodeValue, opcode.toString());
+ cycles += opcode.run(registers, mmu) + 4; // Adding 4 because we fetch the instruction.
+ }
+
+ private void runPrefixedOpcode(int opcodeValue) {
+ Opcode opcode = prefixedOpcodes[opcodeValue];
+
+ if (opcode == null) {
+ Log.error("Unimplemented prefixed opcode 0x%2x!", opcodeValue);
+
+ Log.close();
+
+ throw new IllegalStateException();
+
+ //System.exit(0);
+ }
+
+ //Log.info("Executing prefixed opcode: 0x%2x (%s)", opcodeValue, opcode.toString());
+ cycles += opcode.run(registers, mmu) + 8; // Adding 8 because we fetch the 0xCB prefix and the instruction.
+ }
+
+ @Override
+ public void interruptOccured(int types) {
+ /*if ((types & InterruptManager.VBLANK) != 0) {
+ interruptManager.disable();
+
+ registers.decSP();
+ mmu.write(registers.getPC() >> 8, registers.getSP());
+ registers.decSP();
+ mmu.write(registers.getPC(), registers.getSP());
+
+ Log.info("Pushing 0x%4x to the stack.", registers.getPC());
+
+ registers.setPC(0x40);
+
+ cycles += 12;
+ }*/
+ }
+
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/cpu/Flags.java b/playing-coffee/src/main/java/playingcoffee/core/cpu/Flags.java
new file mode 100644
index 0000000..c4e121a
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/cpu/Flags.java
@@ -0,0 +1,29 @@
+package playingcoffee.core.cpu;
+
+public class Flags {
+
+ private int f; // Technically the "correct" notation
+
+ public static final int ZERO = 1 << 7;
+ public static final int NEGATIVE = 1 << 6;
+ public static final int HALF_CARRY = 1 << 5;
+ public static final int CARRY = 1 << 4;
+
+ public void set(int value) {
+ f = value & 0xFF;
+ }
+
+ public int get() {
+ return f;
+ }
+
+ public void set(int flag, boolean value) {
+ if (value) f |= flag;
+ else f &= ~flag;
+ }
+
+ public boolean get(int flag) {
+ return (f & flag) != 0;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/cpu/Registers.java b/playing-coffee/src/main/java/playingcoffee/core/cpu/Registers.java
new file mode 100644
index 0000000..82c97f6
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/cpu/Registers.java
@@ -0,0 +1,57 @@
+package playingcoffee.core.cpu;
+
+public class Registers {
+
+ private int a, b, c, d, e, h, l;
+
+ private int pc, sp;
+
+ private Flags flags;
+
+ public Registers() {
+ flags = new Flags();
+ }
+
+ public int getA() { return a; }
+ public int getB() { return b; }
+ public int getC() { return c; }
+ public int getD() { return d; }
+ public int getE() { return e; }
+ public int getH() { return h; }
+ public int getL() { return l; }
+
+ public void setA(int value) { a = value & 0xFF; }
+ public void setB(int value) { b = value & 0xFF; }
+ public void setC(int value) { c = value & 0xFF; }
+ public void setD(int value) { d = value & 0xFF; }
+ public void setE(int value) { e = value & 0xFF; }
+ public void setH(int value) { h = value & 0xFF; }
+ public void setL(int value) { l = value & 0xFF; }
+
+ public Flags getFlags() { return flags; }
+
+ public int getAF() { return (a << 8) | flags.get(); }
+ public int getBC() { return (b << 8) | c; }
+ public int getDE() { return (d << 8) | e; }
+ public int getHL() { return (h << 8) | l; }
+
+ public int getPC() { return pc; }
+ public int getSP() { return sp; }
+
+ public void setAF(int value) { flags.set(value >> 8); setA(value); }
+ public void setBC(int value) { setB(value >> 8); setC(value); }
+ public void setDE(int value) { setD(value >> 8); setE(value); }
+ public void setHL(int value) { setH(value >> 8); setL(value); }
+
+ public void setPC(int value) { pc = value & 0xFFFF; }
+ public void setSP(int value) { sp = value & 0xFFFF; }
+
+ public void addPC(int value) { setPC(getPC() + value); };
+ public void addSP(int value) { setSP(getSP() + value); };
+
+ public void incPC() { addPC(1); }
+ public void incSP() { addSP(1); }
+
+ public void decPC() { addPC(-1); }
+ public void decSP() { addSP(-1); }
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java
new file mode 100644
index 0000000..b4fef41
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java
@@ -0,0 +1,57 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.log.Log;
+
+public class ALU16Opcode implements Opcode {
+
+ private final ALU16Type type;
+ private final Argument register;
+
+ public ALU16Opcode(ALU16Type type, Argument register) {
+ this.type = type;
+ this.register = register;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int result = 0;
+
+ switch (type) {
+ case INC:
+ register.write(register.read(registers, mmu) + 1, registers, mmu);
+ break;
+ case DEC:
+ register.write(register.read(registers, mmu) - 1, registers, mmu);
+ break;
+ case ADD:
+ int value = register.read(registers, mmu);
+ result = registers.getHL() + value;
+
+ registers.getFlags().set(Flags.NEGATIVE, false);
+ registers.getFlags().set(Flags.HALF_CARRY, ((value & 0xF) + (registers.getHL() & 0xF) > 0xF));
+ registers.getFlags().set(Flags.CARRY, (result & 0xFFFF0000) != 0);
+
+ register.write(result, registers, mmu);
+
+ break;
+ default:
+ Log.error("wtf!? how did we get here?!?!?");
+ break;
+ }
+
+ return register.getCycles() + 4;
+ }
+
+ public enum ALU16Type {
+ INC, DEC, ADD
+ }
+
+ @Override
+ public String toString() {
+ return type.name() + " " + register.getName();
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/ALUOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/ALUOpcode.java
new file mode 100644
index 0000000..8cfa92a
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/ALUOpcode.java
@@ -0,0 +1,134 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.log.Log;
+
+public class ALUOpcode implements Opcode {
+
+ private final ALUType type;
+ private final Argument register;
+
+ public ALUOpcode(ALUType type, Argument register) {
+ this.type = type;
+ this.register = register;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int result = 0;
+ int registerValue = register.read(registers, mmu);
+
+ switch (type) {
+ case ADD:
+ result = registers.getA() + registerValue;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE, false);
+ registers.getFlags().set(Flags.HALF_CARRY, (registers.getA() & 0xF) + (registerValue & 0xF) > 0xF);
+ registers.getFlags().set(Flags.CARRY, (result & 0xFF00) > 0);
+
+ break;
+ case ADC:
+ int value = registerValue + (registers.getFlags().get(Flags.CARRY) ? 1 : 0);
+
+ result = registers.getA() + value;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE, false);
+ registers.getFlags().set(Flags.HALF_CARRY, (registers.getA() & 0xF) + (value & 0xF) > 0xF);
+ registers.getFlags().set(Flags.CARRY, (result & 0xFF00) > 0);
+
+ break;
+ case AND:
+ result = registers.getA() & registerValue;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE, false);
+ registers.getFlags().set(Flags.HALF_CARRY, true);
+ registers.getFlags().set(Flags.CARRY, false);
+
+ break;
+ case CP:
+ result = registers.getA();
+
+ registers.getFlags().set(Flags.ZERO, registers.getA() == registerValue);
+ registers.getFlags().set(Flags.NEGATIVE, true);
+ registers.getFlags().set(Flags.HALF_CARRY, (registerValue & 0xF) > (registers.getA() & 0xF));
+ registers.getFlags().set(Flags.CARRY, registerValue > registers.getA());
+
+ break;
+ case OR:
+ result = registers.getA() | registerValue;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY | Flags.CARRY, false);
+
+ break;
+ case SBC:
+ value = registerValue + (registers.getFlags().get(Flags.CARRY) ? 1 : 0);
+
+ registers.getFlags().set(Flags.ZERO, value == registers.getA());
+ registers.getFlags().set(Flags.NEGATIVE, true);
+ registers.getFlags().set(Flags.HALF_CARRY, (value & 0xF) > (registers.getA() & 0xF));
+ registers.getFlags().set(Flags.CARRY, value > registers.getA());
+
+ result = registers.getA() - value;
+
+ break;
+ case SUB:
+ registers.getFlags().set(Flags.ZERO, registerValue == registers.getA());
+ registers.getFlags().set(Flags.NEGATIVE, true);
+ registers.getFlags().set(Flags.HALF_CARRY, registerValue > (registers.getA() & 0xF));
+ registers.getFlags().set(Flags.CARRY, registerValue > registers.getA());
+
+ result = registers.getA() - registerValue;
+
+ break;
+ case XOR:
+ result = registers.getA() ^ registerValue;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY | Flags.CARRY, false);
+
+ break;
+ case INC:
+ result = registerValue + 1;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE, false);
+ registers.getFlags().set(Flags.HALF_CARRY, (result & 0x10) != 0);
+
+ register.write(result, registers, mmu);
+ break;
+ case DEC:
+ result = registerValue - 1;
+
+ registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0);
+ registers.getFlags().set(Flags.NEGATIVE, true);
+ registers.getFlags().set(Flags.HALF_CARRY, (registerValue & 0x0F) == 0x0F);
+
+ register.write(result, registers, mmu);
+ break;
+ default:
+ Log.error("wtf!? how did we get here?!?!?");
+ break;
+ }
+
+ if (type != ALUType.INC && type != ALUType.DEC)
+ registers.setA(result);
+
+ return 0;
+ }
+
+ public enum ALUType {
+ ADD, ADC, SUB, SBC, AND, XOR, OR, CP, INC, DEC
+ }
+
+ @Override
+ public String toString() {
+ return type.name() + " A, " + register.getName();
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/Argument.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/Argument.java
new file mode 100644
index 0000000..6250bad
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/Argument.java
@@ -0,0 +1,348 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.log.Log;
+
+public enum Argument {
+
+ // Note: The '_' prefix, specifies the value located at the memory address the register is pointing to.
+ // Example: _HL <=> memory[HL]
+
+ A {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getA();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setA(value);
+ }
+ }, B {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getB();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setB(value);
+ }
+ }, C {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getC();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setC(value);
+ }
+ }, D {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getD();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setD(value);
+ }
+ }, E {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getE();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setE(value);
+ }
+ }, H {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getH();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setH(value);
+ }
+ }, L {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getL();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setL(value);
+ }
+ }, AF {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getAF();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setAF(value);
+ }
+ }, BC {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getBC();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setBC(value);
+ }
+ }, DE {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getDE();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setDE(value);
+ }
+ }, HL {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getHL();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setHL(value);
+ }
+ }, SP {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return registers.getSP();
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ registers.setSP(value);
+ }
+ }, _BC("(BC)", 4) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return mmu.read(registers.getBC());
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ mmu.write(value, registers.getBC());
+ }
+ }, _DE("(DE)", 4) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return mmu.read(registers.getDE());
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ mmu.write(value, registers.getDE());
+ }
+ }, _HL("(HL)", 4) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ return mmu.read(registers.getHL());
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ mmu.write(value, registers.getHL());
+ }
+ }, _HL_INC("(HL+)", 4) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int value = mmu.read(registers.getHL());
+ registers.setHL(registers.getHL() + 1);
+ return value;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ int address = registers.getHL();
+ registers.setHL(registers.getHL() + 1);
+ mmu.write(value, address);
+ }
+ }, _HL_DEC("(HL-)", 4) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int value = mmu.read(registers.getHL());
+ registers.setHL(registers.getHL() - 1);
+ return value;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ int address = registers.getHL();
+ registers.setHL(registers.getHL() - 1);
+ mmu.write(value, address);
+ }
+ }, D8(4) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int value = mmu.read(registers.getPC());
+ registers.incPC();
+
+ return value;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ Log.warn("Why are you writing to this argument?");
+ }
+ }, D16(8) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int value = mmu.read(registers.getPC());
+
+ registers.incPC();
+
+ value |= mmu.read(registers.getPC()) << 8;
+
+ registers.incPC();
+
+ return value;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ Log.warn("Why are you writing to this argument?");
+ }
+ }, I8(4) {
+
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ byte relativeAddress = (byte) mmu.read(registers.getPC());
+
+ registers.incPC();
+
+ return relativeAddress;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ Log.warn("Why are you writing to this argument?");
+ }
+
+ }, _C("(C)", 4) { // memory[0xFF00 + C]
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int value = mmu.read(0xFF00 + registers.getC());
+
+ return value;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ mmu.write(value, 0xFF00 + registers.getC());
+ }
+ }, _D8("(D8)", 8) { // memory[0xFF00 + memory[PC++]]
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int value = mmu.read(0xFF00 + mmu.read(registers.getPC()));
+
+ registers.incPC();
+
+ return value;
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ mmu.write(value, 0xFF00 + mmu.read(registers.getPC()));
+
+ registers.incPC();
+ }
+ }, _D16("(D16)", 12) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int address = mmu.read(registers.getPC());
+
+ registers.incPC();
+
+ address |= mmu.read(registers.getPC()) << 8;
+
+ registers.incPC();
+
+ return mmu.read(address);
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ int address = mmu.read(registers.getPC());
+
+ registers.incPC();
+
+ address |= mmu.read(registers.getPC()) << 8;
+
+ registers.incPC();
+
+ mmu.write(value, address);
+ }
+ }, _D16_SHORT("(D16 (16 bits))", 16) {
+ @Override
+ public int read(Registers registers, MMU mmu) {
+ int address = mmu.read(registers.getPC());
+
+ registers.incPC();
+
+ address |= mmu.read(registers.getPC()) << 8;
+
+ registers.incPC();
+
+ return (mmu.read(address) << 8) | mmu.read(address);
+ }
+
+ @Override
+ public void write(int value, Registers registers, MMU mmu) {
+ int address = mmu.read(registers.getPC());
+
+ registers.incPC();
+
+ address |= mmu.read(registers.getPC()) << 8;
+
+ registers.incPC();
+
+ mmu.write(value >> 8, address);
+ mmu.write(value, address + 1);
+ }
+ };
+
+ private String name;
+ private int cycles;
+
+ Argument(String name, int cycles) {
+ this.name = name;
+ this.cycles = cycles;
+ }
+
+ Argument(int cycles) {
+ this.name = name();
+ this.cycles = cycles;
+ }
+
+ Argument() {
+ this.name = name();
+ this.cycles = 0;
+ }
+
+ public abstract int read(Registers registers, MMU mmu);
+ public abstract void write(int value, Registers registers, MMU mmu);
+
+ public String getName() {
+ return name;
+ }
+
+ public int getCycles() {
+ return cycles;
+ }
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/CallOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/CallOpcode.java
new file mode 100644
index 0000000..2a0e2ad
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/CallOpcode.java
@@ -0,0 +1,60 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.log.Log;
+
+public class CallOpcode implements Opcode {
+
+ private int conditionFlag;
+ private Argument address;
+ private boolean not;
+
+ public CallOpcode(int conditionFlag, Argument address, boolean not) {
+ this.conditionFlag = conditionFlag;
+ this.address = address;
+ this.not = not;
+ }
+
+ public boolean canExecute(Registers registers) {
+ return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not);
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int addressToJump = address.read(registers, mmu);
+
+ if (canExecute(registers)) {
+ registers.decSP();
+ mmu.write(registers.getPC() >> 8, registers.getSP());
+ registers.decSP();
+ mmu.write(registers.getPC(), registers.getSP());
+
+ Log.info("Pushing 0x%4x to the stack.", registers.getPC());
+
+ registers.setPC(addressToJump);
+
+ return 12 + address.getCycles(); // 2 memory writes and additional cycle
+ }
+
+ return address.getCycles();
+ }
+
+ @Override
+ public String toString() {
+ if (conditionFlag == 0)
+ return "CALL " + address.getName();
+
+ char flag = ' ';
+ switch (conditionFlag) {
+ case Flags.ZERO: flag = 'Z'; break;
+ case Flags.NEGATIVE: flag = 'N'; break;
+ case Flags.HALF_CARRY: flag = 'H'; break;
+ case Flags.CARRY: flag = 'C'; break;
+ }
+
+ return "CALL " + (not ? "" : "N") + flag + ", " + address.getName();
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java
new file mode 100644
index 0000000..ece82c6
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java
@@ -0,0 +1,18 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+
+public class ComplementOpcode implements Opcode {
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ registers.setA(~registers.getA());
+
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+
+ return 0;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java
new file mode 100644
index 0000000..0b32d4a
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java
@@ -0,0 +1,17 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+
+public class FlipCarryOpcode implements Opcode {
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, !registers.getFlags().get(Flags.CARRY));
+
+ return 0;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java
new file mode 100644
index 0000000..9c398f6
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java
@@ -0,0 +1,29 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.InterruptManager;
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+
+public class InterruptOpcode implements Opcode {
+
+ private boolean enable;
+ private InterruptManager interruptManager;
+
+ public InterruptOpcode(boolean enable, InterruptManager interruptManager) {
+ this.enable = enable;
+ this.interruptManager = interruptManager;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ if (enable)
+ interruptManager.enable();
+ else
+ interruptManager.disable();
+
+ return 0;
+ }
+
+
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/JumpOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/JumpOpcode.java
new file mode 100644
index 0000000..5444f41
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/JumpOpcode.java
@@ -0,0 +1,50 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+
+public class JumpOpcode implements Opcode {
+
+ private int conditionFlag;
+ private Argument address;
+ private boolean not;
+
+ public JumpOpcode(int conditionFlag, Argument address, boolean not) {
+ this.conditionFlag = conditionFlag;
+ this.address = address;
+ this.not = not;
+ }
+
+ public boolean canExecute(Registers registers) {
+ return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not);
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int addressToJump = address.read(registers, mmu);
+
+ if (canExecute(registers)) {
+ registers.setPC(addressToJump);
+ return address.getCycles() + 4;
+ }
+
+ return address.getCycles();
+ }
+
+ @Override
+ public String toString() {
+ if (conditionFlag == 0)
+ return "JP " + address.getName();
+
+ char flag = ' ';
+ switch (conditionFlag) {
+ case Flags.ZERO: flag = 'Z'; break;
+ case Flags.NEGATIVE: flag = 'N'; break;
+ case Flags.HALF_CARRY: flag = 'H'; break;
+ case Flags.CARRY: flag = 'C'; break;
+ }
+
+ return "JP " + (not ? "" : "N") + flag + ", " + address.getName();
+ }
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java
new file mode 100644
index 0000000..3a804a8
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java
@@ -0,0 +1,51 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+
+public class JumpRelativeOpcode implements Opcode {
+
+ private int conditionFlag;
+ private Argument address;
+ private boolean not;
+
+ public JumpRelativeOpcode(int conditionFlag, Argument address, boolean not) {
+ this.conditionFlag = conditionFlag;
+ this.address = address;
+ this.not = not;
+ }
+
+ public boolean canExecute(Registers registers) {
+ return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not);
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int relativeValue = address.read(registers, mmu);
+
+ int addressToJump = registers.getPC() + relativeValue;
+ if (canExecute(registers)) {
+ registers.setPC(addressToJump);
+ return address.getCycles() + 4;
+ }
+
+ return address.getCycles();
+ }
+
+ @Override
+ public String toString() {
+ if (conditionFlag == 0)
+ return "JR " + address.getName();
+
+ char flag = ' ';
+ switch (conditionFlag) {
+ case Flags.ZERO: flag = 'Z'; break;
+ case Flags.NEGATIVE: flag = 'N'; break;
+ case Flags.HALF_CARRY: flag = 'H'; break;
+ case Flags.CARRY: flag = 'C'; break;
+ }
+
+ return "JR " + (not ? "" : "N") + flag + ", " + address.getName();
+ }
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/LoadOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/LoadOpcode.java
new file mode 100644
index 0000000..a782b65
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/LoadOpcode.java
@@ -0,0 +1,26 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+
+public class LoadOpcode implements Opcode {
+
+ private Argument from, to;
+
+ public LoadOpcode(Argument from, Argument to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ to.write(from.read(registers, mmu), registers, mmu);
+
+ return to.getCycles() + from.getCycles();
+ }
+
+ @Override
+ public String toString() {
+ return "LD " + to.getName() + ", " + from.getName();
+ }
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/Opcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/Opcode.java
new file mode 100644
index 0000000..71cdaa5
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/Opcode.java
@@ -0,0 +1,10 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+
+public interface Opcode {
+
+ public int run(Registers registers, MMU mmu);
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/PopOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/PopOpcode.java
new file mode 100644
index 0000000..ce4daca
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/PopOpcode.java
@@ -0,0 +1,32 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+
+public class PopOpcode implements Opcode {
+
+ private final Argument register;
+
+ public PopOpcode(Argument register) {
+ this.register = register;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int value = mmu.read(registers.getSP());
+ registers.incSP();
+
+ value |= mmu.read(registers.getSP()) << 8;
+ registers.incSP();
+
+ register.write(value, registers, mmu);
+
+ return 8;
+ }
+
+ @Override
+ public String toString() {
+ return "POP " + register.getName();
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/PushOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/PushOpcode.java
new file mode 100644
index 0000000..fee0abf
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/PushOpcode.java
@@ -0,0 +1,35 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.log.Log;
+
+public class PushOpcode implements Opcode {
+
+ private final Argument register;
+
+ public PushOpcode(Argument register) {
+ this.register = register;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int value = register.read(registers, mmu);
+
+ registers.decSP();
+ mmu.write(value >> 8, registers.getSP());
+
+ registers.decSP();
+ mmu.write(value, registers.getSP());
+
+ Log.info("Pushing 0x%4x to the stack.", value);
+
+ return 8;
+ }
+
+ @Override
+ public String toString() {
+ return "PUSH " + register.getName();
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/RestartOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/RestartOpcode.java
new file mode 100644
index 0000000..8ce0964
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/RestartOpcode.java
@@ -0,0 +1,26 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+
+public class RestartOpcode implements Opcode {
+
+ private int address;
+
+ public RestartOpcode(int address) {
+ this.address = address;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ registers.decSP();
+ mmu.write(registers.getPC() >> 8, registers.getSP());
+ registers.decSP();
+ mmu.write(registers.getPC(), registers.getSP());
+
+ registers.setPC(address);
+
+ return 12;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java
new file mode 100644
index 0000000..5c4d903
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java
@@ -0,0 +1,67 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.InterruptManager;
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.log.Log;
+
+public class ReturnOpcode implements Opcode {
+
+ private int conditionFlag;
+ private boolean not;
+ private boolean fromInterupt;
+
+ private final InterruptManager interruptManager;
+
+ public ReturnOpcode(int conditionFlag, boolean not, boolean fromInterupt, final InterruptManager interruptManager) {
+ this.conditionFlag = conditionFlag;
+ this.not = not;
+ this.fromInterupt = fromInterupt;
+ this.interruptManager = interruptManager;
+ }
+
+ public boolean canExecute(Registers registers) {
+ return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not);
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int addressToJump = mmu.read(registers.getSP());
+ registers.incSP();
+
+ addressToJump |= mmu.read(registers.getSP()) << 8;
+ registers.incSP();
+
+ if (canExecute(registers)) {
+ Log.info("Returning from 0x%4x to 0x%4x", registers.getPC(), addressToJump);
+
+ registers.setPC(addressToJump);
+
+ if (fromInterupt) {
+ interruptManager.enable();
+ }
+
+ return 16;
+ }
+
+ return 4;
+ }
+
+ @Override
+ public String toString() {
+ if (conditionFlag == 0)
+ return "RET" + (fromInterupt ? "I" : "");
+
+ char flag = ' ';
+ switch (conditionFlag) {
+ case Flags.ZERO: flag = 'Z'; break;
+ case Flags.NEGATIVE: flag = 'N'; break;
+ case Flags.HALF_CARRY: flag = 'H'; break;
+ case Flags.CARRY: flag = 'C'; break;
+ }
+
+ return "RET" + (fromInterupt ? "I" : "") + " " + (not ? "" : "N") + flag;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java
new file mode 100644
index 0000000..f74c024
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java
@@ -0,0 +1,68 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+
+public class RotateAOpcode implements Opcode {
+
+ private boolean withCarry;
+ private int direction;
+
+ public static final int LEFT = -1;
+ public static final int RIGHT = 1;
+
+ public RotateAOpcode(boolean withCarry, int direction) {
+ this.withCarry = withCarry;
+ this.direction = direction;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ if (direction == LEFT) {
+ if (withCarry) {
+ int value = registers.getA();
+ int result = (value << 1) | ((value >> 7) & 1);
+
+ registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0);
+
+ registers.setA(result);
+ } else {
+ int value = registers.getA();
+ int result = (value << 1) | (registers.getFlags().get(Flags.CARRY) ? 1 : 0);
+
+ registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0);
+
+ registers.setA(result);
+ }
+ } else {
+ if (withCarry) {
+ int value = registers.getA();
+ int result = (value >> 1) | ((value & 1) << 7);
+
+ registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 1) != 0);
+
+ registers.setA(result);
+ } else {
+ int value = registers.getA();
+ int result = (value >> 1) | ((registers.getFlags().get(Flags.CARRY) ? 1 : 0) << 7);
+
+ registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 1) != 0);
+
+ registers.setA(result);
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "R" + (direction == LEFT ? "L" : "R") + (withCarry ? "C" : "" + "A");
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java
new file mode 100644
index 0000000..9b570b6
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java
@@ -0,0 +1,17 @@
+package playingcoffee.core.opcode;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+
+public class SetCarryOpcode implements Opcode {
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, true);
+
+ return 0;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java
new file mode 100644
index 0000000..2053d6a
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java
@@ -0,0 +1,33 @@
+package playingcoffee.core.opcode.prefixed;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.Opcode;
+
+public class BitOpcode implements Opcode {
+
+ private int bit;
+ private Argument argument;
+
+ public BitOpcode(int bit, Argument argument) {
+ this.bit = bit;
+ this.argument = argument;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ registers.getFlags().set(Flags.ZERO, (argument.read(registers, mmu) & (1 << bit)) == 0);
+ registers.getFlags().set(Flags.NEGATIVE, false);
+ registers.getFlags().set(Flags.HALF_CARRY, true);
+
+ return argument.getCycles();
+ }
+
+ @Override
+ public String toString() {
+ return "BIT " + bit + ", " + argument.getName();
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java
new file mode 100644
index 0000000..525480e
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java
@@ -0,0 +1,25 @@
+package playingcoffee.core.opcode.prefixed;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.Opcode;
+
+public class ResetBitOpcode implements Opcode {
+
+ private int bit;
+ private Argument argument;
+
+ public ResetBitOpcode(int bit, Argument argument) {
+ this.bit = bit;
+ this.argument = argument;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ argument.write(argument.read(registers, mmu) & (~(1 << bit)), registers, mmu);
+
+ return argument.getCycles() * 2;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java
new file mode 100644
index 0000000..b13a21a
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java
@@ -0,0 +1,54 @@
+package playingcoffee.core.opcode.prefixed;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.Opcode;
+
+public class RotateOpcode implements Opcode {
+
+ private boolean withCarry;
+ private Argument register;
+ private int direction;
+
+ public static final int LEFT = -1;
+ public static final int RIGHT = 1;
+
+ public RotateOpcode(boolean withCarry, Argument register, int direction) {
+ this.withCarry = withCarry;
+ this.register = register;
+ this.direction = direction;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ if (direction == LEFT) {
+ int value = register.read(registers, mmu);
+ int result = (value << 1) | (!withCarry ? (registers.getFlags().get(Flags.CARRY) ? 1 : 0) : (value >> 7) & 1);
+
+ registers.getFlags().set(Flags.ZERO, result == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0);
+
+ register.write(result, registers, mmu);
+
+ } else {
+ int value = register.read(registers, mmu);
+ int result = (value >> 1) | ((!withCarry ? (registers.getFlags().get(Flags.CARRY) ? 1 : 0) : (value & 0x1)) << 7);
+
+ registers.getFlags().set(Flags.ZERO, result == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 1) != 0);
+
+ register.write(result, registers, mmu);
+ }
+
+ return register.getCycles();
+ }
+
+ @Override
+ public String toString() {
+ return "R" + (direction == LEFT ? "L" : "R") + (withCarry ? "C " : " " + register.getName());
+ }
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java
new file mode 100644
index 0000000..f53b6c5
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java
@@ -0,0 +1,25 @@
+package playingcoffee.core.opcode.prefixed;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.Opcode;
+
+public class SetBitOpcode implements Opcode {
+
+ private int bit;
+ private Argument argument;
+
+ public SetBitOpcode(int bit, Argument argument) {
+ this.bit = bit;
+ this.argument = argument;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ argument.write(argument.read(registers, mmu) | (1 << bit), registers, mmu);
+
+ return argument.getCycles() * 2;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java
new file mode 100644
index 0000000..f75e9bd
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java
@@ -0,0 +1,57 @@
+package playingcoffee.core.opcode.prefixed;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Flags;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.Opcode;
+
+public class ShiftOpcode implements Opcode {
+
+ private ShiftType type;
+ private Argument register;
+
+ public ShiftOpcode(ShiftType type, Argument register) {
+ this.type = type;
+ this.register = register;
+ }
+
+ public enum ShiftType {
+ SLA, SRA, SRL
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int value, result;
+
+ switch (type) {
+ case SLA:
+ value = register.read(registers, mmu);
+ result = value << 1;
+
+ registers.getFlags().set(Flags.ZERO, result == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0);
+ break;
+ case SRA:
+ value = register.read(registers, mmu);
+ result = (value >> 1) | (value & 0x80);
+
+ registers.getFlags().set(Flags.ZERO, result == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 0x1) != 0);
+ break;
+ case SRL:
+ value = register.read(registers, mmu);
+ result = (value >> 1);
+
+ registers.getFlags().set(Flags.ZERO, result == 0);
+ registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false);
+ registers.getFlags().set(Flags.CARRY, (value & 0x1) != 0);
+ break;
+ }
+
+ return register.getCycles() * 2;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java
new file mode 100644
index 0000000..b31cac8
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java
@@ -0,0 +1,25 @@
+package playingcoffee.core.opcode.prefixed;
+
+import playingcoffee.core.MMU;
+import playingcoffee.core.cpu.Registers;
+import playingcoffee.core.opcode.Argument;
+import playingcoffee.core.opcode.Opcode;
+
+public class SwapOpcode implements Opcode {
+
+ private Argument register;
+
+ public SwapOpcode(Argument register) {
+ this.register = register;
+ }
+
+ @Override
+ public int run(Registers registers, MMU mmu) {
+ int value = register.read(registers, mmu);
+
+ register.write(((value & 0xF0) >> 4) | ((value & 0xF) << 4), registers, mmu);
+
+ return register.getCycles() * 2;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/log/Log.java b/playing-coffee/src/main/java/playingcoffee/log/Log.java
new file mode 100644
index 0000000..1bb6478
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/log/Log.java
@@ -0,0 +1,73 @@
+package playingcoffee.log;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.time.LocalTime;
+
+public class Log {
+
+ public static final String ANSI_RESET = "\033[0m";
+ public static final String ANSI_BLACK = "\u001B[30m";
+ public static final String ANSI_RED = "\u001B[31m";
+ public static final String ANSI_GREEN = "\u001B[32m";
+ public static final String ANSI_YELLOW = "\u001B[33m";
+ public static final String ANSI_BLUE = "\u001B[34m";
+ public static final String ANSI_PURPLE = "\u001B[35m";
+ public static final String ANSI_CYAN = "\u001B[36m";
+ public static final String ANSI_WHITE = "\u001B[37m";
+
+ private static FileWriter fileWriter;
+
+ private static boolean useColors = false;
+
+ public static void init() {
+ try {
+ File file = new File("log.txt");
+ file.createNewFile();
+
+ fileWriter = new FileWriter(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.exit(-1);
+ }
+ info("Initialized logger");
+ }
+
+ public static void close() {
+ try {
+ fileWriter.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void info(String format, Object... args) {
+ log("Info", ANSI_RESET, format, args);
+ }
+
+ public static void warn(String format, Object... args) {
+ log("Warning", ANSI_YELLOW, format, args);
+ }
+
+ public static void error(String format, Object... args) {
+ log("Error", ANSI_RED, format, args);
+ }
+
+ public static void fatal(String format, Object... args) {
+ log("Fatal", ANSI_RED, format, args);
+ }
+
+ private static void log(String type, String ansiColor, String format, Object... args) {
+ String finalMessage = "[" + LocalTime.now() + "] " + type + ": " + String.format(format, args);
+
+ ansiColor = useColors ? ansiColor : "";
+
+ System.out.println(ansiColor + finalMessage);
+ try {
+ fileWriter.write(finalMessage + "\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/ppu/OAM.java b/playing-coffee/src/main/java/playingcoffee/ppu/OAM.java
new file mode 100644
index 0000000..8ef0dcd
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/ppu/OAM.java
@@ -0,0 +1,52 @@
+package playingcoffee.ppu;
+
+import playingcoffee.core.MemorySpace;
+import playingcoffee.log.Log;
+
+public class OAM implements MemorySpace {
+
+ public OAMEntry entries[];
+
+ public OAM() {
+ entries = new OAMEntry[40];
+ }
+
+ public int read(int address) {
+ int entry = (address - 0xFE00) / 4;
+
+ switch (address % 4) {
+ case 0: return entries[entry].x;
+ case 1: return entries[entry].y;
+ case 2: return entries[entry].tileNumber;
+ case 3: return entries[entry].flags;
+ }
+
+ Log.error("how in pete's holy christmas tree did we get here?");
+ throw new IllegalArgumentException("Invalid address.");
+ }
+
+ public void write(int value, int address) {
+ int entry = (address - 0xFE00) / 4;
+
+ switch (address % 4) {
+ case 0: entries[entry].x = value;
+ case 1: entries[entry].y = value;
+ case 2: entries[entry].tileNumber = value;
+ case 3: entries[entry].flags = value;
+ }
+
+ Log.error("how in pete's holy christmas tree did we get here?");
+ throw new IllegalArgumentException("Invalid address.");
+ }
+
+ @Override
+ public boolean inMemorySpace(int address) {
+ return (address >= 0xFE00 && address <= 0xFE9F);
+ }
+
+ public class OAMEntry {
+ public int x, y;
+ public int tileNumber;
+ public int flags;
+ }
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/ppu/PPU.java b/playing-coffee/src/main/java/playingcoffee/ppu/PPU.java
new file mode 100644
index 0000000..1a071a7
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/ppu/PPU.java
@@ -0,0 +1,73 @@
+package playingcoffee.ppu;
+
+import playingcoffee.core.InterruptManager;
+import playingcoffee.core.MMU;
+
+public class PPU {
+
+ private final MMU mmu;
+ private final InterruptManager interruptManager;
+
+ private PPURegisters registers;
+ private VRAM vram;
+
+ private int clockCount = 0;
+
+ public PPU(final MMU mmu, final InterruptManager interruptManager) {
+ this.mmu = mmu;
+ this.interruptManager = interruptManager;
+
+ registers = new PPURegisters();
+ vram = new VRAM();
+
+ this.mmu.connectMemorySpace(registers);
+ this.mmu.connectMemorySpace(vram);
+ }
+
+ public void OAMSeach() {
+ registers.setLCDCMode(2);
+ }
+
+ public void pixelTransfer() {
+ registers.setLCDCMode(3);
+ }
+
+ public void HBlank() {
+ registers.setLCDCMode(0);
+ }
+
+ public void VBlank() {
+ registers.setLCDCMode(1);
+ }
+
+ public void clock() {
+ registers.lcdcYCoord = clockCount / 114;
+
+ if (clockCount == 114 * 144)
+ interruptManager.requestInterrupt(InterruptManager.VBLANK);
+
+ if (clockCount < 114 * 144) {
+ if (clockCount % 114 < 20) OAMSeach();
+ else if (clockCount % 114 < 43) pixelTransfer();
+ else HBlank();
+
+ } else {
+ VBlank();
+ }
+
+ clockCount++;
+
+ if (clockCount == 17556) {
+ clockCount = 0;
+ }
+ }
+
+ public PPURegisters getRegisters() {
+ return registers;
+ }
+
+ public VRAM getVram() {
+ return vram;
+ }
+
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/ppu/PPURegisters.java b/playing-coffee/src/main/java/playingcoffee/ppu/PPURegisters.java
new file mode 100644
index 0000000..54695a8
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/ppu/PPURegisters.java
@@ -0,0 +1,61 @@
+package playingcoffee.ppu;
+
+import playingcoffee.core.MemorySpace;
+
+public class PPURegisters implements MemorySpace {
+
+ public int lcdControl; // 0xFF40
+ public int lcdcStatus; // 0xFF41
+ public int scrollY, scrollX; // 0xFF42 - 0xFF43
+ public int lcdcYCoord; // 0xFF44
+ public int lyCompare; // 0xFF45
+ public int dmaTransferStart; // 0xFF46
+ public int bgPalette; // 0xFF47
+ public int objPalette0, objPalette1; // 0xFF48 - 0xFF49
+ public int windowY, windowX; // 0xFF4A - 0xFF4B
+
+ public void setLCDCMode(int mode) {
+ lcdcStatus = (lcdcStatus & 0xFC) | mode;
+ }
+
+ public int read(int address) {
+ switch (address) {
+ case 0xFF40: return lcdControl;
+ case 0xFF41: return lcdcStatus;
+ case 0xFF42: return scrollY;
+ case 0xFF43: return scrollX;
+ case 0xFF44: return lcdcYCoord;
+ case 0xFF45: return lyCompare;
+ case 0xFF46: return dmaTransferStart; // TODO: Do DMA (I have no idea how to do it though...)
+ case 0xFF47: return bgPalette;
+ case 0xFF48: return objPalette0;
+ case 0xFF49: return objPalette1;
+ case 0xFF4A: return windowY;
+ case 0xFF4B: return windowX;
+ }
+
+ throw new IllegalArgumentException("Invalid address");
+ }
+
+ public void write(int value, int address) {
+ switch (address) {
+ case 0xFF40: lcdControl = value; return;
+ case 0xFF41: lcdcStatus = value; return;
+ case 0xFF42: scrollY = value; return;
+ case 0xFF43: scrollX = value; return;
+ case 0xFF45: lyCompare = value; return;
+ case 0xFF46: dmaTransferStart = value; return; // TODO: Do DMA (I have no idea how to do it though...)
+ case 0xFF47: bgPalette = value; return;
+ case 0xFF48: objPalette0 = value; return;
+ case 0xFF49: objPalette1 = value; return;
+ case 0xFF4A: windowY = value; return;
+ case 0xFF4B: windowX = value; return;
+ }
+
+ throw new IllegalArgumentException("Invalid address");
+ }
+
+ public boolean inMemorySpace(int address) {
+ return (address >= 0xFF40 && address <= 0xFF4B);
+ }
+}
diff --git a/playing-coffee/src/main/java/playingcoffee/ppu/VRAM.java b/playing-coffee/src/main/java/playingcoffee/ppu/VRAM.java
new file mode 100644
index 0000000..c314446
--- /dev/null
+++ b/playing-coffee/src/main/java/playingcoffee/ppu/VRAM.java
@@ -0,0 +1,35 @@
+package playingcoffee.ppu;
+
+import playingcoffee.core.MemorySpace;
+
+public class VRAM implements MemorySpace {
+
+ public int[] background0;
+ public int[] background1;
+
+ public VRAM() {
+ background0 = new int[32 * 32];
+ background1 = new int[32 * 32];
+ }
+
+ @Override
+ public int read(int address) {
+ if (address >= 0x9800 && address <= 0x9BFF) return background0[address % (32 * 32)];
+ if (address >= 0x9C00 && address <= 0x9FFF) return background1[address % (32 * 32)];
+
+ return 0;
+ }
+
+ @Override
+ public void write(int value, int address) {
+ if (address >= 0x9800 && address <= 0x9BFF) background0[address % (32 * 32)] = value;
+ if (address >= 0x9C00 && address <= 0x9FFF) background1[address % (32 * 32)] = value;
+
+ }
+
+ @Override
+ public boolean inMemorySpace(int address) {
+ return address >= 0x9800 && address <= 0x9FFF;
+ }
+
+}
diff --git a/playing-coffee/src/test/java/playingcoffee/test/GameBoyTest.java b/playing-coffee/src/test/java/playingcoffee/test/GameBoyTest.java
new file mode 100644
index 0000000..8f2adce
--- /dev/null
+++ b/playing-coffee/src/test/java/playingcoffee/test/GameBoyTest.java
@@ -0,0 +1,26 @@
+package playingcoffee.test;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+public class GameBoyTest {
+
+ @Test
+ public void testCPU() throws IOException {
+ /*Log.init();
+
+
+ byte[] bootRom = Files.readAllBytes(Paths.get("dmg_boot.bin"));
+
+ MMU mmu = new MMU();
+ CPU cpu = new CPU(mmu);
+
+ for (int i = 0; i < 0x100; i++) {
+ mmu.write(Byte.toUnsignedInt(bootRom[i]), i);
+ }
+
+ while (true) cpu.clock();*/
+ }
+
+}