Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ and then delete breakpoint #1, the next breakpoint you add will be
breakpoint #3, not #1. Also, invoking ``reset`` clears breakpoints
too, not just labels.

Comments
-----------

Any text following a `;` will be treated as a comment and removed.
This can be useful for documenting sequences of commands used for `script`.


Command Reference
=================

Expand Down Expand Up @@ -216,6 +223,12 @@ Command Reference
.cycles
12

.. describe:: continue

Continue execution from the current program counter::

.continue

.. describe:: delete_breakpoint <breakpoint_id>

Removes the breakpoint associated with the given identifier::
Expand Down Expand Up @@ -304,6 +317,14 @@ Command Reference
Breakpoint 1 : $5678
Breakpoint 2 : $9ABC

.. describe:: batch <filename>

Read commands from <filename> as if typed interactively,
except that blank lines are ignored rather than repeating the prior command.
As usual, text beginning with ; will be treated as a comment and ignored

.batch some.cmd

.. describe:: load <filename> <address>

Load a binary file into memory starting at the address specified::
Expand Down
12 changes: 8 additions & 4 deletions py65/devices/mpu6502.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ def reprformat(self):

def __repr__(self):
flags = itoa(self.p, 2).rjust(self.BYTE_WIDTH, '0')
indent = ' ' * (len(self.name) + 2)

return self.reprformat() % (indent, self.name, self.pc, self.a,
self.x, self.y, self.sp, flags)
#indent = ' ' * (len(self.name) + 2)
pairs = [''.join(t) for t in zip('NV-BDIZC', flags) if t[0] != '-']
return "%s: PC=%04x A=%02x X=%02x Y=%02x SP=%02x FLAGS=<%s>" % (
self.name, self.pc, self.a, self.x, self.y, self.sp, ' '.join(pairs)
)

#return self.reprformat() % (indent, self.name, self.pc, self.a,
# self.x, self.y, self.sp, flags)

