picoZ80 Guía Técnica

picoZ80 Guía Técnica

Esta guía documenta la arquitectura de hardware del picoZ80, la interfaz de bus PIO del RP2350, el modelo de memoria, la referencia de configuración JSON, el framework de dispositivos virtuales y los procedimientos de depuración. Está destinada a desarrolladores que deseen comprender los aspectos internos, escribir nuevos controladores, portar el firmware a una nueva máquina host o depurar problemas a nivel de firmware.
Para la configuración de usuario final y el uso de la interfaz web, consulte el picoZ80 User Manual. Para la descripción general del proyecto e instrucciones de compilación, consulte la página del proyecto picoZ80.

Arquitectura de Hardware

El picoZ80 integra cinco subsistemas en una sola PCB compacta diseñada para caber dentro del formato de un paquete DIP-40. Toda la lógica opera a 3.3V; la interfaz del bus Z80 se encarga de la conversión de niveles y la corriente de excitación para el bus de 5V del host.

Diagrama de Bloques del Sistema

┌─────────────────────────────────────────────────────────────────────────┐
│                         picoZ80 PCB                                     │
│                                                                         │
│  ┌────────────────────────────┐      ┌──────────────────────────────┐  │
│  │         RP2350B            │      │           ESP32-S3           │  │
│  │  (Cortex-M33, dual core)   │      │                              │  │
│  │                            │      │  ┌──────┐  ┌──────────────┐ │  │
│  │  Core 0: USB, file I/O,    │◄────►│  │  SD  │  │  Web Server  │ │  │
│  │          ESP32 relay       │ FSPI │  │ Card │  │  (Bootstrap) │ │  │
│  │  Core 1: Z80 bus hot loop  │ UART │  └──────┘  └──────────────┘ │  │
│  │                            │      │                              │  │
│  │  PIO 0,1,2: bus interface  │      │  WiFi ─── 802.11 b/g/n AP   │  │
│  │                            │      │           or Client mode     │  │
│  │  16MB SPI Flash            │      └──────────────────────────────┘  │
│  │  8MB PSRAM (SPI)           │                                         │
│  └────────────────────────────┘                                         │
│                │                                                         │
│       ┌────────┴────────┐                                               │
│       │ Z80 Bus Interface│                                               │
│       └────────┬────────┘                                               │
│                │ 5V bus (A0–A15, D0–D7, MREQ, IORQ, RD, WR...)         │
└────────────────┼────────────────────────────────────────────────────────┘
                 │
         ┌───────┴───────┐
         │  Host Z80     │
         │  DIP-40 socket│
         │  (legacy      │
         │   computer)   │
         └───────────────┘

Componentes Principales

Componente Dispositivo Función
MCU PrincipalRP2350B (QFN-80)Dual Cortex-M33, 150MHz (hasta 300MHz OC), 512KB SRAM, 12 state machines PIO, 48 pines GPIO
FlashW25Q128 (16MB SPI)Bootloader, slots de firmware duales, particiones de configuración
PSRAM8MB SPI PSRAM64 × 64KB bancos RAM/ROM para el espacio de direcciones Z80
CoprocesadorESP32-S3-PICO-1WiFi, tarjeta SD, servidor web, OTA
Hub USBCH334FHub USB, puente para actualización de firmware
Fuente de alimentaciónTLV62590BVConvertidor buck síncrono 5V → 3.3V

Asignación GPIO del RP2350B

El paquete QFN-80 del RP2350B proporciona 48 pines GPIO. El picoZ80 utiliza prácticamente todos los pines. La asignación está fijada en el diseño de la placa y se refleja en los programas PIO:
Rango GPIO Señales Dirección
GPIO 0–15 A0–A15 (Bus de Direcciones Z80) Salida (controlado por PIO)
GPIO 16–23 D0–D7 (Bus de Datos Z80) Bidireccional (PIO tri-state)
GPIO 24 MREQ Salida
GPIO 25 IORQ Salida
GPIO 26 RD Salida
GPIO 27 WR Salida
GPIO 28 M1 Salida
GPIO 29 RFSH Salida
GPIO 30 BUSREQ Entrada
GPIO 31 BUSACK Salida
GPIO 32 HALT Salida
GPIO 33 INT Entrada
GPIO 34 NMI Entrada
GPIO 35 WAIT Salida
GPIO 36 CLK Entrada (reloj del host)
GPIO 37 RESET Entrada
GPIO 38–41 ESP32 FSPI (CS, CLK, MOSI, MISO) SPI
GPIO 42–43 ESP32 UART (TX, RX) UART
GPIO 44–45 PSRAM SPI SPI
GPIO 46–47 USB (D+, D–) USB

Arquitectura de Firmware

El firmware del RP2350 está construido con el Raspberry Pi Pico SDK 2.x orientado a la plataforma RP2350-arm-s. El firmware se divide en dos ejecutables independientes: el Bootloader y la Aplicación.

Disposición de la Memoria Flash

Partición Rango de Direcciones Tamaño Contenido
Bootloader 0x10000000 – 0x1001FFFF 128KB Puente USB, actualización de firmware, selector de partición
App Slot 1 0x10020000 – 0x1051FFFF 5MB Firmware Z80 — aplicación activa (slot 1)
App Slot 2 0x10520000 – 0x10A1FFFF 5MB Firmware Z80 — aplicación activa (slot 2)
App Config 1 0x10A20000 – 0x10C9FFFF 2.5MB Imágenes ROM + JSON de configuración minificado (slot 1)
App Config 2 0x10CA0000 – 0x10F1FFFF 2.5MB Imágenes ROM + JSON de configuración minificado (slot 2)
General Config 0x10F20000 – 0x10FFEFFF 892KB Configuración principal, espacio temporal
Partition Table 0x10FFF000 – 0x11000000 4KB Número de slot activo, checksums, metadatos

Responsabilidades Dual-Core

Los dos núcleos Cortex-M33 tienen responsabilidades completamente separadas y se comunican mediante una cola de mensajes inter-core (queue_t). Esta separación asegura que el trabajo no en tiempo real del Core 0 nunca introduzca jitter en las transacciones del bus Z80 del Core 1.
Núcleo Responsabilidades
Core 0 Puente serie USB CDC; coordinación de actualización de firmware; E/S de archivos (retransmitida al ESP32 por UART); despacho de comandos ESP32 (cambios de imagen de disco, recargas de configuración, consultas de versión); gestión de particiones; despacho de mensajes inter-core.
Core 1 Bucle principal de emulación del bus Z80 — se ejecuta exclusivamente. Atiende los FIFOs PIO, resuelve cada transacción de bus contra el mapa de memoria y despacha a: hardware físico del host (PHYSICAL), PSRAM (RAM/ROM) o handler de dispositivo virtual (FUNC). El bucle interno se coloca en SRAM.

Interfaz de Bus PIO

La interfaz del bus Z80 está implementada completamente en ensamblador PIO del RP2350 (z80.pio). El RP2350 proporciona tres bloques PIO (PIO 0, PIO 1, PIO 2) cada uno con cuatro state machines — doce state machines en total, de las cuales el firmware Z80 utiliza las doce.
Los programas PIO se ejecutan independientemente de los núcleos Cortex-M33. La interfaz del bus continúa respondiendo de forma determinista incluso cuando el Core 1 está ocupado con accesos a PSRAM o llamadas a funciones de dispositivos virtuales. Las state machines se comunican mediante flags IRQ PIO en lugar de polling, eliminando la latencia entre máquinas.

Tabla de Programas PIO

PIO State Machine Programa Función
0 SM 0 z80_addr Emite la dirección de 16 bits (A0–A15) en el bus y señala el inicio del ciclo a SM 2.
0 SM 1 z80_data Controla o muestrea D0–D7 con control tri-state; se libera durante BUSRQ.
0 SM 2 z80_cycle Secuenciador de ciclo de bus de nivel superior — orquesta ciclos de fetch, lectura, escritura, E/S y refresco de DRAM.
0 SM 3 z80_fetch Ciclo de fetch de opcode (M1 + MREQ + RD).
1 SM 0 z80_mem_read Ciclo de lectura de memoria (MREQ + RD).
1 SM 1 z80_mem_write Ciclo de escritura de memoria (MREQ + WR).
1 SM 2 z80_io_read Ciclo de lectura de E/S (IORQ + RD).
1 SM 3 z80_io_write Ciclo de escritura de E/S (IORQ + WR).
2 SM 0 z80_busrq Gestiona BUSREQ/BUSACK; libera /IORQ, /MREQ, /RFSH, /M1, /HALT, /WR, /RD.
2 SM 1 z80_nmi Detecta la activación de NMI y señala al Core 1.
2 SM 2 z80_clk_sync Sincroniza las state machines PIO con la señal CLK del Z80 del host.
2 SM 3 z80_int_ack Gestiona ciclos de reconocimiento de interrupción (M1 + IORQ).

Convenciones de Señales IRQ PIO

La comunicación entre state machines utiliza flags IRQ PIO. El Core 1 monitoriza estos flags en el bucle principal para actuar en cada evento del bus:
IRQ Evento
IRQ 0 Dirección válida / inicio de ciclo — ha comenzado un nuevo ciclo de bus y A0–A15 están estables.
IRQ 1 Fase de datos — la dirección del bus de datos ha sido resuelta; D0–D7 deben ser controlados o muestreados.
IRQ 2 T1 detectado — el flanco de subida de T1 en el ciclo actual. Se utiliza para sincronizar operaciones internas con el reloj del host.
IRQ 3 Evento RESET — la línea RESET del host ha sido activada. El Core 1 debe reinicializar el estado de emulación.
IRQ 4 NMI detectado — la línea NMI del host ha sido activada.
IRQ 6 BUSRQ activo — el host ha activado BUSREQ; PIO está liberando el bus.

Generación de Wait States

El programa PIO z80_wait en PIO 2 SM 0 inserta wait states de ciclo T configurables en el bus del host mediante la activación de /WAIT. El número de wait states adicionales se controla por bloque de memoria o E/S mediante el parámetro tcycwait en config.json.
Los wait states son necesarios cuando el RP2350 necesita tiempo adicional para completar un acceso a PSRAM o una llamada a función de dispositivo virtual antes de presentar los datos al bus del host. El parámetro tcycsync habilita la sincronización T1 (z80_sync en PIO 2 SM 1), que bloquea la ventana de acceso a PSRAM al flanco de subida T1 de cada ciclo de bus, previniendo la deriva de temporización en aplicaciones que dependen del reloj del host para temporización precisa (cassette, bit-banging serie).

Arquitectura PIO — Cómo se Recrean los Ciclos de Bus

El subsistema de E/S Programable (PIO) del RP2350 es la tecnología clave que permite al picoZ80 recrear la temporización del bus Z80 con precisión de ciclo. Comprender cómo las state machines PIO trabajan juntas es esencial para cualquiera que modifique la interfaz del bus o depure problemas de temporización.

Fundamentos PIO del RP2350
Cada bloque PIO contiene cuatro state machines (SM) independientes que ejecutan pequeños programas desde una memoria compartida de 32 instrucciones. Las state machines se ejecutan independientemente de los núcleos Cortex-M33 a la frecuencia del reloj del sistema (hasta 300 MHz). Recursos PIO clave utilizados por el picoZ80:
  • TX FIFO — una cola de 4 entradas desde la CPU hacia la state machine. El código C en el Core 1 envía datos (direcciones, palabras de control, instrucciones inyectadas) al FIFO; el programa PIO los extrae usando out o pull.
  • RX FIFO — una cola de 4 entradas desde la state machine hacia la CPU. El PIO envía muestras del bus de datos al FIFO usando in; el Core 1 las lee después de que cada ciclo de bus se complete.
  • Flags IRQ — 8 flags (IRQ 0–7) compartidos entre todas las state machines dentro de un bloque PIO. Los flags también pueden verse entre bloques PIO (IRQ 0–3 en un bloque se mapean a IRQ 4–7 en bloques adyacentes). Las state machines usan irq set / irq wait / irq clear para sincronizarse entre sí y con el código C.
  • Registros scratch X e Y — dos registros de 32 bits por SM utilizados para contadores de bucle y valores temporales.
  • out exec — una instrucción especial que extrae un valor del TX FIFO y lo ejecuta como instrucción PIO. Este es el mecanismo por el cual el código C controla dinámicamente las secuencias de ciclo de bus (ver más abajo).
  • set pins / out pins — controlan pines GPIO directamente. set usa un valor inmediato de 5 bits; out desplaza datos del registro de desplazamiento de salida (OSR) hacia los pines.
  • in pins — muestrea los pines GPIO en el registro de desplazamiento de entrada (ISR), y luego auto-envía al RX FIFO.
  • wait gpio — detiene la SM hasta que un pin GPIO específico alcanza un nivel determinado. Se usa extensivamente para sincronizarse con la señal de reloj CLK del Z80 del host.
  • Side-set — permite que uno o dos pines GPIO se controlen como efecto lateral de cualquier instrucción, sin usar un ciclo de instrucción. El picoZ80 usa side-set de 2 bits para controlar /RD y /WR simultáneamente con otras operaciones.
  • JMP PIN — salto condicional basado en el nivel de un pin GPIO designado. Se usa para verificar /WAIT, BUSREQ, /NMI y /RESET.

El Mecanismo out exec — Inyección Dinámica de Instrucciones
La característica más distintiva del diseño PIO del picoZ80 es el uso de out exec, 16 en la state machine orquestadora z80_cycle. Esta instrucción extrae un valor de 16 bits del TX FIFO y lo ejecuta inmediatamente como una instrucción PIO — el valor no es datos, es la siguiente instrucción que la state machine ejecutará.
Este mecanismo permite al código C en el Core 1 controlar la secuencia de ciclo de bus en tiempo real. En lugar de cargar un programa PIO fijo para cada tipo de ciclo, el Core 1 envía una secuencia de instrucciones PIO pre-codificadas al TX FIFO, y la SM de ciclo las ejecuta una por una:
// z80_cycle SM (PIO 0 SM 2) — the orchestrator
//
// .program z80_cycle
// .side_set 2 opt
// public start_cycle:
//     wait 0 irq 6             ; Pause if BUSACK is active (bus relinquished).
//     irq set 0                ; Signal "ready for new cycle".
//     wait 0 irq 0             ; Wait until C code clears IRQ 0 (address loaded).
//     wait 1 gpio Z80_PIN_CLK  ; Sync to T1 rising edge of host clock.
// cycle_exec:
//     out exec, 16             ; ← Pull next instruction from TX FIFO and execute it.
//     jmp cycle_exec           ; Loop: keep executing injected instructions.
//
// The C code pushes a sequence of encoded PIO instructions into the FIFO.
// Each instruction controls one step of the bus cycle (assert /MREQ, wait for
// clock edge, read data bus, etc.). The sequence ends with a JMP back to
// start_cycle, which restarts the orchestrator for the next bus transaction.
El código C pre-calcula estas secuencias de instrucciones al inicio para cada tipo de ciclo (fetch, lectura de memoria, escritura de memoria, lectura de E/S, escritura de E/S, refresco, reconocimiento de interrupción). Durante la ejecución, el Core 1 selecciona la secuencia pre-construida apropiada y la envía al FIFO. Este enfoque tiene dos ventajas críticas:
  • Eficiencia de espacio de programa — cada bloque PIO tiene solo 32 slots de instrucción. Al inyectar instrucciones dinámicamente, la SM de ciclo necesita solo 7 instrucciones de memoria de programa para orquestar todos los tipos de ciclo. Los programas de tipo de ciclo reales (fetch, lectura, escritura, etc.) existen como arreglos C de instrucciones codificadas, no como programas PIO residentes.
  • Flexibilidad — el código C puede modificar la secuencia de instrucciones inyectada en tiempo de ejecución para manejar casos especiales (por ejemplo, insertar wait states adicionales, omitir la fase de refresco o generar un ciclo no estándar para depuración).

Coordinación de State Machines
Las 12 state machines trabajan como un pipeline coordinado. El siguiente diagrama muestra el flujo de un ciclo típico de lectura de memoria:
                  Core 1 (C code)               PIO State Machines
                  ─────────────                 ──────────────────
    1. Resolve address                          z80_cycle: IRQ 0 set
       from memory map                           (waiting for work)
                  │
    2. Push addr → TX FIFO ──────────────────→ z80_addr: receives addr
       Clear IRQ 0                               outputs A0–A15 on pins
                  │
    3. Push cycle instructions ──────────────→ z80_cycle: out exec, 16
       (e.g. mem_read sequence)                   executes: set /MREQ low
       into cycle SM TX FIFO                      executes: set /RD low
                  │                               executes: wait CLK edges
    4. Wait for RX FIFO ←────────────────────  z80_data: samples D0–D7
       (data byte from bus)                       pushes to RX FIFO
                  │
    5. Read data from                          z80_cycle: JMP start_cycle
       RX FIFO                                    (ready for next cycle)
                  │
    6. Dispatch to PSRAM
       or driver handler
El handshake basado en IRQ asegura que el bus de direcciones esté estable antes de activar las señales de control, y que los datos se muestreen en el punto correcto del ciclo de bus. Las state machines nunca hacen polling — usan wait 0 irq N para dormir hasta que ocurra el evento relevante, consumiendo cero ciclos de CPU mientras esperan.

Ciclo Fetch del Z80 (Ciclo M1) — Paso a Paso
El fetch de opcode es el ciclo de bus Z80 más complejo — combina una lectura de memoria con un ciclo de refresco. El programa z80_fetch se ejecuta durante 4 ciclos T del reloj del host:
Host CLK:  ──┐  ┌──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │  │  │
             └──┘  └──┘  └──┘  └──┘
              T1    T2    T3    T4

A0–A15:    ══╤═══ PC address ══════╤═══ Refresh addr ══╗
             │                     │                    ║
/M1:       ──┘                     └────────────────────╜── (low during T1–T2, high T3–T4)
/MREQ:     ────┘              ┌────┘              ┌──── (low T1↓–T3↑, then T3↓–T4↓ for refresh)
/RD:       ────┘              ┌─────────────────────── (low T1↓–T3↑)
/RFSH:     ────────────────────┘                   ┌── (low T3↑–T4↓)
D0–D7:     ═══════════════╤═══╗                        (sampled at T3↑)
                          │   ║
                       opcode read
El programa PIO implementa esto de la siguiente manera:
  1. Flanco de subida T1 — la SM z80_addr coloca el valor de PC en A0–A15. z80_fetch activa /M1 a nivel bajo mediante set pins.
  2. Flanco de bajada T1 — /MREQ y /RD se activan a nivel bajo (mediante set pins y side-set). La dirección ahora es válida y el sistema de memoria puede comenzar a responder.
  3. T2 — la SM entra en un bucle de wait-state: espera el flanco de subida y luego el de bajada del CLK, y verifica el pin /WAIT mediante jmp pin. Si /WAIT está bajo, la SM repite el bucle (añadiendo ciclos Tw). Si está alto, procede a T3.
  4. Flanco de subida T3 — in pins, 8 muestrea D0–D7 (el byte de opcode) y lo envía al RX FIFO. Se activa IRQ 1 para señalar a z80_data/z80_addr que ahora debe emitirse la dirección de refresco. /M1, /MREQ y /RD se desactivan; /RFSH se activa a nivel bajo.
  5. Flanco de bajada T3 — /MREQ se activa nuevamente (para el strobe de fila de refresco).
  6. T4 — el refresco continúa. Al final de T4, /MREQ y /RFSH se desactivan. La SM de ciclo retorna a start_cycle lista para la siguiente transacción de bus.
El Core 1 lee el opcode del RX FIFO y lo usa para decodificar la instrucción, determinar cuántos ciclos adicionales de memoria o E/S se necesitan, y enviar las secuencias de instrucciones apropiadas.

Ciclos de Lectura y Escritura de Memoria
Los ciclos de lectura y escritura de memoria son más simples que el fetch — abarcan 3 ciclos T sin fase de refresco.
Memory Read:
Host CLK:  ──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │
             └──┘  └──┘  └──┘
              T1    T2    T3

A0–A15:    ══╤═══ address ═══════╗
/MREQ:     ────┘              ┌──── (low T1↓–T3↓)
/RD:       ────┘              ┌──── (low T1↓–T3↓)
D0–D7:     ═══════════════╤═══╗     (sampled at T3↓)


Memory Write:
Host CLK:  ──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │
             └──┘  └──┘  └──┘
              T1    T2    T3

A0–A15:    ══╤═══ address ═══════╗
D0–D7:     ══════╤═══ data ═════╗   (driven from T2 onwards)
/MREQ:     ────┘              ┌──── (low T1↓–T3↓)
/WR:       ──────────┘        ┌──── (low T2↓–T3↓)
Para las lecturas, la SM z80_mem_read activa /MREQ y /RD en el flanco de bajada de T1, espera durante T2 (verificando /WAIT para wait states), y luego muestrea el bus de datos en el flanco de bajada de T3 usando in pins, 8. Para las escrituras, z80_mem_write activa /MREQ en el flanco de bajada de T1, y luego activa /WR en el flanco de bajada de T2 después de que el bus de datos sea controlado por z80_data. Ambos desactivan todas las señales de control al final de T3.

