~spidernet/chip8

Chip-8 emulator written in C99 using SDL2

16a8762 Add Contributing and License sections in README.md

a month ago
~spidernet/chip8-dev

New mailing list added

2 months ago

CHIP-8 logo

#c8emul

CHIP-8 emulator implemented in C99 using the SDL2 multimedia library. It implements all the opcodes of a standard CHIP-8 but without sound.

#Features

  • Quality
    • Compiled with security hardening flags.
    • Static analysis integrated using clang's scan-build using checkers alpha.security, alpha.core.CastSize, alpha.core.CastToStruct, alpha.core.IdenticalExpr, alpha.core.PointerArithm, alpha.core.PointerSub, alpha.core.SizeofPtr, alpha.core.TestAfterDivZero, alpha.unix.
    • Follows FreeBSD coding style.
    • Test harness: opcodes are unit tested.
  • Portable
    • C99 compliant.
    • Uses SDL2 as the only dependency.
    • Easy to compile and uses POSIX make.

#Limitations

  • Audio is not supported

#Build dependencies

Other than SDL2, the only other dependency is a toolchain supporting the following flags:

CFLAGS = -std=c99 -O2 -Wall -Wextra -Wpedantic \
	-Walloca -Wcast-qual -Wconversion -Wformat=2 -Wformat-security \
	-Wnull-dereference -Wstack-protector -Wvla -Warray-bounds \
	-Wbad-function-cast -Wconversion -Wshadow -Wstrict-overflow=4 -Wundef \
	-Wstrict-prototypes -Wswitch-default -Wfloat-equal \
	-Wpointer-arith -Wswitch-enum \
	-D_FORTIFY_SOURCE=2 \
	`sdl2-config --cflags` \
	-fstack-protector-strong -fPIE -fstack-clash-protection

LDFLAGS = -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wl,-z,separate-code

Otherwise you can just remove the security flags and compile it with

CFLAGS = -std=c99 -O2 -Wall -Wextra -Wpedantic `sdl2-config --cflags`
LDFLAGS =

or pass your own flags to make

make CC=gcc CFLAGS=... LDFLAGS=...

#Installation

Clone this repository then

$ make PREFIX=/usr install

This will install the compiled binary under PREFIX (/usr/bin) in this case, if not specified PREFIX will default to /usr/local. For staged installs, DESTDIR is also supported.

#Usage

c8emul receives as input a ROM file and executes it displaying the output in a window of default size 64x32 and with a delay between each cycle of 3ms. The ROM file however, can be omitted, in this case the program takes the input from the standard input until EOF or ^D is reached.

The options are as follows:

  • -s Specify the scaling_factor, i.e. a multiplier that controls how to scale the default 64x32 window size.
  • -d Specify the delay in milliseconds between cycles.

#Examples

Load ROM from stdin and display it in 640x320 with the default 3ms cycle delay:

$ c8emul -s 10

Load ROM from file and scale the window to 640x320 with a 2ms cycle delay:

$ c8emul -s 10 -d 2 Chip8_Picture.ch8

#Test suite

The test suite consists of a series of unit tests, one for each opcode (excluding Cxkk and Dxyn) and for the intialization of the emulator. The suite is contained in the test folder. It's output is similar to googletest's and it can be invoked with make check which, if everything is working should output something similar to

