예제 #1
0
 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)
예제 #2
0
 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)
예제 #3
0
    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
예제 #4
0
파일: stealer.py 프로젝트: biappi/Swift6502
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,
    }
예제 #5
0
    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
예제 #6
0
    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)
예제 #7
0
    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)
예제 #8
0
파일: nsf.py 프로젝트: ralisi/nesfpga
	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
예제 #9
0
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()
예제 #10
0
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()
예제 #11
0
파일: nsf.py 프로젝트: ralisi/nesfpga
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"
예제 #12
0
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"
예제 #13
0
    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
예제 #14
0
파일: test_lorenz.py 프로젝트: mar77i/py65
 def __init__(self):
     MPU.__init__(self, pc=0x816)
     self.disasm = Disassembler(self)
     self.count = 0
예제 #15
0
 def setUp(self):
     self.cpu = MPU()
     print(self.cpu)
예제 #16
0
 def test_ctor_optionally_creates_address_parser(self):
     mpu = MPU()
     asm = Assembler(mpu)
     self.assertFalse(asm._address_parser is None)
예제 #17
0
 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)
예제 #18
0
 def assemble(self, statement, pc=0000):
     mpu = MPU()
     address_parser = AddressParser()
     assembler = Assembler(mpu, address_parser)
     return assembler.assemble(statement, pc)
예제 #19
0
 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)
예제 #20
0
 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)
예제 #21
0
 def reset(self):
     self.cpu = MPU()
     self.start_addr = 0
     self.stop_addr = 0
     self.paused = False
     self.step = False
예제 #22
0
 def __init__(self):
     super().__init__()
     self.mpu = MPU()
     self.regsobj = self.mpu
예제 #23
0
 def __init__(self):
     self.mpu = MPU()
     self.symtab = symtab.SymTab([])  # symtab initially empty
예제 #24
0
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)
예제 #25
0
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
예제 #26
0
 def setUp(self):
     self.cpu = MPU()
예제 #27
0
 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)
예제 #28
0
파일: m6502.py 프로젝트: scirelli/8bitdev
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)