Ciclos de Lectura y Escritura de E/S
Los ciclos de E/S del Z80 usan /IORQ en lugar de /MREQ y siempre incluyen un wait state automático (Tw) entre T2 y T3. Esta es una característica arquitectónica del Z80 — el ciclo adicional da tiempo a los dispositivos de E/S más lentos para responder:
I/O Read:
Host CLK:  ──┐  ┌──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │  │  │
             └──┘  └──┘  └──┘  └──┘
              T1    T2    Tw    T3

A0–A15:    ══╤═══ port address ══════════╗
/IORQ:     ──────┘                    ┌──── (low T2↑–T3↓)
/RD:       ──────┘                    ┌──── (low T2↑–T3↓)
D0–D7:     ═══════════════════════╤═══╗     (sampled at T3↓)
La SM z80_io_read activa /IORQ y /RD en el flanco de subida de T2 (no en T1 como en los ciclos de memoria — esto es la especificación del Z80). El wait state automático Tw se implementa mediante el mismo patrón de bucle jmp pin / wait utilizado para los ciclos de memoria. Las escrituras de E/S siguen el mismo patrón con /WR reemplazando a /RD.

Manejo de BUSREQ / BUSACK
La SM z80_busrq (PIO 2 SM 0) monitoriza el pin de entrada /BUSREQ del host. Cuando /BUSREQ se activa (nivel bajo), la SM:
  1. Activa IRQ 6 para señalar a la SM de ciclo que hay una solicitud de bus pendiente.
  2. Espera a que se complete el ciclo de bus actual (wait 1 irq 0).
  3. Extrae una palabra de control de 32 bits del TX FIFO que especifica las direcciones y valores de los pines para el estado de liberación del bus — esto pone en tri-state los buses de direcciones y datos y activa /BUSACK a nivel bajo.
  4. Permanece en bucle con jmp pin hasta que /BUSREQ se desactiva (nivel alto).
  5. Extrae una segunda palabra de 32 bits para restaurar las direcciones normales de los pines y desactivar /BUSACK.
  6. Limpia IRQ 6, permitiendo que la SM de ciclo reanude su operación.
La SM de ciclo verifica IRQ 6 al inicio de cada ciclo mediante wait 0 irq 6 — si el flag está activado, la SM se detiene hasta que la solicitud de bus se complete. Esto asegura que la liberación del bus ocurra limpiamente entre ciclos, nunca a mitad de ciclo.

Sincronización de Reloj
Todas las SM de tipo de ciclo se sincronizan con el reloj Z80 del host usando wait 1 gpio Z80_PIN_CLK (esperar flanco de subida) y wait 0 gpio Z80_PIN_CLK (esperar flanco de bajada). Esto significa:
  • Los programas PIO son independientes de la frecuencia del reloj — funcionan a cualquier velocidad de reloj del host desde DC hasta la tasa máxima que el RP2350 puede seguir (limitada por el reloj del sistema PIO y la tasa de muestreo GPIO).
  • El reloj PIO de 300 MHz del RP2350 proporciona aproximadamente 85 ciclos PIO por T-state del Z80 a 3.5 MHz, dando más que suficiente tiempo para ejecutar instrucciones PIO, enviar/recibir FIFOs y verificar flags IRQ entre flancos de reloj.
  • La SM z80_sync (PIO 2 SM 1) proporciona una IRQ de sincronización T1 que el código C usa para alinear los accesos a PSRAM con el reloj del host, previniendo la deriva de temporización en software del host sensible al reloj.
  • La SM z80_clk_sync (PIO 2 SM 2) regenera el reloj del host en un GPIO separado, proporcionando una salida de reloj limpia para monitorización externa o disparo de analizador lógico.

Ciclo de Reconocimiento de Interrupción
La SM z80_int_ack (PIO 2 SM 3) implementa la secuencia de reconocimiento de interrupción del Z80. Cuando el código C detecta una condición de interrupción, carga el programa int_ack. Este ciclo es similar a un fetch pero con diferencias clave:
  • /M1 se activa en T1 (como un fetch), pero /IORQ se activa en lugar de /MREQ en el wait state (Tw1).
  • Se insertan dos wait states automáticos (Tw1, Tw2) para dar tiempo al dispositivo que interrumpe a colocar un vector en el bus de datos.
  • El byte de vector se lee de D0–D7 y se envía al RX FIFO.
  • Sigue un ciclo de refresco, idéntico a la fase de refresco del fetch.

Modelo de Memoria

Los accesos a memoria se resuelven a través de tres niveles de latencia creciente. El diseño de tres niveles asegura que el caso común (RAM/ROM respaldada por PSRAM) sea rápido mientras permite máxima flexibilidad para dispositivos virtuales y paso directo al hardware físico del host.

Nivel 1 — Tabla de Despacho SRAM del RP2350

Un arreglo de 128 entradas de valores membankPtr de 32 bits, residente en la SRAM de 512KB del chip del RP2350, proporciona una búsqueda O(1) del tipo de bloque para cada transacción de bus. Una entrada cubre cada bloque de 512 bytes del espacio de direcciones de 64KB del Z80 (128 × 512 = 65,536 bytes). Cada entrada codifica:
  • El tipo de bloque (PHYSICAL, RAM, ROM, FUNC, etc.).
  • Para bloques respaldados por PSRAM: el número de banco PSRAM y el offset.
  • Para bloques FUNC: un índice en la tabla de punteros a funciones de dispositivos virtuales.
Este es el camino más rápido — el Core 1 lee la entrada de la tabla de despacho para la dirección actual en un solo acceso SRAM (cero wait states a 300MHz) antes de decidir qué hacer a continuación.

Nivel 2 — PSRAM Externa

La PSRAM de 8MB está organizada como:
  • 64 bancos × 64KB — datos de imagen RAM o ROM para el espacio de direcciones Z80.
  • 64KB memPtr — arreglo de punteros de redirección por byte para bloques tipo PTR.
  • 64KB memioPtr — arreglo de punteros a funciones para dispositivos FUNC mapeados en memoria.
  • 64KB ioPtr — arreglo de punteros a funciones para dispositivos FUNC de puertos E/S.
El acceso a PSRAM se realiza mediante el periférico SPI dedicado del RP2350 con DMA. La latencia de acceso es determinista y gestionada por el generador de wait states para evitar violaciones de bus.

Nivel 3 — Flash SPI de 16MB

Las imágenes ROM se cargan desde Flash (o la tarjeta SD, a través del ESP32) a PSRAM durante el arranque. En tiempo de ejecución, la Flash no se accede para transacciones de bus — todos los datos ROM se sirven desde PSRAM. La Flash se utiliza para:
  • Bootloader y firmware de aplicación.
  • config.json minificado (almacenado en caché desde la tarjeta SD en cada arranque).
  • Imágenes ROM en particiones App Config (utilizadas cuando no hay tarjeta SD presente).

Tipos de Bloques de Memoria

Tipo Descripción
PHYSICAL Paso directo — el RP2350 libera el bus y la memoria física del host responde. Se utiliza para la ROM y RAM nativas del host.
PHYSICAL_VRAM Como PHYSICAL pero con wait states adicionales para la temporización de la RAM de video del host. Adecuado para regiones VRAM del MZ-700/MZ-80A.
PHYSICAL_HW Paso directo para registros de hardware del host (dispositivos mapeados por E/S en el espacio de memoria).
RAM Lectura/escritura — respaldada por un banco PSRAM. El RP2350 atiende lecturas y escrituras desde/hacia PSRAM.
ROM Solo lectura — respaldada por un banco PSRAM. Los ciclos de escritura se ignoran silenciosamente (el host ve temporización de bus normal pero no se almacenan datos).
VRAM RAM de video respaldada por PSRAM. Los ciclos de escritura se reflejan simultáneamente tanto en PSRAM como en la VRAM física del host.
FUNC Dispositivo virtual — cada acceso dispara una llamada a función C a través de la tabla de punteros a funciones memioPtr o ioPtr, permitiendo emulación arbitraria de E/S.
PTR Redirección por byte — cada byte del bloque de 512 bytes puede apuntar independientemente a cualquier otro tipo de bloque o ubicación PSRAM.

Referencia de Configuración

Todo el comportamiento del picoZ80 se controla mediante config.json en la tarjeta SD. El RP2350 lee y minifica este archivo durante el arranque, almacenando el resultado en Flash. Los arranques posteriores usan la copia en Flash si no hay tarjeta SD presente.
La estructura JSON de nivel superior es:
{
  "esp32": {
    "core":  { ... },
    "wifi":  { ... }
  },
  "rp2350": {
    "core":  { ... },
    "z80":   [ { "memory": [...], "io": [...], "drivers": [...] } ]
  }
}

esp32.core

Clave Tipo Descripción
device cadena Personalidad de CPU — "Z80" para picoZ80, "6502" para pico6502, "6512" para pico6512.
mode entero Modo WiFi de arranque por defecto: 0 = cliente (estación), 1 = Punto de Acceso.

esp32.wifi

Clave Tipo Descripción
override 0/1 Interruptor maestro: 1 = aplicar todas las configuraciones siguientes; 0 = usar configuraciones NVS persistidas.
wifimode cadena "ap" = modo Punto de Acceso; "client" = modo Estación/cliente.
ssid cadena Nombre de red WiFi para crear (AP) o unirse (cliente).
password cadena Contraseña WiFi.
ip cadena Dirección IP fija (por ejemplo, "192.168.1.192").
netmask cadena Máscara de subred (por ejemplo, "255.255.255.0").
gateway cadena Gateway predeterminado (por ejemplo, "192.168.1.1").
dhcp 0/1 Modo cliente: 1 = DHCP; 0 = usar configuración de IP fija.
webfs cadena Directorio raíz del sistema de archivos web en la tarjeta SD (por defecto "webfs").
persist 0/1 1 = escribir configuraciones resueltas en NVS para persistencia entre reinicios.

rp2350.core

Clave Tipo Descripción
cpufreq entero Reloj del sistema RP2350 en Hz (por ejemplo, 300000000). La frecuencia estable máxima depende de la frecuencia PSRAM y el voltaje del núcleo.
psramfreq entero Reloj SPI de PSRAM en Hz (por ejemplo, 133000000).
voltage flotante Voltaje del núcleo RP2350 en voltios (por ejemplo, 1.10). Velocidades de reloj más altas requieren mayor voltaje.
addrDrive integer (0–3) Fuerza de excitación GPIO del bus de direcciones: 0=2mA, 1=4mA, 2=8mA, 3=12mA. Por defecto 0.
addrSlew integer (0–1) Tasa de slew GPIO del bus de direcciones: 0=lento (por defecto), 1=rápido.
dataDrive integer (0–3) Fuerza de excitación GPIO del bus de datos. Misma codificación que addrDrive. Por defecto 0.
dataSlew integer (0–1) Tasa de slew GPIO del bus de datos. Misma codificación que addrSlew. Por defecto 0.
ctrlDrive integer (0–3) Fuerza de excitación GPIO de señales de control. Misma codificación que addrDrive. Por defecto 0.
ctrlSlew integer (0–1) Tasa de slew GPIO de señales de control. Misma codificación que addrSlew. Por defecto 0.
addrSchmitt / dataSchmitt / ctrlSchmitt 0/1 Habilita el disparador Schmitt de entrada para el grupo de bus de direcciones / datos / control. 1 = habilitado (por defecto), 0 = deshabilitado. Se aplica a los pines configurados como entradas.
addrPull / dataPull / ctrlPull integer (0–2) Resistencia de pull para el grupo de bus de direcciones / datos / control: 0 = ninguna, 1 = pull-down, 2 = pull-up.
refresh integer Generación de refresco de DRAM del Z80: 0 = desactivado (sin ciclos de refresco), 1 = un ciclo de refresco en cada búsqueda de opcode, N = un ciclo de refresco por cada N búsquedas. Reduce la sobrecarga del bus en hosts cuya DRAM no requiere refresco por búsqueda.
pinOverrides arreglo Sobrescrituras GPIO por pin. Cada elemento: { "pin": N, "drive": D, "slew": S, "schmitt": 0/1, "pull": P }. Sobrescribe los valores predeterminados a nivel de bus para pines GPIO individuales cuando el hardware específico requiere diferentes características eléctricas.

z80[].memory — Entradas del Mapa de Memoria

El arreglo memory define el mapa de memoria del Z80. Las entradas deben estar ordenadas por dirección. Las regiones deben estar alineadas y dimensionadas como múltiplos de 512 bytes. Los espacios entre entradas se tratan como paso directo PHYSICAL.
Clave Tipo Descripción
enable 0/1 Si esta entrada está activa. Las entradas deshabilitadas se ignoran durante el arranque.
addr hex string Dirección de inicio en el espacio de direcciones Z80 (por ejemplo, "0x0000"). Debe estar alineada a 512 bytes.
size hex string Tamaño de la región en bytes (por ejemplo, "0x2000" para 8KB). Debe ser múltiplo de 512.
type cadena Tipo de bloque — ver Tipos de Bloques de Memoria.
bank entero Número de banco PSRAM (0–63) para tipos RAM/ROM/VRAM/FUNC.
tcycwait entero Wait states adicionales de ciclo T a insertar en cada acceso a esta región.
tcycsync 0/1 Habilitar sincronización T1 para esta región. Requerido para regiones sensibles a la temporización.
task cadena Identificador de tarea opcional para bloques tipo FUNC (cadena de enlace de controlador).
file cadena Ruta en la tarjeta SD a una imagen ROM para precargar en el banco PSRAM durante el arranque (por ejemplo, "/ROM/mz700.rom").
fileofs entero Offset en bytes dentro del archivo de imagen ROM desde donde comenzar la lectura.

z80[].io — Entradas del Mapa de Puertos E/S

El arreglo io mapea rangos de puertos de E/S del Z80 a tipos de bloque. Solo los tipos PHYSICAL y FUNC son significativos para entradas de E/S.
Clave Tipo Descripción
enable 0/1 Si esta entrada de E/S está activa.
addr hex string Dirección de puerto E/S de inicio (por ejemplo, "0xE0").
size hex string Número de puertos consecutivos (por ejemplo, "0x04" para puertos E0–E3).
type cadena PHYSICAL = pasar al host; FUNC = llamar función handler C.
task cadena Cadena de enlace de controlador para entradas tipo FUNC.

z80[].drivers — Instancias de Controladores

El arreglo drivers instancia controladores de dispositivos virtuales y los enlaza a regiones de memoria o E/S. Cada controlador tiene un tipo (el módulo de controlador C), un nombre (identificador de instancia) y uno o más objetos de interfaz que definen las imágenes ROM, mapas de direcciones, mapas de E/S y parámetros para esa instancia de controlador.
"drivers": [
  {
    "enable": 1,
    "name":   "MZ700",
    "type":   "PHYSICAL",
    "if": [
      {
        "enable": 1,
        "name":   "main",
        "type":   "PHYSICAL",
        "rom": [
          {
            "enable":   1,
            "file":     "/MZ700/mz700.rom",
            "loadaddr": [
              {
                "enable":   1,
                "position": 0,
                "addr":     "0x0000",
                "bank":     0,
                "size":     "0x1000",
                "tcycwait": 0,
                "tcycsync": 0
              }
            ]
          }
        ],
        "addrmap": [
          { "enable":1, "srcaddr":"0x0000", "size":"0x1000",
            "dstaddr":"0x0000" }
        ],
        "iomap": [
          { "enable":1, "srcaddr":"0xE0", "size":"0x08",
            "dstaddr":"0xE0", "16bit":0 }
        ],
        "param": [
          { "enable":1, "file":"/config/mz700.cfg" }
        ]
      }
    ]
  },
  {
    "enable": 1,
    "name":   "MZ-1E05",
    "type":   "PHYSICAL",
    "if": [
      {
        "enable": 1,
        "name":   "fdc0",
        "type":   "PHYSICAL",
        "rom":    [],
        "addrmap": [],
        "iomap": [
          { "enable":1, "srcaddr":"0xD8", "size":"0x04",
            "dstaddr":"0xD8", "16bit":0 }
        ],
        "param": [
          { "enable":1, "file":"/DSK/MZ700/disk0.dsk" }
        ]
      }
    ]
  }
]
Cada entrada de controlador tiene un "name" de nivel superior (el módulo de controlador C a instanciar), "type" (PHYSICAL o VIRTUAL), y un arreglo "if" (interfaz) que contiene uno o más objetos de interfaz. Cada objeto de interfaz describe un sub-controlador o tarjeta periférica y contiene estos campos:
Clave Tipo Descripción
enable 0/1 Si esta interfaz está activa (0 = se omite durante la inicialización).
name cadena Identificador de instancia — debe coincidir con un nombre en el interfaceFuncMap[] de la persona.
type cadena PHYSICAL o VIRTUAL.
rom arreglo Imágenes ROM a cargar en PSRAM (ver más abajo).
addrmap arreglo Entradas de remapeo de direcciones de memoria (srcaddr/dstaddr/size).
iomap arreglo Entradas de remapeo de puertos E/S (srcaddr/dstaddr/size/16bit).
param arreglo Parámetros específicos del controlador — típicamente { "enable":1, "file":"/path/to/image" } para imágenes de disco, { "name":"key", "value":"val" } para parámetros con nombre, o { "ip":"a.b.c.d:port", "enable":1 } para la dirección del servidor de archivos de red Celestite.

Puertos base de interfaz reubicables. Para que las tarjetas independientes de máquina puedan usarse en placas personalizadas / de experimentador (ver OpenZ80), varios controladores de interfaz leen su puerto E/S base desde el dstaddr de su entrada iomap (con srcaddr establecido en el puerto auténtico de la tarjeta), usando por defecto su puerto original cuando no hay una entrada iomap presente. Las tarjetas reubicables y sus bases por defecto son MZ-1R12 (0xF8/3), MZ-1R18 (0xEA/2), MZ-1R23 (0xB8/2), MZ-1R37 (0xAC/2), PIO-3034 (0x00/4), MZ-8BIO3 / MZ-1E24 (0xB0/4), MZ-1E05 (0xD8/7) y Celestite (0x60/16). Las claves JSON están en minúsculas (srcaddr / dstaddr) y sus valores se interpretan como números. El campo Base I/O Port en la página de Configuración de la GUI escribe automáticamente la entrada iomap correcta.

rom[].loadaddr[] — Direcciones de Carga ROM

Cada entrada de archivo ROM contiene un arreglo loadaddr que especifica dónde se cargan los datos ROM en PSRAM. Múltiples entradas loadaddr permiten que un solo archivo ROM se divida en rangos de direcciones no contiguos o bancos PSRAM.
Clave Tipo Descripción
enable 0/1 Si esta dirección de carga está activa.
position entero Offset en bytes dentro del archivo ROM desde donde comenzar la lectura.
addr hex string Dirección Z80 destino en el mapa de memoria (por ejemplo, "0x0000").
bank entero Número de banco PSRAM donde cargar.
size hex string Número de bytes a cargar (por ejemplo, "0x1000" para 4KB).
tcycwait entero Wait states extra de ciclo T para esta región.
tcycsync entero Valor de sincronización de ciclo T para esta región.

Compatibilidad Persona–Interfaz

No todos los controladores de interfaz están disponibles para cada persona. La tabla siguiente muestra la compatibilidad actual de interfaces para la serie Sharp MZ / X1. Las personas Amstrad PCW-9512 y PCW-8256/8512 son controladores autónomos con un FDC uPD765 integrado y sus propias interfaces FDC / PRINTER / MEMORY / PAGEPROBE — véase Soporte Amstrad PCW. La persona Tatung Einstein TC-01 es un controlador autónomo con un FDC WD1770 integrado y una sub-interfaz EinsteinFDC. La columna Open muestra las tarjetas independientes de máquina aceptadas por la persona de experimentador OpenZ80.
La tabla muestra qué interfaces pueden usarse con cada persona de máquina de nivel superior a través del arreglo "if". El valor "name" de la interfaz en el JSON debe coincidir con una de las entradas soportadas para la persona elegida.
Interfaz MZ-700 MZ-1500 MZ-80K MZ-800 MZ-80A MZ-2000 MZ-2200 MZ-80B MZ-2500 Open
RFS Sí Sí Sí Sí Sí Sí — — — —
TZFS Sí — — — — — — — — —
MZ-1E05 Sí Sí — Sí — — — — — Sí
MZ80FIO — — Sí Sí — — — — — —
MZ80AFI — — Sí Sí Sí — — — — —
MZ-8BFI / E0054PA — — — Sí — Sí Sí Sí Sí —
MZ-1E14 Sí Sí Sí Sí — — — Sí Sí —
MZ-1E19 Sí Sí Sí Sí — Sí Sí Sí Sí —
MZ-1R12 Sí Sí Sí Sí Sí Sí Sí Sí Sí Sí
MZ-1R18 Sí Sí Sí Sí Sí Sí Sí Sí Sí Sí
MZ-1R23 — Sí — Sí — — — Sí Sí Sí
MZ-1R37 — Sí Sí Sí — — — Sí Sí Sí
PIO-3034 — Sí Sí Sí — — — Sí Sí Sí
Celestite — Sí — Sí — — — Sí Sí Sí
MZ-8BIO3 Sí Sí — Sí — — — Sí — Sí
MZ-1E24 Sí Sí — Sí — — — Sí — Sí
MZ-1E30 — — — — — — — Sí Sí —

