zx81sd: nueva arquitectura para ZX81 + SD81 Booster#1090
Merged
Conversation
New target architecture that compiles ZX BASIC programs for the ZX81
with SD81 Booster expansion (512KB RAM, SD card, AY sound, FPGA video).
Key design decisions:
- Inherits zx48k runtime; only overrides files specific to SD81 hardware
- Two-stage boot: stage 1 at $6000 (external BASIC loader), stage 2 at $0100
- Flat binary ORG $0000 with RST vector table ($0000-$00FF) and 8KB page layout
- Spectrum HiRes mode: framebuffer at $C000 (block 6), attrs at $D800
- Sysvars mapped to $8000+ (blocks 4-5) instead of Spectrum ROM addresses
- SCREEN_ADDR / SCREEN_ATTR_ADDR as RAM pointers (not EQU constants) to
match the indirect addressing used by the inherited zx48k runtime
- CHARS pointer initialised to font_base - 256 (Spectrum convention)
- Bold/italic FLAGS2 check uses direct LD A,(FLAGS2) without PUSH/POP AF
to avoid destroying the Z flag from BIT before CALL NZ
New files:
src/arch/zx81sd/ backend (main.py, generic.py)
src/lib/arch/zx81sd/runtime/ sysvars, bootstrap, charset (INCBIN specfont.bin),
print, border, pause, vsync, paging, vectors, ...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- sysvars.asm/bootstrap.asm: add missing MASK_P sysvar between ATTR_P and ATTR_T so COPY_ATTR no longer corrupts INK/PAPER attributes. - pixel_addr.asm: rework to return bit position directly (matches the interface plot.asm/draw.asm expect) instead of a rotated bitmask. - vsync.asm: fix port ($AF) and semantics (bits 1-6 = pulse counter since last read, auto-resets on IN) per SD81 Booster hardware. - arch_config.asm: pull in pixel_addr.asm/po_gr_1.asm so ROM-call substitutes are available project-wide. - Add native ZX81 keyboard driver (io/keyboard/keyscan.asm) since no ROM is mapped at runtime: scans the $FEFE matrix directly and decodes to ASCII (not the ZX81 ROM's own charset/token codes, since this runtime prints via the Spectrum/ASCII charset). Wire it into INKEY$ (io/keyboard/inkey.asm) and a new stdlib/input.bas that replaces the Spectrum-ROM-dependent LAST_K/BEEPER polling. - beep.asm: key click using the SD81 Booster's emulated Spectrum ULA port ($FBh, bits 4-3), sharing a shadow byte with border.asm so border color and beeper bits don't clobber each other. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…DRAW arc mode Port the Spectrum ROM floating-point calculator to the zx81sd architecture as native runtime code (no Spectrum ROM available): - fp_calc.asm: CALCULATE engine (RST 28h + literal bytecode), FP stack sysvars, arithmetic, comparison, trig/log/exp/sqrt (series-based, as in the ROM), and STK-TO-A/STK-TO-BC/CD-PRMS1 for arc drawing. - stackf.asm: native __FPSTACK_PUSH/__FPSTACK_POP over FP_STKEND. - fp_tostr.asm / printf.asm / str.asm: PRINT and STR$ of FLOAT values (sign + integer part + up to 5 decimals, trailing zeros trimmed). - val.asm: VAL for FLOAT expressions. - draw3.asm: DRAW x,y,angle (arc mode) override, replacing fixed ROM addresses with the routines ported in fp_calc.asm. Includes optional trace instrumentation behind -D DRAW3_DEBUG. - pixel_addr.asm: fix register contract — PIXEL_ADDR now preserves DE like ROM PIXEL-ADD ($22AC). It used D as scratch, which corrupted the Bresenham Y coordinate saved in D' by draw.asm, breaking every non-horizontal DRAW line (found while debugging arc mode). - sysvars.asm / bootstrap.asm: FP calculator sysvars (STKBOT/STKEND/ BREG/MEM + stack and MEM area) and their initialization. - backend main.py: include fp_calc.asm unconditionally in the prologue (RST 28h vector must always exist). Verified on hardware/EightyOne against a real Spectrum reference: PLOT 100,100: DRAW 20,0,3.14159: DRAW 10,10,1.5708: CIRCLE 60,30,20 renders identically on both. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BEEP (sin ROM Spectrum): - io/sound/beeper.asm: bucle BEEPER de la ROM ($03B5) portado al puerto ULA $FB del SD81 (bits como el $FE del Spectrum), con el borde tomado de la copia sombra y sin EI final (el runtime corre con las interrupciones deshabilitadas). La entrada __BEEPER del compilador corrige el periodo precalculado por el frontend (que usa la constante del Spectrum, 437500 = 3.5MHz/8) a los 3.25 MHz del ZX81: HL' = HL*13/14 - 2 (406250/437500 = 13/14 exacto). - io/sound/beep.asm: comando BEEP de la ROM ($03F8) portado sobre el calculador FP propio: misma matematica (incluida la parte fraccionaria del semitono), tabla de semitonos de la ROM, y constante 406250 (= 3.25MHz/8). FIND-INT1/2 sustituidos por __FPSTACK_POP+__FTOU32REG. PLAY (chip A del ZonX, 3 canales): - stdlib/play.bas: copia de zx48k/stdlib/play.bas con 4 cambios documentados en cabecera: puertos ZonX (latch $CF, dato $0F), tabla de divisores regenerada para reloj AY de 1.625 MHz (26MHz/16 en la FPGA), CpuCyclesPerSecond = 3250000 para el tempo, y sin EI final. Verificado en EightyOne con analisis FFT de la salida de audio: beeper y AY al unisono en 440 Hz (LA4), escala y acorde de 3 canales correctos, duraciones exactas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mcu.bas — primitivas del protocolo del MCU (puertos $A7 datos / $AF reloj) con las transferencias en ensamblador (McuSend/McuRecv/ McuSendBlock/McuRecvBlock: camino critico de LOAD/SAVE/F_READ/F_WRITE) y wrappers para todos los comandos del manual: - Sistema: NOP, VERSION, GETBYTE, SETBYTE - Ficheros: PWD, CD, DEL, MKDIR, RMDIR, MOVE, COPY, LOAD, SAVE, TYPE, DIR, FREE (texto y binario), OPENDIR/GETROWLEN/GETROW - Handles (32 bits): F_OPEN (ASCII), F_OPEN_ZX81, F_SEEK, F_READ, F_WRITE, F_CLOSE — verificado contra el firmware (COMMANDS.cpp): el MCU asigna el handle y lo devuelve - Hardware: MC45, 128C/64C, FULLPAGING/HALFPAGING, RAM48, y el mapeador de memoria Map/MapGet (puerto $E7, valido en ambos modos de paginacion con una sola escritura) - Voz: SAY, BINARY_SAY - AY del MCU (chip 2): AY_SET_REG/AY_GET_REG/AY_PLAY (con conversion de octavas mayuscula<->video inverso como el interprete del MCU) - VGM y PEG completos; RTC y BAT - Extensiones no-MCU del BASIC del SD81: HexPoke (*HEX), StrInv/StrBold (*INV/*BOLD); *LDIR/*LDDR cubiertos por MemMove de la stdlib estandar Conversion automatica ASCII<->codigos ZX81 en los comandos de texto. joy.bas (comando 21) reescrito como capa fina sobre mcu.bas. Verificado en EightyOne(SD81): version, get/setbyte, pwd, save+load con verificacion byte a byte, del, free, rtc, bat, dir, AY2 por registros, AyPlay, joystick, mapeador y HexPoke/MemMove/StrInv. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sustituyen a los load.asm/save.asm de zx48k (rutinas de cinta de la ROM Spectrum, inutilizables sin ROM): ahora LOAD/SAVE/VERIFY ... CODE operan sobre la tarjeta SD mediante los comandos 9/10 del MCU. - io/sd81_mcu.asm: primitivas del protocolo del MCU para el runtime (send/recv con espera de reloj, bloques, RECV_VERIFY que compara sin escribir, RECV_SINK para descartar sobrante manteniendo el protocolo sincronizado, conversion ASCII->ZX81 de nombres). - load.asm: LOAD carga el fichero completo (longitud 0) o hasta N bytes (descarta el exceso; fichero mas corto = error). VERIFY compara sin escribir. Errores blandos como en cinta: ERR_NR = 26 (Tape loading) o 14 (nombre invalido) y retorna. - save.asm: SAVE guarda el bloque; longitud 0 = InvalidArg. Interfaz del compilador identica a zx48k (incluida la liberacion del string del nombre con MEM_FREE). Verificado en EightyOne(SD81): ciclo SAVE+LOAD con verificacion byte a byte, VERIFY correcto y con byte corrupto (ERR 26), y carga de fichero inexistente (ERR 26). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
La sysvar UDG apuntaba a __ZX81SD_CHARSET+896 asumiendo una fuente completa de 256 caracteres, pero specfont.bin solo cubre CHR$(32)- CHR$(127) (768 bytes): el puntero caia 128 bytes mas alla del final de la fuente, sobre el codigo del runtime que tocara despues en el enlazado. Cualquier programa que definiera UDGs (POKE USR CHR$(144+n)) machacaba ese codigo — p.ej. comecoquitos.bas corrompia CALC_TEST_ROOM de fp_calc y colgaba el sistema al primer uso del calculador. Fix: charset.asm reserva un area de 21 UDGs (CHR$(144)-CHR$(164), 168 bytes) tras la fuente, y SD81_INIT_SYSVARS apunta UDG a ella y la inicializa con copias de las letras A-U, igual que la ROM del Spectrum. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- keyscan.asm: las letras se decodifican ahora en minuscula, igual que el INKEY$ del Spectrum en modo L (el por defecto). Los programas de la epoca comparan INKEY$ con "o","p","q","a","y","n"... y con mayusculas no respondian (p.ej. examples/comecoquitos.bas). - po_gr_1.asm: reescrito con el algoritmo literal de PO-GR-2 ($0B3E, ROM Spectrum). La version anterior tenia un OR aplicado al registro equivocado (machacaba los bits de cuadrante y dejaba vacia la mitad inferior de CHR$(143), entre otros) y el mapeo izquierda/derecha de los cuadrantes invertido respecto a la ROM (bit0 = superior DERECHO). Verificado con comecoquitos.bas completo (jugable, identico al .tap de Spectrum) y un test de los 16 graficos CHR$(128)-CHR$(143). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… bloque 7 (WIP) - backend/main.py: heap_size/heap_address se asignaban con ADD_IF_NOT_DEFINED, pero Z80Backend.init() ya los definia antes -> el heap se quedaba inline en vez de en $8100. Fix: asignacion directa tras super().init(). - keyscan.asm: rediseno completo. Pulsacion directa = minuscula, SHIFT+letra = mayuscula (antes solo devolvia minuscula), SHIFT+"2" = CAPS LOCK persistente, "." como tecla normal con los simbolos clasicos del ZX81 accesibles como tecla muerta desde INPUT() (ver input.bas). - input.bas: composicion de simbolos ZX81 (tecla muerta "."+tecla) y doble punto = punto literal sin combo. - print.asm: __SCROLL_SCR ya no cae en la ROM del Spectrum ($0DFE) - la implementacion por buffer es ahora la unica rama. - stdlib/scroll.bas: nuevo override zx81sd (llamaba a PIXEL-ADD $22AC de la ROM, sustituido por PIXEL_ADDR propio). - stdlib/cb/maskedsprites.bas: nuevo override que porta MSFS al mapeador de memoria (bloque 7) en vez de BANKM/$7FFD del Spectrum. Sigue en proceso, aun no funciona del todo bien. - mcu.bas: _McuStreamPrint admite controlar el salto de linea final (para listados tipo DIR sin salto extra). - fzx_fonts/: fuentes FZX de ejemplo para la libreria de fuentes. - examples/sd81/filesystem.bas: demo de McuCd/McuDirPrint/McuLoad. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Añade docs/zx81sd/ (README, USO, PRECAUCIONES, CAMBIOS_BASIC, MAP) con la guia de uso de la arquitectura zx81sd, las precauciones a tener en cuenta al escribir/portar software (sysvars, contratos de registros, teclado del ZX81, ausencia de interrupciones, namespaces ASM, eliminacion de codigo muerto, metodologia de simulacion) y el catalogo de cambios de fuente ya necesarios en ejemplos oficiales. Añade a examples/sd81/ los ejemplos adaptados/probados hasta ahora: snake_sd81.bas, flights_sd81.bas, maskedsprites_sd81.bas (MSFS sobre el mapeador de memoria, aun en proceso), pong.bas y block7test.bas. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SD81 Booster (hardware/firmware), EightyOne Cross-platform (emulador usado en el desarrollo) y CPM_SD81. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
winscroll se cree portado/probado pero sin auditoria formal; putchars/ puttile sin auditar (sin ROM detectada a simple vista); screen.bas SI depende de ROM (STK_END/S_SCRNS_BC/RECLAIM2); print42/print64 SI dependen de sysvars Spectrum (UDG/ATTR_P) y son el siguiente objetivo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Overrides completos de la libreria compartida (Britlion): las sysvars fijas de la ROM del Spectrum (UDG $5C7B, CHARS-256 15360, ATTR_P 23693) se sustituyen por los sysvars propios de zx81sd, y las constantes de base de pantalla/atributos (antes literales de $4000/$5800) se parchean en tiempo de ejecucion via automodificacion de codigo, leyendo el byte alto real de SCREEN_ADDR/SCREEN_ATTR_ADDR. Bug real encontrado durante el port: un #include once <sysvars.asm> dentro de estos ficheros, si resultaba ser la primera inclusion de todo el programa, arrastraba bootstrap.asm/charset.asm (con su INCBIN del font completo) justo en el punto del fichero fuente donde se puso el include -- a nivel BASIC eso da "illegal preprocessor character", y dentro de un bloque ASM se emite como codigo ejecutable real (HALT en direccion basura). Fix: no incluir sysvars.asm desde estos ficheros, solo referenciar los simbolos con el prefijo .core. (confiando en que CLS/PRINT ya lo trajeron). Documentado en PRECAUCIONES.md (nueva seccion 8) y CAMBIOS_BASIC.md (nueva seccion 9) como gotcha general para cualquier libreria nueva que necesite sysvars propios. Verificado por simulacion (examples/sd81/print4264.bas): ambas cadenas de prueba se renderizan legibles pixel a pixel, sin corrupcion de memoria, con HALT normal en __END_PROGRAM. Pendiente de confirmar en emulador/hardware real. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- docs/zx81sd/README.md -> src/arch/zx81sd/README.md
- docs/zx81sd/{USO,PRECAUCIONES,CAMBIOS_BASIC,MAP}.md -> src/arch/zx81sd/doc/
- Copia del repositorio complementario (solo para pruebas, no fuente
canonica) las herramientas de empaquetado a src/arch/zx81sd/tools/:
split_sd81.py, zx81_p_loader.py, boot1.asm, boot1.bin.
Enlaces relativos corregidos en toda la documentacion movida. Probado
end-to-end (compilar + split_sd81.py) desde la nueva ubicacion.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix: the packaged program is loaded with LOAD FAST "<PREFIX>" (nothing to select afterwards), not LOAD "" + select. Corrected in USO.md (now USAGE.md) and split_sd81.py's docstring. Translate all zx81sd documentation and tooling to English, including file names: - doc/USO.md -> doc/USAGE.md - doc/PRECAUCIONES.md -> doc/PRECAUTIONS.md - doc/CAMBIOS_BASIC.md -> doc/BASIC_CHANGES.md - doc/MAP.md (kept name, content translated) - README.md (content translated) - tools/split_sd81.py, tools/zx81_p_loader.py, tools/boot1.asm (docstrings/comments translated) - backend/main.py, backend/generic.py (comments translated) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…, F_*) New examples exercising the SD81 Booster MCU's command surface (stdlib/mcu.bas/joy.bas), all compile clean but not yet run on real hardware: - filesystem.bas (expanded): McuPwd/McuCd/McuDirPrint/McuMkdir/ McuRmdir/McuSave/McuCopy/McuMove/McuDel/McuFree - wavplayer.bas: McuLoad with a .WAV file (MCU plays it directly) - vgmplayer.bas: McuPlayVgm/McuContVgm/McuPauseVgm/McuStopVgm - pegdemo.bas: McuLoadPeg/McuPlayPeg/McuStopPeg (byte encoding unconfirmed on hardware, see BASIC_CHANGES.md) - joystick.bas: Joy() programmable joystick mapping - randomaccess.bas: McuFOpenZx/McuFSeek/McuFRead/McuFWrite/McuFClose Found along the way: stdlib/input.bas defines input() as a function (a$ = input(n)), not the native INPUT a$ statement; using the statement form after #include <input.bas> fails to compile. Documented in BASIC_CHANGES.md as a new pattern to watch for. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All six (filesystem, wavplayer, vgmplayer, pegdemo, joystick, randomaccess) tested OK, including pegdemo's PEG byte encoding. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- split_sd81.py: sort/organize the import block - zx81_p_loader.py: drop redundant int() around math.floor() (RUF046) - ruff format on the three files touched this session ruff check/mypy now clean project-wide; the only remaining ruff format finding (src/zxbpp/zxbpp.py) is pre-existing and unrelated to zx81sd, left untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Audited: WinScrollRight/Left/Up/Down have no Spectrum ROM dependency (everything reads SCREEN_ADDR/SCREEN_ATTR_ADDR dynamically), and the sysvars.asm dependency goes through #require (a link-time tag, not a textual #include), so it doesn't risk the INCBIN-inline bug found while porting print42.bas/print64.bas. Compiles clean and verified by simulation (screen/attributes fully touched, clean HALT at __END_PROGRAM). Pending confirmation on real hardware. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
putchars.bas: audited, no ROM dependency (SCREEN_ADDR/SCREEN_ATTR_ADDR read dynamically, #require "sysvars.asm" is a safe link-time tag) -- no override needed, used as-is. puttile.bas: needed an override -- hardcodes the screen/attribute base as immediate constants (add a,64 / add a,88, the Spectrum ROM's fixed $4000/$5800 high bytes) instead of reading SCREEN_ADDR/ SCREEN_ATTR_ADDR. Same bug class as print42.bas/print64.bas; fixed the same way (self-modifying code patches both constants once on entering putTile(), reading the real high bytes at that point). New example examples/sd81/putcharstile.bas exercises putChars/paint/ putTile. Verified by simulation with breakpoints right after each function returns (a naive end-of-program check looked broken due to an unrelated screen scroll triggered by the demo's own final PRINT AT landing on the last screen row -- moved to row 20, false alarm). Pending confirmation on real hardware. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Found on real hardware: activating Superfast HiRes Spectrum mode (POKE 2045,172) does not turn on Chroma81 colour by itself -- screen came up monochrome despite correct INK/PAPER/attributes. EightyOne shows colour regardless, which is an emulator bug (needs reporting/ fixing there separately, not something to work around here). Fix: boot1.asm now also writes to the Chroma81 port ($7FEF) right after activating Spectrum mode: ld bc, $7FEF / ld a, 39 / out (c), a (bit5=1 color on, bit4=0 char-code mode) boot1.bin reassembled with zxbasm.py (34 bytes, was 27 -- exactly the 3 new instructions). Shared stage 1 loader used by every zx81sd program: no program needs recompiling, just copy the updated BOOT1.BIN to the SD card. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Puerto del compilador Boriel ZX BASIC a hardware ZX81 + SD81 Booster (arquitectura
zx81sd).Resumen
zx81sdensrc/lib/arch/zx81sd/(runtime + stdlib), siguiendo la convencion de copiar en vez de modificar los ficheros dezx48kexistentes.Test plan