Add full compiler toolchain, libc, examples and reference docs

First substantive commit: the entire Sprinter C compiler tree on top of
the bare README+gitignore initial commit.

What's in here:
  bin/sprinter-cc        — driver script invoking SDCC + linker + mkexe
  libc/                  — Sprinter-specific libc layer over ESTEX/BIOS
                           (conio, gfx, io, mem, stdio + headers)
  runtime/               — crt0 variants (default/small/banked/minimal)
                           + heap + bank trampolines
  toolchain/             — mkexe (SprintEXE packer, C + tests)
  examples/              — 30 demo programs (gfx, file I/O, env, time, …)
  lib/Makefile           — builds the libc archive (sprinter.lib)
  docs/                  — converted Sprinter manuals + asm reference samples
  third_party/           — solid-c reference compiler dump + sdcc setup script
  release_docs/          — packaging / release notes

gitignore overhaul:
  • Drop dangerous blanket patterns: *.asm (would hide docs/samples/*.asm)
    and *.exe (case-insensitive match was hiding third_party/solid-c/*.EXE
    on macOS APFS).  Replaced with examples/*/*.{asm,exe,…} and lib/*.lib.
  • Restore tracking of toolchain/mkexe/tests/{one,big}.bin — those are
    INPUT fixtures, not build outputs.
  • Collapse the duplicated SDCC/C/Sdcc sections into one section per
    concern (build outputs / vendored / OS-junk).
  • Add .sprinter-cc-*/, build/ (catches lib/build/ too), .claude/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 16:13:21 +03:00
parent f542608b3f
commit c71e249a4e
404 changed files with 75155 additions and 58 deletions
+204
View File
@@ -0,0 +1,204 @@
# IM2 Interrupt Handlers — Design Document
**Status:** РЕАЛИЗАЦИЯ ОТЛОЖЕНА (до пост-релизной версии). Обязательная фича для v2.
Этот документ собирает всё, что мы знаем о прерываниях Sprinter и план реализации user-задаваемых ISR через Z80 IM 2 mode. Когда возьмёмся за реализацию — читать этот файл, чтобы не повторять research.
## Зачем нужны прерывания
- **Timer ISR (50/60 Hz)** — счётчик кадров, плавная анимация без busy-loop, тайминги
- **Mouse / keyboard async-обработка** — без polling
- **Music playback** — AY-3-8910, COVOX через прерывания
- **Real-time games** — input + game logic + render в interrupt-driven архитектуре
## Hardware-факты (из docs/converted)
### Sources of vector 0xFF
| Источник | Detect bit | Частота |
|---|---|---|
| Frame (screen refresh) | (default if none of below) | 50/60 Hz |
| Keyboard | port `0x19` (COM_A) bit 0 | event-driven |
| CBL/COVOX (sound) | port `0xFE` bit 7 (sample request) | sample-rate-dependent |
| Mouse | — (hardware interrupt not wired) | — |
| ISA | другой vector (configurable) | depends |
Источники:
- `docs/converted/Forum.txt:956` — кадровые и клавиатурные прерывания приходят с vector 0xFF; различаются по bit 0 порта 0x19. От мыши прерываний нет
- `docs/converted/Forum.txt:758-764` — CBL также vector 0xFF, отличить по bit 7 порта 0xFE
- `docs/converted/IvanMak.txt:1086``READ_KBD`: IN(0x19), bit 0 = "байт принят"; затем IN(0x18) = data byte; нужно drain FIFO (до 3 байт)
- `docs/converted/IvanMak.txt:1471` — ВАЖНОЕ ОГРАНИЧЕНИЕ: vector table + ISR + stack ОБЯЗАНЫ быть в области `0x8000..0xBFFF` (window 2). Иначе BIOS будет отключать прерывания на каждой вызове функции
### Что DSS делает в своём ISR (предположения, требует verification)
DSS shell имеет свой IM 2 handler:
- Drain'ит keyboard FIFO в свой буфер (читается через ESTEX WAITKEY/SCANKEY)
- Возможно обновляет ESTEX SYSTIME ($21) tick counter
- Возможно poll'ит mouse (хотя hardware-IRQ от mouse нет — может быть software polling)
- Refresh курсора мыши (он же видимый и движется в shell)
Без chain'инга к DSS:
- Сломается клавиатура (ESTEX kbd functions не получат байты)
- Может сломаться SYSTIME counter
- Может перестать обновляться mouse cursor
### IM2-трюк
Стандартная схема для одиночного ISR address:
1. Аллоцировать **257-байтный** буфер заполненный одинаковым байтом `H`
2. Загрузить `I = H` (например `H=0xA3` → table at `0xA300`, обращения `0xA300..0xA400`)
3. При прерывании CPU читает байт по `(I<<8)|v` и следующий
4. Если оба байта = `H` → ISR address = `(H<<8)|H` = `HHHH`
5. По адресу `HHHH` положить `jp real_isr`
Поскольку для нас интересен только vector 0xFF: read bytes at `(0xA3FF)` and `(0xA400)`. Если table заполнена H=0xA3 — оба байта читаются как 0xA3. ISR_ADDR = `0xA3A3`. По адресу 0xA3A3 кладём 3-байтовый `jp _trampoline`.
## Предлагаемый дизайн
### Public API (libc/include/irq.h)
```c
typedef void (*isr_t)(void);
int irq_install(isr_t handler); /* 0 OK, -1 error (already installed) */
void irq_remove(void);
/* Convenience macros — wrap DI/EI when modifying volatile globals
* shared between main and ISR. */
#define IRQ_DISABLE() __asm di __endasm
#define IRQ_ENABLE() __asm ei __endasm
```
Пример использования:
```c
volatile uint16_t ticks = 0;
void on_tick(void) { ticks++; }
int main(void) {
irq_install(on_tick);
uint16_t start = ticks;
while (ticks - start < 50) { /* wait 1s */ }
irq_remove();
}
```
### Внутренности
**Аллокация vector page:**
- Static buffer 513 байт в `_BSS` (sprinter.lib).
- Размер 513 = 256 (выравнивание) + 257 (сама table) — в худшем случае выравнивание тратит 256 байт.
- Внутри буфера ищем 256-byte aligned адрес. SDCC может не поддерживать `__attribute__((aligned(256)))` — придётся через ассемблер с `.area _BSS_ALIGNED` и линкер-флаг для выравнивания, или через runtime поиск aligned position.
- **Alternative:** заранее линкуем vector page по фиксированному адресу через linker flag `-Wl-b_VECTORS=0xA300` (как у банков). Стабильнее.
**Trampoline в W2:**
- Маленький asm-блок (~50 байт) который:
1. `ex af,af'; exx; push ix; push iy` — сохранить ВСЕ регистры
2. `in a, (0xE2); push af` — сохранить current W3 page byte
3. `in a, (0x19); bit 0, a; jr z, _not_kbd` — keyboard?
- keyboard path: chain to DSS old ISR (jp/call to saved address)
4. `in a, (0xFE); bit 7, a; jr z, _not_cbl` — CBL? (Phase 2)
5. Frame path: `ld hl, (user_handler); ld a, h; or l; jr z, _no_user; call hl_indirect`
6. `pop af; out (0xE2), a` — restore W3
7. `pop iy; pop ix; exx; ex af,af'; ei; reti`
**Где живёт trampoline:**
- Для `tiny` mode: `_CODE` = W2 → естественно
- Для `big` mode: `_CODE` = W2 → естественно
- Для `small`/`huge`: `_CODE` = W1, **но trampoline ДОЛЖЕН быть в W2** (W1 может swap'нуться)
- **Решение:** новая linker area `_TRAMP_W2` с absolute address в W2 (например 0xBE00). sprinter-cc размещает её через `-Wl-b_TRAMP_W2=0xBE00`. trampoline.s помечает себя `.area _TRAMP_W2`.
**Chain to DSS:**
- В `irq_install`:
```asm
ld a, i ; A = current vector page high byte
ld (old_I), a
ld h, a
ld l, #0xFF
ld a, (hl) ; A = vector_high (= old_I по trick'у)
ld d, a
ld e, a ; DE = address of DSS's IM2 jp
ld hl, (de) ; HL = DSS's old jp target
ld (dss_old_isr), hl
```
- В trampoline keyboard-path:
```asm
ld hl, (dss_old_isr)
push hl
ret ; jumps to DSS ISR which ends with EI; RETI
```
- **Опасность:** DSS's ISR может предполагать что регистры свежие (как только что от CPU) → возможно нужно НЕ saving некоторые регистры до chain'а
**`irq_remove`:**
- DI
- Restore I to old value
- Restore IM mode (обычно был IM 2 → IM 2; редко IM 1 if shell upgraded)
- Free vector page if dynamically allocated
- EI
## Ограничения user handler'а
User's ISR может:
- Читать/писать volatile globals
- Делать дешёвые арифметические операции
- Менять `g_text_attr` (но не вызывать putch/cputs)
User's ISR НЕ должен:
- Вызывать `printf` / `puts` / `malloc` / любые ESTEX/BIOS функции — они могут не быть re-entrant
- Использовать `gfx_*` — они swap'ят W3, наш trampoline уже сохраняет порт но если внутри ISR будет повторный swap то trampoline не сможет восстановить
- Запускать accelerator (LD D,D и т.д.) — accel меняет систему команд CPU
- Долго работать — ISR должен быть быстрым (< 1ms), иначе пропустим следующий
## Открытые вопросы
1. **Что именно DSS делает в своём ISR** — disassemble DSS или вызвать его с инструментировкой
2. **`ld a, i` semantics** на Sprinter — на Z80 P/V flag отражает IFF2; нужно для save/restore
3. **Alignment vector page** — найти SDCC-совместимый способ: либо linker absolute area, либо runtime align внутри 513-байтного буфера
4. **Re-entrancy ESTEX из main во время ISR**:
- Если main вызывает ESTEX и в это время приходит interrupt → DSS chain'инг должен работать корректно (DSS уже спроектирован под IM 2)
- Если main вызывает BIOS (RST 8) — это отключает прерывания на время вызова, OK
4. **Memory budget** — vector page 513 байт в BSS уменьшит heap. В tiny mode с heap ~10KB это ~5%. OK.
## Phase 1 acceptance
- `examples/irq_test/` — счётчик тиков растёт с 50 Hz
- Клавиатура продолжает работать через DSS chain (можно прервать тест клавишей)
- Корректный exit — DSS shell получает управление обратно без crash
- Работает во всех memory modes (tiny, small, big, huge)
- Memory note `memory/sprinter_im2_isr.md` с описанием ABI и ограничений
## Phase 2 (когда понадобится)
- CBL/COVOX prerequisite handler (для audio playback)
- ISA interrupt handler (для ZX-Bus карт)
- Multiple user handler chain (e.g. tick + sound)
## Альтернатива: отдельный memory mode "im2"
Идея: вместо того чтобы крутить trampoline location во всех существующих режимах, сделать **отдельный `--memory im2`** который:
- Forces CODE в W2 (как tiny)
- Reserves определённый адрес в W2 под vector page и trampoline (например 0xBE00..0xBFFF)
- crt0_im2.s ставит IM 2 в начале (заменяет DSS handler с chain)
- crt0_im2.s восстанавливает на exit
**Плюсы:**
- Меньше matrix-сложности (irq работает только в одном mode)
- Можно агрессивно reserved'ить W2-память
- Тестируется как единое целое
**Минусы:**
- Программам приходится явно выбирать `--memory im2` для использования прерываний
- Дублирование crt0 и runtime
Текущее предложение — пойти этим путём (отдельный mode) для v2, не лезть в существующие crt0.
## Внешние ссылки
- `docs/converted/IvanMak.txt:1040-1054` — секция 9.3 "Прерывания от ISA" + 9.4 "AT-Клавиатура"
- `docs/converted/IvanMak.txt:1469-1473` — IM 2 ограничения (table/stack/ISR в W2)
- `docs/converted/Forum.txt:758-764` — CBL interrupt discrimination
- `docs/converted/Forum.txt:956` + `:1049` — vector 0xFF disambiguation
- `docs/converted/Parinov.txt:601` — IM 1 alternative (handler по адресу 0x0038, не наш путь)
## История
- 2026-06-01 — research собран в этот документ, реализация отложена до v2