Il y a bien quelque chose avec le Z80 qui me ralenti à chaque fois, ce sont les comparaisons. L'égalité, c'est facile. Mais dès qu'il s'agit de comparer deux octets pour savoir si l'un est plus grand que l'autre, strictement ou pas, et encore pire, si ce sont des octets signés, j'y passe du temps... pour souvent me tromper.
Et donc, voici un petit tableau récapitulatif pour m'aider à m'y retrouver, et peut-être que ça pourra aussi vous aider.
Un octet non signé est positif et dans l'intervalle [0, 255]. Après cp b (qui calcule A−B sans stocker le résultat), on a : Z = 1 si A = B et C = 1 si A < B.
C'est à l'été 2017 qu'après avoir commencé à étudier en profondeur le VG5000µ, je me mets en tête de commenter l'intégralité de sa ROM. Il y a bien quelques morceaux de ROM désassemblée qui existent, mais c'est incomplet et surtout, à cause des astuces d'instructions partielles du Z80, pas ré-assemblable à l'identique.
Or, en plus de l'étude, j'aimerais pouvoir modifier la ROM pour faire des essais, voire ajouter des commandes. Je cherche alors un désassembleur Z80 qui puisse aussi ajouter des commentaires et produire un code ré-assemblable. Je n'en trouve pas. Et puis, il y a cette page qui me fait dire que ça ne serait pas si compliqué que ça à faire.
C'est ainsi que je commence à développer un désassembleur Z80, que je nomme alors z80tools, car il n'est alors qu'une partie d'un répertoire d'outils variés permettant d'analyser la ROM. Une partie de ces outils seront extraits …
Ça y est, la nouvelle saison des Retro Programmers United for Obscure System a démarré. Et cette fois, c'est le PHC-25 qui est à l'honneur. Un ordinateur de Sanyo à base de Z80 (d'un clone plus précisément) et d'un MC6845 (... un clone aussi) et plutôt bien fourni en mémoire vive pour cette classe de machines : 16 ko de RAM et 6 ko pour la VRAM, accessible directement par le Z80 (avec des contraintes de timing si on veut éviter des parasites graphiques).
Bien entendu, comme toutes les machines concernées par cette game jam, le PHC-25 est un ordinateur qui n'a pas une grande logithèque, et pas non plus beaucoup d'activité ni de documentation. C'est le principe.
Pour cette Jam, je suis parti sur l'idée que le programme sera en assembleur. Certains vont certainement encore faire des miracles en BASIC, mais ce n'est pas ma tasse de thé, même si je …
Il y a quelques années, j'avais commencé une suite de tests pour le VG5000µ sous la forme d'un programme en mélange de C et assembleur.
L'idée était double : écrire une référence pour s'inspirer dans la programmation du VG5000µ (et particulièrement de l'EF9345), ainsi que
tester la fidélité des émulateurs.
Je me suis servi de cette base de code pour la première raison et mes besoins personnels. J'ai aussi pu corriger une paire de bugs dans
MAME grâce à la seconde utilisation.
Cependant, cette base de code traîne depuis un moment sur mon disque et je n'y passe pas beaucoup de temps. Alors autant que ça serve
à d'autres, et je mets donc aujourd'hui cette base à disposition.
Je ne sais plus trop dans quel état il est. Ça doit globalement fonctionner. J'imagine.
Encore ?! Oui... encore. Une nouvelle manière de gérer la construction d'un programme VG5000µ. Après la version Sublime Text et z80asm en 2018, puis la version Visual Studio Code et sjasmplus en 2020, je voulais essayer autre chose.
J'avais laissé de côté Sublime Text et z80asm pour deux raisons : le changement de license de Sublime Text que je n'avais pas apprécié, et le côté très simpliste de z80asm, dont je touchais des limites.
Pour un nouveau projet, je voulais utiliser z88dk, un kit de développement pour machines Z80, avec du support C et ASM, ainsi que des bibliothèques standards. Je voulais aussi approfondir ma connaissance du support de toolchains avec CMake.
Alors oui, cmake pour un tout petit projet pour des machines des années 80, ça fait un peu surdimensionné... Je le concède. Et ça n'enlève en rien mon envie de fouiller de ce côté.
Il y a presque trois ans (déjà !), j'avais mis en place un environnement de programmation pour me permettre de mettre au point un programme en assembleur Z80 et de l'envoyer vers MAME, afin de réduire le nombre d'opérations manuelles. Le tout à partir de Sublime Text 3.
Peut-être parce que la documentation de cet éditeur n'est pas des plus détaillée, ou peut-être parce que c'est avant tout un éditeur de texte, les extensions autour de l'assembleur Z80 sont peu nombreuses. C'est plutôt du côté de Visual Studio Code que ces extensions sont apparues.
Récemment, j'ai donc fait deux changements dans ma chaîne de mise au point pour VG5000µ. Tout d'abord, j'utilise à présent Visual Studio Code comme éditeur, et ensuite, j'ai changé d'assembleur.
Visual Studio Code
Tout comme Sublime Text 3, l'important pour moi est que l'éditeur puisse fonctionner sur diverses plateformes, et entre autre sur celle que j'utilise pour …
Cet article est la suite de deux précédents articles. Le premier, en plusieurs parties, était l'implémentation de l'affichage d'un « gros pixel » sur l'écran du VG5000µ. Le second était celui sur la possibilité (ou plutôt la difficulté) d'ajouter des commandes au BASIC du VG5000µ.
Il existe cependant une façon d'ajouter des commandes au BASIC... ou presque. Cette possibilité est évoquée brièvement dans le livre « Clefs pour VG5000 » page 98. À charge au lecteur de se débrouiller.
Cette méthode, indiquée dans le livre, est à vrai dire celle qui est utilisée par l'implémentation BASIC de la machine. Chaque routine d'instruction démarre avec HL qui pointe dans le buffer du texte BASIC sur l'octet suivant le token d'instruction, et donc sur ses éventuels paramètres.
C'est donc le cas, comme les autres, de l'instruction CALL. La routine de l'instruction se charge de lire l'adresse puis de l'appeler. Par lecture de ce paramètre, HL a …
Le 8 avril 2017, un message sur le forum system-cfg posait la question de l'existence d'un commentaire extensif de la ROM du VG5000µ. J'y répondais que j'avais quelques notes.
Depuis, sur ce site, j'ai décortiqué un certain nombre de parties, par curiosité personnelle, ou pour répondre à des questions qui se posaient sur le même forum. Au fur et à mesure, je me suis embarqué dans le commentaire exhaustif de la ROM.
Le commentaire
Ce fut long, plutôt long.
Et je publie le résultat aujourd'hui. Ce résultat prend la forme principale de deux fichiers de commentaires, l'un pour la ROM 1.0 et l'autre pour la ROM 1.1. Le tout est disponible ici sur GitHub.
Ce que je ne publie pas aujourd'hui est l'outil qui permet de prend la ROM d'un côté, le fichier de commentaires de l'autre et qui génère un listing assembleur commenté qui peut-être assemblé à …
Après cette implémentation en assembleur Z80 d'une fonction setpoint qui affiche, de manière assez basique, un point à l'écran, je me pose la question d'utiliser un langage de plus haut niveau... mais pas trop.
J'ai une assez longue expérience du C et la question que je me pose est : qu'est-ce que ça donne de programmer en C pour générer du code sur Z80.
Programmer en C a quelques avantages a priori : c'est nettement plus concis et lisible que de l'assembleur, j'y suis plus habitué et c'est portable sur de nombreuses plateformes. C'est le cas d'autres langages, mais le choix naturel pour moi puisque écrire du C m'est habituel. En tout cas bien plus habituel que d'écrire directement de l'assembleur Z80.
Premier essai
Voici le code C d'un premier essai :
#include<stdint.h> // Afin d'utiliser les types standardsvoidsetpoint(uint16_tx,uint8_ty)// Définition de la fonction// En entrée …
À présent que l'on sait diviser par 3, reprenons l'affichage d'un point à l'écran. Pour rappel.
En entrée, nous avons : des coordonnées X et Y, comprises entre 0 et 79 pour X et 0 et 74 pour Y.
En effet de bord, c'est-à-dire en modification de l'état de la machine, nous voulons : le point correspondant à l'écran qui prend la couleur d'encre définie.
Pour cette version, la procédure ne prendra pas d'information de couleur, je me contenterai d'utiliser la couleur d'encre 0 (noir) sur fond 6 (bleu), qui est la combinaison à l'initialisation de la machine.
Les étapes, d'après les articles précédents, sont donc :
À partir de X et Y, trouver les coordonnées du caractère à modifier à l'écran
À partir de X et Y, trouver les coordonnées à l’intérieur du caractère semi-graphique
À partir de coordonnées du caractère, calculer l'adresse mémoire écran correspondante
La méthode de cet article, qui sera le dernier avant de revenir à l'affichage d'un point, va diviser grâce à, globalement, une seule addition. Oui ! Une seule addition.
L'idée
Au tout début de la série d'articles sur la division, j'ai mis en place un système de tests pour m'assurer que mes bouts d'assembleurs faisaient ce qu'il étaient censés faire. Et pour cela, je comparais une série de divisions avec un tableau de résultats.
Mais alors, pourquoi ne pas utiliser un tableau de résultats directement ? On stock quelque part le résultat de toutes les divisions par 3 des nombres entiers de 0 à 255, et on …
Dans le dernier article, j'avais cherché une version plus rapide pour effectuer une division par 3, toujours dans l'optique d'afficher un point à l'écran en transcrivant la routine écrite en BASIC vers de l'assembleur Z80.
Le résultat était mitigé : une routine en moyenne plus rapide et plus stable, mais sur le domaine de définition considéré (le nombre de lignes graphiques du VG5000µ), pas entièrement gagnante.
Dans cet article, je vais donc étudier une autre manière de faire, un peu différente. Alors que les deux premières implémentations permettait de diviser par n'importe quel nombre de 1 à 255, cette nouvelle version est spécialisée dans la division par 3.
Est-ce que cette spécialisation permettra de gagner en performance et/ou en taille ?
L'idée
Le Z80 n'a pas d'instruction pour diviser de manière générale, cela a déjà été mentionné dans les articles précédents. Par contre, il est tout à fait possible de …
Dans le dernier article, j'avais écrit une première manière d'effectuer une division, en spécialisant la routine pour une division par 3 puisque c'est ce qui m'intéresse pour afficher un point à l'écran du VG5000µ.
Pour référence, cette routine nécessitait 15 octets en mémoire et son exécution pouvait prendre jusqu'à 695 cycles, ce qui est plutôt grand.
Micro optimisation
La première optimisation est une micro optimisation. On appelle micro optimisation une amélioration de la routine par un détail de fonctionnement, plutôt que par une réflexion sur l'ensemble de la méthode.
Ici, l'idée est de remplacer le couple push bc / pop bc en début et fin de la routine par un couple exx / exx. L'instruction exx du Z80 effectue un renommage des registresBC, DE et HL. Ces registres (ainsi que d'autres, mais qui ne sont pas touchés par cette instruction) existent en deux exemplaires dans le Z80. Un exemplaire de chaque …
À présent que j'ai un garde fou pour vérifier que je ne fais pas d'erreur d'inattention, me voilà près à diviser des nombres. Pour rappel j'ai besoin de diviser des nombres afin de faire les calculs permettant d'affiche le bon pixel à l'écran.
Pour second rappel, le Z80, au cœur du VG5000µ (et de beaucoup d'autres ordinateurs de l'époque) n'a pas d'instruction de division.
La division
Lorsque je divise de manière entièrea par b, je veux trouver le nombre c tel quel $c * b = a$. Comme la division ne tombe pas toujours juste, j'ai aussi un rester tel que $c * b + r = a$.
Autrement dit, combien de fois dois-je additionner b pour obtenir a (au reste près). Une manière de trouver le résultat est de soustraireb à a autant de fois que l'on peut sans passer sous 0.
Par exemple, $\frac{21}{7}$ se trouve comme ceci …
Après avoir mis en place une vérification (légère) de l'intégrité de la pile, je passe à la vérification de la validité de l'appel d'une fonction.
Le fonctionnement du test est assez simple : je prends une suite de nombres, j'appelle une fonction avec en paramètre chacun de ces nombres, je vérifie que le résultat est conforme à ce que j'attendais.
Par exemple, si je veux tester une fonction diviser par 2 (division entière), je peux utiliser la suite de nombre 0, 10, 32, 255 et comparer les résultats respectifs avec 0, 5, 16, 127 (255 étant impair, le résultat de la division entière est 127, avec un reste égal à 1).
Encore plus simple qu'une division par 2, il y a la fonction identité : celle qui renvoie le paramètre sans le toucher. Tester cette fonction permet de se concentrer sur le développement du test.
Il y a maintenant pas mal de temps, j'avais implémenté, en BASIC, une routine pour afficher un point à l'écran. Puis de là, une routine pour tracer une ligne, puis un cercle. Le constat était que c'était très lent. Le BASIC interprété est déjà plutôt lent de manière générale, et celui du VG5000µ n'est pas particulièrement rapide.
Il y a plusieurs raisons à cela, et ce sera peut-être le contenu d'articles futurs.
Mais en attendant, et après tous les efforts pour se confectionner un environnement de travail pour développer en assembleur, je repars sur l'implémentation du tracé d'un point à l'écran, et cette fois-ci, en assembleur.
L'avantage d'un programme écrit dans un langage de « haut niveau », comme le BASIC, est de simplifier bien des choses. Faire une division d'une variable A par 3 par exemple, peut s'écrire B = A/3. C'est simple, concis, lisible.
Dans les articles précédents sur l'automatisation au niveau des outils de développement, j'avais vu comment injecter un programme dans MAME en émulation VG5000µ d'abord grâce au debuggeur manuellement, puis avec un script LUA grâce aux possibilité d'extensions de MAME.
Ce n'est toujours pas suffisant pour moi. Comme je l'ai déjà écrit dans ces autres articles, je ne veux pas faire ce qu'une automatisation peut faire bien plus facilement, et sans se tromper. Lancer l'assembleur en ligne de commande, puis lancer MAME avec les bons paramètres, cela n'est pas bien compliqué avec un shell moderne (comme fish).
Un des principaux problèmes que j'y vois, outre que ce sont des opérations manuelles, c'est que ce sont des opérations non documentées. Si je fais une pause dans un projet et que j'y reviens deux mois après (ça arrive bien souvent, surtout pour du hobby), il y a de bonnes chances que je …
Puisque je suis actuellement dans l'utilisation de LUA pour scripter MAME, faisons un petit détour pour explorer quelques possibilités.
Les fonctions accessibles aux scripts LUA sont assez nombreuses. Je n'en connais pas de documentation complète si ce n'est dans les sources du programme lui-même à cet endroit : mame/src/frontend/mame/luaengine.cpp. Des groupes de commentaires indiquent les fonctions et leur usage. Reste à utiliser la console pour expérimenter un peu la façon de les appeler.
L'une des possibilités intéressantes est de pouvoir agir sur l'affichage de l'émulateur. Cela peut être assez pratique pour suivre certaines valeurs en mémoire, pour afficher des informations sur le comportement de la machine, voire d'ajouter des fonctionnalités interactives à l'émulateur facilement.
Affichage à l'écran
La première chose à faire pour afficher quelque chose à l'écran est de récupérer un objet permettant de le manipuler. Pour cela, en passant par l'objet manager, on récupère …
La fois dernière était consacrée au lancement d'un programme sur VG5000µ avec l'émulateur MAME de manière à injecter un programme binaire directement en mémoire puis à l'appeler. Tout cela automatiquement. Pour rappel, l'idée derrière cela est d'éviter les erreurs de manipulations qui arrivent de temps en temps.
La première option utilisée dans l'article précédent était d'utiliser un script pour le debuggeur d'une part, et la capacité de MAME à entrer au clavier de la machine émulée une suite de frappes de touches.
Cette méthode fonctionne mais a ses limites : il faut régler le délai avant la frappe de touches de manière empirique, et le script est linéaire. Dans cet article, je vais utiliser une autre possibilité de MAME pour résoudre ces deux écueils.
Reprenons sur la programmation en assembleur sur VG5000µ depuis un ordinateur actuel. Car s'il est possible et tout à fait légitime, par hobby, de programmer directement sur la machine, à l'ancienne, pour « retro programmer » comme il y a du « retro gaming » en éprouvant les sensations originelles, ce n'est pas mon but ici.
Mon but, c'est de pouvoir programmer depuis un éditeur de texte simple, mais pas trop, et de pouvoir mettre au point avec certaines facilités comme un retour d'erreur d'assemblage et l'accès à un debuggeur. Et tout ceci avec une chaîne qui minimise les manipulations répétitives.
Minimiser les manipulations répétitives permet de rester concentré au maximum sur ce que je fais. Je n'ai pas envie, à chaque lancement, de taper des commandes particulières ou de faire quelques clics souris, toujours les mêmes, avec un risque de me tromper et de chasser un bug que je crois être là alors …
La dernière fois, j'avais évoqué le logiciel d'assemblage, ou assembleur comme étant le programme permettant de passer d'un programme sous sa forme source, c'est-à-dire écrite sous forme lisible par un humain, dans une forme binaire, qui sera plus exactement nommée forme objet. C'est cette forme qui pourra être exécutée par la machine.
Pour programmer, quel que soit la forme que revêt cette programmation, on a besoin d'outils. Les outils primaires peuvent être une table de référence, des manuels, un stylo, des feuilles et beaucoup de patience. Rapidement, le besoin d'outils pour effectuer les tâches automatiques et répétitives qui forment la transformation d'un programme source en programme objet se fait sentir. Et quoi de mieux pour traier des tâches automatiques et répétitives de transformation qu'un... ordinateur.
Le problème de la poule et de l'œuf n'est pas le sujet ici. Le principal est qu'à l'heure de l'écriture de cet article, il y …
Après cette pause estivale, reprenons là où l'on était restés. Dans l'article précédent, je parlais du langage machine, une suite de signaux provoquant l'activité d'un processeur selon des directives précises.
Un inconvénient du langage machine, c'est qu'il est peu pratique à manipuler. Par exemple, sur un Z80, charger l'accumulateur (une mémoire spécifique interne au processeur) avec la valeur 1, je dois écrire : 00111110 00000001 en binaire, ou encore 3E 01 en système hexadécimal.
Écrire de cette manière n'est pas simple, prend beaucoup de temps avec un grand risque d'erreurs. Relire est encore pire. Programmez une machine de cette manière et vous ressentirez a priori rapidement le besoin de manipuler des éléments plus faciles à comprendre pour un Humain.
Et c'est ainsi que du langage machine on passe au langage d'assemblage, ou, par abus de langage, à l'assembleur.
L'assembleur
La première chose à savoir est que l'assembleur est un programme. Mais …
Lors des articlesprécédents sur l'affichage, un résultat était net : c'est lent ! Extrêmement lent. Les magazines ou de livres consacrés à la programmation des machines personnelles des années 1980 affirmaient tous ceci : si vous voulez quelque chose de rapide, passez à l'assembleur.
Que signifie utiliser l'assembleur, et en quoi c'est différent du BASIC ? Pourquoi est-ce que c'est plus rapide ? Était-ce vraiment la seule solution ? C'est ce que nous allons voir dans cet article et les suivants.
J'ai tenté plusieurs approches pour arriver au premier programme en assembleur dans une série d'articles. Et j'en suis arrivé à la conclusion qu'il n'y a pas moyen de passer outre quelques explications rapides des constituants de l'ordinateur et de leurs fonctionnements.
On va tout de même garder une vue large et schématique pour la plupart des composants. Pour le microprocesseur, un Z80 sur le VG5000µ, il faudra descendre un peu vers le fonctionnement …