Controladores Integrados

El firmware soporta un sistema de compilación dirigido con cinco modelos objetivo: BaseZ80 (todos los controladores), SharpZ80 (solo Sharp), AmstradZ80 (solo Amstrad), TatungZ80 (solo Tatung) y OpenZ80 (la persona de experimentador independiente de máquina). Las definiciones en tiempo de compilación INCLUDE_SHARP_DRIVERS, INCLUDE_AMSTRAD_DRIVERS, INCLUDE_TATUNG_DRIVERS e INCLUDE_OPEN_DRIVERS controlan qué módulos de controladores se compilan. Las tablas siguientes listan todos los controladores disponibles.
Serie Sharp MZ (INCLUDE_SHARP_DRIVERS)
Cuando el firmware se compila con INCLUDE_SHARP_DRIVERS, los siguientes módulos de controlador se compilan y pueden instanciarse a través del arreglo drivers:
Controlador Cadena de Tipo Descripción
MZ700.c MZ700 Sharp MZ-700 — conmutación de bancos, video, E/S de teclado
MZ80A.c MZ80A Sharp MZ-80A — ROM de monitor (SA-1510), VRAM, emulación Intel 8253 PIT, 8255 PPI, intercambio de memoria MEMSW/MEMSWR (incluyendo conmutación de bancos CP/M), soporte de modo mixto físico+virtual, y soporte para sub-interfaces RFS, MZ80AFI, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18
MZ2000.c MZ2000 Sharp MZ-2000 — conmutación de modo de memoria BST/NST, superposición de VRAM de caracteres + gráficos, 8253 PIT, 8255 PPI, Z80 PIO, MB8866 FDC. Soporta tanto modo físico (reemplazo directo del Z80 con detección automática de modo boot/normal) como modo virtual (emulación completa basada en PSRAM con reflejo de ROM IPL)
MZ2200.c MZ2200 Sharp MZ-2200 — conmutación de modo de memoria BST/NST, superposición VRAM, 8253 PIT, 8255 PPI, Z80 PIO, MB8866 FDC, CRT a color
MZ80B.c MZ80B Sharp MZ-80B — ROM IPL de 2K, BST/NST, pantalla monocromática, páginas GRPH duales, 8253 PIT, 8255 PPI, Z80 PIO
MZ2500.c MZ2500 Sharp MZ-2500 (SuperMZ) — MMU de 8 páginas (64 bloques), modos de compatibilidad MZ-2000/MZ-80B, YM2203, G-CRTC, MB8876 FDC, paleta, controlador de interrupciones. El modo virtual soporta software controlado por interrupciones mediante handlers personalizados fetchByte/RETI (ciclos de bus M1 físicos para temporización del gate array), formato de disco D88 nativo con auto-detección sparse/contiguous, supresión de interrupción de un solo disparo durante la inicialización
MZ1500.c MZ1500 Sharp MZ-1500 — superconjunto del MZ-700 con unidad Quick Disk incorporada, PCG, sonido estéreo PSG (SN76489AN), interfaz de impresora Z80 PIO, 8253 PIT, selección de modo MZ-700/MZ-1500 por DIP switch. Sub-interfaces: RFS, MZ-1E05, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18, MZ-1R23, MZ-1R37, PIO-3034, Celestite
MZ80K.c MZ80K Sharp MZ-80K — ROM de monitor SP-1002 (0x0000–0x0FFF), VRAM de 2KB (0xD000–0xD7FF), teclado 8255 PPI / 8253 PIT / LS367 (0xE000–0xE7FF), ROM de arranque MZ-80FD nativa (0xF000–0xF3FF), intercambio de memoria MEMSW/MEMSWR para CP/M. Modos físico + virtual (el CP/M en modo físico usa un remapeo de acceso Z80 por controlador; el modo virtual es necesario para el CP/M estándar). Dos rutas de disquete: MZ80FIO nativa (T3444M) — interfaz MZ-80FD original, arranca/lee todos los discos MZ-80K; y MZ80AFI (FDC del MZ-80A) — usada para CP/M, arranca el CP/M del MZ-80K y lee los discos CP/M del MZ-80K dentro de CP/M (C:/D:). Solo una interfaz de disquete está activa a la vez; MZ80FIO tiene prioridad si ambas están presentes. Sub-interfaces: RFS, MZ80FIO, MZ80AFI, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18, MZ-1R37, PIO-3034
MZ800.c MZ800 Sharp MZ-800 — controlador de modo dual (compatibilidad MZ-700 + MZ-800 nativo). Espía el registro de Modo de Pantalla del GDG (puerto 0xCE) para conmutar de modo sobre la marcha: el modo nativo añade gráficos 320×200 / 640×200 (planos de VRAM 0x8000–0xBFFF), paleta de 4/16 colores, E/S del GDG mapeada por puertos (0xCC–0xCF, paleta 0xF0), interrupciones vectorizadas IM2 mediante la cadena daisy-chain del Z80-PIO; el modo MZ-700 usa 8255/8253 mapeados en memoria (0xE000–0xE7FF) y VRAM de texto (0xD000). PSG SN76489 (0xF2), FDC WD1773 (0xD8–0xDF), QuickDisk (0xF4–0xF7), puertos de conmutación de bancos de memoria 0xE0–0xE6. El modo virtual reproduce un RETI físico en el bus real para servir el latch en-servicio del PIO.
X1.c X1 Sharp X1 / X1turbo — persona de paso directo físico: la máquina real aporta la RAM, la ROM IPL, la VRAM, el CRTC, la sub-CPU y el PSG, y todos los bloques de memoria pasan directamente al host. El picoZ80 añade el disquete y el cassette virtuales descritos a continuación; solo se interceptan sus puertos
X1FDC.c (usado por X1) Controlador de disquete MB8877A virtual (familia WD1793, un adaptador ligero sobre el motor WD1773 compartido) en los puertos 0xFF8–0xFFF con decodificación completa de 16 bits. Las imágenes Raw, D88 y Extended CPC DSK se detectan por su contenido, no por la extensión. Se soportan medios 2D / 2DD y 2HD de 1MB con selección de medio por el host (las lecturas de 0FFC–0FFF seleccionan FM / MFM / 2HD), arrancando el disk BASIC CZ-8FB02 directamente desde una imagen 2HD. En modo de pletina Virtual el DMA de disquete se declara automáticamente como dispositivo integrado; en modo de pletina Real el DMA y el MB8877 propios de la máquina siguen emparejados
X1CMT.c (usado por X1) Cassette virtual del X1 — reproduce imágenes de forma de onda .tap (un flujo de bits muestreado a tasa constante, no un formato codificado) desde la SD a través de un búfer en anillo, con el transporte gobernado a través de los puertos de la sub-CPU. La grabación captura a 48kHz y nombra la cinta guardada a partir de la cabecera de la cinta
MZ80AFI.c MZ80AFI Interfaz de disquete Sharp MZ-80A — emula el controlador de disco flexible MZ-80A AFI
MZ80FIO.c MZ80FIO Interfaz de disquete Sharp MZ-80FD/MZ-80FIO (MZ-80K) — ROM de arranque FDIF en 0xF000–0xF3FF, puertos T3444M 0xF8–0xFB, hasta 4 unidades, cambio de disco en tiempo de ejecución
T3444M.c (usado por MZ80FIO) FDC Toshiba T3444M/T3444A — disquete nativo del MZ-80K. DSK extendido CPC, 35 pistas, 2 cabezas, 16 sectores/pista, sectores FM de 128 bytes; analizador de DSK Track-Info robusto; 4 imágenes de unidad simultáneas
WD1773.c WD1773 FDC WD1773 — imágenes DSK/RAW/D88 de 80 pistas, 2 cabezas, 8 sectores
QDDrive.c QDDRIVE Unidad QuickDisk Sharp — emulación completa de Z80 SIO/2 con datos de pista espiral, control de motor y E/S asíncrona de tarjeta SD
RFS.c RFS ROM Filing System — carga de MZF, CP/M, BASIC desde tarjeta SD
TZFS.c TZFS TranZPUter Filing System — monitor multibanco funcional + sistema de archivos CP/M, seleccionable en la persona MZ-700. Modos de memoria tranZPUter mediante el puerto 0x60, procesador de servicio virtual K64F mediante OUT (0x68), E/S de sectores CP/M a través del ESP32. ROM roms/tzfs.bin desde TZFS/asm/tzfs.asm
MZ-1E05.c MZ1E05 Unidad de interfaz de disco flexible Sharp MZ-1E05 (basada en WD1773)
MZ8BFI.c MZ8BFI / E0054PA Interfaz de disco flexible MZ-2000 — FDC MB8866 sin ROM de controlador (código en IPL). Soporte de formato D88.
MZ-1E14.c MZ1E14 Controlador QuickDisk MZ-1E14 con ROM BIOS (MZ-700/MZ-800)
MZ-1E19.c MZ1E19 Controlador QuickDisk MZ-1E19 sin ROM BIOS
MZ-1R12.c MZ1R12 Placa RAM de 32KB con batería de respaldo (persistida en tarjeta SD)
MZ-1R18.c MZ1R18 Placa de expansión RAM de 64KB
MZ-1R23.c MZ1R23 MZ-1R23 ROM Kanji de 128KB (patrones JIS 16×16) y MZ-1R24 ROM Diccionario de 256KB. Archivos ROM cargados desde tarjeta SD. Puertos E/S B8h–B9h con lectura auto-incrementada
MZ-1R37.c MZ1R37 MZ-1R37 EMM (Expanded Memory Manager) de 640KB — espacio de direcciones de 20 bits con enganche de dirección por puerto E/S
PIO-3034.c PIO3034 IO DATA PIO-3034 EMM de 320KB — contador de direcciones de 19 bits con puerto de datos auto-incrementado
Celestite.c Celestite Placa compuesta Celestite — controlador Ethernet Wiznet W5100 (emulación de registros), controlador de interrupciones, UFM, CMOS RAM MZ-1R12 de 32KB integrada (expandible a 64KB), EMM MZ-1R37 de 640KB opcional. Puertos E/S 60h–6Fh. El parámetro ip en el arreglo JSON param configura la dirección del servidor de archivos netfs.py (por ejemplo, "192.168.1.210:6800"). Fase 2: networking TCP/IP real a través de puente ESP32 — comandos de socket W5100 reenviados al ESP32 para operaciones de socket BSD reales. IPC inter-core para NET_CFG, NET_SOCK, NET_SEND, NET_RECV, NET_PING
MZ8BIO3.c MZ-8BIO3 Tarjeta serie RS-232C (conector BI), Z80 SIO emulado en los puertos 0xB0–0xB3 (base configurable). Canales A/B puenteados a los puertos serie USB CDC 2 y 3. Sin ROM.
MZ1E24.c MZ-1E24 Tarjeta serie RS-232C (conector Sharp ST); igual que MZ-8BIO3 con cableado de conector diferente.
Z80SIO.c (usado por MZ-8BIO3 / MZ-1E24) Zilog Z80 SIO/2 preciso a nivel de registro: WR0–WR7, RR0–RR2, interrupciones vectorizadas Z80 modo-2, cadena daisy-chain en-servicio de 4 niveles, status-affects-vector. Anillos SPSC sin bloqueo (lock-free) puentean el núcleo Z80 (core 1) y el servicio USB CDC (core 0).
SASI.c + MZ1E30.c MZ-1E30 Controlador de disco duro SASI MZ-1E30 — emula el controlador de disco duro SASI (Shugart Associates System Interface) Sharp MZ-1E30 para MZ-2500/MZ-80B. Hasta 4 destinos de disco (~21.4 MB cada uno, bloques de 256 bytes), ROM IPL de 32KB (accedida por E/S a través de puertos 0xA8–0xA9), E/S de sector bajo demanda desde imágenes de disco en tarjeta SD. Comandos SASI: TEST_UNIT_READY, REQUEST_SENSE, READ(6), WRITE(6), SEEK(6), INQUIRY. Puertos E/S 0xA4–0xA5 (datos/control SASI)
PIT8253.c PIT8253 Emulación Intel 8253 PIT independiente — los seis modos de contador, BCD/binario, latch, lectura/carga LSB/MSB
PPI8255.c PPI8255 Emulación Intel 8255 PPI independiente — E/S Modo 0, set/reset de bits, callbacks de salida, inyección de entrada
Serie Amstrad PCW (INCLUDE_AMSTRAD_DRIVERS)
Cuando el firmware se compila con INCLUDE_AMSTRAD_DRIVERS, los siguientes módulos de controlador se compilan:
Controlador Cadena de Tipo Descripción
PCW9512.c PCW9512 Amstrad PCW-9512 — Z80A @ 4MHz, 512KB RAM con conmutación de páginas de 16KB en 4 bancos (puertos F0–F3), gate array (ASIC) para video/reloj del sistema/enrutamiento FDC/control de motor (puerto F8), stub del controlador de impresora de margarita (puertos FC–FD), emulación de secuencia de arranque, sombra de la DRAM en PSRAM con reparación de video y memoria de expansión. Modos de controlador PHYSICAL y VIRTUAL — véase Soporte Amstrad PCW
PCW8256.c PCW8256 Amstrad PCW-8256/8512 — comparte el núcleo del controlador PCW9512 (condicionado por variante) y añade el modelo de estado de la impresora matricial, cuyos bits de estado en el puerto FDh difieren de los de la margarita. Unidades CF2 de 3”, geometría tomada de la imagen de disco; 256KB (8256) o 512KB (8512) de RAM de máquina, hasta 1.5MB en total con la memoria de expansión
uPD765.c — Emulación FDC NEC uPD765 — soporte de formato CPC DSK (puertos 00–01). Comandos del gate array: finalizar bootstrap, reiniciar, enrutamiento de INT FDC (NMI/INT/ignorar), terminal count, motor on/off. Arranque sin disco desde una imagen .dsk, cambio de pletina Real ↔ Virtual en tiempo de ejecución, imagen de disco físico mediante código Z80 inyectado. Módulo FDC reutilizable (análogo a WD1773.c para Sharp)
Serie Tatung Einstein (INCLUDE_TATUNG_DRIVERS)
Cuando el firmware se compila con INCLUDE_TATUNG_DRIVERS, los siguientes módulos de controlador se compilan:
Controlador Cadena de Tipo Descripción
EinsteinTC01.c EinsteinTC01 Tatung Einstein TC-01 — Z80A @ 4MHz, 64KB RAM + 8KB ROM conmutable (X-TAL MOS), conmutación ROM/RAM por puerto 0x24, VDP TMS9129 con aplicación de temporización entre accesos (~2us de intervalo), PSG AY-3-8910 (puertos 0x02-0x03), Z80 CTC (puertos 0x28-0x2B), Z80 PIO (puertos 0x30-0x33), interfaz de teclado (puerto 0x20). Modos virtual y físico soportados
EinsteinFDC.c — Sub-interfaz FDC Einstein — 2 unidades, soporte de formato DSK/D88. Imagen de disco físico: leer disquete físico a DSK, escribir DSK a disquete físico
WD1770.c — Emulación FDC WD1770 — soporte de formato Extended CPC DSK, D88 y DSK estándar. 40 pistas, 1 cabeza, 10 sectores, 512 bytes (discos de 200KB). Módulo FDC reutilizable para máquinas basadas en WD1770 (separado de WD1773.c usado por Sharp)

TZFS — Modelo de Monitor + CP/M (persona MZ-700)
TZFS.c implementa un monitor de bajo nivel multibanco y sistema de archivos funcional — un conjunto de mejoras sobre el MONITOR 1Z-013A original (acceso a SD, banqueo de ROM, un ensamblador / desensamblador y herramientas) — modelado sobre el TZFS del tranZPUter SW y su procesador de E/S virtual K64F. Se ofrece como una interfaz seleccionable únicamente en la persona MZ-700 (registrado en MZ700.c junto a RFS, y en la práctica mutuamente excluyente con él), no como una persona de nivel superior. CP/M se ejecuta por debajo del monitor.
Emulación de modos de memoria (puerto 0x60)
Al escribir un modo de memoria tranZPUter en el puerto de E/S 0x60 se reapuntan los punteros de banco. Los modos son TZMM_ORIG, TZMM_BOOT, TZMM_TZFS, TZMM_TZFS2, TZMM_TZFS3, TZMM_TZFS4, TZMM_CPM, TZMM_CPM2 y TZMM_COMPAT. La disposición CP/M CPM2 usa paginación del bloque 0 con granularidad de byte — 0x0000–0x003F se asignan al bloque de vectores y 0x0040–0x01FF al inicio del TPA.
Procesador de servicio virtual K64F (puerto 0x68)
El Z80 solicita servicios ejecutando OUT (0x68), que encola un mensaje MSG_TZFS_SVCREQ al Core 0; el Core 0 ejecuta TZFS_processServiceRequest. Los servicios del sistema de archivos son READDIR / NEXTDIR (bloques de directorio de 16 entradas en caché), READFILE / NEXTREADFILE, LOADFILE (consciente de la cabecera MZF y de los objetos de banco), CHANGEDIR y CLOSE. Los servicios de CP/M son LOADBDOS (recarga de CCP + BDOS en arranque en caliente), ADDSDDRIVE, READSDDRIVE y WRITESDDRIVE, más servicios de frecuencia de CPU. Como picoZ80 no tiene acceso directo a la SD, los sectores de 512 bytes de CP/M se leen y escriben a través del ESP32 (ESP_readSector / ESP_writeSector) contra archivos de imagen completos; las rutas de imagen por unidad provienen de las entradas param[].file del JSON de la interfaz, con la plantilla de reserva CPM/SDC16M/RAW/CPMDSK<nn>.RAW. La ROM TZFS es roms/tzfs.bin en la tarjeta SD, ensamblada desde TZFS/asm/tzfs.asm (su BIOS de CP/M desde TZFS/asm/cbios.asm / cpm22.asm). Se distribuye en config_MZ-700_MZ-700.json con "enable": 0 y se activa en el JSON o desde la página de Configuración de la GUI web.

OpenZ80 — Persona de Experimentador (INCLUDE_OPEN_DRIVERS)
El objetivo OpenZ80 (TARGET_MODEL_OPEN → INCLUDE_OPEN_DRIVERS) compila una persona Z80 "vanilla" deliberadamente básica para experimentadores que instalan el picoZ80 en una placa de su propio diseño o en una máquina sin controlador dedicado. En modo PHYSICAL, todo el espacio de 64K de memoria y E/S pasa directamente a la placa real (las tarjetas de interfaz superponen sus puertos E/S); en modo VIRTUAL presenta una RAM plana de 64K en la que las imágenes ROM a nivel de controlador se cargan secuencialmente desde 0x0000. No incorpora hardware de máquina y reutiliza los módulos de tarjetas de interfaz Sharp de forma literal (no mantienen estado específico de la persona), exponiendo solo las tarjetas independientes de máquina a continuación — cada una de las cuales soporta reubicación del puerto E/S base.
Controlador Cadena de Tipo Descripción
Open.c Open Persona Z80 vanilla / de experimentador — sin hardware de máquina. Paso directo físico o RAM virtual plana de 64K con carga de ROM a nivel de controlador desde 0x0000
MZ-1R12.c MZ1R12 Tarjeta RAM-file MZ-1R12 de 32K (base por defecto 0xF8, reubicable)
MZ-1R18.c MZ1R18 Placa RAM MZ-1R18 de 64K (base por defecto 0xEA, reubicable)
MZ-1R23.c MZ1R23 Placa ROM Kanji MZ-1R23 / diccionario MZ-1R24 (base por defecto 0xB8, reubicable)
MZ-1R37.c MZ1R37 MZ-1R37 EMM de 640K (base por defecto 0xAC, reubicable)
PIO-3034.c PIO3034 PIO-3034 EMM paralelo / E/S paralela (base por defecto 0x00, reubicable)
MZ8BIO3.c / MZ1E24.c MZ8BIO3 / MZ1E24 Tarjetas serie RS-232C duales (Z80 SIO, base por defecto 0xB0, reubicable; canales A/B en USB CDC 2/3)
MZ-1E05.c MZ1E05 Interfaz de disquete WD1773 (base por defecto 0xD8, reubicable; requiere una ROM de arranque FDC)
Celestite.c Celestite Placa LAN ESP32 Celestite (base por defecto 0x60, reubicable)
Compile con build_tzpuPico.sh open. Para adaptar el picoZ80 a una nueva máquina, tome uno de los controladores de persona bajo src/drivers/{Sharp,Amstrad,Tatung,Other}/ como base y combínelo con el código fuente de la ROM de monitor / IPL / BIOS CP/M / arranque de disquete correspondiente en los directorios asm/ de los proyectos RFS y TZFS (por ejemplo, RFS/asm/sa1510.asm, RFS/asm/cbios.asm, TZFS/asm/mz2000_ipl.asm, RFS/asm/mz80afi.asm). Consulte la Guía del Desarrollador para el procedimiento paso a paso.

Unidad CMT Virtual (Cassette)

