ApplePy: An Apple ][ emulator in Python by James Tauber

Afcfefa1f067d10bd021de0cc2e5e806?s=47 PyCon 2013
March 18, 2013

ApplePy: An Apple ][ emulator in Python by James Tauber

Afcfefa1f067d10bd021de0cc2e5e806?s=128

PyCon 2013

March 18, 2013
Tweet

Transcript

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

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

    you can do in software? The Original Apple ][
  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
  6. User Program BASIC Interpreter Lower-level ROM Routines 6502 + I/O

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

  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
  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 ...
  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
  11. ... def TXA(self): self.accumulator = self.x_index

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

    = 0 self.sign_flag = 0 ...
  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
  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)
  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
  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())
  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())
  18. def LDA(self, operand_address): self.accumulator = self.update_nz(self.read_byte(operand_address))

  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
  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)
  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)]
  22. Memory 0x0000 – 0xBFFF RAM 0xC000 – 0xCFFF memory-mapped I/O

    0xD000 – 0xFFFF ROM
  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
  24. Display no separate video RAM scanlines are not sequential in

    memory “Venetian Blind” effect
  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
  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], ...
  27. I/O 0xC000 — the keyboard 0xC010 — keyboard strobe 0xC030

    — toggle the speaker 0xC060 — read the cassette
  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
  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
  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
  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
  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
  33. Disk Drive still not working darn magnets Woz’s brilliance is

    my curse :-)
  34. github.com/ jtauber/ applepy

  35. DEMO

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