Skip to content

Instantly share code, notes, and snippets.

@sugarflower
Created December 10, 2023 07:43
Show Gist options
  • Save sugarflower/f29c7d7983189b9a055347772591ff47 to your computer and use it in GitHub Desktop.
Save sugarflower/f29c7d7983189b9a055347772591ff47 to your computer and use it in GitHub Desktop.

とりあえず全体

DI、EI、DAAは未実装、NOPは保留
FレジスタのAフラグは未実装

テスト不足のため結構クリティカルな問題がありそう。

IN/OUTで利用する為Hardwareクラスを追加。

MEMORY_SIZE = 0xffff
#MEMORY_SIZE = 0x0f
import time

class Memory:
    def __init__(self):
        self.memory = bytearray([0] * (MEMORY_SIZE + 1))

    def get(self, addr):
        return self.memory[addr]

    def set(self, addr, value):
        self.memory[addr] = value & 0xff

    def get_word(self, addr):
        return self.get(addr) | self.get(addr + 1) << 8

    def set_word(self, addr, value):
        self.set(addr, (value & 0xff))
        self.set(addr + 1, (value >> 8))

class Register:
    def __init__(self, memory):
        # B C D E H L (M) A F SP PC
        self.register = bytearray([0] * 13)
        self.memory = memory

    def get(self, index):
        if index == 8:
            buf = self.register[8]
            buf = buf & 0b1101_0111
            buf = buf | 0b0000_0010
        else:
            buf = self.register[index]
        return buf

    def set(self, index, value):
        self.register[index] = value & 0xff

        if index == 6:
            self.memory.set(self.get_word(2), value)
        if index == 4 or index == 5:
            self.update()

    def get_word(self, index):
        # BC DE HL AF SP PC
        if index < 3:
            lval = self.get(index << 1)
            hval = self.get((index << 1) + 1)
        else:
            lval = self.get((index << 1) + 1)
            hval = self.get((index << 1) + 2)
        return (lval << 8) | hval

    def set_word(self, index, value):
        # BC DE HL AF SP PC
        hval = value & 0xff
        lval = value >> 8
        if index < 3:
            self.set(index << 1, lval)
            self.set((index <<1 ) + 1, hval)
        else:
            self.set((index << 1) + 1, lval)
            self.set((index << 1) + 2, hval)

    def update(self):
        self.register[6] = self.memory.get(self.get_word(2))

    def monitor(self):
        print("B %02X  C %02X" % (self.get(0), self.get(1)) )
        print("D %02X  E %02X" % (self.get(2), self.get(3)) )
        print("H %02X  L %02X" % (self.get(4), self.get(5)) )
        print("M %02X    SZ0A0P1C" % self.get(6) )
        print("A %02X  F %s" % (self.get(7), format(self.get(8), "08b")) )
        print("SP %04X" % self.get_word(4) )
        print("PC %04X" % self.get_word(5) )
        print("")



def a16(value):
    if value > MEMORY_SIZE:
        value -= MEMORY_SIZE + 1
    elif value < 0:
        value += MEMORY_SIZE
    return value & MEMORY_SIZE

def d16(value):
    if value > 0xffff:
        value -= 0xffff + 1
    elif value < 0:
        value += 0xffff
    return value & 0xffff

def d8(value):
    if value > 0xff:
        value -= 0xff + 1
    elif value < 0:
        value += 0xff
    return value & 0xff


class CPU:
    def __init__(self, memory, register, hardware):
        self.memory = memory
        self.register = register
        self.hardware = hardware
        self.running = True

    def fetch(self):
        pc = self.register.get_word(5)
        value = self.memory.get(pc)
        self.register.set_word(5, a16(pc + 1))
        return value

    def fetch_word(self):
        pc = self.register.get_word(5)
        value = self.memory.get_word(pc)
        self.register.set_word(5, a16(pc + 2))
        return value

    def push(self, value):
        sp = a16(self.register.get_word(4) - 2)
        self.memory.set_word(sp, value)
        self.register.set_word(4, a16(sp))

    def pop(self):
        sp = self.register.get_word(4)
        value = self.memory.get_word(sp)
        self.register.set_word(4, a16(sp + 2))
        return value


    # flg
    # S Z 0 A 0 P 1 C
    # A (HC) not impl
    def check_parity(self, buf):
        v = buf
        v ^= v >> 4
        v ^= v >> 2
        v ^= v >> 1
        v = v & 1
        f = self.register.get(8)
        self.register.set(8, f & 0b11111011 | (v << 2))

    def check_carry(self, buf):
        v = 0
        if buf > 0xff:
            v = 1
        f = self.register.get(8)
        self.register.set(8, f & 0b11111110 | v)

    def check_signed(self, buf):
        v = 0
        if buf < 0 or buf & 0x100 != 0:
            v = 1
        f = self.register.get(8)
        self.register.set(8, f & 0b01111111 | (v << 7))

    def check_zero(self, buf):
        v = 0
        if buf == 0:
            v = 1
        f = self.register.get(8)
        self.register.set(8, f & 0b10111111 | (v << 6))


    def get_parity(self):
        f = self.register.get(8)
        return (f >> 2) & 1

    def get_carry(self):
        f = self.register.get(8)
        return f & 1

    def get_signed(self):
        f = self.register.get(8)
        return (f >> 7) & 1

    def get_zero(self):
        f = self.register.get(8)
        return (f >> 6) & 1


    def set_carry(self, v):
        self.register.set(8, f & 0b11111110 | v)

    def set_signed(self, v):
        self.register.set(8, f & 0b01111111 | (v << 7))


    # group 0 - 3
    # con 0 or 1
    def check_flg(self, op):
        f = op & 1
        t = op >> 1
        if t == 0: # Z
            return self.get_zero() == f
        elif t == 1: # C
            return self.get_carry() == f
        elif t == 2: # P
            return self.get_parity() == f
        elif t == 3: # S
            return self.get_signed() == f


    def calc(self, op, v):
        buf = 0
        acm = self.register.get(7)
        if op == 0: # ADD
            buf = acm + v
        elif op == 1: # ADC
            c = self.get_carry()
            buf = acm + v + c
        elif op == 2: # SUB
            buf = acm - v
        elif op == 3: # SBB
            c = self.get_carry()
            buf = acm - v - c
        elif op == 4: # ANA
            buf = acm & v
        elif op == 5: # XRA
            buf = acm ^ v
        elif op == 6: # ORA
            buf = acm | v
        elif op == 7: # CMP
            buf = acm - v

        self.check_parity(buf)
        self.check_signed(buf)
        self.check_zero(buf)

        if op not in (1, 3):
            self.check_carry(buf)
        if op != 7:
            self.register.set(7, buf)


    def mainloop(self):
        while self.running:
            opcode = self.fetch()
            op1 = opcode >> 6
            op2 = opcode >> 3 & 0x07
            op3 = opcode & 0x07
            
            if op1 == 0:
                if op3 == 0: #NOP 保留
                    pass
                
                elif op3 == 1: #LXI DAD
                    op4 = op2 & 1
                    op2 = op2 >> 1
                    if op2 == 3: # AF指定の場合SPに修正
                        op2 = 4
                    if op4 == 0: #LXI
                        v = self.fetch_word()
                        self.register.set_word(op2, v)
                    else: #DAD
                        hl = self.register.get_word(2)
                        v = self.register.get_word(op2)
                        self.register.set_word(2, d16(hl + v))
                        
                elif op3 == 2: #STAX SHLD STA LDAX LHLD LDA
                    if op2 in (0, 2): #STAX
                        addr = self.register.get_word(op2 >> 1)
                        a = self.register.get(7)
                        self.memory.set(addr, a)
                    elif op2 == 4: #SHLD
                        hl = self.register.get_word(2)
                        addr = self.fetch_word()
                        self.memory.set_word(addr, hl)
                    elif op2 == 6: #STA
                        a = self.register.get(7)
                        addr = self.fetch_word()
                        self.memory.set(addr, a)
                    elif op2 in (1, 3): #LDAX
                        addr = self.register.get_word((op2 - 1) >> 1)
                        v = self.memory.get(addr)
                        self.register.set(7, v)
                    elif op2 == 5: #LHLD
                        addr = self.fetch_word()
                        v = self.memory.get_word(addr)
                        self.register.set_word(2, v)
                    elif op2 == 7: #LDA
                        addr = self.fetch_word()
                        v = self.memory.get(addr)
                        self.register.set(7, v)

                elif op3 == 3: #INX DCX
                    op4 = op2 & 1
                    op2 = op2 >> 1
                    if op4 == 0: #INX
                        v = self.register.get_word(op2) + 1
                        self.register.set_word(op2, d16(v))
                    else: #DCX
                        v = self.register.get_word(op2) - 1
                        self.register.set_word(op2, d16(v))
                        
                elif op3 == 4: #INR
                    v = self.register.get(op2) + 1
                    self.check_parity(v)
                    self.check_signed(v)
                    self.check_zero(v)
                    self.register.set(op2, d8(v))
                    
                elif op3 == 5: #DCR
                    v = self.register.get(op2) - 1
                    self.check_parity(v)
                    self.check_signed(v)
                    self.check_zero(v)
                    self.register.set(op2, d8(v))
                    
                elif op3 == 6: #MVI
                    v = self.fetch()
                    self.register.set(op2, v)
                    
                elif op3 == 7: # RLC RAL DAA STC RRC RAR CMA CMC
                    if op2 == 0: #RLC
                        a = self.register.get(7)
                        c = a >> 7 & 1
                        a = (a << 1) & 0xff | c
                        self.set_carry(c)
                        self.register.set(7, a)

                    elif op2 == 1: #RRC
                        a = self.register.get(7)
                        c = a & 1
                        a = (a >> 1) | (c << 7)
                        self.set_carry(c)
                        self.register.set(7, a)
                        
                    elif op2 == 2: #RAL
                        a = self.register.get(7)
                        b = a >> 7 & 1
                        c = self.get_carry()
                        a = (a << 1) & 0xff | c
                        self.register.set(7, a)
                        self.set_carry(b)

                    elif op2 == 3: #RAR
                        a = self.register.get(7)
                        b = a & 1
                        c = self.get_carry()
                        a = (a >> 1) | (c << 7)
                        self.register.set(7, a)
                        self.set_carry(b)

                    elif op2 == 4: #DAA
                        pass
                    
                    elif op2 == 5: #CMA
                        a = self.register.get(7) ^ 0xff
                        self.register.set(7, a)

                    elif op2 == 6: #STC
                        self.set_carry(1)
                        
                    elif op2 == 7: #CMC
                        c = self.get_carry()
                        self.set_carry(c ^ 1)

            
            elif op1 == 1: # MOV / HLT
                if opcode == 0x76: # HLT
                    self.running = False
                else: # MOV
                    self.register.set(op2, self.register.get(op3))


            elif op1 == 2: # ADD/ ADC / SUB / SBB / ANA / XRA / ORA / CMP
                self.calc(op2, self.register.get(op3))

            elif op1 == 3:
                if op3 == 0: # RNZ RNC RPO RP RZ RC RPE RM
                    if self.check_flg(op2):
                        addr = self.pop()
                        self.register.set_word(5, addr)
                        
                elif op3 == 1: #POP RET PCHL SPHL
                    b1 = op2 & 1
                    b2 = op2 >> 1
                    if b1 == 0: # POP   
                        v = self.pop()
                        self.register.set_word(b2, v)
                    else: # RET PCHL SPHL
                        if b2 == 2: # PCHL
                            hl = self.register.get_word(2)
                            self.register.set_word(5, hl)
                        elif b2 == 3: # SPHL
                            hl = self.register.get_word(2)
                            self.register.set_word(4, hl)
                        else: # RET
                            addr = self.pop()
                            self.register.set_word(5, addr)
                        
                elif op3 == 2: #JNZ JNC JPO JP JZ JC JPE JM
                    addr = self.fetch_word()
                    if self.check_flg(op2):
                        self.register.set_word(5, addr)

                elif op3 == 3: #JMP OUT XTHL DI JMP IN XCHG EI
                    if op2 in (0, 1): # JMP
                        addr = self.fetch_word()
                        self.register.set_word(5, addr)
                    elif op2 == 2: #OUT
                        v = self.fetch()
                        self.hardware.out_(self.register.get(7), v)
                    elif op2 == 3: #IN
                        v = self.fetch()
                        self.register.set(7, self.hardware.in_(v))
                    elif op2 == 4: # XTHL
                        v = self.pop()
                        self.push(self.register.get_word(2))
                        self.register.set_word(2, v)
                    elif op2 == 5: # XCHG
                        v = self.register.get_word(1)
                        self.register.set_word(1, self.register.get_word(2))
                        self.register.set_word(2, v)
                        pass
                    elif op2 == 6: # DI
                        pass
                    elif op2 == 7: # EI
                        pass
                
                elif op3 == 4: #CNZ CNC CPO CP CZ CC CPE CM
                    addr = self.fetch_word()
                    if self.check_flg(op2):
                        pc = self.register.get_word(5)
                        self.push(pc)
                        self.register.set_word(5, addr)

                elif op3 == 5: #PUSH CALL
                    b1 = op2 & 1
                    b2 = op2 >> 1
                    if b1 == 0: # PUSH
                        v = self.register.get_word(b2)
                        self.push(v)
                    else: # CALL
                        addr = self.fetch_word()
                        pc = self.register.get_word(5)
                        self.push(pc)
                        self.register.set_word(5, addr)

                elif op3 == 6: # ADI SUI ANI ORI ACI SBI XRI CPI
                    self.calc(op2, self.fetch())
                
                elif op3 == 7: # RST
                    vec = op2 << 3
                    self.register.set_word(5, addr)



            self.register.update()
            time.sleep(0.001)

            #self.register.monitor()
            
