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();*/ + } + +}