def disassemble(self, bytes, pc=0, mpu=None): if mpu is None: mpu = MPU() address_parser = AddressParser() disasm = Disassembler(mpu, address_parser) mpu.memory[pc:len(bytes) - 81] = bytes return disasm.instruction_at(pc)
def __init__(self, filename): self.nsf = NSFParser(bytearray(file(filename).read())) # Set up Memory with Hooks self.mem = ObservableMemory() self.mem.write(self.nsf.load_addr, self.nsf.data) self.cpu = MPU(self.mem) self.deltaCallTime = 0 self.totalCycles = 0
def get_postconditions(pre): cpu = MPU() cpu.memory = ChangeTrackingMemory(pre['mem']) for r, v in pre['regs'].iteritems(): setattr(cpu, r, v) cpu.step() return { 'regs': {r: getattr(cpu, r) for r in pre['regs'].keys()}, 'mem': cpu.memory.mem, }
def __init__(self): self.mem = Memory() self.cpu = MPU() self.patches = Patches() self.dis = Disassembler(self.cpu) pc_low = self.mem[0xfffc] pc_hi = self.mem[0xfffd] self.cpu.memory = self.mem self.cpu.pc = (pc_hi << 8) + pc_low self.trace = False
def _dont_test_disassemble_wraps_after_top_of_mem(self): ''' TODO: This test fails with IndexError. We should fix this so that it does not attempt to index memory out of range. It does not affect most Py65 users because py65mon uses ObservableMemory, which does not raise IndexError. ''' mpu = MPU() mpu.memory[0xFFFF] = 0x20 # JSR mpu.memory[0x0000] = 0xD2 # mpu.memory[0x0001] = 0xFF # $FFD2 dis = Disassembler(mpu) length, disasm = dis.instruction_at(0xFFFF) self.assertEqual(3, length) self.assertEqual('JSR $ffd2', disasm)
class C64(object): def __init__(self): self.mem = Memory() self.cpu = MPU() self.patches = Patches() self.dis = Disassembler(self.cpu) pc_low = self.mem[0xfffc] pc_hi = self.mem[0xfffd] self.cpu.memory = self.mem self.cpu.pc = (pc_hi << 8) + pc_low self.trace = False def step(self): pc = self.cpu.pc if self.trace: print "0x%04x - %s" % (pc, self.dis.instruction_at(pc)[1]) if kernal.base <= pc and pc <= kernal.end: kernal_function = kernal.resolve(pc) if kernal_function: kernal_function(self) self.cpu.inst_0x60() # RTS return patch = self.patches[pc] if patch: patch(self) return self.cpu.step() def run_for(self, cycles): for x in xrange(cycles): self.step() print print 'Emulation over, did %d CPU cycles.' % cycles def run_until(self, end_pc): while self.cpu.pc != end_pc: self.step()
class NSFSoftCPU: NES_CLK_period = 46 * 12 def __init__(self, filename): self.nsf = NSFParser(bytearray(file(filename).read())) # Set up Memory with Hooks self.mem = ObservableMemory() self.mem.write(self.nsf.load_addr, self.nsf.data) self.cpu = MPU(self.mem) self.deltaCallTime = 0 self.totalCycles = 0 def subscribe_to_write(self, range, callback): self.mem.subscribe_to_write(range, callback) # Call NSF Init code def setup(self, start_song = -1): if start_song == -1: start_song = self.nsf.start_song - 1 # Push a special address -1 to the stack to implement function calls self.cpu.stPushWord(0x1337 - 1) # NSF Style init call self.cpu.a = start_song self.cpu.x = 0 self.cpu.pc = self.nsf.init_addr while self.cpu.pc != 0x1337: self.cpu.step() # Execute 1 CPU Step, or wait to until enough cycles have passed def play_cycle(self): self.deltaCallTime = self.deltaCallTime + self.NES_CLK_period self.check_frame() if self.cpu.pc != 0x1337: self.totalCycles = self.totalCycles + 1 if self.totalCycles == self.cpu.processorCycles: self.cpu.step() # Internal: Check if frame is completed and restart it periodically def check_frame(self): if self.cpu.pc == 0x1337: frame_time = self.nsf.ntsc_ticks * 1000 if self.deltaCallTime >= frame_time: self.deltaCallTime = self.deltaCallTime - frame_time self.cpu.stPushWord(0x1337 - 1) self.cpu.pc = self.nsf.play_addr print "Frame Completed"
class NSFSoftCPU: NES_CLK_period = 46 * 12 def __init__(self, filename): self.nsf = NSFParser(bytearray(file(filename).read())) # Set up Memory with Hooks self.mem = ObservableMemory() self.mem.write(self.nsf.load_addr, self.nsf.data) self.cpu = MPU(self.mem) self.deltaCallTime = 0 self.totalCycles = 0 def subscribe_to_write(self, range, callback): self.mem.subscribe_to_write(range, callback) # Call NSF Init code def setup(self, start_song=-1): if start_song == -1: start_song = self.nsf.start_song - 1 # Push a special address -1 to the stack to implement function calls self.cpu.stPushWord(0x1337 - 1) # NSF Style init call self.cpu.a = start_song self.cpu.x = 0 self.cpu.pc = self.nsf.init_addr while self.cpu.pc != 0x1337: self.cpu.step() # Execute 1 CPU Step, or wait to until enough cycles have passed def play_cycle(self): self.deltaCallTime = self.deltaCallTime + self.NES_CLK_period self.check_frame() if self.cpu.pc != 0x1337: self.totalCycles = self.totalCycles + 1 if self.totalCycles == self.cpu.processorCycles: self.cpu.step() # Internal: Check if frame is completed and restart it periodically def check_frame(self): if self.cpu.pc == 0x1337: frame_time = self.nsf.ntsc_ticks * 1000 if self.deltaCallTime >= frame_time: self.deltaCallTime = self.deltaCallTime - frame_time self.cpu.stPushWord(0x1337 - 1) self.cpu.pc = self.nsf.play_addr print "Frame Completed"
def __init__(self): MPU.__init__(self, pc=0x816) self.disasm = Disassembler(self) self.count = 0
def setUp(self): self.cpu = MPU() print(self.cpu)
def test_ctor_optionally_creates_address_parser(self): mpu = MPU() asm = Assembler(mpu) self.assertFalse(asm._address_parser is None)
def test_ctor_uses_bus_width_from_mpu(self): asm = Assembler(MPU()) self.assertEqual(16, asm.addrWidth) asm = Assembler(MPU65Org16()) self.assertEqual(32, asm.addrWidth)
def assemble(self, statement, pc=0000): mpu = MPU() address_parser = AddressParser() assembler = Assembler(mpu, address_parser) return assembler.assemble(statement, pc)
def test_ctor_uses_provided_mpu_and_address_parser(self): mpu = MPU() address_parser = AddressParser() asm = Assembler(mpu, address_parser) self.assertTrue(asm._mpu is mpu) self.assertTrue(asm._address_parser is address_parser)
def disassemble(self, bytes): mpu = MPU() address_parser = AddressParser() disasm = Disassembler(mpu, address_parser) mpu.memory[0:len(bytes)-1] = bytes return disasm.instruction_at(0)
def reset(self): self.cpu = MPU() self.start_addr = 0 self.stop_addr = 0 self.paused = False self.step = False
def __init__(self): super().__init__() self.mpu = MPU() self.regsobj = self.mpu
def __init__(self): self.mpu = MPU() self.symtab = symtab.SymTab([]) # symtab initially empty
class Machine(MemoryAccess): class Timeout(RuntimeError): ' The emulator ran longer than requested. ' pass def is_little_endian(self): return True def get_memory_seq(self): return self.mpu.memory class Abort(RuntimeError): ' The emulator encoutered an instruction on which to abort.' pass def __init__(self): self.mpu = MPU() self.symtab = symtab.SymTab([]) # symtab initially empty @property def regs(self): m = self.mpu return Registers(m.pc, m.a, m.x, m.y, m.sp, psr=m.p) def setregs(self, r): m = self.mpu if r.pc is not None: m.pc = r.pc if r.a is not None: m.a = r.a if r.x is not None: m.x = r.x if r.y is not None: m.y = r.y if r.sp is not None: m.sp = r.sp flags_masks = ( ('N', 0b10000000), ('V', 0b01000000), ('D', 0b00001000), ('I', 0b00000100), ('Z', 0b00000010), ('C', 0b00000001), ) for (flagname, mask) in flags_masks: flag = getattr(r, flagname) if flag is None: continue elif flag == 0: m.p &= ~mask elif flag == 1: m.p |= mask else: raise ValueError('Bad {} flag value: {}'.format( flagname, flag)) def _stackaddr(self, depth, size): addr = 0x100 + self.mpu.sp + 1 + depth if addr >= 0x201 - size: raise IndexError("stack underflow: addr={:04X} size={}" \ .format(addr, size)) return addr def spbyte(self, depth=0): return self.byte(self._stackaddr(depth, 1)) def spword(self, depth=0): return self.word(self._stackaddr(depth, 2)) def load(self, path): ''' Load the given ``.bin`` file and, if available, the symbols from a ``.rst`` (ASxxxx linker listing file) in the same directory. ''' if path.lower().endswith('.p'): # Assume it's Macro Assembler AS output. self.load_memimage(asl.parse_obj_fromfile(path)) mapfile_path = path[0:-2] + '.map' try: self.symtab = asl.parse_symtab_fromfile(mapfile_path) except FileNotFoundError as err: print('WARNING: could not read symbol table file from path ' \ + mapfile_path, file=stderr) print('FileNotFoundError: ' + str(err), file=stderr) else: # Assume it's the basename of ASxxxx toolchain output. # (This should probably be changed to require something # indicating this explicitly.) self.load_memimage(asxxxx.parse_cocobin_fromfile(path + '.bin')) try: self.symtab = asxxxx.AxSymTab.readsymtabpath(path) except FileNotFoundError as err: print('WARNING: could not read symbol table file from path ' \ + path, file=stderr) print('FileNotFoundError: ' + str(err), file=stderr) def load_memimage(self, memimage): for addr, data in memimage: self.deposit(addr, data) self.mpu.pc = memimage.entrypoint def step(self, count=1, *, trace=False): ''' Execute `count` instructions (default 1). If `trace` is `True`, the current machine state and instruction about to be executed will be printed before executing the step. XXX This should check for stack under/overflow. ''' for _ in repeat(None, count): if trace: print('{} opcode={:02X}' \ .format(self.regs, self.byte(self.regs.pc))) self.mpu.step() # Default maximum number of instructions to execute when using # stepto(), call() and related functions. Even on a relatively # slow modern machine, 100,000 instructions should terminate # within a few seconds. MAXOPS = 100000 def stepto(self, instrs, *, maxops=MAXOPS, trace=False): ''' Step an instruction and then continue stepping until an instruction in `instrs` is reached. Raise a `Timeout` after `maxops`. ''' if not isinstance(instrs, Container): instrs = (instrs, ) self.step(trace=trace) count = maxops - 1 while self.byte(self.mpu.pc) not in instrs: self.step(trace=trace) count -= 1 if count <= 0: raise self.Timeout( 'Timeout after {} instructions: {} opcode={}' \ .format(maxops, self.regs, self.byte(self.regs.pc))) def call(self, addr, regs=Registers(), *, maxops=MAXOPS, aborts=(0x00, ), trace=False): ''' Simulate a JSR to `addr`, after setting any `registers` specified, returning when its corresponding RTS is reached. A `Timeout` will be raised if `maxops` instructions are executed. An `Abort` will be raised if any instructions in the `aborts` collection are about to be executed. (By default this list contains ``BRK``.) `step()` tracing will be enabled if `trace` is `True`. The PC will be left at the final (unexecuted) RTS instruction. Thus, unlike `step()`, this may execute no instructions if the PC is initially pointing to an RTS. JSR and RTS instructions will be tracked to allow the routine to call subroutines, but tricks with the stack (such as pushing an address and executing RTS, with no corresponding JSR) will confuse this routine and may cause it to terminate early or not at all. ''' self.setregs(regs) if addr is not None: self.setregs(Registers(pc=addr)) # Overrides regs I = Instructions stopon = (I.JSR, I.RTS) + tuple(aborts) depth = 0 while True: opcode = self.byte(self.mpu.pc) if opcode == I.RTS: if depth > 0: depth -= 1 else: # We don't execute the RTS because no JSR was called # to come in, so we may have nothing on the stack. return elif opcode == I.JSR: # Enter the new level with the next execution depth += 1 elif opcode in stopon: # Abort raise self.Abort('Abort on opcode={}: {}' \ .format(self.byte(self.regs.pc), self.regs)) self.stepto(stopon, maxops=maxops, trace=trace)
class Machine(GenericMachine): is_little_endian = True def get_memory_seq(self): return self.mpu.memory class Registers(GenericRegisters): machname = '6502' registers = (Reg('pc', 16), Reg('a'), Reg('x'), Reg('y'), Reg('sp')) # No "B flag" here; it's not actually a flag in the PSR, it's # merely set in the value pushed on to the stack on IRQ or `BRK`. srbits = ( Flag('N'), Flag('V'), Bit(1), Bit(1), Flag('D'), Flag('I'), Flag('Z'), Flag('C'), ) srname = 'p' #################################################################### def __init__(self): super().__init__() self.mpu = MPU() self.regsobj = self.mpu def _stackaddr(self, depth, size): ''' Return the address of `size` bytes of data on the stack at `depth` bytes above the current head of the stack. ''' addr = 0x100 + self.mpu.sp + 1 + depth if addr >= 0x201 - size: raise IndexError("stack underflow: addr={:04X} size={}" \ .format(addr, size)) return addr def spbyte(self, depth=0): return self.byte(self._stackaddr(depth, 1)) def spword(self, depth=0): return self.word(self._stackaddr(depth, 2)) #################################################################### # Execution _RTS_opcodes = set([Instructions.RTS]) _ABORT_opcodes = set([Instructions.BRK]) def _getpc(self): return self.mpu.pc def _getsp(self): return self.mpu.sp def _step(self): self.mpu.step() def pushretaddr(self, addr): ''' Like JSR, this pushes `addr` - 1; RTS compensates for this. See MC6800 Family Programming Manual §8.1 p.108. ''' self.mpu.sp -= 2 self.depword(self._stackaddr(0, 2), addr - 1) def getretaddr(self): ''' Like RTS, we compensate for the return address pointing to the last byte of the JSR operand instead of the first byte of the next instruction. See MC6800 Family Programming Manual §8.2 p.108. ''' return self.word(self._stackaddr(0, 2)) + 1
def setUp(self): self.cpu = MPU()
def assemble(self, statement, pc=0000, mpu=None): if mpu is None: mpu = MPU() address_parser = AddressParser() assembler = Assembler(mpu, address_parser) return assembler.assemble(statement, pc)
class Machine(): class Timeout(RuntimeError): ' The emulator ran longer than requested. ' pass class Abort(RuntimeError): ' The emulator encoutered an instruction on which to abort.' pass def __init__(self): self.mpu = MPU() self.symtab = symtab.SymTab([]) # symtab initially empty @property def regs(self): m = self.mpu return Registers(m.pc, m.a, m.x, m.y, m.sp, psr=m.p) def setregs(self, r): m = self.mpu if r.pc is not None: m.pc = r.pc if r.a is not None: m.a = r.a if r.x is not None: m.x = r.x if r.y is not None: m.y = r.y if r.sp is not None: m.sp = r.sp flags_masks = ( ('N', 0b10000000), ('V', 0b01000000), ('D', 0b00001000), ('I', 0b00000100), ('Z', 0b00000010), ('C', 0b00000001), ) for (flagname, mask) in flags_masks: flag = getattr(r, flagname) if flag is None: continue elif flag == 0: m.p &= ~mask elif flag == 1: m.p |= mask else: raise ValueError('Bad {} flag value: {}'.format(flagname, flag)) # XXX This "examine" interface isn't so nice. Perhaps we can condense # in down to a single examine() function that takes a length and type? def byte(self, addr): ' Examine a byte from memory. ' return self.mpu.ByteAt(addr) def bytes(self, addr, len): ' Examine a list of bytes from memory. ' vals = [] for i in range(addr, addr+len): vals.append(self.byte(i)) return vals def word(self, addr): ''' Examine a word from memory. Native endianness is decoded to give a 16-bit int. ''' return self.mpu.WordAt(addr) def words(self, addr, len): ' Examine a list of words from memory. ' vals = [] for i in range(addr, addr+len*2, 2): vals.append(self.word(i)) return vals def _stackaddr(self, depth, size): addr = 0x100 + self.mpu.sp + 1 + depth if addr >= 0x201 - size: raise IndexError("stack underflow: addr={:04X} size={}" \ .format(addr, size)) return addr def spbyte(self, depth=0): return self.byte(self._stackaddr(depth, 1)) def spword(self, depth=0): return self.word(self._stackaddr(depth, 2)) def str(self, addr, len): ' Examine a string from memory. ' # This currently throws an exception if any of the bytes # in the memory range are >0x7f. It's not clear how we # should be decoding those. Possibly we want an option to # clear the high bit on all chars before decoding. return bytes(self.mpu.memory[addr:addr+len]).decode('ASCII') def _deperr(self, addr, message, *errvalues): s = 'deposit @${:04X}: ' + message raise ValueError(s.format(addr, *errvalues)) def deposit(self, addr, *values): ''' Deposit bytes to memory at `addr`. Remaining parameters are values to deposit at contiguous addresses, each of which is a `numbers.Integral` in range 0x00-0xFF or a `Sequence` of such numbers (e.g., `list`, `tuple`, `bytes`). Returns a `list` of all the deposited bytes. ''' def err(s, *errvalues): msg = 'deposit @${:04X}: ' + s raise ValueError(msg.format(addr, *errvalues)) def assertvalue(x): if not isinstance(x, Integral): err('non-integral value {}', repr(x)) if x < 0x00 or x > 0xFF: err('invalid byte value ${:02X}', x) if addr < 0x0000 or addr > 0xFFFF: err('address out of range') data = [] for value in values: if isinstance(value, Integral): assertvalue(value) data.append(value) elif isinstance(value, Sequence): list(map(assertvalue, value)) data += list(value) else: err('invalid argument {}', repr(value)) if addr + len(data) > 0xFFFF: err('data length {} exceeds memory', len(data)) self.mpu.memory[addr:addr+len(data)] = data return data def depword(self, addr, *values): ''' Deposit 16-bit words to memory at `addr` in native endian format. Remaining parameters are values to deposit at contiguous addresses, each of which is a `numbers.Integral` in range 0x0000-0xFFFF or a `Sequence` of such numbers (e.g., `list`, `tuple`, `bytes`). Returns a `list` of all the deposited words as Python ints. This uses `deposit()`, so some error messages may be generated by that function. ''' def assertvalue(x): if not isinstance(x, Integral): self._deperr(addr, 'non-integral value {}', repr(x)) if x < 0x00 or x > 0xFFFF: self._deperr(addr, 'invalid word value ${:02X}', x) words = [] for value in values: if isinstance(value, Integral): assertvalue(value) words.append(value) elif isinstance(value, Sequence): list(map(assertvalue, value)) words += list(value) else: self._deperr(addr, 'invalid argument {}', repr(value)) data = [] for word in words: data.append(word & 0xFF) # LSB first for 6502 data.append((word & 0xFF00) >> 8) # MSB self.deposit(addr, data) return words def load(self, path): ''' Load the given ``.bin`` file and, if available, the symbols from a ``.rst`` (ASxxxx linker listing file) in the same directory. ''' if path.lower().endswith('.p'): # Assume it's Macro Assembler AS output. self.load_memimage(asl.parse_obj_fromfile(path)) mapfile_path = path[0:-2] + '.map' try: self.symtab = asl.parse_symtab_fromfile(mapfile_path) except FileNotFoundError as err: print('WARNING: could not read symbol table file from path ' \ + mapfile_path, file=stderr) print('FileNotFoundError: ' + str(err), file=stderr) else: # Assume it's the basename of ASxxxx toolchain output. # (This should probably be changed to require something # indicating this explicitly.) self.load_memimage(asxxxx.parse_cocobin_fromfile(path + '.bin')) try: self.symtab = asxxxx.AxSymTab.readsymtabpath(path) except FileNotFoundError as err: print('WARNING: could not read symbol table file from path ' \ + path, file=stderr) print('FileNotFoundError: ' + str(err), file=stderr) def load_memimage(self, memimage): for addr, data in memimage: self.deposit(addr, data) self.mpu.pc = memimage.entrypoint def step(self, count=1, *, trace=False): ''' Execute `count` instructions (default 1). If `trace` is `True`, the current machine state and instruction about to be executed will be printed before executing the step. XXX This should check for stack under/overflow. ''' for _ in repeat(None, count): if trace: print('{} opcode={:02X}' \ .format(self.regs, self.byte(self.regs.pc))) self.mpu.step() # Default maximum number of instructions to execute when using # stepto(), call() and related functions. Even on a relatively # slow modern machine, 100,000 instructions should terminate # within a few seconds. MAXOPS = 100000 def stepto(self, instrs, *, maxops=MAXOPS, trace=False): ''' Step an instruction and then continue stepping until an instruction in `instrs` is reached. Raise a `Timeout` after `maxops`. ''' if not isinstance(instrs, Container): instrs = (instrs,) self.step(trace=trace) count = maxops - 1 while self.byte(self.mpu.pc) not in instrs: self.step(trace=trace) count -= 1 if count <= 0: raise self.Timeout( 'Timeout after {} instructions: {} opcode={}' \ .format(maxops, self.regs, self.byte(self.regs.pc))) def call(self, addr, regs=Registers(), *, maxops=MAXOPS, aborts=(0x00,), trace=False): ''' Simulate a JSR to `addr`, after setting any `registers` specified, returning when its corresponding RTS is reached. A `Timeout` will be raised if `maxops` instructions are executed. An `Abort` will be raised if any instructions in the `aborts` collection are about to be executed. (By default this list contains ``BRK``.) `step()` tracing will be enabled if `trace` is `True`. The PC will be left at the final (unexecuted) RTS instruction. Thus, unlike `step()`, this may execute no instructions if the PC is initially pointing to an RTS. JSR and RTS instructions will be tracked to allow the routine to call subroutines, but tricks with the stack (such as pushing an address and executing RTS, with no corresponding JSR) will confuse this routine and may cause it to terminate early or not at all. ''' self.setregs(regs) if addr is not None: self.setregs(Registers(pc=addr)) # Overrides regs I = Instructions stopon = (I.JSR, I.RTS) + tuple(aborts) depth = 0 while True: opcode = self.byte(self.mpu.pc) if opcode == I.RTS: if depth > 0: depth -=1 else: # We don't execute the RTS because no JSR was called # to come in, so we may have nothing on the stack. return elif opcode == I.JSR: # Enter the new level with the next execution depth += 1 elif opcode in stopon: # Abort raise self.Abort('Abort on opcode={}: {}' \ .format(self.byte(self.regs.pc), self.regs)) self.stepto(stopon, maxops=maxops, trace=trace)