95c22be9bd
Скролл региона 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 <noreply@anthropic.com>
162 lines
6.7 KiB
Python
Executable File
162 lines
6.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Отчёт по раскладке памяти образа + проверка лимитов, по .map-файлу
|
|
sdldz80. Печатается для ЛЮБОЙ модели памяти (не только при --bank):
|
|
|
|
- _CODE — основной код (W1 0x4100 или W2 0x8100), размер, конец;
|
|
- данные — _DATA/_BSS/_INITIALIZED/… цепочкой за кодом, окно, конец;
|
|
- статика/куча/стек — где кончается статика и сколько свободно под кучу
|
|
(до ___sdcc_heap_end) и стек;
|
|
- _W3CODE — резидентный код окна W3 (--w3), если есть;
|
|
- _BANKn — банки (big/huge), каждый ≤ 16 КБ (размер окна W3/W1).
|
|
|
|
Раскладка режимов — docs/memory-management.md §4. Линкер сам лимиты не
|
|
проверяет — ловим здесь пост-линк.
|
|
|
|
Usage: check_banks.py <path.map> [--mode MODE] [--heap-top 0xNNNN]
|
|
[--stack 0xNNNN]
|
|
|
|
Выходит с ненулевым кодом, если банк не влез в окно или статика заехала
|
|
за потолок кучи/окна.
|
|
"""
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
|
|
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):
|
|
"""{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 m:
|
|
areas[m.group(1)] = (int(m.group(2), 16), int(m.group(3), 16))
|
|
return areas
|
|
|
|
|
|
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": "ручная раскладка",
|
|
}
|
|
|
|
|
|
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 = []
|
|
|
|
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
|
|
marker = "OK"
|
|
if size > BANK_LIMIT:
|
|
marker = "OVERFLOW"
|
|
fails.append((name, 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:
|
|
if name in ("_HOME", "_W3CODE"):
|
|
print(f" {name}: раскладка не влезла (конец 0x{size & 0xFFFF:04X})")
|
|
else:
|
|
print(f" {name} слишком большой: {size} Б (лимит {BANK_LIMIT})")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|