Génération d’une trame DVI avec un FPGA

Génération d’une trame DVI avec un FPGA

Cet article a pour sujet la génération des signaux vidéo DVI à l’aide d’un FPGA.
En illustration, j’utiliserai la carte MKR VIDOR 4000 pour afficher un dégradé de couleur.

L’envoi d’une trame DVI à un équipement se fait pixel par pixel et ligne par ligne.
Pour chaque pixel on enverra ses 3 composantes Rouge/Vert/Bleu, ainsi qu’une horloge de synchronisation. Il faut pour ceci 4 liaisons séries (ces liaisons étant différentielles il y a physiquement 8 fils).

Chaque coup d’horloge correspondant à un pixel, on va envoyer durant cette période les 8 bits (codés sur 10) de chaque composante.

Ce codage sur 10 bits (TMDS) autorise des très hauts débits, tout en réduisant les interférences électromagnétiques.
Hormis les valeurs de pixels, il est également nécessaire d’envoyer à l’afficheur les fins de ligne et fins d’image afin que celui-ci puisse adapter sa résolution. Ces informations HSYNC (Synchronisation horizontale) et VSYNC (Synchronisation verticale) sont transmises comme des valeurs réservées des composantes.
Les synchronisations horizontale et verticales sont entourées de paliers (Front/Back Porch) codés également avec une valeur réservée.

Voici donc à quoi ressemble une trame :

 

Les symboles HRES et VRES sont les résolutions horizontale et verticale ; HSS et VSS correspondent aux valeurs de début de synchronisation ; HSE et VSE aux valeurs de fin ; et enfin HTOTAL et VTOTAL au nombres de pixel par ligne et au nombre de ligne.

Vous retrouverez ces valeurs dans la plupart des descriptions de format vidéo.

name refresh (Hz) hres hss hse htot vres vss vse vtot hsync

pol

sync

pol

1920×720 60 1920 2008 2208 2496 720 721 724 746

+

1024×768 60 1024 1048 1184 1344 768 771 777 806

800×600 60 800 840 968 1056 600 601 605 628

+

+

800×600 56 800 824 896 1024 600 601 603 625

+

+

640×480 60 640 656 752 800 480 489 492 525

 

Dans notre exemple, la génération des signaux de synchronisation est faite dans un module FRAME_GEN qui recevra en entrée une horloge de fréquence égale à celle des pixels à afficher (CLK_PIX = htot * vtot * refresh). Ce module produira également les coordonnées du pixel en cours à destination des modules de génération graphique.

module FRAME_GEN #(
	parameter pHRES=640,
	parameter pHSS=656,
	parameter pHSE=752,
	parameter pHTOTAL=762,
	
	parameter pVRES=480,
	parameter pVSS=490,
	parameter pVSE=492,
	parameter pVTOTAL=525,
	
	parameter pHSPOL=0,
	parameter pVSPOL=0
 )
 
 (
	input	iCLK_PIX,
	input	iDISP_ENAB,

	output		oHS,
	output reg	oVS,
	output		oVBLANK,

	output [11:0]	oHCNT,
	output [11:0]	oVCNT
 );

	reg [11:0] rHCNT, rVCNT;

	assign oHCNT = rHCNT;
	assign oVCNT = rVCNT;

	assign oBLANK = (((oVCNT >= pVRES) || (oHCNT >= pHRES)) && (iDISP_ENAB == 1)) ? 1 : 0;
	assign oHS = ((oHCNT >= pHSS) && (ohCNT < pHSE))  ? ~pHSPOL : pHSPOL;

	always @(posedge iCLK_PIX or negedge iDISP_ENAB)
	begin
		if (iDISP_ENAB == 0) begin
			rHCNT <= 0;
			vHCNT <= 0;
			oVS <= 0;
		end
		else begin
		  rHCNT <= rHCNT+1;
		  
		  if (rHCNT == pHTOTAL-1) begin // Fin de ligne
			rHCNT <=0;
			rVCNT <= rVCNT+1;
			if (rVCNT == pVTOTAL-1) // Fin de trame
			  rVCNT <=0;
		  end
		  
		  if (rHCNT == pHSS-1) begin	// Debut et fin Sync. V sur debut Sync. H
			if (rVCNT == pVSS-1) 
			  oVS <= ~pVSPOL;
			if (rVCNT == pVSE-1) 
			  oVS <= pVSPOL;
		  end
		end
	end

endmodule

 

Nous allons maintenant créer un fond d’écran avec un dégradé de couleur.
Un module VIDEO_BACKGROUND récupérera en entrée l’horloge CLK_PIX ainsi que les coordonnées X et Y du pixel à afficher.

module VIDEO_BACKGROUND (
	input			iCLK_PIX,
	input [11:0]	iPIXEL_X,
	input [11:0]	iPIXEL_Y,
	
	output [7:0]	oRED,
	output [7:0]	oGRN,
	output [7:0]	oBLU
 );

	reg [7:0] cBLU,cRED,cGRN;

	assign oBLU[7:0] = cBLU;
	assign oGRN[7:0] = cGRN;
	assign oRED[7:0] = cRED;

	always @(negedge iCLK)
	begin
		cBLU[7:0] <= iPIXEL_X[8]==1'b1 ? ~iPIXEL_X[7:0]:iPIXEL_X[7:0];
		cGRN[7:0] <= iPIXEL_Y[8]==1'b1 ? ~iPIXEL_Y[7:0]:iPIXEL_Y[7:0];
		cRED[7:0] <= 8'b00000000; 
	end

