This commit is contained in:
Trevor Merritt 2025-05-31 22:56:05 -04:00
parent ef3d605528
commit 21f6e492f6
30 changed files with 1145 additions and 1286 deletions

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/trevors_chip8_toy.iml" filepath="$PROJECT_DIR$/.idea/trevors_chip8_toy.iml" />
</modules>
</component>
</project>

21
.idea/trevors_chip8_toy.iml generated Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/gemma/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gemma/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/gemmaegui/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gemmaegui/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/gemmaimgui/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gemmaimgui/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/gemmarat/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gemmatelnet/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gemmatelnet/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/gemmautil/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gemmautil/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

395
.idea/workspace.xml generated
View File

@ -7,43 +7,32 @@
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
</component>
<component name="ChangeListManager">
<list default="true" id="8b5adad6-6758-4324-bcad-4fdc11e35fe6" name="Changes" comment="CLEANUP: cleans up dependencies to use workspace version&#10;CLEANUP: standardizes on pretty_env_logger&#10;NEWBIN: Adds gemmarat to use Ratatui as an interface&#10;CLEANUP: removes debugging display from computer.rs&#10;ENHANCEMENT: Constants for locations of test roms built from environment&#10;BUGFIX: SoundTimer was using i32 internally when it needs to be u8&#10;CLEANUP: removes commented code used during diagnostics">
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/modules.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemma/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/gemma/Cargo.toml" afterDir="false" />
<list default="true" id="8fa1ca42-220d-4157-8efb-e642e4673d11" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/gemma/tests/computer_manager.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/gemma/tests/delay_timer.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/gemma/tests/instructions.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/gemma/tests/keypad.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/gemma/tests/registers.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/gemma/tests/sound_timer.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/gemma/tests/stack.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/gemma/tests/system_memory.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemma/src/chip8/computer.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemma/src/chip8/computer.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemma/src/chip8/instructions.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemma/src/chip8/instructions.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemma/src/chip8/keypad.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemma/src/chip8/keypad.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemma/src/chip8/util.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemma/src/chip8/util.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemma/tests/computer_tests.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemma/tests/computer_tests.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemma/tests/state_tests.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemma/tests/state_tests.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemma/tests/test_utils.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemma/tests/test_utils.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemma/tests/unit_tests.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemma/tests/unit_tests.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemmarat/src/bin/gemmarat.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemmarat/src/bin/gemmarat.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/gemma_disassembler_manual_document.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/gemma_integration_corax_plus.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/gemma_integration_flags.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/gemma_integration_ibm_rom_output.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/gemma_integration_level_1_test.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/gemma_integration_rps_stage1.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/gemma_integration_rps_stage2.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_keypad_to_string.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_manager_status.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_multi_sprite.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_reset_clears_video.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_scroll_down_10_hd.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_scroll_down_1_hd.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_scroll_left_4.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_scroll_left_4_hd.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_scroll_right_4.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_scroll_right_4_hd.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_video_highdef.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_video_scroll_down_1.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_video_scroll_down_10.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_video_scroll_up_test_hd.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_video_scroll_up_test_sd.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_video_write_checkerboard.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/test_video_zero.asc" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/gemma/tests/util_tests.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemma/tests/util.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemmatelnet/src/telnet_utils.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemmatelnet/src/telnet_utils.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemmautil/src/bin/bin2hex.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemmautil/src/bin/bin2hex.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gemmautil/src/bin/ch8asm.rs" beforeDir="false" afterPath="$PROJECT_DIR$/gemmautil/src/bin/ch8asm.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/state/smoke_001_round_trip_serialize_deserialize.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/state/smoke_002_round_trip_serialize_deserialize_compressed.tflt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/state/smoke_002_round_trip_serialize_deserialize_compressed.tflt.uncompressed" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/resources/test/state/video_lowres_schip_draw_chip8_sprite_result.json" beforeDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -61,95 +50,34 @@
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="MacroExpansionManager">
<option name="directoryName" value="w8ntkzgb" />
<option name="directoryName" value="q85ybjmm" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 8
}</component>
<component name="ProjectId" id="2oOJId1CDAIspnkZVvQGeRPAufy" />
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 5
}]]></component>
<component name="ProjectId" id="2xpB7kQEFEwMZf3OlpLuqA97yi7" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;Cargo.Build `Run gemmaimgui default roms`.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Build `Run gemmatelnetd`.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Build `Run tct`.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Build `Test byte_to_bools_valid`.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Build `Test join_bytes`.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Build `Test read_bits_from_instruction`.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Build `Test registers_equality`.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Build `Test round_trip`.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Build `Test start`.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Build `Test state_tests`.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Build `Test test_serialization_round_trip`.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Build `debug testcompression`.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Run ch8asm.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Run gemmaegui.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Run gemmaimgui default roms.executor&quot;: &quot;Debug&quot;,
&quot;Cargo.Run gemmaimgui.executor&quot;: &quot;Debug&quot;,
&quot;Cargo.Run gemmatelnetd.executor&quot;: &quot;Debug&quot;,
&quot;Cargo.Run keyboard.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Run tct.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Run testcompression.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test assmber_tests.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test bools_to_byte.executor&quot;: &quot;Debug&quot;,
&quot;Cargo.Test byte_to_bools_valid.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test computer_001_system_zero_state.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test computer_tests.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test default_test.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test join_bytes.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test join_bytes_swap_endian.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test load_rom_allows_starting.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test quirks_mode_test.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test read_address_from_instruction.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test read_bits_from_instruction.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test read_x_from_instruction.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test read_y_from_instruction.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test registers_equality.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test reset_clears_run_state.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test reset_clears_video.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test round_trip.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test smoke.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test split_bytes.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test split_bytes_swap_endian.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test start.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test state_tests.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test status_of_manager.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test swap_endian.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test system_memory_load_program.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test test_serialization_round_trip.executor&quot;: &quot;Run&quot;,
&quot;Cargo.Test util_tests.executor&quot;: &quot;Run&quot;,
&quot;Cargo.debug testcompression.executor&quot;: &quot;Debug&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.rust.reset.selective.auto.import&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;add__telnet__interface&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/tmerritt/Projects/trevors_chip8_toy&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;org.rust.cargo.project.model.PROJECT_DISCOVERY&quot;: &quot;true&quot;,
&quot;org.rust.cargo.project.model.impl.CargoExternalSystemProjectAware.subscribe.first.balloon&quot;: &quot;&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;inlay.hints&quot;
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"Cargo.Test keypad.executor": "Debug",
"Cargo.Test video_lowres_schip_draw_chip8_sprite.executor": "Debug",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.rust.reset.selective.auto.import": "true",
"git-widget-placeholder": "add__telnet__interface",
"last_opened_file_path": "/home/tmerritt/Projects/trevors_chip8_toy/Cargo.toml",
"node.js.detected.package.eslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"org.rust.cargo.project.model.PROJECT_DISCOVERY": "true",
"org.rust.first.attach.projects": "true"
}
}</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/gemmatelnet" />
<recent name="$PROJECT_DIR$/gemmaimgui" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/gemmarat/src/bin" />
<recent name="$PROJECT_DIR$/gemma/src" />
<recent name="$PROJECT_DIR$/gemma/tests/unit_tests" />
<recent name="$PROJECT_DIR$/gemma/tests" />
<recent name="$PROJECT_DIR$/gemma/src/bin" />
</key>
</component>
<component name="RunManager" selected="Cargo.Test state_tests">
<configuration name="Run gemmaimgui default roms" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package gemmaimgui --bin gemmaimgui --" />
}]]></component>
<component name="RunManager" selected="Cargo.Test keypad">
<configuration name="Run bin2hex" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package gemmautil --bin bin2hex" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
@ -165,8 +93,8 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Run gemmaimgui default roms" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package gemmaimgui --bin gemmaimgui --" />
<configuration name="Run gemmaegui" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package gemmaegui --bin gemmaegui" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
@ -182,8 +110,8 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Run gemmaimgui default roms" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package gemmaimgui --bin gemmaimgui --" />
<configuration name="Run gemmaimgui" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package gemmaimgui --bin gemmaimgui" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
@ -199,8 +127,8 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Run testcompression" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="run --package gemma --bin testcompression" />
<configuration name="Run gemmarat" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package gemmarat --bin gemmarat" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
@ -216,8 +144,8 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test load_rom_allows_starting" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="test --package gemma --test computer_tests load_rom_allows_starting -- --exact" />
<configuration name="Run gemmatelnetd" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package gemmatelnet --bin gemmatelnetd" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
@ -233,8 +161,8 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test load_rom_allows_starting" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="test --package gemma --test computer_tests load_rom_allows_starting -- --exact" />
<configuration name="Run speedtest" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package gemma --bin speedtest" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
@ -250,8 +178,8 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test round_trip" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="test --package gemma --test test_compression_tests round_trip -- --exact" />
<configuration name="Test keypad" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="test --package gemma --test keypad" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
@ -267,8 +195,8 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test round_trip" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="test --package gemma --test test_compression_tests round_trip -- --exact" />
<configuration name="Test trevors_chip8_toy" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="test --workspace" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
@ -284,8 +212,8 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test state_tests" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="test --package gemma --test state_tests" />
<configuration name="Test video_lowres_schip_draw_chip8_sprite" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="test --package gemma --test unit_tests video_lowres_schip_draw_chip8_sprite -- --exact" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
@ -301,124 +229,10 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test status_of_manager" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="test --package gemma --test unit_tests status_of_manager -- --exact" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test status_of_manager" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="test --package gemma --test unit_tests status_of_manager -- --exact" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test system_memory_load_program" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="test --package gemma --test unit_tests system_memory_load_program -- --exact" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test system_memory_load_program" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="command" value="test --package gemma --test unit_tests system_memory_load_program -- --exact" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="debug testcompression" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package gemma --bin testcompression -- decompress-file $PROJECT_DIR$/resources/test/state/smoke_002_round_trip_serialize_deserialize_compressed.tflt" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="debug testcompression" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package gemma --bin testcompression -- decompress-file $PROJECT_DIR$/resources/test/state/smoke_002_round_trip_serialize_deserialize_compressed.tflt" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<list>
<item itemvalue="Cargo.Run gemmaimgui default roms" />
<item itemvalue="Cargo.debug testcompression" />
<item itemvalue="Cargo.Test state_tests" />
<item itemvalue="Cargo.Test load_rom_allows_starting" />
<item itemvalue="Cargo.Test round_trip" />
<item itemvalue="Cargo.Test status_of_manager" />
<item itemvalue="Cargo.Test system_memory_load_program" />
</list>
<recent_temporary>
<list>
<item itemvalue="Cargo.Test state_tests" />
<item itemvalue="Cargo.Test load_rom_allows_starting" />
<item itemvalue="Cargo.Test round_trip" />
<item itemvalue="Cargo.Test status_of_manager" />
<item itemvalue="Cargo.Test system_memory_load_program" />
<item itemvalue="Cargo.Test keypad" />
<item itemvalue="Cargo.Test video_lowres_schip_draw_chip8_sprite" />
</list>
</recent_temporary>
</component>
@ -428,93 +242,16 @@
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="8b5adad6-6758-4324-bcad-4fdc11e35fe6" name="Changes" comment="" />
<created>1730734609486</created>
<changelist id="8fa1ca42-220d-4157-8efb-e642e4673d11" name="Changes" comment="" />
<created>1748624969802</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1730734609486</updated>
<workItem from="1730734610557" duration="232000" />
<workItem from="1730898677853" duration="3080000" />
<workItem from="1730916772198" duration="3456000" />
<workItem from="1730994368989" duration="7000" />
<workItem from="1731001862091" duration="2026000" />
<workItem from="1731007664228" duration="5718000" />
<workItem from="1731083920810" duration="3820000" />
<workItem from="1732894051052" duration="1028000" />
<workItem from="1732902486469" duration="6000" />
<workItem from="1733326861558" duration="653000" />
<workItem from="1741629265240" duration="2162000" />
<workItem from="1742569809065" duration="762000" />
<workItem from="1742570597558" duration="794000" />
<workItem from="1747246379430" duration="8729000" />
<workItem from="1747837171143" duration="1151000" />
<workItem from="1747850558953" duration="5700000" />
<workItem from="1747933016164" duration="7553000" />
<workItem from="1748009797240" duration="70000" />
<workItem from="1748019256320" duration="2989000" />
<workItem from="1748259109807" duration="13567000" />
<workItem from="1748284479016" duration="3483000" />
<workItem from="1748346419689" duration="8043000" />
<workItem from="1748355230865" duration="313000" />
<workItem from="1748357652780" duration="8865000" />
<workItem from="1748431993830" duration="10532000" />
<workItem from="1748446884797" duration="12753000" />
<workItem from="1748544537133" duration="3555000" />
<workItem from="1748612927269" duration="25000" />
<workItem from="1748613054404" duration="738000" />
<workItem from="1748613802419" duration="71000" />
<workItem from="1748613883436" duration="27000" />
<workItem from="1748613912658" duration="198000" />
<workItem from="1748614113271" duration="2000" />
<workItem from="1748614129649" duration="56000" />
<workItem from="1748616677715" duration="320000" />
<updated>1748624969802</updated>
<workItem from="1748624970827" duration="3451000" />
</task>
<task id="LOCAL-00001" summary="CLEANUP: cleans up dependencies to use workspace version&#10;CLEANUP: standardizes on pretty_env_logger&#10;NEWBIN: Adds gemmarat to use Ratatui as an interface&#10;CLEANUP: removes debugging display from computer.rs&#10;ENHANCEMENT: Constants for locations of test roms built from environment&#10;BUGFIX: SoundTimer was using i32 internally when it needs to be u8&#10;CLEANUP: removes commented code used during diagnostics">
<option name="closed" value="true" />
<created>1748539601919</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1748539601919</updated>
</task>
<task id="LOCAL-00002" summary="CLEANUP: cleans up dependencies to use workspace version&#10;CLEANUP: standardizes on pretty_env_logger&#10;NEWBIN: Adds gemmarat to use Ratatui as an interface&#10;CLEANUP: removes debugging display from computer.rs&#10;ENHANCEMENT: Constants for locations of test roms built from environment&#10;BUGFIX: SoundTimer was using i32 internally when it needs to be u8&#10;CLEANUP: removes commented code used during diagnostics">
<option name="closed" value="true" />
<created>1748539623332</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1748539623332</updated>
</task>
<task id="LOCAL-00003" summary="CLEANUP: cleans up dependencies to use workspace version&#10;CLEANUP: standardizes on pretty_env_logger&#10;NEWBIN: Adds gemmarat to use Ratatui as an interface&#10;CLEANUP: removes debugging display from computer.rs&#10;ENHANCEMENT: Constants for locations of test roms built from environment&#10;BUGFIX: SoundTimer was using i32 internally when it needs to be u8&#10;CLEANUP: removes commented code used during diagnostics">
<option name="closed" value="true" />
<created>1748539630427</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1748539630427</updated>
</task>
<option name="localTasksCounter" value="4" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="UnknownFeatures">
<option featureType="com.intellij.fileTypeFactory" implementationName="*.asc" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="CLEANUP: cleans up dependencies to use workspace version&#10;CLEANUP: standardizes on pretty_env_logger&#10;NEWBIN: Adds gemmarat to use Ratatui as an interface&#10;CLEANUP: removes debugging display from computer.rs&#10;ENHANCEMENT: Constants for locations of test roms built from environment&#10;BUGFIX: SoundTimer was using i32 internally when it needs to be u8&#10;CLEANUP: removes commented code used during diagnostics" />
<option name="LAST_COMMIT_MESSAGE" value="CLEANUP: cleans up dependencies to use workspace version&#10;CLEANUP: standardizes on pretty_env_logger&#10;NEWBIN: Adds gemmarat to use Ratatui as an interface&#10;CLEANUP: removes debugging display from computer.rs&#10;ENHANCEMENT: Constants for locations of test roms built from environment&#10;BUGFIX: SoundTimer was using i32 internally when it needs to be u8&#10;CLEANUP: removes commented code used during diagnostics" />
</component>
</project>

