Jelora.fr

Ordinateur 8 bits avec processeur Z80 - 1ère partie : Mise en place du CPU et premiers tests

20181014_231305.jpg

Il y a quelques années maintenant, je m’étais fait un ordinateur 8 bits sur avec un processeur Zilog Z80 qui était manipulable et programmable comme l’Altair 8800, une machine qui me fascinait à l’époque. C’est à dire, en rentrant le programme avec des interrupteurs.
Autant dire, quelque chose d’assez long et un peu ennuyant à la longue.
De plus, il n’était pas vraiment upgradable aisément. Je n’avais pas pensé spécialement à cette possibilité, je voulais juste créer un ordinateur de mes mains où on pouvait rentrer des programmes à l’ancienne et où ça marchait (en théorie :D).
Au final, même si son utilisation était assez limitée, j’ai plutôt content d’en avoir fait un mais ça aurait été bien d’avoir un truc un peu mieux fait.
Je me suis donc dit qu’il faudrait que j’en refasse un autre un jour ...

Entre temps, j’ai testé des différents trucs sur des plaques d’essais et vu des composants intéressants puis la vision d’un ordinateur beaucoup plus fun commençait à germer lentement.

Et puis, il y a quelques mois, je me suis lancé dans la construction d’un nouvel ordinateur 8 bits avec processeur Zilog Z80 mais qui, cette fois ci, serait beaucoup mieux pensé.
Pour cela, j’ai décidé d’y aller vraiment en prenant mon temps plutôt de vouloir un énorme truc d’un seul coup. Cela me permettra de mieux y réfléchir et d’arriver à quelque chose de bien meilleur.
Différentes idées me sont venus en tête :
- l’ordinateur aurait son programme de boot en ROM.
- vu la limitation de la quantité de mémoire possible, il y aurait du bank-switching pour la RAM.
- interface clavier/souris, grâce notamment au contrôleur VT82C42 [pdf].
- sortie sonore FM, avec une puce sonore YM2612 [pdf] et SN76489 [pdf] (utilisées dans la Sega Megadrive) et AY-3-8910 [pdf] ainsi qu'une sortie sonore PCM. Ce qui permettrait, qui sait, de faire de la création de chiptune ... ;)
- lecteur de disquette
- interface lecteur de cassette
- sortie vidéo VGA
- etc ...

Mais pourquoi utiliser un processeur Zilog Z80 ? Pourquoi pas un Intel 8080, un MOS 6502 ou un autre CPU ? Pourquoi un processeur 8 bits ?
Et bien parce que :
- c’est un processeur ultra connu des années 70-80 et j’aime beaucoup le rétro-computing. Il est d’une époque où on pouvait mettre les mains dans le cambouis et se bricoler soit même un petit ordinateur dans son coin. Même si je n’ai pas connu cette époque, j’en reste admiratif.
- j’aime bien son fonctionnement et ses interfaces
- il est facile d’en trouver à par cher du tout (merci les chinois !)
- les premiers modèles fonctionnait à 2,5Mhz et les derniers actuels (parce que oui il est toujours fabriqué) vont jusqu’à 20 Mhz !
- quand j’ai commencé à faire de l’électronique, j’en avait acheté dans l’espoir de me faire un ordinateur (ouais j’avais pas froid aux yeux à l’époque !)
- Et dernière raison : parce que. Voilà !
Donc tout plein de raison tout à fait valables qui font que j’avais envie de m’amuser avec ce processeur ! :D


Mais avant tout, il fait bien commencer quelque part !


1) Circuit CPU + Base de temps + Affichage à LEDs des bus

La première étape a été de faire fonctionner le CPU et de pouvoir visualiser son fonctionnement en affichant les différents bus qu’il utilise.

Le Zilog Z80 utilise 3 types de bus :
- un bus d’adressage 16 bits, qui permet de sélectionner un emplacement mémoire parmi 64Ko de mémoire à accès direct ou un port d’entrée-sortie particulier parmi les 256 possibles.
- un bus de données 8 bits, où les échanges d’information se font.
- un bus de contrôle, où le CPU indique s’il veut un accès mémoire ou un port, s’il veut lire ou écrire ou si un périphérique veut envoyer des informations.

bus_z80.JPG

z80_pinout.jpg

Datasheet du processeur Zilog Z80 : pdf


Et ça a donné ça :
20181014_230041.jpg

