- 6o6 est un projet qui exécute à nouveau un 6502 en logiciel sur un NMOS 6502 aux protections limitées, ajoutant une couche d’exécution virtuelle contrôlable à d’anciens systèmes 8 bits
- Il contrôle l’exécution des instructions et les accès mémoire du code invité, et fournit des fonctions comme le remappage d’adresses, le blocage des lectures/écritures illégales et le piégeage des opcodes jam
- Son principe central consiste à réutiliser directement l’ALU du 6502 hôte : les registres et flags invités sont chargés dans l’hôte, la même instruction y est exécutée, puis le résultat est sauvegardé
- La validation a utilisé la 6502 functional test suite de Klaus Dormann et un environnement de test basé sur lib6502 ; la configuration optimisée exécute 1 602 516 769 instructions, soit 36,5 % de moins que la configuration non optimisée
- The Incredible KIMplement 1.0, publié en même temps, ainsi que plusieurs exemples, montrent l’étendue des usages de 6o6 : émulation du KIM-1, virtualisation imbriquée, changement de tâches et système de mémoire externe basé sur geoRAM
Ce que 6o6 et KIMplement ont publié
- The Incredible KIMplement 1.0 émule le MOS/Commodore KIM-1, un ordinateur monocarte 6502 de 1 Ko à 1 MHz
- Il fonctionne sur un Commodore 64 sans extension
- Il prend en charge le TTY intégré du KIM, avec aussi un accès possible via le port série de l’ordinateur réel
- L’espace d’adressage est étendu à 16K
- 6o6 est l’abréviation de « 6502-on-6502 » : un CPU virtuel NMOS 6502 complet, entièrement logiciel, qui s’exécute sur un CPU 6502
- Il contrôle l’exécution du code invité
- Il piège les opcodes non documentés et les opcodes jam
- Il abstrait tous les accès mémoire
- Il prend en charge le remappage d’adresses, l’interception des lectures/écritures illégales et l’exécution fondée sur une mémoire virtuelle
- Sur Commodore 64 et Apple IIe, il exécute un
hello worldinvité, et la virtualisation imbriquée — exécuter 6o6 à l’intérieur de 6o6 — fonctionne également- Le stage 1 s’exécute presque immédiatement
- Le stage 2 est plus lent
- Le stage 3 est très lent, mais fonctionne
Pourquoi le 6502 a besoin de virtualisation
- Sur les premiers ordinateurs personnels, un seul programme contrôlait généralement toute la machine, et un programme défaillant pouvait être corrigé par un redémarrage
- Dans les environnements multi-utilisateurs ou multitâches, un code fautif peut corrompre d’autres espaces d’adressage, exécuter des instructions dangereuses ou monopoliser les ressources
- Le NMOS 6502 est un CPU simple composé de moins d’environ 4 000 transistors, ce qui limite ses fonctions de protection
- De nombreux systèmes historiques à NMOS 6502 permettaient difficilement de déplacer librement la zero page ou l’emplacement de la pile processeur
- Ils ne disposaient pas d’une fonction permettant de remapper des adresses de code vers un emplacement arbitraire et de les exécuter sans fixup
- Ils ne pouvaient pas interdire de manière globale l’accès à certains emplacements mémoire
- Si un opcode jam non documenté, ou
KIL, était exécuté, le processeur pouvait se bloquer complètement
- Certains problèmes peuvent être atténués par le matériel
- Générer périodiquement une NMI permet d’interrompre de l’extérieur un processus qui tente de monopoliser le système en réglant le flag d’interruption
- Certains noyaux multitâches 6502 implémentent ainsi un changement de tâches préemptif
- Un émulateur in-circuit comme « Trap65 » d’Eastern House Software pouvait remplacer des opcodes incorrects par des BRK piégeables, mais il était coûteux et limité face aux manipulations de bus complexes
Mode d’exécution de 6o6
- Une approche par simple interpréteur peut être pratique sur 6502
- Le 6502 a peu de registres
- Il compte 56 instructions et peu de modes d’adressage
- Le suivi de l’état du processeur est relativement simple
- La « virtualisation » de 6o6 tient au fait qu’il utilise l’ALU du 6502 hôte pour les opérations internes de l’invité
- L’accumulator invité et les flags sont chargés dans le CPU hôte
- La même instruction que celle que l’invité doit exécuter est exécutée sur l’hôte
- Le résultat et les flags sont sauvegardés, puis l’état de l’hôte est nettoyé
- L’avantage de cette approche est de ne pas devoir réimplémenter directement l’arithmétique et la gestion des flags
- Le decimal mode, c’est-à-dire l’arithmétique BCD, fonctionne naturellement
- Comme le calcul est effectué par un vrai 6502, le résultat est identique à celui d’un 6502
- La même méthode est utilisée lors de la lecture de valeurs mémoire ou du transfert de registres afin de gérer les flags négatif et zéro
- L’implémentation utilise du code auto-modifiant ; des précautions particulières sont donc nécessaires pour la placer en ROM
Structure de la VM, du harness et du noyau
- La VM 6o6 joue le rôle d’engine, et non de système complet
- L’environnement d’exécution est divisé en trois parties
- VM : CPU virtuel indépendant du matériel, fonctionnant sur un vrai 6502
- Harness : interface vers la mémoire invitée et le matériel administré
- Noyau : boucle de contrôle qui appelle la VM et traite les exceptions et l’état invité
- Le harness fournit une interface binaire via une jump table standardisée
- Il implémente les load/store à une adresse donnée
- Il traite l’instruction fetch
- Il maintient la pile matérielle et le stack pointer
- La VM ne suppose ni taille de page ni existence de paging mémoire
- Le harness peut implémenter aussi bien une traduction d’adresses simple par addition et bit shift qu’une mémoire virtuelle paginée
- Sans exposer de page fault à l’extérieur, le harness peut effectuer le paging in/out pendant les load/store
- Il peut aussi déclencher des exceptions de protection
- Le noyau lance l’exécution de la VM et interprète les codes d’état renvoyés par celle-ci
- Il traite les exceptions générées par le harness ou par 6o6 lui-même
- Il inspecte ou modifie les registres invités et le PC
- Il peut traiter certaines routines de service en natif
- Entre deux appels à la VM, le CPU invité est « arrêté », ce qui permet de capturer son état ou de changer de contexte
- La VM ne déclenche pas directement d’IRQ, NMI ou reset virtuels
- Le noyau décide quand ces événements se produisent
- BRK est pris en charge, mais la VM renvoie une exception au lieu de configurer la pile puis de sauter vers le nouveau PC
Utilisation de 6o6 dans KIMplement
- Le harness de KIMplement virtualise la pile standard 6502 et l’extension KIM-4
$0000-$17ff: mémoire en lecture/écriture$1800-$1fff: ROM$2000-$3fff: RAM$4000-$fff7: espace non mappé et non inscriptible$1ff8-$1fffest miroir de$fff8-$ffffpour les vecteurs
- Le Commodore 64 hôte stocke les 16K inférieurs dans
$4000-$7fff, le reste étant synthétisé par le harness - Le noyau KIMplement traite l’émulation RRIOT, l’affichage LED, les services TTY, l’injection de NMI pour stop et le Single-Step Switch, ainsi que le piégeage de certaines parties du ROM monitor KIM-1
- Après l’exécution de la VM, le noyau KIMplement inspecte le PC de 6o6 pour décider s’il doit intercepter la routine courante
- Le TTY et certaines fonctions sont implémentés ainsi
Optimisation des performances
- Les appels entre 6o6 et le harness peuvent constituer un goulot d’étranglement important
- Même une instruction simple nécessite au moins un fetch
- L’adressage indirect peut générer davantage d’accès mémoire
- Dans KIMplement 0.2, certaines opérations de load mémoire ont été inline via des macros de préprocesseur
- Les routines de load pour adresses virtuelles arbitraires et les routines optimisées pour la zero page ont été connectées directement à la VM
- La vitesse s’est nettement améliorée, mais la taille de la VM a augmenté
- Les store, moins fréquents et plus complexes, restent des appels de sous-routines
- Dans les itérations récentes, l’inefficacité de la manière dont les macros inline accédaient au program counter a aussi été corrigée, améliorant encore l’instruction fetch
- KIMplement 0.3 ajoute une forme primitive d’instruction fusion appelée « extra helpings »
- Les instructions qui ne touchent pas à la mémoire n’ont pas besoin de revenir immédiatement au noyau
- Cela cible les instructions à valeur immédiate, les instructions centrées sur l’accumulator, la plupart des instructions implied et les branch non pris
- En cas de load/store, de changement non séquentiel du PC ou d’exception, la VM cesse de tenter de regrouper les instructions
- Les extra helpings ne rendent pas la VM elle-même plus rapide
- Dans un système comme KIMplement, qui restreint les fonctions selon la position du PC, ils peuvent même être légèrement plus lents
- En revanche, ils accélèrent d’autres parties du système en évitant d’exécuter inutilement le noyau pour chaque instruction ne produisant aucun changement observable
- Comme ils peuvent gêner les applications devant contrôler précisément le PC, des options permettent de les désactiver progressivement ou totalement
Validation et résultats des tests
- La validation utilise la functional test suite de Klaus Dormann
- Le binaire fourni ne fait aucune hypothèse sur le matériel
- La réussite est signalée par une boucle infinie à un emplacement précis
- Les tests sont configurés pour s’exécuter directement dans le shell à l’aide du lib6502 CPU emulator de Ian Piumarta
- lib6502 a d’abord échoué à cause d’un edge case en decimal mode, puis a réussi après patch
- Le binaire fourni par Klaus couvre les 64K complets, il était donc impossible de le placer avec 6o6 dans l’espace d’adressage 6502 de base
- Un patch de système minimal à bank switching 32K a été ajouté à lib6502
- La zone
$7000-$efffest utilisée, avec les 32K initiaux et finaux du binaire de test placés dans deux banks différentes
- Trois configurations ont été testées
- Sans extra helpings ni inline fetch macro
- Avec inline fetch macro, sans extra helpings
- Avec inline fetch macro et extra helpings
- Les trois configurations réussissent la suite Klaus
- Les résultats en nombre d’instructions sont les suivants
- lib6502 sans 6o6 : 30 646 178 instructions
- 6o6 sans optimisation : 2 188 322 914 instructions
- Avec inline fetch macro : 1 713 350 225 instructions
- Avec inline fetch macro et extra helpings : 1 602 516 769 instructions
- La configuration 6o6 la plus rapide exécute 36,5 % d’instructions en moins que la configuration la moins optimisée
- La configuration la plus rapide exécute en moyenne 52,3 instructions par instruction invitée
- Ce chiffre inclut le harness, le noyau et l’exécution de 6o6
- Comme le cycle count varie selon les instructions, il ne doit pas être interprété comme un facteur de vitesse
Exemples inclus
- L’exemple hello world exécute d’abord le même programme sur le CPU natif, puis via 6o6
- Sur Commodore 64, il est mappé sur la routine de sortie de caractères
$ffd2; sur Apple II, sur$fded - Lorsque le noyau détecte que le PC pointe vers la routine de sortie de caractères, il récupère l’accumulator invité, appelle la routine ROM native, puis extrait la return address de la pile pour revenir à la boucle
- Sur Commodore 64, il est mappé sur la routine de sortie de caractères
- L’exemple inception utilise le même harness et le même noyau pour que 6o6 s’exécute lui-même comme payload
- Chaque stage possède sa propre zero page et sa propre stack
- Comme 6o6 utilise actuellement du code auto-modifiant, chaque stage nécessite une copie de la VM
- Au stage 3, la majeure partie de la mémoire est occupée par trois copies de la VM ; avec l’inline fetch macro, chaque VM dépasse 10 Ko
- Dans l’exécution imbriquée, l’appel
CHROUTest transmis du stage 3 au stage 2, puis au stage 1, avant d’arriver finalement à la routine native - La fin du payload utilise l’instruction RTS comme une sorte de « kick »
- Comme aucune return address n’est présente sur la pile au démarrage, RTS provoque un stack underflow
- Lorsque le harness le signale comme exception, le noyau le traite comme une fin normale
- Même aux stages profonds, le même mécanisme propage l’événement jusqu’au noyau supérieur
- Sur Apple II, il est possible de relancer l’exécution avec
CALL 2051, et sur Commodore 64 avecRUN- La version Apple II utilise aussi la zone DOS résidente au-dessus de
$9000, il est donc recommandé de redémarrer après exécution
- La version Apple II utilise aussi la zone DOS résidente au-dessus de
Exemple de changement de tâches
- L’exemple tasks est un petit noyau de task switching alternant entre deux tâches indépendantes
- Chaque tâche dispose de sa propre zero page, de sa stack et d’une petite zone d’adresses de code, et n’a pas connaissance de l’autre tâche ni de l’existence de la VM
- Une tâche affiche l’alphabet, l’autre affiche des chiffres
- Les chiffres sont affichés en reverse video pour les distinguer visuellement
- Chaque pression sur une touche change de tâche
- Les deux tâches utilisent le même emplacement de zero page pour stocker leur état, mais comme leurs zero pages sont distinctes, chacune reprend à son dernier emplacement
- Le changement de contexte nécessite les informations de tâche courante et les zones de sauvegarde de l’état A, X, Y, P, S et PC de chaque tâche
- Le harness consulte la tâche « on CPU » pour choisir les adresses physiques de la zero page, de la stack et du code exécuté
- Lors d’un changement, le noyau sauvegarde et recharge les autres états, puis marque une autre tâche comme « on processor »
Exemple de mémoire externe 64K basée sur geoRAM
- L’exemple vmgr est propre au Commodore 64 et fournit, sous forme de mémoire externe, un espace d’adressage 64K qui n’utilise pas la RAM du système lui-même
- geoRAM est un périphérique de RAM paginée différent de la REU officielle de Commodore
- La REU est centrée sur le DMA et utilise le MOS 8726 REC pour effectuer des lectures, écritures et échanges avec la main memory
- geoRAM mappe la mémoire via une window page de 256 octets dans la plage I/O
$de00 - Les registres de contrôle se trouvent à
$dffe,$dfff - Les clones compatibles modernes peuvent atteindre 4 Mo
- VICE prend en charge l’émulation de geoRAM
- L’exemple utilise la ROM du module processeur 6502 fourni pour le kit computer Z80 RC2014
- La ROM contient un monitor et EhBASIC de Lee Davison
- La ROM utilisée est une ROM précompilée disponible sur GitHub, dans sa version 6551
- Le harness ignore les écritures à partir de
$c100, la zone de ROM invitée- Les écritures sous 16K utilisent un fast path
- Au-delà, mask et shift ajustent la bank geoRAM
- La page geoRAM courante est mise en cache pour éviter l’étape de configuration lorsque l’accès se fait sur la même page
- Dans cet exemple, le noyau et le main program sont fusionnés
- Il vérifie la présence et le fonctionnement de geoRAM
- Il copie l’image ROM dans geoRAM
- BRK renvoie au monitor
- Les illegal instructions et les user-defined instruction traps sont traités comme BRK
- Le vecteur série de la ROM RC2014 est intercepté pour émuler un terminal simple
- Il convertit les caractères entre PETSCII et terminal
- Il maintient un petit curseur
- Il ajuste les registres invités et les flags selon le résultat
CTRL-SHIFT-Commodorepermet de reset le système émulé tout en conservant la mémoire
- Au cold start d’EhBASIC, si la taille mémoire n’est pas saisie manuellement, il faut environ une minute pour trouver 32768 octets libres sur la combinaison C64 et geoRAM
- La ROM est compilée avec une hard cap à
$8000, ce qui la limite en pratique à 32768 octets même s’il y en a davantage - D’autres éléments peuvent être placés dans
$8000-$c0ff - EhBASIC n’accepte pas les commandes ou keywords en minuscules : tout doit être saisi en majuscules
- La ROM est compilée avec une hard cap à
- Il fonctionne aussi sur un vrai Commodore 128DCR avec une cartouche geoRAM 512K
- Les calculs en virgule flottante fonctionnent correctement
- Les bad instructions sont immédiatement interceptées de manière contrôlée
- À l’exception de la window de 256 octets, le système affiché à l’écran ne s’exécute pas dans l’espace d’adressage propre du 6502
- Même avec une geoRAM 512K, on peut héberger séparément huit systèmes 6502 64K sous forme de tâches
Améliorations et usages futurs
- Des améliorations permettant d’exécuter 6o6 depuis une ROM sont possibles, mais elles exigeraient du refactoring et pourraient ralentir l’ensemble ; cela ressemble donc plutôt à une option
- L’émulation du 65816 est considérée hors périmètre, mais émuler des instructions CMOS sur un système NMOS pourrait être possible
- Comme l’ALU est utilisée, même si un NMOS 6502 émule un CMOS 65C02, les flags seront positionnés selon le comportement NMOS
- L’inverse vaut également
- L’adressage est actuellement écrit selon le mode de fonctionnement des CPU NMOS
- L’approche par inline memory macro offre des possibilités de peephole optimization
- Un pass « post-preprocessor » pourrait être placé avant l’assemblage réel
- Comme la toolchain deviendrait plus complexe, il faudrait confirmer le gain général
- L’un des usages explicites de 6o6 est d’exécuter du code téléchargé sans casser la tâche courante
- Il existe une idée consistant à l’utiliser dans un client Gopher pour exécuter dynamiquement ce qui a été téléchargé
- Si l’on concevait directement un nouveau système 6502, il pourrait être plus rapide d’implémenter les fonctions nécessaires dans le matériel
- Sur les CPU NMOS avec peu de protections, ou lorsqu’on veut minimiser le silicium supplémentaire, 6o6 devient une alternative flexible et adaptable
Distribution et licence
- The Incredible KIMplement est disponible sur sa page d’accueil et sur GitHub
- 6o6 est disponible sur GitHub, avec les quatre exemples abordés dans l’article
- La mise à jour KIMplement 1.0 porte surtout sur un nettoyage pour publication et de petites corrections de bugs
- KIMplement inclut aussi Tiny PILOT fourni par Dave Hassler
- Tiny PILOT est une implémentation publiée en 1979 dans MICRO magazine par Nicholas Vrtis, complétée par des patches de Bob Applegate et Dave Hassler
- Dave Hassler a aussi porté ELIZA depuis l’implémentation Atari PILOT de 1980 par Carol Shaw et Harry Stewart
- KIMplement et 6o6 sont tous deux distribués sous Floodgap Free Software License
1 commentaires
Commentaires sur Hacker News
Même avec le 6502, simple et limité, c’est toujours fascinant de voir cette architecture vieille de presque 50 ans être poussée vers de nouvelles limites
Certains SoC destinés à des marchés ultra low-cost et à très grande échelle embarquent encore aujourd’hui un cœur 6502
Ça ne semble pas facile de battre un cœur RISC-V à 10 centimes
Au début, j’ai imaginé en riant un SoC multicœur contenant 6502 cœurs 6502, mais sur FPGA ça ferait sans doute un projet amusant
J’utilise sur un Apple 2 une carte d’extension à vitesse variable avec un 65816, le successeur 16 bits, mais la plupart du temps je l’exécute en mode 8 bits. C’est parce que ça fonctionne bien et que la majorité du code de bibliothèque est aussi en 8 bits
À cette vitesse, la puce est rapide. C’est particulièrement vrai si on pense aux modèles simples où la RAM et le CPU sont cadencés en 1:1. Dans mon cas, je peux exécuter du code via un bus à 1 MHz, donc la plupart des opérations multi-cycles reviennent à un seul cycle de bus par fetch mémoire
Sinon, la carte dispose de 1 MB de RAM, qui tourne à la vitesse du CPU (0.15~16MHz). Cela suffit à faire tourner de gros programmes écrits dans des langages de haut niveau à une vitesse tout à fait exploitable. Et en assembleur, c’est évidemment ridiculement rapide
C’est un environnement très amusant pour bricoler toutes sortes de choses
Peut-on exécuter GEOS à l’intérieur d’une fenêtre GEOS ?
Ce billet est resté un bon moment sans commentaires, donc je m’étais dit que j’y reviendrais plus tard, mais l’essentiel tient dans la manière dont le Commodore 64 émule un système totalement différent basé sur le 6502
“6o6”, c’est-à-dire “6502-on-6502”, est un CPU NMOS 6502 totalement virtualisé par logiciel, qui tourne sur un CPU 6502, contrôle complètement l’exécution du code invité, y compris les opcode non documentés et les traps sur les opcode jam, et abstrait aussi entièrement les accès mémoire
Cela permet donc le remappage d’adresses, l’interception des lectures/écritures illégales, et même l’exécution avec une mémoire virtuelle complète. Non seulement il passe la suite complète de tests fonctionnels, mais il virtualise même lui-même en train de se virtualiser lui-même : c’est un travail impressionnant, pas seulement du point de vue du 6502, mais sous tous les angles
Ça m’a aussi rappelé une vidéo sur l’existence d’un mode protégé sur le Zilog Z80 : https://www.youtube.com/watch?v=DLSUAVPKeYk
Ça m’a rappelé l’époque où j’apprenais seul le langage assembleur 6502. Il y avait un livre appelé “The Visual Computer” et un émulateur fourni sur disquette, et ça avait vraiment été une révélation
J’ai retrouvé le PDF du livre [1], mais je ne sais pas si le logiciel présent sur la disquette existe encore quelque part
[1] https://files.commodore.software/reference-material/books/c6...
“Visual 6502” est le nom d’un simulateur moderne du 6502 au niveau des portes et des transistors : http://visual6502.org/JSSim/index.html
https://archive.fo/2u3Y8