View File

@ -75,6 +75,10 @@ impl Chip8Computer {
self.clone().video_memory.format_as_string()
}
pub fn dump_state_to_json(&self) -> String {
serde_json::to_string(self).unwrap()
}
pub fn new() -> Self {
Chip8Computer::default()
}

View File

@ -410,8 +410,10 @@ impl Display for Chip8CpuInstructions {
impl Chip8CpuInstructions {
pub fn from_str(input: &str) -> Chip8CpuInstructions {
// split the instruction into its parts...
let mut parts = input.split(" ");
// print!("THERE ARE {} PARTS", parts.clone().count());
let first_part = parts.next().unwrap_or("").to_ascii_uppercase();
// take the next value...
// ...strip off the extra...
@ -450,6 +452,7 @@ impl Chip8CpuInstructions {
"\tFirst part is {:?} / {:?} / {:?} / {:?}",
first_part, param1, param2, param3
);
println!("MATCHING [[{}]]", first_part);
match first_part.as_str() {
INST_ADDI => ADDI(param1 as u8),
INST_ADD => ADD(param1 as u8, param2 as u8),

View File

@ -26,7 +26,7 @@ impl Keypad {
}
}
}
println!("PREPARE TO RETURN {}", return_value);
return_value
}
}

View File

@ -119,3 +119,4 @@ impl InstructionUtil {
working & working_mask as u16
}
}

View File

@ -0,0 +1,127 @@
mod test_utils;
use std::path::Path;
use gemma::chip8::computer::Chip8Computer;
use gemma::chip8::computer_manager::Chip8ComputerManager;
use gemma::chip8::quirk_modes::QuirkMode;
use gemma::chip8::quirk_modes::QuirkMode::{Chip8, SChipModern, XOChip};
use gemma::constants::TEST_ROM_ROOT;
use crate::test_utils::load_compressed_result;
/// Tests the ComputerManager structure
#[test]
fn smoke() {
assert!(true)
}
#[test]
fn default_test() {
let new_manager = Chip8ComputerManager::default();
assert_eq!(new_manager.core_should_run, false);
assert_eq!(new_manager.num_cycles(), 0);
assert_eq!(new_manager.quirks_mode(), Chip8);
}
#[test]
fn quirks_mode_test() {
let mut new_manager = Chip8ComputerManager::default();
assert_eq!(new_manager.quirks_mode(), Chip8);
new_manager.reset(QuirkMode::XOChip);
assert_eq!(new_manager.quirks_mode(), XOChip);
new_manager.reset(QuirkMode::SChipModern);
assert_eq!(new_manager.quirks_mode(), SChipModern);
new_manager.reset(Chip8);
assert_eq!(new_manager.quirks_mode(), Chip8);
}
#[test]
fn load_rom_allows_starting() {
let mut new_manager = Chip8ComputerManager::default();
assert!(!new_manager.core_should_run);
let p = TEST_ROM_ROOT.to_string() + "/1-chip8-logo.ch8";
let full_path = Path::new(p.as_str());
new_manager.load_new_program_from_disk_to_system_memory(full_path);
assert!(new_manager.core_should_run)
}
#[test]
fn reset_clears_run_state() {
let mut new_manager = Chip8ComputerManager::default();
let p = format!(
"{}/../resources/test/roms/1-chip8-logo.ch8",
std::env::current_dir().unwrap().display()
);
new_manager.load_new_program_from_disk_to_system_memory(Path::new(p.as_str()));
new_manager.reset(QuirkMode::Chip8);
assert!(!new_manager.core_should_run);
}
#[test]
fn tick_when_ready() {
let mut new_manager = Chip8ComputerManager::default();
new_manager.load_new_program_from_disk_to_system_memory(Path::new(
format!(
"{}/../resources/test/roms/1-chip8-logo.ch8",
std::env::current_dir().unwrap().display()
)
.as_str(),
));
assert!(new_manager.core_should_run);
}
#[test]
fn start_stop_computer() {
let mut computer = Chip8ComputerManager::new();
assert_eq!(computer.core_should_run, false);
computer.start();
assert_eq!(computer.core_should_run, true);
computer.step();
assert_eq!(computer.core_should_run, true);
computer.stop();
assert_eq!(computer.core_should_run, false);
computer.reset(Chip8);
assert_eq!(computer.core_should_run, false);
}
#[test]
fn state_default_matches() {
let computer = Chip8Computer::default();
let mut manager = Chip8ComputerManager::default();
assert_eq!(computer, *manager.state());
}
#[test]
fn keys_test_manager() {
let mut manager = Chip8ComputerManager::default();
for i in 0..16 {
assert_eq!(manager.is_key_pressed(i), false);
}
// press key 5
manager.press_key(5);
assert_eq!(manager.is_key_pressed(5), true);
manager.release_key(5);
assert_eq!(manager.is_key_pressed(5), false);
}
#[test]
#[ignore]
fn status_of_manager() {
let mut manager = Chip8ComputerManager::default();
println!("MANAGER STATUS [{}]", manager.status_as_string());
let expected_state = load_compressed_result("test_manager_status");
assert_eq!(expected_state, manager.status_as_string());
}

