Skip to content →

a short fun project

After getting sidetracked for a while with other projects, I wanted to get back into emulation development with something fun, easy and short. So I decided to go for the very well-known arcade game of Space Invaders.

Space Invaders Arcade

Space Invaders is running on an Intel 8080 clocked at 1996800 Hz. It has a 256×224 monochrome display (which is actually laying on the side) fed by a 0x1c00 big VRAM. It can push some 8-bit data to output devices (sound) and grab some 8-bit data from input devices (controllers and DIP switches for game options). Other than that we only need to implement 2 simple interrupts (VBLANK and some kind of beam-centered interrupt), and we should be good to go.

setting up

To change stuff for once, I decided to create this emulator in Python, just for funsies. The library pygame helps us with setting up a game window and handle controls.

import os
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
import pygame
from cpu import getPC, getVRAM, cpu_8080, loadROM, memory, getREGS, getSP, getFLAGS, loadSI, setKeys, interrupt


def ppu(screen, surface):
    surface = pygame.Surface((256, 224))
    pa = pygame.PixelArray(surface)
    vram = getVRAM()
    for y in range(224):
        for x in range(256):
            white = vram[y*0x20+x//8] >> (x % 8) & 1
            if white != 0:
                pa[x,y] = (255, 255, 255)
            else:
                pa[x,y] = (0, 0, 0)
    del pa
    surface = pygame.transform.scale(surface, (512, 448))
    surface = pygame.transform.rotate(surface, 90)
    screen.blit(surface, (0,0))


def main():
    loadSI()

	# initialize the pygame module
    pygame.init()
    # set title
    pygame.display.set_caption("[ q00.si - space invaders arcade emuladore ]")
     
    # create a surface on screen that has the size of 240 x 180
    screen = pygame.display.set_mode((448,512))	# orig size is 256 / 224
    surface = pygame.Surface((256, 224))
    pa = pygame.PixelArray(surface)
    vram = getVRAM()
    for y in range(224):
    	for x in range(256):
            white = vram[y*0x20+x//8] >> (7-(x % 8)) & 1
            if white != 0:
                pa[x,y] = (255, 255, 255)
            else:
                pa[x,y] = (0, 0, 0)
    del pa
    surface = pygame.transform.scale(surface, (512, 448))
    screen.blit(surface, (0,0))
     
    # define a variable to control the main loop
    running = True
     
    # main loop
    cnt = 0
    last_cnt = 0
    frame = 0
    while running:
        # event handling, gets all event from the event queue
        L = 0
        R = 0
        F = 0
        C = 0
        ST = 0
        for event in pygame.event.get():
            # only do something if the event is of type QUIT
            if event.type == pygame.QUIT:
                # change the value to False, to exit the main loop
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    print("Left")
                    L = 1
                if event.key == pygame.K_RIGHT:
                    print("Right")
                    R = 1
                if event.key == pygame.K_SPACE:
                    print("Space")
                    F = 1
                if event.key == pygame.K_RETURN:
                    print("Return")
                    C = 1
                if event.key == pygame.K_ESCAPE:
                    print("Escape")
                    ST = 1
                setKeys(L, R, F, C, ST)
        #   step PPU
        if cnt > 16640 and last_cnt <= 16640:
            interrupt(0x08)
        if cnt > 33280:
            cnt = cnt - 33280
            interrupt(0x10)
            ppu(screen, surface)
            pygame.display.update()
        #   step CPU    
        last_cnt = cnt
        cnt = cnt + cpu_8080[memory[getPC()]]()

# run the main function only if this module is executed as the main script
# (if you import this as a module then nothing is executed)
if __name__=="__main__":
	# call the main function
    main()

(This is the already finished main.py, so don’t be irritated. It just shows how easy we set up the pygame code and modify the texture to be displayed)

the cpu – intel 8080

`After already having emulated a (modified) Z80 for the GameBoy, we can see that emulating the 8080 is very similar to it, and doesn’t need a lot of special work. We have a simple 256 opcode matrix:

  x0  x1  x2  x3  x4  x5  x6  x7  x8  x9  xA  xB  xC  xD  xE  xF 
 0x NOP
1  4
– – – – –
LXI B,d16
3  10
– – – – –
STAX B
1  7
– – – – –
INX B
1  5
– – – – –
INR B
1  5
S Z A P –
DCR B
1  5
S Z A P –
MVI B,d8
2  7
– – – – –
RLC
1  4
– – – – C
*NOP
1  4
– – – – –
DAD B
1  10
– – – – C
LDAX B
1  7
– – – – –
DCX B
1  5
– – – – –
INR C
1  5
S Z A P –
DCR C
1  5
S Z A P –
MVI C,d8
2  7
– – – – –
RRC
1  4
– – – – C
 1x *NOP
1  4
– – – – –
LXI D,d16
3  10
– – – – –
STAX D
1  7
– – – – –
INX D
1  5
– – – – –
INR D
1  5
S Z A P –
DCR D
1  5
S Z A P –
MVI D,d8
2  7
– – – – –
RAL
1  4
– – – – C
*NOP
1  4
– – – – –
DAD D
1  10
– – – – C
LDAX D
1  7
– – – – –
DCX D
1  5
– – – – –
INR E
1  5
S Z A P –
DCR E
1  5
S Z A P –
MVI E,d8
2  7
– – – – –
RAR
1  4
– – – – C
 2x *NOP
1  4
– – – – –
LXI H,d16
3  10
– – – – –
SHLD a16
3  16
– – – – –
INX H
1  5
– – – – –
INR H
1  5
S Z A P –
DCR H
1  5
S Z A P –
MVI H,d8
2  7
– – – – –
DAA
1  4
S Z A P C
*NOP
1  4
– – – – –
DAD H
1  10
– – – – C
LHLD a16
3  16
– – – – –
DCX H
1  5
– – – – –
INR L
1  5
S Z A P –
DCR L
1  5
S Z A P –
MVI L,d8
2  7
– – – – –
CMA
1  4
– – – – –
 3x *NOP
1  4
– – – – –
LXI SP,d16
3  10
– – – – –
STA a16
3  13
– – – – –
INX SP
1  5
– – – – –
INR M
1  10
S Z A P –
DCR M
1  10
S Z A P –
MVI M,d8
2  10
– – – – –
STC
1  4
– – – – C
*NOP
1  4
– – – – –
DAD SP
1  10
– – – – C
LDA a16
3  13
– – – – –
DCX SP
1  5
– – – – –
INR A
1  5
S Z A P –
DCR A
1  5
S Z A P –
MVI A,d8
2  7
– – – – –
CMC
1  4
– – – – C
 4x MOV B,B
1  5
– – – – –
MOV B,C
1  5
– – – – –
MOV B,D
1  5
– – – – –
MOV B,E
1  5
– – – – –
MOV B,H
1  5
– – – – –
MOV B,L
1  5
– – – – –
MOV B,M
1  7
– – – – –
MOV B,A
1  5
– – – – –
MOV C,B
1  5
– – – – –
MOV C,C
1  5
– – – – –
MOV C,D
1  5
– – – – –
MOV C,E
1  5
– – – – –
MOV C,H
1  5
– – – – –
MOV C,L
1  5
– – – – –
MOV C,M
1  7
– – – – –
MOV C,A
1  5
– – – – –
 5x MOV D,B
1  5
– – – – –
MOV D,C
1  5
– – – – –
MOV D,D
1  5
– – – – –
MOV D,E
1  5
– – – – –
MOV D,H
1  5
– – – – –
MOV D,L
1  5
– – – – –
MOV D,M
1  7
– – – – –
MOV D,A
1  5
– – – – –
MOV E,B
1  5
– – – – –
MOV E,C
1  5
– – – – –
MOV E,D
1  5
– – – – –
MOV E,E
1  5
– – – – –
MOV E,H
1  5
– – – – –
MOV E,L
1  5
– – – – –
MOV E,M
1  7
– – – – –
MOV E,A
1  5
– – – – –
 6x MOV H,B
1  5
– – – – –
MOV H,C
1  5
– – – – –
MOV H,D
1  5
– – – – –
MOV H,E
1  5
– – – – –
MOV H,H
1  5
– – – – –
MOV H,L
1  5
– – – – –
MOV H,M
1  7
– – – – –
MOV H,A
1  5
– – – – –
MOV L,B
1  5
– – – – –
MOV L,C
1  5
– – – – –
MOV L,D
1  5
– – – – –
MOV L,E
1  5
– – – – –
MOV L,H
1  5
– – – – –
MOV L,L
1  5
– – – – –
MOV L,M
1  7
– – – – –
MOV L,A
1  5
– – – – –
 7x MOV M,B
1  7
– – – – –
MOV M,C
1  7
– – – – –
MOV M,D
1  7
– – – – –
MOV M,E
1  7
– – – – –
MOV M,H
1  7
– – – – –
MOV M,L
1  7
– – – – –
HLT
1  7
– – – – –
MOV M,A
1  7
– – – – –
MOV A,B
1  5
– – – – –
MOV A,C
1  5
– – – – –
MOV A,D
1  5
– – – – –
MOV A,E
1  5
– – – – –
MOV A,H
1  5
– – – – –
MOV A,L
1  5
– – – – –
MOV A,M
1  7
– – – – –
MOV A,A
1  5
– – – – –
 8x ADD B
1  4
S Z A P C
ADD C
1  4
S Z A P C
ADD D
1  4
S Z A P C
ADD E
1  4
S Z A P C
ADD H
1  4
S Z A P C
ADD L
1  4
S Z A P C
ADD M
1  7
S Z A P C
ADD A
1  4
S Z A P C
ADC B
1  4
S Z A P C
ADC C
1  4
S Z A P C
ADC D
1  4
S Z A P C
ADC E
1  4
S Z A P C
ADC H
1  4
S Z A P C
ADC L
1  4
S Z A P C
ADC M
1  7
S Z A P C
ADC A
1  4
S Z A P C
 9x SUB B
1  4
S Z A P C
SUB C
1  4
S Z A P C
SUB D
1  4
S Z A P C
SUB E
1  4
S Z A P C
SUB H
1  4
S Z A P C
SUB L
1  4
S Z A P C
SUB M
1  7
S Z A P C
SUB A
1  4
S Z A P C
SBB B
1  4
S Z A P C
SBB C
1  4
S Z A P C
SBB D
1  4
S Z A P C
SBB E
1  4
S Z A P C
SBB H
1  4
S Z A P C
SBB L
1  4
S Z A P C
SBB M
1  7
S Z A P C
SBB A
1  4
S Z A P C
 Ax ANA B
1  4
S Z A P C
ANA C
1  4
S Z A P C
ANA D
1  4
S Z A P C
ANA E
1  4
S Z A P C
ANA H
1  4
S Z A P C
ANA L
1  4
S Z A P C
ANA M
1  7
S Z A P C
ANA A
1  4
S Z A P C
XRA B
1  4
S Z A P C
XRA C
1  4
S Z A P C
XRA D
1  4
S Z A P C
XRA E
1  4
S Z A P C
XRA H
1  4
S Z A P C
XRA L
1  4
S Z A P C
XRA M
1  7
S Z A P C
XRA A
1  4
S Z A P C
 Bx ORA B
1  4
S Z A P C
ORA C
1  4
S Z A P C
ORA D
1  4
S Z A P C
ORA E
1  4
S Z A P C
ORA H
1  4
S Z A P C
ORA L
1  4
S Z A P C
ORA M
1  7
S Z A P C
ORA A
1  4
S Z A P C
CMP B
1  4
S Z A P C
CMP C
1  4
S Z A P C
CMP D
1  4
S Z A P C
CMP E
1  4
S Z A P C
CMP H
1  4
S Z A P C
CMP L
1  4
S Z A P C
CMP M
1  7
S Z A P C
CMP A
1  4
S Z A P C
 Cx RNZ
1  11/5
– – – – –
POP B
1  10
– – – – –
JNZ a16
3  10
– – – – –
JMP a16
3  10
– – – – –
CNZ a16
3  17/11
– – – – –
PUSH B
1  11
– – – – –
ADI d8
2  7
S Z A P C
RST 0
1  11
– – – – –
RZ
1  11/5
– – – – –
RET
1  10
– – – – –
JZ a16
3  10
– – – – –
*JMP a16
3  10
– – – – –
CZ a16
3  17/11
– – – – –
CALL a16
3  17
– – – – –
ACI d8
2  7
S Z A P C
RST 1
1  11
– – – – –
 Dx RNC
1  11/5
– – – – –
POP D
1  10
– – – – –
JNC a16
3  10
– – – – –
OUT d8
2  10
– – – – –
CNC a16
3  17/11
– – – – –
PUSH D
1  11
– – – – –
SUI d8
2  7
S Z A P C
RST 2
1  11
– – – – –
RC
1  11/5
– – – – –
*RET
1  10
– – – – –
JC a16
3  10
– – – – –
IN d8
2  10
– – – – –
CC a16
3  17/11
– – – – –
*CALL a16
3  17
– – – – –
SBI d8
2  7
S Z A P C
RST 3
1  11
– – – – –
 Ex RPO
1  11/5
– – – – –
POP H
1  10
– – – – –
JPO a16
3  10
– – – – –
XTHL
1  18
– – – – –
CPO a16
3  17/11
– – – – –
PUSH H
1  11
– – – – –
ANI d8
2  7
S Z A P C
RST 4
1  11
– – – – –
RPE
1  11/5
– – – – –
PCHL
1  5
– – – – –
JPE a16
3  10
– – – – –
XCHG
1  5
– – – – –
CPE a16
3  17/11
– – – – –
*CALL a16
3  17
– – – – –
XRI d8
2  7
S Z A P C
RST 5
1  11
– – – – –
 Fx RP
1  11/5
– – – – –
POP PSW
1  10
S Z A P C
JP a16
3  10
– – – – –
DI
1  4
– – – – –
CP a16
3  17/11
– – – – –
PUSH PSW
1  11
– – – – –
ORI d8
2  7
S Z A P C
RST 6
1  11
– – – – –
RM
1  11/5
– – – – –
SPHL
1  5
– – – – –
JM a16
3  10
– – – – –
EI
1  4
– – – – –
CM a16
3  17/11
– – – – –
*CALL a16
3  17
– – – – –
CPI d8
2  7
S Z A P C
RST 7
1  11
– – – – –
       Misc/control instructions
       Jumps/calls
       8bit load/store/move instructions
       16bit load/store/move instructions
       8bit arithmetic/logical instructions
       16bit arithmetic/logical instructions
 
 
Length in bytes →
 
  INS reg
2  7
S Z A P C
  ← Instruction mnemonic
← Duration in cycles
← Flags affected
  Duration of conditional calls and returns is different when action is taken or not. This is indicated by two numbers separated by “/”. The higher number (on the left side of “/”) means duration of instruction when action is taken, the lower number (on the right side of “/”) means duration of instruction when action is not taken.
All instructions marked by “*” are only alternative opcodes for existing instructions. Those alternative opcodes should not be used.

(credits to https://pastraiser.com/)

Again equipped with the already known Flags (Sign, Zero, Auxiliary Carry, Parity, Carry) and Registers (AF, BC, DE, HL), emulating the 8080 doesn’t really turn out to be a big deal.

testing the cpu

To verify the 8080 works correctly, I decided to go for the TST8080.COM test rom, which can be found here.

def loadROM(file):
	with open(file, "rb") as f:
		res = f.read()
		c = 0
		for r in res:
			memory[0x100 + c] = r
			c = c + 1
	memory[0x7] = 0xc9 # patch
	memory[0x5] = 0xd3 # patch
	memory[0x6] = 0x01 # patch
	memory[0x0] = 0xd3 # patch

The test rom gets loaded to 0x100 in memory and we need to apply some special patches for it to run properly. The PC then just starts at 0x100 as well, an we are good to go, to compare to a proper stack trace of the ROM.

*** TEST: cpu_tests/TST8080.COM
 PC: 0100, AF: 0002, BC: 0000, DE: 0000, HL: 0000, SP: 0000, CYC: 0    (C3 B2 01 4D)   (D3) (0000)
 PC: 01B2, AF: 0002, BC: 0000, DE: 0000, HL: 0000, SP: 0000, CYC: 10    (31 BD 07 21)   (D3) (0000)
 PC: 01B5, AF: 0002, BC: 0000, DE: 0000, HL: 0000, SP: 07BD, CYC: 20    (21 03 01 CD)   (D3) (0000)
 PC: 01B8, AF: 0002, BC: 0000, DE: 0000, HL: 0103, SP: 07BD, CYC: 30    (CD 4B 01 E6)   (4D) (0103)
 PC: 014B, AF: 0002, BC: 0000, DE: 0000, HL: 0103, SP: 07BB, CYC: 47    (D5 EB 0E 09)   (4D) (0103)
 PC: 014C, AF: 0002, BC: 0000, DE: 0000, HL: 0103, SP: 07B9, CYC: 58    (EB 0E 09 CD)   (4D) (0103)
 PC: 014D, AF: 0002, BC: 0000, DE: 0103, HL: 0000, SP: 07B9, CYC: 62    (0E 09 CD 05)   (D3) (0000)
 PC: 014F, AF: 0002, BC: 0009, DE: 0103, HL: 0000, SP: 07B9, CYC: 69    (CD 05 00 D1)   (D3) (0000)
 PC: 0005, AF: 0002, BC: 0009, DE: 0103, HL: 0000, SP: 07B7, CYC: 86    (D3 01 C9 00)   (D3) (0000)
 MICROCOSM ASSOCIATES 8080/8085 CPU DIAGNOSTIC
 VERSION 1.0  (C) 1980
 PC: 0007, AF: 0002, BC: 0009, DE: 0103, HL: 0000, SP: 07B7, CYC: 96    (C9 00 00 00)   (D3) (0000)
 PC: 0152, AF: 0002, BC: 0009, DE: 0103, HL: 0000, SP: 07B9, CYC: 106    (D1 C9 0E 02)   (D3) (0000)
 PC: 0153, AF: 0002, BC: 0009, DE: 0000, HL: 0000, SP: 07BB, CYC: 116    (C9 0E 02 CD)   (D3) (0000)
 PC: 01BB, AF: 0002, BC: 0009, DE: 0000, HL: 0000, SP: 07BD, CYC: 126    (E6 00 CA C3)   (D3) (0000)
 PC: 01BD, AF: 0046, BC: 0009, DE: 0000, HL: 0000, SP: 07BD, CYC: 133    (CA C3 01 CD)   (D3) (0000)
...

The full working tracelog can be downloaded here.

Comments

Leave a Reply