La CMT Virtual es una pletina de cassette virtual a nivel de forma de onda para la serie Sharp MZ. Emula la forma de onda del cassette en lugar de interceptar el cargador, por lo que desde el punto de vista del Sharp es indistinguible de una pletina real: LOAD del monitor, BASIC, cargadores personalizados y turbo, y SAVE, todos funcionan sin cambios. La carga desde tarjeta SD sigue siendo el camino rápido; la CMT Virtual es el camino auténtico — tome un .mzf y cárguelo "como un cassette real", o grabe un programa en ejecución de vuelta a un nuevo .mzf.
El controlador es un único motor — src/drivers/Sharp/CMT.c y src/include/drivers/Sharp/CMT.h (CMT_Init / CMT_Reset / CMT_PollCB / CMT_TaskProcessor) — registrado en el interfaceFuncMap[] de cada máquina como {"CMT", false, CMT_Init, CMT_Reset, CMT_PollCB, CMT_TaskProcessor} y ofrecido como interfaz seleccionable dondequiera que la máquina tenga un puerto de cassette. Toda la temporización se ejecuta contra el reloj de pared (time_us_64() — el mismo dominio de temporización de disparo único que utiliza la CMT del RFS), nunca contra los T-states emulados del Z80. El diseño completo está documentado en docs/VIRTUAL_CMT_DESIGN.md en el repositorio picoZ80.

Dos Familias de Hardware, Un Motor

El hardware de cassette del MZ se divide en dos familias, ambas gobernadas por el mismo motor de códec. La familia se infiere de la persona activa.
Familia Máquinas Bus / Puertos Baudios Semi-onda (0 / 1)
SIMPLE MZ-80K / MZ-80A / MZ-700 / MZ-800 8255 motor on/off, cinta lineal; 8255 mapeado en memoria en E000–E003 (el MZ-800 en su lugar usa los puertos E/S D0–D3) 1200 baudios 0 ≈ 504 µs, 1 ≈ 958 µs (el MZ-800 lo ajusta a ≈ 379 / 964 µs)
CONTROLLED MZ-80B / MZ-2000 / MZ-2200 / MZ-2500 Transporte controlado por computadora (PLAY / STOP / FF / REW / EJECT + búsqueda de programa APSS); engancha ioPtr[] en E0–E3 2000 baudios ≈ 333 / 667 µs (variante ≈ 1800 baudios)
Códec compartido. Ambas familias comparten el mismo entramado de byte, bloque y archivo. Un byte es un bit de inicio largo seguido de 8 bits de datos, MSB primero. Un bloque es una cabecera (leader), una marca de cinta (tape-mark), los bytes de datos y una suma de comprobación de 16 bits (un conteo de los bits en 1). Un archivo es un registro de cabecera (info) seguido de un registro de datos, cada uno escrito dos veces, de modo que una única lectura física dispone de una segunda copia a la que recurrir.

La Cinta Virtual — una Cola Ordenada

La cinta virtual es una cola de hasta 16 archivos MZF configurada por la GUI y ordenada, editable en vivo desde la GUI web (añadir / eliminar / reordenar). Al final de cada programa la cola avanza automáticamente — comportamiento de cinta consecutiva — de modo que el siguiente LOAD encuentra el siguiente programa. En la familia controlada, FF / REW saltan programas enteros y APSS busca el límite del programa siguiente o anterior y se detiene automáticamente. La cola se suministra al controlador a través del arreglo param de la interfaz como una lista ordenada de entradas { "enable", "file" }. Un nuevo panel de la GUI web — esp32/webserver/js/cmt.js — gestiona la cola.

Real ↔ Virtual — Paso Directo, Superposición, Supresión

La conmutación en vivo Real ↔ Virtual ya no es exclusiva de la CMT — ahora cubre las interfaces CMT, Disquete (FDD) y Quick Disk (QD). En la GUI web la conmutación de pletina es un elemento de menú: CMT en el menú CMT; Floppy y QD como elementos de menú disponibles en todas las páginas, cada uno mostrado solo cuando la interfaz correspondiente está habilitada. Al seleccionarlo se conmuta un flag en vivo del controlador a través de la cola de comandos inversos ESP32 → RP2350, sin reinicio. Para una CMT los dos modos difieren en cómo el controlador trata la pletina físicamente conectada:
  • VIRTUAL — el controlador superpone los bits del cassette virtual en el lado de lectura y suprime las escrituras de motor y transporte a la pletina física. El Sharp carga desde la cinta virtual y la pletina real permanece inactiva.
  • REAL — paso directo puro. La CMT espía en modo solo lectura y el motor acciona la pletina real; el cassette físico se comporta exactamente como si el picoZ80 no estuviera presente.
Disquete y QD siguen el mismo contrato VIRTUAL/REAL: Virtual = el firmware emula la unidad a partir de imágenes respaldadas en la SD (una cinta virtual en el caso de la CMT); Real = paso directo puro a la unidad físicamente conectada, con el firmware espiando en modo solo lectura. Una interfaz configurada como física ahora arranca en modo Real, con el menú y la conmutación disponibles para poder pasarla a Virtual en vivo (sin reinicio). Disquete y QD también ganaron cada uno un botón EJECT (menú Floppy / QD de la GUI web): una ruta de montaje vacía desmonta la unidad, de modo que el FDC notifica que no hay disco. Los modos de pletina se notifican mediante INF — la CMT ya notificaba su modo, al que ahora se unen los nuevos campos fddMode y qdMode.
Implementación: una tarea DRIVE_MODE_CHANGE con comandos inversos FDDM / QDDM sobre la cola de comandos inversos ESP32 → RP2350 — el mismo mecanismo que la conmutación de la CMT — instala o desinstala los hooks de bus de cada controlador en cada cambio de modo. En modo Real el paso directo del SIO del QD real está ritmado: un suelo de recuperación de 10 µs entre accesos en los puertos F4–F7, porque el Z80 SIO necesita varios ciclos de reloj de recuperación entre accesos.
Debido a que la conmutación es en vivo, puede cargar desde una cinta virtual y luego hacer SAVE a una pletina física, o cargar desde una pletina física y grabar en una cinta virtual, sin reiniciar.

Grabación (SAVE) — el Decodificador de Forma de Onda

Cuando el motor está funcionando en modo grabación, el controlador muestrea la línea de datos de escritura y la decodifica de vuelta a un archivo. Cada intervalo de semi-onda se clasifica como corto o largo, los pares corto/largo se convierten en bits, los bits se convierten en bytes, y se valida la suma de comprobación de 16 bits. A partir de los registros recuperados se sintetiza un MZF y se guarda en la tarjeta SD como:
CMT/taperecord_<name>_<YYYYMMDD_HHMMSS>.mzf
donde <name> es el nombre de archivo de la cabecera del Sharp transcodificado del código de visualización del Sharp a ASCII y luego saneado para FAT (los caracteres ilegales se convierten en _). Cuando el reloj del ESP32 no ha sido ajustado, la marca de tiempo recurre a un número de secuencia (taperecord_<name>_0001.mzf) para evitar la colisión con la fecha "1980" de FAT. El nombre real del Sharp se almacena literalmente dentro de la cabecera del MZF, de modo que el programa conserva su nombre original cuando se recarga.

Motor de Reproducción e Internos del Controlador

La reproducción utiliza un cursor de flancos en streaming que calcula la siguiente transición de datos de lectura sobre la marcha contra el reloj de pared. Es O(1) amortizado y no mantiene un buffer de flancos gigante pre-expandido, por lo que una imagen de cinta completa cuesta casi nada de RAM independientemente de su longitud. Las dos familias enganchan el bus de forma distinta:
  • Familia simple — el controlador re-tipifica el bloque del 8255 del cassette (512 bytes, E000–E1FF = bloque 112) a MEMBANK_TYPE_RAM e instala un handler de bloque completo. Los registros no relacionados con el cassette de ese bloque — el teclado (E000 / E001), el 8253 (E004–E007), el joystick (E008) y sus espejos — pasan directamente a través de Z80CPU_readPhysicalMem / Z80CPU_writePhysicalMem, y solo los bits del cassette del Puerto C se superponen en las lecturas y se espían en las escrituras. El camino crítico ajustado PHYSICAL_HW permanece intacto.
  • Familia controlada — el controlador engancha ioPtr[] en los puertos E0–E3 y acciona allí el transporte controlado por computadora.
Las lecturas y escrituras de SD se entregan al Core 0 a través de las colas inter-core requestQueue / responseQueue, de modo que el bucle crítico del Core 1 nunca se bloquea por el almacenamiento. Como se señaló anteriormente, la cola de cinta llega a través del arreglo param de la interfaz y la familia se infiere de la persona.

Arranque de Disquete del MZ-800 desde TZFS

TZFS ahora puede arrancar disquetes de MZ-80A, MZ-700 y MZ-800 — incluyendo CP/M del MZ-800 — directamente desde dentro del monitor, y regresar a TZFS con el interruptor de RESET del hardware después de que un SO arrancado desde disquete haya tomado el control. Esto abarca cambios tanto en la ROM de TZFS como en el firmware del picoZ80. La versión de TZFS es ahora v1.8.3.

Cargador de Arranque TZFS — GETBOOTDSK y Domesticado del 8253

GETBOOTDSK ahora acepta cualquier id de máquina Sharp (01 / 02 / 03) junto con la firma "IPLPRO". Pre-pagina la DRAM del bloque 7 en 0000–0FFF para sistemas operativos que cargan por debajo de 1000H (por ejemplo CP/M del MZ-800), y entrega al programa cargado BC = 0200H — exactamente como lo hace el IPL nativo 9Z-504M — de modo que un cargador de segunda etapa pueda gobernar su propia lectura de directorio.
Antes de ejecutar JP (HL), TZFS domestica el reloj 8253: reprograma el contador 2 a aproximadamente 1 Hz y enmascara el bit 2 del Puerto C del 8255, reflejando el SORES del IPL. Esto evita que un contador de libre ejecución inunde el Z80 con interrupciones RST 38H y aborte un cargador que rehabilita las interrupciones tempranamente — el fallo que producía "P-CP/M80 → No system file".

Handler de Fetch A10-Toggle del MZ-1E05

El controlador MZ-1E05 del picoZ80 añade MZ1E05_IO_A10Toggle, un handler de fetch de aceleración por hardware del FDC que aplica un OR entre DRQ y A10 en las direcciones 0xF3FE / 0xF7FE. Esto permite que la rutina DSKREAD de TZFS transmita un sector de arranque en el MZ-800 exactamente como ya lo hace en el MZ-80A.

Modos de Memoria TZMM_DSKLOAD / TZMM_DSKRUN

Dos modos de memoria de TZFS del picoZ80, TZMM_DSKLOAD y TZMM_DSKRUN, cargan y luego ejecutan un SO arrancado desde disquete en la DRAM nativa del bloque 0 exactamente como lo hace el propio IPL de la máquina, de modo que el SO arrancado ve el mapa de memoria que espera en lugar de la disposición de bancos de TZFS.
Un modo complementario, TZMM_QDLOAD (un mapa TZFS4 con la RAM baja en el bloque 0), sirve a la ruta de arranque del Quick Disk para que un programa cargado desde el QD quede donde luego lo ejecuta la transferencia de control (exec hand-off).

Seguimiento de Fuente PCG cgWindow (MZ-800)

El controlador MZ800 del picoZ80 ahora rastrea el cgWindow de modo que un SO en modo MZ-800 que carga una fuente PCG (mediante IN 0xE0) alcanza la CG-ROM / CG-RAM real mientras se ejecuta bajo TZFS. Esto corrige el texto ilegible bajo el BASIC de disco, mientras que juegos nativos como Flappy — que no tocan la ventana PCG — no se ven afectados.

Restauración de Reset a TZFS

Cuando se pulsa el interruptor de RESET del hardware después de un SO arrancado desde disquete, el firmware recarga el monitor prístino del bloque 0 y la UROM de TZFS — que el SO sobrescribió mientras se ejecutaba en TZMM_DSKRUN, ya que la ROM y la RAM del bloque 0 comparten la misma PSRAM — y luego arranca en frío TZFS. El interruptor de reset, por lo tanto, regresa a TZFS en lugar de caer al monitor 1Z-013A desnudo.

Correcciones de Errores Relacionadas

  • Desajuste de banco PSRAM de Flappy — bajo TZFS, 0x0000–0x0FFF ahora se mapea desde el banco 7 de modo que un programa nativo que hace LDIR de código a la RAM baja y luego cambia el modo de video vea la misma memoria en ambos caminos (una corrección general, no específica de Flappy).
  • Recuperación del GDG en reset por hardware — initVideoText se vuelve a ejecutar una vez que /RESET se libera, de modo que el GDG se re-escanea y se completa la sincronización HBLK del monitor.
  • Retardo de estabilización de reset físico — un retardo de 10 ms permite que el GDG / 8255 a bordo terminen su reset por hardware antes de que el firmware reinicialice el video.

Soporte Amstrad PCW

El modelo AmstradZ80 incorpora los procesadores de texto Amstrad PCW — el PCW-8256, el PCW-8512 y el PCW-9512 — a los hosts soportados. El PCW es arquitectónicamente distinto de las máquinas Sharp: su gate array (ASIC) media en todos los accesos a la DRAM, ejecutando una rotación fija a 1MHz de ranuras de captura de video, refresco y CPU, y marcando el ritmo del Z80 con una onda /WAIT de marcha libre. Eso lo convierte en el host más sensible a la temporización de cuantos acciona el picoZ80. Por ello el controlador combina una precisión de temporización del bus a nivel de nanosegundos — medida frente a un Z80 real con un analizador lógico, véase Fidelidad de la Temporización del Bus — con una arquitectura de sombra en PSRAM que saca por completo la DRAM de la máquina de la ruta crítica de la CPU.

Variantes de Máquina

Un único núcleo de controlador — src/drivers/Amstrad/PCW9512.c — sirve a toda la familia, condicionado por variante en la inicialización. La persona PCW9512 modela la máquina de margarita con unidades de 3.5" de 720K; la persona PCW8256 cubre tanto el PCW-8256 como el PCW-8512 (impresora matricial, unidades CF2 de 3"). Las dos máquinas difieren únicamente en la impresora: PCW8256.c aporta el modelo de estado de la impresora matricial, cuyos bits de estado en el puerto FDh no comparten los significados de la margarita en el mismo puerto. La geometría de la unidad procede de la imagen de disco montada, por lo que no hace falta ningún cambio de controlador entre imágenes CF2 de 180K y de 720K.

Modos de Controlador PHYSICAL y VIRTUAL

El type de nivel superior del controlador selecciona uno de dos modos de funcionamiento. Ambos siguen exactamente la misma ruta de preparación de la máquina física — la diferencia está en qué protege a la CPU de la DRAM de la máquina:
  • PHYSICAL — cada acceso es un ciclo de bus real, exactamente como lo emitiría un Z80 genuino. Es el modo auténtico, y la referencia de precisión: el bus está ajustado en temporización al silicio real. La temporización interna marginal del gate array todavía puede provocar algún píxel suelto ocasional, porque no hay sombra que corrija un ciclo de DRAM mal capturado.
  • VIRTUAL — la misma máquina física, más la sombra de la DRAM descrita a continuación. La CPU se ejecuta desde la sombra: estable, notablemente más rápida (LocoScript lo hace visiblemente), inmune a las peculiaridades de la DRAM de la máquina y capaz de albergar memoria de expansión — mientras la pantalla y el hardware se comportan de forma idéntica.
El nombre de interfaz heredado MIRROR de configuraciones anteriores sigue reconociéndose y simplemente habilita la sombra — el modo VIRTUAL del controlador es el modo espejo.

La Sombra de la DRAM

En modo VIRTUAL el controlador mantiene una sombra byte a byte de la DRAM de la máquina en la PSRAM del RP2350. El hook de escritura se instala desde el encendido, de modo que cada escritura de la CPU llega tanto a la sombra como al bus y la sombra nunca puede desincronizarse. Los hooks de lectura y de fetch se arman cinco segundos después del bootstrap, una vez que la carga del sistema operativo está en marcha. A partir de ese momento:
  • Los accesos servidos no usan el bus. Un fetch o una lectura de un byte en sombra no realiza ningún ciclo de bus — la ejecución corre desde la PSRAM. Cada acceso servido se ritma con ~250 ciclos del RP2350 (~758ns, unos 3 estados T a 4MHz) para que los bucles de retardo calibrados por software mantengan una temporización auténtica.
  • Un mapa de bits de validez por byte garantiza la corrección: un byte que el hook de escritura nunca ha visto se lee siempre desde el bus real.
  • Una guarda de teclado de 16 bytes en la parte alta del bloque 3 se lee siempre desde el bus — el controlador de teclado 8041 del PCW escribe la matriz de teclas en la DRAM a espaldas de la CPU, así que esa ventana nunca debe servirse desde la sombra.
  • Un keepalive M1 emite un ciclo real de fetch de opcode, ajustado en temporización, por cada 256 fetches servidos (~10kHz). El gate array deriva su planificación de refresco de la DRAM y la generación de la interrupción de 300Hz del tráfico de bus observado, de modo que esto mantiene viva la máquina mientras elimina ~99.6% de los strobes del bus.
  • Un filtro de escritura solo-video envía los ciclos de escritura al bus real únicamente donde el ASIC lee de verdad — la roller RAM y las líneas de barrido a las que apuntan sus entradas — rastreado en un mapa de granularidad de 512 bytes sobre los 128K inferiores. El mapa falla en modo abierto: ante cualquier duda la escritura pasa al bus.
El efecto neto es que la DRAM de la máquina solo la lee el ASIC (para el video), la CPU nunca consume un byte corrompido y el bus transporta una pequeña fracción de su tráfico normal — lo que de por sí es la mayor reducción de exposición a los ciclos marginales del gate array.

Reparación de Video

Como la sombra contiene el valor verdadero de cada byte de video, la corrupción residual de la DRAM ya no necesita evitarse a la perfección — se repara. Un barrido en segundo plano de verificar y luego escribir recorre las regiones de video, relee cada byte desde la DRAM real y reescribe desde la sombra únicamente los daños genuinos. El barrido está dirigido por los fetches en breves mini-ráfagas, cubriendo la pantalla completa en aproximadamente 0.6 segundos, de modo que cualquier corrupción es un transitorio de menos de un segundo. Dado que el barrido solo escribe cuando hay discrepancia, su recuento de bytes reparados es un verdadero medidor de corrupción, notificado en la línea periódica de telemetría PCW HEALTH.

Disquete — uPD765 Virtual, Guarda del FDC y Cambio de Pletina

La interfaz FDC selecciona un NEC uPD765 virtual que sirve imágenes .dsk en formato CPC desde la tarjeta SD. Con un FDC virtual la máquina arranca sin disco: el manejador se instala antes de que se ejecute el bootstrap y notifica "no listo" en el registro de estado principal mientras se carga la imagen, de modo que la ROM de arranque simplemente espera — exactamente como lo haría con una unidad que está acelerando. Un PCW con la unidad de disquete averiada o ausente arranca y funciona íntegramente desde imágenes.
Con un FDC físico, una guarda del FDC protege el bucle de transferencia: el uPD765 real sufre un overrun si un byte no se atiende en ~26µs, así que cualquier acceso a un puerto del FDC aparca el servicio de lectura/fetch de la sombra durante la transferencia, rearmándose tras un segundo de silencio del FDC. Los accesos al FDC virtual usan en su lugar un tiempo de espera separado de 100ms para la reparación, de modo que la reparación nunca perturba una transferencia de sector pero se reanuda casi inmediatamente después.
La pletina de disquete puede cambiarse entre Virtual y Real en tiempo de ejecución desde el menú Floppy de la GUI web. Al cambiar a la unidad real se acondiciona primero el controlador físico — un SPECIFY y un RECALIBRATE con purga de interrupciones obsoletas, ya que un controlador que nunca ha sido tocado no tiene ni idea de dónde están sus cabezas — y después se reproduce la elección del BIOS sobre el enrutamiento de la interrupción del FDC a NMI. Al volver atrás se reinicia el controlador virtual conservando sus imágenes cargadas. Esto permite leer discos reales, o rescatarlos a imágenes, en mitad de la sesión en una máquina que arrancó sin disco.

Memoria de Expansión

La interfaz MEMORY (llamada EXPANSION en configuraciones más antiguas — se aceptan ambos nombres) añade memoria por bancos servida desde la PSRAM por encima de la RAM propia de la máquina. La sombra proporciona 96 bancos de 16K — 1.5MB de memoria direccionable en total — de modo que un PCW-8256 de 256K gana hasta 1.25MB y un PCW-8512/9512 de 512K gana 1MB. CP/M dimensiona la memoria extra en una unidad RAM M: mucho mayor. Las páginas de expansión existen solo en la PSRAM y nunca se ponen en el bus: la DRAM real de la máquina simplemente se repite por encima de su tamaño instalado, así que la expansión se comporta como una RAM perfecta y rápida. La PSRAM está organizada como sigue (páginas de 64KB):
Páginas de 64KB Contenido
0–7 Conjunto de trabajo del núcleo del firmware
8–31 Sombra de la DRAM + memoria de expansión (1.5MB)
32–34 Mapa de bits de validez de la sombra
35–47 Búfer de imagen de la unidad de disco 0
48–60 Búfer de imagen de la unidad de disco 1
61 Área temporal para la creación de imágenes de disco
62–63 Libre

