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
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ SPF comes on a virtual disk image that can be reconstituted to a physical floppy
SPF has the ability to do some rough timing calculations for what might loosely be termed "benchmarking."
An internal clock on the Apple side is required for this functionality to be enabled. Supported clock protocols include:

1. IIgs built-in clock
2. NoSlotClock
3. Thunderclock
4. ROMX family

The reason why SPF can't/doesn't use the built-in ProDOS clock interface is it only gives information to the minute
resolution - and we need seconds. (More resolution would be nice, but seconds seems to be the best we can hope for in the universe of Apple II clocks.)
SPF has a compatibility layer that can identify and use various clocks and their particular protocols.
1. IIgs built-in clock
2. **Applied Engineering DClock** (Apple **IIc** only, AE Memory Expansion; [`doc/D-Clock.dsk`](doc/D-Clock.dsk) / [`doc/DClock_Notes.md`](doc/DClock_Notes.md)) — tried **before** any **`$C0C0`** MegaFlash probe so AE RAM is not pre-disturbed.
3. NoSlotClock (**not** used on IIc — unsafe without NSC hardware)
4. ROMX family
5. [MegaFlash](https://github.com/ThomasFok/MegaFlash) (IIc: CPU-socket + ROM; **`$C0C0`**) — after ROMX, same as on other machines.
6. Thunderclock (slot firmware signature scan)

**Detection order in `InitTime`** (first match wins; see `src/prodos/gettime.asm`): **IIgs** → on **IIc**: **DClock** → (not NoSlotClock) → **ROMX** → **MegaFlash** → **Thunderclock**. Elsewhere: **NoSlotClock** → **ROMX** → **MegaFlash** → **Thunderclock**. **Tradeoff:** an IIc with **only** MegaFlash (no AE DClock) still sees the DClock probe first at **`$C0C0`**; that configuration may mis-detect or need a build tweak.

There is **no** ProDOS `GET_TIME` / global-page fallback: that path only had minute resolution and was removed in favor of hardware clocks (including DClock on the IIc).

Additional slot-based clocks (such as the Thunderclock) can be added as long as firmware can be detectected and the time-getting routine is added.
All clock functionality is implemented in [src/prodos/gettime.asm](https://github.com/ADTPro/spf/blob/master/src/prodos/gettime.asm).
Binary file added build/SPF.BIN
Binary file not shown.
Binary file added build/lib/AppleCommander-ac-13.0.jar
Binary file not shown.
Binary file added doc/D-Clock.dsk
Binary file not shown.
Binary file added doc/DClock - Users Manual v1.0.pdf
Binary file not shown.
64 changes: 64 additions & 0 deletions doc/DClock_Notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Applied Engineering DClock Support

SPF detects and reads the **Applied Engineering DClock** for the Apple IIc (real-time clock on the AE memory expansion card, DS1215).

**Primary implementation source:** AE **`SOURCE.UTIL`** on the factory disk image [`doc/D-Clock.dsk`](D-Clock.dsk) (ORCA assembler listing, October 1988): `REAddr` / `REData` at `$C0C0` / `$C0C3`, `ReadTime` / `WriteTime` / `VerifyTime`, unlock pattern `C5 3A A3 5C` repeated, `Read8` / `Write8` bit-serial I/O, `$080000` save/restore.

**Manual:** Applied Engineering *DClock – Users Manual v1.0*, **Appendix B – DClock Register Structures** (`doc/DClock - Users Manual v1.0.pdf`).

Cross-check: [a2stuff/prodos-drivers](https://github.com/a2stuff/prodos-drivers) `clocks/dclock/dclock.system.s` (same protocol; includes fixes for original AE loader bugs). [Apple II DeskTop (a2desktop)](https://github.com/a2stuff/a2d) ships the **jumbo** combined driver (`clocks/jumbo/clock.system.s`): NoSlotClock → ROMX → **DClock** → … — **no MegaFlash** at `$C0C0`.

---

## Appendix B summary (manual)

### DS1215 / memory coexistence

- The clock shares the DRAM location at **bit 0 of bank 3** on the IIc memory expansion card.
- **Activation:** one **read** of that bit position, then **64 writes** of the unlock pattern **LSB first**: `$5CA3` `$3AC5` `$5CA3` `$3AC5` (same as eight bytes `$5C $A3 $3A $C5` × 2, each byte shifted out LSB first in the driver).
- Any deviation or an intervening read aborts the enable.
- After enable, **eight registers × eight bits** are read or written as a **single 64-bit block**, **LSB of register 0 first**. Data is **BCD** unless noted.

### Register layout (order = first byte read … last)

| Reg | Meaning | Manual (Appendix B) |
|-----|---------|----------------------|
| **0** | Fractional seconds | Bits 0–7: **0.00–0.99** (hundredths of a second) |
| **1** | Seconds | Bits 0–6: 00–59; bit 7 unused |
| **2** | Minutes | Bits 0–6: 00–59; bit 7 unused |
| **3** | Hours / mode | Bits 0–5: hour **00–23** (24-hour mode); bit 7 = 1 selects **12-hour mode** (software should use **24-hour mode**) |
| **4** | Day of week + control | Bits 0–2: day **01–07** (Sun–Sat); other bits include oscillator/reset (see manual) |
| **5** | Date of month | Bits 0–5: **01–31**; bits 6–7 unused |
| **6** | Month | Bits 0–4: **01–12**; bits 5–7 unused |
| **7** | Year | Bits 0–7: year **00–99** BCD |

SPF stores `DCBuf` (**0 = register 0** … **7 = register 7**) after a full read.

### SPF use of the fields

- **Detection:** `DClockValidTime` checks all eight registers against Appendix B ranges (same bounds as `VerifyTime` in AE `SOURCE.UTIL`).
- **GetTime:** Converts **reg 0–3** from packed BCD to binary into `TimeNow`…`TimeNow+3` (hours, minutes, seconds, fractional seconds). Seconds and minutes are masked with **`$7F`**; hours with **`$3F`** so mode/flag bits do not affect 24-hour decode.

---

## Hardware assumptions

- **Apple IIc ROM** — `VERSION` ($FBB3) = `$06` is required to **attempt** DClock (IIc and IIc Plus). SPF does **not** require `ZIDBYTE` = `$00` for DClock detection (the AE installer did), so DClock is still tried on ROM variants where `$FBC0` ≠ `$00`.
- **Slot 4 slinky** — `LDA $C400` selects slot 4 ROM; `$C0C0–$C0C3` used as slinky pointer / data (per driver and manual hardware path).
- **Not for IIe/II+** — No DClock on those models in this configuration.

## Detection order (current `src/prodos/gettime.asm`)

When `VERSION` (`$FBB3`) = **`$06`** (Apple IIc family): **DClock** is tried **first** (before NoSlotClock, ROMX, or MegaFlash). **NoSlotClock is never probed** on IIc — it is not stock on the //c and can crash machines that only have AE RAM + DClock.

**MegaFlash** on IIc runs only in the shared path **after ROMX**, so a machine with **AE DClock and no MegaFlash** is not pre-poked with MegaFlash’s **`$C0C0`** protocol. **Tradeoff:** IIc with **only** MegaFlash (no AE) still receives the DClock slinky sequence first.

There is no ProDOS-only clock fallback in SPF; timing relies on these hardware paths.

## Slinky save/restore

The code saves and restores the expansion RAM byte at slinky address **`$080000`** while touching the clock so memory is not corrupted (same idea as the fixed reference driver).

## IIc Plus

The manual notes the **IIc Plus** does not use the same connectors; identification bytes may differ from classic IIc. If detection fails on a IIc Plus, use another supported clock or adjust identification if AE documents a different test.
78 changes: 78 additions & 0 deletions doc/MegaFlash_Clock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# MegaFlash Clock Support - Design Notes

This document captures the reasoning and design decisions behind adding MegaFlash clock support to SPF.

## Background

**MegaFlash** is an internal storage device for Apple IIc/IIc+ computers ([ThomasFok/MegaFlash](https://github.com/ThomasFok/MegaFlash)). On a **regular IIc** it installs in the **CPU socket** (with the stock processor moved to the board); the kit’s **ROM upgrade** is what enables **slot‑4–style MegaFlash behavior**—the fixed **`$C0C0`–`$C3`** command/status window—on **any** IIc, not only machines that originally had a physical slot 4 card. It includes:
- Flash storage (128MB/256MB)
- Real-time clock with ProDOS driver
- NTP time sync capability

SPF's benchmark feature requires second-resolution timing. The standard ProDOS clock interface only provides minute resolution, so SPF uses a compatibility layer that detects and uses various hardware clocks directly.

## Research Process

1. **Identification**: MegaFlash was identified from the GitHub repo. The README lists "Real Time Clock with ProDOS clock driver" as a feature.

2. **Architecture**: MegaFlash is IIc/IIc+ only—CPU-socket install plus replacement/system ROM that wires the firmware’s **MegaFlash driver at `$C0C0`–`$C3`** (the same addresses the IIc uses for slot 4 I/O). Unlike a separate slotted card (Thunderclock), there is no slot scan; the addresses are fixed. Activation is handled in MegaFlash ROM; SPF does not perform any separate “magic” sequence—it only checks for presence and reads the time.

3. **API Discovery**: From `common/defines.inc` and `firmware/megaflash.s`:
- I/O: `cmdreg`/`statusreg`=$C0C0, `paramreg`=$C0C1, `datareg`=$C0C2, `idreg`=$C0C3
- `CMD_GETDEVINFO` ($10) returns signature bytes $88, $74 in paramreg for detection
- `CMD_GETPRODOS25TIME` ($18) returns 6-byte ProDOS 2.5 timestamp (includes seconds)

4. **Time Format**: From `pico/rtc.c` and `pico/cmdhandler.c`:
- ProDOS legacy (4 bytes): date_lo, date_hi, minute, hour — **no seconds**
- ProDOS 2.5 (6 bytes): t4ms, second, time_lo, time_hi, date_lo, date_hi
- The time word: `time = mday<<11 | hour<<6 | min` (min=bits 0-5, hour=bits 6-10, mday=bits 11-15)

## Design Decisions

### 1. Detection Order

In current SPF, **MegaFlash** is probed after **ROMX** and before **Thunderclock** on **all** CPUs (including IIc). On IIc, **DClock** runs **earlier** in `InitTime` (before NoSlotClock / ROMX / MegaFlash) so AE RAM at **`$C0C0`** is not hit with MegaFlash’s command protocol first. **Tradeoff:** IIc with **MegaFlash only** (no AE) still runs the DClock probe first.

Rationale:
- IIgs and NoSlotClock first (common)
- ROMX (replacement ROM family)
- MegaFlash (IIc/IIc+ internal) before generic slotted clocks
- Thunderclock via slot signature scan
- No ProDOS-only clock vector in SPF (see README)

### 2. ProDOS 2.5 vs ProDOS Legacy Format

We use `CMD_GETPRODOS25TIME` exclusively because:
- SPF needs **seconds** for elapsed-time benchmarks
- ProDOS legacy format only provides hour and minute
- ProDOS 2.5 format includes the second byte

### 3. Bit Extraction for Hour and Minute

The time word layout: `[mday:5][hour:5][min:6]`

- **Minutes**: `time & $3F` (low 6 bits)
- **Hours**: `(time >> 6) & $1F` (next 5 bits)

Decomposed for 6502:
- `hour_lo2 = (time_lo >> 6)` — bits 6–7 of time_lo become bits 0–1 of hour
- `hour_hi3 = (time_hi & $07) << 2` — bits 0–2 of time_hi (time bits 8–10) become bits 2–4 of hour
- `hour = hour_lo2 | hour_hi3` (5 bits, 0–23)

### 4. Hundredths of Seconds

MegaFlash provides `t4ms` (0–249, units of 4ms), so hundredths could be approximated. We use 0 for consistency with other clocks (GS, ROMX, Thunderclock) that don't expose sub-second resolution.

### 5. Error Handling

- **Detection timeout**: If the busy flag stays set for 100 polls, we assume no MegaFlash.
- **GetTime error**: If the status register error bit is set after the time command, we skip updating TimeNow and return (leaving prior values).

### 6. RTC Not Initialized

If MegaFlash's RTC has never been set (no NTP, no manual set), the firmware returns all zeros. SPF will show 00:00:00 and benchmarks would report ~0 elapsed time. This is a user configuration issue; we document it in the README.

## Implementation Notes

- **MFTime buffer**: 4 bytes for temp storage during extraction. [0] holds intermediate hour bits, [1]=sec, [2–3]=time word.
- **Activation**: Handled in MegaFlash ROM; SPF does not perform the magic address sequence.
131 changes: 131 additions & 0 deletions doc/Throughput_Notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Theoretical Maximum Read/Write Throughput

This document records the reasoning behind the estimate that the ProDOS code path in SPF has a **theoretical maximum** of roughly **100–150 KB/s** to a storage device on a 1 MHz Apple II.

## Code Path Summary

### File read/write (FileRead / FileWrite in fileops.asm)

- **MLI calls:** `OS_READFILE` / `OS_WRITEFILE` (ProDOS $CA / $CB).
- **Parameter block:** `FILE_RD` / `FILE_WR` in prodosvars.asm:
- Buffer: `BIGBUF+1024` ($6A00)
- Length per call: `FILE_RLEN` / `FILE_WLEN` = **$4000 = 16,384 bytes (16 KB)**
- **Loop:** One MLI call per 16 KB; then escape check, spinner, 16-bit chunk count decrement, branch. Loop overhead is small (tens of cycles) compared to the MLI + driver work.

### Block read (BlockRead in fileops.asm)

- **MLI call:** `OS_READBLOCK` (ProDOS $80).
- **Parameter block:** `READ_BLK` in prodosvars.asm:
- Buffer: `READ_BLK_BUF` = BIGBUF ($6600)
- **512 bytes** per call (one ProDOS block).
- **Loop:** One MLI call per 512 bytes; block number increment, escape check, decrement count, spinner.

## Where the Limit Comes From

Even if the **storage device** could transfer data instantly, throughput is capped by:

1. **CPU and bus:** 6502 at ~1.023 MHz ⇒ ~1.023e6 cycles/sec.
2. **ProDOS MLI:** Dispatch and file/block handling per call (non-zero but small compared to data movement).
3. **Block driver:** For each 512-byte block the driver must copy data to/from the I/O buffer. A minimal LDA/STA–style copy is on the order of **~4–8 cycles per byte** ⇒ **~2K–4K cycles per 512-byte block**.
4. **File path:** One READ_FILE/WRITE_FILE of 16 KB causes ProDOS to perform **32** block-sized operations internally. So per 16 KB we have 32 × (driver copy + block dispatch).
5. **Block path:** One READ_BLOCK per 512 bytes ⇒ more MLI and dispatch overhead per byte than the file path.

## Rough Cycle Budget (1 MHz, device “instant”)

- **File path, per 16 KB:** 32 blocks × ~4000 cycles (copy + overhead) ≈ **128,000 cycles**.
- 1,023,000 / 128,000 ≈ **8** full 16 KB chunks per second ⇒ **8 × 16 KB/s ≈ 128 KB/s**.
- **Block path:** One 512-byte block per call; similar per-byte copy cost plus more calls per KB ⇒ slightly lower ceiling, on the order of **~80–120 KB/s**.

Hence the **theoretical maximum** for this ProDOS/SPF design is in the **~100–150 KB/s** range; the file path is slightly better because it does 16 KB per MLI call instead of 512 bytes per call.

## Practical Note

In real use the **storage device** (floppy, hard disk, CFFA, etc.) is almost always the bottleneck, so observed speeds are at or below this CPU/ProDOS ceiling. This note is only about the upper bound imposed by the current code path and 6502/bus, not by the hardware behind the driver.

---

## Apple IIc with 4 MHz cached CPU (e.g. IIc+, Zip Chip)

On an Apple IIc with an accelerated/cached processor (e.g. IIc+ or IIc with Zip Chip), the **CPU** can run at 4 MHz when executing from internal ROM or cache, while the **system bus** (slot I/O, expansion, and usually main RAM) remains at **1 MHz** for compatibility.

### What runs at which speed

- **SPF’s loop** (escape check, spinner, decrement chunk count, branch): often runs at **4 MHz** when that code lives in fast ROM or is cached.
- **ProDOS MLI** (JSR $BF00) and all code inside ProDOS: in system ROM → **1 MHz** (bus access).
- **Block driver**: typically in slot ROM or bank-switched RAM → **1 MHz**.
- **I/O buffer** (BIGBUF at $6600): main RAM → usually **1 MHz** on the bus.
- **Storage device I/O** (slot or internal): bus cycles are **1 MHz**.

So the **entire data path**—MLI, driver, buffer access, and device transfer—still runs at 1 MHz. Only the small “between MLI calls” loop runs at 4 MHz.

### Effect on benchmark results

- **Throughput (KB/s)**: Effectively **the same** as on a 1 MHz machine. The bottleneck is still 1 MHz bus cycles for the actual I/O and buffer copies. You might see **1–3% higher** numbers at most from the loop overhead being 4× faster.
- **Wall-clock time**: The benchmark finishes **slightly faster** (again on the order of a few percent) because the per-iteration overhead (spinner, escape check, decrement) uses fewer real microseconds.

So SPF’s reported **KB/s** should be **almost unchanged** on a 4 MHz cached IIc; the 1 MHz system bus, not the CPU clock, limits transfer rate to storage.

---

## Disk write data generation: RAM read vs. CPU-generated pattern

For low-level disk writing (e.g. Disk II), which is faster for producing the byte stream to the drive: **reading from a pre-filled buffer in RAM**, or **having the CPU generate a repeating pattern** (e.g. $55/$AA = 01010101 / 10101010) on the fly?

### Reading from RAM

Typical loop for 512 bytes:

```
LDA buf,Y ; 4 cycles (absolute indexed)
STA $C08x ; 4 cycles (STA to drive softswitch)
INY
BNE loop
; inc page for next 256 bytes, repeat
```

- Per-byte: ~**12 cycles** (8 for LDA+STA, 4 for INY+BNE)
- 512 bytes ≈ **6144 cycles**
- With tighter loop structure: ~**8–9 cycles/byte** ⇒ ~**4096–4608 cycles** for 512 bytes

### Generating pattern on the fly

For alternating $55/$AA:

**Simple loop with EOR:**
```
LDA #$55
loop: STA $C08x
EOR #$FF ; flip to $AA
INY
BNE loop
```
≈ **14 cycles/byte** ⇒ **~7168 cycles** for 512 bytes — slower than RAM read.

**Unrolled alternating:**
```
; 256 iterations of:
LDA #$55
STA $C08x
LDA #$AA
STA $C08x
```
- Per 2 bytes: **2+4+2+4 = 12 cycles**
- 512 bytes: **3072 cycles**

**2-byte lookup table:** Typically **~10–14 cycles/byte** ⇒ **~5120–7168 cycles** — no win over unrolled generation and usually slower than RAM read.

### Comparison

| Method | Cycles for 512 bytes |
|--------------------------------|----------------------|
| **Generate (unrolled $55/$AA)** | ~3072 |
| **Read from RAM** | ~4096–6144 |
| **Generate (loop + EOR)** | ~7168 |

**Conclusion:** Generating an alternating 010101 pattern on the fly with an **unrolled** $55/$AA loop is **faster** than reading from RAM—roughly **25–50%** fewer cycles for the same data size.

### Caveats

1. **Disk II timing:** The physical drive has its own timing limits. If it cannot accept bytes at the rate the CPU can supply them, the bus-speed advantage of generation is irrelevant.
2. **Pattern flexibility:** For arbitrary or varying data, reading from RAM is usually simpler and often fast enough.
3. **Code size:** The unrolled generator uses more ROM; the RAM-read approach is more compact.
Loading