Welcome back! Have you tried to figure out the opcode $31? In any case the instruction name is LXI. This loads a 16-bit register with a 16-bit immediate value. For those of you who don't have any assembly language knowledge, the term immediate means a value that's specified along with the instruction, exactly like our previous JMP instruction that had its destination address specified as an immediate value. If summary, the instruction name LXI means Load eXtended Immediate or something like that. So let's not waste more time and demonstrate what this instruction means in emulated code. Open up cpu.bmx and go inside method ExecuteInstruction(), inside the select block, and add:

            Case $01, $11, $21, $31
                Instruction_LXI()

Like ealier, you see that we are handling more than one opcode with the same method. Add this new method:

    Method Instruction_LXI()
        'Local name$
        Local data16:Short = FetchRomShort()
   
        Select current_inst
            Case $01
                'name = "BC"
                SetBC(data16)
            Case $11
                'name = "DE"
                SetDE(data16)
            Case $21
                'name = "HL"
                SetHL(data16)
            Case $31
                'name = "SP"
                SetSP(data16)
        End Select
   
        'Disassembly("LXI "+name$+","+HWord$(data16))
    End Method

So finally to answer the question, instruction $31 basically does SetSP(FetchRomShort()). Congratulations if you managed to find it by yourself! However, running the project now won't get us much far: another unknown instruction, this time its $06. Before we add new instructions, we are going to put in some useful stuff that will needed eventually. Near the top of the file, add this import:

Import "io.bmx"

We'll need to access our I/O ports eventually. We'll also need methods to access (read and write) values to memory, and place or retrieve values off the stack, thru the SP register. Add theses inside the Type:

    Method ReadByte:Byte(inAddress:Short)
        Return memoryptr[inAddress]
    End Method
   
    Method ReadShort:Short(inAddress:Short)
        Return (memoryptr[inAddress+1] Shl 8) + (memoryptr[inAddress])
    End Method

    Method WriteByte(inAddress:Short, inByte:Byte)
        memoryptr[inAddress] = inByte
    End Method
   
    Method WriteShort(inAddress:Short, inWord:Short)
        memoryptr[inAddress+1] = inWord Shr 8
        memoryptr[inAddress] = inWord
    End Method

    Method StackPush(inValue:Short)
        SP :- 2
        WriteShort(SP, inValue)
    End Method
   
    Method StackPop:Short()
        Local temp:Short = ReadShort(SP)
        SP :+ 2
        Return temp
    End Method

Let's add all remaning instructions in one big blast. You should know where to add these lines:

            Case $3e, $06, $0e, $16, $1e, $26, $2e, $36
                Instruction_MVI()
            Case $cd, $c4, $cc, $d4, $dc
                Instruction_CALL()
            Case $0a, $1a, $3a
                Instruction_LDA()
            Case $77, $70, $71, $72, $73, $74, $75
                Instruction_MOVHL()
            Case $03, $13, $23, $33
                Instruction_INX()
            Case $0b, $1b, $2b, $3b
                Instruction_DCX()
            Case $3d, $05, $0d, $15, $1d, $25, $2d, $35
                Instruction_DEC()
            Case $3c, $04, $0c, $14, $1c, $24, $2c, $34
                Instruction_INC()
            Case $c9, $c0, $c8, $d0, $d8
                Instruction_RET()
            Case $7F, $78, $79, $7A, $7B, $7C, $7D, $7E
                Instruction_MOV()
            Case $47, $40, $41, $42, $43, $44, $45, $46
                Instruction_MOV()
            Case $4f, $48, $49, $4a, $4b, $4c, $4d, $4e
                Instruction_MOV()
            Case $57, $50, $51, $52, $53, $54, $55, $56
                Instruction_MOV()
            Case $5f, $58, $59, $5a, $5b, $5c, $5d, $5e
                Instruction_MOV()
            Case $67, $60, $61, $62, $63, $64, $65, $66
                Instruction_MOV()
            Case $6f, $68, $69, $6a, $6b, $6c, $6d, $6e
                Instruction_MOV()
            Case $bf, $b8, $b9, $ba, $bb, $bc, $bd, $be, $fe
                Instruction_CMP()
            Case $c5, $d5, $e5, $f5
                Instruction_PUSH()
            Case $c1, $d1, $e1, $f1
                Instruction_POP()
            Case $09, $19, $29, $39
                Instruction_DAD()
            Case $eb
                Instruction_XCHG()
            Case $e3
                Instruction_XTHL()
            Case $d3
                Instruction_OUTP()
            Case $db
                Instruction_INP()
            Case $e9
                Instruction_PCHL()
            Case $c7, $cf, $d7, $df, $e7, $ef, $f7, $ff
                Instruction_RST()
            Case $07
                Instruction_RLC()
            Case $17
                Instruction_RAL()
            Case $0f
                Instruction_RRC()
            Case $1f
                Instruction_RAR()
            Case $a7, $a0, $a1, $a2, $a3, $a4, $a5, $a6, $e6
                Instruction_AND()
            Case $87, $80, $81, $82, $83, $84, $85, $86, $c6
                Instruction_ADD()
            Case $02, $12, $32
                Instruction_STA()
            Case $af, $a8, $a9, $aa, $ab, $ac, $ad, $ae, $ee
                Instruction_XOR()
            Case $f3
                Instruction_DI()
            Case $fb
                Instruction_EI()
            Case $37
                Instruction_STC()
            Case $3f
                Instruction_CMC()
            Case $b7, $b0, $b1, $b2, $b3, $b4, $b5, $b6, $f6
                Instruction_OR()
            Case $97, $90, $91, $92, $93, $94, $95, $96, $d6
                Instruction_SUB()
            Case $2a
                Instruction_LHLD()
            Case $22
                Instruction_SHLD()
            Case $de
                Instruction_SBBI()
            Case $27
                Instruction_DAA()
            Case $2f
                Instruction_CMA()
            Case $8f, $88, $89, $8a, $8b, $8c, $8d, $8e, $ce
                Instruction_ADC()

