From fcb1d585a0995f0c9889da15890d7d1d0e93cf62 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 14 Aug 2011 17:21:03 -0400 Subject: [PATCH 01/11] added test_run to run CPU over a fragment of memory with no UI event handling (for automated testings) --- applepy.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/applepy.py b/applepy.py index 4cab99c..521214a 100644 --- a/applepy.py +++ b/applepy.py @@ -773,6 +773,21 @@ class CPU: pygame.display.flip() update_cycle = 0 + def test_run(self, start, end): + self.program_counter = start + while True: + if self.program_counter == end: + break + op = self.read_pc_byte() + func = self.ops[op] + if func is None: + print "UNKNOWN OP" + print hex(self.program_counter - 1) + print hex(op) + break + else: + self.ops[op]() + #### def get_pc(self, inc=1): From d7035ca4805b40376dc640f056c6983d2537eb66 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 14 Aug 2011 20:26:51 -0400 Subject: [PATCH 02/11] notes on cycle times --- cycle_notes.txt | 137 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 cycle_notes.txt diff --git a/cycle_notes.txt b/cycle_notes.txt new file mode 100644 index 0000000..549cfdd --- /dev/null +++ b/cycle_notes.txt @@ -0,0 +1,137 @@ +There are two ways we could represent cycle information: + +1. just have an array of cycles for each opcode + the adjustment for + page boundary crossing (which seems opcode-specific, oddly) + +2. add cycles in individual methods like those accessing memory and the + operations themselves, to be model *why* something takes the cycles it + does. + +I prefer 2 on the grounds of it being more instructive but it assumes that +the way we do things is closely aligned to the way the 6502 is doing them +internally. Even if we end up having to do 1, I'd love to understand and +document some of the "why". + +What follows is an attempt to "find the patterns" in the cycle times (as +given on http://www.6502.org/tutorials/6502opcodes.html ) + +There are 12 classes of instructions when it comes to cycle times: + + +Class Ia +(followed by ADC, BIT, CMP, CPX, CPY, EOR, LDA, LDX, LDY, SBC, STX, STY) + +immediate 2 +zero page 3 +zero page, x 4 +zero page, y 4 +absolute 4 +absolute, x 4 (+1 if page crossed) +absolute, y 4 (+1 if page crossed) +indirect, x 6 +indirect, y 5 (+1 if page crossed) + +Note 1: the zero page indexed and x-index indirect don't have the page cross +addition because they wrap. + + +Class Ib +(followed by STA) + +zero page 3 +zero page, x 4 +zero page, y 4 +absolute 4 +absolute, x 5 +absolute, y 5 +indirect, x 6 +indirect, y 6 + +Note 2: just like Class Ia BUT takes the cycles it would take if there's a +page cross even if there isn't. + +Question 1: why is this? + + +Class Ic +(followed by AND, ORA) + +immediate 2 +zero page 2 +zero page, x 3 +absolute 4 +absolute, x 4 (+1 if page crossed) +absolute, y 4 (+1 if page crossed) +indirect, x 6 +indirect, y 5 (+1 if page crossed) + +Note 3: just like class Ia except the zero page are a cycle faster + +Question 2: why is this? is it a bug on the webpage? + + +Class II +(followed by ASL, DEC, INC, LSR, ROL, ROR) + +implied 2 +zero page 5 +zero page, x 6 +absolute 6 +absolute, x 7 + +Note 4: looks like class Ib + 2 in the non-implied cases + +Question 3: why does absolute, x not have a page crossing addition? same +reason as for Ib? + + +Class IIIa +(followed by CLC, CLD, CLI, CLV, DEX, DEY, INX, INY, NOP, SEC, SED, SEI, TAX, +TAY, TSX, TXA, TXS, TYA) + +implied 2 + + +Class IIIb +(followed by PHA, PHP) + +implied 3 + + +Class IIIc +(followed by PLA, PLP) + +implied 4 + + +Class IIId +(followed by RTI, RTS) + +implied 6 + + +Class IIIe +(followed by BRK) + +implied 7 + + +Class IV +(followed by BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS) + +branch not taken 2 +branch taken 3 (+1 is page crossed) + + +Class V +(followed by JMP) + +absolute 3 +indirect 5 + + +Class VI +(followed by JSR) + +absolute 6 + From 8e1b71dbca296d0819a21289a325bd5f5b2a8b86 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 14 Aug 2011 20:36:48 -0400 Subject: [PATCH 03/11] typo and formatting fixes in cycle notes --- cycle_notes.txt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cycle_notes.txt b/cycle_notes.txt index 549cfdd..755f49e 100644 --- a/cycle_notes.txt +++ b/cycle_notes.txt @@ -4,8 +4,7 @@ There are two ways we could represent cycle information: page boundary crossing (which seems opcode-specific, oddly) 2. add cycles in individual methods like those accessing memory and the - operations themselves, to be model *why* something takes the cycles it - does. + operations themselves, to model *why* something takes the cycles it does. I prefer 2 on the grounds of it being more instructive but it assumes that the way we do things is closely aligned to the way the 6502 is doing them @@ -89,31 +88,31 @@ Class IIIa (followed by CLC, CLD, CLI, CLV, DEX, DEY, INX, INY, NOP, SEC, SED, SEI, TAX, TAY, TSX, TXA, TXS, TYA) -implied 2 +implied 2 Class IIIb (followed by PHA, PHP) -implied 3 +implied 3 Class IIIc (followed by PLA, PLP) -implied 4 +implied 4 Class IIId (followed by RTI, RTS) -implied 6 +implied 6 Class IIIe (followed by BRK) -implied 7 +implied 7 Class IV @@ -133,5 +132,5 @@ indirect 5 Class VI (followed by JSR) -absolute 6 +absolute 6 From 2caed7b36d4fca6759f9e7eb893f9f4fd1123a95 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 14 Aug 2011 20:57:22 -0400 Subject: [PATCH 04/11] updated notes, fixing what seems to a mistake on the webpage I referenced --- cycle_notes.txt | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/cycle_notes.txt b/cycle_notes.txt index 755f49e..740eb18 100644 --- a/cycle_notes.txt +++ b/cycle_notes.txt @@ -14,11 +14,15 @@ document some of the "why". What follows is an attempt to "find the patterns" in the cycle times (as given on http://www.6502.org/tutorials/6502opcodes.html ) -There are 12 classes of instructions when it comes to cycle times: +NOTE: there appears to be an error in AND and ORA zero page timings on that +webpage given above. I've now corrected this below. + +There are 11 classes of instructions when it comes to cycle times: Class Ia -(followed by ADC, BIT, CMP, CPX, CPY, EOR, LDA, LDX, LDY, SBC, STX, STY) +(followed by ADC, AND, BIT, CMP, CPX, CPY, EOR, LDA, LDX, LDY, ORA, SBC, STX, +STY) immediate 2 zero page 3 @@ -52,23 +56,6 @@ page cross even if there isn't. Question 1: why is this? -Class Ic -(followed by AND, ORA) - -immediate 2 -zero page 2 -zero page, x 3 -absolute 4 -absolute, x 4 (+1 if page crossed) -absolute, y 4 (+1 if page crossed) -indirect, x 6 -indirect, y 5 (+1 if page crossed) - -Note 3: just like class Ia except the zero page are a cycle faster - -Question 2: why is this? is it a bug on the webpage? - - Class II (followed by ASL, DEC, INC, LSR, ROL, ROR) @@ -80,7 +67,7 @@ absolute, x 7 Note 4: looks like class Ib + 2 in the non-implied cases -Question 3: why does absolute, x not have a page crossing addition? same +Question 2: why does absolute, x not have a page crossing addition? same reason as for Ib? From 12671d81cb3304676fe2f062941b8c9486f7e75d Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 14 Aug 2011 21:37:34 -0400 Subject: [PATCH 05/11] worked out why STA seemed an exception --- cycle_notes.txt | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/cycle_notes.txt b/cycle_notes.txt index 740eb18..15ab7aa 100644 --- a/cycle_notes.txt +++ b/cycle_notes.txt @@ -17,43 +17,28 @@ given on http://www.6502.org/tutorials/6502opcodes.html ) NOTE: there appears to be an error in AND and ORA zero page timings on that webpage given above. I've now corrected this below. -There are 11 classes of instructions when it comes to cycle times: +There are 10 classes of instructions when it comes to cycle times: -Class Ia -(followed by ADC, AND, BIT, CMP, CPX, CPY, EOR, LDA, LDX, LDY, ORA, SBC, STX, -STY) +Class I +(followed by ADC, AND, BIT, CMP, CPX, CPY, EOR, LDA, LDX, LDY, ORA, SBC, STA, +STX, STY) immediate 2 zero page 3 zero page, x 4 zero page, y 4 absolute 4 -absolute, x 4 (+1 if page crossed) -absolute, y 4 (+1 if page crossed) +absolute, x 4 (+1 if page crossed or writing) +absolute, y 4 (+1 if page crossed or writing) indirect, x 6 -indirect, y 5 (+1 if page crossed) +indirect, y 5 (+1 if page crossed or writing) Note 1: the zero page indexed and x-index indirect don't have the page cross addition because they wrap. - -Class Ib -(followed by STA) - -zero page 3 -zero page, x 4 -zero page, y 4 -absolute 4 -absolute, x 5 -absolute, y 5 -indirect, x 6 -indirect, y 6 - -Note 2: just like Class Ia BUT takes the cycles it would take if there's a -page cross even if there isn't. - -Question 1: why is this? +Note 2: writes to indexed non-zero-page memory (e.g. STA) have the +1 even +if not page crossing. Class II @@ -65,10 +50,8 @@ zero page, x 6 absolute 6 absolute, x 7 -Note 4: looks like class Ib + 2 in the non-implied cases - -Question 2: why does absolute, x not have a page crossing addition? same -reason as for Ib? +Note 3: looks like class I +2 (with the absolute, x +1 even if not page +crossing) Class IIIa From e2211e4189f7a8dbf2e822330ee195bbd3a5e7fc Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 14 Aug 2011 21:50:55 -0400 Subject: [PATCH 06/11] more groking of why memory-based ASL, DEC, INC, LSR, ROL and ROR take what they take --- cycle_notes.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cycle_notes.txt b/cycle_notes.txt index 15ab7aa..bd19f16 100644 --- a/cycle_notes.txt +++ b/cycle_notes.txt @@ -50,8 +50,10 @@ zero page, x 6 absolute 6 absolute, x 7 -Note 3: looks like class I +2 (with the absolute, x +1 even if not page -crossing) +Note 3: these take 2 cycles longer than Class I because they involve a +read-modify-write. Because the absolute, x is a write to an indexed +non-zero-page memory location, there is an additional +1 even if not page +crossing (see Note 2) Class IIIa From 1159cef81ca1f365061f09f6c1298b975c8612e7 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 14 Aug 2011 23:45:10 -0400 Subject: [PATCH 07/11] added notes on implementation that seems to give the right result --- cycle_notes.txt | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/cycle_notes.txt b/cycle_notes.txt index bd19f16..05cf883 100644 --- a/cycle_notes.txt +++ b/cycle_notes.txt @@ -91,7 +91,7 @@ Class IV (followed by BCC, BCS, BEQ, BMI, BNE, BPL, BVC, BVS) branch not taken 2 -branch taken 3 (+1 is page crossed) +branch taken 3 (+1 if page crossed) Class V @@ -106,3 +106,33 @@ Class VI absolute 6 + + +This seems a possible implementation (not yet considering page boundaries): + +1. all instructions start with 2 +2. absolute (including absolute indexed) adds 2 +3. absolute indexed adds an additional 1 *if* instruction is of RMW type +4. zero page (including zero page indexed) adds 1 +5. zero page indexed adds an addition 1 +6. indirect (JMP) adds 4 +7. indirect x adds 4 +8. indirect y adds 3 plus an additional 1 *if* instruction is of RMW type +9. ASL, LSR, ROL, ROR add 2 cycles if not implied +10. JMP subtracts 1 cycle +11. JSR adds 2 +12. RTS adds 4 +13. branches add 1 if taken +14. DEC and INC add 2 cycles +15. PHA and PHP add 1 cycle +16. PLA and PLP add 2 cycles +17. BRK adds 5 cycles +18. RTI adds 4 cycles + +RMW instructions are the absolute,x of ASL, DEC, INC, LSR ROL, ROR and STA +as well as indirect,y and absolute,y of STA + + +It may be possible to simplify even further given the particular functions +some of these share in command (where the cycle change could be placed +instead) From a92dcfecd3343f5b70c11c8e27b269639ee900d8 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 14 Aug 2011 23:50:34 -0400 Subject: [PATCH 08/11] implemented cycle calculation (except for page boundary crossing) --- applepy.py | 65 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/applepy.py b/applepy.py index 521214a..a858913 100644 --- a/applepy.py +++ b/applepy.py @@ -583,6 +583,8 @@ class CPU: self.stack_pointer = 0xFF + self.cycles = 0 + self.setup_ops() self.reset() @@ -604,7 +606,7 @@ class CPU: self.ops[0x18] = lambda: self.CLC() self.ops[0x19] = lambda: self.ORA(self.absolute_y_mode()) self.ops[0x1D] = lambda: self.ORA(self.absolute_x_mode()) - self.ops[0x1E] = lambda: self.ASL(self.absolute_x_mode()) + self.ops[0x1E] = lambda: self.ASL(self.absolute_x_mode(rmw=True)) self.ops[0x20] = lambda: self.JSR(self.absolute_mode()) self.ops[0x21] = lambda: self.AND(self.indirect_x_mode()) self.ops[0x24] = lambda: self.BIT(self.zero_page_mode()) @@ -623,7 +625,7 @@ class CPU: self.ops[0x38] = lambda: self.SEC() self.ops[0x39] = lambda: self.AND(self.absolute_y_mode()) self.ops[0x3D] = lambda: self.AND(self.absolute_x_mode()) - self.ops[0x3E] = lambda: self.ROL(self.absolute_x_mode()) + self.ops[0x3E] = lambda: self.ROL(self.absolute_x_mode(rmw=True)) self.ops[0x40] = lambda: self.RTI() self.ops[0x41] = lambda: self.EOR(self.indirect_x_mode()) self.ops[0x45] = lambda: self.EOR(self.zero_page_mode()) @@ -641,7 +643,7 @@ class CPU: self.ops[0x58] = lambda: self.CLI() self.ops[0x59] = lambda: self.EOR(self.absolute_y_mode()) self.ops[0x5D] = lambda: self.EOR(self.absolute_x_mode()) - self.ops[0x5E] = lambda: self.LSR(self.absolute_x_mode()) + self.ops[0x5E] = lambda: self.LSR(self.absolute_x_mode(rmw=True)) self.ops[0x60] = lambda: self.RTS() self.ops[0x61] = lambda: self.ADC(self.indirect_x_mode()) self.ops[0x65] = lambda: self.ADC(self.zero_page_mode()) @@ -659,7 +661,7 @@ class CPU: self.ops[0x78] = lambda: self.SEI() self.ops[0x79] = lambda: self.ADC(self.absolute_y_mode()) self.ops[0x7D] = lambda: self.ADC(self.absolute_x_mode()) - self.ops[0x7E] = lambda: self.ROR(self.absolute_x_mode()) + self.ops[0x7E] = lambda: self.ROR(self.absolute_x_mode(rmw=True)) self.ops[0x81] = lambda: self.STA(self.indirect_x_mode()) self.ops[0x84] = lambda: self.STY(self.zero_page_mode()) self.ops[0x85] = lambda: self.STA(self.zero_page_mode()) @@ -670,14 +672,14 @@ class CPU: self.ops[0x8D] = lambda: self.STA(self.absolute_mode()) self.ops[0x8E] = lambda: self.STX(self.absolute_mode()) self.ops[0x90] = lambda: self.BCC(self.relative_mode()) - self.ops[0x91] = lambda: self.STA(self.indirect_y_mode()) + self.ops[0x91] = lambda: self.STA(self.indirect_y_mode(rmw=True)) self.ops[0x94] = lambda: self.STY(self.zero_page_x_mode()) self.ops[0x95] = lambda: self.STA(self.zero_page_x_mode()) self.ops[0x96] = lambda: self.STX(self.zero_page_y_mode()) self.ops[0x98] = lambda: self.TYA() - self.ops[0x99] = lambda: self.STA(self.absolute_y_mode()) + self.ops[0x99] = lambda: self.STA(self.absolute_y_mode(rmw=True)) self.ops[0x9A] = lambda: self.TXS() - self.ops[0x9D] = lambda: self.STA(self.absolute_x_mode()) + self.ops[0x9D] = lambda: self.STA(self.absolute_x_mode(rmw=True)) self.ops[0xA0] = lambda: self.LDY(self.immediate_mode()) self.ops[0xA1] = lambda: self.LDA(self.indirect_x_mode()) self.ops[0xA2] = lambda: self.LDX(self.immediate_mode()) @@ -719,7 +721,7 @@ class CPU: self.ops[0xD8] = lambda: self.CLD() self.ops[0xD9] = lambda: self.CMP(self.absolute_y_mode()) self.ops[0xDD] = lambda: self.CMP(self.absolute_x_mode()) - self.ops[0xDE] = lambda: self.DEC(self.absolute_x_mode()) + self.ops[0xDE] = lambda: self.DEC(self.absolute_x_mode(rmw=True)) self.ops[0xE0] = lambda: self.CPX(self.immediate_mode()) self.ops[0xE1] = lambda: self.SBC(self.indirect_x_mode()) self.ops[0xE4] = lambda: self.CPX(self.zero_page_mode()) @@ -738,7 +740,7 @@ class CPU: self.ops[0xF8] = lambda: self.SED() self.ops[0xF9] = lambda: self.SBC(self.absolute_y_mode()) self.ops[0xFD] = lambda: self.SBC(self.absolute_x_mode()) - self.ops[0xFE] = lambda: self.INC(self.absolute_x_mode()) + self.ops[0xFE] = lambda: self.INC(self.absolute_x_mode(rmw=True)) def reset(self): self.program_counter = self.memory.read_word(self.RESET_VECTOR) @@ -747,6 +749,7 @@ class CPU: update_cycle = 0 quit = False while not quit: + self.cycles += 2 # all instructions take this as a minimum op = self.read_pc_byte() func = self.ops[op] if func is None: @@ -776,6 +779,7 @@ class CPU: def test_run(self, start, end): self.program_counter = start while True: + self.cycles += 2 # all instructions take this as a minimum if self.program_counter == end: break op = self.read_pc_byte() @@ -841,30 +845,44 @@ class CPU: return self.get_pc() def absolute_mode(self): + self.cycles += 2 return self.read_pc_word() - def absolute_x_mode(self): + def absolute_x_mode(self, rmw=False): + if rmw: + self.cycles += 1 return self.absolute_mode() + self.x_index - def absolute_y_mode(self): + def absolute_y_mode(self, rmw=False): + if rmw: + self.cycles += 1 return self.absolute_mode() + self.y_index def zero_page_mode(self): + self.cycles += 1 return self.read_pc_byte() def zero_page_x_mode(self): + self.cycles += 1 return (self.zero_page_mode() + self.x_index) % 0x100 def zero_page_y_mode(self): + self.cycles += 1 return (self.zero_page_mode() + self.y_index) % 0x100 def indirect_mode(self): + self.cycles += 2 return self.memory.read_word_bug(self.absolute_mode()) def indirect_x_mode(self): + self.cycles += 4 return self.memory.read_word_bug((self.read_pc_byte() + self.x_index) % 0x100) - def indirect_y_mode(self): + def indirect_y_mode(self, rmw=False): + if rmw: + self.cycles += 4 + else: + self.cycles += 3 return self.memory.read_word_bug(self.read_pc_byte()) + self.y_index def relative_mode(self): @@ -931,6 +949,7 @@ class CPU: if operand_address is None: self.accumulator = self.update_nzc(self.accumulator << 1) else: + self.cycles += 2 self.memory.write_byte(operand_address, self.update_nzc(self.memory.read_byte(operand_address) << 1)) def ROL(self, operand_address=None): @@ -940,6 +959,7 @@ class CPU: a = a | 0x01 self.accumulator = self.update_nzc(a) else: + self.cycles += 2 m = self.memory.read_byte(operand_address) << 1 if self.carry_flag: m = m | 0x01 @@ -952,6 +972,7 @@ class CPU: self.carry_flag = self.accumulator % 2 self.accumulator = self.update_nz(self.accumulator >> 1) else: + self.cycles += 2 m = self.memory.read_byte(operand_address) if self.carry_flag: m = m | 0x100 @@ -963,53 +984,65 @@ class CPU: self.carry_flag = self.accumulator % 2 self.accumulator = self.update_nz(self.accumulator >> 1) else: + self.cycles += 2 self.carry_flag = self.memory.read_byte(operand_address) % 2 self.memory.write_byte(operand_address, self.update_nz(self.memory.read_byte(operand_address) >> 1)) # JUMPS / RETURNS def JMP(self, operand_address): + self.cycles -= 1 self.program_counter = operand_address def JSR(self, operand_address): + self.cycles += 2 self.push_word(self.program_counter - 1) self.program_counter = operand_address def RTS(self): + self.cycles += 4 self.program_counter = self.pull_word() + 1 # BRANCHES def BCC(self, operand_address): if not self.carry_flag: + self.cycles += 1 self.program_counter = operand_address def BCS(self, operand_address): if self.carry_flag: + self.cycles += 1 self.program_counter = operand_address def BEQ(self, operand_address): if self.zero_flag: + self.cycles += 1 self.program_counter = operand_address def BNE(self, operand_address): if not self.zero_flag: + self.cycles += 1 self.program_counter = operand_address def BMI(self, operand_address): if self.sign_flag: + self.cycles += 1 self.program_counter = operand_address def BPL(self, operand_address): if not self.sign_flag: + self.cycles += 1 self.program_counter = operand_address def BVC(self, operand_address): if not self.overflow_flag: + self.cycles += 1 self.program_counter = operand_address def BVS(self, operand_address): if self.overflow_flag: + self.cycles += 1 self.program_counter = operand_address # SET / CLEAR FLAGS @@ -1038,6 +1071,7 @@ class CPU: # INCREMENT / DECREMENT def DEC(self, operand_address): + self.cycles += 2 self.memory.write_byte(operand_address, self.update_nz(self.memory.read_byte(operand_address) - 1)) def DEX(self): @@ -1047,6 +1081,7 @@ class CPU: self.y_index = self.update_nz(self.y_index - 1) def INC(self, operand_address): + self.cycles += 2 self.memory.write_byte(operand_address, self.update_nz(self.memory.read_byte(operand_address) + 1)) def INX(self): @@ -1058,15 +1093,19 @@ class CPU: # PUSH / PULL def PHA(self): + self.cycles += 1 self.push_byte(self.accumulator) def PHP(self): + self.cycles += 1 self.push_byte(self.status_as_byte()) def PLA(self): + self.cycles += 2 self.accumulator = self.update_nz(self.pull_byte()) def PLP(self): + self.cycles += 2 self.status_from_byte(self.pull_byte()) # LOGIC @@ -1154,12 +1193,14 @@ class CPU: pass def BRK(self): + self.cycles += 5 self.push_word(self.program_counter + 1) self.push_byte(self.status_as_byte()) self.program_counter = self.memory.read_word(0xFFFE) self.break_flag = 1 def RTI(self): + self.cycles += 4 self.status_from_byte(self.pull_byte()) self.program_counter = self.pull_word() From be914223174656e4480e40d804dd3dd454c90111 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Mon, 15 Aug 2011 00:21:20 -0400 Subject: [PATCH 09/11] refactored memory access so cycle can be passed in --- applepy.py | 83 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/applepy.py b/applepy.py index a858913..d7a5fb0 100644 --- a/applepy.py +++ b/applepy.py @@ -271,7 +271,7 @@ class SoftSwitches: self.kbd = 0x00 self.display = display - def read_byte(self, address): + def read_byte(self, cycle, address): assert 0xC000 <= address <= 0xCFFF if address == 0xC000: return self.kbd @@ -316,22 +316,22 @@ class Memory: if address < 0xC000: self.ram.load(address, data) - def read_byte(self, address): + def read_byte(self, cycle, address): if address < 0xC000: return self.ram.read_byte(address) elif address < 0xD000: - return self.softswitches.read_byte(address) + return self.softswitches.read_byte(cycle, address) else: return self.rom.read_byte(address) - def read_word(self, address): - return self.read_byte(address) + (self.read_byte(address + 1) << 8) + def read_word(self, cycle, address): + return self.read_byte(cycle, address) + (self.read_byte(cycle + 1, address + 1) << 8) - def read_word_bug(self, address): + def read_word_bug(self, cycle, address): if address % 0x100 == 0xFF: - return self.read_byte(address) + (self.read_byte(address & 0xFF00) << 8) + return self.read_byte(cycle, address) + (self.read_byte(cycle + 1, address & 0xFF00) << 8) else: - return self.read_word(address) + return self.read_word(cycle, address) def write_byte(self, address, value): if address < 0xC000: @@ -743,7 +743,7 @@ class CPU: self.ops[0xFE] = lambda: self.INC(self.absolute_x_mode(rmw=True)) def reset(self): - self.program_counter = self.memory.read_word(self.RESET_VECTOR) + self.program_counter = self.read_word(self.RESET_VECTOR) def run(self): update_cycle = 0 @@ -799,11 +799,20 @@ class CPU: self.program_counter += inc return pc + def read_byte(self, address): + return self.memory.read_byte(self.cycles, address) + + def read_word(self, address): + return self.memory.read_word(self.cycles, address) + + def read_word_bug(self, address): + return self.memory.read_word_bug(self.cycles, address) + def read_pc_byte(self): - return self.memory.read_byte(self.get_pc()) + return self.read_byte(self.get_pc()) def read_pc_word(self): - return self.memory.read_word(self.get_pc(2)) + return self.read_word(self.get_pc(2)) #### @@ -827,7 +836,7 @@ class CPU: def pull_byte(self): self.stack_pointer = (self.stack_pointer + 1) % 0x100 - return self.memory.read_byte(self.STACK_PAGE + self.stack_pointer) + return self.read_byte(self.STACK_PAGE + self.stack_pointer) def push_word(self, word): hi, lo = divmod(word, 0x100) @@ -837,7 +846,7 @@ class CPU: def pull_word(self): s = self.STACK_PAGE + self.stack_pointer + 1 self.stack_pointer += 2 - return self.memory.read_word(s) + return self.read_word(s) #### @@ -872,22 +881,22 @@ class CPU: def indirect_mode(self): self.cycles += 2 - return self.memory.read_word_bug(self.absolute_mode()) + return self.read_word_bug(self.absolute_mode()) def indirect_x_mode(self): self.cycles += 4 - return self.memory.read_word_bug((self.read_pc_byte() + self.x_index) % 0x100) + return self.read_word_bug((self.read_pc_byte() + self.x_index) % 0x100) def indirect_y_mode(self, rmw=False): if rmw: self.cycles += 4 else: self.cycles += 3 - return self.memory.read_word_bug(self.read_pc_byte()) + self.y_index + return self.read_word_bug(self.read_pc_byte()) + self.y_index def relative_mode(self): pc = self.get_pc() - return pc + 1 + signed(self.memory.read_byte(pc)) + return pc + 1 + signed(self.read_byte(pc)) #### @@ -906,13 +915,13 @@ class CPU: # LOAD / STORE def LDA(self, operand_address): - self.accumulator = self.update_nz(self.memory.read_byte(operand_address)) + self.accumulator = self.update_nz(self.read_byte(operand_address)) def LDX(self, operand_address): - self.x_index = self.update_nz(self.memory.read_byte(operand_address)) + self.x_index = self.update_nz(self.read_byte(operand_address)) def LDY(self, operand_address): - self.y_index = self.update_nz(self.memory.read_byte(operand_address)) + self.y_index = self.update_nz(self.read_byte(operand_address)) def STA(self, operand_address): self.memory.write_byte(operand_address, self.accumulator) @@ -950,7 +959,7 @@ class CPU: self.accumulator = self.update_nzc(self.accumulator << 1) else: self.cycles += 2 - self.memory.write_byte(operand_address, self.update_nzc(self.memory.read_byte(operand_address) << 1)) + self.memory.write_byte(operand_address, self.update_nzc(self.read_byte(operand_address) << 1)) def ROL(self, operand_address=None): if operand_address is None: @@ -960,7 +969,7 @@ class CPU: self.accumulator = self.update_nzc(a) else: self.cycles += 2 - m = self.memory.read_byte(operand_address) << 1 + m = self.read_byte(operand_address) << 1 if self.carry_flag: m = m | 0x01 self.memory.write_byte(operand_address, self.update_nzc(m)) @@ -973,7 +982,7 @@ class CPU: self.accumulator = self.update_nz(self.accumulator >> 1) else: self.cycles += 2 - m = self.memory.read_byte(operand_address) + m = self.read_byte(operand_address) if self.carry_flag: m = m | 0x100 self.carry_flag = m % 2 @@ -985,8 +994,8 @@ class CPU: self.accumulator = self.update_nz(self.accumulator >> 1) else: self.cycles += 2 - self.carry_flag = self.memory.read_byte(operand_address) % 2 - self.memory.write_byte(operand_address, self.update_nz(self.memory.read_byte(operand_address) >> 1)) + self.carry_flag = self.read_byte(operand_address) % 2 + self.memory.write_byte(operand_address, self.update_nz(self.read_byte(operand_address) >> 1)) # JUMPS / RETURNS @@ -1072,7 +1081,7 @@ class CPU: def DEC(self, operand_address): self.cycles += 2 - self.memory.write_byte(operand_address, self.update_nz(self.memory.read_byte(operand_address) - 1)) + self.memory.write_byte(operand_address, self.update_nz(self.read_byte(operand_address) - 1)) def DEX(self): self.x_index = self.update_nz(self.x_index - 1) @@ -1082,7 +1091,7 @@ class CPU: def INC(self, operand_address): self.cycles += 2 - self.memory.write_byte(operand_address, self.update_nz(self.memory.read_byte(operand_address) + 1)) + self.memory.write_byte(operand_address, self.update_nz(self.read_byte(operand_address) + 1)) def INX(self): self.x_index = self.update_nz(self.x_index + 1) @@ -1111,13 +1120,13 @@ class CPU: # LOGIC def AND(self, operand_address): - self.accumulator = self.update_nz(self.accumulator & self.memory.read_byte(operand_address)) + self.accumulator = self.update_nz(self.accumulator & self.read_byte(operand_address)) def ORA(self, operand_address): - self.accumulator = self.update_nz(self.accumulator | self.memory.read_byte(operand_address)) + self.accumulator = self.update_nz(self.accumulator | self.read_byte(operand_address)) def EOR(self, operand_address): - self.accumulator = self.update_nz(self.accumulator ^ self.memory.read_byte(operand_address)) + self.accumulator = self.update_nz(self.accumulator ^ self.read_byte(operand_address)) # ARITHMETIC @@ -1127,7 +1136,7 @@ class CPU: a2 = self.accumulator a1 = signed(a2) - m2 = self.memory.read_byte(operand_address) + m2 = self.read_byte(operand_address) m1 = signed(m2) # twos complement addition @@ -1147,7 +1156,7 @@ class CPU: a2 = self.accumulator a1 = signed(a2) - m2 = self.memory.read_byte(operand_address) + m2 = self.read_byte(operand_address) m1 = signed(m2) # twos complement subtraction @@ -1165,7 +1174,7 @@ class CPU: # BIT def BIT(self, operand_address): - value = self.memory.read_byte(operand_address) + value = self.read_byte(operand_address) self.sign_flag = ((value >> 7) % 2) # bit 7 self.overflow_flag = ((value >> 6) % 2) # bit 6 self.zero_flag = [0, 1][((self.accumulator & value) == 0)] @@ -1173,17 +1182,17 @@ class CPU: # COMPARISON def CMP(self, operand_address): - result = self.accumulator - self.memory.read_byte(operand_address) + result = self.accumulator - self.read_byte(operand_address) self.carry_flag = [0, 1][(result >= 0)] self.update_nz(result) def CPX(self, operand_address): - result = self.x_index - self.memory.read_byte(operand_address) + result = self.x_index - self.read_byte(operand_address) self.carry_flag = [0, 1][(result >= 0)] self.update_nz(result) def CPY(self, operand_address): - result = self.y_index - self.memory.read_byte(operand_address) + result = self.y_index - self.read_byte(operand_address) self.carry_flag = [0, 1][(result >= 0)] self.update_nz(result) @@ -1196,7 +1205,7 @@ class CPU: self.cycles += 5 self.push_word(self.program_counter + 1) self.push_byte(self.status_as_byte()) - self.program_counter = self.memory.read_word(0xFFFE) + self.program_counter = self.read_word(0xFFFE) self.break_flag = 1 def RTI(self): From 35c0e699915733b9de3f11cad3dbf0c728156b84 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Mon, 15 Aug 2011 00:26:52 -0400 Subject: [PATCH 10/11] pass in None for cycles so tests run --- tests.py | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/tests.py b/tests.py index 05b2645..11fa2f4 100644 --- a/tests.py +++ b/tests.py @@ -9,17 +9,17 @@ class TestMemory(unittest.TestCase): def test_load(self): self.memory.load(0x1000, [0x01, 0x02, 0x03]) - self.assertEqual(self.memory.read_byte(0x1000), 0x01) - self.assertEqual(self.memory.read_byte(0x1001), 0x02) - self.assertEqual(self.memory.read_byte(0x1002), 0x03) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x01) + self.assertEqual(self.memory.read_byte(None, 0x1001), 0x02) + self.assertEqual(self.memory.read_byte(None, 0x1002), 0x03) def test_write(self): self.memory.write_byte(0x1000, 0x11) self.memory.write_byte(0x1001, 0x12) self.memory.write_byte(0x1002, 0x13) - self.assertEqual(self.memory.read_byte(0x1000), 0x11) - self.assertEqual(self.memory.read_byte(0x1001), 0x12) - self.assertEqual(self.memory.read_byte(0x1002), 0x13) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x11) + self.assertEqual(self.memory.read_byte(None, 0x1001), 0x12) + self.assertEqual(self.memory.read_byte(None, 0x1002), 0x13) class TestLoadStoreOperations(unittest.TestCase): @@ -98,17 +98,17 @@ class TestLoadStoreOperations(unittest.TestCase): def test_STA(self): self.cpu.accumulator = 0x37 self.cpu.STA(0x2000) - self.assertEqual(self.memory.read_byte(0x2000), 0x37) + self.assertEqual(self.memory.read_byte(None, 0x2000), 0x37) def test_STX(self): self.cpu.x_index = 0x38 self.cpu.STX(0x2000) - self.assertEqual(self.memory.read_byte(0x2000), 0x38) + self.assertEqual(self.memory.read_byte(None, 0x2000), 0x38) def test_STY(self): self.cpu.y_index = 0x39 self.cpu.STY(0x2000) - self.assertEqual(self.memory.read_byte(0x2000), 0x39) + self.assertEqual(self.memory.read_byte(None, 0x2000), 0x39) class TestRegisterTransferOperations(unittest.TestCase): @@ -550,17 +550,17 @@ class TestIncrementDecrementOperations(unittest.TestCase): def test_INC(self): self.memory.write_byte(0x1000, 0x00) self.cpu.INC(0x1000) - self.assertEqual(self.memory.read_byte(0x1000), 0x01) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x01) self.assertEqual(self.cpu.sign_flag, 0) self.assertEqual(self.cpu.zero_flag, 0) self.memory.write_byte(0x1000, 0x7F) self.cpu.INC(0x1000) - self.assertEqual(self.memory.read_byte(0x1000), 0x80) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x80) self.assertEqual(self.cpu.sign_flag, 1) self.assertEqual(self.cpu.zero_flag, 0) self.memory.write_byte(0x1000, 0xFF) self.cpu.INC(0x1000) - self.assertEqual(self.memory.read_byte(0x1000), 0x00) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x00) self.assertEqual(self.cpu.sign_flag, 0) self.assertEqual(self.cpu.zero_flag, 1) @@ -601,17 +601,17 @@ class TestIncrementDecrementOperations(unittest.TestCase): def test_DEC(self): self.memory.write_byte(0x1000, 0x01) self.cpu.DEC(0x1000) - self.assertEqual(self.memory.read_byte(0x1000), 0x00) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x00) self.assertEqual(self.cpu.sign_flag, 0) self.assertEqual(self.cpu.zero_flag, 1) self.memory.write_byte(0x1000, 0x80) self.cpu.DEC(0x1000) - self.assertEqual(self.memory.read_byte(0x1000), 0x7F) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x7F) self.assertEqual(self.cpu.sign_flag, 0) self.assertEqual(self.cpu.zero_flag, 0) self.memory.write_byte(0x1000, 0x00) self.cpu.DEC(0x1000) - self.assertEqual(self.memory.read_byte(0x1000), 0xFF) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0xFF) self.assertEqual(self.cpu.sign_flag, 1) self.assertEqual(self.cpu.zero_flag, 0) @@ -665,7 +665,7 @@ class TestShiftOperations(unittest.TestCase): self.assertEqual(self.cpu.carry_flag, 0) self.memory.write_byte(0x1000, 0x02) self.cpu.ASL(0x1000) - self.assertEqual(self.memory.read_byte(0x1000), 0x04) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x04) self.assertEqual(self.cpu.sign_flag, 0) self.assertEqual(self.cpu.zero_flag, 0) self.assertEqual(self.cpu.carry_flag, 0) @@ -685,7 +685,7 @@ class TestShiftOperations(unittest.TestCase): self.assertEqual(self.cpu.carry_flag, 1) self.memory.write_byte(0x1000, 0x01) self.cpu.LSR(0x1000) - self.assertEqual(self.memory.read_byte(0x1000), 0x00) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x00) self.assertEqual(self.cpu.sign_flag, 0) self.assertEqual(self.cpu.zero_flag, 1) self.assertEqual(self.cpu.carry_flag, 1) @@ -714,14 +714,14 @@ class TestShiftOperations(unittest.TestCase): self.cpu.carry_flag = 0 self.memory.write_byte(0x1000, 0x80) self.cpu.ROL(0x1000) - self.assertEqual(self.memory.read_byte(0x1000), 0x00) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x00) self.assertEqual(self.cpu.sign_flag, 0) self.assertEqual(self.cpu.zero_flag, 1) # @@@ self.assertEqual(self.cpu.carry_flag, 1) self.cpu.carry_flag = 1 self.memory.write_byte(0x1000, 0x80) self.cpu.ROL(0x1000) - self.assertEqual(self.memory.read_byte(0x1000), 0x01) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x01) self.assertEqual(self.cpu.sign_flag, 0) self.assertEqual(self.cpu.zero_flag, 0) # @@@ self.assertEqual(self.cpu.carry_flag, 1) @@ -744,14 +744,14 @@ class TestShiftOperations(unittest.TestCase): self.cpu.carry_flag = 0 self.memory.write_byte(0x1000, 0x01) self.cpu.ROR(0x1000) - self.assertEqual(self.memory.read_byte(0x1000), 0x00) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x00) self.assertEqual(self.cpu.sign_flag, 0) self.assertEqual(self.cpu.zero_flag, 1) # @@@ self.assertEqual(self.cpu.carry_flag, 1) self.cpu.carry_flag = 1 self.memory.write_byte(0x1000, 0x01) self.cpu.ROR(0x1000) - self.assertEqual(self.memory.read_byte(0x1000), 0x80) + self.assertEqual(self.memory.read_byte(None, 0x1000), 0x80) self.assertEqual(self.cpu.sign_flag, 1) # @@@ self.assertEqual(self.cpu.zero_flag, 0) # @@@ self.assertEqual(self.cpu.carry_flag, 1) @@ -771,8 +771,8 @@ class TestJumpCallOperations(unittest.TestCase): self.cpu.program_counter = 0x1000 self.cpu.JSR(0x2000) self.assertEqual(self.cpu.program_counter, 0x2000) - self.assertEqual(self.memory.read_byte(self.cpu.STACK_PAGE + self.cpu.stack_pointer + 1), 0xFF) - self.assertEqual(self.memory.read_byte(self.cpu.STACK_PAGE + self.cpu.stack_pointer + 2), 0x0F) + self.assertEqual(self.memory.read_byte(None, self.cpu.STACK_PAGE + self.cpu.stack_pointer + 1), 0xFF) + self.assertEqual(self.memory.read_byte(None, self.cpu.STACK_PAGE + self.cpu.stack_pointer + 2), 0x0F) def test_RTS(self): self.memory.write_byte(self.cpu.STACK_PAGE + 0xFF, 0x12) @@ -931,9 +931,9 @@ class TestSystemFunctionOperations(unittest.TestCase): self.cpu.BRK() self.assertEqual(self.cpu.program_counter, 0x2000) self.assertEqual(self.cpu.break_flag, 1) - self.assertEqual(self.memory.read_byte(self.cpu.STACK_PAGE + self.cpu.stack_pointer + 1), status) - self.assertEqual(self.memory.read_byte(self.cpu.STACK_PAGE + self.cpu.stack_pointer + 2), 0x01) - self.assertEqual(self.memory.read_byte(self.cpu.STACK_PAGE + self.cpu.stack_pointer + 3), 0x10) + self.assertEqual(self.memory.read_byte(None, self.cpu.STACK_PAGE + self.cpu.stack_pointer + 1), status) + self.assertEqual(self.memory.read_byte(None, self.cpu.STACK_PAGE + self.cpu.stack_pointer + 2), 0x01) + self.assertEqual(self.memory.read_byte(None, self.cpu.STACK_PAGE + self.cpu.stack_pointer + 3), 0x10) def test_RTI(self): self.memory.write_byte(self.cpu.STACK_PAGE + 0xFF, 0x12) From 4dd414dea86277e8f71a50b43eae416703962464 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Mon, 15 Aug 2011 02:22:58 -0400 Subject: [PATCH 11/11] implemented speaker; not a bad hack :-) --- applepy.py | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/applepy.py b/applepy.py index d7a5fb0..446c268 100644 --- a/applepy.py +++ b/applepy.py @@ -3,6 +3,7 @@ # originally written 2001, updated 2011 +import numpy import pygame import colorsys @@ -238,6 +239,30 @@ class Display: del pixels +class Speaker: + + CPU_CYCLES_PER_SAMPLE = 70 + CHECK_INTERVAL = 1000 + + def __init__(self): + pygame.mixer.pre_init(44100, -16, 1) + pygame.init() + self.reset() + + def toggle(self, cycle): + if self.last_toggle is not None: + l = (cycle - self.last_toggle) / Speaker.CPU_CYCLES_PER_SAMPLE + self.buffer.extend([0, 0.8] if self.polarity else [0, -0.8]) + self.buffer.extend(l * [0.5] if self.polarity else [-0.5]) + self.polarity = not self.polarity + self.last_toggle = cycle + + def reset(self): + self.last_toggle = None + self.buffer = [] + self.polarity = False + + class ROM: def __init__(self, start, size): @@ -267,9 +292,10 @@ class RAM(ROM): class SoftSwitches: - def __init__(self, display): + def __init__(self, display, speaker): self.kbd = 0x00 self.display = display + self.speaker = speaker def read_byte(self, cycle, address): assert 0xC000 <= address <= 0xCFFF @@ -278,7 +304,8 @@ class SoftSwitches: elif address == 0xC010: self.kbd = self.kbd & 0x7F elif address == 0xC030: - pass # toggle speaker + if self.speaker: + self.speaker.toggle(cycle) elif address == 0xC050: self.display.txtclr() elif address == 0xC051: @@ -302,15 +329,16 @@ class SoftSwitches: class Memory: - def __init__(self, display=None): + def __init__(self, display=None, speaker=None): self.display = display + self.speaker = speaker self.rom = ROM(0xD000, 0x3000) # available from http://www.easy68k.com/paulrsm/6502/index.html self.rom.load_file(0xD000, "A2ROM.BIN") self.ram = RAM(0x0000, 0xC000) - self.softswitches = SoftSwitches(display) + self.softswitches = SoftSwitches(display, speaker) def load(self, address, data): if address < 0xC000: @@ -340,6 +368,13 @@ class Memory: self.display.update(address, value) if 0x2000 <= address < 0x5FFF and self.display: self.display.update(address, value) + + def update(self, cycle): + if self.speaker.buffer and (cycle - self.speaker.last_toggle) > self.speaker.CHECK_INTERVAL: + sample_array = numpy.array(self.speaker.buffer) + sound = pygame.sndarray.make_sound(sample_array) + sound.play() + self.speaker.reset() class Disassemble: @@ -774,6 +809,7 @@ class CPU: update_cycle += 1 if update_cycle >= 1024: pygame.display.flip() + self.memory.update(self.cycles) update_cycle = 0 def test_run(self, start, end): @@ -1220,7 +1256,8 @@ class CPU: if __name__ == "__main__": display = Display() - mem = Memory(display) + speaker = Speaker() + mem = Memory(display, speaker) cpu = CPU(mem) cpu.run()