Git Product home page Git Product logo

chip8-ce's Introduction

chip8-ce

A fast CHIP-8 emulator for the TI-84+ CE, written in Ez80 Assembly and C.

Building

Uses the CE-Programming toolchain.

$ make

Motivation

I've always been interested in really low level programming, but never quite got into it simply because it was too weird and difficult. I decided that if I wanted to dip my toe in the water, so to speak, I should write something relatively simple to start. I wrote this to familiarize myself with assembly in general, but quickly discovered that ez80 assembly (or z80 assembly, for that matter) was even more difficult than I anticipated. The consequence of the z80 design team using random-logic in their processor was a more feature rich processor but also a weirdly unorthogonal instruction set, at least by modern standards.

For instance, it's my impression that the three "general-purpose" registers, BC, DE, and HL are less "general purpose" than one would think. HL is really only used as a pointer, for example. BC and DE are used for looping, but BC gains more use because of the djnz instruction. However by far the greatest hurdle for me was the lack of some kind of base+offset addressing mode. Any pointer arithmetic basically has to go through a bunch of loads, adds, and stores which is only made more frustrating by the add instruction's limited addressing modes. That's why C is quite slow on the z80 as it requires a lot of pointer arithmetic for arrays and certain stack loads or stores. Amusingly, on the actual CHIP-8 pretty much every register is general purpose (and there are more of them) except for VF and sometimes V0. Although the ez80 apparently benefits from a full 24-bit ALU and a pretty good pipeline, it's still limited by the instruction set but not nearly as badly as the earlier Ti-8x series.

Another aspect of writing this was that I wanted to write an emulator (or, in this case, a bytecode interpreter). This isn't anything fancy like a JIT or whatnot, it's a simple fetch, decode, execute loop type operation not unlike the same operations that were often found in older CPUS.

This entire experience has been really humbling. I had pretty good faith in my ability to code and learn new languages, so I decided that a challenge would be fun. The truth is that debugging this has mostly been a huge nightmare, but learning and initially writing assembly was weirdly soothing, and extremely rewarding.

Implementation Status

CHIP-8 Instructions

