Conversion CGA à VGA avec un FPGA



Les sorties video du Tandy 1000

L'ordinateur personnel Tandy 1000 EX comporte deux sorties vidéo: NTSC composite (Sortie télé) et compatible CGA (Color Graphics Adapter). Pour être exact, le circuit vidéo du Tandy 1000 est de type TGA, mais électriquement, c'est la même chose que CGA.

Voici quelques exemples d'images obtenues via la sortie NTSC:



La qualité d'image NTSC est bien inférieure à celle qu'il est possible d'obtenir en CGA. Malheureusement, je n'ai pas d'écran CGA. Par contre, des écrans VGA ce n'est pas ce qui manque! Je me suis donc intéressé à la possibilité de faire une conversion CGA à VGA.

goto top Retour en haut


De CGA à VGA

CGA:
  • Rafraîchissement vertical: 60Hz (Fixe)
  • Rafraîchissement horizontal: 15.75 kHz (Fixe)
  • Signal vidéo digital à 4-bit (Rouge, Vert, Bleu et intensité)
  • Connecteur DB9
VGA:
  • Rafraîchissement vertical: 60Hz (Variable)
  • Rafraîchissement horizontal: 31.46875 kHz (Variable)
  • Signal vidéo analogique à 3 canaux (Rouge, Vert, Bleu)
  • Connecteur HD15

Un moniteur VGA standard n'accepte pas un signal dont la fréquence de rafraîchissement horizontale est aussi basse que 15 kHz. On ne peut donc pas s'en tirer simplement en faisant passer la synchro (hsync et vsync) directement et en utilisant un circuit simple de résistances pour générer les voltages RVB pour le VGA à partir des signaux digitaux CGA.

Par contre, on remarque que la fréquence de rafraîchissement horizontal en VGA est environ le double qu'en CGA. Il y a donc deux fois plus de lignes dans une trame VGA. En répétant chaque ligne CGA deux fois (à la fréquence VGA de 31 kHz), nous faisons une pierre deux coups. Nous évitons d'obtenir une image compressée verticalement n'occupant que la moitié de l'écran, mais en plus nous doublons la fréquence de rafraîchissement de sorte qu'un écran VGA accepte alors le signal.

Ce principe est connu sous le nom de « scan doubler ».

goto top Retour en haut


Le montage

La réalisation d'un scan doubler pour passer de CGA à VGA était l'excuse parfaite pour m'acheter un kit de développement FPGA (Kit Altera DE1 de TerasIC) pour m'exercer à résoudre des problèmes avec une approche nouvelle pour moi.

Le kit comporte deux connecteurs à 40 broches permettant d'utilser les entrées/sorties logiques du FPGA. Il s'agit toutefois de logique fonctionnant à 3.3 volt alors que le système CGA fonctionne à 5 volt. Afin de ne pas endommager le FPGA, j'ai eu recours à des résistances pour fabriquer un réducteur de tension simple, comme ceci:

Les résistances utilisées doivent tous être de valeur égale. J'ai utilisé des résistance de 220Ω, mais c'est peut-être un peu trop bas car l'impédence d'entrée résultante est de 660Ω alors qu'avec un vrai écran CGA, ce serait plutôt 4.7kΩ... Si c'était à refaire, j'utiliserait des résistances de 1.5kΩ.

Voici quelques photos du montage:
Le kit DE1

Le kit DE1

Le kit et le diviseur de tension

Le kit et le diviseur de tension

Zoom sur le diviseur de tension

Zoom sur le diviseur de tension


goto top Retour en haut


Développement

Voici quelques images que j'ai obtenues au début alors que je m'amusait à modifier un exemple de générateur d'image VGA pour affichier des donnés provenant de l'entrée CGA stockées dans un petit bloc de mémoire. C'était une impasse et j'ai du tout refaire plus tard, mais cela m'a permit de vérifier que le montage fonctionnait (i.e. Les signaux passaient).
Aucune synchronsation

Aucune synchronsation

Syncho. horizontale seulement

Syncho. horizontale seulement



Le kit de développement DE1 est doté de mémoires externes que j'aurais pu utiliser, mais je voulais essayer de tout faire à même le FPGA. Malheureusement, il n'était pas possible de créer une instance d'un bloc de mémoire assez volumineux pour contenir une une trame CGA complète en couleur alors j'ai dû prendre une approche traitant les lignes individuelles plutôt qu'une image complète.