Ce circuit ne fait rien de particulier hormis simplement clignoter mais justement si ça clignote c’est que c’est bon, le processeur tente de parcourir une éventuelle mémoire pour y trouver des instructions à exécuter.
L’affichage des bus pendant le fonctionnement me permettra de visualiser si le futur programme exécuté tourne bien.
Ce circuit sert de base et tout ce qu’il sera créé ensuite y sera connecté.

Voici le schéma :
Prototype ordinateur 8bits Z80 - Circuit CPU - Horloge système - Affichage bus.JPG

Version pdf : lien

Explications :
On a ici :
- un processeur Zilog Z80 pouvant tourner jusqu’à 8Mhz (je le changerai plus tard pour une version plus puissante)
- une horloge système à base de NE555 [pdf] tournant environ à 35Hz, ce qui est suffisant pour l’instant et qui me permet de voir ce qui se passe avec mes yeux. Un oscillateur à quartz à fréquence bien plus élevée sera utilisé plus tard.
- des LEDs qui affichent les différents bus et qui se sont pas reliés directement au processeur. En effet, toutes ces LEDs consomment pas mal d’intensité,environ 370mA maximum au total, ce qui n’est pas négligeable, et si elles sont reliés directement au processeur, celui-ci va chauffer inutilement. Les LEDs des bus d’adressage et de données sont reliés à travers des buffers 8 bits 74LS245 [pdf] afin que la consommation des LEDs soit répercutée sur ces buffers plutôt que le CPU. En ce qui concerne les LEDs du bus de contrôle, elles sont reliés via des portes inverseuses 74LS04 [pdf] car l’ensemble de ce bus fonctionne en état inversé, la visualisation de ce bus est ainsi plus facile.
De plus, vu que les bus sont des bus 3 états et donc peuvent se retrouver en état haute impédance, le fait de ne pas avoir de composant faisant perdre cet état, en laissant circuler du courant, est meilleur.
- des condensateurs de 100nF sont appliqués à chaque composant. Lorsqu’on utilise la technologie TTL, il est recommandé d’en utiliser sur chaque puce car celles ci ont une pointe de consommation instantanée au moment où un de leur bit change d’état. Ces condensateurs pallient à cette pointe de consommation et évitent des parasitages.


Ensuite, on a quelque chose qui tourne mais il faut lui donner quelque chose de vrai à lui faire tourner !


2) Circuit de développement

J’ai donc commencé à réfléchir comment j’allais faire pour pouvoir faire mes développements et tester les différents programmes que j’aurais créé.

J’aurai pu utiliser simplement EEPROM que j’écrivais avec un programmateur de ROM, que je mettais en place puis testais sur le circuit, que je retirais ensuite pour l’effacer et puis réécrivais, etc ... et cela à chaque nouvelle modification du code mais c’est une opération manuelle longue et ennuyante, ce qui risque de fortement de me soûler !

J’ai réfléchi alors à un moyen de pouvoir exécuter du code à la volée qui ne serait pas stockée dans une ROM mais envoyé depuis mon PC où un circuit repérerait ce que demande le Z80, transmet l’information demandée à mon PC et celui ci renverrait l’information correspondante venant directement du code en cours et cela sans à avoir les étapes de manipulation physique/effacement/réécriture EEPROM en boucle.

J’ai donc commencé un premier circuit qui fait appel à un micro-contrôleur PIC16F873a [pdf] qui va récupérer un instantané de chaque bus du CPU, transmettre cet instantané via une liaison série à un PC et éventuellement appliquer une donnée 8 bits sur le bus de données si le PC lui en a envoyé une.

Ce qui donne ça :
20181118_182356.jpg
20181118_182336.jpg

Avec le petit schéma qui va bien :
Prototype ordinateur 8bits Z80 - Circuit de développement.JPG
Version pdf : lien

Explication :
Comme dit plus haut, on a un microcontrôleur va récupérer un instantané des bus.
Cet instantané est fait grâce à 5 puces 74LS373 [pdf] correspondant à 8 bascules D, donc gérant 8 bits de chaque bus chacune. Le contenu stocké dans les bascules D de chaque puce est ensuite récupéré puce par puce par le microcontrôleur. Une bascule D joue le rôle ici de mémoire 1 bit.