endmodule

 

Nous avons vu qu’il fallait pour chaque période de CLK_PIX, générer 10 bits par composante RVB. Avoir une horloge 10 fois plus rapide risquerait de ne pas être supporté par le FPGA pour la plupart des résolutions. Nous allons donc utiliser le DDR en transférant les données à la fois sur les fronts montant et descendant de l’horloge. Dans ce cas, il nous suffit d’avoir une fréquence d’horloge égale à CLK_PIX * 5.

La génération des horloges par une PLL, est avec le choix des paramètres de résolution, la partie la plus sensible à prendre en considération si vous souhaitez voir s’afficher une image sur votre téléviseur (les moniteurs informatiques semblent plus conciliant).

Il nous faut :
– CLK_PIX au plus proche de htot * vtot * refresh.
– CLK_PIXx5 exactement égale à 5 fois CLK_PIX.
– CLK_PIXx2 (utilisée par la suite) exactement égale à 2 fois CLK_PIX.

Les sorties d’horloge de la PLL se configurent avec 2 coefficients : multiplicateur (M) + diviseur (N).
Fsortie = Fentrée * M / N

Si nous prenons le cas des paramètres d’affichage présent dans le module FRAME_GEN :

name refresh (Hz) hres hss hse htot vres vss vse vtot hsync

pol

sync

pol

640×480 60 640 656 752 762 480 490 492 525

+

+

La fréquence CLK_PIX doit être égale à 24.003 Mhz (arrondissons à 24Mhz).
L’entrée d’horloge que nous utiliserons est le signal à 48Mhz envoyé par le SAMD21 de la carte MKR 4000.
Nous aurons donc :

  • inclk0 = 48Mhz
  • c0 (48Mhz) : M = 1, N = 1
  • c1 (24Mhz) : M = 1, N = 2
  • c2 (120Mhz) : M = 5, N = 2

Sous Quartus je vous conseille d’utiliser le « Megawizard Plug-In Manager », pour générer cette PLL « PLL_VIDEO ». Vous pourrez ainsi aisément saisir ses paramètres et les modifier par la suite.

 

      

 

Pour la génération des signaux DVI (nommés oHDMI_TX[2:0] et oHDMI_CLK) nous récupérerons les modules DVI_OUT et TMDS_ENCODE développés par Dario Pennisi pour la carte MKR VIDOR 4000.

Il ne nous reste plus qu’à assembler les différents modules dans le fichier MKRVIDOR4000_top.v

 

// signal declaration

wire        wOSC_CLK;

wire [7:0]  wDVI_RED,wDVI_GRN,wDVI_BLU;
wire        wDVI_HS, wDVI_VS, wDVI_DE;
wire		wDVI_OUT_ENABLE;

wire        wVID_CLK, wVID_CLKx5;
wire		wVID_PLL_LOCK;

wire [11:0]	wVID_X,wVID_Y;

assign wDVI_OUT_ENABLE = iHDMI_HPD && wVID_PLL_LOCK;

// internal oscillator
cyclone10lp_oscillator   osc
  ( 
  .clkout(wOSC_CLK),
  .oscena(1'b1));

FRAME_GEN FRAME_GEN_inst
(
	.iCLK_PIX(wVID_CLK),
	.iDISP_ENAB(wDVI_OUT_ENABLE),

	.oHS(wDVI_HS),
	.oVS(wDVI_VS),
	.oDE(wDVI_DE),

	.oHCNT(wVID_X),
	.oVCNT(wVID_Y)
 );

  
VIDEO_BACKGROUND VIDEO_BACKGROUND_inst 
(
	.iCLK_PIX(wVID_CLK),
	.iPIXEL_X(wVID_X),
	.iPIXEL_Y(wVID_Y),
	
	.oRED(wDVI_RED),
	.oGRN(wDVI_GRN),
	.oBLU(wDVI_BLU)
);
 
DVI_OUT DVIOUT_inst(
	.iPCLK(wVID_CLK),
	.iSCLK(wVID_CLKx5),
	.iRED(wDVI_RED),
	.iGRN(wDVI_GRN),
	.iBLU(wDVI_BLU),
	.iHS(wDVI_HS),
	.iVS(wDVI_VS),
	.iDE(wDVI_DE),
	
	// Attention à l'inversion des signaux pour oHDMI_TX
	.oDVI_DATA({oHDMI_TX[0],oHDMI_TX[1],oHDMI_TX[2]}),
	.oDVI_CLK(oHDMI_CLK)
	
);
 
PLL_VIDEO PLL_VIDEO_inst(
  .areset(1'b0),
  .inclk0(iCLK),
  .c1(wVID_CLK),
  .c2(wVID_CLKx5),
   
  .locked(wVID_PLL_LOCK));

 

Après compilation du projet, transformation du fichier ttf en app.h, et téléversement du Sketch dans votre carte, vous devriez voir apparaître l’écran suivant sur votre TV :

Vous trouverez le projet Quartus et le sketch associé sur la page de téléchargement
Dans le prochain article nous verrons comment afficher un terminal utilisant une liaison série.

 

 

 

Une réponse

  1. DjTGv dit :

    Bonjour Philippe,

    Top ! Impatient d’obtenir le convertisseur micro hdmi vers vga que j’ai commandé car en effet mon
    téléviseur ne supporte pas le 640×480 et je compte brancher mon Arduino FPGA Vidor MKR4000 à un
    ancien moniteur (si si j’en ai encore un 😉

    Merci pour ces articles passionnant.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *