├── .gitignore ├── images └── BRIX.gif ├── msvc ├── dll │ ├── 32 │ │ └── SDL2.dll │ └── 64 │ │ └── SDL2.dll └── lib │ ├── 32 │ ├── SDL2.lib │ ├── SDL2main.lib │ └── SDL2test.lib │ └── 64 │ ├── SDL2.lib │ ├── SDL2main.lib │ └── SDL2test.lib ├── gnu-mingw ├── dll │ ├── 32 │ │ ├── SDL2.dll │ │ └── sdl2-config │ └── 64 │ │ ├── SDL2.dll │ │ └── sdl2-config └── lib │ ├── 32 │ ├── libSDL2.a │ ├── libSDL2.dll.a │ ├── libSDL2main.a │ ├── libSDL2_test.a │ ├── libSDL2main.la │ ├── libSDL2_test.la │ └── libSDL2.la │ └── 64 │ ├── libSDL2.a │ ├── libSDL2.dll.a │ ├── libSDL2main.a │ ├── libSDL2_test.a │ ├── libSDL2main.la │ ├── libSDL2_test.la │ └── libSDL2.la ├── .editorconfig ├── Cargo.toml ├── LICENSE.md ├── src ├── lib.rs ├── bitrange.rs ├── main.rs ├── instructions.rs └── cpu.rs ├── README.md ├── Cargo.lock └── tests └── chip8.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /data 4 | /SDL2.dll -------------------------------------------------------------------------------- /images/BRIX.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/images/BRIX.gif -------------------------------------------------------------------------------- /msvc/dll/32/SDL2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/msvc/dll/32/SDL2.dll -------------------------------------------------------------------------------- /msvc/dll/64/SDL2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/msvc/dll/64/SDL2.dll -------------------------------------------------------------------------------- /msvc/lib/32/SDL2.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/msvc/lib/32/SDL2.lib -------------------------------------------------------------------------------- /msvc/lib/64/SDL2.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/msvc/lib/64/SDL2.lib -------------------------------------------------------------------------------- /gnu-mingw/dll/32/SDL2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/gnu-mingw/dll/32/SDL2.dll -------------------------------------------------------------------------------- /gnu-mingw/dll/64/SDL2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/gnu-mingw/dll/64/SDL2.dll -------------------------------------------------------------------------------- /msvc/lib/32/SDL2main.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/msvc/lib/32/SDL2main.lib -------------------------------------------------------------------------------- /msvc/lib/32/SDL2test.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/msvc/lib/32/SDL2test.lib -------------------------------------------------------------------------------- /msvc/lib/64/SDL2main.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/msvc/lib/64/SDL2main.lib -------------------------------------------------------------------------------- /msvc/lib/64/SDL2test.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/msvc/lib/64/SDL2test.lib -------------------------------------------------------------------------------- /gnu-mingw/lib/32/libSDL2.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/gnu-mingw/lib/32/libSDL2.a -------------------------------------------------------------------------------- /gnu-mingw/lib/64/libSDL2.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/gnu-mingw/lib/64/libSDL2.a -------------------------------------------------------------------------------- /gnu-mingw/lib/32/libSDL2.dll.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/gnu-mingw/lib/32/libSDL2.dll.a -------------------------------------------------------------------------------- /gnu-mingw/lib/32/libSDL2main.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/gnu-mingw/lib/32/libSDL2main.a -------------------------------------------------------------------------------- /gnu-mingw/lib/64/libSDL2.dll.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/gnu-mingw/lib/64/libSDL2.dll.a -------------------------------------------------------------------------------- /gnu-mingw/lib/64/libSDL2main.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/gnu-mingw/lib/64/libSDL2main.a -------------------------------------------------------------------------------- /gnu-mingw/lib/32/libSDL2_test.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/gnu-mingw/lib/32/libSDL2_test.a -------------------------------------------------------------------------------- /gnu-mingw/lib/64/libSDL2_test.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiver/chip8/HEAD/gnu-mingw/lib/64/libSDL2_test.a -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{rs}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [*.{toml,yml}}] 13 | indent_style = space 14 | indent_size = 4 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chip8" 3 | version = "0.1.0" 4 | authors = ["Robert Vally "] 5 | build = "build.rs" 6 | 7 | [dependencies] 8 | byteorder="1" 9 | enum-primitive-derive = "^0.1" 10 | num-traits = "^0.1" 11 | rand = "0.4" 12 | sdl2 = "0.31" 13 | failure = "0.1.1" -------------------------------------------------------------------------------- /gnu-mingw/lib/32/libSDL2main.la: -------------------------------------------------------------------------------- 1 | # libSDL2main.la - a libtool library file 2 | # Generated by libtool (GNU libtool) 2.4.2 3 | # 4 | # Please DO NOT delete this file! 5 | # It is necessary for linking the library. 6 | 7 | # The name that we can dlopen(3). 8 | dlname='' 9 | 10 | # Names of this library. 11 | library_names='' 12 | 13 | # The name of the static archive. 14 | old_library='libSDL2main.a' 15 | 16 | # Linker flags that can not go in dependency_libs. 17 | inherited_linker_flags='' 18 | 19 | # Libraries that this one depends upon. 20 | dependency_libs='' 21 | 22 | # Names of additional weak libraries provided by this library 23 | weak_library_names='' 24 | 25 | # Version information for libSDL2main. 26 | current=0 27 | age=0 28 | revision=0 29 | 30 | # Is this an already installed library? 31 | installed=yes 32 | 33 | # Should we warn about portability when linking against -modules? 34 | shouldnotlink=no 35 | 36 | # Files to dlopen/dlpreopen 37 | dlopen='' 38 | dlpreopen='' 39 | 40 | # Directory that this library needs to be installed in: 41 | libdir='/Users/slouken/release/SDL/SDL2-2.0.8/i686-w64-mingw32/lib' 42 | -------------------------------------------------------------------------------- /gnu-mingw/lib/32/libSDL2_test.la: -------------------------------------------------------------------------------- 1 | # libSDL2_test.la - a libtool library file 2 | # Generated by libtool (GNU libtool) 2.4.2 3 | # 4 | # Please DO NOT delete this file! 5 | # It is necessary for linking the library. 6 | 7 | # The name that we can dlopen(3). 8 | dlname='' 9 | 10 | # Names of this library. 11 | library_names='' 12 | 13 | # The name of the static archive. 14 | old_library='libSDL2_test.a' 15 | 16 | # Linker flags that can not go in dependency_libs. 17 | inherited_linker_flags='' 18 | 19 | # Libraries that this one depends upon. 20 | dependency_libs='' 21 | 22 | # Names of additional weak libraries provided by this library 23 | weak_library_names='' 24 | 25 | # Version information for libSDL2_test. 26 | current=0 27 | age=0 28 | revision=0 29 | 30 | # Is this an already installed library? 31 | installed=yes 32 | 33 | # Should we warn about portability when linking against -modules? 34 | shouldnotlink=no 35 | 36 | # Files to dlopen/dlpreopen 37 | dlopen='' 38 | dlpreopen='' 39 | 40 | # Directory that this library needs to be installed in: 41 | libdir='/Users/slouken/release/SDL/SDL2-2.0.8/i686-w64-mingw32/lib' 42 | -------------------------------------------------------------------------------- /gnu-mingw/lib/64/libSDL2main.la: -------------------------------------------------------------------------------- 1 | # libSDL2main.la - a libtool library file 2 | # Generated by libtool (GNU libtool) 2.4.2 3 | # 4 | # Please DO NOT delete this file! 5 | # It is necessary for linking the library. 6 | 7 | # The name that we can dlopen(3). 8 | dlname='' 9 | 10 | # Names of this library. 11 | library_names='' 12 | 13 | # The name of the static archive. 14 | old_library='libSDL2main.a' 15 | 16 | # Linker flags that can not go in dependency_libs. 17 | inherited_linker_flags='' 18 | 19 | # Libraries that this one depends upon. 20 | dependency_libs='' 21 | 22 | # Names of additional weak libraries provided by this library 23 | weak_library_names='' 24 | 25 | # Version information for libSDL2main. 26 | current=0 27 | age=0 28 | revision=0 29 | 30 | # Is this an already installed library? 31 | installed=yes 32 | 33 | # Should we warn about portability when linking against -modules? 34 | shouldnotlink=no 35 | 36 | # Files to dlopen/dlpreopen 37 | dlopen='' 38 | dlpreopen='' 39 | 40 | # Directory that this library needs to be installed in: 41 | libdir='/Users/slouken/release/SDL/SDL2-2.0.8/x86_64-w64-mingw32/lib' 42 | -------------------------------------------------------------------------------- /gnu-mingw/lib/64/libSDL2_test.la: -------------------------------------------------------------------------------- 1 | # libSDL2_test.la - a libtool library file 2 | # Generated by libtool (GNU libtool) 2.4.2 3 | # 4 | # Please DO NOT delete this file! 5 | # It is necessary for linking the library. 6 | 7 | # The name that we can dlopen(3). 8 | dlname='' 9 | 10 | # Names of this library. 11 | library_names='' 12 | 13 | # The name of the static archive. 14 | old_library='libSDL2_test.a' 15 | 16 | # Linker flags that can not go in dependency_libs. 17 | inherited_linker_flags='' 18 | 19 | # Libraries that this one depends upon. 20 | dependency_libs='' 21 | 22 | # Names of additional weak libraries provided by this library 23 | weak_library_names='' 24 | 25 | # Version information for libSDL2_test. 26 | current=0 27 | age=0 28 | revision=0 29 | 30 | # Is this an already installed library? 31 | installed=yes 32 | 33 | # Should we warn about portability when linking against -modules? 34 | shouldnotlink=no 35 | 36 | # Files to dlopen/dlpreopen 37 | dlopen='' 38 | dlpreopen='' 39 | 40 | # Directory that this library needs to be installed in: 41 | libdir='/Users/slouken/release/SDL/SDL2-2.0.8/x86_64-w64-mingw32/lib' 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Robert Vally 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gnu-mingw/lib/32/libSDL2.la: -------------------------------------------------------------------------------- 1 | # libSDL2.la - a libtool library file 2 | # Generated by libtool (GNU libtool) 2.4.2 3 | # 4 | # Please DO NOT delete this file! 5 | # It is necessary for linking the library. 6 | 7 | # The name that we can dlopen(3). 8 | dlname='../bin/SDL2.dll' 9 | 10 | # Names of this library. 11 | library_names='libSDL2.dll.a' 12 | 13 | # The name of the static archive. 14 | old_library='libSDL2.a' 15 | 16 | # Linker flags that can not go in dependency_libs. 17 | inherited_linker_flags='' 18 | 19 | # Libraries that this one depends upon. 20 | dependency_libs=' -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lversion -luuid' 21 | 22 | # Names of additional weak libraries provided by this library 23 | weak_library_names='' 24 | 25 | # Version information for libSDL2. 26 | current=8 27 | age=8 28 | revision=0 29 | 30 | # Is this an already installed library? 31 | installed=yes 32 | 33 | # Should we warn about portability when linking against -modules? 34 | shouldnotlink=no 35 | 36 | # Files to dlopen/dlpreopen 37 | dlopen='' 38 | dlpreopen='' 39 | 40 | # Directory that this library needs to be installed in: 41 | libdir='/Users/slouken/release/SDL/SDL2-2.0.8/i686-w64-mingw32/lib' 42 | -------------------------------------------------------------------------------- /gnu-mingw/lib/64/libSDL2.la: -------------------------------------------------------------------------------- 1 | # libSDL2.la - a libtool library file 2 | # Generated by libtool (GNU libtool) 2.4.2 3 | # 4 | # Please DO NOT delete this file! 5 | # It is necessary for linking the library. 6 | 7 | # The name that we can dlopen(3). 8 | dlname='../bin/SDL2.dll' 9 | 10 | # Names of this library. 11 | library_names='libSDL2.dll.a' 12 | 13 | # The name of the static archive. 14 | old_library='libSDL2.a' 15 | 16 | # Linker flags that can not go in dependency_libs. 17 | inherited_linker_flags='' 18 | 19 | # Libraries that this one depends upon. 20 | dependency_libs=' -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lversion -luuid' 21 | 22 | # Names of additional weak libraries provided by this library 23 | weak_library_names='' 24 | 25 | # Version information for libSDL2. 26 | current=8 27 | age=8 28 | revision=0 29 | 30 | # Is this an already installed library? 31 | installed=yes 32 | 33 | # Should we warn about portability when linking against -modules? 34 | shouldnotlink=no 35 | 36 | # Files to dlopen/dlpreopen 37 | dlopen='' 38 | dlpreopen='' 39 | 40 | # Directory that this library needs to be installed in: 41 | libdir='/Users/slouken/release/SDL/SDL2-2.0.8/x86_64-w64-mingw32/lib' 42 | -------------------------------------------------------------------------------- /gnu-mingw/dll/32/sdl2-config: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | prefix=/usr/local/i686-w64-mingw32 4 | exec_prefix=${prefix} 5 | exec_prefix_set=no 6 | libdir=${exec_prefix}/lib 7 | 8 | #usage="\ 9 | #Usage: $0 [--prefix[=DIR]] [--exec-prefix[=DIR]] [--version] [--cflags] [--libs]" 10 | usage="\ 11 | Usage: $0 [--prefix[=DIR]] [--exec-prefix[=DIR]] [--version] [--cflags] [--libs] [--static-libs]" 12 | 13 | if test $# -eq 0; then 14 | echo "${usage}" 1>&2 15 | exit 1 16 | fi 17 | 18 | while test $# -gt 0; do 19 | case "$1" in 20 | -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; 21 | *) optarg= ;; 22 | esac 23 | 24 | case $1 in 25 | --prefix=*) 26 | prefix=$optarg 27 | if test $exec_prefix_set = no ; then 28 | exec_prefix=$optarg 29 | fi 30 | ;; 31 | --prefix) 32 | echo $prefix 33 | ;; 34 | --exec-prefix=*) 35 | exec_prefix=$optarg 36 | exec_prefix_set=yes 37 | ;; 38 | --exec-prefix) 39 | echo $exec_prefix 40 | ;; 41 | --version) 42 | echo 2.0.8 43 | ;; 44 | --cflags) 45 | echo -I${prefix}/include/SDL2 -Dmain=SDL_main 46 | ;; 47 | --libs) 48 | echo -L${exec_prefix}/lib -lmingw32 -lSDL2main -lSDL2 -mwindows 49 | ;; 50 | --static-libs) 51 | # --libs|--static-libs) 52 | echo -L${exec_prefix}/lib -lmingw32 -lSDL2main -lSDL2 -mwindows -Wl,--no-undefined -lm -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lversion -luuid -static-libgcc 53 | ;; 54 | *) 55 | echo "${usage}" 1>&2 56 | exit 1 57 | ;; 58 | esac 59 | shift 60 | done 61 | -------------------------------------------------------------------------------- /gnu-mingw/dll/64/sdl2-config: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | prefix=/usr/local/x86_64-w64-mingw32 4 | exec_prefix=${prefix} 5 | exec_prefix_set=no 6 | libdir=${exec_prefix}/lib 7 | 8 | #usage="\ 9 | #Usage: $0 [--prefix[=DIR]] [--exec-prefix[=DIR]] [--version] [--cflags] [--libs]" 10 | usage="\ 11 | Usage: $0 [--prefix[=DIR]] [--exec-prefix[=DIR]] [--version] [--cflags] [--libs] [--static-libs]" 12 | 13 | if test $# -eq 0; then 14 | echo "${usage}" 1>&2 15 | exit 1 16 | fi 17 | 18 | while test $# -gt 0; do 19 | case "$1" in 20 | -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; 21 | *) optarg= ;; 22 | esac 23 | 24 | case $1 in 25 | --prefix=*) 26 | prefix=$optarg 27 | if test $exec_prefix_set = no ; then 28 | exec_prefix=$optarg 29 | fi 30 | ;; 31 | --prefix) 32 | echo $prefix 33 | ;; 34 | --exec-prefix=*) 35 | exec_prefix=$optarg 36 | exec_prefix_set=yes 37 | ;; 38 | --exec-prefix) 39 | echo $exec_prefix 40 | ;; 41 | --version) 42 | echo 2.0.8 43 | ;; 44 | --cflags) 45 | echo -I${prefix}/include/SDL2 -Dmain=SDL_main 46 | ;; 47 | --libs) 48 | echo -L${exec_prefix}/lib -lmingw32 -lSDL2main -lSDL2 -mwindows 49 | ;; 50 | --static-libs) 51 | # --libs|--static-libs) 52 | echo -L${exec_prefix}/lib -lmingw32 -lSDL2main -lSDL2 -mwindows -Wl,--no-undefined -lm -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lversion -luuid -static-libgcc 53 | ;; 54 | *) 55 | echo "${usage}" 1>&2 56 | exit 1 57 | ;; 58 | esac 59 | shift 60 | done 61 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate byteorder; 2 | extern crate rand; 3 | extern crate sdl2; 4 | extern crate failure; 5 | 6 | mod bitrange; 7 | pub mod instructions; 8 | pub mod cpu; 9 | 10 | use std::fs::File; 11 | use std::io::Read; 12 | use std::time::Duration; 13 | 14 | pub use failure::{Error, Fail}; 15 | 16 | use sdl2::render::Canvas; 17 | use sdl2::video::Window; 18 | use sdl2::EventPump; 19 | use sdl2::Sdl; 20 | 21 | pub const FRAME_TICK: Duration = Duration::from_millis(16); 22 | pub const CPU_TICK: Duration = Duration::from_millis(2); 23 | 24 | pub const FONT4X5: [u8; 80] = [ 25 | 0xf0, 0x90, 0x90, 0x90, 0xf0, // 0 26 | 0x20, 0x60, 0x20, 0x20, 0x70, // 1 27 | 0xf0, 0x10, 0xf0, 0x80, 0xf0, // 2 28 | 0xf0, 0x10, 0xf0, 0x10, 0xf0, // 3 29 | 0x90, 0x90, 0xf0, 0x10, 0x10, // 4 30 | 0xf0, 0x80, 0xf0, 0x10, 0xf0, // 5 31 | 0xf0, 0x80, 0xf0, 0x90, 0xf0, // 6 32 | 0xf0, 0x10, 0x20, 0x40, 0x40, // 7 33 | 0xf0, 0x90, 0xf0, 0x90, 0xf0, // 8 34 | 0xf0, 0x90, 0xf0, 0x10, 0xf0, // 9 35 | 0xf0, 0x90, 0xf0, 0x90, 0x90, // A 36 | 0xe0, 0x90, 0xe0, 0x90, 0xe0, // B 37 | 0xf0, 0x80, 0x80, 0x80, 0x80, // C 38 | 0xe0, 0x90, 0x90, 0x90, 0xe0, // D 39 | 0xf0, 0x80, 0xf0, 0x80, 0xf0, // E 40 | 0xf0, 0x80, 0xf0, 0x80, 0x80, // F 41 | ]; 42 | 43 | pub struct Context { 44 | pub sdl_context: Option, 45 | pub canvas: Option>, 46 | pub events: Option, 47 | pub grid: Vec, 48 | pub key_map: [u8; 16], 49 | } 50 | 51 | 52 | pub fn read_binary(filename: &String) -> Result, Error> { 53 | let mut file = File::open(filename)?; 54 | let mut buf = Vec::new(); 55 | file.read_to_end(&mut buf)?; 56 | Ok(buf) 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CHIP-8 Emulator in Rust 2 | 3 | ![Brix](https://raw.githubusercontent.com/shiver/chip8/master/images/BRIX.gif) 4 | 5 | A short weekend project to get some understanding around emulators and a little more experience with Rust. 6 | If you have any suggestions or comments regarding either the code, emulators or Rust in general I would be happy to hear from you! 7 | 8 | I have tested this on both Windows 10 and Arch Linux. 9 | 10 | ## Requirements 11 | 12 | - CHIP-8 programs 13 | 14 | See `Resources used during development` section below. 15 | 16 | - Rust 1.26+ 17 | - SDL2 development libraries 18 | 19 | ### Linux 20 | 21 | If you're running Linux, simply install the relevant package for your distribution. Such as `libsdl2-dev` for Ubuntu. 22 | 23 | ### Windows 24 | 25 | I have included the SDL2-2.0.8 pre-compiled binaries for `MSVC` and `MINGW`. However, I can only confirm having tested with the `MSVC` binaries. 26 | 27 | ## How to run 28 | 29 | $ cargo run -- 30 | 31 | ## Testing 32 | 33 | $ cargo test 34 | running 24 tests 35 | test test_add_const ... ok 36 | test test_add ... ok 37 | test test_assign_value ... ok 38 | ... 39 | 40 | ## Contributions 41 | 42 | Contributions are welcome! Whether in the form of pull requests, suggestions, or comments. I would be happy to discuss any aspect of the project. 43 | 44 | ## Resources used during development 45 | 46 | **CHIP-8 Info**: 47 | 48 | - https://en.wikipedia.org/wiki/CHIP-8 49 | - http://devernay.free.fr/hacks/chip8/C8TECH10.HTM 50 | 51 | **Programs**: 52 | 53 | - https://www.zophar.net/pdroms/chip8.htmll 54 | 55 | ## License 56 | 57 | `chip8` is distributed under the terms of the MIT license. 58 | 59 | See LICENSE.md for details. 60 | -------------------------------------------------------------------------------- /src/bitrange.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | pub trait BitRange { 4 | fn range_u8(&self, range: Range) -> u8; 5 | fn range_u16(&self, range: Range) -> u16; 6 | 7 | // TODO: Add if I need them 8 | // fn range_u32(&self, range: Range) -> u32; 9 | // fn range_u64(&self, range: Range) -> u64; 10 | } 11 | 12 | impl BitRange for u32 { 13 | fn range_u8(&self, range: Range) -> u8 { 14 | let num_bits = (range.end - range.start) + 1; 15 | 16 | assert!(num_bits > 0); 17 | assert!(num_bits < 32); 18 | 19 | let mask = 2_u32.pow(num_bits as u32) - 1; 20 | 21 | ((self >> range.start) & mask) as u8 22 | } 23 | 24 | fn range_u16(&self, range: Range) -> u16 { 25 | let num_bits = (range.end - range.start) + 1; 26 | 27 | assert!(num_bits > 0); 28 | assert!(num_bits < 32); 29 | 30 | let mask = 2_u32.pow(num_bits as u32) - 1; 31 | 32 | ((self >> range.start) & mask) as u16 33 | } 34 | } 35 | 36 | impl BitRange for u16 { 37 | fn range_u8(&self, range: Range) -> u8 { 38 | let num_bits = (range.end - range.start) + 1; 39 | 40 | assert!(num_bits > 0); 41 | assert!(num_bits < 16); 42 | 43 | let mask = 2_u16.pow(num_bits as u32) - 1; 44 | ((self >> range.start) & mask) as u8 45 | } 46 | 47 | fn range_u16(&self, range: Range) -> u16 { 48 | let num_bits = (range.end - range.start) + 1; 49 | 50 | assert!(num_bits > 0); 51 | assert!(num_bits < 32); 52 | 53 | let mask = 2_u16.pow(num_bits as u32) - 1; 54 | 55 | ((self >> range.start) & mask) as u16 56 | } 57 | } 58 | 59 | impl BitRange for u8 { 60 | fn range_u8(&self, range: Range) -> u8 { 61 | let num_bits = (range.end - range.start) + 1; 62 | 63 | assert!(num_bits > 0); 64 | assert!(num_bits < 8); 65 | 66 | let mask = 2_u8.pow(num_bits as u32) - 1; 67 | ((self >> range.start) & mask) as u8 68 | } 69 | 70 | fn range_u16(&self, range: Range) -> u16 { 71 | let num_bits = (range.end - range.start) + 1; 72 | 73 | assert!(num_bits > 0); 74 | assert!(num_bits < 8); 75 | 76 | let mask = 2_u8.pow(num_bits as u32) - 1; 77 | 78 | ((self >> range.start) & mask) as u16 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate chip8; 2 | extern crate sdl2; 3 | #[macro_use] 4 | extern crate failure; 5 | 6 | use std::env; 7 | use std::time::Instant; 8 | use std::process::exit; 9 | 10 | use sdl2::event::Event; 11 | use sdl2::keyboard::Keycode; 12 | 13 | use failure::{Error, err_msg}; 14 | use chip8::{read_binary, Context, FRAME_TICK, CPU_TICK}; 15 | use chip8::cpu::CPU; 16 | use chip8::instructions::Instruction; 17 | 18 | fn init_canvas(context: &mut Context) -> Result<&Context, Error> { 19 | let sdl_context = match sdl2::init() { 20 | Ok(v) => v, 21 | Err(s) => return Err(err_msg(s)), 22 | }; 23 | 24 | let video_subsystem = match sdl_context.video() { 25 | Ok(v) => v, 26 | Err(s) => return Err(err_msg(s)), 27 | }; 28 | 29 | let window = video_subsystem 30 | .window("CHIP-8", 800, 400) 31 | .position_centered() 32 | .build()?; 33 | 34 | context.sdl_context = Some(sdl_context); 35 | context.canvas = Some(window.into_canvas().build()?); 36 | 37 | Ok(context) 38 | } 39 | 40 | fn init_event_subsystem(context: &mut Context) -> Result<&Context, Error> { 41 | if let Some(ref sdl_context) = context.sdl_context { 42 | context.events = match sdl_context.event_pump() { 43 | Ok(v) => Some(v), 44 | Err(s) => return Err(err_msg(s)), 45 | }; 46 | 47 | Ok(context) 48 | } else { 49 | Err(format_err!("SDL context should have been available, but it wasn't!")) 50 | } 51 | } 52 | 53 | fn main() { 54 | let mut context = Context { 55 | canvas: None, 56 | grid: vec![0; 2046], 57 | key_map: [0; 16], 58 | events: None, 59 | sdl_context: None, 60 | }; 61 | match init_canvas(&mut context) { 62 | Err(e) => println!("Failed to initialise canvas: {}", e), 63 | Ok(_) => (), 64 | } 65 | match init_event_subsystem(&mut context) { 66 | Err(e) => println!("Failed to initialise event system: {}", e), 67 | Ok(_) => (), 68 | } 69 | 70 | let filename = env::args().nth(1).expect("filename?"); 71 | 72 | let data = match read_binary(&filename) { 73 | Ok(data) => data, 74 | Err(e) => { 75 | println!("Error reading binary \"{}\": {}", filename, e); 76 | exit(1); 77 | } 78 | }; 79 | 80 | let mut cpu = CPU::new(&data, context.canvas); 81 | 82 | let mut event_pump = context 83 | .events 84 | .expect("Event subsystem should have been available, but it wasn't!"); 85 | 86 | let mut cpu_last = Instant::now(); 87 | let mut frame_last = Instant::now(); 88 | 'running: loop { 89 | for event in event_pump.poll_iter() { 90 | match event { 91 | Event::Quit { .. } | 92 | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => break 'running, 93 | 94 | Event::KeyDown { keycode: Some(Keycode::Space), .. } => cpu.keys[15] ^= 1, 95 | 96 | Event::KeyDown { keycode: Some(Keycode::Left), .. } => cpu.keys[4] = 1, 97 | Event::KeyDown { keycode: Some(Keycode::Right), .. } => cpu.keys[6] = 1, 98 | Event::KeyDown { keycode: Some(Keycode::Up), .. } => cpu.keys[8] = 1, 99 | Event::KeyDown { keycode: Some(Keycode::Down), .. } => cpu.keys[2] = 1, 100 | Event::KeyDown { keycode: Some(Keycode::Return), .. } => cpu.keys[5] = 1, 101 | 102 | Event::KeyUp { keycode: Some(Keycode::Left), .. } => cpu.keys[4] = 0, 103 | Event::KeyUp { keycode: Some(Keycode::Right), .. } => cpu.keys[6] = 0, 104 | Event::KeyUp { keycode: Some(Keycode::Up), .. } => cpu.keys[8] = 0, 105 | Event::KeyUp { keycode: Some(Keycode::Down), .. } => cpu.keys[2] = 0, 106 | Event::KeyUp { keycode: Some(Keycode::Return), .. } => cpu.keys[5] = 0, 107 | 108 | _ => {} 109 | } 110 | } 111 | 112 | if cpu.keys[15] != 1 { 113 | if cpu_last.elapsed() >= CPU_TICK { 114 | match cpu.fetch_opcode() { 115 | Ok(raw_opcode) => { 116 | if let Some(instruction) = Instruction::from_u16(&raw_opcode) { 117 | match cpu.do_instruction(&instruction) { 118 | Err(e) => println!("Instruction execution failed: {}", e), 119 | Ok(_) => (), 120 | } 121 | } 122 | }, 123 | Err(e) => println!("Could not fetch opcode: {}", e) 124 | } 125 | 126 | cpu_last = Instant::now(); 127 | } 128 | } 129 | 130 | if frame_last.elapsed() >= FRAME_TICK { 131 | cpu.show(); 132 | frame_last = Instant::now(); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/instructions.rs: -------------------------------------------------------------------------------- 1 | use bitrange::BitRange; 2 | 3 | type GPR = u8; 4 | type Address = u16; 5 | type HalfWord = u8; 6 | 7 | #[derive(Debug, PartialEq)] 8 | pub enum Instruction { 9 | ClearDisplay, 10 | Return, 11 | JumpToAddress(Address), 12 | CallSubroutine(Address), 13 | SkipIfEqual(GPR, HalfWord), 14 | SkipIfNotEqual(GPR, HalfWord), 15 | SkipIfEqualRegister(GPR, HalfWord), 16 | LoadConst(GPR, HalfWord), 17 | AddConst(GPR, HalfWord), 18 | AssignValue(GPR, GPR), 19 | SetOr(GPR, GPR), 20 | SetAnd(GPR, GPR), 21 | SetXor(GPR, GPR), 22 | Add(GPR, GPR), 23 | Subtract(GPR, GPR), 24 | ShiftRight(GPR, GPR), 25 | Reduce(GPR, GPR), 26 | ShiftLeft(GPR, GPR), 27 | SkipIfNotEqualRegister(GPR, GPR), 28 | SetMemoryAddress(Address), 29 | JumpToV0Address(Address), 30 | BitwiseRandom(GPR, HalfWord), 31 | DrawSprite(GPR, GPR, HalfWord), 32 | SkipIfPressed(GPR), 33 | SkipIfNotPressed(GPR), 34 | LoadDelay(GPR), 35 | WaitForPress(GPR), 36 | SetDelay(GPR), 37 | SetSound(GPR), 38 | AddOffset(GPR), 39 | SetMemoryForFont(GPR), 40 | SetBCD(GPR), 41 | DumpReg(GPR), 42 | LoadReg(GPR), 43 | } 44 | 45 | fn first(value: &u16) -> u8 { 46 | value.range_u8(12..15) 47 | } 48 | 49 | fn second(value: &u16) -> u8 { 50 | value.range_u8(8..11) 51 | } 52 | 53 | fn third(value: &u16) -> u8 { 54 | value.range_u8(4..7) 55 | } 56 | 57 | fn last(value: &u16) -> u8 { 58 | value.range_u8(0..3) 59 | } 60 | 61 | fn last_two(value: &u16) -> u8 { 62 | value.range_u8(0..7) 63 | } 64 | 65 | fn last_three(value: &u16) -> u16 { 66 | value.range_u16(0..11) 67 | } 68 | 69 | impl Instruction { 70 | pub fn from_u16(value: &u16) -> Option { 71 | match first(&value) { 72 | 0x0 => { 73 | match last_two(&value) { 74 | 0xE0 => Some(Instruction::ClearDisplay), 75 | 0xEE => Some(Instruction::Return), 76 | _ => None, 77 | } 78 | } 79 | 0x1 => Some(Instruction::JumpToAddress(last_three(&value))), 80 | 0x2 => Some(Instruction::CallSubroutine(last_three(&value))), 81 | 0x3 => Some(Instruction::SkipIfEqual(second(&value), last_two(&value))), 82 | 0x4 => Some(Instruction::SkipIfNotEqual(second(&value), last_two(&value))), 83 | 0x5 => Some(Instruction::SkipIfEqualRegister(second(&value), third(&value))), 84 | 0x6 => Some(Instruction::LoadConst(second(&value), last_two(&value))), 85 | 0x7 => Some(Instruction::AddConst(second(&value), last_two(&value))), 86 | 0x8 => { 87 | match last(&value) { 88 | 0x0 => Some(Instruction::AssignValue(second(&value), third(&value))), 89 | 0x1 => Some(Instruction::SetOr(second(&value), third(&value))), 90 | 0x2 => Some(Instruction::SetAnd(second(&value), third(&value))), 91 | 0x3 => Some(Instruction::SetXor(second(&value), third(&value))), 92 | 0x4 => Some(Instruction::Add(second(&value), third(&value))), 93 | 0x5 => Some(Instruction::Subtract(second(&value), third(&value))), 94 | 0x6 => Some(Instruction::ShiftRight(second(&value), third(&value))), 95 | 0x7 => Some(Instruction::Reduce(second(&value), third(&value))), 96 | 0xE => Some(Instruction::ShiftLeft(second(&value), third(&value))), 97 | _ => None, 98 | } 99 | } 100 | 0x9 => Some(Instruction::SkipIfNotEqualRegister(second(&value), third(&value))), 101 | 0xA => Some(Instruction::SetMemoryAddress(last_three(&value))), 102 | 0xB => Some(Instruction::JumpToV0Address(last_three(&value))), 103 | 0xC => Some(Instruction::BitwiseRandom(second(&value), last_two(&value))), 104 | 0xD => Some(Instruction::DrawSprite(second(&value), third(&value), last(&value))), 105 | 0xE => { 106 | match last_two(&value) { 107 | 0x9E => Some(Instruction::SkipIfPressed(second(&value))), 108 | 0xA1 => Some(Instruction::SkipIfNotPressed(second(&value))), 109 | _ => None, 110 | } 111 | } 112 | 0xF => { 113 | match last_two(&value) { 114 | 0x07 => Some(Instruction::LoadDelay(second(&value))), 115 | 0x0A => Some(Instruction::WaitForPress(second(&value))), 116 | 0x15 => Some(Instruction::SetDelay(second(&value))), 117 | 0x18 => Some(Instruction::SetSound(second(&value))), 118 | 0x1E => Some(Instruction::AddOffset(second(&value))), 119 | 0x29 => Some(Instruction::SetMemoryForFont(second(&value))), 120 | 0x33 => Some(Instruction::SetBCD(second(&value))), 121 | 0x55 => Some(Instruction::DumpReg(second(&value))), 122 | 0x65 => Some(Instruction::LoadReg(second(&value))), 123 | _ => None, 124 | } 125 | } 126 | 127 | _ => None, 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/cpu.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Cursor, Error, Write}; 2 | 3 | use sdl2::pixels::Color; 4 | use sdl2::rect::Rect; 5 | use sdl2::render::Canvas; 6 | use sdl2::video::Window; 7 | 8 | use byteorder::{BigEndian, ReadBytesExt}; 9 | use rand; 10 | 11 | use instructions::Instruction; 12 | use FONT4X5; 13 | 14 | const GRID_WIDTH: usize = 64; 15 | const GRID_HEIGHT: usize = 32; 16 | 17 | pub struct CPU { 18 | pub regs: [u8; 16], 19 | pub address: u16, 20 | pub stack: Vec, 21 | pub memory: Cursor>, 22 | 23 | pub delay_timer: u8, 24 | pub sound_timer: u8, 25 | 26 | pub pc: usize, 27 | 28 | pub keys: [u8; 16], 29 | pub display: Option>, 30 | pub grid: Vec, 31 | } 32 | 33 | impl CPU { 34 | pub fn new(data: &Vec, display: Option>) -> CPU { 35 | let mut memory = vec![0; 4096]; 36 | for i in 0..data.len() { 37 | memory[0x200 + i] = data[i]; 38 | } 39 | 40 | for i in 0..FONT4X5.len() { 41 | memory[0x0 + i] = FONT4X5[i]; 42 | } 43 | 44 | CPU { 45 | regs: [0; 16], 46 | address: 0, 47 | stack: vec![], 48 | memory: Cursor::new(memory), 49 | delay_timer: 0, 50 | sound_timer: 0, 51 | pc: 0x200, 52 | keys: [0; 16], 53 | display: display, 54 | grid: vec![0; GRID_WIDTH * GRID_HEIGHT], 55 | } 56 | } 57 | 58 | pub fn show(&mut self) { 59 | if let Some(ref mut canvas) = self.display { 60 | canvas.set_draw_color(Color::RGB(0, 0, 0)); 61 | canvas.clear(); 62 | canvas.set_draw_color(Color::RGB(255, 255, 255)); 63 | 64 | for y in 0..GRID_HEIGHT { 65 | for x in 0..GRID_WIDTH { 66 | if self.grid[(y * GRID_WIDTH) + x] == 1 { 67 | canvas 68 | .fill_rect(Rect::new((x as u8) as i32 * 10, 69 | (y as u8) as i32 * 10, 70 | 10, 71 | 10)).unwrap(); 72 | } 73 | } 74 | } 75 | 76 | canvas.present(); 77 | } 78 | } 79 | 80 | fn clear(&mut self) { 81 | for idx in 0..GRID_WIDTH * GRID_HEIGHT { 82 | self.grid[idx] = 0; 83 | } 84 | } 85 | 86 | fn inc_pc(&mut self) { 87 | self.pc += 2; 88 | } 89 | 90 | pub fn fetch_opcode(&mut self) -> Result { 91 | self.memory.set_position(self.pc as u64); 92 | self.memory.read_u16::() 93 | } 94 | 95 | fn timer_tick(&mut self) { 96 | if self.delay_timer > 0 { 97 | self.delay_timer -= 1; 98 | } 99 | 100 | if self.sound_timer > 0 { 101 | self.sound_timer -= 1; 102 | } 103 | } 104 | 105 | pub fn do_instruction(&mut self, instruction: &Instruction) -> Result<(), Error> { 106 | let mut should_increment = true; 107 | self.timer_tick(); 108 | 109 | match instruction { 110 | Instruction::ClearDisplay => { 111 | self.clear(); 112 | } 113 | 114 | Instruction::Return => { 115 | if let Some(ret) = self.stack.pop() { 116 | self.pc = ret as usize; 117 | should_increment = true; 118 | }; 119 | } 120 | 121 | Instruction::JumpToAddress(address) => { 122 | self.pc = *address as usize; 123 | should_increment = false; 124 | } 125 | 126 | Instruction::CallSubroutine(address) => { 127 | self.stack.push(self.pc); 128 | self.pc = *address as usize; 129 | should_increment = false; 130 | } 131 | 132 | Instruction::SkipIfEqual(vx, value) => { 133 | if self.regs[*vx as usize] == *value { 134 | self.inc_pc(); 135 | } 136 | } 137 | 138 | Instruction::SkipIfNotEqual(vx, value) => { 139 | if self.regs[*vx as usize] != *value { 140 | self.inc_pc(); 141 | } 142 | } 143 | 144 | Instruction::SkipIfEqualRegister(vx, vy) => { 145 | if self.regs[*vx as usize] == self.regs[*vy as usize] { 146 | self.inc_pc(); 147 | } 148 | } 149 | 150 | Instruction::LoadConst(vx, value) => { 151 | self.regs[*vx as usize] = *value; 152 | } 153 | 154 | Instruction::AddConst(vx, value) => { 155 | let idx = *vx as usize; 156 | self.regs[idx] = self.regs[idx].wrapping_add(*value); 157 | } 158 | 159 | Instruction::AssignValue(vx, vy) => { 160 | self.regs[*vx as usize] = self.regs[*vy as usize]; 161 | } 162 | 163 | Instruction::SetOr(vx, vy) => { 164 | self.regs[*vx as usize] |= self.regs[*vy as usize]; 165 | } 166 | 167 | Instruction::SetAnd(vx, vy) => { 168 | self.regs[*vx as usize] &= self.regs[*vy as usize]; 169 | } 170 | 171 | Instruction::SetXor(vx, vy) => { 172 | self.regs[*vx as usize] ^= self.regs[*vy as usize]; 173 | } 174 | 175 | Instruction::Add(vx, vy) => { 176 | let x = self.regs[*vx as usize]; 177 | let y = self.regs[*vy as usize]; 178 | 179 | let ret = x as u16 + y as u16; 180 | self.regs[*vx as usize] = x.wrapping_add(y); 181 | self.regs[0xF] = (ret > 255) as u8; 182 | } 183 | 184 | Instruction::Subtract(vx, vy) => { 185 | let x = self.regs[*vx as usize]; 186 | let y = self.regs[*vy as usize]; 187 | 188 | self.regs[0xF] = (x > y) as u8; 189 | self.regs[*vx as usize] = x.wrapping_sub(y); 190 | } 191 | 192 | Instruction::ShiftRight(vx, vy) => { 193 | self.regs[0xF] = self.regs[*vy as usize] & 1; 194 | self.regs[*vx as usize] = self.regs[*vy as usize] >> 1; 195 | } 196 | 197 | Instruction::Reduce(vx, vy) => { 198 | let x = self.regs[*vx as usize]; 199 | let y = self.regs[*vy as usize]; 200 | 201 | self.regs[*vx as usize] = y.wrapping_sub(x); 202 | self.regs[0xF] = (y > x) as u8; 203 | } 204 | 205 | Instruction::ShiftLeft(vx, vy) => { 206 | self.regs[0xF] = self.regs[*vy as usize] >> 7; 207 | self.regs[*vx as usize] = self.regs[*vy as usize] << 1; 208 | } 209 | 210 | Instruction::SkipIfNotEqualRegister(vx, vy) => { 211 | if self.regs[*vx as usize] != self.regs[*vy as usize] { 212 | self.inc_pc(); 213 | } 214 | } 215 | 216 | Instruction::SetMemoryAddress(address) => { 217 | self.address = *address; 218 | } 219 | 220 | Instruction::JumpToV0Address(address) => { 221 | self.pc = (*address + self.regs[0] as u16) as usize; 222 | should_increment = false; 223 | } 224 | 225 | Instruction::BitwiseRandom(vx, value) => { 226 | self.regs[*vx as usize] = rand::random::() & *value; 227 | } 228 | 229 | Instruction::DrawSprite(vx, vy, height) => { 230 | let mut start_x = self.regs[*vx as usize]; 231 | let mut start_y = self.regs[*vy as usize]; 232 | let height = *height; 233 | self.regs[0xf] = 0; 234 | 235 | if start_x > (GRID_WIDTH - 1) as u8 { 236 | start_x = 0; 237 | } 238 | 239 | for y in 0..height { 240 | self.memory.set_position((self.address + y as u16) as u64); 241 | let row = self.memory.read_u8()?; 242 | 243 | let final_y = y as u16 + start_y as u16; 244 | for x in 0..8 { 245 | let final_x = x as u16 + start_x as u16; 246 | if final_x > (GRID_WIDTH - 1) as u16 { 247 | continue; 248 | } 249 | 250 | let grid_pos = ((final_y * GRID_WIDTH as u16) + final_x) as usize; 251 | if (row >> 7 - x) & 1 != 0 { 252 | self.regs[0xf] = (self.grid[grid_pos] == 1) as u8; 253 | self.grid[grid_pos] ^= 1; 254 | } 255 | } 256 | } 257 | } 258 | 259 | Instruction::SkipIfPressed(vx) => { 260 | let key = self.regs[*vx as usize] as usize; 261 | if self.keys[key] == 1 { 262 | self.inc_pc(); 263 | } 264 | } 265 | Instruction::SkipIfNotPressed(vx) => { 266 | let key = self.regs[*vx as usize] as usize; 267 | if self.keys[key] != 1 { 268 | self.inc_pc(); 269 | } 270 | } 271 | 272 | Instruction::LoadDelay(vx) => { 273 | self.regs[*vx as usize] = self.delay_timer; 274 | } 275 | 276 | Instruction::WaitForPress(vx) => { 277 | let key = self.regs[*vx as usize] as usize; 278 | if self.keys[key] != 1 { 279 | should_increment = false; 280 | } 281 | } 282 | 283 | Instruction::SetDelay(vx) => { 284 | self.delay_timer = self.regs[*vx as usize]; 285 | } 286 | 287 | Instruction::SetSound(vx) => { 288 | self.sound_timer = self.regs[*vx as usize]; 289 | } 290 | 291 | Instruction::AddOffset(vx) => { 292 | self.address += self.regs[*vx as usize] as u16; 293 | } 294 | 295 | Instruction::SetMemoryForFont(vx) => { 296 | self.address = (self.regs[*vx as usize] * 5) as u16; 297 | } 298 | 299 | Instruction::SetBCD(vx) => { 300 | let val = self.regs[*vx as usize]; 301 | 302 | let h = val / 100; 303 | let t = (val / 10) % 10; 304 | let d = (val % 100) % 10; 305 | self.memory.set_position(self.address as u64); 306 | self.memory.write(&[h, t, d])?; 307 | } 308 | 309 | Instruction::DumpReg(vx) => { 310 | for idx in 0..*vx + 1 { 311 | self.memory.set_position(self.address as u64); 312 | self.memory.write(&[self.regs[idx as usize]])?; 313 | self.address += 1; 314 | } 315 | } 316 | 317 | Instruction::LoadReg(vx) => { 318 | for idx in 0..*vx + 1 { 319 | self.memory.set_position((self.address as u16) as u64); 320 | self.regs[idx as usize] = self.memory.read_u8()?; 321 | self.address += 1; 322 | } 323 | } 324 | } 325 | 326 | if should_increment { 327 | self.inc_pc(); 328 | } 329 | 330 | Ok(()) 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "backtrace" 3 | version = "0.3.7" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "rustc-demangle 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 10 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 11 | ] 12 | 13 | [[package]] 14 | name = "backtrace-sys" 15 | version = "0.1.16" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | dependencies = [ 18 | "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", 19 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 20 | ] 21 | 22 | [[package]] 23 | name = "bitflags" 24 | version = "0.7.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | 27 | [[package]] 28 | name = "bitflags" 29 | version = "1.0.3" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | 32 | [[package]] 33 | name = "byteorder" 34 | version = "1.2.2" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | 37 | [[package]] 38 | name = "cc" 39 | version = "1.0.15" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | 42 | [[package]] 43 | name = "cfg-if" 44 | version = "0.1.3" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | 47 | [[package]] 48 | name = "chip8" 49 | version = "0.1.0" 50 | dependencies = [ 51 | "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 52 | "enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 53 | "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 54 | "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 56 | "sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", 57 | ] 58 | 59 | [[package]] 60 | name = "enum-primitive-derive" 61 | version = "0.1.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | dependencies = [ 64 | "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 67 | ] 68 | 69 | [[package]] 70 | name = "failure" 71 | version = "0.1.1" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | dependencies = [ 74 | "backtrace 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 76 | ] 77 | 78 | [[package]] 79 | name = "failure_derive" 80 | version = "0.1.1" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | dependencies = [ 83 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 86 | ] 87 | 88 | [[package]] 89 | name = "fuchsia-zircon" 90 | version = "0.3.3" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | dependencies = [ 93 | "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 95 | ] 96 | 97 | [[package]] 98 | name = "fuchsia-zircon-sys" 99 | version = "0.3.3" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | 102 | [[package]] 103 | name = "lazy_static" 104 | version = "0.2.11" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | 107 | [[package]] 108 | name = "libc" 109 | version = "0.2.40" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | 112 | [[package]] 113 | name = "num" 114 | version = "0.1.42" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | dependencies = [ 117 | "num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 120 | ] 121 | 122 | [[package]] 123 | name = "num-integer" 124 | version = "0.1.36" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | dependencies = [ 127 | "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 128 | ] 129 | 130 | [[package]] 131 | name = "num-iter" 132 | version = "0.1.35" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | dependencies = [ 135 | "num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 136 | "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 137 | ] 138 | 139 | [[package]] 140 | name = "num-traits" 141 | version = "0.1.43" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | dependencies = [ 144 | "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 145 | ] 146 | 147 | [[package]] 148 | name = "num-traits" 149 | version = "0.2.2" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | 152 | [[package]] 153 | name = "quote" 154 | version = "0.3.15" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | 157 | [[package]] 158 | name = "rand" 159 | version = "0.3.22" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | dependencies = [ 162 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 165 | ] 166 | 167 | [[package]] 168 | name = "rand" 169 | version = "0.4.2" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | dependencies = [ 172 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 173 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 174 | "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 175 | ] 176 | 177 | [[package]] 178 | name = "rustc-demangle" 179 | version = "0.1.8" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | 182 | [[package]] 183 | name = "sdl2" 184 | version = "0.31.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | dependencies = [ 187 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 188 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 189 | "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", 190 | "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 191 | "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", 192 | "sdl2-sys 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", 193 | ] 194 | 195 | [[package]] 196 | name = "sdl2-sys" 197 | version = "0.31.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | dependencies = [ 200 | "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 201 | ] 202 | 203 | [[package]] 204 | name = "syn" 205 | version = "0.11.11" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | dependencies = [ 208 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 209 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 210 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 211 | ] 212 | 213 | [[package]] 214 | name = "synom" 215 | version = "0.11.3" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | dependencies = [ 218 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 219 | ] 220 | 221 | [[package]] 222 | name = "synstructure" 223 | version = "0.6.1" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | dependencies = [ 226 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 227 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 228 | ] 229 | 230 | [[package]] 231 | name = "unicode-xid" 232 | version = "0.0.4" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | 235 | [[package]] 236 | name = "winapi" 237 | version = "0.3.4" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | dependencies = [ 240 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 241 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 242 | ] 243 | 244 | [[package]] 245 | name = "winapi-i686-pc-windows-gnu" 246 | version = "0.4.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | 249 | [[package]] 250 | name = "winapi-x86_64-pc-windows-gnu" 251 | version = "0.4.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | 254 | [metadata] 255 | "checksum backtrace 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea58cd16fd6c9d120b5bcb01d63883ae4cc7ba2aed35c1841b862a3c7ef6639" 256 | "checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661" 257 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 258 | "checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" 259 | "checksum byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "73b5bdfe7ee3ad0b99c9801d58807a9dbc9e09196365b0203853b99889ab3c87" 260 | "checksum cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0ebb87d1116151416c0cf66a0e3fb6430cccd120fd6300794b4dfaa050ac40ba" 261 | "checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" 262 | "checksum enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b90e520ec62c1864c8c78d637acbfe8baf5f63240f2fb8165b8325c07812dd" 263 | "checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82" 264 | "checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b" 265 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 266 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 267 | "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 268 | "checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b" 269 | "checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" 270 | "checksum num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d26da319fb45674985c78f1d1caf99aa4941f785d384a2ae36d0740bc3e2fe" 271 | "checksum num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "4b226df12c5a59b63569dd57fafb926d91b385dfce33d8074a412411b689d593" 272 | "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" 273 | "checksum num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee092fcdf725aee04dd7da1d21debff559237d49ef1cb3e69bcb8ece44c7364" 274 | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 275 | "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" 276 | "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" 277 | "checksum rustc-demangle 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "76d7ba1feafada44f2d38eed812bd2489a03c0f5abb975799251518b68848649" 278 | "checksum sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a74c2a98a354b20713b90cce70aef9e927e46110d1bc4ef728fd74e0d53eba60" 279 | "checksum sdl2-sys 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c543ce8a6e33a30cb909612eeeb22e693848211a84558d5a00bb11e791b7ab7" 280 | "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" 281 | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" 282 | "checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" 283 | "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" 284 | "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" 285 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 286 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 287 | -------------------------------------------------------------------------------- /tests/chip8.rs: -------------------------------------------------------------------------------- 1 | extern crate chip8; 2 | extern crate byteorder; 3 | 4 | use std::io::Write; 5 | 6 | use byteorder::{ByteOrder, WriteBytesExt, BigEndian}; 7 | use chip8::cpu::CPU; 8 | use chip8::instructions::Instruction; 9 | 10 | #[test] 11 | fn test_opcode_to_instruction() { 12 | assert_eq!(Instruction::from_u16(&0x00e0), 13 | Some(Instruction::ClearDisplay)); 14 | assert_eq!(Instruction::from_u16(&0x00ee), Some(Instruction::Return)); 15 | assert_eq!(Instruction::from_u16(&0x1123), 16 | Some(Instruction::JumpToAddress(0x0123))); 17 | assert_eq!(Instruction::from_u16(&0x2234), 18 | Some(Instruction::CallSubroutine(0x0234))); 19 | assert_eq!(Instruction::from_u16(&0x3345), 20 | Some(Instruction::SkipIfEqual(0x3, 0x45))); 21 | assert_eq!(Instruction::from_u16(&0x4456), 22 | Some(Instruction::SkipIfNotEqual(0x4, 0x56))); 23 | assert_eq!(Instruction::from_u16(&0x5560), 24 | Some(Instruction::SkipIfEqualRegister(0x5, 0x6))); 25 | assert_eq!(Instruction::from_u16(&0x6678), 26 | Some(Instruction::LoadConst(0x6, 0x78))); 27 | assert_eq!(Instruction::from_u16(&0x7789), 28 | Some(Instruction::AddConst(0x7, 0x89))); 29 | 30 | assert_eq!(Instruction::from_u16(&0x8890), 31 | Some(Instruction::AssignValue(0x8, 0x9))); 32 | assert_eq!(Instruction::from_u16(&0x8891), 33 | Some(Instruction::SetOr(0x8, 0x9))); 34 | assert_eq!(Instruction::from_u16(&0x8892), 35 | Some(Instruction::SetAnd(0x8, 0x9))); 36 | assert_eq!(Instruction::from_u16(&0x8893), 37 | Some(Instruction::SetXor(0x8, 0x9))); 38 | assert_eq!(Instruction::from_u16(&0x8894), 39 | Some(Instruction::Add(0x8, 0x9))); 40 | assert_eq!(Instruction::from_u16(&0x8895), 41 | Some(Instruction::Subtract(0x8, 0x9))); 42 | assert_eq!(Instruction::from_u16(&0x8896), 43 | Some(Instruction::ShiftRight(0x8, 0x9))); 44 | assert_eq!(Instruction::from_u16(&0x8897), 45 | Some(Instruction::Reduce(0x8, 0x9))); 46 | assert_eq!(Instruction::from_u16(&0x889e), 47 | Some(Instruction::ShiftLeft(0x8, 0x9))); 48 | 49 | assert_eq!(Instruction::from_u16(&0x9910), 50 | Some(Instruction::SkipIfNotEqualRegister(0x9, 0x1))); 51 | assert_eq!(Instruction::from_u16(&0xabcd), 52 | Some(Instruction::SetMemoryAddress(0xbcd))); 53 | assert_eq!(Instruction::from_u16(&0xbcde), 54 | Some(Instruction::JumpToV0Address(0xcde))); 55 | assert_eq!(Instruction::from_u16(&0xcdef), 56 | Some(Instruction::BitwiseRandom(0xd, 0xef))); 57 | assert_eq!(Instruction::from_u16(&0xd12f), 58 | Some(Instruction::DrawSprite(0x1, 0x2, 0xf))); 59 | assert_eq!(Instruction::from_u16(&0xef9e), 60 | Some(Instruction::SkipIfPressed(0xf))); 61 | assert_eq!(Instruction::from_u16(&0xeaa1), 62 | Some(Instruction::SkipIfNotPressed(0xa))); 63 | 64 | assert_eq!(Instruction::from_u16(&0xf107), 65 | Some(Instruction::LoadDelay(0x1))); 66 | assert_eq!(Instruction::from_u16(&0xf20a), 67 | Some(Instruction::WaitForPress(0x2))); 68 | assert_eq!(Instruction::from_u16(&0xf315), 69 | Some(Instruction::SetDelay(0x3))); 70 | assert_eq!(Instruction::from_u16(&0xf418), 71 | Some(Instruction::SetSound(0x4))); 72 | assert_eq!(Instruction::from_u16(&0xf51e), 73 | Some(Instruction::AddOffset(0x5))); 74 | assert_eq!(Instruction::from_u16(&0xf629), 75 | Some(Instruction::SetMemoryForFont(0x6))); 76 | assert_eq!(Instruction::from_u16(&0xf733), 77 | Some(Instruction::SetBCD(0x7))); 78 | assert_eq!(Instruction::from_u16(&0xf855), 79 | Some(Instruction::DumpReg(0x8))); 80 | assert_eq!(Instruction::from_u16(&0xf965), 81 | Some(Instruction::LoadReg(0x9))); 82 | } 83 | 84 | #[test] 85 | fn test_clear_and_basics() { 86 | let mut data = [0; 2]; 87 | BigEndian::write_u16(&mut data, 0x00e0); 88 | let mut cpu = CPU::new(&data.to_vec(), None); 89 | assert_eq!(cpu.pc, 0x200); 90 | 91 | cpu.stack.push(0x300); 92 | 93 | let raw = cpu.fetch_opcode().unwrap(); 94 | assert_eq!(raw, 0x00e0); 95 | 96 | let instruction = Instruction::from_u16(&raw); 97 | assert_eq!(instruction, Some(Instruction::ClearDisplay)); 98 | cpu.do_instruction(&instruction.unwrap()).unwrap(); 99 | assert_eq!(cpu.pc, 0x202); 100 | } 101 | 102 | #[test] 103 | fn test_return() { 104 | let mut cpu = CPU::new(&vec![], None); 105 | cpu.stack.push(0x400); 106 | cpu.do_instruction(&Instruction::Return).unwrap(); 107 | assert_eq!(cpu.stack.len(), 0); 108 | assert_eq!(cpu.pc, 0x402); 109 | } 110 | 111 | #[test] 112 | fn test_jump_to_address() { 113 | let mut cpu = CPU::new(&vec![], None); 114 | cpu.do_instruction(&Instruction::JumpToAddress(0x412)) 115 | .unwrap(); 116 | assert_eq!(cpu.pc, 0x412); 117 | } 118 | 119 | #[test] 120 | fn test_call_subroutine() { 121 | let mut cpu = CPU::new(&vec![], None); 122 | cpu.pc = 0x655; 123 | cpu.do_instruction(&Instruction::CallSubroutine(0x595)) 124 | .unwrap(); 125 | assert_eq!(cpu.stack, vec![0x655]); 126 | assert_eq!(cpu.pc, 0x595); 127 | } 128 | 129 | #[test] 130 | fn test_skip_if_equal() { 131 | let mut cpu = CPU::new(&vec![], None); 132 | cpu.pc = 0x655; 133 | cpu.regs[0x5] = 0x23; 134 | cpu.do_instruction(&Instruction::SkipIfEqual(0x5, 0x23)) 135 | .unwrap(); 136 | assert_eq!(cpu.pc, 0x659); 137 | 138 | cpu.regs[0x5] = 0x24; 139 | cpu.do_instruction(&Instruction::SkipIfEqual(0x5, 0x23)) 140 | .unwrap(); 141 | assert_eq!(cpu.pc, 0x65B); 142 | } 143 | 144 | #[test] 145 | fn test_skip_if_not_equal() { 146 | let mut cpu = CPU::new(&vec![], None); 147 | cpu.pc = 0x655; 148 | cpu.regs[0x5] = 0x24; 149 | cpu.do_instruction(&Instruction::SkipIfEqual(0x5, 0x23)) 150 | .unwrap(); 151 | assert_eq!(cpu.pc, 0x657); 152 | 153 | cpu.regs[0x5] = 0x23; 154 | cpu.do_instruction(&Instruction::SkipIfEqual(0x5, 0x23)) 155 | .unwrap(); 156 | assert_eq!(cpu.pc, 0x65B); 157 | } 158 | 159 | #[test] 160 | fn test_skip_if_equal_register() { 161 | let mut cpu = CPU::new(&vec![], None); 162 | cpu.pc = 0x655; 163 | cpu.regs[0x5] = 0x14; 164 | cpu.regs[0x6] = 0x14; 165 | cpu.do_instruction(&Instruction::SkipIfEqualRegister(0x5, 0x6)) 166 | .unwrap(); 167 | assert_eq!(cpu.pc, 0x659); 168 | 169 | cpu.pc = 0x655; 170 | cpu.regs[0x5] = 0x04; 171 | cpu.regs[0x6] = 0x14; 172 | cpu.do_instruction(&Instruction::SkipIfEqualRegister(0x5, 0x6)) 173 | .unwrap(); 174 | assert_eq!(cpu.pc, 0x657); 175 | } 176 | 177 | #[test] 178 | fn test_load_const() { 179 | let mut cpu = CPU::new(&vec![], None); 180 | cpu.regs[0xe] = 0x0; 181 | cpu.do_instruction(&Instruction::LoadConst(0xe, 0x6A)) 182 | .unwrap(); 183 | assert_eq!(cpu.pc, 0x202); 184 | assert_eq!(cpu.regs[0xe], 0x6a); 185 | } 186 | 187 | #[test] 188 | fn test_add_const() { 189 | let mut cpu = CPU::new(&vec![], None); 190 | cpu.regs[0x1] = 0x12; 191 | cpu.do_instruction(&Instruction::AddConst(0x1, 0x13)) 192 | .unwrap(); 193 | assert_eq!(cpu.pc, 0x202); 194 | assert_eq!(cpu.regs[0x1], 0x25); 195 | } 196 | 197 | #[test] 198 | fn test_assign_value() { 199 | let mut cpu = CPU::new(&vec![], None); 200 | cpu.regs[0x2] = 0xff; 201 | cpu.regs[0x3] = 0xaa; 202 | cpu.do_instruction(&Instruction::AssignValue(0x2, 0x3)) 203 | .unwrap(); 204 | assert_eq!(cpu.pc, 0x202); 205 | assert_eq!(cpu.regs[0x2], 0xaa); 206 | 207 | cpu.regs[0x2] = 0xff; 208 | cpu.do_instruction(&Instruction::AssignValue(0x3, 0x2)) 209 | .unwrap(); 210 | assert_eq!(cpu.pc, 0x204); 211 | assert_eq!(cpu.regs[0x3], 0xff); 212 | } 213 | 214 | #[test] 215 | fn test_set_or() { 216 | let mut cpu = CPU::new(&vec![], None); 217 | cpu.regs[0x2] = 0x01; 218 | cpu.regs[0x3] = 0x03; 219 | cpu.do_instruction(&Instruction::SetOr(0x2, 0x3)).unwrap(); 220 | assert_eq!(cpu.pc, 0x202); 221 | assert_eq!(cpu.regs[0x2], 0x03); 222 | assert_eq!(cpu.regs[0x3], 0x03); 223 | } 224 | 225 | #[test] 226 | fn test_set_and() { 227 | let mut cpu = CPU::new(&vec![], None); 228 | cpu.regs[0x2] = 0b11; 229 | cpu.regs[0x3] = 0b10; 230 | cpu.do_instruction(&Instruction::SetAnd(0x2, 0x3)).unwrap(); 231 | assert_eq!(cpu.pc, 0x202); 232 | assert_eq!(cpu.regs[0x2], 0b10); 233 | assert_eq!(cpu.regs[0x3], 0b10); 234 | } 235 | 236 | #[test] 237 | fn test_add() { 238 | let mut cpu = CPU::new(&vec![], None); 239 | cpu.regs[0x2] = 253; 240 | cpu.regs[0x3] = 1; 241 | cpu.do_instruction(&Instruction::Add(0x2, 0x3)).unwrap(); 242 | assert_eq!(cpu.pc, 0x202); 243 | assert_eq!(cpu.regs[0x2], 254); 244 | assert_eq!(cpu.regs[0xf], 0); 245 | 246 | cpu.regs[0x2] = 254; 247 | cpu.regs[0x3] = 3; 248 | cpu.do_instruction(&Instruction::Add(0x2, 0x3)).unwrap(); 249 | assert_eq!(cpu.regs[0x2], 1); 250 | assert_eq!(cpu.regs[0xf], 1); 251 | } 252 | 253 | #[test] 254 | fn test_subtract() { 255 | let mut cpu = CPU::new(&vec![], None); 256 | cpu.regs[0x2] = 2; 257 | cpu.regs[0x3] = 1; 258 | cpu.do_instruction(&Instruction::Subtract(0x2, 0x3)) 259 | .unwrap(); 260 | assert_eq!(cpu.pc, 0x202); 261 | assert_eq!(cpu.regs[0x2], 1); 262 | assert_eq!(cpu.regs[0xf], 1); 263 | 264 | cpu.regs[0x2] = 1; 265 | cpu.regs[0x3] = 2; 266 | cpu.do_instruction(&Instruction::Subtract(0x2, 0x3)) 267 | .unwrap(); 268 | assert_eq!(cpu.regs[0x2], 255); 269 | assert_eq!(cpu.regs[0xf], 0); 270 | } 271 | 272 | #[test] 273 | fn test_shift_right() { 274 | let mut cpu = CPU::new(&vec![], None); 275 | cpu.regs[0x4] = 0b00000000; 276 | cpu.regs[0x5] = 0b11101110; 277 | cpu.do_instruction(&Instruction::ShiftRight(0x4, 0x5)) 278 | .unwrap(); 279 | assert_eq!(cpu.pc, 0x202); 280 | assert_eq!(cpu.regs[0x4], 0b01110111); 281 | assert_eq!(cpu.regs[0x5], 0b11101110); 282 | assert_eq!(cpu.regs[0xf], 0); 283 | 284 | cpu.regs[0x4] = 0b00000000; 285 | cpu.regs[0x5] = 0b01110111; 286 | cpu.do_instruction(&Instruction::ShiftRight(0x4, 0x5)) 287 | .unwrap(); 288 | assert_eq!(cpu.regs[0x4], 0b00111011); 289 | assert_eq!(cpu.regs[0x5], 0b01110111); 290 | assert_eq!(cpu.regs[0xf], 1); 291 | } 292 | 293 | #[test] 294 | fn test_reduce() { 295 | let mut cpu = CPU::new(&vec![], None); 296 | cpu.regs[0x4] = 1; 297 | cpu.regs[0x5] = 243; 298 | cpu.do_instruction(&Instruction::Reduce(0x4, 0x5)).unwrap(); 299 | assert_eq!(cpu.pc, 0x202); 300 | assert_eq!(cpu.regs[0x4], 242); 301 | assert_eq!(cpu.regs[0x5], 243); 302 | assert_eq!(cpu.regs[0xf], 1); 303 | 304 | cpu.regs[0x4] = 3; 305 | cpu.regs[0x5] = 2; 306 | cpu.do_instruction(&Instruction::Reduce(0x4, 0x5)).unwrap(); 307 | assert_eq!(cpu.pc, 0x204); 308 | assert_eq!(cpu.regs[0x4], 255); 309 | assert_eq!(cpu.regs[0x5], 2); 310 | assert_eq!(cpu.regs[0xf], 0); 311 | } 312 | 313 | #[test] 314 | fn test_shift_left() { 315 | let mut cpu = CPU::new(&vec![], None); 316 | cpu.regs[0x4] = 0b00000000; 317 | cpu.regs[0x5] = 0b11101110; 318 | cpu.do_instruction(&Instruction::ShiftLeft(0x4, 0x5)) 319 | .unwrap(); 320 | assert_eq!(cpu.pc, 0x202); 321 | assert_eq!(cpu.regs[0x4], 0b11011100); 322 | assert_eq!(cpu.regs[0x5], 0b11101110); 323 | assert_eq!(cpu.regs[0xf], 1); 324 | 325 | cpu.regs[0x4] = 0b00000000; 326 | cpu.regs[0x5] = 0b01110111; 327 | cpu.do_instruction(&Instruction::ShiftLeft(0x4, 0x5)) 328 | .unwrap(); 329 | assert_eq!(cpu.regs[0x4], 0b11101110); 330 | assert_eq!(cpu.regs[0x5], 0b01110111); 331 | assert_eq!(cpu.regs[0xf], 0); 332 | } 333 | 334 | #[test] 335 | fn test_set_memory_address() { 336 | let mut cpu = CPU::new(&vec![], None); 337 | assert_eq!(cpu.address, 0x0); 338 | assert_eq!(cpu.pc, 0x200); 339 | cpu.do_instruction(&Instruction::SetMemoryAddress(0x2b4)) 340 | .unwrap(); 341 | assert_eq!(cpu.pc, 0x202); 342 | assert_eq!(cpu.address, 0x2b4); 343 | } 344 | 345 | #[test] 346 | fn test_set_bcd() { 347 | let mut cpu = CPU::new(&vec![], None); 348 | cpu.address = 0x0300; 349 | cpu.regs[0x0] = 129; 350 | cpu.do_instruction(&Instruction::SetBCD(0x0)).unwrap(); 351 | let raw = cpu.memory.clone().into_inner(); 352 | assert_eq!(raw[0x300], 1); 353 | assert_eq!(raw[0x301], 2); 354 | assert_eq!(raw[0x302], 9); 355 | assert_eq!(raw[0x303], 0); 356 | 357 | cpu.address = 0x0400; 358 | cpu.regs[0x1] = 19; 359 | cpu.do_instruction(&Instruction::SetBCD(0x1)).unwrap(); 360 | let raw = cpu.memory.clone().into_inner(); 361 | assert_eq!(raw[0x400], 0); 362 | assert_eq!(raw[0x401], 1); 363 | assert_eq!(raw[0x402], 9); 364 | assert_eq!(raw[0x403], 0); 365 | 366 | cpu.address = 0x0500; 367 | cpu.regs[0x2] = 8; 368 | cpu.do_instruction(&Instruction::SetBCD(0x2)).unwrap(); 369 | let raw = cpu.memory.clone().into_inner(); 370 | assert_eq!(raw[0x500], 0); 371 | assert_eq!(raw[0x501], 0); 372 | assert_eq!(raw[0x502], 8); 373 | assert_eq!(raw[0x503], 0); 374 | } 375 | 376 | #[test] 377 | fn test_dump_reg() { 378 | let mut cpu = CPU::new(&vec![], None); 379 | cpu.regs[0x0] = 0x01; 380 | cpu.regs[0x1] = 0x02; 381 | cpu.regs[0x2] = 0x03; 382 | cpu.regs[0x3] = 0x04; 383 | cpu.regs[0x4] = 0x05; 384 | cpu.regs[0x5] = 0x06; 385 | cpu.regs[0x6] = 0x07; 386 | cpu.regs[0x7] = 0x08; 387 | cpu.regs[0x8] = 0x09; 388 | cpu.regs[0x9] = 0x0a; 389 | cpu.regs[0xa] = 0x0b; 390 | cpu.regs[0xb] = 0x0c; 391 | cpu.regs[0xc] = 0x0d; 392 | cpu.regs[0xd] = 0x0e; 393 | cpu.regs[0xe] = 0x0f; 394 | cpu.address = 0x0300; 395 | cpu.memory.set_position(cpu.address as u64); 396 | cpu.do_instruction(&Instruction::DumpReg(0xe)).unwrap(); 397 | assert_eq!(cpu.pc, 0x202); 398 | 399 | let raw = cpu.memory.into_inner(); 400 | assert_eq!(cpu.address, 0x30f); 401 | assert_eq!(raw[0x0300], 0x1); 402 | assert_eq!(raw[0x0301], 0x2); 403 | assert_eq!(raw[0x0302], 0x3); 404 | assert_eq!(raw[0x0303], 0x4); 405 | assert_eq!(raw[0x0304], 0x5); 406 | assert_eq!(raw[0x0305], 0x6); 407 | assert_eq!(raw[0x0306], 0x7); 408 | assert_eq!(raw[0x0307], 0x8); 409 | assert_eq!(raw[0x0308], 0x9); 410 | assert_eq!(raw[0x0309], 0xa); 411 | assert_eq!(raw[0x030a], 0xb); 412 | assert_eq!(raw[0x030b], 0xc); 413 | assert_eq!(raw[0x030c], 0xd); 414 | assert_eq!(raw[0x030d], 0xe); 415 | assert_eq!(raw[0x030e], 0xf); 416 | } 417 | 418 | #[test] 419 | fn test_load_reg() { 420 | let mut cpu = CPU::new(&vec![], None); 421 | 422 | cpu.address = 0x0300; 423 | cpu.memory.set_position(cpu.address as u64); 424 | cpu.memory 425 | .write_all(&[0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 426 | 0x10]) 427 | .unwrap(); 428 | cpu.do_instruction(&Instruction::LoadReg(0xe)).unwrap(); 429 | assert_eq!(cpu.pc, 0x202); 430 | 431 | assert_eq!(cpu.address, 0x30f); 432 | assert_eq!(cpu.regs[0x0], 0x01); 433 | assert_eq!(cpu.regs[0x1], 0x02); 434 | assert_eq!(cpu.regs[0x2], 0x03); 435 | assert_eq!(cpu.regs[0x3], 0x04); 436 | assert_eq!(cpu.regs[0x4], 0x05); 437 | assert_eq!(cpu.regs[0x5], 0x06); 438 | assert_eq!(cpu.regs[0x6], 0x07); 439 | assert_eq!(cpu.regs[0x7], 0x08); 440 | assert_eq!(cpu.regs[0x8], 0x09); 441 | assert_eq!(cpu.regs[0x9], 0x0a); 442 | assert_eq!(cpu.regs[0xa], 0x0b); 443 | assert_eq!(cpu.regs[0xb], 0x0c); 444 | assert_eq!(cpu.regs[0xc], 0x0d); 445 | assert_eq!(cpu.regs[0xd], 0x0e); 446 | assert_eq!(cpu.regs[0xe], 0x0f); 447 | assert_eq!(cpu.regs[0xf], 0x00); 448 | } 449 | 450 | #[test] 451 | fn test_set_memory_for_font() { 452 | let mut cpu = CPU::new(&vec![], None); 453 | cpu.regs[0x0] = 0; 454 | cpu.do_instruction(&Instruction::SetMemoryForFont(0x0)) 455 | .unwrap(); 456 | assert_eq!(cpu.address, 0x0); 457 | 458 | cpu.regs[0x0] = 9; 459 | cpu.do_instruction(&Instruction::SetMemoryForFont(0x0)) 460 | .unwrap(); 461 | assert_eq!(cpu.address, 45); 462 | 463 | cpu.regs[0x0] = 0xf; 464 | cpu.do_instruction(&Instruction::SetMemoryForFont(0x0)) 465 | .unwrap(); 466 | assert_eq!(cpu.address, 75); 467 | } 468 | 469 | #[test] 470 | fn test_it() { 471 | let mut data = vec![0; 4]; 472 | data.write_u16::(0xf029).unwrap(); 473 | data.write_u16::(0xd00f).unwrap(); 474 | } 475 | --------------------------------------------------------------------------------