picoZ80 Guide Technique

picoZ80 Guide Technique

Ce guide documente l'architecture matérielle du picoZ80, l'interface bus PIO du RP2350, le modèle mémoire, la référence de configuration JSON, le framework de périphériques virtuels et les procédures de débogage. Il est destiné aux développeurs souhaitant comprendre les mécanismes internes, écrire de nouveaux pilotes, porter le firmware sur une nouvelle machine hôte ou déboguer des problèmes au niveau firmware.
Pour la configuration utilisateur et l'utilisation de l'interface web, consultez le Manuel Utilisateur picoZ80. Pour la présentation du projet et les instructions de compilation, consultez la page du projet picoZ80.

Architecture Matérielle

Le picoZ80 intègre cinq sous-systèmes sur un unique PCB compact, conçu pour s'inscrire dans l'empreinte d'un boîtier DIP-40. Toute la logique fonctionne en 3,3V ; l'interface bus Z80 gère la conversion de niveaux et l'amplification de courant pour le bus hôte 5V.

Schéma Bloc du Système

┌─────────────────────────────────────────────────────────────────────────┐
│                         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│                                               │
│       │ (40-pin DIP out) │                                               │
│       └────────┬────────┘                                               │
│                │ 5V bus (A0–A15, D0–D7, MREQ, IORQ, RD, WR...)         │
└────────────────┼────────────────────────────────────────────────────────┘
                 │
         ┌───────┴───────┐
         │  Host Z80     │
         │  DIP-40 socket│
         │  (legacy      │
         │   computer)   │
         └───────────────┘

Composants Principaux

Composant Référence Rôle
MCU principalRP2350B (QFN-80)Dual Cortex-M33, 150MHz (jusqu'à 300MHz en OC), 512KB SRAM, 12 machines d'état PIO, 48 broches GPIO
FlashW25Q128 (16MB SPI)Bootloader, double slot firmware, partitions de configuration
PSRAM8MB SPI PSRAM64 × 64KB banques RAM/ROM pour l'espace d'adressage Z80
CoprocesseurESP32-S3-PICO-1WiFi, carte SD, serveur web, OTA
Hub USBCH334FHub USB, pont pour mise à jour firmware
AlimentationTLV62590BVConvertisseur buck synchrone 5V → 3,3V

Attribution GPIO du RP2350B

Le boîtier QFN-80 du RP2350B offre 48 broches GPIO. Le picoZ80 utilise la quasi-totalité de ces broches. L'attribution est fixée dans la conception de la carte et reflétée dans les programmes PIO :
Plage GPIO Signaux Direction
GPIO 0–15 A0–A15 (Bus d’Adresses Z80) Sortie (pilotée par PIO)
GPIO 16–23 D0–D7 (Bus de Données Z80) Bidirectionnel (trois-états PIO)
GPIO 24 MREQ Sortie
GPIO 25 IORQ Sortie
GPIO 26 RD Sortie
GPIO 27 WR Sortie
GPIO 28 M1 Sortie
GPIO 29 RFSH Sortie
GPIO 30 BUSREQ Entrée
GPIO 31 BUSACK Sortie
GPIO 32 HALT Sortie
GPIO 33 INT Entrée
GPIO 34 NMI Entrée
GPIO 35 WAIT Sortie
GPIO 36 CLK Entrée (horloge hôte)
GPIO 37 RESET Entrée
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

Architecture Firmware

Le firmware du RP2350 est compilé avec le Raspberry Pi Pico SDK 2.x ciblant la plateforme RP2350-arm-s. Le firmware est divisé en deux exécutables indépendants : le Bootloader et l'Application.

Organisation de la Mémoire Flash

Partition Plage d’Adresses Taille Contenu
Bootloader 0x10000000 – 0x1001FFFF 128KB Pont USB, mise à jour firmware, sélecteur de partition
App Slot 1 0x10020000 – 0x1051FFFF 5MB Firmware Z80 — application active (slot 1)
App Slot 2 0x10520000 – 0x10A1FFFF 5MB Firmware Z80 — application active (slot 2)
App Config 1 0x10A20000 – 0x10C9FFFF 2.5MB Images ROM + config JSON minifiée (slot 1)
App Config 2 0x10CA0000 – 0x10F1FFFF 2.5MB Images ROM + config JSON minifiée (slot 2)
General Config 0x10F20000 – 0x10FFEFFF 892KB Paramètres du cœur, espace de travail
Partition Table 0x10FFF000 – 0x11000000 4KB Numéro de slot actif, sommes de contrôle, métadonnées

Responsabilités Dual-Core

Les deux cœurs Cortex-M33 se voient attribuer des responsabilités entièrement séparées et communiquent via une file de messages inter-cœurs (queue_t). Cette séparation garantit que le travail non temps réel sur le Core 0 n'introduit jamais de gigue dans les transactions bus Z80 sur le Core 1.
Core Responsabilités
Core 0 Pont série USB CDC ; coordination de la mise à jour firmware ; E/S fichiers (relayées à l'ESP32 via UART) ; dispatch de commandes ESP32 (changements d'images disque, rechargements de configuration, requêtes de version) ; gestion des partitions ; dispatch des messages inter-cœurs.
Core 1 Boucle chaude d'émulation du bus Z80 — s'exécute exclusivement. Alimente les FIFO PIO, résout chaque transaction bus par rapport à la carte mémoire, et dispatch vers : le matériel hôte physique (PHYSICAL), la PSRAM (RAM/ROM), ou le handler de périphérique virtuel (FUNC). La boucle interne est placée en SRAM.

Interface Bus PIO

L'interface bus Z80 est implémentée entièrement en assembleur PIO du RP2350 (z80.pio). Le RP2350 fournit trois blocs PIO (PIO 0, PIO 1, PIO 2) contenant chacun quatre machines d'état — douze machines d'état au total, toutes utilisées par le firmware Z80.
Les programmes PIO s'exécutent indépendamment des cœurs Cortex-M33. L'interface bus continue de répondre de manière déterministe même lorsque le Core 1 est occupé par des accès PSRAM ou des appels de fonctions de périphériques virtuels. Les machines d'état communiquent via des drapeaux IRQ PIO plutôt que par scrutation, éliminant la latence inter-machines.

Tableau des Programmes PIO

PIO Machine d’État Programme Fonction
0 SM 0 z80_addr Émet l’adresse 16 bits (A0–A15) sur le bus et signale le début du cycle au SM 2.
0 SM 1 z80_data Pilote ou échantillonne D0–D7 avec contrôle trois-états ; relâché pendant BUSRQ.
0 SM 2 z80_cycle Séquenceur de cycle bus de haut niveau — orchestre les cycles de fetch, lecture, écriture, E/S et rafraîchissement DRAM.
0 SM 3 z80_fetch Cycle de fetch d’opcode (M1 + MREQ + RD).
1 SM 0 z80_mem_read Cycle de lecture mémoire (MREQ + RD).
1 SM 1 z80_mem_write Cycle d’écriture mémoire (MREQ + WR).
1 SM 2 z80_io_read Cycle de lecture E/S (IORQ + RD).
1 SM 3 z80_io_write Cycle d’écriture E/S (IORQ + WR).
2 SM 0 z80_busrq Gère BUSREQ/BUSACK ; relâche /IORQ, /MREQ, /RFSH, /M1, /HALT, /WR, /RD.
2 SM 1 z80_nmi Détecte l’assertion NMI et signale le Core 1.
2 SM 2 z80_clk_sync Synchronise les machines d’état PIO sur le signal CLK du Z80 hôte.
2 SM 3 z80_int_ack Gère les cycles d’acquittement d’interruption (M1 + IORQ).

Conventions de Signaux IRQ PIO

La communication inter-machines d'état utilise les drapeaux IRQ PIO. Le Core 1 surveille ces drapeaux dans la boucle chaude pour agir sur chaque événement bus :
IRQ Événement
IRQ 0 Adresse valide / début de cycle — un nouveau cycle bus a commencé et A0–A15 sont stables.
IRQ 1 Phase données — la direction du bus de données a été résolue ; D0–D7 doit être piloté ou échantillonné.
IRQ 2 T1 détecté — le front montant de T1 sur le cycle courant. Utilisé pour synchroniser les opérations internes avec l’horloge hôte.
IRQ 3 Événement RESET — la ligne RESET de l’hôte a été activée. Le Core 1 doit réinitialiser l’état d’émulation.
IRQ 4 NMI détecté — la ligne NMI de l’hôte a été activée.
IRQ 6 BUSRQ actif — l’hôte a activé BUSREQ ; le PIO relâche le bus.

Génération de Wait States

Le programme PIO z80_wait dans PIO 2 SM 0 insère des wait states de T-cycle configurables sur le bus hôte en activant /WAIT. Le nombre de wait states supplémentaires est contrôlé par bloc mémoire ou E/S via le paramètre tcycwait dans config.json.
Les wait states sont nécessaires lorsque le RP2350 a besoin de temps supplémentaire pour terminer un accès PSRAM ou un appel de fonction de périphérique virtuel avant de présenter les données au bus hôte. Le paramètre tcycsync active la synchronisation T1 (z80_sync dans PIO 2 SM 1), qui verrouille la fenêtre d'accès PSRAM sur le front montant T1 de chaque cycle bus, empêchant la dérive temporelle dans les applications qui dépendent de l'horloge hôte pour un timing précis (cassette, bit-banging série).

Architecture PIO — Comment les Cycles Bus Sont Recréés

Le sous-système d'E/S Programmable (PIO) du RP2350 est la technologie clé qui permet au picoZ80 de recréer un timing de bus Z80 précis au cycle près. Comprendre comment les machines d'état PIO fonctionnent ensemble est essentiel pour quiconque souhaite modifier l'interface bus ou déboguer des problèmes de timing.

Fondamentaux du PIO RP2350
Chaque bloc PIO contient quatre machines d'état (SM) indépendantes qui exécutent de petits programmes à partir d'une mémoire partagée de 32 instructions. Les machines d'état fonctionnent indépendamment des cœurs Cortex-M33 à la fréquence d'horloge système (jusqu'à 300 MHz). Ressources PIO clés utilisées par le picoZ80 :
  • TX FIFO — une file de 4 entrées du CPU vers la machine d'état. Le code C sur le Core 1 pousse des données (adresses, mots de contrôle, instructions injectées) dans la FIFO ; le programme PIO les extrait avec out ou pull.
  • RX FIFO — une file de 4 entrées de la machine d'état vers le CPU. Le PIO pousse les échantillons du bus de données dans la FIFO avec in ; le Core 1 les lit après la fin de chaque cycle bus.
  • Drapeaux IRQ — 8 drapeaux (IRQ 0–7) partagés entre toutes les machines d'état au sein d'un bloc PIO. Les drapeaux sont également visibles entre blocs PIO (IRQ 0–3 d'un bloc correspondent à IRQ 4–7 des blocs adjacents). Les machines d'état utilisent irq set / irq wait / irq clear pour se synchroniser entre elles et avec le code C.
  • Registres scratch X et Y — deux registres 32 bits par SM utilisés pour les compteurs de boucle et les valeurs temporaires.
  • out exec — une instruction spéciale qui extrait une valeur de la TX FIFO et l'exécute comme instruction PIO. C'est le mécanisme par lequel le code C contrôle dynamiquement les séquences de cycles bus (voir ci-dessous).
  • set pins / out pins — pilotent directement les broches GPIO. set utilise une valeur immédiate de 5 bits ; out décale les données du registre de décalage de sortie (OSR) vers les broches.
  • in pins — échantillonne les broches GPIO dans le registre de décalage d'entrée (ISR), puis pousse automatiquement vers la RX FIFO.
  • wait gpio — bloque le SM jusqu'à ce qu'une broche GPIO spécifique atteigne un niveau spécifié. Utilisé intensivement pour se synchroniser avec le signal d'horloge du Z80 hôte.
  • Side-set — permet de piloter une ou deux broches GPIO comme effet de bord de toute instruction, sans utiliser un cycle d'instruction. Le picoZ80 utilise un side-set de 2 bits pour contrôler /RD et /WR simultanément avec d'autres opérations.
  • JMP PIN — saut conditionnel basé sur le niveau d'une broche GPIO désignée. Utilisé pour tester /WAIT, BUSREQ, /NMI et /RESET.

Le Mécanisme out exec — Injection Dynamique d'Instructions
La caractéristique la plus distinctive de la conception PIO du picoZ80 est l'utilisation de out exec, 16 dans la machine d'état orchestratrice z80_cycle. Cette instruction extrait une valeur de 16 bits de la TX FIFO et l'exécute immédiatement comme instruction PIO — la valeur n'est pas une donnée, elle est la prochaine instruction que la machine d'état exécutera.
Ce mécanisme permet au code C sur le Core 1 de contrôler la séquence de cycles bus en temps réel. Plutôt que de charger un programme PIO fixe pour chaque type de cycle, le Core 1 pousse une séquence d'instructions PIO pré-encodées dans la TX FIFO, et le SM de cycle les exécute une par une :
// 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.
Le code C pré-calcule ces séquences d'instructions au démarrage pour chaque type de cycle (fetch, lecture mémoire, écriture mémoire, lecture E/S, écriture E/S, rafraîchissement, acquittement d'interruption). Pendant l'exécution, le Core 1 sélectionne la séquence pré-construite appropriée et la pousse dans la FIFO. Cette approche offre deux avantages critiques :
  • Efficacité de l'espace programme — chaque bloc PIO ne dispose que de 32 emplacements d'instructions. En injectant les instructions dynamiquement, le SM de cycle n'a besoin que de 7 instructions de mémoire programme pour orchestrer tous les types de cycles. Les programmes de type cycle réels (fetch, lecture, écriture, etc.) existent sous forme de tableaux C d'instructions encodées, et non comme programmes PIO résidents.
  • Flexibilité — le code C peut modifier la séquence d'instructions injectées à l'exécution pour gérer des cas particuliers (par ex. insertion de wait states supplémentaires, omission de la phase de rafraîchissement, ou génération d'un cycle non standard pour le débogage).

Coordination des Machines d'État
Les 12 machines d'état fonctionnent comme un pipeline coordonné. Le diagramme suivant montre le flux d'un cycle de lecture mémoire typique :
                  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
Le handshake basé sur les IRQ garantit que le bus d'adresses est stable avant que les signaux de contrôle ne soient activés, et que les données sont échantillonnées au point correct du cycle bus. Les machines d'état ne scrutent jamais — elles utilisent wait 0 irq N pour se mettre en veille jusqu'à ce que l'événement pertinent se produise, ne consommant aucun cycle CPU pendant l'attente.

Cycle de Fetch Z80 (Cycle M1) — Étape par Étape
Le fetch d'opcode est le cycle bus Z80 le plus complexe — il combine une lecture mémoire avec un cycle de rafraîchissement. Le programme z80_fetch s'exécute sur 4 T-cycles de l'horloge hôte :
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
Le programme PIO implémente ceci comme suit :
  1. Front montant T1 — le SM z80_addr place la valeur PC sur A0–A15. z80_fetch active /M1 à l'état bas via set pins.
  2. Front descendant T1 — /MREQ et /RD sont activés à l'état bas (via set pins et side-set). L'adresse est maintenant valide et le système mémoire peut commencer à répondre.
  3. T2 — le SM entre dans une boucle de wait state : il attend le front montant puis descendant du CLK, puis vérifie la broche /WAIT via jmp pin. Si /WAIT est bas, le SM boucle (ajoutant des cycles Tw). S'il est haut, il passe à T3.
  4. Front montant T3 — in pins, 8 échantillonne D0–D7 (l'octet d'opcode) et le pousse dans la RX FIFO. IRQ 1 est activé pour signaler à z80_data/z80_addr que l'adresse de rafraîchissement doit maintenant être émise. /M1, /MREQ et /RD sont désactivés ; /RFSH est activé à l'état bas.
  5. Front descendant T3 — /MREQ est de nouveau activé (pour le strobe de ligne de rafraîchissement).
  6. T4 — le rafraîchissement continue. À la fin de T4, /MREQ et /RFSH sont désactivés. Le SM de cycle retourne à start_cycle, prêt pour la prochaine transaction bus.
Le Core 1 lit l'opcode depuis la RX FIFO et l'utilise pour décoder l'instruction, déterminer combien de cycles mémoire ou E/S subséquents sont nécessaires, et pousser les séquences d'instructions appropriées.

Cycles de Lecture et Écriture Mémoire
Les cycles de lecture et d'écriture mémoire sont plus simples que le fetch — ils s'étendent sur 3 T-cycles sans phase de rafraîchissement.
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↓)
Pour les lectures, le SM z80_mem_read active /MREQ et /RD au front descendant T1, attend pendant T2 (vérifiant /WAIT pour les wait states), puis échantillonne le bus de données au front descendant T3 avec in pins, 8. Pour les écritures, z80_mem_write active /MREQ au front descendant T1, puis active /WR au front descendant T2 après que le bus de données est piloté par z80_data. Les deux désactivent tous les signaux de contrôle à la fin de T3.

Cycles de Lecture et Écriture E/S
Les cycles E/S du Z80 utilisent /IORQ au lieu de /MREQ et incluent toujours un wait state automatique (Tw) entre T2 et T3. C'est une caractéristique architecturale du Z80 — le cycle supplémentaire donne aux périphériques E/S plus lents le temps de répondre :
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↓)
Le SM z80_io_read active /IORQ et /RD au front montant T2 (et non T1 comme pour les cycles mémoire — c'est la spécification du Z80). Le wait state automatique Tw est implémenté par le même schéma de boucle jmp pin / wait utilisé pour les cycles mémoire. Les écritures E/S suivent le même schéma avec /WR remplaçant /RD.

Gestion BUSREQ / BUSACK
Le SM z80_busrq (PIO 2 SM 0) surveille la broche d'entrée /BUSREQ de l'hôte. Quand /BUSREQ passe à l'état actif (bas), le SM :
  1. Active IRQ 6 pour signaler au SM de cycle qu'une requête bus est en attente.
  2. Attend la fin du cycle bus courant (wait 1 irq 0).
  3. Extrait un mot de contrôle de 32 bits de la TX FIFO qui spécifie les directions et valeurs des broches pour l'état de relâchement du bus — ceci met en haute impédance les bus d'adresses et de données et active /BUSACK à l'état bas.
  4. Boucle sur jmp pin jusqu'à ce que /BUSREQ devienne inactif (haut).
  5. Extrait un second mot de 32 bits pour restaurer les directions normales des broches et désactiver /BUSACK.
  6. Efface IRQ 6, permettant au SM de cycle de reprendre.
Le SM de cycle vérifie IRQ 6 au début de chaque cycle via wait 0 irq 6 — si le drapeau est activé, le SM se bloque jusqu'à ce que la requête bus soit terminée. Ceci garantit que le relâchement du bus se fait proprement entre les cycles, jamais en milieu de cycle.

Synchronisation d'Horloge
Tous les SM de type cycle se synchronisent sur l'horloge Z80 hôte via wait 1 gpio Z80_PIN_CLK (attendre le front montant) et wait 0 gpio Z80_PIN_CLK (attendre le front descendant). Cela signifie :
  • Les programmes PIO sont indépendants de la fréquence d'horloge — ils fonctionnent à n'importe quelle vitesse d'horloge hôte, du DC à la vitesse maximale que le RP2350 peut suivre (limitée par l'horloge système PIO et la fréquence d'échantillonnage GPIO).
  • L'horloge PIO du RP2350 à 300 MHz fournit environ 85 cycles PIO par T-state du Z80 à 3,5 MHz, offrant largement assez de temps pour exécuter les instructions PIO, pousser/extraire les FIFO et vérifier les drapeaux IRQ entre les fronts d'horloge.
  • Le SM z80_sync (PIO 2 SM 1) fournit une IRQ de synchronisation T1 que le code C utilise pour aligner les accès PSRAM avec l'horloge hôte, empêchant la dérive temporelle dans les logiciels hôte sensibles au timing.
  • Le SM z80_clk_sync (PIO 2 SM 2) régénère l'horloge hôte sur un GPIO séparé, fournissant une sortie d'horloge propre pour la surveillance externe ou le déclenchement d'un analyseur logique.

Cycle d'Acquittement d'Interruption
Le SM z80_int_ack (PIO 2 SM 3) implémente la séquence d'acquittement d'interruption du Z80. Lorsque le code C détecte une condition d'interruption, il charge le programme int_ack. Ce cycle est similaire à un fetch mais avec des différences clés :
  • /M1 est activé à T1 (comme un fetch), mais /IORQ est activé au lieu de /MREQ au wait state (Tw1).
  • Deux wait states automatiques (Tw1, Tw2) sont insérés pour donner au périphérique interrompant le temps de placer un vecteur sur le bus de données.
  • L'octet de vecteur est lu depuis D0–D7 et poussé dans la RX FIFO.
  • Un cycle de rafraîchissement suit, identique à la phase de rafraîchissement du fetch.

Modèle Mémoire

Les accès mémoire sont résolus à travers trois niveaux de latence croissante. La conception à trois niveaux garantit que le cas courant (RAM/ROM sauvegardée en PSRAM) est rapide tout en permettant une flexibilité maximale pour les périphériques virtuels et le pass-through matériel hôte physique.

Niveau 1 — Table de Dispatch en SRAM RP2350

Un tableau de 128 entrées de valeurs membankPtr sur 32 bits, résidant dans les 512KB de SRAM embarquée du RP2350, fournit une recherche de type de bloc en O(1) pour chaque transaction bus. Une entrée couvre chaque bloc de 512 octets de l'espace d'adressage Z80 de 64KB (128 × 512 = 65 536 octets). Chaque entrée encode :
  • Le type de bloc (PHYSICAL, RAM, ROM, FUNC, etc.).
  • Pour les blocs sauvegardés en PSRAM : le numéro de banque PSRAM et l'offset.
  • Pour les blocs FUNC : un index dans la table de pointeurs de fonctions des périphériques virtuels.
C'est le chemin le plus rapide — le Core 1 lit l'entrée de la table de dispatch pour l'adresse courante en un seul accès SRAM (zéro wait state à 300MHz) avant de décider de la suite.

Niveau 2 — PSRAM Externe

Les 8MB de PSRAM sont organisés comme suit :
  • 64 banques × 64KB — données d'images RAM ou ROM pour l'espace d'adressage Z80.
  • 64KB memPtr — tableau de pointeurs de redirection par octet pour les blocs de type PTR.
  • 64KB memioPtr — tableau de pointeurs de fonctions pour les périphériques FUNC mappés en mémoire.
  • 64KB ioPtr — tableau de pointeurs de fonctions pour les périphériques FUNC de port E/S.
La PSRAM est accédée via le périphérique SPI dédié du RP2350 avec DMA. La latence d'accès est déterministe et gérée par le générateur de wait states pour éviter les violations de bus.

Niveau 3 — Flash SPI 16MB

Les images ROM sont chargées depuis la Flash (ou la carte SD, via l'ESP32) dans la PSRAM au démarrage. À l'exécution, la Flash n'est pas accédée pour les transactions bus — toutes les données ROM sont servies depuis la PSRAM. La Flash est utilisée pour :
  • Le bootloader et le firmware applicatif.
  • Le config.json minifié (mis en cache depuis la carte SD à chaque démarrage).
  • Les images ROM dans les partitions App Config (utilisées lorsqu'aucune carte SD n'est présente).

Types de Blocs Mémoire

Type Description
PHYSICAL Pass-through — le RP2350 relâche le bus et la mémoire physique de l’hôte répond. Utilisé pour la ROM et la RAM natives de l’hôte.
PHYSICAL_VRAM Comme PHYSICAL mais avec des wait states supplémentaires pour le timing de la VRAM hôte. Adapté aux régions VRAM du MZ-700/MZ-80A.
PHYSICAL_HW Pass-through pour les registres matériels de l’hôte (périphériques mappés en E/S dans l’espace mémoire).
RAM Lecture/écriture — sauvegardé par une banque PSRAM. Le RP2350 dessert les lectures et écritures depuis/vers la PSRAM.
ROM Lecture seule — sauvegardé par une banque PSRAM. Les cycles d’écriture sont silencieusement ignorés (l’hôte voit un timing bus normal mais aucune donnée n’est stockée).
VRAM RAM vidéo sauvegardée en PSRAM. Les cycles d’écriture sont dupliqués simultanément vers la PSRAM et la VRAM physique de l’hôte.
FUNC Périphérique virtuel — chaque accès déclenche un appel de fonction C via la table de pointeurs de fonctions memioPtr ou ioPtr, permettant une émulation E/S arbitraire.
PTR Redirection par octet — chaque octet du bloc de 512 octets peut pointer indépendamment vers tout autre type de bloc ou emplacement PSRAM.

Référence de Configuration

Tout le comportement du picoZ80 est contrôlé par config.json sur la carte SD. Le RP2350 lit et minifie ce fichier au démarrage, stockant le résultat en Flash. Les démarrages suivants utilisent la copie Flash si aucune carte SD n'est présente.
La structure JSON de haut niveau est :
{
  "esp32": {
    "core":  { ... },
    "wifi":  { ... }
  },
  "rp2350": {
    "core":  { ... },
    "z80":   [ { "memory": [...], "io": [...], "drivers": [...] } ]
  }
}

esp32.core

Clé Type Description
device string Personnalité CPU — "Z80" pour picoZ80, "6502" pour pico6502, "6512" pour pico6512.
mode integer Mode WiFi par défaut au démarrage : 0 = client (station), 1 = Point d’Accès.

esp32.wifi

Clé Type Description
override 0/1 Commutateur principal : 1 = appliquer tous les paramètres ci-dessous ; 0 = utiliser les paramètres NVS persistants.
wifimode string "ap" = mode Point d’Accès ; "client" = mode Station/client.
ssid string Nom du réseau WiFi à créer (AP) ou rejoindre (client).
password string Phrase secrète WiFi.
ip string Adresse IP fixe (par ex. "192.168.1.192").
netmask string Masque de sous-réseau (par ex. "255.255.255.0").
gateway string Passerelle par défaut (par ex. "192.168.1.1").
dhcp 0/1 Mode client : 1 = DHCP ; 0 = utiliser les paramètres IP fixes.
webfs string Répertoire racine du système de fichiers web sur la carte SD (par défaut "webfs").
persist 0/1 1 = écrire les paramètres résolus en NVS pour la persistance entre les redémarrages.

rp2350.core

Clé Type Description
cpufreq integer Horloge système du RP2350 en Hz (par ex. 300000000). La fréquence stable maximale dépend de la fréquence PSRAM et de la tension du cœur.
psramfreq integer Horloge SPI de la PSRAM en Hz (par ex. 133000000).
voltage float Tension du cœur du RP2350 en volts (par ex. 1.10). Des vitesses d’horloge plus élevées nécessitent une tension plus élevée.
addrDrive integer (0–3) Force de pilotage GPIO du bus d’adresses : 0=2mA, 1=4mA, 2=8mA, 3=12mA. Par défaut 0.
addrSlew integer (0–1) Vitesse de transition GPIO du bus d’adresses : 0=lente (par défaut), 1=rapide.
dataDrive integer (0–3) Force de pilotage GPIO du bus de données. Même encodage que addrDrive. Par défaut 0.
dataSlew integer (0–1) Vitesse de transition GPIO du bus de données. Même encodage que addrSlew. Par défaut 0.
ctrlDrive integer (0–3) Force de pilotage GPIO des signaux de contrôle. Même encodage que addrDrive. Par défaut 0.
ctrlSlew integer (0–1) Vitesse de transition GPIO des signaux de contrôle. Même encodage que addrSlew. Par défaut 0.
addrSchmitt / dataSchmitt / ctrlSchmitt 0/1 Active le trigger de Schmitt en entrée pour le groupe de bus d’adresses / données / contrôle. 1 = activé (par défaut), 0 = désactivé. S’applique aux broches configurées en entrée.
addrPull / dataPull / ctrlPull integer (0–2) Résistance de tirage pour le groupe de bus d’adresses / données / contrôle : 0 = aucune, 1 = tirage vers le bas (pull-down), 2 = tirage vers le haut (pull-up).
refresh integer Génération du rafraîchissement DRAM Z80 : 0 = désactivé (aucun cycle de rafraîchissement), 1 = un cycle de rafraîchissement à chaque fetch d’opcode, N = un cycle de rafraîchissement tous les N fetchs. Réduit la charge sur le bus pour les hôtes dont la DRAM ne nécessite pas de rafraîchissement à chaque fetch.
pinOverrides array Surcharges GPIO par broche. Chaque élément : { "pin": N, "drive": D, "slew": S, "schmitt": 0/1, "pull": P }. Surcharge les paramètres par défaut au niveau bus pour des broches GPIO individuelles lorsque du matériel spécifique nécessite des caractéristiques électriques différentes.

z80[].memory — Entrées de Carte Mémoire

Le tableau memory définit la carte mémoire Z80. Les entrées doivent être ordonnées par adresse. Les régions doivent être alignées et dimensionnées en multiples de 512 octets. Les espaces entre les entrées sont traités comme du pass-through PHYSICAL.
Clé Type Description
enable 0/1 Indique si cette entrée est active. Les entrées désactivées sont ignorées au démarrage.
addr hex string Adresse de début dans l’espace d’adressage Z80 (par ex. "0x0000"). Doit être alignée sur 512 octets.
size hex string Taille de la région en octets (par ex. "0x2000" pour 8KB). Doit être un multiple de 512.
type string Type de bloc — voir Types de Blocs Mémoire.
bank integer Numéro de banque PSRAM (0–63) pour les types RAM/ROM/VRAM/FUNC.
tcycwait integer Wait states de T-cycle supplémentaires à insérer à chaque accès à cette région.
tcycsync 0/1 Activer la synchronisation T1 pour cette région. Requis pour les régions sensibles au timing.
task string Identifiant de tâche optionnel pour les blocs de type FUNC (chaîne de liaison au pilote).
file string Chemin sur la carte SD vers une image ROM à précharger dans la banque PSRAM au démarrage (par ex. "/ROM/mz700.rom").
fileofs integer Offset en octets dans le fichier image ROM à partir duquel commencer la lecture.

z80[].io — Entrées de Carte de Ports E/S

Le tableau io mappe les plages de ports E/S Z80 vers des types de blocs. Seuls les types PHYSICAL et FUNC sont pertinents pour les entrées E/S.
Clé Type Description
enable 0/1 Indique si cette entrée E/S est active.
addr hex string Adresse de port E/S de début (par ex. "0xE0").
size hex string Nombre de ports consécutifs (par ex. "0x04" pour les ports E0–E3).
type string PHYSICAL = passer à l’hôte ; FUNC = appeler la fonction handler C.
task string Chaîne de liaison au pilote pour les entrées de type FUNC.

z80[].drivers — Instances de Pilotes

Le tableau drivers instancie les pilotes de périphériques virtuels et les lie aux régions mémoire ou E/S. Chaque pilote a un type (le module pilote C), un nom (identifiant d'instance), et un ou plusieurs objets interface qui définissent les images ROM, les cartes d'adresses, les cartes E/S et les paramètres pour cette instance de pilote.
"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" }
        ]
      }
    ]
  }
]
Chaque entrée de pilote a un "name" de haut niveau (le module pilote C à instancier), un "type" (PHYSICAL ou VIRTUAL), et un tableau "if" (interface) contenant un ou plusieurs objets interface. Chaque objet interface décrit un sous-pilote ou une carte périphérique et contient ces champs :
Clé Type Description
enable 0/1 Indique si cette interface est active (0 = ignorée pendant l’initialisation).
name string Identifiant d’instance — doit correspondre à un nom dans le tableau interfaceFuncMap[] du persona.
type string PHYSICAL ou VIRTUAL.
rom array Images ROM à charger en PSRAM (voir ci-dessous).
addrmap array Entrées de remappage d’adresses mémoire (srcaddr/dstaddr/size).
iomap array Entrées de remappage de ports E/S (srcaddr/dstaddr/size/16bit).
param array Paramètres spécifiques au pilote — typiquement { "enable":1, "file":"/path/to/image" } pour les images disque, { "name":"key", "value":"val" } pour les paramètres nommés, ou { "ip":"a.b.c.d:port", "enable":1 } pour l’adresse du serveur de fichiers réseau Celestite.