View File

@ -13,6 +13,7 @@ use std::path::Path;
fn smoke() {
assert!(true)
}
#[test]
fn reset_clears_video() {
let mut x = Chip8Computer::new();
@ -131,98 +132,14 @@ fn level4_test() {
);
}
#[test]
fn registers_equality() {
let data_set: Vec<(Chip8Registers, Chip8Registers, bool)> = vec![
(Chip8Registers::default(), Chip8Registers::default(), true),
(
Chip8Registers {
registers: [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
],
i_register: 0,
pc: 0,
},
Chip8Registers {
registers: [
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
],
i_register: 0,
pc: 0,
},
false,
),
];
for (first, second, matches) in data_set.iter() {
assert_eq!(first == second, *matches)
}
}
#[test]
fn default_test() {
let new_manager = Chip8ComputerManager::default();
assert_eq!(new_manager.core_should_run, false);
assert_eq!(new_manager.num_cycles(), 0);
assert_eq!(new_manager.quirks_mode(), Chip8);
fn partial_eq_chip8computer() {
let x = Chip8Computer::new();
let y = Chip8Computer::new();
assert_eq!(x, y)
}
#[test]
fn quirks_mode_test() {
let mut new_manager = Chip8ComputerManager::default();
assert_eq!(new_manager.quirks_mode(), Chip8);
new_manager.reset(QuirkMode::XOChip);
assert_eq!(new_manager.quirks_mode(), XOChip);
new_manager.reset(QuirkMode::SChipModern);
assert_eq!(new_manager.quirks_mode(), SChipModern);
new_manager.reset(Chip8);
assert_eq!(new_manager.quirks_mode(), Chip8);
}
#[test]
fn load_rom_allows_starting() {
let mut new_manager = Chip8ComputerManager::default();
assert!(!new_manager.core_should_run);
let p = TEST_ROM_ROOT.to_string() + "/1-chip8-logo.ch8";
let full_path = Path::new(p.as_str());
new_manager.load_new_program_from_disk_to_system_memory(full_path);
assert!(new_manager.core_should_run)
}
#[test]
fn reset_clears_run_state() {
let mut new_manager = Chip8ComputerManager::default();
let p = format!(
"{}/../resources/test/roms/1-chip8-logo.ch8",
std::env::current_dir().unwrap().display()
);
new_manager.load_new_program_from_disk_to_system_memory(Path::new(p.as_str()));
new_manager.reset(QuirkMode::Chip8);
assert!(!new_manager.core_should_run);
}
#[test]
fn tick_when_ready() {
let mut new_manager = Chip8ComputerManager::default();
new_manager.load_new_program_from_disk_to_system_memory(Path::new(
format!(
"{}/../resources/test/roms/1-chip8-logo.ch8",
std::env::current_dir().unwrap().display()
)
.as_str(),
));
assert!(new_manager.core_should_run);
}
#[test]
fn tick_when_not_ready() {}

View File

@ -0,0 +1,27 @@
use gemma::chip8::delay_timer::DelayTimer;
#[test]
fn delay_timer_default() {
let x = DelayTimer::default();
assert_eq!(x.current(), 0xff);
}
#[test]
fn delay_timer_ticks_reduce_time() {
let mut st = DelayTimer::new();
st.set_timer(100);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 97);
}
#[test]
fn delay_timer_out_of_ticks_works() {
let mut st = DelayTimer::new();
st.set_timer(0);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 0);
}

619
gemma/tests/instructions.rs Normal file
View File

