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 @@ -48,6 +48,7 @@ whatever node appeared beside it.
| `hostenter.py <path> [phrase]` | The other shape from `exttest.py`, for a host that hands the machine back: launch, the port drops, the host runs and resets, and the main image prints its `VmFail` record on the way back up. With a `phrase` that record is the assertion; without one the run reports what it saw and asserts nothing, because a launch that failed after the jump reboots the same way a good one does. A host that stays resident never gives the main image's port back, and what that looks like here depends on whether minimal's orphan node stayed behind: with no node, `REBOOT_TIMEOUT` seconds and then `the board did not come back`; with one -- the usual case -- the screen read that follows fails instead, `no reply -- DMA read is not compiled into this image, or this is not the main image`, which names the wrong diagnosis for a host that is running exactly as intended. Use `exttest.py` for those; it treats no image answering as the success it is. |
| `peek.py <hex addr> <len>` | Hex dump C64 memory. |
| `spans.py [NMI period]` | `WriteC64Spans` against a C64 running `spanclient.a`: read-back across slice boundaries, the 1,024-slice refusal, what one slice costs with the C64 loop writing, only reading, and only reading with the screen blanked, and timer NMIs lost per job against slice size. Needs `acme` on `PATH`, and resets the C64 when done. |
| `regsweep.py <dwell secs> <value> ...` | The read margin of the Special IO handler chosen in Settings, per `docs/Architecture/Constraints.md`. Runs `regread.a`, which reads the REU's address and length registers back, checks that `$DF10-$DF1F` read `$FF` beside a TR page at `$DE00`, and checks the TR page's `TRM`/`TRB` IDs. Then, for each value, sets `nS_PLAprop` (`tp###`) and prints the wrong answers the C64 counted in `dwell` seconds, and restores the defaults (`td`) at the end. Needs `acme` on `PATH` and an image built with `Dbg_SerTimChg` in `TeensyROM.h`. |
| `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`). |
Expand Down
126 changes: 126 additions & 0 deletions tools/bench/regread.a
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
; C64 side of regsweep.py: reads the cartridge's registers in a loop and counts
; wrong answers at $C000, where regsweep.py reads them by DMA.
; $C000/1 passes (256 rounds each) $C002-4 REU register errors $C005-7 $FF-page errors
; $C008-A TR page ID errors $C00B layout: 0 plain REU, 1 REU+TR page at $DE, 2 GeoRAM (TR page at $DF)
; Error counts are 24-bit: with every read failing they pass 65535 within 4 seconds.
!cpu 6502
res = $c000
val = $fb
page = $fc ; pointer $fc/$fd to the TR page
save = $02
* = $0801
!word +, 10
!byte $9e
!text "2061"
!byte 0
+ !word 0
lda #0
ldx #$0b
- sta res,x
dex
bpl -
sta page
lda #$df
jsr hasTRB
bcc geo
lda #$de
jsr hasTRB
bcc beside
lda #0
beq setmode
geo lda #2
bne setmode
beside lda #1
setmode sta res+$0b

loop lda res+$0b
cmp #2
beq trpage
ldx #6 ; REU address and length registers read back as written
- txa
clc
adc val
sta $df02,x
dex
bpl -
ldx #6
- txa
clc
adc val
cmp $df02,x
beq +
inc res+2
bne +
inc res+3
bne +
inc res+4
+ dex
bpl -
lda res+$0b
beq next
ldx #$0f ; beside the TR page, $DF10-$DF1F read $FF
- lda $df10,x
cmp #$ff
beq +
inc res+5
bne +
inc res+6
bne +
inc res+7
+ dex
bpl -
trpage jsr checkIDs
next inc val
bne loop
inc res
bne loop
inc res+1
jmp loop

checkIDs ; page+3..5 = "TRM", page+$10..$12 = "TRB"
ldy #2
- sty save
tya
clc
adc #3
tay
lda (page),y
ldy save
cmp trm,y
beq +
jsr idErr
+ tya
clc
adc #$10
tay
lda (page),y
ldy save
cmp trb,y
beq +
jsr idErr
+ dey
bpl -
rts

idErr inc res+8
bne +
inc res+9
bne +
inc res+$0a
+ rts

hasTRB sta page+1 ; carry clear when "TRB" is at A*256+$10
ldy #$12
- lda (page),y
cmp trb-$10,y
bne +
dey
cpy #$10
bcs -
clc
rts
+ sec
rts

trm !text "TRM"
trb !text "TRB"
74 changes: 74 additions & 0 deletions tools/bench/regsweep.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Read margin of the Special IO handler in use: raise nS_PLAprop while regread.a
reads the cartridge's registers, and count the wrong answers at each step.
regsweep.py DWELL VALUE...

Needs acme on PATH, an image built with Dbg_SerTimChg, the Special IO handler
under test chosen in Settings, and the C64 at the menu. Restores the default
timing when done, and leaves regread running.
"""
import os, subprocess, sys, tempfile, time
from protocol import DRIVE_SD
from trlink import Link

RESULTS = 0xC000
LAYOUTS = {0: 'plain REU', 1: 'REU with the TR page at $DE00', 2: 'GeoRAM with the TR page at $DF00'}


def counters(tr):
b = tr.peek(RESULTS, 12)
return dict(passes=b[0] | b[1] << 8, reu=int.from_bytes(b[2:5], 'little'),
ff=int.from_bytes(b[5:8], 'little'), ids=int.from_bytes(b[8:11], 'little'), layout=b[11])


def start_regread(tr):
here = os.path.dirname(os.path.abspath(__file__))
with tempfile.TemporaryDirectory() as work:
subprocess.run(['acme', '-f', 'cbm', '-o', os.path.join(work, 'regread.prg'),
os.path.join(here, 'regread.a')], check=True, cwd=work)
tr.post(os.path.join(work, 'regread.prg'), '/REGREAD.PRG')
tr.launch('/REGREAD.PRG', DRIVE_SD)
time.sleep(3)
first = counters(tr)['passes']
time.sleep(1)
if counters(tr)['passes'] == first:
raise SystemExit('regread is not running')


def timing(tr, command):
tr.drain(0.2)
tr.wr(command)
return tr.text()


def main():
if len(sys.argv) < 3:
raise SystemExit(__doc__)
dwell = float(sys.argv[1])
values = [int(v) for v in sys.argv[2:]]
with Link() as tr:
if 'nS_PLAprop' not in timing(tr, b't'):
raise SystemExit('no timing commands: this image was built without Dbg_SerTimChg')
start_regread(tr)
print(f'layout: {LAYOUTS.get(counters(tr)["layout"], "unknown")}')
try:
for value in values:
shown = timing(tr, b'tp%03d' % value)
if 'nS_PLAprop %03d' % value not in shown:
raise SystemExit(f'tp{value:03d} not taken:\n{shown}')
time.sleep(0.5)
before = counters(tr)
time.sleep(dwell)
after = counters(tr)
added = {k: after[k] - before[k] & 0xFFFFFF for k in ('reu', 'ff', 'ids')}
added['passes'] = after['passes'] - before['passes'] & 0xFFFF
print(f'nS_PLAprop {value:3d} passes +{added["passes"]:5d} REU registers +{added["reu"]:6d} '
f'$FF page +{added["ff"]:6d} TR page IDs +{added["ids"]:6d}', flush=True)
finally:
if 'Defaults set' not in timing(tr, b'td'):
raise SystemExit('td not confirmed: the board is still running the last value')


if __name__ == '__main__':
main()
Loading