Tuesday, 23 March 2021

Lunchtime Coder #4 - Space Invaders Emulator


Have you ever wanted to take a trip down nostalgia lane and relive the “good old days” of late 70s/early 80s arcade gaming?

No?

Me neither... but apparently there are some odd types around who miss the retroness of old skool arcade gaming in smoke filled germ ridden festering pits of arcadiness, and this led to the miracle of EMULATION !

But... Playing on emulators is one thing... Writing my own one? Now thats a tough old well done rump!

(BTW I kid... I bloody love emulators!)



I did some on line research for a good entry point into writing emulators.
A 'Chip 8' emulator was recommended as a good starting point, however because I felt that I was good at writing shit code, and because I had already done some work on screen interpreters and reading data from other platforms code and converting it, I arrogantly decided to jump in on the next level up and write an old skool arcade machine emulator, and there isn't an arcade machine more iconic than Space Invaders.

I had given writing an emulator some thought previously, and had a rough idea of what would be required from a coding point of view.

It was surprising to me how close I was to the mark!

A Space Invaders emulator needs the following:

  • Memory Buffer - This should be constructed in the same fashion as the original platform accesses its roms and ram. Space invaders accesses the 8 kB of roms and the 8 kB of ram as a single 16 kB block of memory, so an array with 16384 byte slots will do the job nicely.
    Some people will swear that you need to provide the emulator with more than 16 kB of memory due to the "Ram Mirror". If your emulator works correctly, 16 kB is fine and the program should not try to access Ram beyond this point.
  • Intel 8080 processor running at approx. 2 MHz - This should be a continuous loop that reads opcodes from the roms and executes the required code to simulate the processor executing the opcode. Its important to know how many bytes each opcode is, and how many processor clock cycles each one takes to execute.
  • Interrupt generator - The processor has an interrupt pin, and enable/disable interrupts opcodes. If the processor has interrupts enabled, and hardware external to the processor poles the interrut pin, then the processor should pause the code its currently running and run an externally supplied opcode.
    Interrupt opcodes are called Resets or RSTs. There are 8 RST opcodes and each one tells the processor to go and run a subroutine at a fixed memory location.
    In the hardware itself, the screen is continuously being updated so that every millisecond that the code is running, an updated screen is also being drawn. For an emulator, the process can be simplified as we can update the whole screen far quicker than the original hardware could. So we can run enough opcodes to use up 1/60th of 2 MHz processor clock cycles, and then update the screen in a flash.
    The screen updates at 60 FPS, and there are two interrupts generated per screen update. A RST1 aka RST 08 and a RST2 aka RST 10. These are generated just before the top half of the screen starts drawing, and again just before the bottom half of the screen starts drawing. The processor runs at 2000000 clocks per second. If there is 60 FPS then the screen should update every 33333 processor clock cycles, and the interrupts should be generated every 16666 clock cycles (ish) for an emulator to work correctly.
    Interestingly, its not the speed of the processor that governs the speed of the game. Its the rate of the interrupts. The way that Space Invaders code works is that it runs a chunk of code, and then goes into an infinite loop that cannot be resolved by the code. Its waiting for an interrupt. When the interrupt occurs, a subroutine is run and then the program returns to the infinite loop but the subroutine resolves the loop and the code continues running.
    If we speed up the processor by changing the interrupt timing to every 33333 clock cycles and the display update to every 66666 clock cycles still running at 60 FPS (Effectively doubling the CPU speed), we still get the same speed game because of the infinite loops that wait for the interrupts to occur.
    The only way to speed up the game itself is to increase the rate that the interrupts occur so as above but have the interrupts still occuring at 16666 clock cycles, so four interrupts per screen update at 60 FPS with screen updates running every 66666 processor clock cycles will double the game speed.
  • Display Output - The video output is stored in the top 7 kB of the 16 kBs of rom and ram as a 1 bit bitmap. Normally these bits would be drawn from left to right of screen starting at the top and working your way down row by row. Space Invaders however has the screen rotated 90 degrees anti-clockwise so the bits draw from bottom to top starting at the far left and working your way right column by column. If your processor is running at 2 MHz, and the display refresh rate is 60 Hz then you want to update your display every 33333 processor clock cycles. This can be tied in with the interrupt generator as in the real hardware. As you execute opcodes, count how many cycles you have used. When you get to >= 16666, preform a RST1 and reset the counter. When you get to 16666 again, perform a RST2 and update the display. Reset the counter and repeat.
    Colour can be added at the display drawing stage. For the original Space Invaders, the output was monochrome only and any colour that was seen (Green shields and red command ships) was done by reflecting the screen off of a mirror that had been treated with tinted gel in certain areas giving a coloured graphics effect.
  • 16 Bit Shift Register - Space Invaders has a piece of hardware called a 16 bit shift register. It shifts bits to the right by a set number between 0 and 7. It is loaded, and the shift amount set using the OUT opcodes 2 and 4, and the result is read back using the In Opcode 3. This is used predominantly to shift the space invaders to be drawn onto the correct pixel row e.g. the sprites are stored to be drawn from 0. Using the shift register you can quickly shift each sprite to be drawn from row 3 onwards and split the draw inbetween two bytes rather than having the 8 pixels in a single byte.
  • DIP switches, Coin Mech. and Joystick and Button Inputs - These are all read using the IN opcode. The IN Opcode can be set to read one of 8 different inputs using the byte after the opcode to identify the port to be read. The port value is read into the accumulator. The DIP switches set the amount of lives at the start (3 to 6) and the score at which a bonus base is earned (1000 or 1500). IN 1 reads credits and P1 controls as well as P1 or P2 start. IN 2 reads P2 controls and dip switch settings.
  • Sound Output - The sounds are controlled by the OUT opcode using bits set in either OUT 3 or OUT 5. I used MAME sound samples for my emulator as the sound itself is very hard to emulate, and even MAME have only recently got it sorted.

Once you have all this down... you are there and emulating. I was suprised at how erm.... straight forward it was to write this emulator. I won't say simple or easy as there is some work to do, and some testing/debugging when things go wrong, but overall it was enjoyable and a really good learning experience.

For those of you that want to have a go, good luck and here are some hints:

  • Download a PDF of the Intel 8080 processor manual. It is written excellently with detailed descriptions of what each opcode does. Its invaluable for writing an emulator.

  • Write an opcode test bed program and test all of your opcodes before you add them to your emulator and ensure that your output exactly matches what the Intel 8080 manual says it should be.

  • Add error checks into your code. Every time you write to memory, check that you are not writing to the rom area or writing above 16 kB. Monitor the Stack depth.

  • Do not believe everything you are told on websites/forums. Some of the information can be questionable, or downright wrong.

  • Do not obsess over interrupt timing. Set it at a logical value and leave it there, even if you have a bug and changing the value changes the bug behaviour!

  • You can get away with only implement the parts of the 808 processor necessary to get the emulator running.

    The Space Invaders game itself doesn't use the Aux. Carry flag (only used for calculating the correct number of credits). I dont think it uses the Parity flag either. It also only uses 128 out of the 256 processor opcodes.

    I started my emulator as a framework with no opcodes in it and an error message that told me if an opcode required by the code wasn't implemented. I then let the code tell me what opcodes it wanted and implemented them as they were asked for.

    After the first 50 are implemented, you will see Scores and credits on the screen, and your emulator will go into its first invinite loop. This is then the time to implement the interrupt generator.

And the rest... you will have to make up as you go. Good luck!

No comments:

Post a comment