CHIP-8 Emulator (bonus)

Context

Created back in the 70s, CHIP-8 is a Virtual Instruction Set that was meant to allow developing games on COSMAC VIP and Telmac microcomputers without worrying about the underlying architecture. Back then, computers used to have only 8KB of RAM, split equally between the kernel and the user application. B, predecessor to the C language you are so fond of, was developed on such systems. The reason why the language is called B is because Ken Thompson and Dennis Ritchie only had one byte left for the name in the compiler's source code, so they shortened it to just one letter. When Ritchie added data structures to the language it was considered such a paradigm shift that it warranted giving it a new name (B++ –> C). Needless to say, systems were pretty resource constrained.

But back to our assignment. The CHIP-8 architecture was comprised of two elements: a 512 byte interpreter and a 3.5KB game. This may not seem like a lot, but it was enough to implement Pong and Space Invaders. Your task is to write a CHIP-8 interpreter in C/C++ (i.e. not Python).

Code Skeleton

This archive contains the skeleton you will be using. It already implements an SDL2-based module that will handle rendering the 64×32 screen for you. Compile and run the application to see how you're supposed to interact with it.

Install libsdl2-dev and any other libraries before compiling.

The structure of the skeleton source code is as follows:

  • include/: Here you can find the headers.
    • util.h Contains some useful macros (e.g.: DIE, RET, etc.) Use them if you wish.
  • src/: Here you will add any new source files that you will write. Try keeping things modular: different files for the instruction decoders, sound system, etc.
    • cli_args.c: Here you will find an argp-based CLI argument parser. Add new arguments to this file. The structure that is exported to the main() function is defined in cli_args.h.
    • display.c: This is the implementation of the GUI. You don't have to make any changes to it. Just use the functions in your instruction decodes (e.g.: clear_screen() for the 00E0 instruction).
    • main.c: A sample program that renders a random hex digit to the screen; replace this with your implementation.
  • makefile: It has the usual build and clean targets. Unless you want to write the interpreter in C++, you probably won't have to change anything. If you need other external libraries, add the required flags to the LDFLAGS variable (e.g.: -lm for basic math library). Any new sources that you add to src/ will be automatically detected and compiled into the final binary.

Resources

The following links should contain all the information needed to implement the emulator

System Specifications

This section contains a short description of the components of a CHIP-8 system. This documentation is not exhaustive. Also consult the resources linked above.

CPU

The processing power of modern CPUs exceeds that of the CHIP-8 system by orders of magnitude. Consequently, you will have to limit the interpreter's CPU frequency to around 200-500Hz. Add a CLI argument for the CPU frequency in cli_args.c.

Memory

As mentioned before, the first 512 (0x00 - 0xFF) bytes of RAM are reserved for the interpreter, so no program will try to access it. Don't worry, your interpreter doesn't need to fit in this space. Note that people tend to store the built-in font sprites at the 0x50 offset. These font sprites are the binary representation of hex digits ranging from 0 to F (see the global array in main.c).

Registers

The CHIP-8 system has a number of registers:

  • VX: 16 8-bit general purpose registers. the X is VX represents the register index (e.g.: V0, V1, etc.) Among these, VF doubles as the “flag register”. Certain bits are set or cleared depending on the result of related instructions (e.g.: VF = 1 if the result of an addition overflows the 8-bit destination register).
  • PC: This is the Program Counter. It contains the address (16-bit) of the next instruction to be executed. Since each instruction takes exactly 2 bytes, you will increment this by 2 when an instruction is retired.
  • SP: Yes, CHIP-8 also supports function calls. However, the stack only contains the return address of each call. Since the Stack Pointer is an 8-bit register, it follows that you can have a maximum recursion depth of 16, so 16x 2-byte addresses. Note that you don't have to implement the Stack inside the 4KB of system RAM. Just use a uint16_t stack[16] and start off with SP = 0.

In addition to all this, you also have two Timer Registers. The Timer Registers can be set by the program and are automatically decremented at 60Hz (i.e.: decremented by 1, 60 times per second). Note that their value should never underflow, so when it hits 0, stop the decrementing process.

  • ST: The Sound Timer will make the system generate a beep while it's value is above 0. Implementing sound is a bonus task.
  • DT: The Delay Timer is not optional. Many programs have their internal logic tied to this register. Note that your CPU does not stop while the Delay Timer's value is above 0. Decrementing DT should be asynchronous.

Keyboard

The computers that originally used CHIP-8 had a 4×4 keypad, as shown in the figure below. You can map these keys to your keyboard anyway you want. Note that SDL2 also implements support for keyboard input detection. Check out SDL_GetKeyboardState(), or the event driven API.

Display

The original CHIP-8 used a 64×32 pixel monochrome display, (0, 0) being the top-left corner and (63, 31) being the bottom-right corner. The implementation of the display is already provided in display.c. Feel free to change the color scheme by modifying the DARK_{R,G,B} and LIGHT_{R,G,B} macro definitions.

Grading

The base assignment constitutes 2.5p out of your final grade (100p homework = 2.5p final grade). The 100p are split between the following tasks:

  • [5p] CLI arguments: Add CPU frequency as an CLI argument with argp.
  • [5p] Registers & RAM: Correctly define / instantiate the system registers & guest RAM memory. Load the ROM contents at the correct offset in the guest RAM.
  • [10p] IBM logo: Implementing the required instructions, you are able to run the IBM logo (see the Test ROMs section below).
  • [15p] CPU frequency: The emulated CPU runs exactly at the specified frequency.
  • [15p] Keyboard input: Able to process keyboard input, detecting key down / up events.
  • [15p] Delay Timer: Behaves appropriately when set / queried by relevant instructions.
  • [30p] Remaining instructions: Ignoring the 0NNN instruction and the 6 that are required for the IBM logo program, these points are split across the remaining 28 instructions which you implement.
  • [5p] Space Invaders: Add an option to use SUPER-CHIP8 flavor of 8XY6 and 8XYE instructions (game depends on this).

There are also two bonus tasks that amount to 20% of assignment base score (i.e.: 20p bonus = 0.5p final grade).

  • [5p] man page: Write your README as a man page (instructions here).
  • [15p] sound support: Implement the Sound Timer. The tone signal must be generated using portaudio. Make sure to add an option for selecting the audio device; the default device is not always the correct one (e.g.: it could be an HDMI sink in stead of pulseaudio or pipewire).

Write a README containing the description of your implementation, design choices, challenges you encountered, etc. Feel free to add your feedback here as well. All submissions that do not include a README will be ignored.


NOTE: Assistants are free do deduct points for bad / illegible code.

Test ROMs

When evaluating your implementation, we are going to run a few games and test ROMs. The test ROMs are CHIP-8 binaries that target certain instructions and tell you if your decoders behave as expected. Needless to say, you first need to correctly implement a subset of the CHIP-8 instructions that are needed to execute the tests.

  • IBM logo: This is the first program that you should attempt to get running. It depends on only 6 instructions:
    • 00E0: clear screen
    • 1NNN: jump to address NNN
    • 6XNN: set the value of the Vx register to NN
    • 7XNN: add NN to register Vx
    • ANNN: set the value of the I register to NNN
    • DXYN: display a N-byte sprite at location Vx, Vy
  • chip8-test-rom 1: Tests the 18 instructions described in the README.
  • chip8-test-rom 2: More tests.
  • chip8-test-suite: A suite of 5 different tests. Needs keyboard input to be implemented first.
  • CHIP-8 games pack: A collection of games for your personal enjoyment.

FAQ

Q: Can I write the emulator in something other than C/C++?
A: No.

Q: What platform will this assignment be tested on?
A: Linux only.

Q: How do I read the instruction codes in the specification?
A: In the spec, the instructions are represented as 4 characters, each signifying a nibble (4 bits). Hex digits (0-9A-F) are invariants and you should try to match them exactly. E.g. 00EE (RET) is composed of 4 constant nibbles. Whenever you see X or Y, that part of the opcode is variable and it represents what general purpose register (V) is involved in that operation. E.g. FX65 (LD Vx, [I]) will load in register Vx the value located at the memory address stored in register I; basically, it's dereferencing a pointer. Finally, whenever you see a sequence of K or N characters (notation can differ), know that these are immediate values. These are basically arguments for the instructions that are embedded in the opcode itself. E.g. 6XKK (LD Vx, byte) will load the value KK into Vx. So for 614F, we will have V1 = 0x4F.

Q: There are so many CHIP-8 emulators out there. What if I'm tempted to “take inspiration” from some of them?
A: Once you come up with a solution of your own and implement it, it's ok to look at other approaches. After all, to write good code you first need to read good code. However, taking inspiration and “taking inspiration” are two different things. In addition to a manual review, we'll also perform an AST comparison of your submitted code, against each other and as many public CHIP-8 projects as we can find. Just keep that in mind :p

TODO: Collect questions from Teams / lab and add them here.

ii/assignments/s2/chip8.txt · Last modified: 2023/04/26 11:37 by florin.stancu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0