Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

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!

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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 ...

Slide 10

Slide 10 text

... 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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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)

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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())

Slide 17

Slide 17 text

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())

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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)

Slide 21

Slide 21 text

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)]

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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], ...

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

github.com/ jtauber/ applepy

Slide 35

Slide 35 text

DEMO

Slide 36

Slide 36 text

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