Ports de base d’interface relocalisables. Afin que les cartes indépendantes de la machine puissent être utilisées sur des cartes personnalisées / d’expérimentation (voir OpenZ80), plusieurs pilotes d’interface lisent leur port d’E/S de base depuis le dstaddr de leur entrée iomap (avec srcaddr réglé sur le port authentique de la carte), revenant à leur port d’origine lorsqu’aucune entrée iomap n’est présente. Les cartes relocalisables et leurs bases par défaut sont 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) et Celestite (0x60/16). Les clés JSON sont en minuscules (srcaddr / dstaddr) et leurs valeurs sont interprétées comme des nombres. Le champ Base I/O Port de la page Configuration GUI écrit automatiquement l’entrée iomap correcte.

rom[].loadaddr[] — Adresses de Chargement ROM

Chaque entrée de fichier ROM contient un tableau loadaddr spécifiant où les données ROM sont chargées en PSRAM. Plusieurs entrées loadaddr permettent de répartir un seul fichier ROM sur des plages d'adresses non contiguës ou des banques PSRAM différentes.
Clé Type Description
enable 0/1 Indique si cette adresse de chargement est active.
position integer Offset en octets dans le fichier ROM à partir duquel commencer la lecture.
addr hex string Adresse Z80 cible dans la carte mémoire (par ex. "0x0000").
bank integer Numéro de banque PSRAM dans laquelle charger.
size hex string Nombre d’octets à charger (par ex. "0x1000" pour 4KB).
tcycwait integer Wait states de T-cycle supplémentaires pour cette région.
tcycsync integer Valeur de synchronisation de T-cycle pour cette région.

Compatibilité Persona–Interface

Tous les pilotes d'interface ne sont pas disponibles pour chaque persona. Le tableau ci-dessous montre la compatibilité actuelle des interfaces pour les séries Sharp MZ / X1. Les personas Amstrad PCW-9512 et PCW-8256/8512 sont des pilotes autonomes avec un FDC uPD765 intégré et leurs propres interfaces FDC / PRINTER / MEMORY / PAGEPROBE — voir Prise en charge de l'Amstrad PCW. Le persona Tatung Einstein TC-01 est un pilote autonome avec un FDC WD1770 intégré et une sous-interface EinsteinFDC. La colonne Open montre les cartes indépendantes de la machine acceptées par le persona expérimentateur OpenZ80.
Le tableau montre quelles interfaces peuvent être utilisées avec chaque persona machine de haut niveau via le tableau "if". La valeur "name" de l'interface dans le JSON doit correspondre à l'une des entrées supportées pour le persona choisi.
Interface MZ-700 MZ-1500 MZ-80K MZ-800 MZ-80A MZ-2000 MZ-2200 MZ-80B MZ-2500 Open
RFS Yes Yes Yes Yes Yes Yes — — — —
TZFS Yes — — — — — — — — —
MZ-1E05 Yes Yes — Yes — — — — — Yes
MZ80FIO — — Yes Yes — — — — — —
MZ80AFI — — Yes Yes Yes — — — — —
MZ-8BFI / E0054PA — — — Yes — Yes Yes Yes Yes —
MZ-1E14 Yes Yes Yes Yes — — — Yes Yes —
MZ-1E19 Yes Yes Yes Yes — Yes Yes Yes Yes —
MZ-1R12 Yes Yes Yes Yes Yes Yes Yes Yes Yes Yes
MZ-1R18 Yes Yes Yes Yes Yes Yes Yes Yes Yes Yes
MZ-1R23 — Yes — Yes — — — Yes Yes Yes
MZ-1R37 — Yes Yes Yes — — — Yes Yes Yes
PIO-3034 — Yes Yes Yes — — — Yes Yes Yes
Celestite — Yes — Yes — — — Yes Yes Yes
MZ-8BIO3 Yes Yes — Yes — — — Yes — Yes
MZ-1E24 Yes Yes — Yes — — — Yes — Yes
MZ-1E30 — — — — — — — Yes Yes —

Pilotes Intégrés

