Le cœur 6502
Le 6502 de Phosphoric ne pense plus en instructions mais en cycles : chaque instruction est un plan de micro-opérations, une par cycle, chacune avec son accès au bus. Voici comment, et comment on le sait.
Dans cette page
Ce qu’il faut reproduire
Le 6502 NMOS de l’Oric fait un accès au bus à chaque cycle, sans exception. Quand une instruction n’a rien d’utile à lire ou à écrire pendant un cycle, le processeur fait quand même un accès : un accès factice. Il relit l’opcode suivant, lit une adresse avant de l’indexer, réécrit une valeur avant de la modifier (pour les instructions lecture-modification-écriture), lit la pile sans s’en servir.
Ces accès sont invisibles pour la RAM. Mais sur un registre d’entrée-sortie, ils comptent : lire le registre de données d’un ACIA consomme l’octet reçu, lire certains registres du VIA efface un drapeau d’interruption.
Un plan de micro-opérations
Le cœur de Phosphoric (src/cpu/) décrit chaque instruction comme une suite de micro-opérations,
une par cycle, chacune faisant exactement son accès au bus : y compris les accès factices de
l’indexation en page zéro, de la traversée de page, de l’écriture-retour des instructions RMW, et des
lectures de pile mortes.
- 256 opcodes sur 256 : les 151 officiels et les 105 « illégaux » du NMOS.
- 13 modes d’adressage, le mode décimal du NMOS, l’IRQ sensible au niveau.
- Divergence assumée : les 12 opcodes JAM arrêtent le processeur au lieu de bloquer le bus.
Les interruptions au cycle pénultième
Le 6502 échantillonne ses lignes d’interruption pendant l’avant-dernier cycle d’une instruction. Phosphoric fait de même, ce qui donne gratuitement plusieurs comportements du vrai processeur :
- une interruption ne peut pas être prise avant l’instruction en cours ;
- une ligne qui s’active pendant le dernier cycle d’une instruction est vue trop tard : elle n’est honorée qu’après l’instruction suivante ;
SEIne protège pas l’instruction qui le suit d’une IRQ déjà en attente, etCLIouPLPretardent symétriquement son arrivée d’une instruction ;- une NMI qui arrive pendant un
BRKle détourne vers son propre vecteur.
L’oracle : comment on le sait
make test-cycle rejoue les vecteurs SingleStepTests/65x02 : 10 000 cas par opcode, chacun avec
l’état de départ, l’état d’arrivée et la trace du bus attendue, cycle par cycle. Il note quatre
propriétés séparément, de la plus faible à la plus forte :
| Propriété | Ce qu’elle prouve | Niveau |
|---|---|---|
| état final (registres et RAM) | le calcul est juste | indépendant du temps |
| total de cycles par instruction | les compteurs sont exacts | N1 |
| sous-séquence du bus | aucun accès parasite, aucune inversion | N2 |
| séquence exacte du bus | le bon accès à chaque cycle | N3 |
Les vecteurs, volumineux (environ 1 Go) et tiers, ne sont pas dans le dépôt :
tools/fetch_vectors.sh les télécharge. Sans eux, la cible se met en attente (SKIP) proprement, et
reste donc dans make tests et dans la CI.
De 44,26 % à 100 %
ancien cœur (--cpu-legacy) | cœur micro-séquencé (défaut) | |
|---|---|---|
| séquence exacte du bus | 44,26 % | 100,00 % |
| cycles sans adresse | 12,8 % des cycles au démarrage | 0 |
| accès factices du NMOS | absents | tous émis |
| prise des interruptions | à la frontière d’instruction | au cycle pénultième |
drapeau I retardé (CLI, SEI, PLP) | non | oui |
NMI qui détourne un BRK | non | oui |
| coût par trame (budget de 20 ms) | 1,9 % | 2,5 % |
Le chiffre de 44,26 % a été mesuré le 10 septembre 2026 sur une exécution exhaustive : 244 opcodes (hors les 12 JAM) × 10 000 cas = 2 440 000 cas, en 6,4 secondes. C’est le point de départ publié de la version 2.0. Dès l’étape suivante (V2-S2), le cœur micro-séquencé atteignait 100,00 % sur les quatre propriétés. Un plancher dans le test interdit de redescendre.
Le test fonctionnel
Le test fonctionnel de référence du 6502, 6502_functional_test, passe en entier : il atteint son
piège de réussite en $3469 après environ 96 millions de cycles émulés. Il confirmait déjà que le
déficit de l’ancien cœur était temporel, pas logique.
Deux cœurs, un seul calcul
Les deux moteurs partagent les mêmes fonctions de calcul : drapeaux, décimal, opcodes illégaux. Seul
l’ordonnancement des cycles diffère. L’ancien reste disponible par --cpu-legacy ; il ne sait pas
s’arrêter entre deux cycles, et force donc aussi le rendu de l’ULA ligne par ligne.
Le passage au nouveau cœur a été vérifié à l’identique : démarrages de l’Oric-1 et de l’Atmos identiques octet pour octet, treize programmes réels identiques à l’écran, et le test fonctionnel réussi au même nombre de cycles (96 241 367) sur les deux cœurs.
Ce que ça change pour les programmes
La fidélité a un prix pour les programmes trop confiants. Le POKE du BASIC écrit avec STA (zp),Y,
qui fait une lecture factice avant d’écrire. Un POKE 1021,C vers le registre de données d’un ACIA
mange donc l’octet qui venait d’arriver. Un programme d’écho écrit en BASIC perd des octets, sur
Phosphoric comme sur une vraie machine. La trace du bus le montre en deux lignes :
R $03FD $52 <- lecture factice du POKE : l'octet « R » est mangé
W $03FD $4F <- l'écriture voulue
Un pilote série s’écrit donc en assembleur, avec des STA ou STY absolus, qui n’ont pas de
cycle factice. Avant la 2.0, Phosphoric laissait passer ces programmes : il était plus permissif que
le matériel.
Les traces
--trace FICHIER: une ligne par instruction, désassemblée, avec les registres.--cycle-trace FICHIER: une ligne par cycle, au formatCYCLE T ADDR DATA PC A X Y SP P FLAGS IRQ, oùTvautR(lecture),W(écriture) oui(cycle interne). Elle sert à comparer, ligne à ligne, Phosphoric à un autre émulateur ou à du matériel instrumenté. Les lignesin’apparaissent qu’avec--cpu-legacy: le cœur par défaut fait un vrai accès à chaque cycle.--profile FICHIER: nombre de passages et cycles par adresse, histogramme des opcodes, les 20 points chauds.