El Modelo de Paginación — Medido, No Supuesto

La sombra debe seguir con exactitud la decodificación de bancos del gate array, así que el modelo de paginación se midió en hardware real con el diagnóstico PAGEPROBE del controlador (un barrido de un solo disparo que siembra firmas a través de todos los valores del registro de banco y relee qué página física mapea cada uno — la máquina se sacrifica a la medición y necesita un ciclo de alimentación después). Se sondearon los gate arrays tanto del PCW-8256 como del PCW-9512 y decodifican de forma idéntica:
  • Paginación PCW extendida (bit 7 del valor de banco a 1): página física = (value & 0x7F) mod nBanks — el aliasing por módulo es real y está verificado.
  • Paginación al estilo CPC (bit 7 a 0): página de lectura (value >> 4) & 7 y página de escritura value & 7 separadas.
  • Bits de bloqueo del puerto F4 fuerzan las lecturas de un bloque bloqueado a su banco de escritura. El orden de los bits no es secuencial y se determinó empíricamente: bloque 0 = 0x40, bloque 1 = 0x10, bloque 2 = 0x20, bloque 3 = 0x80.

Trabajo de Temporización y Telemetría

La compilación Amstrad lleva su propia copia del motor de bus PIO — src/z80_amstrad.pio, idéntico byte a byte a z80.pio en el momento de su creación — de modo que el trabajo de temporización específico del PCW nunca puede perturbar el motor probado con el que funcionan las máquinas Sharp, Tatung y Base. Sobre él, el controlador usa flujos de instrucciones ajustados en temporización que sitúan cada flanco de las líneas de control donde lo pone un Z80 real: la comparación con analizador lógico mostró que los flujos originales activaban las líneas de control 10–25ns antes que el silicio real, y que el gate array decodificaba mal, de forma visible, esos flancos tempranos. Medidas adicionales:
  • Ciclos de E/S y escritura sincronizados con la rejilla — cada petición de E/S y de escritura se fija a la fase en la que el gate array la integra en su rotación de DRAM a 1MHz, en lugar de forzar al ASIC a insertar una ranura de servicio fuera de la rejilla.
  • Reconocimiento de interrupción IM1 — la compilación Amstrad emite el ciclo de bus real de reconocimiento M1+IORQ en cada interrupción aceptada, como hace el silicio genuino. (Existe un flag de compilación genérico Z80_IM1_INTA para otras máquinas, desactivado por defecto.)
  • Perfilador de rejilla WAIT sin analizador lógico — en cada arranque el controlador muestrea los pines /WAIT y CLK con una resolución de ~13ns e imprime una línea PCW GRID: (periodo de WAIT, anchura de pulso, periodo de CLK, fase) frente a la tarjeta de referencia, de modo que la temporización del gate array de una máquina puede comprobarse sin conectar un analizador lógico.
  • Telemetría de salud — una línea periódica PCW HEALTH: notifica recuentos de huecos de fetch, divergencia de la sombra, enrutamiento de escrituras (bus frente a sombra), bytes reparados y estadísticas del barrido.
Las personas PCW suministradas ejecutan el RP2350 a 330MHz con el núcleo a 1.20V.

Dispositivos Periféricos Virtuales en la Compilación Amstrad

Restricción: el framework de Dispositivos Periféricos Virtuales no está disponible en la práctica en la compilación Amstrad — sus límites de instancias se recortan a un único dispositivo para liberar la SRAM interna que necesita la sombra de la DRAM. Las demás compilaciones no se ven afectadas.

Fidelidad de la Temporización del Bus — picoZ80 frente a un Z80 Real

¿Cuánto se acerca el bus del picoZ80 al silicio genuino? En el Amstrad PCW — el host cuyo gate array media en cada acceso a memoria y es por tanto el juez más severo — la pregunta se respondió midiendo: se capturó la temporización de los ciclos de bus del picoZ80 y se comparó con la de un Zilog Z80 genuino en el mismo PCW-8256, en la misma placa y monitor.

Método

  • Capturado con un analizador lógico Kingst LA5016 a 500MHz (resolución de 2ns) sobre el bus en funcionamiento: CLK, /WAIT, /MREQ, /RD, /WR, /IORQ, /M1, /RFSH, /INT, A15, A0, D7, D0.
  • Cada cifra es la mediana de varios miles de ciclos en una ventana en régimen estacionario.
  • Las capturas de referencia del Z80 real y las del picoZ80 se tomaron en condiciones equivalentes (CP/M en el prompt A> / copia con PIP). El picoZ80 se midió a dos relojes de núcleo, 340MHz y 350MHz, para mostrar que el resultado es independiente del reloj.
  • Las posiciones de los flancos se expresan respecto al flanco de CLK más cercano: los strobes de memoria se refieren al flanco descendente de CLK (mitad del estado T); /M1 y /IORQ se refieren al flanco ascendente de CLK (inicio de T1), tal como los especifica la hoja de datos del Z80.

Comparación de Temporización

Parámetro Z80 real picoZ80 @340MHz picoZ80 @350MHz Δ frente al real
Periodo de CLK 250 ns 250 ns 250 ns 0 ns
Pulso alto de /WAIT 184 ns 182 ns 182 ns −2 ns
Caída de /MREQ (tras CLK↓) +42 ns +48 ns +46 ns +4…6 ns
Caída de /RD (tras CLK↓) +52 ns +48 ns +48 ns −4 ns
Caída de /WR (tras CLK↓) +36 ns +46 ns +46 ns +10 ns
Caída de /M1 (tras CLK↑) +54 ns +52 ns +50 ns −2…4 ns
Caída de /IORQ (tras CLK↑) +40 ns +28 ns +28 ns −12 ns
Anchura a nivel bajo de /MREQ (ciclo de mem.) 500 ns 498 ns 496 ns −2…4 ns
Dirección válida antes de /MREQ 115 ns ~185 ns ~180 ns +65 ns (antes)
(Columna Δ: a qué distancia del Z80 real queda el picoZ80; el signo indica más tarde (+) o más temprano (−).)

Qué Muestran las Cifras

  • Los tres strobes de la DRAM coinciden con el silicio real dentro de ~10ns, la mayoría dentro de 5ns: /MREQ, /RD y /M1 están todos a pocos nanosegundos del Z80 genuino, y /M1 — el identificador de fetch de opcode al que el ASIC del PCW es más sensible — cae en el objetivo a +50…52ns frente a los +54ns del chip real.
  • El resultado es independiente del reloj: 340MHz y 350MHz producen las mismas posiciones de flanco dentro de la resolución de medida, confirmando que la temporización la fija la lógica de ciclo, no el reloj de núcleo del RP2350.
  • La anchura del pulso de /MREQ coincide en ~500ns (un ciclo de memoria completo), así que el gate array ve el strobe activado durante la misma duración que con un Z80 real.
  • El establecimiento de dirección es más largo que el de un Z80 real, no más corto (~180ns frente a 115ns). El picoZ80 impone la dirección pronto y la mantiene estable durante todo el ciclo, de modo que el ASIC siempre captura una dirección asentada — una desviación segura, no un riesgo.
  • /IORQ es la mayor diferencia, 12ns antes. Los ciclos de E/S se dejan deliberadamente en marcha libre en lugar de alinearlos a la ranura, porque el gate array se refasa alrededor de la E/S por diseño; esto no afecta a la integridad de la DRAM.

Resumen

En todas las señales que gobiernan la DRAM del PCW, el picoZ80 reproduce un Zilog Z80 real dentro de un margen de un dígito hasta ~10 nanosegundos, con independencia del reloj de núcleo. Las únicas desviaciones significativas son el strobe de E/S (12ns antes, en ciclos que el ASIC refasa de todos modos) y un establecimiento de dirección más largo y más seguro. Para el acceso a memoria — la ruta que importa para la corrección — el bus del picoZ80 es, a la resolución de un analizador de 500MHz, el mismo que el silicio real. Este trabajo se realizó sobre el Amstrad PCW porque su gate array es el observador más exigente de la temporización del bus entre los hosts soportados; los flujos de ciclo ajustados en temporización están condicionados a Amstrad, mientras que todas las demás máquinas ejecutan el motor original, probado desde hace mucho tiempo.

Framework de Dispositivos Virtuales

El tipo de bloque FUNC permite la emulación arbitraria de E/S llamando funciones handler C en cada acceso al bus. Cualquier bloque de 512 bytes de memoria o rango de puertos E/S puede estar respaldado por una función.

Firmas de Funciones Handler

Los handlers FUNC de memoria se almacenan en la tabla memioPtr en PSRAM. Los handlers FUNC de E/S se almacenan en la tabla ioPtr. Las firmas de las funciones son:
/* Memory read handler */
uint8_t mem_read_handler(uint16_t addr, void *ctx);

/* Memory write handler */
void mem_write_handler(uint16_t addr, uint8_t data, void *ctx);

/* I/O read handler */
uint8_t io_read_handler(uint8_t port, void *ctx);

/* I/O write handler */
void io_write_handler(uint8_t port, uint8_t data, void *ctx);
Las funciones handler se llaman directamente desde el bucle principal del Core 1. Deben completarse antes de que expiren los wait states del ciclo de bus actual — mantenga los handlers breves y evite cualquier operación bloqueante (E/S de archivos, UART, etc.). Si un handler necesita disparar una operación más larga (por ejemplo, cargar un sector de disco), debe enviar un mensaje al Core 0 a través de la cola inter-core y retornar inmediatamente con un byte de estado, difiriendo la E/S real al Core 0.

Escribir un Nuevo Controlador

Para añadir soporte para un nuevo periférico o máquina host:
  1. Crear un nuevo archivo .c / .h en el directorio src/drivers/.
  2. Implementar funciones handler de lectura y escritura que coincidan con las firmas anteriores.
  3. Registrar los punteros a funciones handler en las tablas memioPtr o ioPtr durante la inicialización del controlador.
  4. Añadir el controlador al objetivo de compilación CMakeLists.txt.
  5. Añadir una entrada de cadena de tipo para que el parser de configuración JSON pueda instanciar el controlador por nombre.
  6. Documentar las claves param del controlador en el archivo de cabecera del controlador.
La función de inicialización del controlador se llama una vez durante el arranque, después de que se haya parseado config.json. El controlador recibe un puntero a su bloque de configuración de interfaz y debe configurar cualquier estado interno y registrar sus handlers en ese momento.

Dispositivos Periféricos Virtuales

Un dispositivo periférico virtual es un chip de apoyo completo — un Z80 DMA, un Z80 CTC, un 8255, etc. — añadido a una máquina simplemente declarándolo en config.json. El firmware modela el circuito integrado real, registro por registro, y el chip aparece en el espacio de direcciones del Z80 exactamente igual que lo haría una pieza física soldada a una placa. Los dispositivos se cablean entre sí mediante una netlist, de modo que el pin de salida de un chip puede excitar el pin de entrada de otro, igual que hace una pista en un circuito impreso.
Este es un mecanismo distinto del Marco de Dispositivos Virtuales descrito anteriormente. Un bloque FUNC es un handler — código C que el firmware llama en cada acceso al bus, escrito por un desarrollador y compilado dentro del firmware. Un dispositivo periférico virtual es una declaración — una instancia de un chip que el firmware ya modela, creada a partir de datos de configuración y sin código alguno. También es distinto de una tarjeta de interfaz, que es un compuesto específico de máquina (imágenes ROM, medios, mapeo de direcciones) declarado bajo drivers[].if[]. Los tres son pares, no capas: una tarjeta de interfaz puede muy bien contener el mismo chip que declara un dispositivo.
El propósito es permitir que cualquiera construya su propio sistema Z80 sobre un picoZ80 sin escribir firmware. Una personalidad de máquina aporta la CPU, la memoria y el vídeo; los dispositivos aportan los periféricos; y la netlist aporta el cableado entre ellos.

El Catálogo de Dispositivos

Hay trece dispositivos disponibles. La columna name es la cadena exacta que se usa en config.json. La columna Core de servicio importa para la temporización: los dispositivos servidos en el Core 0 son avanzados por un temporizador de marcha libre independiente del Z80, mientras que los dispositivos del Core 1 son accionados por la actividad del bus.
name Chip modelado Pines Tamaño de decodificación Core de servicio
Z80DMA Controlador de Acceso Directo a Memoria Zilog Z8410 10 1 Core 1
Z80CTC Circuito Contador / Temporizador Zilog Z8430 10 4 Core 0
Z80PIO Controlador de Entrada / Salida Paralela Zilog Z8420 23 4 Core 1
PIT8253 Temporizador de Intervalo Programable Intel 8253 / 8254 6 4 Core 0
PPI8255 Interfaz Periférica Programable Intel 8255 24 4 Core 1
WD1773 Controlador de Disquete Western Digital WD1773 5 8 Core 1
MC6850 Puerto serie asíncrono ACIA Motorola 68B50 4 2 Core 1
I8251 Puerto serie USART Intel 8251 / 8251A 7 2 Core 1
Z80DART Zilog Z80 DART — canal serie asíncrono dual (comparte el modelo de chip del Z80 SIO) 13 4 Core 1
MSM5832 Reloj de tiempo real / calendario OKI MSM5832 1 16 Core 1
Centronics Puerto de impresora paralelo Centronics — la interfaz, no un chip 4 2 Core 1
Latch no es un chip real — un latch / puerto de propósito general 16 1 Core 1
SignalPort no es un chip real — un puerto de prueba y puente 16 1 Core 1
Además de los dispositivos declarados, siempre existe un pseudo-dispositivo permanente llamado cpu. Expone los propios pines de control del procesador, de modo que un dispositivo pueda cablearse a la máquina misma.

Declarar un Dispositivo

Los dispositivos se declaran en un array device dentro de un objeto de partición z80[], junto a los arrays ya existentes memory, io y drivers. Cada partición lleva sus propios dispositivos y su propia netlist, así que ambas pueden equiparse de forma muy distinta. Fíjese bien en que name selecciona qué chip, mientras que type selecciona qué espacio de direcciones — esto suele pillar desprevenida a la gente.
Clave Tipo Requerido Por defecto Descripción
name cadena sí — Tipo de chip del catálogo anterior. No distingue mayúsculas de minúsculas. Un nombre no reconocido se omite con un mensaje de registro.
addr número sí — Dirección base de la decodificación.
id cadena no dev<i>N</i> Etiqueta de la instancia, usada para referirse a este chip al cablearlo. Máximo 11 caracteres; los nombres más largos se truncan.
type cadena no "IO" Espacio de direcciones — "MEMORY" coloca el chip en el espacio de memoria; cualquier otro valor lo coloca en el espacio de E/S.
size número no 1 Número de direcciones consecutivas decodificadas.
enable número no habilitado 0 deshabilita el dispositivo. Use el número 0, no false.
busack número no 1 Activar el reconocimiento de bus del anfitrión mientras este dispositivo posee el bus. Solo Z80 DMA.
maxburst número no 4096 Máximo de bytes movidos antes de permitir que el procesador recupere el bus. Solo Z80 DMA; 0 elimina la salvaguarda.
Algunos dispositivos admiten claves adicionales propias. Las claves machine, drives y file del controlador de disquete y la clave clockhz del 8253 se describen junto a sus pinouts más abajo; los dispositivos serie, de reloj, de impresora y de latch admiten las siguientes:
Clave Se aplica a Por defecto Descripción
usb MC6850, I8251, Z80DART, Centronics 2 (serie), 4 (impresora) El canal USB CDC (0–4) que transporta el flujo de datos del dispositivo. El DART reclama dos canales consecutivos: usb es el canal A y usb+1 es el canal B.
hours MSM5832 24 horas El número 12 selecciona el modo de 12 horas; cualquier otro valor, u omitir la clave, selecciona el modo de 24 horas.
strobe Centronics auto "control" captura el byte a imprimir en el flanco ascendente del bit 0 del registro de control; por defecto se captura en la propia escritura del registro de datos.
int Centronics 0 Un valor distinto de cero activa la línea de interrupción a medida que se acepta cada byte; la lectura de estado la borra.
readback Latch muestreo "latch" hace que una lectura devuelva el último byte escrito; por defecto se muestrean los ocho pines IN.
Adónde van los flujos de bytes. Cada dispositivo serie es un flujo de bytes en uno de los cinco canales USB CDC del puerto USB del picoZ80 — conecte la placa a un PC y los datos del chip aparecen allí como un puerto serie ordinario. Los registros de velocidad en baudios y de formato de palabra de los chips serie se registran pero no se simulan: los datos se mueven a la velocidad del USB independientemente de lo que programe el invitado. La impresora es solo de salida y activa BUSY mientras su cola de 511 bytes está llena, de modo que el controlador del invitado reintenta y nunca se pierde ningún carácter. Los comandos ser, rtc y prn del shell de depuración inyectan caracteres en estos dispositivos, y leen de ellos, directamente — no hace falta conectar ningún terminal.
Los dispositivos se aplican después de las regiones memory e io de la personalidad, por lo que la decodificación de un dispositivo es la que manda — prevalece sobre cualquier región genérica que cubra la misma dirección. La decodificación debe caer íntegramente dentro del espacio de 64K o el dispositivo se omite.
"device": [ {
              "name":     "Z80DMA",
              "id":       "dma0",
              "enable":   1,
              "type":     "IO",
              "addr":     0x0F,
              "size":     1,
              "busack":   1,
              "maxburst": 4096
            },
            {
              "name":     "Z80CTC",
              "id":       "ctc0",
              "enable":   1,
              "type":     "IO",
              "addr":     0x10,
              "size":     4
            } ]

El array interlink es la netlist. Cada entrada describe un cable: un único pin de destino, y una o varias fuentes que lo excitan. Un pin se referencia como <id>.<PIN>, por ejemplo dma0.RDY o cpu.INT. Ninguna de las dos partes distingue mayúsculas de minúsculas.
Clave Tipo Requerido Descripción
dst cadena sí El único pin de entrada que este cable excita. Cada entrada puede ser excitada por exactamente un cable.
src array sí De uno a ocho términos fuente. Un término es una referencia a un pin, o la cadena literal "0" o "1". Un ! inicial invierte ese término.
op cadena no Cómo se combinan varias fuentes — BUF, NOT, AND, OR, NAND, NOR, XOR, XNOR. Por defecto BUF para una fuente y OR para dos o más.
enable número no 0 deshabilita este cable.
name cadena no Una etiqueta para su propia comodidad. El firmware la ignora.
Importante: una red transporta el nivel eléctrico del pin, nunca un estado abstracto de "activado". 1 es alto y 0 es bajo, exactamente como lo leería un voltímetro. Los pines activos a nivel bajo como /INT y /BUSREQ reposan en alto y se activan pasando a bajo. Esto es lo que hace que la netlist se comporte como un esquema: una NOR de dos salidas de temporizador activas a nivel alto hace en el picoZ80 exactamente lo que hace sobre el papel.

Las Tres Maneras de Excitar un Pin

Directamente, uno a uno — omita op e indique una única fuente. Esto es un cable sencillo desde la salida de un chip hasta la entrada de otro.
{ "name": "la peticion de datos del disquete excita el DMA",
  "dst":  "dma0.RDY",
  "src":  [ "fdc0.DRQ" ] }
Amarrado en firme a un nivel fijo — indique el literal "0" o "1" como única fuente. Es el equivalente a atar un pin a masa o a la línea de alimentación, y es lo que debe hacer con cada entrada que el diseño no utilice. Una entrada sin cablear no es un error, pero se notificará en el arranque y leerá su nivel de reposo, de modo que amarrarla explícitamente documenta su intención.
{ "dst": "dma0.CE",   "src": [ "0" ] }      /* atado a bajo - seleccionado de forma permanente */
{ "dst": "dma0.WAIT", "src": [ "1" ] }      /* atado a alto - nunca se activa                 */
A través de una condición lógica — indique de dos a ocho fuentes y un op. Los términos se combinan de izquierda a derecha, y cualquier término puede invertirse individualmente con un ! inicial. Es el equivalente a soldar una pequeña puerta lógica entre los chips.
/* El DMA funciona solo mientras ninguna salida de temporizador este alta.     */
{ "name": "el temporizador habilita el DMA",
  "dst":  "dma0.RDY",
  "op":   "NOR",
  "src":  [ "ctc0.ZCTO0", "ctc0.ZCTO1" ] }

/* El bit 1 del puerto A, invertido, excita el bit 0 del puerto B.             */
{ "dst": "pio0.B0", "src": [ "!pio0.A1" ] }
Las líneas de interrupción son un caso especial que conviene entender. En el hardware real cada pin /INT es una salida de drenador abierto: cualquier chip puede tirar de la línea compartida hacia abajo, y esta solo sube cuando todos la liberan. La netlist reproduce esto con un AND de todas las salidas de interrupción. Debe escribir "op":"AND" de forma explícita — el valor por defecto para varias fuentes es OR, lo que invertiría el significado y dispararía una interrupción solo cuando todos los chips la activasen a la vez.
{ "name": "cadena de interrupciones",
  "dst":  "cpu.INT",
  "op":   "AND",
  "src":  [ "dma0.INT", "ctc0.INT", "pio0.INT" ] }

Prioridad de Interrupción — la Cadena Daisy

La prioridad entre chips que interrumpen la decide cómo los cablee usted, no un número en la configuración — exactamente igual que en un sistema Z80 real. Cada chip tiene una entrada IEI (interrupt enable in) y una salida IEO (interrupt enable out). Cablear el IEO de un chip al IEI del siguiente sitúa al segundo por debajo del primero en prioridad. El primer chip de la cadena tiene su IEI amarrado a 1.
Un chip pone su IEO en alto solo cuando su propio IEI está alto y además ni está interrumpiendo ni está siendo atendido. Esa única regla produce todo el comportamiento: un chip que está siendo atendido mantiene bloqueado todo lo que está por debajo de él hasta que termina, y un chip cuyo IEI ha pasado a bajo sabe que un dispositivo de mayor prioridad tiene la atención del procesador y permanece callado.
/* ctc0 tiene la prioridad mas alta, luego dma0, y luego pio0.                 */
{ "dst": "ctc0.IEI", "src": [ "1" ] },
{ "dst": "dma0.IEI", "src": [ "ctc0.IEO" ] },
{ "dst": "pio0.IEI", "src": [ "dma0.IEO" ] }
El modo de interrupción 2 está totalmente soportado. Al reconocerse la interrupción, el dispositivo de mayor prioridad que la esté solicitando y no esté bloqueado suministra su vector programado, se marca a sí mismo en servicio y limpia su indicador de pendiente; un RETI limpia de nuevo el estado en servicio y libera a los chips situados por debajo.
Las tarjetas físicamente instaladas siempre tienen prioridad sobre los dispositivos declarados. Un periférico real en el bus del anfitrión tiene sus propios pines IEI e IEO, que el picoZ80 no puede ver ni arbitrar — no hay pines de procesador libres para ellos — por lo que el firmware ofrece primero el reconocimiento a cualquier handler de tarjeta existente y solo lo reclama cuando ninguna tarjeta responde. Esto es lo que permite añadir dispositivos a una máquina que ya lleva instalada, por ejemplo, una tarjeta MZ-8BIO3 o una tarjeta serie MZ-1E24 sin perturbarla.

Patillaje de los Dispositivos

Las tablas siguientes enumeran todos los pines que cada dispositivo expone a la netlist. La dirección se da desde el punto de vista del chip. Reposo es el nivel que lee una entrada cuando nada la excita. Los pines marcados como declarado existen y pueden cablearse, pero el firmware todavía no actúa sobre ellos — se listan para que una configuración escrita hoy siga siendo correcta cuando entren en funcionamiento.
cpu — el pseudo-dispositivo del procesador
Pin Dirección Reposo Función
INT ent 1 Petición de interrupción enmascarable. El único destino funcional en la CPU. Ponerlo a nivel bajo genera una interrupción.
BUSACK sal 1 Reconocimiento de bus. Declarado — todavía no se excita, así que se lee permanentemente alto.
BUSRQ, NMI, RESET, WAIT ent 1 Declarados — cablearlos todavía no tiene efecto.
M1, CLK, HALT sal — Declarados — todavía no se excitan.
Z80DMA — Acceso Directo a Memoria (Z8410)
Pin Dirección Reposo Función
RDY ent 1 Petición de transferencia desde el periférico. La única entrada funcional. Su sentido activo lo fija el registro WR5 del chip, no la configuración. Una transición al estado activo inicia una transferencia.
BUSREQ sal 1 Petición de bus, activa a nivel bajo, drenador abierto. Se pone a bajo mientras el DMA retiene el bus.
INT sal 1 Petición de interrupción, activa a nivel bajo, drenador abierto.
IEI ent 1 Entrada de prioridad de interrupción. Cablear desde el IEO del chip anterior, o amarrar a 1 si este es el primero de la cadena.
IEO sal 1 Salida de prioridad de interrupción. Cablear al IEI del siguiente chip.
BAI, CE, WAIT, M1 ent 1 Declarados — el modelo no los consulta.
BAO sal 1 Declarado — todavía no se excita.
Z80CTC — Contador / Temporizador (Z8430)
Pin Dirección Reposo Función
CLKTRG0 … CLKTRG3 ent 0 Entrada de reloj o disparo para los canales 0 a 3. El flanco significativo lo elige la palabra de control del canal.
ZCTO0 … ZCTO2 sal 0 Cuenta a cero / fin de tiempo para los canales 0 a 2, emitido como un pulso breve. El canal 3 no tiene pin de salida, exactamente igual que en el dispositivo real.
INT sal 1 Petición de interrupción, activa a nivel bajo, drenador abierto.
IEI ent 1 Entrada de prioridad de interrupción — véase la cadena daisy.
IEO sal 1 Salida de prioridad de interrupción.
Z80PIO — Entrada / Salida Paralela (Z8420)
Pin Dirección Reposo Función
A0 … A7 bidireccional 1 Bits de datos del puerto A. La dirección sigue el modo programado por el Z80 en tiempo de ejecución.
B0 … B7 bidireccional 1 Bits de datos del puerto B.
ASTB, BSTB ent 1 Entradas de estrobo de puerto, activas a nivel bajo. El flanco de subida es el significativo.
ARDY, BRDY sal 0 Salidas de listo del protocolo de puerto, activas a nivel alto.
INT sal 1 Petición de interrupción, activa a nivel bajo, drenador abierto.
IEI ent 1 Entrada de prioridad de interrupción — véase la cadena daisy.
IEO sal 1 Salida de prioridad de interrupción.
Las cuatro direcciones decodificadas son, en orden: datos del puerto A, control del puerto A, datos del puerto B y control del puerto B. El reinicio selecciona el modo 1 (entrada) en ambos puertos.
PIT8253 — Temporizador de Intervalo (8253 / 8254)
Pin Dirección Reposo Función
GATE0 … GATE2 ent 1 Entradas de habilitación de los contadores. Los modos 0, 2, 3 y 4 responden al nivel; los modos 1 y 5 se disparan con el flanco de subida.
OUT0 … OUT2 sal 1 Salidas de los contadores.
Las cuatro direcciones decodificadas son los contadores 0, 1 y 2 seguidos de la palabra de control. El 8253 no tiene pin de interrupción. A este dispositivo se le aplica una clave de configuración adicional: "clockhz" fija la frecuencia de entrada del contador en hercios, con valor por defecto 1000000. Las máquinas Sharp MZ accionan su temporizador a 895000.
PPI8255 — Interfaz Paralela (8255)
Pin Dirección Reposo Función
PA0 … PA7 bidireccional 1 Bits de datos del puerto A.
PB0 … PB7 bidireccional 1 Bits de datos del puerto B.
PC0 … PC7 bidireccional 1 Bits de datos del puerto C. Las mitades alta y baja tienen direcciones independientes.
Las cuatro direcciones decodificadas son los puertos A, B y C seguidos de la palabra de control. Las líneas de protocolo de los modos 1 y 2 no se exponen a la netlist.
Limitación conocida
Excitar cualquier pin de entrada individual de un puerto del 8255 borra actualmente los demás bits de entrada de ese mismo puerto. Hasta que esto se corrija, use los pines de entrada del 8255 de uno en uno, o utilice en su lugar un Z80 PIO — el PIO trata cada bit de forma independiente y no se ve afectado.
WD1773 — Controlador de Disquete
Pin Dirección Reposo Función
DDEN ent 1 Selección de doble densidad, activa a nivel bajo. Un nivel bajo selecciona MFM.
WPRT ent 1 Protección contra escritura, activa a nivel bajo.
INTRQ sal 0 Petición de interrupción. Activa a nivel alto en este dispositivo, a diferencia de los chips de Zilog.
DRQ sal 0 Petición de datos. Pensada para excitar el pin RDY de un DMA.
MOTOR sal 0 Motor encendido.
Las ocho direcciones decodificadas siguen la disposición del Sharp MZ-1E05: estado y comando, pista, sector, datos, selección de unidad y motor, selección de cara, densidad y una segunda lectura de estado. Se aplican tres claves de configuración adicionales — "machine" selecciona la geometría de la imagen ("MZ-80B", "MZ-2500", o MZ-700 si se omite), "drives" fija el número de unidades de 1 a 4, y "file" nombra la imagen o imágenes de disco a montar. Solo puede declararse un WD1773.
MC6850 — Puerto Serie (ACIA 68B50)
Pin Dirección Reposo Función
DCD ent 0 Detección de portadora, activa a nivel bajo. En reposo está activada para que un diseño sin cablear siga recibiendo.
CTS ent 0 Listo para enviar (clear to send), activa a nivel bajo. En reposo está activada para que un diseño sin cablear siga transmitiendo.
RTS sal 1 Petición de envío (request to send), activa a nivel bajo. Solo se activa cuando el registro de control lo selecciona.
IRQ sal 1 Petición de interrupción, activa a nivel bajo, drenador abierto.
Las dos direcciones decodificadas son control (escritura) / estado (lectura) y, a continuación, datos. Escribir 11 en el campo de división del contador del registro de control es el reset maestro del chip, exactamente como en el componente real; por lo demás, la relación de división y el formato de palabra se registran pero no se simulan. Un overrun de recepción se señala en el registro de estado y se borra leyendo el registro de datos.
Nota de cableado
La salida de interrupción del 68B50 se llama IRQ, como en su hoja de datos — no INT. El auto-cableado al añadir de la interfaz web acepta cualquiera de los dos nombres y lo une a cpu.INT, en AND cableado con las demás fuentes de interrupción (interfaz web 3.58 en adelante; las versiones anteriores solo reconocían INT y dejaban sin cablear la interrupción del 68B50).
I8251 — Puerto Serie (USART 8251)
Pin Dirección Reposo Función
DSR ent 0 Equipo de datos listo (data set ready), activa a nivel bajo. En reposo está activada.
CTS ent 0 Listo para enviar (clear to send), activa a nivel bajo. En reposo está activada.
DTR sal 1 Terminal de datos listo (data terminal ready), activa a nivel bajo.
RTS sal 1 Petición de envío (request to send), activa a nivel bajo.
TXRDY sal 0 Transmisor listo, activa a nivel alto.
RXRDY sal 0 Receptor listo, activa a nivel alto.
INT sal 1 Petición de interrupción, activa a nivel bajo, drenador abierto. RxRDY y TxRDY se combinan en OR sobre este pin.
Las dos direcciones decodificadas son datos y, a continuación, control (escritura) / estado (lectura) — nótese el orden opuesto al del 68B50. Se respeta la secuencia de escritura característica del 8251: tras el reset la primera escritura de control es la instrucción MODE, un modo síncrono va seguido de uno o dos caracteres SYNC, y solo entonces entra en vigor el registro COMMAND; un comando con el bit de reset interno devuelve el chip a la espera de un modo. Los caracteres de sincronismo se almacenan pero no se emula ningún entramado síncrono, y los bits de error de paridad y de trama nunca se activan. Una escritura de datos con el transmisor deshabilitado se descarta, como en el componente real.
Z80DART — Puerto Serie Dual (Z80 DART)
Pin Dirección Reposo Función
DCDA, DCDB ent 0 Detección de portadora de los canales A y B, activa a nivel bajo. En reposo está activada.
CTSA, CTSB ent 0 Listo para enviar de los canales A y B, activa a nivel bajo. En reposo está activada.
RIA, RIB ent 1 Indicador de llamada. Declarado — el modelo no los consulta.
RTSA, RTSB sal 1 Petición de envío, activa a nivel bajo, siguiendo a WR5.
DTRA, DTRB sal 1 Terminal de datos listo, activa a nivel bajo, siguiendo a WR5.
INT sal 1 Petición de interrupción, activa a nivel bajo, drenador abierto.
IEI ent 1 Entrada de prioridad de interrupción — véase la cadena daisy.
IEO sal 1 Salida de prioridad de interrupción.
Las cuatro direcciones decodificadas son, en orden: datos A, control A, datos B, control B. El DART es el único dispositivo serie que se incorpora a la cadena daisy de interrupciones, con interrupciones totalmente vectorizadas — el vector de WR2 se suministra en el reconocimiento con status-affects-vector aplicado, exactamente como hace el SIO. No es casualidad: el DART comparte el modelo de chip del Z80 SIO en el firmware — un mismo circuito integrado, dos caras de dispositivo — y se ofrece aquí como "el SIO sin los modos síncronos", que nunca se dan sobre un flujo de bytes USB. Un overrun de recepción fija el bit de error en lugar de descartarse en silencio. El DART ocupa dos canales USB: su canal usb para A y el siguiente para B.
MSM5832 — Reloj de Tiempo Real
Pin Dirección Reposo Función
HOLD ent 0 Congela los registros legibles, activa a nivel alto. Al liberarla se relee el reloj inmediatamente.
Los cuatro bits bajos de dirección son la selección de registro del chip, razón por la que el dispositivo ocupa un bloque de dieciséis direcciones: los registros 0 a 12 son los trece dígitos BCD desde los segundos hasta el año. El reloj obtiene la hora del sistema del ESP32 a través del enlace entre procesadores — el reloj propio del ESP32 lo fija el navegador en cada carga de página o el SNTP de la pasarela, y la hora local que notifica se reintenta cada diez segundos hasta que ha sido fijada de verdad, de modo que el chip lee la fecha y la hora correctas poco después de que lo haga la red. El comando rtc del shell de depuración muestra si el reloj se ha sincronizado. Un reloj que el programa invitado ya ha puesto en hora por sí mismo se deja intacto. Escribir fija el reloj como un desplazamiento respecto a la hora del sistema: una rutina de puesta en hora escribe los dígitos y termina con el registro de unidades de segundos, que es la escritura que recalcula el desplazamiento. El modo de doce horas (la clave hours) activa el flag PM en el registro de decenas de horas exactamente como lo hace el componente real.
Centronics — Puerto de Impresora
Pin Dirección Reposo Función
BUSY sal 0 Impresora ocupada, activa a nivel alto. Se activa mientras la cola de impresión está llena.
ACK sal 1 Reconocimiento. Declarado — todavía no se excita; el reconocimiento aparece en su lugar en el registro de estado.
STROBE ent 1 Strobe externo. Declarado — no se actúa sobre él; el strobe se realiza mediante el registro de control o la escritura de datos.
INT sal 1 Interrupción en el reconocimiento, activa a nivel bajo, drenador abierto. Se habilita con la clave de configuración int.
Las dos direcciones decodificadas son datos (una lectura devuelve el latch, una escritura captura un byte) y, a continuación, estado (lectura) / control (escritura). Es un modelo de la interfaz, no de ningún chip concreto: el extremo remoto es un canal serie USB, así que el registro de estado siempre notifica una impresora presente, seleccionada y en buen estado. El bit de estado de reconocimiento es de un solo disparo y se borra con la lectura de estado, y el bit de control INIT vacía la cola.
Latch — latch / puerto de propósito general
Pin Dirección Reposo Función
IN0 … IN7 ent 0 Una lectura del Z80 en la dirección ensambla estos ocho niveles en los bits 0 a 7.
OUT0 … OUT7 sal 0 Una escritura del Z80 excita estos ocho niveles a partir de los bits 0 a 7.
La lógica de pegamento que todo diseño casero tiene para la conmutación de bancos y las líneas de control — invisible en el hardware real porque no es más que un latch de la serie 74 y un búfer, pero aquí está cableada directamente en la netlist y es direccionable desde código Z80. Una escritura excita los ocho cables OUT; una lectura muestrea los ocho cables IN, o devuelve el último byte escrito cuando se establece "readback": "latch". El latch decodifica una única dirección, y su valor sobrevive a un reset del Z80 — solo se borra cuando se aplica la configuración.
SignalPort — puerto de prueba y puente
Pin Dirección Reposo Función
IN0 … IN7 ent 0 Una lectura del puerto por parte del Z80 devuelve estos ocho niveles como los bits 0 a 7.
OUT0 … OUT7 sal 0 Una escritura del Z80 excita estos ocho niveles.
Esto no es el modelo de ningún chip real. Es el instrumento de laboratorio de la netlist: pone ocho cables en una única dirección de E/S para que un programa que corra en el Z80 pueda inyectar señales en el diseño y muestrear lo que vuelve. Es la forma más directa de probar una idea de cableado antes de comprometerse con ella.

Ejemplo Resuelto — Añadir un Z80 DMA

Lo siguiente es una configuración completa y funcional. Coloca un Z80 DMA en el puerto de E/S 0x0F, un Z80 CTC en 0x10, y cablea ambos de modo que el temporizador habilite la transferencia. Toda entrada del DMA no utilizada se amarra a un nivel fijo, y las dos salidas de interrupción se combinan mediante AND cableada sobre el pin de interrupción del procesador.
"device": [ { "name": "Z80DMA", "id": "dma0", "enable": 1,
              "type": "IO", "addr": 0x0F, "size": 1,
              "busack": 1, "maxburst": 4096 },

            { "name": "Z80CTC", "id": "ctc0", "enable": 1,
              "type": "IO", "addr": 0x10, "size": 4 } ],

"interlink": [
    /* Entradas del DMA no usadas, amarradas para documentar su estado.        */
    { "name": "reconocimiento de bus del dma", "dst": "dma0.BAI",  "src": [ "cpu.BUSACK" ] },
    { "name": "habilitacion de chip del dma",  "dst": "dma0.CE",   "src": [ "0" ] },
    {                                          "dst": "dma0.WAIT", "src": [ "1" ] },
    {                                          "dst": "dma0.IEI",  "src": [ "1" ] },
    {                                          "dst": "dma0.M1",   "src": [ "1" ] },

    /* Entradas de disparo del CTC no usadas.                                  */
    { "dst": "ctc0.CLKTRG2", "src": [ "0" ] },
    { "dst": "ctc0.CLKTRG3", "src": [ "0" ] },
    { "dst": "ctc0.IEI",     "src": [ "1" ] },

    /* Dos salidas del temporizador habilitan la peticion de transferencia
       a traves de una puerta NOR.                                             */
    { "name": "el temporizador habilita el dma",
      "dst":  "dma0.RDY", "op": "NOR",
      "src":  [ "ctc0.ZCTO0", "ctc0.ZCTO1" ] },

    /* Ambas salidas de interrupcion son de drenador abierto y forman una
       AND cableada sobre la CPU.                                              */
    { "name": "cadena de interrupciones",
      "dst":  "cpu.INT", "op": "AND",
      "src":  [ "dma0.INT", "ctc0.INT" ] } ]
Todo lo demás sobre el DMA — si mueve bytes o busca uno, en qué dirección los mueve, la longitud del bloque, las direcciones, y si trabaja en modo byte, ráfaga o continuo — lo programa el Z80 a través de los propios registros del chip en tiempo de ejecución, exactamente igual que ocurriría con hardware real. La configuración decide únicamente dónde se sitúa el chip y a qué está cableado.
Una transferencia de memoria a memoria merece una nota, porque es lo primero que casi todo el mundo intenta. Tal transferencia no tiene ningún periférico que la solicite, así que no hay línea RDY que excitar. El Z80 DMA resuelve esto en el hardware real con el comando Force Ready (B3), que suministra internamente la condición de listo, y el modelo del picoZ80 hace lo mismo. Recuerde que ENABLE DMA (87) debe ser el último byte escrito, porque casi cualquier otro byte de control deshabilita el chip. Force Ready se cancela al liberar el bus y al final del bloque, de modo que una transferencia que deba reiniciarse a sí misma tiene que tomar su señal de listo de una fuente cableada, como la salida de un temporizador, en lugar del comando Force Ready.

Límites y Restricciones Actuales

El marco está acotado por tablas de tamaño fijo. Sobrepasar cualquiera de ellas se notifica en el registro de arranque, pero por lo demás la configuración parecerá cargarse.
Límite Valor Notas
Dispositivos 12 Un único presupuesto compartido por todos los tipos — doce chips en total, en cualquier combinación. Las entradas adicionales se ignoran.
Señales en total 192 Entre todos los dispositivos, más los nueve pines de la CPU.
Cables del interlink 96 Las entradas adicionales se ignoran.
Fuentes por cable 8 Los términos adicionales se ignoran.
Iteraciones de estabilización 8 Suficientes para biestables acoplados en cruz; una oscilación genuina se notifica.
Identificador de dispositivo 11 caracteres Los identificadores más largos se truncan.
Dos excepciones al presupuesto compartido: solo puede declararse un WD1773, y los cuatro dispositivos de flujo — MC6850, I8251, Z80DART y Centronics — toman sus ranuras de línea de interrupción de un único fondo común; un chip que exceda el fondo se inicializa igualmente pero funciona sin línea de interrupción. Los dispositivos con muchos pines agotan antes el presupuesto de señales: un 8255 son 24 pines, un PIO 23, un Latch o SignalPort 16.
Los nueve pines de la CPU se registran los últimos, así que un diseño que agote la tabla de señales pierde cpu.INT el primero y todos los cables de interrupción fallan silenciosamente al resolverse. Si las interrupciones dejan de funcionar en un diseño grande, cuente sus pines.
Limitaciones Importantes
  • Los dispositivos contadores solo avanzan bajo el modelo BaseZ80. El tick de marcha libre del Core 0 que da reloj al Z80 CTC y al 8253 solo lo arranca el firmware BaseZ80. Bajo las compilaciones Sharp, Amstrad, Tatung y OpenZ80 estos dos dispositivos se declaran y son direccionables, pero sus contadores no corren.
  • Un dispositivo no se reinicia con un reinicio del procesador. Los dispositivos se inicializan una sola vez, cuando se aplica la configuración.
  • Un DMA virtual no puede ser disparado por un periférico real. El picoZ80 alcanza únicamente el zócalo del Z80, y todos los pines de propósito general del RP2350 están ya asignados, así que no hay manera de introducir en la netlist la línea de petición de datos de una tarjeta física. Un DMA virtual toma su petición de una constante, de Force Ready, o de otro dispositivo virtual — siendo la salida DRQ del controlador de disquete el ejemplo obvio. Esto es una propiedad del hardware, no un defecto.
  • Las claves mirror, refresh y trace se aceptan pero actualmente no tienen efecto.