Il y donc deux blocs de mémoire utilisés en alternance. Pendant qu'une ligne CGA est stockée dans l'un, l'autre est rejouée au double de la vitesse vers la sortie VGA. Chaque échantillon stocké en mémoire contient les 4 bits de couleurs CGA ainsi que les signaux HSYNC et VSYNC. Ainsi je n'ai pas à me soucier de générer les signaux de synchronisation, mais en revanche ils ne sont problement pas très standard.

Beaucoup de « jitter »

Beaucoup de « jitter »

J'ai eu quelques difficultés. D'abord, comme je n'ai pas accès au signal d'horloge du générateur vidéo (le «pixel clock») je dois échantilloner à une fréquence très élevée pour détecteur les fronts «hsync». Par contre, je ne peux pas utiliser la même fréquence pour le stockage des pixels car à cette vitesse, la taille du bloc de mémoire requis devient trop grande. Je dois donc pour le stockage employer une horloge de fréquence plus basse obtenue en divisiant l'horloge principale pour rester en phase. Je resynchronise toutefois cette horloge dérivée en remettant les compteurs de division à zéro après chaque «hsync», histoire de garder constant le temps d'échantillonage des pixels par rapport au front «hsync». Sans cela, il y a toutes sortes d'effets indésirables (les lignes qui dancent, des caractères difformes, etc) causés par des battements entre l'horloge du générateur vidéo et celle d'échantillonnage.

La tentation était très forte de modifier mon Tandy 1000 pour pouvoir en extraire l'horloge utilisée pour générer l'image (via un connecteur BNC que j'ajouterais à l'arrière) mais j'ai résisté et finalement obtenu une image stable!

L'image est stable seulement sur un tube cathodique. Si l'on regarde de très très près, on apperçoit encore un peu de «jitter» mais c'est du domaine de ce qui est acceptable.

Problèmes sur LCD

Problèmes sur LCD

