$30 off During Our Annual Pro Sale. View Details »

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. 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!
  2. innovations in: display sound disk why implement in hardware what

    you can do in software? The Original Apple ][
  3. 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
  4. 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
  5. 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 ...
  6. ... 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
  7. 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
  8. 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)
  9. LDA Addressing Modes LDA #$10 LDA $10 LDA $1010 LDA

    $10,X LDA $1010,X LDA $1010,Y LDA ($1010,X) LDA ($1010),Y
  10. 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())
  11. 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())
  12. 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
  13. 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)
  14. 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)]
  15. 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
  16. 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
  17. 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], ...
  18. I/O 0xC000 — the keyboard 0xC010 — keyboard strobe 0xC030

    — toggle the speaker 0xC060 — read the cassette
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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