That's a lot of different opcodes isn't it? Well it is pretty much all the CPU can do. Nothing more, nothing less. If you look at the different names, there isn't a lot of things actually. There's a bunch of load / move, addition, subtraction, shift, and, or, xor, rotate, statck stuff, and a couple of special instructions here and there. This CPU can't even multiply or divide. Ready for a big block of code? You asked for it:

    Method Instruction_MVI()
        'Local name$
        Local data8:Byte = FetchRomByte()
   
        Select current_inst
            Case $3e
                'name = "A"
                SetA(data8)
            Case $06
                'name = "B"
                SetB(data8)
            Case $0e
                'name = "C"
                SetC(data8)
            Case $16
                'name = "D"
                SetD(data8)
            Case $1e
                'name = "E"
                SetE(data8)
            Case $26
                'name = "H"
                SetH(data8)
            Case $2e
                'name = "L"
                SetL(data8)
            Case $36
                'name$ = "[HL]"
                WriteByte(HL, data8)
        End Select
   
        'Disassembly("MVI "+name$+","+HByte$(data8))
    End Method
   
    Method Instruction_CALL()
        'Local name$
        Local condition:Byte = True
        Local data16:Short = FetchRomShort()
   
        Select current_inst
            Case $cd
                'name = "CALL"
            Case $c4
                'name = "CALL NZ"
                condition = Not ZERO
            Case $cc
                'name = "CALL Z"
                condition = ZERO
            Case $d4
                'name = "CALL NC"
                condition = Not CARRY
            Case $dc
                'name = "CALL C"
                condition = CARRY
        End Select
   
        'Disassembly(name$+" "+HWord$(data16)+"  condition="+condition)
        If condition Then
            StackPush(PC)
            PC = data16
        End If
    End Method
   
    Method Instruction_RET()
        'Local name$
        Local condition:Byte = True
   
        Select current_inst
            Case $c9
                'name = "RET"
            Case $c0
                'name = "RET NZ"
                condition = Not ZERO
            Case $c8
                'name = "RET Z"
                condition = ZERO
            Case $d0
                'name = "RET NC"
                condition = Not CARRY
            Case $d8
                'name = "RET C"
                condition = CARRY
        End Select
   
        'Disassembly(name$+"   condition="+condition)
        If condition Then
            PC = StackPop()
        End If
    End Method

    Method Instruction_LDA()
        'Local name$
        Local source:Short
       
        Select current_inst
            Case $0a
                'name = "BC"
                source = BC
            Case $1a
                'name = "DE"
                source = DE
            Case $3a
                source = FetchRomShort()
                'name = HWord$(source)
        End Select
       
        SetA(ReadByte(source))
   
        'Disassembly("LDA ["+name$+"]")
    End Method

    Method Instruction_PUSH()
        'Local name$
        Local value:Short
       
        Select current_inst
            Case $c5
                'name = "BC"
                value = BC
            Case $d5
                'name = "DE"
                value = DE
            Case $e5
                'name = "HL"
                value = HL
            Case $f5
                'name ="AF"
                value = (A Shl 8)
                If SIGN Then value = value | BIT7
                If ZERO Then value = value | BIT6
                If INTERRUPT Then value = value | BIT5
                If HALFCARRY Then value = value | BIT4
                If CARRY Then value = value | BIT0
        End Select
       
        StackPush(value)
   
        'Disassembly("PUSH "+name$)
    End Method

    Method Instruction_POP()
        'Local name$
        Local value:Short = StackPop()
       
        Select current_inst
            Case $c1
                'name = "BC"
                SetBC(value)
            Case $d1
                'name = "DE"
                SetDE(value)
            Case $e1
                'name = "HL"
                SetHL(value)
            Case $f1
                'name ="AF"
                A = value Shr 8
                SIGN = (value & BIT7)
                ZERO = (value & BIT6)
                INTERRUPT = (value & BIT5)
                HALFCARRY = (value & BIT4)
                CARRY = (value & BIT0)
        End Select
       
        'Disassembly("POP "+name$)
    End Method

    Method Instruction_MOVHL()
        'Local name$
   
        Select current_inst
            Case $77
                'name = "A"
                WriteByte(HL, A)
            Case $70
                'name = "B"
                WriteByte(HL, B)
            Case $71
                'name = "C"
                WriteByte(HL, C)
            Case $72
                'name = "D"
                WriteByte(HL, D)
            Case $73
                'name = "E"
                WriteByte(HL, E)
            Case $74
                'name = "H"
                WriteByte(HL, H)
            Case $75
                'name = "L"
                WriteByte(HL, L)
        End Select
   
        'Disassembly("MOV [HL],"+ name$)
    End Method
   
    Method Instruction_MOV()
        'Local src$, dst$
   
        Select current_inst
            Case $7f
                'dst = "A"; src = "A"
                SetA(A)
            Case $78
                'dst = "A"; src = "B"
                SetA(B)
            Case $79
                'dst = "A"; src = "C"
                SetA(C)
            Case $7a
                'dst = "A"; src = "D"
                SetA(D)
            Case $7b
                'dst = "A"; src = "E"
                SetA(E)
            Case $7c
                'dst = "A"; src = "H"
                SetA(H)
            Case $7d
                'dst = "A"; src = "L"
                SetA(L)
            Case $7e
                'dst = "A"; src = "[HL]"
                SetA(ReadByte(HL))
               
            Case $47
                'dst = "B"; src = "A"
                SetB(A)
            Case $40
                'dst = "B"; src = "B"
                SetB(B)
            Case $41
                'dst = "B"; src = "C"
                SetB(C)
            Case $42
                'dst = "B"; src = "D"
                SetB(D)
            Case $43
                'dst = "B"; src = "E"
                SetB(E)
            Case $44
                'dst = "B"; src = "H"
                SetB(H)
            Case $45
                'dst = "B"; src = "L"
                SetB(L)
            Case $46
                'dst = "B"; src = "[HL]"
                SetB(ReadByte(HL))

            Case $4f
                'dst = "C"; src = "A"
                SetC(A)
            Case $48
                'dst = "C"; src = "B"
                SetC(B)
            Case $49
                'dst = "C"; src = "C"
                SetC(C)
            Case $4a
                'dst = "C"; src = "D"
                SetC(D)
            Case $4b
                'dst = "C"; src = "E"
                SetC(E)
            Case $4c
                'dst = "C"; src = "H"
                SetC(H)
            Case $4d
                'dst = "C"; src = "L"
                SetC(L)
            Case $4e
                'dst = "C"; src = "[HL]"
                SetC(ReadByte(HL))
       
            Case $57
                'dst = "D"; src = "A"
                SetD(A)
            Case $50
                'dst = "D"; src = "B"
                SetD(B)
            Case $51
                'dst = "D"; src = "C"
                SetD(C)
            Case $52
                'dst = "D"; src = "D"
                SetD(D)
            Case $53
                'dst = "D"; src = "E"
                SetD(E)
            Case $54
                'dst = "D"; src = "H"
                SetD(H)
            Case $55
                'dst = "D"; src = "L"
                SetD(L)
            Case $56
                'dst = "D"; src = "[HL]"
                SetD(ReadByte(HL))

            Case $5f
                'dst = "E"; src = "A"
                SetE(A)
            Case $58
                'dst = "E"; src = "B"
                SetE(B)
            Case $59
                'dst = "E"; src = "C"
                SetE(C)
            Case $5a
                'dst = "E"; src = "D"
                SetE(D)
            Case $5b
                'dst = "E"; src = "E"
                SetE(E)
            Case $5c
                'dst = "E"; src = "H"
                SetE(H)
            Case $5d
                'dst = "E"; src = "L"
                SetE(L)
            Case $5e
                'dst = "E"; src = "[HL]"
                SetE(ReadByte(HL))

            Case $67
                'dst = "H"; src = "A"
                SetH(A)
            Case $60
                'dst = "H"; src = "B"
                SetH(B)
            Case $61
                'dst = "H"; src = "C"
                SetH(C)
            Case $62
                'dst = "H"; src = "D"
                SetH(D)
            Case $63
                'dst = "H"; src = "E"
                SetH(E)
            Case $64
                'dst = "H"; src = "H"
                SetH(H)
            Case $65
                'dst = "H"; src = "L"
                SetH(L)
            Case $66
                'dst = "H"; src = "[HL]"
                SetH(ReadByte(HL))
               
            Case $6f
                'dst = "L"; src = "A"
                SetL(A)
            Case $68
                'dst = "L"; src = "B"
                SetL(B)
            Case $69
                'dst = "L"; src = "C"
                SetL(C)
            Case $6a
                'dst = "L"; src = "D"
                SetL(D)
            Case $6b
                'dst = "L"; src = "E"
                SetL(E)
            Case $6c
                'dst = "L"; src = "H"
                SetL(H)
            Case $6d
                'dst = "L"; src = "L"
                SetL(L)
            Case $6e
                'dst = "L"; src = "[HL]"
                SetL(ReadByte(HL))
        End Select
   
        'Disassembly("MOV "+dst$+","+src$)
    End Method
   
    Method Instruction_INX()
        'Local name$
   
        Select current_inst
            Case $03
                'name = "BC"
                SetBC(BC+1)
            Case $13
                'name = "DE"
                SetDE(DE+1)
            Case $23
                'name = "HL"
                SetHL(HL+1)
            Case $33
                'name = "SP"
                SetSP(SP+1)
        End Select
   
        'Disassembly("INX "+name$)
    End Method
   
    Method Instruction_DAD()
        'Local name$
   
        Select current_inst
            Case $09
                'name = "BC"
                AddHL(BC)
            Case $19
                'name = "DE"
                AddHL(DE)
            Case $29
                'name = "HL"
                AddHL(HL)
            Case $39
                'name = "SP"
                AddHL(SP)
        End Select
       
        'Disassembly("DAD HL,"+name$)
    End Method

    Method AddHL(inValue:Short)
        Local value:Int = HL + inValue;
        SetHL(value)
   
        CARRY = (value > 65535)
    End Method
   
    Method Instruction_DCX()
        'Local name$
   
        Select current_inst
            Case $0b
                'name = "BC"
                SetBC(BC-1)
            Case $1b
                'name = "DE"
                SetDE(DE-1)
            Case $2b
                'name = "HL"
                SetHL(HL-1)
            Case $3b
                'name = "SP"
                SetSP(SP-1)
        End Select
   
        'Disassembly("DCX "+name$)
    End Method
   
    Method Instruction_DEC()
        'Local name$
   
        Select current_inst
            Case $3d
                'name = "A"
                SetA(PerformDec(A))
            Case $05
                'name = "B"
                SetB(PerformDec(B))
            Case $0d
                'name = "C"
                SetC(PerformDec(C))
            Case $15
                'name = "D"
                SetD(PerformDec(D))
            Case $1d
                'name = "E"
                SetE(PerformDec(E))
            Case $25
                'name = "H"
                SetH(PerformDec(H))
            Case $2d
                'name = "L"
                SetL(PerformDec(L))
            Case $35
                'name = "[HL]"
                Local data8:Byte = ReadByte(HL)
                WriteByte(HL, PerformDec(data8))
        End Select
   
        'Disassembly("DEC "+ name$)
    End Method
   
    Method Instruction_INC()
        'Local name$
   
        Select current_inst
            Case $3c
                'name = "A"
                SetA(PerformInc(A))
            Case $04
                'name = "B"
                SetB(PerformInc(B))
            Case $0c
                'name = "C"
                SetC(PerformInc(C))
            Case $14
                'name = "D"
                SetD(PerformInc(D))
            Case $1c
                'name = "E"
                SetE(PerformInc(E))
            Case $24
                'name = "H"
                SetH(PerformInc(H))
            Case $2c
                'name = "L"
                SetL(PerformInc(L))
            Case $34
                'name = "[HL]"
                Local data8:Byte = ReadByte(HL)
                WriteByte(HL, PerformInc(data8))
        End Select
   
        'Disassembly("INC "+ name$)
    End Method
   
    Method Instruction_AND()
        'Local name$
   
        Select current_inst
            Case $a7
                'name = "A"
                PerformAnd(A)
            Case $a0
                'name = "B"
                PerformAnd(B)
            Case $a1
                'name = "C"
                PerformAnd(C)
            Case $a2
                'name = "D"
                PerformAnd(D)
            Case $a3
                'name = "E"
                PerformAnd(E)
            Case $a4
                'name = "H"
                PerformAnd(H)
            Case $a5
                'name = "L"
                PerformAnd(L)
            Case $a6
                'name = "[HL]"
                PerformAnd(ReadByte(HL))
            Case $e6
                Local immediate:Byte = FetchRomByte()
                'name = HByte$(immediate)
                PerformAnd(immediate)
        End Select
   
        'Disassembly("AND "+ name$)
    End Method
   
    Method Instruction_XOR()
        'Local name$
   
        Select current_inst
            Case $af
                'name = "A"
                PerformXor(A)
            Case $a8
                'name = "B"
                PerformXor(B)
            Case $a9
                'name = "C"
                PerformXor(C)
            Case $aa
                'name = "D"
                PerformXor(D)
            Case $ab
                'name = "E"
                PerformXor(E)
            Case $ac
                'name = "H"
                PerformXor(H)
            Case $ad
                'name = "L"
                PerformXor(L)
            Case $ae
                'name = "[HL]"
                PerformXor(ReadByte(HL))
            Case $ee
                Local immediate:Byte = FetchRomByte()
                'name = HByte$(immediate)
                PerformXor(immediate)
        End Select
   
        'Disassembly("XOR "+ name$)
    End Method
   
    Method Instruction_OR()
        'Local name$
   
        Select current_inst
            Case $b7
                'name = "A"
                PerformOr(A)
            Case $b0
                'name = "B"
                PerformOr(B)
            Case $b1
                'name = "C"
                PerformOr(C)
            Case $b2
                'name = "D"
                PerformOr(D)
            Case $b3
                'name = "E"
                PerformOr(E)
            Case $b4
                'name = "H"
                PerformOr(H)
            Case $b5
                'name = "L"
                PerformOr(L)
            Case $b6
                'name = "[HL]"
                PerformOr(ReadByte(HL))
            Case $f6
                Local immediate:Byte = FetchRomByte()
                'name = HByte$(immediate)
                PerformOr(immediate)
        End Select
   
        'Disassembly("OR "+ name$)
    End Method
       
    Method Instruction_ADD()
        'Local name$
   
        Select current_inst
            Case $87
                'name = "A"
                PerformByteAdd(A)
            Case $80
                'name = "B"
                PerformByteAdd(B)
            Case $81
                'name = "C"
                PerformByteAdd(C)
            Case $82
                'name = "D"
                PerformByteAdd(D)
            Case $83
                'name = "E"
                PerformByteAdd(E)
            Case $84
                'name = "H"
                PerformByteAdd(H)
            Case $85
                'name = "L"
                PerformByteAdd(L)
            Case $86
                'name = "[HL]"
                PerformByteAdd(ReadByte(HL))
            Case $c6
                Local immediate:Byte = FetchRomByte()
                'name = HByte$(immediate)
                PerformByteAdd(immediate)
        End Select
   
        'Disassembly("ADD "+ name$)
    End Method
   
    Method Instruction_ADC()
        'Local name$
        Local carryvalue:Byte = 0
        If CARRY Then carryvalue = 1
   
        Select current_inst
            Case $8f
                'name = "A"
                PerformByteAdd(A, carryvalue)
            Case $88
                'name = "B"
                PerformByteAdd(B, carryvalue)
            Case $89
                'name = "C"
                PerformByteAdd(C, carryvalue)
            Case $8a
                'name = "D"
                PerformByteAdd(D, carryvalue)
            Case $8b
                'name = "E"
                PerformByteAdd(E, carryvalue)
            Case $8c
                'name = "H"
                PerformByteAdd(H, carryvalue)
            Case $8d
                'name = "L"
                PerformByteAdd(L, carryvalue)
            Case $8e
                'name = "[HL]"
                PerformByteAdd(ReadByte(HL), carryvalue)
            Case $ce
                Local immediate:Byte = FetchRomByte()
                'name = HByte$(immediate)
                PerformByteAdd(immediate, carryvalue)
        End Select
   
        'Disassembly("ADC "+ name$)
    End Method
       
    Method Instruction_SUB()
        'Local name$
   
        Select current_inst
            Case $97
                'name = "A"
                PerformByteSub(A)
            Case $90
                'name = "B"
                PerformByteSub(B)
            Case $91
                'name = "C"
                PerformByteSub(C)
            Case $92
                'name = "D"
                PerformByteSub(D)
            Case $93
                'name = "E"
                PerformByteSub(E)
            Case $94
                'name = "H"
                PerformByteSub(H)
            Case $95
                'name = "L"
                PerformByteSub(L)
            Case $96
                'name = "[HL]"
                PerformByteSub(ReadByte(HL))
            Case $d6
                Local immediate:Byte = FetchRomByte()
                'name = HByte$(immediate)
                PerformByteSub(immediate)
        End Select
   
        'Disassembly("SUB "+ name$)
    End Method
   
    Method Instruction_SBBI()
        Local immediate:Byte = FetchRomByte()
        Local carryvalue:Byte = 0
        If CARRY Then carryvalue = 1
        PerformByteSub(immediate, carryvalue)
        'Disassembly("SBBI "+HByte$(immediate))
    End Method
   
    Method Instruction_CMP()
        'Local name$
        Local value:Byte
   
        Select current_inst
            Case $bf
                'name = "A"
                value = A
            Case $b8
                'name = "B"
                value = B
            Case $b9
                'name = "C"
                value = C
            Case $ba
                'name = "D"
                value = D
            Case $bb
                'name = "E"
                value = E
            Case $bc
                'name = "H"
                value = H
            Case $bd
                'name = "L"
                value = L
            Case $be
                'name = "[HL]"
                value = ReadByte(HL)
            Case $fe
                value = FetchRomByte()
                'name = HByte$(value)
        End Select
       
        PerformCompSub(value)
        'Disassembly("CMP "+name$)
    End Method
   
    Method Instruction_XCHG()
        Local temp:Short = DE
        SetDE(HL)
        SetHL(temp)
        'Disassembly("XCHG DE,HL")
    End Method
   
    Method Instruction_XTHL()
        Local temp:Byte = H
        SetH(ReadByte(SP+1))
        WriteByte(SP+1, temp)
   
        temp = L
        SetL(ReadByte(SP))
        WriteByte(SP, temp)
   
        'Disassembly("XTHL HL,[SP]")
    End Method
   
    Method Instruction_OUTP()
        Local port:Byte = FetchRomByte()
        io.OutputPort(port, A)
       
        'Disassembly("OUTP "+HByte$(port)+" A="+A)
    End Method
   
    Method Instruction_INP()
        Local port:Byte = FetchRomByte()
        SetA(io.InputPort(port))
       
        'Disassembly("INP "+HByte$(port)+" A="+A)
    End Method
   
    Method Instruction_PCHL()
        PC = HL
        'Disassembly("PCHL")
    End Method
   
    Method Instruction_RST()
        'Local name$
        Local address:Short
   
        Select current_inst
            Case $c7
                'name = "RST0"
                address = $0
            Case $cf
                'name = "RST1"
                address = $8
            Case $d7
                'name = "RST2"
                address = $10
            Case $df
                'name = "RST3"
                address = $18
            Case $e7
                'name = "RST4"
                address = $20
            Case $ef
                'name = "RST5"
                address = $28
            Case $f7
                'name = "RST6"
                address = $30
            Case $ff
                'name = "RST7"
                address = $38
        End Select
   
        'Disassembly(name)
   
        StackPush(PC)
        PC = address
    End Method
   
    Method Instruction_RLC()
        SetA((A Shl 1) | (A Shr 7))
        CARRY = A & BIT0
   
        'Disassembly("RLC")
    End Method
   
    Method Instruction_RAL()
        Local temp:Byte = A
   
        setA(A Shl 1)
        If(CARRY) Then setA(A | BIT0)
        CARRY = temp & BIT7
   
        'Disassembly("RAL")
    End Method
   
    Method Instruction_RRC()
        SetA((A Shr 1) | (A Shl 7))
        CARRY = A & BIT7
        'Disassembly("RRC")
    End Method
   
    Method Instruction_RAR()   
        Local temp:Byte = A
   
        setA(A Shr 1)
        If(CARRY) Then setA(A | BIT7)
        CARRY = temp & BIT0
   
        'Disassembly("RAR")
    End Method
   
    Method Instruction_STA()
        'Local src$
   
        Select current_inst
            Case $02
                'src = "BC"
                WriteByte(BC, A)
            Case $12
                'src = "DE"
                WriteByte(DE, A)
            Case $32
                Local immediate:Short = FetchRomShort()
                WriteByte(immediate, A)
                'src = HWord$(immediate)
        End Select
   
        'Disassembly("STA "+src$)
    End Method
   
    Method Instruction_DI()
        INTERRUPT = False
        'Disassembly("DI")
    End Method
   
    Method Instruction_EI()
        INTERRUPT = True
        'Disassembly("EI")
    End Method
   
    Method Instruction_STC()
        CARRY = True
        'Disassembly("STC")
    End Method
   
    Method Instruction_CMC()
        CARRY = Not CARRY
        'Disassembly("CMC")
    End Method
   
    Method Instruction_LHLD()
        Local immediate:Short = FetchRomShort()
        SetHL(ReadShort(immediate))
        'Disassembly("LHLD ["+HWord$(immediate)+"]")
    End Method
   
    Method Instruction_SHLD()
        Local immediate:Short = FetchRomShort()
        WriteShort(immediate, HL)
        'Disassembly("SHLD ["+HWord$(immediate)+"]")
    End Method
   
    Method Instruction_DAA()
        If(((A & $0F) > 9) Or (HALFCARRY))
            A :+ $06
            HALFCARRY = True
        Else
            HALFCARRY = False
        End If
   
        If((A > $9F) Or (CARRY))
            A :+ $60
            CARRY = True
        Else
            CARRY = False
        End If
   
        setFlagZeroSign()
        'Disassembly("DAA")
    End Method
   
    Method Instruction_CMA()
        setA(A ~ 255)
        'Disassembly("CMA")
    End Method
   
    Method setFlagZeroSign()
        ZERO = (A = 0)
        SIGN = (A&128)
    End Method

    Method PerformAnd(inValue:Byte)
        SetA(A & inValue)
        CARRY = False
        HALFCARRY = False
        setFlagZeroSign()
    End Method

    Method PerformXor(inValue:Byte)
        SetA(A ~ inValue)
        CARRY = False
        HALFCARRY = False
        setFlagZeroSign()
    End Method

    Method PerformOr(inValue:Byte)
        SetA(A | inValue)
        CARRY = False
        HALFCARRY = False
        setFlagZeroSign()
    End Method
   
    Method PerformByteAdd(inValue:Byte, inCarryValue:Byte=0)
        Local value:Int = A + inValue + inCarryValue
        HALFCARRY = ((A ~ inValue ~ value) & $10)
        setA(value)

        CARRY = (value > 255)
        setFlagZeroSign()
    End Method
   
    Method PerformInc:Byte(inSource:Byte)
        Local value:Int = inSource + 1

        HALFCARRY = ((value & $F) <> 0)

        ZERO = ((value & 255) = 0)
        SIGN = (value & 128)
        Return value
    End Method
   
    Method PerformDec:Byte(inSource:Byte)
        Local value:Int = inSource - 1

        HALFCARRY = ((value & $F) = 0)

        ZERO = ((value & 255) = 0)
        SIGN = (value & 128)
        Return value
    End Method
   
    Method PerformByteSub(inValue:Byte, inCarryValue:Byte=0)
        Local value:Byte = A - inValue - inCarryValue

        CARRY = ((value >= A) And (inValue | inCarryValue))
   
        HALFCARRY = ((A ~ inValue ~ value) & $10)

        setA(value)
        setFlagZeroSign()
    End Method

    Method PerformCompSub:Byte(inValue:Byte)
        Local value:Byte = A - inValue

        CARRY = ((value >= A) And (inValue))
        HALFCARRY = ((A ~ inValue ~ value) & $10)
        ZERO = (value = 0)
        SIGN = (value & 128)
    End Method

That's a big chunk of code. You deserve some reward. Let's run and see what happens. After all, we emulate all instructions so it should gives us something to see..

WHAT? Nothing at all? Ah, there is something very important that we didn't code yet. Our video system is not actually displaying anything except an empty rectangle. Let's code the video translation of the game's memory into our 32-bit pixmap. Open up video.bmx. Since everything in here is not very helpful, scrap the entire file and replace it with this code:

SuperStrict
Import "cpu.bmx"

Function FastGLDrawPixmap(p:TPixmap, x:Int, y:Int)
    SetBlend(SOLIDBLEND)
    glDisable(GL_TEXTURE_2D)
    glpixelzoom(1, -1)
    glRasterPos2i(0, 0)
    glBitmap(0, 0, 0, 0, x, -y, Null)
    glPixelStorei(GL_UNPACK_ROW_LENGTH, p.pitch Shr 2)
    glDrawPixels(p.width, p.height, GL_RGBA, GL_UNSIGNED_BYTE, p.pixels)
    SetBlend(ALPHABLEND)
End Function

Global video: Tvideo = New Tvideo

Type Tvideo
    Field screen: TPixmap
    Field screenptr: Int Ptr
    Field pitch: Int
    Field x: Int
    Field y: Int
   
    Method Init()
        screen = CreatePixmap(256, 224, PF_RGBA8888)
        screen.ClearPixels(0)
        screenptr = Int Ptr(screen.pixels)
        pitch = screen.pitch Shr 2
        x = 20
        y = 40
       
        DebugLog("Video initialized.")
    End Method
   
    Method Draw()
        CopyScreen()
        SetColor(255, 255, 255)
       
        ' Perform a fast OpenGL drawpixel instead of DrawPixmap because it's slow
        FastGLDrawPixmap(screen, x, y)
       
        SetColor(200, 200, 200)
        DrawLine(x, y-1, x+screen.width, y-1)
        DrawLine(x+screen.width, y, x+screen.width, y+screen.height)
        DrawLine(x, y+screen.height, x+screen.width, y+screen.height)
        DrawLine(x-1, y-1, x-1, y+screen.height)
    End Method
   
    Method CopyScreen()
        For Local j:Int=0 Until 224
            Local src:Int = $2400 + (j Shl 5)

            Local dst:Int = j*pitch
            For Local i:Int=0 Until 32
                Local vram:Byte = cpu.memoryptr[src]
                src :+ 1

                For Local b:Int=0 Until 8
                    Local color:Int = 0
                    If (vram&1) Then color = $FFFFFFFF
                    screenptr[dst] = color
                    dst :+ 1
                    vram = vram Shr 1
                Next
            Next
        Next
    End Method
End Type

To understand what's going on, you will need to remember that Space Invaders uses a 256 x 224, 1-bit per pixel display. Another very important information that we need to know is where in memory is the screen buffer? This could be discovered by trial and error, but in our case we know from that it is located at $2400. The first loop  inside CopyScreen() loops for each horizontal line, from 0 to 223. Then each line is made of 256 pixels. However since the display uses 1 bit per pixel, it means that a single byte will contain 8 pixels. We have to read 32 bytes, and for each of these bytes, loop their 8 bits. If the bit is set, we output a white pixel (by outputing $FFFFFFFF in our 32-bit pixmap), else let the pixel be black. Fair enough, this should do the trick. Let's run again, this time we should see something! If anything went according to plan, you should see this:

That's a great start! But there is obviously two things going wrong here. The first thing we notice: the display is rotated 90 degree. That's normal: if you remember the real machine specification, the actual arcade monitor was placed vertically (i.e.: the monitor was on its side). See http://en.wikipedia.org/wiki/Space_invaders. This means that in order to correct the display orientation, we are going to rotate counter-clockwise while translating the display into our pixmap. We'll handle that in the next chapter.

The second thing you can feel wrong is that the game is not doing anything else. It feels like its crashed or something. Why is that? Ah, there's another very important thing that we didn't emulate yet: the interrupts! The interrupt are used as a way of telling the CPU that the video monitor is going to start to display a frame and also when the frame display is completed. That's two interrupts that should occur every frame. Go back to cpu.bmx and add a few new field variables to emulate the interrupt mecanism:

    ' Interrupt handling
    Field interrupt_alternate: Int
    Field count_instructions: Int
    Field half_instruction_per_frame: Int = instruction_per_frame Shr 1