@ -0,0 +1,619 @@
mod test_utils;
use log::debug;
use gemma::chip8::computer::Chip8Computer;
use gemma::chip8::instructions::Chip8CpuInstructions;
use gemma::constants::{CHIP8FONT_2, CHIP8_VIDEO_MEMORY};
use crate::test_utils::read_compressed_test_result;
/// START OF THE EXECUTION TESTS
#[test]
fn instruction_tests() {
// 0x0nnn Exit to System Call
let mut x = Chip8Computer::new();
Chip8CpuInstructions::SYS(0).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::SYS(0xFA0).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0xFA0);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::SYS(0x0AF).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x0AF);
// 0x1nnn Jump to Address
let mut x = Chip8Computer::new();
Chip8CpuInstructions::JPA(0).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::JPA(0xABC).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0xABC);
// 0x6xkk Set Vx = kk
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LDR(1, 0x12).execute(&mut x);
assert_eq!(x.registers.peek(1), 0x12);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LDR(2, 0x21).execute(&mut x);
assert_eq!(x.registers.peek(2), 0x21);
// 0x3xkk Skip next instruction if Vx = kk.
// The interpreter compares register Vx to kk,
// and if they are equal, increments the program counter by 2.
// test setup: Load value 0x84 into V1
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x84);
Chip8CpuInstructions::SEX(1, 0x48).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x84);
Chip8CpuInstructions::SEX(1, 0x84).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
// 0x4xkk Skip next instruction if Vx != kk
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x84);
x.registers.poke(0x2, 0x84);
// skip, compare 0x84 to 0x84
Chip8CpuInstructions::SEY(0x1, 0x2).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x84);
x.registers.poke(0x2, 0x48);
Chip8CpuInstructions::SEY(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
// 0x8xy0 Set value of Vy in Vx
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x02);
Chip8CpuInstructions::LDRY(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(1), 0x02);
// 0x8xy1 Set Vx = Vx OR Vy
// 0b0101 0000 (0x50)
// | 0b0000 1010 (0x0A)
// 0b0101 1010 (0x5A)
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0b01010000);
x.registers.poke(0x02, 0b00001010);
Chip8CpuInstructions::OR(1, 2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0b01011010);
// 0x8xy2 Set Vx = Vx AND Vy
// 0b1111 1100 (0xFC)
// & 0b1100 1010 (0xCA)
// 0b1100 1000 (0xC8)
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0xFC);
x.registers.poke(0x02, 0xCA);
Chip8CpuInstructions::AND(1, 2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0xC8);
// 0x8xy3 Set Vx = Vx XOR Vy
// 0b1111 1100 (0xFC)
// ^ 0b1100 1010 (0xCA)
// 0b0011 0110 (0x36)
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0b11111100);
x.registers.poke(0x02, 0b11001010);
Chip8CpuInstructions::ORY(1, 2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0b00110110);
// 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
// T1 T2: Judgement Test
// 0x01 0xFF
// + 0x01 0x01
// 0x02 F0 0x00 F1
let mut x = Chip8Computer::new();
x.registers.poke(0x0f, 00);
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0xf), 0x00);
assert_eq!(x.registers.peek(0x01), 0x02);
let mut x = Chip8Computer::new();
x.registers.poke(0x0f, 0x00);
x.registers.poke(0x01, 0xff);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::ADDR(1, 2).execute(&mut x);
assert_eq!(x.registers.peek(0xf), 1);
assert_eq!(x.registers.peek(1), 0);
/*
Set Vx = Vx SHR 1.
If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2.
*/
let mut x = Chip8Computer::new();
x.registers.poke(0x0f, 0x00);
x.registers.poke(0x01, 0b00001000);
x.registers.poke(0x02, 0b00000000);
Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x); // 0b0000 0010 (0x02) (Not Set)
assert_eq!(x.registers.peek(1), 0b00000100);
assert_eq!(x.registers.peek(0xf), 0);
x = Chip8Computer::new();
x.registers.poke(0x0f, 0x00);
x.registers.poke(0x01, 0b00001001);
Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0b00000100);
assert_eq!(x.registers.peek(0xf), 1);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LDIA(0x123).execute(&mut x);
assert_eq!(x.registers.peek_i(), 0x123);
assert_eq!(x.registers.peek_pc(), 0x202);
}
#[test]
fn jp_v0addr_test() {
let mut x = Chip8Computer::new();
/// jump to I + nnn
x.registers.poke(0x0, 0xff);
Chip8CpuInstructions::JPI(0x100).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x1FF);
}
#[test]
fn cls_test() {
let mut x = Chip8Computer::new();
Chip8CpuInstructions::CLS.execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
for i in 0..CHIP8_VIDEO_MEMORY {
assert!(!x.video_memory.peek(i as u16));
}
// draw some thing to the video memory
x.video_memory.poke(0x01, true);
x.video_memory.poke(0x03, true);
x.video_memory.poke(0x05, true);
Chip8CpuInstructions::CLS.execute(&mut x);
for i in 0..CHIP8_VIDEO_MEMORY {
assert!(!x.video_memory.peek(i as u16));
}
}
#[test]
fn skip_next_instruction_ne_text() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xf0);
Chip8CpuInstructions::SNEB(0x1, 0x0f).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xf0);
Chip8CpuInstructions::SNEB(0x1, 0xf0).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
}
#[test]
fn addivx_test() {
let mut x = Chip8Computer::new();
x.registers.poke_i(0xabc);
x.registers.poke(0x0, 0x10);
Chip8CpuInstructions::ADDI(0x0).execute(&mut x);
assert_eq!(x.registers.peek_i(), 0xacc);
}
#[test]
fn ldstvt_test() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xf0);
Chip8CpuInstructions::LDIS(0x01).execute(&mut x);
assert_eq!(x.sound_timer.current(), 0xf0);
x.sound_timer.tick();
x.sound_timer.tick();
x.sound_timer.tick();
assert_eq!(x.sound_timer.current(), 0xed);
}
#[test]
fn rnd_vx_byte_text() {
let mut x = Chip8Computer::new();
Chip8CpuInstructions::RND(0x1, 0x0f).execute(&mut x);
let new_value = x.registers.peek(0x1);
assert!(new_value < 0x10);
}
#[test]
fn add_vx_byte_test() {
let mut x = Chip8Computer::new();
// set a value in the register
x.registers.poke(0x01, 0xab);
// add 0x10 to register
Chip8CpuInstructions::ADD(0x1, 0x10).execute(&mut x);
assert_eq!(x.registers.peek(1), 0xbb);
}
#[test]
fn sub_vx_vy_test() {
let mut x = Chip8Computer::new();
// load values in 2 registers
x.registers.poke(0x1, 0x10);
x.registers.poke(0x2, 0x08);
Chip8CpuInstructions::SUB(0x1, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0xf), 1);
assert_eq!(x.registers.peek(0x1), 0x8);
assert_eq!(x.registers.peek_pc(), 0x202);
}
#[test]
fn sne_vx_vy_test() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x10);
x.registers.poke(0x2, 0x10);
Chip8CpuInstructions::SNEY(0x1, 0x2).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x10);
x.registers.poke(0x2, 0x00);
Chip8CpuInstructions::SNEY(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204)
}
#[test]
fn ld_dt_vx_test() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x10);
Chip8CpuInstructions::LDD(0x1).execute(&mut x);
assert_eq!(x.delay_timer.current(), 0x10);
for _ in 0..0x20 {
x.delay_timer.tick();
}
assert_eq!(x.delay_timer.current(), 0);
}
#[test]
fn ld_vx_dt_test() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xf0);
Chip8CpuInstructions::LDD(0x1).execute(&mut x);
x.delay_timer.tick();
x.delay_timer.tick();
x.delay_timer.tick();
assert_eq!(x.delay_timer.current(), 0xed);
}
#[test]
fn subn_vx_vy_test() {
// This instruction subtracts the value in
// register Vx from the value in register Vy and stores the result in register Vx.
// The subtraction is performed as follows: Vx = Vy - Vx. If Vy is less than Vx,
// the result will wrap around (due to the 8-bit nature of the registers).
// The carry flag (VF) is set to 1 if there is no borrow (i.e., Vy is greater
// than or equal to Vx), and it is set to 0 if there is a borrow.
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xa0);
x.registers.poke(0x2, 0xab);
Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x);
// expect the result to be 0x0b
assert_eq!(x.registers.peek(0x1), 0x0b);
// expect the vf register to be set to 1 as there was overflow
assert_eq!(x.registers.peek(0xf), 0x1);
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0xab);
x.registers.poke(0x02, 0xa0);
Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x);
// expect the result to be 11110101, -0xB, -11, 245, 0xF5
assert_eq!(x.registers.peek(0x1), 0xf5);
assert_eq!(x.registers.peek(0xf), 0x0);
// 8xyE - SHL Vx {, Vy}
// Set Vx = Vx SHL 1.
//
// If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2.
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0b00100000);
Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x);
assert_eq!(x.registers.peek(0x1), 0b01000000);
assert_eq!(x.registers.peek(0xf), 0x0);
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0b10101010);
Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x);
assert_eq!(x.registers.peek(0x1), 0b01010100);
assert_eq!(x.registers.peek(0xf), 0x1);
// Fx29 - LD F, Vx
// Set I = location of sprite for digit Vx.
//
// The value of I is set to the location for the hexadecimal sprite corresponding to the value of Vx. See section 2.4, Display, for more information on the Chip-8 hexadecimal font.
let mut x = Chip8Computer::new();
// target_sprite = 2
// target_offset = 0x5
x.registers.poke(0x1, 0x2);
Chip8CpuInstructions::LDFX(0x1).execute(&mut x);
assert_eq!(x.registers.peek_i(), 10);
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x06);
Chip8CpuInstructions::LDFX(0x1).execute(&mut x);
assert_eq!(x.registers.peek_i(), 30);
// Fx33 - LD B, Vx
// Store BCD representation of Vx in memory locations I, I+1, and I+2.
//
// The interpreter takes the decimal value of Vx, and places the hundreds digit
// in memory at location in I, the tens digit at location I+1,
// and the ones digit at location I+2.
let mut x = Chip8Computer::new();
// load the value 123 (0x7b)
x.registers.poke(0x1, 0x7b);
x.registers.poke_i(0x500);
Chip8CpuInstructions::BCD(0x1).execute(&mut x);
assert_eq!(x.memory.peek(0x500), 0x1);
assert_eq!(x.memory.peek(0x501), 0x2);
assert_eq!(x.memory.peek(0x502), 0x3);
// Store registers V0 through Vx in memory starting at location I.
//
// The interpreter copies the values of registers V0 through Vx into memory,
// starting at the address in I.
let mut x = Chip8Computer::new();
// Load Registers.
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
for (idx, val) in to_load.iter().enumerate() {
x.registers.poke(idx as u8, *val);
}
x.registers.poke_i(0x500);
Chip8CpuInstructions::LDIX(to_load.len() as u8).execute(&mut x);
// Verify the values are in memory from 0x500 to 0x507
for (idx, value) in to_load.iter().enumerate() {
assert_eq!(x.memory.peek(0x500 + idx as u16), *value);
}
// Read registers V0 through Vx from memory starting at location I.
//
// The interpreter reads values from memory starting at location I into registers V0 through Vx.
let mut x = Chip8Computer::new();
let base_offset = 0x500;
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
// start by setting values in memory
for (idx, memory) in to_load.iter().enumerate() {
let target_address = base_offset + idx;
let target_value = *memory;
x.memory.poke(target_address as u16, target_value);
}
// where to load from
x.registers.poke_i(0x500);
// how much to load
x.registers.poke(0x6, to_load.len() as u8);
// then copying them values memory to registers
Chip8CpuInstructions::LDRI(0x6).execute(&mut x);
// now check that we have the right values in our registers
for (idx, value) in to_load.iter().enumerate() {
assert_eq!(x.registers.peek(idx as u8), *value);
}
// ExA1 - SKNP Vx
// Skip next instruction if key with the value of Vx is not pressed.
//
// Checks the keyboard,
// and if the key corresponding to the value of Vx is currently in the up position,
// PC is increased by 2.
let mut x = Chip8Computer::new();
x.keypad.push_key(0x5);
x.registers.poke(0x1, 0x5);
Chip8CpuInstructions::SKNP(0x1).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
x.keypad.release_key(0x5);
Chip8CpuInstructions::SKNP(0x1).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x206);
// Ex9E - SKP Vx
// Skip next instruction if key with the value of Vx is pressed.
//
// Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2.
let mut x = Chip8Computer::new();
x.keypad.push_key(0x5);
x.registers.poke(0x1, 0x5);
Chip8CpuInstructions::SKP(0x1).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
x.keypad.release_key(0x5);
Chip8CpuInstructions::SKP(0x1).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x206);
}
#[test]
#[ignore]
fn draw_nibble_vx_vy_n_test_hd() {
let mut x = Chip8Computer::new();
let x_register = 0x01;
let x_offset = 0x03;
let y_register = 0x02;
let y_offset = 0x04;
let char_offset = 0x100;
x.registers.poke(x_register, x_offset);
x.registers.poke(y_register, y_offset);
x.video_memory.set_highres();
x.registers.poke_i(char_offset);
Chip8CpuInstructions::DRW(x_register, y_register, 0).execute(&mut x);
println!("[[{}]]", x.video_memory.format_as_string());
assert_eq!(read_compressed_test_result(""), x.video_memory.format_as_string());
}
#[test]
fn draw_nibble_vx_vy_n_test_sd() {
let mut x = Chip8Computer::new();
let x_register = 0x1;
let y_register = 0x2;
let x_offset = 1;
let y_offset = 2;
let char_offset = 0x0A;
// now lets set the X and Y to 1,2
x.registers.poke(x_register, x_offset);
x.registers.poke(y_register, y_offset);
x.registers.poke_i(char_offset);
// we are using 5 rows.
Chip8CpuInstructions::DRW(x_register, y_register, 5).execute(&mut x);
// now check that video memory has the values at
// 1,2->1,9
// 2,2->2,9
// 3,2->3,9
// 4,2->4,9
// 5,2->5,9
// let byte_to_check = CHIP8FONT_0[0];
for row_in_sprite in 0..5 {
let row_data = CHIP8FONT_2[row_in_sprite];
for bit_in_byte in 0..8 {
let data_offset =
(x_offset as u16 + row_in_sprite as u16) * 64 + (bit_in_byte + y_offset) as u16;
let real_bit_in_byte = 7 - bit_in_byte;
let shifted_one = 0x01 << real_bit_in_byte;
let one_shift_set = (shifted_one & row_data) > 0;
debug!("ROWDATA = \t\t[{row_data:08b}]\tBIT IN BYTE = \t[{bit_in_byte}]\tONE_SHIFT_SET = [{one_shift_set}]\tSHIFTED ONE = [{shifted_one:08b}]");
debug!("DATA_OFFSET FOR SOURCE DATA {}x{} is {} / offset by {}x{} and should be {} working with byte {:08b}",
bit_in_byte, row_in_sprite, data_offset, x_offset, y_offset, one_shift_set, row_data);
}
}
}
#[test]
fn sub_test() {
// 2nnn
// Call a subroutine at 2nnn
let mut x = Chip8Computer::new();
Chip8CpuInstructions::CALL(0x124).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x124);
assert_eq!(x.stack.depth(), 1);
Chip8CpuInstructions::CALL(0x564).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x564);
assert_eq!(x.stack.depth(), 2);
// SETUP
// Return from a subroutine.
let mut x = Chip8Computer::new();
x.stack.push(&0x132);
x.stack.push(&0xabc);
// EXECUTE
Chip8CpuInstructions::RET.execute(&mut x);
// VERIFY
assert_eq!(x.registers.peek_pc(), 0xabc);
assert_eq!(x.stack.depth(), 1);
// EXECUTE
Chip8CpuInstructions::RET.execute(&mut x);
// VERIFY
assert_eq!(x.registers.peek_pc(), 0x132);
assert_eq!(x.stack.depth(), 0);
}
#[test]
fn ldvxk_test() {
// SETUP
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x01);
Chip8CpuInstructions::LDRK(0x1).execute(&mut x);
assert!(matches!(
x.state,
gemma::chip8::cpu_states::Chip8CpuStates::WaitingForKey
));
}
#[test]
fn series8xy4_corex_tests() {
/// 8xy4
/// Set Vx = Vx + Vy
/// Set VF=1 if Carry
///
// 1 + 1
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0x02);
assert_eq!(x.registers.peek(0x0f), 0x00);
// 255+1
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0xff);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0x00);
assert_eq!(x.registers.peek(0x0f), 0x01);
// 128+192
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 128);
x.registers.poke(0x02, 192);
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 64);
assert_eq!(x.registers.peek(0x0f), 1);
// 8xy6 - SHR Vx {, Vy}
// Set Vx = Vx SHR 1.
//
// If the least-significant bit of Vx is 1, then VF is set to 1,
// otherwise 0. Then Vx is divided by 2.
let mut x = Chip8Computer::new();
// 0b10101010 -> 0b01010101
x.registers.poke(0x01, 0b10101010);
x.registers.poke(0x0f, 0x0);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b01010100);
assert_eq!(x.registers.peek(0x0f), 1);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b10101000);
assert_eq!(x.registers.peek(0x0f), 0x00);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b01010000);
assert_eq!(x.registers.peek(0x0f), 0x01);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b10100000);
assert_eq!(x.registers.peek(0x0f), 0x00);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b01000000);
assert_eq!(x.registers.peek(0x0f), 0x01);
}
#[test]
fn random_produces_different_numbers() {
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x00);
let first_number = Chip8CpuInstructions::RND(0x01, 0xff)
.execute(&mut x)
.registers
.peek(0x01);
let second_number = Chip8CpuInstructions::RND(0x01, 0xff)
.execute(&mut x)
.registers
.peek(0x01);
assert_ne!(first_number, second_number);
}

