Aller au contenu
Phosphoricwiki
Sommaire

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.
L'accès factice qui mange un octetLe POKE du BASIC écrit avec STA (zp),Y. En six cycles, le 6502 NMOS lit l'opcode, l'adresse de page zéro, puis le pointeur, puis fait une lecture factice de l'adresse visée avant d'y écrire. Si l'adresse est le registre de données d'un ACIA, cette lecture consomme l'octet reçu : un programme d'écho en BASIC perd des octets, sur la vraie machine comme dans Phosphoric depuis la 2.0.POKE 1021,C vers le registre de données d'un ACIASTA ($zp),Y : 6 cycles, 6 accès au buscycle 1R PClit l'opcode$91cycle 2R PC+1lit l'adressede page zérocycle 3R zplit le pointeur(octet bas)cycle 4R zp+1lit le pointeur(octet haut)cycle 5R $03FDlecture facticede l'adresse viséecycle 6W $03FDl'écriturevouluel'octet reçu est consommé iciUn cycle factice est un vrai cycle de bus : la puce ne sait pas qu'il est factice.Avant la 2.0, Phosphoric n'émettait pas ces accès : il était plus permissif que le matériel.Pilote série : écrire en assembleur, avec STA absolu (sans cycle factice).
STA (zp),Y en six cycles : le cinquième est une lecture factice de l’adresse visée.

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 ;
  • SEI ne protège pas l’instruction qui le suit d’une IRQ déjà en attente, et CLI ou PLP retardent symétriquement son arrivée d’une instruction ;
  • une NMI qui arrive pendant un BRK le 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 prouveNiveau
état final (registres et RAM)le calcul est justeindépendant du temps
total de cycles par instructionles compteurs sont exactsN1
sous-séquence du busaucun accès parasite, aucune inversionN2
séquence exacte du busle bon accès à chaque cycleN3

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 bus44,26 %100,00 %
cycles sans adresse12,8 % des cycles au démarrage0
accès factices du NMOSabsentstous émis
prise des interruptionsà la frontière d’instructionau cycle pénultième
drapeau I retardé (CLI, SEI, PLP)nonoui
NMI qui détourne un BRKnonoui
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 format CYCLE T ADDR DATA PC A X Y SP P FLAGS IRQ, où T vaut R (lecture), W (écriture) ou i (cycle interne). Elle sert à comparer, ligne à ligne, Phosphoric à un autre émulateur ou à du matériel instrumenté. Les lignes i n’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.

Sources dans le dépôt : docs/ACCURACY.md, docs/specs/V2_CYCLE_ACCURACY.md, docs/articles/v2-cycle-stepped.md, README.md.