pico6502 Guide du Developpeur
Vue d'ensemble
Le firmware du pico6502 est structuré de manière identique au firmware du picoZ80. Il utilise la même architecture RP2350B double cœur, le même modèle mémoire PSRAM, le même système de configuration JSON et le même framework de pilotes — les principales différences sont :
- M6502CPU.c / M6502CPU.h remplacent Z80CPU.c / Z80CPU.h comme fichiers principaux de dispatch et de framework de pilotes.
- Le 6502 n'a pas d'espace d'adressage E/S séparé — il n'y a pas de tableau
ioPtr[]et pas de section JSONiomap. Tous les périphériques sont virtualisés sous forme de blocs de typeFUNCdans la carte mémoire de 64 Ko. - L'interface bus PIO (
M6502.pio) implémente une horloge biphasée PHI1/PHI2 plutôt que le modèle à horloge unique du Z80. - Le répertoire cible de build est
src/model/Base6502plutôt queBaseZ80; la commande de build est./build_tzpuPico.sh 6502. - La clé de section CPU du JSON est
"6502"plutôt que"z80"— commez80[], c'est un tableau indexé par partition flash, et chaque entrée de partition portecputype("6502"ou"6512") etdbeoverride.
Tout le reste — le modèle de files inter-cœurs, les callbacks de cycle de vie des pilotes (
reset_ptr, poll_ptr, task_ptr), la table d'enregistrement virtualFuncMap[], les constantes de type de bloc mémoire, la struct t_6502PSRAM, le protocole du coprocesseur ESP32 et le système de build — est partagé avec la base de code du picoZ80.
Arborescence des sources
Tout le code source se trouve sous
projects/tzpuPico/ à la racine du dépôt. La disposition ci-dessous met en évidence les fichiers spécifiques au pico6502 :
tzpuPico/
├── CMakeLists.txt Fichier de build de niveau supérieur
├── src/
│ ├── CMakeLists.txt Fichier de build au niveau source — ajoutez ici les nouveaux fichiers pilotes
│ ├── M6502.c *** FICHIER CLÉ : le cœur d'émulation 6502 — les 151 instructions
│ │ officielles + tous les opcodes non documentés ; aucune dépendance
│ │ RP2350, il se compile donc nativement pour les tests et le sim
│ ├── M6502CPU.c *** FICHIER CLÉ : orchestrateur du bus sur Core 1, framework de pilotes,
│ │ virtualFuncMap, analyse de la config (miroir de Z80CPU.c)
│ ├── M6502dis.c Désassembleur + assembleur une ligne (table générée à partir
│ │ des propres cas de décodage de M6502.c)
│ ├── M6502.pio Moteur de bus PIO — brochage de l'interface bufferisée v2.3 ; toutes les
│ │ SM du bus se synchronisent sur CLK2_IN
│ ├── dbgsh.c Shell de débogage indépendant du CPU (partagé avec le Z80 — dispatch
│ │ via la vtable t_CpuOps)
│ ├── Z80CPU.c / Z80CPU.h Équivalent Z80 (non utilisé pour les builds pico6502)
│ ├── FSPI.c / FSPI.h Interface SPI flash (partagé)
│ ├── ESP.c / ESP.h Couche de communication ESP32 (partagé)
│ ├── psram.c / psram.h Allocation et gestion de la PSRAM (partagé)
│ ├── cJSON.c / cJSON.h Parseur JSON pour config.json (partagé)
│ ├── include/
│ │ ├── M6502CPU.h *** FICHIER CLÉ : toutes les définitions de types et macros 6502
│ │ ├── M6502.h Définitions du cœur
│ │ ├── M6502dis.h API assembleur/désassembleur
│ │ ├── cpu.h vtable t_CpuOps + CPU_MEMBANKPTR()/CPU_PSRAM() —
│ │ │ ce qui rend dbgsh indépendant du CPU
│ │ ├── dbghooks.h Définitions partagées des points d'arrêt/hooks de débogage
│ │ ├── flash_ram.h Stockage de la config en flash + la région non volatile BBC
│ │ └── drivers/
│ │ ├── Other/
│ │ │ └── Open6502.h En-tête du pilote Open6502
│ │ ├── I8271.h API du modèle de puce Intel 8271 (indépendant de l'hôte)
│ │ ├── WD177x.h API du modèle de puce WD1770/1772/1773 (indépendant de l'hôte)
│ │ ├── MC6854.h API du modèle de puce ADLC Motorola MC6854 (indépendant de l'hôte)
│ │ └── Acorn/
│ │ ├── BBCModelB.h En-tête du pilote BBC-B
│ │ ├── BBCCMT.h En-tête du lecteur de cassettes UEF
│ │ ├── BBCFDC.h En-tête de l'interface disquette 8271 virtuelle
│ │ ├── BBCMMC.h En-tête de la carte MMFS virtuelle
│ │ └── BBCNet.h En-tête du module Econet virtuel
│ ├── drivers/
│ │ ├── Other/
│ │ │ └── Open6502.c Persona vanille/expérimentateur — chargement de ROM, protection
│ │ │ en écriture de la ROM de la banque 0 (l'Apple I tourne dessus)
│ │ ├── I8271.c Modèle de puce FDC Intel 8271 — jeu de commandes de la datasheet, média abstrait
│ │ ├── WD177x.c Modèle de puce FDC WD1770/1772/1773 — Types I–IV, lecture/écriture de piste brute
│ │ ├── MC6854.c Modèle de puce ADLC MC6854 — FIFO au niveau trame, état, interruptions
│ │ └── Acorn/
│ │ ├── BBCModelB.c *** LE PILOTE BBC MODEL B : connaissance de la machine pour le moteur
│ │ │ de bus (BBCModelB_Tuning), supports sideways virtuels,
│ │ │ support OS, RAM Model A, mode Master, persistance CMOS/SWRAM
│ │ ├── BBCCMT.c *** PREMIER PÉRIPHÉRIQUE VIRTUEL : lecteur de cassettes UEF — ACIA 6850 virtuel
│ │ │ + Serial ULA, ligne IRQ virtuelle, LOAD + SAVE, UEF gzip
│ │ ├── BBCFDC.c Glu disquette virtuelle : 8271 à $FE80 ou 1770 + latch (cartes Acorn /
│ │ │ Watford / Master), INT→/NMI via le timer de périphérique, deux
│ │ │ lecteurs à partir d'images SSD/DSD/DDS/DDD/ADF/ADL/MMB (appariement des faces),
│ │ │ cache PSRAM à écriture différée
│ │ ├── BBCMMC.c Carte MMFS virtuelle : recouvrement partiel du User VIA, machine d'état
│ │ │ SD SPI, façade FAT16 sur le MMB, cache à écriture différée
│ │ └── BBCNet.c Econet virtuel : ADLC à $FEA0 + liaisons de stations, handshake en
│ │ quatre temps ↔ AUN via le socket UDP de l'ESP32, anneaux PSRAM
│ ├── test/
│ │ └── M6502/ Tests de conformité natifs sur l'hôte (make -C src/test/M6502 run)
│ └── model/
│ ├── CMakeLists.txt Macro add_m6502_model_targets() — 4 variantes par modèle,
│ │ LTO partiel sur M6502.c + M6502CPU.c
│ ├── Base6502/ Modèle vanille (INCLUDE_OPEN6502_DRIVERS)
│ │ ├── main.c Point d'entrée (boucle de service Core 0 + lancement Core 1, stockage
│ │ │ de la config en flash, horloge/tension, superviseur POR)
│ │ ├── main_memmap_partition_1.ld Script d'édition de liens pour le Slot 1
│ │ └── main_memmap_partition_2.ld Script d'édition de liens pour le Slot 2
│ ├── BBC6502/ Base6502 + INCLUDE_ACORN_DRIVERS (réutilise Base6502/main.c)
│ └── Bootloader/
│ ├── CMakeLists.txt
│ └── main.c Bootloader partagé (identique au picoZ80)
├── sim/
│ ├── 6502/ pico6502sim — simulateur hôte autonome (voir ci-dessous)
│ └── src/
│ ├── host_apple1.c Hôte Apple I synthétique
│ └── host_bbc.c Hôte BBC Model B synthétique (VIA système, clavier, MODE 7)
├── tools/econet/ Démons AUN embarqués (aund, PiEconetBridge) + build.sh,
│ fileserver.sh, examples/, README.md
├── PIPELINE_REDESIGN.md Le dossier de conception de la campagne de vitesse du moteur de bus
├── timing/, *.kvdat, *.py Captures d'analyseur logique (6502 réel vs pico6502) + outils
└── esp32/
├── CMakeLists.txt
├── personas/ config_<Partition1>_<Partition2>.json — Basic6502, Apple-I, BBC-B
└── main/
└── ... Firmware ESP32 (partagé avec le picoZ80 ; GUI web consciente du CPU)
Différences clés avec le picoZ80
Pas d'espace d'adressage E/S séparé
La différence architecturale la plus importante est que le 6502 n'a pas d'instructions
IN/OUT et pas de signal IORQ. Chaque périphérique doit apparaître quelque part dans la carte mémoire de 64 Ko. Dans le picoZ80, les périphériques virtuels peuvent être enregistrés soit via memioPtr[] (mappés en mémoire), soit via ioPtr[] (mappés en E/S). Dans le pico6502 il n'y a que memioPtr[] — mais puisque tous les accès périphériques passent par le bus mémoire, cela couvre tous les cas.
La struct
t_6502PSRAM n'a donc pas de tableau ioPtr[]. La configuration JSON n'a pas de tableau "io". La boucle de dispatch dans M6502CPU.c vérifie uniquement le tableau _membankPtr[] et, pour les blocs MEMBANK_TYPE_FUNC, appelle le handler memioPtr[] correspondant.
Différences de l'interface bus PIO
Le 6502 utilise un schéma d'horloge biphasée non chevauchante. En mode 6502, PHI0 est une entrée externe et le RP2350 en génère PHI1 et PHI2 via une machine d'état du PIO 2 (
m6502_clock_6502) ; en mode 6512, l'hôte fournit les deux phases. Dans les deux cas, chaque machine d'état du bus se synchronise sur CLK2_IN, le PHI2 réellement présent sur le support. C'est différent du Z80, qui utilise un signal CLK unique — et le moteur lui-même est d'une tout autre nature que celui du Z80, à cause de ce que la mise en service du BBC Model B a enseigné : aucun timing de bus côté C. Huit machines d'état sont chargées :
| Bloc PIO | Machines d’état | Rôle |
|---|---|---|
| PIO 0 | m6502_addr (SM 0), m6502_latch (SM 2) |
Moteur d’adresse — échelle / contrôlé / rafale — propriétaire de A0–A15, R/W et SYNC ; latch de lecture transparent échantillonnant au front descendant de PHI2 |
| PIO 1 | m6502_cycle (SM 0), m6502_so, m6502_irq, m6502_nmi (SM 1–3) |
Séquenceur de cycles du chemin contrôlé (états d’attente RDY) ; moniteurs SO / IRQ / NMI |
| PIO 2 | m6502_clock_6502 ou m6502_clock_6512 (SM 0), m6502_data (SM 1) |
Génération de PHI1/PHI2 (ou garé pour le 6512) ; pilote des données d’écriture asservi à R/W |
Les conventions des drapeaux IRQ du PIO :
| Drapeau IRQ | Signal | Signification |
|---|---|---|
| IRQ 0 | Parcage contrôlé | m6502_addr est garé à addr_gated jusqu’à ce que Core 1 pousse un mot et efface le drapeau (E/S, opérations sérialisées et sur blocs en quarantaine) |
| IRQ 4 (PIO 0) | Adresse valide | Une adresse de lecture a été pilotée — m6502_latch démarre une capture |
| IRQ 4 (PIO 1) | NMI | Broche d’interruption non masquable au niveau bas ; Core 1 détecte le front |
| IRQ 5 | IRQ | Broche d’interruption masquable au niveau bas ; échantillonnée par instruction (niveau) |
| IRQ 6 | SO | Broche Set Overflow activée |
Programmation PIO — Comment le code C pilote les cycles bus
Sur le picoZ80, Core 1 pousse des séquences d'instructions
out exec de 16 bits dans une machine d'état de cycle pour chaque transaction bus. Le chemin rapide du pico6502 fait quelque chose de différent : Core 1 pousse un seul mot de 32 bits {instr:16 | ~addr:16} vers la machine d'état d'adresse et ne touche jamais au timing. La SM charge l'adresse inversée dans X, exécute le demi-mot supérieur (set y, count-1 pour une rafale de lecture de count octets, jmp wdrive pour une écriture), et pilote le bus sur sa propre grille « front descendant PHI2 frais + tADS ». La coordination pour un fetch d'instruction en rafale :
Core 1 (C code) PIO State Machines
───────────── ──────────────────
1. Core calls fetchOpcode → m6502_addr: idling on the ladder — after each
m6502FetchOpcodeFused(addr) genuine PHI2 fall + tADS it polls the FIFO at
│ 4 rungs (~cycles 17/30/43/68)
2. Shadow valid for addr? ──yes──→ (serve: byte returned to the core NOW; a no-capture
│ no read may still run so the bus stays identical)
3. Push {set y,5 | ~addr} ─────────→ m6502_addr: out x,16 / out exec,16
(burst of 6) read_next: mov pins,!x → A0–A15 driven
│ irq set 4 → "address valid"
│ m6502_latch: wait irq4, wait PHI2 rise, spin while
│ high, sample AFTER the fall, push
│ m6502_addr: jmp x-- (address ascends), next fall,
│ drive again … six bytes, one per cycle
4. Pop RX FIFO ←─────────────────── one capture word per driven address
verify against shadow (must match)
extra bytes → harvest buffer
│
5. Core executes; operands come
from the harvest buffer / shadow
with zero bus operations
WRITE: push {data} to m6502_data first, then {jmp wdrive | ~addr} to m6502_addr.
wdrive: set R/W low, drive address — m6502_data (wait 0 gpio RNW) drives D0–D7,
start_addr restores R/W high at the next fall (+~45ns hold) — one cycle, one strobe.
I/O: push a gated word (irq0 handshake) — the classic serialised cycle via m6502_cycle,
followed by the I/O chase (a no-capture park read of $FFFC).
Pour les fondamentaux du PIO et le mécanisme
out exec, voir le Guide Technique picoZ80 — Plongée dans l'architecture PIO ; pour la description complète de l'échelle, de la rafale, du shadow, du serve et de la chasse E/S, voir le Guide Technique pico6502. Les points spécifiques au 6502 qu'un auteur de pilote doit connaître :
- La spéculation est optionnelle et déclarée par le pilote. Rien au-delà de l'étape 2 ne se produit tant que la fenêtre E/S n'est pas connue (
m6502IoKnown()). Une persona la déclare — ainsi que la fenêtre d'écriture shadow, le registre de pagination et la profondeur de rafale — via sa fonction de tuning (ci-dessous), le JSON, ou lebuspipe iorangedu shell. - Le shadow est un oracle, pas un cache. Chaque octet venant du bus lui est comparé ; une divergence met en quarantaine ce bloc de 256 octets sur le chemin contrôlé et incrémente un compteur qui doit être à zéro après tout test d'endurance. Tout ce qu'un hôte peut modifier dans le dos du CPU (régions paginées, ROM ignorant les écritures, E/S) doit être déclaré afin que le shadow y soit invalidé ou jamais construit.
- Synchronisation sur la phase d'horloge — tout attend sur
CLK2_IN, jamais sur une copie interne. - Pas de BUSREQ/BUSACK — le 6502 n'a pas de mécanisme de requête de bus ; la libération du bus relève du domaine DBE de l'hôte.
- RDY au lieu de WAIT — honoré sur le chemin contrôlé uniquement ; aucun des hôtes testés ne le bascule.
Struct t_6502PSRAM
Équivalent de
t_Z80PSRAM dans le picoZ80, mais sans le tableau ioPtr[] :
// src/include/M6502CPU.h (simplified)
typedef struct {
uint8_t RAM[MAX_MEMORY_BANKS * MEMORY_PAGE_SIZE]; // 64 banks × 64KB = 4MB RAM/ROM area
MemoryFunc memPtr[MEMORY_PAGE_SIZE]; // 64K per-byte read redirect (PTR type)
MemoryFunc memioPtr[MEMORY_PAGE_SIZE]; // 64K memory-mapped handler array (FUNC type)
// NOTE: no ioPtr[] — the 6502 has no separate I/O space
} t_6502PSRAM;
Signature de la fonction handler
Le typedef
MemoryFunc est le même que pour le picoZ80 :
// Handler called for MEMBANK_TYPE_FUNC blocks on every access typedef uint8_t (*MemoryFunc)(M6502CPU *cpu, bool read, uint16_t addr, uint8_t data); // Parameters: // cpu — pointer to the M6502CPU struct (access PSRAM, state, queues via this) // read — true for a 6502 read cycle (RNW=1), false for a write cycle (RNW=0) // addr — 16-bit address that triggered this handler // data — byte written (only meaningful when read=false) // Return: // byte to place on the data bus (only meaningful when read=true)
Comme il n'y a pas d'espace E/S, il n'y a pas de typedef
IOFunc séparé — MemoryFunc gère tous les accès périphériques.
Shell de débogage — Un seul shell, deux CPU
src/dbgsh.c est compilé dans les deux familles de firmware et dispatche via la vtable t_CpuOps de src/include/cpu.h. Les drapeaux de capacité de la vtable — hasIoSpace, hasM1Cycle, hasInterruptModes, tous faux sur le 6502 — plus une compilation conditionnelle par CPU façonnent le jeu de commandes : le build Z80 compte 61 commandes, le build 6502 45. Les seize commandes exclues de la compilation sur le 6502 sont celles des E/S et des périphériques Z80 (iomap, in, out, fwait, iowait, intcount, dev, stall, sig, dma, ctc, pio, ser, rtc, prn) ; busdiag est remplacé par une implémentation spécifique au 6502 plutôt que supprimé. En échange, le 6502 gagne les diagnostics de mise en service de la carte — busdiag, busdiag clk, busdiag watch [n] et piodbg — écrits avant le matériel v2.3 afin qu'une carte toute neuve puisse être diagnostiquée depuis le shell (voir le Guide Technique — Architecture du shell de débogage).
Framework de pilotes
Le framework de pilotes de
M6502CPU.c est structurellement identique au framework de pilotes du picoZ80. Reportez-vous à la section Guide du Développeur picoZ80 — Framework de pilotes pour la description complète de :
- La table d'enregistrement
virtualFuncMap[]et la façon dont le champ JSON"name"est mis en correspondance avec une fonction d'initialisation de pilote. - Le typedef
VirtualFuncet la rechercheM6502CPU_getVirtualFunc(). - Le flux d'initialisation en deux passes (passe de validation puis passe de configuration).
- Les callbacks de cycle de vie des pilotes :
reset_ptr,poll_ptr,task_ptr.
Les seules différences de nommage sont :
| picoZ80 | pico6502 | Notes |
|---|---|---|
struct Z80CPU |
struct M6502CPU |
Mêmes champs, même disposition |
Z80CPU_getVirtualFunc() |
M6502CPU_getVirtualFunc() |
Même logique de recherche |
Z80CPU_cpu() |
M6502CPU_cpu() |
Point d’entrée de la boucle chaude de Core 1 |
Z80CPU_readMem() |
M6502CPU_readMem() |
Dispatch des lectures mémoire |
Z80CPU_writeMem() |
M6502CPU_writeMem() |
Dispatch des écritures mémoire |
Z80CPU_readIO() |
— | Pas d’équivalent — le 6502 n’a pas d’espace E/S |
Z80CPU_writeIO() |
— | Pas d’équivalent — le 6502 n’a pas d’espace E/S |
cpu->_z80PSRAM |
cpu->_6502PSRAM |
Pointeur vers la struct PSRAM |
Le pilote Open6502
Deux pilotes de machine sont enregistrés dans le
virtualFuncMap[] de M6502CPU.c. Open6502 (src/drivers/Other/Open6502.c, INCLUDE_OPEN6502_DRIVERS) est calqué sur le pilote expérimentateur Open du côté Z80 et est délibérément minimal et indépendant de la machine : il charge des images ROM au niveau pilote dans les tranches bank:addr configurées, marque les blocs ROM de la banque 0 en lecture seule, et laisse par ailleurs l'espace d'adressage exactement tel que le tableau JSON "memory" l'a défini — une persona nue laisse tout passer vers l'hôte. La réplique d'Apple I tourne dessus. BBC-B (src/drivers/Acorn/BBCModelB.c, INCLUDE_ACORN_DRIVERS, modèle BBC6502 uniquement) est le premier pilote de machine, décrit dans sa propre section ci-dessous ; la famille Acorn porte également trois interfaces qui se lient à la persona et sont #includees après elle : BBCCMT.c (lecteur de cassettes UEF), BBCFDC.c (contrôleur de disquettes virtuel — un Intel 8271 ou un WD1770 derrière un latch de carte, par-dessus les modèles de puce indépendants de l'hôte de src/drivers/I8271.c et src/drivers/WD177x.c), BBCMMC.c (carte MMFS virtuelle) et BBCNet.c (module Econet virtuel par-dessus le modèle MC6854 de src/drivers/MC6854.c).
Ce qu'il n'offre délibérément pas, ce sont les cartes d'interface. La persona Open du Z80 en propose neuf (MZ-1R12/18/23/37, PIO-3034, MZ-8BIO3, MZ-1E24, MZ-1E05, Celestite), mais chacune d'elles est écrite contre t_Z80CPU et s'attache via la table des ports E/S du Z80 — que le 6502 n'a pas. interfaceFuncMap est documenté dans le source comme le point d'extension pour l'arrivée de cartes natives 6502.
Boucle de dispatch de Core 1
La boucle de Core 1 dans
M6502CPU_cpu() est le monde du processeur émulé : contrairement au côté Z80, où le vrai Z80 est le maître du bus et Core 1 répond à ses cycles, ici Core 1 est le CPU et les cycles bus sont ses propres accès mémoire. La boucle :
- Superviseur de mise sous tension : maintenir l'hôte en reset jusqu'à ce que les services de Core 0 soient prêts (plafond de 15 s) ; garer R/W au niveau haut.
- Servir les requêtes de maintien/parcage de Core 0 avec une boucle d'attente résidant en RAM (sûre pendant l'effacement de la flash), la ligne /RES anti-rebond (la séquence de reset s'exécute après relâchement), le maintien de libération de bus DBE du 6512, et le détecteur de front /NMI.
- Appeler
g_fastTickHooksi un pilote en a installé un (le cadencement en bauds du lecteur de cassettes), et lepoll_ptrdes pilotes environ toutes les 250 ms. - Exécuter
m6502_run(1024)— ou une instruction à la fois en mode pas à pas, point d'arrêt ou trace, avec vérification optionnelle par relecture de l'opcode. À l'intérieur, les callbacksfetchOpcode/readMem/writeMemdu cœur résolvent chaque adresse contre_membankPtr[addr >> 9]et dispatchent selon le type de bloc : PHYSICAL / PHYSICAL_HW (une opération bus via le moteur ci-dessus), RAM / ROM (PSRAM), FUNC (memioPtr[addr]), PTR (redirectionmemPtr[]). - Échantillonner les interruptions une fois par instruction — l'instantané du /IRQ physique combiné par OU avec
g_m6502VirtIrq— et délivrer IRQ/NMI via une séquence de sept cycles qui vide tout prefetch en cours.
// Simplified M6502CPU_readMem / M6502CPU_writeMem dispatch
uint8_t M6502CPU_readMem(M6502CPU *cpu, uint16_t addr)
{
uint32_t entry = cpu->_membankPtr[addr >> 9];
uint8_t type = (entry >> 24) & 0xFF;
uint8_t bank = (entry >> 16) & 0xFF;
uint32_t base = (entry & 0xFFFF) << 9;
switch(type)
{
case MEMBANK_TYPE_RAM:
case MEMBANK_TYPE_ROM:
return cpu->_6502PSRAM->RAM[(bank * MEMORY_PAGE_SIZE) + addr];
case MEMBANK_TYPE_FUNC:
if(cpu->_6502PSRAM->memioPtr[addr] != NULL)
return cpu->_6502PSRAM->memioPtr[addr](cpu, true, addr, 0);
return 0x00;
case MEMBANK_TYPE_PTR:
if(cpu->_6502PSRAM->memPtr[addr] != NULL)
return cpu->_6502PSRAM->memPtr[addr](cpu, true, addr, 0);
return 0x00;
case MEMBANK_TYPE_PHYSICAL:
default:
return 0xFF; // Pass-through to real host hardware
}
}
Écrire un nouveau pilote
Le processus d'écriture d'un pilote pico6502 est identique au processus picoZ80 décrit dans le Guide du Développeur picoZ80 — Écrire un nouveau pilote. Les six étapes sont les mêmes :
- Créer un fichier d'en-tête dans
src/include/drivers/<Family>/MyDriver.h - Créer un fichier d'implémentation dans
src/drivers/<Family>/MyDriver.c - Activer la définition de compilation
INCLUDE_<FAMILY>_DRIVERSde la famille pour le build du modèle (les sources des pilotes sont#includees in situ, et non listées comme unités de compilation séparées) - Inclure le pilote dans
M6502CPU.csous la garde#ifdef INCLUDE_<FAMILY>_DRIVERS - Enregistrer le pilote dans
virtualFuncMap[]dansM6502CPU.c - Ajouter une entrée de pilote JSON dans
config.json
La différence clé est qu'au lieu d'un paramètre
Z80CPU *cpu, toutes les fonctions reçoivent un paramètre M6502CPU *cpu, et que l'on accède à la PSRAM via cpu->_6502PSRAM plutôt que cpu->_z80PSRAM. Il n'y a pas d'emplacements ioPtr[] à installer — tous les périphériques virtuels sont enregistrés dans memioPtr[].
Modèle de pilote minimal
Ce qui suit montre un pilote de périphérique 6502 minimal — un VIA 6522 (Versatile Interface Adapter) virtualisé à l'adresse
0xC000–0xC00F :
// src/include/drivers/Acorn/VIA6522.h #ifndef VIA6522_H #define VIA6522_H #include "M6502CPU.h" int VIA6522_Init(M6502CPU *cpu, t_drvConfig *drvConfig, int pass); #endif
// src/drivers/BBC/VIA6522.c
#include "../../include/M6502CPU.h"
#include "../../include/drivers/Acorn/VIA6522.h"
// --- Internal state ---
typedef struct {
uint8_t regs[16]; // VIA internal registers (ORB, ORA, DDRB, DDRA, T1CL, T1CH, ...)
bool irqPending;
} t_VIA6522State;
static t_VIA6522State ViaState = { .irqPending = false };
// --- Memory handler (called for every access to 0xC000–0xC00F) ---
static uint8_t VIA6522_Handler(M6502CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
uint8_t reg = addr & 0x0F; // VIA has 16 internal registers
if(read)
{
return ViaState.regs[reg];
}
else
{
ViaState.regs[reg] = data;
// TODO: implement timer, shift register, port logic as needed
return 0;
}
}
// --- Reset handler (called when host system resets) ---
static uint8_t VIA6522_Reset(M6502CPU *cpu)
{
memset(&ViaState.regs, 0, sizeof(ViaState.regs));
ViaState.irqPending = false;
return 0;
}
// --- Init function (called during JSON config parse) ---
int VIA6522_Init(M6502CPU *cpu, t_drvConfig *drvConfig, int pass)
{
if(pass == 0)
{
// Validation pass — just check the name matches
return (strcasecmp(drvConfig->name, "via6522") == 0) ? 1 : 0;
}
// Configuration pass — install handlers
// Install the handler for all 16 VIA registers at 0xC000–0xC00F
for(uint16_t addr = 0xC000; addr <= 0xC00F; addr++)
{
cpu->_6502PSRAM->memioPtr[addr] = VIA6522_Handler;
}
// Register reset callback
cpu->reset_ptr = VIA6522_Reset;
debugf("VIA6522: Initialised at 0xC000–0xC00F\n");
return 1;
}
Configuration JSON
Pour activer le pilote VIA6522, ajoutez un bloc
FUNC à la carte mémoire et une entrée de pilote :
{
"rp2350": {
"6502": {
"memory": [
{ "enable":1, "addr":"0x0000", "size":"0xC000",
"type":"RAM", "bank":0, "task":"", "file":"" },
{ "enable":1, "addr":"0xC000", "size":"0x0010",
"type":"FUNC", "bank":0, "task":"via6522", "file":"" },
{ "enable":1, "addr":"0xE000", "size":"0x2000",
"type":"ROM", "bank":0, "task":"", "file":"/ROM/bios.rom" }
],
"drivers": [
{ "enable":1, "name":"via6522" }
]
}
}
}
Enregistrement dans virtualFuncMap
Ajoutez une entrée à la table
virtualFuncMap[] dans M6502CPU.c. La chaîne name est comparée sans distinction de casse au champ "name" du pilote dans le JSON :
// In M6502CPU.c — add to virtualFuncMap[]
static const t_VirtualFuncMap virtualFuncMap[] = {
{ "Open6502", Open6502_Init }, // existing entries
{ "BBC-B", BBCModelB_Init },
{ "via6522", VIA6522_Init }, // your new driver
{ NULL, NULL } // terminator
};
Modification de CMakeLists.txt
Les sources des pilotes suivent le modèle du Z80 : elles sont
#includees in situ par M6502CPU.c sous la garde INCLUDE_<FAMILY>_DRIVERS de la famille (c'est ainsi qu'Open6502.c est intégré sous INCLUDE_OPEN6502_DRIVERS), de sorte que c'est la définition de compilation qui compte plutôt qu'une entrée de fichier source. Activez la définition de votre famille pour le modèle dans les fichiers de build :
# The Acorn family already exists — it is what the BBC6502 model enables: # src/model/CMakeLists.txt # add_m6502_model_targets(BBC6502 "" "INCLUDE_OPEN6502_DRIVERS;INCLUDE_ACORN_DRIVERS") # A new family gets its own INCLUDE_<FAMILY>_DRIVERS define in the model's list. # And in M6502CPU.c, alongside the existing includes: #ifdef INCLUDE_ACORN_DRIVERS #include "../drivers/Acorn/BBCModelB.c" #include "../drivers/Acorn/BBCCMT.c" // binds to the persona, hence after it #include "../drivers/Acorn/VIA6522.c" // your new driver #endif
Compilation et test
# From the project root — Base6502 only, or the full build for BBC6502 ./build_tzpuPico.sh 6502 ./build_tzpuPico.sh # Flash via USB mass-storage cp build/bin/model/BBC6502/BBC6502_0x10020000.uf2 /media/$USER/RPI-RP2/ # Or OTA: upload via http://<device-ip>/ota-rp2350.htm # Or prove the change first with no hardware at all: cd sim/6502 && mkdir -p build && cd build && cmake .. && make -j$(nproc) cd ../.. && ./run.sh apple1
Modèles de hooks mémoire
Comme le 6502 n'a pas d'espace E/S, tous les modèles de hooks utilisent
memioPtr[]. Les modèles sont les mêmes que ceux décrits dans le Guide du Développeur picoZ80 — Modèles de hooks mémoire, le modèle de handler de port E/S étant remplacé par un handler mappé en mémoire sur une plage d'adresses quelconque. Les modèles clés pour les pilotes 6502 sont :
Bloc FUNC — Périphérique virtuel
Installer un handler pour une plage d'adresses contiguë. C'est le modèle standard pour toute émulation de périphérique 6502 :
// Install handler for address range 0xD000–0xD0FF (256-byte peripheral block)
for(uint16_t addr = 0xD000; addr <= 0xD0FF; addr++)
{
cpu->_6502PSRAM->memioPtr[addr] = MyPeripheral_Handler;
}
Interception des écritures RAM
Intercepter les écritures dans une région RAM tout en continuant à autoriser les lectures depuis la PSRAM. Utile pour suivre les accès en page zéro ou détecter les écritures à des adresses spécifiques :
// Handler intercepts writes; passes reads to PSRAM
static uint8_t ZeroPage_Intercept(M6502CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
if(read)
{
// Read from PSRAM normally
return cpu->_6502PSRAM->RAM[addr];
}
else
{
// Write to PSRAM and update shadow state
cpu->_6502PSRAM->RAM[addr] = data;
MyDriver_OnZeroPageWrite(addr, data);
return 0;
}
}
// In init: set the block type to FUNC and install the handler
cpu->_membankPtr[0x0000 >> 9] = MEMBANK_ENCODE(MEMBANK_TYPE_FUNC, 0, 0x0000);
cpu->_6502PSRAM->memioPtr[0x0000] = ZeroPage_Intercept;
// ... repeat for each 512-byte block in the range
Adresses éparses / individuelles
Pour un matériel où seules quelques adresses comptent au sein d'une région plus vaste (par ex. un décodeur d'adresses 4 bits), installez le handler uniquement sur ces adresses précises et laissez le reste renvoyer
0xFF ou les données de la PSRAM :
// Only addresses 0xFFFE and 0xFFFF (6502 reset vector) are intercepted cpu->_6502PSRAM->memioPtr[0xFFFE] = ResetVector_Lo_Handler; cpu->_6502PSRAM->memioPtr[0xFFFF] = ResetVector_Hi_Handler;
Personas machine — Apple I et BBC Model B
Trois machines sont livrées sous forme de personas, appariées une par partition flash dans
esp32/personas/config_<Partition1>_<Partition2>.json : Basic6502 (« Vanilla 6502 » — un simple 6502 dans le support sans rien de virtualisé ; c'est la persona avec laquelle mettre en service une nouvelle carte), Apple-I (l'hôte Apple I / réplique RC6502 sur Open6502 ; son PIA 6821, sa RAM et sa ROM moniteur sont tous sur l'hôte, et le moniteur Woz et le BASIC tournent sur la carte v2.3a), et BBC-B — le Model B sur son propre pilote.
Le pilote BBC-B — La connaissance de la machine sous forme de pilote
BBCModelB.c est une persona à passage physique : le Model B conserve sa RAM, sa ROM et ses périphériques, et le pilote apporte les faits sur la carte mémoire dont le moteur de bus a besoin avant de pouvoir spéculer. Cette seule déclaration fait passer le CPU du mode vanille conservateur au moteur pleine vitesse éprouvé par la campagne, le bus restant identique octet pour octet du début à la fin. Le modèle est la fonction de tuning, appelée par l'analyseur de configuration lorsque le pilote est sélectionné, avant que les propres clés JSON de l'objet pilote ne soient appliquées, de sorte que chaque valeur est une valeur par défaut que la configuration peut remplacer :
void BBCModelB_Tuning(t_M6502CPU *cpu)
{
g_ioRegionLo = 0xFC00; // FRED/JIM/SHEILA: the engine never speculates into or across it.
g_ioRegionHi = 0xFF00;
g_shadowWrLo = 0x0000; // main RAM: this CPU is the sole writer -> write-through shadow.
g_shadowWrHi = 0x8000;
g_pageRegAddr = 0xFE30; // ROMSEL, partially decoded (hence the mask)...
g_pageRegMask = 0xFFF0;
g_pageRegionLo = 0x8000; // ...banks the sideways ROM window.
g_pageRegionHi = 0xC000;
g_burstSpec = 6; // MMFS-safe at 6 with ROMSEL declared (the map barrier).
}
Un pilote pour une autre machine fournit les mêmes cinq faits pour sa propre carte mémoire. Les machines sœurs dotées d'un silicium ou d'une carte mémoire différents méritent leurs propres pilotes — le 65C12, la RAM shadow et la pagination du Master sont aujourd'hui gérés comme un
mastermode de ce pilote, et seraient scindés comme première sœur de ce type. Par-dessus le passage physique, le pilote virtualise ce dont la vraie machine peut être équipée en extension :
- Supports sideways ROM/RAM — chacune des seize banques ROMSEL peut être peuplée virtuellement depuis la PSRAM (banques de pool 1–4, 16K par support) : une image ROM depuis la SD (ROM virtuelle) ou de la RAM sideways inscriptible. Un support activé prend le pas sur la banque physique ; l'écriture ROMSEL atteint toujours le latch matériel afin que le décodage propre de l'hôte reste cohérent, mais tant qu'une banque remplacée est sélectionnée, chaque lecture et écriture de
$8000–$BFFFest servie depuis la PSRAM. C'est ainsi que MMFS ou une ROM de langage est ajoutée sans ouvrir la machine. - Support OS — une image OS de 16K virtualisée sur
$C000–$FFFFavec la fenêtre E/S laissée en passage et les vecteurs$FF00–$FFFFgérés octet par octet ;fileoffsetextrait l'OS d'une image plus grande (MegaROM). - RAM Model A — 16K de RAM adossée à la PSRAM superposée en
$4000–$7FFFafin que l'OS voie un Model B de 32K. Zéro cycle bus — ce qui est aussi la réserve : l'ULA vidéo lit la mémoire écran dans la DRAM de l'hôte et ne peut pas voir la RAM virtuelle, de sorte que le comportement de l'écran sur un vrai Model A reste à vérifier (une option d'écriture traversante, comme celle utilisée par le chemin vidéo PCW du picoZ80, est la suite probable). - Mode Master — shadow LYNNE de 20K, HAZEL de 8K et ANDY de 4K avec ACCCON (
$FE34) virtualisé, une CMOS/RTC virtuelle sur le VIA système, et le cœur CMOS forcé car MOS 3.x est du code 65C12. Attend une image MOS 3.x dans le support OS. Le DFS (banque 9) et l'ADFS (banque 13) de la MegaROM ne sont préréglés que lorsqueBBCFDC_MasterFdc()signale un 1770 virtuel sur la carte Master (ou quepresetdiscromsle force) ; sinon la banque 13 est laissée hors du masque d'alias afin que la ROM de système de fichiers propre à l'hôte apparaisse, conformément à la valeur CMOS par défaut FILE 13. - Persistance — CMOS virtuelle et RAM sideways sauvegardées sur SD (par défaut), dans la région non volatile de la flash (
flash_ram.h, écrite via un message inter-cœursMSG_WRITE_NVRAMpendant que Core 1 est garé en RAM), ou pas du tout. - Interfaces — le tableau
if[]lieCMT-UEF(lecteur de cassettes),BBC-FDC(8271 ou WD1770 virtuel avec deux lecteurs),BBC-MMC(carte MMFS virtuelle) etBBC-NET(module Econet virtuel, AUN sur IP). Chacune est capturée parM6502CPU_configDriversFromJSONdans l'appelSetConfigde l'interface avant que l'init de la persona ne s'exécute, et chacune expose des points d'entréeInit/Remap/FastTick/Poll/HostResetque la persona appelle.
BBCCMT — le premier périphérique virtuel, et la ligne IRQ virtuelle
BBCCMT.c est le modèle pour tout périphérique virtuel 6502 qui interrompt. Le BBC charge les cassettes via un ACIA 6850 ($FE08/09) alimenté par le Serial ULA ($FE10), et le chargement est piloté par interruption : le MOS active l'interruption de réception de l'ACIA et consomme les octets et les événements de porteuse DCD depuis son handler d'IRQ. Le pilote virtualise les deux puces avec des entrées FUNC par octet, lit une image UEF (éventuellement gzippée — décompressée sur place avec le puff sans malloc de zlib) depuis un tampon de transit en PSRAM chargé de manière asynchrone via la liaison ESP, et cadence les octets à de vrais 1200/300 bauds depuis un g_fastTickHook qui s'exécute dans la boucle de Core 1 même sans cassette dans le lecteur. Chaque événement lève g_m6502VirtIrq, que le cœur combine par OU avec le /IRQ physique lors de son échantillonnage par instruction — de sorte qu'un périphérique virtuel interrompt exactement comme le ferait un périphérique réel, sans aucune implication du PIO. Les écritures passent toujours aux puces physiques (le vrai relais moteur cliquète), les trois bits d'activation d'interruption de l'ACIA physique étant masqués afin qu'il ne puisse pas affirmer un /IRQ que le MOS ne pourrait pas voir dans le registre d'état virtuel. SAVE est modélisé d'après la ROM MOS 1.20 elle-même (B-em n'a pas de modèle d'émission) : le SAVE du CFS est piloté par l'interruption d'émission, donc l'ACIA virtuel abaisse TDRE lors d'une écriture de donnée, le réaffirme un temps-octet plus tard et lève l'IRQ chaque fois que TDRE est positionné avec l'interruption d'émission activée ; le flux capturé est encodé dans un nouvel UEF nommé d'après l'en-tête de bloc CFS. La sémantique des registres suit B-em 0.6, contre lequel le LOAD du CFS du MOS est éprouvé ; le lecteur a été développé et exercé dans le BBC synthétique du simulateur avant de tourner sur le matériel.
BBCFDC et BBCMMC — Modèles de puce, caches de blocs et propriété du NMI
Les interfaces disquette et MMFS ajoutent des modèles qu'un auteur de périphérique de stockage réutilisera. Premièrement, séparer la puce de l'hôte.
src/drivers/I8271.c modélise l'Intel 8271 purement d'après sa datasheet — les numéros de registre tels que le BBC les décode (+0 commande/état, +1 paramètre/résultat, +2 reset, +4 données), chaque commande, de scan et lecture/écriture jusqu'à read ID, verify, format, seek, specify et les registres spéciaux, la sémantique des bits d'état et de résultat, et le protocole de transfert par requête de données sans DMA pour lequel la famille DFS d'Acorn a été écrite ($8C par octet, $18 à l'achèvement). src/drivers/WD177x.c fait de même pour la famille WD1770/1772/1773 : commandes de Type I avec les vitesses de pas de la datasheet, la mise en rotation et la vérification ; lecture/écriture de secteur de Type II avec les options multi-secteurs et de stabilisation ; Read Address, Read Track (la piste brute avec les gaps, les marques d'adresse et les CRC tels que la datasheet les dispose) et Write Track (l'hôte diffuse une piste brute, la puce décode les marques et le média est formaté avec les ID rencontrés) de Type III ; Force Interrupt de Type IV ; timing moteur marche/arrêt. Aucun des deux modèles ne sait quoi que ce soit des disques : une table de callbacks média (t_I8271Media, t_WD177xMedia) fournit la disponibilité, la protection en écriture, la géométrie, une recherche de pointeur de secteur qui peut répondre PENDING pendant que l'hôte récupère le bloc de façon asynchrone, une horloge, et quoi faire de la sortie d'interruption. BBCFDC.c est la glu Model B autour de l'un ou l'autre, sélectionné par la clé chip, et pour le 1770 ajoute le latch de contrôle des lecteurs de la carte — l'extension Acorn ($FE80 : sélection du lecteur, face, densité, reset), la carte Watford (même adresse, sa propre disposition de bits) ou le Master 128 ($FE24) — chaque disposition lue dans la ROM DFS écrite pour cette carte. Tout hôte 6502 équipé de l'une ou l'autre puce peut réutiliser le modèle avec sa propre glu.
Deuxièmement, le média comme cache de blocs en PSRAM. Ni un MMB (jusqu'à 100 Mo) ni un jeu d'images de disque n'ont besoin de tenir en mémoire : les deux pilotes maintiennent un cache à correspondance directe d'emplacements de 512 octets (les 1024 emplacements du FDC à 0x070000, les 768 du MMC en haut du pool, tous deux via l'alias PSRAM non caché afin que Core 0 remplisse et que Core 1 serve une seule fenêtre cohérente), récupèrent les blocs manquants avec MSG_READ_SECTOR — un seul en vol, le modèle de puce recevant PENDING et réessayant depuis son tick — et réécrivent les blocs sales avec MSG_WRITE_SECTOR 300 ms après leur dernière mise à jour, la politique de planification différée des pilotes de disquette Z80 à la granularité du secteur. Les réponses sont consultées et retirées de la file inter-cœurs uniquement lorsqu'elles appartiennent au pilote, de sorte que les achèvements des autres pilotes ne sont jamais perturbés. Le FDC mappe les deux faces de chaque lecteur sur jusqu'à deux fichiers (SSD, DSD avec les faces entrelacées par piste, ou un disque à 8192 + n × 204800 dans un MMB), avec une clé de cache lecteur × 2 + index de fichier afin que les fichiers de faces appariés ne partagent jamais un espace de clés ; le MMC enveloppe le MMB dans un secteur de boot, une FAT et un répertoire racine FAT16 synthétiques afin que l'hypothèse de MMFS « 16 premières entrées racine, premier cluster contigu » tienne et que les blocs de la zone de données se mappent directement sur des décalages en octets du MMB.
Troisièmement, possédez la ligne d'interruption que vous virtualisez, et cadencez-la. Le contrôleur de disquettes du Model B pilote /NMI, et un vrai contrôleur est toujours dans la machine ; le contrôleur virtuel ne transmet jamais ses écritures de registre au bus (sur un hôte Model B, les adresses $FE24–$FE2F du Master se décoderaient même vers l'ULA vidéo), et tant qu'il est activé positionne g_m6502NmiPhysMask afin que les fronts /NMI physiques soient comptés et ignorés — l'INT de la puce (8271) ou INTRQ|DRQ (1770) devient le seul front /NMI que le cœur voit. Les changements d'état déclenchant une interruption sont armés dans le modèle de puce et appliqués — état, DRQ et INTRQ ensemble — lorsque le timer de périphérique du cœur se déclenche un temps-octet plus tard (64 µs en FM, 32 µs en MFM), car un handler NMI de système de fichiers qui lit lui-même la puce ne doit jamais trouver l'événement suivant déjà imbriqué dans le handler. En mode non bridé, chaque délai mécanique se réduit à zéro et l'impulsion d'index bascule à chaque lecture d'état, ce dont les boucles d'attente active des ROM ont besoin. Le MMC fait l'équivalent pour le User VIA : les écritures sur le port B ne sont jamais répercutées (une carte MMFS physique les exécuterait), les bits de mode SR sont masqués de l'ACR que voit le vrai VIA, et le bit 2 de l'IER est masqué afin que la vraie puce ne décale ni n'interrompe jamais pour le compte du pilote. Le reset de l'hôte est transmis (HostReset) car la broche RESET du vrai 8271 est sur /RES et un octet laissé en vol à travers un BREAK serait sinon servi au DFS fraîchement démarré. Les changements d'image à chaud arrivent de Core 0 sous la forme CHGD,<drive>,<file>[,<side>,<kind>] avec le 6502 maintenu ; les blocs sales de l'image sortante sont vidés de manière synchrone avant le remontage.
BBCNet — Econet comme pont de protocole
Le module Econet est la première interface dont l'autre extrémité est une autre machine, et il montre comment un périphérique virtuel 6502 atteint le réseau de l'ESP32. Le modèle de puce,
src/drivers/MC6854.c, travaille au niveau trame plutôt qu'au niveau bit : l'hôte lui remet une trame reçue entière, qui s'écoule dans la FIFO de réception de 3 octets un octet par temps-octet de ligne (40 µs à 200 kbit/s) avec ADDRESS PRESENT sur le premier octet et FRAME VALID sur le dernier ; les écritures du CPU remplissent la FIFO d'émission, qui se vide au rythme des octets dans un tampon de trame, TDRA cadençant la boucle « deux octets par NMI » de la ROM, et une écriture dans le registre de terminaison (ou CR2 TX LAST DATA) clôt la trame et la restitue entière. Toute condition de réception ou d'émission qui survient alors que son activation d'interruption est positionnée affirme IRQ, que la glu transforme en front /NMI.
Au-dessus de la puce, BBCNet.c exécute sur le tick rapide de Core 1 une machine d'état qui est le handshake en quatre temps d'Econet vu depuis l'autre extrémité : lorsque la ROM émet un scout, la glu l'acquitte localement après un temps de trame réaliste, prend la trame de données qui suit et l'envoie comme un seul datagramme unicast AUN, et lorsque l'ack AUN du pair arrive le présente à la ROM comme trame finale ; dans l'autre sens, un unicast entrant est proposé à la ROM comme scout, l'ack de la ROM libère la trame de données, et l'ack final de la ROM repart comme ack AUN. Les immédiats et les broadcasts se mappent sur leurs propres types AUN, un scout auquel la ROM ne répond pas sous 50 ms est traité comme « n'écoute pas sur ce port », et un pair qui ne répond pas dans le délai de l'étape (5 s — des allers-retours WiFi de 1–2 s ont été observés) est abandonné, comme le ferait le propre timeout de la ROM. Core 1 ne touche jamais au réseau : les trames vont dans un anneau PSRAM (8 emplacements sortants) et arrivent d'un autre (16 entrants), tous deux à emplacements de 1600 octets à 0x0F0000 via l'alias non caché, et BBCNet_Core0Service() — appelé depuis la boucle principale de Core 0 à chaque itération — ouvre un socket UDP sur l'ESP32 via l'IPC réseau (NET_SOCK avec le type datagramme et le broadcast activé, NET_SEND adressé avec la destination dans l'en-tête de trame, NET_RECV), dérive notre station de l'adresse IP que l'ESP32 rapporte lorsque station vaut 0, et attribue chaque datagramme entrant à une station par son adresse source — l'entrée explicite du serveur de fichiers d'abord, puis la table hosts[], puis la convention AUN. Un pilote qui a besoin du réseau n'a donc besoin d'aucune modification de l'ESP32 ; il lui faut un hook de service sur Core 0 et deux anneaux.
Simulateur hôte (pico6502sim)
sim/6502/ construit pico6502sim : les vraies sources du firmware pico6502 — M6502.c, M6502CPU.c, M6502dis.c, dbgsh.c, model/Base6502/main.c, plus toute la moitié ESP32 avec sa GUI web et une carte SD virtuelle — compilées nativement et exécutées sans affichage sur l'hôte de développement. C'est ainsi que le firmware v2.3a a été éprouvé de bout en bout avant qu'aucun matériel n'existe — shell de débogage, liaison ESP32, configuration des personas et GUI web tous exercés pendant que la carte était encore en cours de routage — et cela reste l'endroit où les nouveaux pilotes sont écrits : l'hôte BBC synthétique et les pilotes Acorn (INCLUDE_ACORN_DRIVERS dans sim/6502/CMakeLists.txt) permettent d'exercer le LOAD et le SAVE du lecteur UEF contre le vrai MOS avant le matériel.
Contrairement au pZ80sim du côté Z80, qui emprunte ses machines hôtes au Common Source Code Project, aucune des cibles 6502 n'est modélisée par CSP — pico6502sim est donc autonome : pas de CSP, pas de Qt, pas de SDL2, pas de FFmpeg. Un compilateur C/C++, pthreads et zlib suffisent (plus pioasm de l'arborescence pico-sdk pour générer M6502.pio.h). Et là où pZ80sim fait avancer Core 1 en ligne depuis l'ordonnanceur de CSP, pico6502sim donne à Core 1 un vrai thread exécutant la propre boucle de M6502CPU_cpu() — le même code que celui que le matériel exécute.
Compilation et exécution
cd sim/6502 mkdir -p build && cd build cmake .. && make -j$(nproc) # Launch a machine (from sim/) ../run.sh apple1 # Apple I, synthetic host, 1.023 MHz ../run.sh bbcmodelb # BBC Model B, synthetic host, 2 MHz — MODE 7 on the terminal ../run.sh bbcmaster # BBC Master 128, headless, 2 MHz (CMOS core)
Options CMake :
-DPICO6502_WITH_ESP32=OFF supprime l'ESP32 virtuel/la GUI web ; -DPICO6502_WITH_DBGSH=OFF supprime le shell de débogage. Ligne de commande : --host=apple1\|bbc\|none, --rom=<file> (ou la variable d'environnement PICO6502_ROM), --verbose, --trace. Les variables d'environnement partagent le préfixe PZ80SIM_ avec pZ80sim (PZ80SIM_SD, PZ80SIM_RUN, PZ80SIM_HTTP_PORT, PZ80SIM_TCP_BASE, PZ80SIM_VERBOSE, PZ80SIM_TRACE) — le lanceur est partagé. Les trois machines peuvent tourner simultanément :
| Machine | cputype | Horloge hôte | GUI web | Bloc TCP série | dbgsh | Modèle d’hôte |
|---|---|---|---|---|---|---|
| Apple I | 6502 | 1,023 MHz | 8110 | 5400–5405 | 5401 | apple1 — hôte synthétique |
| BBC Model B | 6502 | 2 MHz | 8111 | 5410–5415 | 5411 | bbc — hôte synthétique |
| BBC Master 128 | 65C12 | 2 MHz | 8112 | 5420–5425 | 5421 | aucun (sans affichage), cœur CMOS |
Chaque canal série est exposé à la fois comme un pty hôte et comme un port TCP ; le shell de débogage est sur CDC 1 (
tcp base+1), et sur l'Apple I le terminal propre à la machine est le port série virtuel A (tcp base+2). Le lanceur écrit la persona de chaque machine sur sa propre carte SD virtuelle au démarrage (sim/sdcard-<machine> ; sdcard-bbccmt est une carte prête à l'emploi avec le pilote BBC-B, l'interface CMT-UEF et deux cassettes de test). Comme sur le matériel, le firmware 6502 applique config.json dans son stockage flash sur une commande CFG — l'application de la GUI web, ou GET /tasks/reloadcfg — et redémarre dessus ; l'image flash simulée est reconstruite depuis la carte à chaque démarrage sauf si PZ80SIM_KEEP_FLASH=1.
L'hôte Apple I synthétique
sim/src/host_apple1.c modélise la machine contre laquelle la carte est mise en service :
| Plage | Contenu |
|---|---|
$0000–$7FFF |
RAM, 32K |
$D010–$D013 |
PIA 6821 — en miroir sur tout $D000–$DFFF (décodage partiel, comme sur la vraie machine) |
$E000–$FFFF |
Fenêtre ROM de 8K — une image plus petite se charge en haut |
Le clavier et l'affichage sont sur le port série virtuel A, l'entrée convertie en majuscules, Entrée→CR, Retour arrière→
_. Aucune ROM n'est livrée (le moniteur Woz appartient à Apple) : le lanceur cherche dans la bibliothèque de ROM et dans le répertoire roms/APPLE1/ de la carte les noms habituels (apple1.rom, wozmon.rom, …), et --rom= prend le dessus. Sans ROM, un programme intégré de ~35 octets de bannière/écho s'exécute — ce qui prouve le fetch, l'exécution, le branchement, l'appel et le PIA dans les deux sens : sur une carte qui n'a jamais fonctionné, l'essentiel de ce que l'on veut établir en premier.
L'hôte BBC Model B synthétique
sim/src/host_bbc.c (septembre 2026) modélise suffisamment d'un Model B pour que le MOS démarre et pour que la persona, les supports sideways et le lecteur de cassettes puissent être développés contre lui. Son VIA 6522 système — timers T1/T2, IFR/IER, le latch adressable IC32, le bus de données lent et la matrice clavier — est porté depuis B-em 0.6 (Tom Walker), avec un vsync à 50 Hz sur CA1 ; le timer à 100 Hz et le vsync sont ce dont le démarrage du MOS a besoin. bbc_init() charge le MOS de 16K (OS 1.20) puis basic2.rom dans la banque 15 et dfs.rom dans la banque 14 depuis le même répertoire (SD/default/roms/BBC/).
| Plage | Contenu |
|---|---|
$0000–$7FFF |
RAM, 32K |
$8000–$BFFF |
16 banques sideways sélectionnées par ROMSEL ($FE30) |
$C000–$FBFF, $FF00–$FFFF |
MOS |
$FC00–$FDFF |
FRED / JIM — se lisent 0xFF |
$FE00–$FEFF |
SHEILA — VIA système modélisé ; registres du CRTC et de l’ULA vidéo seulement stockés ; User VIA, FDC, Econet et Tube se lisent comme absents ; ACIA inerte (le BBCCMT du firmware le virtualise) |
Les caractères clavier sur le port série virtuel A sont mappés dans la matrice avec une séquence appui / maintien / relâchement / pause ; le télétexte MODE 7 est rendu en direct en ANSI sur le même terminal, déclenché par changement à environ 12 Hz depuis l'adresse de début du CRTC. Les modes graphiques ne sont pas dessinés. Les liens clavier sont réglés pour MODE 7 et sans auto-boot.
Attacher votre propre machine hôte
Ajouter un modèle de machine est une interface à quatre fonctions dans
sim/src/sim6502.h :
typedef struct {
uint8_t (*fetchOp) (uint16_t addr); // opcode fetch (SYNC asserted)
uint8_t (*readMem) (uint16_t addr); // data read
void (*writeMem)(uint16_t addr, uint8_t data); // write
void (*hostReset)(void); // host-side reset
} t_Sim6502BusOps;
void sim6502_setBusOps(const t_Sim6502BusOps *ops); // NULL restores the floating bus
void sim6502_assertIRQ(bool active); // level: re-assert every cycle while low
void sim6502_assertNMI(bool active); // edge-triggered
fetchOp est distinct de readMem parce qu'un vrai 6502 n'affirme SYNC que pour le fetch de l'opcode — les garder distincts exerce les deux mêmes chemins du firmware que le matériel. Sans hôte attaché, les lectures bus renvoient 0xFF : un bus 6502 non piloté avec des résistances de rappel se lit 0xFF, et 0xFF est ISC $nnnn,X — une instruction lecture-modification-écriture non documentée qui ne s'arrête ni ne branche, de sorte que l'émulation parcourt librement l'espace d'adressage exactement comme le ferait un support déconnecté, et la sortie de busdiag du simulateur est directement comparable à celle d'une carte morte.
Ce que le simulateur ne peut pas vous dire : il n'y a pas d'horloge, pas de relation de phase PHI1/PHI2, pas de temps de setup ou de hold, pas de délai de propagation des buffers et pas de contention de bus — précisément ce que la carte v2.2 a mal fait, ce que l'interface bufferisée v2.3 a corrigé, et ce à quoi la mise en service de septembre 2026 a ensuite consacré son temps d'analyseur logique. Une exécution propre ici valide la logique du firmware ; la justesse du moteur de bus est établie sur le matériel par le vérificateur shadow,
pipetest et les captures LA5016.
Interaction Core 0 / Core 1
Le modèle de files inter-cœurs est identique à celui du picoZ80. Reportez-vous au Guide du Développeur picoZ80 — Interaction Core 0 / Core 1 pour la description complète et l'exemple de code. La seule différence de nommage est que la file est accédée via un pointeur
M6502CPU *cpu plutôt que Z80CPU *cpu.
Règle clé : les callbacks de handler et de poll s'exécutent sur Core 1. Toute E/S fichier, commande UART vers l'ESP32 ou autre opération bloquante doit être postée à Core 0 via
cpu->requestQueue. Les résultats sont renvoyés via cpu->responseQueue et traités dans task_ptr.
Pièges courants
- Installer un handler dans ioPtr au lieu de memioPtr. Le pico6502 n'a pas de tableau
ioPtr[]. Tous les handlers de périphériques vont dansmemioPtr[]. Tenter d'utiliserioPtr[]provoquera une erreur de compilation ou un comportement indéfini. - Attendre un appel de dispatch E/S. Il n'y a pas de
M6502CPU_readIO()ni deM6502CPU_writeIO(). Si vous portez un pilote Z80 qui utiliseioPtr[], déplacez tous ces handlers versmemioPtr[]aux adresses mémoire appropriées. - Bloquer dans un handler. Tout appel à
debugf,sleep_msoufopendepuis l'intérieur d'un handler bloquera Core 1 et corrompra le timing du bus 6502. Utilisez la file inter-cœurs pour toutes les opérations bloquantes. - Nom virtualFuncMap non concordant. Le champ JSON
"name"est comparé sans distinction de casse à la tablevirtualFuncMap[]deM6502CPU.c. Une faute de frappe dans l'un ou l'autre sautera silencieusement le pilote. Ajoutez undebugftemporaire dansM6502CPU_getVirtualFunc()pour diagnostiquer. - Erreur d'une unité dans la plage du handler. Utilisez
addr <= endAddret nonaddr < endAddr + 1— ce dernier risque un débordement d'entier à0xFFFF. Utilisezaddr <= 0xFFFFavec une variable de boucleuint32_tsi la plage s'étend jusqu'au sommet de la mémoire. - Oublier de positionner le type de bloc à FUNC. Installer un handler dans
memioPtr[]n'a aucun effet si les entrées_membankPtr[]correspondantes indiquent toujoursMEMBANK_TYPE_RAMouMEMBANK_TYPE_ROM. Appelez toujoursMEMBANK_ENCODE(MEMBANK_TYPE_FUNC, ...)pour chaque bloc de 512 octets de la plage de votre handler. - Ne pas déclarer ce que l'hôte peut modifier dans le dos du CPU. Le shadow est vérifié contre chaque octet du bus et une seule divergence met le bloc en quarantaine pour la session. Si votre machine pagine une région, ignore les écritures dans une région, ou a des E/S quelque part, déclarez-le (fenêtre E/S, registre de pagination et masque, fenêtre d'écriture shadow) dans la fonction de tuning du pilote — la corruption de Galaxians à ~10 ppm « les bits ne font que tomber » et le défaut de reconnaissance de disque de MMFS étaient tous deux des problèmes de carte mémoire non déclarée.
- Lever une interruption depuis un handler en manipulant les drapeaux PIO. Positionnez
g_m6502VirtIrqà la place ; le cœur l'échantillonne avec la ligne physique une fois par instruction. Le cadencement appartient à ung_fastTickHook, pas au handler. - Timing en C. Rien sur Core 1 ne doit tenter de placer un front de bus ; sept schémas d'émission rapide côté C ont passé tous les tests synthétiques et ont corrompu les données en exécution réelle. Si un hôte a besoin d'un timing de bus différent, c'est une modification du PIO, exposée sous forme de réglage (
readsettle,writestrobes, les délais de l'échelle). - reset_ptr non effacé à l'arrêt du pilote. Si votre pilote est reconfiguré à l'exécution et que le callback de reset n'est plus valide, mettez
cpu->reset_ptràNULLavant de réinitialiser, sinon Core 1 appellera un pointeur de fonction obsolète au prochain reset.
Sites de référence
| Ressource | Lien |
|---|---|
| Page du projet pico6502 | /pico6502/ |
| Manuel Utilisateur pico6502 | /pico6502-usermanual/ |
| Guide Technique pico6502 | /pico6502-technicalguide/ |
| Guide du Développeur picoZ80 | /picoz80-developersguide/ |
| Page du projet picoZ80 | /picoz80/ |
| Datasheet RP2350 | datasheets.raspberrypi.com |
| API Multicore du Pico SDK | raspberrypi.github.io/pico-sdk-doxygen |
| Datasheet MOS 6502 | archive.org |
| Guide matériel du BBC Micro | stardot.org.uk |
| Bibliothèque cJSON | github.com/DaveGamble/cJSON |
Avis réglementaire sans fil
Cet appareil intègre un module sans fil ESP32-S3-PICO-1 qui émet dans la bande ISM 2,4 GHz, ce qui en fait un émetteur intentionnel au sens des réglementations radiofréquence du monde entier (y compris la Partie 15 Sous-partie C de la FCC aux États-Unis et la Directive sur les équipements radio 2014/53/UE dans l'Union européenne).
Bien que le module ESP32-S3-PICO-1 lui-même dispose de certifications réglementaires préexistantes (FCC, CE et autres), ces certifications au niveau du module ne s'étendent pas automatiquement à un produit fini qui intègre le module. L'exemption pour 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 distincte.
Limitations importantes
Il incombe exclusivement au constructeur de s'assurer que tout appareil construit à partir de ces conceptions est conforme à toutes les réglementations radiofréquence applicables dans sa juridiction. L'auteur fournit ces conceptions pour un usage personnel, éducatif et amateur et ne garantit en aucune manière qu'un appareil construit à partir de celles-ci satisfait aux exigences réglementaires pour une distribution commerciale.
- Les appareils assemblés ne doivent pas être vendus, proposés à la vente, offerts ou distribués de quelque autre manière à des tiers, sauf si le produit fini a été testé de manière indépendante et a obtenu sa propre autorisation d'équipement (par ex. FCC ID, marquage CE avec évaluation par un organisme notifié) dans la juridiction concernée.
- Construire ce projet pour un usage personnel en quantités limitées est généralement autorisé au titre des dispositions relatives aux amateurs et à l'usage expérimental (par ex. FCC § 15.23), à condition que l'appareil ne cause pas d'interférences nuisibles.
- Les exigences réglementaires varient selon les pays. Les constructeurs hors des États-Unis doivent consulter leur autorité nationale des radiofréquences pour connaître les règles applicables.
Il incombe exclusivement au constructeur de s'assurer que tout appareil construit à partir de ces conceptions est conforme à toutes les réglementations radiofréquence applicables dans sa juridiction. L'auteur fournit ces conceptions pour un usage personnel, éducatif et amateur et ne garantit en aucune manière qu'un appareil construit à partir de celles-ci satisfait aux exigences réglementaires pour une distribution commerciale.