Le firmware supporte un système de compilation ciblé avec cinq cibles de modèle : BaseZ80 (tous les pilotes), SharpZ80 (Sharp uniquement), AmstradZ80 (Amstrad uniquement), TatungZ80 (Tatung uniquement) et OpenZ80 (le persona expérimentateur indépendant de la machine). Les defines de compilation INCLUDE_SHARP_DRIVERS, INCLUDE_AMSTRAD_DRIVERS, INCLUDE_TATUNG_DRIVERS et INCLUDE_OPEN_DRIVERS contrôlent quels modules pilotes sont compilés. Les tableaux ci-dessous listent tous les pilotes disponibles.
Série Sharp MZ (INCLUDE_SHARP_DRIVERS)
Lorsque le firmware est compilé avec INCLUDE_SHARP_DRIVERS, les modules pilotes suivants sont inclus et peuvent être instanciés via le tableau drivers :
Pilote Chaîne de Type Description
MZ700.c MZ700 Sharp MZ-700 bank switching, vidéo, E/S clavier
MZ80A.c MZ80A Sharp MZ-80A — ROM moniteur (SA-1510), VRAM, émulation Intel 8253 PIT, 8255 PPI, échange mémoire MEMSW/MEMSWR (y compris bank switching CP/M), prise en charge du mode mixte physique+virtuel, et support des sous-interfaces RFS, MZ80AFI, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18
MZ2000.c MZ2000 Sharp MZ-2000 — commutation de mode mémoire BST/NST, overlay VRAM caractères + graphiques, 8253 PIT, 8255 PPI, Z80 PIO, MB8866 FDC. Prend en charge à la fois le mode physique (remplacement Z80 avec détection automatique du mode boot/normal) et le mode virtuel (émulation complète basée sur PSRAM avec mirroring de la ROM IPL)
MZ2200.c MZ2200 Sharp MZ-2200 — commutation de mode mémoire BST/NST, overlay VRAM, 8253 PIT, 8255 PPI, Z80 PIO, MB8866 FDC, CRT couleur
MZ80B.c MZ80B Sharp MZ-80B — ROM IPL 2K, BST/NST, affichage monochrome, doubles pages GRPH, 8253 PIT, 8255 PPI, Z80 PIO
MZ2500.c MZ2500 Sharp MZ-2500 (SuperMZ) — MMU à 8 pages (64 blocs), modes de compatibilité MZ-2000/MZ-80B, YM2203, G-CRTC, MB8876 FDC, palette, contrôleur d’interruption. Le mode virtuel prend en charge les logiciels pilotés par interruption via des handlers fetchByte/RETI personnalisés (cycles bus M1 physiques pour le cadençage du gate array), format de disque natif D88 avec détection automatique sparse/contiguë, suppression d’interruption ponctuelle pendant l’initialisation
MZ1500.c MZ1500 Sharp MZ-1500 — surensemble du MZ-700 avec lecteur Quick Disk intégré, PCG, son PSG stéréo (SN76489AN), interface imprimante Z80 PIO, 8253 PIT, sélection de mode MZ-700/MZ-1500 par commutateur DIP. Sous-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 moniteur SP-1002 (0x0000–0x0FFF), 2KB VRAM (0xD000–0xD7FF), clavier 8255 PPI / 8253 PIT / LS367 (0xE000–0xE7FF), ROM de boot native MZ-80FD (0xF000–0xF3FF), échange mémoire MEMSW/MEMSWR pour CP/M. Modes physique + virtuel (le CP/M en mode physique utilise le remap d’accès Z80 propre au pilote ; le mode virtuel est requis pour le CP/M d’origine). Deux chemins floppy : MZ80FIO natif (T3444M) — interface MZ-80FD d’origine, boote/lit tous les disques MZ-80K ; et MZ80AFI (FDC MZ-80A) — utilisé pour CP/M, boote le CP/M MZ-80K et lit les disques CP/M MZ-80K depuis CP/M (C:/D:). Une seule interface floppy est active à la fois ; MZ80FIO l’emporte si les deux sont présentes. Sous-interfaces : RFS, MZ80FIO, MZ80AFI, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18, MZ-1R37, PIO-3034
MZ800.c MZ800 Sharp MZ-800 — pilote à double mode (compatibilité MZ-700 + MZ-800 natif). Espionne le registre de mode d’affichage du GDG (port 0xCE) pour basculer de mode à la volée : le mode natif ajoute les graphiques 320×200 / 640×200 (plans VRAM 0x8000–0xBFFF), une palette 4/16 couleurs, une E/S GDG mappée sur ports (0xCC–0xCF, palette 0xF0), des interruptions vectorisées IM2 via la chaîne daisy-chain du Z80-PIO ; le mode MZ-700 utilise les 8255/8253 mappés en mémoire (0xE000–0xE7FF) et la VRAM texte (0xD000). PSG SN76489 (0xF2), WD1773 FDC (0xD8–0xDF), QuickDisk (0xF4–0xF7), ports de banking mémoire 0xE0–0xE6. Le mode virtuel rejoue un RETI physique sur le bus réel pour servir le verrou in-service du PIO.
X1.c X1 Sharp X1 / X1turbo — persona en passthrough physique : la machine réelle fournit la RAM, la ROM IPL, la VRAM, le CRTC, le sous-CPU et le PSG, et chaque bloc mémoire est transmis à l’hôte. Le picoZ80 ajoute la disquette et la cassette virtuelles ci-dessous ; seuls leurs ports sont interceptés
X1FDC.c (utilisé par X1) Contrôleur de disquette virtuel MB8877A (famille WD1793, un mince adaptateur au-dessus du moteur WD1773 partagé) aux ports 0xFF8–0xFFF avec décodage 16 bits complet. Les images Raw, D88 et Extended CPC DSK sont détectées par leur contenu, non par leur extension. Les supports 2D / 2DD et 2HD 1MB sont pris en charge avec sélection de média par l’hôte (les lectures de 0FFC–0FFF sélectionnent FM / MFM / 2HD), permettant de démarrer le disk BASIC CZ-8FB02 directement depuis une image 2HD. En mode deck Virtuel, le DMA disquette est déclaré automatiquement comme périphérique intégré ; en mode deck Réel, le DMA et le MB8877 propres à la machine restent appariés
X1CMT.c (utilisé par X1) Cassette virtuelle X1 — diffuse des images de forme d’onde .tap (un flux de bits échantillonné à cadence constante, pas un format encodé) depuis la SD via un tampon circulaire, le transport étant commandé via les ports du sous-CPU. L’enregistrement capture à 48kHz et nomme la bande sauvegardée d’après l’en-tête de bande
MZ80AFI.c MZ80AFI Interface floppy Sharp MZ-80A — émule le contrôleur de disquette AFI du MZ-80A
MZ80FIO.c MZ80FIO Interface floppy Sharp MZ-80FD/MZ-80FIO (MZ-80K) — ROM de boot FDIF à 0xF000–0xF3FF, ports T3444M 0xF8–0xFB, jusqu’à 4 lecteurs, changement de disque à l’exécution
T3444M.c (utilisé par MZ80FIO) Toshiba T3444M/T3444A FDC — floppy natif MZ-80K. CPC extended DSK, 35 pistes, 2 têtes, 16 secteurs/piste, secteurs FM de 128 octets ; analyseur DSK Track-Info robuste ; 4 images de lecteur simultanées
WD1773.c WD1773 WD1773 FDC — images DSK/RAW/D88 80 pistes, 2 têtes, 8 secteurs
QDDrive.c QDDRIVE Lecteur Sharp QuickDisk — émulation complète Z80 SIO/2 avec données de piste en spirale, contrôle moteur et E/S carte SD asynchrone
RFS.c RFS ROM Filing System — chargement MZF, CP/M, BASIC depuis carte SD
TZFS.c TZFS TranZPUter Filing System — moniteur multi-bancs fonctionnel + système de fichiers CP/M, sélectionnable sur le persona MZ-700. Modes mémoire tranZPUter via le port 0x60, processeur de service K64F virtuel via OUT (0x68), E/S secteur CP/M via l’ESP32. ROM roms/tzfs.bin depuis TZFS/asm/tzfs.asm
MZ-1E05.c MZ1E05 Unité d’interface de disquette Sharp MZ-1E05 (basée sur WD1773)
MZ8BFI.c MZ8BFI / E0054PA Interface de disquette MZ-2000 — MB8866 FDC sans ROM pilote (code dans l’IPL). Prise en charge du format D88.
MZ-1E14.c MZ1E14 Contrôleur QuickDisk MZ-1E14 avec ROM BIOS (MZ-700/MZ-800)
MZ-1E19.c MZ1E19 Contrôleur QuickDisk MZ-1E19 sans ROM BIOS
MZ-1R12.c MZ1R12 Carte RAM 32KB sauvegardée par batterie (persistée sur carte SD)
MZ-1R18.c MZ1R18 Carte d’extension RAM 64KB
MZ-1R23.c MZ1R23 ROM Kanji 128KB MZ-1R23 (motifs JIS 16×16) et ROM Dictionnaire 256KB MZ-1R24. Fichiers ROM chargés depuis la carte SD. Ports E/S B8h–B9h avec lecture à auto-incrément
MZ-1R37.c MZ1R37 MZ-1R37 640KB EMM (Expanded Memory Manager) — espace d’adressage 20 bits avec verrouillage d’adresse par port E/S
PIO-3034.c PIO3034 IO DATA PIO-3034 320KB EMM — compteur d’adresses 19 bits avec port de données à auto-incrément
Celestite.c Celestite Carte composite Celestite — contrôleur Ethernet Wiznet W5100 (émulation de registres), contrôleur d’interruption, UFM, RAM CMOS 32KB MZ-1R12 intégrée (extensible à 64KB), MZ-1R37 640KB EMM optionnel. Ports E/S 60h–6Fh. Le paramètre ip dans le tableau JSON param configure l’adresse du serveur de fichiers netfs.py (par ex. "192.168.1.210:6800"). Phase 2 : réseau TCP/IP réel via le pont ESP32 — les commandes socket W5100 sont transmises à l’ESP32 pour les opérations BSD socket réelles. IPC inter-cœurs pour NET_CFG, NET_SOCK, NET_SEND, NET_RECV, NET_PING
MZ8BIO3.c MZ-8BIO3 Carte série RS-232C (connecteur BI), Z80 SIO émulé aux ports 0xB0–0xB3 (base configurable). Canaux A/B pontés vers les ports série USB CDC 2 et 3. Pas de ROM.
MZ1E24.c MZ-1E24 Carte série RS-232C (connecteur Sharp ST) ; identique au MZ-8BIO3 avec un câblage de connecteur différent.
Z80SIO.c (utilisé par MZ-8BIO3 / MZ-1E24) Zilog Z80 SIO/2 fidèle au niveau des registres : WR0–WR7, RR0–RR2, interruptions vectorisées Z80 mode-2, daisy-chain in-service à 4 niveaux, status-affects-vector. Des anneaux SPSC sans verrou relient le cœur Z80 (core 1) et le service USB CDC (core 0).
SASI.c + MZ1E30.c MZ-1E30 Contrôleur de disque dur SASI MZ-1E30 — émule le contrôleur de disque dur SASI (Shugart Associates System Interface) Sharp MZ-1E30 pour MZ-2500/MZ-80B. Jusqu’à 4 cibles de disque (~21,4 Mo chacune, blocs de 256 octets), ROM IPL 32KB (accès E/S via les ports 0xA8–0xA9), E/S secteur à la demande depuis les images disque de la carte SD. Commandes SASI : TEST_UNIT_READY, REQUEST_SENSE, READ(6), WRITE(6), SEEK(6), INQUIRY. Ports E/S 0xA4–0xA5 (données/contrôle SASI)
PIT8253.c PIT8253 Émulation autonome Intel 8253 PIT — les six modes de compteur, BCD/binaire, latch, lecture/chargement LSB/MSB
PPI8255.c PPI8255 Émulation autonome Intel 8255 PPI — E/S Mode 0, set/reset de bit, callbacks de sortie, injection d’entrée
Série Amstrad PCW (INCLUDE_AMSTRAD_DRIVERS)
Lorsque le firmware est compilé avec INCLUDE_AMSTRAD_DRIVERS, les modules pilotes suivants sont inclus :
Pilote Chaîne de Type Description
PCW9512.c PCW9512 Amstrad PCW-9512 — Z80A @ 4MHz, 512KB RAM avec commutation de pages 16KB à 4 banques (ports F0–F3), gate array (ASIC) pour vidéo/horloge système/routage FDC/contrôle moteur (port F8), stub de contrôleur d’imprimante à marguerite (ports FC–FD), émulation de la séquence bootstrap, shadow DRAM en PSRAM avec réparation vidéo et mémoire d’extension. Modes de pilote PHYSICAL et VIRTUAL — voir Prise en charge de l’Amstrad PCW
PCW8256.c PCW8256 Amstrad PCW-8256/8512 — partage le cœur du pilote PCW9512 (aiguillage par variante) et ajoute le modèle de statut de l’imprimante matricielle, dont les bits de statut du port FDh diffèrent de ceux de la marguerite. Lecteurs 3” CF2, géométrie tirée de l’image disque ; 256KB (8256) ou 512KB (8512) de RAM machine, jusqu’à 1,5MB au total avec la mémoire d’extension
uPD765.c — Émulation NEC uPD765 FDC — prise en charge du format CPC DSK (ports 00–01). Commandes du gate array : fin du bootstrap, redémarrage, routage INT FDC (NMI/INT/ignorer), terminal count, moteur marche/arrêt. Démarrage sans disque depuis une image .dsk, permutation de deck Réel ↔ Virtuel à l’exécution, imagerie de disque physique via code Z80 injecté. Module FDC réutilisable (analogue à WD1773.c pour Sharp)
Série Tatung Einstein (INCLUDE_TATUNG_DRIVERS)
Lorsque le firmware est compilé avec INCLUDE_TATUNG_DRIVERS, les modules pilotes suivants sont inclus :
Pilote Chaîne de Type Description
EinsteinTC01.c EinsteinTC01 Tatung Einstein TC-01 — Z80A @ 4MHz, 64KB RAM + 8KB ROM commutable (X-TAL MOS), bascule ROM/RAM via le port 0x24, TMS9129 VDP avec imposition d’un timing inter-accès (intervalle ~2us), AY-3-8910 PSG (ports 0x02-0x03), Z80 CTC (ports 0x28-0x2B), Z80 PIO (ports 0x30-0x33), interface clavier (port 0x20). Modes virtuel et physique pris en charge
EinsteinFDC.c — Sous-interface Einstein FDC — 2 lecteurs, prise en charge des formats DSK/D88. Imagerie de disque physique : lecture d’une disquette physique vers DSK, écriture d’un DSK vers une disquette physique
WD1770.c — Émulation WD1770 FDC — prise en charge des formats Extended CPC DSK, D88 et DSK standard. 40 pistes, 1 tête, 10 secteurs, 512 octets (disquettes 200KB). Module FDC réutilisable pour les machines basées sur WD1770 (distinct de WD1773.c utilisé par Sharp)

TZFS — Modèle Moniteur + CP/M (persona MZ-700)
TZFS.c implémente un moniteur bas niveau multi-bancs fonctionnel et un système de fichiers — un ensemble d'améliorations par rapport au MONITOR 1Z-013A d'origine (accès SD, commutation de bancs ROM, un assembleur / désassembleur et des outils) — modelé sur le TZFS du tranZPUter SW et son processeur d'E/S virtuel K64F. Il est proposé comme interface sélectionnable sur le persona MZ-700 uniquement (enregistré dans MZ700.c aux côtés de RFS, et en pratique mutuellement exclusif avec lui), et non comme un persona de premier niveau. CP/M s'exécute sous le moniteur.
Émulation des modes mémoire (port 0x60)
L'écriture d'un mode mémoire tranZPUter sur le port d'E/S 0x60 redirige les pointeurs de banc. Les modes sont TZMM_ORIG, TZMM_BOOT, TZMM_TZFS, TZMM_TZFS2, TZMM_TZFS3, TZMM_TZFS4, TZMM_CPM, TZMM_CPM2 et TZMM_COMPAT. La disposition CP/M CPM2 utilise une pagination du bloc 0 à granularité octet — 0x0000–0x003F correspondent au bloc de vecteurs et 0x0040–0x01FF au début de la TPA.
Processeur de service K64F virtuel (port 0x68)
Le Z80 demande des services en exécutant OUT (0x68), ce qui met en file un message MSG_TZFS_SVCREQ vers le Core 0 ; le Core 0 exécute TZFS_processServiceRequest. Les services du système de fichiers sont READDIR / NEXTDIR (blocs de répertoire de 16 entrées mis en cache), READFILE / NEXTREADFILE, LOADFILE (compatible en-tête MZF et objet-banc), CHANGEDIR et CLOSE. Les services CP/M sont LOADBDOS (rechargement CCP + BDOS au démarrage à chaud), ADDSDDRIVE, READSDDRIVE et WRITESDDRIVE, ainsi que des services de fréquence CPU. Comme le picoZ80 n'a pas d'accès SD direct, les secteurs CP/M de 512 octets sont lus et écrits via l'ESP32 (ESP_readSector / ESP_writeSector) sur des fichiers d'image complète ; les chemins d'image par lecteur proviennent des entrées param[].file du JSON d'interface, avec le modèle de repli CPM/SDC16M/RAW/CPMDSK<nn>.RAW. La ROM TZFS est roms/tzfs.bin sur la carte SD, assemblée depuis TZFS/asm/tzfs.asm (son BIOS CP/M depuis TZFS/asm/cbios.asm / cpm22.asm). Elle est livrée dans config_MZ-700_MZ-700.json avec "enable": 0 et est activée dans le JSON ou depuis la page Configuration de l'interface web GUI.

OpenZ80 — Persona Expérimentateur (INCLUDE_OPEN_DRIVERS)
La cible OpenZ80 (TARGET_MODEL_OPEN → INCLUDE_OPEN_DRIVERS) compile un persona Z80 « vanilla » délibérément nu, destiné aux expérimentateurs qui intègrent le picoZ80 dans une carte de leur propre conception ou dans une machine sans pilote dédié. En mode PHYSICAL, l'ensemble de l'espace mémoire et d'E/S 64K est transmis à la carte réelle (les cartes d'interface superposent leurs ports d'E/S) ; en mode VIRTUAL, il présente une RAM 64K plate dans laquelle les images ROM au niveau du pilote sont chargées séquentiellement à partir de 0x0000. Il ne comporte aucun matériel machine et réutilise tels quels les modules de cartes d'interface Sharp (ils ne conservent aucun état spécifique à un persona), n'exposant que les cartes indépendantes de la machine ci-dessous — chacune prenant en charge la relocalisation du port d'E/S de base.
Driver Type String Description
Open.c Open Persona Z80 vanilla / expérimentateur — aucun matériel machine. Passage direct physique ou RAM virtuelle 64K plate avec chargement de ROM au niveau du pilote depuis 0x0000
MZ-1R12.c MZ1R12 Carte fichier-RAM 32K MZ-1R12 (base par défaut 0xF8, relocalisable)
MZ-1R18.c MZ1R18 Carte RAM 64K MZ-1R18 (base par défaut 0xEA, relocalisable)
MZ-1R23.c MZ1R23 Carte ROM Kanji MZ-1R23 / dictionnaire MZ-1R24 (base par défaut 0xB8, relocalisable)
MZ-1R37.c MZ1R37 EMM 640K MZ-1R37 (base par défaut 0xAC, relocalisable)
PIO-3034.c PIO3034 EMM parallèle / E/S parallèle PIO-3034 (base par défaut 0x00, relocalisable)
MZ8BIO3.c / MZ1E24.c MZ8BIO3 / MZ1E24 Cartes série RS-232C doubles (Z80 SIO, base par défaut 0xB0, relocalisable ; canaux A/B sur USB CDC 2/3)
MZ-1E05.c MZ1E05 Interface disquette WD1773 (base par défaut 0xD8, relocalisable ; nécessite une ROM d’amorçage FDC)
Celestite.c Celestite Carte LAN ESP32 Celestite (base par défaut 0x60, relocalisable)
Compilez avec build_tzpuPico.sh open. Pour adapter le picoZ80 à une nouvelle machine, prenez comme base l'un des pilotes de persona situés sous src/drivers/{Sharp,Amstrad,Tatung,Other}/ et associez-le à la source ROM de moniteur / IPL / BIOS CP/M / amorçage disquette correspondante dans les répertoires asm/ des projets RFS et TZFS (p. ex. RFS/asm/sa1510.asm, RFS/asm/cbios.asm, TZFS/asm/mz2000_ipl.asm, RFS/asm/mz80afi.asm). Consultez le Guide du Développeur pour la procédure pas à pas.

Unité CMT Virtuelle (Cassette)

Le CMT virtuel est un lecteur de cassette virtuel au niveau de la forme d'onde pour la série Sharp MZ. Il émule la forme d'onde de la cassette plutôt que d'intercepter le loader, de sorte que du côté du Sharp il est indiscernable d'un lecteur réel : le LOAD du moniteur, le BASIC, les loaders personnalisés et turbo, ainsi que le SAVE fonctionnent tous sans modification. Le chargement depuis la carte SD reste la voie rapide ; le CMT virtuel est la voie authentique — prenez un .mzf et chargez-le « comme une vraie cassette », ou enregistrez un programme en cours d'exécution vers un nouveau .mzf.
Le pilote est un moteur unique — src/drivers/Sharp/CMT.c et src/include/drivers/Sharp/CMT.h (CMT_Init / CMT_Reset / CMT_PollCB / CMT_TaskProcessor) — enregistré dans le interfaceFuncMap[] de chaque machine sous la forme {"CMT", false, CMT_Init, CMT_Reset, CMT_PollCB, CMT_TaskProcessor} et proposé comme interface sélectionnable partout où la machine dispose d'un port cassette. Tout le timing s'appuie sur l'horloge murale (time_us_64() — le même domaine de timing one-shot utilisé par le CMT de RFS), jamais sur les T-states Z80 émulés. La conception complète est documentée dans docs/VIRTUAL_CMT_DESIGN.md du dépôt picoZ80.

Deux Familles Matérielles, un Seul Moteur

Le matériel cassette du MZ se répartit en deux familles, toutes deux pilotées par le même moteur de codec. La famille est déduite du persona actif.
Famille Machines Bus / Ports Baud Demi-onde (0 / 1)
SIMPLE MZ-80K / MZ-80A / MZ-700 / MZ-800 8255 moteur on/off, bande linéaire ; 8255 mappé en mémoire à E000–E003 (le MZ-800 utilise à la place les ports E/S D0–D3) 1200 baud 0 ≈ 504 µs, 1 ≈ 958 µs (le MZ-800 ajuste à ≈ 379 / 964 µs)
CONTROLLED MZ-80B / MZ-2000 / MZ-2200 / MZ-2500 Transport commandé par ordinateur (PLAY / STOP / FF / REW / EJECT + recherche de programme APSS) ; se branche sur ioPtr[] en E0–E3 2000 baud ≈ 333 / 667 µs (variante ≈ 1800 baud)
Codec partagé. Les deux familles partagent le même cadrage d'octet, de bloc et de fichier. Un octet est un long bit de start suivi de 8 bits de données, MSB en premier. Un bloc est un leader, une marque de bande (tape-mark), les octets de données, et une somme de contrôle 16 bits (un décompte des bits à 1). Un fichier est un enregistrement d'en-tête (info) suivi d'un enregistrement de données, chacun écrit deux fois, de sorte qu'une seule lecture physique dispose d'une seconde copie de repli.

La Bande Virtuelle — une File Ordonnée

La bande virtuelle est une file ordonnée de jusqu'à 16 fichiers MZF configurée via l'interface graphique, modifiable en direct depuis l'interface web (ajout / suppression / réordonnancement). À la fin de chaque programme, la file avance automatiquement — comportement de bande enchaînée — de sorte que le prochain LOAD trouve le programme suivant. Sur la famille controlled, FF / REW sautent des programmes entiers et APSS cherche la limite du programme suivant ou précédent et s'arrête automatiquement. La file est fournie au pilote via le tableau param de l'interface, sous la forme d'une liste ordonnée d'entrées { "enable", "file" }. Un nouveau panneau de l'interface web — esp32/webserver/js/cmt.js — gère la file.

