From 95c22be9bd31aee21b701ef372a330fee55fd781 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Thu, 23 Jul 2026 19:38:00 +0300 Subject: [PATCH] =?UTF-8?q?libbgi:=20=D1=81=D0=BA=D1=80=D0=BE=D0=BB=D0=BB-?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=BC=D0=B8=D1=82=D0=B8=D0=B2=D1=8B=20+=20--?= =?UTF-8?q?w3=20+=20=D0=BE=D1=82=D1=87=D1=91=D1=82=20=D1=80=D0=B0=D1=81?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D0=B4=D0=BA=D0=B8=20=D0=BF=D0=B0=D0=BC=D1=8F?= =?UTF-8?q?=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Скролл региона video->video (неактивная страница -> активная, банк 0x50: копия = скролл + heal цели): - gfx_scroll_h / _bgi_scroll_rows_raw — горизонтальный, построчно без страйдов (~54Т/строку), DI/EI бандами по 16 строк, h=0=>256; - gfx_scroll_v / _bgi_scroll_cols_raw — верт. И/ИЛИ гориз. за один проход без буфера (колонка = accel-burst LD A,A, STOP между read/write делает промежуточный OUT Port_Y безопасным), банды по 16 колонок; - _gfx_addr_shadow_base (адрес неактивной страницы) + gfx_rect_t. Пример examples/scroll. check_banks.py + sprinter-cc: отчёт раскладки памяти для ЛЮБОЙ модели (W1/W2 код/данные, остаток кучи/стека, W3 при --w3, банки при --bank), не только при --bank. Док docs/memory-management.md §10. --w3 (резидентный код окна W3) + сопутствующее: crt0_banked W3_RESIDENT, mkexe -W, tests/w3probe. Co-Authored-By: Claude Opus 4.8 --- bin/sprinter-cc | 81 +- docs/libc-reference.md | 8 + docs/memory management | 67 -- docs/memory-management.md | 308 ++++++ docs/samples/balls/balls.ACT | Bin 0 -> 512 bytes docs/samples/balls/balls.asm | 721 +++++++++++++ docs/samples/balls/balls.bmp | Bin 0 -> 1172 bytes docs/samples/balls/balls.exe | Bin 0 -> 2111 bytes docs/samples/balls/balls.spr | 58 + docs/samples/balls/balls.txt | 38 + docs/samples/balls/balls128.exe | Bin 0 -> 2111 bytes docs/samples/balls/balls225.exe | Bin 0 -> 2111 bytes docs/samples/balls/balls64.exe | Bin 0 -> 2111 bytes docs/samples/balls/bgspr.act | Bin 0 -> 512 bytes docs/samples/balls/bgspr.bmp | Bin 0 -> 82168 bytes docs/samples/balls/bgspr.spr | 1501 ++++++++++++++++++++++++++ docs/samples/balls/dss.inc | 68 ++ docs/samples/balls/head.inc | 20 + docs/samples/balls/make.bat | 20 + docs/samples/balls/math.inc | 89 ++ docs/samples/balls/misc.asm | 113 ++ docs/samples/balls/sjasm.exe | Bin 0 -> 258048 bytes examples/scroll/Makefile | 4 + examples/scroll/run.sh | 4 + examples/scroll/scroll-impl-guide.md | 223 ++++ examples/scroll/scroll.c | 87 ++ libbgi/_bgi.h | 25 + libbgi/bgi256/_bgi_scroll_cols_raw.c | 102 ++ libbgi/bgi256/_bgi_scroll_rows_raw.c | 129 +++ libbgi/common/_gfx_state.c | 1 + libbgi/common/gfx_scroll_h.c | 64 ++ libbgi/common/gfx_scroll_v.c | 63 ++ libbgi/common/gfx_set_draw_page.c | 8 +- libbgi/include/sprite.h | 12 + runtime/crt0_banked.s | 23 + tests/w3probe/Makefile | 42 + tests/w3probe/bigbank.c | 12 + tests/w3probe/hugebank.c | 13 + tests/w3probe/w3big.c | 26 + tests/w3probe/w3huge.c | 33 + tests/w3probe/w3huge_res.c | 26 + tests/w3probe/w3probe.c | 27 + tests/w3probe/w3res.c | 25 + tests/w3probe/w3tiny.c | 30 + toolchain/check_banks.py | 169 ++- toolchain/mkexe/mkexe.c | 18 +- 46 files changed, 4125 insertions(+), 133 deletions(-) delete mode 100644 docs/memory management create mode 100644 docs/memory-management.md create mode 100644 docs/samples/balls/balls.ACT create mode 100644 docs/samples/balls/balls.asm create mode 100644 docs/samples/balls/balls.bmp create mode 100644 docs/samples/balls/balls.exe create mode 100644 docs/samples/balls/balls.spr create mode 100644 docs/samples/balls/balls.txt create mode 100644 docs/samples/balls/balls128.exe create mode 100644 docs/samples/balls/balls225.exe create mode 100644 docs/samples/balls/balls64.exe create mode 100644 docs/samples/balls/bgspr.act create mode 100644 docs/samples/balls/bgspr.bmp create mode 100644 docs/samples/balls/bgspr.spr create mode 100644 docs/samples/balls/dss.inc create mode 100644 docs/samples/balls/head.inc create mode 100644 docs/samples/balls/make.bat create mode 100644 docs/samples/balls/math.inc create mode 100644 docs/samples/balls/misc.asm create mode 100644 docs/samples/balls/sjasm.exe create mode 100644 examples/scroll/Makefile create mode 100755 examples/scroll/run.sh create mode 100644 examples/scroll/scroll-impl-guide.md create mode 100644 examples/scroll/scroll.c create mode 100644 libbgi/bgi256/_bgi_scroll_cols_raw.c create mode 100644 libbgi/bgi256/_bgi_scroll_rows_raw.c create mode 100644 libbgi/common/gfx_scroll_h.c create mode 100644 libbgi/common/gfx_scroll_v.c create mode 100644 tests/w3probe/Makefile create mode 100644 tests/w3probe/bigbank.c create mode 100644 tests/w3probe/hugebank.c create mode 100644 tests/w3probe/w3big.c create mode 100644 tests/w3probe/w3huge.c create mode 100644 tests/w3probe/w3huge_res.c create mode 100644 tests/w3probe/w3probe.c create mode 100644 tests/w3probe/w3res.c create mode 100644 tests/w3probe/w3tiny.c diff --git a/bin/sprinter-cc b/bin/sprinter-cc index 21c1eac..45e1e22 100755 --- a/bin/sprinter-cc +++ b/bin/sprinter-cc @@ -15,8 +15,8 @@ # 0xA2 to auto-detect whether DSS already mapped W2 # (program > 16 KB) or whether we need to allocate # it ourselves (program < 16 KB). Covers 0..30 KB. -# big: tiny + banked code in W1 [TODO] -# huge: small + banked code in W3 [TODO] +# big: tiny + banked code in W1 +# huge: small + banked code in W3 # manual: explicit via --memory-manual / --code-loc / --data-loc # --memory-manual SPEC placement spec, only with --memory manual. # SPEC = comma-separated KEY=VAL list: @@ -33,6 +33,18 @@ # -Wl FLAG extra linker flag (repeatable) # --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked # automatically and adds -Wl-b_BANKN=0x{N}C000 +# --w3 FILE.c place FILE.c resident in window 3 (0xC000), called +# DIRECTLY (no trampoline). Repeatable. Defaults to +# --memory small; works in tiny|small|big|huge. DSS maps +# all image pages incl. W3. Compiled with --codeseg/ +# --constseg W3CODE (writable statics stay in W2). In huge +# the resident page SHARES W3 with trampoline banks: crt0 +# captures/restores it (W3_RESIDENT), mkexe gets -W. +# Rules: W3 code must NOT switch the W3 page; W1/W2 code that +# swaps W3 to another page must restore it; no writable data +# belongs in W3 (code + rodata only). From a __banked +# context the resident W3 code is unreachable (but resident +# -> __banked via the W1 trampoline is fine). # --mkexe FLAG extra mkexe flag (repeatable; e.g. --mkexe -p --mkexe 0) # --max-allocs N SDCC --max-allocs-per-node (default: 100000 — smaller/ # faster code at the cost of compile time; pass a lower @@ -88,6 +100,11 @@ SOURCES=() LD_EXTRA=() MKEXE_EXTRA=() BANK_SPECS=() # entries like "1=engine.c" +W3_SPECS=() # entries like "mod.c" — резидентные модули окна W3 (--w3) +W3_RELS=() # заполняется при компиляции W3-модулей +W3_LD_FLAGS=() # -Wl-b_W3CODE=0xC000, если есть --w3 +USER_SET_MEMORY="" # непусто, если --memory задан явно (для --w3 авто-small) +W3_RESIDENT_HUGE=0 # 1, если --w3 в режиме huge (резидент делит W3 с банками) MAX_ALLOCS="100000" # sdcc --max-allocs-per-node; дефолт 100000 — агрессивная # регистровая аллокация (медленнее компиляция, меньше/ # быстрее код; как fast-сборки библиотек). --max-allocs N @@ -112,8 +129,9 @@ while [[ $# -gt 0 ]]; do -S) STACK_ADDR="$2"; shift 2;; --code-loc) USER_CODE_LOC="$2"; shift 2;; --data-loc) USER_DATA_LOC="$2"; shift 2;; - --memory) MEMORY_MODE="$2"; shift 2;; + --memory) MEMORY_MODE="$2"; USER_SET_MEMORY=1; shift 2;; --memory-manual) MEMORY_MANUAL="$2"; shift 2;; + --w3) W3_SPECS+=("$2"); shift 2;; --stack-size) STACK_SIZE="$2"; shift 2;; -Wl) LD_EXTRA+=("$2"); shift 2;; --bank) BANK_SPECS+=("$2"); shift 2;; @@ -132,6 +150,24 @@ done [[ -z "$OUT" ]] && { echo "sprinter-cc: -o NAME is required" >&2; exit 1; } [[ ${#SOURCES[@]} -eq 0 ]] && { echo "sprinter-cc: no input files" >&2; exit 1; } +# ------- --w3: резидентный код окна W3 (подход A) ---------------------------- +# W3-модули едут в область W3CODE @0xC000 и вызываются напрямую (без +# трамплинов). Работает поверх small-лейаута: main-код с 0x4100 (W1/W2), +# W3 свободно, DSS маппит все страницы образа сам (проверено tests/w3probe). +# --w3 подразумевает small; с huge несовместим (там W3 занят банками). +if [[ ${#W3_SPECS[@]} -gt 0 ]]; then + [[ -z "$USER_SET_MEMORY" ]] && MEMORY_MODE="small" # --w3 → small по умолчанию + case "$MEMORY_MODE" in + tiny|small|big|huge) : ;; # поддержаны (huge — резидент делит W3 с банками) + *) echo "sprinter-cc: --w3 поддержан для tiny|small|big|huge (дано: $MEMORY_MODE)" >&2; exit 1;; + esac + # huge: резидентный W3-код (0xC000, HOME) сосуществует с трамплин-банками + # W3 — crt0_banked захватывает резидентную страницу и возвращает её + # дефолтом после загрузки банков (см. crt0_banked.s, W3_RESIDENT). + W3_RESIDENT_HUGE=0 + [[ "$MEMORY_MODE" == "huge" ]] && W3_RESIDENT_HUGE=1 +fi + # ------- BGI graphics driver selection (--gfx) ------------------------------- # graphics.h — mode-agnostic слой; конкретный видеорежим задаёт driver- # архив. Одновременно только один. drv16 пока не реализован. @@ -186,7 +222,8 @@ case "$MEMORY_MODE" in huge) # small layout + banked code in W3. crt0_banked auto-detects W2 # the same way crt0_small does, then loads banks from the .EXE. - MODE_CODE_LOC="0x4100"; MODE_DATA_LOC="0x8000" + # MODE_CODE_LOC="0x4100"; MODE_DATA_LOC="0x8000" + MODE_CODE_LOC="0x4100"; MODE_DATA_LOC="0" ;; manual) # Defaults if SPEC omits a key. @@ -280,6 +317,7 @@ asm_runtime() { local prefix="" [[ $DEBUG_RT -eq 1 ]] && prefix+="DEBUG_RT = 1"$'\n' [[ $BANK_W1 -eq 1 ]] && prefix+="BANK_W1 = 1"$'\n' + [[ $W3_RESIDENT_HUGE -eq 1 ]] && prefix+="W3_RESIDENT = 1"$'\n' if [[ -n "$prefix" ]]; then local patched="$WORK/$(basename "$src" .s)_patched.s" { printf "%s" "$prefix"; cat "$src"; } > "$patched" @@ -305,9 +343,14 @@ if [[ -n "$STACK_SIZE" ]]; then printf " .module sprinter_heap_top\n ___sdcc_heap_end = 0x%04X\n .globl ___sdcc_heap_end\n" \ "$_heap_top" > "$WORK/heap_top.s" HEAP_TOP_SRC="$WORK/heap_top.s" - [[ $VERBOSE -eq 1 ]] && echo " heap_top: custom HEAP_TOP=$(printf '0x%04X' $_heap_top) (stack reserve $_stack_sz bytes)" + HEAP_TOP_VAL=$(printf '0x%04X' "$_heap_top") + [[ $VERBOSE -eq 1 ]] && echo " heap_top: custom HEAP_TOP=$HEAP_TOP_VAL (stack reserve $_stack_sz bytes)" else HEAP_TOP_SRC="$RUNTIME/heap_top.s" + # Дефолт из runtime/heap_top.s (для отчёта раскладки) — обычно 0xBB00. + HEAP_TOP_VAL=$(grep -oiE '___sdcc_heap_end[[:space:]]*=[[:space:]]*0x[0-9A-Fa-f]+' "$RUNTIME/heap_top.s" \ + | grep -oiE '0x[0-9A-Fa-f]+' | head -1) + HEAP_TOP_VAL="${HEAP_TOP_VAL:-0xBB00}" fi run "$SDASZ80" -o "$HEAP_TOP_REL" "$HEAP_TOP_SRC" @@ -322,6 +365,20 @@ for src in "${SOURCES[@]}"; do USER_RELS+=("$rel") done +# 2b. --w3 resident modules → .rel. --codeseg/--constseg W3CODE кладёт код +# и rodata в W3 (0xC000); --dataseg НЕ трогаем — писучие статики +# остаются в обычном _DATA (W2). Прямые вызовы, без трамплинов. +if [[ ${#W3_SPECS[@]} -gt 0 ]]; then + for src in "${W3_SPECS[@]}"; do + rel="$WORK/w3_$(basename "$src" .c).rel" + run "$SDCC" "${CC_FLAGS[@]}" --codeseg W3CODE --constseg W3CODE \ + -c -o "$rel" "$src" + W3_RELS+=("$rel") + done + W3_LD_FLAGS+=("-Wl-b_W3CODE=0xC000") + [[ $VERBOSE -eq 1 ]] && echo " w3: ${#W3_SPECS[@]} module(s) resident @0xC000 (direct call)" +fi + # 3. bank infrastructure — required whenever crt0_banked is in play. # Always assemble bank.s for _bank_pages + the bcall/bjump trampolines. # If the user did not pass any --bank, generate a tiny stub providing @@ -369,6 +426,7 @@ IHX="$WORK/$(basename "$OUT" .exe).ihx" LINK_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size --code-loc "$CODE_LOC" --data-loc "$DATA_LOC") LINK_FLAGS+=("${BANK_LD_FLAGS[@]}") +LINK_FLAGS+=("${W3_LD_FLAGS[@]}") for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done # libsprinter.lib via -l/-L (sdcc passes -lsprinter through to sdldz80). # @@ -379,12 +437,12 @@ for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done # the warning is just noise. In verbose mode show everything. if [[ $VERBOSE -eq 1 ]]; then run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \ - "$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \ + "$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" "${W3_RELS[@]}" \ "-L$LIB_DIR" "${GFX_LD[@]}" "$LIBC_LD" else # Drop the warning line + its two follow-up "Library:" lines. run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \ - "$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \ + "$CRT0_REL" "$HEAP_TOP_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" "${W3_RELS[@]}" \ "-L$LIB_DIR" "${GFX_LD[@]}" "$LIBC_LD" 2>&1 \ | awk ' /^\?ASlink-Warning-Definition of public symbol/ { skip = 3 } @@ -393,9 +451,11 @@ else ' fi -# Quick bank-size check (only meaningful if there are banks). -if [[ ${#BANK_SPECS[@]} -gt 0 ]] && [[ -f "${IHX%.ihx}.map" ]]; then - python3 "$CHECK_BANKS" "${IHX%.ihx}.map" || true +# Отчёт по раскладке памяти + проверка лимитов — для ЛЮБОЙ модели памяти +# (сколько свободно в W1/W2, W3 при --w3, и в каждом банке при --bank). +if [[ -f "${IHX%.ihx}.map" ]]; then + python3 "$CHECK_BANKS" "${IHX%.ihx}.map" \ + --mode "$MEMORY_MODE" --heap-top "$HEAP_TOP_VAL" --stack "$STACK_ADDR" || true fi # 5. mkexe → .exe. In BIG mode tell mkexe banks live at 0x4000 (W1). @@ -404,6 +464,7 @@ fi MK_PREFIX=() [[ $VERBOSE -eq 1 ]] && MK_PREFIX+=(-v) [[ $BANK_W1 -eq 1 ]] && MK_PREFIX+=(-B 0x4000) +[[ $W3_RESIDENT_HUGE -eq 1 ]] && MK_PREFIX+=(-W) MK_PREFIX+=("${MKEXE_EXTRA[@]}") MK_PREFIX+=(-L "$LOAD_ADDR" -E "$ENTRY_ADDR" -S "$STACK_ADDR" -o "$OUT") run "$MKEXE" "${MK_PREFIX[@]}" "$IHX" diff --git a/docs/libc-reference.md b/docs/libc-reference.md index 261203a..18bd895 100644 --- a/docs/libc-reference.md +++ b/docs/libc-reference.md @@ -259,6 +259,14 @@ putsprite новой (save-буфер не нужен). Низкий урове рисовать банком 0x50, спрайты/оверлеи — putsprite/0x5C; буферы образов — вне W3 (< 0xC000). Тесты: tests/sprites, tests/gfxbanks, tests/bgi_img. +Скролл региона из НЕактивной страницы в активную (): +`gfx_scroll_h(area, dx, dirty)` — горизонтальный (dx>0 = вправо; быстрый +построчный accel-скролл без страйдов, DI-банды по 16 строк), +`gfx_scroll_v(area, dy, dirty)` — вертикальный (dy>0 = вниз; через +строку-буфер на стеке, ПОКА не оптимален — Port_Y один на burst, разный +read/write Y невозможен без ломающего accel OUT). Копия = скролл + heal +цели (банк 0x50). Открывшуюся полосу |d| не заполняют — возвращают в +*dirty (NULL = не нужно). Пример: examples/scroll. Стиль линий (Ф2d): `setlinestyle(style,upattern,thick)`/`getlinesettings` (SOLID/DOTTED/CENTER/DASHED/USERBIT + NORM/THICK) — на line/rectangle/ drawpoly. diff --git a/docs/memory management b/docs/memory management deleted file mode 100644 index 5379221..0000000 --- a/docs/memory management +++ /dev/null @@ -1,67 +0,0 @@ - -1) если я и DATA и CODE размещаю в одном окне (W1 - #4000 или W2 - #8000, неважно), -то при вызове set_videomode глобальные переменные (errno, g_text_attr) не меняют -своих значений. -если же DATA и CODE находятся в разных окнах (не важно где DATA - в W1 или W2, главное -что не в том где CODE) - то при вызове se_videomode значения глобальных переменных меняются - -То есть похоже что для DATA не назначается отдельный блок памяти а назначается только для CODE -Это полностью соответствует документации - если приложение менее 16К (как у нас) то ему выделяется -только одна страница. И получается что работа со второй страницей идет несанкционированно (ей память -не выделена). - -Потому предлагается -1) сейчас размещать ВСЕ в одной странице (и DATA и CODE и стек) - в W2. - -2) дальше - добавить в нашу обертку sprinter-cc режимы памяти - --tiny - все приложение помещается в одну страницу - в W2 (и DATA и CODE и стек) --small - приложение помещается в две страницы - CODE в W1, DATA и стек - в W2. -в этом режиме над отдельно выделять и маппить страницу в W2 для DATA и стека --big - DATA, CODE и стек помещаются в одну страницу W2 как в -tiny, добавляется поддержка banked в W1, -страница W3 остается служебной и для работы с граффикой из banked code --huge - приложение помещается в двух страницах как и -small но так же добавляется поддержка banked но -уже в страницу W3 - -Из документации - - - > Теперь адресса #4000..#7FFF,#8000..#BFFF,#C000..#FFFF, когда ДСС - передаёт управление эти прогораммам, какие банки там нахадятся по - умолчанию? - - В зависимости от адреса загрузки и размера приложения DSS выделяет - необходимое число страниц памяти. Так при размере меньше 16К будет - выделена - одна страница, при размере больше 16К - две, и т.д. В окна с - "неиспользуемым" адресном пространством будет подключатся - специальная страница #FF.Если приложению требуется памяти больше чем - зарезервировано в exe-файле, оно должно выделить себе дополнительный - блок памяти самостоятельно. - - - > В конфигурации спринтер - > по #0000..#3FFF, при работе ДСС находится сама ДСС с её Резетами, - > чтоб использывать когда сюда подставленна страница пользователся - > резеты не доступны!. - - Это так в нижних 16K находится DSS / BIOS в остальных 48К - приложение, но с - определенными особенностями. Стек не должен быть выше #BFFF при - вызове DSS и ниже #8000 при вызове некоторых функций BIOS. - - -Так же посмотри вот сюда - возможно нам придется для режимов -small и -huge делать свой первичным загрузчиком - -Из документации - - - Выполнение EXE-файла осуществляется по следующим пунктам: - 1) Открывает exe-файл на чтение; - 2) Считывает в рабочую область префикс exe-файла; - 3) Выделяет блок памяти, требуемый для загрузки всего файла или первичного - загрузчика, если его размер не равен нулю; - 4) Сохраняет стек; - 5) Подключает страницы из выделенного блока; - 6) Строит префикс запуска программы и устанавливает на него регистр IX; - 7) Считывает файл по адресу указанному в смещении 16 (Адрес расположения кода в - памяти); - 8) Закрывает exe-файл, если это не первичный загрузчик; - 9) Устанавливает стек равным значению из смещения 20 (Адрес расположения стека); - 10) Передает управление по адресу указанному в смещении 18 (Адрес запуска); diff --git a/docs/memory-management.md b/docs/memory-management.md new file mode 100644 index 0000000..07ace4f --- /dev/null +++ b/docs/memory-management.md @@ -0,0 +1,308 @@ +# Управление памятью в sprinter-cc + +Документ описывает модель памяти Sprinter (Sp2000), режимы памяти обёртки +`sprinter-cc` и способы размещения кода/данных: single-page, split, банки +(трамплины) и резидентный код окна W3 (`--w3`). + +Всё в этом документе подтверждено сборкой и прогоном в MAME v3.06 / DSS 1.71.57 +(см. `tests/w3probe`, `tests/banktest`, `tests/banklocl`). + +--- + +## 1. Аппаратная модель памяти + +Z80 видит 64 КБ, разбитые на **четыре окна по 16 КБ**. Каждое окно независимо +маппится на физическую страницу через порт-регистр страницы: + +| Окно | Адреса | Порт страницы | Назначение по умолчанию | +|------|---------------|---------------|--------------------------| +| W0 | `0x0000-0x3FFF` | `0x82` | **DSS / BIOS** (RST-ы, системные вызовы) | +| W1 | `0x4000-0x7FFF` | `0xA2` | приложение | +| W2 | `0x8000-0xBFFF` | `0xC2` | приложение | +| W3 | `0xC000-0xFFFF` | `0xE2` | приложение / графика / банки | + +- Запись в порт `0x{8/A/C/E}2` меняет физ-страницу окна; чтение возвращает + текущую страницу. +- **W0 занят DSS/BIOS** — там живут RST-обработчики (`RST #08` BIOS, `RST #10` + ESTEX). Пока в W0 стоит системная страница, вызовы доступны; подменять W0 + нельзя без потери RST-ов. +- «Неиспользуемое» окно маппится на **специальную страницу `#FF`**: чтение даёт + `0xFF`, запись игнорируется. Это ключевая причина «молчаливой» порчи данных — + см. §7. + +### Порты в C + +`` даёт SFR-обёртки: `_io_page_w0..w3` (чтение/запись порта), +`sprinter_page_w0..w3(page)`. + +--- + +## 2. Как DSS загружает EXE + +Из документации DSS, последовательность `EXEC`: + +1. Открыть exe-файл на чтение. +2. Считать префикс exe в рабочую область. +3. **Выделить блок памяти** размером под весь файл (если `loader==0`) или под + первичный загрузчик (`loader>0`). +4. Сохранить стек. +5. **Подключить страницы** выделенного блока в окна (последовательно от окна + адреса загрузки: W1→W2→W3…). +6. Построить префикс запуска → регистр `IX`. +7. Считать файл по адресу загрузки (смещение 16 в заголовке). +8. Закрыть exe, **если это не первичный загрузчик** (`loader==0`). +9. Установить `SP` = значение из смещения 20 (адрес стека). +10. Передать управление по адресу из смещения 18 (entry). + +**Следствия, на которых стоит вся схема памяти:** + +- DSS выделяет **число страниц по размеру образа**: `<16 КБ` → 1 страница, + `16..32 КБ` → 2, `32..48 КБ` → 3. Лишние окна = страница `#FF`. +- Страницы маппятся **подряд** начиная с окна адреса загрузки. Образ, тянущийся + `0x4100..0xFFFF` (3 страницы), даёт W1+W2+W3 замапленными автоматически — + **это и есть база для резидентного кода W3** (§6, подход A). +- `loader>0` (multi-bank .exe) → DSS грузит только HOME-часть и **оставляет + файл открытым** (handle в `IX-3`), а crt0 дочитывает банки сам (§5). + +Упаковкой в этот формат занимается `toolchain/mkexe`. + +--- + +## 3. Правила стека (критично) + +- **`SP ≤ 0xBFFF`** при вызовах DSS (ESTEX, `RST #10`). +- **`SP ≥ 0x8000`** при вызовах некоторых функций BIOS (`RST #08`). +- Пересечение этих требований → **стек обязан жить в W2** (`0x8000-0xBFFF`). + По умолчанию `SP` инициализируется в `0xBFFE`. + +Проверено (`tests/w3probe`): во всех режимах на входе `main` `SP ≈ 0xBFFC`, т.е. +в W2. + +**Chicken-and-egg для split-режимов** (small/huge): DSS ставит `SP=0xBFFE` из +заголовка, но для программ `<16 КБ` окно W2 ещё не выделено (там `#FF`). Пуши +туда теряются, первый `call` возвращается в мусор. Поэтому `crt0_small`/ +`crt0_banked` сначала работают на **загрузочном стеке в W1** (реальное ОЗУ), +маппят W2 и только потом переставляют `SP=0xBFFE`. Маппинг W2 делается через +**ESTEX `$3A SETWIN2`** (не BIOS `$C4`+OUT — тому нужен стек уже в W2). + +--- + +## 4. Режимы памяти + +Выбираются флагом `sprinter-cc --memory MODE` (по умолчанию `tiny`). Режим задаёт +адрес кода, размещение данных, crt0 и наличие банков. + +| Режим | CODE | DATA/BSS | Стек | Банки | crt0 | Первичный загрузчик | +|--------|------|----------|------|-------|------|----------------------| +| `tiny` | W2 `0x8100` | за кодом (W2) | W2 | — | `crt0.s` | нет (1 страница) | +| `small` | W1 `0x4100` | за кодом (W1→W2) | W2 | — | `crt0_small.s` | да (сам маппит W2) | +| `big` | W2 `0x8100` | за кодом (W2) | W2 | W1 (трамплины) | `crt0_banked.s` (BANK_W1) | да | +| `huge` | W1 `0x4100` | за кодом (W1→W2) | W2 | W3 (трамплины) | `crt0_banked.s` | да | +| `manual` | явно | явно | W2 | — | `crt0.s` | зависит | + +Во всех режимах **DATA цепляется линкером сразу за кодом** (`--data-loc 0`), а не +кладётся по фиксированному адресу. Раньше `huge` использовал фиксированный +`DATA=0x8000`, что ломалось при коде `>16 КБ` (код перетекал в W2 и накрывал +DATA); сейчас DATA динамически идёт за концом кода. + +### 4.1 `tiny` — всё в одной странице + +``` +0x8000..0x80FF зарезервировано (startup-prefix) +0x8100 _start / _CODE … _DATA … _BSS … _HEAP +0xBB00 heap top (по умолчанию) +0xBFFE стек ↓ +``` + +- Один блок 16 КБ, DSS маппит его в W2. W1 и W3 = `#FF`. +- Ничего выделять/маппить не надо; `crt0.s` предполагает, что W2 уже дан DSS. +- Практический потолок кода+данных+кучи+стека ≈ 14 КБ. +- `--code-loc 0x8100 --data-loc 0`; mkexe `-L 0x8100 -E 0x8100 -S 0xBFFE`. + +### 4.2 `small` — CODE в W1, данные перетекают в W2 + +``` +0x4100 _CODE … (W1) + … за кодом → _DATA _BSS _HEAP (W1, перетекает в W2) +0xBB00 heap top +0xBFFE стек ↓ (W2) +``` + +- Покрывает ~0..30 КБ (код+данные вместе). +- `crt0_small.s` авто-определяет W2: читает порт `0xC2`. Если `≠0xFF` — DSS уже + дал W2 (образ `>16 КБ`), маппить не надо. Если `=0xFF` — сам выделяет страницу + (`ESTEX $3D GETMEM`) и маппит (`ESTEX $3A SETWIN2`). +- `--code-loc 0x4100 --data-loc 0`. + +### 4.3 `big` — tiny + банки в W1 + +- База как `tiny` (CODE+DATA+стек в W2, `0x8100`). +- **Свапаемые банки в W1** (`0x4000-0x7FFF`, порт `0xA2`), вызываются через + трамплины (§5). `crt0_banked.s` собирается с `BANK_W1=1`. +- mkexe получает `-B 0x4000` (банки живут в W1). Виртуальный адрес банка N = + `0x{N}4000`. +- W3 свободно — доступно под графику или резидентный код (`--w3`). + +### 4.4 `huge` — small + банки в W3 + +- База как `small` (CODE `0x4100`, DATA за кодом, авто-детект W2). +- **Свапаемые банки в W3** (`0xC000-0xFFFF`, порт `0xE2`), через трамплины. + Виртуальный адрес банка N = `0x{N}C000`. +- `crt0_banked.s` (без `BANK_W1`) грузит банки из .exe после старта. + +### 4.5 `manual` — явное размещение + +`--memory manual --memory-manual SPEC`, где SPEC = список `KEY=VAL`: +`CODE=W1|W2`, `DATA=W1|W2|SAME`, `BANKED=W1|W3`. Плюс прямые `--code-loc` / +`--data-loc` / `-L` / `-E` / `-S` перекрывают дефолты любого режима. + +--- + +## 5. Банки и трамплины (`--bank`) + +Для `big`/`huge`. Модуль-банк собирается в отдельную область и линкуется по +**виртуальному 24-битному адресу** (`bank_id` в старшем байте): + +``` +sprinter-cc --memory huge --bank 1=engine.c --bank 2=audio.c -o app.exe main.c +``` + +- Банк N компилируется `--codeseg/--constseg/--dataseg BANKN`, линкуется + `-Wl-b_BANKN=0x{N}C000` (huge) или `0x{N}4000` (big). +- `main.c` обязан объявить `const uint8_t n_banks = N;` — `crt0_banked` читает + это **до gsinit**, поэтому только `const` (инициализатор ещё не скопирован). +- Функции банка помечаются `__banked` — SDCC генерирует вызов через трамплин + `___sdcc_bcall_ehl` (в `_CODE`/W1, всегда замаплен): он сохраняет текущую + страницу окна, маппит нужный банк (`_bank_pages[id]`), `jp` в функцию, по + возврату восстанавливает страницу. +- Загрузка: mkexe пакует `header + HOME + bank1(16К) + bank2(16К)…`, + `loader=размер HOME`; `crt0_banked` выделяет страницы (`GETMEM`), маппит и + дочитывает каждый банк `ESTEX READ` из открытого файла. +- Проверка размеров банков — `toolchain/check_banks.py` (часть отчёта + раскладки, см. §10). + +Writable bank-local данные возможны, но с оговорками — см. +`memory/bank_local_data_pattern`. + +--- + +## 6. Резидентный код окна W3 (`--w3`) + +**Альтернатива банкам без трамплинов.** Модуль размещается резидентно в W3 +(`0xC000`) и вызывается **прямым `call`** — как обычная функция. Работает во +**всех режимах** (`tiny|small|big|huge`); по умолчанию подразумевает `small`. + +``` +sprinter-cc --w3 render.c -o app.exe main.c # → small +sprinter-cc --memory huge --w3 render.c --bank 1=lvl.c -o app.exe main.c +``` + +**Как работает (подход A):** образ с областью `W3CODE`@`0xC000` тянется до `0xC0xx` +(≥3 страницы), и DSS сам маппит W3 при загрузке (§2) — **загрузчик в crt0 не +нужен** (кроме huge). Прямые вызовы резолвятся линкером в реальные `0xC0xx`. + +**Механизм сборки:** +- W3-модуль компилируется `--codeseg W3CODE --constseg W3CODE` — код и rodata в + W3. **`--dataseg` НЕ переопределяется**: писучие статики уходят в обычный + `_DATA` (W2). Отсюда правило «в W3 только код + rodata». +- Линк `-Wl-b_W3CODE=0xC000`. + +**Раскладка страниц по режимам (проверено MAME):** + +| Режим | W1 | W2 | W3 | +|-------|----|----|----| +| tiny | `#FF` (не исп.) | код+данные | **резидент** | +| small | код | данные | **резидент** | +| big | банк (трамплин) | код+данные | **резидент** | +| huge | код | данные | **резидент делит окно с трамплин-банками** | + +**huge — особый случай (резидент + банки в одном окне W3):** +- `crt0_banked` под `.ifdef W3_RESIDENT` захватывает физ-страницу резидента + (`in a,(0xE2)`) сразу после загрузки DSS и **возвращает её дефолтом** после + цикла загрузки банков (иначе в W3 остался бы последний банк). +- Трамплин на каждый `__banked`-вызов сам сохраняет/восстанавливает страницу + W3 — поэтому дефолтная страница обязана быть резидентной. +- mkexe получает флаг `-W` (разрешить HOME тянуться в W3 при наличии W3-банков — + намеренное совмещение). + +**Правила разработчика:** +- Код в W3 **не переключает** страницу W3. +- Код в W1/W2, свапающий W3 на другую страницу (скретч, графика), обязан + **вернуть исходную** (`in a,(0xE2)` → работа → `out (0xE2),a`); оборачивать в + `DI/EI`, если есть ISR, дёргающий W3. +- В W3 — **только код + rodata**, писучих переменных там быть не должно. +- Из `__banked`-контекста (пока в W3 замаплен банк) резидентный W3-код + **недостижим** транзитивно. Обратное — резидент → `__banked` через трамплин + W1 — **работает** (трамплин вернёт резидентную страницу перед `ret`). + +Подробности и артефакты — `memory/w3_resident_code`, тест `tests/w3probe`. + +--- + +## 7. Куча и стек + +- **Стек**: init `SP=0xBFFE`, растёт вниз. Меняется через `-S 0xADDR`. +- **Куча**: от конца `_BSS` вверх до `___sdcc_heap_end` (по умолчанию `0xBB00` → + ~1278 байт под стек). `malloc` берёт `&___sdcc_heap_end` как потолок. +- `runtime/heap.s` — динамическая куча (авто-размер = зазор BSS…heap_top), НЕ + фиксированный `.ds`. +- `--stack-size N` регенерирует `heap_top` как `HEAP_TOP = 0xBFFF - N`, зажимая + рост кучи ради стека. + +--- + +## 8. Семейство crt0 + +Выбирается режимом; `--crt0=TYPE` перекрывает. + +| crt0 | Файл | Для чего | +|------|------|----------| +| `default` | `runtime/crt0.s` | tiny/manual; парсит argv, argv[0] через APPINFO | +| `minimal` | `runtime/crt0_minimal.s` | tiny без argv (меньше размер) | +| `small` | `runtime/crt0_small.s` | small; авто-детект/выделение W2 | +| `banked` | `runtime/crt0_banked.s` | big/huge; авто-детект W2 + загрузка банков | + +`crt0`/`bank.s` собираются **пер-сборка** внутри `sprinter-cc` (с префиксами +`BANK_W1` / `W3_RESIDENT` / `DEBUG_RT`), а не бандлятся в библиотеку. + +--- + +## 9. Типовые грабли + +- **`static`-переменные читаются как `0xFF` / не меняются** — DATA попала в + невыделенное окно (`#FF`). Причина: код и данные в разных окнах при образе + `<16 КБ`, где DSS дал только одну страницу. Лечится правильным режимом + (`small`/`huge`) или единым окном (`tiny`). См. + `memory/sprinter_memory_modes`. +- **Крэш после первого `call` в split-режиме** — стек ещё в невыделенном W2. + Решает загрузочный стек в W1 (уже в crt0). +- **Банк «прыгает в мусор»** — таблица `_bank_pages[]` в `_DATA` занулилась + gsinit’ом; она обязана жить в `_CODE`. Уже исправлено в `bank.s`. +- **`--w3`: резидент недоступен из банка** — ожидаемо (см. §6); держи вход в + резидент только из W1/W2 или из самого W3-кода. + +--- + +## 10. Отчёт по раскладке памяти (после линковки) + +`sprinter-cc` печатает отчёт `toolchain/check_banks.py` для **любой** модели +памяти (по `.map`). Показывает, где легли код/данные и **сколько свободно** +в каждом окне и банке: + +``` +memory: huge — CODE в W1 (0x4100), DATA→W2, банки в W3 + _CODE @ 0x4100 size 3767 (W1) → 0x4FB7 + данные @ 0x4FB7 size 336 (W1) → 0x5107 [_DATA/_BSS/_INITIALIZED/…] + статика до 0x5107 — куча 0x5107..0xBB00 (27129 Б), стек 0xBB00..0xBFFE (1279 Б) + _W3CODE @ 0xC000 size 249 (резидент W3) → 0xC0F9, 16135 Б свободно до 0x10000 OK + _BANK1 @ 0x0001C000 size 219 / 16384 ( 1.3%) → 16165 Б свободно OK +``` + +- `_CODE` / `данные` — окно (W1/W2), размер, конец; строка `статика … куча … + стек …` = свободное место в W1/W2 (под кучу malloc до `___sdcc_heap_end` и + под стек). +- `_W3CODE` — только при `--w3`: остаток окна W3. +- `_BANKn` — только при `--bank`: занятость/остаток каждого 16 КБ-банка. +- Ненулевой выход (проглатывается `|| true`), если банк > 16 КБ или статика + заехала за `heap_top`/`0xC000`. +``` diff --git a/docs/samples/balls/balls.ACT b/docs/samples/balls/balls.ACT new file mode 100644 index 0000000000000000000000000000000000000000..c969a602a5508e9f888f65f1a59681185551dd6a GIT binary patch literal 512 zcmZQzU|{(F|38Bc&25JhJ#Qlg|}mr`~a1n>a@15RO=L14qZOBZ_q=MXtT+91uIC$0PR?(Hkc zfBsNp<5`fABixsgp60nP$;r+j@t4@itAM*RxPkB0tJU{=Jm*q;zNBRL?T)i%GJT9WYq|r2u zbB>SPhG7UH)O;MrzVB;3&+{}*Yu+?x8jbVz&oF${7(!?HIDXI=`_}S#{-iNak-5??PdSC0K2{H9~WS^m;K`c9MQ@LqLuH7R(>M-9?vH7m1h%mm1h&Rm1h%0w;BMB zCiG!7031!&^{WBkXu@_{4FE?I#^l(3N-LgHVEZYpcuGCnPie(dn%I6y6HlI~;mUVJ z4OhM;YPj-|sNw8j4+RJekPScw1qcj~4L}P82n>)7K!gGW1}G1J_sW81`$IveENHer h6tv2MX8S`yR2H<&4`#qKY`S#s7&UJhHIEGDB5&)WQLq32 literal 0 HcmV?d00001 diff --git a/docs/samples/balls/balls.exe b/docs/samples/balls/balls.exe new file mode 100644 index 0000000000000000000000000000000000000000..751a58c4e2875b8cf0c1eb3f6b7e8971dc9513bf GIT binary patch literal 2111 zcmeHHZD?Cn7(OS7X}a1hx0Bh@Kle6MJA{#SKMY-WcWXC?!b%Ca3XW}Q8_QhV%qA@T zNYb90_LfMM6`4hUh?FwLKbyrG)QRch=8pB?!leWo?pWHT%EBGpDh$+6&rPzn+Y0^^ z#1qcP``qV!^W5jW$-&nS0-yuy0|B)9-Fo={>A&rOk|qrz`5;s~A;}Z9*Cp}_BC{cK zFGMPa$fQVwA#!Ah{4hkm68Wecu01cw3&b=$9@~qQ)FE?yB36%hXl4@FTt??MI`~`V+AV#T>`z|)1@(Vxf8wqo_9IGV()6nr{a@RQFE69O zH+G%sSlq9^Ia@nhxoG(24YRZoi;03`_I3B`)FeSz*~$eoj}q=^HiX6D~TK5Hp^*KGVp06xMnB`0borQ|}* zc_|sL`9w0w6Iforbmb9j0ts>~MBWK$_s~Q>67jek=0KDYW}pBaJQz?v{&zpOu_$mi zW6Z7CniK(R002~;U^Mi};nzKa)pB2x4M1MDP(Vw1K4*W&aB`I!9H9@#f5zM;DNPQb zR8>2gKY^-(sOGw)aPLcGk4W2;0|FD>Pn)-qw&A=FMWW?P+||c3a^-ZCMLGHI#@wfy z(7-4I3=h+$HlYoM8SqFoFlrv8xxu<>{XTm4mHkuf3g!mSGUzOff(t(NA~!h3pfPwN z7i2jWDB5EioFm)CxOI+PMDdK`0ZKPymchfIj#2_kkk#?4ich~WFfc}-RAise2C`qU zQ@G#^wZ*)L2MnM;RnrRfy)#%Q?0Cw-3)+DfhTWs*+Dm_09WvFi^M9UM!PL!M4t}7E zd^xP7vuWiRP>w*(2FYPbY1DDcfV4?UQ2#7&7un!5TPq5Rm7oDBKHWw!-)B=wia$zw zlmnyLz>+>+i&i09rH#a!R6r6h!8=gKxt=a)7+X)cG0f=KU-MToAldaX(=gT~Qd`%E z9&5MtE$f5g{_He+OH+Z9IKoF+13y!46q{R4m6wRkybwD6_IotwmBU60^~j=GlML6% zVaq*yNXwY1Hu6!ixx9Fd-7`W*&b$p-17!k@0#^k$4@TR!S#IP0sp=xtQC(~=!aSrG zhI7}3dn46Ffw48%kb@0a^FKXzMSBM#oE;sWU7pk3Ab)bU zdO=G&jq_OUq+y`J+4Y>4b)R;1v2UF0aIxSeC*9guubb_5Iok?gQ+?yh4Ye#Kvt4d4 uON+Z(on9B)>FRWM_j09MK!NdUm$QXdq1hgnmpyZ`&E@WNx4AS1OXuJ0DEndn literal 0 HcmV?d00001 diff --git a/docs/samples/balls/balls.spr b/docs/samples/balls/balls.spr new file mode 100644 index 0000000..91bcc2b --- /dev/null +++ b/docs/samples/balls/balls.spr @@ -0,0 +1,58 @@ +  + + + + +  + + + + + + + + +  + + + + + + +  + + + + +  + + +  + + +  + + + +  +  + +   +  + +   +  + +   +  + +   +  + +   + + +  + + +  \ No newline at end of file diff --git a/docs/samples/balls/balls.txt b/docs/samples/balls/balls.txt new file mode 100644 index 0000000..a565d0b --- /dev/null +++ b/docs/samples/balls/balls.txt @@ -0,0 +1,38 @@ + evo-sdk ( C) zx-evo, + . , + . : + +balls.asm - +dss.inc - +head.inc - exe +misc.inc - "" . +math.inc - , ( HTC) +make.bat - . make src_name. +sjasm.exe - . . +balls.exe - exe. 32 . +balls64.exe - 64 . +balls128.exe - 128 . +balls225.exe - 225 . +balls.ect - . 32. + bmp . +balls.spr - (). bmp. +bgspr.act - . 32 bmp. +bgspr.spr - , bmp. + + 64*16 (.. 4 , +16*16), 32 , .. 128 . + 320*256, 128 . + , , +"" 8 , 128 . + ( "", , + . + winhex bmp , bmp + , . + . + . + . 0 7fh. + . 256 , 128 + , . + != 0xff, +=128. + + !!! \ No newline at end of file diff --git a/docs/samples/balls/balls128.exe b/docs/samples/balls/balls128.exe new file mode 100644 index 0000000000000000000000000000000000000000..f56f417ff9f26879a97acfa2b1ed7bd2f20ba9da GIT binary patch literal 2111 zcmeHHZD?Cn7(OS7NxIrBx0Bh@Kle6MJA{#SKMYxScWXC?!b%Ca3XaXRiDj;BW)qfv zBx&wVdrPFs#Ll8WL`s?BpUq+o>cn($bH{jacv8UJ5I!=5e;C4F32a=7+Ruqn78{4h6MK0%bI8<~N;L8;G*KyJsw8t8nfvrG zg_K@D3MqYvnH5b(Qw#v=x_5-E>bR>_2_Hzaph;mMs=SZ)3T;b}gP z&KvVp<$=!vM!ZGHlc|S+>8rV&Hgo?)+vAsh;Rn98J$6Tz_>s@#bL6WR0$(Knfp=;I|{{J+$DD1T-#184#zq8OT8g3kH>s|J~0mByh}) z1a&j9CWS*P005E4>2>{5^i?lsG2c^V15lF9t)k_e;1~=; zH7GD)Aghn9a1L)5W-W900zaFVy+H1P)Dn0Q)DTK=5z-oVMfPhq20BU)GEN@!w7Y`Js>6@wwltwW&PV3m2D!tI!cCxBmXl1#`@weV1L7x=Wn~6te)tYp) zL5iC1qC;xNOr4&M3$0aUYwX?;GK%JH$QrEVNEE!nc~~&ovCVu74NTP;6h~c|&45@) zE({f~4fVz948eETU|kW`A=Urf+-3D$OuJL6-kI6V?V9*=H(#b zv)u>UI!Ii?iYFBVP0sFTe6;73yPJOfM5mhuFF48OqJ18^$L(~jf-Q~BFE-g}LZ-Vt vKAIHwv^#xny35_=>FHxCwt!W}%iYd4QiWuD-9GyC376Z`<#D-H22JMQR8sl! literal 0 HcmV?d00001 diff --git a/docs/samples/balls/balls225.exe b/docs/samples/balls/balls225.exe new file mode 100644 index 0000000000000000000000000000000000000000..71857d0ca67d6b698c7b4260131426c1fd3c9303 GIT binary patch literal 2111 zcmeHHZD?Cn7(OS-l619MZYQ&)f9`FjW(Xteei*v$?$+)Dg_RO;9XK}AHkP@znN3*w zk)*vh?Jbci6IqM?5UFK~e>RI)sX3zrgXxMOLTDhqdXt1wVQJvZsvZY%gx z5KlNC?{lB`&2yjgCI^ll06+uQ1`qh<@7BWqPycNPXPc$^5~ z`ypI8geL{e4dEk0_=h3J5qQHh5}>4OW`}KzN$Z zp$o=bMXCRDzY%X1a%AeEfBJfMhuz$F+5Xg(U-`N0gTkwM*!0-IlmQ$S-A9_Yk+k8gkB>wv7MN>KW)#Y42#auv-Hn*f zHlTq~3g{lCO>ICM3{&8-YGBkfNOFU9R_$JL_f`E<^b%qQ&rz|&t!nCKDNObyiG`2X7EKmnUg(0?uOJNcoftSN?-xf8g^OsY1aojN)P19{0sR& z{!4latvEw$G3{mn1;|g;xI}#K43=}-pLVdEdSJO>_vpFyvY(cRjCJ(vpJ$d3akG$v z9_qrEhUIKND<22)5y;peJ}ki`HY-l zkC7f_z-T_Ope@!SRqz&ZJ@FP1kVXseE|gHAr;8fE*jmEPVM@F9n!n-!(XNdfhmk6g z*t$yeSh_85SsoVm=cno0stS}w5jINe*jR;LXl^-GQ7SaET3 zLULiKaBZkJVl@QbTZMH6Scg>qvolxKcQNfssd{6{%-yQ!i{wA%iP7Th=?QwbOGbh_z?oM}`OJ&ex{tYFf`-uPm literal 0 HcmV?d00001 diff --git a/docs/samples/balls/balls64.exe b/docs/samples/balls/balls64.exe new file mode 100644 index 0000000000000000000000000000000000000000..674cfddc13754f1dcc867ecae03332b0383dca20 GIT binary patch literal 2111 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 UWpr7mbh;MiE-HIS^z7{a0D4gDx&QzG literal 0 HcmV?d00001 diff --git a/docs/samples/balls/bgspr.spr b/docs/samples/balls/bgspr.spr new file mode 100644 index 0000000..841bc3b --- /dev/null +++ b/docs/samples/balls/bgspr.spr @@ -0,0 +1,1501 @@ +                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  + +  ''          + + + + +   '            + + + + + + +    ''        + + + + + + +    ''        + + + + + + +   ''        + + + + + + +   '''         + + + + + + + + + +   ''%'        + + + + + + + + + + + +  + ''%%          + + + + + + + + + + + + + + ''%%%''''          + + + + + + + + + + + + + + + + + ''''%%%'''''         + + + + + + + + + + + + + + + + + + + + + ''''%%%'''''         + + + + + + + + + + + + + + + + + + + + + '''''%%%%'''''''        + + + + + + + + + + + + + + + + + + + + + + + + + + ''%%%%%'''''''''        + + + + + + + + + + + + + + + + + + + + + + + %%&&&''%%''''''''           + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &&&&%%''''''''''          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &&'''''''''''        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &&'''''''''''        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ''''''''''         + + + + + + + + + + + + + + + + + + + + + + + + + + + +'''''''''''        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +'''%%%''''''''       + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +,,''%%%'''''''''''      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +,,'''%%%%'''''''''''  + +    + + + + + + +  + +  + + + + + + + + + + + +  + + + + + +,,,,%''%%%&'''''''''''  + + + +    + + + + + +  +  + + + + + + + + + + + + + + +,,,,%''%%%&'''''''''''  + + + +    + + + + + +  +  + + + + + + + + + + + + + + +,,,,,,%%%%&&''''%'''%''  + + + +   + +  + + + + + + + + + + + +  + + + + + + + + + +  + + + + + + + + +,,,,,%''%&&%,,,''%%%%%%'  + + + + + +  + + + +  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +,,,,,%''&&&%,,,'''%%%%%%% + + + + + +  + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  + + + + + + + + +,,,,,,''''&&%,,,,,'''%%%% + + + + + + + +  + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + +  + + + + + + + + + + + +,,,,,,,,'''%&&%',,,,''%&&& + + + + + + + +  + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +,,,,,,,,'''%&&%',,,,''%&&& + + + + + + + +  + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +,,,,,,,'''%%&%%',,,,''%&&& + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +,,,,,,'''%%%&&,,,,,,'%%&& + + + + + + + + + + + + + + + + + + + + + + + +  + + + + + + + + + + + + + + + + + + + + + + + + + + + + +,,,,,,,''''%&&&,,,,,''%%& + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + + + + + + + +,,,,,,,,%'''%&&%&,,,,''%&% + + + + + + + + + +  + + + + + + + + + + + + +   + + + + + + + + + + + + + + + +,,,,,,,,'''%%%%&%,,,,''%& + + + + + + + + + +  + + + + + + + + + +   + + + + + + + + + + +,,,,,,,,''%%%%&&&&,,,,,''%&& + + + + + + + + + + +  + + + + + + + +  + + + + + + + + + + + + +,,,,,,,,''%%%%&&&&,,,,,''%&& + + + + + + + + + + +  + + + + + + + +  + + + + + + + + + + + + +,,,,,,,,,''%%%''%&%&,,,,,'''%&& + + + + + + + + + + + + + + + + + + + + +  + + + + + + + + + + + + + + +,,,,,,,,,,,,,''''%%%%''%%&&,,,,,''%%&& + + + + + + + + + + + + + + + + + + + +  +  + + + + + + + + + + + + + + + +,,,,,,,,,,,''''''%%%'%%'%&%,,,,''%%%&% + + + + + + + + + + + + + + +  + + + + + + + + + + + + + + + + + +,,,,,'''%%''%%%%%%'&&%&&,,,'''''%&& + + + + + + + + + + + + + +  + + + + + + + + + + + + + +%,,,'''''%%'''%'%%%'&&&,,''''''%%&'' + + + + + + + + + + + """" + + + + + + + + + + + + + %,,,'''''%%'''%'%%%'&&&,,''''''%%&'' + + + + + + + + + + + """" + + + + + + + + + + + + + '''%%'%%&''%%%%'%%%%%,,,''''''%%&&%%%'''' + + + + + + + + + + #""""" + + + + + + + + + + + + + + + + ''%%%'&&&'%%'%'%%&,''''''%%%&%'''''' + + + + + + + + + + +  + +###""""" + + + + + + + + + + + + + + + + + + + + +  + + + + + + + + + %%''&%&'''''&&''''''%%%''''' + +  + + + + + +#####""# + + + + + + + + + + + + + + + + + + + + +  + + + + +  '%%'''%&%''' +  + + + + + + +#######"$$$ + + + + + + + + + + + + + + + + + + +  + +  ''%&'''  + + + +#$$####""$$$$ +" + + + + + + + + + + + + + + + + + + + + + ''%&'''  + + + +#$$####""$$$$ +" + + + + + + + + + + + + + + + + + + + + + ''%''' +##"$$$""$$"$$$$!"$ + + + + + + + + + + + + + + +  +   '''''' + +#!$""$$"$$$"$$$$! + + + + + + + + + + +  '''!!"""""#!#!$$"$$"$$$"$$"$"!#$$"$$$" + + + + +    ''!"!##"""#!###!#$"$$"$$$"""$!#!#$$"$$$"$$" + +  '##""!####"#!#####!#$"$$"$""$$$###!#$$"$$$"$$"   '!##"!#!!######!#######!#$""""$$"######!#$$"$$$"$$"    '!##"!#!!######!#######!#$""""$$"######!#$$"$$$"$$"    ''''' !#!"##!######!#########!#$$$$##""######!#$"$$"$$"     ''    !!#"" !!!!!!!!!####!!!!!!!##"###""#!!!!!!           !!"!! !!!!!!!#!!!!!!!!!!!###""###"#!!!!!        !!# ! !!!#!!!!! !!!####""###!              !!! !!##                !!! !!##                                                                                                                                                                                              ++           +++/                  +++++((              ++++///((              ++++///((                   /+++//((((            ///++/((((((                   ///++/((((((                    ///++((((                    ///++(                    ///++(                 )....))+/          //))..)...       ///))--.....-       /--))-))....---      ////---)-)))..-----      ////------))).----)..)     ////------))).----)..)  /---   ..))///------)))-----).).--// ///---  --)-...//-------))---/)))..---///////----  ----)...-//----.../////)))------//////////------/    ---)))).-////--....///---////---////////////////--/      /////----))))-////---....-/---/////---///////////--/      /////----))))-////---....-/---/////---/////-/    ///////----))--///----))).)/--/////---    //////////----/)--///-----))))--/////--      /////////---/////////----)))/////        /////////////////--))//         ///////////--//    ///////    ///////    /                      ++  ++++++  ++++++/-------- ++++++++///------------..... ++++++++++/////-------...------.........++++++++++++///////-----......//----.......))..+++++++++///+++++///////-))))).......///--.....)))))))..+++++++//////++++++////////)))))).......////.....))))))----++++/////////++++++////////)))))).......////.....))))))----++++//////////++++++//////////))))))......./////..)))))------.+++/////******///++++///////////.))))))....--/////..)))------...++////**//****////++++///////.....))..-----////..)-----....))++///***///*((////+++++/////////..---------///.)-///....)))))++//***((///(((////+++++////////----------//////////..))))))+++/////**(((((///((////+++++////////----------//////////..))))))+++/////**(((((///((*///++++++///////--------)........///.))))))++++/////**(((((((//((*//+++++++++/////////------).........///.)))---++++//////*(((((((**(((*//+++++++//////++////////---)))))...)))..////)----++++/////*//(((((((****(((//+++++///////////++//))))))))))))))..////-----+++///////*///((((*******((//++++///////////////+)))))))).--///-----++++///****//*///(((********(//+++/////////////////////+//---///------+++++///*******/*///(**********(//+++/////////////////////+//---///------+++++///*******/*///(**********///////*******/////////////+///-----++++++++//**********/*//((*********///////************/////////++///+++++++++++/************//((((*********///////************//*****////++++/+++++++++++++++++////*************//((((********/////(((*********/((/*******/////+++/+++++++++++++++///////(//*************//((((((*****////(((((******//((((**********////++////+++++++++++++++//////*/((/***********(((//(((((*****////(((((******//((((**********////++////+++++++++++++++//////*/((/***********(((//(((((*****////((((((*****//((((***********////+++++++++++++++++++++++++/////****/((/**********((((/((((((****////*(((((((***//((((*************////////++++++++++++++++++++++++////*******/((/*********((((//(((((((((////***((((((**//((((((**************///////////+++++++++++///////////////******(((((/(//*********((((//((((((((//*(****(((((**//((((((((**********************////++++++++////////////////****((((((((((/***(*****(((((//(((((((((*((*****((**//(((((((((*************************//+++++++++/////++++++//////////////////**(((((((((///((((((**(((((((/(((((((((*((*****((**//(((((((((*************************//+++++++++/////++++++//////////////////**(((((((((///((((((**(((((((/((((((((*((((******///(((((((((((************************///+++++++++++++++++++///////+++++++++///////////////***///*((((((((((//(((((((((((((((/((((((((*(((((**((*/(((((((((((((((*((********************/////++++++++++++++++++++++++++/////////+/++++++++++////////////////******/*(((((((((((/*((((((((((((/(//(((((((*(((((((((//((((((((((((((((((*********************/////+++++++++++++++++++++++++++////////////++++++++++++++++++++++////////////////********/(((((((((((((((((((((((((////((((((*((((((((((/((((((((((((((((((((((**************((********////++++++++++++++++++++++++++++++////////////////+++++++++++++++++++++++++++++++++++++//(((((((((((((*********((/(((((((((((((((((((((((((*///(((((*(((((((((//(((((((((((((((((((((((************(((((*******///////+++++++++++++++++++++++++++++++/////////////////++++++++//++++++++++++++++++++++++++///++++++++++++++++++++++/+++++++++++++++///((((((((((((((((******(((((((((((((((((((((((((((((**//(((((((((((((((/(((((((((((((((((((((((*((((*******(((((((*******///////////++++++++/++++++++++++++++++++++++++/////////////////+++++++++++++/////++++++++++++++++++++//++++++++++++++///////+++++++++++++++++++++//////++++++++++++++++++///////(((((((((((((((((**(((((((((((((((((((//((((((((******//((((((((((((((/(((((((((((((((((((((((*((((*******(((((((*******///////////++++++++/++++++++++++++++++++++++++/////////////////+++++++++++++/////++++++++++++++++++++//++++++++++++++///////+++++++++++++++++++++//////++++++++++++++++++///////(((((((((((((((((**(((((((((((((((((((//((((((((******//((((((((((((((/(((((((((((((((((((((**((((((((((*((((((((**************///////////////+++++++++++++++++++++++++++++++++++//////////////////++++++++++++//////////+++++++++++++++++///////++++++++/////////////+++++++++++++++////////////+++++++++++++++++++//////////((((((((((((((((((((((((((((((((((((((////((//((((*******//(((((((((((((/((((((((((((((((((((**((((((((((((((((((((((((**********************/////////++++++++++++++++++++++++++++///////////////////+++++++++++++///////////++++++++//////++//////++++//////////////////++++//////////////////////++++++++++++++++++///////((((((((((((((((((((((((((((((((((((((((/////////////*********//(( \ No newline at end of file diff --git a/docs/samples/balls/dss.inc b/docs/samples/balls/dss.inc new file mode 100644 index 0000000..dbd5cc2 --- /dev/null +++ b/docs/samples/balls/dss.inc @@ -0,0 +1,68 @@ +;------------------------- +;dss functions defines +;file functions +fopen equ 11h +fclose equ 12h +fread equ 13h +fwrite equ 14h +move_fp equ 15h +fgetattr equ 16h +fgetdt equ 17h +fsetdt equ 18h +fcreate equ 0ah +fcreaten equ 0bh +chdir equ 1dh +curdir equ 1eh +;memory functions +setwin1 equ 39h +getmem equ 3dh +setmem equ 3fh +freemem equ 3eh +;keyb functions +waitkey equ 30h +scankey equ 31h +quit equ 41h +getarg equ 43h +;vmode functions +setvmode equ 50h +getvmode equ 51h +selvpage equ 54h +;screen and text functions +pchar equ 5bh +pchars equ 5ch +;other +getver equ 0 +;end dss defines +;------------------------- +;hardware defines +port_y equ 89h ;port for Y coord +_320p equ 81h ;320 pixels mode +rgmod equ 0c9h +border equ 0feh +rgscr equ 0e9h +rgacc equ 0a9h + +norm_scr equ 50h +;trans_scr equ 54h +trans_scr equ 00001000b +tmp_scr equ 00000100b + + +e_cache equ 0fbh +d_cache equ 7bh +sys_port3c equ 3ch +sys_port7c equ 7ch + +cpu_w0 equ 82h ;cpu window 0 = addr 0000h +cpu_w1 equ 0a2h ;... 1 = 4000h +cpu_w2 equ 0c2h ;... 2 = 8000h +cpu_w3 equ 0e2h ;... 3 = 0c000h +;end hardware defines +;------------------------- +;characters +cr equ 0dh +lf equ 0ah +tab equ 9 +space equ 20h +;------------------------ + diff --git a/docs/samples/balls/head.inc b/docs/samples/balls/head.inc new file mode 100644 index 0000000..da66767 --- /dev/null +++ b/docs/samples/balls/head.inc @@ -0,0 +1,20 @@ +; .Z80 +; ASEG +; org 100h + org 8100h-512 + + db "EXE" + db 0 + dw 200h + dw 0 + dw 0 + dw 0 + dw 0 + dw 0 + dw entry + dw entry + dw 0bfffh + ds 490 + +; .PHASE 8100h + diff --git a/docs/samples/balls/make.bat b/docs/samples/balls/make.bat new file mode 100644 index 0000000..de301d8 --- /dev/null +++ b/docs/samples/balls/make.bat @@ -0,0 +1,20 @@ +@echo off +if "%1" == "" goto error + +if EXIST %1.exe ( + del %1.exe +) +sjasm.exe -L %1.asm %1.exe %1.lst +if errorlevel 1 goto ERR +echo Ok! +goto END + +:ERR +del %1.exe +pause +echo 訡 樨... +goto END + +:error +echo usage: make sourcefile +:END \ No newline at end of file diff --git a/docs/samples/balls/math.inc b/docs/samples/balls/math.inc new file mode 100644 index 0000000..2c058f8 --- /dev/null +++ b/docs/samples/balls/math.inc @@ -0,0 +1,89 @@ +;Input: H = Multiplier, E = Multiplicand, L = 0, D = 0 +;Output: HL = Product +mul8_8: ld b,7 + sla h ; optimised 1st iteration + jr nc,$+3 + ld l,e +.loop: add hl,hl ; unroll 7 times + jr nc,$+3 ; ... + add hl,de ; ... + djnz .loop + ret + + +lmod: call ldiv + ex de,hl + ret + +ldiv: xor a + ex af,af' + ex de,hl + jr dv1 + +adiv: ld a,h + xor d ;set sign flag for quotient + ld a,h ;get sign of dividend + ex af,af' + call negif + ex de,hl + call negif +dv1: ld b,1 + ld a,h + or l + ret z +dv8: push hl + add hl,hl + jr c,dv2 + ld a,d + cp h + jr c,dv2 + jp nz,dv6 + ld a,e + cp l + jr c,dv2 +dv6: pop af + inc b + jp dv8 + +dv2: pop hl + ex de,hl + push hl + ld hl,0 + ex (sp),hl + +dv4: ld a,h + cp d + jr c,dv3 + jp nz,dv5 + ld a,l + cp e + jr c,dv3 + +dv5: sbc hl,de +dv3: ex (sp),hl + ccf + adc hl,hl + srl d + rr e + ex (sp),hl + djnz dv4 + pop de + ex de,hl + ex af,af' + call m,negat + ex de,hl + or a ;test remainder sign bit + call m,negat + ex de,hl + ret + +negif: bit 7,h + ret z +negat: ld b,h + ld c,l + ld hl,0 + or a + sbc hl,bc + ret + + diff --git a/docs/samples/balls/misc.asm b/docs/samples/balls/misc.asm new file mode 100644 index 0000000..d047ec6 --- /dev/null +++ b/docs/samples/balls/misc.asm @@ -0,0 +1,113 @@ +; . +; A . +; , -1 (255) +; , 0. +quit0: pop ix + ld b,a + ld c,quit + rst 10h + jp $ ; + +;-[]------------------------------------------------------------ +; misc procedures and functions... +;--------------------------------------------------------------- +open: ld a,1 + ld c,fopen + rst 10h + ret + +close: ld c,fclose + rst 10h + ret + +read: ld c,fread + rst 10h + ret + +;dir_ret: ld hl,cur_dir +; ld c,chdir +; rst 10h +; ret + +gmem: ld c,getmem + ld b,1 + rst 10h + ret + +; . +; C = () +; HL = . +save_pg: in a,(c) + ld (hl),a + ret + +; . +; C = () +; HL = . +restore_pg: ld a,(hl) + out (c),a + ret + + +; +open_err: + ld hl,open_err_str + ld c,pchars + rst 10h +.err_file: ld hl,0 + ld c,pchars + rst 10h + ld hl,crlf0 + ld c,pchars + rst 10h + ld a,-1 + jp quit0 + + +; +rd_err: + ld hl,read_err_str + ld c,pchars + rst 10h +.err_file: ld hl,0 + ld c,pchars + rst 10h + ld hl,crlf0 + ld c,pchars + rst 10h + ld a,-1 + jp quit0 + + +; +gmem_err: ld hl,gmem_err_str + ld c,pchars + rst 10h + ld a,-1 + jp quit0 + +; +vm_err: ld hl,vm_err_str + ld c,pchars + rst 10h + jp quit0 + + IFDEF debug +debug_print: ld c,pchars + rst 10h + ret + ENDIF + +; +;tok_err: +; ld hl,token_err_str +; ld c,pchars +; rst 10h +;.token_ptr: ld hl,0 +; ld c,pchars +; rst 10h +; ld hl,crlf0 +; ld c,pchars +; rst 10h +; ld a,-1 +; jp quit0 diff --git a/docs/samples/balls/sjasm.exe b/docs/samples/balls/sjasm.exe new file mode 100644 index 0000000000000000000000000000000000000000..2aab2e5c3cdad4daf5fc0c05c9f465a4cb818d8d GIT binary patch literal 258048 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 juG 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 `dst_addr = src_addr + 0x140` копирует page0→page1 без ремаппинга W3. +> Со сдвигом: `dst = src ± 0x140 + dx`. + +**Банки (значение W3-страницы, порт `0xE2` = `0x50..0x5F`)** — режим ЗАПИСИ, +действует на все примитивы до смены (`libbgi/include/gfx.h`): +| Банк | Имя | Запись | Чтение | +|---|---|---|---| +| `0x50` | `GFX_BANK_NORMAL` | VRAM **+ ОЗУ-копия** | ОЗУ-копия | +| `0x54` | `GFX_BANK_NOSHADOW` | только VRAM | ОЗУ-копия | +| `0x58` | `GFX_BANK_TRANSPARENT` | VRAM, байт `0xFF` не пишется | ОЗУ-копия | +| `0x5C` | `GFX_BANK_SPRITE` | VRAM, `0xFF` не пишется | ОЗУ-копия | + +Чтение `#50..#5F` **всегда** отдаёт ОЗУ-копию (видео-ОЗУ write-only). `0xFF` — +аппаратно-прозрачный цвет (`GFX_TRANSPARENT`). + +**Акселератор** (`C-Compiler/docs/converted/accel_r.txt`): внутр. ОЗУ-буфер +1..256 байт. Управление опкодами-«NOP»: +- `LD B,B` — стоп; `LD D,D` затем `LD A,n` — размер блока (`n=0` → 256); +- `LD L,L` — **горизонтальная** копия (буфер ← `LD A,(HL)`; буфер → `LD (DE),A`); +- `LD A,A` (`0x7F`) — **вертикальная** копия (линии экрана, Port_Y шагает внутри + burst'а); +- `LD C,C` / `LD E,E` — fill (гориз./верт.). + +Скорость ≈ **байт / 7 мкс**. Полный экран 320×256 ≈ **26 мс** (это **>1 кадра** +@ 50 Гц — держать в уме, §9). На время работы акселератора — **DI** (меняется +система команд, ISR сломается). + +--- + +## 2. Ключевой инсайт: чистый фон достаётся бесплатно + +- Спрайты рисуются банком с битом 2 (`0x54`/`0x5C`) → **только VRAM**, + ОЗУ-копию не трогают. +- ⇒ ОЗУ-копия (`0x50`) любой страницы — это фон **без спрайтов**, всегда. +- ⇒ скролл-копия из `0x50` в `0x50` читает чистый фон и пишет VRAM+тень — это + **одновременно скролл И heal** целевой страницы (её старые спрайты затираются + сдвинутым фоном). + +Ровно так работает `gfx_heal` (частный случай `src==dst`, §3). Скролл — тот же +механизм, но `src != dst`. + +--- + +## 3. Что уже есть в libbgi — образцы для копирования + +| Файл | Что | Зачем образец | +|---|---|---| +| `bgi256/_bgi_copy_rows_raw.c` | гориз. leaf (`LD L,L`), src/dst + страйды, Port_Y на строку | ядро всех блитов; страйды, SMC, DI | +| `bgi256/_bgi_heal_rows_raw.c` | `src==dst==экран`, страйды 0 | спец-случай heal | +| `bgi256/_bgi_blit_cols_raw.c` | **верт. leaf** (`LD A,A`), колонка=burst, Port_Y сброс на колонку, `dst+=1` | **прямой образец для `scroll_h`** | +| `common/_gfx_heal_full.c` | клип по экрану + нарезка полос >256 + вызов leaf | образец обёртки-ядра | +| `common/gfx_heal.c` | save-банк → `0x50` → `_bgi_begin` → ядро → `_bgi_end` → restore | образец верхней обёртки | +| `docs/converted/accel_r.txt` | screen→screen верт. копия LDIR-идиомом, `dst=src+0x140` | канонический скролл-паттерн | + +Правило из `_bgi_copy_rows_raw`: **одна сторона копии — видео (в W3, строку даёт +Port_Y), другая может быть линейным буфером ВНЕ W3** (`< 0xC000`). Для скролла +обе стороны — видео (см. §4). + +--- + +## 4. `gfx_scroll_h` — горизонтальный (наш, приоритет) + +**Идиом: вертикальный режим `LD A,A`, проход по колонкам.** Высота playfield +(144) ≤ 256 → одна колонка = один burst, **резать 320-строку на 256+64 НЕ надо** +(главная причина брать верт. режим). Это доковый screen→screen паттерн +(`accel_r.txt`) + `_bgi_blit_cols_raw`. + +Параметры на колонку: +- `src = base(src_page) + x` — банк `0x50`, чтение = ОЗУ-копия = чистый фон; +- `dst = base(dst_page) + x + dx` — банк `0x50`, запись = VRAM + тень dst; +- `base`: page0 `0xC000`, page1 `0xC140`; +- размер блока = высота playfield (`h`, для Loom 144); +- Port_Y start = верх playfield (`y0`, для Loom 0); +- копируем `(w − |dx|)` колонок; вакантные `|dx|` колонок **не трогаем**. + +**Знак:** зафиксировать `dx > 0` = сдвиг вправо (открывается слева), `dx < 0` = +влево; вернуть открывшийся край в `*dirty`. + +**Порядок (по образцу докового LDIR-примера + `_bgi_blit_cols_raw`):** банда +колонок под DI → `LD D,D`/`LD A,h` (block) → `LD A,A` (верт. режим) → на колонку +`{ read col в accel; write col из accel; src++; dst++ }` → `EI`. При straight-copy +Port_Y авто-шагает (доковый LDIR его не трогает) — но `_bgi_blit_cols_raw` +сбрасывает Port_Y=`y0` на каждую колонку; **проверить, нужен ли сброс** (§7.1 +покажет). + +**Возврат:** `*dirty` = прямоугольник `|dx|` колонок с открывшегося края; +полосу не чистим — её дозаполняет вызывающий (в Loom — декодер EGA-страйпов). + +```c +/* Горизонтальный скролл playfield. Копирует чистый фон (ОЗУ-копия src_page) + со сдвигом dx в dst_page (обе стороны банк 0x50: запись бьёт VRAM+тень). + dx>0 — вправо. Вакантную полосу НЕ заполняет — возвращает в *dirty. */ +void gfx_scroll_h(uint8_t src_page, uint8_t dst_page, + const gfx_rect_t *area, int dx, gfx_rect_t *dirty); +``` + +--- + +## 5. `gfx_scroll_v` — вертикальный (симметрия; Loom не нужен) + +**Асимметрия железа:** горизонтальный сдвиг — это смещение CPU-адреса (src и dst +независимы) → легко. **Вертикальный сдвиг — это смещение Port_Y, а Port_Y один +на burst** → в простом LDIR-идиоме read и write идут с одного Port_Y, сдвига по +Y нет. Поэтому `scroll_v` сложнее `scroll_h`. Два пути: + +- **(A) менять Port_Y между fill и flush одного burst'а**: `OUT Port_Y=y_src`; + read col в accel; `OUT Port_Y=y_src+dy`; write col из accel. Требует, чтобы + accel-буфер пережил `OUT` между чтением и записью — **не доказано** доком/ + тестами (§7.3). Если пройдёт — симметрично `scroll_h`, верт. режим. +- **(B) безопасный fallback**: grab (video→RAM-буфер вне W3, гориз. режим), + затем blit (буфер→video со сдвигом Y — буферная сторона CPU-адресуема, сдвиг + любой). Два прохода + буфер, но заведомо работает. + +**Рекомендация:** реализовать (A), если §7.3 пройдёт; иначе (B). Делать +**последним** — для Loom не критично. API симметричный (`dy` вместо `dx`): + +```c +void gfx_scroll_v(uint8_t src_page, uint8_t dst_page, + const gfx_rect_t *area, int dy, gfx_rect_t *dirty); +``` + +--- + +## 6. Общие требования реализации + +- **DI по банде, НЕ один DI на весь проход.** Полный playfield ~14 мс под DI + порвёт будущий CBL-звук. Одна колонка (144 Б ≈ 20 мкс) — безопасное DI-окно; + резать проход на банды колонок/строк с `EI` между (как `_bgi_copy_rows_raw` + режет ≤16 строк). Пока звука нет — не критично, но заложить сразу. +- **Клип по `area`** (образец — `_gfx_heal_full`). +- **Буфер (если путь (B))** — вне W3 (`< 0xC000`). +- **Верхняя обёртка**: save банк → `gfx_set_bank(GFX_BANK_NORMAL)` → + `_bgi_begin()` → ядро → `_bgi_end()` → restore (образец — `gfx_heal.c`). +- **Размещение**: leaf `bgi256/_bgi_scroll_cols_raw.c` (+ `_rows_raw` для (A)), + обёртки `common/gfx_scroll_h.c` / `gfx_scroll_v.c`, объявления в + `include/gfx.h`. `gfx_rect_t` — если ещё нет, определить там же + (`int x,y,w,h`). +- **Для Loom (не требование к примитиву, но контекст)**: вызывающий держит + `dx` кратным 8 (ширина EGA-страйпа) → `dirty` = целые страйпы. Обёртка этому + не мешает, но и не навязывает. + +--- + +## 7. ПРОВЕРИТЬ ПЕРВЫМ — блокеры (dev-MAME) + +1. **Верт. режим (`LD A,A`) в банке `0x50` читает ОЗУ-копию (чистый фон) и + пишет VRAM+тень.** Гориз. heal это доказывает построчно; для верт. прохода — + прогон: нарисовать фон, поверх спрайт банком `0x5C`, сделать `scroll_h` на + dx>0, убедиться что спрайт **не размазался** (копировался фон, не VRAM со + спрайтом). Заодно выяснить, нужен ли сброс Port_Y на колонку (§4). +2. **ОЗУ-копия per-page или общая.** `C-Compiler/applications/PoP/docs/double_buffer_plan.md` + пишет «теневая копия одна — общая». Если тень физически одна (а не адресуется + по базе `0xC000/0xC140` как VRAM), page0 и page1 не удержат фон **разных** + положений камеры во время скролла → пинг-понг (§8) сломается. Проверить: + записать разный фон в page0 и page1 банком `0x50`, сверить чтение обеих. +3. **(только для `scroll_v`, путь A)** accel-буфер переживает `OUT Port_Y` между + fill и flush одного burst'а. Доковый straight-copy этого не проверяет. + +--- + +## 8. Порядок кадра в Loom (контекст использования `scroll_h`) + +Пинг-понг двух страниц; спрайты в VRAM-only банк, поэтому тень каждой страницы +остаётся чистым фоном: + +1. `gfx_set_draw_page(B)` — B = скрытая; +2. `gfx_scroll_h(A, B, area, dx, &dirty)` — B получает сдвинутый чистый фон в + VRAM+тень; старые спрайты B затёрты (copy = heal); +3. декодировать EGA-страйпы фона в `dirty`, **банк `0x50`** (иначе тень B + останется с дырой — следующий скролл из B прочитает мусор); +4. вывести актора/спрайты в B, банк `0x5C`; +5. `gfx_wait_vsync()` → `gfx_set_visible_page(B)`. + +Следующий кадр: роли A/B меняются, читаем уже чистую тень B. Смаза нет by design +(§2). При `dx==0` (камера стоит) — обычный per-sprite `gfx_heal`, а не полная +копия. + +--- + +## 9. Бюджет и проверка корректности + +- 320×256 ≈ 26 мс (**>кадр**); playfield 320×144 ≈ **14–15 мс** (<кадр); скролл + копирует `(320−dx)` колонок → чуть меньше. Итого копия ~кадр + докод полосы + + спрайты; целевой бюджет ~2 кадра/шаг (plan.md §6). **Замерить на PoC-1** + маркерами `OUT (0xFE)` (до появления звука порт свободен). +- Корректность — визуально/дифф-тестом в dev-MAME. Фон-декодер уже проверен + диффом против ScummVM; скролл проверять на реальной комнате шире 320. + +--- + +## 10. Связанные документы + +`plan.md §6` (графика, скроллинг), `toolchain-requirements.md §1` (этот +примитив), `decisions.md`. Железо: `C-Compiler/docs/converted/accel_r.txt`, +`libbgi/include/gfx.h`, `C-Compiler/docs/sprite-api-design.md`, +`C-Compiler/docs/memory-management.md`. diff --git a/examples/scroll/scroll.c b/examples/scroll/scroll.c new file mode 100644 index 0000000..7be012f --- /dev/null +++ b/examples/scroll/scroll.c @@ -0,0 +1,87 @@ +/* + * scroll — визуальная проверка скролл-примитивов libbgi (gfx_scroll_h / + * gfx_scroll_v). Копирует регион из НЕактивной страницы в активную со + * сдвигом и переключает страницы. + * + * ДИАГНОСТИКА. Страница 0 — диагональные полосы color=((x+y)>>3)&15 + * (любой сдвиг ломает непрерывность диагоналей; фаза по Y ловит + * вертикальный «съезд» Port_Y-квирка). Страница 1 — сплошной серый + * фон-маркер: после скролла на нём проступают ровно скопированные + * прямоугольники, по их границам видно корректность (позицию/высоту). + * + * Два региона в одном кадре: слева ГОРИЗОНТАЛЬНЫЙ скролл (dx>0 — картинка + * вправо, открывается серая полоса слева), справа ВЕРТИКАЛЬНЫЙ (dy>0 — + * вниз, открывается серая полоса сверху). Если верт. Port_Y-квирк не + * вылечен — правый прямоугольник съедет вниз/опустеет сверху. Esc — + * выход. + */ + +#include +#include +#include +#include + +static uint8_t egapal[16 * 4]; + +__sfr __at (0xFE) io_border; + +/* Диагональные полосы 16-цветной палитры (8-пиксельные ступени). */ +static void draw_diagonals(void) +{ + int bx, by; + for (by = 0; by < 256; by += 8) + for (bx = 0; bx < 320; bx += 8) { + setfillstyle(SOLID_FILL, (((bx + by) >> 3) & 15)); + bar(bx, by, bx + 7, by + 7); + } +} + +/* Регион горизонтального скролла (слева) и вертикального (справа). */ +static gfx_rect_t area_h = { 0, 56, 320, 144 }; +// static gfx_rect_t area_h = { 16, 48, 80, 80 }; +// static gfx_rect_t area_v = { 176, 48, 80, 80 }; + +int main(void) +{ + uint8_t hidden; + + initgraph(); + + /* Палитра страницы 1 = палитре страницы 0 (initgraph грузит EGA + * только в палитру 0). */ + gfx_pal_get(0, 0, 16, egapal); + gfx_pal_load(1, 0, 16, egapal); + + /* Страница 0 — диагонали (источник фона). */ + gfx_set_draw_page(0); + draw_diagonals(); + + gfx_set_draw_page(1); + draw_diagonals(); + + gfx_set_visible_page(0); + + /* Рисуем на скрытой странице (1), потом флип. */ + hidden = gfx_get_visible_page() ^ 1; /* = 1 */ + gfx_set_draw_page(hidden); + + + + for (;;) { + if (kbhit() && getch() == 27) + break; + io_border = 2; + gfx_scroll_v(&area_h, 2, 0); /* вправо на 48 px */ + // gfx_scroll_v(&area_v, 2, 0); /* вниз на 48 px */ + io_border = 0; + gfx_wait_vsync(); + gfx_wait_vsync(); + + hidden = gfx_get_visible_page() ^ 1; + gfx_set_draw_page(hidden ^ 1); + gfx_set_visible_page(hidden); + } + + closegraph(); + return 0; +} diff --git a/libbgi/_bgi.h b/libbgi/_bgi.h index dddcdb4..10f6ba7 100644 --- a/libbgi/_bgi.h +++ b/libbgi/_bgi.h @@ -37,6 +37,7 @@ extern uint8_t _bgi_prevmode; /* видеорежим до initgraph */ /* CPU-адрес колонки 0 текущей draw-страницы: 0xC000 (page 0) или * 0xC140 (page 1); каждый примитив использует его вместо константы. */ extern uint16_t _gfx_addr_base; +extern uint16_t _gfx_addr_shadow_base; /* ---- Низкоуровневый видеорежим (libc/video) ---------------------- */ uint8_t _videomode_raw_get(void); @@ -108,6 +109,30 @@ void _bgi_heal_rows_raw(uint8_t *scr, int y0, uint8_t w, uint8_t h); void _bgi_hspan (int x, int y, int len, uint8_t color); void _bgi_clearall(uint8_t color); + +/* Скролл-ядра video->video (банк 0x50: копия = скролл + heal цели; без + * скобки/клиппинга; DI/EI бандами по 16 ВНУТРИ; перекрытия нет — стороны + * в разных страницах). Контракты — в шапках bgi256/_bgi_scroll_*_raw.c. + * + * _bgi_scroll_rows_raw — ядро gfx_scroll_h: сдвиг ТОЛЬКО по X (разница + * адресов src/dst), построчно, БЕЗ страйдов — один OUT Port_Y на строку + * (~54Т/строку, на 5-7% быстрее cols). w — байт/строка (0=256), h — + * строк 1..256 (0=256, конвенция accel), y0 — стартовый Port_Y (++ на строку). */ +void _bgi_scroll_rows_raw(uint16_t src, uint16_t dst, + uint8_t w, uint8_t h, uint8_t y0); + +/* _bgi_scroll_cols_raw — ядро gfx_scroll_v: сдвиг по X И/ИЛИ Y за один + * проход БЕЗ буфера. Колонка = вертикальный accel-burst (LD A,A): читаем + * с Port_Y=y0, пишем с Port_Y=y1 (сдвиг dy = y1−y0; dx — в адресах). STOP + * между read и write делает промежуточный OUT Port_Y безопасным. w — + * число КОЛОНОК 1..255 (0=выход), h — высота колонки = размер accel-блока + * (0=256), y0/y1 — Port_Y чтения/записи. */ +void _bgi_scroll_cols_raw(uint16_t src, uint16_t dst, + uint8_t w, uint8_t h, uint8_t y0, uint8_t y1); + + + + /* Ядро блиттинга gfx_blit_part/gfx_blit/gfx_heal (Фаза B) — публичные, * прототипы в ; спрайтовые обёртки putsprite/movesprite — в * . Реализации: common/gfx_blit_part.c и соседи. */ diff --git a/libbgi/bgi256/_bgi_scroll_cols_raw.c b/libbgi/bgi256/_bgi_scroll_cols_raw.c new file mode 100644 index 0000000..6d84556 --- /dev/null +++ b/libbgi/bgi256/_bgi_scroll_cols_raw.c @@ -0,0 +1,102 @@ +/* + * _bgi_scroll_cols_raw — accel-скролл КОЛОНКАМИ video->video (mode 0x81). + * УНИВЕРСАЛЕН: делает и горизонтальный, и ВЕРТИКАЛЬНЫЙ сдвиг за один + * проход, БЕЗ буфера-посредника. Ядро gfx_scroll_v (вертикаль); может + * служить и горизонтали (gfx_scroll_h держит свой rows-вариант — он + * на ~5-7% быстрее, т.к. на строку нужен один OUT Port_Y, а не два). + * + * КАК РАБОТАЕТ. Колонка = один вертикальный accel-burst (LD A,A, 0x7F — + * «копия блока для вертикальных линий»): read набирает h байт колонки src + * с Port_Y=y0 (Port_Y авто-инкрементится внутри burst'а: y0→y0+h), write + * выгружает их в колонку dst с Port_Y=y1 (y1→y1+h). Вертикальный сдвиг = + * разница y0/y1; горизонтальный = разница адресов src/dst (dx). Оба сразу + * = произвольный (dx,dy). dst/src двигаются на +1 (следующий x) за колонку. + * + * ПОЧЕМУ РАБОТАЕТ смена Port_Y между read и write (в отличие от наивного + * rows-варианта, где это ломало accel). Ключ — STOP (LD B,B) ПЕРЕД + * OUT Port_Y=y1: он разоружает accel (m_acc_dir=0), поэтому fetch операнда + * OUT (immediate байт 0x89 из программной памяти) НЕ перезапускает + * read-burst. Затем LD A,A снова взводит вертикальный режим для записи. + * Оба OUT'а (y0 и y1) выполняются только пока accel РАЗОРУЖЁН. Порядок на + * колонку: {OUT y0 · arm · read · STOP · OUT y1 · arm · write · STOP}. + * (Подробно про квирк перезапуска — memory/accel_operand_fetch_retrigger.) + * + * DI/EI — БАНДАМИ по 16 колонок ВНУТРИ (как _gfx_recthfill256): держать DI + * на весь скролл нельзя (порвёт CBL-звук). Размер блока (h) армируется в + * КАЖДОЙ скобке (в окне EI CBL-ISR мог бы переармировать accel). Единая + * тело-петля для полных банд и хвоста (счётчик колонок decrement'ится в + * стек-слоте); y0 держим в C (LD A,C 4Т вместо LD A,#imm 7Т на колонку), + * y1 — SMC-immediate (грузится только пока разоружены — безопасно). + * + * ВХОД (__sdcccall(1)): src->HL, dst->DE; стек: w 4(ix) — число КОЛОНОК + * 1..255 (0 = тихий выход; ровно 256 режет вызывающий), h 5(ix) — высота + * колонки = размер accel-блока 1..256 (0 = 256, конвенция accel), + * y0 6(ix) — Port_Y чтения, y1 7(ix) — Port_Y записи. Callee-pop 4 байта. + * + * Raw: клиппинга нет (вызывающий), W3 замаплен (_bgi_begin/end снаружи), + * банк 0x50 (запись бьёт VRAM+тень — скролл И heal цели). Перекрытия + * src/dst нет by design — стороны в РАЗНЫХ видеостраницах. Колонок >255 и + * колонки >256 строк режет вызывающий. Клоббер: AF/BC; HL/DE/IX + * сохраняются (LD A,A/LD D,D — accel-опкоды-no-op'ы, регистры не трогают). + */ + +#include "../_bgi.h" + +void _bgi_scroll_cols_raw(uint16_t src, uint16_t dst, + uint8_t w, uint8_t h, uint8_t y0, uint8_t y1) __naked { + (void)src; (void)dst; (void)w; (void)h; (void)y0; (void)y1; + __asm + push ix + ld ix, #0 + add ix, sp + ;; HL = src, DE = dst (адреса-колонки; строку задаёт Port_Y) + ld a, 5 (ix) + ld (sc_col_len), a ; SMC: размер accel-блока <- h (0 = 256) + ld a, 7 (ix) + ld (sc_col_y1), a ; SMC: Port_Y записи <- y1 + ld c, 6 (ix) ; C = Port_Y чтения (y0) — LD A,C дешевле imm + sc_col_band: + ld a, 4 (ix) ; колонок осталось + or a, a + jr Z, sc_col_done ; 0 => выход + cp a, #16 + jr NC, sc_col_full ; >=16 — полная банда + ld b, a ; <16 — хвост: B = остаток + jr sc_col_go + sc_col_full: + ld b, #16 ; 16 колонок в банде + sc_col_go: + ld a, 4 (ix) + sub a, b + ld 4 (ix), a ; колонок -= B + di + ld d, d ; 0x52 — режим размера блока (no-op для D) + ld a, #0 + sc_col_len = . - 1 + ld b, b ; выключить — размер сохранён (B=счётчик цел) + sc_col_next: + ld a, c ; Port_Y чтения (y0) + out (#0x89), a ; accel РАЗОРУЖЁН — операнд OUT безопасен + ld a, a ; 0x7F — арм вертикальной копии + ld a, (hl) ; read-burst: колонка src -> буфер (Port_Y y0..) + ld b, b ; 0x40 — STOP (разоружить перед OUT y1) + ld a, #0 + sc_col_y1 = . - 1 + out (#0x89), a ; Port_Y записи (y1) — accel разоружён + ld a, a ; 0x7F — арм вертикальной копии + ld (de), a ; write-burst: буфер -> колонка dst (Port_Y y1..) + ld b, b ; 0x40 — STOP + inc hl ; src += 1 (следующий x) + inc de ; dst += 1 + djnz sc_col_next + ei ; окно прерываний между бандами + jr sc_col_band + sc_col_done: + pop ix + ;; callee-pop 4 байта (w, h, y0, y1) + pop hl ; ret-адрес + pop af + pop af + jp (hl) + __endasm; +} diff --git a/libbgi/bgi256/_bgi_scroll_rows_raw.c b/libbgi/bgi256/_bgi_scroll_rows_raw.c new file mode 100644 index 0000000..2774253 --- /dev/null +++ b/libbgi/bgi256/_bgi_scroll_rows_raw.c @@ -0,0 +1,129 @@ +/* + * _bgi_scroll_rows_raw — БЫСТРАЯ построчная копия video->video со сдвигом + * ТОЛЬКО по X для gfx_scroll_h (mode 0x81). Специализация _bgi_copy_rows_raw + * для скролла: обе стороны — видео, ОБА страйда НУЛЕВЫЕ (CPU-адрес строки + * фиксирован, строку выбирает Port_Y), поэтому адресной арифметики в цикле + * НЕТ ВООБЩЕ — ни страйдовых add (как у copy_rows), ни даже inc адресов + * (как у heal). От _bgi_heal_rows_raw отличается тем, что src != dst + * (HL — колонка-старт src-страницы, DE — dst-страницы со сдвигом dx): + * горизонтальный сдвиг целиком в разнице адресов, задаётся вызывающим. + * + * Вертикального сдвига НЕТ (read и write одной строки на одном Port_Y) — + * для сдвига по Y есть _bgi_scroll_cols_raw (колонки, вертикальный режим). + * Зато rows на ~5-7% быстрее cols: на строку один OUT Port_Y (54Т/строку), + * а cols нужен OUT на чтение И на запись колонки. + * + * ПОРЯДОК на строку: {OUT Port_Y · arm(LD L,L) · read(LD A,(HL)) · + * write(LD (DE),A) · STOP(LD B,B)}. Между read- и write-триггером — + * НИЧЕГО: на Sprinter accel перехватывает любое чтение программной памяти, + * а fetch операнда любой инструкции там (immediate у OUT/ADD) спурьезно + * перезапустил бы read-burst и набил буфер кодом. Port_Y ставится ДО + * arm'а (accel разоружён STOP'ом прошлой строки — операнд OUT безопасен). + * (memory/accel_operand_fetch_retrigger.) + * + * DI/EI — БАНДАМИ по 16 строк ВНУТРИ (как _gfx_recthfill256): накрывать DI + * весь сдвиг нельзя (порвёт CBL-звук). Армирование размера блока — в + * КАЖДОЙ скобке (в окне EI CBL-ISR армирует accel своим размером). Число + * полных 16-банд и хвост считаем ОДИН раз на входе (precompute) — в петле + * банд остаётся дешёвый dec (без per-band `sub 16` через IX-слот). + * Регистры упёрты (HL src, DE dst, B djnz, C бегущий Port_Y) — быстрее уже + * некуда: out(c) без стопов потребовал бы Port_Y-регистра, которого нет. + * + * ВХОД (__sdcccall(1)): src->HL, dst->DE; стек: w 4(ix) — байт в строке + * (размер accel-блока, 1..256, 0=256), h 5(ix) — строк 1..256 (0 = 256, + * КОНВЕНЦИЯ accel — как у w), y0 6(ix) — стартовый Port_Y (инкремент на + * строку). Callee-pop 3 байта. + * + * Raw: клиппинга нет (вызывающий), W3 замаплен (_bgi_begin/end снаружи), + * банк 0x50 (запись бьёт VRAM+тень — скролл И heal цели). Перекрытия + * src/dst нет by design — стороны в РАЗНЫХ видеостраницах. Строку >256 + * байт режет вызывающий. Клоббер: AF/BC; HL/DE/IX сохраняются + * (адреса-константы не трогаются — LD L,L/LD D,D это accel-опкоды-no-op'ы). + */ + +#include "../_bgi.h" + +void _bgi_scroll_rows_raw(uint16_t src, uint16_t dst, + uint8_t w, uint8_t h, uint8_t y0) __naked { + (void)src; (void)dst; (void)w; (void)h; (void)y0; + __asm + push ix + ld ix, #0 + add ix, sp + ;; HL = src, DE = dst (адреса-константы; строку задаёт Port_Y) + ld a, 4 (ix) + ld (sc_row_len), a ; SMC: размер блока <- w (0 = 256) + ld (sc_row_len2), a ; ...и в хвостовую скобку + ld c, 6 (ix) ; C = бегущий Port_Y (y0) + + ;; --- precompute: полных 16-банд (ix+5) + хвост (SMC); h=0 => 256 --- + ld a, 5 (ix) + and #0x0F + ld (sc_row_tail), a ; хвост = h & 15 (0 и при h=0, и при h=256) + ld a, 5 (ix) + or a, a + jr NZ, sc_row_shr + ld a, #16 ; h=0 => 256 => ровно 16 полных банд + jr sc_row_setfull + sc_row_shr: + rrca + rrca + rrca + rrca + and #0x0F ; full = h >> 4 + sc_row_setfull: + ld 5 (ix), a ; ix+5 = число полных 16-строчных банд + + sc_row_band: + ld a, 5 (ix) + or a, a + jr Z, sc_row_tailband ; полные банды кончились + dec 5 (ix) ; full -= 1 + ld b, #16 ; 16 строк в банде + di + ld d, d ; 0x52 — режим размера блока (no-op для D) + ld a, #0 + sc_row_len = . - 1 + ld b, b ; выключить — размер сохранён (B=16 цел) + sc_row_next: + ld a, c + out (#0x89), a ; Port_Y = y (accel ВЫКЛЮЧЕН — операнд безопасен) + inc c ; y++ + ld l, l ; 0x6D — арм гориз. копии (no-op для L) + ld a, (hl) ; read-burst: src-строка -> буфер + ld (de), a ; write-burst: буфер -> dst-строка (вплотную!) + ld b, b ; 0x40 — стоп + djnz sc_row_next + ei ; окно прерываний между бандами + jr sc_row_band + ;; --- хвост h&15 строк: отдельная скобка со СВОИМ армированием --- + sc_row_tailband: + ld a, #0 + sc_row_tail = . - 1 + or a, a + jr Z, sc_row_done ; хвоста нет + ld b, a ; B = остаток строк (1..15) + di + ld d, d + ld a, #0 + sc_row_len2 = . - 1 + ld b, b + sc_row_trow: + ld a, c + out (#0x89), a + inc c + ld l, l + ld a, (hl) + ld (de), a + ld b, b + djnz sc_row_trow + ei + sc_row_done: + pop ix + ;; callee-pop 3 байта (w, h, y0) + pop hl ; ret-адрес + pop af + inc sp + jp (hl) + __endasm; +} diff --git a/libbgi/common/_gfx_state.c b/libbgi/common/_gfx_state.c index b093212..32abfcf 100644 --- a/libbgi/common/_gfx_state.c +++ b/libbgi/common/_gfx_state.c @@ -18,6 +18,7 @@ uint8_t _gfx_bank = 0x50; * (CPU 0xC000+), page 1 — 320..639 (CPU 0xC140+), остаток — дескрипторы * режима/палитра, их не трогаем. */ uint16_t _gfx_addr_base = 0xC000; +uint16_t _gfx_addr_shadow_base = 0xC140; /* Клип спрайтов по экрану: 1 = включён (безопасно, дефолт), 0 = выключен * (приложение само гарантирует, что спрайты не выходят за края — даёт diff --git a/libbgi/common/gfx_scroll_h.c b/libbgi/common/gfx_scroll_h.c new file mode 100644 index 0000000..706100e --- /dev/null +++ b/libbgi/common/gfx_scroll_h.c @@ -0,0 +1,64 @@ +/* + * gfx_scroll_h — ГОРИЗОНТАЛЬНЫЙ скролл региона (mode-agnostic обёртка). + * + * Берёт чистый фон из НЕактивной (теневой) страницы и копирует его со + * сдвигом dx по X в активную (draw) страницу. Обе стороны — банк 0x50 + * (GFX_BANK_NORMAL): запись бьёт VRAM + ОЗУ-копию, т.е. копия + * ОДНОВРЕМЕННО скроллит И «лечит» целевую страницу (старые спрайты + * затираются сдвинутым фоном; scroll-impl-guide.md §2). + * + * dx > 0 — сдвиг в сторону УВЕЛИЧЕНИЯ X (изображение едет вправо, + * открывается полоса слева); dx < 0 — влево (открывается справа); + * dx == 0 — чистая копия фона (полный heal целевой страницы). В обеих + * страницах остаёмся в пределах area. Открывшуюся |dx|-полосу НЕ + * заполняем — возвращаем её в *dirty (её дозаполняет вызывающий). + * + * РЕАЛИЗАЦИЯ — построчно, через _bgi_scroll_rows_raw (без страйдов, один + * OUT Port_Y на строку — на ~5-7% быстрее колоночного _bgi_scroll_cols_raw, + * которому на колонку нужен OUT и на чтение, и на запись). Горизонтальный + * сдвиг — чистая разница CPU-адресов src/dst (src_x vs dst_x), Port_Y для + * read и write один (одна строка). DI/EI бандами по 16 строк — ВНУТРИ + * leaf'а; он же режет высоту на банды и принимает h 1..256 (0=256), так что + * здесь делим только ширину >256 (лимит accel-блока). Перекрытий src/dst + * нет: стороны в разных страницах. + */ + +#include "../include/sprite.h" +#include "../_bgi.h" + +void gfx_scroll_h(const gfx_rect_t *area, int16_t dx, gfx_rect_t *dirty) { + + uint8_t saved = gfx_get_bank(); + uint16_t adx = (dx < 0) ? (uint16_t)(-dx) : (uint16_t)dx; + /* src-строка стартует правее на |dx| при сдвиге влево, dst — правее + * на dx при сдвиге вправо; так оба края остаются внутри area. */ + uint16_t src_x = area->x + ((dx < 0) ? adx : 0); + uint16_t dst_x = area->x + ((dx > 0) ? adx : 0); + uint16_t w = area->w - adx; /* байт (пикселей) в строке */ + uint8_t h = (uint8_t)area->h; /* строк (256 => 0 = 256 у leaf) */ + uint16_t src = _gfx_addr_shadow_base + src_x; + uint16_t dst = _gfx_addr_base + dst_x; + + gfx_set_bank(GFX_BANK_NORMAL); + _bgi_begin(); + + /* Ширина >256 байт — банды ≤256 (размер accel-блока, 0=256). Высоту и + * DI/EI-банды по 16 строк leaf делает сам (h: 0=256). Между бандами по + * ширине CPU-адрес двигаем на 256 (Port_Y выбирает строку). */ + while (w) { + uint8_t cw = (w > 256) ? 256 : (uint8_t)w; /* 0 => 256 */ + _bgi_scroll_rows_raw(src, dst, cw, h, (uint8_t)area->y); + src += 256; dst += 256; + w -= (w > 256) ? 256 : w; + } + + _bgi_end(); + gfx_set_bank(saved); + + if (dirty) { + dirty->x = (dx >= 0) ? area->x : (int16_t)(area->x + area->w - adx); + dirty->y = area->y; + dirty->w = adx; + dirty->h = area->h; + } +} diff --git a/libbgi/common/gfx_scroll_v.c b/libbgi/common/gfx_scroll_v.c new file mode 100644 index 0000000..b1518b4 --- /dev/null +++ b/libbgi/common/gfx_scroll_v.c @@ -0,0 +1,63 @@ +/* + * gfx_scroll_v — ВЕРТИКАЛЬНЫЙ скролл региона (mode-agnostic обёртка). + * + * Симметрична gfx_scroll_h, но сдвиг по Y. Берёт чистый фон из НЕактивной + * (теневой) страницы и копирует его со сдвигом dy по Y в активную (draw) + * страницу. Банк 0x50: копия = скролл + heal целевой страницы (старые + * спрайты затираются сдвинутым фоном; см. gfx_scroll_h / guide §2). + * + * dy > 0 — сдвиг в сторону УВЕЛИЧЕНИЯ Y (изображение едет вниз, + * открывается полоса сверху); dy < 0 — вверх (открывается снизу); + * dy == 0 — чистая копия (полный heal). Открывшуюся |dy|-полосу НЕ + * заполняем — возвращаем в *dirty. + * + * РЕАЛИЗАЦИЯ — колонками, через _bgi_scroll_cols_raw (вертикальный режим + * accel): колонку читаем с Port_Y=ys, пишем с Port_Y=yd (= ys+dy) — сдвиг + * по Y без буфера-посредника (STOP между read и write делает промежуточный + * OUT Port_Y безопасным; см. шапку leaf'а). Один проход, вдвое быстрее + * прежнего grab→blit-через-буфер. Ширину >255 режем на банды колонок + * (leaf сам делит их на DI-скобки по 16). Высота колонки h = area->h−|dy| + * (≤256, 0=256 у accel). Перекрытия src/dst нет: стороны в разных страницах. + */ + +#include "../include/sprite.h" +#include "../_bgi.h" + +void gfx_scroll_v(const gfx_rect_t *area, int16_t dy, gfx_rect_t *dirty) { + + uint8_t saved = gfx_get_bank(); + uint16_t ady = (dy < 0) ? (uint16_t)(-dy) : (uint16_t)dy; + /* Строку чтения (ys) сдвигаем вниз на |dy| при сдвиге вверх, строку + * записи (yd) — вниз на dy при сдвиге вниз; края в пределах area. */ + uint8_t ys = (uint8_t)(area->y + ((dy < 0) ? ady : 0)); + uint8_t yd = (uint8_t)(area->y + ((dy > 0) ? ady : 0)); + uint16_t hb = area->h - ady; /* строк к переносу (≤256) */ + + if (hb) { /* 0 = переносить нечего */ + uint8_t hblk = (uint8_t)hb; /* размер accel-блока: 256 => 0 */ + uint16_t w = area->w; + uint16_t src = _gfx_addr_shadow_base + area->x; + uint16_t dst = _gfx_addr_base + area->x; + + gfx_set_bank(GFX_BANK_NORMAL); + _bgi_begin(); + + /* >255 колонок — банды (leaf 8-битный по числу колонок; 0=выход, + * поэтому ≤255, а не 256). Высота колонки одна на весь проход. */ + while (w) { + uint8_t cw = (w > 255) ? 255 : (uint8_t)w; + _bgi_scroll_cols_raw(src, dst, cw, hblk, ys, yd); + src += cw; dst += cw; w -= cw; + } + + _bgi_end(); + gfx_set_bank(saved); + } + + if (dirty) { + dirty->x = area->x; + dirty->y = (dy >= 0) ? area->y : (int16_t)(area->y + area->h - ady); + dirty->w = area->w; + dirty->h = ady; + } +} diff --git a/libbgi/common/gfx_set_draw_page.c b/libbgi/common/gfx_set_draw_page.c index 04d9b30..b0641fa 100644 --- a/libbgi/common/gfx_set_draw_page.c +++ b/libbgi/common/gfx_set_draw_page.c @@ -10,5 +10,11 @@ void gfx_set_draw_page(uint8_t page) _gfx_draw_page = page & 1; /* Прямые константы короче (0xC000 + (cond ? 0x140 : 0)) на 3 * инструкции Z80 — SDCC не сворачивает сложение констант. */ - _gfx_addr_base = _gfx_draw_page ? 0xC140 : 0xC000; + if(_gfx_draw_page) { + _gfx_addr_base = 0xC140; + _gfx_addr_shadow_base = 0xC000; + } else { + _gfx_addr_base = 0xC000; + _gfx_addr_shadow_base = 0xC140; + } } diff --git a/libbgi/include/sprite.h b/libbgi/include/sprite.h index a3df7e1..bb8133a 100644 --- a/libbgi/include/sprite.h +++ b/libbgi/include/sprite.h @@ -308,4 +308,16 @@ void atlas_sprite_init(sprite_t *s, const atlas_t *a, uint8_t idx); void gfx_w0_map(uint8_t page); void gfx_w0_unmap(void); +typedef struct { + int16_t x, y; + uint16_t w, h; +} gfx_rect_t; + +/* Скролл региона area из НЕактивной страницы в активную со сдвигом на + * dx/dy (0 = чистая копия/heal, >0 = в сторону увеличения координат, + * <0 = уменьшения). Открывшуюся полосу |d| НЕ заполняют — возвращают + * в *dirty (может быть NULL). Банк 0x50: копия = скролл + heal цели. */ +void gfx_scroll_h(const gfx_rect_t *area, int16_t dx, gfx_rect_t *dirty); +void gfx_scroll_v(const gfx_rect_t *area, int16_t dy, gfx_rect_t *dirty); + #endif diff --git a/runtime/crt0_banked.s b/runtime/crt0_banked.s index aa4df32..6d19522 100644 --- a/runtime/crt0_banked.s +++ b/runtime/crt0_banked.s @@ -121,6 +121,17 @@ w2_join: ld (_estex_block_id), a ld (_estex_startup_ix), ix +.ifdef W3_RESIDENT + ;; --w3 в HUGE: резидентный код окна W3 (0xC000) — часть HOME-образа, + ;; замаплен DSS. Загрузка банков ниже подменяет W3 своими страницами; + ;; запомним физ-страницу резидента, чтобы вернуть её дефолтом после + ;; цикла — тогда прямые вызовы в резидентный W3-код снова корректны. + ;; (Трамплин bank.s сам сохраняет/восстанавливает W3 на каждый __banked + ;; вызов, поэтому дефолтная страница обязана быть резидентной.) + in a, (#0xE2) + ld (_w3_resident_page), a +.endif + ;; ---- Allocate _n_banks pages via ESTEX GETMEM ---- ld a, (_n_banks) or a, a @@ -182,6 +193,14 @@ load_bank_done: rst #0x10 ;; Ignore CF from close. +.ifdef W3_RESIDENT + ;; Вернуть резидентную страницу W3 дефолтом (цикл выше оставил в W3 + ;; последний банк). После этого прямые call'ы в резидентный код 0xC0xx + ;; корректны, а трамплины будут сохранять/возвращать именно её. + ld a, (_w3_resident_page) + out (#0xE2), a +.endif + skip_bank_load: ;; ---- Standard SDCC init path ---- call gsinit @@ -411,6 +430,10 @@ _estex_block_id:: .ds 1 _bank_block_id:: .ds 1 +.ifdef W3_RESIDENT +_w3_resident_page: + .ds 1 +.endif .area _DATA _argc:: diff --git a/tests/w3probe/Makefile b/tests/w3probe/Makefile new file mode 100644 index 0000000..ebebd01 --- /dev/null +++ b/tests/w3probe/Makefile @@ -0,0 +1,42 @@ +# w3probe — проверка --w3 (резидентный код окна W3, прямой вызов без +# трамплинов) во ВСЕХ режимах памяти. Все пробы переиспользуют w3res.c +# (резидентный W3-модуль: w3_show/w3_probe/w3_sig). +# +# small : код 0x4100 (W1/W2), W3 резидент. w3probe.exe +# tiny : код 0x8100 (W2), W1 не исп., W3 резидент. w3tiny.exe +# big : код 0x8100 (W2), банки в W1, W3 резидент. w3big.exe +# huge : код 0x4100, W3 ДЕЛЯТ резидент + трамплин-банки. w3huge.exe +# +# Проверено в MAME (2026-07-21): все режимы работают, включая huge — +# резидент↔W3-банк через трамплин в W1, crt0_banked возвращает резидентную +# страницу дефолтом. Раскладка страниц: tiny W1=FF (не исп.), big/huge W1=F1. + +PROJ_ROOT := $(abspath $(CURDIR)/../..) +CC := $(PROJ_ROOT)/bin/sprinter-cc +MAKE_HDD := $(PROJ_ROOT)/toolchain/make_hdd.sh +HDD_IMG := $(PROJ_ROOT)/mame/v306/IMG/test_hdd.chd + +EXES := w3probe.exe w3tiny.exe w3big.exe w3huge.exe + +all: $(EXES) + +w3probe.exe: w3probe.c w3res.c + $(CC) --memory small --w3 w3res.c -o $@ w3probe.c + +w3tiny.exe: w3tiny.c w3res.c + $(CC) --memory tiny --w3 w3res.c -o $@ w3tiny.c + +w3big.exe: w3big.c w3res.c bigbank.c + $(CC) --memory big --w3 w3res.c --bank 1=bigbank.c -o $@ w3big.c + +w3huge.exe: w3huge.c w3res.c w3huge_res.c hugebank.c + $(CC) --memory huge --w3 w3res.c --w3 w3huge_res.c --bank 1=hugebank.c -o $@ w3huge.c + +# Упаковать все четыре на HDD (D:) для прогона в MAME. После — рестарт MAME. +hdd: $(EXES) + $(MAKE_HDD) $(HDD_IMG) $(EXES) + +clean: + rm -rf .sprinter-cc-* $(EXES) + +.PHONY: all hdd clean diff --git a/tests/w3probe/bigbank.c b/tests/w3probe/bigbank.c new file mode 100644 index 0000000..93299b1 --- /dev/null +++ b/tests/w3probe/bigbank.c @@ -0,0 +1,12 @@ +/* bigbank.c — свапаемый банк в W1 (big mode, трамплин через порт 0xA2). + * Отдельно от резидентного W3-кода (w3res.c) — проверяем сосуществование. */ + +#include +#include +#include + +void bigbank_func(void) __banked +{ + printf("W1-BANK: bigbank_func @ ~0x4xxx (trampoline), W1 phys=0x%02X\n", + _io_page_w1); +} diff --git a/tests/w3probe/hugebank.c b/tests/w3probe/hugebank.c new file mode 100644 index 0000000..c785fe7 --- /dev/null +++ b/tests/w3probe/hugebank.c @@ -0,0 +1,13 @@ +/* hugebank.c — СВАПАЕМЫЙ банк в окне W3 (huge mode, трамплин через 0xE2). + * Отдельная страница, подменяет резидентный код на время __banked-вызова; + * трамплин обязан вернуть резидентную страницу по выходу. */ + +#include +#include +#include + +void hugebank_func(void) __banked +{ + printf("W3-BANK: hugebank_func (trampoline), W3 phys now=0x%02X\n", + _io_page_w3); +} diff --git a/tests/w3probe/w3big.c b/tests/w3probe/w3big.c new file mode 100644 index 0000000..587358e --- /dev/null +++ b/tests/w3probe/w3big.c @@ -0,0 +1,26 @@ +/* w3big.c — проба --w3 в режиме big. + * big: код 0x8100 (W2), свапаемые банки в W1 (трамплины), W3 свободно под + * резидент. Проверяем сосуществование: W1-банк (трамплин) + W3-резидент + * (прямой вызов) в одной программе. */ + +#include +#include +#include + +/* crt0_banked читает это ДО gsinit — только `const`. */ +const uint8_t n_banks = 1; + +void bigbank_func(void) __banked; /* W1-банк, трамплин */ +extern uint8_t w3_probe(uint8_t x); /* W3-резидент, прямо */ +extern void w3_show(void); + +int main(void) +{ + printf("BIG : main @ 0x%04X | W1=%02X W2=%02X W3=%02X\n", + (unsigned)&main, _io_page_w1, _io_page_w2, _io_page_w3); + bigbank_func(); /* W1 через трамплин */ + w3_show(); /* W3 напрямую */ + printf("BIG : w3_probe(0x42)=0x%02X (expect 0xBD), exit to DSS.\n", + (unsigned)w3_probe(0x42)); + return 0; +} diff --git a/tests/w3probe/w3huge.c b/tests/w3probe/w3huge.c new file mode 100644 index 0000000..86ae070 --- /dev/null +++ b/tests/w3probe/w3huge.c @@ -0,0 +1,33 @@ +/* w3huge.c — проба --w3 в режиме huge (самый сложный случай). + * huge: код 0x4100 (W1/W2), окно W3 ДЕЛЯТ резидентный код (0xC000, прямой + * вызов) и свапаемые трамплин-банки. crt0_banked захватывает резидентную + * страницу и возвращает её дефолтом после загрузки банков; трамплин + * сохраняет/восстанавливает W3 на каждый __banked-вызов. + * + * Ключевая проверка: w3_show() (резидент) вызывается ДО и ПОСЛЕ + * hugebank_func() (банк). Второй вызов проходит только если трамплин + * вернул резидентную страницу в W3. */ + +#include +#include +#include + +const uint8_t n_banks = 1; /* один свапаемый W3-банк */ + +void hugebank_func(void) __banked; /* W3-банк, трамплин */ +extern uint8_t w3_probe(uint8_t x); /* W3-резидент, прямой вызов */ +extern void w3_show(void); /* W3-резидент, прямой вызов */ +extern void w3_resident_calls_bank(void); /* W3-резидент, зовёт банк */ + +int main(void) +{ + printf("HUGE: main @ 0x%04X | W1=%02X W2=%02X W3(resident)=%02X\n", + (unsigned)&main, _io_page_w1, _io_page_w2, _io_page_w3); + w3_show(); /* резидент напрямую (до банка) */ + hugebank_func(); /* HOME → W3-банк (свап+возврат) */ + w3_resident_calls_bank(); /* резидент → W3-банк (трамплин W1) */ + w3_show(); /* резидент СНОВА — возврат стр. */ + printf("HUGE: w3_probe(0x42)=0x%02X (expect 0xBD), exit to DSS.\n", + (unsigned)w3_probe(0x42)); + return 0; +} diff --git a/tests/w3probe/w3huge_res.c b/tests/w3probe/w3huge_res.c new file mode 100644 index 0000000..c6870c6 --- /dev/null +++ b/tests/w3probe/w3huge_res.c @@ -0,0 +1,26 @@ +/* w3huge_res.c — второй резидентный W3-модуль для huge-пробы. + * + * Содержит РЕЗИДЕНТНУЮ функцию (W3CODE @0xC000, прямой вызов), которая + * сама зовёт W3 __banked-функцию (свапаемый банк, трамплин). Этот случай + * ДОЛЖЕН работать: трамплин ___sdcc_bcall_ehl живёт в _CODE (W1, всегда + * замаплен) — он сохраняет резидентную страницу W3, маппит банк, а по + * возврату восстанавливает резидента. Адрес возврата (0xC0xx) снова + * валиден, и управление корректно возвращается в резидентную функцию. + * + * (Контраст с запретом «из __banked-контекста нельзя дёрнуть резидент»: + * здесь наоборот — резидент зовёт банк, и это ок.) */ + +#include +#include +#include + +void hugebank_func(void) __banked; /* W3-банк, трамплин */ + +void w3_resident_calls_bank(void) +{ + printf("W3-RES: w3_resident_calls_bank @ 0x%04X -> calling W3 __banked...\n", + (unsigned)&w3_resident_calls_bank); + hugebank_func(); /* резидент → банк через трамплин W1 */ + printf("W3-RES: returned into resident OK (W3 phys now=0x%02X)\n", + _io_page_w3); +} diff --git a/tests/w3probe/w3probe.c b/tests/w3probe/w3probe.c new file mode 100644 index 0000000..2a3725a --- /dev/null +++ b/tests/w3probe/w3probe.c @@ -0,0 +1,27 @@ +/* w3probe.c — явная проба подхода A для --w3. + * + * main (W1/W2) зовёт функцию w3_show() в W3, которая печатает свой + * собственный адрес (должен быть 0xC0xx) через printf. Затем main + * проверяет прямой вызов с возвратом и ЧИСТО выходит в DSS (return 0), + * чтобы вернулось приглашение шелла. */ + +#include +#include + +extern const uint8_t w3_sig; /* rodata в W3 */ +extern uint8_t w3_probe(uint8_t x); /* код в W3, прямой вызов */ +extern void w3_show(void); /* код в W3, зовёт printf */ + +int main(void) +{ + printf("HOME: main @ 0x%04X | w3_probe @ 0x%04X | w3_show @ 0x%04X\n", + (unsigned)&main, (unsigned)&w3_probe, (unsigned)&w3_show); + + w3_show(); /* W3-код печатает свой адрес */ + + printf("HOME: w3_probe(0x42) = 0x%02X (expect 0xBD)\n", + (unsigned)w3_probe(0x42)); + + printf("HOME: done, exiting to DSS.\n"); + return 0; /* чистый выход → приглашение DSS */ +} diff --git a/tests/w3probe/w3res.c b/tests/w3probe/w3res.c new file mode 100644 index 0000000..9615d7d --- /dev/null +++ b/tests/w3probe/w3res.c @@ -0,0 +1,25 @@ +/* w3res.c — резидентный модуль окна W3 (проба подхода A, явная версия). + * + * Компилируется с --codeseg W3CODE --constseg W3CODE (БЕЗ --dataseg), + * линкуется по -Wl-b_W3CODE=0xC000. Здесь только код + rodata. */ + +#include +#include + +/* rodata в W3 (для проверки, что область реально по 0xC000). */ +const uint8_t w3_sig = 0x5A; + +/* Функция, живущая в W3. Печатает СВОЙ адрес через printf — а printf + * лежит в libc (_CODE = W1/W2). Тем самым проверяем оба направления: + * W3-код исполняется по 0xC0xx И умеет звать код в W1/W2 обычным call'ом. */ +void w3_show(void) +{ + printf("W3 : w3_show @ 0x%04X | w3_sig @ 0x%04X = 0x%02X\n", + (unsigned)&w3_show, (unsigned)&w3_sig, (unsigned)w3_sig); +} + +/* Прямой вызов с возвратом значения: x^0xFF. */ +uint8_t w3_probe(uint8_t x) +{ + return (uint8_t)(x ^ 0xFF); +} diff --git a/tests/w3probe/w3tiny.c b/tests/w3probe/w3tiny.c new file mode 100644 index 0000000..b4c1990 --- /dev/null +++ b/tests/w3probe/w3tiny.c @@ -0,0 +1,30 @@ +/* w3tiny.c — проба --w3 в режиме tiny. + * tiny: код с 0x8100 (W2), W1 не используется, W3 свободно под резидент. + * Образ тянется 0x8100..0xC0xx (2 страницы) → DSS маппит W2 и W3. */ + +#include +#include + +extern uint8_t w3_probe(uint8_t x); +extern void w3_show(void); + +uint8_t w1_p, w2_p, w3_p; /* физ-страницы окон (заполняются asm) */ + +int main(void) +{ + __asm + in a,(#0xA2) + ld (_w1_p), a + in a,(#0xC2) + ld (_w2_p), a + in a,(#0xE2) + ld (_w3_p), a + __endasm; + + printf("TINY: main @ 0x%04X | pages W1=%02X W2=%02X W3=%02X\n", + (unsigned)&main, w1_p, w2_p, w3_p); + w3_show(); + printf("TINY: w3_probe(0x42)=0x%02X (expect 0xBD), exit to DSS.\n", + (unsigned)w3_probe(0x42)); + return 0; +} diff --git a/toolchain/check_banks.py b/toolchain/check_banks.py index 3295e1c..fdd9f1a 100755 --- a/toolchain/check_banks.py +++ b/toolchain/check_banks.py @@ -1,65 +1,140 @@ #!/usr/bin/env python3 """ -Parse an SDCC .map file produced by sdldz80 and verify that every named -bank fits inside its 16 KB window. +Отчёт по раскладке памяти образа + проверка лимитов, по .map-файлу +sdldz80. Печатается для ЛЮБОЙ модели памяти (не только при --bank): -The Sprinter toolchain expects each `_BANKn` area (with n >= 1) to occupy -at most 16384 bytes — that is the size of CPU window 3 where banked code -runs at execution time. The SDCC linker itself does not enforce this -limit, so we catch it post-link. + - _CODE — основной код (W1 0x4100 или W2 0x8100), размер, конец; + - данные — _DATA/_BSS/_INITIALIZED/… цепочкой за кодом, окно, конец; + - статика/куча/стек — где кончается статика и сколько свободно под кучу + (до ___sdcc_heap_end) и стек; + - _W3CODE — резидентный код окна W3 (--w3), если есть; + - _BANKn — банки (big/huge), каждый ≤ 16 КБ (размер окна W3/W1). -We also surface the HOME budget: anything left between the end of _CODE -and the start of window 2 (0x8000) is leftover space for adding code/data -without banking. +Раскладка режимов — docs/memory-management.md §4. Линкер сам лимиты не +проверяет — ловим здесь пост-линк. -Usage: check_banks.py +Usage: check_banks.py [--mode MODE] [--heap-top 0xNNNN] + [--stack 0xNNNN] -Exits non-zero with a clear message if any bank is over its limit. +Выходит с ненулевым кодом, если банк не влез в окно или статика заехала +за потолок кучи/окна. """ +import argparse import re import sys -BANK_LIMIT = 16 * 1024 -# HOME upper bound depends on layout: -# HUGE: CODE at 0x4100 (W1), HOME may spill into W2 → ceiling 0xC000 -# BIG: CODE at 0x8100 (W2), HOME stays in W2 → ceiling 0xC000 -# tiny/small: same ceiling — anything in W3 is bank territory. -# We pick the ceiling per-image based on where _CODE lives, so we don't -# falsely flag W2-resident code as "spilled into stack/heap". -HOME_CEILING = 0xC000 +BANK_LIMIT = 16 * 1024 # окно W3/W1 под банк +W_CEILING = 0xC000 # верх W2 (дальше — только W3/банки) + +# Области .map, составляющие HOME (код + данные), в порядке цепочки линкера. +# Всё, что не банк и не резидент W3 и лежит < 0xC000. +_BANK_RE = re.compile(r"_BANK\d+$") + + +def win_label(addr): + """Окно по адресу (0-based физического образа игнорируем — берём Z80-view).""" + a = addr & 0xFFFF + if 0x4000 <= a < 0x8000: + return "W1" + if 0x8000 <= a < 0xC000: + return "W2" + if 0xC000 <= a <= 0xFFFF: + return "W3" + return "??" def parse_map(path): - """ - Returns a dict {area_name: (addr, size)} for area lines like: - _CODE 00004100 00000313 = ... - """ + """{area_name: (addr, size)} для строк вида `_CODE 00004100 00000313 = ...`.""" line_re = re.compile(r"^\s*(_\w+)\s+([0-9A-Fa-f]{8})\s+([0-9A-Fa-f]{8})\s*=") areas = {} with open(path) as f: for ln in f: m = line_re.match(ln) - if not m: - continue - name = m.group(1) - addr = int(m.group(2), 16) - size = int(m.group(3), 16) - areas[name] = (addr, size) + if m: + areas[m.group(1)] = (int(m.group(2), 16), int(m.group(3), 16)) return areas -def main(): - if len(sys.argv) != 2: - sys.exit("usage: check_banks.py ") +MODE_DESC = { + "tiny": "CODE+DATA в W2 (0x8100)", + "small": "CODE в W1 (0x4100), DATA→W2", + "big": "CODE+DATA в W2 (0x8100), банки в W1", + "huge": "CODE в W1 (0x4100), DATA→W2, банки в W3", + "manual": "ручная раскладка", +} - areas = parse_map(sys.argv[1]) + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("mapfile") + ap.add_argument("--mode", default="") + ap.add_argument("--heap-top", default="0xBB00") + ap.add_argument("--stack", default="0xBFFE") + args = ap.parse_args() + + areas = parse_map(args.mapfile) + heap_top = int(args.heap_top, 0) + stack_top = int(args.stack, 0) fails = [] - bank_names = sorted( - n for n in areas if re.fullmatch(r"_BANK\d+", n) - ) + desc = MODE_DESC.get(args.mode, "") + hdr = f"memory: {args.mode}" if args.mode else "memory:" + if desc: + hdr += f" — {desc}" + print(hdr) + + # --- HOME: код + данные (всё < 0xC000, кроме банков) -------------- + home = {n: v for n, v in areas.items() + if not _BANK_RE.match(n) and n != "_W3CODE" and (v[0] & 0xFFFF) < W_CEILING} + + if "_CODE" in areas: + caddr, csize = areas["_CODE"] + cend = caddr + csize + print(f" _CODE @ 0x{caddr & 0xFFFF:04X} size {csize:>6} " + f"({win_label(caddr)}) → 0x{cend & 0xFFFF:04X}") + + # данные = все HOME-области, кроме _CODE (цепляются за кодом) + data = {n: v for n, v in home.items() if n != "_CODE"} + if data: + dstart = min(v[0] for v in data.values()) + dend = max(v[0] + v[1] for v in data.values()) + print(f" данные @ 0x{dstart & 0xFFFF:04X} size {dend - dstart:>6} " + f"({win_label(dstart)}) → 0x{dend & 0xFFFF:04X}" + f" [_DATA/_BSS/_INITIALIZED/…]") + else: + dend = cend + + # статика/куча/стек + static_end = max(v[0] + v[1] for v in home.values()) + heap_free = heap_top - static_end + stack_room = stack_top + 1 - heap_top + print(f" статика до 0x{static_end & 0xFFFF:04X} — куча " + f"0x{static_end & 0xFFFF:04X}..0x{heap_top & 0xFFFF:04X} ({heap_free} Б), " + f"стек 0x{heap_top & 0xFFFF:04X}..0x{stack_top & 0xFFFF:04X} ({stack_room} Б)") + if static_end > heap_top: + print(f" ОШИБКА: статика заехала за heap_top 0x{heap_top & 0xFFFF:04X} " + f"(нет места под кучу/стек)") + fails.append(("_HOME", static_end)) + elif static_end > W_CEILING: + print(f" ОШИБКА: статика вышла за 0x{W_CEILING:04X}") + fails.append(("_HOME", static_end)) + + # --- Резидентный код окна W3 (--w3) ------------------------------- + if "_W3CODE" in areas: + waddr, wsize = areas["_W3CODE"] + wend = waddr + wsize + wfree = 0x10000 - wend + marker = "OK" + if wend > 0x10000: + marker = "OVERFLOW" + fails.append(("_W3CODE", wsize)) + print(f" _W3CODE @ 0x{waddr & 0xFFFF:04X} size {wsize:>6} " + f"(резидент W3) → 0x{wend & 0xFFFF:04X}, {wfree} Б свободно до 0x10000 {marker}") + + # --- Банки (big/huge) --------------------------------------------- + bank_names = sorted(n for n in areas if _BANK_RE.match(n)) for name in bank_names: addr, size = areas[name] pct = size * 100.0 / BANK_LIMIT @@ -67,24 +142,18 @@ def main(): if size > BANK_LIMIT: marker = "OVERFLOW" fails.append((name, size)) - print(f" {name:<12} @ 0x{addr:08X} size {size:>5} / {BANK_LIMIT} ({pct:5.1f}%) {marker}") - - # HOME budget — _CODE can land in W1 (huge/small) or W2 (big/tiny). - if "_CODE" in areas: - addr, size = areas["_CODE"] - end = addr + size - budget_remaining = HOME_CEILING - end - which_window = "W2 (0x8000-0xBFFF)" if addr >= 0x8000 else "HOME (0x4100-0xBFFF)" - print(f" _CODE @ 0x{addr:08X} size {size:>5} → ends at 0x{end:04X}, " - f"{which_window} has {budget_remaining} bytes free before 0x{HOME_CEILING:04X}") - if budget_remaining < 0: - print(f" ERROR: _CODE extends past 0x{HOME_CEILING:04X} (stack/heap territory)") - fails.append(("_CODE", size)) + # виртуальный адрес: старший байт = номер банка, младшие 16 — окно + free = BANK_LIMIT - size + print(f" {name:<12} @ 0x{addr:08X} size {size:>6} / {BANK_LIMIT} " + f"({pct:4.1f}%) → {free} Б свободно {marker}") if fails: print() for name, size in fails: - print(f" {name} too big: {size} bytes (limit {BANK_LIMIT})") + if name in ("_HOME", "_W3CODE"): + print(f" {name}: раскладка не влезла (конец 0x{size & 0xFFFF:04X})") + else: + print(f" {name} слишком большой: {size} Б (лимит {BANK_LIMIT})") sys.exit(1) diff --git a/toolchain/mkexe/mkexe.c b/toolchain/mkexe/mkexe.c index 888bf9c..b126c24 100644 --- a/toolchain/mkexe/mkexe.c +++ b/toolchain/mkexe/mkexe.c @@ -277,11 +277,14 @@ int main(int argc, char **argv) { uint32_t pad_byte = 0xFF; uint32_t bank_base = DEFAULT_BANK_BASE; int verbose = 0; + int allow_w3_home = 0; /* -W: HOME может тянуться в W3 при W3-банках + (резидентный код --w3 в HUGE делит окно) */ for (int i = 1; i < argc; i++) { const char *a = argv[i]; if (!strcmp(a, "-h") || !strcmp(a, "--help")) { usage(stdout); return 0; } if (!strcmp(a, "-v")) { verbose = 1; continue; } + if (!strcmp(a, "-W")) { allow_w3_home = 1; continue; } if (!strcmp(a, "-o") && i + 1 < argc) { out_path = argv[++i]; continue; } if (!strcmp(a, "-L") && i + 1 < argc) { if (parse_addr(argv[++i], &load_addr) < 0) { fprintf(stderr, "mkexe: bad -L\n"); return 1; } @@ -386,12 +389,17 @@ int main(int argc, char **argv) { } } - /* HOME must not extend past 0xBFFF (W3 = bank territory in HUGE, - free in BIG but still off-limits to HOME). */ - if (home->hi >= 0xC000u) { + /* HOME может тянуться в W3 (0xC000..0xFFFF) — это резидентный код + окна W3 (--w3, подход A): DSS грузит непрерывный образ и маппит + W1/W2/W3. Запрещаем ТОЛЬКО когда W3 реально занят трамплин-банками + (HUGE: bank_base=0xC000, max_bank>0) — тогда HOME и банки + столкнулись бы в одном окне. */ + if (home->hi >= 0xC000u && max_bank > 0 && bank_base == DEFAULT_BANK_BASE + && !allow_w3_home) { fprintf(stderr, - "mkexe: HOME image extends to 0x%04X, past window 2 end (0xBFFF).\n" - " Code grew too big for HOME — move some .c files into a bank.\n", + "mkexe: HOME image extends to 0x%04X, into W3 (0xC000+) which\n" + " holds trampoline banks in HUGE mode — collision.\n" + " Move some .c files into a bank, or pass -W for --w3 residents.\n", home->hi); return 2; }