443 lines
27 KiB
Markdown
443 lines
27 KiB
Markdown
# 1200 baud: two machines in real time, and the CONNECT 1200 auto-baud path
|
|
|
|
> **Note on addresses.** This report records a run made before the probe-hardening pass, which
|
|
> relocated six internal helpers. Addresses quoted below are the ones those routines had at the
|
|
> time of the run. See `ADDRESS_CHANGES.md` for the mapping onto the current source.
|
|
|
|
1200 is the rate most likely to matter in practice. It is the other speed the stock user-port module
|
|
can produce, so it is the only place besides 300 where a SwiftLink player and an unmodified user-port
|
|
player can meet, and it is the rate a real Hayes modem's `CONNECT 1200` result code puts the driver
|
|
on by itself. Until now it had never been driven at all: `testReport.md` records that the only
|
|
control-register values the module had ever been seen to write were `$1E` (the probe), `$15` (300)
|
|
and `$18` (2400).
|
|
|
|
Everything below is VICE 3.7.1 (`x64sc`), NTSC, `-acia1mode 1 -acia1base 0xDE00 -acia1irq 1`, on
|
|
**2026-08-22**, against a disk built from the current source with `./build.sh`
|
|
(`checkAbi.py` prints `ABI CHECK PASSED`). Both emulators run at **true C64 speed** - warp is used
|
|
only for the loader and switched off through the monitor's `warp` command before the options menu -
|
|
and their own cycle counters are read at every window edge to prove it.
|
|
|
|
Short answer:
|
|
|
|
* **1200 baud works, in both directions, on both machines, for the whole of six 75-second windows
|
|
over three boots.** 452 seconds of measured link time, 13121 bytes one way and 12109 the other,
|
|
and **not one second in either direction carried zero bytes**.
|
|
* **The transmitter never parked in the old deadlock state.** Thirty state readings at the window
|
|
edges: `txCharActive` `$E5BB` and `aciaCommandShadow` `$E5BE` were only ever seen as the two
|
|
self-consistent pairs, `$00`/`$09` (idle) and `$01`/`$05` (a byte of ours in the chip). The pair
|
|
that defined the fault - `$E5BB` set while `$E5BE` bit 2 is clear - did not occur once, and the
|
|
script's deadlock probe never fired.
|
|
* **1200 is the first rate at which the 20-byte receive ring has been seen to overflow.** Thirty-two
|
|
overflows across the six windows. Every one of them was a **ring overflow, not a bad character** -
|
|
a conditional tracepoint proves that for the nineteen in the four windows it was live in, and the
|
|
other thirteen carry the same fingerprint - and every one of them was on the same machine. The link
|
|
resynchronised and carried on each time; it never wedged, and `connectionPhase` was back at 2 or 3
|
|
at every edge with `isLinkActive` `$E03B` = `$01`.
|
|
* **The auto-baud path really does select 1200.** A fake Hayes modem answered the module's AT string
|
|
with `CONNECT 1200`; the driver sniffed it and wrote ACIA control `$17`. Driving `$E7B2` directly
|
|
with X = 0, 3 and 6 gave `$15`, `$17` and `$18` - the three-byte stride of `baudEntryTable` `$E6FD`
|
|
behaving, not just the one index.
|
|
|
|
Scripts: `testRealtime.py` (unchanged in method, three small additions noted in section 6) and the
|
|
new `test1200AutoBaud.py`. Logs: `testLogs/rate1200Run1.txt`, `testLogs/rate1200Run2.txt`,
|
|
`testLogs/rate1200Run3.txt`, `testLogs/autoBaud1200Run4.txt` (and the three failed attempts that
|
|
preceded it, `autoBaud1200Run1..3.txt`). Per-second byte counts in
|
|
`testLogs/realtime.r12*.run1200.w*.samples.txt`, tracepoint output in the matching `*.trace.txt`,
|
|
screenshots `shots/rtr12*.png` and `shots/abab4*.png`.
|
|
|
|
---
|
|
|
|
## 1. How 1200 was selected, and what the chip was given
|
|
|
|
`baudIndex` `$E055` comes off the disk as 0 every time the module is loaded, so 1200 has to be asked
|
|
for. Both machines were given **C= + 1** at the "PRESS A OR O" prompt, before the link was ever
|
|
opened - the order `README.md` tells the player to use - with the Commodore key on **Tab**, which is
|
|
where VICE's default symbolic keymap puts it.
|
|
|
|
The hot key took on the first press on both machines in all three runs, and it programmed the 6551
|
|
immediately rather than waiting for the link:
|
|
|
|
| Reading, at the A/O prompt, before the link opened | A | B |
|
|
|---|---|---|
|
|
| `baudIndex` `$E055` | 3 | 3 |
|
|
| ACIA control register `$DE03` | `$17` | `$17` |
|
|
| `bitPeriodLo` `$E056` (the live control byte) | `$17` | `$17` |
|
|
| `connectionPhase` `$E040` / `isLinkActive` `$E03B` | 0 / `$00` | 0 / `$00` |
|
|
| `aciaCommandShadow` `$E5BE` | `$00` | `$00` |
|
|
|
|
`$17` is `$10` (8 data bits, one stop bit, internal baud-rate generator) OR rate bits `$7`, which on
|
|
a SwiftLink's doubled 3.6864 MHz crystal is 1200 baud. It matches `baudEntryTable+1` at `$E701` in
|
|
the built image, where the seven entries read
|
|
|
|
```
|
|
$E6FD: B3 15 00 B1 17 00 B2 18 00 B4 1A 00 B9 1C 00 B0 1E 00 B8 1F 00
|
|
C=+3 300 C=+1 1200 C=+2 2400 C=+4 4800 C=+9 9600 C=+0 19200 C=+8 38400
|
|
```
|
|
|
|
One thing worth writing down because it looks alarming in a log: at that same pre-link edge
|
|
`linkErrorCount` `$E047` reads **72** on both machines. 72 is `$48`, the `H` of the leftover junk
|
|
bytes `44 54 48` ("DTH") the module ships at `$E045-$E047`; nothing has cleared it yet because the
|
|
link has not been opened. It goes to `$00` when the link comes up and stays there.
|
|
|
|
Both machines reached `connectionPhase` 3 - the game's own packet protocol - in every run, and the
|
|
side-0/side-1 negotiation completed: one machine ends on "OPPONENT PICKING GAME/MAP." and the other
|
|
on the GAME TYPE menu. That is the same game state the 300- and 2400-baud runs in
|
|
`realtimeReport.md` were measured in, which is what makes the three rates comparable.
|
|
|
|
---
|
|
|
|
## 2. Bytes each way, per window
|
|
|
|
`conn0` is machine A (answer, the first to open its ACIA); `conn1` is machine B (originate). "speed"
|
|
is each emulator's own free-running cycle counter over the window against the wall clock - the check
|
|
that this was measured at true C64 speed and not on a machine that could not keep up.
|
|
|
|
| Run | Window | Wall | A speed | B speed | A -> B | B -> A |
|
|
|---|---|---:|---:|---:|---|---|
|
|
| 1 | 1/2 | 75.67 s | 100.9% | 100.5% | 1675 B = **22.14 B/s** | 1584 B = **20.93 B/s** |
|
|
| 1 | 2/2 | 75.58 s | 101.3% | 100.9% | 3042 B = **40.25 B/s** | 2577 B = **34.10 B/s** |
|
|
| 2 | 1/2 | 75.09 s | 101.0% | 100.6% | 773 B = **10.29 B/s** | 760 B = **10.12 B/s** |
|
|
| 2 | 2/2 | 75.36 s | 101.2% | 100.8% | 792 B = **10.51 B/s** | 802 B = **10.64 B/s** |
|
|
| 3 | 1/2 | 75.67 s | 101.0% | 100.5% | 3433 B = **45.37 B/s** | 3248 B = **42.92 B/s** |
|
|
| 3 | 2/2 | 75.14 s | 100.9% | 100.5% | 3406 B = **45.33 B/s** | 3138 B = **41.76 B/s** |
|
|
| **all six** | | **452.51 s** | | | **13121 B = 29.00 B/s** | **12109 B = 26.76 B/s** |
|
|
|
|
The 8N1 ceiling at 1200 baud is 120 bytes/s. The six-window average is 24% of it in one direction
|
|
and 22% in the other, but the average is not the interesting number, because **the line does get
|
|
saturated in bursts**: the per-second buckets run from 6 to 119 bytes/s, and 119 is 99% of what 1200
|
|
baud can carry in a second. What sets the average is the game, not the wire - the frame layer starts
|
|
at most one thing per raster IRQ and the packet layer exchanges one packet per lock-step turn.
|
|
|
|
Per-second buckets, from `testLogs/realtime.r12*.run1200.w*.samples.txt`, one line per second per
|
|
window:
|
|
|
|
| Run | Window | conn0 (A -> B) | conn1 (B -> A) | seconds at zero |
|
|
|---|---|---|---|---|
|
|
| 1 | 1/2 | 15 to 79 B/s | 14 to 100 B/s | none |
|
|
| 1 | 2/2 | 15 to 88 B/s | 13 to 88 B/s | none |
|
|
| 2 | 1/2 | 6 to 47 B/s | 6 to 69 B/s | none |
|
|
| 2 | 2/2 | 6 to 61 B/s | 6 to 84 B/s | none |
|
|
| 3 | 1/2 | 19 to 89 B/s | 18 to 99 B/s | none |
|
|
| 3 | 2/2 | 15 to 74 B/s | 13 to **119** B/s | none |
|
|
|
|
**Not one second, in either direction, in any of the six windows, carried zero bytes.** That is the
|
|
single most important line in this report: the failure mode this driver has to avoid is a machine
|
|
that stops talking, and at 1200 baud it never did, not even in the windows where the receive ring was
|
|
overflowing.
|
|
|
|
### `$55` ARQ lead bytes
|
|
|
|
The frame layer's lead byte is `$55`. These are raw occurrences in the window's byte stream, so a
|
|
`$55` that happens to be payload is counted too - the number is a floor on the frame rate, not an
|
|
exact frame count.
|
|
|
|
| Run | Window | `$55` A -> B | per second | `$55` B -> A | per second |
|
|
|---|---|---:|---:|---:|---:|
|
|
| 1 | 1/2 | 553 | 7.3 | 580 | 7.7 |
|
|
| 1 | 2/2 | 713 | 9.4 | 733 | 9.7 |
|
|
| 2 | 1/2 | 279 | 3.7 | 254 | 3.4 |
|
|
| 2 | 2/2 | 297 | 3.9 | 291 | 3.9 |
|
|
| 3 | 1/2 | 786 | 10.4 | 751 | 9.9 |
|
|
| 3 | 2/2 | 758 | 10.1 | 688 | 9.2 |
|
|
| **all six** | | **3386** | 7.5 | **3297** | 7.3 |
|
|
|
|
Every window carried real ARQ frames in both directions. For comparison, the 2400-baud windows in
|
|
`realtimeReport.md` section 8 carried about 250 `$55` per direction per window - roughly 3.4 frames a
|
|
second - so 1200 baud in this same game state is doing between one and three times as much frame
|
|
traffic as 2400 did, which is a statement about how much the two games had to say to each other in
|
|
each run rather than about the rates.
|
|
|
|
### Byte-sync floods
|
|
|
|
`beginByteSyncPhase` pours out `$00` until the peer answers, so a run of eight or more `$00` in the
|
|
stream is the signature of a link that has dropped out of the packet phase and is resynchronising.
|
|
|
|
| Run | Window | A -> B runs of >= 8 zeros / longest | B -> A runs / longest |
|
|
|---|---|---|---|
|
|
| 1 | 1/2 | 3 / 42 | 2 / 25 |
|
|
| 1 | 2/2 | 18 / 87 | 17 / 54 |
|
|
| 2 | 1/2 | 0 / 6 | 1 / 42 |
|
|
| 2 | 2/2 | 0 / 1 | 0 / 1 |
|
|
| 3 | 1/2 | 33 / 48 | 30 / 39 |
|
|
| 3 | 2/2 | 33 / 57 | 33 / 50 |
|
|
|
|
Runs 1 and 3 resynchronised repeatedly and run 2 barely at all. The resyncs track the error counts
|
|
in section 3 exactly, and every one of them ended - the longest flood in 452 seconds was 87 bytes,
|
|
under a second of line time at 1200 baud. Compare the pre-fix 2400-baud failure in
|
|
`realtimeReport.md` section 4, where one direction carried 4495 bytes of `$00` and nothing else for
|
|
an entire 75-second window and never came back.
|
|
|
|
---
|
|
|
|
## 3. Errors: all thirty-two were receive-ring overflows, all on the same machine
|
|
|
|
The NMI receiver pushes two different things through `reportLinkError` `$E3BC` into `countLinkError`
|
|
`$E403`: a character whose status shows overrun, framing or parity error, and a good character that
|
|
had nowhere to go because the 20-byte receive ring was already full. `linkErrorCount` `$E047` cannot
|
|
tell them apart and cannot even be trusted as a total, because `restartByteSync` clears it at `$E814`
|
|
- which is why it reads `$00` at every window edge in this report while errors were plainly
|
|
happening.
|
|
|
|
So a second tracepoint was added, conditional so that it fires only on the overflow case:
|
|
|
|
```
|
|
trace exec $e403 # every character the receiver threw away
|
|
trace exec $e505 if x > $13 # ... and the subset thrown away because the ring was full
|
|
```
|
|
|
|
`$E505` is the `CPX #$14` inside `pushUartRxRing`, and X was loaded with `uartRxCount` three cycles
|
|
earlier at `$E502`, so `X > $13` means the ring already held all twenty characters. (The monitor
|
|
reads condition numbers as hex - `x > 19` would have meant `X > $19` - which is why the constant is
|
|
written `$13`.) A tracepoint prints and carries on; it never stops the emulator.
|
|
|
|
Machines were told apart afterwards by the free-running cycle counter each trace line carries: the
|
|
two emulators start seconds apart and their stopwatch ranges never overlap.
|
|
|
|
| Run | Window | `$E403` total | of which ring-full `$E505` | on machine A | on machine B |
|
|
|---|---|---:|---:|---:|---:|
|
|
| 1 | 1/2 | 7 | not instrumented | 0 | 7 |
|
|
| 1 | 2/2 | 6 | not instrumented | 0 | 6 |
|
|
| 2 | 1/2 | 8 | **8** | 0 | 8 |
|
|
| 2 | 2/2 | 3 | **3** | 0 | 3 |
|
|
| 3 | 1/2 | 7 | **7** | 0 | 7 |
|
|
| 3 | 2/2 | 1 | **1** | 0 | 1 |
|
|
| **all six** | | **32** | **19 of 19, in the four windows it was live in** | **0** | **32** |
|
|
|
|
In runs 2 and 3, where both tracepoints were live, the two counts are **equal in every window**: 8
|
|
and 8, 3 and 3, 7 and 7, 1 and 1. Not one character was rejected for a bad status. Run 1 had only
|
|
the `$E403` tracepoint, but its trace lines carry the same fingerprint - `X` reads `$14` at `$E403`
|
|
on all thirteen hits, which is `uartRxCount` = 20 left in X by the ring-full exit - so those thirteen
|
|
were overflows too.
|
|
|
|
**That is the ceiling the task brief suspected, and it is real at 1200 baud, not only at 38400.** The
|
|
raw receive ring is 20 bytes, only the once-per-frame tick drains it, and `awaitWholePacket` will not
|
|
copy a packet out until the whole payload and its check byte are resident - up to 17 bytes - so the
|
|
real headroom is about three bytes. At 1200 baud a video frame is about 2.0 character times, so a
|
|
steady 1200-baud stream cannot overflow it; a burst that arrives while the tick is not draining can,
|
|
and did. There is no RTS throttling anywhere in this driver.
|
|
|
|
**What the driver does about it is the answer that matters.** A non-zero `linkErrorCount` makes
|
|
`runLinkStateMachine` restart the byte sync whatever phase it was in, and that is exactly what the
|
|
`$00` floods in section 2 are. Every one of them completed, the link came back to phase 3, and the
|
|
per-second byte counts never dropped to zero in either direction. **It degrades and recovers. It
|
|
does not wedge.**
|
|
|
|
Two honest caveats. First, **every overflow in all three runs was on machine B**, the originate side
|
|
and the second emulator to be started, and machine A recorded not a single one in 452 seconds. That
|
|
is not explained. It is not the game screen - B was on the GAME TYPE menu in runs 1 and 3 and on
|
|
"OPPONENT PICKING GAME/MAP." in run 2, and it overflowed in all three. Both machines held 100.5% or
|
|
better of real time throughout, so it is not one emulator falling behind. Second, VICE's ACIA is
|
|
byte level: it paces whole bytes at the rate the control register asks for but does not shift them
|
|
bit by bit, so the framing and parity errors a real noisy line would produce cannot appear on this
|
|
rig at all. "Zero bad characters" is a statement about this emulation, not about a real cable.
|
|
|
|
---
|
|
|
|
## 4. The transmitter: the deadlock state never occurred
|
|
|
|
`txCharActive` `$E5BB` and `aciaCommandShadow` `$E5BE` were read on both machines at the open of each
|
|
series and at the close of each window, with both emulators stopped.
|
|
|
|
| `txCharActive` | `aciaCommandShadow` | Readings | Meaning |
|
|
|---|---|---:|---|
|
|
| `$00` | `$00` | 6 | at the A/O prompt, before the ACIA was ever programmed |
|
|
| `$00` | `$09` | 10 | idle: transmit interrupt disarmed, nothing believed to be in the chip |
|
|
| `$01` | `$05` | 14 | busy: transmit interrupt armed, a byte of ours in the chip |
|
|
| **`$01`** | **`$09`** | **0** | the deadlock - **never seen** |
|
|
|
|
Thirty readings, only the two self-consistent pairs. `testRealtime.isTxDeadlocked` reported `False`
|
|
at every edge and its repair probe - which would have written `$00` over `$E5BB` from the monitor and
|
|
said so in the log - never ran, so nothing in these numbers is the test rig patching the driver up.
|
|
|
|
The full edge table, all three runs, in order (open of the series, close of window 1, close of window
|
|
2). `err` is `linkErrorCount` `$E047`, `pend` is `uartPendingCount` `$E0A5`:
|
|
|
|
| Run | Edge | A: phase / active / err / `$E5BB` / `$E5BE` / pend | B: phase / active / err / `$E5BB` / `$E5BE` / pend |
|
|
|---|---|---|---|
|
|
| 1 | before the link opened | 0 / `$00` / `$48` / `$00` / `$00` / `$00` | 0 / `$00` / `$48` / `$00` / `$00` / `$00` |
|
|
| 1 | open of the series | 3 / `$01` / `$00` / `$00` / `$09` / `$00` | 3 / `$01` / `$00` / `$00` / `$09` / `$00` |
|
|
| 1 | close of w1 | 2 / `$01` / `$00` / `$01` / `$05` / `$01` | 3 / `$01` / `$00` / `$01` / `$05` / `$03` |
|
|
| 1 | close of w2 | 3 / `$01` / `$00` / `$00` / `$09` / `$00` | 2 / `$01` / `$00` / `$01` / `$05` / `$01` |
|
|
| 2 | before the link opened | 0 / `$00` / `$48` / `$00` / `$00` / `$00` | 0 / `$00` / `$48` / `$00` / `$00` / `$00` |
|
|
| 2 | open of the series | 2 / `$01` / `$00` / `$01` / `$05` / `$01` | 3 / `$01` / `$00` / `$01` / `$05` / `$03` |
|
|
| 2 | close of w1 | 3 / `$01` / `$00` / `$00` / `$09` / `$00` | 3 / `$01` / `$00` / `$01` / `$05` / `$03` |
|
|
| 2 | close of w2 | 3 / `$01` / `$00` / `$01` / `$05` / `$01` | 3 / `$01` / `$00` / `$01` / `$05` / `$03` |
|
|
| 3 | before the link opened | 0 / `$00` / `$48` / `$00` / `$00` / `$00` | 0 / `$00` / `$48` / `$00` / `$00` / `$00` |
|
|
| 3 | open of the series | 2 / `$01` / `$00` / `$01` / `$05` / `$01` | 3 / `$01` / `$00` / `$01` / `$05` / `$02` |
|
|
| 3 | close of w1 | 3 / `$01` / `$00` / `$00` / `$09` / `$00` | 2 / `$01` / `$00` / `$01` / `$05` / `$01` |
|
|
| 3 | close of w2 | 3 / `$01` / `$00` / `$01` / `$05` / `$03` | 2 / `$01` / `$00` / `$00` / `$09` / `$00` |
|
|
|
|
`isLinkActive` `$E03B` was `$01` at every edge after the link came up and `$00` at every pre-link
|
|
edge. `linkErrorCount` `$E047` read `$00` at every edge after the link came up - which, as section 3
|
|
explains, means "no error since the last resync" and not "no errors", and is the reason the
|
|
tracepoints exist. `connectionPhase` `$E040` moves between 3 and 2 as the link drops into and out of
|
|
a byte-sync round, exactly as it did in the healthy 300-baud run of `realtimeReport.md` section 3.
|
|
`uartPendingCount` never rose above `$03`, so neither transmitter ever had a backlog.
|
|
|
|
The ACIA control register `$DE03` and `bitPeriodLo` `$E056` read `$17` and `baudIndex` `$E055` read 3
|
|
at all thirty readings on both machines. Nothing reprogrammed the rate behind the driver's back and
|
|
nothing drifted.
|
|
|
|
---
|
|
|
|
## 5. The auto-baud path: `CONNECT 1200` really does select 1200
|
|
|
|
`switchTo1200Baud` `$E7B2` is reached from `$E7B0` (`LDX #$03`) when `serviceModemInput` sees a CR
|
|
whose preceding character was `'0'` - the last digit of a verbose `CONNECT 1200`. That caller
|
|
predates the SwiftLink table, so the question is whether X = 3 still lands where the table says it
|
|
does. It was tested twice, once through the real path and once by driving the entry point directly.
|
|
Script: `test1200AutoBaud.py`. Log: `testLogs/autoBaud1200Run4.txt`.
|
|
|
|
### 5.1 Through a fake Hayes modem
|
|
|
|
One machine, its serial line wired not to a peer but to a TCP listener in the test script that
|
|
behaves like a modem far enough to answer the AT string. The emulator was left completely alone
|
|
across the critical window - the answer timer at `$E7D5` gives the reply about 121 frames, two
|
|
emulated seconds, and every monitor command stops the emulated clock.
|
|
|
|
What the fake modem saw and said, from its own timestamped log:
|
|
|
|
```
|
|
+100.757 the emulator opened the serial line
|
|
+100.787 from the C64: '\r'
|
|
+101.870 from the C64: 'A' 'T' 'Q' '0' 'V' '1' 'X' '1' 'A' (answer-mode Hayes string)
|
|
+102.201 from the C64: '\r'
|
|
+102.202 answered '\r\nCONNECT 1200\r'
|
|
+104.780 from the C64: '\x00' ... the byte-sync flood of connection phase 2
|
|
```
|
|
|
|
The `$00` flood starting 2.58 s after the AT string's CR is the 121-frame answer timer expiring and
|
|
the state machine falling out of terminal mode into byte sync, which is what should happen. What it
|
|
left behind:
|
|
|
|
| Reading | Value | |
|
|
|---|---|---|
|
|
| ACIA control register `$DE03` | **`$17`** | 8N1, internal baud generator, rate bits `$7` = **1200 baud** |
|
|
| `bitPeriodLo` `$E056` | **`$17`** | the driver's own mirror of the live control byte |
|
|
| `baudIndex` `$E055` | **0** | *unchanged* - see below |
|
|
| `lastModemChar` `$EB02` | `$20` | the space `$E7B5` substitutes for the CR after the sniff |
|
|
| `connectionPhase` `$E040` / `isLinkActive` `$E03B` | 2 / `$01` | in byte sync, link up |
|
|
| `linkErrorCount` `$E047` | `$00` | |
|
|
|
|
**`$17` with `baudIndex` still 0 can only have come from `switchTo1200Baud`.** The only three
|
|
callers of `loadBaudParameters` are `openCommLink` `$E2C9`, which loads X from `baudIndex` and would
|
|
therefore have written `$15`; the speed hot keys, which do `STX baudIndex` at `$E350` on the way in
|
|
and would have left `$E055` = 3; and `$E7B2`, which loads X immediately and touches `$E055` never.
|
|
No hot key was pressed in this run.
|
|
|
|
### 5.2 Driving `$E7B2` directly
|
|
|
|
With the emulator stopped, the ACIA was forced back to 300 baud, `$E056` cleared, `txCharActive`
|
|
`$E5BB` set to `$01` by hand, PC put on `$E7B2` and X loaded through the monitor; `n` then steps over
|
|
the `JSR` and its whole subroutine. Doing it for three indices exercises the three-byte stride
|
|
rather than the single value the sniffer uses:
|
|
|
|
| X | `$DE03` before | `$DE03` after | `$E056` after | `$E5BB` after | `$E055` after | Expected |
|
|
|---:|---|---|---|---|---|---|
|
|
| 0 | `$15` | **`$15`** | `$15` | `$00` | 0 (untouched) | `$15`, 300 baud - PASS |
|
|
| **3** | `$15` | **`$17`** | `$17` | `$00` | 0 (untouched) | **`$17`, 1200 baud - PASS** |
|
|
| 6 | `$15` | **`$18`** | `$18` | `$00` | 0 (untouched) | `$18`, 2400 baud - PASS |
|
|
|
|
So the behaviour matches the table, and matches the built image, where `baudEntryTable+1` at `$E701`
|
|
is `$17`. Two side effects of the current `loadBaudParameters` show up in that table and are worth
|
|
recording:
|
|
|
|
* **`txCharActive` is cleared.** `$E5BB` was set to `$01` before each entry and read `$00` after, which
|
|
is `aciaSetControlIdle` `$E04C` doing its job - the third of the three deadlock fix sites in
|
|
`realtimeReport.md` section 8. A `CONNECT 1200` that arrives while a character is half way out of
|
|
the chip cannot strand the flag.
|
|
* **`baudIndex` `$E055` is *not* updated.** `loadBaudParameters` writes the control register and
|
|
`bitPeriodLo` and nothing else. This is faithful to the stock driver, which behaved the same way,
|
|
but it has a consequence: `openCommLink` `$E2C6` reloads the rate from `baudIndex` whenever it
|
|
finds `isLinkActive` = 0, so a link that is **torn down and re-opened** after a `CONNECT 1200`
|
|
comes back at **300 baud**, silently. A re-open on a live link (C= + RETURN, which reaches
|
|
`openCommLink` with `isLinkActive` already set) skips `loadBaudParameters` entirely and keeps 1200.
|
|
|
|
### 5.3 What the source said about it
|
|
|
|
The comment on `$E7B0`/`$E7B2` was wrong in two ways and has been corrected - **comments only, the
|
|
assembled image is byte-for-byte identical before and after, and `checkAbi.py` still passes**:
|
|
|
|
* `$E7B0` ended `"so this caller still selects 120053 cycles = 1200 baud"`. The `53 cycles` is
|
|
wreckage from the stock driver's comment, where the number really was a CIA timer value.
|
|
* `$E7B2` said `"copy that entry into the live bit period $E056/$E057 and the pacing divisor $E058"`,
|
|
which is what the *stock* `loadBaudParameters` did. The SwiftLink one writes `$E056` and the ACIA
|
|
control register; `$E057` and `$E058` have been dead since the software UART was replaced. It now
|
|
says what it does, and records the `baudIndex` trap above.
|
|
|
|
---
|
|
|
|
## 6. What was changed in the test rig, and what went wrong in it
|
|
|
|
Three additions to `testRealtime.py`, all additive:
|
|
|
|
* each machine's tracepoint output is now kept separately (`tracedBy`), so a hit can be attributed to
|
|
one end;
|
|
* the conditional overflow tracepoint `trace exec $e505 if x > $13` is set alongside `$e403`;
|
|
* the hit counter's regular expression was wrong and is fixed. The monitor announces a hit as
|
|
`#1 (Trace exec e403)` and disassembles the line as `.C:e403 ...` - **no dollar sign on either** -
|
|
so a pattern of `C:\$e403` matches nothing. **The consequence is in the logs of runs 2 and 3:
|
|
their own printed summaries say "0 tracepoint hits" and that is false.** The counts in section 3
|
|
are from the saved `*.trace.txt` files, re-counted afterwards, and they are the real ones. Run 1's
|
|
log predates the change and counted `(Trace` instead, which was right while `$e403` was the only
|
|
tracepoint.
|
|
|
|
`test1200AutoBaud.py` is new. Three attempts failed before the one reported above, all for rig
|
|
reasons rather than driver reasons, and two of them are worth passing on:
|
|
|
|
* **`answerModemPrompts` can return before the link could possibly have opened.** It uses
|
|
"`$E5BE` is non-zero" as its test, and at true C64 speed the 1541 takes the best part of a minute to
|
|
fetch the opponent module off track 34. Until it lands, `$E000-$EFFF` still holds the previous
|
|
overlay - `$E5BB-$E5BE` read `E8 84 18 AC` in the first attempt, and `$AC` was taken for an open
|
|
link while the screen still said "WORKING...". The module's stored bytes there are all `$00`, so
|
|
the test is sound once the module is resident; the fix is to wait for residency first.
|
|
* **`$E013/$E014` is not a safe residency test either**, although `testRealtime.selectRate` uses it:
|
|
the pre-load wreckage read `B7 0D` - exactly the patched value - in the second attempt.
|
|
`test1200AutoBaud.waitForModule` waits on all sixteen bytes of the `$E000` jump table instead, and
|
|
re-offers the fire button every six polls, because in the third attempt the press that starts the
|
|
load simply did not land.
|
|
|
|
One self-inflicted loss: `./build.sh` was run to rebuild the disk while a measurement run was in
|
|
flight, which rewrites the `.d64` the running emulators have attached. That run was killed and
|
|
restarted rather than trusted, and its emulators and Xvfb displays were cleaned up by hand. The
|
|
three runs reported here all ran against a stable image.
|
|
|
|
---
|
|
|
|
## 7. What this does not show
|
|
|
|
* **VICE, not hardware.** No physical SwiftLink, no real cable, no real modem. The `CONNECT 1200`
|
|
in section 5 came from fifteen bytes written into a TCP socket by a Python script.
|
|
* **VICE's ACIA is byte level.** It cannot produce framing or parity errors, so "every error was a
|
|
ring overflow" is a result about this rig. On a real line the same tracepoints would be expected to
|
|
show bad characters as well.
|
|
* **The overflows are unexplained.** Thirty-two of them, all on machine B, none on machine A, over
|
|
three boots. What burst produces them, and why only one end sees them, was not chased - doing it
|
|
needs the NMI instrumented, which stops the emulator and destroys the timing being measured.
|
|
* **No stock user-port peer.** 1200 is where a SwiftLink player and an unmodified user-port player
|
|
would meet, and that pairing still has not been run. Both machines here were SwiftLink builds.
|
|
* **No complete game.** Same as every earlier report: the two machines reach the GAME TYPE menu and
|
|
stay there.
|
|
* **Six windows is not a long soak.** 452 seconds at 1200 baud is enough to say the deadlock did not
|
|
recur and the link recovers from overflow, and not enough to say it never will.
|
|
* **The auto-baud path was exercised once end to end**, in answer mode, on a link with no peer behind
|
|
the fake modem. The originate string `ATQ0V1X1D` and a `CONNECT 1200` arriving on a link that then
|
|
goes on to play a real game were not tried.
|
|
|
|
---
|
|
|
|
## 8. Files
|
|
|
|
| File | What it is |
|
|
|---|---|
|
|
| `test1200AutoBaud.py` | new: one machine, a fake Hayes modem that answers `CONNECT 1200`, then `$E7B2` driven directly with X = 0, 3 and 6 |
|
|
| `testLogs/rate1200Run1.txt` | run 1 - C= + 1 at the A/O prompt, two 75 s windows |
|
|
| `testLogs/rate1200Run2.txt` | run 2 - a second boot, same sequence |
|
|
| `testLogs/rate1200Run3.txt` | run 3 - a third boot, same sequence, with both tracepoints live |
|
|
| `testLogs/realtime.r12*.run1200.w*.samples.txt` | per-second byte counts, both directions, every window |
|
|
| `testLogs/realtime.r12*.run1200.w*.trace.txt` | raw `$E403` and `$E505` tracepoint output, with the cycle counter that attributes each hit to a machine |
|
|
| `testLogs/autoBaud1200Run4.txt` | the auto-baud run reported in section 5 |
|
|
| `testLogs/autoBaud1200Run1.txt`, `...Run2.txt`, `...Run3.txt` | the three rig failures described in section 6 |
|
|
| `testLogs/autoBaud1200.ab4.modem.txt` | the fake modem's own timestamped event log |
|
|
| `shots/rtr120*.png`, `shots/rtr12b0*.png`, `shots/rtr12c0*.png` | the menu, the linked pair and the end of each window, runs 1 to 3 |
|
|
| `shots/abab401Menu.png`, `shots/abab402AfterConnect.png` | the auto-baud run |
|