J'ai eu quelques problèmes à faire accepter le signal VGA par mes écrans LCD. À présent ça fonctionne mais les imperfections du mon signal vidéo semblent se faire amplifier par l'écran. Cette fois il semble s'agit de battements entre la fréquence d'échantillonage du LCD (lui aussi doit travailler très fort sans avoir accès à l'horloge du générateur) et mon horloge. Comme mon signal n'est pas parfaitement standard, cela ne donne pas de très bons résultats. Je crois à ce stade que je devrais refaire le projet en stockant une image complète en mémoire externe, que je pourrai ensuite reproduire vers l'écran VGA en respectant le standard. L'image à droite montre le problème: Certains pixels ne s'alignent pas verticalement.

Dans l'ensemble, je suis très satisfait du résultat car j'utilise un écran à tube cathodique. Mais je vais sans doute revisiter le projet puisque je vois déjà plusieurs choses à améliorer (refaire).

Petits «glitchs» de couleur

Petits «glitchs» de couleur

L'écran LCD qui refuse le signal...

L'écran LCD qui refuse le signal...


goto top Retour en haut


Les couleurs

Un dernier détail était celui des couleurs. Pour avoir des couleurs le plus rapidement possible, j'ai d'abord codé la conversion de couleur CGA RGBI vers VGA RGB de manière naïve, c'est à dire ainsi:
assign oR = {iIBGR[3],{7{iIBGR[0]}}};
assign oG = {iIBGR[3],{7{iIBGR[1]}}};
assign oB = {iIBGR[3],{7{iIBGR[2]}}};
(Exemple: Pour le rouge, si le bit d'intensité I est à 1, la sortie rouge analogique sera à 0xff ou 0x7f selon le bit du rouge côté CGA, si le bit d'intensité est à 0, la sortie sera alors 0x00 ou 0x7f. Même chose pour le vert et le bleu).

Wikipedia m'a apprit que cette approche était erronée. D'abord il y a une exception pour le jaune. Lorsque les bits rouge et vert sont à 1 mais que le bit d'intensité est 0, ce n'est pas du «jaune foncé» qu'on doit obtenir, mais du brun. De plus, la valeurs analogique doivent être 0x00 ou 0xAA (Intensité basse) et 0x55 et 0xFF (Intensité haute), sinon la différence entre gris foncé et gris pâle (couleurs 7 et 8) est beaucoup trop petite.

J'ai réécrit le code pour obtenir les bonnes couleurs:
always @(iIBGR)
begin
        if (iIBGR[3]) // High intensity/ light colors
        begin
                oR = iIBGR[0] ? 'hff : 'h55;
                oG = iIBGR[1] ? 'hff : 'h55;
                oB = iIBGR[2] ? 'hff : 'h55;
        end
        else
        begin
                if (iIBGR[2:0] == 3'b011)
                begin
                        oR = 'haa; oG = 'h55; oB = 'h00; // Brown is an exception (darker green)
                end
                else // Regular colors
                begin
                        oR = iIBGR[0] ? 'haa : 'h00;
                        oG = iIBGR[1] ? 'haa : 'h00;
                        oB = iIBGR[2] ? 'haa : 'h00;
                end
        end
end
Voici une comparaison des couleurs obtenues:
Couleurs erronées

Couleurs erronées

Couleurs correctes

Couleurs correctes

Pour comparaison: L'horreur NTSC

Pour comparaison: L'horreur NTSC



Je me suis servi de l'interpréteur de basic inclut dans la disquette de démarrage Tandy pour générer les tests de couleurs ci-dessus. Ah, que de nostalgie! Voici le listing:
10 CLS
20 FOR I = 0 TO 15
22 FOR J = 0 TO 7
30 COLOR I,J
40 PRINT " ** ";
45 NEXT J
46 COLOR I,0
47 GOSUB 100
48 PRINT
50 NEXT I
60 COLOR 15,0
99 END
100 FOR T = 65 TO 90
101 PRINT CHR$(T);
102 NEXT T
103 RETURN

goto top Retour en haut


Effet « scanlines »

Omettre d'afficher les lignes paires du côté VGA donne un bel effet « scanlines ».

Effect «scanlines»

Effect «scanlines»



L'effet est contrôlé via l'interrupteur SW0 de la carte de développement DE1:
always @ (posedge VGA_CTRL_CLK)
begin
	CGA_DATA_DOUBLESCAN <= (writebuf ? buf0_output : buf1_output) & ((vga_even & SW[0]) ? 6'h30 : 6'h3f);
end

goto top Retour en haut


Le résultat

Il n'y a qu'à comparer les images ci-dessous avec celles du début de cet article pour conclure qu'il est absolument indiscutable que la qualité d'image résultante est stupéfiante en comparaison. En fait, la qualité d'image est identique à celle obtenu lorsque ce jeu tourne sur une PC avec une carte VGA. Je trouve que c'est en fait un peu trop pur alors j'utilise l'effet « scanlines ».

MS-DOS 2.11. Vieux.

MS-DOS 2.11. Vieux.

Monuments of mars

Monuments of mars

Monuments of mars

Monuments of mars

Monuments of mars

Monuments of mars


goto top Retour en haut


Code source

Avertissement: Le code source est mis à votre disposition tel-quel et je n'offre absolument aucune garantie de quelque sorte que ce soit. L'ensemble est très certainement de mauvaise qualité et sans doute truffé d'erreurs de débutant et de mauvaises pratiques de programmation verilog. Mon excuse: Il s'agit de mon tout premier projet FPGA en verilog.
Fichier(s)DateDescription
cga2vga_scandoubler1.tar.gz (22 KB)
22 Novembre 2015 (Dimanche) Version initale (Approche «scan doubler» ligne par ligne) sans utilisation de mémoire externe.

goto top Retour en haut


Références

#DescriptionAdresse
1 Lecture Notes | Complex Digital Systems | Electrical Engineering and Computer Science | MIT OpenCourseWare http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-884-complex-digital-systems-spring-2005/lecture-notes/
2 Les exemples du CD fourni avec le kit Altera DE1 de terasIC http://www.terasic.com.tw/cgi-bin/page/archive.pl?No=83
3 Wikipedia: Color Graphics Adapter https://en.wikipedia.org/wiki/Color_Graphics_Adapter

goto top Retour en haut


Avertissement

Je ne saurais être tenu responsable pour les dommages que l'utilisation des informations ou la mise en œuvre des instructions présentées sur cette page pourrait causer à votre équipement, à vous-même ou à autrui. Aussi, je ne donne aucune garantie quant à l'exactitude des informations et à leur fonctionnement.

goto top Retour en haut


Les marques de commerce utilisées dans ce site appartiennent à leurs propriétaires respectifs.
Copyright © 2002-2017, Raphaël Assénat
Site codé avecSite codé avec vimDernière mise à jour: 29 Novembre 2015 (Dimanche)