Given the information presented you should be able to get a long way. You're going to have to decide which way you want to go with the emulator - whether you want to do a full 8080 emulation, or just implement the instructions needed to play the game.
If you do a full emulation, you're going to need a few new tools in your toolbox. I'll cover that in the next section.
The other way to go is to emulate only the instructions used by the game. We'll continue to fill out the giant switch statement we made in the Emulator Shell section. You repeat a process like this until you don't get any unimplemented instructions:
Run your emulator on the Space Invaders ROM
The call to UnimplementedInstruction()
will exit when an instruction isn't done
Emulate that instruction
Goto step 1
The first thing I did when I started to write my emulator was to add in the code from my disassembler. That way I could print out the instruction that was about to be executed:
int Emulate8080Op(State8080* state) { unsigned char *opcode = &state->memory[state->pc]; Disassemble8080Op(state->memory, state->pc); switch (*opcode) { case 0x00: //NOP /* ... */ } /* print out processor state */ printf("\tC=%d,P=%d,S=%d,Z=%d\n", state->cc.cy, state->cc.p, state->cc.s, state->cc.z); printf("\tA $%02x B $%02x C $%02x D $%02x E $%02x H $%02x L $%02x SP %04x\n", state->a, state->b, state->c, state->d, state->e, state->h, state->l, state->sp); }
I also added code at the end to print out all the registers and condition flags.
The good news is that you only have to do a subset of the 8080's opcodes to get about 50,000 instructions into the program. I'll even give you the list of opcodes you'll need to implement:
Opcode | Instruction |
---|---|
0x00 | NOP |
0x01 | LXI B,D16 |
0x05 | DCR B |
0x06 | MVI B,D8 |
0x09 | DAD B |
0x0d | DCR C |
0x0e | MVI C,D8 |
0x0f | RRC |
0x11 | LXI D,D16 |
0x13 | INX D |
0x19 | DAD D |
0x1a | LDAX D |
0x21 | LXI H,D16 |
0x23 | INX H |
0x26 | MVI H,D8 |
0x29 | DAD H |
0x31 | LXI SP,D16 |
0x32 | STA adr |
0x36 | MVI M,D8 |
0x3a | LDA adr |
0x3e | MVI A,D8 |
0x56 | MOV D,M |
0x5e | MOV E,M |
0x66 | MOV H,M |
0x6f | MOV L,A |
0x77 | MOV M,A |
0x7a | MOV A,D |
0x7b | MOV A,E |
0x7c | MOV A,H |
0x7e | MOV A,M |
0xa7 | ANA A |
0xaf | XRA A |
0xc1 | POP B |
0xc2 | JNZ adr |
0xc3 | JMP adr |
0xc5 | PUSH B |
0xc6 | ADI D8 |
0xc9 | RET |
0xcd | CALL adr |
0xd1 | POP D |
0xd3 | OUT D8 |
0xd5 | PUSH D |
0xe1 | POP H |
0xe5 | PUSH H |
0xe6 | ANI D8 |
0xeb | XCHG |
0xf1 | POP PSW |
0xf5 | PUSH PSW |
0xfb | EI |
0xfe | CPI D8 |
That's only 50 instructions, and 10 of those are moves which are trivial to implement.
I have some bad news. Your emulator is almost certainly not going to work right, and bugs in code like this are really hard to find. If you know which instruction is going bad (like a jump or call that goes off to nonsense code), you can try to fix it by inspecting your code.
Besides just staring at your code, the only other way to fix a problem is to compare your emulator against one that is known to work. You assume the other one is always right, and that any difference is a bug in your emulator. One approach would be to use my emulator. You could run them side by side manually. It would probably save time overall if you integrated my code into your project for a process like this:
Create state for yours
Create state for mine
For next instruction
Call yours with your state
Call mine with my state
Compare our two states
error on any difference
goto 3
Another way is to use this site manually. It is a Javascript 8080 emulator that even has the Space Invaders ROM built in. Here is a process for that:
Restart the Space Invaders emulation by pushing the "Space Invaders" button
Click the "Run 1" button to execute an instruction.
Execute the next instruction on your emulator
Compare the processor state to yours
If the state matches, goto step 2
If the state doesn't match, your emulation of this instruction is bad. Fix it, then restart from step 1.
I used this method in the beginning to debug my 8080 emulator. I'm not going to lie, this can be slow going. A lot of my problems turned out to be typos and copy-paste errors that were easy to fix once they were identified.
As you step through your code, a lot of the first 30000 instructions are spent in a loop around $1a5f. If you watch on the javascript emulator, you can see this code is copying data to the screen. I bet this code gets called a lot.
After the screen is drawn once, About 50,000 instructions in, the program gets stuck in this infinite loop:
0ada LDA $20c0 0add ANA A 0ade JNZ $0ada
It is waiting for memory location $20c0 to get changed to zero. Since the code in this loop certainly isn't going to change $20c0, it must be a signal from somewhere else. It's time to talk about emulating the arcade machine hardware.
Before you move on to the next section, make sure your CPU emulator gets to this infinite loop.
View my source for reference
← Prev: more-about-binary-numbers Next: full-8080-emulation →Post questions or comments on Twitter @realemulator101, or if you find issues in the code, file them on the github repository.