Réel ↔ Virtuel — Passthrough, Superposition, Suppression

La bascule Réel ↔ Virtuel en direct n'est plus réservée au CMT — elle couvre désormais les interfaces CMT, Disquette (FDD) et Quick Disk (QD). Dans l'interface web, la bascule de deck est un élément de menu : CMT dans le menu CMT ; Floppy et QD comme éléments de menu disponibles sur toutes les pages, chacun affiché uniquement lorsque l'interface correspondante est activée. Sa sélection bascule un drapeau de pilote en direct via la file de commandes inverses ESP32 → RP2350, sans redémarrage. Pour un CMT, les deux modes diffèrent par la manière dont le pilote traite le lecteur physiquement connecté :
  • VIRTUAL — le pilote superpose les bits de la cassette virtuelle sur le côté lecture et supprime les écritures moteur et transport vers le lecteur physique. Le Sharp charge depuis la bande virtuelle et le lecteur réel reste inactif.
  • REAL — passthrough pur. Le CMT espionne en lecture seule et le moteur entraîne le lecteur réel ; la cassette physique se comporte exactement comme si le picoZ80 n'était pas présent.
Disquette et QD suivent le même contrat VIRTUAL/REAL : Virtual = le firmware émule le lecteur à partir d'images stockées sur SD (une bande virtuelle pour le CMT) ; Real = passthrough pur vers le lecteur physiquement connecté, le firmware espionnant en lecture seule. Une interface configurée en physique démarre désormais en mode Real, le menu et la bascule restant disponibles pour passer en Virtual en direct (sans redémarrage). Disquette et QD ont également gagné chacun un bouton EJECT (menu Floppy / QD de l'interface web) : un chemin de montage vide démonte le lecteur, de sorte que le FDC signale l'absence de disque. Les modes de deck sont rapportés via INF — le CMT rapportait déjà son mode, rejoint maintenant par les nouveaux champs fddMode et qdMode.
Implémentation : une tâche DRIVE_MODE_CHANGE avec les commandes inverses FDDM / QDDM via la file de commandes inverses ESP32 → RP2350 — le même mécanisme que la bascule CMT — installe ou désinstalle les hooks de bus propres à chaque pilote à chaque changement de mode. En mode Real, le passthrough du SIO QD réel est cadencé : un plancher de récupération de 10 µs entre accès sur les ports F4–F7, car le Z80 SIO a besoin de plusieurs cycles d'horloge de récupération entre deux accès.
Comme la bascule est en direct, vous pouvez charger depuis une bande virtuelle puis faire un SAVE vers un lecteur physique, ou charger depuis un lecteur physique et enregistrer vers une bande virtuelle, sans redémarrer.

Enregistrement (SAVE) — le Décodeur de Forme d'Onde

Lorsque le moteur tourne en mode enregistrement, le pilote échantillonne la ligne d'écriture des données et la décode à nouveau en un fichier. Chaque intervalle de demi-onde est classé court ou long, les paires court/long deviennent des bits, les bits deviennent des octets, et la somme de contrôle 16 bits est validée. À partir des enregistrements récupérés, un MZF est synthétisé et sauvegardé sur la carte SD sous la forme :
CMT/taperecord_<name>_<YYYYMMDD_HHMMSS>.mzf
où <name> est le nom de fichier de l'en-tête Sharp transcodé du display code Sharp vers l'ASCII puis assaini pour FAT (les caractères illégaux deviennent _). Lorsque l'horloge de l'ESP32 n'a pas été réglée, l'horodatage se replie sur un numéro de séquence (taperecord_<name>_0001.mzf) pour éviter la collision de date « 1980 » de FAT. Le vrai nom Sharp est stocké tel quel à l'intérieur de l'en-tête MZF, de sorte que le programme conserve son nom d'origine lorsqu'il est rechargé.

Moteur de Lecture et Internes du Pilote

La lecture utilise un curseur de fronts en flux (streaming edge cursor) qui calcule à la volée la prochaine transition des données lues par rapport à l'horloge murale. Il est en O(1) amorti et ne conserve aucun énorme tampon de fronts pré-développé, de sorte qu'une image de bande entière ne coûte presque aucune RAM quelle que soit sa longueur. Les deux familles se branchent sur le bus différemment :
  • Famille simple — le pilote re-type le bloc 8255 de la cassette (512 octets, E000–E1FF = bloc 112) en MEMBANK_TYPE_RAM et installe un handler de bloc entier. Les registres non-cassette de ce bloc — le clavier (E000 / E001), le 8253 (E004–E007), le joystick (E008) et leurs miroirs — passent directement via Z80CPU_readPhysicalMem / Z80CPU_writePhysicalMem, et seuls les bits cassette du Port C sont superposés en lecture et espionnés en écriture. Le chemin critique PHYSICAL_HW optimisé reste inchangé.
  • Famille controlled — le pilote se branche sur ioPtr[] aux ports E0–E3 et y pilote le transport commandé par ordinateur.
Les lectures et écritures SD sont confiées au Core 0 via les files inter-cœurs requestQueue / responseQueue, de sorte que la boucle chaude du Core 1 ne se bloque jamais sur le stockage. Comme indiqué ci-dessus, la file de bande arrive via le tableau param de l'interface et la famille est déduite du persona.

Amorçage Disquette MZ-800 depuis TZFS

TZFS peut désormais amorcer les disquettes MZ-80A, MZ-700 et MZ-800 — y compris le CP/M du MZ-800 — directement depuis le moniteur, et revenir à TZFS via le commutateur RESET matériel après qu'un OS amorcé depuis disquette a pris le relais. Cela couvre des changements à la fois dans la ROM TZFS et le firmware picoZ80. La version de TZFS est désormais v1.8.3.

Loader d'Amorçage TZFS — GETBOOTDSK et Maîtrise du 8253

GETBOOTDSK accepte désormais tout identifiant de machine Sharp (01 / 02 / 03) en plus de la signature "IPLPRO". Il pré-pagine la DRAM du bloc 7 dans 0000–0FFF pour les systèmes d'exploitation qui se chargent sous 1000H (par exemple le CP/M du MZ-800), et remet au programme chargé BC = 0200H — exactement comme le fait l'IPL 9Z-504M natif — afin qu'un loader de second étage puisse piloter sa propre lecture de répertoire.
Avant d'exécuter JP (HL), TZFS maîtrise l'horloge 8253 : il reprogramme le compteur 2 à environ 1 Hz et masque le bit 2 du Port C du 8255, reproduisant le SORES de l'IPL. Cela empêche un compteur en roue libre d'inonder le Z80 d'interruptions RST 38H et d'avorter un loader qui réactive les interruptions trop tôt — la panne qui produisait « P-CP/M80 → No system file ».

Handler de Fetch A10-Toggle du MZ-1E05

Le pilote MZ-1E05 du picoZ80 ajoute MZ1E05_IO_A10Toggle, un handler de fetch d'accélération matérielle du FDC qui effectue un OU logique entre DRQ et A10 aux adresses 0xF3FE / 0xF7FE. Cela permet à la routine DSKREAD de TZFS de streamer un secteur d'amorçage sur le MZ-800 exactement comme elle le fait déjà sur le MZ-80A.

Modes Mémoire TZMM_DSKLOAD / TZMM_DSKRUN

Deux modes mémoire TZFS du picoZ80, TZMM_DSKLOAD et TZMM_DSKRUN, chargent puis exécutent un OS amorcé depuis disquette dans la DRAM native du bloc 0 exactement comme le fait l'IPL propre à la machine, de sorte que l'OS amorcé voie la carte mémoire qu'il attend plutôt que la disposition en banques de TZFS.
Un mode compagnon, TZMM_QDLOAD (une carte TZFS4 avec la RAM basse en bloc 0), sert le chemin d'amorçage Quick Disk afin qu'un programme chargé depuis le QD atterrisse là où le passage de relais à l'exécution le lancera ensuite.

Suivi de la Fenêtre cgWindow des Polices PCG (MZ-800)

Le pilote MZ800 du picoZ80 suit désormais la cgWindow afin qu'un OS en mode MZ-800 qui charge une police PCG (via IN 0xE0) atteigne la vraie CG-ROM / CG-RAM pendant qu'il tourne sous TZFS. Cela corrige le texte brouillé sous le BASIC disquette, tandis que les jeux natifs tels que Flappy — qui ne touchent pas la fenêtre PCG — ne sont pas affectés.

Restauration Reset-vers-TZFS

Lorsque le commutateur RESET matériel est actionné après un OS amorcé depuis disquette, le firmware recharge le moniteur du bloc 0 intact et l'UROM TZFS — que l'OS avait écrasés pendant son exécution en TZMM_DSKRUN, puisque la ROM et la RAM du bloc 0 pointent vers la même PSRAM — puis amorce TZFS à froid. Le commutateur reset ramène donc à TZFS au lieu de retomber sur le moniteur 1Z-013A nu.

Corrections de Bogues Associées

  • Décalage de banque PSRAM de Flappy — sous TZFS, 0x0000–0x0FFF est désormais mappé depuis la banque 7 afin qu'un programme natif qui LDIR du code dans la RAM basse puis change de mode vidéo voie la même mémoire sur les deux chemins (une correction générale, non spécifique à Flappy).
  • Récupération du GDG au reset matériel — initVideoText est réexécuté une fois que /RESET est relâché, de sorte que le GDG rescanne et que la synchronisation HBLK du moniteur s'achève.
  • Délai de stabilisation au reset physique — un délai de 10 ms laisse le GDG / 8255 embarqués achever leur reset matériel avant que le firmware ne réinitialise la vidéo.

Prise en charge de l'Amstrad PCW

Le modèle AmstradZ80 apporte les machines de traitement de texte Amstrad PCW — PCW-8256, PCW-8512 et PCW-9512 — aux hôtes pris en charge. Le PCW est, par son architecture, très différent des machines Sharp : son gate array (ASIC) arbitre chaque accès DRAM, en exécutant une rotation fixe à 1MHz de créneaux de lecture vidéo, de rafraîchissement et de CPU, et en cadençant le Z80 avec une onde /WAIT en roue libre. Cela en fait l'hôte le plus sensible au timing que le picoZ80 pilote. Le pilote associe donc une précision de timing bus à la nanoseconde près — mesurée face à un vrai Z80 avec un analyseur logique, voir Fidélité du Timing Bus — à une architecture de shadow en PSRAM qui retire entièrement la DRAM de la machine du chemin critique du CPU.

Variantes de Machine

Un seul cœur de pilote — src/drivers/Amstrad/PCW9512.c — dessert toute la famille, avec un aiguillage par variante à l'initialisation. Le persona PCW9512 modélise la machine à marguerite avec des lecteurs 3,5" 720K ; le persona PCW8256 couvre à la fois le PCW-8256 et le PCW-8512 (imprimante matricielle, lecteurs 3" CF2). Les deux machines ne diffèrent que par l'imprimante : PCW8256.c fournit le modèle de statut de l'imprimante matricielle, dont les bits de statut au port FDh n'ont pas les mêmes significations que ceux de la marguerite au même port. La géométrie des lecteurs provient de l'image disque montée, de sorte qu'aucun changement de pilote n'est nécessaire entre des images CF2 180K et des images 720K.

Modes de Pilote PHYSICAL et VIRTUAL

Le type de premier niveau du pilote sélectionne l'un des deux modes de fonctionnement. Les deux suivent exactement le même chemin de configuration de la machine physique — la différence réside dans ce qui protège le CPU de la DRAM de la machine :
  • PHYSICAL — chaque accès est un véritable cycle bus, exactement tel qu'un Z80 authentique l'émettrait. C'est le mode authentique, et la référence de précision : le bus est aligné en timing sur le vrai silicium. Le timing interne marginal du gate array peut encore provoquer d'occasionnels pixels parasites, car aucun shadow ne vient corriger un cycle DRAM mal verrouillé.
  • VIRTUAL — la même machine physique, plus le shadow DRAM décrit ci-dessous. Le CPU s'exécute depuis le shadow : stable, sensiblement plus rapide (de façon visible sous LocoScript), immunisé contre les particularités de la DRAM de la machine, et capable de porter de la mémoire d'extension — tandis que l'écran et le matériel se comportent de manière identique.
Le nom d'interface hérité MIRROR des configurations antérieures est toujours reconnu et active simplement le shadow — le mode VIRTUAL du pilote est le mode miroir.

Le Shadow DRAM

En mode VIRTUAL, le pilote conserve une copie shadow octet pour octet de la DRAM de la machine dans la PSRAM du RP2350. Le hook d'écriture est installé dès la mise sous tension, de sorte que chaque écriture du CPU atterrit dans le shadow aussi bien que sur le bus, et le shadow ne peut jamais se désynchroniser. Les hooks de lecture et de fetch s'arment cinq secondes après le bootstrap, une fois le chargement du système d'exploitation engagé. À partir de là :
  • Les accès servis se font sans bus. Un fetch ou une lecture d'un octet présent dans le shadow n'effectue aucun cycle bus — l'exécution se déroule depuis la PSRAM. Chaque accès servi est cadencé à ~250 cycles RP2350 (~758ns, soit environ 3 T-states à 4MHz) afin que les boucles de délai calibrées par logiciel conservent un timing authentique.
  • Une bitmap de validité par octet garantit l'exactitude : un octet que le hook d'écriture n'a jamais vu est toujours lu depuis le bus réel.
  • Une garde clavier de 16 octets en haut du bloc 3 est toujours lue sur le bus — le contrôleur clavier 8041 du PCW écrit la matrice de touches dans la DRAM à l'insu du CPU, cette fenêtre ne doit donc jamais être servie depuis le shadow.
  • Un keepalive M1 émet un véritable cycle de fetch d'opcode, aligné en timing, tous les 256 fetches servis (~10kHz). Le gate array dérive sa planification du rafraîchissement DRAM et sa génération d'interruptions à 300Hz du trafic bus observé ; ce mécanisme maintient donc la machine en vie tout en supprimant ~99,6 % des strobes bus.
  • Un filtre d'écriture vidéo uniquement n'envoie les cycles d'écriture sur le bus réel que là où l'ASIC lit réellement — la roller RAM et les lignes de balayage que ses entrées désignent — le tout suivi dans une carte à granularité de 512 octets sur les 128K inférieurs. La carte est à sûreté ouverte (fail open) : à la moindre incertitude, l'écriture passe sur le bus.
L'effet net est que la DRAM de la machine n'est lue que par l'ASIC (pour la vidéo), que le CPU ne consomme jamais un octet corrompu, et que le bus ne transporte qu'une petite fraction de son trafic normal — ce qui constitue en soi la plus grande réduction d'exposition aux cycles marginaux du gate array.

Réparation Vidéo

Puisque le shadow détient la vraie valeur de chaque octet vidéo, la corruption résiduelle de la DRAM n'a plus besoin d'être parfaitement évitée — elle est réparée. Un balayage vérifier-puis-écrire en arrière-plan parcourt les régions vidéo, relit chaque octet depuis la DRAM réelle et ne réécrit depuis le shadow que les dommages avérés. Le balayage est piloté par les fetches, en courtes mini-rafales, et couvre l'écran complet en environ 0,6 seconde, de sorte que toute corruption n'est qu'un transitoire inférieur à la seconde. Comme le balayage n'écrit qu'en cas de différence, son compteur d'octets réparés constitue un véritable indicateur de corruption, rapporté sur la ligne de télémétrie périodique PCW HEALTH.

Disquette — uPD765 Virtuel, Garde FDC et Permutation de Deck

L'interface FDC sélectionne un NEC uPD765 virtuel servant des images .dsk au format CPC depuis la carte SD. Avec un FDC virtuel, la machine démarre sans disque : le handler est installé avant l'exécution du bootstrap et signale « not ready » dans le registre de statut principal pendant le chargement de l'image, de sorte que la ROM de démarrage se contente d'attendre — exactement comme elle le ferait pour un lecteur en cours de mise en rotation. Un PCW dont le lecteur de disquette est mort ou absent démarre et fonctionne entièrement à partir d'images.
Avec un FDC physique, une garde FDC protège la boucle de transfert : le vrai uPD765 subit un dépassement (overrun) si un octet n'est pas servi dans les ~26µs, de sorte que tout accès à un port FDC suspend le service lecture/fetch du shadow pendant la durée du transfert, avec réarmement après une seconde de silence du FDC. Les accès au FDC virtuel utilisent à la place un délai de retenue de la réparation distinct de 100ms, de sorte que la réparation ne perturbe jamais un transfert de secteur mais reprend presque immédiatement après.
Le deck disquette peut être permuté entre Virtuel et Réel à l'exécution depuis le menu Floppy de l'interface web. Le passage au lecteur réel conditionne d'abord le contrôleur physique — un SPECIFY et un RECALIBRATE avec purge des interruptions obsolètes, puisqu'un contrôleur jamais sollicité n'a aucune idée de la position de ses têtes — puis rejoue le choix de routage interruption-FDC-vers-NMI effectué par le BIOS. Le retour en arrière réinitialise le contrôleur virtuel tout en conservant ses images chargées. Cela permet de lire des disques réels, ou de les sauver en images, en cours de session sur une machine qui a démarré sans disque.

Mémoire d'Extension

L'interface MEMORY (nommée EXPANSION dans les configurations plus anciennes — les deux noms sont acceptés) ajoute de la mémoire paginée servie depuis la PSRAM au-dessus de la RAM propre de la machine. Le shadow fournit 96 banques de 16K — 1,5MB de mémoire adressable au total — de sorte qu'un PCW-8256 de 256K gagne jusqu'à 1,25MB et qu'un PCW-8512/9512 de 512K gagne 1MB. CP/M dimensionne la mémoire supplémentaire en un lecteur RAM M: beaucoup plus grand. Les pages d'extension n'existent qu'en PSRAM et ne sont jamais placées sur le bus : la DRAM réelle de la machine se trouve simplement répliquée (aliasing) au-dessus de sa taille installée, de sorte que l'extension se comporte comme une RAM parfaite et rapide. La PSRAM est organisée comme suit (pages de 64KB) :
Pages de 64KB Contenu
0–7 Ensemble de travail du cœur du firmware
8–31 Shadow DRAM + mémoire d’extension (1,5MB)
32–34 Bitmap de validité du shadow
35–47 Tampon d’image du lecteur de disque 0
48–60 Tampon d’image du lecteur de disque 1
61 Zone de travail de l’imagerie disque
62–63 Réserve

Le Modèle de Pagination — Mesuré, Non Supposé