Opcode Mnemonic Description C Pseudocode Status
0NNN SYS $NNN Run RCA1802 program at address NNN. Unused in most modern implementations. N/A N/A
00E0 CLS Clear screen (graphics memory) gfx_clear(); ✔️
00EE RET Pop an address from the stack and set PC to this address. return; ✔️
1NNN JP $NNN Set PC to NNN. PC = 0xNNN; ✔️
2NNN CALL $NNN Push PC to the stack and jump to address NNN. *(0xNNN)(); ✔️
3XNN SE VX, $NN Skip the next instruction if VX equals NN. if (V[X] == 0xNN) { PC += 2; } ✔️
4XNN SNE VX, $NN Skip the next instruction if VX does not equal NN. if (V[X] != 0xNN) { PC += 2; } ✔️
5XY0 SE VX, VY Skip the next instruction if VX equals VY. if (V[X] == V[Y]) { PC += 2; } ✔️
6XNN LD VX, $NN Set VX to NN. V[X] = 0xNN; ✔️
7XNN ADD VX, $NN Set VX to VX + NN V[X] = V[X] + 0xNN; ✔️
8XY0 LD VX, VY Set VX to VY. V[X] = V[Y]; ✔️
8XY1 OR VX, VY Set VX to VX OR VY. V[X] = V[X] | V[Y]; ✔️
8XY2 AND VX, VY Set VX to VX AND VY. V[X] = V[X] & V[Y]; ✔️
8XY3 XOR VX, VY Set VX to VX XOR VY. V[X] = V[X] ^ V[Y]; ✔️
8XY4 ADD VX, VY Set VX to VX + VY. Set VF to carry. V[X] = V[X] + V[Y]; V[F] = carry; ✔️
8XY5 SUB VX, VY Set VX to VX - VY. Set VF to not borrow. V[X] = V[X] - V[Y]; V[F] = !borrow; ✔️
8X06 SHR VX Set VF to VX's least significant bit. Set VX to VX shifted right 1. V[X] = V[X] >> 1; V[F] = lsb; ✔️
8XY7 SUBN VX, VY Set VX to VY - VX. Set VF to not borrow. V[X] = V[Y] - V[X]; V[F] = !borrow; ✔️
8X0E SHL VX Set VF to VX's most significant bit. Set VX to VX shifted left 1. V[X] = V[X] << 1; V[F] = msb; ✔️
9XY0 SNE VX, VY Skip the next instruction if VX does not equal VY. if (V[X] != V[Y]) { PC += 2; } ✔️
ANNN LD I, $NNN Set I to NNN. I = 0xNNN; ✔️
BNNN JP V0, $NNN Set PC to NNN + V0. PC = 0xNNN + V[0]; ✔️
CXNN RND VX, $NN Set VX to a random value ANDed with NN. V[X] = rand() & 0xNN; ✔️
DXYN DRW VX, VY, $N Draw a bitmapped sprite at (VX, VY) with height N starting from M[I]. gfx_sprite(V[X], V[Y], 0xN, memory[I]); ✔️
EX9E SKP VX Skip the next instruction if key VX is pressed. if (keypad[V[X] & 0xF]) { PC += 2; } ✔️
EXA1 SKNP VX Skip the next instruction if key VX is not pressed. if (!keypad[V[X] & 0xF]) { PC += 2; } ✔️
FX07 LD VX, DT Set VX to the delay timer. V[X] = timer_delay; ✔️
FX0A LD VX, K Wait until a key is pressed, and then set VX to the key. while (!key_pressed) {} V[X] = key; ✔️
FX15 LD DT, VX Set the delay timer to VX. timer_delay = V[X]; ✔️
FX18 LD ST, VX Set the sound timer to VX. timer_sound = V[X]; ✔️
FX1E ADD I, VX Set I to I + VX. I = I + V[X]; ✔️
FX29 FNT VX Set I to the location of character VX's font sprite. I = font[V[X]]; ✔️
FX33 BCD VX Set M[I, I+1, I+2] to the decimal digits of the BCD character in VX. bcd_store(&(memory[I]), V[X]); ✔️
FX55 SR (I), VX Store registers up to VX at M[I..I+X]. for (int k=0;k<X;k++) {memory[I+k] = V[k];} ✔️
FX65 LR VX, (I) Load registers from M[I..I+X] into registers up to VX. for (int k=0;k<X;k++) {V[k] = memory[I+k];} ✔️

SUPERCHIP Instructions

Opcode Mnemonic Description C Pseudocode Status
00CN SCD $N Scroll the screen down N lines. gfx_scroll_down(0xN);
00FB SCR Scroll the screen right four pixels. gfx_scroll_right(4);
00FC SCL Scroll the screen left four pixels. gfx_scroll_left(4);
00FD EXIT Exit the program. exit(0);
00FE LOW Set the graphics mode to regular CHIP-8 mode (64x32 pixels) gfx_mode = 0;
00FF HIGH Set the graphics mode to SUPERCHIP mode (128*64 pixels) gfx_mode = 1;
DXY0 XDRW VX, VY If in SUPERCHIP graphics, draw a 1616 sprite at (VX*, VY) from M[I]. if (gfx_mode == 1) {gfx_xsprite(V[X], V[Y], memory[I]);}
FX30 XFNT VX Set I to the location of character VX's large font sprite. I = xfont[V[X]];
FX75 SRPL VX Store V0..VX in some kind of non-volatile storage. for (int i=0;i<X;i++) {write(V[i]);}
FX85 LRPL VX Load V0..VX from some kind of non-volatile storage. for (int i=0;i<X;i++) {V[i] = read();}

Special Thanks

While writing this, I used Cowgod's excellent documentation of the CHIP-8. This was a huge help. Without it the emulator wouldn't be nearly as accurate.

chip8-ce's People

Contributors

citruscs avatar mnurzia avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.