class Hardware:
    def __init__(self):
        pass

    def in_(self, n):
        return 0xff

    def out_(self, a, n):
        if n == 5:
            print(chr(a), end="")
        
    

if __name__ == "__main__":
    memory = Memory()
    register = Register(memory)
    hardware = Hardware()
    cpu = CPU(memory=memory, register=register, hardware=hardware)

    """
            ld hl, msg  
    loop:   ld a,(hl)    
            out(5), a
            inc hl
            or a
            jp nz, loop
            halt

    msg:  db "Hello, world!", 0
    """

    pg = (
          0x21, 0x0c, 0x00, 0x7e,  0xd3, 0x05, 0x23, 0xb7,
          0xc2, 0x03, 0x00, 0x76,  0x48, 0x65, 0x6c, 0x6c,
          0x6f, 0x2c, 0x20, 0x77,  0x6f, 0x72, 0x6c, 0x64,
          0x21, 0x0d, 0x0a, 0x00,
          )
    
    for i, p in enumerate(pg):
        memory.set(i, p)

    cpu.mainloop()


    # memory monitor
    #for i in range(0x10):
    #    print(format(memory.get(i + 0xfff0), "02X"), end=" ")
    #print()

問題としてa16という関数がメモリ用に作ってあるのに16bitレジスタの補正にも利用されていた。
MEMORY_SIZEが0xffffではない場合に計算がずれてしまう。
計算用にはd16という関数を作った。
それに従ってa8はd8に名称を変更。

pushで16bit演算の補正を行っていなかったためアドレスがずれてしまう問題があった。pushしてpopを実行すると値が正しく取れなかった。

まだ問題があるかもしれないけれどもテストしきれていない。
Hello, world!を表示するコードについては動いた。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment