1
0
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:
kris 2019-08-20 14:12:24 +01:00
commit 430fec1e28
16 changed files with 392 additions and 324 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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):

View File

@ -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):

View File

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

View File

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

View File

@ -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')

View File

@ -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,

View File

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

View File

@ -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': [

View File

@ -1,6 +1,6 @@
[tox]
envlist =
py26,py27,py32,py33,py34,py35,pypy
py27,py34,py35,py36,py37,pypy
[testenv]
commands =