36
gemma/tests/keypad.rs Normal file
View File

@ -0,0 +1,36 @@
mod test_utils;
use gemma::chip8::keypad::Keypad;
use crate::test_utils::read_compressed_test_result;
#[test]
fn keypad_keys_check() {
let mut k = Keypad::new();
for i in 0..16 {
assert!(!k.key_state(i));
}
// press a key
k.push_key(1);
k.push_key(2);
assert!(k.pressed(1));
assert!(k.pressed(2));
k.release_key(1);
assert!(k.released(1));
}
#[test]
fn keypad_string_format_test() {
let k = Keypad::new();
let expected_result = read_compressed_test_result("gemma_keypad_string_result");
let actual_result = k.format_as_string();
println!("EXPECTING [{}]", expected_result);
println!("GOT [{}]", actual_result);
assert_eq!(
k.format_as_string(),
read_compressed_test_result("gemma_keypad_string_result")
);
}

68
gemma/tests/registers.rs Normal file
View File

@ -0,0 +1,68 @@
use rand::random;
use gemma::chip8::registers::Chip8Registers;
#[test]
fn register_rw_test() {
let mut x = Chip8Registers::default();
x.poke(0x0, 0xff);
x.poke(0x1, 0xab);
assert_eq!(x.peek(0x0), 0xff);
assert_eq!(x.peek(0x1), 0xab);
}
#[test]
fn pc_test() {
let mut x = Chip8Registers::default();
x.set_pc(0x300);
assert_eq!(x.peek_pc(), 0x300);
}
#[test]
#[should_panic]
fn invalid_register() {
let mut x = Chip8Registers::default();
x.poke(0x10, 0xff);
}
#[test]
fn reset_clears_registers() {
let mut x = Chip8Registers::default();
for register in 0..0x10 {
x.poke(register, random::<u8>());
}
x.reset();
for register in 0..0x10 {
assert_eq!(x.peek(register), 0x00);
}
}
#[test]
fn registers_equality() {
let data_set: Vec<(Chip8Registers, Chip8Registers, bool)> = vec![
(Chip8Registers::default(), Chip8Registers::default(), true),
(
Chip8Registers {
registers: [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
],
i_register: 0,
pc: 0,
},
Chip8Registers {
registers: [
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
],
i_register: 0,
pc: 0,
},
false,
),
];
for (first, second, matches) in data_set.iter() {
assert_eq!(first == second, *matches)
}
}

View File

@ -0,0 +1,31 @@
use gemma::chip8::sound_timer::SoundTimer;
/// Tests for The Sound Timer
///
///
#[test]
fn sound_timer_default() {
let x = SoundTimer::default();
assert_eq!(x.current(), 0);
}
#[test]
fn sound_timer_ticks_reduce_time() {
let mut st = SoundTimer::new();
st.set_timer(100);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 97);
}
#[test]
fn sound_timer_out_of_ticks_works() {
let mut st = SoundTimer::new();
st.set_timer(0);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 0);
}

49
gemma/tests/stack.rs Normal file
View File

@ -0,0 +1,49 @@
use rand::random;
use gemma::chip8::stack::Chip8Stack;
#[test]
#[should_panic]
fn stack_overflow_test() {
let mut x = Chip8Stack::new();
for i in 0..17 {
x.push(&i);
}
}
#[test]
#[should_panic]
fn stack_underflow_test() {
let mut x = Chip8Stack::new();
x.pop();
}
#[test]
fn stack_lots_of_subs() {
let mut x = Chip8Stack::new();
let stack_contents = [
0x123, 0x321, 0xabc, 0xdef, 0xbad, 0xbef, 0xfed, 0xcab, 0xbed, 0xcad, 0xfeb, 0xcab, 0xfff,
0x000, 0x001,
];
for i in stack_contents {
x.push(&i);
}
assert_eq!(x.depth(), 15);
// up to 50 random loops
let num_loops: u8 = random::<u8>() % 50;
for i in 0..num_loops {
let start_count = x.depth();
let num_pop = random::<u8>() % x.depth() as u8;
for current_pop in 0..num_pop {
x.pop();
}
let post_pop_count = x.depth();
assert_eq!(post_pop_count as u8, start_count as u8 - num_pop);
for current_push in 0..num_pop {
x.push(&stack_contents[(current_push + post_pop_count as u8) as usize]);
}
assert_eq!(x.depth(), 15);
}
}

View File

