diff --git a/.gitattributes b/.gitattributes index 8bee55d..8a79974 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -tests/sm83/** linguist-vendored -tests/blargg/** linguist-vendored -tests/acid/** linguist-vendored -tests/mooneye/** linguist-vendored +gb/tests/sm83/** linguist-vendored +gb/tests/blargg/** linguist-vendored +gb/tests/acid/** linguist-vendored +gb/tests/mooneye/** linguist-vendored diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 42adabb..b13b324 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,7 +36,7 @@ jobs: sudo apt install libwayland-dev libasound2-dev libudev-dev pkg-config - name: Build release - run: cargo build --release + run: cargo build -p engram-gb --release - name: Rename binary (Unix) if: matrix.runner != 'windows-latest' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2545c86..aa8009c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,4 +35,4 @@ jobs: sudo apt install libwayland-dev libasound2-dev libudev-dev pkg-config - name: Test emulator - run: cargo test + run: cargo test -p engram-gb diff --git a/.gitignore b/.gitignore index 4638956..0a8b0aa 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ tests/blargg/*/source/ *.s mooneye/ *ppu_registers.txt* +*.txt +*.bin +*.sav +*.gba diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..11afda9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1434 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alsa" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d" +dependencies = [ + "alsa-sys", + "bitflags 2.13.1", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad7569085a265dd3f607ebecce7458eaab2132a84393534c95b18dcbc3f31e04" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "coreaudio-rs" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58" +dependencies = [ + "bitflags 2.13.1", + "libc", + "objc2-audio-toolbox", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "cpal" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f77b11176c37874be37e8d691c946e31b2b8c357abce9526f6a99eb469e1028" +dependencies = [ + "alsa", + "block2", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "objc2", + "objc2-audio-toolbox", + "objc2-avf-audio", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", + "web-sys", + "windows", + "windows-core", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.1", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "engram-gb" +version = "0.1.1" +dependencies = [ + "chrono", + "cpal", + "macroquad", + "rfd", + "rtrb", + "serde", + "serde_json", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fontdue" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7894823fa221401399e2598f8b63f81ac77ff5c63248b7656779bff1632d7d3d" +dependencies = [ + "hashbrown 0.15.5", + "ttf-parser", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gba" +version = "0.1.0" + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", + "png", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.19", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + +[[package]] +name = "macroquad" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cbd54d99d4d3ac59deed7ee4927bbbf4eb33a55635530af3be9395a55746090" +dependencies = [ + "fontdue", + "glam", + "image", + "macroquad_macro", + "miniquad", + "quad-rand", +] + +[[package]] +name = "macroquad_macro" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b1d96218903768c1ce078b657c0d5965465c95a60d2682fd97443c9d2483dd" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "miniquad" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbaea978d741f3b60ced666b06a2a1e4a2bc81cb8b9dd29f4a40965e1f59e65" +dependencies = [ + "libc", + "ndk-sys 0.2.2", + "objc-rs", + "winapi", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.1", + "jni-sys 0.3.1", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc-rs" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a1e7069a2525126bf12a9f1f7916835fafade384fb27cabf698e745e2a1eb8" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-audio-toolbox" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08" +dependencies = [ + "bitflags 2.13.1", + "libc", + "objc2", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-avf-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" +dependencies = [ + "dispatch2", + "objc2", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-audio-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" +dependencies = [ + "bitflags 2.13.1", + "objc2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.1", + "block2", + "dispatch2", + "libc", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.1", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quad-rand" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a651516ddc9168ebd67b24afd085a718be02f8858fe406591b013d101ce2f40" + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rfd" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220" +dependencies = [ + "block2", + "dispatch2", + "js-sys", + "libc", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "percent-encoding", + "pollster", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "web-sys", + "windows-sys", +] + +[[package]] +name = "rtrb" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ade083ccbb4bf536df69d1f6432cc23deb7acccff86b183f3923a6fd56a1153" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016ccf01d1c58b6f8999612813e17c9b2390f7d70671428869913310f83f54b8" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" +dependencies = [ + "bitflags 2.13.1", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index be3cc4b..b0b7e2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,3 @@ -[package] -name = "engram" -version = "0.1.1" -edition = "2024" -authors = ["Donisha Smith "] -license-file = "LICENSE" - -[dependencies] -macroquad = { version = "0.4", default-features = false } -rfd = "0.17" -chrono = "0.4" -cpal = "0.18.1" -rtrb = "0.3.4" - -[dev-dependencies] -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -[profile.dev] -opt-level = 1 +[workspace] +members = ["gb", "gba"] +resolver = "2" diff --git a/gb/Cargo.toml b/gb/Cargo.toml new file mode 100644 index 0000000..c8f52ae --- /dev/null +++ b/gb/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "engram-gb" +version = "0.1.1" +edition = "2024" +authors = ["Donisha Smith "] +license-file = "LICENSE" + +[dependencies] +macroquad = { version = "0.4", default-features = false } +rfd = "0.17" +chrono = "0.4" +cpal = "0.18.1" +rtrb = "0.3.4" + +[dev-dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[profile.dev] +opt-level = 1 diff --git a/src/audio.rs b/gb/src/audio.rs similarity index 100% rename from src/audio.rs rename to gb/src/audio.rs diff --git a/src/components/apu/fir.rs b/gb/src/components/apu/fir.rs similarity index 100% rename from src/components/apu/fir.rs rename to gb/src/components/apu/fir.rs diff --git a/src/components/apu/mod.rs b/gb/src/components/apu/mod.rs similarity index 100% rename from src/components/apu/mod.rs rename to gb/src/components/apu/mod.rs diff --git a/src/components/apu/noise.rs b/gb/src/components/apu/noise.rs similarity index 100% rename from src/components/apu/noise.rs rename to gb/src/components/apu/noise.rs diff --git a/src/components/apu/pulse.rs b/gb/src/components/apu/pulse.rs similarity index 100% rename from src/components/apu/pulse.rs rename to gb/src/components/apu/pulse.rs diff --git a/src/components/apu/sound_control.rs b/gb/src/components/apu/sound_control.rs similarity index 100% rename from src/components/apu/sound_control.rs rename to gb/src/components/apu/sound_control.rs diff --git a/src/components/apu/wave.rs b/gb/src/components/apu/wave.rs similarity index 100% rename from src/components/apu/wave.rs rename to gb/src/components/apu/wave.rs diff --git a/src/components/bootloader.rs b/gb/src/components/bootloader.rs similarity index 100% rename from src/components/bootloader.rs rename to gb/src/components/bootloader.rs diff --git a/src/components/bus.rs b/gb/src/components/bus.rs similarity index 100% rename from src/components/bus.rs rename to gb/src/components/bus.rs diff --git a/src/components/cpu/alu.rs b/gb/src/components/cpu/alu.rs similarity index 100% rename from src/components/cpu/alu.rs rename to gb/src/components/cpu/alu.rs diff --git a/src/components/cpu/cycles.rs b/gb/src/components/cpu/cycles.rs similarity index 100% rename from src/components/cpu/cycles.rs rename to gb/src/components/cpu/cycles.rs diff --git a/src/components/cpu/instructions.rs b/gb/src/components/cpu/instructions.rs similarity index 100% rename from src/components/cpu/instructions.rs rename to gb/src/components/cpu/instructions.rs diff --git a/src/components/cpu/interrupts.rs b/gb/src/components/cpu/interrupts.rs similarity index 100% rename from src/components/cpu/interrupts.rs rename to gb/src/components/cpu/interrupts.rs diff --git a/src/components/cpu/mod.rs b/gb/src/components/cpu/mod.rs similarity index 100% rename from src/components/cpu/mod.rs rename to gb/src/components/cpu/mod.rs diff --git a/src/components/cpu/registers.rs b/gb/src/components/cpu/registers.rs similarity index 100% rename from src/components/cpu/registers.rs rename to gb/src/components/cpu/registers.rs diff --git a/src/components/gameboy.rs b/gb/src/components/gameboy.rs similarity index 100% rename from src/components/gameboy.rs rename to gb/src/components/gameboy.rs diff --git a/src/components/joypad.rs b/gb/src/components/joypad.rs similarity index 100% rename from src/components/joypad.rs rename to gb/src/components/joypad.rs diff --git a/src/components/mod.rs b/gb/src/components/mod.rs similarity index 100% rename from src/components/mod.rs rename to gb/src/components/mod.rs diff --git a/src/components/ppu/attributes.rs b/gb/src/components/ppu/attributes.rs similarity index 100% rename from src/components/ppu/attributes.rs rename to gb/src/components/ppu/attributes.rs diff --git a/src/components/ppu/mod.rs b/gb/src/components/ppu/mod.rs similarity index 100% rename from src/components/ppu/mod.rs rename to gb/src/components/ppu/mod.rs diff --git a/src/components/ppu/palette.rs b/gb/src/components/ppu/palette.rs similarity index 100% rename from src/components/ppu/palette.rs rename to gb/src/components/ppu/palette.rs diff --git a/src/components/ppu/sprites.rs b/gb/src/components/ppu/sprites.rs similarity index 100% rename from src/components/ppu/sprites.rs rename to gb/src/components/ppu/sprites.rs diff --git a/src/components/ppu/vram.rs b/gb/src/components/ppu/vram.rs similarity index 100% rename from src/components/ppu/vram.rs rename to gb/src/components/ppu/vram.rs diff --git a/src/components/rom/cartridge.rs b/gb/src/components/rom/cartridge.rs similarity index 100% rename from src/components/rom/cartridge.rs rename to gb/src/components/rom/cartridge.rs diff --git a/src/components/rom/mbc.rs b/gb/src/components/rom/mbc.rs similarity index 100% rename from src/components/rom/mbc.rs rename to gb/src/components/rom/mbc.rs diff --git a/src/components/rom/mod.rs b/gb/src/components/rom/mod.rs similarity index 100% rename from src/components/rom/mod.rs rename to gb/src/components/rom/mod.rs diff --git a/src/components/timer.rs b/gb/src/components/timer.rs similarity index 100% rename from src/components/timer.rs rename to gb/src/components/timer.rs diff --git a/src/components/utils.rs b/gb/src/components/utils.rs similarity index 100% rename from src/components/utils.rs rename to gb/src/components/utils.rs diff --git a/src/lib.rs b/gb/src/lib.rs similarity index 100% rename from src/lib.rs rename to gb/src/lib.rs diff --git a/src/main.rs b/gb/src/main.rs similarity index 99% rename from src/main.rs rename to gb/src/main.rs index 980f5ca..149d226 100644 --- a/src/main.rs +++ b/gb/src/main.rs @@ -7,7 +7,7 @@ //#![windows_subsystem = "windows"] -use engram::{ +use engram_gb::{ audio::{AUDIO_BUFFER_CAPACITY, AUDIO_TARGET_OCCUPANCY, AudioOutput}, components::{gameboy::GameBoy, rom::cartridge::Cartridge}, render::Screen, diff --git a/src/render.rs b/gb/src/render.rs similarity index 100% rename from src/render.rs rename to gb/src/render.rs diff --git a/tests/blargg/cgb_sound/cgb_sound.gb b/gb/tests/blargg/cgb_sound/cgb_sound.gb similarity index 100% rename from tests/blargg/cgb_sound/cgb_sound.gb rename to gb/tests/blargg/cgb_sound/cgb_sound.gb diff --git a/tests/blargg/cgb_sound/readme.txt b/gb/tests/blargg/cgb_sound/readme.txt similarity index 100% rename from tests/blargg/cgb_sound/readme.txt rename to gb/tests/blargg/cgb_sound/readme.txt diff --git a/tests/blargg/cgb_sound/rom_singles/01-registers.gb b/gb/tests/blargg/cgb_sound/rom_singles/01-registers.gb similarity index 100% rename from tests/blargg/cgb_sound/rom_singles/01-registers.gb rename to gb/tests/blargg/cgb_sound/rom_singles/01-registers.gb diff --git a/tests/blargg/cgb_sound/rom_singles/02-len ctr.gb b/gb/tests/blargg/cgb_sound/rom_singles/02-len ctr.gb similarity index 100% rename from tests/blargg/cgb_sound/rom_singles/02-len ctr.gb rename to gb/tests/blargg/cgb_sound/rom_singles/02-len ctr.gb diff --git a/tests/blargg/cgb_sound/rom_singles/03-trigger.gb b/gb/tests/blargg/cgb_sound/rom_singles/03-trigger.gb similarity index 100% rename from tests/blargg/cgb_sound/rom_singles/03-trigger.gb rename to gb/tests/blargg/cgb_sound/rom_singles/03-trigger.gb diff --git a/tests/blargg/cgb_sound/rom_singles/04-sweep.gb b/gb/tests/blargg/cgb_sound/rom_singles/04-sweep.gb similarity index 100% rename from tests/blargg/cgb_sound/rom_singles/04-sweep.gb rename to gb/tests/blargg/cgb_sound/rom_singles/04-sweep.gb diff --git a/tests/blargg/cgb_sound/rom_singles/05-sweep details.gb b/gb/tests/blargg/cgb_sound/rom_singles/05-sweep details.gb similarity index 100% rename from tests/blargg/cgb_sound/rom_singles/05-sweep details.gb rename to gb/tests/blargg/cgb_sound/rom_singles/05-sweep details.gb diff --git a/tests/blargg/cgb_sound/rom_singles/06-overflow on trigger.gb b/gb/tests/blargg/cgb_sound/rom_singles/06-overflow on trigger.gb similarity index 100% rename from tests/blargg/cgb_sound/rom_singles/06-overflow on trigger.gb rename to gb/tests/blargg/cgb_sound/rom_singles/06-overflow on trigger.gb diff --git a/tests/blargg/cgb_sound/rom_singles/07-len sweep period sync.gb b/gb/tests/blargg/cgb_sound/rom_singles/07-len sweep period sync.gb similarity index 100% rename from tests/blargg/cgb_sound/rom_singles/07-len sweep period sync.gb rename to gb/tests/blargg/cgb_sound/rom_singles/07-len sweep period sync.gb diff --git a/tests/blargg/cgb_sound/rom_singles/08-len ctr during power.gb b/gb/tests/blargg/cgb_sound/rom_singles/08-len ctr during power.gb similarity index 100% rename from tests/blargg/cgb_sound/rom_singles/08-len ctr during power.gb rename to gb/tests/blargg/cgb_sound/rom_singles/08-len ctr during power.gb diff --git a/tests/blargg/cgb_sound/rom_singles/09-wave read while on.gb b/gb/tests/blargg/cgb_sound/rom_singles/09-wave read while on.gb similarity index 100% rename from tests/blargg/cgb_sound/rom_singles/09-wave read while on.gb rename to gb/tests/blargg/cgb_sound/rom_singles/09-wave read while on.gb diff --git a/tests/blargg/cgb_sound/rom_singles/10-wave trigger while on.gb b/gb/tests/blargg/cgb_sound/rom_singles/10-wave trigger while on.gb similarity index 100% rename from tests/blargg/cgb_sound/rom_singles/10-wave trigger while on.gb rename to gb/tests/blargg/cgb_sound/rom_singles/10-wave trigger while on.gb diff --git a/tests/blargg/cgb_sound/rom_singles/11-regs after power.gb b/gb/tests/blargg/cgb_sound/rom_singles/11-regs after power.gb similarity index 100% rename from tests/blargg/cgb_sound/rom_singles/11-regs after power.gb rename to gb/tests/blargg/cgb_sound/rom_singles/11-regs after power.gb diff --git a/tests/blargg/cgb_sound/rom_singles/12-wave.gb b/gb/tests/blargg/cgb_sound/rom_singles/12-wave.gb similarity index 100% rename from tests/blargg/cgb_sound/rom_singles/12-wave.gb rename to gb/tests/blargg/cgb_sound/rom_singles/12-wave.gb diff --git a/gb/tests/blargg/cgb_sound/source/01-registers.s b/gb/tests/blargg/cgb_sound/source/01-registers.s new file mode 100644 index 0000000..2846344 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/04-sweep.s b/gb/tests/blargg/cgb_sound/source/04-sweep.s new file mode 100644 index 0000000..2e17f35 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/05-sweep details.s b/gb/tests/blargg/cgb_sound/source/05-sweep details.s new file mode 100644 index 0000000..5130d5c --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/06-overflow on trigger.s b/gb/tests/blargg/cgb_sound/source/06-overflow on trigger.s new file mode 100644 index 0000000..cf52679 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/07-len sweep period sync.s b/gb/tests/blargg/cgb_sound/source/07-len sweep period sync.s new file mode 100644 index 0000000..ece4b6d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/08-len ctr during power.s b/gb/tests/blargg/cgb_sound/source/08-len ctr during power.s new file mode 100644 index 0000000..7850d6b --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/09-wave read while on.s b/gb/tests/blargg/cgb_sound/source/09-wave read while on.s new file mode 100644 index 0000000..a9f7b7d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/10-wave trigger while on.s b/gb/tests/blargg/cgb_sound/source/10-wave trigger while on.s new file mode 100644 index 0000000..b1e2432 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/11-regs after power.s b/gb/tests/blargg/cgb_sound/source/11-regs after power.s new file mode 100644 index 0000000..19fb2bf --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/12-wave.s b/gb/tests/blargg/cgb_sound/source/12-wave.s new file mode 100644 index 0000000..d4af192 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/common/build_gbs.s b/gb/tests/blargg/cgb_sound/source/common/build_gbs.s new file mode 100644 index 0000000..3c70c07 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/common/build_rom.s b/gb/tests/blargg/cgb_sound/source/common/build_rom.s new file mode 100644 index 0000000..1814a91 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/common/console.bin b/gb/tests/blargg/cgb_sound/source/common/console.bin new file mode 100644 index 0000000..b02f2d3 Binary files /dev/null and b/gb/tests/blargg/cgb_sound/source/common/console.bin differ diff --git a/gb/tests/blargg/cgb_sound/source/common/console.s b/gb/tests/blargg/cgb_sound/source/common/console.s new file mode 100644 index 0000000..0ec439d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/common/delay.s b/gb/tests/blargg/cgb_sound/source/common/delay.s new file mode 100644 index 0000000..180e3c6 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/common/gb.inc b/gb/tests/blargg/cgb_sound/source/common/gb.inc new file mode 100644 index 0000000..2d0118d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/common/macros.inc b/gb/tests/blargg/cgb_sound/source/common/macros.inc new file mode 100644 index 0000000..ee4646e --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/common/numbers.s b/gb/tests/blargg/cgb_sound/source/common/numbers.s new file mode 100644 index 0000000..5646966 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/common/printing.s b/gb/tests/blargg/cgb_sound/source/common/printing.s new file mode 100644 index 0000000..bb9389b --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/common/shell.s b/gb/tests/blargg/cgb_sound/source/common/shell.s new file mode 100644 index 0000000..b70e417 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/common/testing.s b/gb/tests/blargg/cgb_sound/source/common/testing.s new file mode 100644 index 0000000..17919a5 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/linkfile b/gb/tests/blargg/cgb_sound/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/gb/tests/blargg/cgb_sound/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/gb/tests/blargg/cgb_sound/source/readme.txt b/gb/tests/blargg/cgb_sound/source/readme.txt new file mode 100644 index 0000000..e9cd8d9 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cgb_sound/source/shell.inc b/gb/tests/blargg/cgb_sound/source/shell.inc new file mode 100644 index 0000000..0d43faa --- /dev/null +++ b/gb/tests/blargg/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/tests/blargg/cpu_instrs/cpu_instrs.gb b/gb/tests/blargg/cpu_instrs/cpu_instrs.gb similarity index 100% rename from tests/blargg/cpu_instrs/cpu_instrs.gb rename to gb/tests/blargg/cpu_instrs/cpu_instrs.gb diff --git a/tests/blargg/cpu_instrs/individual/01-special.gb b/gb/tests/blargg/cpu_instrs/individual/01-special.gb similarity index 100% rename from tests/blargg/cpu_instrs/individual/01-special.gb rename to gb/tests/blargg/cpu_instrs/individual/01-special.gb diff --git a/tests/blargg/cpu_instrs/individual/02-interrupts.gb b/gb/tests/blargg/cpu_instrs/individual/02-interrupts.gb similarity index 100% rename from tests/blargg/cpu_instrs/individual/02-interrupts.gb rename to gb/tests/blargg/cpu_instrs/individual/02-interrupts.gb diff --git a/tests/blargg/cpu_instrs/individual/03-op sp,hl.gb b/gb/tests/blargg/cpu_instrs/individual/03-op sp,hl.gb similarity index 100% rename from tests/blargg/cpu_instrs/individual/03-op sp,hl.gb rename to gb/tests/blargg/cpu_instrs/individual/03-op sp,hl.gb diff --git a/tests/blargg/cpu_instrs/individual/04-op r,imm.gb b/gb/tests/blargg/cpu_instrs/individual/04-op r,imm.gb similarity index 100% rename from tests/blargg/cpu_instrs/individual/04-op r,imm.gb rename to gb/tests/blargg/cpu_instrs/individual/04-op r,imm.gb diff --git a/tests/blargg/cpu_instrs/individual/05-op rp.gb b/gb/tests/blargg/cpu_instrs/individual/05-op rp.gb similarity index 100% rename from tests/blargg/cpu_instrs/individual/05-op rp.gb rename to gb/tests/blargg/cpu_instrs/individual/05-op rp.gb diff --git a/tests/blargg/cpu_instrs/individual/06-ld r,r.gb b/gb/tests/blargg/cpu_instrs/individual/06-ld r,r.gb similarity index 100% rename from tests/blargg/cpu_instrs/individual/06-ld r,r.gb rename to gb/tests/blargg/cpu_instrs/individual/06-ld r,r.gb diff --git a/tests/blargg/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb b/gb/tests/blargg/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb similarity index 100% rename from tests/blargg/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb rename to gb/tests/blargg/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb diff --git a/tests/blargg/cpu_instrs/individual/08-misc instrs.gb b/gb/tests/blargg/cpu_instrs/individual/08-misc instrs.gb similarity index 100% rename from tests/blargg/cpu_instrs/individual/08-misc instrs.gb rename to gb/tests/blargg/cpu_instrs/individual/08-misc instrs.gb diff --git a/tests/blargg/cpu_instrs/individual/09-op r,r.gb b/gb/tests/blargg/cpu_instrs/individual/09-op r,r.gb similarity index 100% rename from tests/blargg/cpu_instrs/individual/09-op r,r.gb rename to gb/tests/blargg/cpu_instrs/individual/09-op r,r.gb diff --git a/tests/blargg/cpu_instrs/individual/10-bit ops.gb b/gb/tests/blargg/cpu_instrs/individual/10-bit ops.gb similarity index 100% rename from tests/blargg/cpu_instrs/individual/10-bit ops.gb rename to gb/tests/blargg/cpu_instrs/individual/10-bit ops.gb diff --git a/tests/blargg/cpu_instrs/individual/11-op a,(hl).gb b/gb/tests/blargg/cpu_instrs/individual/11-op a,(hl).gb similarity index 100% rename from tests/blargg/cpu_instrs/individual/11-op a,(hl).gb rename to gb/tests/blargg/cpu_instrs/individual/11-op a,(hl).gb diff --git a/tests/blargg/cpu_instrs/readme.txt b/gb/tests/blargg/cpu_instrs/readme.txt similarity index 100% rename from tests/blargg/cpu_instrs/readme.txt rename to gb/tests/blargg/cpu_instrs/readme.txt diff --git a/gb/tests/blargg/cpu_instrs/source/01-special.s b/gb/tests/blargg/cpu_instrs/source/01-special.s new file mode 100644 index 0000000..64f931c --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/02-interrupts.s b/gb/tests/blargg/cpu_instrs/source/02-interrupts.s new file mode 100644 index 0000000..de0555d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/03-op sp,hl.s b/gb/tests/blargg/cpu_instrs/source/03-op sp,hl.s new file mode 100644 index 0000000..fac0534 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/08-misc instrs.s b/gb/tests/blargg/cpu_instrs/source/08-misc instrs.s new file mode 100644 index 0000000..3c447d0 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/09-op r,r.s b/gb/tests/blargg/cpu_instrs/source/09-op r,r.s new file mode 100644 index 0000000..a9a3fca --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/common/crc_fast.s b/gb/tests/blargg/cpu_instrs/source/common/crc_fast.s new file mode 100644 index 0000000..1772ae5 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/common/delay.s b/gb/tests/blargg/cpu_instrs/source/common/delay.s new file mode 100644 index 0000000..7b43471 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/common/gb.inc b/gb/tests/blargg/cpu_instrs/source/common/gb.inc new file mode 100644 index 0000000..31bbf14 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/common/instr_test.s b/gb/tests/blargg/cpu_instrs/source/common/instr_test.s new file mode 100644 index 0000000..8ddf48f --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/common/macros.inc b/gb/tests/blargg/cpu_instrs/source/common/macros.inc new file mode 100644 index 0000000..8c396c4 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/common/multi_custom.s b/gb/tests/blargg/cpu_instrs/source/common/multi_custom.s new file mode 100644 index 0000000..4dbae9d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/common/numbers.s b/gb/tests/blargg/cpu_instrs/source/common/numbers.s new file mode 100644 index 0000000..5646966 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/common/printing.s b/gb/tests/blargg/cpu_instrs/source/common/printing.s new file mode 100644 index 0000000..ad9d811 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/common/runtime.s b/gb/tests/blargg/cpu_instrs/source/common/runtime.s new file mode 100644 index 0000000..43aac88 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/common/testing.s b/gb/tests/blargg/cpu_instrs/source/common/testing.s new file mode 100644 index 0000000..c8c5aac --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/cpu_instrs/source/linkfile b/gb/tests/blargg/cpu_instrs/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/gb/tests/blargg/cpu_instrs/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/gb/tests/blargg/cpu_instrs/source/shell.inc b/gb/tests/blargg/cpu_instrs/source/shell.inc new file mode 100644 index 0000000..277a9b7 --- /dev/null +++ b/gb/tests/blargg/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/tests/blargg/dmg_sound/dmg_sound.gb b/gb/tests/blargg/dmg_sound/dmg_sound.gb similarity index 100% rename from tests/blargg/dmg_sound/dmg_sound.gb rename to gb/tests/blargg/dmg_sound/dmg_sound.gb diff --git a/tests/blargg/dmg_sound/readme.txt b/gb/tests/blargg/dmg_sound/readme.txt similarity index 100% rename from tests/blargg/dmg_sound/readme.txt rename to gb/tests/blargg/dmg_sound/readme.txt diff --git a/tests/blargg/dmg_sound/rom_singles/01-registers.gb b/gb/tests/blargg/dmg_sound/rom_singles/01-registers.gb similarity index 100% rename from tests/blargg/dmg_sound/rom_singles/01-registers.gb rename to gb/tests/blargg/dmg_sound/rom_singles/01-registers.gb diff --git a/tests/blargg/dmg_sound/rom_singles/02-len ctr.gb b/gb/tests/blargg/dmg_sound/rom_singles/02-len ctr.gb similarity index 100% rename from tests/blargg/dmg_sound/rom_singles/02-len ctr.gb rename to gb/tests/blargg/dmg_sound/rom_singles/02-len ctr.gb diff --git a/tests/blargg/dmg_sound/rom_singles/03-trigger.gb b/gb/tests/blargg/dmg_sound/rom_singles/03-trigger.gb similarity index 100% rename from tests/blargg/dmg_sound/rom_singles/03-trigger.gb rename to gb/tests/blargg/dmg_sound/rom_singles/03-trigger.gb diff --git a/tests/blargg/dmg_sound/rom_singles/04-sweep.gb b/gb/tests/blargg/dmg_sound/rom_singles/04-sweep.gb similarity index 100% rename from tests/blargg/dmg_sound/rom_singles/04-sweep.gb rename to gb/tests/blargg/dmg_sound/rom_singles/04-sweep.gb diff --git a/tests/blargg/dmg_sound/rom_singles/05-sweep details.gb b/gb/tests/blargg/dmg_sound/rom_singles/05-sweep details.gb similarity index 100% rename from tests/blargg/dmg_sound/rom_singles/05-sweep details.gb rename to gb/tests/blargg/dmg_sound/rom_singles/05-sweep details.gb diff --git a/tests/blargg/dmg_sound/rom_singles/06-overflow on trigger.gb b/gb/tests/blargg/dmg_sound/rom_singles/06-overflow on trigger.gb similarity index 100% rename from tests/blargg/dmg_sound/rom_singles/06-overflow on trigger.gb rename to gb/tests/blargg/dmg_sound/rom_singles/06-overflow on trigger.gb diff --git a/tests/blargg/dmg_sound/rom_singles/07-len sweep period sync.gb b/gb/tests/blargg/dmg_sound/rom_singles/07-len sweep period sync.gb similarity index 100% rename from tests/blargg/dmg_sound/rom_singles/07-len sweep period sync.gb rename to gb/tests/blargg/dmg_sound/rom_singles/07-len sweep period sync.gb diff --git a/tests/blargg/dmg_sound/rom_singles/08-len ctr during power.gb b/gb/tests/blargg/dmg_sound/rom_singles/08-len ctr during power.gb similarity index 100% rename from tests/blargg/dmg_sound/rom_singles/08-len ctr during power.gb rename to gb/tests/blargg/dmg_sound/rom_singles/08-len ctr during power.gb diff --git a/tests/blargg/dmg_sound/rom_singles/09-wave read while on.gb b/gb/tests/blargg/dmg_sound/rom_singles/09-wave read while on.gb similarity index 100% rename from tests/blargg/dmg_sound/rom_singles/09-wave read while on.gb rename to gb/tests/blargg/dmg_sound/rom_singles/09-wave read while on.gb diff --git a/tests/blargg/dmg_sound/rom_singles/10-wave trigger while on.gb b/gb/tests/blargg/dmg_sound/rom_singles/10-wave trigger while on.gb similarity index 100% rename from tests/blargg/dmg_sound/rom_singles/10-wave trigger while on.gb rename to gb/tests/blargg/dmg_sound/rom_singles/10-wave trigger while on.gb diff --git a/tests/blargg/dmg_sound/rom_singles/11-regs after power.gb b/gb/tests/blargg/dmg_sound/rom_singles/11-regs after power.gb similarity index 100% rename from tests/blargg/dmg_sound/rom_singles/11-regs after power.gb rename to gb/tests/blargg/dmg_sound/rom_singles/11-regs after power.gb diff --git a/tests/blargg/dmg_sound/rom_singles/12-wave write while on.gb b/gb/tests/blargg/dmg_sound/rom_singles/12-wave write while on.gb similarity index 100% rename from tests/blargg/dmg_sound/rom_singles/12-wave write while on.gb rename to gb/tests/blargg/dmg_sound/rom_singles/12-wave write while on.gb diff --git a/gb/tests/blargg/dmg_sound/source/01-registers.s b/gb/tests/blargg/dmg_sound/source/01-registers.s new file mode 100644 index 0000000..2846344 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/04-sweep.s b/gb/tests/blargg/dmg_sound/source/04-sweep.s new file mode 100644 index 0000000..2e17f35 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/05-sweep details.s b/gb/tests/blargg/dmg_sound/source/05-sweep details.s new file mode 100644 index 0000000..5130d5c --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/06-overflow on trigger.s b/gb/tests/blargg/dmg_sound/source/06-overflow on trigger.s new file mode 100644 index 0000000..cf52679 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/07-len sweep period sync.s b/gb/tests/blargg/dmg_sound/source/07-len sweep period sync.s new file mode 100644 index 0000000..ece4b6d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/08-len ctr during power.s b/gb/tests/blargg/dmg_sound/source/08-len ctr during power.s new file mode 100644 index 0000000..7850d6b --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/09-wave read while on.s b/gb/tests/blargg/dmg_sound/source/09-wave read while on.s new file mode 100644 index 0000000..a9f7b7d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/10-wave trigger while on.s b/gb/tests/blargg/dmg_sound/source/10-wave trigger while on.s new file mode 100644 index 0000000..b1e2432 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/11-regs after power.s b/gb/tests/blargg/dmg_sound/source/11-regs after power.s new file mode 100644 index 0000000..e1b1e6b --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/12-wave write while on.s b/gb/tests/blargg/dmg_sound/source/12-wave write while on.s new file mode 100644 index 0000000..fc45cd8 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/common/build_gbs.s b/gb/tests/blargg/dmg_sound/source/common/build_gbs.s new file mode 100644 index 0000000..3c70c07 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/common/build_rom.s b/gb/tests/blargg/dmg_sound/source/common/build_rom.s new file mode 100644 index 0000000..4ae0351 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/common/console.bin b/gb/tests/blargg/dmg_sound/source/common/console.bin new file mode 100644 index 0000000..b02f2d3 Binary files /dev/null and b/gb/tests/blargg/dmg_sound/source/common/console.bin differ diff --git a/gb/tests/blargg/dmg_sound/source/common/console.s b/gb/tests/blargg/dmg_sound/source/common/console.s new file mode 100644 index 0000000..0ec439d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/common/delay.s b/gb/tests/blargg/dmg_sound/source/common/delay.s new file mode 100644 index 0000000..180e3c6 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/common/gb.inc b/gb/tests/blargg/dmg_sound/source/common/gb.inc new file mode 100644 index 0000000..2d0118d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/common/macros.inc b/gb/tests/blargg/dmg_sound/source/common/macros.inc new file mode 100644 index 0000000..ee4646e --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/common/numbers.s b/gb/tests/blargg/dmg_sound/source/common/numbers.s new file mode 100644 index 0000000..5646966 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/common/printing.s b/gb/tests/blargg/dmg_sound/source/common/printing.s new file mode 100644 index 0000000..bb9389b --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/common/shell.s b/gb/tests/blargg/dmg_sound/source/common/shell.s new file mode 100644 index 0000000..b70e417 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/common/testing.s b/gb/tests/blargg/dmg_sound/source/common/testing.s new file mode 100644 index 0000000..17919a5 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/linkfile b/gb/tests/blargg/dmg_sound/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/gb/tests/blargg/dmg_sound/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/gb/tests/blargg/dmg_sound/source/readme.txt b/gb/tests/blargg/dmg_sound/source/readme.txt new file mode 100644 index 0000000..e9cd8d9 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/dmg_sound/source/shell.inc b/gb/tests/blargg/dmg_sound/source/shell.inc new file mode 100644 index 0000000..0d43faa --- /dev/null +++ b/gb/tests/blargg/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/tests/blargg/halt_bug.gb b/gb/tests/blargg/halt_bug.gb similarity index 100% rename from tests/blargg/halt_bug.gb rename to gb/tests/blargg/halt_bug.gb diff --git a/tests/blargg/instr_timing/instr_timing.gb b/gb/tests/blargg/instr_timing/instr_timing.gb similarity index 100% rename from tests/blargg/instr_timing/instr_timing.gb rename to gb/tests/blargg/instr_timing/instr_timing.gb diff --git a/tests/blargg/instr_timing/readme.txt b/gb/tests/blargg/instr_timing/readme.txt similarity index 100% rename from tests/blargg/instr_timing/readme.txt rename to gb/tests/blargg/instr_timing/readme.txt diff --git a/gb/tests/blargg/instr_timing/source/common/build_gbs.s b/gb/tests/blargg/instr_timing/source/common/build_gbs.s new file mode 100644 index 0000000..b0b5866 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/instr_timing/source/common/build_rom.s b/gb/tests/blargg/instr_timing/source/common/build_rom.s new file mode 100644 index 0000000..6a1f07f --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/instr_timing/source/common/console.bin b/gb/tests/blargg/instr_timing/source/common/console.bin new file mode 100644 index 0000000..b02f2d3 Binary files /dev/null and b/gb/tests/blargg/instr_timing/source/common/console.bin differ diff --git a/gb/tests/blargg/instr_timing/source/common/console.s b/gb/tests/blargg/instr_timing/source/common/console.s new file mode 100644 index 0000000..e98c027 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/instr_timing/source/common/delay.s b/gb/tests/blargg/instr_timing/source/common/delay.s new file mode 100644 index 0000000..7b43471 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/instr_timing/source/common/gb.inc b/gb/tests/blargg/instr_timing/source/common/gb.inc new file mode 100644 index 0000000..31bbf14 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/instr_timing/source/common/macros.inc b/gb/tests/blargg/instr_timing/source/common/macros.inc new file mode 100644 index 0000000..8c396c4 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/instr_timing/source/common/numbers.s b/gb/tests/blargg/instr_timing/source/common/numbers.s new file mode 100644 index 0000000..5646966 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/instr_timing/source/common/printing.s b/gb/tests/blargg/instr_timing/source/common/printing.s new file mode 100644 index 0000000..ad9d811 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/instr_timing/source/common/runtime.s b/gb/tests/blargg/instr_timing/source/common/runtime.s new file mode 100644 index 0000000..43aac88 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/instr_timing/source/common/testing.s b/gb/tests/blargg/instr_timing/source/common/testing.s new file mode 100644 index 0000000..c8c5aac --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/instr_timing/source/common/timer.s b/gb/tests/blargg/instr_timing/source/common/timer.s new file mode 100644 index 0000000..722bce2 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/instr_timing/source/instr_timing.s b/gb/tests/blargg/instr_timing/source/instr_timing.s new file mode 100644 index 0000000..cb5a537 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/instr_timing/source/linkfile b/gb/tests/blargg/instr_timing/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/gb/tests/blargg/instr_timing/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/gb/tests/blargg/instr_timing/source/shell.inc b/gb/tests/blargg/instr_timing/source/shell.inc new file mode 100644 index 0000000..277a9b7 --- /dev/null +++ b/gb/tests/blargg/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/tests/blargg/interrupt_time/interrupt_time.gb b/gb/tests/blargg/interrupt_time/interrupt_time.gb similarity index 100% rename from tests/blargg/interrupt_time/interrupt_time.gb rename to gb/tests/blargg/interrupt_time/interrupt_time.gb diff --git a/gb/tests/blargg/interrupt_time/interrupt_time.s b/gb/tests/blargg/interrupt_time/interrupt_time.s new file mode 100644 index 0000000..431a063 --- /dev/null +++ b/gb/tests/blargg/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/tests/blargg/mem_timing-2/mem_timing.gb b/gb/tests/blargg/mem_timing-2/mem_timing.gb similarity index 100% rename from tests/blargg/mem_timing-2/mem_timing.gb rename to gb/tests/blargg/mem_timing-2/mem_timing.gb diff --git a/gb/tests/blargg/mem_timing-2/mem_timing.sav b/gb/tests/blargg/mem_timing-2/mem_timing.sav new file mode 100644 index 0000000..6d17cf9 Binary files /dev/null and b/gb/tests/blargg/mem_timing-2/mem_timing.sav differ diff --git a/tests/blargg/mem_timing-2/readme.txt b/gb/tests/blargg/mem_timing-2/readme.txt similarity index 100% rename from tests/blargg/mem_timing-2/readme.txt rename to gb/tests/blargg/mem_timing-2/readme.txt diff --git a/tests/blargg/mem_timing-2/rom_singles/01-read_timing.gb b/gb/tests/blargg/mem_timing-2/rom_singles/01-read_timing.gb similarity index 100% rename from tests/blargg/mem_timing-2/rom_singles/01-read_timing.gb rename to gb/tests/blargg/mem_timing-2/rom_singles/01-read_timing.gb diff --git a/tests/blargg/mem_timing-2/rom_singles/02-write_timing.gb b/gb/tests/blargg/mem_timing-2/rom_singles/02-write_timing.gb similarity index 100% rename from tests/blargg/mem_timing-2/rom_singles/02-write_timing.gb rename to gb/tests/blargg/mem_timing-2/rom_singles/02-write_timing.gb diff --git a/tests/blargg/mem_timing-2/rom_singles/03-modify_timing.gb b/gb/tests/blargg/mem_timing-2/rom_singles/03-modify_timing.gb similarity index 100% rename from tests/blargg/mem_timing-2/rom_singles/03-modify_timing.gb rename to gb/tests/blargg/mem_timing-2/rom_singles/03-modify_timing.gb diff --git a/gb/tests/blargg/mem_timing-2/source/01-read_timing.s b/gb/tests/blargg/mem_timing-2/source/01-read_timing.s new file mode 100644 index 0000000..8849322 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/02-write_timing.s b/gb/tests/blargg/mem_timing-2/source/02-write_timing.s new file mode 100644 index 0000000..4281403 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/common/build_gbs.s b/gb/tests/blargg/mem_timing-2/source/common/build_gbs.s new file mode 100644 index 0000000..3c70c07 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/common/build_rom.s b/gb/tests/blargg/mem_timing-2/source/common/build_rom.s new file mode 100644 index 0000000..ee91181 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/common/console.bin b/gb/tests/blargg/mem_timing-2/source/common/console.bin new file mode 100644 index 0000000..b02f2d3 Binary files /dev/null and b/gb/tests/blargg/mem_timing-2/source/common/console.bin differ diff --git a/gb/tests/blargg/mem_timing-2/source/common/console.s b/gb/tests/blargg/mem_timing-2/source/common/console.s new file mode 100644 index 0000000..0ec439d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/common/delay.s b/gb/tests/blargg/mem_timing-2/source/common/delay.s new file mode 100644 index 0000000..180e3c6 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/common/gb.inc b/gb/tests/blargg/mem_timing-2/source/common/gb.inc new file mode 100644 index 0000000..2d0118d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/common/macros.inc b/gb/tests/blargg/mem_timing-2/source/common/macros.inc new file mode 100644 index 0000000..ee4646e --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/common/numbers.s b/gb/tests/blargg/mem_timing-2/source/common/numbers.s new file mode 100644 index 0000000..5646966 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/common/printing.s b/gb/tests/blargg/mem_timing-2/source/common/printing.s new file mode 100644 index 0000000..bb9389b --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/common/shell.s b/gb/tests/blargg/mem_timing-2/source/common/shell.s new file mode 100644 index 0000000..d47c4a9 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/common/testing.s b/gb/tests/blargg/mem_timing-2/source/common/testing.s new file mode 100644 index 0000000..17919a5 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/common/tima_64.s b/gb/tests/blargg/mem_timing-2/source/common/tima_64.s new file mode 100644 index 0000000..ff40e07 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/linkfile b/gb/tests/blargg/mem_timing-2/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/gb/tests/blargg/mem_timing-2/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/gb/tests/blargg/mem_timing-2/source/readme.txt b/gb/tests/blargg/mem_timing-2/source/readme.txt new file mode 100644 index 0000000..e9cd8d9 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing-2/source/shell.inc b/gb/tests/blargg/mem_timing-2/source/shell.inc new file mode 100644 index 0000000..0d43faa --- /dev/null +++ b/gb/tests/blargg/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/tests/blargg/mem_timing/individual/01-read_timing.gb b/gb/tests/blargg/mem_timing/individual/01-read_timing.gb similarity index 100% rename from tests/blargg/mem_timing/individual/01-read_timing.gb rename to gb/tests/blargg/mem_timing/individual/01-read_timing.gb diff --git a/tests/blargg/mem_timing/individual/02-write_timing.gb b/gb/tests/blargg/mem_timing/individual/02-write_timing.gb similarity index 100% rename from tests/blargg/mem_timing/individual/02-write_timing.gb rename to gb/tests/blargg/mem_timing/individual/02-write_timing.gb diff --git a/tests/blargg/mem_timing/individual/03-modify_timing.gb b/gb/tests/blargg/mem_timing/individual/03-modify_timing.gb similarity index 100% rename from tests/blargg/mem_timing/individual/03-modify_timing.gb rename to gb/tests/blargg/mem_timing/individual/03-modify_timing.gb diff --git a/tests/blargg/mem_timing/mem_timing.gb b/gb/tests/blargg/mem_timing/mem_timing.gb similarity index 100% rename from tests/blargg/mem_timing/mem_timing.gb rename to gb/tests/blargg/mem_timing/mem_timing.gb diff --git a/tests/blargg/mem_timing/readme.txt b/gb/tests/blargg/mem_timing/readme.txt similarity index 100% rename from tests/blargg/mem_timing/readme.txt rename to gb/tests/blargg/mem_timing/readme.txt diff --git a/gb/tests/blargg/mem_timing/source/01-read_timing.s b/gb/tests/blargg/mem_timing/source/01-read_timing.s new file mode 100644 index 0000000..b159342 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/02-write_timing.s b/gb/tests/blargg/mem_timing/source/02-write_timing.s new file mode 100644 index 0000000..4281403 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/common/build_gbs.s b/gb/tests/blargg/mem_timing/source/common/build_gbs.s new file mode 100644 index 0000000..b0b5866 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/common/build_rom.s b/gb/tests/blargg/mem_timing/source/common/build_rom.s new file mode 100644 index 0000000..6a1f07f --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/common/console.bin b/gb/tests/blargg/mem_timing/source/common/console.bin new file mode 100644 index 0000000..b02f2d3 Binary files /dev/null and b/gb/tests/blargg/mem_timing/source/common/console.bin differ diff --git a/gb/tests/blargg/mem_timing/source/common/console.s b/gb/tests/blargg/mem_timing/source/common/console.s new file mode 100644 index 0000000..e98c027 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/common/delay.s b/gb/tests/blargg/mem_timing/source/common/delay.s new file mode 100644 index 0000000..7b43471 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/common/gb.inc b/gb/tests/blargg/mem_timing/source/common/gb.inc new file mode 100644 index 0000000..31bbf14 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/common/macros.inc b/gb/tests/blargg/mem_timing/source/common/macros.inc new file mode 100644 index 0000000..8c396c4 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/common/numbers.s b/gb/tests/blargg/mem_timing/source/common/numbers.s new file mode 100644 index 0000000..5646966 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/common/printing.s b/gb/tests/blargg/mem_timing/source/common/printing.s new file mode 100644 index 0000000..ad9d811 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/common/runtime.s b/gb/tests/blargg/mem_timing/source/common/runtime.s new file mode 100644 index 0000000..43aac88 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/common/testing.s b/gb/tests/blargg/mem_timing/source/common/testing.s new file mode 100644 index 0000000..c8c5aac --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/common/tima_64.s b/gb/tests/blargg/mem_timing/source/common/tima_64.s new file mode 100644 index 0000000..eb4f66b --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/mem_timing/source/linkfile b/gb/tests/blargg/mem_timing/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/gb/tests/blargg/mem_timing/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/gb/tests/blargg/mem_timing/source/shell.inc b/gb/tests/blargg/mem_timing/source/shell.inc new file mode 100644 index 0000000..277a9b7 --- /dev/null +++ b/gb/tests/blargg/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/tests/blargg/oam_bug/oam_bug.gb b/gb/tests/blargg/oam_bug/oam_bug.gb similarity index 100% rename from tests/blargg/oam_bug/oam_bug.gb rename to gb/tests/blargg/oam_bug/oam_bug.gb diff --git a/gb/tests/blargg/oam_bug/oam_bug.sav b/gb/tests/blargg/oam_bug/oam_bug.sav new file mode 100644 index 0000000..6d17cf9 Binary files /dev/null and b/gb/tests/blargg/oam_bug/oam_bug.sav differ diff --git a/tests/blargg/oam_bug/readme.txt b/gb/tests/blargg/oam_bug/readme.txt similarity index 100% rename from tests/blargg/oam_bug/readme.txt rename to gb/tests/blargg/oam_bug/readme.txt diff --git a/tests/blargg/oam_bug/rom_singles/1-lcd_sync.gb b/gb/tests/blargg/oam_bug/rom_singles/1-lcd_sync.gb similarity index 100% rename from tests/blargg/oam_bug/rom_singles/1-lcd_sync.gb rename to gb/tests/blargg/oam_bug/rom_singles/1-lcd_sync.gb diff --git a/tests/blargg/oam_bug/rom_singles/2-causes.gb b/gb/tests/blargg/oam_bug/rom_singles/2-causes.gb similarity index 100% rename from tests/blargg/oam_bug/rom_singles/2-causes.gb rename to gb/tests/blargg/oam_bug/rom_singles/2-causes.gb diff --git a/tests/blargg/oam_bug/rom_singles/3-non_causes.gb b/gb/tests/blargg/oam_bug/rom_singles/3-non_causes.gb similarity index 100% rename from tests/blargg/oam_bug/rom_singles/3-non_causes.gb rename to gb/tests/blargg/oam_bug/rom_singles/3-non_causes.gb diff --git a/tests/blargg/oam_bug/rom_singles/4-scanline_timing.gb b/gb/tests/blargg/oam_bug/rom_singles/4-scanline_timing.gb similarity index 100% rename from tests/blargg/oam_bug/rom_singles/4-scanline_timing.gb rename to gb/tests/blargg/oam_bug/rom_singles/4-scanline_timing.gb diff --git a/tests/blargg/oam_bug/rom_singles/5-timing_bug.gb b/gb/tests/blargg/oam_bug/rom_singles/5-timing_bug.gb similarity index 100% rename from tests/blargg/oam_bug/rom_singles/5-timing_bug.gb rename to gb/tests/blargg/oam_bug/rom_singles/5-timing_bug.gb diff --git a/tests/blargg/oam_bug/rom_singles/6-timing_no_bug.gb b/gb/tests/blargg/oam_bug/rom_singles/6-timing_no_bug.gb similarity index 100% rename from tests/blargg/oam_bug/rom_singles/6-timing_no_bug.gb rename to gb/tests/blargg/oam_bug/rom_singles/6-timing_no_bug.gb diff --git a/tests/blargg/oam_bug/rom_singles/7-timing_effect.gb b/gb/tests/blargg/oam_bug/rom_singles/7-timing_effect.gb similarity index 100% rename from tests/blargg/oam_bug/rom_singles/7-timing_effect.gb rename to gb/tests/blargg/oam_bug/rom_singles/7-timing_effect.gb diff --git a/tests/blargg/oam_bug/rom_singles/8-instr_effect.gb b/gb/tests/blargg/oam_bug/rom_singles/8-instr_effect.gb similarity index 100% rename from tests/blargg/oam_bug/rom_singles/8-instr_effect.gb rename to gb/tests/blargg/oam_bug/rom_singles/8-instr_effect.gb diff --git a/gb/tests/blargg/oam_bug/source/1-lcd_sync.s b/gb/tests/blargg/oam_bug/source/1-lcd_sync.s new file mode 100644 index 0000000..c21aa4d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/2-causes.s b/gb/tests/blargg/oam_bug/source/2-causes.s new file mode 100644 index 0000000..43c8dce --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/3-non_causes.s b/gb/tests/blargg/oam_bug/source/3-non_causes.s new file mode 100644 index 0000000..130475e --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/4-scanline_timing.s b/gb/tests/blargg/oam_bug/source/4-scanline_timing.s new file mode 100644 index 0000000..f1e4bf9 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/5-timing_bug.s b/gb/tests/blargg/oam_bug/source/5-timing_bug.s new file mode 100644 index 0000000..2d11446 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/6-timing_no_bug.s b/gb/tests/blargg/oam_bug/source/6-timing_no_bug.s new file mode 100644 index 0000000..b85a96f --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/7-timing_effect.s b/gb/tests/blargg/oam_bug/source/7-timing_effect.s new file mode 100644 index 0000000..a558868 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/8-instr_effect.s b/gb/tests/blargg/oam_bug/source/8-instr_effect.s new file mode 100644 index 0000000..7034373 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/common/build_gbs.s b/gb/tests/blargg/oam_bug/source/common/build_gbs.s new file mode 100644 index 0000000..3c70c07 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/common/build_rom.s b/gb/tests/blargg/oam_bug/source/common/build_rom.s new file mode 100644 index 0000000..4ae0351 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/common/console.bin b/gb/tests/blargg/oam_bug/source/common/console.bin new file mode 100644 index 0000000..b02f2d3 Binary files /dev/null and b/gb/tests/blargg/oam_bug/source/common/console.bin differ diff --git a/gb/tests/blargg/oam_bug/source/common/console.s b/gb/tests/blargg/oam_bug/source/common/console.s new file mode 100644 index 0000000..0ec439d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/common/delay.s b/gb/tests/blargg/oam_bug/source/common/delay.s new file mode 100644 index 0000000..180e3c6 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/common/gb.inc b/gb/tests/blargg/oam_bug/source/common/gb.inc new file mode 100644 index 0000000..2d0118d --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/common/macros.inc b/gb/tests/blargg/oam_bug/source/common/macros.inc new file mode 100644 index 0000000..ee4646e --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/common/numbers.s b/gb/tests/blargg/oam_bug/source/common/numbers.s new file mode 100644 index 0000000..5646966 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/common/ppu.s b/gb/tests/blargg/oam_bug/source/common/ppu.s new file mode 100644 index 0000000..27ccb3f --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/common/printing.s b/gb/tests/blargg/oam_bug/source/common/printing.s new file mode 100644 index 0000000..bb9389b --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/common/shell.s b/gb/tests/blargg/oam_bug/source/common/shell.s new file mode 100644 index 0000000..b70e417 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/common/testing.s b/gb/tests/blargg/oam_bug/source/common/testing.s new file mode 100644 index 0000000..17919a5 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/linkfile b/gb/tests/blargg/oam_bug/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/gb/tests/blargg/oam_bug/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/gb/tests/blargg/oam_bug/source/readme.txt b/gb/tests/blargg/oam_bug/source/readme.txt new file mode 100644 index 0000000..e9cd8d9 --- /dev/null +++ b/gb/tests/blargg/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/gb/tests/blargg/oam_bug/source/shell.inc b/gb/tests/blargg/oam_bug/source/shell.inc new file mode 100644 index 0000000..0d43faa --- /dev/null +++ b/gb/tests/blargg/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/tests/blargg/readme.txt b/gb/tests/blargg/readme.txt similarity index 100% rename from tests/blargg/readme.txt rename to gb/tests/blargg/readme.txt diff --git a/tests/gb_tests.rs b/gb/tests/gb_tests.rs similarity index 97% rename from tests/gb_tests.rs rename to gb/tests/gb_tests.rs index 0555cb8..ca72df5 100644 --- a/tests/gb_tests.rs +++ b/gb/tests/gb_tests.rs @@ -1,4 +1,4 @@ -use engram::components::{gameboy::GameBoy, rom::cartridge::Cartridge}; +use engram_gb::components::{gameboy::GameBoy, rom::cartridge::Cartridge}; use std::path::PathBuf; fn run_blargg_cpu_rom(rom: &str) { diff --git a/tests/sm83/LICENSE b/gb/tests/sm83/LICENSE similarity index 100% rename from tests/sm83/LICENSE rename to gb/tests/sm83/LICENSE diff --git a/tests/sm83/README.md b/gb/tests/sm83/README.md similarity index 100% rename from tests/sm83/README.md rename to gb/tests/sm83/README.md diff --git a/tests/sm83/v2/00.json b/gb/tests/sm83/v2/00.json similarity index 100% rename from tests/sm83/v2/00.json rename to gb/tests/sm83/v2/00.json diff --git a/tests/sm83/v2/01.json b/gb/tests/sm83/v2/01.json similarity index 100% rename from tests/sm83/v2/01.json rename to gb/tests/sm83/v2/01.json diff --git a/tests/sm83/v2/02.json b/gb/tests/sm83/v2/02.json similarity index 100% rename from tests/sm83/v2/02.json rename to gb/tests/sm83/v2/02.json diff --git a/tests/sm83/v2/03.json b/gb/tests/sm83/v2/03.json similarity index 100% rename from tests/sm83/v2/03.json rename to gb/tests/sm83/v2/03.json diff --git a/tests/sm83/v2/04.json b/gb/tests/sm83/v2/04.json similarity index 100% rename from tests/sm83/v2/04.json rename to gb/tests/sm83/v2/04.json diff --git a/tests/sm83/v2/05.json b/gb/tests/sm83/v2/05.json similarity index 100% rename from tests/sm83/v2/05.json rename to gb/tests/sm83/v2/05.json diff --git a/tests/sm83/v2/06.json b/gb/tests/sm83/v2/06.json similarity index 100% rename from tests/sm83/v2/06.json rename to gb/tests/sm83/v2/06.json diff --git a/tests/sm83/v2/07.json b/gb/tests/sm83/v2/07.json similarity index 100% rename from tests/sm83/v2/07.json rename to gb/tests/sm83/v2/07.json diff --git a/tests/sm83/v2/08.json b/gb/tests/sm83/v2/08.json similarity index 100% rename from tests/sm83/v2/08.json rename to gb/tests/sm83/v2/08.json diff --git a/tests/sm83/v2/09.json b/gb/tests/sm83/v2/09.json similarity index 100% rename from tests/sm83/v2/09.json rename to gb/tests/sm83/v2/09.json diff --git a/tests/sm83/v2/0a.json b/gb/tests/sm83/v2/0a.json similarity index 100% rename from tests/sm83/v2/0a.json rename to gb/tests/sm83/v2/0a.json diff --git a/tests/sm83/v2/0b.json b/gb/tests/sm83/v2/0b.json similarity index 100% rename from tests/sm83/v2/0b.json rename to gb/tests/sm83/v2/0b.json diff --git a/tests/sm83/v2/0c.json b/gb/tests/sm83/v2/0c.json similarity index 100% rename from tests/sm83/v2/0c.json rename to gb/tests/sm83/v2/0c.json diff --git a/tests/sm83/v2/0d.json b/gb/tests/sm83/v2/0d.json similarity index 100% rename from tests/sm83/v2/0d.json rename to gb/tests/sm83/v2/0d.json diff --git a/tests/sm83/v2/0e.json b/gb/tests/sm83/v2/0e.json similarity index 100% rename from tests/sm83/v2/0e.json rename to gb/tests/sm83/v2/0e.json diff --git a/tests/sm83/v2/0f.json b/gb/tests/sm83/v2/0f.json similarity index 100% rename from tests/sm83/v2/0f.json rename to gb/tests/sm83/v2/0f.json diff --git a/tests/sm83/v2/11.json b/gb/tests/sm83/v2/11.json similarity index 100% rename from tests/sm83/v2/11.json rename to gb/tests/sm83/v2/11.json diff --git a/tests/sm83/v2/12.json b/gb/tests/sm83/v2/12.json similarity index 100% rename from tests/sm83/v2/12.json rename to gb/tests/sm83/v2/12.json diff --git a/tests/sm83/v2/13.json b/gb/tests/sm83/v2/13.json similarity index 100% rename from tests/sm83/v2/13.json rename to gb/tests/sm83/v2/13.json diff --git a/tests/sm83/v2/14.json b/gb/tests/sm83/v2/14.json similarity index 100% rename from tests/sm83/v2/14.json rename to gb/tests/sm83/v2/14.json diff --git a/tests/sm83/v2/15.json b/gb/tests/sm83/v2/15.json similarity index 100% rename from tests/sm83/v2/15.json rename to gb/tests/sm83/v2/15.json diff --git a/tests/sm83/v2/16.json b/gb/tests/sm83/v2/16.json similarity index 100% rename from tests/sm83/v2/16.json rename to gb/tests/sm83/v2/16.json diff --git a/tests/sm83/v2/17.json b/gb/tests/sm83/v2/17.json similarity index 100% rename from tests/sm83/v2/17.json rename to gb/tests/sm83/v2/17.json diff --git a/tests/sm83/v2/18.json b/gb/tests/sm83/v2/18.json similarity index 100% rename from tests/sm83/v2/18.json rename to gb/tests/sm83/v2/18.json diff --git a/tests/sm83/v2/19.json b/gb/tests/sm83/v2/19.json similarity index 100% rename from tests/sm83/v2/19.json rename to gb/tests/sm83/v2/19.json diff --git a/tests/sm83/v2/1a.json b/gb/tests/sm83/v2/1a.json similarity index 100% rename from tests/sm83/v2/1a.json rename to gb/tests/sm83/v2/1a.json diff --git a/tests/sm83/v2/1b.json b/gb/tests/sm83/v2/1b.json similarity index 100% rename from tests/sm83/v2/1b.json rename to gb/tests/sm83/v2/1b.json diff --git a/tests/sm83/v2/1c.json b/gb/tests/sm83/v2/1c.json similarity index 100% rename from tests/sm83/v2/1c.json rename to gb/tests/sm83/v2/1c.json diff --git a/tests/sm83/v2/1d.json b/gb/tests/sm83/v2/1d.json similarity index 100% rename from tests/sm83/v2/1d.json rename to gb/tests/sm83/v2/1d.json diff --git a/tests/sm83/v2/1e.json b/gb/tests/sm83/v2/1e.json similarity index 100% rename from tests/sm83/v2/1e.json rename to gb/tests/sm83/v2/1e.json diff --git a/tests/sm83/v2/1f.json b/gb/tests/sm83/v2/1f.json similarity index 100% rename from tests/sm83/v2/1f.json rename to gb/tests/sm83/v2/1f.json diff --git a/tests/sm83/v2/20.json b/gb/tests/sm83/v2/20.json similarity index 100% rename from tests/sm83/v2/20.json rename to gb/tests/sm83/v2/20.json diff --git a/tests/sm83/v2/21.json b/gb/tests/sm83/v2/21.json similarity index 100% rename from tests/sm83/v2/21.json rename to gb/tests/sm83/v2/21.json diff --git a/tests/sm83/v2/22.json b/gb/tests/sm83/v2/22.json similarity index 100% rename from tests/sm83/v2/22.json rename to gb/tests/sm83/v2/22.json diff --git a/tests/sm83/v2/23.json b/gb/tests/sm83/v2/23.json similarity index 100% rename from tests/sm83/v2/23.json rename to gb/tests/sm83/v2/23.json diff --git a/tests/sm83/v2/24.json b/gb/tests/sm83/v2/24.json similarity index 100% rename from tests/sm83/v2/24.json rename to gb/tests/sm83/v2/24.json diff --git a/tests/sm83/v2/25.json b/gb/tests/sm83/v2/25.json similarity index 100% rename from tests/sm83/v2/25.json rename to gb/tests/sm83/v2/25.json diff --git a/tests/sm83/v2/26.json b/gb/tests/sm83/v2/26.json similarity index 100% rename from tests/sm83/v2/26.json rename to gb/tests/sm83/v2/26.json diff --git a/tests/sm83/v2/27.json b/gb/tests/sm83/v2/27.json similarity index 100% rename from tests/sm83/v2/27.json rename to gb/tests/sm83/v2/27.json diff --git a/tests/sm83/v2/28.json b/gb/tests/sm83/v2/28.json similarity index 100% rename from tests/sm83/v2/28.json rename to gb/tests/sm83/v2/28.json diff --git a/tests/sm83/v2/29.json b/gb/tests/sm83/v2/29.json similarity index 100% rename from tests/sm83/v2/29.json rename to gb/tests/sm83/v2/29.json diff --git a/tests/sm83/v2/2a.json b/gb/tests/sm83/v2/2a.json similarity index 100% rename from tests/sm83/v2/2a.json rename to gb/tests/sm83/v2/2a.json diff --git a/tests/sm83/v2/2b.json b/gb/tests/sm83/v2/2b.json similarity index 100% rename from tests/sm83/v2/2b.json rename to gb/tests/sm83/v2/2b.json diff --git a/tests/sm83/v2/2c.json b/gb/tests/sm83/v2/2c.json similarity index 100% rename from tests/sm83/v2/2c.json rename to gb/tests/sm83/v2/2c.json diff --git a/tests/sm83/v2/2d.json b/gb/tests/sm83/v2/2d.json similarity index 100% rename from tests/sm83/v2/2d.json rename to gb/tests/sm83/v2/2d.json diff --git a/tests/sm83/v2/2e.json b/gb/tests/sm83/v2/2e.json similarity index 100% rename from tests/sm83/v2/2e.json rename to gb/tests/sm83/v2/2e.json diff --git a/tests/sm83/v2/2f.json b/gb/tests/sm83/v2/2f.json similarity index 100% rename from tests/sm83/v2/2f.json rename to gb/tests/sm83/v2/2f.json diff --git a/tests/sm83/v2/30.json b/gb/tests/sm83/v2/30.json similarity index 100% rename from tests/sm83/v2/30.json rename to gb/tests/sm83/v2/30.json diff --git a/tests/sm83/v2/31.json b/gb/tests/sm83/v2/31.json similarity index 100% rename from tests/sm83/v2/31.json rename to gb/tests/sm83/v2/31.json diff --git a/tests/sm83/v2/32.json b/gb/tests/sm83/v2/32.json similarity index 100% rename from tests/sm83/v2/32.json rename to gb/tests/sm83/v2/32.json diff --git a/tests/sm83/v2/33.json b/gb/tests/sm83/v2/33.json similarity index 100% rename from tests/sm83/v2/33.json rename to gb/tests/sm83/v2/33.json diff --git a/tests/sm83/v2/34.json b/gb/tests/sm83/v2/34.json similarity index 100% rename from tests/sm83/v2/34.json rename to gb/tests/sm83/v2/34.json diff --git a/tests/sm83/v2/35.json b/gb/tests/sm83/v2/35.json similarity index 100% rename from tests/sm83/v2/35.json rename to gb/tests/sm83/v2/35.json diff --git a/tests/sm83/v2/36.json b/gb/tests/sm83/v2/36.json similarity index 100% rename from tests/sm83/v2/36.json rename to gb/tests/sm83/v2/36.json diff --git a/tests/sm83/v2/37.json b/gb/tests/sm83/v2/37.json similarity index 100% rename from tests/sm83/v2/37.json rename to gb/tests/sm83/v2/37.json diff --git a/tests/sm83/v2/38.json b/gb/tests/sm83/v2/38.json similarity index 100% rename from tests/sm83/v2/38.json rename to gb/tests/sm83/v2/38.json diff --git a/tests/sm83/v2/39.json b/gb/tests/sm83/v2/39.json similarity index 100% rename from tests/sm83/v2/39.json rename to gb/tests/sm83/v2/39.json diff --git a/tests/sm83/v2/3a.json b/gb/tests/sm83/v2/3a.json similarity index 100% rename from tests/sm83/v2/3a.json rename to gb/tests/sm83/v2/3a.json diff --git a/tests/sm83/v2/3b.json b/gb/tests/sm83/v2/3b.json similarity index 100% rename from tests/sm83/v2/3b.json rename to gb/tests/sm83/v2/3b.json diff --git a/tests/sm83/v2/3c.json b/gb/tests/sm83/v2/3c.json similarity index 100% rename from tests/sm83/v2/3c.json rename to gb/tests/sm83/v2/3c.json diff --git a/tests/sm83/v2/3d.json b/gb/tests/sm83/v2/3d.json similarity index 100% rename from tests/sm83/v2/3d.json rename to gb/tests/sm83/v2/3d.json diff --git a/tests/sm83/v2/3e.json b/gb/tests/sm83/v2/3e.json similarity index 100% rename from tests/sm83/v2/3e.json rename to gb/tests/sm83/v2/3e.json diff --git a/tests/sm83/v2/3f.json b/gb/tests/sm83/v2/3f.json similarity index 100% rename from tests/sm83/v2/3f.json rename to gb/tests/sm83/v2/3f.json diff --git a/tests/sm83/v2/40.json b/gb/tests/sm83/v2/40.json similarity index 100% rename from tests/sm83/v2/40.json rename to gb/tests/sm83/v2/40.json diff --git a/tests/sm83/v2/41.json b/gb/tests/sm83/v2/41.json similarity index 100% rename from tests/sm83/v2/41.json rename to gb/tests/sm83/v2/41.json diff --git a/tests/sm83/v2/42.json b/gb/tests/sm83/v2/42.json similarity index 100% rename from tests/sm83/v2/42.json rename to gb/tests/sm83/v2/42.json diff --git a/tests/sm83/v2/43.json b/gb/tests/sm83/v2/43.json similarity index 100% rename from tests/sm83/v2/43.json rename to gb/tests/sm83/v2/43.json diff --git a/tests/sm83/v2/44.json b/gb/tests/sm83/v2/44.json similarity index 100% rename from tests/sm83/v2/44.json rename to gb/tests/sm83/v2/44.json diff --git a/tests/sm83/v2/45.json b/gb/tests/sm83/v2/45.json similarity index 100% rename from tests/sm83/v2/45.json rename to gb/tests/sm83/v2/45.json diff --git a/tests/sm83/v2/46.json b/gb/tests/sm83/v2/46.json similarity index 100% rename from tests/sm83/v2/46.json rename to gb/tests/sm83/v2/46.json diff --git a/tests/sm83/v2/47.json b/gb/tests/sm83/v2/47.json similarity index 100% rename from tests/sm83/v2/47.json rename to gb/tests/sm83/v2/47.json diff --git a/tests/sm83/v2/48.json b/gb/tests/sm83/v2/48.json similarity index 100% rename from tests/sm83/v2/48.json rename to gb/tests/sm83/v2/48.json diff --git a/tests/sm83/v2/49.json b/gb/tests/sm83/v2/49.json similarity index 100% rename from tests/sm83/v2/49.json rename to gb/tests/sm83/v2/49.json diff --git a/tests/sm83/v2/4a.json b/gb/tests/sm83/v2/4a.json similarity index 100% rename from tests/sm83/v2/4a.json rename to gb/tests/sm83/v2/4a.json diff --git a/tests/sm83/v2/4b.json b/gb/tests/sm83/v2/4b.json similarity index 100% rename from tests/sm83/v2/4b.json rename to gb/tests/sm83/v2/4b.json diff --git a/tests/sm83/v2/4c.json b/gb/tests/sm83/v2/4c.json similarity index 100% rename from tests/sm83/v2/4c.json rename to gb/tests/sm83/v2/4c.json diff --git a/tests/sm83/v2/4d.json b/gb/tests/sm83/v2/4d.json similarity index 100% rename from tests/sm83/v2/4d.json rename to gb/tests/sm83/v2/4d.json diff --git a/tests/sm83/v2/4e.json b/gb/tests/sm83/v2/4e.json similarity index 100% rename from tests/sm83/v2/4e.json rename to gb/tests/sm83/v2/4e.json diff --git a/tests/sm83/v2/4f.json b/gb/tests/sm83/v2/4f.json similarity index 100% rename from tests/sm83/v2/4f.json rename to gb/tests/sm83/v2/4f.json diff --git a/tests/sm83/v2/50.json b/gb/tests/sm83/v2/50.json similarity index 100% rename from tests/sm83/v2/50.json rename to gb/tests/sm83/v2/50.json diff --git a/tests/sm83/v2/51.json b/gb/tests/sm83/v2/51.json similarity index 100% rename from tests/sm83/v2/51.json rename to gb/tests/sm83/v2/51.json diff --git a/tests/sm83/v2/52.json b/gb/tests/sm83/v2/52.json similarity index 100% rename from tests/sm83/v2/52.json rename to gb/tests/sm83/v2/52.json diff --git a/tests/sm83/v2/53.json b/gb/tests/sm83/v2/53.json similarity index 100% rename from tests/sm83/v2/53.json rename to gb/tests/sm83/v2/53.json diff --git a/tests/sm83/v2/54.json b/gb/tests/sm83/v2/54.json similarity index 100% rename from tests/sm83/v2/54.json rename to gb/tests/sm83/v2/54.json diff --git a/tests/sm83/v2/55.json b/gb/tests/sm83/v2/55.json similarity index 100% rename from tests/sm83/v2/55.json rename to gb/tests/sm83/v2/55.json diff --git a/tests/sm83/v2/56.json b/gb/tests/sm83/v2/56.json similarity index 100% rename from tests/sm83/v2/56.json rename to gb/tests/sm83/v2/56.json diff --git a/tests/sm83/v2/57.json b/gb/tests/sm83/v2/57.json similarity index 100% rename from tests/sm83/v2/57.json rename to gb/tests/sm83/v2/57.json diff --git a/tests/sm83/v2/58.json b/gb/tests/sm83/v2/58.json similarity index 100% rename from tests/sm83/v2/58.json rename to gb/tests/sm83/v2/58.json diff --git a/tests/sm83/v2/59.json b/gb/tests/sm83/v2/59.json similarity index 100% rename from tests/sm83/v2/59.json rename to gb/tests/sm83/v2/59.json diff --git a/tests/sm83/v2/5a.json b/gb/tests/sm83/v2/5a.json similarity index 100% rename from tests/sm83/v2/5a.json rename to gb/tests/sm83/v2/5a.json diff --git a/tests/sm83/v2/5b.json b/gb/tests/sm83/v2/5b.json similarity index 100% rename from tests/sm83/v2/5b.json rename to gb/tests/sm83/v2/5b.json diff --git a/tests/sm83/v2/5c.json b/gb/tests/sm83/v2/5c.json similarity index 100% rename from tests/sm83/v2/5c.json rename to gb/tests/sm83/v2/5c.json diff --git a/tests/sm83/v2/5d.json b/gb/tests/sm83/v2/5d.json similarity index 100% rename from tests/sm83/v2/5d.json rename to gb/tests/sm83/v2/5d.json diff --git a/tests/sm83/v2/5e.json b/gb/tests/sm83/v2/5e.json similarity index 100% rename from tests/sm83/v2/5e.json rename to gb/tests/sm83/v2/5e.json diff --git a/tests/sm83/v2/5f.json b/gb/tests/sm83/v2/5f.json similarity index 100% rename from tests/sm83/v2/5f.json rename to gb/tests/sm83/v2/5f.json diff --git a/tests/sm83/v2/60.json b/gb/tests/sm83/v2/60.json similarity index 100% rename from tests/sm83/v2/60.json rename to gb/tests/sm83/v2/60.json diff --git a/tests/sm83/v2/61.json b/gb/tests/sm83/v2/61.json similarity index 100% rename from tests/sm83/v2/61.json rename to gb/tests/sm83/v2/61.json diff --git a/tests/sm83/v2/62.json b/gb/tests/sm83/v2/62.json similarity index 100% rename from tests/sm83/v2/62.json rename to gb/tests/sm83/v2/62.json diff --git a/tests/sm83/v2/63.json b/gb/tests/sm83/v2/63.json similarity index 100% rename from tests/sm83/v2/63.json rename to gb/tests/sm83/v2/63.json diff --git a/tests/sm83/v2/64.json b/gb/tests/sm83/v2/64.json similarity index 100% rename from tests/sm83/v2/64.json rename to gb/tests/sm83/v2/64.json diff --git a/tests/sm83/v2/65.json b/gb/tests/sm83/v2/65.json similarity index 100% rename from tests/sm83/v2/65.json rename to gb/tests/sm83/v2/65.json diff --git a/tests/sm83/v2/66.json b/gb/tests/sm83/v2/66.json similarity index 100% rename from tests/sm83/v2/66.json rename to gb/tests/sm83/v2/66.json diff --git a/tests/sm83/v2/67.json b/gb/tests/sm83/v2/67.json similarity index 100% rename from tests/sm83/v2/67.json rename to gb/tests/sm83/v2/67.json diff --git a/tests/sm83/v2/68.json b/gb/tests/sm83/v2/68.json similarity index 100% rename from tests/sm83/v2/68.json rename to gb/tests/sm83/v2/68.json diff --git a/tests/sm83/v2/69.json b/gb/tests/sm83/v2/69.json similarity index 100% rename from tests/sm83/v2/69.json rename to gb/tests/sm83/v2/69.json diff --git a/tests/sm83/v2/6a.json b/gb/tests/sm83/v2/6a.json similarity index 100% rename from tests/sm83/v2/6a.json rename to gb/tests/sm83/v2/6a.json diff --git a/tests/sm83/v2/6b.json b/gb/tests/sm83/v2/6b.json similarity index 100% rename from tests/sm83/v2/6b.json rename to gb/tests/sm83/v2/6b.json diff --git a/tests/sm83/v2/6c.json b/gb/tests/sm83/v2/6c.json similarity index 100% rename from tests/sm83/v2/6c.json rename to gb/tests/sm83/v2/6c.json diff --git a/tests/sm83/v2/6d.json b/gb/tests/sm83/v2/6d.json similarity index 100% rename from tests/sm83/v2/6d.json rename to gb/tests/sm83/v2/6d.json diff --git a/tests/sm83/v2/6e.json b/gb/tests/sm83/v2/6e.json similarity index 100% rename from tests/sm83/v2/6e.json rename to gb/tests/sm83/v2/6e.json diff --git a/tests/sm83/v2/6f.json b/gb/tests/sm83/v2/6f.json similarity index 100% rename from tests/sm83/v2/6f.json rename to gb/tests/sm83/v2/6f.json diff --git a/tests/sm83/v2/70.json b/gb/tests/sm83/v2/70.json similarity index 100% rename from tests/sm83/v2/70.json rename to gb/tests/sm83/v2/70.json diff --git a/tests/sm83/v2/71.json b/gb/tests/sm83/v2/71.json similarity index 100% rename from tests/sm83/v2/71.json rename to gb/tests/sm83/v2/71.json diff --git a/tests/sm83/v2/72.json b/gb/tests/sm83/v2/72.json similarity index 100% rename from tests/sm83/v2/72.json rename to gb/tests/sm83/v2/72.json diff --git a/tests/sm83/v2/73.json b/gb/tests/sm83/v2/73.json similarity index 100% rename from tests/sm83/v2/73.json rename to gb/tests/sm83/v2/73.json diff --git a/tests/sm83/v2/74.json b/gb/tests/sm83/v2/74.json similarity index 100% rename from tests/sm83/v2/74.json rename to gb/tests/sm83/v2/74.json diff --git a/tests/sm83/v2/75.json b/gb/tests/sm83/v2/75.json similarity index 100% rename from tests/sm83/v2/75.json rename to gb/tests/sm83/v2/75.json diff --git a/tests/sm83/v2/77.json b/gb/tests/sm83/v2/77.json similarity index 100% rename from tests/sm83/v2/77.json rename to gb/tests/sm83/v2/77.json diff --git a/tests/sm83/v2/78.json b/gb/tests/sm83/v2/78.json similarity index 100% rename from tests/sm83/v2/78.json rename to gb/tests/sm83/v2/78.json diff --git a/tests/sm83/v2/79.json b/gb/tests/sm83/v2/79.json similarity index 100% rename from tests/sm83/v2/79.json rename to gb/tests/sm83/v2/79.json diff --git a/tests/sm83/v2/7a.json b/gb/tests/sm83/v2/7a.json similarity index 100% rename from tests/sm83/v2/7a.json rename to gb/tests/sm83/v2/7a.json diff --git a/tests/sm83/v2/7b.json b/gb/tests/sm83/v2/7b.json similarity index 100% rename from tests/sm83/v2/7b.json rename to gb/tests/sm83/v2/7b.json diff --git a/tests/sm83/v2/7c.json b/gb/tests/sm83/v2/7c.json similarity index 100% rename from tests/sm83/v2/7c.json rename to gb/tests/sm83/v2/7c.json diff --git a/tests/sm83/v2/7d.json b/gb/tests/sm83/v2/7d.json similarity index 100% rename from tests/sm83/v2/7d.json rename to gb/tests/sm83/v2/7d.json diff --git a/tests/sm83/v2/7e.json b/gb/tests/sm83/v2/7e.json similarity index 100% rename from tests/sm83/v2/7e.json rename to gb/tests/sm83/v2/7e.json diff --git a/tests/sm83/v2/7f.json b/gb/tests/sm83/v2/7f.json similarity index 100% rename from tests/sm83/v2/7f.json rename to gb/tests/sm83/v2/7f.json diff --git a/tests/sm83/v2/80.json b/gb/tests/sm83/v2/80.json similarity index 100% rename from tests/sm83/v2/80.json rename to gb/tests/sm83/v2/80.json diff --git a/tests/sm83/v2/81.json b/gb/tests/sm83/v2/81.json similarity index 100% rename from tests/sm83/v2/81.json rename to gb/tests/sm83/v2/81.json diff --git a/tests/sm83/v2/82.json b/gb/tests/sm83/v2/82.json similarity index 100% rename from tests/sm83/v2/82.json rename to gb/tests/sm83/v2/82.json diff --git a/tests/sm83/v2/83.json b/gb/tests/sm83/v2/83.json similarity index 100% rename from tests/sm83/v2/83.json rename to gb/tests/sm83/v2/83.json diff --git a/tests/sm83/v2/84.json b/gb/tests/sm83/v2/84.json similarity index 100% rename from tests/sm83/v2/84.json rename to gb/tests/sm83/v2/84.json diff --git a/tests/sm83/v2/85.json b/gb/tests/sm83/v2/85.json similarity index 100% rename from tests/sm83/v2/85.json rename to gb/tests/sm83/v2/85.json diff --git a/tests/sm83/v2/86.json b/gb/tests/sm83/v2/86.json similarity index 100% rename from tests/sm83/v2/86.json rename to gb/tests/sm83/v2/86.json diff --git a/tests/sm83/v2/87.json b/gb/tests/sm83/v2/87.json similarity index 100% rename from tests/sm83/v2/87.json rename to gb/tests/sm83/v2/87.json diff --git a/tests/sm83/v2/88.json b/gb/tests/sm83/v2/88.json similarity index 100% rename from tests/sm83/v2/88.json rename to gb/tests/sm83/v2/88.json diff --git a/tests/sm83/v2/89.json b/gb/tests/sm83/v2/89.json similarity index 100% rename from tests/sm83/v2/89.json rename to gb/tests/sm83/v2/89.json diff --git a/tests/sm83/v2/8a.json b/gb/tests/sm83/v2/8a.json similarity index 100% rename from tests/sm83/v2/8a.json rename to gb/tests/sm83/v2/8a.json diff --git a/tests/sm83/v2/8b.json b/gb/tests/sm83/v2/8b.json similarity index 100% rename from tests/sm83/v2/8b.json rename to gb/tests/sm83/v2/8b.json diff --git a/tests/sm83/v2/8c.json b/gb/tests/sm83/v2/8c.json similarity index 100% rename from tests/sm83/v2/8c.json rename to gb/tests/sm83/v2/8c.json diff --git a/tests/sm83/v2/8d.json b/gb/tests/sm83/v2/8d.json similarity index 100% rename from tests/sm83/v2/8d.json rename to gb/tests/sm83/v2/8d.json diff --git a/tests/sm83/v2/8e.json b/gb/tests/sm83/v2/8e.json similarity index 100% rename from tests/sm83/v2/8e.json rename to gb/tests/sm83/v2/8e.json diff --git a/tests/sm83/v2/8f.json b/gb/tests/sm83/v2/8f.json similarity index 100% rename from tests/sm83/v2/8f.json rename to gb/tests/sm83/v2/8f.json diff --git a/tests/sm83/v2/90.json b/gb/tests/sm83/v2/90.json similarity index 100% rename from tests/sm83/v2/90.json rename to gb/tests/sm83/v2/90.json diff --git a/tests/sm83/v2/91.json b/gb/tests/sm83/v2/91.json similarity index 100% rename from tests/sm83/v2/91.json rename to gb/tests/sm83/v2/91.json diff --git a/tests/sm83/v2/92.json b/gb/tests/sm83/v2/92.json similarity index 100% rename from tests/sm83/v2/92.json rename to gb/tests/sm83/v2/92.json diff --git a/tests/sm83/v2/93.json b/gb/tests/sm83/v2/93.json similarity index 100% rename from tests/sm83/v2/93.json rename to gb/tests/sm83/v2/93.json diff --git a/tests/sm83/v2/94.json b/gb/tests/sm83/v2/94.json similarity index 100% rename from tests/sm83/v2/94.json rename to gb/tests/sm83/v2/94.json diff --git a/tests/sm83/v2/95.json b/gb/tests/sm83/v2/95.json similarity index 100% rename from tests/sm83/v2/95.json rename to gb/tests/sm83/v2/95.json diff --git a/tests/sm83/v2/96.json b/gb/tests/sm83/v2/96.json similarity index 100% rename from tests/sm83/v2/96.json rename to gb/tests/sm83/v2/96.json diff --git a/tests/sm83/v2/97.json b/gb/tests/sm83/v2/97.json similarity index 100% rename from tests/sm83/v2/97.json rename to gb/tests/sm83/v2/97.json diff --git a/tests/sm83/v2/98.json b/gb/tests/sm83/v2/98.json similarity index 100% rename from tests/sm83/v2/98.json rename to gb/tests/sm83/v2/98.json diff --git a/tests/sm83/v2/99.json b/gb/tests/sm83/v2/99.json similarity index 100% rename from tests/sm83/v2/99.json rename to gb/tests/sm83/v2/99.json diff --git a/tests/sm83/v2/9a.json b/gb/tests/sm83/v2/9a.json similarity index 100% rename from tests/sm83/v2/9a.json rename to gb/tests/sm83/v2/9a.json diff --git a/tests/sm83/v2/9b.json b/gb/tests/sm83/v2/9b.json similarity index 100% rename from tests/sm83/v2/9b.json rename to gb/tests/sm83/v2/9b.json diff --git a/tests/sm83/v2/9c.json b/gb/tests/sm83/v2/9c.json similarity index 100% rename from tests/sm83/v2/9c.json rename to gb/tests/sm83/v2/9c.json diff --git a/tests/sm83/v2/9d.json b/gb/tests/sm83/v2/9d.json similarity index 100% rename from tests/sm83/v2/9d.json rename to gb/tests/sm83/v2/9d.json diff --git a/tests/sm83/v2/9e.json b/gb/tests/sm83/v2/9e.json similarity index 100% rename from tests/sm83/v2/9e.json rename to gb/tests/sm83/v2/9e.json diff --git a/tests/sm83/v2/9f.json b/gb/tests/sm83/v2/9f.json similarity index 100% rename from tests/sm83/v2/9f.json rename to gb/tests/sm83/v2/9f.json diff --git a/tests/sm83/v2/README.md b/gb/tests/sm83/v2/README.md similarity index 100% rename from tests/sm83/v2/README.md rename to gb/tests/sm83/v2/README.md diff --git a/tests/sm83/v2/a0.json b/gb/tests/sm83/v2/a0.json similarity index 100% rename from tests/sm83/v2/a0.json rename to gb/tests/sm83/v2/a0.json diff --git a/tests/sm83/v2/a1.json b/gb/tests/sm83/v2/a1.json similarity index 100% rename from tests/sm83/v2/a1.json rename to gb/tests/sm83/v2/a1.json diff --git a/tests/sm83/v2/a2.json b/gb/tests/sm83/v2/a2.json similarity index 100% rename from tests/sm83/v2/a2.json rename to gb/tests/sm83/v2/a2.json diff --git a/tests/sm83/v2/a3.json b/gb/tests/sm83/v2/a3.json similarity index 100% rename from tests/sm83/v2/a3.json rename to gb/tests/sm83/v2/a3.json diff --git a/tests/sm83/v2/a4.json b/gb/tests/sm83/v2/a4.json similarity index 100% rename from tests/sm83/v2/a4.json rename to gb/tests/sm83/v2/a4.json diff --git a/tests/sm83/v2/a5.json b/gb/tests/sm83/v2/a5.json similarity index 100% rename from tests/sm83/v2/a5.json rename to gb/tests/sm83/v2/a5.json diff --git a/tests/sm83/v2/a6.json b/gb/tests/sm83/v2/a6.json similarity index 100% rename from tests/sm83/v2/a6.json rename to gb/tests/sm83/v2/a6.json diff --git a/tests/sm83/v2/a7.json b/gb/tests/sm83/v2/a7.json similarity index 100% rename from tests/sm83/v2/a7.json rename to gb/tests/sm83/v2/a7.json diff --git a/tests/sm83/v2/a8.json b/gb/tests/sm83/v2/a8.json similarity index 100% rename from tests/sm83/v2/a8.json rename to gb/tests/sm83/v2/a8.json diff --git a/tests/sm83/v2/a9.json b/gb/tests/sm83/v2/a9.json similarity index 100% rename from tests/sm83/v2/a9.json rename to gb/tests/sm83/v2/a9.json diff --git a/tests/sm83/v2/aa.json b/gb/tests/sm83/v2/aa.json similarity index 100% rename from tests/sm83/v2/aa.json rename to gb/tests/sm83/v2/aa.json diff --git a/tests/sm83/v2/ab.json b/gb/tests/sm83/v2/ab.json similarity index 100% rename from tests/sm83/v2/ab.json rename to gb/tests/sm83/v2/ab.json diff --git a/tests/sm83/v2/ac.json b/gb/tests/sm83/v2/ac.json similarity index 100% rename from tests/sm83/v2/ac.json rename to gb/tests/sm83/v2/ac.json diff --git a/tests/sm83/v2/ad.json b/gb/tests/sm83/v2/ad.json similarity index 100% rename from tests/sm83/v2/ad.json rename to gb/tests/sm83/v2/ad.json diff --git a/tests/sm83/v2/ae.json b/gb/tests/sm83/v2/ae.json similarity index 100% rename from tests/sm83/v2/ae.json rename to gb/tests/sm83/v2/ae.json diff --git a/tests/sm83/v2/af.json b/gb/tests/sm83/v2/af.json similarity index 100% rename from tests/sm83/v2/af.json rename to gb/tests/sm83/v2/af.json diff --git a/tests/sm83/v2/b0.json b/gb/tests/sm83/v2/b0.json similarity index 100% rename from tests/sm83/v2/b0.json rename to gb/tests/sm83/v2/b0.json diff --git a/tests/sm83/v2/b1.json b/gb/tests/sm83/v2/b1.json similarity index 100% rename from tests/sm83/v2/b1.json rename to gb/tests/sm83/v2/b1.json diff --git a/tests/sm83/v2/b2.json b/gb/tests/sm83/v2/b2.json similarity index 100% rename from tests/sm83/v2/b2.json rename to gb/tests/sm83/v2/b2.json diff --git a/tests/sm83/v2/b3.json b/gb/tests/sm83/v2/b3.json similarity index 100% rename from tests/sm83/v2/b3.json rename to gb/tests/sm83/v2/b3.json diff --git a/tests/sm83/v2/b4.json b/gb/tests/sm83/v2/b4.json similarity index 100% rename from tests/sm83/v2/b4.json rename to gb/tests/sm83/v2/b4.json diff --git a/tests/sm83/v2/b5.json b/gb/tests/sm83/v2/b5.json similarity index 100% rename from tests/sm83/v2/b5.json rename to gb/tests/sm83/v2/b5.json diff --git a/tests/sm83/v2/b6.json b/gb/tests/sm83/v2/b6.json similarity index 100% rename from tests/sm83/v2/b6.json rename to gb/tests/sm83/v2/b6.json diff --git a/tests/sm83/v2/b7.json b/gb/tests/sm83/v2/b7.json similarity index 100% rename from tests/sm83/v2/b7.json rename to gb/tests/sm83/v2/b7.json diff --git a/tests/sm83/v2/b8.json b/gb/tests/sm83/v2/b8.json similarity index 100% rename from tests/sm83/v2/b8.json rename to gb/tests/sm83/v2/b8.json diff --git a/tests/sm83/v2/b9.json b/gb/tests/sm83/v2/b9.json similarity index 100% rename from tests/sm83/v2/b9.json rename to gb/tests/sm83/v2/b9.json diff --git a/tests/sm83/v2/ba.json b/gb/tests/sm83/v2/ba.json similarity index 100% rename from tests/sm83/v2/ba.json rename to gb/tests/sm83/v2/ba.json diff --git a/tests/sm83/v2/bb.json b/gb/tests/sm83/v2/bb.json similarity index 100% rename from tests/sm83/v2/bb.json rename to gb/tests/sm83/v2/bb.json diff --git a/tests/sm83/v2/bc.json b/gb/tests/sm83/v2/bc.json similarity index 100% rename from tests/sm83/v2/bc.json rename to gb/tests/sm83/v2/bc.json diff --git a/tests/sm83/v2/bd.json b/gb/tests/sm83/v2/bd.json similarity index 100% rename from tests/sm83/v2/bd.json rename to gb/tests/sm83/v2/bd.json diff --git a/tests/sm83/v2/be.json b/gb/tests/sm83/v2/be.json similarity index 100% rename from tests/sm83/v2/be.json rename to gb/tests/sm83/v2/be.json diff --git a/tests/sm83/v2/bf.json b/gb/tests/sm83/v2/bf.json similarity index 100% rename from tests/sm83/v2/bf.json rename to gb/tests/sm83/v2/bf.json diff --git a/tests/sm83/v2/c0.json b/gb/tests/sm83/v2/c0.json similarity index 100% rename from tests/sm83/v2/c0.json rename to gb/tests/sm83/v2/c0.json diff --git a/tests/sm83/v2/c1.json b/gb/tests/sm83/v2/c1.json similarity index 100% rename from tests/sm83/v2/c1.json rename to gb/tests/sm83/v2/c1.json diff --git a/tests/sm83/v2/c2.json b/gb/tests/sm83/v2/c2.json similarity index 100% rename from tests/sm83/v2/c2.json rename to gb/tests/sm83/v2/c2.json diff --git a/tests/sm83/v2/c3.json b/gb/tests/sm83/v2/c3.json similarity index 100% rename from tests/sm83/v2/c3.json rename to gb/tests/sm83/v2/c3.json diff --git a/tests/sm83/v2/c4.json b/gb/tests/sm83/v2/c4.json similarity index 100% rename from tests/sm83/v2/c4.json rename to gb/tests/sm83/v2/c4.json diff --git a/tests/sm83/v2/c5.json b/gb/tests/sm83/v2/c5.json similarity index 100% rename from tests/sm83/v2/c5.json rename to gb/tests/sm83/v2/c5.json diff --git a/tests/sm83/v2/c6.json b/gb/tests/sm83/v2/c6.json similarity index 100% rename from tests/sm83/v2/c6.json rename to gb/tests/sm83/v2/c6.json diff --git a/tests/sm83/v2/c7.json b/gb/tests/sm83/v2/c7.json similarity index 100% rename from tests/sm83/v2/c7.json rename to gb/tests/sm83/v2/c7.json diff --git a/tests/sm83/v2/c8.json b/gb/tests/sm83/v2/c8.json similarity index 100% rename from tests/sm83/v2/c8.json rename to gb/tests/sm83/v2/c8.json diff --git a/tests/sm83/v2/c9.json b/gb/tests/sm83/v2/c9.json similarity index 100% rename from tests/sm83/v2/c9.json rename to gb/tests/sm83/v2/c9.json diff --git a/tests/sm83/v2/ca.json b/gb/tests/sm83/v2/ca.json similarity index 100% rename from tests/sm83/v2/ca.json rename to gb/tests/sm83/v2/ca.json diff --git a/tests/sm83/v2/cb.json b/gb/tests/sm83/v2/cb.json similarity index 100% rename from tests/sm83/v2/cb.json rename to gb/tests/sm83/v2/cb.json diff --git a/tests/sm83/v2/cc.json b/gb/tests/sm83/v2/cc.json similarity index 100% rename from tests/sm83/v2/cc.json rename to gb/tests/sm83/v2/cc.json diff --git a/tests/sm83/v2/cd.json b/gb/tests/sm83/v2/cd.json similarity index 100% rename from tests/sm83/v2/cd.json rename to gb/tests/sm83/v2/cd.json diff --git a/tests/sm83/v2/ce.json b/gb/tests/sm83/v2/ce.json similarity index 100% rename from tests/sm83/v2/ce.json rename to gb/tests/sm83/v2/ce.json diff --git a/tests/sm83/v2/cf.json b/gb/tests/sm83/v2/cf.json similarity index 100% rename from tests/sm83/v2/cf.json rename to gb/tests/sm83/v2/cf.json diff --git a/tests/sm83/v2/d0.json b/gb/tests/sm83/v2/d0.json similarity index 100% rename from tests/sm83/v2/d0.json rename to gb/tests/sm83/v2/d0.json diff --git a/tests/sm83/v2/d1.json b/gb/tests/sm83/v2/d1.json similarity index 100% rename from tests/sm83/v2/d1.json rename to gb/tests/sm83/v2/d1.json diff --git a/tests/sm83/v2/d2.json b/gb/tests/sm83/v2/d2.json similarity index 100% rename from tests/sm83/v2/d2.json rename to gb/tests/sm83/v2/d2.json diff --git a/tests/sm83/v2/d4.json b/gb/tests/sm83/v2/d4.json similarity index 100% rename from tests/sm83/v2/d4.json rename to gb/tests/sm83/v2/d4.json diff --git a/tests/sm83/v2/d5.json b/gb/tests/sm83/v2/d5.json similarity index 100% rename from tests/sm83/v2/d5.json rename to gb/tests/sm83/v2/d5.json diff --git a/tests/sm83/v2/d6.json b/gb/tests/sm83/v2/d6.json similarity index 100% rename from tests/sm83/v2/d6.json rename to gb/tests/sm83/v2/d6.json diff --git a/tests/sm83/v2/d7.json b/gb/tests/sm83/v2/d7.json similarity index 100% rename from tests/sm83/v2/d7.json rename to gb/tests/sm83/v2/d7.json diff --git a/tests/sm83/v2/d8.json b/gb/tests/sm83/v2/d8.json similarity index 100% rename from tests/sm83/v2/d8.json rename to gb/tests/sm83/v2/d8.json diff --git a/tests/sm83/v2/d9.json b/gb/tests/sm83/v2/d9.json similarity index 100% rename from tests/sm83/v2/d9.json rename to gb/tests/sm83/v2/d9.json diff --git a/tests/sm83/v2/da.json b/gb/tests/sm83/v2/da.json similarity index 100% rename from tests/sm83/v2/da.json rename to gb/tests/sm83/v2/da.json diff --git a/tests/sm83/v2/dc.json b/gb/tests/sm83/v2/dc.json similarity index 100% rename from tests/sm83/v2/dc.json rename to gb/tests/sm83/v2/dc.json diff --git a/tests/sm83/v2/de.json b/gb/tests/sm83/v2/de.json similarity index 100% rename from tests/sm83/v2/de.json rename to gb/tests/sm83/v2/de.json diff --git a/tests/sm83/v2/df.json b/gb/tests/sm83/v2/df.json similarity index 100% rename from tests/sm83/v2/df.json rename to gb/tests/sm83/v2/df.json diff --git a/tests/sm83/v2/e0.json b/gb/tests/sm83/v2/e0.json similarity index 100% rename from tests/sm83/v2/e0.json rename to gb/tests/sm83/v2/e0.json diff --git a/tests/sm83/v2/e1.json b/gb/tests/sm83/v2/e1.json similarity index 100% rename from tests/sm83/v2/e1.json rename to gb/tests/sm83/v2/e1.json diff --git a/tests/sm83/v2/e2.json b/gb/tests/sm83/v2/e2.json similarity index 100% rename from tests/sm83/v2/e2.json rename to gb/tests/sm83/v2/e2.json diff --git a/tests/sm83/v2/e5.json b/gb/tests/sm83/v2/e5.json similarity index 100% rename from tests/sm83/v2/e5.json rename to gb/tests/sm83/v2/e5.json diff --git a/tests/sm83/v2/e6.json b/gb/tests/sm83/v2/e6.json similarity index 100% rename from tests/sm83/v2/e6.json rename to gb/tests/sm83/v2/e6.json diff --git a/tests/sm83/v2/e7.json b/gb/tests/sm83/v2/e7.json similarity index 100% rename from tests/sm83/v2/e7.json rename to gb/tests/sm83/v2/e7.json diff --git a/tests/sm83/v2/e8.json b/gb/tests/sm83/v2/e8.json similarity index 100% rename from tests/sm83/v2/e8.json rename to gb/tests/sm83/v2/e8.json diff --git a/tests/sm83/v2/e9.json b/gb/tests/sm83/v2/e9.json similarity index 100% rename from tests/sm83/v2/e9.json rename to gb/tests/sm83/v2/e9.json diff --git a/tests/sm83/v2/ea.json b/gb/tests/sm83/v2/ea.json similarity index 100% rename from tests/sm83/v2/ea.json rename to gb/tests/sm83/v2/ea.json diff --git a/tests/sm83/v2/ee.json b/gb/tests/sm83/v2/ee.json similarity index 100% rename from tests/sm83/v2/ee.json rename to gb/tests/sm83/v2/ee.json diff --git a/tests/sm83/v2/ef.json b/gb/tests/sm83/v2/ef.json similarity index 100% rename from tests/sm83/v2/ef.json rename to gb/tests/sm83/v2/ef.json diff --git a/tests/sm83/v2/f0.json b/gb/tests/sm83/v2/f0.json similarity index 100% rename from tests/sm83/v2/f0.json rename to gb/tests/sm83/v2/f0.json diff --git a/tests/sm83/v2/f1.json b/gb/tests/sm83/v2/f1.json similarity index 100% rename from tests/sm83/v2/f1.json rename to gb/tests/sm83/v2/f1.json diff --git a/tests/sm83/v2/f2.json b/gb/tests/sm83/v2/f2.json similarity index 100% rename from tests/sm83/v2/f2.json rename to gb/tests/sm83/v2/f2.json diff --git a/tests/sm83/v2/f5.json b/gb/tests/sm83/v2/f5.json similarity index 100% rename from tests/sm83/v2/f5.json rename to gb/tests/sm83/v2/f5.json diff --git a/tests/sm83/v2/f6.json b/gb/tests/sm83/v2/f6.json similarity index 100% rename from tests/sm83/v2/f6.json rename to gb/tests/sm83/v2/f6.json diff --git a/tests/sm83/v2/f7.json b/gb/tests/sm83/v2/f7.json similarity index 100% rename from tests/sm83/v2/f7.json rename to gb/tests/sm83/v2/f7.json diff --git a/tests/sm83/v2/f8.json b/gb/tests/sm83/v2/f8.json similarity index 100% rename from tests/sm83/v2/f8.json rename to gb/tests/sm83/v2/f8.json diff --git a/tests/sm83/v2/f9.json b/gb/tests/sm83/v2/f9.json similarity index 100% rename from tests/sm83/v2/f9.json rename to gb/tests/sm83/v2/f9.json diff --git a/tests/sm83/v2/fa.json b/gb/tests/sm83/v2/fa.json similarity index 100% rename from tests/sm83/v2/fa.json rename to gb/tests/sm83/v2/fa.json diff --git a/tests/sm83/v2/fe.json b/gb/tests/sm83/v2/fe.json similarity index 100% rename from tests/sm83/v2/fe.json rename to gb/tests/sm83/v2/fe.json diff --git a/tests/sm83/v2/ff.json b/gb/tests/sm83/v2/ff.json similarity index 100% rename from tests/sm83/v2/ff.json rename to gb/tests/sm83/v2/ff.json diff --git a/gba/Cargo.toml b/gba/Cargo.toml new file mode 100644 index 0000000..342690a --- /dev/null +++ b/gba/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "gba" +version = "0.1.0" +edition = "2024" + +[dependencies] + +[profile.dev] +opt-level = 1 diff --git a/gba/src/components/apu/mod.rs b/gba/src/components/apu/mod.rs new file mode 100644 index 0000000..14fbf9d --- /dev/null +++ b/gba/src/components/apu/mod.rs @@ -0,0 +1,9 @@ +// https://gbadev.net/gbadoc/audio/introduction.html + +pub struct APU {} + +impl APU { + pub fn new() -> Self { + Self {} + } +} diff --git a/gba/src/components/bios.rs b/gba/src/components/bios.rs new file mode 100644 index 0000000..b8d1608 --- /dev/null +++ b/gba/src/components/bios.rs @@ -0,0 +1,4 @@ +// BIOS Functions need implementing: https://problemkaputt.de/gbatek-bios-function-summary.htm +// https://github.com/mgba-emu/mgba/blob/master/src/gba/hle-bios.c +// https://github.com/mgba-emu/mgba/blob/master/src/gba/hle-bios.s +// https://github.com/mgba-emu/mgba/blob/master/src/gba/bios.c diff --git a/gba/src/components/bus.rs b/gba/src/components/bus.rs new file mode 100644 index 0000000..9a24ef5 --- /dev/null +++ b/gba/src/components/bus.rs @@ -0,0 +1,727 @@ +// https://mgba.io/2015/06/27/cycle-counting-prefetch/ +// https://developer.arm.com/documentation/ddi0084/f/memory-interface/bus-cycle-types/sequential-cycles +// https://corrupt.wiki/systems/gameboy-advance/bizhawk-memory-domains +// https://medium.com/@michelheily/hello-gba-journey-of-making-an-emulator-part-1-8793000e8606 +// https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm#Memory%20Map +// https://www.nesdev.org/wiki/Open_bus_behavior +// https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm#Memory%20Map +// https://www.chibiakumas.com/arm/gba.php + +// https://blog.asie.pl/2025/09/wonderful-update-september-2025/ + +// Just do an afterboot startup + +// https://problemkaputt.de/gbatek.htm#GBAUnpredictableThings + +use crate::components::{ + apu::APU, gamepak::GamePak, ppu::PPU, scheduler::Scheduler, utils::zero_arr, +}; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum AccessType { + Sequential, // Memory address related to previous address, incremented by + 2 (half word) or +4 (word) + Nonsequential, // Memory address is fetched and has nothing to do with the previous instruction +} + +// Note, there is an addition GBA cycle type: Internal, no memory access, performing a complex internal operation like a multiply, only 1 cycle + +mod sealed { + pub trait Sealed {} + + impl Sealed for u8 {} + + impl Sealed for u16 {} + + impl Sealed for u32 {} +} + +pub trait BusValue: sealed::Sealed + Sized + Copy { + fn read(bus: &mut Bus, address: u32, access_type: AccessType) -> Self; + + fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType); +} + +// need to add a generic with trait bound to cpu to create a mock bus for testing +pub trait AddressBus { + fn read_u8(&mut self, address: u32, access: AccessType) -> u8; + fn read_u16(&mut self, address: u32, access: AccessType) -> u16; + fn read_u32(&mut self, address: u32, access: AccessType) -> u32; + fn write_u8(&mut self, address: u32, value: u8, access: AccessType); + fn write_u16(&mut self, address: u32, value: u16, access: AccessType); + fn write_u32(&mut self, address: u32, value: u32, access: AccessType); + fn idle(&mut self, cycles: u64); +} + +pub struct Bus { + pub scheduler: Scheduler, + bios: Box<[u8; 0x4000]>, + ewram: Box<[u8; 0x40000]>, + iwram: Box<[u8; 0x8000]>, + last_read: u32, + last_bios_fetch: u32, // According to medium article, MMBN6 has an email bug due to null pointer dereference in the BIOS + // region [00DCh+8] in bios is 0xE129F000; https://problemkaputt.de/gbatek.htm#GBAUnpredictableThings + apu: APU, + ppu: PPU, + gamepak: GamePak, +} + +impl Bus { + pub fn new(gamepak: GamePak) -> Self { + Self { + scheduler: Scheduler::new(), + bios: zero_arr(), + ewram: zero_arr(), + iwram: zero_arr(), + last_read: 0, + last_bios_fetch: 0xE129F000, + ppu: PPU::new(), + gamepak, + apu: APU::new(), + } + } + + #[inline] + fn ewram_index(address: u32) -> usize { + (address & 0x3FFFF) as usize + } + + #[inline] + fn iwram_index(address: u32) -> usize { + (address & 0x7FFF) as usize + } + + #[inline] + pub fn palette_index(address: u32) -> usize { + (address & 0x3FF) as usize + } + + #[inline] + pub fn oam_index(address: u32) -> usize { + (address & 0x3FF) as usize + } + + #[inline] + pub fn vram_index(address: u32) -> usize { + let index = (address & 0x1FFFE) as usize; + let index = if index >= 0x18000 { + index - 0x8000 + } else { + index + }; + + index + } + + fn backup_byte(&self, address: u32) -> Option { + if self.gamepak.backup_memory.is_empty() { + return None; + } + + let base_address = 0x0E000000; + let mask = (self.gamepak.backup_memory.len() - 1) as u32; + let index = ((address - base_address) & mask) as usize; + + Some(index) + } + + #[inline] + pub fn read_backup_byte(&self, address: u32) -> u8 { + match self.backup_byte(address) { + Some(index) => self.gamepak.backup_memory[index], + None => 0, + } + } + + #[inline] + pub fn write_backup_byte(&mut self, address: u32, value: u8) { + match self.backup_byte(address) { + Some(index) => { + self.gamepak.backup_memory[index] = value; + self.gamepak.ram_updated = true; + } + None => {} + } + } + + pub fn read(&mut self, address: u32, access_type: AccessType) -> T { + T::read(self, address, access_type) + } + + pub fn write(&mut self, address: u32, value: T, access_type: AccessType) { + T::write(self, address, value, access_type) + } + + pub fn idle(&mut self, cycles: u64) { + self.scheduler.current += cycles; + } + + pub fn cost(&mut self, address: u32, width: u32, access_type: AccessType) { + let region = (address >> 24) as usize; + let cycles = match region { + 0x00 | 0x03 | 0x04 | 0x07 => 1, + 0x02 => { + if width == 32 { + 6 + } else { + 3 + } + } + 0x05 | 0x06 => { + if width == 32 { + 2 + } else { + 1 + } + } + 0x08..=0x0D => self.rom_cost(width, access_type), + 0x0E | 0x0F => 5, + _ => 1, + }; + + self.scheduler.current += cycles as u64; + } + + pub fn rom_cost(&mut self, width: u32, access_type: AccessType) -> usize { + 0 + } + + // Take regiisters from an early commit and just map them back for now + // GBATEK lists 32-bit registers at TWO halfword addresses (e.g. "40000D4h,0D6h"). + // Both halves are independently addressable, so each gets its own table entry (0x40000D4 = bits 0-15, 0x40000D6 = bits 16-31) + fn read_register_16(&mut self, address: u32) -> u16 { + match address { + // LCD I/O Registers + 0x4000000 => {} // LCD Control (DISPCNT), 16 bit register (read + write) + 0x4000002 => {} // Undocumented 16 bit register (read + write) + 0x4000004 => {} // Stat & LYC, 16 bit register (read + write) + 0x4000006 => {} // LY, 16 bit, (VCOUNT), read only + 0x4000008 => {} // BG0 Control (BG0CNT) 16 bit register (read + write) + 0x400000A => {} // BG1 Control (BG1CNT) 16 bit register (read + write) + 0x400000C => {} // BG2 Control (BG2CNT) 16 bit register (read + write) + 0x400000E => {} // BG3 Control (BG3CNT) 16 bit register (read + write) + 0x4000048 => {} // Inside of Window 0 and 1 (WININ), 16 bit register (read + write) + 0x400004A => {} // Inside of OBJ Window & Outside of Windows 2 (WINOUT) (read + write) + 0x400004E => {} // Not Used + 0x4000050 => {} // Color Special Effects Selection (BLDCNT), 16 bit register (read + write) + 0x4000052 => {} // Alpha Blending Coefficients (BLDALPHA), 16 bit register (read + write) + 0x4000056 => {} // Not Used + + // Sound Registers + 0x4000060 => {} // Channel 1 Sweep register (NR10) (SOUND1CNT_L), 16 bit register (read + write) + 0x4000062 => {} // Channel 1 Duty/Length/Envelope (NR11, NR12) (SOUND1CNT_H), 16 bit register (read + write) + 0x4000064 => {} // Channel 1 Frequency/Control (NR13, NR14) (SOUND1CNT_X), 16 bit register (read + write) + 0x4000066 => {} // Not Used + 0x4000068 => {} // Channel 2 Duty/Length/Envelope (NR21, NR22) (SOUND2CNT_L), 16 bit register (read + write) + 0x400006A => {} // Not Used + 0x400006C => {} // Channel 2 Frequency/Control (NR23, NR24) (SOUND2CNT_H), 16 bit register (read + write) + 0x400006E => {} // Not Used + 0x4000070 => {} // Channel 3 Stop/Wave RAM select (NR30) (SOUND3CNT_L), 16 bit register (read + write) + 0x4000072 => {} // Channel 3 Length/Volume (NR31, NR32), 16 bit register (read + write) + 0x4000074 => {} // Channel 3 Frequency/Control (NR33, NR34) (SOUND3CNT_X), 16 bit register (read + write) + 0x4000076 => {} // Not Used + 0x4000078 => {} // Channel 4 Length/Envelope (NR41, NR42) (SOUND4CNT_L), 16 bit register (read + write) + 0x400007A => {} // Not Used + 0x400007C => {} // Channel 4 Frequency/Control (NR43, NR44) (SOUND4CNT_H), 16 bit register (read + write) + 0x400007E => {} // Not Used + 0x4000080 => {} // Control Stereo/Volume/Enable (NR50, NR51) (SOUNDCNT_L), 16 bit register (read + write) + 0x4000082 => {} // Control Mixing/DMA Control (SOUNDCNT_H), 16 bit register (read + write) + 0x4000084 => {} // Control Sound on/off (NR52) (SOUNDCNT_X), 16 bit register (read + write) + 0x4000086 => {} // Not Used + 0x4000088 => {} // BIOS/Sound PWM Control (SOUNDBIAS), 16 bit register (read + write) + 0x400008A => {} // Not Used + 0x4000090 => {} // Channel 3 Wave Pattern RAM (2 banks) (WAVE_RAM) 2x10h in size, (read + write) + 0x40000A8 => {} // Not Used + + // DMA Transfer Channels + 0x40000BA => {} // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) + 0x40000C6 => {} // DMA 1 Control (DMA1CNT_H), 16 bit register (read + write) + 0x40000D2 => {} // DMA 2 Control (DMA2CNT_H), 16 bit register (read + write) + 0x40000DE => {} // DMA 3 Control (DMA3CNT_H), 16 bit register (read + write) + 0x40000E0 => {} // Not Used + + // Timer Registers + 0x4000100 => {} // Timer 0 Counter/Reload (TM0CNT_L), 16 bit register (read + write) + 0x4000102 => {} // Timer 0 Control (TM0CNT_H), 16 bit register (read + write) + 0x4000104 => {} // Timer 1 Counter/Reload (TM1CNT_L), 16 bit register (read + write) + 0x4000106 => {} // Timer 1 Control (TM1CNT_H), 16 bit register (read + write) + 0x4000108 => {} // Timer 2 Counter/Reload (TM2CNT_L), 16 bit register (read + write) + 0x400010A => {} // Timer 2 Control (TM2CNT_H), 16 bit register (read + write) + 0x400010C => {} // Timer 3 Counter/Reload (TM3CNT_L), 16 bit register (read + write) + 0x400010E => {} // Timer 3 Control (TM3CNT_H), 16 bit register (read + write) + 0x4000110 => {} // Not Used + + // Serial Communication (1) + 0x4000120 => {} // SIO Data (Normal-32bit Mode; shared with SIO Data 0 (Parent) (SIODATA32). SIO Data is a 32 bit register and SIO Data 0 (Parent) (Multi-Player Mode) is a 16 bit register (read + write) (SIOMULTI0) + 0x4000122 => {} // SIO Data 1 (1st Child) (Multi-Player Mode) (SIOMULTI1), 16 bit register (read + write) + 0x4000124 => {} // SIO Data 2 (2nd Child) (Multi-Player Mode) (SIOMULTI2), 16 bit register (read + write) + 0x4000126 => {} // SIO Data 3 (3rd Child) (Multi-Player Mode) (SIOMULTI3), 16 bit register (read + write) + 0x4000128 => {} // SIO Control Register (SIOCNT), 16 bit register (read + write) + 0x400012A => {} // SIO Data (Local of MultiPlayer; shared with SIODATA8) (SIOMLT_SEND), 16 bit register (read + write); SIO Data (Normal-8bit and UART Mode) (SIODATA8), 16 bit register (read + write) + 0x400012C => {} // Not Used + + // Keypad Input + 0x4000130 => {} // Key Status (KEYINPUT), 16 bit register read only + 0x4000132 => {} // Key Interrupt Control (KEYCNT), 16 bit register (read + write) + + // Serial Communication (2) + 0x4000134 => {} // SIO Mode Select/General Purpose Data (RCNT), 16 bit register (read + write) + 0x4000136 => {} // Ancient - Infrared Register (Prototypes only) (IR) + 0x4000138 => {} // Not Used + 0x4000140 => {} // SIO JOY Bus Control (JOYCNT), 16 bit register (read + write) + 0x4000142 => {} // Not Used + 0x4000150 => {} // SIO JOY Bus Receive Data (JOY_RECV), 32 bit register (read + write) + 0x4000154 => {} // SIO JOY Bus Transmit Data (JOY_TRANS), 32 bit register (read + write) + 0x4000158 => {} // SIO JOY Bus Receive Status (JOYSTAT), 16 bit register (read + maybe write?) + 0x400015A => {} // Not Used + + //Interrupt, Waitstate, and Power-Down Control + 0x4000200 => {} // Interrupt Enable Register (IE), 16 bit register (read + write) + 0x4000202 => {} // Interrupt Request Flags / IRQ Acknowledge (IF), 16 bit register (read + write) + 0x4000204 => {} // Game Pak Waitstate Control (AITCNT), 16 bit register (read + write) + 0x4000206 => {} // Not used + 0x4000208 => {} // Interrupt Master Enable Register (IME), 16 bit register (read + write) + 0x400020A => {} // Not used + 0x4000300 => {} // Undocumented - Post Boot Flag (POSTFLG), 8 bit register (read + write) + 0x4000301 => {} // Undocumented - Power Down Control (HALTCNT), 8 bit register (write only) + 0x4000302 => {} // Not used + 0x4000410 => {} // Undocumented - Purpose Unknown / Bug ??? 0FFh + 0x4000411 => {} // Not used + 0x4000800 => {} // Undocumented - Internal Memory Control, 32 bit register (read + write) + 0x4000804 => {} // Not used + address if (address & 0xFF00FFFF) == 0x04000800 => {} // Mirrors of 4000800h (repeated each 64K), 32 bit (read + write) + _ => {} + } + + 0 as u16 + } + + fn write_register_16(&mut self, address: u32, value: u16) { + match address { + // LCD I/O Registers + 0x4000000 => {} // LCD Control (DISPCNT), 16 bit register (read + write) + 0x4000002 => {} // Undocumented 16 bit register (read + write) + 0x4000004 => {} // Stat & LYC, 16 bit register (read + write) + 0x4000008 => {} // BG0 Control (BG0CNT) 16 bit register (read + write) + 0x400000A => {} // BG1 Control (BG1CNT) 16 bit register (read + write) + 0x400000C => {} // BG2 Control (BG2CNT) 16 bit register (read + write) + 0x400000E => {} // BG3 Control (BG3CNT) 16 bit register (read + write) + 0x4000010 => {} // BG0 X-Offset (BG0HOFS) 16 bit register (write only) + 0x4000012 => {} // BG0 Y-Offset (BG0VOFS) 16 bit register (write only) + 0x4000014 => {} // BG1 X-Offset (BG1HOFS) 16 bit register (write only) + 0x4000016 => {} // BG1 Y-Offset (BG1VOFS) 16 bit register (write only) + 0x4000018 => {} // BG2 X-Offset (BG2HOFS) 16 bit register (write only) + 0x400001A => {} // BG2 Y-Offset (BG2VOFS) 16 bit register (write only) + 0x400001C => {} // BG3 X-Offset (BG3HOFS) 16 bit register (write only) + 0x400001E => {} // BG3 Y-Offset (BG3VOFS) 16 bit register (write only) + 0x4000020 => {} // BG2 Rotation/Scaling Parameter A (dx) (BG2PA), 16 bit register (write only) + 0x4000022 => {} // BG2 Rotation/Scaling Parameter B (dmx) (BG2PB), 16 bit register (write only) + 0x4000024 => {} // BG2 Rotation/Scaling Parameter C (dy) (BG2PC), 16 bit register (write only) + 0x4000026 => {} // BG2 Rotation/Scaling Parameter D (dmy) (BG2PD), 16 bit register (write only) + 0x4000028 => {} // BG2 Reference Point X-Coordinate (BG2X), 32 bit register (write only) + 0x400002C => {} // BG2 Reference Point Y-Coordinate (BG2Y), 32 bit register (write only) + 0x4000030 => {} // BG3 Rotation/Scaling Parameter A (dx) (BG3PA), 16 bit register (write only) + 0x4000032 => {} // BG3 Rotation/Scaling Parameter B (dmx) (BG3PB), 16 bit register (write only) + 0x4000034 => {} // BG3 Rotation/Scaling Parameter C (dy) (BG3PC), 16 bit register (write only) + 0x4000036 => {} // BG3 Rotation/Scaling Parameter D (dmy) (BG3PD), 16 bit register (write only) + 0x4000038 => {} // BG3 Reference Point X-Coordinate (BG3X), 32 bit register (write only) + 0x400003C => {} // BG3 Reference Point Y-Coordinate (BG3Y), 32 bit register (write only) + 0x4000040 => {} // Window 0 Horizontal Dimensions (WIN0H), 16 bit register (write only) + 0x4000042 => {} // Window 1 Horizontal Dimensions (WIN1H), 16 bit register (write only) + 0x4000044 => {} // Window 0 Vertical Dimensions (WIN0V), 16 bit register (write only) + 0x4000046 => {} // Window 1 Vertical Dimensions (WIN1V), 16 bit register (write only) + 0x4000048 => {} // Inside of Window 0 and 1 (WININ), 16 bit register (read + write) + 0x400004A => {} // Inside of OBJ Window & Outside of Windows 2 (WINOUT), 16 bit register (read + write) + 0x400004C => {} // Mosaic Size (MOSAIC), 16 bit register (write only) + 0x400004E => {} // Not Used + 0x4000050 => {} // Color Special Effects Selection (BLDCNT), 16 bit register (read + write) + 0x4000052 => {} // Alpha Blending Coefficients (BLDALPHA), 16 bit register (read + write) + 0x4000054 => {} // Brightness (Fade-In/Out) Coefficient (BLDY), 16 bit register (write only) + 0x4000056 => {} // Not Used + + // Sound Registers + 0x4000060 => {} // Channel 1 Sweep register (NR10) (SOUND1CNT_L), 16 bit register (read + write) + 0x4000062 => {} // Channel 1 Duty/Length/Envelope (NR11, NR12) (SOUND1CNT_H), 16 bit register (read + write) + 0x4000064 => {} // Channel 1 Frequency/Control (NR13, NR14) (SOUND1CNT_X), 16 bit register (read + write) + 0x4000066 => {} // Not Used + 0x4000068 => {} // Channel 2 Duty/Length/Envelope (NR21, NR22) (SOUND2CNT_L), 16 bit register (read + write) + 0x400006A => {} // Not Used + 0x400006C => {} // Channel 2 Frequency/Control (NR23, NR24) (SOUND2CNT_H), 16 bit register (read + write) + 0x400006E => {} // Not Used + 0x4000070 => {} // Channel 3 Stop/Wave RAM select (NR30) (SOUND3CNT_L), 16 bit register (read + write) + 0x4000072 => {} // Channel 3 Length/Volume (NR31, NR32), 16 bit register (read + write) + 0x4000074 => {} // Channel 3 Frequency/Control (NR33, NR34) (SOUND3CNT_X), 16 bit register (read + write) + 0x4000076 => {} // Not Used + 0x4000078 => {} // Channel 4 Length/Envelope (NR41, NR42) (SOUND4CNT_L), 16 bit register (read + write) + 0x400007A => {} // Not Used + 0x400007C => {} // Channel 4 Frequency/Control (NR43, NR44) (SOUND4CNT_H), 16 bit register (read + write) + 0x400007E => {} // Not Used + 0x4000080 => {} // Control Stereo/Volume/Enable (NR50, NR51) (SOUNDCNT_L), 16 bit register (read + write) + 0x4000082 => {} // Control Mixing/DMA Control (SOUNDCNT_H), 16 bit register (read + write) + 0x4000084 => {} // Control Sound on/off (NR52) (SOUNDCNT_X), 16 bit register (read + write) + 0x4000086 => {} // Not Used + 0x4000088 => {} // BIOS/Sound PWM Control (SOUNDBIAS), 16 bit register (read + write) + 0x400008A => {} // Not Used + 0x4000090 => {} // Channel 3 Wave Pattern RAM (2 banks) (WAVE_RAM) 2x10h in size, (read + write) + 0x40000A0 => {} // Channel A FIFO, Data 0-3, (FIFO_A) (write only), 32 bit register + 0x40000A4 => {} // Channel B FIFO, Data 0-3, (FIFO_B) (write only), 32 bit register + 0x40000A8 => {} // Not Used + + // DMA Transfer Channels + 0x40000B0 => {} // DMA 0 Source Address (DMA0SAD), 32 bit register (write only) + 0x40000B4 => {} // DMA 0 Destination Address (DMA0DAD), 32 bit register (write only) + 0x40000B8 => {} // DMA 0 Word Count (DMA0CNT_L), 16 bit register (write only) + 0x40000BA => {} // DMA 0 Control (DMA0CNT_H), 16 bit register (read + write) + 0x40000BC => {} // DMA 1 Source Address (DMA1SAD), 32 bit register (write only) + 0x40000C0 => {} // DMA 1 Destination Address (DMA1DAD), 32 bit register (write only) + 0x40000C6 => {} // DMA 1 Control (DMA1CNT_H), 16 bit register (read + write) + 0x40000C8 => {} // DMA 2 Source Address (DMA2SAD), 32 bit register (write only) + 0x40000CC => {} // DMA 2 Destination Address (DMA2DAD), 32 bit register (write only) + 0x40000D0 => {} // DMA 2 Word Count (DMA2CNT_L), 16 bit register (write only) + 0x40000D2 => {} // DMA 2 Control (DMA2CNT_H), 16 bit register (read + write) + 0x40000D4 => {} // DMA 3 Source Address (DMA3SAD), 32 bit register (write only) + 0x40000D8 => {} // DMA 3 Destination Address (DMA3DAD), 32 bit register (write only) + 0x40000DC => {} // DMA 3 Word Count (DMA3CNT_L), 16 bit register (write only) + 0x40000DE => {} // DMA 3 Control (DMA3CNT_H), 16 bit register (read + write) + 0x40000E0 => {} // Not Used + + // Timer Registers + 0x4000100 => {} // Timer 0 Counter/Reload (TM0CNT_L), 16 bit register (read + write) + 0x4000102 => {} // Timer 0 Control (TM0CNT_H), 16 bit register (read + write) + 0x4000104 => {} // Timer 1 Counter/Reload (TM1CNT_L), 16 bit register (read + write) + 0x4000106 => {} // Timer 1 Control (TM1CNT_H), 16 bit register (read + write) + 0x4000108 => {} // Timer 2 Counter/Reload (TM2CNT_L), 16 bit register (read + write) + 0x400010A => {} // Timer 2 Control (TM2CNT_H), 16 bit register (read + write) + 0x400010C => {} // Timer 3 Counter/Reload (TM3CNT_L), 16 bit register (read + write) + 0x400010E => {} // Timer 3 Control (TM3CNT_H), 16 bit register (read + write) + 0x4000110 => {} // Not Usedrol (TM3CNT_H), 16 bit register (read + write) + 0x4000112 => {} // Not Used + + // Serial Communication (1) + 0x4000120 => {} // SIO Data (Normal-32bit Mode; shared with SIO Data 0 (Parent) (SIODATA32). SIO Data is a 32 bit register and SIO Data 0 (Parent) (Multi-Player Mode) is a 16 bit register (read + write) (SIOMULTI0) + 0x4000122 => {} // SIO Data 1 (1st Child) (Multi-Player Mode) (SIOMULTI1), 16 bit register (read + write) + 0x4000124 => {} // SIO Data 2 (2nd Child) (Multi-Player Mode) (SIOMULTI2), 16 bit register (read + write) + 0x4000126 => {} // SIO Data 3 (3rd Child) (Multi-Player Mode) (SIOMULTI3), 16 bit register (read + write) + 0x4000128 => {} // SIO Control Register (SIOCNT), 16 bit register (read + write) + 0x400012A => {} // SIO Data (Local of MultiPlayer; shared with SIODATA8) (SIOMLT_SEND), 16 bit register (read + write); SIO Data (Normal-8bit and UART Mode) (SIODATA8), 16 bit register (read + write) + 0x400012C => {} // Not Used + + // Keypad Input + 0x4000132 => {} // Key Interrupt Control (KEYCNT), 16 bit register (read + write) + + // Serial Communication (2) + 0x4000134 => {} // SIO Mode Select/General Purpose Data (RCNT), 16 bit register (read + write) + 0x4000136 => {} // Ancient - Infrared Register (Prototypes only) (IR) + 0x4000138 => {} // Not Used + 0x4000140 => {} // SIO JOY Bus Control (JOYCNT), 16 bit register (read + write) + 0x4000142 => {} // Not Used + 0x4000150 => {} // SIO JOY Bus Receive Data (JOY_RECV), 32 bit register (read + write) + 0x4000154 => {} // SIO JOY Bus Transmit Data (JOY_TRANS), 32 bit register (read + write) + 0x4000158 => {} // SIO JOY Bus Receive Status (JOYSTAT), 16 bit register (read + maybe write?) + 0x400015A => {} // Not Used + + //Interrupt, Waitstate, and Power-Down Control + 0x4000200 => {} // Interrupt Enable Register (IE), 16 bit register (read + write) + 0x4000202 => {} // Interrupt Request Flags / IRQ Acknowledge (IF), 16 bit register (read + write) + 0x4000204 => {} // Game Pak Waitstate Control (AITCNT), 16 bit register (read + write) + 0x4000206 => {} // Not used + 0x4000208 => {} // Interrupt Master Enable Register (IME), 16 bit register (read + write) + 0x400020A => {} // Not used + 0x4000300 => {} // Undocumented - Post Boot Flag (POSTFLG), 8 bit register (read + write) + 0x4000301 => {} // Undocumented - Power Down Control (HALTCNT), 8 bit register (write only) + 0x4000302 => {} // Not used + 0x4000410 => {} // Undocumented - Purpose Unknown / Bug ??? 0FFh + 0x4000411 => {} // Not used + 0x4000800 => {} // Undocumented - Internal Memory Control, 32 bit register (read + write) + 0x4000804 => {} // Not used + address if (address & 0xFF00FFFF) == 0x04000800 => {} // Mirrors of 4000800h (repeated each 64K), 32 bit (read + write) + _ => {} + } + } + + fn open(&self) -> u32 { + 0 + } +} + +impl BusValue for u8 { + fn read(bus: &mut Bus, address: u32, access_type: AccessType) -> Self { + bus.cost(address, 8, access_type); + + match address >> 24 { + 0x00 => bus.bios[(address & 0x3FFE) as usize], + 0x02 => bus.ewram[Bus::ewram_index(address)], + 0x03 => bus.iwram[Bus::iwram_index(address)], + 0x04 => { + let half_word = bus.read_register_16(address & !1); + if address & 1 == 0 { + half_word as u8 + } else { + (half_word >> 8) as u8 + } + } + 0x05 => bus.ppu.palette_ram[Bus::palette_index(address)], + 0x06 => bus.ppu.vram[Bus::vram_index(address)], + 0x07 => bus.ppu.oam[Bus::oam_index(address)], + 0x08..=0x0D => bus.gamepak.read_rom_region(address), + 0x0E | 0x0F => bus.read_backup_byte(address), + _ => bus.open() as u8, + } + } + + fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType) { + bus.cost(address, 8, access_type); + + match address >> 24 { + 0x00 => {} + 0x02 => bus.ewram[Bus::ewram_index(address)] = value, + 0x03 => bus.iwram[Bus::iwram_index(address)] = value, + 0x04 => { + let half_word = bus.read_register_16(address & !1); + let new_half_word = if address & 1 == 0 { + (half_word & 0xFF00) | value as u16 + } else { + (half_word & 0x00FF) | (value as u16) << 8 + }; + + bus.write_register_16(address & !1, new_half_word); + } + 0x05 => { + let index = Bus::palette_index(address) & !1; + bus.ppu.palette_ram[index] = value; + bus.ppu.palette_ram[index + 1] = value; + } + 0x06 => { + // TODO: regquires byte duplication based on MOde, bg/character region + let index = Bus::vram_index(address) & !1; + bus.ppu.vram[index] = value; + bus.ppu.vram[index + 1] = value; + } + 0x07 => {} + 0x08..=0x0D => {} + 0x0E | 0x0F => bus.write_backup_byte(address, value), + _ => {} + } + } +} + +impl BusValue for u16 { + fn read(bus: &mut Bus, address: u32, access_type: AccessType) -> Self { + bus.cost(address, 16, access_type); + let address = address & !1; // ensure even + let little_endian = + |arr: &[u8], index: usize| u16::from_le_bytes([arr[index], arr[index + 1]]); + + match address >> 24 { + 0x00 => little_endian(&*bus.bios, (address & 0x3FFE) as usize), + 0x02 => little_endian(&*bus.ewram, Bus::ewram_index(address)), + 0x03 => little_endian(&*bus.iwram, Bus::iwram_index(address)), + 0x04 => bus.read_register_16(address), + 0x05 => little_endian(&*bus.ppu.palette_ram, Bus::palette_index(address)), + 0x06 => little_endian(&*bus.ppu.vram, Bus::vram_index(address)), + 0x07 => little_endian(&*bus.ppu.oam, Bus::oam_index(address)), + 0x08..=0x0D => u16::from_le_bytes([ + bus.gamepak.read_rom_region(address), + bus.gamepak.read_rom_region(address + 1), + ]), + 0x0E | 0x0F => { + let byte = bus.read_backup_byte(address) as u16; + (byte << 8) | byte + } + _ => bus.open() as u16, + } + } + + fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType) { + bus.cost(address, 16, access_type); + let bytes = value.to_le_bytes(); + let address = address & !1; // ensure even + + match address >> 24 { + 0x00 => {} // BIOS no write, + 0x02 => { + let index = Bus::ewram_index(address); + bus.ewram[index] = bytes[0]; + bus.ewram[index + 1] = bytes[1]; + } + 0x03 => { + let index = Bus::iwram_index(address); + bus.iwram[index] = bytes[0]; + bus.iwram[index + 1] = bytes[1]; + } + 0x04 => bus.write_register_16(address, value), + 0x05 => { + let index = Bus::palette_index(address); + bus.ppu.palette_ram[index] = bytes[0]; + bus.ppu.palette_ram[index + 1] = bytes[1]; + } + 0x06 => { + let index = Bus::vram_index(address); + bus.ppu.vram[index] = bytes[0]; + bus.ppu.vram[index + 1] = bytes[1]; + } + 0x07 => { + let index = Bus::oam_index(address); + bus.ppu.oam[index] = bytes[0]; + bus.ppu.oam[index + 1] = bytes[1]; + } + + 0x08..=0x0D => {} + 0x0E | 0x0F => bus.write_backup_byte(address, bytes[0]), + _ => {} + } + } +} + +impl BusValue for u32 { + fn read(bus: &mut Bus, address: u32, access_type: AccessType) -> Self { + bus.cost(address, 32, access_type); + let address = address & !3; // every 4th address + + let little_endian = |arr: &[u8], index: usize| { + u32::from_le_bytes([arr[index], arr[index + 1], arr[index + 2], arr[index + 3]]) + }; + + match address >> 24 { + 0x00 => little_endian(&*bus.bios, (address & 0x3FFF) as usize), + 0x02 => little_endian(&*bus.ewram, Bus::ewram_index(address)), + 0x03 => little_endian(&*bus.iwram, Bus::iwram_index(address)), + 0x04 => { + let low_half_word = bus.read_register_16(address); + let high_half_word = bus.read_register_16(address + 2); + + (high_half_word as u32) << 16 | low_half_word as u32 + } + 0x05 => little_endian(&*bus.ppu.palette_ram, Bus::palette_index(address)), + 0x06 => little_endian(&*bus.ppu.vram, Bus::vram_index(address)), + 0x07 => little_endian(&*bus.ppu.oam, Bus::oam_index(address)), + 0x08..=0x0D => u32::from_le_bytes([ + bus.gamepak.read_rom_region(address), + bus.gamepak.read_rom_region(address + 1), + bus.gamepak.read_rom_region(address + 2), + bus.gamepak.read_rom_region(address + 3), + ]), + 0x0E | 0x0F => { + let byte = bus.read_backup_byte(address) as u32; + (byte << 24) | (byte << 16) | (byte << 8) | byte + } + _ => bus.open() as u32, + } + } + + fn write(bus: &mut Bus, address: u32, value: Self, access_type: AccessType) { + bus.cost(address, 32, access_type); + let bytes = value.to_le_bytes(); + let address = address & !3; // every 4th address + + match address >> 24 { + 0x00 => {} // BIOS no write, + 0x02 => { + let index = Bus::ewram_index(address); + bus.ewram[index] = bytes[0]; + bus.ewram[index + 1] = bytes[1]; + bus.ewram[index + 2] = bytes[2]; + bus.ewram[index + 3] = bytes[3]; + } + 0x03 => { + let index = Bus::iwram_index(address); + bus.iwram[index] = bytes[0]; + bus.iwram[index + 1] = bytes[1]; + bus.iwram[index + 2] = bytes[2]; + bus.iwram[index + 3] = bytes[3]; + } + 0x04 => { + bus.write_register_16(address, u16::from_le_bytes([bytes[0], bytes[1]])); + bus.write_register_16(address + 2, u16::from_le_bytes([bytes[2], bytes[3]])); + } + 0x05 => { + let index = Bus::palette_index(address); + bus.ppu.palette_ram[index] = bytes[0]; + bus.ppu.palette_ram[index + 1] = bytes[1]; + bus.ppu.palette_ram[index + 2] = bytes[2]; + bus.ppu.palette_ram[index + 3] = bytes[3]; + } + 0x06 => { + let index = Bus::vram_index(address); + bus.ppu.vram[index] = bytes[0]; + bus.ppu.vram[index + 1] = bytes[1]; + bus.ppu.vram[index + 2] = bytes[2]; + bus.ppu.vram[index + 3] = bytes[3]; + } + 0x07 => { + let index = Bus::oam_index(address); + bus.ppu.oam[index] = bytes[0]; + bus.ppu.oam[index + 1] = bytes[1]; + bus.ppu.oam[index + 2] = bytes[2]; + bus.ppu.oam[index + 3] = bytes[3]; + } + + 0x08..=0x0D => {} + 0x0E | 0x0F => bus.write_backup_byte(address, bytes[0]), + _ => {} + } + } +} + +impl AddressBus for Bus { + fn read_u8(&mut self, address: u32, access: AccessType) -> u8 { + self.read::(address, access) + } + + fn read_u16(&mut self, address: u32, access: AccessType) -> u16 { + self.read::(address, access) + } + + fn read_u32(&mut self, address: u32, access: AccessType) -> u32 { + self.read::(address, access) + } + + fn write_u8(&mut self, address: u32, value: u8, access: AccessType) { + self.write::(address, value, access); + } + + fn write_u16(&mut self, address: u32, value: u16, access: AccessType) { + self.write::(address, value, access); + } + + fn write_u32(&mut self, address: u32, value: u32, access: AccessType) { + self.write::(address, value, access); + } + + fn idle(&mut self, cycles: u64) { + Bus::idle(self, cycles); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::components::gamepak::GamePak; + + #[test] + fn test_bus_write_u32() { + let gamepak = GamePak::mock(); + let mut bus = Bus::new(gamepak); + + let address = 0x05000001; + let value = 0b100000001 as u32; + let access_type = AccessType::Sequential; + bus.write::(address, value, access_type); + + assert_eq!(bus.scheduler.current, 2); + assert_eq!(&bus.ppu.palette_ram[..4], [1, 1, 0, 0]); + + let gamepak = GamePak::mock(); + let mut bus = Bus::new(gamepak); + bus.write(address, value, access_type); + + assert_eq!(bus.scheduler.current, 2); + assert_eq!(&bus.ppu.palette_ram[..4], [1, 1, 0, 0]); + + let gamepak = GamePak::mock(); + let mut bus = Bus::new(gamepak); + bus.write_u32(address, value, access_type); + + assert_eq!(bus.scheduler.current, 2); + assert_eq!(&bus.ppu.palette_ram[..4], [1, 1, 0, 0]); + } +} diff --git a/gba/src/components/cpu/mod.rs b/gba/src/components/cpu/mod.rs new file mode 100644 index 0000000..9e297de --- /dev/null +++ b/gba/src/components/cpu/mod.rs @@ -0,0 +1,267 @@ +use crate::components::bus::{AccessType, AddressBus}; +/* +The ARM University Program, ARM Architecture Fundamentals: https://www.youtube.com/watch?v=7LqPJGnBPMM + +- Load/store architecture: only load and store can deal with memory, hence processing memory values requires loading +into source register, execution of some instruction, then storing value to some register. No instructions like certain +GBC instructions that can add some value to some value at a specific memory address (ADD A, (HL)) in one instruction. + +- Each processor mode has access to its own stack space and its own private subset of registers (banked) + +13 general purpose registers: + Low registers: r0-r7 + High registers: r8-r12 + +3 special registers + stack pointer (sp) - r13 + link register (lr) - r14 + program counter (pc) - r15 + +- privileged state can manually change mode bits, set autoomatically when a mode change occurs due to exception (e.g., interrupt) + +- Exception Handling Steps: + 1) Save processor status + - Copy cpsr into spsr_ to hold snapshot of current mode processor state + - Stores the return address in lr_ + 2) Change processor status for exception + - Mode field bits + - ARM or Thumb state + - interrupt disable bits (if needed) + - sets pc to vector address + 3) Execute + 4) Return to main + - Restore cpsr from spsr_ + - Restore pc from lr_ + +Instructions +- Each instruction is conditional; hence it can be a Noop if condition flags fail +- By default condition flags are not changed + +Example Instructions: +SUB r0, r1, #5 -> r0 = r1 - 5 +ADD r2, r3, r3, LSL #2 (add with inline shift) -> r2 = r3 + (r3 * 4) +ANDS r4, r4, #0x20 (suffix x means alu condition codes will reflect operation results) -> r4 &= 0x20; +ADDEQ r5, r5, r6 (instruction only executes if the EQ condition is true at the executation stage of pipeline else its noop) -> if (EQ) r5 += r6 +B