mirror of
https://github.com/mnaberez/py65.git
synced 2024-10-31 22:06:12 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
430fec1e28
14
.travis.yml
14
.travis.yml
@ -2,22 +2,20 @@ language: python
|
||||
sudo: false
|
||||
matrix:
|
||||
include:
|
||||
- python: 2.6
|
||||
env: TOXENV=py26
|
||||
- python: 2.7
|
||||
env: TOXENV=py27
|
||||
- python: 3.2
|
||||
env: TOXENV=py32
|
||||
- python: 3.3
|
||||
env: TOXENV=py33
|
||||
- python: 3.4
|
||||
env: TOXENV=py34
|
||||
- python: 3.5
|
||||
env: TOXENV=py35
|
||||
- python: 3.6
|
||||
env: TOXENV=py36
|
||||
- python: 3.7
|
||||
env: TOXENV=py37
|
||||
dist: xenial # required for Python >= 3.7
|
||||
- python: pypy
|
||||
env: TOXENV=pypy
|
||||
install:
|
||||
# "virtualenv<14.0.0" is needed for Python 3.2 compat
|
||||
- travis_retry pip install "virtualenv<14.0.0" tox
|
||||
- travis_retry pip install virtualenv tox
|
||||
script:
|
||||
- travis_retry tox
|
||||
|
49
CHANGES.txt
49
CHANGES.txt
@ -1,5 +1,26 @@
|
||||
0.25.dev0 (Next Release)
|
||||
------------------------
|
||||
2.0.0.dev0 (Next Release)
|
||||
-------------------------
|
||||
|
||||
- Support for some older Python versions has been dropped. On Python 3,
|
||||
Py65 now requires Python 3.4 or later. On Python 2, Py65 now requires
|
||||
Python 2.7.
|
||||
|
||||
1.1.0 (2018-07-01)
|
||||
------------------
|
||||
|
||||
- The ``Monitor`` class now allows the default memory to be supplied in
|
||||
the constructor. Patch by Irmen de Jong.
|
||||
|
||||
- Fixed a bug where setting the MPU via ``py65mon`` command line arguments
|
||||
would have no effect. Reported by Michael A. Morris, patch by Ed Spittles.
|
||||
|
||||
- The ``itoa()`` function in ``conversions.py`` now raises an error when an
|
||||
unsupported base is given. Patch by Scot W. Stevenson.
|
||||
|
||||
- The unused hexdump loader utility has been removed.
|
||||
|
||||
1.0.0 (2017-05-11)
|
||||
------------------
|
||||
|
||||
- Fixed a bug where the ordering of ``py65mon`` command line arguments
|
||||
produced different results. Arguments can now be specified in any
|
||||
@ -13,10 +34,12 @@
|
||||
the Z flag correctly. Thanks to Kris Kennaway for reporting it.
|
||||
|
||||
0.24 (2015-03-31)
|
||||
-----------------
|
||||
|
||||
- Released as a universal wheel.
|
||||
|
||||
0.23 (2015-02-10)
|
||||
-----------------
|
||||
|
||||
- Added a workaround to $F001 output to catch encoding errors and
|
||||
display a "?" instead of crashing. This condition can occur if
|
||||
@ -24,11 +47,13 @@
|
||||
the terminal's character encoding.
|
||||
|
||||
0.22 (2015-02-09)
|
||||
-----------------
|
||||
|
||||
- Fixed a bug where ``py65mon --rom`` would raise an exception
|
||||
on Python 3.
|
||||
|
||||
0.21 (2015-02-09)
|
||||
-----------------
|
||||
|
||||
- Added support for breakpoints in the monitor. Contributed by
|
||||
Alessandro Gatti.
|
||||
@ -39,12 +64,14 @@
|
||||
- Fixed console input when run under Python 3 on Windows. Closes #27.
|
||||
|
||||
0.20 (2014-05-08)
|
||||
-----------------
|
||||
|
||||
- Page wrapping for indexed indirect (X) operations on 65C02 has been
|
||||
restored. This reverts the change introduced in 0.18. We now believe
|
||||
that this mode works the same on the 65C02 as it does on the 6502.
|
||||
|
||||
0.19 (2014-03-12)
|
||||
-----------------
|
||||
|
||||
- Fixed 65C02 opcode $D2: CMP Zero Page, Indirect.
|
||||
|
||||
@ -54,6 +81,7 @@
|
||||
on $F005 have been changed to use $F004.
|
||||
|
||||
0.18 (2014-01-30)
|
||||
-----------------
|
||||
|
||||
- Fixed a bug in RTS where popping $FFFF off the stack would cause
|
||||
the program counter to overflow to $10000. It now wraps around
|
||||
@ -69,15 +97,18 @@
|
||||
- Removed page wrap bug from indexed indirect (X) operations on 65C02.
|
||||
|
||||
0.17 (2013-10-26)
|
||||
-----------------
|
||||
|
||||
- Added support for Python 3.2 and 3.3 based on work by David Beazley.
|
||||
|
||||
0.16 (2013-04-03)
|
||||
-----------------
|
||||
|
||||
- Fixed a bug in the monitor that caused loading from the command
|
||||
line with "--rom" to crash.
|
||||
|
||||
0.15 (2013-01-06)
|
||||
-----------------
|
||||
|
||||
- Disassembling can now wrap around memory if the start address
|
||||
given is greater than the end address.
|
||||
@ -86,6 +117,7 @@
|
||||
"start:end+1" for consistency with the other commands.
|
||||
|
||||
0.14 (2012-11-30)
|
||||
-----------------
|
||||
|
||||
- Assembling now detects syntax errors, overflows, and bad labels
|
||||
separately and shows specific error messages.
|
||||
@ -109,11 +141,13 @@
|
||||
the top of memory would not be displayed properly.
|
||||
|
||||
0.13 (2012-11-15)
|
||||
-----------------
|
||||
|
||||
- Fixed a bug where negative numbers could be entered
|
||||
for addresses in the monitor.
|
||||
|
||||
0.12 (2012-02-16)
|
||||
-----------------
|
||||
|
||||
- Fixed a bug that caused ``help cd`` to raise an exception
|
||||
in the monitor.
|
||||
@ -128,6 +162,7 @@
|
||||
- Added "h" as a monitor shortcut for "help".
|
||||
|
||||
0.11 (2012-01-07)
|
||||
-----------------
|
||||
|
||||
- Added a new 65Org16 MPU simulation written by Ed Spittles.
|
||||
|
||||
@ -139,6 +174,7 @@
|
||||
- Python versions earlier than 2.6 are no longer supported.
|
||||
|
||||
0.10 (2011-08-27)
|
||||
-----------------
|
||||
|
||||
- Fixed long-standing bugs in relative branch calculations in the
|
||||
assembler and disassembler. Based on a patch by Ed Spittles.
|
||||
@ -147,6 +183,7 @@
|
||||
Patch by Martti Kühne.
|
||||
|
||||
0.9 (2011-03-27)
|
||||
----------------
|
||||
|
||||
- Fixed two monitor tests that were broken under Windows. Thanks
|
||||
to Oscar Lindberg for reporting this.
|
||||
@ -158,6 +195,7 @@
|
||||
the decimal handling code.
|
||||
|
||||
0.8 (2010-03-08)
|
||||
----------------
|
||||
|
||||
- Fixed deprecation warnings on Python 2.6
|
||||
|
||||
@ -174,6 +212,7 @@
|
||||
consistency with VICE.
|
||||
|
||||
0.7 (2009-09-03)
|
||||
----------------
|
||||
|
||||
- When using the monitor, the nonblocking character input at
|
||||
$F004 should now work on the Microsoft Windows platform.
|
||||
@ -213,6 +252,7 @@
|
||||
a range of memory to a binary file.
|
||||
|
||||
0.6 (2009-08-11)
|
||||
----------------
|
||||
|
||||
- Added monitor shortcut "a" for "assemble".
|
||||
|
||||
@ -226,6 +266,7 @@
|
||||
Closes #3.
|
||||
|
||||
0.5 (2009-08-06)
|
||||
----------------
|
||||
|
||||
- Fixed signatures of getc/putc callbacks in monitor that were broken
|
||||
when the ObservableMemory interface changed in 0.3. Closes #1.
|
||||
@ -233,10 +274,12 @@
|
||||
- Fixed that ROL would not properly set the Z flag. Closes #2.
|
||||
|
||||
0.4 (2009-06-06)
|
||||
----------------
|
||||
|
||||
- Added ez_setup.py to bootstrap setuptools installation.
|
||||
|
||||
0.3 (2009-06-03)
|
||||
----------------
|
||||
|
||||
- Added shortcuts for monitor commands such as "m" for "memory". These
|
||||
are mostly the same as the VICE monitor shortcuts.
|
||||
@ -266,6 +309,7 @@
|
||||
- Python 2.4 or later is now required.
|
||||
|
||||
0.2 (2008-11-09)
|
||||
----------------
|
||||
|
||||
- Added a new "disassemble" command to the monitor. It can disassemble
|
||||
any range of memory ("disassemble c000:c010"). If labels have been
|
||||
@ -292,5 +336,6 @@
|
||||
command "help command" for help on any command.
|
||||
|
||||
0.1 (2008-11-21)
|
||||
----------------
|
||||
|
||||
- First release.
|
||||
|
25
LICENSE.txt
25
LICENSE.txt
@ -1,25 +1,30 @@
|
||||
Copyright (c) 2008-2014, Mike Naberezny and contributors.
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2008-2018, Mike Naberezny and contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Mike Naberezny nor the names of the contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
@ -13,8 +13,6 @@
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from datetime import date
|
||||
|
||||
# If your extensions are in another directory, add it here. If the directory
|
||||
@ -51,7 +49,7 @@ copyright = u'2008-%d, Mike Naberezny and contributors' % year
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.25.dev0'
|
||||
version = '1.1.0.dev0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
|
@ -76,7 +76,7 @@ class MPU:
|
||||
self.a = 0
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.p = self.p = self.BREAK | self.UNUSED
|
||||
self.p = self.BREAK | self.UNUSED
|
||||
self.processorCycles = 0
|
||||
|
||||
# Helpers for addressing modes
|
||||
|
@ -127,4 +127,8 @@ class Disassembler:
|
||||
disasm += ' %s,Y' % address_or_label
|
||||
length = 2
|
||||
|
||||
else:
|
||||
msg = "Addressing mode: %r" % addressing
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
return (length, disasm)
|
||||
|
104
py65/monitor.py
104
py65/monitor.py
@ -42,20 +42,39 @@ class Monitor(cmd.Cmd):
|
||||
Microprocessors = {'6502': NMOS6502, '65C02': CMOS65C02,
|
||||
'65Org16': V65Org16}
|
||||
|
||||
def __init__(self, mpu_type=NMOS6502, completekey='tab', stdin=None,
|
||||
stdout=None, argv=None):
|
||||
def __init__(self, argv=None, stdin=None, stdout=None,
|
||||
mpu_type=NMOS6502, memory=None,
|
||||
putc_addr=0xF001, getc_addr=0xF004):
|
||||
self.mpu_type = mpu_type
|
||||
self.putc_addr = 0xF001
|
||||
self.getc_addr = 0xF004
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
self.memory = memory
|
||||
self.putc_addr = putc_addr
|
||||
self.getc_addr = getc_addr
|
||||
self._breakpoints = []
|
||||
self._width = 78
|
||||
self.prompt = "."
|
||||
self._add_shortcuts()
|
||||
cmd.Cmd.__init__(self, completekey, stdin, stdout)
|
||||
self._parse_args(argv)
|
||||
self._reset(self.mpu_type,self.getc_addr,self.putc_addr)
|
||||
cmd.Cmd.__init__(self, stdin=stdin, stdout=stdout)
|
||||
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
load, rom, goto = self._parse_args(argv)
|
||||
|
||||
self._reset(self.mpu_type, self.getc_addr, self.putc_addr)
|
||||
|
||||
if load is not None:
|
||||
self.do_load("%r" % load)
|
||||
|
||||
if goto is not None:
|
||||
self.do_goto(goto)
|
||||
|
||||
if rom is not None:
|
||||
# load a ROM and run from the reset vector
|
||||
self.do_load("%r top" % rom)
|
||||
physMask = self._mpu.memory.physMask
|
||||
reset = self._mpu.RESET & physMask
|
||||
dest = self._mpu.memory[reset] + \
|
||||
(self._mpu.memory[reset + 1] << self.byteWidth)
|
||||
self.do_goto("$%x" % dest)
|
||||
|
||||
def _parse_args(self, argv):
|
||||
try:
|
||||
@ -67,10 +86,7 @@ class Monitor(cmd.Cmd):
|
||||
self._usage()
|
||||
self._exit(1)
|
||||
|
||||
load = None
|
||||
rom = None
|
||||
goto = None
|
||||
mpu = None
|
||||
load, rom, goto = None, None, None
|
||||
|
||||
for opt, value in options:
|
||||
if opt in ('-i', '--input'):
|
||||
@ -79,6 +95,19 @@ class Monitor(cmd.Cmd):
|
||||
if opt in ('-o', '--output'):
|
||||
self.putc_addr = int(value, 16)
|
||||
|
||||
if opt in ('-m', '--mpu'):
|
||||
mpu_type = self._get_mpu(value)
|
||||
if mpu_type is None:
|
||||
mpus = sorted(self.Microprocessors.keys())
|
||||
msg = "Fatal: no such MPU. Available MPUs: %s"
|
||||
self._output(msg % ', '.join(mpus))
|
||||
sys.exit(1)
|
||||
self.mpu_type = mpu_type
|
||||
|
||||
if opt in ("-h", "--help"):
|
||||
self._usage()
|
||||
self._exit(0)
|
||||
|
||||
if opt in ('-l', '--load'):
|
||||
load = value
|
||||
|
||||
@ -88,43 +117,7 @@ class Monitor(cmd.Cmd):
|
||||
if opt in ('-g', '--goto'):
|
||||
goto = value
|
||||
|
||||
if opt in ('-m', '--mpu'):
|
||||
mpu = value
|
||||
|
||||
elif opt in ("-h", "--help"):
|
||||
self._usage()
|
||||
self._exit(0)
|
||||
|
||||
if (mpu is not None) or (rom is not None):
|
||||
if mpu is None:
|
||||
mpu = "6502"
|
||||
if self._get_mpu(mpu) is None:
|
||||
mpus = list(self.Microprocessors.keys())
|
||||
mpus.sort()
|
||||
msg = "Fatal: no such MPU. Available MPUs: %s"
|
||||
self._output(msg % ', '.join(mpus))
|
||||
sys.exit(1)
|
||||
cmd = "mpu %s" % mpu
|
||||
self.onecmd(cmd)
|
||||
|
||||
if load is not None:
|
||||
cmd = "load %s" % load
|
||||
self.onecmd(cmd)
|
||||
|
||||
if goto is not None:
|
||||
cmd = "goto %s" % goto
|
||||
self.onecmd(cmd)
|
||||
|
||||
if rom is not None:
|
||||
# load a ROM and run from the reset vector
|
||||
cmd = "load '%s' top" % rom
|
||||
self.onecmd(cmd)
|
||||
physMask = self._mpu.memory.physMask
|
||||
reset = self._mpu.RESET & physMask
|
||||
dest = self._mpu.memory[reset] + \
|
||||
(self._mpu.memory[reset + 1] << self.byteWidth)
|
||||
cmd = "goto %08x" % dest
|
||||
self.onecmd(cmd)
|
||||
return load, rom, goto
|
||||
|
||||
def _usage(self):
|
||||
usage = __doc__ % sys.argv[0]
|
||||
@ -148,15 +141,16 @@ class Monitor(cmd.Cmd):
|
||||
|
||||
return result
|
||||
|
||||
def _reset(self, mpu_type,getc_addr=0xF004,putc_addr=0xF001):
|
||||
self._mpu = mpu_type()
|
||||
def _reset(self, mpu_type, getc_addr=0xF004, putc_addr=0xF001):
|
||||
self._mpu = mpu_type(memory=self.memory)
|
||||
self.addrWidth = self._mpu.ADDR_WIDTH
|
||||
self.byteWidth = self._mpu.BYTE_WIDTH
|
||||
self.addrFmt = self._mpu.ADDR_FORMAT
|
||||
self.byteFmt = self._mpu.BYTE_FORMAT
|
||||
self.addrMask = self._mpu.addrMask
|
||||
self.byteMask = self._mpu.byteMask
|
||||
self._install_mpu_observers(getc_addr,putc_addr)
|
||||
if getc_addr and putc_addr:
|
||||
self._install_mpu_observers(getc_addr, putc_addr)
|
||||
self._address_parser = AddressParser()
|
||||
self._disassembler = Disassembler(self._mpu, self._address_parser)
|
||||
self._assembler = Assembler(self._mpu, self._address_parser)
|
||||
@ -229,7 +223,7 @@ class Monitor(cmd.Cmd):
|
||||
break
|
||||
return mpu
|
||||
|
||||
def _install_mpu_observers(self,getc_addr,putc_addr):
|
||||
def _install_mpu_observers(self, getc_addr, putc_addr):
|
||||
def putc(address, value):
|
||||
try:
|
||||
self.stdout.write(chr(value))
|
||||
@ -245,7 +239,7 @@ class Monitor(cmd.Cmd):
|
||||
byte = 0
|
||||
return byte
|
||||
|
||||
m = ObservableMemory(addrWidth=self.addrWidth)
|
||||
m = ObservableMemory(subject=self.memory, addrWidth=self.addrWidth)
|
||||
m.subscribe_to_write([self.putc_addr], putc)
|
||||
m.subscribe_to_read([self.getc_addr], getc)
|
||||
|
||||
|
@ -1985,6 +1985,137 @@ class Common6502Tests:
|
||||
self.assertEqual(0x0001, mpu.pc)
|
||||
self.assertEqual(0, mpu.p & mpu.OVERFLOW)
|
||||
|
||||
# Compare instructions
|
||||
|
||||
# See http://6502.org/tutorials/compare_instructions.html
|
||||
# and http://www.6502.org/tutorials/compare_beyond.html
|
||||
# Cheat sheet:
|
||||
#
|
||||
# - Comparison is actually subtraction "register - memory"
|
||||
# - Z contains equality result (1 equal, 0 not equal)
|
||||
# - C contains result of unsigned comparison (0 if A<m, 1 if A>=m)
|
||||
# - N holds MSB of subtraction result (*NOT* of signed subtraction)
|
||||
# - V is not affected by comparison
|
||||
# - D has no effect on comparison
|
||||
|
||||
# CMP Immediate
|
||||
|
||||
def test_cmp_imm_sets_zero_carry_clears_neg_flags_if_equal(self):
|
||||
"""Comparison: A == m"""
|
||||
mpu = self._make_mpu()
|
||||
# $0000 CMP #10 , A will be 10
|
||||
self._write(mpu.memory, 0x0000, (0xC9, 10))
|
||||
mpu.a = 10
|
||||
mpu.step()
|
||||
self.assertEqual(0x0002, mpu.pc)
|
||||
self.assertEqual(0, mpu.p & mpu.NEGATIVE)
|
||||
self.assertEqual(mpu.ZERO, mpu.p & mpu.ZERO)
|
||||
self.assertEqual(mpu.CARRY, mpu.p & mpu.CARRY)
|
||||
|
||||
def test_cmp_imm_clears_zero_carry_takes_neg_if_less_unsigned(self):
|
||||
"""Comparison: A < m (unsigned)"""
|
||||
mpu = self._make_mpu()
|
||||
# $0000 CMP #10 , A will be 1
|
||||
self._write(mpu.memory, 0x0000, (0xC9, 10))
|
||||
mpu.a = 1
|
||||
mpu.step()
|
||||
self.assertEqual(0x0002, mpu.pc)
|
||||
self.assertEqual(mpu.NEGATIVE, mpu.p & mpu.NEGATIVE) # 0x01-0x0A=0xF7
|
||||
self.assertEqual(0, mpu.p & mpu.ZERO)
|
||||
self.assertEqual(0, mpu.p & mpu.CARRY)
|
||||
|
||||
def test_cmp_imm_clears_zero_sets_carry_takes_neg_if_less_signed(self):
|
||||
"""Comparison: A < #nn (signed), A negative"""
|
||||
mpu = self._make_mpu()
|
||||
# $0000 CMP #1, A will be -1 (0xFF)
|
||||
self._write(mpu.memory, 0x0000, (0xC9, 1))
|
||||
mpu.a = 0xFF
|
||||
mpu.step()
|
||||
self.assertEqual(0x0002, mpu.pc)
|
||||
self.assertEqual(mpu.NEGATIVE, mpu.p & mpu.NEGATIVE) # 0xFF-0x01=0xFE
|
||||
self.assertEqual(0, mpu.p & mpu.ZERO)
|
||||
self.assertEqual(mpu.CARRY, mpu.p & mpu.CARRY) # A>m unsigned
|
||||
|
||||
def test_cmp_imm_clears_zero_carry_takes_neg_if_less_signed_nega(self):
|
||||
"""Comparison: A < m (signed), A and m both negative"""
|
||||
mpu = self._make_mpu()
|
||||
# $0000 CMP #0xFF (-1), A will be -2 (0xFE)
|
||||
self._write(mpu.memory, 0x0000, (0xC9, 0xFF))
|
||||
mpu.a = 0xFE
|
||||
mpu.step()
|
||||
self.assertEqual(0x0002, mpu.pc)
|
||||
self.assertEqual(mpu.NEGATIVE, mpu.p & mpu.NEGATIVE) # 0xFE-0xFF=0xFF
|
||||
self.assertEqual(0, mpu.p & mpu.ZERO)
|
||||
self.assertEqual(0, mpu.p & mpu.CARRY) # A<m unsigned
|
||||
|
||||
def test_cmp_imm_clears_zero_sets_carry_takes_neg_if_more_unsigned(self):
|
||||
"""Comparison: A > m (unsigned)"""
|
||||
mpu = self._make_mpu()
|
||||
# $0000 CMP #1 , A will be 10
|
||||
self._write(mpu.memory, 0x0000, (0xC9, 1))
|
||||
mpu.a = 10
|
||||
mpu.step()
|
||||
self.assertEqual(0x0002, mpu.pc)
|
||||
self.assertEqual(0, mpu.p & mpu.NEGATIVE) # 0x0A-0x01 = 0x09
|
||||
self.assertEqual(0, mpu.p & mpu.ZERO)
|
||||
self.assertEqual(mpu.CARRY, mpu.p & mpu.CARRY) # A>m unsigned
|
||||
|
||||
def test_cmp_imm_clears_zero_carry_takes_neg_if_more_signed(self):
|
||||
"""Comparison: A > m (signed), memory negative"""
|
||||
mpu = self._make_mpu()
|
||||
# $0000 CMP #$FF (-1), A will be 2
|
||||
self._write(mpu.memory, 0x0000, (0xC9, 0xFF))
|
||||
mpu.a = 2
|
||||
mpu.step()
|
||||
self.assertEqual(0x0002, mpu.pc)
|
||||
self.assertEqual(0, mpu.p & mpu.NEGATIVE) # 0x02-0xFF=0x01
|
||||
self.assertEqual(0, mpu.p & mpu.ZERO)
|
||||
self.assertEqual(0, mpu.p & mpu.CARRY) # A<m unsigned
|
||||
|
||||
def test_cmp_imm_clears_zero_carry_takes_neg_if_more_signed_nega(self):
|
||||
"""Comparison: A > m (signed), A and m both negative"""
|
||||
mpu = self._make_mpu()
|
||||
# $0000 CMP #$FE (-2), A will be -1 (0xFF)
|
||||
self._write(mpu.memory, 0x0000, (0xC9, 0xFE))
|
||||
mpu.a = 0xFF
|
||||
mpu.step()
|
||||
self.assertEqual(0x0002, mpu.pc)
|
||||
self.assertEqual(0, mpu.p & mpu.NEGATIVE) # 0xFF-0xFE=0x01
|
||||
self.assertEqual(0, mpu.p & mpu.ZERO)
|
||||
self.assertEqual(mpu.CARRY, mpu.p & mpu.CARRY) # A>m unsigned
|
||||
|
||||
|
||||
# CPX Immediate
|
||||
|
||||
def test_cpx_imm_sets_zero_carry_clears_neg_flags_if_equal(self):
|
||||
"""Comparison: X == m"""
|
||||
mpu = self._make_mpu()
|
||||
# $0000 CPX #$20
|
||||
self._write(mpu.memory, 0x0000, (0xE0, 0x20))
|
||||
mpu.x = 0x20
|
||||
mpu.step()
|
||||
self.assertEqual(0x0002, mpu.pc)
|
||||
self.assertEqual(mpu.ZERO, mpu.p & mpu.ZERO)
|
||||
self.assertEqual(mpu.CARRY, mpu.p & mpu.CARRY)
|
||||
self.assertEqual(0, mpu.p & mpu.NEGATIVE)
|
||||
|
||||
|
||||
# CPY Immediate
|
||||
|
||||
def test_cpy_imm_sets_zero_carry_clears_neg_flags_if_equal(self):
|
||||
"""Comparison: Y == m"""
|
||||
mpu = self._make_mpu()
|
||||
# $0000 CPY #$30
|
||||
self._write(mpu.memory, 0x0000, (0xC0, 0x30))
|
||||
mpu.y = 0x30
|
||||
mpu.step()
|
||||
self.assertEqual(0x0002, mpu.pc)
|
||||
self.assertEqual(mpu.ZERO, mpu.p & mpu.ZERO)
|
||||
self.assertEqual(mpu.CARRY, mpu.p & mpu.CARRY)
|
||||
self.assertEqual(0, mpu.p & mpu.NEGATIVE)
|
||||
|
||||
|
||||
|
||||
# DEC Absolute
|
||||
|
||||
def test_dec_abs_decrements_memory(self):
|
||||
|
@ -11,6 +11,16 @@ class MPUTests(unittest.TestCase, Common6502Tests):
|
||||
mpu = self._make_mpu()
|
||||
self.assertTrue('65C02' in repr(mpu))
|
||||
|
||||
# Reset
|
||||
|
||||
def test_reset_clears_decimal_flag(self):
|
||||
# W65C02S Datasheet, Apr 14 2009, Table 7-1 Operational Enhancements
|
||||
# NMOS 6502 decimal flag = indetermine after reset, CMOS 65C02 = 0
|
||||
mpu = self._make_mpu()
|
||||
mpu.p = mpu.DECIMAL
|
||||
mpu.reset()
|
||||
self.assertEqual(0, mpu.p & mpu.DECIMAL)
|
||||
|
||||
# ADC Zero Page, Indirect
|
||||
|
||||
def test_adc_bcd_off_zp_ind_carry_clear_in_accumulator_zeroes(self):
|
||||
|
@ -158,7 +158,7 @@ class MonitorTests(unittest.TestCase):
|
||||
stdout = StringIO()
|
||||
mon = Monitor(stdout=stdout)
|
||||
mon.do_add_label('c000 base')
|
||||
mon.do_assemble('c000 rts')
|
||||
mon.do_assemble('base rts')
|
||||
|
||||
mpu = mon._mpu
|
||||
self.assertEqual(0x60, mpu.memory[0xC000])
|
||||
@ -1144,6 +1144,107 @@ class MonitorTests(unittest.TestCase):
|
||||
out = stdout.getvalue()
|
||||
self.assertTrue(out.startswith("width <columns>"))
|
||||
|
||||
def test_external_memory(self):
|
||||
stdout = StringIO()
|
||||
memory = bytearray(65536)
|
||||
memory[10] = 0xff
|
||||
mon = Monitor(memory=memory, stdout=stdout, putc_addr=None, getc_addr=None)
|
||||
self.assertEqual(0xff, memory[10], "memory must remain untouched")
|
||||
mon.do_mem('0008:000c')
|
||||
mon.do_fill('0000:0020 ab')
|
||||
self.assertEqual(0xab, memory[10], "memory must have been modified")
|
||||
out = stdout.getvalue()
|
||||
self.assertTrue(out.startswith('0008: 00 00 ff 00 00'), "monitor must see pre-initialized memory")
|
||||
|
||||
# command line options
|
||||
|
||||
def test_argv_mpu(self):
|
||||
argv = ['py65mon', '--mpu', '65c02']
|
||||
stdout = StringIO()
|
||||
mon = Monitor(argv=argv, stdout=stdout)
|
||||
self.assertEqual('65C02', mon._mpu.name)
|
||||
|
||||
def test_argv_mpu_invalid(self):
|
||||
argv = ['py65mon', '--mpu', 'bad']
|
||||
stdout = StringIO()
|
||||
try:
|
||||
Monitor(argv=argv, stdout=stdout)
|
||||
except SystemExit as exc:
|
||||
self.assertEqual(1, exc.code)
|
||||
self.assertTrue("Fatal: no such MPU." in stdout.getvalue())
|
||||
|
||||
def test_argv_goto(self):
|
||||
argv = ['py65mon', '--goto', 'c000']
|
||||
stdout = StringIO()
|
||||
memory = bytearray(0x10000)
|
||||
memory[0xc000] = 0xea # c000 nop
|
||||
memory[0xc001] = 0xea # c001 nop
|
||||
memory[0xc002] = 0x00 # c002 brk
|
||||
mon = Monitor(argv=argv, stdout=stdout, memory=memory)
|
||||
self.assertEqual(0xc002, mon._mpu.pc)
|
||||
|
||||
def test_argv_load(self):
|
||||
with tempfile.NamedTemporaryFile('wb+') as f:
|
||||
data = bytearray([0xab, 0xcd])
|
||||
f.write(data)
|
||||
f.flush()
|
||||
|
||||
argv = ['py65mon', '--load', f.name]
|
||||
stdout = StringIO()
|
||||
mon = Monitor(argv=argv, stdout=stdout)
|
||||
self.assertEqual(list(data), mon._mpu.memory[:len(data)])
|
||||
|
||||
def test_argv_rom(self):
|
||||
with tempfile.NamedTemporaryFile('wb+') as f:
|
||||
rom = bytearray(4096)
|
||||
rom[0] = 0xea # f000 nop
|
||||
rom[1] = 0xea # f001 nop
|
||||
rom[2] = 0x00 # f002 brk
|
||||
rom[-2] = 0xf000 & 0xff # fffc reset vector low
|
||||
rom[-3] = 0xf000 >> 8 # fffd reset vector high
|
||||
f.write(rom)
|
||||
f.flush()
|
||||
|
||||
argv = ['py65mon', '--rom', f.name]
|
||||
stdout = StringIO()
|
||||
mon = Monitor(argv=argv, stdout=stdout)
|
||||
self.assertEqual(list(rom), mon._mpu.memory[-len(rom):])
|
||||
self.assertEqual(0xf002, mon._mpu.pc)
|
||||
|
||||
def test_argv_input(self):
|
||||
argv = ['py65mon', '--input', 'abcd']
|
||||
stdout = StringIO()
|
||||
mon = Monitor(argv=argv, stdout=stdout)
|
||||
read_subscribers = mon._mpu.memory._read_subscribers
|
||||
self.assertEqual(1, len(read_subscribers))
|
||||
self.assertTrue('getc' in repr(read_subscribers[0xabcd]))
|
||||
|
||||
def test_argv_output(self):
|
||||
argv = ['py65mon', '--output', 'dcba']
|
||||
stdout = StringIO()
|
||||
mon = Monitor(argv=argv, stdout=stdout)
|
||||
write_subscribers = mon._mpu.memory._write_subscribers
|
||||
self.assertEqual(1, len(write_subscribers))
|
||||
self.assertTrue('putc' in repr(write_subscribers[0xdcba]))
|
||||
|
||||
def test_argv_combination_rom_mpu(self):
|
||||
with tempfile.NamedTemporaryFile('wb+') as f:
|
||||
rom = bytearray(4096)
|
||||
rom[0] = 0xea # f000 nop
|
||||
rom[1] = 0xea # f001 nop
|
||||
rom[2] = 0x00 # f002 brk
|
||||
rom[-2] = 0xf000 & 0xff # fffc reset vector low
|
||||
rom[-3] = 0xf000 >> 8 # fffd reset vector high
|
||||
f.write(rom)
|
||||
f.flush()
|
||||
|
||||
argv = ['py65mon', '--rom', f.name, '--mpu', '65c02',]
|
||||
stdout = StringIO()
|
||||
mon = Monitor(argv=argv, stdout=stdout)
|
||||
self.assertEqual('65C02', mon._mpu.name)
|
||||
self.assertEqual(list(rom), mon._mpu.memory[-len(rom):])
|
||||
self.assertEqual(0xf002, mon._mpu.pc)
|
||||
|
||||
def test_suite():
|
||||
return unittest.findTestCases(sys.modules[__name__])
|
||||
|
||||
|
@ -16,6 +16,9 @@ class ConversionsTopLevelTests(unittest.TestCase):
|
||||
self.assertEqual('1010', itoa(10, base=2))
|
||||
self.assertEqual('-1010', itoa(-10, base=2))
|
||||
|
||||
def test_itoa_unsupported_base(self):
|
||||
self.assertRaises(ValueError, itoa, 0, base=17)
|
||||
|
||||
def test_convert_to_bin(self):
|
||||
self.assertEqual(0, convert_to_bin(0))
|
||||
self.assertEqual(99, convert_to_bin(0x99))
|
||||
|
@ -1,129 +0,0 @@
|
||||
import unittest
|
||||
import sys
|
||||
from py65.utils.hexdump import load, Loader
|
||||
|
||||
|
||||
class TopLevelHexdumpTests(unittest.TestCase):
|
||||
def test_load(self):
|
||||
text = 'c000: aa bb'
|
||||
start, data = load(text)
|
||||
self.assertEqual(0xC000, start)
|
||||
self.assertEqual([0xAA, 0xBB], data)
|
||||
|
||||
|
||||
class HexdumpLoaderTests(unittest.TestCase):
|
||||
def test_empty_string_does_nothing(self):
|
||||
text = ''
|
||||
loader = Loader(text)
|
||||
self.assertEqual(None, loader.start_address)
|
||||
self.assertEqual([], loader.data)
|
||||
|
||||
def test_all_whitespace_does_nothing(self):
|
||||
text = " \r\n \t \n"
|
||||
loader = Loader(text)
|
||||
self.assertEqual(None, loader.start_address)
|
||||
self.assertEqual([], loader.data)
|
||||
|
||||
def test_raises_when_start_address_not_found(self):
|
||||
text = 'aa bb cc'
|
||||
try:
|
||||
Loader(text)
|
||||
self.fail()
|
||||
except ValueError as exc:
|
||||
msg = 'Start address was not found in data'
|
||||
self.assertEqual(msg, str(exc))
|
||||
|
||||
def test_raises_when_start_address_is_invalid(self):
|
||||
text = 'oops: aa bb cc'
|
||||
try:
|
||||
Loader(text)
|
||||
self.fail()
|
||||
except ValueError as exc:
|
||||
msg = 'Could not parse address: oops'
|
||||
self.assertEqual(msg, str(exc))
|
||||
|
||||
def test_raises_when_start_address_is_too_short(self):
|
||||
text = '01: aa bb cc'
|
||||
try:
|
||||
Loader(text)
|
||||
self.fail()
|
||||
except ValueError as exc:
|
||||
msg = 'Expected address to be 2 bytes, got 1'
|
||||
self.assertEqual(msg, str(exc))
|
||||
|
||||
def test_raises_when_start_address_is_too_long(self):
|
||||
text = '010304: aa bb cc'
|
||||
try:
|
||||
Loader(text)
|
||||
self.fail()
|
||||
except ValueError as exc:
|
||||
msg = 'Expected address to be 2 bytes, got 3'
|
||||
self.assertEqual(msg, str(exc))
|
||||
|
||||
def test_raises_when_next_address_is_unexpected(self):
|
||||
text = "c000: aa\nc002: cc"
|
||||
try:
|
||||
Loader(text)
|
||||
self.fail()
|
||||
except ValueError as exc:
|
||||
msg = 'Non-contigous block detected. Expected next ' \
|
||||
'address to be $c001, label was $c002'
|
||||
self.assertEqual(msg, str(exc))
|
||||
|
||||
def test_raises_when_data_is_invalid(self):
|
||||
text = 'c000: foo'
|
||||
try:
|
||||
Loader(text)
|
||||
self.fail()
|
||||
except ValueError as exc:
|
||||
msg = 'Could not parse data: foo'
|
||||
self.assertEqual(msg, str(exc))
|
||||
|
||||
def test_loads_data_without_dollar_signs(self):
|
||||
text = 'c000: aa bb'
|
||||
load = Loader(text)
|
||||
self.assertEqual(0xC000, load.start_address)
|
||||
self.assertEqual([0xAA, 0xBB], load.data)
|
||||
|
||||
def test_loads_data_with_some_dollar_signs(self):
|
||||
text = '$c000: aa $bb'
|
||||
load = Loader(text)
|
||||
self.assertEqual(0xC000, load.start_address)
|
||||
self.assertEqual([0xAA, 0xBB], load.data)
|
||||
|
||||
def test_loads_multiline_data_with_unix_endings(self):
|
||||
text = '$c000: aa bb\n$c002: cc'
|
||||
load = Loader(text)
|
||||
self.assertEqual(0xC000, load.start_address)
|
||||
self.assertEqual([0xAA, 0xBB, 0xCC], load.data)
|
||||
|
||||
def test_loads_multiline_data_with_dos_endings(self):
|
||||
text = '$c000: aa bb\r\n$c002: cc'
|
||||
load = Loader(text)
|
||||
self.assertEqual(0xC000, load.start_address)
|
||||
self.assertEqual([0xAA, 0xBB, 0xCC], load.data)
|
||||
|
||||
def test_ignores_semicolon_comments(self):
|
||||
text = 'c000: aa bb ;comment'
|
||||
load = Loader(text)
|
||||
self.assertEqual(0xC000, load.start_address)
|
||||
self.assertEqual([0xAA, 0xBB], load.data)
|
||||
|
||||
def test_ignores_double_dash_comments(self):
|
||||
text = 'c000: aa bb -- comment'
|
||||
load = Loader(text)
|
||||
self.assertEqual(0xC000, load.start_address)
|
||||
self.assertEqual([0xAA, 0xBB], load.data)
|
||||
|
||||
def test_ignores_pound_comments(self):
|
||||
text = 'c000: aa bb # comment'
|
||||
load = Loader(text)
|
||||
self.assertEqual(0xC000, load.start_address)
|
||||
self.assertEqual([0xAA, 0xBB], load.data)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.findTestCases(sys.modules[__name__])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
@ -1,20 +1,12 @@
|
||||
|
||||
|
||||
def itoa(num, base=10):
|
||||
""" Convert a decimal number to its equivalent in another base.
|
||||
This is essentially the inverse of int(num, base).
|
||||
"""Convert a decimal number to its equivalent in base 2 or 16; base 10
|
||||
is silently passed through. Other bases raise a ValueError. Returns a
|
||||
string with hex digits lowercase.
|
||||
"""
|
||||
negative = num < 0
|
||||
if negative:
|
||||
num = -num
|
||||
digits = []
|
||||
while num > 0:
|
||||
num, last_digit = divmod(num, base)
|
||||
digits.append('0123456789abcdefghijklmnopqrstuvwxyz'[last_digit])
|
||||
if negative:
|
||||
digits.append('-')
|
||||
digits.reverse()
|
||||
return ''.join(digits)
|
||||
fmt = _itoa_fmts.get(base)
|
||||
if fmt is None:
|
||||
raise ValueError("Unsupported base: %r" % base)
|
||||
return fmt.format(num)
|
||||
|
||||
|
||||
def convert_to_bin(bcd):
|
||||
@ -25,6 +17,13 @@ def convert_to_bcd(bin):
|
||||
return _bin2bcd[bin]
|
||||
|
||||
|
||||
_itoa_fmts = {
|
||||
2: "{0:b}",
|
||||
10: "{0}",
|
||||
16: "{0:x}"
|
||||
}
|
||||
|
||||
|
||||
_bcd2bin = (
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13,
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 20, 21, 22, 23, 24, 25,
|
||||
|
@ -1,90 +0,0 @@
|
||||
from binascii import a2b_hex
|
||||
|
||||
|
||||
def load(text):
|
||||
load = Loader(text)
|
||||
return (load.start_address, load.data)
|
||||
|
||||
|
||||
class Loader:
|
||||
def __init__(self, text):
|
||||
self.load(text)
|
||||
|
||||
def load(self, text):
|
||||
self._reset()
|
||||
|
||||
for line in text.splitlines():
|
||||
self._parse_line(line)
|
||||
|
||||
def _reset(self):
|
||||
self.data = []
|
||||
self.start_address = None
|
||||
self.current_address = None
|
||||
|
||||
def _parse_line(self, line):
|
||||
line = self._remove_comments(line)
|
||||
pieces = line.strip().split()
|
||||
|
||||
for piece in pieces:
|
||||
if piece.startswith('$'):
|
||||
piece = piece[1:]
|
||||
|
||||
if piece.endswith(':'):
|
||||
self._parse_address(piece[:-1])
|
||||
|
||||
else:
|
||||
self._parse_bytes(piece)
|
||||
|
||||
def _remove_comments(self, line):
|
||||
for delimiter in (';', '--', '#'):
|
||||
pos = line.find(delimiter)
|
||||
if pos != -1:
|
||||
line = line[:pos]
|
||||
return line
|
||||
|
||||
def _parse_address(self, piece):
|
||||
try:
|
||||
binstr = a2b_hex(piece.encode('utf-8'))
|
||||
if isinstance(binstr, str):
|
||||
addr_bytes = [ ord(b) for b in binstr ]
|
||||
else: # Python 3
|
||||
addr_bytes = [ b for b in binstr ]
|
||||
except (TypeError, ValueError):
|
||||
msg = "Could not parse address: %s" % piece
|
||||
raise ValueError(msg)
|
||||
|
||||
if len(addr_bytes) != 2:
|
||||
msg = "Expected address to be 2 bytes, got %d" % (
|
||||
len(addr_bytes))
|
||||
raise ValueError(msg)
|
||||
|
||||
address = (addr_bytes[0] << 8) + addr_bytes[1]
|
||||
|
||||
if self.start_address is None:
|
||||
self.start_address = address
|
||||
self.current_address = address
|
||||
|
||||
elif address != (self.current_address):
|
||||
msg = "Non-contigous block detected. Expected next address " \
|
||||
"to be $%04x, label was $%04x" % (self.current_address,
|
||||
address)
|
||||
raise ValueError(msg)
|
||||
|
||||
def _parse_bytes(self, piece):
|
||||
if self.start_address is None:
|
||||
msg = "Start address was not found in data"
|
||||
raise ValueError(msg)
|
||||
|
||||
else:
|
||||
try:
|
||||
binstr = a2b_hex(piece.encode('utf-8'))
|
||||
if isinstance(binstr, str):
|
||||
data_bytes = [ ord(b) for b in binstr ]
|
||||
else: # Python 3
|
||||
data_bytes = [ b for b in binstr ]
|
||||
except (TypeError, ValueError):
|
||||
msg = "Could not parse data: %s" % piece
|
||||
raise ValueError(msg)
|
||||
|
||||
self.current_address += len(data_bytes)
|
||||
self.data.extend(data_bytes)
|
17
setup.py
17
setup.py
@ -1,4 +1,4 @@
|
||||
__version__ = '0.25.dev0'
|
||||
__version__ = '2.0.0.dev0'
|
||||
|
||||
import sys
|
||||
|
||||
@ -6,11 +6,11 @@ py_version = sys.version_info[:2]
|
||||
PY3 = py_version[0] == 3
|
||||
|
||||
if PY3:
|
||||
if py_version < (3, 2):
|
||||
raise RuntimeError('On Python 3, Py65 requires Python 3.2 or later')
|
||||
if py_version < (3, 4):
|
||||
raise RuntimeError('On Python 3, Py65 requires Python 3.4 or later')
|
||||
else:
|
||||
if py_version < (2, 6):
|
||||
raise RuntimeError('On Python 2, Py65 requires Python 2.6 or later')
|
||||
if py_version < (2, 7):
|
||||
raise RuntimeError('On Python 2, Py65 requires Python 2.7 or later')
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
@ -25,12 +25,12 @@ CLASSIFIERS = [
|
||||
'Operating System :: POSIX',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Assembly',
|
||||
'Topic :: Software Development :: Assemblers',
|
||||
'Topic :: Software Development :: Disassemblers',
|
||||
@ -59,7 +59,6 @@ setup(
|
||||
tests_require=[],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
namespace_packages=['py65'],
|
||||
test_suite="py65.tests",
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
Loading…
Reference in New Issue
Block a user