J’aurai pu utiliser un microcontrôleur avec beaucoup plus d’entrée pour me passer des 74LS373 mais je n’avais pas ça en stock sur le moment et je n’avais pas envie de commander un éventuel coûteux microcontrôleur ne sachant ce qu’allait donner le résultat.

Vu qu’il fait des tentatives de récupération d’instantané très souvent chacune seconde (tourne en boucle) et qu’il ne va renvoyer inutilement l’état de chaque bus si ceci n’ont pas changé entre deux  récupération d’instantané, le microcontrôleur va comparer les valeurs des bus de l’instantané en cours à l’instantané précédent. S’il y a une différence, c’est là que l’information est envoyé au PC par voix série.
L’information envoyée au PC est sous cette forme de 9 octets :

LECT<busDeDonnées><busDeControl(2 octets)><busDAdresse(2 octets)>

La suite de caractères "LECT" permet de faire détecter par la suite le début d’un envoi d’instantané.

Dans le microcontrôleur, la configuration de la liaison série se fait via 3 registres :
- TXSTA, le registre de configuration de transmission
- RCSTA, le registre de configuration de réception
- SPBRG, le registre contenant une valeur correspondant à la vitesse de la transmission issue d’un calcul.
Le microcontrôleur est configuré de la sorte :

Code:1.
2.
3.
SPBRG = 12;
TXSTA = 0b00100100;
RCSTA = 0b10010000;

Le registre TXSTA a :
- le bit 5 (TXEN) activé permettant d’activer la transmission
- le bit 2 (BRGH) activé permettant d’être en mode asynchrone rapide qui permet d’avoir des vitesses de transfert série plus élevés que le mode lent.
Le registre RCSTA a :
- le bit 7 (SPEN) activé pour activer le port série sur les pattes RC6 et RC7.
- le bit 4 (CREN) activé pour recevoir en permanence dans un buffer interne ce qui arrive sur l’entrée série, même quand le programme n’est pas en train de lire le port série. Le buffer interne ayant une capacité de seulement 1 octet, il peut arriver qu’il soit déjà rempli et que le code ne l’a pas vidé en le lisant alors qu’un second octet arrive. Quand cela arrive, on a une "Overrun Error" (bit 1 activé) et il faut passer le bit 4 à 0 puis à nouveau à 1 pour débloquer la situation. Si non, aucun nouvel octet ne pourra être récupéré depuis le port série tant que cette erreur sera toujours là.
Le registre SPBRG contient le résultat d’un calcul déterminant le vitesse de la liaison série. Dans notre cas la formule est la suivante :

Débit transmission = FreqQuartz / (16 x ( valeurSPBRG + 1))

Afin de trouver la meilleure valeur pour la plus grande vitesse de transmission avec un quartz de 4Mhz, j’ai un tableau de calcul :
calcul_valeur_vitesse_transmission_serie.JPG

Lien pour récupérer le tableau de calcul : calcul port série.ods (fichier LibreOffice Calc)

Pour un quartz de 4MHz, avec la valeur 12, j’obtiens un début de 19200 bits/s ce qui est tout à fait correct pour faire seulement des tests dans un premier temps.
Plus tard, on pourrait éventuellement changer le quartz pour une valeur plus élevée et ainsi obtenir une vitesses de transfert plus importante.
Exemple : avec un quartz de 18,432Mhz, avec la valeur 4, je pourrais avoir un débit de 230400 bits/s. (Ça va beaucoup plus vite ! :D)
On pourra remarquer que le tableau donne des valeurs pas toujours entières. Il faut donc choisir une valeur la plus proche d’un entier car le registre SPBRG ne prend que des entiers. Le fait d’arrondir à l’entier le plus proche fait que la communication n’est pas exactement au débit dont on espérait mais les contrôleurs série ont une certaine latitude et permettent de ne pas être exactement à un débit standard au bit près.

La transmission série entre le microcontrôleur et le PC est assurée via un convertisseur USB/série CH340G [pdf].

