diff --git a/README.md b/README.md index 2c916a0..26b448a 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/build/SPF.BIN b/build/SPF.BIN new file mode 100644 index 0000000..d92b2c4 Binary files /dev/null and b/build/SPF.BIN differ diff --git a/build/lib/AppleCommander-ac-13.0.jar b/build/lib/AppleCommander-ac-13.0.jar new file mode 100644 index 0000000..0d06489 Binary files /dev/null and b/build/lib/AppleCommander-ac-13.0.jar differ diff --git a/doc/D-Clock.dsk b/doc/D-Clock.dsk new file mode 100644 index 0000000..9a160ae Binary files /dev/null and b/doc/D-Clock.dsk differ diff --git a/doc/DClock - Users Manual v1.0.pdf b/doc/DClock - Users Manual v1.0.pdf new file mode 100644 index 0000000..ab9b577 Binary files /dev/null and b/doc/DClock - Users Manual v1.0.pdf differ diff --git a/doc/DClock_Notes.md b/doc/DClock_Notes.md new file mode 100644 index 0000000..943c419 --- /dev/null +++ b/doc/DClock_Notes.md @@ -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. diff --git a/doc/MegaFlash_Clock.md b/doc/MegaFlash_Clock.md new file mode 100644 index 0000000..136e25b --- /dev/null +++ b/doc/MegaFlash_Clock.md @@ -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. diff --git a/doc/Throughput_Notes.md b/doc/Throughput_Notes.md new file mode 100644 index 0000000..b4d9833 --- /dev/null +++ b/doc/Throughput_Notes.md @@ -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. diff --git a/src/prodos/gettime.asm b/src/prodos/gettime.asm index 560d9c1..86bbadd 100644 --- a/src/prodos/gettime.asm +++ b/src/prodos/gettime.asm @@ -3,6 +3,10 @@ ; Copyright (C) 2013 - 2025 by David Schmidt ; 1110325+david-schmidt@users.noreply.github.com ; +; DClock: set DEBUG_DCLOCK_ONLY to 1 for IIc-only init + screen dump (set 0 for release). +; +DEBUG_DCLOCK_ONLY = 0 +; ; This program is free software; you can redistribute it and/or modify it ; under the terms of the GNU General Public License as published by the ; Free Software Foundation; either version 2 of the License, or (at your @@ -22,13 +26,48 @@ InitTime: sec jsr $FE1F ; CheckForGS bcc FoundClockGS + lda #DEBUG_DCLOCK_ONLY + beq InitTime_standard + jmp InitTime_debug_dclock +InitTime_standard: + ; Apple IIc (VERSION=$06): DClock before anything else on $C0C0 (MegaFlash probe + ; after ROMX). Probing MegaFlash first can upset AE RAM / DClock on MF-less IIc. + lda VERSION + cmp #$06 + bne InitTime_not_iic + jsr CheckForDClock + bcc FoundClockDClock +InitTime_not_iic: + lda VERSION + cmp #$06 + beq InitTime_skip_nsc jsr CheckForNoSlotClock bcc FoundClockNoSlot +InitTime_skip_nsc: jsr CheckForROMX bcc FoundClockROMX + jsr CheckForMegaFlash + bcs InitTime_megaMiss + jmp FoundClockMegaFlash +InitTime_megaMiss: jsr CheckForSlottedClocks + bcc InitTime_builtin_ok +InitTime_no_clock_any: + sec + rts +InitTime_builtin_ok: + clc rts +; IIc only: skip all other clocks; print diagnostics; patch GetTime if DClock OK. +InitTime_debug_dclock: + lda VERSION + cmp #$06 + bne InitTime_no_clock_any + jsr DclockDebugProbe + bcs InitTime_no_clock_any + jmp FoundClockDClock + FoundClockGS: ;--------------------------------------------------------- ; Patch the entry point of GetTime to the IIgs version @@ -59,6 +98,340 @@ FoundClockROMX: sta GetTime+2 rts +FoundClockMegaFlash: +;--------------------------------------------------------- +; Patch the entry point of GetTime to the MegaFlash version +;--------------------------------------------------------- + lda #GetTimeMegaFlash + sta GetTime+2 + rts + +FoundClockDClock: + lda #GetTimeDClock + sta GetTime+2 + rts + +;---------------------------------------------------------------------- +; Applied Engineering DClock (IIc Memory Expansion, DS1215 at slinky $080000 bit0). +; Protocol from AE DClock disk SOURCE.UTIL (ORCA), 1988 — same unlock pattern as +; DCLOCK.SYSTEM on doc/D-Clock.dsk: eight bytes C5 3A A3 5C × 2, each bit LSB-first. +;---------------------------------------------------------------------- +DC_ROMSLOT = $C400 +DC_READDR = $C0C0 +DC_REDATA = $C0C3 +DC_C8OFF = $CFFF + +CheckForDClock: + jsr DclockRead + jsr DClockValidTime + rts + +DclockReset: + lda DC_C8OFF + lda DC_ROMSLOT + lda #$00 + sta DC_READDR + sta DC_READDR+1 + lda #$08 + sta DC_READDR+2 + lda DC_REDATA + rts + +DclockEnable: + ldy #$00 +: lda DCUnlockPattern,y + jsr DclockWrite8 + iny + cpy #$08 + bcc :- + rts + +DCUnlockPattern: + .byte $C5, $3A, $A3, $5C, $C5, $3A, $A3, $5C + +; Pattern byte in A (SOURCE.UTIL Write8); must not clobber A before first bit write. +DclockWrite8: + sta DCW8Byte + ldy #$08 + lda #$00 + sta DC_READDR + sta DC_READDR+1 + lda #$08 + sta DC_READDR+2 +DclockWbit: + lda DCW8Byte + sta DC_REDATA + lda #$00 + sta DC_READDR + sta DC_READDR+1 + lsr DCW8Byte + dey + bne DclockWbit + rts + +DclockRead8: + ldy #$08 + lda #$00 + sta DC_READDR + sta DC_READDR+1 + lda #$08 + sta DC_READDR+2 + lda #$00 ; running byte (AE Read8 assumes A defined at first pha) +DclockRbit: + pha + lda DC_REDATA + lsr a + pla + ror a + dey + bne DclockRbit + rts + +DclockRestore: + lda #$08 + sta DC_READDR+2 + lda #$00 + sta DC_READDR + sta DC_READDR+1 + lda DCPreserve + sta DC_REDATA + lda DC_C8OFF + rts + +; DEBUG: two reads + validation + decoded TimeNow to screen (monitor PRBYTE/COUT1). +DclockDebugProbe: + jsr CROUT + ldy #$00 +: lda DCdHdr,y + beq :+ + jsr COUT1 + iny + bne :- +: jsr DclockRead + jsr DCdbgPrRaw1 + jsr DClockValidTime + bcs DCdbgFail + ldy #$00 +: lda DCdOk,y + beq :+ + jsr COUT1 + iny + bne :- +: jsr CROUT + jsr DclockRead + jsr DCdbgPrRaw2 + jsr DClockValidTime + bcs DCdbgFail + jsr DCdbgFillTimeNow + ldy #$00 +@binLbl: + lda DCdBin,y + beq @binDone + jsr COUT1 + iny + bne @binLbl +@binDone: + ldx #$00 +@tLp: + lda TimeNow,x + jsr PRBYTE + lda #$a0 + jsr COUT1 + inx + cpx #$04 + bcc @tLp + ldy #$00 +@dnLp: + lda DCdDone,y + beq DCdbgExitOk + jsr COUT1 + iny + bne @dnLp +DCdbgExitOk: + jsr CROUT + clc + rts +DCdbgFail: + ldy #$00 +: lda DCdBad,y + beq :+ + jsr COUT1 + iny + bne :- +: jsr CROUT + sec + rts + +DCdbgPrRaw1: + ldy #$00 +@r1: + lda DCdR1,y + beq DCdbgPr8 + jsr COUT1 + iny + bne @r1 +DCdbgPrRaw2: + ldy #$00 +@r2: + lda DCdR2,y + beq DCdbgPr8 + jsr COUT1 + iny + bne @r2 +DCdbgPr8: + ldy #$00 +@p8: + lda DCBuf,y + jsr PRBYTE + lda #$a0 + jsr COUT1 + iny + cpy #$08 + bcc @p8 + jmp CROUT + +DCdbgFillTimeNow: + lda DCBuf+0 + jsr DC_BCD2Binary + sta TimeNow+3 + lda DCBuf+1 + and #$7F + jsr DC_BCD2Binary + sta TimeNow+2 + lda DCBuf+2 + and #$7F + jsr DC_BCD2Binary + sta TimeNow+1 + lda DCBuf+3 + and #$3F + jsr DC_BCD2Binary + sta TimeNow + rts + +DCdHdr: .byte "DCLOCK DEBUG (IIc only)", 0 +DCdR1: .byte "R1 ", 0 +DCdR2: .byte "R2 ", 0 +DCdOk: .byte "VALID ", 0 +DCdBad: .byte "VALID? FAIL ", 0 +DCdBin: .byte "BIN H M S 1/100 ", 0 +DCdDone: .byte "PATCH OK", 0 + +DclockRead: + jsr DclockReset + sta DCPreserve + jsr DclockEnable + ldy #$00 +: jsr DclockRead8 + sta DCBuf,y + iny + cpy #$08 + bcc :- + jmp DclockRestore + +DClockValidTime: + ldx #$00 + ldy #$99 + lda DCBuf+0 + jsr DC_ValidBCD + bcs DCvt_bad + ldx #$00 + ldy #$59 + lda DCBuf+1 + jsr DC_ValidBCD + bcs DCvt_bad + ldx #$00 + ldy #$59 + lda DCBuf+2 + jsr DC_ValidBCD + bcs DCvt_bad + ldx #$00 + ldy #$23 + lda DCBuf+3 + jsr DC_ValidBCD + bcs DCvt_bad + ldx #$01 + ldy #$07 + lda DCBuf+4 + jsr DC_ValidBCD + bcs DCvt_bad + ldx #$01 + ldy #$31 + lda DCBuf+5 + jsr DC_ValidBCD + bcs DCvt_bad + ldx #$01 + ldy #$12 + lda DCBuf+6 + jsr DC_ValidBCD + bcs DCvt_bad + ldx #$00 + ldy #$99 + lda DCBuf+7 + jsr DC_ValidBCD + bcs DCvt_bad + clc + rts +DCvt_bad: + sec + rts + +; A = BCD, X = low bound BCD, Y = high BCD — CC if X <= A <= Y +DC_ValidBCD: + sed + stx DCBcdScratch + cmp DCBcdScratch + bcc DCv_bad + sty DCBcdScratch + cmp DCBcdScratch + beq DCv_ok + bcs DCv_bad +DCv_ok: + cld + clc + rts +DCv_bad: + cld + sec + rts + +; BCD in A -> binary in A (SOURCE.UTIL BCD2Binary) +DC_BCD2Binary: + pha + and #$F0 + lsr a + sta DCBcdScratch + lsr a + lsr a + adc DCBcdScratch + sta DCBcdScratch + pla + and #$0F + adc DCBcdScratch + rts + +GetTimeDClock: + jsr DclockRead + lda DCBuf+0 + jsr DC_BCD2Binary + sta TimeNow+3 + lda DCBuf+1 + and #$7F + jsr DC_BCD2Binary + sta TimeNow+2 + lda DCBuf+2 + and #$7F + jsr DC_BCD2Binary + sta TimeNow+1 + lda DCBuf+3 + and #$3F + jsr DC_BCD2Binary + sta TimeNow + rts + GetTime: jsr $0000 rts @@ -176,6 +549,106 @@ GetTimeROMX: sta TimeNow+3 rts +;--------------------------------------------------------- +; MegaFlash clock support (Apple IIc/IIc+ with MegaFlash) +; https://github.com/ThomasFok/MegaFlash +; +; Design notes: doc/MegaFlash_Clock.md +; +; Activation is handled in MegaFlash ROM; we only check for presence and read time. +; Detection: CMD_GETDEVINFO returns signature $88,$74 in paramreg. +; +; GetTime: CMD_GETPRODOS25TIME returns 6 bytes. Time word (bytes 2-3) +; packs [mday:5][hour:5][min:6]; we extract hour=(time>>6)&$1F, +; min=time&$3F. Byte 1 = seconds. +;--------------------------------------------------------- +MF_CMDSTATUS = $C0C0 +MF_PARAM = $C0C1 +MF_CMD_GETDEVINFO = $10 +MF_CMD_GETPRODOS25TIME = $18 +MF_SIGNATURE1 = $88 +MF_SIGNATURE2 = $74 + +CheckForMegaFlash: + ; ROM activates MegaFlash if present; just check for presence + ; Send GETDEVINFO command + lda #MF_CMD_GETDEVINFO + sta MF_CMDSTATUS + ; Wait for busy to clear (with timeout) + ldx #100 +: bit MF_CMDSTATUS + bpl :+ + dex + bne :- + ; Timeout - MegaFlash not present + sec + rts +: ; Check for error (bit 6) + bvs CheckMegaFlashFail + ; Verify signature bytes + lda MF_PARAM + cmp #MF_SIGNATURE1 + bne CheckMegaFlashFail + lda MF_PARAM + cmp #MF_SIGNATURE2 + bne CheckMegaFlashFail + ; MegaFlash found + clc + rts +CheckMegaFlashFail: + sec + rts + +GetTimeMegaFlash: + ; Request ProDOS 2.5 format time (includes seconds) + lda #MF_CMD_GETPRODOS25TIME + sta MF_CMDSTATUS + ; Wait for completion (bounded — slinky or non-MF hardware can leave busy stuck) + ldx #100 +: bit MF_CMDSTATUS + bpl GetTimeMFReady + dex + bne :- + jmp GetTimeMFDone ; timeout — do not read param stream +GetTimeMFReady: + bvs GetTimeMFDone ; Error - skip update + ; Read 6 bytes: [0]=4ms units, [1]=sec, [2-3]=time word, [4-5]=date + lda MF_PARAM ; t4ms - discard + lda MF_PARAM ; seconds + sta MFTime+1 + lda MF_PARAM ; time lo + sta MFTime+2 + lda MF_PARAM ; time hi + sta MFTime+3 + lda MF_PARAM ; date lo - discard + lda MF_PARAM ; date hi - discard + ; Extract: min = time & $3F, hour = (time >> 6) & $1F + lda MFTime+2 + and #$3F + sta TimeNow+1 ; Minutes + lda MFTime+2 + lsr a + lsr a + lsr a + lsr a + lsr a + lsr a + sta MFTime+0 ; temp: hour low 2 bits + lda MFTime+3 + and #$07 ; hour bits from high byte + asl a + asl a + ora MFTime+0 + sta TimeNow ; Hours + lda MFTime+1 + sta TimeNow+2 ; Seconds + lda #$00 + sta TimeNow+3 ; Hundredths (MegaFlash has 4ms, we use 0 for simplicity) +GetTimeMFDone: + rts + +MFTime: .res 4 ; Temp: [0]=hour bits, [1]=sec, [2]=time_lo, [3]=time_hi + CheckForNoSlotClock: jsr PrepNoSlotClock ; Prepare the driver jsr NSCEntry ; Call it to get the time diff --git a/src/prodos/prodosconst.i b/src/prodos/prodosconst.i index 2024639..72faadf 100644 --- a/src/prodos/prodosconst.i +++ b/src/prodos/prodosconst.i @@ -68,6 +68,13 @@ BITMAP = $BF58 ; bitmap of low 48k of memory DEVICE = $BF30 ; last drive+slot used, DSSS0000 DEVCNT = $BF31 ; Count (minus 1) of active devices DEVLST = $BF32 ; List of active devices (Slot, drive, id =DSSSIIII) +; ProDOS global page — date/time and machine ID (Apple ProDOS 8) +DATETIME = $BF06 ; JMP to installed clock driver (3 bytes: $4C lo hi) +DATELO = $BF90 +DATEHI = $BF91 +TIMELO = $BF92 ; minute (0–59) +TIMEHI = $BF93 ; hour (0–23) +MACHID = $BF98 ; bit 0 = clock card recognized (per Apple) ;--------------------------------------------------------- ; Monitor equates @@ -97,7 +104,8 @@ CROUT = $FD8E ; Output return character PRDEC = $ED24 ; Print pointer as decimal DELAY = $FCA8 ; Monitor delay: # cycles = (5*A*A + 27*A + 26)/2 MEMMOVE = $FE2C ; Perform memory move: A1-A2 TO A4 -VERSION = $FBB3 ; Version byte +VERSION = $FBB3 ; Version byte ($06 = Apple IIc family) +ZIDBYTE = $FBC0 ; IIc sub-model ID ($00 = classic IIc; non-zero e.g. IIc Plus) ROM = $C082 ; Enables rom ;--------------------------------------------------------- diff --git a/src/prodos/prodosvars.asm b/src/prodos/prodosvars.asm index 0327166..ebc98f3 100644 --- a/src/prodos/prodosvars.asm +++ b/src/prodos/prodosvars.asm @@ -132,3 +132,11 @@ READ_BLK_UNIT: .byte 0 READ_BLK_BUF: .addr BIGBUF READ_BLK_NUM: .addr $0000 ; Block number +;---------------------------------------------------------------------- +; Applied Engineering DClock (IIc Memory Expansion) — see doc/DClock_Notes.md +;---------------------------------------------------------------------- +DCPreserve: .res 1 ; Slinky byte at $080000 save/restore +DCBcdScratch: .res 1 ; BCD2Binary + ValidBCD temp +DCBuf: .res 8 ; Raw BCD from chip (AE SOURCE.UTIL layout) +DCW8Byte: .res 1 ; DclockWrite8 working copy of pattern byte + diff --git a/src/sos/conio.asm b/src/sos/conio.asm index 6b0bdcf..c222946 100644 --- a/src/sos/conio.asm +++ b/src/sos/conio.asm @@ -79,8 +79,8 @@ INIT_SCREEN: ora #$40 ; Turn $C000-$CFFF to I/O sta $FFDF ; Write the environment register -; Points SOS NMI vector at the debug routine in SOS. It normally -; points at an RTS so that hitting RESET doesnt do anything. This +; Points SOS� NMI vector at the debug routine in SOS. It normally +; points at an RTS so that hitting RESET doesn�t do anything. This ; changes it so when you hit RESET, SOS enters a routine that saves all the ; important stuff, and jumps into the built in monitor. To reenter SOS, do ; a 198CG from the monitor. Known to work through SOS 1.3. diff --git a/src/timetest.asm b/src/timetest.asm index c0a6a67..8835525 100644 --- a/src/timetest.asm +++ b/src/timetest.asm @@ -438,6 +438,14 @@ PrintTimeDifference: @next: dex bne @more @done: + lda Elapsed + ora Elapsed+1 + bne :+ + lda #1 + sta Elapsed + lda #0 + sta Elapsed+1 +: lda Elapsed sta