Para el procedimiento paso a paso usando la interfaz web, consulte el Manual de Usuario del picoZ80. Para la estructura interna, las herramientas del shell de depuración y cómo añadir un nuevo modelo de dispositivo, consulte la Guía del Desarrollador.

ICE (Shell de Depuración)

El picoZ80 incluye un shell de depuración ICE (In-Circuit Emulator) integrado en el Canal 1 USB CDC (el segundo puerto serie enumerado cuando la placa está conectada por USB). El shell se ejecuta en el Core 0 y se comunica con el bucle de emulación del Core 1 mediante flags compartidos en la estructura de contexto Z80CPU. El shell de depuración solo está disponible en la variante de firmware DBGSH, que se compila con la definición INCLUDE_DBGSH.

Canales Serie USB CDC

El picoZ80 enumera hasta cinco puertos serie USB CDC cuando se conecta a un host. CDC 0 y CDC 1 sirven a los UARTs físicos / puente ESP32 y al shell de depuración ICE (CDC 1, variantes DBGSH). CDC 2 y CDC 3 son los dos canales (A y B) de una tarjeta serie RS-232C virtual — el controlador MZ-8BIO3 o MZ-1E24 — cuando uno de ellos está configurado en config.json, y son también los canales por defecto que reclaman los dispositivos serie declarados (MC6850, I8251, Z80DART — véase Dispositivos Periféricos Virtuales). CDC 4 es el canal dedicado a un dispositivo de impresora Centronics declarado. Estos puertos no tienen un UART físico detrás: son puentes de búfer en anillo hacia el chip emulado, de modo que los datos escritos en el host aparecen en el dispositivo que ve el invitado, y viceversa.

Arquitectura

  • Entrada/Salida: Canal 1 USB CDC a 115200 baudios. El prompt del shell es dbg> . Se soporta historial de comandos (16 entradas) y eco de caracteres.
  • Breakpoints: Hasta 8 breakpoints simultáneos almacenados en cpu->dbgBpAddr[]. El Core 1 verifica el arreglo de breakpoints antes de cada fetch de opcode; al encontrar una coincidencia, establece cpu->hold = true y señala al Core 0 mediante dbgBpHit.
  • Paso a paso: El comando step establece cpu->dbgStepCount. El Core 1 decrementa este contador después de cada instrucción, deteniendo automáticamente cuando llega a cero. Se muestra el estado de registros antes/después y la instrucción desensamblada para cada paso.
  • Traza de ejecución: Un buffer circular de 512 entradas (cpu->dbgTrace[]) registra PC, opcode y registro de flags para cada instrucción ejecutada cuando la traza está habilitada. Cada entrada de 32 bits empaqueta [31:16]=PC, [15:8]=opcode, [7:0]=registro F.
  • Acceso a memoria: El acceso a memoria física (dm p, wm p) genera ciclos de bus Z80 reales a través de las state machines PIO. El acceso virtual (dm v, wm v) lee/escribe PSRAM directamente. El modo automático (wm sin calificador) sigue el mapa de memoria. El acceso RP2350 (dm r) lee el espacio de direcciones del microcontrolador host con validación de rango.
  • Hold/Release: El comando hold establece cpu->hold = true. El Core 1 confirma mediante cpu->holdAck, asegurando que la CPU esté inactiva antes de que el shell acceda al estado compartido.
  • Break: El comando break detiene el Core 1 y luego reporta el PC actual, la instrucción a punto de ejecutarse (desensamblada) y el conjunto completo de registros — la forma más rápida de ver dónde se encuentra un programa en ejecución, antes de avanzar paso a paso o inspeccionar. go/cont reanuda.
  • Teclas rápidas y abreviaturas: Para reducir la escritura durante la depuración, los comandos más comunes tienen teclas rápidas de una sola letra — c=cont, s=step, g=go, b=break, r=regs, h=hold — que se resuelven antes de la coincidencia por prefijo, por lo que nunca se tratan como ambiguas. Cualquier otro comando puede introducirse como su prefijo único más corto (p. ej. ste=step, sta=status, dis=disassemble); un prefijo ambiguo lista los candidatos, y un nombre escrito por completo siempre tiene prioridad.

Referencia de Comandos

Comando Sintaxis Descripción
help help Listar todos los comandos
regs regs Volcar todos los registros Z80, flags y contador de ciclos
dm dm <p|f|v|r> <addr> [len] Volcar memoria (física / fetch / virtual / RP2350)
search search [p|v] <start> <end> <hex..>|"text" Buscar en memoria un patrón de bytes o cadena de texto ASCII. p = bus físico, v = PSRAM virtual, omitir para mapeado. Las coincidencias se muestran con 8 bytes de contexto. Auto-detiene la CPU para acceso físico/mapeado. Patrón de hasta 32 bytes
cmp cmp [f] <phys> <virt> <len> Comparar memoria del bus físico con PSRAM virtual
dis dis [p|v] [addr] [count] Desensamblar código Z80
asm asm [addr] Ensamblador Z80 interactivo
memmap memmap [block] Mostrar tabla de punteros de banco de memoria
memptr memptr [addr] Mostrar tabla PSRAM memPtr
iomap iomap [port] Mostrar tabla de handlers de puertos E/S
status status Estado del sistema (frecuencia CPU, PSRAM, tiempo de actividad)
ver ver Versión de firmware e información de partición
drivers drivers Listar controladores e interfaces activos
hold hold Pausar emulación de CPU
release release Reanudar emulación de CPU
break break Detener el Z80 en ejecución y reportar dónde se detuvo — PC, la instrucción (bytes + desensamblado) a punto de ejecutarse, y un volcado completo de registros. A diferencia de hold (que pausa silenciosamente), break muestra el estado para que puedas ver exactamente dónde está la ejecución (p. ej. cuando está atascado en un bucle). Reemitir break vuelve a mostrar el estado. Reanudar con go/cont o avanzar paso a paso con step. Expira (~3 s) si el Z80 se mantiene en reset o su reloj está bloqueado
go go Continuar (liberar hold, breakpoints activos)
cont cont Alias de go. Continuar ejecución (liberar hold, breakpoints activos)
step step [n] Ejecutar paso a paso n instrucciones
next next [n] Ejecutar paso a paso n instrucciones, saltando por encima de las llamadas a subrutina (alias n)
bp bp <addr> Establecer breakpoint (máximo 8)
bc bc <n|*> Limpiar breakpoint n o todos
bl bl Listar breakpoints
wm wm [p|v] <addr> <byte>... Escribir en memoria (física/virtual/auto)
fill fill [p|v] <addr> <len> [w|d] <val> Llenar memoria con un valor constante
copy copy <pv|fp|vp> <src> <len> <dst> Copiar memoria entre física y virtual
memtest memtest <addr> <len> [pattern] Probar memoria física (escritura+lectura, escritura+fetch, intercalado)
in in <port> Leer puerto E/S Z80
out out <port> <byte> Escribir puerto E/S Z80
trace trace <on|off|dump [n]|clear|rt|byte ...> Control de traza de ejecución; rt habilita salida de traza en tiempo real; byte habilita traza a nivel de byte
verify verify <on|off> Alternar verificación completa de fetch de opcode
fwait fwait <0-4> Forzar wait states extra en M1 (fetch de opcode); 0 = desactivado (por defecto)
iowait iowait <0-8> Forzar wait states extra en ciclo E/S; 0 = desactivado (por defecto)
corrupt corrupt [clear] Mostrar o limpiar corrupciones de fetch detectadas
fdctrace fdctrace <on|off|dump> Habilitar/deshabilitar traza de E/S FDC; dump muestra las últimas 64 operaciones
qdtrace qdtrace <on|off|dump> Habilitar/deshabilitar traza de E/S Quick Disk; dump muestra las últimas 64 operaciones
qdprobe qdprobe [seconds] / qdprobe eng [pace_us] Accionar manualmente el SIO del Quick Disk REAL (puertos F4/F6/F7) y mapear sus señales — motor en marcha, búsquedas (hunts) y eventos de sincronismo (A = ChA RR0: b3=HDST, b4=hunt, b0=Rx; B = ChB RR0: b3=HOME) — durante hasta seconds (por defecto 20, máximo 60), manteniendo detenida la CPU Z80 hasta que go la reanude. eng [pace_us] reproduce la secuencia exacta de init/hunt/lectura del motor QD de TZFS a un ritmo entre accesos configurable, para validar la temporización del hardware real
piodbg piodbg [clear] Mostrar diagnósticos de hardware PIO del RP2350 (FDEBUG, FSTAT, FIFO, PCs, GPIO); clear reinicia flags persistentes
load load <p|v> <file> <addr> [len] [ofs] Cargar un archivo desde la tarjeta SD del ESP32 en memoria Z80. p = bus físico, v = banco 0 de PSRAM virtual. file relativo a /sdcard/. Si se omite len, se carga el archivo completo (hasta 64KB); si se especifica, máximo 1MB. ofs opcional para offset del archivo. Auto-detiene la CPU para escrituras físicas. Usa banco 63 de PSRAM como buffer temporal
save save <p|pf|v> <file> <addr> <len> Guardar memoria Z80 en un archivo en la tarjeta SD del ESP32. p = bus físico, pf = fetch físico (M1), v = banco 0 de PSRAM virtual. file relativo a /sdcard/. Máximo 64KB. Auto-detiene la CPU para lecturas físicas. Refresco periódico de DRAM durante lecturas físicas
dir dir [path] Listar archivos en la tarjeta SD del ESP32. La ruta opcional es relativa a /sdcard/. Muestra nombres de archivo y tamaños
echo echo [on|off] Alternar eco del terminal
reset reset Forzar reset del Z80
set set <reg|flags|memmap|memptr|iomap> <idx> <val> Modificar registro Z80, flags, mapa de memoria, memPtr o entrada de mapa E/S en tiempo de ejecución
hist hist [n] Mostrar historial de comandos (preservado entre sesiones mediante ESP32 NVS)
savehst savehst Forzar guardado del historial de comandos en ESP32 NVS
ipl ipl Realizar un reset IPL (modo BST) alternando el bit 3 del Puerto C del 8255 PPI. Reinicia al modo boot sin un reset completo del Z80
mmutrace mmutrace Volcar información de traza específica de la máquina (estado MMU, instantáneas de registros E/S). La salida varía según la persona a través del handler de traza registrado
intcount intcount Mostrar conteo de reconocimiento de interrupciones y estado actual de interrupción
psync psync [start end] Sincronizar memoria física con PSRAM. Rango de direcciones opcional; por defecto el espacio completo de direcciones
dskimage dskimage read <filename> [cylinders] [heads] / dskimage write <filename> Crear imagen de un disquete físico a un archivo DSK en la tarjeta SD (read), o escribir un archivo DSK desde la tarjeta SD a un disquete físico (write). dskimage <filename> por defecto es lectura (compatible con versiones anteriores). Auto-detecta geometría si se omite
busdiag busdiag Mostrar diagnósticos de bus (estado PIO, niveles de señal, contención de bus)
fdcimage fdcimage Mostrar estado y progreso de creación de imagen FDC
fdcdiag fdcdiag Mostrar información de diagnóstico FDC (estado del controlador, volcado de registros)
gadiag gadiag Mostrar información de diagnóstico del gate array (estado de comandos, enrutamiento de interrupciones)
ser ser [id] [tx <text> \| rx] Estado de un dispositivo serie (MC6850 / I8251 / Z80DART). tx inyecta caracteres en el dispositivo, rx lee lo que envió el invitado — sin necesidad de terminal. En el DART la inyección es solo por el canal A
rtc rtc [id] Volcar los registros del reloj de tiempo real MSM5832 decodificados en una fecha y hora legibles
prn prn [id] [dump] Estado del dispositivo de impresora Centronics; dump vacía la cola de impresión en el shell en lugar de en el USB
Teclas rápidas y abreviaturas. Los comandos no necesitan escribirse completos. Los seis comandos más usados tienen teclas rápidas de una sola letra — c (cont), s (step), g (go), b (break), r (regs) y h (hold) — y cualquier otro comando puede introducirse como su prefijo único más corto (por ejemplo ste para step, sta para status, dis para disassemble). Un nombre de comando escrito por completo siempre tiene prioridad, y un prefijo ambiguo imprime la lista de comandos coincidentes.

Coprocesador ESP32

El módulo ESP32-S3-PICO-1 actúa como coprocesador manejando todas las funciones de red y almacenamiento. Se comunica con el RP2350 a través de dos interfaces:
  • FSPI (60MHz, SPI de 4 hilos) — Protocolo IPC Binario v1.1 — transferencia masiva de datos de alta velocidad (imágenes ROM, lecturas/escrituras de sectores de disco, descarga de archivo de configuración). El protocolo usa un encabezado de trama binario fijo de 64 bytes con verificación de integridad CRC32 (reemplazando el anterior checksum XOR). Los canales DMA se pre-asignan durante la inicialización y nunca se liberan, eliminando la sobrecarga de claim/unclaim por transferencia y las condiciones de carrera. Las transferencias de sectores en ráfaga permiten hasta 16 × 512 bytes de sectores (8KB) en una sola transacción SPI, mejorando significativamente los tiempos de carga de imágenes de disquete y QuickDisk. El canal DMA RX se eleva a PRIORIDAD ALTA para prevenir desbordamiento del FIFO causado por la contención del bus QMI PSRAM del Core 1.
  • UART (460.8kbaud) — la ruta del puente USB-a-UART: el RP2350 reenvía un canal USB CDC a la consola del ESP32 para que el ESP32 pueda flashearse y monitorizarse a través del puerto USB del RP2350, y el ESP32 a su vez puede programar el RP2350.
El enlace FSPI se endureció en septiembre de 2026 después de que una clase de fallos diferidos por punteros descontrolados se rastreara hasta una respuesta tardía que llegaba después de que un comando hubiera expirado y desplazaba una trama todas las respuestas posteriores. Ahora cada respuesta lleva el opcode y el número de secuencia del comando que la originó y se valida al recibirla; una trama que no coincide se descarta, se contabiliza y el comando se reintenta. Toda la ruta caliente del SPI (CRC, aborto del DMA, transferencia, envío/recepción y el sondeo periódico NOP) se ejecuta desde la RAM en lugar de desde la flash XIP, después de que unos fallos de instrucción indefinida se rastrearan hasta recargas XIP corrompidas bajo contención de la PSRAM; con los búferes DMA en SRAM el enlace es estable a 60 MHz con un reloj de sistema de 320 MHz.

Modos de Red

El firmware ESP32 soporta tres modos de red, seleccionados en tiempo de compilación mediante archivos sdkconfig pre-construidos:
Modo Archivo de Config WiFi USB NCM Consola FCC/RED Requerido
Solo WiFi sdkconfig.mode_wifi_only Sí No USB Serial/JTAG Sí
WiFi + NCM sdkconfig.mode_wifi_and_ncm Sí Sí TinyUSB CDC-ACM Sí
Solo NCM sdkconfig.mode_ncm_only No Sí TinyUSB CDC-ACM No
USB NCM (Network Control Model) presenta un adaptador Ethernet CDC-NCM en el puerto USB OTG del ESP32-S3 (GPIO 19/20). Un dispositivo USB compuesto expone tanto un puerto serie CDC-ACM (para registro de depuración) como la interfaz de red NCM. El servidor DHCP integrado asigna al host una dirección IP de la subred 192.168.7.0/24, con el picoZ80 accesible en 192.168.7.1. El tiempo de arrendamiento es de 120 minutos.
En modo WiFi+NCM, el servidor HTTP se enlaza a INADDR_ANY:80 y sirve ambas interfaces simultáneamente. WiFi se conecta asíncronamente por lo que la interfaz USB NCM está disponible inmediatamente al encender.
Cuando solo NCM está habilitado, la radio WiFi se deshabilita completamente, la página del WiFi Manager se elimina de la interfaz web, y el título del panel de estado del Dashboard cambia de "WiFi Configuration" a "Network Configuration" (mostrando el estado USB NCM en lugar de SSID/detalles WiFi). La red de adaptación de antena del ESP32-S3 no necesita ser poblada en la PCB.

Interfaz de Tarjeta SD

El ESP32 gestiona la tarjeta SD a través de su interfaz SPI. La tarjeta SD se monta como FAT32 y todo el acceso a archivos desde el RP2350 es mediado por el ESP32 — el RP2350 envía comandos de E/S de archivos por el enlace FSPI/UART y el ESP32 realiza las operaciones reales de lectura/escritura FAT32.
La tarjeta SD también es directamente accesible para el servidor web del ESP32, que sirve archivos desde el directorio webfs/ y permite al Administrador de Archivos navegar y modificar el contenido de la tarjeta mediante HTTP.
Corrección de lectura de directorios grandes. Se ha corregido un "SD Read error" intermitente de TZFS en directorios grandes. El código antiguo realizaba reexploraciones de directorio O(N²) que excedían la ventana de handshake del FSPI cuando la caché de la SD estaba fría; se han sustituido por un cursor de directorio persistente MZFDIR en el lado del ESP32 más un reintento de la caché de directorio en el lado del RP2350, de modo que los directorios grandes se enumeran de forma fiable.

Servidor Web

El ESP32 ejecuta un servidor HTTP en el puerto 80 (sin TLS — solo para uso en red local). Todos los recursos web (HTML, CSS, JavaScript) se sirven desde el directorio webfs/ en la tarjeta SD, permitiendo que la interfaz web se actualice sin reflashear el firmware del ESP32. El servidor web maneja:
  • Servir recursos web estáticos desde el directorio webfs/ de la tarjeta SD.
  • Endpoints de API REST para datos JSON (estado del sistema, lectura/escritura de configuración, operaciones de archivos).
  • Endpoints de carga de firmware OTA tanto para el RP2350 como para el ESP32.
  • Conexión WebSocket para actualizaciones de estado del Dashboard en tiempo real.

Protocolo de Comandos RP2350 ↔ ESP32

El RP2350 (Core 0) se comunica con el ESP32 usando el protocolo binario de tramas IPC sobre el enlace FSPI. Cada comando es un opcode de un solo byte en una cabecera binaria fija (número de secuencia, longitud del payload, campo de nombre de archivo) seguida de un payload opcional y un trailer CRC32; el ESP32 responde con una trama de respuesta que repite el opcode y el número de secuencia, un código de estado (OK, ERR, BUSY, CRCFAIL, NOTFOUND o PARTIAL) y cualquier dato de respuesta. Los comandos que el ESP32 necesita enviar al RP2350 — cambios de persona, conmutaciones del modo de pletina, cambios de imagen de disquete/QD, recargas de configuración — viajan de vuelta en la respuesta al sondeo periódico NOP del RP2350 como comandos inversos.
Categorías comunes de comandos:
  • E/S de Archivos — abrir, leer, escribir, cerrar, listado de directorio, stat de archivo.
  • Configuración — solicitar contenido de config.json, escribir configuración actualizada, solicitud de recarga.
  • Disco — montar/desmontar imagen de disco, leer/escribir sector (retransmitido desde emulación WD1773 y QDDrive). Los nombres de archivo de la imagen de disquete y QuickDisk actualmente montada son rastreados por el ESP32 y mostrados en el menú de Acciones de la interfaz web.
  • Sistema — consulta de versión, solicitud de reinicio, lectura/escritura NVS.
La lista completa de opcodes está en la Guía del Desarrollador — Opcodes de Comandos; incluye las transferencias de archivos y sectores, INF (informe de versión/partición/estado de pletina), los listados de directorio DIR/MZFDIR, las cinco operaciones de red usadas por la placa Celestite y TIME, que devuelve la hora del sistema del ESP32 (segundos de época local más un flag de sincronización) para el reloj de tiempo real MSM5832.

Comandos IPC de Red