def step(self):
instructCode = self.memory[self.pc]
Expand Down
114 changes: 93 additions & 21 deletions py65/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
-l, --load <file> : Load a file at address 0
-r, --rom <file> : Load a rom at the top of address space and reset into it
-g, --goto <address> : Perform a goto command after loading any files
-b, --batch <file> : Run a batch of commands from file
-i, --input <address> : define location of getc (default $f004)
-o, --output <address> : define location of putc (default $f001)
"""
Expand All @@ -34,6 +35,14 @@
from py65.utils.conversions import itoa
from py65.memory import ObservableMemory


def to_chr(c, enc='ascii7', nonprintable='.'):
"""Convert a byte to a printable character, based on end scheme"""
if enc == 'ascii7':
c &= 0x7f
return chr(c) if 0x20 <= c < 0x7f else nonprintable


class Monitor(cmd.Cmd):

Microprocessors = {'6502': NMOS6502, '65C02': CMOS65C02,
Expand All @@ -48,8 +57,10 @@ def __init__(self, argv=None, stdin=None, stdout=None,
self.getc_addr = getc_addr
self._breakpoints = []
self._width = 78
self._show_ascii = False
self.prompt = "."
self._add_shortcuts()
self.batch = False

# Save the current system input mode so it can be restored after
# after processing commands and before exiting.
Expand All @@ -69,7 +80,7 @@ def __init__(self, argv=None, stdin=None, stdout=None,

if argv is None:
argv = sys.argv
load, rom, goto = self._parse_args(argv)
load, rom, goto, batch = self._parse_args(argv)

self._reset(self.mpu_type, self.getc_addr, self.putc_addr)

Expand All @@ -79,6 +90,9 @@ def __init__(self, argv=None, stdin=None, stdout=None,
if goto is not None:
self.do_goto(goto)

if batch is not None:
self.do_batch(batch)

if rom is not None:
# load a ROM and run from the reset vector
self.do_load("%r top" % rom)
Expand Down Expand Up @@ -107,15 +121,15 @@ def __del__(self):

def _parse_args(self, argv):
try:
shortopts = 'hi:o:m:l:r:g:'
longopts = ['help', 'mpu=', 'input=', 'output=', 'load=', 'rom=', 'goto=']
options, args = getopt.getopt(argv[1:], shortopts, longopts)
shortopts = 'hi:o:m:l:r:g:b:'
longopts = ['help', 'mpu=', 'input=', 'output=', 'load=', 'rom=', 'goto=', 'batch=']
options, _ = getopt.getopt(argv[1:], shortopts, longopts)
except getopt.GetoptError as exc:
self._output(exc.args[0])
self._usage()
self._exit(1)

load, rom, goto = None, None, None
load, rom, goto, batch = None, None, None, None

for opt, value in options:
if opt in ('-i', '--input'):
Expand Down Expand Up @@ -146,14 +160,18 @@ def _parse_args(self, argv):
if opt in ('-g', '--goto'):
goto = value

return load, rom, goto
if opt in ('-b', '--batch'):
batch = value

return load, rom, goto, batch

def _usage(self):
usage = __doc__ % sys.argv[0]
self._output(usage)

def onecmd(self, line):
line = self._preprocess_line(line)
if line:
line = self._preprocess_line(line)

result = None
try:
Expand All @@ -164,7 +182,7 @@ def onecmd(self, line):
error = ''.join(traceback.format_exception(e))
self._output(error)

if not line.startswith("quit"):
if line and not line.startswith("quit"):
self._output_mpu_status()

# Switch back to the previous input mode.
Expand Down Expand Up @@ -192,6 +210,8 @@ def _add_shortcuts(self):
'a': 'assemble',
'ab': 'add_breakpoint',
'al': 'add_label',
'b': 'batch',
'c': 'continue',
'd': 'disassemble',
'db': 'delete_breakpoint',
'dl': 'delete_label',
Expand Down Expand Up @@ -413,16 +433,11 @@ def _interactive_assemble(self, args):
self.stdout.write("\r$%s ?Syntax\n" % addr)

def do_disassemble(self, args):
splitted = shlex.split(args)
if len(splitted) != 1:
split = shlex.split(args)
if len(split) != 1:
return self.help_disassemble()

address_parts = splitted[0].split(":")
start = self._address_parser.number(address_parts[0])
if len(address_parts) > 1:
end = self._address_parser.number(address_parts[1])
else:
end = start
start, end = self._address_parser.range(split[0], no_wrap=False)

max_address = (2 ** self._mpu.ADDR_WIDTH) - 1
cur_address = start
Expand All @@ -439,6 +454,7 @@ def do_disassemble(self, args):
if start > end and cur_address > max_address:
needs_wrap = False
cur_address = 0
self._address_parser.next_addr = cur_address

def _format_disassembly(self, address, length, disasm):
cur_address = address
Expand Down Expand Up @@ -492,6 +508,39 @@ def do_goto(self, args):
brks = [0x00] # BRK
self._run(stopcodes=brks)

def help_continue(self):
self._output("continue")
self._output("Continues execution from the current PC")

def do_continue(self, args):
brks = [0x00] # BRK
self._run(stopcodes=brks)

def help_batch(self):
self._output("batch <commmands>")
self._output("Read and execute commands from the specified file")

def do_batch(self, args):
try:
self.cmdqueue += open(args).read().splitlines()
self.batch = True
except OSError:
self._output("Couldn't read commands from %s" % args)

def precmd(self, line):
if self.batch and line:
self._output(line)
return line

def postcmd(self, stop, line):
if self.batch and not self.cmdqueue:
self.batch = False
return stop

def emptyline(self):
if not self.batch:
return cmd.Cmd.emptyline(self)

def _run(self, stopcodes):
stopcodes = set(stopcodes)
breakpoints = set(self._breakpoints)
Expand Down Expand Up @@ -684,6 +733,7 @@ def format(msb, lsb):
return (msb << 8) + lsb
bytes = list(map(format, bytes[0::2], bytes[1::2]))

self._address_parser.next_addr = start
self._fill(start, start, bytes)

def help_save(self):
Expand Down Expand Up @@ -767,27 +817,49 @@ def _fill(self, start, end, filler):
self._output(("Wrote +%d bytes from " + starttoend) % fmt)

def help_mem(self):
self._output("mem <address_range>")
self._output("mem <address_range> [noascii|ascii]")
self._output("Display the contents of memory.")
self._output("Optional format toggles ascii display.")
self._output('Range is specified like "<start:end>".')

def do_mem(self, args):
split = shlex.split(args)
if len(split) != 1:
if len(split) not in [1, 2]:
return self.help_mem()
if len(split) == 2:
flag = split[1].lower()
if 'asc' not in flag:
return self.help_mem()
self._show_ascii = flag.startswith('asc')

start, end = self._address_parser.range(split[0])

line = self.addrFmt % start + ":"
if self._show_ascii:
pad = " "
chrs = " "
else:
pad = " "
chrs = ""
right_justify = 0
for address in range(start, end + 1):
byte = self._mpu.memory[address]
more = " " + self.byteFmt % byte

exceeded = len(line) + len(more) > self._width
more = pad + self.byteFmt % byte
exceeded = len(line) + len(chrs) + len(more) > self._width
if exceeded:
right_justify = len(line)
if self._show_ascii:
line += chrs
chrs = " "
self._output(line)
line = self.addrFmt % address + ":"
line += more
if self._show_ascii:
chrs += to_chr(byte)
if self._show_ascii:
if right_justify:
line += ' ' * (right_justify - len(line))
line += chrs
self._output(line)

def help_add_label(self):
Expand Down
27 changes: 18 additions & 9 deletions py65/utils/addressing.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def __init__(self, maxwidth=16, radix=16, labels={}):
"""
self.radix = radix
self.maxwidth = maxwidth

self.next_addr = 0
self.labels = {}
for k, v in labels.items():
self.labels[k] = self._constrain(v)
Expand Down Expand Up @@ -84,18 +84,27 @@ def number(self, num):
except ValueError:
raise KeyError("Label not found: %s" % num)

def range(self, addresses):
"""Parse a string containing an address or a range of addresses
into a tuple of (start address, end address)
def range(self, addresses, no_wrap=True):
"""
Parse a string containing an address or a range of addresses
into a tuple of (start address, end address).
Start should be an address, label or empty implying default, e.g. _mpu.pc
A singleton implies a single byte.
A range specified with : or , extends to the end address or label
A range specified with / interprets end as an offset from start
"""
matches = re.match('^([^:,]+)\s*[:,]+\s*([^:,]+)$', addresses)
if matches:
start, end = map(self.number, matches.groups(0))
# split and keep delim, e.g. 123+456 => ['123', '+', '456']
parts = re.split(r'([:,/])', addresses.strip())
start = self.number(parts[0]) if parts[0] else self.next_addr
if len(parts) >= 3:
v = self.number(parts[2])
end = start + max(0, v-1) if parts[1] == '/' else v
else:
start = end = self.number(addresses)
end = start

if start > end:
if no_wrap and start > end:
start, end = end, start
self.next_addr = (end + 1) % self._maxaddr
return (start, end)

def _constrain(self, address):
Expand Down