Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ApplePy: An Apple ][ emulator in Python

ApplePy: An Apple ][ emulator in Python

ApplePy is an Apple ][ emulator written in Python. It combines emulation of the 6502 microprocessor with emulation of the keyboard, display (including graphics mode), speaker, cassette and disk drive. This talk will provide a background to Apple ][ internals then dive into the Python code and the challenges of emulating hardware.

James Tauber

March 07, 2013
Tweet

More Decks by James Tauber

Other Decks in Programming

Transcript

  1. ApplePy
    An Apple ][ emulator in Python
    James Tauber
    @jtauber

    View Slide

  2. View Slide

  3. 1977: Apple ][
    6502 at 1MHz
    Integer BASIC
    4K RAM ($1298) up to 48K ($2638)
    1979: Apple ][+
    48K RAM standard
    (additional bank-switched 16K available)
    Applesoft BASIC from Microsoft
    1983: Apple /
    /e
    64K RAM standard
    lowercase letters!

    View Slide

  4. innovations in:
    display
    sound
    disk
    why implement in hardware what you
    can do in software?
    The Original Apple ][

    View Slide

  5. May 2001: SourceForge
    implemented decent amount of 6502
    got a ROM booting but Integer BASIC didn’t work
    February 2007: Google Code
    massive refactor of 6502 code
    August 2011: Github
    patches within hours from Greg Hewgill
    completed 6502 support, low and high res graphics,
    sound, cassette and more
    ApplePy

    View Slide

  6. User
    Program
    BASIC Interpreter
    Lower-level ROM Routines
    6502 + I/O
    Logic Gates
    Transistors

    View Slide

  7. User
    Program
    BASIC Interpreter
    Lower-level ROM Routines
    6502 + I/O

    View Slide

  8. 6502 Overview
    3510 transistors
    8-bit data
    16-bit addresses
    3 general and 3 special-purpose registers
    accumulator register (A)
    2 index registers (X, Y)
    status register (P)
    stack pointer (S)
    program counter (PC)
    about 60 commands

    View Slide

  9. class CPU:
    def __init__(self):
    self.accumulator = 0x00
    self.x_index = 0x00
    ...
    self.setup_ops()
    ...
    def setup_ops(self):
    self.ops = [None] * 0x100
    ...
    self.ops[0x8A] = self.TXA
    ...

    View Slide

  10. ...
    def run(self):
    while True:
    op = self.read_byte(self.get_pc())
    self.ops[op]()
    def get_pc(self):
    pc = self.program_counter
    self.program_counter += 1
    return pc

    View Slide

  11. ...
    def TXA(self):
    self.accumulator = self.x_index

    View Slide

  12. def __init__(self):
    ...
    self.carry_flag = 0
    self.zero_flag = 0
    self.overflow_flag = 0
    self.sign_flag = 0
    ...

    View Slide

  13. def TXA(self):
    self.accumulator = self.update_nz(self.x_index)
    def update_nz(self, value):
    value = value % 0x100
    self.zero_flag = 1 if (value == 0) else 0
    self.sign_flag = 1 if (value & 0x80) else 0
    return value

    View Slide

  14. def setup_ops(self):
    ...
    self.ops[0xCA] = self.DEX
    self.ops[0xE8] = self.INX
    ...
    def DEX(self):
    self.x_index = self.update_nz(self.x_index - 1)
    def INX(self):
    self.x_index = self.update_nz(self.x_index + 1)

    View Slide

  15. LDA Addressing Modes
    LDA #$10
    LDA $10
    LDA $1010
    LDA $10,X
    LDA $1010,X
    LDA $1010,Y
    LDA ($1010,X)
    LDA ($1010),Y

    View Slide

  16. self.ops[0xA1] = lambda: self.LDA(self.indirect_x_mode())
    self.ops[0xA5] = lambda: self.LDA(self.zero_page())
    self.ops[0xA9] = lambda: self.LDA(self.immediate_mode())
    self.ops[0xAD] = lambda: self.LDA(self.absolute_mode())
    self.ops[0xB1] = lambda: self.LDA(self.indirect_y_mode())
    self.ops[0xB5] = lambda: self.LDA(self.zero_page_x_mode())
    self.ops[0xB9] = lambda: self.LDA(self.absolute_y_mode())
    self.ops[0xBD] = lambda: self.LDA(self.absolute_x_mode())

    View Slide

  17. def immediate_mode(self):
    return self.get_pc()
    def zero_page_mode(self):
    return self.read_byte(self.get_pc())
    def absolute_mode(self):
    return self.read_word(self.get_pc(2))
    def absolute_x_mode(self):
    return self.absolute_mode() + self.x_index
    def indirect_mode(self):
    return self.read_word_bug(self.absolute_mode())

    View Slide

  18. def LDA(self, operand_address):
    self.accumulator =
    self.update_nz(self.read_byte(operand_address))

    View Slide

  19. def JMP(self, operand_address):
    self.program_counter = operand_address
    def JSR(self, operand_address):
    self.push_word(self.program_counter - 1)
    self.program_counter = operand_address
    def RTS(self):
    self.program_counter = self.pull_word() + 1

    View Slide

  20. def push_byte(self, byte):
    self.write_byte(self.STACK_PAGE + self.stack_pointer, byte)
    self.stack_pointer = (self.stack_pointer - 1) % 0x100
    def push_word(self, word):
    hi, lo = divmod(word, 0x100)
    self.push_byte(hi)
    self.push_byte(lo)

    View Slide

  21. def ADC(self, operand_address):
    a2 = self.accumulator
    a1 = signed(a2)
    m2 = self.read_byte(operand_address)
    m1 = signed(m2)
    # twos complement addition
    result1 = a1 + m1 + self.carry_flag
    # unsigned addition
    result2 = a2 + m2 + self.carry_flag
    self.accumulator = self.update_nzc(result2)
    # perhaps this could be calculated from result2 but result1
    # is more intuitive
    self.overflow_flag = [0,1][(result1>127)|(result1<-128)]

    View Slide

  22. Memory
    0x0000 – 0xBFFF RAM
    0xC000 – 0xCFFF memory-mapped I/O
    0xD000 – 0xFFFF ROM

    View Slide

  23. Original Apple ][ ROM
    $F800-$FFFF (2k)
    Monitor: screen/keyboard I/O, disassembler, memory
    utils, multiply/divide, etc
    $F689-$F7FC (372 bytes)
    sweet-16 interpreter
    $F500-$F63C; $F666-$F668 (320 bytes)
    mini assembler
    $F425-$F4FB;$F63D-$F65D (248 bytes)
    un-used floating point routines
    $E000-$F424 (5k)
    Integer BASIC

    View Slide

  24. Display
    no separate video RAM
    scanlines are not sequential in memory
    “Venetian Blind” effect

    View Slide

  25. base = address - start_text
    hi, lo = divmod(base, 0x80)
    row_group, column = divmod(lo, 0x28)
    row = hi + 8 * row_group
    mode, ch = divmod(value, 0x40)
    There and Back Again
    BASCALC PHA
    LSR A
    AND #$03
    ORA #$04
    STA BASH
    PLA
    AND #$18
    BCC BSCLC2
    ADC #$7F
    BSCLC2 STA BASL
    ASL
    ASL
    ORA BASL
    STA BASL
    RTS

    View Slide

  26. characters = [
    [0b00000, 0b01110, 0b10001, 0b10101,
    0b10111, 0b10110, 0b10000, 0b01111],
    [0b00000, 0b00100, 0b01010, 0b10001,
    0b10001, 0b11111, 0b10001, 0b10001],
    [0b00000, 0b11110, 0b10001, 0b10001,
    0b11110, 0b10001, 0b10001, 0b11110],
    [0b00000, 0b01110, 0b10001, 0b10000,
    0b10000, 0b10000, 0b10001, 0b01110],
    [0b00000, 0b11110, 0b10001, 0b10001,
    0b10001, 0b10001, 0b10001, 0b11110],
    [0b00000, 0b11111, 0b10000, 0b10000,
    0b11110, 0b10000, 0b10000, 0b11111],
    [0b00000, 0b11111, 0b10000, 0b10000,
    0b11110, 0b10000, 0b10000, 0b10000],
    [0b00000, 0b01111, 0b10000, 0b10000,
    0b10000, 0b10011, 0b10001, 0b01111],
    ...

    View Slide

  27. I/O
    0xC000 — the keyboard
    0xC010 — keyboard strobe
    0xC030 — toggle the speaker
    0xC060 — read the cassette

    View Slide

  28. for event in pygame.event.get():
    ...
    if event.type = pygame.KEYDOWN:
    key = ord(event.unicode)
    if event.key = pygame.K_LEFT:
    key = 0x08
    if event.key == pygame.K_RIGHT:
    key = 0x15
    if key:
    if key == 0x7F:
    key = 0x08
    self.softswitches.kbd = 0x80 + (key & 0x7F)
    Keyboard

    View Slide

  29. Speaker
    reading 0xC030 toggles the speaker
    we pass around the cycle count
    everywhere
    when speaker is toggled we note the
    cycle and append to a sound wave array
    everyone once in a while, we play the
    sound wave array

    View Slide

  30. Cassette
    almost the reverse of the speaker
    given a sound wave
    when softswitch is read, convert cycle number
    to how far into the sound wave we are
    keep track of whether we’ve crossed zero since
    last time
    so far works about 10% of the time

    View Slide

  31. Hi-Res Color
    Each row of 280 pixels represented by
    40 bytes
    7 bits are the pixels
    high-bit is the palette
    odd/even bits are different colors
    palette 1: black, green, magenta, white
    palette 2: black, blue, orange, white

    View Slide

  32. Hi-Res Color
    1 0 0 0 1 0 1 0 0 1 0 1 1 1 0 0
    0 0 0 1 1 0 1 0
    try setting this magenta
    and you get this

    View Slide

  33. Disk Drive
    still not working
    darn magnets
    Woz’s brilliance is my curse :-)

    View Slide

  34. github.com/
    jtauber/
    applepy

    View Slide

  35. DEMO

    View Slide

  36. ApplePy
    An Apple ][ emulator in Python
    James Tauber
    @jtauber

    View Slide