La implementación de red Celestite Fase 2 añade cinco comandos IPC inter-core que el RP2350 usa para solicitar operaciones de red al ESP32. Estos comandos se reenvían por el enlace FSPI/UART y el ESP32 realiza las operaciones de socket BSD correspondientes. Los connects no bloqueantes usan select() con timeout, y flags pendientes por socket rastrean las operaciones en curso.
Comando Opcode Descripción
IPCF_CMD_NET_CFG 0x10 Obtener configuración de red del ESP32 — devuelve dirección IP, gateway, máscara de subred y dirección MAC
IPCF_CMD_NET_SOCK 0x11 Operación de ciclo de vida de socket — abrir, conectar, escuchar, cerrar o desconectar un socket
IPCF_CMD_NET_SEND 0x12 Enviar datos a un socket abierto
IPCF_CMD_NET_RECV 0x13 Recibir datos de un socket abierto
IPCF_CMD_NET_PING 0x14 Solicitud de eco ICMP (ping)
IPCF_CMD_TIME 0x15 Obtención de la hora del sistema — segundos de época local de 64 bits más un flag de sincronización; usado por devicesTimeSync() para poner en hora las instancias del RTC MSM5832
Los comandos de socket W5100 soportados a través de esta capa IPC son: OPEN, CONNECT, LISTEN, SEND, RECV, CLOSE y DISCON. El ESP32 los traduce a llamadas estándar de la API de socket BSD (socket(), connect(), listen(), send(), recv(), close(), shutdown()), permitiendo que la placa Celestite se comunique con servicios de red como el servidor de archivos netfs.py.

Watchdog y Diagnósticos de Arranque

El firmware del RP2350 utiliza un temporizador watchdog de hardware para detectar y recuperarse de bloqueos durante el arranque y paradas del bucle principal. El watchdog se habilita tempranamente en la secuencia de arranque con un timeout de 30 segundos y se actualiza (watchdog_update()) en cada hito importante. Si cualquier etapa de arranque o iteración del bucle principal tarda más que el timeout, el watchdog reinicia el RP2350 automáticamente.

Seguimiento del Progreso de Arranque

El progreso de arranque se rastrea usando los registros scratch del watchdog del RP2350, que sobreviven a resets del watchdog (pero no a resets de encendido). Esto permite al firmware determinar, después de un reset del watchdog, exactamente a qué etapa de arranque se llegó antes del bloqueo.
Registro Scratch Nombre Contenido
scratch[0–3] Historial de arranque Últimos cuatro intentos de reset — cada entrada codifica (attempt_count << 24) | (stage << 16) | (resetCause & 0xFFFF). Las entradas se desplazan en cada reset del watchdog: [0]←[1]←[2]←[3]←actual.
scratch[4] Diagnósticos SPI Contadores de diagnóstico empaquetados para el enlace FSPI: breadcrumbs, tipo de mensaje, contadores de timeout T1/T3, conteo de tramas erróneas y conteo OK.
scratch[5] Marcador mágico Establecido a 0xB00710BE para indicar que los registros scratch contienen datos válidos de progreso de arranque.
scratch[6] Etapa actual El código de etapa de arranque más reciente (ver tabla siguiente).
scratch[7] Causa de reset El código de causa de reset del controlador de reset de hardware.
Los códigos de etapa de arranque progresan desde 0x01 (inicio) hasta 0x10 (bucle principal iniciado). Las sub-etapas dentro del bucle principal (0x11–0x17) y el procesamiento de comandos inter-core (0x20–0x27) proporcionan seguimiento de grano fino:
Código Etapa Descripción
0x01 BOOTP_START Punto de entrada alcanzado
0x02 BOOTP_CLK_SET Reloj del sistema configurado
0x03 BOOTP_PSRAM_INIT Inicialización de PSRAM comenzada
0x04 BOOTP_PSRAM_OK PSRAM inicializada exitosamente
0x05 BOOTP_STDIO_INIT USB stdio inicializado
0x06 BOOTP_PIO_INIT State machines PIO cargadas
0x07 BOOTP_Z80_INIT Contexto de CPU Z80 inicializado
0x08 BOOTP_USB_INIT Puente USB inicializado
0x0A BOOTP_ESP_HS_SYNC Sincronización de handshake SPI ESP32
0x0B BOOTP_CORE1_LAUNCH Core 1 lanzado
0x0D BOOTP_FSPI_INIT IPC binario FSPI inicializado
0x0E BOOTP_ESP_INIT Comunicación ESP32 lista
0x10 BOOTP_MAIN_LOOP Bucle principal iniciado
0x11–0x17 Sub-etapas del bucle principal Sondeo USB, inter-core, SPI NOP/CMD, tareas
0x20–0x27 Comandos inter-core Carga de disquete, carga QD, carga RAMFILE, E/S de archivos

Log Persistente en PSRAM (plogf)

Los últimos 4KB de la PSRAM de 8MB (dirección 0x117FF000) están reservados para un log de depuración persistente que sobrevive a resets del watchdog. La macro plogf() escribe mensajes estilo printf en este buffer durante el arranque, antes de que USB esté disponible para la salida normal de debugf(). En el siguiente arranque exitoso, la función dump_plog() envía cualquier mensaje capturado a la consola de depuración y luego limpia el buffer. El log usa una estructura simple: un marcador mágico de 4 bytes (0x504C4F47 = "PLOG"), un contador de longitud de 4 bytes, y un buffer de texto circular de 3840 bytes.

Diagnósticos de Fallos

El firmware instala handlers de fallo Cortex-M33 para fallos graves, fallos de gestión de memoria, fallos de bus y fallos de uso. Cuando ocurre un fallo, el handler guarda una instantánea de diagnóstico completa en los últimos 256 bytes de PSRAM (dirección 0x117FFF00) con un marcador mágico (0xFA017000), el tipo de fallo, todos los registros relevantes (PC, LR, SP, R0–R3, R12, PSR), el Registro de Estado de Fallo Configurable (CFSR), el Registro de Estado de Fallo Grave (HFSR), el Registro de Dirección de Fallo de Bus (BFAR), el Registro de Dirección de Fallo de Gestión de Memoria (MMFAR) y el ID del núcleo. El handler luego entra en un bucle infinito, permitiendo que el watchdog dispare un reset. En el siguiente arranque, el firmware verifica si hay un diagnóstico de fallo válido y envía la información capturada mediante debugf(), permitiendo análisis post-mortem sin requerir una sesión de depurador en vivo.

Limpieza de Configuración Flash

El mecanismo de actualización OTA del RP2350 soporta dos operaciones adicionales más allá de la carga de firmware:
  • Limpiar App Config (FW_CFGCLEAR_ID = 0xB1D7E5FA) — borra la partición App Config (imágenes ROM y JSON minificado) asociada con el slot de firmware destino. Esto fuerza al firmware a releer config.json desde la tarjeta SD en el siguiente arranque, lo cual es necesario cuando el esquema de configuración ha cambiado entre versiones de firmware.
  • Limpiar Flash Header (FW_HDRCLEAR_ID = 0xC2E8F6AB) — reinicia el encabezado de partición Flash a valores de fábrica. La configuración del bootloader (partición 0) se preserva, pero todos los metadatos de particiones de aplicación se reconstruyen desde cero. Úselo cuando la tabla de particiones se haya corrompido o al regresar a una versión anterior de firmware que espera un diseño de partición diferente.
Ambas operaciones se activan desde las casillas de verificación de la página web OTA del RP2350 y son ejecutadas por el bootloader durante el proceso de actualización de firmware.

Depuración SWD — RP2350

El RP2350 soporta depuración completa a nivel de código fuente sobre ARM Serial Wire Debug (SWD). Conecte una sonda compatible con CMSIS-DAP (Raspberry Pi Debug Probe, Black Magic Probe o similar) a los Pines 1 (SWCLK), 2 (SWDIO) y 5 (GND) del conector de depuración.

Configuración de OpenOCD

El picoZ80 requiere una pequeña modificación al script estándar de destino OpenOCD del RP2350 para habilitar depuración SMP con puertos GDB separados por núcleo:
sudo cp /usr/local/share/openocd/scripts/target/rp2350.cfg \
        /usr/local/share/openocd/scripts/target/rp2350_tzpu.cfg
Edite rp2350_tzpu.cfg — busque la línea target smp dentro del bloque if {[string compare $_USE_CORE SMP] == 0} y elimine el # inicial:
# Before:
    #target smp $_TARGETNAME_0 $_TARGETNAME_1

# After:
    target smp $_TARGETNAME_0 $_TARGETNAME_1
Este único cambio hace que OpenOCD registre el Core 0 en el puerto GDB 3333 y el Core 1 en el puerto GDB 3334, permitiendo sesiones GDB independientes por núcleo. Inicie OpenOCD antes de iniciar GDB:
openocd -f interface/cmsis-dap.cfg -f target/rp2350_tzpu.cfg -c "adapter speed 5000"

Configuración de GDB

Añada lo siguiente a ~/.gdbinit (con rutas absolutas que coincidan con la ubicación de su proyecto) para permitir la carga automática de archivos .gdbinit por directorio:
set history save on
set history filename ~/.gdb_history
set history size 65536
add-auto-load-safe-path /path/to/project/build/bin/model/BaseZ80/.gdbinit
add-auto-load-safe-path /path/to/project/build/bin/model/Bootloader/.gdbinit
Depuración del Bootloader
# Terminal 1 — Core 0 (port 3333)
cd build/bin/model/Bootloader
cp ../../../../.gdbinit.bootloader.3333 .gdbinit
gdb-multiarch Bootloader.elf

# Terminal 2 — Core 1 (port 3334)
cd build/bin/model/Bootloader
cp ../../../../.gdbinit.bootloader.3334 .gdbinit
gdb-multiarch Bootloader.elf
Depuración del Firmware Principal
# Terminal 1 — Core 0 (port 3333)
cd build/bin/model/BaseZ80
cp ../../../../.gdbinit.3333 .gdbinit
gdb-multiarch BaseZ80_0x10020000.elf

# Terminal 2 — Core 1 (port 3334)
cd build/bin/model/BaseZ80
cp ../../../../.gdbinit.3334 .gdbinit
gdb-multiarch BaseZ80_0x10020000.elf

# Memory dump (from GDB prompt) — hex + ASCII:
(gdb) xac 0x20000000 64
El comando GDB xac <address> <count> está definido en los archivos .gdbinit.3333 / .gdbinit.3334. Vuelca memoria como salida combinada hexadecimal y ASCII y es útil para inspeccionar contenidos de bancos PSRAM y estado de dispositivos mapeados en memoria.

Depuración USB del ESP32

El coprocesador ESP32-S3 tiene una interfaz USB-JTAG incorporada — no se requiere sonda de depuración externa. Conecte un cable USB desde el PC host directamente al puerto USB del ESP32 en la placa picoZ80.
# Start OpenOCD for ESP32-S3
openocd -f board/esp32s3-builtin.cfg

# In a second terminal — launch Xtensa GDB
xtensa-esp32s3-elf-gdb esp32/build/main.elf
(gdb) target extended-remote :3333
Asegúrese de que el ELF fue compilado desde la misma revisión de código fuente que el firmware ejecutándose en el dispositivo, para que los símbolos y direcciones se alineen correctamente.

Sistema de Compilación

El firmware del picoZ80 usa CMake con el Raspberry Pi Pico SDK 2.x. El sistema de compilación produce el firmware Bootloader y la Aplicación. La aplicación se compila en cuatro variantes — dos particiones (Partición 1 en 0x10020000, Partición 2 en 0x10520000) cada una en configuraciones estándar y DBGSH. Las variantes DBGSH añaden INCLUDE_DBGSH a los flags de compilación, habilitando el shell de depuración completo en el Canal 1 USB CDC. El firmware ESP32 se compila separadamente usando ESP-IDF v5.4, gestionado mediante Docker.
La forma más rápida de obtener un entorno funcional es el script automatizado setup_picoZ80 (macOS/Linux y Windows), que instala el SDK y todas las dependencias y crea scripts de compilación listos para usar — consulte el README y la Guía del Desarrollador. Los objetivos, flags y comandos de CMake que se describen a continuación documentan la compilación subyacente que el script automatiza, para quienes prefieran configurarla manualmente.

Objetivos de Compilación CMake

Objetivo Salida Dirección Flash Notas
Bootloader Bootloader.elf, Bootloader.uf2 0x10000000  
BaseZ80_0x10020000 BaseZ80_0x10020000.elf, .bin 0x10020000 (Slot 1) Estándar (sin shell de depuración)
BaseZ80_0x10520000 BaseZ80_0x10520000.elf, .bin 0x10520000 (Slot 2) Estándar (sin shell de depuración)
BaseZ80_DBGSH_0x10020000 BaseZ80_DBGSH_0x10020000.elf, .bin 0x10020000 (Slot 1) DBGSH — incluye shell de depuración ICE
BaseZ80_DBGSH_0x10520000 BaseZ80_DBGSH_0x10520000.elf, .bin 0x10520000 (Slot 2) DBGSH — incluye shell de depuración ICE
Los modelos SharpZ80, AmstradZ80, TatungZ80 y OpenZ80 siguen el mismo patrón de slot / DBGSH, por ejemplo OpenZ80_0x10020000, OpenZ80_0x10020000_DBGSH, OpenZ80_0x10520000 y OpenZ80_0x10520000_DBGSH (cada uno emite .elf, .bin, .hex y .map; los slots de aplicación usan .bin simple, no UF2). Compile un solo modelo con el filtro build_tzpuPico.sh, por ejemplo build_tzpuPico.sh open.

Flags Clave de Compilación CMake

Flag Efecto
INCLUDE_SHARP_DRIVERS Compila todos los controladores de periféricos Sharp MZ (MZ700, MZ80K, MZ800, MZ80A, MZ80B, MZ2000, MZ2200, MZ2500, MZ1500, WD1773, T3444M, QDDrive, RFS, TZFS, MZ-1E05, MZ80AFI, MZ80FIO, MZ8BFI, MZ8BIO3, MZ1E24, Z80SIO, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18, MZ-1R23, MZ-1R37, PIO-3034, Celestite, MZ-1E30).
INCLUDE_AMSTRAD_DRIVERS Compila los controladores de periféricos Amstrad PCW (PCW9512, uPD765).
INCLUDE_TATUNG_DRIVERS Compila los controladores de periféricos Tatung Einstein (EinsteinTC01, EinsteinFDC, WD1770).
INCLUDE_OPEN_DRIVERS Compila la persona de experimentador OpenZ80 (Open.c) y las tarjetas de interfaz independientes de máquina (MZ-1R12/1R18/1R23/1R37, PIO-3034, MZ8BIO3, MZ1E24, Z80SIO, MZ-1E05, WD1773, Celestite).
TARGET_MODEL_TATUNG Establece Tatung Einstein como el modelo objetivo exclusivo (usado en la compilación TatungZ80).
TARGET_MODEL_OPEN Establece la persona de experimentador OpenZ80 como el modelo objetivo exclusivo (usado en la compilación OpenZ80; habilita INCLUDE_OPEN_DRIVERS).
INCLUDE_DBGSH Compila el shell de depuración ICE en el Canal 1 USB CDC. Presente solo en variantes de compilación DBGSH.
CMAKE_BUILD_TYPE=Debug Habilita símbolos de depuración y deshabilita optimización. Requerido para depuración GDB a nivel de código fuente.
CMAKE_BUILD_TYPE=Release Optimización completa (-O3). Usado para firmware de producción.

Comandos de Compilación

# First time: clone and build the SDK
./get_and_build_sdk.sh

# Standard release build (RP2350 only)
./build_tzpuPico.sh

# Debug build
./build_tzpuPico.sh DEBUG

# Full build: RP2350 + ESP32 (ESP32 built via Docker)
./build_tzpuPico.sh ALL

# ESP32 only, using the Docker idf54 alias
cd projects/tzpuPico/esp32
idf54 build
El script build_tzpuPico.sh incrementa automáticamente el número de versión en una compilación exitosa y copia los archivos de salida versionados a fw/uf2/ (bootloader UF2) y fw/bin/ (binario de aplicación para OTA). El UF2 del Bootloader se usa solo para el flasheo inicial mediante almacenamiento masivo USB. Los binarios de slot de aplicación usan formato binario simple (no UF2) porque residen en direcciones de Flash no estándar.

Selección de Modo de Red ESP32

Para cambiar el modo de red, copie el archivo de configuración pre-construido apropiado a sdkconfig antes de compilar:
cd esp32/
cp sdkconfig.mode_ncm_only sdkconfig    # NCM only (FCC/RED safe)
# or: cp sdkconfig.mode_wifi_only sdkconfig
# or: cp sdkconfig.mode_wifi_and_ncm sdkconfig
idf.py build
idf.py flash

Sitios de Referencia

Recurso Enlace
Página del proyecto picoZ80 /es/picoz80/
Manual de Usuario picoZ80 /es/picoz80-usermanual/
Página del proyecto pico6502 /es/pico6502/
Documentos de diseño (repositorio projects/tzpuPico/docs/) PICOZ80_SIMULATOR_DESIGN.md, VIRTUAL_CMT_DESIGN.md, VIRTUAL_CMT_X1_DESIGN.md, VIRTUAL_DEVICE_GUI_DESIGN.md, VIRTUAL_Z80CTC_Z80PIO_DESIGN.md, VIRTUAL_Z80DMA_DESIGN.md, VIRTUAL_Z80SIO_DESIGN.md, X1_DISK_IMAGING.md — las notas de diseño que hay detrás del simulador, las pletinas de cassette virtuales, la GUI de dispositivos/interlink, los modelos de periféricos Z80 y la creación de imágenes de disco del X1
Estudio de temporización (repositorio projects/tzpuPico/picoZ80_vs_realZ80_timing.md) Datos de origen de la sección Fidelidad de la Temporización del Bus
Hoja de datos RP2350 datasheets.raspberrypi.com
Referencia PIO RP2350 datasheets.raspberrypi.com — Appendix B
Documentación Pico SDK raspberrypi.github.io/pico-sdk-doxygen
Referencia Técnica ESP32-S3 docs.espressif.com
Guía de Programación ESP-IDF docs.espressif.com/esp-idf
Manual de Usuario CPU Zilog Z80 zilog.com
Documentación OpenOCD openocd.org
Vista previa del proyecto en X (Twitter) engineerswork1

Aviso Regulatorio de Comunicaciones Inalámbricas

Este dispositivo incorpora un módulo inalámbrico ESP32-S3-PICO-1 capaz de transmitir en la banda ISM de 2.4 GHz, lo que lo convierte en un radiador intencional bajo las regulaciones de radiofrecuencia a nivel mundial (incluyendo FCC Part 15 Subpart C en los Estados Unidos y la Directiva de Equipos Radioeléctricos 2014/53/EU en la Unión Europea) cuando el firmware WiFi está instalado.
Configuración de Fábrica
Tal como se envía, la placa picoZ80 viene flasheada con el firmware solo NCM (sdkconfig.mode_ncm_only). En esta configuración, la radio WiFi está completamente deshabilitada y los componentes de la red de adaptación de antena del ESP32-S3 no están poblados en la PCB. Debido a que no ocurre transmisión RF, el dispositivo no es un radiador intencional y no requiere certificación FCC, CE/RED o equivalente. Puede venderse, distribuirse o regalarse sin autorización regulatoria.
Añadir WiFi
Los usuarios finales pueden poblar la red de adaptación de antena y flashear firmware con WiFi habilitado (sdkconfig.mode_wifi_only o sdkconfig.mode_wifi_and_ncm) para uso personal, experimental o educativo. Una vez que el WiFi está habilitado, el dispositivo se convierte en un radiador intencional y se aplican las siguientes reglas:
Aunque el módulo ESP32-S3-PICO-1 por sí mismo lleva certificaciones regulatorias preexistentes (FCC, CE y otras), esas certificaciones a nivel de módulo no se extienden automáticamente a un producto terminado que incorpore el módulo. La exención de módulo pre-certificado permite a individuos aficionados construir un número limitado de dispositivos para uso personal, experimental o educativo sin obtener una autorización de equipo separada.
Limitaciones Importantes
  • Los dispositivos con firmware WiFi no deben venderse, ofrecerse a la venta, regalarse ni distribuirse de otra manera a terceros a menos que el producto terminado haya sido probado independientemente y se le haya otorgado su propia autorización de equipo (por ejemplo, FCC ID, marcado CE con evaluación de un Organismo Notificado) en la jurisdicción correspondiente.
  • Construir este proyecto con WiFi habilitado para uso personal en cantidades limitadas generalmente está permitido bajo las disposiciones de uso aficionado y experimental (por ejemplo, FCC § 15.23), siempre que el dispositivo no cause interferencia perjudicial.
  • La venta comercial con WiFi habilitado requiere certificación completa FCC/RED (o equivalente) a nivel de producto.
  • Los requisitos regulatorios varían según el país. Los constructores fuera de los Estados Unidos deben consultar a su autoridad nacional de radiofrecuencia para las reglas aplicables.
Responsabilidad del Constructor
Es responsabilidad exclusiva del constructor asegurar que cualquier dispositivo construido a partir de estos diseños cumpla con todas las regulaciones de radiofrecuencia aplicables en su jurisdicción. El autor proporciona estos diseños para uso personal, educativo y de aficionados y no hace ninguna representación de que un dispositivo construido a partir de ellos satisfaga los requisitos regulatorios para distribución comercial.