Official Chip8 Roms working in my emulator:
Words cannot accurately describe how awesome it feels to load a ROM and it works…
Hi guys. Decided to write another blog before I potentially disappear from the face of the world for three months. Which now depends on whether the people who needed me to look after that house for them go on their vacation or not.Anyway, I've been doing having trouble working with game development recently. No such problem though when it comes to making useless things that aren't really needed… On that note, I've been studying various CPU architectures, emulation techniques, and VMs, mostly because they're interesting. And thus, at some unknown hour on I-forget-which-day-sometime-last-week, I decided to write an emulator… or a VM, actually.I chose the CHIP8 system, one of the earlier VMs designed for game development on a couple of old computers (The VIP something or other and one of the RC1805 based systems). Later, this became the S-CHIP system that was eventually used in several graphing calculators.Anyway, the CHIP8 has a few simple specs. It is limited to 4K of memory, has a very low resolution of 64x32 pixels, monotone (On and off, basically). It only has 35 opcodes. Interestingly for an 8-bit system, it has 16 general purpose registers. This means that with a bit of clever programming, you can combine registers to work with larger numbers. At the same time, it has built in sprite handling (Bit sprites. Each byte is a line of the sprite, since these are essentially 1 bit images).Let me not ramble too much about it. I didn't actually get around to writing the emulator. Though I did write something… an assembler for the system; because obviously if I'm going to bother writing an emulator, I want to be able to write my own code for it too :PI took some liberty when designing the language. There are several basic directives that don't make it to the binary. One of these is EQU, which I have chosen to treat similarly to C's #define pre-processor directive, via substitution during the assembly.I added a couple of 'fake' keywords that improve code readability too, such as an END keyword that helps when working with loops. Anyway, here's a bit of sample code:
The MAIN directive causes the assembler to add the offset of the instruction following MAIN to be stored in the symbol table, and a JMP <address of main> opcode is added to the beginning of the code (With all offsets shifted by one, naturally).This allows for easier program design, in my opinion, allowing me to have the subroutines wherever I want them. Interesting little factoid about the hex numbers. I had to write a simple parser that could convert them from strings to numbers. This is done in two parts. First is a function that's basically a giant switch statement. In hindsight, this should be a lookup table. Though I'm not optimizing for speed here.
It basically takes a char, returns the value required. Simple enough.The second part of the code is basically a string parser. I work backwards from the end of the string to make things easier.
This works on both common hex representations (0x000 and 000h). Knowing the language, there was probably a stupidly simple standard library or Boost function that does this conversion that I forgot about, but knowing me, I enjoyed making it myself. Anyway… I have some amusing plans for this. I want to make a Java VM for the S-CHIP soon, and work on adding it to Minecraft as a 'wrist' computer, fully programmable in-game. Just the kind of project I like.Well I forgot what the rest of this blog was going to be about… so I'll close it off for now.
; Test code for both ASM8 and EMUL8
; Does something really simple (Adds numbers, loops)
EQU random_var 0x200
SUBROUTINE add_loop
ADD V1 V4 ; Adds contents of V4 to V1
ENDSUB ; Fake keyword
; Main program begins here.
MAIN
SET V1 0x00
SET V4 0x01
LABEL lp1
CALLSUB add_loop
JMP lp1
END
int get_num_from_hex(char c)
{
switch(c)
{
case '0':
return 0;
case '1':
return 1;
case '2':
return 2;
case '3':
return 3;
case '4':
return 4;
case '5':
return 5;
case '6':
return 6;
case '7':
return 7;
case '8':
return 8;
case '9':
return 9;
case 'A':
return 10;
case 'B':
return 11;
case 'C':
return 12;
case 'D':
return 13;
case 'E':
return 14;
case 'F':
return 15;
case 'a':
return 10;
case 'b':
return 11;
case 'c':
return 12;
case 'd':
return 13;
case 'e':
return 14;
case 'f':
return 15;
default:
return 0;
}
}
word str_to_hex(string in)
{
// Parses an input hex string into it's decimal equivalent.
word t = 0;
int base = 1;
// First check that this -is- hex. Return it's value immediately otherwise.
bool ishex = false;
for(unsigned int i = 0; i < in.length(); i++)
{
if(in[[i] == 'x' || in[[i] == 'X' || in[[i] == 'h' || in[[i] == 'H')ishex = true;
}
if(!ishex)
{
t = atoi(in.c_str());
return t;
}
for(unsigned int i = in.length()-1; i >= 0; i--)
{
if(in[i] == 'x' || in[i] == 'X')break;
if(in[i] == 'h' || in[i] == 'H')continue; // For the 000h style numbers.
int v = get_num_from_hex(in[[i]);
t += (v)*(base);
base <<= 4;
}
return t;
}
This is pretty cool, has the assembler been made already or are you still busy?
The assembler is complete, and I finished the emulator itself last night, and have been running tests on it. My next test is going to be running some of the official ROMS for the system, and seeing if they work.
Screenshot:I read your blog, but I don't know shit about emulation, so I had nothing to say.
But now that you're a successful creator of emulators, could you make me a PS2 emulator that can run the Shadow of the Colossus .iso at a decent framerate?Thanks.Nice progress! But emulation is too slow these days, you should work on making it a dynamic recompiler so I can play my chip8 collection at slightly closer-to-native speeds.
/me doesn't actually have a chip8 collection, /sHeh. Well, since the Chip8 runs at 60hz, I'll assume you're running a Commodore 64. :P