This is a very rough approximation of the real interrupt mecanism. We are going to count for half of our 'instruction per frame' variable, trigger an interrupt, then another half, trigger the other interrupt. Find the big ExecuteInstruction() method and add these lines near its end (between End Select and End Method):

        End Select
        count_instructions :+ 1
               
        If count_instructions >= half_instruction_per_frame Then
            If INTERRUPT Then
                ' Two interrupt occur every frame: (address $08 and $10)
                INTERRUPT = False
                StackPush(PC)
                If interrupt_alternate = 0 Then
                    PC = $08
                Else
                    PC = $10
                End If
                interrupt_alternate = 1 - interrupt_alternate
                count_instructions = 0
            End If
        End If
    End Method

Look at the code, it does exactly what I just explained. There is also an additional if INTERRUPT here. Do you remember why we should be doing that? That's because the CPU has one flag that controls whether interrupts are allowed to occur or not at a particular moment. This flag is extremely vital to any CPU, even modern ones. Without it, an external interrupt might occur while the SP (stack pointer) is not yet set or points to an invalid memory address. The CPU pushes the current PC down the stack, and then jumps to the interrupt start address which is $08 and $10 alternating.

I think we are ready to run for good this time. Cross your fingers and fire it up! It works! Marvelous! If you don't remember by heart (or by looking at io.bmx) here's the key mapping:

  • 3 / 5: insert a coin
  • 1: Start one player game
  • 2: Start two player game
  • T: Tilt (instantly game over and you lose your money)
  • Left / Right: Move cannon
  • Space bar: Shoot

When you get tired of looking at a rotated display, go to the next chapter to fix this up:

Chapter 6: Finishing touch

Back to tutorial home page