Skip to content
Merged
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
1 change: 1 addition & 0 deletions tools/bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ whatever node appeared beside it.
| `grants.py` | Service bit 20 end to end: packages `grants/module.cpp` and `grants/client.a` as `/VMS/GRANTS`, then launches one content file per slice size. The client grants a slice from a CIA2 timer NMI every 200 cycles, when it will end before the next tick, and counts the NMIs it loses; the module writes 4 KiB to `$4000` for 1.5 s and exits with its job count and that loss, which the main image prints on the way back. Each row also checks a sum the client takes over what it holds after the last job, and a slice that never fits, like a client that never grants (the first row), ends the job `VM_C64_NO_GRANT`. `grants.py 120 140` runs only those rows. Needs `acme` on `PATH` and a host serving bit 20 installed. |
| `screen.py` | The C64 text screen. |
| `keypress.py [code]` | Put a key in the keyboard buffer (default `Y`; F1/F3/F5/F7 are `0x85`..`0x88`). |
| `type.py [--c128] '<text>'` | Type a line into the keyboard buffer, ten keys at a time; `\n` is RETURN. `--c128` for a C128 in 128 mode. |
| `colors.py` | Top row, its colour RAM and the VIC colour registers. |
| `listen.py [secs] [hex]` | Echo serial output, optionally sending bytes first. |
| `probe.py` | Which image is running -- main, minimal or silent -- and its build banner. |
Expand Down
2 changes: 2 additions & 0 deletions tools/bench/c64.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
VIC_DEN = 0x10

KEYBUF, KEYCOUNT = 0x0277, 0x00C6 # KERNAL keyboard buffer, and the count GETIN reads
KEYBUF_128, KEYCOUNT_128 = 0x034A, 0x00D0 # the same pair in C128 mode
KEYBUF_SIZE = 10
PETSCII_U, PETSCII_Y = 0x55, 0x59
F1, F3, F5, F7, F8 = 0x85, 0x86, 0x87, 0x88, 0x8C

Expand Down
33 changes: 33 additions & 0 deletions tools/bench/test_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: MIT
"""Tests for type.py's text-to-key-codes step. python3 -m unittest discover -s tools/bench"""
import importlib
import unittest

keys = importlib.import_module('type').keys


class Keys(unittest.TestCase):
def test_letters_go_in_unshifted(self):
self.assertEqual(keys('list'), b'LIST')

def test_newline_is_return(self):
self.assertEqual(keys('run\\n'), b'RUN\r')

def test_hex_escape_is_sent_as_written(self):
self.assertEqual(keys('\\x61\\x0a\\x85'), b'\x61\x0a\x85')

def test_backslash_escapes_itself(self):
self.assertEqual(keys('a\\\\b'), b'A\\B')

def test_malformed_escape_is_refused(self):
for text in ('ab\\', '\\xZZ', '\\x6', '\\t', '\\u0041'):
with self.subTest(text=text), self.assertRaises(ValueError):
keys(text)

def test_non_ascii_is_refused(self):
with self.assertRaises(ValueError):
keys('café')


if __name__ == '__main__':
unittest.main()
69 changes: 69 additions & 0 deletions tools/bench/type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Type a line into the KERNAL keyboard buffer by DMA, a buffer-full at a time.

type.py [--c128] '<text>'