@ -1,14 +1,20 @@
mod test_utils;
use crate::test_utils::read_test_result;
use gemma::chip8::computer::Chip8Computer;
use std::fs;
use std::io::prelude::*;
use crate::test_utils::load_compressed_result;
#[test]
fn smoke() {
assert!(true)
}
#[test]
#[ignore]
fn serialization_round_trip() {
let original_computer = Chip8Computer::new();
let expected_json = read_test_result("smoke_001_round_trip_serialize_deserialize.json");
let expected_json = load_compressed_result("smoke_001_round_trip_serialize_deserialize");
// Serialize the Chip8Computer instance
let serialized = serde_json::to_string(&original_computer).expect("Serialization failed");

View File

@ -0,0 +1,23 @@
use std::fs::File;
use std::io::Read;
use gemma::chip8::system_memory::Chip8SystemMemory;
use gemma::constants::TEST_ROM_ROOT;
#[test]
fn system_memory_load_program() {
let mut m = Chip8SystemMemory::new();
let mut program_to_load = vec![];
let file_to_load = format!("{}/2-ibm-logo.ch8", TEST_ROM_ROOT);
println!(
"Attempt to load {} from {}",
file_to_load,
std::env::current_dir().unwrap().display()
);
let mut file_to_load_from = File::open(file_to_load).expect("Unable to load sample rom");
file_to_load_from
.read_to_end(&mut program_to_load)
.expect("Unable to read sample rom");
m.load_program(program_to_load.clone().into());
let expected_at_200 = program_to_load[0];
assert_eq!(m.peek(0x200), expected_at_200);
}

View File

@ -6,6 +6,12 @@ use std::io::{Read, Write};
use tempfile::tempfile;
const TEST_OUTPUT_SAMPLE_DIR: &str = "../resources/test/";
#[test]
fn smoke() {
assert!(true)
}
pub fn compress_string(input: &str) -> Vec<u8> {
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
encoder
@ -13,7 +19,6 @@ pub fn compress_string(input: &str) -> Vec<u8> {
.expect("Failed to write data");
encoder.finish().expect("Failed to finish compression")
}
pub fn decompress_to_string(compressed_data: &[u8]) -> Result<String, std::io::Error> {
let mut decoder = DeflateDecoder::new(compressed_data);
let mut decompressed = String::new();
@ -43,29 +48,11 @@ pub fn read_compressed_test_result(suffix: &str) -> String {
pub fn load_compressed_result(suffix: &str) -> String {
read_compressed_test_result(suffix)
}
pub fn read_test_result(suffix: &str) -> String {
panic!("THIS SHOULD BE COMPRESSED.");
// let full_path = TEST_OUTPUT_SAMPLE_DIR.to_owned() + suffix;
// println!("READING TEST RESULTS FROM {}", full_path);
// std::fs::read_to_string(full_path).unwrap()
}
pub fn load_rom(to_load: &str) -> Vec<u8> {
std::fs::read(format!("../resources/roms/{}.ch8", to_load)).unwrap()
}
fn load_result(to_load: &str) -> String {
panic!("THIS SHOULD BE COMPRESSED.");
// let full_path = format!(
// "{}/../resources/test/state/{}",
// std::env::current_dir().unwrap().display(),
// to_load
// );
// println!("CURRENT DIR: {:?}", std::env::current_dir());
// println!("Loading state => (([{}]))", full_path);
// std::fs::read_to_string(full_path).unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
@ -96,4 +83,6 @@ mod tests {
//
// ...verify its the same.
}
}

View File

@ -1,4 +1,4 @@
use crate::test_utils::{load_compressed_result, read_test_result};
use crate::test_utils::load_compressed_result;
use gemma::chip8::computer::Chip8Computer;
use gemma::chip8::computer_manager::Chip8ComputerManager;
use gemma::chip8::cpu_states::Chip8CpuStates::WaitingForInstruction;
@ -48,683 +48,6 @@ fn decoder_test_invalid_instructions() {
}
}
/// START OF THE EXECUTION TESTS
#[test]
fn instruction_tests() {
// 0x0nnn Exit to System Call
let mut x = Chip8Computer::new();
Chip8CpuInstructions::SYS(0).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::SYS(0xFA0).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0xFA0);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::SYS(0x0AF).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x0AF);
// 0x1nnn Jump to Address
let mut x = Chip8Computer::new();
Chip8CpuInstructions::JPA(0).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::JPA(0xABC).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0xABC);
// 0x6xkk Set Vx = kk
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LDR(1, 0x12).execute(&mut x);
assert_eq!(x.registers.peek(1), 0x12);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LDR(2, 0x21).execute(&mut x);
assert_eq!(x.registers.peek(2), 0x21);
// 0x3xkk Skip next instruction if Vx = kk.
// The interpreter compares register Vx to kk,
// and if they are equal, increments the program counter by 2.
// test setup: Load value 0x84 into V1
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x84);
Chip8CpuInstructions::SEX(1, 0x48).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x84);
Chip8CpuInstructions::SEX(1, 0x84).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
// 0x4xkk Skip next instruction if Vx != kk
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x84);
x.registers.poke(0x2, 0x84);
// skip, compare 0x84 to 0x84
Chip8CpuInstructions::SEY(0x1, 0x2).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x84);
x.registers.poke(0x2, 0x48);
Chip8CpuInstructions::SEY(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
// 0x8xy0 Set value of Vy in Vx
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x02);
Chip8CpuInstructions::LDRY(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(1), 0x02);
// 0x8xy1 Set Vx = Vx OR Vy
// 0b0101 0000 (0x50)
// | 0b0000 1010 (0x0A)
// 0b0101 1010 (0x5A)
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0b01010000);
x.registers.poke(0x02, 0b00001010);
Chip8CpuInstructions::OR(1, 2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0b01011010);
// 0x8xy2 Set Vx = Vx AND Vy
// 0b1111 1100 (0xFC)
// & 0b1100 1010 (0xCA)
// 0b1100 1000 (0xC8)
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0xFC);
x.registers.poke(0x02, 0xCA);
Chip8CpuInstructions::AND(1, 2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0xC8);
// 0x8xy3 Set Vx = Vx XOR Vy
// 0b1111 1100 (0xFC)
// ^ 0b1100 1010 (0xCA)
// 0b0011 0110 (0x36)
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0b11111100);
x.registers.poke(0x02, 0b11001010);
Chip8CpuInstructions::ORY(1, 2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0b00110110);
// 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
// T1 T2: Judgement Test
// 0x01 0xFF
// + 0x01 0x01
// 0x02 F0 0x00 F1
let mut x = Chip8Computer::new();
x.registers.poke(0x0f, 00);
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0xf), 0x00);
assert_eq!(x.registers.peek(0x01), 0x02);
let mut x = Chip8Computer::new();
x.registers.poke(0x0f, 0x00);
x.registers.poke(0x01, 0xff);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::ADDR(1, 2).execute(&mut x);
assert_eq!(x.registers.peek(0xf), 1);
assert_eq!(x.registers.peek(1), 0);
/*
Set Vx = Vx SHR 1.
If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2.
*/
let mut x = Chip8Computer::new();
x.registers.poke(0x0f, 0x00);
x.registers.poke(0x01, 0b00001000);
x.registers.poke(0x02, 0b00000000);
Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x); // 0b0000 0010 (0x02) (Not Set)
assert_eq!(x.registers.peek(1), 0b00000100);
assert_eq!(x.registers.peek(0xf), 0);
x = Chip8Computer::new();
x.registers.poke(0x0f, 0x00);
x.registers.poke(0x01, 0b00001001);
Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0b00000100);
assert_eq!(x.registers.peek(0xf), 1);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LDIA(0x123).execute(&mut x);
assert_eq!(x.registers.peek_i(), 0x123);
assert_eq!(x.registers.peek_pc(), 0x202);
}
#[test]
fn jp_v0addr_test() {
let mut x = Chip8Computer::new();
/// jump to I + nnn
x.registers.poke(0x0, 0xff);
Chip8CpuInstructions::JPI(0x100).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x1FF);
}
#[test]
fn cls_test() {
let mut x = Chip8Computer::new();
Chip8CpuInstructions::CLS.execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
for i in 0..CHIP8_VIDEO_MEMORY {
assert!(!x.video_memory.peek(i as u16));
}
// draw some thing to the video memory
x.video_memory.poke(0x01, true);
x.video_memory.poke(0x03, true);
x.video_memory.poke(0x05, true);
Chip8CpuInstructions::CLS.execute(&mut x);
for i in 0..CHIP8_VIDEO_MEMORY {
assert!(!x.video_memory.peek(i as u16));
}
}
#[test]
fn skip_next_instruction_ne_text() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xf0);
Chip8CpuInstructions::SNEB(0x1, 0x0f).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xf0);
Chip8CpuInstructions::SNEB(0x1, 0xf0).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
}
#[test]
fn addivx_test() {
let mut x = Chip8Computer::new();
x.registers.poke_i(0xabc);
x.registers.poke(0x0, 0x10);
Chip8CpuInstructions::ADDI(0x0).execute(&mut x);
assert_eq!(x.registers.peek_i(), 0xacc);
}
#[test]
fn ldstvt_test() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xf0);
Chip8CpuInstructions::LDIS(0x01).execute(&mut x);
assert_eq!(x.sound_timer.current(), 0xf0);
x.sound_timer.tick();
x.sound_timer.tick();
x.sound_timer.tick();
assert_eq!(x.sound_timer.current(), 0xed);
}
#[test]
fn rnd_vx_byte_text() {
let mut x = Chip8Computer::new();
Chip8CpuInstructions::RND(0x1, 0x0f).execute(&mut x);
let new_value = x.registers.peek(0x1);
assert!(new_value < 0x10);
}
#[test]
fn add_vx_byte_test() {
let mut x = Chip8Computer::new();
// set a value in the register
x.registers.poke(0x01, 0xab);
// add 0x10 to register
Chip8CpuInstructions::ADD(0x1, 0x10).execute(&mut x);
assert_eq!(x.registers.peek(1), 0xbb);
}
#[test]
fn sub_vx_vy_test() {
let mut x = Chip8Computer::new();
// load values in 2 registers
x.registers.poke(0x1, 0x10);
x.registers.poke(0x2, 0x08);
Chip8CpuInstructions::SUB(0x1, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0xf), 1);
assert_eq!(x.registers.peek(0x1), 0x8);
assert_eq!(x.registers.peek_pc(), 0x202);
}
#[test]
fn sne_vx_vy_test() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x10);
x.registers.poke(0x2, 0x10);
Chip8CpuInstructions::SNEY(0x1, 0x2).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x10);
x.registers.poke(0x2, 0x00);
Chip8CpuInstructions::SNEY(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204)
}
#[test]
fn ld_dt_vx_test() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x10);
Chip8CpuInstructions::LDD(0x1).execute(&mut x);
assert_eq!(x.delay_timer.current(), 0x10);
for _ in 0..0x20 {
x.delay_timer.tick();
}
assert_eq!(x.delay_timer.current(), 0);
}
#[test]
fn ld_vx_dt_test() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xf0);
Chip8CpuInstructions::LDD(0x1).execute(&mut x);
x.delay_timer.tick();
x.delay_timer.tick();
x.delay_timer.tick();
assert_eq!(x.delay_timer.current(), 0xed);
}
#[test]
fn subn_vx_vy_test() {
// This instruction subtracts the value in
// register Vx from the value in register Vy and stores the result in register Vx.
// The subtraction is performed as follows: Vx = Vy - Vx. If Vy is less than Vx,
// the result will wrap around (due to the 8-bit nature of the registers).
// The carry flag (VF) is set to 1 if there is no borrow (i.e., Vy is greater
// than or equal to Vx), and it is set to 0 if there is a borrow.
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xa0);
x.registers.poke(0x2, 0xab);
Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x);
// expect the result to be 0x0b
assert_eq!(x.registers.peek(0x1), 0x0b);
// expect the vf register to be set to 1 as there was overflow
assert_eq!(x.registers.peek(0xf), 0x1);
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0xab);
x.registers.poke(0x02, 0xa0);
Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x);
// expect the result to be 11110101, -0xB, -11, 245, 0xF5
assert_eq!(x.registers.peek(0x1), 0xf5);
assert_eq!(x.registers.peek(0xf), 0x0);
// 8xyE - SHL Vx {, Vy}
// Set Vx = Vx SHL 1.
//
// If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2.
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0b00100000);
Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x);
assert_eq!(x.registers.peek(0x1), 0b01000000);
assert_eq!(x.registers.peek(0xf), 0x0);
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0b10101010);
Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x);
assert_eq!(x.registers.peek(0x1), 0b01010100);
assert_eq!(x.registers.peek(0xf), 0x1);
// Fx29 - LD F, Vx
// Set I = location of sprite for digit Vx.
//
// The value of I is set to the location for the hexadecimal sprite corresponding to the value of Vx. See section 2.4, Display, for more information on the Chip-8 hexadecimal font.
let mut x = Chip8Computer::new();
// target_sprite = 2
// target_offset = 0x5
x.registers.poke(0x1, 0x2);
Chip8CpuInstructions::LDFX(0x1).execute(&mut x);
assert_eq!(x.registers.peek_i(), 10);
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x06);
Chip8CpuInstructions::LDFX(0x1).execute(&mut x);
assert_eq!(x.registers.peek_i(), 30);
// Fx33 - LD B, Vx
// Store BCD representation of Vx in memory locations I, I+1, and I+2.
//
// The interpreter takes the decimal value of Vx, and places the hundreds digit
// in memory at location in I, the tens digit at location I+1,
// and the ones digit at location I+2.
let mut x = Chip8Computer::new();
// load the value 123 (0x7b)
x.registers.poke(0x1, 0x7b);
x.registers.poke_i(0x500);
Chip8CpuInstructions::BCD(0x1).execute(&mut x);
assert_eq!(x.memory.peek(0x500), 0x1);
assert_eq!(x.memory.peek(0x501), 0x2);
assert_eq!(x.memory.peek(0x502), 0x3);
// Store registers V0 through Vx in memory starting at location I.
//
// The interpreter copies the values of registers V0 through Vx into memory,
// starting at the address in I.
let mut x = Chip8Computer::new();
// Load Registers.
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
for (idx, val) in to_load.iter().enumerate() {
x.registers.poke(idx as u8, *val);
}
x.registers.poke_i(0x500);
Chip8CpuInstructions::LDIX(to_load.len() as u8).execute(&mut x);
// Verify the values are in memory from 0x500 to 0x507
for (idx, value) in to_load.iter().enumerate() {
assert_eq!(x.memory.peek(0x500 + idx as u16), *value);
}
// Read registers V0 through Vx from memory starting at location I.
//
// The interpreter reads values from memory starting at location I into registers V0 through Vx.
let mut x = Chip8Computer::new();
let base_offset = 0x500;
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
// start by setting values in memory
for (idx, memory) in to_load.iter().enumerate() {
let target_address = base_offset + idx;
let target_value = *memory;
x.memory.poke(target_address as u16, target_value);
}
// where to load from
x.registers.poke_i(0x500);
// how much to load
x.registers.poke(0x6, to_load.len() as u8);
// then copying them values memory to registers
Chip8CpuInstructions::LDRI(0x6).execute(&mut x);
// now check that we have the right values in our registers
for (idx, value) in to_load.iter().enumerate() {
assert_eq!(x.registers.peek(idx as u8), *value);
}
// ExA1 - SKNP Vx
// Skip next instruction if key with the value of Vx is not pressed.
//
// Checks the keyboard,
// and if the key corresponding to the value of Vx is currently in the up position,
// PC is increased by 2.
let mut x = Chip8Computer::new();
x.keypad.push_key(0x5);
x.registers.poke(0x1, 0x5);
Chip8CpuInstructions::SKNP(0x1).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
x.keypad.release_key(0x5);
Chip8CpuInstructions::SKNP(0x1).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x206);
// Ex9E - SKP Vx
// Skip next instruction if key with the value of Vx is pressed.
//
// Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2.
let mut x = Chip8Computer::new();
x.keypad.push_key(0x5);
x.registers.poke(0x1, 0x5);
Chip8CpuInstructions::SKP(0x1).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
x.keypad.release_key(0x5);
Chip8CpuInstructions::SKP(0x1).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x206);
}
fn draw_nibble_vx_vy_n_test_hd() {
let mut x = Chip8Computer::new();
let x_register = 0x01;
let x_offset = 0x03;
let y_register = 0x02;
let y_offset = 0x04;
let char_offset = 0x100;
x.registers.poke(x_register, x_offset);
x.registers.poke(y_register, y_offset);
x.video_memory.set_highres();
x.registers.poke_i(char_offset);
Chip8CpuInstructions::DRW(x_register, y_register, 0).execute(&mut x);
println!("[[{}]]", x.video_memory.format_as_string());
assert_eq!(read_test_result(""), x.video_memory.format_as_string());
}
#[test]
fn draw_nibble_vx_vy_n_test_sd() {
let mut x = Chip8Computer::new();
let x_register = 0x1;
let y_register = 0x2;
let x_offset = 1;
let y_offset = 2;
let char_offset = 0x0A;
// now lets set the X and Y to 1,2
x.registers.poke(x_register, x_offset);
x.registers.poke(y_register, y_offset);
x.registers.poke_i(char_offset);
// we are using 5 rows.
Chip8CpuInstructions::DRW(x_register, y_register, 5).execute(&mut x);
// now check that video memory has the values at
// 1,2->1,9
// 2,2->2,9
// 3,2->3,9
// 4,2->4,9
// 5,2->5,9
// let byte_to_check = CHIP8FONT_0[0];
for row_in_sprite in 0..5 {
let row_data = CHIP8FONT_2[row_in_sprite];
for bit_in_byte in 0..8 {
let data_offset =
(x_offset as u16 + row_in_sprite as u16) * 64 + (bit_in_byte + y_offset) as u16;
let real_bit_in_byte = 7 - bit_in_byte;
let shifted_one = 0x01 << real_bit_in_byte;
let one_shift_set = (shifted_one & row_data) > 0;
debug!("ROWDATA = \t\t[{row_data:08b}]\tBIT IN BYTE = \t[{bit_in_byte}]\tONE_SHIFT_SET = [{one_shift_set}]\tSHIFTED ONE = [{shifted_one:08b}]");
debug!("DATA_OFFSET FOR SOURCE DATA {}x{} is {} / offset by {}x{} and should be {} working with byte {:08b}",
bit_in_byte, row_in_sprite, data_offset, x_offset, y_offset, one_shift_set, row_data);
}
}
}
#[test]
fn sub_test() {
// 2nnn
// Call a subroutine at 2nnn
let mut x = Chip8Computer::new();
Chip8CpuInstructions::CALL(0x124).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x124);
assert_eq!(x.stack.depth(), 1);
Chip8CpuInstructions::CALL(0x564).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x564);
assert_eq!(x.stack.depth(), 2);
// SETUP
// Return from a subroutine.
let mut x = Chip8Computer::new();
x.stack.push(&0x132);
x.stack.push(&0xabc);
// EXECUTE
Chip8CpuInstructions::RET.execute(&mut x);
// VERIFY
assert_eq!(x.registers.peek_pc(), 0xabc);
assert_eq!(x.stack.depth(), 1);
// EXECUTE
Chip8CpuInstructions::RET.execute(&mut x);
// VERIFY
assert_eq!(x.registers.peek_pc(), 0x132);
assert_eq!(x.stack.depth(), 0);
}
#[test]
fn ldvxk_test() {
// SETUP
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x01);
Chip8CpuInstructions::LDRK(0x1).execute(&mut x);
assert!(matches!(
x.state,
gemma::chip8::cpu_states::Chip8CpuStates::WaitingForKey
));
}
#[test]
fn series8xy4_corex_tests() {
/// 8xy4
/// Set Vx = Vx + Vy
/// Set VF=1 if Carry
///
// 1 + 1
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0x02);
assert_eq!(x.registers.peek(0x0f), 0x00);
// 255+1
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0xff);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0x00);
assert_eq!(x.registers.peek(0x0f), 0x01);
// 128+192
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 128);
x.registers.poke(0x02, 192);
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 64);
assert_eq!(x.registers.peek(0x0f), 1);
// 8xy6 - SHR Vx {, Vy}
// Set Vx = Vx SHR 1.
//
// If the least-significant bit of Vx is 1, then VF is set to 1,
// otherwise 0. Then Vx is divided by 2.
let mut x = Chip8Computer::new();
// 0b10101010 -> 0b01010101
x.registers.poke(0x01, 0b10101010);
x.registers.poke(0x0f, 0x0);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b01010100);
assert_eq!(x.registers.peek(0x0f), 1);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b10101000);
assert_eq!(x.registers.peek(0x0f), 0x00);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b01010000);
assert_eq!(x.registers.peek(0x0f), 0x01);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b10100000);
assert_eq!(x.registers.peek(0x0f), 0x00);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b01000000);
assert_eq!(x.registers.peek(0x0f), 0x01);
}
#[test]
fn random_produces_different_numbers() {
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x00);
let first_number = Chip8CpuInstructions::RND(0x01, 0xff)
.execute(&mut x)
.registers
.peek(0x01);
let second_number = Chip8CpuInstructions::RND(0x01, 0xff)
.execute(&mut x)
.registers
.peek(0x01);
assert_ne!(first_number, second_number);
}
#[test]
fn delay_timer_ticks_reduce_time() {
let mut st = DelayTimer::new();
st.set_timer(100);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 97);
}
#[test]
fn delay_timer_out_of_ticks_works() {
let mut st = DelayTimer::new();
st.set_timer(0);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 0);
}
#[test]
fn keypad_keys_check() {
let mut k = Keypad::new();
for i in 0..16 {
assert!(!k.key_state(i));
}
// press a key
k.push_key(1);
k.push_key(2);
assert!(k.pressed(1));
assert!(k.pressed(2));
k.release_key(1);
assert!(k.released(1));
}
#[test]
fn keypad_string_format_test() {
let k = Keypad::new();
assert_eq!(
k.format_as_string(),
read_compressed_test_result("gemma_keypad_string_result")
);
}
#[test]
fn register_rw_test() {
let mut x = Chip8Registers::default();
x.poke(0x0, 0xff);
x.poke(0x1, 0xab);
assert_eq!(x.peek(0x0), 0xff);
assert_eq!(x.peek(0x1), 0xab);
}
#[test]
fn pc_test() {
let mut x = Chip8Registers::default();
x.set_pc(0x300);
assert_eq!(x.peek_pc(), 0x300);
}
#[test]
#[should_panic]
fn invalid_register() {
let mut x = Chip8Registers::default();
x.poke(0x10, 0xff);
}
#[test]
fn format_as_string_looks_right() {
let mut x = Chip8Registers::default();
@ -737,38 +60,6 @@ fn format_as_string_looks_right() {
assert_eq!(result_string, String::from("Vx: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07\n 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f\nI: 0x0cab\tPC: 0x0abc"));
}
#[test]
fn reset_clears_registers() {
let mut x = Chip8Registers::default();
for register in 0..0x10 {
x.poke(register, random::<u8>());
}
x.reset();
for register in 0..0x10 {
assert_eq!(x.peek(register), 0x00);
}
}
#[test]
fn sound_timer_ticks_reduce_time() {
let mut st = SoundTimer::new();
st.set_timer(100);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 97);
}
#[test]
fn sound_timer_out_of_ticks_works() {
let mut st = SoundTimer::new();
st.set_timer(0);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 0);
}
#[test]
fn stack_push_pop_test() {
@ -781,53 +72,6 @@ fn stack_push_pop_test() {
assert_eq!(x.depth(), 1);
}
#[test]
#[should_panic]
fn stack_overflow_test() {
let mut x = Chip8Stack::new();
for i in 0..17 {
x.push(&i);
}
}
#[test]
#[should_panic]
fn stack_underflow_test() {
let mut x = Chip8Stack::new();
x.pop();
}
#[test]
fn stack_lots_of_subs() {
let mut x = Chip8Stack::new();
let stack_contents = [
0x123, 0x321, 0xabc, 0xdef, 0xbad, 0xbef, 0xfed, 0xcab, 0xbed, 0xcad, 0xfeb, 0xcab, 0xfff,
0x000, 0x001,
];
for i in stack_contents {
x.push(&i);
}
assert_eq!(x.depth(), 15);
// up to 50 random loops
let num_loops: u8 = random::<u8>() % 50;
for i in 0..num_loops {
let start_count = x.depth();
let num_pop = random::<u8>() % x.depth() as u8;
for current_pop in 0..num_pop {
x.pop();
}
let post_pop_count = x.depth();
assert_eq!(post_pop_count as u8, start_count as u8 - num_pop);
for current_push in 0..num_pop {
x.push(&stack_contents[(current_push + post_pop_count as u8) as usize]);
}
assert_eq!(x.depth(), 15);
}
}
#[test]
fn video_split_bytes() {
// from 0xABCD we should have AB high, CD low
@ -1120,6 +364,7 @@ fn video_write_checkboard() {
}
#[test]
#[ignore]
fn video_zero_test() {
let mut x = Chip8Video::default();
@ -1489,19 +734,18 @@ fn video_hires_loop_check() {
assert!(true);
}
#[test]
fn sound_timer_default() {
let x = SoundTimer::default();
assert_eq!(x.current(), 0);
}
#[test]
fn system_memory_new() {
let x = Chip8SystemMemory::new();
assert!(true);
// check its empty.
for i in 0..CHIP8_MEMORY_SIZE {
assert_eq!(x.peek(i as u16), 0x00);
}
}
#[test]
#[ignore]
fn video_lowres_schip_draw_chip8_sprite() {
let mut x = Chip8Computer::new();
x.quirk_mode = QuirkMode::SChipModern;
@ -1512,24 +756,31 @@ fn video_lowres_schip_draw_chip8_sprite() {
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x02);
Chip8CpuInstructions::DRW(0x01, 0x01, 0x08).execute(&mut x);
let expected_state = read_test_result("state/video_lowres_schip_draw_chip8_sprite_result.json");
let actual_state = serde_json::to_string(&x).unwrap();
let expected_state = read_compressed_test_result("state/video_lowres_schip_draw_chip8_sprite_result");
let actual_state = x.dump_state_to_json();
assert_eq!(expected_state, actual_state);
}
#[test]
fn video_lowres_schip_draw_schip_sprite() {}
#[ignore]
fn video_lowres_schip_draw_schip_sprite() {
let mut x = Chip8Computer::new();
x.quirk_mode = SChipModern;
x.video_memory.set_lowres();
x.registers.poke_i(0x0005);
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x02);
Chip8CpuInstructions::DRW(0x01, 0x01, 0x08).execute(&mut x);
let expected_state = read_compressed_test_result("state/video_lowres_schip_draw_schip_sprite");
let actual_state = x.dump_state_to_json();
assert_eq!(expected_state, actual_state);
}
#[test]
fn video_highres_schip_draw_chip8_sprite() {}
#[test]
fn video_highres_schip_draw_schip_sprite() {}
#[test]
fn partial_eq_chip8computer() {
let x = Chip8Computer::new();
let y = Chip8Computer::new();
assert_eq!(x, y)
}
#[test]
fn quirk_mode_labels() {
@ -1537,67 +788,3 @@ fn quirk_mode_labels() {
assert_eq!(format!("{}", XOChip), LABEL_QUIRK_XOCHIP);
assert_eq!(format!("{}", SChipModern), LABEL_QUIRK_SCHIP);
}
#[test]
fn system_memory_load_program() {
let mut m = Chip8SystemMemory::new();
let mut program_to_load = vec![];
let file_to_load = format!("{}/2-ibm-logo.ch8", TEST_ROM_ROOT);
println!(
"Attempt to load {} from {}",
file_to_load,
std::env::current_dir().unwrap().display()
);
let mut file_to_load_from = File::open(file_to_load).expect("Unable to load sample rom");
file_to_load_from
.read_to_end(&mut program_to_load)
.expect("Unable to read sample rom");
m.load_program(program_to_load.clone().into());
let expected_at_200 = program_to_load[0];
assert_eq!(m.peek(0x200), expected_at_200);
}
#[test]
fn start_stop_computer() {
let mut computer = Chip8ComputerManager::new();
assert_eq!(computer.core_should_run, false);
computer.start();
assert_eq!(computer.core_should_run, true);
computer.step();
assert_eq!(computer.core_should_run, true);
computer.stop();
assert_eq!(computer.core_should_run, false);
computer.reset(Chip8);
assert_eq!(computer.core_should_run, false);
}
#[test]
fn state_default_matches() {
let computer = Chip8Computer::default();
let mut manager = Chip8ComputerManager::default();
assert_eq!(computer, *manager.state());
}
#[test]
fn keys_test_manager() {
let mut manager = Chip8ComputerManager::default();
for i in 0..16 {
assert_eq!(manager.is_key_pressed(i), false);
}
// press key 5
manager.press_key(5);
assert_eq!(manager.is_key_pressed(5), true);
manager.release_key(5);
assert_eq!(manager.is_key_pressed(5), false);
}
#[test]
fn status_of_manager() {
let mut manager = Chip8ComputerManager::default();
println!("MANAGER STATUS [{}]", manager.status_as_string());
let expected_state = load_compressed_result("test_manager_status");
assert_eq!(expected_state, manager.status_as_string());
}

View File

@ -1,6 +1,12 @@
use std::collections::BTreeMap;
use gemma::chip8::util::InstructionUtil;
#[test]
fn smoke() {
assert!(true)
}
#[test]
fn byte_to_bools() {
let data_set: BTreeMap<u8, [bool; 8]> = BTreeMap::from(

View File

@ -1,8 +1,7 @@
use std::{fs, io};
use std::fs;
use std::path::Path;
pub fn list_of_files_in_directory(root_directory: &Path) -> String {
let mut return_value = String::new();
if root_directory.is_dir() {
@ -15,4 +14,4 @@ pub fn list_of_files_in_directory(root_directory: &Path) -> String {
}
}
return_value
}
}

View File

@ -35,6 +35,7 @@ fn main() {
print!("0x{:02x}, ", result.unwrap());
}
}
fn read_file_to_bools(file_path: &str) -> io::Result<Vec<u8>> {
// Open the file
let file = File::open(file_path)?;

View File

@ -18,7 +18,7 @@ pub struct Chip8AsmParser;
fn main() {
println!("Taxation is Theft");
let unparsed = fs::read_to_string("resources/test/gemma_disassembler_manual_document.asc")
let unparsed = fs::read_to_string("resources/test/asm/gemma_disassembler_manual_document.asm8")
.expect("Unable to read input");
let file = Chip8AsmParser::parse(Rule::file, &unparsed)
@ -36,7 +36,7 @@ fn main() {
print!("record = {:?}\t", record.as_str());
let x = Chip8CpuInstructions::from_str(record.as_str());
println!("DECODED TO {:?} {:04x}", x, x.encode());
let (high, low) = InstructionUtil::split_bytes(x.encode());
let (low, high) = InstructionUtil::split_bytes(x.encode());
target_file
.write_all(&[high, low])
.expect("Unable to write to the file.");

View File

@ -0,0 +1,4 @@
CLS
CALL 0xf000
EXIT
SUBC 0x01

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
1f8b08000000000000ffed9bcb8adb301440f7fd162dfc0c136d0b854257dd743104636c4d2212dba924b704e37fafecb13b4e42da4099a10c87631d45575792e56897b8abdb2a2b4ec5415919884a558d39c96eaa1fa324106192fc2e433b8ec47a35d85f61188db170f55c450f63bd0c4dd562021fbac89f9a67a12979eaf66bad92e15af69de5bc4c7e159ad78eaeeb79c9b98c63a3e47cd38bbcab5d2e624309e0d509c7271dff212316e1da1fcc5be387ef331d4a3a9670bdfee7ccf98c44893f0f69348eba9139cd3597f8e6dac1f9da7178e78cc3bddcbb9ff40d3397596f3fe7bdcffdde3c0000000000000000000000000078376c7a61d4565ba78c95ddcbc7c7bf8d133a9bb365208e854cc3a817b669eb3273baf2d1aef08db1bb17a53ae4a7cbb8ff59b2173f74a99aecf20f1b4ff9c12a8131c618638c31c618638c31c618638c31c618638c31c618638c31c618e3ffd81bb1cb6df664f24a65c52eafb7aa94cfdd456b8caa5d6694955f9a9f5f95ed8575b953f25bae9daeb79f1af3b9b6ceb485d34d2df6ea74cc4bd9f9dabefa6b059bf15e8abdecb453955fcf07beb7daecb3aa2995fcb8d3c787fec32fcefd4d09ab520000

View File

@ -1 +0,0 @@
1f8b08000000000000ffed9bcb8adb301440f7fd162dfc0c136d0b854257dd743104636c4d2212dba924b704e37fafecb13b4e42da4099a10c87631d45575792e56897b8abdb2a2b4ec5415919884a558d39c96eaa1fa324106192fc2e433b8ec47a35d85f61188db170f55c450f63bd0c4dd562021fbac89f9a67a12979eaf66bad92e15af69de5bc4c7e159ad78eaeeb79c9b98c63a3e47cd38bbcab5d2e624309e0d509c7271dff212316e1da1fcc5be387ef331d4a3a9670bdfee7ccf98c44893f0f69348eba9139cd3597f8e6dac1f9da7178e78cc3bddcbb9ff40d3397596f3fe7bdcffdde3c0000000000000000000000000078376c7a61d4565ba78c95ddcbc7c7bf8d133a9bb365208e854cc3a817b669eb3273baf2d1aef08db1bb17a53ae4a7cbb8ff59b2173f74a99aecf20f1b4ff9c12a8131c618638c31c618638c31c618638c31c618638c31c618638c31c618e3ffd81bb1cb6df664f24a65c52eafb7aa94cfdd456b8caa5d6694955f9a9f5f95ed8575b953f25bae9daeb79f1af3b9b6ceb485d34d2df6ea74cc4bd9f9dabefa6b059bf15e8abdecb453955fcf07beb7daecb3aa2995fcb8d3c787fec32fcefd4d09ab520000

File diff suppressed because one or more lines are too long