458 lines
31 KiB
Markdown
458 lines
31 KiB
Markdown
# The high rates: 4800, 9600, 19200, 38400, and a rate mismatch
|
|
|
|
> **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.
|
|
>
|
|
> **Note on the recommendation: "must not be selected: 38400" has been overturned.** It was written
|
|
> from the four wedges in sections 5 and 6, on a build whose NMI handler could be re-entered without
|
|
> bound. `wedgeDiagnosis.md` then established that mechanism - 34 nested handler frames decoded off a
|
|
> live page 1, three timed stack descents, and the cycle budget behind them - and the driver now
|
|
> carries a re-entrancy guard. On that build 38400 is selectable again and did not wedge in seven
|
|
> boots, and 9600 and 19200 produce no receive errors and no lost transmit characters at all
|
|
> (`portCompatReport.md` sections 3 and 4, `testReport.md` section 11). The measurements below are
|
|
> left exactly as they were taken: they are the record of the build that had the fault.
|
|
|
|
The reason to care about the top of the baud table is not speed. It is **hardware compatibility**: a
|
|
C64 Ultimate's SwiftLink emulation may hand this driver a much faster line than *Modem Wars* was ever
|
|
written for, and the question that matters is not how much the driver can carry but whether it
|
|
**survives** what it is handed - stays in sync, or degrades and recovers, and never wedges.
|
|
|
|
Everything here 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 in every pair 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 each
|
|
emulator's own free-running cycle counter is read at both edges of every measurement window to prove
|
|
it. **Every window in this report ran at 100.5% of real time or better on both machines.**
|
|
|
|
Script: the new `testHighRates.py`, which reuses `testRealtime.py` (the socket null modem, the boot,
|
|
the hot-key rate selection, the stopwatch check, the transmitter-deadlock probe) and adds what this
|
|
question needs. Logs: `testLogs/highRate4800.txt`, `highRate9600.txt`, `highRate19200.txt`,
|
|
`highRate38400.txt`, `highRate38400run2.txt`, `highRate38400run3.txt`, `highRate38400run4.txt`,
|
|
`highRateMismatch.txt`, with per-second byte counts and raw tracepoint output in
|
|
`testLogs/highRate.*.samples.txt` / `*.trace.txt` / `*.ring.txt` and screenshots in `shots/hrh*.png`.
|
|
|
|
---
|
|
|
|
## The answer, in one table
|
|
|
|
| Rate | Hot key | Control | Both reached phase 3 | Sampling intervals carrying nothing | Receive-ring overflows | Transmit characters lost | Byte-sync rounds | Outcome |
|
|
|---|---|---|---|---|---|---|---|---|
|
|
| 4800 | C= + 4 | `$1A` | yes | **none**, 0 of 450 | **0** in 20 s of tracing | 0.8% / 1.5% | ~1.0/s, 98% completed | **healthy** |
|
|
| 9600 | C= + 9 | `$1C` | yes | **none**, 0 of 442 | 8 and 41 in 20.6 s | 2.1% / 2.1% | ~1.7/s, 79% completed | **degrades, recovers** |
|
|
| 19200 | C= + 0 | `$1E` | yes | **none**, 0 of 450 | 128 and 74 in 20.4 s | 2.2% / 2.3% | ~1.6/s, 76% completed | **degrades, recovers** |
|
|
| 38400 | C= + 8 | `$1F` | 3 of 4 boots, briefly | **every interval**, one or both directions, for the whole 170-246 s of every boot | 1259 in 21 s on the stuck machine | - | it stops resyncing | **WEDGES, 4 of 4 boots**, once by crashing the CPU |
|
|
| 38400 vs 300 | - | `$1F` / `$15` | yes | **none**, 0 of 474 | **0** | 0.0% / 0.1% | 0.2-0.5/s, completions >= entries | **healthy** - but see section 7 |
|
|
|
|
**Plain recommendation.**
|
|
|
|
* **Safe to advertise: 300, 1200, 2400, 4800.** 4800 behaved the way 2400 does after the transmitter
|
|
fix: continuous traffic in both directions every second, not one receive-ring overflow, not one bad
|
|
character, the ring never more than a third full.
|
|
* **Document as degrading but usable: 9600 and 19200.** Both keep the link alive and both keep the
|
|
game moving. Both also spend a visible part of every second re-synchronising: the 20-byte receive
|
|
ring overflows, `linkErrorCount` forces a byte-sync round, the round completes, the packet layer
|
|
carries on. Nothing is lost that the ARQ layer does not recover. What they do **not** buy is
|
|
throughput - the extra line time goes into resync floods, not into frames.
|
|
* **Must not be selected: 38400.** *(Overturned - see the note at the top of this file.)* Four
|
|
consecutive boots, four wedges, every one inside the first
|
|
minute after the link came up, none of which recovered in the 170 to 246 seconds each was then
|
|
watched for. On the fourth boot VICE reported `*** Main CPU: JAM at $0007` on one machine - the
|
|
6502 executing an illegal opcode in zero page. It is not the old 2400-baud transmitter deadlock, and
|
|
the driver's own self-heal cannot reach it. Sections 5 and 6 have the stuck bytes.
|
|
* **The rate table itself is fine.** Every hot key programmed the chip correctly, first press, every
|
|
time: `$1A`, `$1C`, `$1E` and `$1F` all appeared in `$DE03` with the matching `baudIndex`. 38400
|
|
fails at the protocol, not at the register.
|
|
|
|
---
|
|
|
|
## 1. What actually limits this driver, arithmetically
|
|
|
|
The raw receive ring at `$E430` holds **20 characters**, only the once-per-frame link tick drains it,
|
|
and `awaitWholePacket` `$E9D9` refuses to copy a packet body out until the whole payload **and** its
|
|
check byte are resident - up to 17 bytes - so the genuine headroom is about three bytes. There is no
|
|
RTS throttling anywhere in the driver. So the number that decides everything is how many characters
|
|
can arrive between two drains:
|
|
|
|
| Rate | Characters/s | Characters per video frame | A full 22-byte transmit ring emptied onto the wire takes |
|
|
|---|---:|---:|---:|
|
|
| 300 | 30 | 0.50 | 733 ms = 43.9 frames |
|
|
| 1200 | 120 | 2.01 | 183 ms = 11.0 frames |
|
|
| 2400 | 240 | 4.01 | 91.7 ms = 5.5 frames |
|
|
| 4800 | 480 | 8.02 | 45.8 ms = 2.7 frames |
|
|
| 9600 | 960 | 16.05 | 22.9 ms = 1.4 frames |
|
|
| 19200 | 1920 | 32.09 | 11.5 ms = 0.69 frames |
|
|
| 38400 | 3840 | 64.19 | 5.7 ms = 0.34 frames |
|
|
|
|
Two figures in the existing documents are wrong and are worth correcting here, because the size of
|
|
the problem depends on them. The brief for this work said a video frame is "roughly 640 character
|
|
times" at 38400 and `spec_uart.md` hazard 2 says "about 200"; at 3840 characters a second and 59.826
|
|
frames a second it is **64.2**. The ring is therefore about three times too small at 38400, not
|
|
thirty times - which is why 19200, at 32 characters a frame, still works.
|
|
|
|
The second column is the one that decides where the overflows start. The frame layer fills the
|
|
22-byte **transmit** ring and the NMI empties it at line rate, so a peer's ARQ frame arrives as a
|
|
burst. At 300 baud that burst is spread over 44 video frames and cannot overflow anything. At 9600
|
|
it lands inside one and a half frames, at 19200 inside one, and at 38400 three of them can land
|
|
inside a single frame.
|
|
|
|
**Relay direction, and how machines are told apart.** The null modem counts bytes per connection;
|
|
`conn0` is whichever emulator opened its serial line first, which is machine A (the answer side, the
|
|
one the script drives first). That is an inference, the same one every earlier report in this
|
|
directory makes, and where it matters below it is corroborated by something machine-specific - each
|
|
emulator's own log, or its own monitor socket, or a rate that only one of the two was set to.
|
|
|
|
---
|
|
|
|
## 2. 4800 baud: healthy
|
|
|
|
Both machines took **C= + 4** at the "PRESS A OR O" prompt; `baudIndex` `$E055` went to 9 and the
|
|
ACIA control register `$DE03` to `$1A` on both, and both reached `connectionPhase` 3. Four windows,
|
|
246.5 seconds of measured link time.
|
|
|
|
| Window | Wall | A speed | B speed | A -> B | B -> A | resync floods | `$55` lead bytes |
|
|
|---|---:|---:|---:|---|---|---|---|
|
|
| clean 1 | 75.19 s | 101.0% | 100.6% | 681 B = **9.06 B/s** | 687 B = **9.14 B/s** | 0 / 0 | 255 / 258 |
|
|
| clean 2 | 75.15 s | 100.9% | 100.5% | 3418 B = **45.48 B/s** | 3053 B = **40.62 B/s** | 44 / 23 | 629 / 761 |
|
|
| sync tracepoints | 76.06 s | 100.9% | 100.5% | 2764 B = 36.34 B/s | 2678 B = 35.21 B/s | 32 / 36 | 622 / 659 |
|
|
| error tracepoints | 20.06 s | 103.5% | 102.0% | 866 B = 43.18 B/s | 996 B = 49.66 B/s | 8 / 8 | 140 / 165 |
|
|
|
|
* **Not one of the 450 sampling intervals - 225 in each direction - carried zero bytes.** The
|
|
per-second buckets ran 6 to 12 B/s in the quiet window and 12 to 175 B/s in the busy one.
|
|
* **Not one receive error of any kind.** The `countLinkError` `$E403` tracepoint recorded **zero**
|
|
hits on either machine in the 20-second instrumented window, and so did the conditional ring-full
|
|
tracepoint `trace exec $e505 if x > $13`.
|
|
* **The resyncs that do happen are the packet layer's own, not the UART's.** With no `$E403` hits at
|
|
all, the 44 and 23 byte-sync floods in window 2 came from `receivePacket`'s `unwindAndResync` paths
|
|
(two `$00` in a row, an invalid length code, a bad check byte), not from a dropped character.
|
|
* **They complete.** Tracepoints on `beginByteSyncPhase` `$E805` and on the instruction that
|
|
finishes a sync, `sta ackPending` `$E855`, counted 78 entries / 76 completions on A and 80 / 78 on
|
|
B in 76 seconds.
|
|
* **The ring stays nearly empty.** `uartRxCount` `$E42D`, sampled 24 times per machine with the
|
|
emulator stopped: A min 0, max 7, mean 1.1; B min 0, max 5, mean 0.8. Never at the 20-byte limit.
|
|
* `connectionPhase` `$E040` was seen at both 2 and 3 on both machines, `isLinkActive` `$E03B` = `$01`
|
|
at every edge, and `txCharActive` `$E5BB` / `aciaCommandShadow` `$E5BE` were only ever the two
|
|
self-consistent pairs `$00`/`$09` and `$01`/`$05`. The deadlock probe never fired.
|
|
|
|
## 3. 9600 and 19200: they degrade, and they recover
|
|
|
|
Same method, one boot each, four windows each. These two are reported together because they differ
|
|
only in degree.
|
|
|
|
### 9600 baud (`$DE03` = `$1C`, `baudIndex` 12)
|
|
|
|
| Window | Wall | A -> B | B -> A | resync floods | `$55` | per-second range |
|
|
|---|---:|---|---|---|---|---|
|
|
| clean 1 | 75.18 s | 3122 B = **41.53 B/s** | 2734 B = **36.37 B/s** | 28 / 28 | 652 / 617 | 6-345 / 6-276 |
|
|
| clean 2 | 75.23 s | 3647 B = **48.48 B/s** | 3985 B = **52.97 B/s** | 32 / 43 | 700 / 705 | 12-200 / 12-275 |
|
|
| sync tracepoints | 75.53 s | 4313 B = 57.11 B/s | 4214 B = 55.79 B/s | 31 / 59 | 810 / 730 | 14-259 / 14-228 |
|
|
| error tracepoints | 20.61 s | 1017 B = 49.35 B/s | 884 B = 42.90 B/s | 9 / 9 | 223 / 164 | 12-68 / 13-72 |
|
|
|
|
### 19200 baud (`$DE03` = `$1E`, `baudIndex` 15)
|
|
|
|
| Window | Wall | A -> B | B -> A | resync floods | `$55` | per-second range |
|
|
|---|---:|---|---|---|---|---|
|
|
| clean 1 | 75.13 s | 4702 B = **62.59 B/s** | 5377 B = **71.57 B/s** | 72 / 47 | 717 / 980 | 12-306 / 14-377 |
|
|
| clean 2 | 75.14 s | 4885 B = **65.01 B/s** | 4406 B = **58.64 B/s** | 58 / 50 | 770 / 808 | 14-371 / 12-300 |
|
|
| sync tracepoints | 75.78 s | 4623 B = 61.00 B/s | 4014 B = 52.97 B/s | 66 / 30 | 749 / 659 | 12-237 / 12-142 |
|
|
| error tracepoints | 20.35 s | 1188 B = 58.38 B/s | 1292 B = 63.49 B/s | 21 / 10 | 207 / 312 | 18-170 / 18-217 |
|
|
|
|
**The three things that decide the verdict, at both rates:**
|
|
|
|
* **No sampling interval carried nothing.** 0 of 442 at 9600 and 0 of 450 at 19200, counting both
|
|
directions, including inside the windows where the ring was overflowing.
|
|
* **The errors are real, and every one is a ring overflow.** In the instrumented windows the
|
|
unconditional `$E403` tracepoint and the conditional `$E505 if x > $13` one returned **identical
|
|
counts**: 8 and 8 on A, 41 and 41 on B at 9600; 128 and 128 on A, 74 and 74 on B at 19200. Not one
|
|
character was rejected for a bad status. That is the 20-byte ring being outrun by a burst, exactly
|
|
as `spec_uart.md` hazard 2 predicts, and it is the same signature `rate1200Report.md` found at 1200
|
|
- there are simply far more of them.
|
|
* **Every degradation was followed by a recovery.** `$E805` / `$E855`: 124 entries / 101 completions
|
|
on A and 128 / 100 on B at 9600; 100 / 87 on A and 135 / 91 on B at 19200 - roughly 1.3 to 1.8
|
|
byte-sync rounds a second, two thirds to seven eighths of which reached `sta ackPending` and went
|
|
back to the packet phase inside the same window (81% and 78% at 9600, 87% and 67% at 19200). (A round that does not reach `$E855` is not a
|
|
loss; the next one starts immediately, and `$E855` under-counts anyway, because `restartByteSync`
|
|
`$E80F` re-enters a sync without passing `$E805`.) `connectionPhase` was found at both 2 and 3 on
|
|
both machines, and `uartRxCount` sampled with the emulator stopped never once read the 20-byte
|
|
limit: A max 14 / B max 4 at 9600, A max 10 / B max 4 at 19200.
|
|
|
|
**And they buy nothing.** The `$55` frame-lead counts, a floor on how many ARQ frames crossed the
|
|
wire, are 629-761 per window at 4800, 617-705 at 9600 and 717-980 at 19200. The *bytes* on the wire
|
|
climb from 45 B/s to 65 B/s as the rate goes up, and what climbs with them is the `$00` byte-sync
|
|
flood. Counting only the three 75-second windows of each run, so that the lengths are comparable:
|
|
0 to 44 floods per window at 4800, 28 to 59 at 9600, 30 to 72 at 19200. Above 4800 the extra line
|
|
rate is spent re-synchronising, not playing.
|
|
|
|
## 4. The other degradation: the driver overwrites its own outgoing characters
|
|
|
|
VICE logs `Acia1: ACIA: data register written although data has not been sent yet.` every time the
|
|
data register is written before the previous character has left - one transmitted character lost
|
|
each time, on emulation and on a real 6551 alike. Each emulator writes its own log, so the loss
|
|
counts below belong to a named machine with certainty; the denominators come from the relay and so
|
|
inherit the `conn0` = A inference of section 1. `realtimeReport.md` saw a handful of these at 2400;
|
|
they scale hard with rate:
|
|
|
|
| Rate | A lost | of offered | B lost | of offered |
|
|
|---|---:|---|---:|---|
|
|
| 4800 | 132 | 16207 = **0.8%** | 226 | 14689 = **1.5%** |
|
|
| 9600 | 387 | 18829 = **2.1%** | 403 | 19054 = **2.1%** |
|
|
| 19200 | 542 | 24165 = **2.2%** | 528 | 22600 = **2.3%** |
|
|
| 38400 vs 300 | 1 | 14769 = 0.0% | 6 | 9719 = 0.1% |
|
|
|
|
(The 38400/38400 boots produced 0 to 52 apiece, but they went silent within a minute, so their totals
|
|
mean nothing.) Every one of these is a byte the peer never sees, so it is a checksum failure and a
|
|
retransmit at the far end - which is a large part of why the resync rate in section 3 climbs the way
|
|
it does. **How the driver comes to write a busy data register was not traced**; the documented
|
|
`commNmiHandler` re-entrancy race is the obvious suspect and nothing measured here confirms it.
|
|
|
|
## 5. 38400 baud: it wedges, four times out of four
|
|
|
|
**Four boots, four wedges, no recoveries.** Every one happened within about a minute of the link
|
|
coming up - before the first 75-second window opened - and none showed any sign of coming back
|
|
during the 170 to 246 seconds of measurement that followed. The four are not identical, which is
|
|
itself worth recording: this is not one reproducible bug with one signature.
|
|
|
|
| Boot | Both reached phase 3? | A -> B, per window | B -> A, per window | How it ended up |
|
|
|---|---|---|---|---|
|
|
| 1 `h384` | no (`{'A': 2, 'B': 2}` after 180 s) | 998 / 1000 / 998 / 272 B, all `$00`/`$FF`, **no `$55` at all** | **0 / 0 / 0 / 0** | A stuck in byte sync; B's game back on the plain OPTIONS menu, its ring full at 20/20 |
|
|
| 2 `h384b` | yes | **0 / 0 / 0 / 0** | **0 / 0 / 0 / 0** | both frozen in phase 3, both rings full at 20/20, total silence both ways for 245 s |
|
|
| 3 `h384c` | yes | 4494 / 4495 / 4495 / 1259 B, **every byte `$00`**, 59.83 B/s = exactly one byte per video frame | **0 / 0 / 0 / 0** | A flooding byte sync for ever; B frozen in phase 3 on "OPPONENT PICKING GAME/MAP." |
|
|
| 4 `h384d` | yes | **0 / 0 / 0** | 1000 / 1000 / 266 B of `$00`/`$FF` at 13.3 B/s | **A's CPU jammed**; B alone in byte sync for ever |
|
|
|
|
Boot 3's error window is the clearest measurement of what the line does to a stuck machine: in 20.4
|
|
seconds machine B's receiver counted **1259** rejected characters and the conditional tracepoint says
|
|
**1259** of them were the 20-byte ring being full. `linkErrorCount` `$E047` is useless as a total
|
|
here because it is one byte that simply wraps - across boot 3's four window edges it read 208, 21,
|
|
94, 206.
|
|
|
|
## 6. The stuck bytes
|
|
|
|
`realtimeReport.md` pinned the 2400-baud fault to one byte: `txCharActive` `$E5BB` = `$01` while
|
|
`aciaCommandShadow` `$E5BE` said the transmit interrupt was off. **This is not that fault.** In
|
|
every 38400 wedge those two bytes were self-consistent - `$00` / `$09`, "idle, nothing in the chip" -
|
|
`testRealtime.isTxDeadlocked` reported `False` at every edge, and the repair probe that would have
|
|
written `$00` over `$E5BB` never ran. The driver's own `healStuckTransmitter`, added for the 2400
|
|
fault, has nothing to heal here.
|
|
|
|
### 6.1 Boot 3: `uartPendingCount` out of range
|
|
|
|
Machine B, read through the monitor with the emulator stopped, six times over six seconds, every
|
|
reading identical, and unchanged across four windows spanning 245 seconds:
|
|
|
|
| Byte | Value | What it means |
|
|
|---|---|---|
|
|
| `uartPendingCount` `$E0A5` | **`$FF`** | 255 characters "queued for the UART that have not finished transmitting" |
|
|
| `uartTxCount` `$E414` | `$00` | ...while the 22-byte transmit ring is **empty** |
|
|
| `uartRxCount` `$E42D` | **`$14`** = 20 | the receive ring is full, and stays full |
|
|
| `uartRxReadIndex` / `uartRxWriteIndex` `$E42E`/`$E42F` | `$06` / `$0D` | frozen - nothing pushed, nothing popped |
|
|
| `connectionPhase` `$E040` | `3` | frozen in the packet phase |
|
|
| `isLinkActive` `$E03B` | `$01` | the link is still "up" |
|
|
| `txCharActive` `$E5BB` / `aciaCommandShadow` `$E5BE` | `$00` / `$09` | self-consistent: not the 2400 deadlock |
|
|
| `nmiSuspendRequest`/`nmiSuspendAck` `$E039`/`$E03A` | `$00` / `$00` | the disk-load suspend handshake is **not** stuck |
|
|
| `$DE03` / `$DE02` | `$1F` / `$09` | the 6551 is correctly programmed at 38400, receiver interrupt on |
|
|
| `$0BA5` | `$00` | still a modem game, not switched to solo |
|
|
| `$01` | `$35` | I/O banked in - the module is reachable |
|
|
| PC | wanders (`$41C5`, `$C4EF`, `$C4F6`) | the CPU is running game code, not spinning in the driver |
|
|
|
|
**`uartPendingCount` `$E0A5` = `$FF` with an empty transmit ring is the byte that is stuck**, and it
|
|
is exactly the failure `spec_uart.md` hazard 1 warns about: *"make sure it can reach 0. If it never
|
|
reaches 0, `serviceLinkTick $E76E` never clears `$E0A4`/`$E0A6`, `sendPacketFsm $E26E` never
|
|
completes an exchange, and the game locks in lock-step forever."* Machine B's screen agrees - it
|
|
sits on "OPPONENT PICKING GAME/MAP.", waiting for a peer that can never satisfy it. Boot 2 shows the
|
|
same byte out of range on both machines at once, `$39` on A and `$79` on B, both rings full, both
|
|
ends silent.
|
|
|
|
The driver has a mechanism meant to prevent exactly this. `startNextTxChar` `$E607` resynchronises
|
|
the count whenever it runs with the ring dry:
|
|
|
|
```
|
|
lda uartTxCount ; E60C is there really something in the ring?
|
|
bne L_E616 ; E60F yes - decide whether it may go out
|
|
sta uartPendingCount ; E611 the ring is dry: A = 0 resynchronises the count
|
|
```
|
|
|
|
and the once-per-frame `$E00C` service falls into it from `$E604` on every raster IRQ the link is up.
|
|
On the wedged machine the ring **is** dry and the count is **not** zero, and it stayed that way for
|
|
245 seconds - so that resynchronisation was not running, which means the game above had stopped
|
|
calling `$E00C`. Which of the two stopped first was not determined.
|
|
|
|
### 6.2 Boot 4: the CPU jammed
|
|
|
|
Machine A, six readings over six seconds, then two more after the repair probe - every one identical:
|
|
|
|
```
|
|
ADDR A X Y SP 00 01 NV-BDIZC
|
|
.;0007 00 01 00 4a 5e 15 00110100
|
|
```
|
|
|
|
and its own emulator log says so outright:
|
|
|
|
```
|
|
*** Main CPU: JAM at $0007
|
|
```
|
|
|
|
The program counter is in **zero page**, it does not move between samples while the cycle counter
|
|
advances by 1.38 million, and the two processor-port bytes have been overwritten: `$00` (the data
|
|
direction register) reads `$5E` instead of `$2F` and `$01` reads `$15`. None of the other fifteen
|
|
emulator logs in this report records a JAM.
|
|
|
|
The rest of that machine at the same moment:
|
|
|
|
| Byte | Value |
|
|
|---|---|
|
|
| `uartRxCount` `$E42D`, read/write index | `$14` = 20 (full), `$10` / `$10`, frozen |
|
|
| `uartTxCount` `$E414` | `$00` - transmit ring empty |
|
|
| `uartPendingCount` `$E0A5` | `$00` |
|
|
| `$DE01` status / `$DE02` command / `$DE03` control | `$9C` / **`$05`** / `$1F` |
|
|
| `aciaCommandShadow` `$E5BE` / `txCharActive` `$E5BB` | **`$09`** / `$00` |
|
|
| `$E039` / `$E03A` | `$00` / `$00` |
|
|
|
|
The line worth staring at is the command register. **The chip says `$05` - the transmit interrupt is
|
|
armed - while the driver's shadow says `$09`, transmit interrupt off, and `txCharActive` says nothing
|
|
of ours is in the chip.** Status `$9C` has bit 7 (IRQ), bit 4 (TDRE) and bit 3 (RDRF) set. A 6551
|
|
with the transmit interrupt enabled and TDRE true asserts `/IRQ` again as soon as the status read
|
|
releases it, and this driver's NMI is the only thing that reads that status. That the CPU ended up
|
|
executing zero page with its bank register smashed is consistent with runaway NMI re-entrancy - the
|
|
race the source documents at `commNmiHandler` - but **nothing here measures the path from one to the
|
|
other**, and this shape occurred once in four boots.
|
|
|
|
### 6.3 The repair probe: nothing in the driver brings it back
|
|
|
|
`realtimeReport.md` proved its diagnosis of the 2400 deadlock by writing a single `$00` over `$E5BB`
|
|
from the monitor and watching the machine come back to life. The same probe was run here, on boot 4:
|
|
`$00` written over `uartPendingCount` `$E0A5`, twenty seconds observed, then `$00` over `txCharActive`
|
|
`$E5BB` as well and another twenty seconds.
|
|
|
|
```
|
|
20 s after clearing uartPendingCount $E0A5: conn0 0 bytes, conn1 288 bytes
|
|
20 s after clearing txCharActive $E5BB too: conn0 0 bytes, conn1 294 bytes
|
|
```
|
|
|
|
`conn1` is the machine that was still talking; 288 and 294 bytes in twenty seconds is the 13.3 B/s it
|
|
was already producing, unchanged. `conn0`, the wedged machine, produced **nothing** either time.
|
|
That is the honest result and it is a negative one: on this boot the wedged CPU had jammed, and no
|
|
value written into any driver byte can restart a jammed 6502, so the probe could not have worked.
|
|
**The boot-3 shape - `uartPendingCount` = `$FF` on a machine whose CPU is still running - is the one
|
|
worth probing, and it has not been.** That run predates the probe, and the shape did not recur in
|
|
the boot that had it.
|
|
|
|
## 7. The rate mismatch: 38400 against 300
|
|
|
|
One machine given **C= + 8**, the other left at the disk default of 300, a link opened between them,
|
|
245.8 seconds measured. Confirmed at every window edge: `$DE03` = `$1F` on A and `$15` on B,
|
|
`baudIndex` 18 and 0.
|
|
|
|
| Window | Wall | A (38400) -> B | B (300) -> A | overflows | `$55` | resync floods |
|
|
|---|---:|---|---|---|---|---|
|
|
| clean 1 | 75.18 s | 2274 B = **30.25 B/s** | 2226 B = **29.61 B/s** | 0 | 950 / 738 | 0 / 0 |
|
|
| clean 2 | 75.21 s | 2335 B = **31.05 B/s** | 2160 B = **28.72 B/s** | 0 | 787 / 834 | 0 / 1 |
|
|
| sync tracepoints | 75.34 s | 2701 B = 35.85 B/s | 1900 B = 25.22 B/s | 0 | 824 / 502 | 14 / 0 |
|
|
| error tracepoints | 20.04 s | 750 B = 37.43 B/s | 590 B = 29.45 B/s | **0** | 299 / 62 | 0 / 0 |
|
|
|
|
**Both ends reached `connectionPhase` 3, not one of the 474 sampling intervals carried nothing,
|
|
there was not a single receive-ring overflow or bad character at either end, and neither machine
|
|
wedged.** It is
|
|
the healthiest run in this whole report. `uartRxCount` sampled 24 times per machine read max 0 on
|
|
the fast side and max 2 on the slow side. The byte-sync tracepoints counted 12 entries / 13
|
|
completions on A and 35 / 35 on B - the lowest resync rate of any pair measured here. (Completions can exceed
|
|
entries because `restartByteSync` `$E80F` re-enters a sync without passing `$E805`.)
|
|
|
|
Two things explain that, and the second is why the result must be read carefully.
|
|
|
|
* **The pair simply ran at the slower of the two rates.** The slow side is pinned at 28.7-29.6 B/s,
|
|
which is 300 baud's 8N1 ceiling to within a byte, and the fast side tracks it at 30-37 B/s. The
|
|
fast side never actually floods, because the packet layer is lock-step - it will not send the next
|
|
thing until the previous exchange completes, so the peer's rate paces it. That is genuine
|
|
backpressure, and it is worth knowing that the ARQ layer throttles a fast end for free.
|
|
* **VICE's ACIA is byte level.** It paces whole characters at whatever rate the control register
|
|
asks for but does not shift them bit by bit, so two ends set to different rates do **not** garble
|
|
each other here. On real hardware a 300-baud receiver sampling a 38400-baud line would raise
|
|
framing and overrun errors on nearly every character; every one of those goes through
|
|
`reportLinkError` `$E3BC` into `countLinkError` `$E403`, and a non-zero `linkErrorCount` makes
|
|
`runLinkStateMachine` restart byte sync whatever phase it was in. The symptom on hardware would be
|
|
the one `README.md` section 4 already describes: a link stuck in phase 2 for ever, border red,
|
|
never green.
|
|
|
|
So the honest statement is: **on this rig a rate mismatch does not wedge either end and does not even
|
|
hurt - it degrades to the slower rate and keeps playing.** What it cannot tell you is whether a real
|
|
38400-baud line into a real 300-baud receiver wedges, because the error storm that would drive it
|
|
cannot be produced here. What it does establish is that the slow side survives being handed a peer
|
|
whose timing is nothing like its own, and that neither state machine falls over on the mismatch
|
|
itself.
|
|
|
|
For the Ultimate specifically, that splits the worry in two. If the device honours the control
|
|
register - as VICE and a real SwiftLink do - the two ends agree and this test says nothing bad
|
|
happens. If it ignores the baud bits and runs at its own default, the failure mode is the
|
|
garbage-on-the-line case above, which this rig cannot reproduce; the mitigation is the same either
|
|
way, and it is the recommendation at the top: do not offer 38400 at all, and treat 9600 and 19200 as
|
|
degraded modes.
|
|
|
|
---
|
|
|
|
## 8. What went wrong in the rig, and what it cost
|
|
|
|
* **Two runs were lost to the module-residency trap `rate1200Report.md` section 6 documented**, and
|
|
it is worth repeating because it bit again. `testTwoMachines.answerModemPrompts` uses "`$E5BE` is
|
|
non-zero" as its "the link is open" test and `testRealtime.selectRate` uses "`$E013/$E014` reads
|
|
`B7 0D`" as its "the module is resident" test, and while the 1541 is still fetching track 34 at
|
|
true C64 speed, `$E000-$EFFF` still holds the previous overlay. The first 19200 and 38400 attempts
|
|
read `$E5BB-$E5BE` = `E8 84 18 AC` on machine A and took `$AC` for an open link; that machine then
|
|
never selected its rate and never opened a link at all. Both runs were killed, their emulators and
|
|
Xvfb displays cleaned up by hand, and `testHighRates.moduleResident` now waits for all sixteen
|
|
bytes of the `$E000` jump table first - which is what `test1200AutoBaud.waitForModule` already did.
|
|
Neither lost run is used for anything above.
|
|
* **4800 and 9600 were measured with two emulator pairs on the box at once**, as were 38400 boot 2
|
|
and the mismatch; the rest ran alone. This is recorded because it is the sort of thing that
|
|
invalidates a real-time measurement - but it did not: every window in the report, parallel or not,
|
|
reports both machines at 100.5% of real time or better against their own cycle counters. The
|
|
residency failures above came from the *fixed sleeps* in the boot sequence being too short when the
|
|
host was loaded, not from the measurement windows.
|
|
* **The instrumented windows are not free, and are kept separate for that reason.** A clean window
|
|
carries no tracepoints at all, so its byte counts are the honest throughput; the `$E805`/`$E855`
|
|
window and the short `$E403`/`$E505` window carry theirs. The error windows consistently read
|
|
102-103.5% of real time rather than 100.5-101%, which is pause-and-resume overhead landing inside a
|
|
shorter window, not the emulator running fast.
|
|
* **`$DE03` sometimes reads `$00` and `$DE01` `$FF` on a machine that is running perfectly well.**
|
|
That is not the chip being reprogrammed; it is the monitor reading through a CPU that happens to
|
|
have the I/O area banked out at that instant - `readFilmByte`, `writeFilmByte` and `copyPageUnderIo`
|
|
set `$01 = $34` for up to 2600 cycles at a time. Two of the four 38400 boots caught a machine that
|
|
way at every window edge, which briefly looked like a driver fault and is not one. Any future test
|
|
that reads the cartridge page through the monitor should read `$01` alongside it; `wedgeDump` does.
|
|
|
|
## 9. What this does not show
|
|
|
|
* **VICE, not hardware.** No physical SwiftLink, no real cable, and above all no C64 Ultimate - the
|
|
device this whole exercise is about is one this report has never touched.
|
|
* **VICE's ACIA is byte level**, so no framing or parity error can occur on this rig at all. "Every
|
|
error was a ring overflow" is a statement about this emulation; on a real line the same tracepoints
|
|
would be expected to show bad characters too, and section 7 is limited by this more than anything
|
|
else here.
|
|
* **One boot per rate for 4800, 9600 and 19200.** 245 seconds each is enough to say the link keeps
|
|
moving and recovers, and not enough to say it always will. 38400 got four boots because it failed.
|
|
* **The 38400 wedge is characterised, not explained.** What is established is which bytes are stuck
|
|
at what values in two of the four shapes, that the driver's own `uartPendingCount` resynchronisation
|
|
is not running while they are, that one boot ended in a CPU JAM, and that the 2400-baud repair does
|
|
not apply. How `uartPendingCount` leaves the range the layers above wait on was not traced - `$FF`
|
|
is what a `dec uartPendingCount` produces from zero and the NMI has such a decrement at `$E6C9`, but
|
|
nothing measured connects the two. Nor was the path from `$DE02` = `$05` against a `$09` shadow to
|
|
a jammed CPU traced. Both need the NMI instrumented, which stops the emulator and destroys the
|
|
timing being measured.
|
|
* **The `uartPendingCount` repair probe has not been run against the shape it was written for.** See
|
|
section 6.3.
|
|
* **No stock user-port peer, and no complete game.** As in every earlier report, both machines are
|
|
SwiftLink builds, and they reach the GAME TYPE menu and stay there.
|
|
* **Nothing was fixed.** This report characterises; no driver source file was changed, and
|
|
`checkAbi.py` passes on the image every run above was made against.
|
|
|
|
## 10. Files
|
|
|
|
| File | What it is |
|
|
|---|---|
|
|
| `testHighRates.py` | the test: per-machine rate selection, clean and instrumented windows, `uartRxCount` sampling, the wedge dump and the `uartPendingCount` probe |
|
|
| `testLogs/highRate4800.txt`, `highRate9600.txt`, `highRate19200.txt` | the three rates above 2400 that survive |
|
|
| `testLogs/highRate38400.txt`, `...run2.txt`, `...run3.txt`, `...run4.txt` | the four 38400 boots, in the order of section 5 |
|
|
| `testLogs/highRateMismatch.txt` | 38400 on one machine, 300 on the other |
|
|
| `testLogs/highRate.*.samples.txt` | per-second byte counts, both directions, every window |
|
|
| `testLogs/highRate.*.trace.txt` | raw tracepoint output, with the cycle counter that attributes a hit to a machine |
|
|
| `testLogs/highRate.*.ring.txt` | the `uartRxCount` / phase / transmitter samples behind sections 2, 3 and 5 |
|
|
| `shots/hrh*.png` | the menu, the linked pair and the end of every window, for each run |
|