`\\n` is RETURN, `\\\\` a backslash and `\\xNN` any key code. Letters go in unshifted (uppercase on the
power-on character set).
--c128 uses the C128-mode buffer; C64 mode, including a C128 in 64 mode, is the default.
"""
import re
import sys
import time
from c64 import KEYBUF, KEYBUF_128, KEYBUF_SIZE, KEYCOUNT, KEYCOUNT_128
from trlink import Link

DRAIN_TIMEOUT = 30
RETURN = 0x0D
TOKEN = re.compile(r'\\x([0-9a-fA-F]{2})|\\(.?)|(.)', re.DOTALL)


def main():
args = sys.argv[1:]
c128 = '--c128' in args
args = [a for a in args if a != '--c128']
if len(args) != 1:
sys.exit(__doc__.strip())
keybuf, keycount = (KEYBUF_128, KEYCOUNT_128) if c128 else (KEYBUF, KEYCOUNT)
try:
data = keys(args[0])
except ValueError as e:
sys.exit(str(e))
with Link() as tr:
for i in range(0, len(data), KEYBUF_SIZE):
wait_until_read(tr, keycount, i)
chunk = data[i:i + KEYBUF_SIZE]
if not (tr.poke(keybuf, chunk) and tr.poke(keycount, [len(chunk)])):
sys.exit(f'poke refused at character {i}')
wait_until_read(tr, keycount, len(data))


def keys(text):
if not text.isascii():
raise ValueError('ASCII only: write any other key code as \\xNN')
out = bytearray()
for code, escaped, plain in TOKEN.findall(text):
if code:
out.append(int(code, 16))
elif plain:
out += plain.upper().encode()
elif escaped == 'n':
out.append(RETURN)
elif escaped == '\\':
out += b'\\'
else:
raise ValueError(f'unknown escape \\{escaped}: use \\n, \\\\ or \\xNN')
return bytes(out)


def wait_until_read(tr, keycount, at):
start = time.time()
while tr.peek(keycount, 1)[0]:
if time.time() - start > DRAIN_TIMEOUT:
sys.exit(f'keyboard buffer not read for {DRAIN_TIMEOUT} s, at character {at}')
time.sleep(0.1)


if __name__ == '__main__':
main()
15 changes: 15 additions & 0 deletions tools/c128/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# C128 tools

Scripts for working on C128 code for the TR in VICE. Python 3, standard library only.

| Script | Does |
|--------|------|
| `vicemon.py "<before>" "<after>"` | Run VICE remote-monitor commands, resume, and run more once a breakpoint stops it, on one connection. |

## Headless VICE

The headless VICE build (`--enable-headlessui`) runs without a window. It cannot save
screenshots, so read results from memory through the monitor instead (`bsave`, or `m`).

x128 -default -remotemonitor -monlog -monlogname mon.log -limitcycles 2000000000 &
python3 tools/c128/vicemon.py "break 2f3c" "m 2f3c 2f4f"
66 changes: 66 additions & 0 deletions tools/c128/vicemon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Run VICE remote-monitor commands across a breakpoint, on one connection.

vicemon.py "<cmds before x>" "<cmds after the break>"

Commands are separated by ';'. The first set runs, then `x` resumes the emulator; once the
monitor prompt comes back (a breakpoint or watchpoint hit), the second set runs. Start
VICE with -remotemonitor (127.0.0.1:6510, or $VICE_MONITOR as host or host:port).
"""
import os
import re
import socket
import sys

PROMPT = re.compile(rb'\(C:\$[0-9a-fA-F]{4}\) $')
COMMAND_TIMEOUT = 30
GREETING_WAIT = 0.5
DEFAULT_HOST, DEFAULT_PORT = '127.0.0.1', 6510


def main():
if len(sys.argv) != 3:
sys.exit(__doc__.strip())
host, _, port = os.environ.get('VICE_MONITOR', DEFAULT_HOST).partition(':')
s = socket.create_connection((host or DEFAULT_HOST, int(port) if port else DEFAULT_PORT))

def send(cmd):
s.sendall((cmd + '\n').encode())

def until_prompt(timeout, what):
s.settimeout(timeout)
out = b''
try:
while not PROMPT.search(out):
data = s.recv(65536)
if not data:
sys.exit(f'VICE closed the monitor connection {what}')
out += data
except socket.timeout:
sys.exit(f'no monitor prompt within {timeout} s {what}')
return out.decode('latin1')

def run(cmds, show):
for cmd in commands(cmds):
send(cmd)
out = until_prompt(COMMAND_TIMEOUT, f'after {cmd!r}')
if show:
print(out)

s.settimeout(GREETING_WAIT)
try:
s.recv(65536)
except socket.timeout:
pass
run(sys.argv[1], show=False)
send('x')
print(until_prompt(None, 'before stopping'))
run(sys.argv[2], show=True)


def commands(cmds):
return [c.strip() for c in cmds.split(';') if c.strip()]


main()
Loading