Au niveau du PC, un code en Java lit le port série en permanence.
L’utilisation du port série en Java est assurée par la librairie JSSC (Java Simple Serial Connector) (Maven).
Le code attend alors une séquence commençant par "LECT". Les données qui suivent, correspondant à l’état des bus d’adresse, de données et de contrôle, comme précisé plus haut et sont ensuite extraites et analysés.
Le code Java contient un code programme pour Z80 sous forme binaire de 25 octets qui est sensé être accessible dans 25 premières octets de la mémoire de l’ordinateur.
Si le CPU demande une donnée dans cette zone mémoire en lecture, alors le code Java retourne l’information correspondante au microcontrôleur.
Deux types d’ordres peuvent être envoyés au microcontrôleur :
- la commande WED (Write and Enable Data) suivi de l’information à envoyer sur le bus de données.
- la commande DDA (Disable Data Access) qui efface la donnée envoyé sur le bus de données par le microcontrôleur et repasse la sortie en haute-impédance, ce qui permet de simuler que rien n’a réagit à l’instruction demandée par le CPU.

De retour sur le microcontrôleur, si celui reçoit un ordre d’envoyer une valeur sur le bus de données, il envoie d’abord la valeur 8 bits sur un buffer 74LS373, monté dans l’autre sens par rapport aux autres et qui, cette fois ci, ne va pas lire le bus de données mais écrire dessus. Puis il active la sortie du buffer pour envoyer la valeur reçu du PC sur le bus de données.

Et ainsi du code binaire pour Z80, qui est directement lu sur PC, est exécuté sur un vrai processeur.

Code source du micro-contrôleur : lien
Code source code Java sous Eclipse : lien



3) Port de sortie 0 pour tests

J’ai ajouté un affichage du port de sortie 0 comme port de sortie de test 8 bits.
20181125_195335.jpg

Et encore, le schéma qui va bien :
Prototype ordinateur 8bits Z80 - Port de sortie 0 à leds.JPG
Version pdf : lien

Explication :
Il s’agit tout simplement d’un buffer 8 bits 74LS373 qui récupérer la valeur du bus de données lorsque le CPU demande l’écriture (WR) du port (IORQ) de sortie que l’adresse 0 (A0 à A7 à la valeur 0). Un ensemble de portes logiques permettent répondre à cette condition.
Le buffer ayant la sortie toujours active (OE = 0), la valeur récupérée est tout le temps affichée.

Le programme binaire contenu dans le code génère un bit sur ce port de sortie et lui faire des allé-retours.

...
--:-- / --:--
  • Télécharger ...
    • Qualité
      -

      Un magnifique jeu de lumières sur de la bonne musique ;)

      Code source du code de test pour Z80 :

      Code:1.
      2.
      3.
      4.
      5.
      6.
      7.
      8.
      9.
      10.
      11.
      12.
      13.
      14.
          LD A,(1)
      Debut:
          OUT (0),A
          RLCA
          BIT (7),A
          JR NZ,(3)
          JP Debut
      Debut2:
          OUT (0),A
          RRCA
          BIT (0),A
          JRN Z,(3)
          JP Debut2
          JP Debut






      En prenant un peu de recul, le circuit de développement actuel peut-être très pratique pour ne plus s’infliger la procédure routinière de manipulation d’EEPROM mais il a un désavantage : vu qu’il y a un traitement d’analyse par un microcontrôleur et un programme externe de ce qui est demandé par le CPU, il faut que tout le processus d’analyse ait le temps de répondre à ce qu’il veut avant qu’il ne demande autre chose. Donc la vitesse du CPU doit être assez lente si on veut pouvoir exécuter du code avec un tel circuit.
      J’avais estimée que la fréquence maximale du processeur ne devrait pas dépasser les 1KHz avec la débit série maximum donné en exemple plus haut.
      Si on fait que tester des bouts de code ça va, mais quand on va vouloir faire des gros traitements, ça va être moins bien. Il y a une sacrée différence entre 4Mhz et seulement 1KHz ...

      Donc je pense refaire ce circuit et en faire un autre va écrire dans une EEPROM via un  microcontrôleur. Certes, on reviens à un processus d’effacement/écriture d’EEPROM mais cette fois, la ROM ne va pas quitter le circuit pour aller dans un programmateur de ROM, elle sera programmée par le microcontrôleur qui recevra le nouveau code depuis une liaison série.
      De cette manière, on perd la possibilité d’exécuter du code depuis le PC mais le code est traité à vitesse réelle. On se trouve à un moyen de programmation similaire à la programmation de microcontrôleurs PIC via un kit PicKit3.

      Suite au prochain épisode ... ;)

      Ajouter un commentaire

      Nom/Pseudo :

      *

      Email :

      Site web :

      Commentaire :

      *

      Vérification:


      *