./tests
[----------] Test environment set-up.
[==========] Running test cases.
[ RUN      ] PC set to 0x200
[       OK ] PC set to 0x200
[ RUN      ] Font is loaded
[       OK ] Font is loaded
[ RUN      ] 00E0: display cleared
[       OK ] 00E0: display cleared
[ RUN      ] 00EE: RET from subroutine
[       OK ] 00EE: RET from subroutine
[ RUN      ] 1123: JMP to location 0x123
[       OK ] 1123: JMP to location 0x123
[ RUN      ] 2456: call subroutine at addr 0x456
[       OK ] 2456: call subroutine at addr 0x456
[ RUN      ] 3879: skip next instruction, V8 = 0x79
[       OK ] 3879: skip next instruction, V8 = 0x79
[ RUN      ] 4123: skip next instruction, V1 != 0x23
[       OK ] 4123: skip next instruction, V1 != 0x23
[ RUN      ] 5210: skip next instruction, V2 = V1
[       OK ] 5210: skip next instruction, V2 = V1
[ RUN      ] 6456: set V4 = 0x56
[       OK ] 6456: set V4 = 0x56
[ RUN      ] 7789: set V7 = 0x89
[       OK ] 7789: set V7 = 0x89
[ RUN      ] 8980: set V9 = V8
[       OK ] 8980: set V9 = V8
[ RUN      ] 8981: set V9 = V9 OR V8
[       OK ] 8981: set V9 = V9 OR V8
[ RUN      ] 8982: set V9 = V9 AND V8
[       OK ] 8982: set V9 = V9 AND V8
[ RUN      ] 8983: set V9 = V9 XOR V8
[       OK ] 8983: set V9 = V9 XOR V8
[ RUN      ] 8984: set V9 = V9 + V8, VF = carry
[       OK ] 8984: set V9 = V9 + V8, VF = carry
[ RUN      ] 8985: set V9 = V9 - V8, VF = not borrow
[       OK ] 8985: set V9 = V9 - V8, VF = not borrow
[ RUN      ] 8986: set V9 = V9 SHR 1
[       OK ] 8986: set V9 = V9 SHR 1
[ RUN      ] 8987: set V9 = V8 - V9, VF = not borrow
[       OK ] 8987: set V9 = V8 - V9, VF = not borrow
[ RUN      ] 898E: set V9 = V9 SHL 1
[       OK ] 898E: set V9 = V9 SHL 1
[ RUN      ] 9120: skip next instruction, V1 != V2
[       OK ] 9120: skip next instruction, V1 != V2
[ RUN      ] A123: set I to 0x123
[       OK ] A123: set I to 0x123
[ RUN      ] B345: jump to location 0x345 + 0x12 (V0)
[       OK ] B345: jump to location 0x345 + 0x12 (V0)
[ RUN      ] E69E: skip next instruction, key with value of V6 is pressed
[       OK ] E69E: skip next instruction, key with value of V6 is pressed
[ RUN      ] E1A1: skip next instruction, key with value of V1 is not pressed
[       OK ] E1A1: skip next instruction, key with value of V1 is not pressed
[ RUN      ] F507: set V5 to delay timer value
[       OK ] F507: set V5 to delay timer value
[ RUN      ] F20A: key 'x' is pressed, V2 = 0
[       OK ] F20A: key 'x' is pressed, V2 = 0
[ RUN      ] F515: set delay timer = V5
[       OK ] F515: set delay timer = V5
[ RUN      ] F718: set sound timer = V7
[       OK ] F718: set sound timer = V7
[ RUN      ] F01E: set I = I + V0
[       OK ] F01E: set I = I + V0
[ RUN      ] F929: set I to location of sprite of '2'
[       OK ] F929: set I to location of sprite of '2'
[ RUN      ] F333: store BCD representation of V3 in memory locations I, I+1, I+2
[       OK ] F333: store BCD representation of V3 in memory locations I, I+1, I+2
[ RUN      ] F255: store registers V0-V2 in memory starting from location I
[       OK ] F255: store registers V0-V2 in memory starting from location I
[ RUN      ] F165: read registers V0-V1 in memory starting from location I
[       OK ] F165: read registers V0-V1 in memory starting from location I
[==========] 34 test cases ran.
[  PASSED  ] 34 tests.
[  FAILED  ] 0 tests.
[----------] Test environment teardown.

#Static analysis

Static analysis on the code base is done by using clang's static analyzer run through scan-build.sh which wraps the scan-build utility. The checkers used are part of the Experimental Checkers (aka alpha checkers):

  • alpha.security
  • alpha.core.CastSize
  • alpha.core.CastToStruct
  • alpha.core.IdenticalExpr
  • alpha.core.PointerArithm
  • alpha.core.PointerSub
  • alpha.core.SizeofPtr
  • alpha.core.TestAfterDivZero
  • alpha.unix

#Contributing

Send patches on the mailing list, report bugs on the issue tracker.

#License

BSD 2-Clause FreeBSD License, see LICENSE.