Le shadow doit suivre exactement le décodage de banques du gate array ; le modèle de pagination a donc été mesuré sur du matériel réel avec le diagnostic PAGEPROBE du pilote (un balayage unique qui sème des signatures à travers chaque valeur de registre de banque et relit quelle page physique chacune mappe — la machine est sacrifiée à la mesure et nécessite ensuite un cycle d'alimentation). Les gate arrays du PCW-8256 et du PCW-9512 ont tous deux été sondés et décodent de manière identique :
  • Pagination PCW étendue (bit 7 de la valeur de banque à 1) : page physique = (value & 0x7F) mod nBanks — l'aliasing modulo est réel et vérifié.
  • Pagination de style CPC (bit 7 à 0) : page de lecture (value >> 4) & 7 et page d'écriture value & 7 distinctes.
  • Les bits de verrouillage du port F4 forcent les lectures d'un bloc verrouillé vers sa banque d'écriture. L'ordre des bits n'est pas séquentiel et a été établi empiriquement : bloc 0 = 0x40, bloc 1 = 0x10, bloc 2 = 0x20, bloc 3 = 0x80.

Travaux de Timing et Télémétrie

La build Amstrad embarque sa propre copie du moteur de bus PIO — src/z80_amstrad.pio, identique octet pour octet à z80.pio lors de sa création — de sorte que les travaux de timing spécifiques au PCW ne puissent jamais perturber le moteur éprouvé sur lequel tournent les machines Sharp, Tatung et Base. Par-dessus, le pilote utilise des flux d'instructions alignés en timing qui placent chaque front de ligne de contrôle là où un vrai Z80 le place : la comparaison à l'analyseur logique a montré que les flux d'origine affirmaient les lignes de contrôle 10–25ns plus tôt que le vrai silicium, et que le gate array décodait visiblement mal ces fronts précoces. Autres mesures :
  • Cycles d'E/S et d'écriture synchronisés sur la grille — chaque requête d'E/S et d'écriture est calée sur la phase à laquelle le gate array la fusionne dans sa rotation DRAM à 1MHz, au lieu de forcer l'ASIC à insérer un créneau de service hors grille.
  • Acquittement d'interruption IM1 — la build Amstrad émet le véritable cycle bus d'acquittement M1+IORQ à chaque interruption acceptée, comme le fait le silicium authentique. (Un drapeau de compilation générique Z80_IM1_INTA existe pour les autres machines, désactivé par défaut.)
  • Profileur de grille WAIT sans analyseur logique — à chaque démarrage, le pilote échantillonne les broches /WAIT et CLK à une résolution de ~13ns et imprime une ligne PCW GRID: (période WAIT, largeur d'impulsion, période CLK, phase) comparée à la carte de référence, de sorte que le timing du gate array d'une machine puisse être vérifié sans brancher d'analyseur logique.
  • Télémétrie de santé — une ligne périodique PCW HEALTH: rapporte le nombre de trous de fetch, la divergence du shadow, le routage des écritures (bus vs shadow), les octets réparés et les statistiques de balayage.
Les personas PCW fournis font tourner le RP2350 à 330MHz avec le cœur à 1,20V.

Circuits Périphériques Virtuels sur la Build Amstrad

Restriction : le framework des Circuits Périphériques Virtuels est en pratique indisponible sur la build Amstrad — ses limites d'instances sont réduites à un seul périphérique afin de libérer la SRAM interne dont le shadow DRAM a besoin. Les autres builds ne sont pas concernées.

Fidélité du Timing Bus — picoZ80 face à un Vrai Z80

À quel point le bus du picoZ80 est-il proche du silicium authentique ? Sur l'Amstrad PCW — l'hôte dont le gate array arbitre chaque accès mémoire et constitue donc le juge le plus sévère — la question a trouvé sa réponse par la mesure : le timing des cycles bus du picoZ80 a été capturé et comparé à celui d'un authentique Zilog Z80 dans le même PCW-8256, sur la même carte et avec le même moniteur.

Méthode

  • Capture avec un analyseur logique Kingst LA5016 à 500MHz (résolution de 2ns) sur le bus en fonctionnement : CLK, /WAIT, /MREQ, /RD, /WR, /IORQ, /M1, /RFSH, /INT, A15, A0, D7, D0.
  • Chaque valeur est la médiane sur plusieurs milliers de cycles dans une fenêtre en régime établi.
  • Les captures de référence du vrai Z80 et celles du picoZ80 ont été prises dans des conditions identiques (CP/M à l'invite A> / copie PIP). Le picoZ80 a été mesuré à deux fréquences de cœur, 340MHz et 350MHz, pour montrer que le résultat est indépendant de l'horloge.
  • Les positions des fronts sont données par rapport au front CLK le plus proche : les strobes mémoire se réfèrent au front descendant de CLK (milieu de T-state) ; /M1 et /IORQ se réfèrent au front montant de CLK (début de T1), conformément à la manière dont la fiche technique du Z80 les spécifie.

Comparaison des Timings

Paramètre Z80 réel picoZ80 @340MHz picoZ80 @350MHz Δ vs réel
Période CLK 250 ns 250 ns 250 ns 0 ns
Impulsion haute /WAIT 184 ns 182 ns 182 ns −2 ns
Descente /MREQ (après CLK↓) +42 ns +48 ns +46 ns +4…6 ns
Descente /RD (après CLK↓) +52 ns +48 ns +48 ns −4 ns
Descente /WR (après CLK↓) +36 ns +46 ns +46 ns +10 ns
Descente /M1 (après CLK↑) +54 ns +52 ns +50 ns −2…4 ns
Descente /IORQ (après CLK↑) +40 ns +28 ns +28 ns −12 ns
Largeur à l’état bas /MREQ (cycle mémoire) 500 ns 498 ns 496 ns −2…4 ns
Adresse valide avant /MREQ 115 ns ~185 ns ~180 ns +65 ns (plus tôt)
(Colonne Δ : écart du picoZ80 par rapport au vrai Z80 ; le signe indique plus tard (+) ou plus tôt (−).)

Ce que Montrent les Chiffres

  • Les trois strobes DRAM correspondent au vrai silicium à ~10ns près, la plupart à 5ns près : /MREQ, /RD et /M1 sont tous à quelques nanosecondes du Z80 authentique, et /M1 — l'identifiant de fetch d'opcode auquel l'ASIC du PCW est le plus sensible — tombe sur la cible à +50…52ns contre +54ns pour le vrai circuit.
  • Le résultat est indépendant de l'horloge : 340MHz et 350MHz produisent les mêmes positions de fronts à la résolution de mesure près, confirmant que le timing est fixé par la logique de cycle et non par l'horloge cœur du RP2350.
  • La largeur d'impulsion /MREQ correspond à ~500ns (un cycle mémoire complet), de sorte que le gate array voit le strobe affirmé pendant la même durée qu'avec un vrai Z80.
  • Le temps d'établissement de l'adresse est plus long que celui d'un vrai Z80, et non plus court (~180ns contre 115ns). Le picoZ80 pilote l'adresse tôt et la maintient stable pendant tout le cycle, de sorte que l'ASIC verrouille toujours une adresse établie — un écart sûr, pas un risque.
  • /IORQ présente le plus grand écart, avec 12ns d'avance. Les cycles d'E/S sont délibérément laissés en roue libre plutôt qu'alignés sur les créneaux, car le gate array se rephase autour des E/S par conception ; cela n'affecte pas l'intégrité de la DRAM.

Résumé

Sur chaque signal qui pilote la DRAM du PCW, le picoZ80 reproduit un vrai Zilog Z80 à quelques nanosecondes près (de l'unité à ~10 ns), indépendamment de l'horloge cœur. Les seuls écarts significatifs sont le strobe d'E/S (12ns d'avance, sur des cycles que l'ASIC rephase de toute façon) et un temps d'établissement d'adresse plus long, donc plus sûr. Pour l'accès mémoire — le chemin qui compte pour l'exactitude — le bus du picoZ80 est, à la résolution d'un analyseur à 500MHz, identique au vrai silicium. Ces travaux ont été menés sur l'Amstrad PCW parce que son gate array est l'observateur le plus exigeant du timing bus parmi les hôtes pris en charge ; les flux de cycles alignés en timing sont réservés à la build Amstrad, tandis que toutes les autres machines utilisent le moteur d'origine éprouvé de longue date.

Framework de Périphériques Virtuels

Le type de bloc FUNC permet une émulation E/S arbitraire en appelant des fonctions handler C à chaque accès bus. N'importe quel bloc de 512 octets de mémoire ou plage de ports E/S peut être soutenu par une fonction.

Signatures des Fonctions Handler

Les handlers FUNC mémoire sont stockés dans la table memioPtr en PSRAM. Les handlers FUNC E/S sont stockés dans la table ioPtr. Les signatures de fonctions sont :
/* 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);
Les fonctions handler sont appelées directement depuis la boucle chaude du Core 1. Elles doivent se terminer avant que les wait states du cycle bus courant n'expirent — gardez les handlers courts et évitez toute opération bloquante (E/S fichier, UART, etc.). Si un handler doit déclencher une opération plus longue (par ex. charger un secteur de disque), il doit poster un message au Core 0 via la file inter-cœurs et retourner immédiatement avec un octet de statut, reportant l'E/S réelle au Core 0.

Écrire un Nouveau Pilote

Pour ajouter le support d'un nouveau périphérique ou d'une nouvelle machine hôte :
  1. Créez un nouveau fichier .c / .h dans le répertoire src/drivers/.
  2. Implémentez les fonctions handler de lecture et d'écriture correspondant aux signatures ci-dessus.
  3. Enregistrez les pointeurs de fonctions handler dans les tables memioPtr ou ioPtr pendant l'initialisation du pilote.
  4. Ajoutez le pilote à la cible de compilation CMakeLists.txt.
  5. Ajoutez une entrée de chaîne de type pour que l'analyseur de configuration JSON puisse instancier le pilote par nom.
  6. Documentez les clés param du pilote dans le fichier d'en-tête de votre pilote.
La fonction d'initialisation du pilote est appelée une fois au démarrage, après l'analyse de config.json. Le pilote reçoit un pointeur vers son bloc de configuration d'interface et doit configurer tout état interne et enregistrer ses handlers à ce moment.

Circuits Périphériques Virtuels

Un circuit périphérique virtuel est un circuit de support complet — un Z80 DMA, un Z80 CTC, un 8255 et ainsi de suite — ajouté à une machine en le déclarant dans config.json. Le firmware modélise le circuit intégré réel, registre par registre, et le circuit apparaît dans l'espace d'adressage du Z80 exactement comme le ferait un composant physique soudé sur une carte. Les circuits sont ensuite reliés entre eux par une netlist (liste d'interconnexions), de sorte que la broche de sortie d'un circuit peut piloter la broche d'entrée d'un autre, tout comme une piste sur un circuit imprimé.
Il s'agit d'un mécanisme différent du Framework de Périphériques Virtuels décrit plus haut. Un bloc FUNC est un handler — du code C que le firmware appelle à chaque accès bus, écrit par un développeur et compilé dans le firmware. Un circuit périphérique virtuel est une déclaration — une instance d'un circuit que le firmware modélise déjà, créée à partir de données de configuration sans écrire la moindre ligne de code. Il se distingue également d'une carte d'interface, qui est un composite spécifique à une machine (images ROM, supports, mappage d'adresses) déclaré sous drivers[].if[]. Les trois sont des pairs, non des couches superposées : une carte d'interface peut fort bien contenir le même circuit qu'un périphérique déclare.
L'objectif est de permettre à chacun de construire son propre système Z80 sur un picoZ80 sans écrire de firmware. Une personnalité machine fournit le CPU, la mémoire et la vidéo ; les périphériques fournissent les circuits de support ; la netlist fournit le câblage entre eux.

Le Catalogue des Périphériques

Treize périphériques sont disponibles. La colonne name donne la chaîne exacte à utiliser dans config.json. La colonne Cœur de service importe pour le timing : les périphériques traités sur le Core 0 sont avancés par un timer libre indépendant du Z80, tandis que les périphériques du Core 1 sont pilotés par l'activité du bus.
name Circuit modélisé Broches Taille de décodage Cœur de service
Z80DMA Contrôleur d’accès direct à la mémoire Zilog Z8410 10 1 Core 1
Z80CTC Circuit compteur / temporisateur Zilog Z8430 10 4 Core 0
Z80PIO Contrôleur d’entrées / sorties parallèles Zilog Z8420 23 4 Core 1
PIT8253 Temporisateur programmable à intervalles Intel 8253 / 8254 6 4 Core 0
PPI8255 Interface périphérique programmable Intel 8255 24 4 Core 1
WD1773 Contrôleur de disquettes Western Digital WD1773 5 8 Core 1
MC6850 Port série asynchrone Motorola 68B50 ACIA 4 2 Core 1
I8251 Port série Intel 8251 / 8251A USART 7 2 Core 1
Z80DART Zilog Z80 DART — double canal série asynchrone (partage le modèle de circuit du Z80 SIO) 13 4 Core 1
MSM5832 Horloge temps réel / calendrier OKI MSM5832 1 16 Core 1
Centronics Port imprimante parallèle Centronics — l’interface, pas un circuit 4 2 Core 1
Latch pas un circuit réel — un verrou (latch) / port à usage général 16 1 Core 1
SignalPort pas un circuit réel — un port de test et de pontage 16 1 Core 1
Outre les périphériques déclarés, un pseudo-périphérique permanent nommé cpu existe toujours. Il expose les broches de contrôle du processeur lui-même, afin qu'un périphérique puisse être relié à la machine elle-même.

Déclarer un Périphérique

Les périphériques sont déclarés dans un tableau device à l'intérieur d'un objet de partition z80[], aux côtés des tableaux memory, io et drivers existants. Chaque partition porte ses propres périphériques et sa propre netlist, de sorte que les deux peuvent être équipées de manière très différente. Notez attentivement que name sélectionne quel circuit, tandis que type sélectionne quel espace d'adressage — c'est une source d'erreur fréquente.
Clé Type Requis Défaut Description
name string oui — Type de circuit issu du catalogue ci-dessus. Insensible à la casse. Un nom non reconnu est ignoré avec un message dans le journal.
addr number oui — Adresse de base du décodage.
id string non dev<i>N</i> Étiquette d’instance, utilisée pour désigner ce circuit lors du câblage. Maximum 11 caractères ; les noms plus longs sont tronqués.
type string non "IO" Espace d’adressage — "MEMORY" place le circuit dans l’espace mémoire, toute autre valeur le place dans l’espace d’E/S.
size number non 1 Nombre d’adresses consécutives décodées.
enable number non activé 0 désactive le périphérique. Utilisez le nombre 0, et non false.
busack number non 1 Affirme l’acquittement de bus de l’hôte tant que ce périphérique possède le bus. Z80 DMA uniquement.
maxburst number non 4096 Nombre maximum d’octets déplacés avant que le processeur ne soit autorisé à reprendre la main. Z80 DMA uniquement ; 0 supprime la garde.
Certains périphériques acceptent des clés supplémentaires qui leur sont propres. Les clés machine, drives et file du contrôleur de disquettes et la clé clockhz du 8253 sont décrites avec leurs brochages ci-dessous ; les périphériques série, horloge, imprimante et verrou acceptent les suivantes :
Clé S’applique à Défaut Description
usb MC6850, I8251, Z80DART, Centronics 2 (série), 4 (imprimante) Le canal USB CDC (0–4) qui transporte le flux de données du périphérique. Le DART réclame deux canaux consécutifs : usb est le canal A et usb+1 le canal B.
hours MSM5832 24 heures Le nombre 12 sélectionne le mode 12 heures ; toute autre valeur, ou l’omission de la clé, sélectionne le mode 24 heures.
strobe Centronics auto "control" verrouille l’octet à imprimer sur le front montant du bit 0 du registre de contrôle ; par défaut, il est verrouillé lors de l’écriture du registre de données elle-même.
int Centronics 0 Une valeur non nulle affirme la ligne d’interruption à chaque octet accepté ; la lecture du statut l’efface.
readback Latch sample "latch" fait qu’une lecture renvoie le dernier octet écrit ; par défaut, les huit broches IN sont échantillonnées.
Où vont les flux d'octets. Chaque périphérique série est un flux d'octets sur l'un des cinq canaux USB CDC du port USB du picoZ80 — branchez la carte sur un PC et les données du circuit y apparaissent comme un port série ordinaire. Les registres de vitesse et de format de mot des circuits série sont enregistrés mais non simulés : les données circulent à la vitesse de l'USB quoi que programme l'invité. L'imprimante est en sortie seule et affirme BUSY tant que sa file de 511 octets est pleine, de sorte que le pilote de l'invité réessaie et qu'aucun caractère n'est jamais perdu. Les commandes ser, rtc et prn du shell de débogage injectent des caractères dans ces périphériques, et en relisent, directement — aucun terminal n'a besoin d'être connecté.
Les périphériques sont appliqués après les régions memory et io de la personnalité, de sorte qu'un décodage de périphérique fait autorité — il l'emporte sur toute région générale couvrant la même adresse. Le décodage doit tenir entièrement dans l'espace de 64 Ko, sans quoi le périphérique est ignoré.
"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
            } ]

Le tableau interlink est la netlist. Chaque entrée décrit un fil : une seule broche de destination et une ou plusieurs sources qui la pilotent. Une broche est désignée par <id>.<PIN>, par exemple dma0.RDY ou cpu.INT. Les deux parties sont insensibles à la casse.
Clé Type Requis Description
dst string oui L’unique broche d’entrée que ce fil pilote. Chaque entrée ne peut être pilotée que par un seul fil.
src array oui De un à huit termes source. Un terme est une référence de broche, ou la chaîne littérale "0" ou "1". Un ! en tête inverse ce terme.
op string non Manière dont les sources multiples se combinent — BUF, NOT, AND, OR, NAND, NOR, XOR, XNOR. Par défaut BUF pour une source unique et OR pour deux sources ou plus.
enable number non 0 désactive ce fil.
name string non Une étiquette pour votre propre usage. Le firmware l’ignore.
Important : un net transporte le niveau électrique de la broche, jamais un état abstrait « affirmé ». 1 est le niveau haut et 0 le niveau bas, exactement comme le lirait un voltmètre. Les broches actives à l'état bas telles que /INT et /BUSREQ reposent au niveau haut et sont affirmées en passant au niveau bas. C'est ce qui fait que la netlist se comporte comme un schéma : un NOR de deux sorties de temporisateur actives à l'état haut fait sur le picoZ80 précisément ce qu'il fait sur le papier.

Les Trois Façons de Piloter une Broche

Directement, un à un — omettez op et donnez une source unique. C'est un simple fil de la sortie d'un circuit vers l'entrée d'un autre.
{ "name": "floppy data request drives the DMA",
  "dst":  "dma0.RDY",
  "src":  [ "fdc0.DRQ" ] }
Relié en dur à un niveau fixe — donnez le littéral "0" ou "1" comme unique source. C'est l'équivalent d'attacher une broche à la masse ou au rail d'alimentation, et c'est ce que l'on fait de toute entrée qu'un montage n'utilise pas. Une entrée non câblée n'est pas une erreur, mais elle sera signalée au démarrage et lira son niveau de repos ; la relier explicitement documente donc votre intention.
{ "dst": "dma0.CE",   "src": [ "0" ] }      /* relié au niveau bas  - sélectionné en permanence */
{ "dst": "dma0.WAIT", "src": [ "1" ] }      /* relié au niveau haut - jamais affirmé            */
Au travers d'une condition logique — donnez de deux à huit sources et un op. Les termes sont combinés de gauche à droite, et chaque terme peut être inversé individuellement par un ! en tête. C'est l'équivalent de souder une petite porte logique entre les circuits.
/* Le DMA ne tourne que si aucune sortie de temporisateur n'est haute.        */
{ "name": "timer gates the DMA",
  "dst":  "dma0.RDY",
  "op":   "NOR",
  "src":  [ "ctc0.ZCTO0", "ctc0.ZCTO1" ] }

/* Le bit 1 du port A, inversé, pilote le bit 0 du port B.                    */
{ "dst": "pio0.B0", "src": [ "!pio0.A1" ] }
Les lignes d'interruption constituent un cas particulier qu'il vaut la peine de comprendre. Sur le matériel réel, chaque broche /INT est une sortie à drain ouvert : n'importe quel circuit peut tirer la ligne partagée vers le bas, et celle-ci ne remonte que lorsque tous l'ont relâchée. La netlist reproduit cela par un AND de toutes les sorties d'interruption. Vous devez écrire "op":"AND" explicitement — la valeur par défaut pour plusieurs sources est OR, ce qui inverserait le sens et ne déclencherait une interruption que lorsque tous les circuits l'affirment en même temps.
{ "name": "interrupt chain",
  "dst":  "cpu.INT",
  "op":   "AND",
  "src":  [ "dma0.INT", "ctc0.INT", "pio0.INT" ] }

Priorité des Interruptions — la Chaîne Daisy-Chain

La priorité entre circuits interrupteurs est décidée par la façon dont vous les câblez, et non par un nombre dans la configuration — exactement comme sur un système Z80 réel. Chaque circuit possède une entrée IEI (interrupt enable in) et une sortie IEO (interrupt enable out). Relier l'IEO d'un circuit à l'IEI du suivant place le second en dessous du premier dans la priorité. Le premier circuit de la chaîne a son IEI relié en dur à 1.
Un circuit pilote son IEO au niveau haut uniquement lorsque son propre IEI est haut et qu'il n'est ni en train d'interrompre ni en cours de service. Cette seule règle produit l'ensemble du comportement : un circuit en cours de service maintient tout ce qui se trouve en dessous de lui à l'arrêt jusqu'à ce qu'il ait terminé, et un circuit dont l'IEI est passé au niveau bas sait qu'un périphérique de priorité supérieure a l'attention du processeur et se tient coi.
/* ctc0 est le plus prioritaire, puis dma0, puis pio0.                        */
{ "dst": "ctc0.IEI", "src": [ "1" ] },
{ "dst": "dma0.IEI", "src": [ "ctc0.IEO" ] },
{ "dst": "pio0.IEI", "src": [ "dma0.IEO" ] }
Le mode d'interruption 2 est entièrement supporté. À l'acquittement, le périphérique de plus haute priorité qui est demandeur et n'est pas bloqué fournit son vecteur programmé, se marque en service et efface son drapeau en attente ; un RETI efface à nouveau l'état en service et libère les circuits situés en dessous de lui.
Les cartes physiquement installées ont toujours priorité sur les périphériques déclarés. Un véritable périphérique sur le bus de l'hôte possède ses propres broches IEI et IEO, que le picoZ80 ne peut ni voir ni arbitrer — il ne reste aucune broche de processeur disponible pour cela — aussi le firmware propose-t-il l'acquittement à tout handler de carte existant en premier, et ne le réclame que si aucune carte ne répond. C'est ce qui permet d'ajouter des périphériques à une machine déjà équipée, par exemple, d'une MZ-8BIO3 ou d'une carte série MZ-1E24 sans la perturber.

Brochages des Périphériques

Les tableaux ci-dessous listent chaque broche que chaque périphérique expose à la netlist. La direction est donnée du point de vue du circuit. Le repos est le niveau que lit une entrée lorsque rien ne la pilote. Les broches marquées déclarée existent et peuvent être câblées, mais ne sont pas encore prises en compte par le firmware — elles sont listées afin qu'une configuration écrite aujourd'hui reste correcte lorsqu'elles deviendront actives.
cpu — le pseudo-périphérique processeur
Broche Direction Repos Fonction
INT entrée 1 Demande d’interruption masquable. La seule destination fonctionnelle sur le CPU. La mettre au niveau bas lève une interruption.
BUSACK sortie 1 Acquittement de bus. Déclarée — pas encore pilotée, elle se lit donc en permanence au niveau haut.
BUSRQ, NMI, RESET, WAIT entrée 1 Déclarées — les câbler n’a encore aucun effet.
M1, CLK, HALT sortie — Déclarées — pas encore pilotées.
Z80DMA — Accès Direct à la Mémoire (Z8410)
Broche Direction Repos Fonction
RDY entrée 1 Demande de transfert émanant du périphérique. La seule entrée fonctionnelle. Son sens actif est fixé par le registre WR5 du circuit, non par la configuration. Une transition vers l’état actif démarre un transfert.
BUSREQ sortie 1 Demande de bus, active à l’état bas, drain ouvert. Mise au niveau bas tant que le DMA détient le bus.
INT sortie 1 Demande d’interruption, active à l’état bas, drain ouvert.
IEI entrée 1 Entrée de priorité d’interruption. À relier à l’IEO du circuit précédent, ou à 1 s’il s’agit du premier de la chaîne.
IEO sortie 1 Sortie de priorité d’interruption. À relier à l’IEI du circuit suivant.
BAI, CE, WAIT, M1 entrée 1 Déclarées — le modèle ne les consulte pas.
BAO sortie 1 Déclarée — pas encore pilotée.
Z80CTC — Compteur / Temporisateur (Z8430)
Broche Direction Repos Fonction
CLKTRG0 … CLKTRG3 entrée 0 Entrée d’horloge ou de déclenchement pour les canaux 0 à 3. Le front significatif est choisi par le mot de contrôle du canal.
ZCTO0 … ZCTO2 sortie 0 Zero-count / time-out pour les canaux 0 à 2, émis sous forme d’une brève impulsion. Le canal 3 n’a pas de broche de sortie, exactement comme sur le circuit réel.
INT sortie 1 Demande d’interruption, active à l’état bas, drain ouvert.
IEI entrée 1 Entrée de priorité d’interruption — voir la chaîne daisy-chain.
IEO sortie 1 Sortie de priorité d’interruption.
Z80PIO — Entrées / Sorties Parallèles (Z8420)
Broche Direction Repos Fonction
A0 … A7 bidirectionnelle 1 Bits de données du port A. La direction suit le mode programmé par le Z80 à l’exécution.
B0 … B7 bidirectionnelle 1 Bits de données du port B.
ASTB, BSTB entrée 1 Entrées de strobe de port, actives à l’état bas. C’est le front montant qui est significatif.
ARDY, BRDY sortie 0 Sorties « prêt » du dialogue de port, actives à l’état haut.
INT sortie 1 Demande d’interruption, active à l’état bas, drain ouvert.
IEI entrée 1 Entrée de priorité d’interruption — voir la chaîne daisy-chain.
IEO sortie 1 Sortie de priorité d’interruption.
Les quatre adresses décodées sont, dans l'ordre : données du port A, contrôle du port A, données du port B, contrôle du port B. Le reset sélectionne le mode 1 (entrée) sur les deux ports.
PIT8253 — Temporisateur à Intervalles (8253 / 8254)
Broche Direction Repos Fonction
GATE0 … GATE2 entrée 1 Entrées de validation des compteurs. Les modes 0, 2, 3 et 4 répondent au niveau ; les modes 1 et 5 se déclenchent sur le front montant.
OUT0 … OUT2 sortie 1 Sorties des compteurs.
Les quatre adresses décodées sont les compteurs 0, 1 et 2 suivis du mot de contrôle. Le 8253 n'a pas de broche d'interruption. Une clé de configuration supplémentaire s'applique à ce périphérique : "clockhz" fixe la fréquence d'entrée des compteurs en Hertz, avec pour valeur par défaut 1000000. Les machines Sharp MZ pilotent leur temporisateur à 895000.
PPI8255 — Interface Parallèle (8255)
Broche Direction Repos Fonction
PA0 … PA7 bidirectionnelle 1 Bits de données du port A.
PB0 … PB7 bidirectionnelle 1 Bits de données du port B.
PC0 … PC7 bidirectionnelle 1 Bits de données du port C. Les moitiés haute et basse ont des directions indépendantes.
Les quatre adresses décodées sont les ports A, B et C suivis du mot de contrôle. Les lignes de dialogue des modes 1 et 2 ne sont pas exposées à la netlist.
Limitation connue
Piloter une broche d'entrée quelconque d'un port du 8255 efface actuellement les autres bits d'entrée de ce même port. Tant que ce point n'est pas corrigé, utilisez les broches d'entrée du 8255 un bit à la fois, ou utilisez plutôt un Z80 PIO — le PIO traite chaque bit indépendamment et n'est pas affecté.
WD1773 — Contrôleur de Disquettes
Broche Direction Repos Fonction
DDEN entrée 1 Sélection double densité, active à l’état bas. Un niveau bas sélectionne le MFM.
WPRT entrée 1 Protection en écriture, active à l’état bas.
INTRQ sortie 0 Demande d’interruption. Active à l’état haut sur ce périphérique, contrairement aux circuits Zilog.
DRQ sortie 0 Demande de données. Destinée à piloter la broche RDY d’un DMA.
MOTOR sortie 0 Moteur en marche.
Les huit adresses décodées suivent la disposition du Sharp MZ-1E05 : statut et commande, piste, secteur, données, sélection de lecteur et moteur, sélection de face, densité, et une seconde lecture de statut. Trois clés de configuration supplémentaires s'appliquent — "machine" sélectionne la géométrie des images ("MZ-80B", "MZ-2500", ou MZ-700 si omise), "drives" fixe le nombre de lecteurs de 1 à 4, et "file" nomme l'image ou les images disque à monter. Un seul WD1773 peut être déclaré.
MC6850 — Port Série (68B50 ACIA)
Broche Direction Repos Fonction
DCD entrée 0 Détection de porteuse, active à l’état bas. Au repos affirmée afin qu’un montage non câblé reçoive quand même.
CTS entrée 0 Prêt à émettre (clear to send), actif à l’état bas. Au repos affirmé afin qu’un montage non câblé émette quand même.
RTS sortie 1 Demande pour émettre (request to send), active à l’état bas. Pilotée active uniquement lorsque le registre de contrôle le sélectionne.
IRQ sortie 1 Demande d’interruption, active à l’état bas, drain ouvert.
Les deux adresses décodées sont contrôle (écriture) / statut (lecture), puis données. Écrire 11 dans le champ de division du compteur du registre de contrôle constitue la réinitialisation maîtresse du circuit, exactement comme sur le composant réel ; le rapport de division et le format de mot sont par ailleurs enregistrés mais non simulés. Un dépassement en réception est signalé dans le registre de statut et effacé par la lecture du registre de données.
Note de câblage
La sortie d'interruption du 68B50 se nomme IRQ, comme sur sa fiche technique — et non INT. Le câblage automatique à l'ajout de l'interface web accepte l'un ou l'autre nom et la relie à cpu.INT, en ET câblé avec les autres sources d'interruption (interface web 3.58 et ultérieures ; les versions antérieures ne reconnaissaient que INT et laissaient l'interruption du 68B50 non câblée).
I8251 — Port Série (8251 USART)
Broche Direction Repos Fonction
DSR entrée 0 Data set ready, actif à l’état bas. Au repos affirmé.
CTS entrée 0 Clear to send, actif à l’état bas. Au repos affirmé.
DTR sortie 1 Data terminal ready, actif à l’état bas.
RTS sortie 1 Request to send, actif à l’état bas.
TXRDY sortie 0 Émetteur prêt, actif à l’état haut.
RXRDY sortie 0 Récepteur prêt, actif à l’état haut.
INT sortie 1 Demande d’interruption, active à l’état bas, drain ouvert. RxRDY et TxRDY sont combinés en OU sur cette broche.
Les deux adresses décodées sont données, puis contrôle (écriture) / statut (lecture) — notez l'ordre inverse de celui du 68B50. La séquence d'écriture caractéristique du 8251 est respectée : après la réinitialisation, la première écriture de contrôle est l'instruction MODE, un mode synchrone est suivi de son ou ses deux caractères SYNC, et ce n'est qu'alors que le registre COMMAND prend effet ; une commande portant le bit de réinitialisation interne ramène le circuit en attente d'un mode. Les caractères de synchronisation sont mémorisés mais aucun tramage synchrone n'est émulé, et les bits d'erreur de parité et de trame ne sont jamais levés. Une écriture de données avec l'émetteur désactivé est rejetée, comme sur le composant réel.
Z80DART — Double Port Série (Z80 DART)
Broche Direction Repos Fonction
DCDA, DCDB entrée 0 Détection de porteuse pour les canaux A et B, active à l’état bas. Au repos affirmée.
CTSA, CTSB entrée 0 Clear to send pour les canaux A et B, actif à l’état bas. Au repos affirmé.
RIA, RIB entrée 1 Indicateur de sonnerie. Déclarées — le modèle ne les consulte pas.
RTSA, RTSB sortie 1 Request to send, actif à l’état bas, suivant WR5.
DTRA, DTRB sortie 1 Data terminal ready, actif à l’état bas, suivant WR5.
INT sortie 1 Demande d’interruption, active à l’état bas, drain ouvert.
IEI entrée 1 Priorité d’interruption en entrée — voir la chaîne daisy-chain.
IEO sortie 1 Priorité d’interruption en sortie.
Les quatre adresses décodées sont, dans l'ordre : données A, contrôle A, données B, contrôle B. Le DART est le seul périphérique série à rejoindre la chaîne daisy-chain d'interruptions, avec des interruptions entièrement vectorisées — le vecteur WR2 est fourni à l'acquittement avec application du « status affects vector », exactement comme le fait le SIO. Ce n'est pas un hasard : le DART partage le modèle de circuit du Z80 SIO dans le firmware — un seul CI, deux visages de périphérique — et est proposé ici comme « le SIO sans les modes synchrones », lesquels ne se présentent jamais sur un flux d'octets USB. Un dépassement en réception verrouille le bit d'erreur au lieu d'être ignoré silencieusement. Le DART occupe deux canaux USB : son canal usb pour A et le suivant pour B.
MSM5832 — Horloge Temps Réel
Broche Direction Repos Fonction
HOLD entrée 0 Gèle les registres lisibles, actif à l’état haut. Son relâchement relit immédiatement l’horloge.
Les quatre bits d'adresse de poids faible constituent la sélection de registre du circuit, raison pour laquelle le périphérique occupe un bloc de seize adresses : les registres 0 à 12 sont les treize chiffres BCD des secondes jusqu'à l'année. L'horloge récupère l'heure courante auprès de l'ESP32 via la liaison inter-processeurs — l'horloge propre de l'ESP32 est réglée par le navigateur à chaque chargement de page ou par SNTP via la passerelle, et l'heure locale qu'il rapporte est redemandée toutes les dix secondes jusqu'à ce qu'elle ait réellement été réglée, de sorte que le circuit lit la date et l'heure correctes peu après que le réseau les a obtenues. La commande rtc du shell de débogage indique si l'horloge s'est synchronisée. Une horloge que le programme invité a déjà réglée lui-même est laissée intacte. L'écriture règle l'horloge sous forme de décalage par rapport à l'heure système : une routine de réglage écrit les chiffres et termine par le registre des unités de secondes, qui est l'écriture recalculant le décalage. Le mode 12 heures (clé hours) positionne le drapeau PM dans le registre des dizaines d'heures exactement comme le composant réel.
Centronics — Port Imprimante
Broche Direction Repos Fonction
BUSY sortie 0 Imprimante occupée, actif à l’état haut. Affirmé tant que la file d’impression est pleine.
ACK sortie 1 Acquittement. Déclarée — pas encore pilotée ; l’acquittement apparaît à la place dans le registre de statut.
STROBE entrée 1 Strobe externe. Déclarée — sans effet ; le strobe passe par le registre de contrôle ou l’écriture de données.
INT sortie 1 Interruption à l’acquittement, active à l’état bas, drain ouvert. Activée par la clé de configuration int.
Les deux adresses décodées sont données (une lecture renvoie le verrou, une écriture verrouille un octet), puis statut (lecture) / contrôle (écriture). Il s'agit d'un modèle de l'interface, non d'un circuit particulier : l'autre extrémité est un canal série USB, de sorte que le registre de statut signale toujours une imprimante présente, sélectionnée et en bon état. Le bit de statut d'acquittement est un monocoup effacé par la lecture du statut, et le bit de contrôle INIT vide la file.
Latch — verrou / port à usage général
Broche Direction Repos Fonction
IN0 … IN7 entrée 0 Une lecture Z80 de l’adresse assemble ces huit niveaux dans les bits 0 à 7.
OUT0 … OUT7 sortie 0 Une écriture Z80 pilote ces huit niveaux à partir des bits 0 à 7.
La logique de liaison que tout montage amateur possède pour la commutation de banques et les lignes de contrôle — invisible sur du matériel réel parce qu'il ne s'agit que d'un verrou de la série 74 et d'un tampon, mais ici câblée directement dans la netlist et adressable depuis le code Z80. Une écriture pilote les huit fils OUT ; une lecture échantillonne les huit fils IN, ou renvoie le dernier octet écrit lorsque "readback": "latch" est défini. Le verrou décode une seule adresse, et sa valeur survit à une réinitialisation du Z80 — elle n'est effacée que lorsque la configuration est appliquée.
SignalPort — port de test et de pontage
Broche Direction Repos Fonction
IN0 … IN7 entrée 0 Une lecture du port par le Z80 renvoie ces huit niveaux sous forme des bits 0 à 7.
OUT0 … OUT7 sortie 0 Une écriture du Z80 pilote ces huit niveaux.
Ce n'est pas le modèle d'un circuit réel. C'est l'instrument d'établi de la netlist : il place huit fils sur une seule adresse d'E/S afin qu'un programme s'exécutant sur le Z80 puisse injecter des signaux dans le montage et échantillonner ce qui en revient. C'est la manière la plus directe de tester une idée de câblage avant de s'y engager.

Exemple Détaillé — Ajouter un Z80 DMA

Ce qui suit est une configuration complète et fonctionnelle. Elle place un Z80 DMA sur le port d'E/S 0x0F, un Z80 CTC sur 0x10, et relie les deux de sorte que le temporisateur conditionne le transfert. Chaque entrée inutilisée du DMA est reliée en dur à un niveau fixe, et les deux sorties d'interruption sont combinées par un ET câblé sur la broche d'interruption du processeur.
"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": [
    /* Entrées DMA inutilisées, reliées pour documenter leur état.            */
    { "name": "dma bus acknowledge", "dst": "dma0.BAI",  "src": [ "cpu.BUSACK" ] },
    { "name": "dma chip enable",     "dst": "dma0.CE",   "src": [ "0" ] },
    {                                "dst": "dma0.WAIT", "src": [ "1" ] },
    {                                "dst": "dma0.IEI",  "src": [ "1" ] },
    {                                "dst": "dma0.M1",   "src": [ "1" ] },

    /* Entrées de déclenchement CTC inutilisées.                              */
    { "dst": "ctc0.CLKTRG2", "src": [ "0" ] },
    { "dst": "ctc0.CLKTRG3", "src": [ "0" ] },
    { "dst": "ctc0.IEI",     "src": [ "1" ] },

    /* Deux sorties du temporisateur conditionnent la demande via un NOR.     */
    { "name": "timer gates the dma",
      "dst":  "dma0.RDY", "op": "NOR",
      "src":  [ "ctc0.ZCTO0", "ctc0.ZCTO1" ] },

    /* Les deux sorties d'interruption sont à drain ouvert et en ET câblé.    */
    { "name": "interrupt chain",
      "dst":  "cpu.INT", "op": "AND",
      "src":  [ "dma0.INT", "ctc0.INT" ] } ]
Tout le reste concernant le DMA — qu'il déplace des octets ou qu'il en recherche un, dans quelle direction il les déplace, la longueur du bloc, les adresses, le mode octet, rafale ou continu — est programmé par le Z80 à travers les registres du circuit à l'exécution, exactement comme cela se ferait sur du matériel réel. La configuration décide uniquement où se trouve le circuit et à quoi il est relié.
Un transfert mémoire vers mémoire mérite une note, car c'est la première chose que la plupart des gens essaient. Un tel transfert n'a aucun périphérique pour le demander, il n'y a donc aucune ligne RDY à piloter. Sur le matériel réel, le Z80 DMA résout cela avec la commande Force Ready (B3), qui fournit la condition « prêt » en interne, et le modèle du picoZ80 fait de même. N'oubliez pas que ENABLE DMA (87) doit être le dernier octet écrit, car presque tous les autres octets de contrôle désactivent le circuit. Force Ready est annulé par la libération du bus et par la fin de bloc ; un transfert qui doit se relancer lui-même doit donc tirer son signal « prêt » d'une source câblée, telle qu'une sortie de temporisateur, plutôt que de la commande Force Ready.

Limites et Restrictions Actuelles

Le framework est borné par des tailles de tables fixes. Le dépassement de l'une d'elles est signalé dans le journal de démarrage, mais la configuration semblera par ailleurs se charger normalement.
Limite Valeur Notes
Périphériques 12 Un budget unique partagé par tous les types — douze circuits au total, en toute combinaison. Les entrées supplémentaires sont ignorées.
Signaux au total 192 Tous périphériques confondus, plus les neuf broches du CPU.
Fils d’interlink 96 Les entrées supplémentaires sont ignorées.
Sources par fil 8 Les termes supplémentaires sont ignorés.
Itérations de stabilisation 8 Suffisant pour des bascules couplées en croix ; une véritable oscillation est signalée.
Identifiant de périphérique 11 caractères Les identifiants plus longs sont tronqués.
Deux exceptions au budget partagé : un seul WD1773 peut être déclaré, et les quatre périphériques à flux — MC6850, I8251, Z80DART et Centronics — puisent leurs emplacements de ligne d'interruption dans un pool commun ; un circuit au-delà du pool s'initialise quand même mais fonctionne sans ligne d'interruption. Les périphériques gourmands en broches épuisent le budget de signaux le plus vite : un 8255 représente 24 broches, un PIO 23, un Latch ou un SignalPort 16.
Les neuf broches du CPU sont enregistrées en dernier ; un montage qui épuise la table des signaux perd donc cpu.INT en premier, et tous les fils d'interruption échouent silencieusement à se résoudre. Si les interruptions cessent de fonctionner dans un montage important, comptez vos broches.
Limitations Importantes
  • Les périphériques compteurs n'avancent que sous le modèle BaseZ80. Le tick libre du Core 0 qui cadence le Z80 CTC et le 8253 n'est démarré que par le firmware BaseZ80. Sous les compilations Sharp, Amstrad, Tatung et OpenZ80, ces deux périphériques sont déclarés et adressables mais leurs compteurs ne tournent pas.
  • Un périphérique n'est pas réinitialisé par un reset du processeur. Les périphériques sont initialisés une seule fois, lors de l'application de la configuration.
  • Un DMA virtuel ne peut pas être déclenché par un périphérique réel. Le picoZ80 n'atteint que le support du Z80, et chaque broche à usage général du RP2350 est déjà attribuée ; il n'existe donc aucun moyen d'amener la ligne de demande de données d'une carte physique dans la netlist. Un DMA virtuel tire sa demande d'une constante, de Force Ready, ou d'un autre périphérique virtuel — la sortie DRQ du contrôleur de disquettes en étant l'exemple évident. C'est une propriété du matériel, non un défaut.
  • Les clés mirror, refresh et trace sont acceptées mais n'ont actuellement aucun effet.
Pour la procédure pas à pas via l'interface web, consultez le Manuel Utilisateur picoZ80. Pour la structure interne, l'outillage du shell de débogage et la manière d'ajouter un nouveau modèle de périphérique, consultez le Guide du Développeur.

ICE (Shell de Débogage)

Le picoZ80 inclut un shell de débogage ICE (In-Circuit Emulator) intégré sur le canal USB CDC 1 (le second port série énuméré lorsque la carte est connectée via USB). Le shell s'exécute sur le Core 0 et communique avec la boucle d'émulation du Core 1 via des drapeaux partagés dans la structure de contexte Z80CPU. Le shell de débogage n'est disponible que dans la variante firmware DBGSH, compilée avec le define INCLUDE_DBGSH.

Canaux série USB CDC

Le picoZ80 énumère jusqu'à cinq ports série USB CDC lorsqu'il est connecté à un hôte. CDC 0 et CDC 1 desservent les UART physiques / le pont ESP32 et le shell de débogage ICE (CDC 1, variantes DBGSH). CDC 2 et CDC 3 sont les deux canaux (A et B) d'une carte série RS-232C virtuelle — le pilote MZ-8BIO3 ou MZ-1E24 — lorsqu'une est configurée dans config.json, et sont également les canaux par défaut réclamés par les périphériques série déclarés (MC6850, I8251, Z80DART — voir Circuits Périphériques Virtuels). CDC 4 est le canal dédié d'un périphérique imprimante Centronics déclaré. Ces ports n'ont aucun UART physique derrière eux : ce sont des ponts à tampon circulaire vers le circuit émulé, de sorte que les données écrites côté hôte apparaissent sur le périphérique vu par l'invité, et vice versa.

Architecture

  • Entrée/Sortie : Canal USB CDC 1 à 115200 bauds. L'invite du shell est dbg> . L'historique des commandes (16 entrées) et l'écho des caractères sont supportés.
  • Points d'arrêt : Jusqu'à 8 points d'arrêt simultanés stockés dans cpu->dbgBpAddr[]. Le Core 1 vérifie le tableau de points d'arrêt avant chaque fetch d'opcode ; en cas de correspondance, il met cpu->hold = true et signale le Core 0 via dbgBpHit.
  • Pas à pas : La commande step définit cpu->dbgStepCount. Le Core 1 décrémente ce compteur après chaque instruction, se mettant automatiquement en pause lorsqu'il atteint zéro. L'état des registres avant/après et l'instruction désassemblée sont affichés pour chaque pas.
  • Trace d'exécution : Un tampon circulaire de 512 entrées (cpu->dbgTrace[]) enregistre PC, opcode et registre de drapeaux pour chaque instruction exécutée lorsque le traçage est activé. Chaque entrée de 32 bits encode [31:16]=PC, [15:8]=opcode, [7:0]=registre F.
  • Accès mémoire : L'accès mémoire physique (dm p, wm p) effectue de vrais cycles bus Z80 via les machines d'état PIO. L'accès virtuel (dm v, wm v) lit/écrit directement la PSRAM. Le mode automatique (wm sans qualificatif) suit la carte mémoire. L'accès RP2350 (dm r) lit l'espace d'adressage du microcontrôleur hôte avec validation de plage.
  • Hold/Release : La commande hold met cpu->hold = true. Le Core 1 acquitte via cpu->holdAck, garantissant que le CPU est au repos avant que le shell n'accède à l'état partagé.
  • Break : La commande break met le Core 1 en pause puis affiche le PC courant, l'instruction sur le point d'être exécutée (désassemblée) et l'ensemble complet des registres — le moyen le plus rapide de voir où en est un programme en cours d'exécution, avant de passer en pas à pas ou d'inspecter. go/cont reprend l'exécution.
  • Touches rapides et abréviations : Pour réduire la frappe pendant le débogage, les commandes les plus courantes disposent de touches rapides à une lettre — c=cont, s=step, g=go, b=break, r=regs, h=hold — qui sont résolues avant la correspondance par préfixe, de sorte qu'elles ne sont jamais considérées comme ambiguës. Toute autre commande peut être saisie sous la forme de son plus court préfixe unique (par ex. ste=step, sta=status, dis=disassemble) ; un préfixe ambigu liste les candidats, et un nom entièrement saisi l'emporte toujours.

Référence des Commandes

Commande Syntaxe Description
help help Lister toutes les commandes
regs regs Afficher tous les registres Z80, drapeaux et compteur de cycles
dm dm <p|f|v|r> <addr> [len] Dump mémoire (physique / fetch / virtuel / RP2350)
search search [p|v] <start> <end> <hex..>|"text" Rechercher en mémoire un motif d’octets ou une chaîne ASCII. p = bus physique, v = PSRAM virtuelle, omettre pour mappé. Les correspondances sont affichées avec 8 octets de contexte. Le CPU est automatiquement mis en pause pour les accès physiques/mappés. Motif jusqu’à 32 octets
cmp cmp [f] <phys> <virt> <len> Comparer la mémoire bus physique avec la PSRAM virtuelle
dis dis [p|v] [addr] [count] Désassembler du code Z80
asm asm [addr] Assembleur Z80 interactif
memmap memmap [block] Afficher la table de pointeurs de banques mémoire
memptr memptr [addr] Afficher la table memPtr en PSRAM
iomap iomap [port] Afficher la table des handlers de ports E/S
status status Statut système (fréquence CPU, PSRAM, uptime)
ver ver Version firmware et informations de partition
drivers drivers Lister les pilotes et interfaces actifs
hold hold Mettre en pause l’émulation CPU
release release Reprendre l’émulation CPU
break break Arrêter le Z80 en cours d’exécution et indiquer où il s’est arrêté — PC, l’instruction (octets + désassemblage) sur le point d’être exécutée, et un dump complet des registres. Contrairement à hold (qui met en pause silencieusement), break affiche l’état pour que vous puissiez voir exactement où en est l’exécution (par ex. lorsqu’elle est bloquée dans une boucle). Réémettre break réaffiche l’état. Reprendre avec go/cont ou exécuter en pas à pas avec step. Expire (~3 s) si le Z80 est maintenu en reset ou si son horloge est coupée
go go Continuer (relâcher le hold, points d’arrêt actifs)
cont cont Alias pour go. Continuer l’exécution (relâcher le hold, points d’arrêt actifs)
step step [n] Pas à pas de n instructions
next next [n] Pas à pas de n instructions en enjambant les appels de sous-programmes (alias n)
bp bp <addr> Définir un point d’arrêt (max 8)
bc bc <n|*> Effacer le point d’arrêt n ou tous
bl bl Lister les points d’arrêt
wm wm [p|v] <addr> <byte>... Écrire en mémoire (physique/virtuelle/auto)
fill fill [p|v] <addr> <len> [w|d] <val> Remplir la mémoire avec une valeur constante
copy copy <pv|fp|vp> <src> <len> <dst> Copier la mémoire entre physique et virtuelle
memtest memtest <addr> <len> [pattern] Tester la mémoire physique (écriture+lecture, écriture+fetch, entrelacé)
in in <port> Lire un port E/S Z80
out out <port> <byte> Écrire sur un port E/S Z80
trace trace <on|off|dump [n]|clear|rt|byte ...> Contrôle de la trace d’exécution ; rt active la sortie de trace en temps réel ; byte active le traçage par octet
verify verify <on|off> Activer/désactiver la vérification complète du fetch d’opcode
fwait fwait <0-4> Forcer des wait states supplémentaires pour le fetch M1 ; 0 = désactivé (par défaut)
iowait iowait <0-8> Forcer des wait states supplémentaires pour les cycles E/S ; 0 = désactivé (par défaut)
corrupt corrupt [clear] Afficher ou effacer les corruptions de fetch détectées
fdctrace fdctrace <on|off|dump> Activer/désactiver la trace E/S du FDC ; dump affiche les 64 dernières opérations
qdtrace qdtrace <on|off|dump> Activer/désactiver la trace E/S du Quick Disk ; dump affiche les 64 dernières opérations
qdprobe qdprobe [seconds] / qdprobe eng [pace_us] Piloter manuellement le SIO du Quick Disk RÉEL (ports F4/F6/F7) et cartographier ses signaux — moteur en marche, recherches (hunts) et événements de synchronisation (A = ChA RR0 : b3=HDST, b4=hunt, b0=Rx ; B = ChB RR0 : b3=HOME) — pendant au plus seconds (20 par défaut, 60 max), en retenant le CPU Z80 jusqu’à ce que go le relance. eng [pace_us] rejoue la séquence exacte init/hunt/lecture du moteur QD de TZFS à une cadence inter-accès réglable, pour valider le timing sur matériel réel
piodbg piodbg [clear] Afficher les diagnostics matériels PIO du RP2350 (FDEBUG, FSTAT, FIFO, PCs, GPIO) ; clear réinitialise les drapeaux persistants
load load <p|v> <file> <addr> [len] [ofs] Charger un fichier depuis la carte SD de l’ESP32 en mémoire Z80. p = bus physique, v = banque PSRAM virtuelle 0. file relatif à /sdcard/. Si len est omis, le fichier entier est chargé (jusqu’à 64KB) ; si spécifié, max 1MB. ofs optionnel pour l’offset fichier. Le CPU est automatiquement mis en pause pour les écritures physiques. Utilise la banque PSRAM 63 comme tampon de travail
save save <p|pf|v> <file> <addr> <len> Sauvegarder la mémoire Z80 dans un fichier sur la carte SD de l’ESP32. p = bus physique, pf = fetch physique (M1), v = banque PSRAM virtuelle 0. file relatif à /sdcard/. Max 64KB. Le CPU est automatiquement mis en pause pour les lectures physiques. Rafraîchissement DRAM périodique pendant les lectures physiques
dir dir [path] Lister les fichiers sur la carte SD de l’ESP32. Le chemin optionnel est relatif à /sdcard/. Affiche les noms de fichiers et tailles
echo echo [on|off] Activer/désactiver l’écho terminal
reset reset Forcer un reset Z80
set set <reg|flags|memmap|memptr|iomap> <idx> <val> Modifier un registre Z80, des drapeaux, une carte mémoire, un memPtr ou une entrée de carte E/S à l’exécution
hist hist [n] Afficher l’historique des commandes (préservé entre les sessions via NVS de l’ESP32)
savehst savehst Forcer la sauvegarde de l’historique des commandes en NVS de l’ESP32
ipl ipl Effectuer un reset IPL (mode BST) en basculant le bit 3 du Port C du 8255 PPI. Réinitialise en mode boot sans reset Z80 complet
mmutrace mmutrace Dump des informations de trace spécifiques à la machine (état MMU, snapshots de registres E/S). La sortie varie selon le persona via le handler de trace enregistré
intcount intcount Afficher le compteur d’acquittements d’interruption et l’état d’interruption courant
psync psync [start end] Synchroniser la mémoire physique vers la PSRAM. Plage d’adresses optionnelle ; par défaut tout l’espace d’adressage
dskimage dskimage read <filename> [cylinders] [heads] / dskimage write <filename> Imager une disquette physique vers un fichier DSK sur la carte SD (read), ou écrire un fichier DSK depuis la carte SD vers une disquette physique (write). dskimage <filename> effectue par défaut une lecture (rétrocompatible). Détection automatique de la géométrie si omise
busdiag busdiag Afficher les diagnostics bus (état PIO, niveaux de signaux, contention bus)
fdcimage fdcimage Afficher le statut et la progression de l’imagerie FDC
fdcdiag fdcdiag Afficher les informations de diagnostic FDC (état du contrôleur, dump des registres)
gadiag gadiag Afficher les informations de diagnostic du gate array (état des commandes, routage des interruptions)
ser ser [id] [tx <text> \| rx] État d’un périphérique série (MC6850 / I8251 / Z80DART). tx injecte des caractères dans le périphérique, rx lit ce que l’invité a envoyé — aucun terminal nécessaire. L’injection sur le DART ne concerne que le canal A
rtc rtc [id] Afficher les registres de l’horloge temps réel MSM5832 décodés en une date et une heure lisibles
prn prn [id] [dump] État du périphérique imprimante Centronics ; dump vide la file d’impression vers le shell au lieu de l’USB
Touches rapides et abréviations. Les commandes n'ont pas besoin d'être saisies en entier. Les six commandes les plus utilisées disposent de touches rapides à une lettre — c (cont), s (step), g (go), b (break), r (regs) et h (hold) — et toute autre commande peut être saisie sous la forme de son plus court préfixe unique (par exemple ste pour step, sta pour status, dis pour disassemble). Un nom de commande entièrement saisi a toujours la priorité, et un préfixe ambigu affiche la liste des commandes correspondantes.

Coprocesseur ESP32

Le module ESP32-S3-PICO-1 agit comme coprocesseur gérant toutes les fonctions réseau et stockage. Il communique avec le RP2350 via deux interfaces :
  • FSPI (60MHz, SPI 4 fils) — Protocole IPC Binaire v1.1 — transfert de données en masse à haute vitesse (images ROM, lectures/écritures de secteurs disque, téléchargement de fichier de configuration). Le protocole utilise un en-tête de trame binaire fixe de 64 octets avec vérification d'intégrité CRC32 (remplaçant l'ancien checksum XOR). Les canaux DMA sont pré-alloués à l'initialisation et jamais libérés, éliminant le surcoût de claim/unclaim par transfert et les conditions de course. Les transferts de secteurs en rafale permettent jusqu'à 16 × 512 octets (8KB) en une seule transaction SPI, améliorant significativement les temps de chargement d'images disquette et QuickDisk. Le canal DMA RX est élevé en HAUTE PRIORITÉ pour prévenir le débordement FIFO causé par la contention du bus QMI PSRAM du Core 1.
  • UART (460,8 kbauds) — le chemin de pont USB-vers-UART : le RP2350 relaie un canal USB CDC vers la console de l'ESP32 afin que l'ESP32 puisse être flashé et surveillé via le port USB du RP2350, et l'ESP32 peut à son tour programmer le RP2350.
La liaison FSPI a été durcie en septembre 2026 après qu'une classe de plantages différés par pointeur fou a été attribuée à une réponse tardive arrivant après l'expiration du délai d'une commande et décalant d'une trame toutes les réponses suivantes. Chaque réponse porte désormais l'opcode et le numéro de séquence de la commande d'origine et est validée à la réception ; une trame qui ne correspond pas est rejetée, comptabilisée, et la commande est réessayée. L'ensemble du chemin critique SPI (CRC, abandon DMA, transfert, émission/réception et le sondage NOP périodique) s'exécute depuis la RAM plutôt que depuis la flash XIP, après que des fautes d'instruction non définie ont été attribuées à des rechargements XIP corrompus sous contention PSRAM ; avec les tampons DMA en SRAM, la liaison est stable à 60 MHz avec une horloge système de 320 MHz.

Modes Réseau

Le firmware ESP32 supporte trois modes réseau, sélectionnés à la compilation via des fichiers sdkconfig pré-construits :
Mode Fichier de config WiFi USB NCM Console FCC/RED requis
WiFi uniquement sdkconfig.mode_wifi_only Oui Non USB Serial/JTAG Oui
WiFi + NCM sdkconfig.mode_wifi_and_ncm Oui Oui TinyUSB CDC-ACM Oui
NCM uniquement sdkconfig.mode_ncm_only Non Oui TinyUSB CDC-ACM Non
USB NCM (Network Control Model) présente un adaptateur Ethernet CDC-NCM sur le port USB OTG de l'ESP32-S3 (GPIO 19/20). Un périphérique USB composite expose à la fois un port série CDC-ACM (pour la journalisation de débogage) et l'interface réseau NCM. Le serveur DHCP intégré attribue à l'hôte une adresse IP depuis le sous-réseau 192.168.7.0/24, le picoZ80 étant accessible à 192.168.7.1. La durée du bail est de 120 minutes.
En mode WiFi+NCM, le serveur HTTP écoute sur INADDR_ANY:80 et dessert les deux interfaces simultanément. Le WiFi se connecte de manière asynchrone, donc l'interface USB NCM est disponible immédiatement à la mise sous tension.
Lorsque seul le NCM est activé, la radio WiFi est complètement désactivée, la page WiFi Manager est supprimée de l'interface web, et le titre du panneau de statut du Dashboard passe de "WiFi Configuration" à "Network Configuration" (affichant le statut USB NCM au lieu des détails SSID/WiFi). Le réseau d'adaptation d'antenne de l'ESP32-S3 n'a pas besoin d'être assemblé sur le PCB.

Interface Carte SD

L'ESP32 gère la carte SD via son interface SPI. La carte SD est montée en FAT32 et tout accès fichier depuis le RP2350 est médié par l'ESP32 — le RP2350 envoie des commandes d'E/S fichier via le lien FSPI/UART et l'ESP32 effectue les opérations de lecture/écriture FAT32 réelles.
La carte SD est également directement accessible au serveur web de l'ESP32, qui sert les fichiers depuis le répertoire webfs/ et permet au gestionnaire de fichiers de parcourir et modifier le contenu de la carte via HTTP.
Correctif de lecture des grands répertoires. Une erreur TZFS intermittente « SD Read error » sur les grands répertoires est corrigée. L'ancien code effectuait des rebalayages de répertoire en O(N²) qui dépassaient la fenêtre de handshake FSPI lorsque le cache SD était froid ; ils sont remplacés par un curseur de répertoire persistant MZFDIR côté ESP32 plus une relance du cache de répertoire côté RP2350, de sorte que les grands répertoires s'énumèrent de manière fiable.

Serveur Web

L'ESP32 exécute un serveur HTTP sur le port 80 (pas de TLS — usage réseau local uniquement). Tous les assets web (HTML, CSS, JavaScript) sont servis depuis le répertoire webfs/ de la carte SD, permettant la mise à jour de l'interface web sans reflasher le firmware ESP32. Le serveur web gère :
  • La diffusion d'assets web statiques depuis le répertoire webfs/ de la carte SD.
  • Les endpoints d'API REST pour les données JSON (statut système, lecture/écriture de configuration, opérations sur les fichiers).
  • Les endpoints de téléchargement de firmware OTA pour le RP2350 et l'ESP32.
  • La connexion WebSocket pour les mises à jour de statut du Dashboard en temps réel.

Protocole de Commande RP2350 ↔ ESP32

Le RP2350 (Core 0) communique avec l'ESP32 en utilisant le protocole de trames IPC binaire via le lien FSPI. Chaque commande est un opcode d'un seul octet dans un en-tête binaire fixe (numéro de séquence, longueur de charge utile, champ de nom de fichier) suivi d'une charge utile optionnelle et d'une remorque CRC32 ; l'ESP32 répond par une trame de réponse reprenant l'opcode et le numéro de séquence, un code de statut (OK, ERR, BUSY, CRCFAIL, NOTFOUND ou PARTIAL) et les éventuelles données de réponse. Les commandes que l'ESP32 doit envoyer au RP2350 — changements de persona, bascules de mode de deck, changements d'image disquette/QD, rechargements de configuration — reviennent en tant que commandes inverses dans la réponse au sondage NOP périodique du RP2350.
Catégories de commandes courantes :
  • E/S fichier — ouvrir, lire, écrire, fermer, listing de répertoire, stat fichier.
  • Configuration — demander le contenu de config.json, écrire la configuration mise à jour, demande de rechargement.
  • Disque — monter/démonter une image disque, lire/écrire un secteur (relayé depuis l'émulation WD1773 et QDDrive). Les noms de fichiers des images disquette et QuickDisk actuellement montées sont suivis par l'ESP32 et affichés dans le menu Actions de l'interface web.
  • Système — requête de version, demande de redémarrage, lecture/écriture NVS.
La liste complète des opcodes figure dans le Guide du Développeur — Opcodes de Commande ; elle comprend les transferts de fichiers et de secteurs, INF (rapport de version/partition/état des decks), les listings de répertoire DIR/MZFDIR, les cinq opérations réseau utilisées par la carte Celestite et TIME, qui renvoie l'heure courante de l'ESP32 (secondes d'époque locales plus un drapeau de synchronisation) pour l'horloge temps réel MSM5832.

Commandes IPC Réseau

L'implémentation réseau Celestite Phase 2 ajoute cinq commandes IPC inter-cœurs que le RP2350 utilise pour demander des opérations réseau à l'ESP32. Ces commandes sont transmises via le lien FSPI/UART et l'ESP32 effectue les opérations BSD socket correspondantes. Les connexions non bloquantes utilisent select() avec un timeout, et des drapeaux en attente par socket suivent les opérations en cours.
Commande Opcode Description
IPCF_CMD_NET_CFG 0x10 Obtenir la configuration réseau de l’ESP32 — retourne l’adresse IP, la passerelle, le masque de sous-réseau et l’adresse MAC
IPCF_CMD_NET_SOCK 0x11 Opération de cycle de vie socket — ouvrir, connecter, écouter, fermer ou déconnecter un socket
IPCF_CMD_NET_SEND 0x12 Envoyer des données sur un socket ouvert
IPCF_CMD_NET_RECV 0x13 Recevoir des données depuis un socket ouvert
IPCF_CMD_NET_PING 0x14 Requête ICMP echo (ping)
IPCF_CMD_TIME 0x15 Récupération de l’heure courante — secondes d’époque locales sur 64 bits plus un drapeau de synchronisation ; utilisée par devicesTimeSync() pour régler les instances de RTC MSM5832
Les commandes socket W5100 supportées à travers cette couche IPC sont : OPEN, CONNECT, LISTEN, SEND, RECV, CLOSE et DISCON. L'ESP32 les traduit en appels standard de l'API BSD socket (socket(), connect(), listen(), send(), recv(), close(), shutdown()), permettant à la carte Celestite de communiquer avec des services réseau tels que le serveur de fichiers netfs.py.

Watchdog et Diagnostics de Démarrage

Le firmware du RP2350 utilise un timer watchdog matériel pour détecter et récupérer des blocages au démarrage et des stalls de la boucle principale. Le watchdog est activé tôt dans la séquence de démarrage avec un timeout de 30 secondes et est relancé (watchdog_update()) à chaque jalon majeur. Si une étape de démarrage ou une itération de la boucle principale prend plus longtemps que le timeout, le watchdog réinitialise automatiquement le RP2350.

Suivi de la Progression du Démarrage

La progression du démarrage est suivie à l'aide des registres scratch du watchdog du RP2350, qui survivent aux resets watchdog (mais pas aux resets de mise sous tension). Cela permet au firmware de déterminer, après un reset watchdog, exactement quelle étape de démarrage a été atteinte avant le blocage.
Registre Scratch Nom Contenu
scratch[0–3] Historique de démarrage Les quatre dernières tentatives de reset — chaque entrée encode (attempt_count << 24) | (stage << 16) | (resetCause & 0xFFFF). Les entrées se décalent à chaque reset watchdog : [0]←[1]←[2]←[3]←courant.
scratch[4] Diagnostics SPI Compteurs de diagnostic empaquetés pour le lien FSPI : breadcrumbs, type de message, compteurs de timeout T1/T3, compteur de trames invalides et compteur OK.
scratch[5] Marqueur magique Défini à 0xB00710BE pour indiquer que les registres scratch contiennent des données de progression de démarrage valides.
scratch[6] Étape courante Le code d’étape de démarrage le plus récent (voir le tableau ci-dessous).
scratch[7] Cause du reset Le code de cause de reset du contrôleur de reset matériel.
Les codes d'étape de démarrage progressent de 0x01 (début) jusqu'à 0x10 (boucle principale atteinte). Les sous-étapes au sein de la boucle principale (0x11–0x17) et le traitement des commandes inter-cœurs (0x20–0x27) fournissent un suivi fin :
Code Étape Description
0x01 BOOTP_START Point d’entrée atteint
0x02 BOOTP_CLK_SET Horloge système configurée
0x03 BOOTP_PSRAM_INIT Initialisation PSRAM démarrée
0x04 BOOTP_PSRAM_OK PSRAM initialisée avec succès
0x05 BOOTP_STDIO_INIT USB stdio initialisé
0x06 BOOTP_PIO_INIT Machines d’état PIO chargées
0x07 BOOTP_Z80_INIT Contexte CPU Z80 initialisé
0x08 BOOTP_USB_INIT Pont USB initialisé
0x0A BOOTP_ESP_HS_SYNC Synchronisation du handshake SPI ESP32
0x0B BOOTP_CORE1_LAUNCH Core 1 lancé
0x0D BOOTP_FSPI_INIT IPC binaire FSPI initialisé
0x0E BOOTP_ESP_INIT Communication ESP32 prête
0x10 BOOTP_MAIN_LOOP Boucle principale atteinte
0x11–0x17 Sous-étapes boucle principale USB poll, inter-cœurs, SPI NOP/CMD, tâches
0x20–0x27 Commandes inter-cœurs Chargement disquette, chargement QD, chargement RAMFILE, E/S fichier

Journal Persistant PSRAM (plogf)

Les derniers 4KB des 8MB de PSRAM (adresse 0x117FF000) sont réservés pour un journal de débogage persistant qui survit aux resets watchdog. La macro plogf() écrit des messages de type printf dans ce tampon pendant le démarrage, avant que l'USB ne soit disponible pour la sortie debugf() normale. Au prochain démarrage réussi, la fonction dump_plog() affiche les messages capturés sur la console de débogage puis efface le tampon. Le journal utilise une structure simple : un marqueur magique de 4 octets (0x504C4F47 = "PLOG"), un compteur de longueur de 4 octets et un tampon texte circulaire de 3840 octets.

Diagnostics de Fautes

Le firmware installe des handlers de fautes Cortex-M33 pour les fautes matérielles, les fautes de gestion mémoire, les fautes bus et les fautes d'utilisation. Lorsqu'une faute se produit, le handler sauvegarde un snapshot diagnostique complet dans les derniers 256 octets de la PSRAM (adresse 0x117FFF00) avec un marqueur magique (0xFA017000), le type de faute, tous les registres pertinents (PC, LR, SP, R0–R3, R12, PSR), le Configurable Fault Status Register (CFSR), le Hard Fault Status Register (HFSR), le Bus Fault Address Register (BFAR), le Memory Management Fault Address Register (MMFAR) et l'identifiant du cœur. Le handler entre ensuite dans une boucle infinie, permettant au watchdog de déclencher un reset. Au prochain démarrage, le firmware vérifie la présence d'un diagnostic de faute valide et affiche les informations capturées via debugf(), permettant une analyse post-mortem sans nécessiter une session de débogueur en direct.

Effacement de la Configuration Flash

Le mécanisme de mise à jour OTA du RP2350 supporte deux opérations supplémentaires au-delà du téléchargement de firmware :
  • Effacer App Config (FW_CFGCLEAR_ID = 0xB1D7E5FA) — efface la partition App Config (images ROM et JSON minifié) associée au slot firmware cible. Ceci force le firmware à relire config.json depuis la carte SD au prochain démarrage, ce qui est nécessaire lorsque le schéma de configuration a changé entre les versions de firmware.
  • Effacer l'En-tête Flash (FW_HDRCLEAR_ID = 0xC2E8F6AB) — réinitialise l'en-tête de partition flash aux valeurs d'usine. La configuration du bootloader (partition 0) est préservée, mais toutes les métadonnées des partitions applicatives sont reconstruites à partir de zéro. Utilisez ceci lorsque la table de partitions est corrompue ou lors d'un retour à une version antérieure du firmware qui attend une disposition de partitions différente.
Les deux opérations sont déclenchées depuis les cases à cocher de la page web OTA du RP2350 et sont exécutées par le bootloader pendant le processus de mise à jour du firmware.

Débogage SWD — RP2350

Le RP2350 supporte le débogage complet au niveau source via ARM Serial Wire Debug (SWD). Connectez une sonde compatible CMSIS-DAP (Raspberry Pi Debug Probe, Black Magic Probe ou similaire) aux broches 1 (SWCLK), 2 (SWDIO) et 5 (GND) du connecteur de débogage.

Configuration OpenOCD

Le picoZ80 nécessite une petite modification du script cible OpenOCD standard du RP2350 pour activer le débogage SMP avec des ports GDB séparés par cœur :
sudo cp /usr/local/share/openocd/scripts/target/rp2350.cfg \
        /usr/local/share/openocd/scripts/target/rp2350_tzpu.cfg
Éditez rp2350_tzpu.cfg — trouvez la ligne target smp à l'intérieur du bloc if {[string compare $_USE_CORE SMP] == 0} et supprimez le # initial :
# Before:
    #target smp $_TARGETNAME_0 $_TARGETNAME_1

# After:
    target smp $_TARGETNAME_0 $_TARGETNAME_1
Ce simple changement fait que OpenOCD enregistre le Core 0 sur le port GDB 3333 et le Core 1 sur le port GDB 3334, permettant des sessions GDB indépendantes par cœur. Lancez OpenOCD avant de démarrer GDB :
openocd -f interface/cmsis-dap.cfg -f target/rp2350_tzpu.cfg -c "adapter speed 5000"

Configuration GDB

Ajoutez ce qui suit à ~/.gdbinit (avec les chemins absolus correspondant à l'emplacement de votre projet) pour permettre le chargement automatique des fichiers .gdbinit par répertoire :
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
Débogage du 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
Débogage du 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
La commande GDB xac <address> <count> est définie dans les fichiers .gdbinit.3333 / .gdbinit.3334. Elle dump la mémoire en sortie combinée hexadécimale et ASCII et est utile pour inspecter le contenu des banques PSRAM et l'état des périphériques mappés en mémoire.

Débogage USB de l'ESP32

Le coprocesseur ESP32-S3 dispose d'une interface USB-JTAG intégrée — aucune sonde de débogage externe n'est requise. Connectez un câble USB depuis le PC hôte directement au port USB de l'ESP32 sur la carte 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
Assurez-vous que l'ELF a été compilé à partir de la même révision source que le firmware exécuté sur le dispositif, afin que les symboles et adresses soient correctement alignés.

Système de Compilation

Le firmware du picoZ80 utilise CMake avec le Raspberry Pi Pico SDK 2.x. Le système de compilation produit le firmware Bootloader et Application. L'application est compilée en quatre variantes — deux partitions (Partition 1 à 0x10020000, Partition 2 à 0x10520000) chacune en configuration standard et DBGSH. Les variantes DBGSH ajoutent INCLUDE_DBGSH aux drapeaux de compilation, activant le shell de débogage complet sur le canal USB CDC 1. Le firmware ESP32 est compilé séparément avec ESP-IDF v5.4, géré via Docker.
La façon la plus rapide d'obtenir un environnement fonctionnel est le script automatisé setup_picoZ80 (macOS/Linux et Windows), qui installe le SDK et toutes les dépendances et crée des scripts de build prêts à l'emploi — voir le README et le Guide du Développeur. Les cibles CMake, les drapeaux et les commandes ci-dessous documentent la compilation sous-jacente que le script automatise, pour ceux qui préfèrent la configurer manuellement.

Cibles de Compilation CMake

Cible Sortie Adresse Flash Notes
Bootloader Bootloader.elf, Bootloader.uf2 0x10000000  
BaseZ80_0x10020000 BaseZ80_0x10020000.elf, .bin 0x10020000 (Slot 1) Standard (pas de shell de débogage)
BaseZ80_0x10520000 BaseZ80_0x10520000.elf, .bin 0x10520000 (Slot 2) Standard (pas de shell de débogage)
BaseZ80_DBGSH_0x10020000 BaseZ80_DBGSH_0x10020000.elf, .bin 0x10020000 (Slot 1) DBGSH — inclut le shell de débogage ICE
BaseZ80_DBGSH_0x10520000 BaseZ80_DBGSH_0x10520000.elf, .bin 0x10520000 (Slot 2) DBGSH — inclut le shell de débogage ICE
Les modèles SharpZ80, AmstradZ80, TatungZ80 et OpenZ80 suivent le même schéma de slot / DBGSH, p. ex. OpenZ80_0x10020000, OpenZ80_0x10020000_DBGSH, OpenZ80_0x10520000 et OpenZ80_0x10520000_DBGSH (chacun produit .elf, .bin, .hex et .map ; les slots d'application utilisent un simple .bin, pas UF2). Compilez un seul modèle avec le filtre build_tzpuPico.sh, p. ex. build_tzpuPico.sh open.

Drapeaux de Compilation CMake Principaux

Drapeau Effet
INCLUDE_SHARP_DRIVERS Compile tous les pilotes de périphériques 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 Compile les pilotes de périphériques Amstrad PCW (PCW9512, uPD765).
INCLUDE_TATUNG_DRIVERS Compile les pilotes de périphériques Tatung Einstein (EinsteinTC01, EinsteinFDC, WD1770).
INCLUDE_OPEN_DRIVERS Compile le persona expérimentateur OpenZ80 (Open.c) et les cartes d’interface indépendantes de la machine (MZ-1R12/1R18/1R23/1R37, PIO-3034, MZ8BIO3, MZ1E24, Z80SIO, MZ-1E05, WD1773, Celestite).
TARGET_MODEL_TATUNG Définit le Tatung Einstein comme modèle cible exclusif (utilisé dans le build TatungZ80).
TARGET_MODEL_OPEN Définit le persona expérimentateur OpenZ80 comme modèle cible exclusif (utilisé dans le build OpenZ80 ; active INCLUDE_OPEN_DRIVERS).
INCLUDE_DBGSH Compile le shell de débogage ICE sur le canal USB CDC 1. Présent uniquement dans les variantes de build DBGSH.
CMAKE_BUILD_TYPE=Debug Active les symboles de débogage et désactive l’optimisation. Requis pour le débogage au niveau source avec GDB.
CMAKE_BUILD_TYPE=Release Optimisation complète (-O3). Utilisé pour le firmware de production.

Commandes de Compilation

# 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
Le script build_tzpuPico.sh incrémente automatiquement le numéro de version lors d'une compilation réussie et copie les fichiers de sortie versionnés vers fw/uf2/ (UF2 du bootloader) et fw/bin/ (binaire applicatif pour OTA). L'UF2 du Bootloader est utilisé uniquement pour le flashage initial par stockage de masse USB. Les binaires des slots applicatifs utilisent le format binaire brut (pas UF2) car ils résident à des adresses Flash non standard.

Sélection du Mode Réseau ESP32

Pour changer de mode réseau, copiez le fichier de configuration pré-construit approprié vers sdkconfig avant la compilation :
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

Sites de Référence

Ressource Lien
Page du projet picoZ80 /picoz80/
Manuel utilisateur picoZ80 /picoz80-usermanual/
Page du projet pico6502 /pico6502/
Documents de conception (dépôt 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 — les notes de conception du simulateur, des decks cassette virtuels, de l’interface périphériques/interlink, des modèles de périphériques Z80 et de l’imagerie disque X1
Étude de timing (dépôt projects/tzpuPico/picoZ80_vs_realZ80_timing.md) Données sources de la section Fidélité du Timing Bus
Datasheet RP2350 datasheets.raspberrypi.com
Référence PIO RP2350 datasheets.raspberrypi.com — Appendix B
Documentation Pico SDK raspberrypi.github.io/pico-sdk-doxygen
Référence technique ESP32-S3 docs.espressif.com
Guide de programmation ESP-IDF docs.espressif.com/esp-idf
Manuel utilisateur CPU Zilog Z80 zilog.com
Documentation OpenOCD openocd.org
Aperçu du projet sur X (Twitter) engineerswork1

Avis Réglementaire sur les Communications Sans Fil

Cet appareil intègre un module sans fil ESP32-S3-PICO-1 capable d'émettre dans la bande ISM 2,4 GHz, ce qui en fait un émetteur intentionnel au titre des réglementations de radiofréquence dans le monde entier (incluant le FCC Part 15 Subpart C aux États-Unis et la Directive Équipements Radio 2014/53/UE dans l'Union Européenne) lorsque le firmware WiFi est installé.
Configuration Livrée
Tel que livré, la carte picoZ80 est flashée avec le firmware NCM uniquement (sdkconfig.mode_ncm_only). Dans cette configuration, la radio WiFi est complètement désactivée et les composants du réseau d'adaptation d'antenne de l'ESP32-S3 ne sont pas assemblés sur le PCB. Comme aucune émission RF n'a lieu, l'appareil n'est pas un émetteur intentionnel et ne nécessite pas de certification FCC, CE/RED ou équivalente. Il peut être vendu, distribué ou offert sans autorisation réglementaire.
Ajout du WiFi
Les utilisateurs finaux peuvent assembler le réseau d'adaptation d'antenne et flasher le firmware avec WiFi activé (sdkconfig.mode_wifi_only ou sdkconfig.mode_wifi_and_ncm) pour un usage personnel, expérimental ou éducatif. Une fois le WiFi activé, l'appareil devient un émetteur intentionnel et les règles suivantes s'appliquent :
Bien que le module ESP32-S3-PICO-1 lui-même possède des certifications réglementaires préexistantes (FCC, CE et autres), ces certifications au niveau module ne s'étendent pas automatiquement à un produit fini qui intègre le module. L'exemption de module pré-certifié permet aux amateurs individuels de construire un nombre limité d'appareils pour un usage personnel, expérimental ou éducatif sans obtenir d'autorisation d'équipement séparée.
Limitations Importantes
  • Les appareils avec firmware WiFi ne doivent pas être vendus, proposés à la vente, offerts ou autrement distribués à des tiers à moins que le produit fini n'ait été testé de manière indépendante et n'ait obtenu sa propre autorisation d'équipement (par ex. FCC ID, marquage CE avec évaluation d'un Organisme Notifié) dans la juridiction concernée.
  • La construction de ce projet avec WiFi activé pour un usage personnel en quantités limitées est généralement autorisée sous les dispositions d'usage amateur et expérimental (par ex. FCC § 15.23), à condition que l'appareil ne cause pas d'interférences nuisibles.
  • La vente commerciale avec WiFi activé nécessite une certification FCC/RED (ou équivalente) complète au niveau produit.
  • Les exigences réglementaires varient selon les pays. Les constructeurs en dehors des États-Unis doivent consulter leur autorité nationale de radiofréquence pour les règles applicables.
Responsabilité du Constructeur
Il incombe uniquement au constructeur de s'assurer que tout appareil construit à partir de ces conceptions est conforme à toutes les réglementations de radiofréquence applicables dans sa juridiction. L'auteur fournit ces conceptions pour un usage personnel, éducatif et amateur et ne garantit pas qu'un appareil construit à partir de celles-ci satisfait aux exigences réglementaires pour une distribution commerciale.