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:
@@ -0,0 +1,15 @@
|
||||
# Sprinter C Compiler — Документация пользователя (Русский)
|
||||
|
||||
Это документация, поставляемая в составе релизного tarball. Английская
|
||||
версия — `../en/README.md`.
|
||||
|
||||
## Содержание
|
||||
|
||||
| Файл | Тема |
|
||||
|---|---|
|
||||
| `getting_started.md` | Установка, первая сборка, заметки про MAME |
|
||||
| `sprinter_cc.md` | Драйвер компилятора — все флаги |
|
||||
| `memory_modes.md` | tiny / small / big / huge / manual |
|
||||
| `headers.md` | Публичные заголовки и что в них |
|
||||
| `examples.md` | Обзор 27 встроенных примеров |
|
||||
| `platform_reference.md` | Особенности платформы Sprinter и нюансы разработки (на основе наших dev-заметок) |
|
||||
@@ -0,0 +1,75 @@
|
||||
# Обзор примеров
|
||||
|
||||
Релиз содержит 27 примеров в каталоге `examples/`. Каждый — самодостаточная
|
||||
демо-программа с комментариями (использовались как regression-тесты при разработке).
|
||||
|
||||
## Сборка примера
|
||||
|
||||
```sh
|
||||
cd examples/hello
|
||||
make
|
||||
```
|
||||
|
||||
Результат — `hello.exe` рядом с `hello.c` через `examples/example.mk`.
|
||||
|
||||
## Категории
|
||||
|
||||
### Hello world / основы
|
||||
* **`hello`** — stdio + conio Turbo-C-стиль цвета
|
||||
* **`argv`** — парсинг argv в crt0
|
||||
* **`conio`** — smoke test conio API
|
||||
* **`attrprob`** — пробинг байта атрибутов Sprinter
|
||||
|
||||
### Файловый ввод-вывод
|
||||
* **`cat`** — читает и печатает TEST.TXT
|
||||
* **`seek`** — 32-битный lseek по файлу в 100 КБ
|
||||
* **`ls`** — листинг каталога через ffirst/fnext
|
||||
* **`filetest`** — FILE* стримы (`fopen`/`fread`/`fwrite`/`fclose`)
|
||||
* **`stattest`** — `stat`/`fstat` для файлов и каталогов
|
||||
* **`openenv`** — флаги open() + environment variables
|
||||
|
||||
### Память и банки
|
||||
* **`malloc`** — стресс-тест heap (200+ allocations)
|
||||
* **`mem_test`** — page allocator + `bank_read`/`bank_write`
|
||||
* **`banked`** — banked-код в W3 (huge mode)
|
||||
* **`bankedbg`** — banked-код в W1 (big mode)
|
||||
* **`banklocl`** — bank-local статические данные и BSS
|
||||
|
||||
### Мышь
|
||||
* **`mouse`** — драйвер в текстовом режиме
|
||||
* **`gfx_mous`** — мышь с пользовательским bitmap-курсором в графическом режиме
|
||||
|
||||
### Графика
|
||||
* **`gfx_demo`** — 320×256×256: линии, прямоугольники, fill через accelerator
|
||||
* **`gfx_d16`** — 640×256×16: те же примитивы в 16-цветном режиме
|
||||
* **`gfx_text`** — bitmap-текст на графическом экране
|
||||
|
||||
### Прочее
|
||||
* **`errno`** — errno / strerror / perror
|
||||
* **`timedir`** — дата/время + листинг каталога
|
||||
* **`ptime`** — POSIX time API (time / localtime / mktime)
|
||||
* **`strtest`** — `<string.h>` тест (из SDCC's z80.lib)
|
||||
* **`stdlib`** — `<stdlib.h>` тест (qsort / rand / strtol / etc.)
|
||||
* **`assrtest`** — assert()
|
||||
* **`rt_test`** — runtime helpers (sleep, setjmp, atexit)
|
||||
|
||||
## Example.mk
|
||||
|
||||
Каждый пример использует `examples/example.mk`. Минимальный Makefile:
|
||||
|
||||
```makefile
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := my_app
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
```
|
||||
|
||||
Опциональные параметры (задаются до `include`):
|
||||
|
||||
```makefile
|
||||
MEMORY := huge # по умолчанию tiny
|
||||
STACK_SIZE := 4096 # по умолчанию ~1278
|
||||
EXTRA_SRCS := helper.c util.c # дополнительные .c в той же папке
|
||||
EXTRA_FLAGS := --bank 1=engine.c --debug # pass-through в sprinter-cc
|
||||
```
|
||||
|
||||
Используйте этот template для своих программ.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Начало работы
|
||||
|
||||
## Что в tarball
|
||||
|
||||
После распаковки `sprinter-c-v1.0-<host>.tar.gz` вы получаете:
|
||||
|
||||
* **`bin/sprinter-cc`** — драйвер C → SprintEXE (bash-скрипт).
|
||||
* **`third_party/sdcc/`** — vendored SDCC 4.5 для шага C → Z80.
|
||||
* **`libc/include/`** — заголовки для подключения в ваших программах.
|
||||
* **`lib/sprinter.lib`** — целевая libc для Sprinter (предсобрана; пересобирается через `make`, если изменили исходники libc).
|
||||
* **`runtime/`** — варианты crt0 и runtime-помощники (ассемблируются per-build).
|
||||
* **`toolchain/mkexe/`** — host-утилита, упаковывающая `.ihx` SDCC в SprintEXE.
|
||||
* **`examples/`** — 27 готовых программ.
|
||||
* **`docs/{en,ru}/`** — эта документация.
|
||||
|
||||
## Первая сборка
|
||||
|
||||
```sh
|
||||
cd sprinter-c-v1.0-<host>
|
||||
make all # пересобрать lib + все примеры (~30 с)
|
||||
```
|
||||
|
||||
Если `make` жалуется на отсутствующий бинарник SDCC — загрузите его один раз:
|
||||
|
||||
```sh
|
||||
make sdcc # скачивает SDCC 4.5 если не vendored
|
||||
```
|
||||
|
||||
## Сборка своей программы
|
||||
|
||||
```sh
|
||||
cat > hello.c <<EOF
|
||||
#include <stdio.h>
|
||||
int main(void) {
|
||||
puts("Hello, Sprinter!");
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
bin/sprinter-cc -o hello.exe hello.c
|
||||
```
|
||||
|
||||
`hello.exe` теперь — корректный SprintEXE, который можно запустить на
|
||||
Sprinter / MAME / в любом ESTEX DSS шелле.
|
||||
|
||||
## Запуск на железе или в эмуляторе
|
||||
|
||||
Релиз **не содержит** эмулятор MAME или образы ROM / DSS / HDD Sprinter —
|
||||
они большие и имеют свои лицензии. Для тестирования:
|
||||
|
||||
* **MAME:** установите MAME 0.283+ отдельно, получите образы Sprinter Sp2000
|
||||
у Peters Plus, смонтируйте FAT12-флоп с вашими `.exe` файлами как `-flop1`.
|
||||
* **Реальный Sprinter:** скопируйте `.exe` на флоп или раздел HDD, видимый
|
||||
для DSS, затем `RUN HELLO` в шелле.
|
||||
|
||||
## Что дальше
|
||||
|
||||
* Прочитайте `sprinter_cc.md` про флаги компилятора.
|
||||
* Прочитайте `memory_modes.md` когда не хватит 14 КБ кода.
|
||||
* Просмотрите `examples/` — каждый файл это рабочая программа с комментариями.
|
||||
* `headers.md` — список публичных API.
|
||||
* `platform_reference.md` — глубокие нюансы платформы и компилятора (от граблей до подводных камней).
|
||||
@@ -0,0 +1,50 @@
|
||||
# Заголовки
|
||||
|
||||
Все `#include` живут в `libc/include/`.
|
||||
|
||||
## Стандартный C
|
||||
|
||||
| Заголовок | Источник | Что предоставляет |
|
||||
|---|---|---|
|
||||
| `<stdio.h>` | наша libc + SDCC | `printf`, `puts`, `putchar`, `getchar`, `sprintf`, `FILE *`, `fopen`/`fread`/.../`fclose`, плюс `hex8/16/32`, `dec8/16/32`, `gets` |
|
||||
| `<stdlib.h>` | SDCC z80.lib | `malloc`, `free`, `calloc`, `realloc`, `atoi`, `atof`, `atol`, `strtol`, `qsort`, `bsearch`, `rand`, `srand`, `abs`, `div`, `exit`, ... |
|
||||
| `<string.h>` | SDCC z80.lib | `memcpy`, `memcmp`, `memset`, `memchr`, `memmove`, вся семья `strXxx` |
|
||||
| `<ctype.h>` | SDCC z80.lib | `tolower`, `toupper`, `isalnum`, `isdigit`, ... |
|
||||
| `<math.h>` | SDCC z80.lib | `sinf`, `cosf`, `sqrtf`, ... |
|
||||
| `<errno.h>` | наша libc | `errno` + константы ошибок + `strerror` |
|
||||
| `<setjmp.h>` | наша libc | `setjmp` / `longjmp` |
|
||||
| `<assert.h>` | SDCC | макрос `assert` |
|
||||
| `<unistd.h>` | наша libc | `read`, `write`, `close`, `lseek`, `unlink`, `SEEK_SET`/`CUR`/`END` |
|
||||
| `<fcntl.h>` | наша libc | `open`, `creat`, `O_RDONLY`/`O_WRONLY`/`O_CREAT`/... |
|
||||
| `<sys/stat.h>` | наша libc | `stat`, `fstat`, `struct stat` |
|
||||
| `<time.h>` | наша libc | `getdatetime`, `setdatetime` + POSIX `time`/`localtime`/`gmtime`/`mktime`/`asctime`/`ctime` |
|
||||
|
||||
## Sprinter-специфичные
|
||||
|
||||
| Заголовок | Что предоставляет |
|
||||
|---|---|
|
||||
| `<conio.h>` | `putch`, `cputs`, `cprintf`, `kbhit`, `getch`, `getche`, `clrscr`, `gotoxy`, `wherex/y`, `wrchar`, `rdchar`, `textcolor`, `textbackground`, `textattr`, `get_videomode`, `set_videomode`, `COLOR_*` enum, `KEEP_EXIST_ATTR` |
|
||||
| `<gfx.h>` | Графика для 320×256×256 и 640×256×16: `gfx_init`/`gfx_done`, `gfx_pal_load`/`gfx_pal_set`, `gfx_clear`, `gfx_putpixel`, `gfx_hline`/`gfx_vline`, `gfx_rect`/`gfx_fill_rect`, `gfx_line`, `gfx_text`/`gfx_putchar`, все варианты `*16` для 16-color режима, управление шрифтом через `gfx_load_default_font`/`gfx_set_font` |
|
||||
| `<mouse.h>` | Полная обёртка из 14 функций драйвера: `mouse_init`/`mouse_show`/`mouse_hide`/`mouse_read`/`mouse_goto`/`mouse_bounds_*`/`mouse_text_cursor`/`mouse_load_cursor`/`mouse_get_cursor`/`mouse_set_sensitivity`/`mouse_get_sensitivity_*`/`mouse_video_mode_changed`/`mouse_refresh`, плюс структуры `mouse_cursor_t` и `mouse_state_t` |
|
||||
| `<dir.h>` | `chdir`, `getcwd`, `mkdir`, `rmdir`, `ffirst`, `fnext`, структура `ffblk` |
|
||||
| `<sprinter.h>` | Сырые номера портов, константы ESTEX/BIOS function numbers, `__sfr` intrinsics для paging, `print_hex`, `getenv`, `putenv` |
|
||||
| `<sprinter_exit.h>` | `exit`, `_exit`, `atexit` |
|
||||
| `<sprinter_mem.h>` | `mem_alloc_pages`, `mem_free_block`, `mem_get_page`, `mem_info`, `bank_read`, `bank_write`, `bank_load_byte`, `bank_store_byte` |
|
||||
| `<sprinter_compat.h>` | Solid-C compatibility shims — подтягивает стандартные заголовки и добавляет типы `BOOL`/`uint`/`WORD`/`f_point`, алиасы `setmem`/`movmem`, `inp`/`outp`, `enable`/`disable`, `min`/`max`, `home()`, `seek`/`tell`/`remove`, `_ffirst`, mouse-алиасы `ms_*` и т.д. |
|
||||
|
||||
## Быстрая навигация: я хочу...
|
||||
|
||||
* **Вывести текст** → `<stdio.h>` (`printf` / `puts` — быстро, без цвета) или
|
||||
`<conio.h>` (`cprintf` / `cputs` — применяет `textcolor`).
|
||||
* **Прочитать клавишу** → `<conio.h>`: `getch()` (blocking, без эхо),
|
||||
`getche()` (с эхо), `kbhit()` (non-blocking poll).
|
||||
* **Открыть / прочитать / записать файл** → `<unistd.h>` + `<fcntl.h>` (POSIX)
|
||||
или `<stdio.h>` (семья `fopen`).
|
||||
* **Прочитать каталог** → `<dir.h>`: `ffirst` / `fnext`.
|
||||
* **Нарисовать пиксели** → `<gfx.h>`.
|
||||
* **Выделить память** → `<stdlib.h>`: `malloc` / `free` / `calloc` / `realloc`.
|
||||
* **Получить текущее время** → `<time.h>`: `getdatetime` или POSIX `time`/`localtime`.
|
||||
* **Прочитать мышь** → `<mouse.h>`.
|
||||
* **Прочитать env var** → `<sprinter.h>`: `getenv` / `putenv`.
|
||||
* **Задать цвет текста** → `<conio.h>`: `textcolor(COLOR_YELLOW)`,
|
||||
`textbackground(COLOR_BLUE)`, или `textattr(COLOR(fg, bg))`.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Режимы памяти
|
||||
|
||||
Адресное пространство CPU Sprinter — четыре окна по 16 КБ. ESTEX DSS
|
||||
выделяет страницы RAM по размеру программы — программам ≤16 КБ
|
||||
выделяется **одна** страница. Из-за этого "очевидная" Spectrum-style
|
||||
раскладка "код в W1, данные в W2" для маленьких программ молча ломается.
|
||||
Именно поэтому `sprinter-cc` имеет явные memory modes.
|
||||
|
||||
## Пять режимов
|
||||
|
||||
| Mode | Код в | Данные в | Банки в | Когда использовать |
|
||||
|---|---|---|---|---|
|
||||
| `tiny` (default) | W2 (0x8100+) | сразу после кода | — | код + данные ≤ 14 КБ |
|
||||
| `small` | W1 (0x4100+) | сразу после кода | — | код + данные ≤ 30 КБ |
|
||||
| `big` | W2 (0x8100+) | сразу после кода | **W1** (0x4000) | tiny + дополнительные banked-модули |
|
||||
| `huge` | W1 (0x4100+) | W2 (0x8000+) | **W3** (0xC000) | small + дополнительные banked-модули |
|
||||
| `manual` | задаётся вручную | вручную | вручную | специальные случаи |
|
||||
|
||||
## Как выбирать
|
||||
|
||||
Начните с **`tiny`**. Если `sprinter-cc` ругается "_CODE too big" или
|
||||
программа таинственно не запускается — сразу переходите на `small`.
|
||||
|
||||
Если кодовая база большая (>32 КБ) и нужно держать большую часть кода
|
||||
вне always-resident окна — используйте `huge` и разбивайте модули на
|
||||
банки через `--bank N=mod.c`. `big` — та же идея, но банки в W1 вместо
|
||||
W3 — полезно когда W3 нужен для железа (графика, mapped-memory).
|
||||
|
||||
## Стек и heap
|
||||
|
||||
Стек начинается с `0xBFFE` (верх W2) и растёт вниз. Heap начинается
|
||||
сразу после BSS и растёт вверх. По умолчанию они делят W2, с ~1.2 КБ
|
||||
зарезервированными под стек и остальным под heap.
|
||||
|
||||
`--stack-size N` резервирует больше (или меньше) под стек ценой heap.
|
||||
|
||||
## Что внутри
|
||||
|
||||
Каждый режим выбирает свой `runtime/crt0_*.s`:
|
||||
|
||||
* `tiny`: `crt0.s` — SP = 0xBFFE, парсит argv, вызывает main.
|
||||
* `small`: `crt0_small.s` — читает порт 0xC2 чтобы проверить дала ли
|
||||
DSS уже W2. Если нет — выделяет страницу через ESTEX `$3D` и маппит
|
||||
через `$3A SETWIN2` **до** переключения стека (BIOS-вызовы требуют
|
||||
стек в W2, ESTEX — нет).
|
||||
* `big`: `crt0_banked.s` с `BANK_W1=1` — банки живут по `0x{N}4000` и
|
||||
trampoline'ы используют порт `0xA2`.
|
||||
* `huge`: `crt0_banked.s` (default `BANK_W1=0`) — банки по `0x{N}C000`
|
||||
через порт `0xE2`. Также включает small-mode W2 auto-allocation.
|
||||
|
||||
Для `manual` см. синтаксис `sprinter-cc --memory-manual SPEC` в `sprinter_cc.md`.
|
||||
@@ -0,0 +1,374 @@
|
||||
# Platform Reference — особенности разработки под Sprinter
|
||||
|
||||
Документ собирает все нетривиальные нюансы платформы Sprinter и нашего
|
||||
C-toolchain'а, накопленные в процессе разработки. Если вы пишете
|
||||
программу и что-то "молча не работает" — скорее всего ответ здесь.
|
||||
|
||||
---
|
||||
|
||||
## 1. Архитектура Sprinter (cheat-sheet)
|
||||
|
||||
* **CPU:** Z84C15 (Z80 совместимый), 21 МГц / 3.5 МГц.
|
||||
* **Адресное пространство:** 4 окна по 16 КБ:
|
||||
* **W0** (0x0000..0x3FFF) — ESTEX DSS система
|
||||
* **W1** (0x4000..0x7FFF) — обычно HOME-программа
|
||||
* **W2** (0x8000..0xBFFF) — обычно данные / стек
|
||||
* **W3** (0xC000..0xFFFF) — обычно banked / видео
|
||||
* **Порты page-select:** 0x82 / 0xA2 / 0xC2 / 0xE2 для W0..W3 соответственно.
|
||||
Запись номера страницы переключает окно. **Чтение** — возвращает текущий
|
||||
номер страницы (полезно для детектирования).
|
||||
* **Системные вызовы:**
|
||||
* `RST 10h` — ESTEX DSS, номер функции в C
|
||||
* `RST 8` — BIOS, номер функции в C
|
||||
* `RST 30h` — Mouse driver, номер функции в C
|
||||
* **Видеорежимы:** 320×256×256, 640×256×16, 80×32 text, 40×32 text.
|
||||
* **Формат EXE:** SprintEXE (512-байтный header + образ HOME + опциональные банки).
|
||||
|
||||
---
|
||||
|
||||
## 2. Подводные камни вызовов системы
|
||||
|
||||
### IX **обязательно** сохранять
|
||||
|
||||
ESTEX и BIOS клобберят IX без предупреждения. SDCC использует IX как
|
||||
frame pointer. Каждая обёртка над `RST 10h` / `RST 8` / `RST 30h`
|
||||
ДОЛЖНА оборачивать вызов в `push ix` / `pop ix`:
|
||||
|
||||
```asm
|
||||
push ix
|
||||
ld c, #0x47 ; ESTEX APPINFO
|
||||
rst #0x10
|
||||
pop ix
|
||||
```
|
||||
|
||||
Забудешь — frame pointer уедет, локальные переменные станут мусором,
|
||||
debug будет долгим.
|
||||
|
||||
### BIOS требует стек в W2
|
||||
|
||||
BIOS-вызовы (`RST 8` / `CALL 3D13h`) требуют SP в диапазоне 0x8000..0xBFFF
|
||||
(W2). ESTEX-вызовы — нет (они используют свой стек).
|
||||
|
||||
Практическое следствие: в `crt0_small.s` нельзя выделять W2 через
|
||||
`BIOS $C4 EMM_GETPAGE + OUT (0xC2)`, потому что на этот момент стек
|
||||
ещё в W1. Делается через **ESTEX `$3A SETWIN2`** — он маппит страницу
|
||||
сразу из ESTEX без BIOS.
|
||||
|
||||
### ESTEX `$46 ENV` — баг в документации
|
||||
|
||||
`DiskSyscalls.txt v1.6` пишет: `A=0 — FOUND, A=1 — NOT FOUND`. Реально
|
||||
наоборот: **A=0 — NOT FOUND**. Все наши getenv/putenv учитывают это.
|
||||
|
||||
### ESTEX `$21 SYSTIME` — день недели 1-based
|
||||
|
||||
`dow` (day-of-week) возвращается как `1..7` где `1 = Sunday`, `7 = Saturday`.
|
||||
Если код ждёт `0..6` или `1..7` начиная с Monday — будет смещение.
|
||||
|
||||
### ESTEX `$19 F_FIRST` и каталоги "." / ".."
|
||||
|
||||
Запись с именем `"."` или `".."` для родительского каталога **не возвращается**
|
||||
функцией F_FIRST при поиске `"*.*"` в подкаталоге. Если они нужны —
|
||||
итерироваться через явные `"."` и `".."` запросы.
|
||||
|
||||
### `puts()` после `PCHARS` иногда теряет следующий байт
|
||||
|
||||
ESTEX `$5C PCHARS` после себя оставляет cursor state в котором
|
||||
**следующий** `$5B PUTCHAR` может быть проигнорирован. Решение:
|
||||
если нужна новая строка после PCHARS — встроить `\r\n` ВНУТРИ строки
|
||||
для PCHARS, не вызывать отдельный PUTCHAR. Наш `puts()` так и делает.
|
||||
|
||||
---
|
||||
|
||||
## 3. SDCC ABI — нетривиальные моменты
|
||||
|
||||
### `__sdcccall(1)` — смешанная схема передачи
|
||||
|
||||
* **1-й 16-битный аргумент** → HL
|
||||
* **2-й 16-битный аргумент** → DE
|
||||
* **3-й и далее** → стек
|
||||
* **1-й uint8_t / char** → A (не L!)
|
||||
* **Long-аргументы на стеке** → caller pops
|
||||
* **Int-аргументы на стеке** → callee pops
|
||||
|
||||
### Возврат значений
|
||||
|
||||
* `int` / `uint16_t` / `pointer` → **DE** (НЕ HL как в старых SDCC!)
|
||||
* `char` / `uint8_t` → A (low byte of DE)
|
||||
* `long` / `uint32_t` → DE:HL (DE=low word, HL=high word)
|
||||
* `float` → DE:HL по тому же layout
|
||||
|
||||
Самая частая ошибка — `ld a, l` в обёртках для char-возврата. Нужно
|
||||
`ld a, e` (потому что char идёт в low byte регистра возврата = E).
|
||||
|
||||
### `__asm` блок клобберит DE, но SDCC об этом не знает
|
||||
|
||||
SDCC иногда сохраняет указатель аргумента в DE между C-кодом и
|
||||
inline asm. Если внутри `__asm` написать `ld a, d` или `ld a, e`
|
||||
(например для извлечения возврата из RST), DE будет клобберн, и
|
||||
post-asm код типа `c->field = ...` запишет в случайный адрес.
|
||||
|
||||
**Решение:** парковать указатель в static BSS перед `__asm`, после
|
||||
загружать заново:
|
||||
|
||||
```c
|
||||
static mouse_cursor_t *dest = 0;
|
||||
void mouse_get_cursor(mouse_cursor_t *c) {
|
||||
dest = c;
|
||||
__asm
|
||||
; ... clobbers DE ...
|
||||
__endasm;
|
||||
mouse_cursor_t *p = dest; // SDCC fetches fresh from BSS
|
||||
p->width = mc_width; // writes to correct address
|
||||
}
|
||||
```
|
||||
|
||||
### "Static без инициализатора" грабли BSS
|
||||
|
||||
Несколько подряд `static uint8_t x;` БЕЗ `=0` могут сколлапсировать в
|
||||
**один и тот же адрес** — записи в одну стомпают другие.
|
||||
|
||||
```c
|
||||
static uint8_t a; // адрес 0x9100
|
||||
static uint8_t b; // ТОЖЕ адрес 0x9100!
|
||||
static uint8_t c; // ТОЖЕ 0x9100!
|
||||
|
||||
a = 0xAA; b = 0xBB; c = 0xCC;
|
||||
// a == b == c == 0xCC
|
||||
```
|
||||
|
||||
**Решение:** всегда инициализировать: `static uint8_t a = 0;` — SDCC
|
||||
гарантированно резервирует разные адреса.
|
||||
|
||||
### z80.lib почти полная — НЕ переписывать
|
||||
|
||||
SDCC z80.lib содержит работающие реализации:
|
||||
- `atoi / atol / atof / strtol / strtoul`
|
||||
- `malloc / free / calloc / realloc` (мы только переопределили heap location)
|
||||
- `qsort / bsearch / rand / srand / abs / div`
|
||||
- Полный `<string.h>` (memcpy/memset/strlen/strcmp/strcpy/strchr/strstr/strtok/etc.)
|
||||
- `<ctype.h>` (toupper/tolower/isalpha/isdigit/etc.)
|
||||
- `<math.h>` (sinf/cosf/sqrtf/etc.)
|
||||
|
||||
Линкер автоматически тянет нужное из z80.lib когда есть unresolved
|
||||
symbol. Не переписывать ради переписывания.
|
||||
|
||||
---
|
||||
|
||||
## 4. Banking — нюансы
|
||||
|
||||
### ABI banked-вызовов
|
||||
|
||||
SDCC эмитит для `void f(int x) __banked`:
|
||||
- символ `b_f = N` (bank id = число из `--codeseg BANKn`)
|
||||
- символ `_f` = адрес внутри банка (с `bank_id` в верхнем 8-битном байте)
|
||||
|
||||
Вызов:
|
||||
```asm
|
||||
ld hl, #arg_value
|
||||
push hl
|
||||
ld e, #b_f ; E = bank id
|
||||
ld hl, #_f ; HL = target addr (low 16 bits)
|
||||
call ___sdcc_bcall_ehl
|
||||
pop af ; caller cleans up arg
|
||||
```
|
||||
|
||||
### Стековый "spacer" в trampoline
|
||||
|
||||
Между ret-адресом callee'я и аргументами трамплин ОБЯЗАН вставить
|
||||
**ровно 3 байта** (1 сохранённая страница + 2 байта внутреннего
|
||||
bcall return). SDCC компилирует доступ к аргументам с offset'ом +5
|
||||
от стека. Любая разница ломает все banked-вызовы.
|
||||
|
||||
### CRITICAL: `pop af; out (n), a` клобберит A
|
||||
|
||||
Старый trampoline восстанавливал W3-страницу через `pop af` —
|
||||
**клоббит A**, а SDCC возвращает uint8_t/char именно в A. Все
|
||||
banked-функции с char-возвратом тихо теряли результат.
|
||||
|
||||
Текущая версия использует `pop bc; ld c, #port; out (c), b` — порт
|
||||
через C, значение через B, A сохраняется нетронутым.
|
||||
|
||||
### Bank-local статические данные
|
||||
|
||||
Для модуля целиком в банке:
|
||||
```sh
|
||||
sdcc --codeseg BANK1 --constseg BANK1 --dataseg BANK1 -c bank1.c
|
||||
```
|
||||
Всё (код + const + BSS) живёт в банке. `mkexe -p 0` нужен чтобы BSS
|
||||
загружался обнулённым (иначе будет FF из padding).
|
||||
|
||||
Heap через `malloc()` из banked-функции работает прозрачно — heap в
|
||||
W2 (HOME), W2 trampoline никогда не свопит, указатель валиден из
|
||||
любого контекста.
|
||||
|
||||
---
|
||||
|
||||
## 5. Видео-режимы и графика
|
||||
|
||||
### Mode 0x81 (320×256×256)
|
||||
|
||||
* Адресация: pixel `(x, y)` → CPU-адрес `0xC000 + x` с **Port_Y (0x89) = y**
|
||||
* 320 байт на видимую строку
|
||||
* Палитра: 256 цветов из 4 палитр (BIOS `$A4 PIC_SET_PAL`)
|
||||
* Cleanup: 0x300..0x39F = mode-descriptors, 0x3E0..0x3FF = palette данные — НЕ трогать в обычной отрисовке
|
||||
|
||||
### Mode 0x82 (640×256×16)
|
||||
|
||||
* Та же row-addressing что и 320 mode (320 байт на строку)
|
||||
* НО каждый байт = 2 пикселя по 4 бита
|
||||
* **HIGH nibble (биты 7-4) = LEFT пиксель** (even x)
|
||||
* **LOW nibble (биты 3-0) = RIGHT пиксель** (odd x)
|
||||
* Доки пишут "первыми младшие 4 бита" — это про **временной** порядок
|
||||
в FPGA-сериализаторе, **не пространственный** на экране
|
||||
* Палитра: 16 нижних цветов из любой из 4 палитр
|
||||
|
||||
### Графический Accelerator
|
||||
|
||||
CPU-опкоды используются как control-сигналы (NOP-tricks):
|
||||
- `LD D,D` (0x52) — "ждать LD A, imm для размера блока"
|
||||
- `LD C,C` (0x49) — Fill mode (горизонталь): `LD (HL),A` заполняет N байт
|
||||
- `LD E,E` (0x5B) — Fill mode (вертикаль): `LD (HL),A` заполняет N pix вертикально (авто-Y инкремент)
|
||||
- `LD L,L` (0x6D) — Copy (horizontal)
|
||||
- `LD A,A` (0x7F) — Copy (vertical)
|
||||
- `LD B,B` (0x40) — выключить accel
|
||||
|
||||
**КРИТИЧНО:** размер блока должен быть **immediate операндом** `LD A, n` (опкод 0x3E nn) сразу после `LD D,D`. Accel snoop'ит этот байт. `LD A, (mem)` (опкод 0x3A) — другой 2-й байт, accel захватит мусор.
|
||||
|
||||
**КРИТИЧНО 2:** между `LD C,C` (Fill mode) и `LD (HL),A` (fire) **нельзя** ставить второй `LD A, #imm` — accel re-interpret'ит его как новый block-size. Color через `C`/`B` регистр + `LD A, C` (опкод 0x79, 1 байт).
|
||||
|
||||
**Скорость:** ~7 µs/byte vs ~14-20 µs/byte ручного цикла → ~2-3× быстрее.
|
||||
|
||||
**Прерывания:** DI/EI обязательны вокруг accel — он подменяет систему команд CPU, ISR в это время крашит.
|
||||
|
||||
Реализация в `libc/gfx/gfx_lines.c` — `gfx_hline`/`gfx_vline`/`gfx_rect`/`gfx_fill_rect`. `gfx_clear` использует accel-burst column-major (320 vfill'ов × 256 пикселей за burst, ~4× быстрее ручного цикла).
|
||||
|
||||
### Формат шрифта BIOS
|
||||
|
||||
Шрифт 2 КБ = 256 chars × 8 rows × 1 byte/row, **interleaved row-major**:
|
||||
`offset = row * 256 + char_code`.
|
||||
|
||||
То есть row 0 всех 256 chars лежит в 0x000..0x0FF, row 1 в 0x100..0x1FF, ...
|
||||
Не "char 0 в 0x000..0x007, char 1 в 0x008..0x00F"! Наивное `font[char*8+row]` даст нечитаемую кашу.
|
||||
|
||||
Bit order внутри byte: **MSB-first**. Bit 7 = крайний левый пиксель.
|
||||
|
||||
Получить системный шрифт: BIOS `$B8 WIN_GET_ZG`, DE = destination, читает 2 КБ.
|
||||
|
||||
---
|
||||
|
||||
## 6. Memory modes
|
||||
|
||||
DSS выделяет страницы RAM по размеру программы. **Программа ≤16 КБ получает только 1 страницу.** В остальные окна подключается "страница `0xFF`" (read = 0xFF, write игнорируется).
|
||||
|
||||
Из-за этого классическая раскладка "код в W1 (0x4100+), данные в W2 (0x8000+)" для маленькой программы **молча не работает** — write в W2 уходит в никуда.
|
||||
|
||||
Решение — наши 5 режимов:
|
||||
|
||||
| Mode | Layout | Trick |
|
||||
|---|---|---|
|
||||
| tiny | CODE + DATA в W2 | DSS гарантирует W2 (загружает в неё образ) |
|
||||
| small | CODE в W1, DATA chained | crt0_small читает порт 0xC2 — если 0xFF, выделяет W2 через ESTEX `$3D`/`$3A` |
|
||||
| big | tiny + банки в W1 | crt0_banked с BANK_W1=1, trampoline свопит порт 0xA2 |
|
||||
| huge | small + банки в W3 | crt0_banked с default BANK_W1=0, trampoline на port 0xE2 |
|
||||
|
||||
### Детектирование W2 — порт 0xC2
|
||||
|
||||
`IN A, (0xC2)` возвращает текущую страницу в W2. `0xFF` = "не выделена". Используется в crt0_small для auto-detect.
|
||||
|
||||
**Грабли:** в первой версии стоял `IN A, (0xA2)` — это **W1**, не W2! Для small mode там всегда code-page (не 0xFF) → auto-detect не срабатывал, W2 не выделялась, программа крашилась.
|
||||
|
||||
---
|
||||
|
||||
## 7. Mouse driver
|
||||
|
||||
* Всё через `RST 30h`, номер функции в `C`.
|
||||
* Координаты в **пикселях**. Для text mode 03h (80×32) делить x/8 и y/8.
|
||||
* **Sensitivity = divider** (не коэффициент): меньше = быстрее курсор.
|
||||
Документация ProgrammerManual пишет наоборот — это ошибка.
|
||||
* MAME `$0E GET_SENSITIVE` возвращает 0 (stub). Workaround: всегда
|
||||
ставить значение через `mouse_set_sensitivity()` при старте.
|
||||
* MAME `$0B RETURN_CURSOR` пишет битмап в IX-буфер, но не обновляет H/L/D/E.
|
||||
`mouse_get_cursor()` вернёт width/height/hot_x/hot_y как 0 — это известное ограничение эмулятора.
|
||||
* **Cursor bitmap format**: 1 byte per pixel, row-major; `0xFF = transparent`.
|
||||
Cursor живёт в отдельном видео-банке, не в 0x50 page.
|
||||
|
||||
### `$81 CHANGE VIDEO MODE`
|
||||
|
||||
При смене видеорежима — звать `mouse_video_mode_changed(new_mode)`.
|
||||
**Важно:** аргумент `A = режим экрана` обязателен, в документации
|
||||
указан но легко пропустить. Без него драйвер не пересинхронизирует
|
||||
координаты, и в graphics mode может остаться text-mode XOR-курсор.
|
||||
|
||||
---
|
||||
|
||||
## 8. Linker warnings — что есть и почему
|
||||
|
||||
`sdldz80` пишет `?ASlink-Warning-Definition of public symbol '_X' found more than once` когда наша `sprinter.lib` override'ит функцию из SDCC's `z80.lib`.
|
||||
|
||||
Текущие overrides:
|
||||
- `_puts` — наш через PCHARS+\r\n vs SDCC стандартный
|
||||
- `___sdcc_heap` — наш heap в W2 vs стандартный
|
||||
- `_asctime`, `_localtime` — наш `posix_time.c` vs SDCC's `time.rel` (требует _RtcRead)
|
||||
|
||||
Линкер берёт **первое найденное** определение — это наши. Warning только шум.
|
||||
|
||||
`sprinter-cc` отфильтровывает эти warning-блоки из вывода `sdcc` (3 строки: warning + 2 follow-up `Library:` строк). Через `-v` всё видно.
|
||||
|
||||
---
|
||||
|
||||
## 9. Текстовый вывод — Turbo-C convention
|
||||
|
||||
В Sprinter нет ESTEX-функции "set persistent attribute" — только WRCHAR пишет char+attr единоразово. Поэтому два набора функций:
|
||||
|
||||
| Группа | Header | Скорость | Цвет |
|
||||
|---|---|---|---|
|
||||
| stdio | `<stdio.h>` | Fast (~5 µs/char через PCHARS / PUTCHAR) | НЕТ — ambient |
|
||||
| conio | `<conio.h>` | Slow (~50 µs/char через WRCHAR) | ДА — `g_text_attr` |
|
||||
|
||||
`puts` / `printf` / `putchar` — быстрые без цвета. Цвет = whatever shell оставил. Программа должна `clrscr_attr(attr)` если нужен конкретный default.
|
||||
|
||||
`cputs` / `cprintf` / `putch` — медленные с цветом. Применяют `g_text_attr` (`textcolor`/`textbackground`/`textattr`). При `g_text_attr == KEEP_EXIST_ATTR` (0xFFFF) — fallback на fast path.
|
||||
|
||||
**Cputs/putch НЕ делают `\n` → CR LF translation** (как в Turbo C). Caller должен явно писать `"\r\n"`. `puts` делает.
|
||||
|
||||
---
|
||||
|
||||
## 10. Прочее
|
||||
|
||||
### `dec/hex8/16/32` — мини-форматтеры
|
||||
|
||||
Solid-C-style минимальный вывод чисел без формата:
|
||||
```c
|
||||
hex8(0xAB); // печатает "AB"
|
||||
hex16(0xCAFE); // "CAFE"
|
||||
dec16(50000); // "50000"
|
||||
```
|
||||
Использовать когда не хочется тащить полный printf.
|
||||
|
||||
### `<sprinter_compat.h>`
|
||||
|
||||
Единый header который подтягивает все стандартные + добавляет Solid-C
|
||||
shims: `BOOL`/`uint`/`WORD`/`f_point` types, `setmem`/`movmem`/`min`/`max`
|
||||
макросы, `inp`/`outp`, `enable`/`disable`, `ms_*` mouse aliases.
|
||||
Программы из Solid-C 2004 портируются с минимальными правками.
|
||||
|
||||
### `--debug` runtime flag
|
||||
|
||||
```sh
|
||||
sprinter-cc --debug -o foo.exe foo.c
|
||||
```
|
||||
Prepend'ит `DEBUG_RT = 1` в crt0 + передаёт `-DDEBUG_RT` в SDCC. Открывает symbol `_w2_self_allocated` (uint8_t) — runtime diagnostic кто аллоцировал W2 (0 = DSS, 1 = crt0 сам). Полезно для troubleshooting'а в small mode.
|
||||
|
||||
### MAME testing workflow
|
||||
|
||||
```sh
|
||||
make floppy # пакует все .exe + data в mame/v306/IMG/mc.img
|
||||
cd mame/v306 && ./run_mame.sh
|
||||
```
|
||||
Имена файлов на флопе должны быть 8.3 (FAT12). Все примеры названы соответственно — `banked_big → bankedbg`, `seek_demo → seek`, `time_dir_test → timedir`, etc.
|
||||
|
||||
---
|
||||
|
||||
## История изменений
|
||||
|
||||
- 2026-06-01 — первый релиз v1.0
|
||||
@@ -0,0 +1,82 @@
|
||||
# `sprinter-cc` — драйвер компилятора
|
||||
|
||||
Однострочный вход во всю цепочку инструментов. Принимает `.c` файлы и
|
||||
опции, выдаёт SprintEXE.
|
||||
|
||||
## Синопсис
|
||||
|
||||
```
|
||||
sprinter-cc -o OUT.exe SRC.c [more.c ...] [options]
|
||||
```
|
||||
|
||||
## Опции
|
||||
|
||||
### Раскладка памяти
|
||||
|
||||
| Флаг | Описание |
|
||||
|---|---|
|
||||
| `--memory MODE` | `tiny` (по умолчанию), `small`, `big`, `huge`, `manual`. См. `memory_modes.md`. |
|
||||
| `--memory-manual SPEC` | Для `--memory manual`: список `KEY=VAL` через запятую, например `CODE=W2,DATA=W2,BANKED=W3`. |
|
||||
| `--stack-size N` | Сколько байт зарезервировать под стек. По умолчанию ≈1278. Большее значение уменьшает heap. |
|
||||
|
||||
### Организация кода
|
||||
|
||||
| Флаг | Описание |
|
||||
|---|---|
|
||||
| `--bank N=FILE.c` | Компилировать FILE.c в банк N (1..15). Повторяемый. Функции в banked-файлах нужны с квалификатором `__banked`. |
|
||||
| `--crt0=TYPE` | Переопределение startup-файла: `default` / `minimal` / `banked` / `small`. Обычно выбирается автоматически по memory mode. |
|
||||
|
||||
### Диагностика
|
||||
|
||||
| Флаг | Описание |
|
||||
|---|---|
|
||||
| `--debug` | Подмешивает `DEBUG_RT = 1` в crt0 + передаёт `-DDEBUG_RT` в SDCC. Открывает runtime-symbols типа `_w2_self_allocated`. |
|
||||
| `-v` | Verbose — печать каждой подкоманды. |
|
||||
| `-h` / `--help` | Встроенная справка. |
|
||||
|
||||
### Pass-through
|
||||
|
||||
| Флаг | Описание |
|
||||
|---|---|
|
||||
| `-I PATH` | Дополнительный include-путь. |
|
||||
| `-Wl FLAG` | Передать FLAG линкеру. |
|
||||
| `--mkexe FLAG` | Передать FLAG в mkexe (например `--mkexe -p --mkexe 0` для zero-padded банков). |
|
||||
| `-L 0xADDR` | Переопределить load-адрес. |
|
||||
| `-E 0xADDR` | Переопределить entry-адрес. |
|
||||
| `-S 0xADDR` | Переопределить стартовый стек. По умолчанию `0xBFFE`. |
|
||||
|
||||
## Примеры
|
||||
|
||||
Минимальная сборка:
|
||||
```sh
|
||||
sprinter-cc -o hello.exe hello.c
|
||||
```
|
||||
|
||||
Программа побольше (не помещается в 14 КБ):
|
||||
```sh
|
||||
sprinter-cc --memory small -o big.exe big.c
|
||||
```
|
||||
|
||||
Многобанковая игра:
|
||||
```sh
|
||||
sprinter-cc --memory huge -o game.exe \
|
||||
main.c --bank 1=engine.c --bank 2=ai.c --bank 3=audio.c
|
||||
```
|
||||
|
||||
Свой размер стека:
|
||||
```sh
|
||||
sprinter-cc --stack-size 4096 -o app.exe app.c
|
||||
```
|
||||
|
||||
## Что происходит внутри
|
||||
|
||||
1. Выбирает crt0 на основе `--memory` (и наличия `--bank`).
|
||||
2. Ассемблирует crt0 (с опциональным `DEBUG_RT` / `BANK_W1`).
|
||||
3. Ассемблирует `heap_top.s` (custom значение если `--stack-size`).
|
||||
4. Каждый `.c` → `.rel` через SDCC.
|
||||
5. Bank-исходники компилируются с `--codeseg/--constseg/--dataseg BANK_n`.
|
||||
6. Компилирует trampoline `runtime/bank.s` (если есть банки).
|
||||
7. Линкует всё в `.ihx`, запускает `check_banks.py` для проверки 16 КБ лимита.
|
||||
8. Вызывает `toolchain/mkexe/mkexe` для упаковки `.ihx` в SprintEXE.
|
||||
|
||||
Промежуточные файлы лежат в `.sprinter-cc-<basename>/` рядом с выходным.
|
||||
Reference in New Issue
Block a user