< Very High Speed Integrated Circuit Hardware Description Language
fin de la boite de navigation du chapitre

Un des microcontroleur 8 bits les plus utilisés a été fabriqué par Microchip : c’est le 16F84. Un projet d'informatique embarquée le mettant en œuvre est décrit dans le chapitre sur CQPIC 16F84. Pour le moment nous allons nous intéresser à encore plus petit, le 16C57. Il possède un jeu d'instructions plus réduit : ses instructions sont codées sur 12 bits contre 14 sur le 16F84. Nous nous proposons dans ce chapitre de réaliser un système monopuce à l'aide d'un programme VHDL simulant un PIC 16C57 et si possible de le programmer en C. Je parle bien de simulation car un programme VHDL décrivant un processeur n'est jamais complètement équivalent au processeur que vous pouvez acheter.

Remarque

Le contenu de ce paragraphe peut sembler redondant avec les chapitres CQPIC 16F84 et ATMega8. Je rappelle que tous ces chapitres sont des projets qui font exactement la même chose, gérer un jeu de pong, d'où cette redondance. Il vous est demandé de choisir votre architecture et de lire le chapitre correspondant. Désolé, mais il en faut pour tous les goûts !

Nous allons commencer par rappeler les instructions assembleurs du PIC 16C57. A priori on ne programmera pas en assembleur, mais utiliser des architectures si petites demande parfois de savoir le faire.

Présentation du PIC 16C57

Dans cette section nous allons présenter le PIC 16C57 existant, je veux dire le composant commercialisé. Nous nous intéresserons au processeur softcore un peu plus loin.

Mémoire RAM du 16C57

Nous allons commencer par présenter le plus difficile, l'architecture du banc de registres (Register File dans la terminologie anglo-saxonne que l’on pourrait traduire par dossier/classeur de registres, mais je préfère registres mémoire ou mémoire de registres ou banc de registres). Je parle de difficultés parce qu’il y a plusieurs banques mémoires ce qui est une caractéristique des architectures 8 bits Microchip.

Le banc de registres est divisée en quatre banques. Nous avons d’abord la série des (vrais) registres jusqu'au PORTC. Cette partie de mémoire est suivie par 8 registres d’usage général pouvant être utilisés comme mémoire simple. Cet ensemble est identique quelle que soit la banque choisie. Ensuite vient une série de registre généraux qui eux dépendent de la banque mémoire choisie. Voici le schéma simplifié de cette architecture.

RAM et Registres
Adr.Banque0Banque1Banque2Banque3Adr.
00hIndirect addr.Indirect addr.Indirect addr.Indirect addr.60h
01hTMR0TMR0TMR0TMR0
02hPCLPCLPCLPCL
03hSTATUSSTATUSSTATUSSTATUS
04hFSRFSRFSRFSR
05hPORTAPORTAPORTAPORTA
06hPORTBPORTBPORTBPORTB
07hPORTCPORTCPORTCPORTC
08h - 0Fh8 registres généraux identiques sur les 4 banques28h - 2FH48h -4Fh68h -6FH
10h - 1Fh16 registres généraux16 registres généraux16 registres généraux16 registres généraux70H - 7FH

Calculons la taille de la mémoire de registres généraux :

4 x 16 + 1 x 8 = 72 octets.

C'est tout ce qu’il y a de disponible comme mémoire dans cette architecture.

Les registres oubliés

Dans la suite de ce cours, des registres TRISx et OPTION pourront apparaître. Ils ne sont pas présents dans la description que l’on vient de faire car ils ne sont accessibles que par des instructions spécialisées TRIS et OPTION présentées dans la section suivante.

Le jeu d'instructions du 16C57

Cette section est difficile à comprendre. Même si elle ne fait intervenir que des notions du niveau indiqué, il est conseillé d'avoir du recul sur les notions présentées pour bien assimiler ce qui suit. Cependant, ce contenu n'est pas fondamental et peut être sauté en première lecture.

Voici l’ensemble des instructions 12 bits du PIC 16C57. Vous trouverez en colonne de gauche le code binaire de l'instruction se trouvant au centre puis une courte explication de ce que fait l'instruction.

Les opérandes peuvent être de plusieurs types:

  • f : adresse mémoire de registres (register file address) de 00 à 7F
  • W : registre de travail
  • d : sélection de destination : d=0 vers W, d=1 vers f
  • pp : numéro de PORT entre 1 et 3 sur deux bits
  • bbb : adresse de bit dans un registre 8 bits (sur 3 bits)
  • k : champ littéral (8, ou 9 bits)
  • PC compteur programme
Jeu d'instructions 12-bit pour PIC 16C57
Opcode (binary)MnemonicDescription
0000 0000 0000NOPPas d'opération
0000 0000 0010OPTIONChargement du registre OPTION avec le contenu de W
0000 0000 0011SLEEParrête le processeur
0000 0000 0100CLRWDTReset du timer watchdog
0000 0000 01ppTRIS PChargement de W dans le registre de direction (P=1..3)
0000 001 fffffMOVWF fRecopie de W dans f
0000 010 xxxxxCLRWPositionne W à 0 (idem à CLR x, W)
0000 011 fffffCLRF fPositionne f à 0 (idem à CLR f, F)
0000 10d fffffSUBWF f, dSoustrait W de (d = f − W)
0000 11d fffffDECF f, dDécrément f (d = f − 1)
0001 00d fffffIORWF f, dOU Inclusif W avec F (d = f OR W)
0001 01d fffffANDWF f, dET entre W et F (d = f AND W)
0001 10d fffffXORWF f, dOU Exclusif W avec F (d = f XOR W)
0001 11d fffffADDWF f, dAdditionne W avec F (d = f + W)
0010 00d fffffMOVF f, drecopie F (d = f)
0010 01d fffffCOMF f, dComplement f (d = NOT f)
0010 10d fffffINCF f, dIncrément f (d = f + 1)
0010 11d fffffDECFSZ f, dDecrément f (d = f − 1) et saut si zero
0011 00d fffffRRF f, dRotation droite F (rotation droite avec la retenue)
0011 01d fffffRLF f, dRotation gauche F (rotation gauche avec la retenue)
0011 10d fffffSWAPF f, df>>4)
0011 11d fffffINCFSZ f, dIncrément f (d = f + 1) et saut si zero
0100 bbb fffffBCF f, bRaZ d'un bit de f (Clear bit b of f)
0101 bbb fffffBSF f, bMise à 1 d'un bit de f (Set bit b of f)
0110 bbb fffffBTFSC f, btest de bit de f, saute si zéro (Test bit b of f)
0111 bbb fffffBTFSS f, btest de bit de f, saute si un (Test bit b of f)
1000 kkkkkkkkRETLW kPositionne W à k et retour
1001 kkkkkkkkCALL kSauve l'adresse de retour, charge PC avec k
1010 kkkkkkkkkGOTO ksaut à l'adresse k (9 bits!)
1100 kkkkkkkkMOVLW kChargement littéral de W (W = k)
1101 kkkkkkkkIORLW kOU Inclusif littéral avec W (W = k OR W)
1110 kkkkkkkkANDLW kET littéral avec W (W = k AND W)
1111 kkkkkkkkXORLW kOU exclusif or littéral avec W (W = k XOR W)

Dans la terminologie Microchip, littéral signifie en adressage immédiat.

Accès à la mémoire de registres

Dans les instructions faisant intervenir la mémoire RAM (comme MOVWF f), f désigne une adresse mémoire qui semble être codée par fffff (5 bits) dans l'op-code. En fait il faut 7 bits pour coder l'adresse puisqu'elle peut aller jusqu'à 7Fh. Les deux bits manquants viennent d'ailleurs. D'où viennent-ils ? Les trois bits de poids fort du registre STATUS sont réservés à cet usage. Mais comme on l'a expliqué, la pagination est sur quatre pages différentes ce qui nécessite seulement deux bits (b6 et b5).

Mémoire ROM du 16C57

La taille de la ROM est de 2048 x 12 bits. C'est une mémoire très modeste qui limitera par la suite la taille des programmes que l’on fera tourner dans cette architecture, surtout si les programmes proviennent d'un compilateur.

Les trois ports du 16C57

Le PIC(R) 16C57 possède trois PORTs bidirectionnels appelés PORTA, PORTB et PORTC. La direction est choisie avec un registre TRIS dans lequel on écrit avec une instruction spéciale.

Avant de poursuivre, nous allons essayer de faire un parallèle avec le PIC 16C57 que l’on vient de décrire et le processeur softcore correspondant.

Le processeur softcore SLC1657

Dans la suite nous parlerons indifféremment de processeur softcore ou de cœur de processeur et le plus souvent encore de cœur.

Le SLC1657 est un cœur de processeur compatible avec le PIC® 16C57 de Microchip. On peut le trouver sur Internet : slc1657.zip

Il a été développé par SiliCore (sa dernière version date de septembre 2003) : Présentation du projet

Voici maintenant une description du cœur sous forme de programme VHDL.

entity TOPLOGIC is
    port (
            EADR:       out std_logic_vector(  6 downto 0 );
            EALU:       out std_logic_vector(  7 downto 0 );
            EMCLK_16:   out std_logic;
            EPRC:       out std_logic_vector( 10 downto 0 );
            EWERAM:     out std_logic;
            GP:         in  std_logic_vector(  7 downto 0 );
            MCLK:       in  std_logic;
            PCOUT0:     out std_logic_vector(  7 downto 0 );
            PCOUT1:     out std_logic_vector(  7 downto 0 );
            PCOUT2:     out std_logic_vector(  7 downto 0 );
            PTIN0:      in  std_logic_vector(  7 downto 0 );
            PTIN1:      in  std_logic_vector(  7 downto 0 );
            PTIN2:      in  std_logic_vector(  7 downto 0 );
            PTOUT0:     out std_logic_vector(  7 downto 0 );
            PTOUT1:     out std_logic_vector(  7 downto 0 );
            PTOUT2:     out std_logic_vector(  7 downto 0 );
            PTSTB0:     out std_logic;
            PTSTB1:     out std_logic;
            PTSTB2:     out std_logic;
            PRESET:     in  std_logic;
            RESET:      in  std_logic;
            ROM:        in  std_logic_vector( 11 downto 0 );
            SLEEP:      out std_logic;
            TESTIN:     in  std_logic;
            TMRCLK:     in  std_logic
        );
end entity TOPLOGIC;

Pour ce cœur, EPRC correspond au bus d'adresse de la ROM, tandis que "ROM" correspond au bus de données de cette même ROM. La RAM est gérée quant à elle avec EALU comme bus d'adresses et avec GP comme bus de données.

À noter que contrairement au PIC® 16F57, le composant SLC1657 utilise une entrée spécifique TMRCLK pour le timer0 (pour le PIC® c’est le bit b4 (appelé T0CKl) du PORTA).

Ensemble implanté dans le processeur softcore

L'ensemble des fichiers disponibles dans le projet SLC1657 ne décrit pas la RAM, la ROM et pas complètement les PORTs. Les concepteurs l'ont décidé car ces composants sont trop spécifiques aux fabricants.

Avant de passer aux périphériques nécessaires au bon fonctionnement, il nous faut aborder le problème très spécifique des ports.

Les trois ports du SLC1657

Les ports sont des entités très spécifiques dans un micro contrôleur car ils sont bidirectionnels. Ce côté bidirectionnel est difficile à implanter de manière générale dans un FPGA puisqu’il dépend du fabricant et donc de l'environnement d’utilisation. Alors, puisqu'elle dépend de l'environnement de programmation, cette description VHDL est laissée à l'utilisateur du cœur SLC1657.

En principe, cette bidirectionnalité est gérée par un registre spécial TRIS associé à chacun des ports. Ces registres spécifiques existent dans le cœur, ils s'appellent PCOUT0, PCOUT1 et PCOUT2. Ils sont tous sur 8 bits, contrairement au PIC® 16F57 pour lequel le PORTA ne possède que 4 bits et peuvent être utilisés soit comme ports généraux en sortie, soit pour la gestion bidirectionnelle. Les ports proprement dit se décomposent alors en port d'entrée PTIN0, PTIN1 et PTIN2 et en ports de sortie PTOUT0, PTOUT1 et PTOUT2.

Remarque : pour des raisons de compatibilité le port PCOUTi n’est pas accessible de manière normale. Si i=0 l'écriture dans PCOUT0 se fait par l'instruction :

              // en langage C
              TRISA=0xFF;

tandis qu'en assembleur on écrira :

             ; assembleur
             movlw 255        ;1:input
             TRIS      PORTA ;PORTA en entree

même si les commentaires de ce programme ne sont pas appropriés dans le cas où l’on n'a pas un port bidirectionnel (ce qui, je le rappelle, est notre cas).

Abordons maintenant les deux autres périphériques nécessaires, la RAM et la ROM. Ces deux composants ne font pas partie du cœur tout simplement parce que les implantations de ceux-ci sont spécifiques aux FPGA cibles.

La RAM

Nous avons déjà eu l’occasion de présenter celle-ci, en tout cas du point de vue de sa capacité. Sa spécificité est qu’il s'agit d'une mémoire à écriture synchrone mais à lecture asynchrone. Il est facile de trouver des exemples d'une telle mémoire en VHDL. Nous avons choisi :

             library ieee;
             use ieee.std_logic_1164.all;
             use ieee.numeric_std.all;
             entity xilinx_one_port_ram_sync is
               generic(
                  ADDR_WIDTH: integer:=7;
                  DATA_WIDTH: integer:=8
               );
               port(
                  clk: in std_logic;
                  we: in std_logic;
                  addr: in std_logic_vector(ADDR_WIDTH-1 downto 0);
                  din: in std_logic_vector(DATA_WIDTH-1 downto 0);
                  dout: out std_logic_vector(DATA_WIDTH-1 downto 0)
                );
             end xilinx_one_port_ram_sync;
             architecture beh_arch of xilinx_one_port_ram_sync is
               type ram_type is array (2**ADDR_WIDTH-1 downto 0)
                    of std_logic_vector (DATA_WIDTH-1 downto 0);
               signal ram: ram_type;
             begin
               process (clk)
               begin
                  if (clk'event and clk = '1') then
                     if (we='1') then
                        ram(to_integer(unsigned(addr))) <= din;
                        end if;
                  end if;
               end process;
                         dout <= ram(to_integer(unsigned(addr)));
             end beh_arch;
Remarque

Une mauvaise implémentation de cette mémoire est fatale au bon fonctionnement du processeur, surtout s'il est utilisé avec un compilateur C qui utilise des variables locales.

La ROM

C'est là que se situe le programme à exécuter. Puisque tout compilateur ou assembleur génère un fichier hex, il nous est nécessaire de transformer ce fichier hex en fichier VHDL. Ceci est laissé à un programme C décrit un peu plus loin. Commençons à présenter un exemple de ROM.

             library IEEE;
             use IEEE.std_logic_1164.all;
             use IEEE.numeric_std.all;
             entity PIC_ROM is
               port (
                          Addr : in std_logic_vector(10 downto 0);
                          Data : out std_logic_vector(11 downto 0));
               end PIC_ROM;
               architecture first of PIC_ROM is
               begin
                Data <=
                    "000000000000" When to_integer(unsigned(Addr)) = 0000 Else
                    "110011111111" When to_integer(unsigned(Addr)) = 0000 Else
                    "000000000101" When to_integer(unsigned(Addr)) = 0001 Else
                    "110000000000" When to_integer(unsigned(Addr)) = 0002 Else
                    "000000000110" When to_integer(unsigned(Addr)) = 0003 Else
                    "001000000101" When to_integer(unsigned(Addr)) = 0004 Else
                    "000000100110" When to_integer(unsigned(Addr)) = 0005 Else
                    "101000000100" When to_integer(unsigned(Addr)) = 0006 Else
                    "101000000000" When to_integer(unsigned(Addr)) = 2047 Else
                    "000000000000";
               end first;

Cet exemple montre un contenu mais chaque programme donnera une ROM différente. Il est à noter que ce que l’on présente est du VHDL alors que chaque compilateur ou assembleur ne délivre qu'un fichier au format HEX. Il faudra donc un utilitaire pour transformer le fichier HEX en fichier VHDL car il s'agit d'une opération qui peut se faire automatiquement. Nous utiliserons un programme en C que nous donnons en annexe 1.

Assemblage du cœur, des mémoires et du module VGA

Avant d'aller plus loin, relisez Interfaces VGA et PS2.

À la fin de ce précédent chapitre Interfaces VGA, nous avons présenté un module capable de dessiner une balle et deux raquettes, ces trois objets étant mobiles.

Utile aussi de lire ou de relire l'article sur le VGA.

Donnons une image simplifiée de ce que l’on cherche à faire.

Interface VGA et processeur

La gestion de l'écran VGA peut se présenter comme une boite, certes complexe, mais qui sait dessiner :

  • une balle
  • deux raquettes

La seule chose qui sera demandé au processeur sera de gérer toutes les coordonnées de ces éléments.

Toute interface entre un processeur et de la logique externe passe par un ou plusieurs ports. Nous donnons maintenant l'assemblage complet de notre projets sous la forme de choix technologiques avec d'un côté les entrées sorties (E/S) du module VGA et de l'autre les entrées sorties du processeur softcore.

choix technologiques
E/S module VGAE/S SLC1657
x_rect<9:8>TRISA<1:0>
x_rect<7:0>PORTA<7:0>
y_rect<9:8>TRISB<1:0>
y_rect<7:0>PORTB<7:0>
y_raqG<7:0>PORTC<7:0>
y_raqD<7:0>TRISC<7:0>

Ces choix montrent qu'on utilise 4 PORTs 8 bits pour faire les deux signaux 10 bits nécessaires aux deux coordonnées du rectangle (qui sera une balle) et deux ports 8 bits pour faire les deux entrées des positions des raquettes droites et gauches.

Exercice 1

Donner le programme VHDL correspondant aux choix technologiques ci-dessus qui assemble le composant SLC1657, la RAM, la ROM et le module VGA. Voir VGA-Exercice 2 pour le composant VGAtop, description de la RAM, de la ROM et le composant SLC1657.

Modifier les coordonnées de la balle et des raquettes en C

On va s'intéresser ici à la gestion de la coordonnée X de la balle. Le sous-programme responsable de cette action s’appelle "setX".

Le sous-programme "setX"

Notre problème est tout simple : si l’on regarde le tableau des choix technologiques, on s'aperçoit qu'on veut prendre une valeur sur 10 bits (en fait passée en paramètre comme "unsigned int" (soit 16 bits)), de la couper en deux pour la ranger dans deux registres différents. Le sous programme en C pur est alors :

void setX(unsigned int x){
  PORTA=x;
  TRISA=x>>8;
}

Le contenu de ce programme est complètement déterminé par la partie matérielle.

Utiliser le Timer0 avec le langage C

Nous aimerions bien explorer maintenant la possibilité de faire une attente (malheureusement active) en utilisant le seul et unique timer de notre architecture, à savoir le timer0. Commençons par présenter la documentation du Timer0 sous la forme d'un schéma :

Le timer 0 dans le SLC1657

La figure ci-dessus montre clairement la gestion du timer0 du SLC1657. Elle est légèrement différente de ce qui est matériellement réalisé par Microchip dans son PIC 16C57. Le registre général de gestion du timer 0 s’appelle OPTION dans le PIC et TCO dans le SLC1657. Dans le programme en C on utilisera naturellement "OPTION" pour désigner ce registre.

Exemple

On désire réaliser une attente la plus longue possible avec le timer 0. Une division par 256 sur le quartz interne est donc nécessaire. La figure vous permet de trouver les valeurs à mettre dans le registre OPTION.

Voici donc un exemple de code :

void wait(unsigned char tempo){ // temporisation avec timer0
  OPTION=0x07; // div par 256 et source=quartz
  TMR0 =0;
  while(TMR0<tempo);
}

La boucle d'attente est réalisée par un "while".

Fin de l'exemple

Le sous-programme "wait" de l'exemple ci-dessus appelé avec une valeur de 250 est un peu plus rapide qu'un appel de

void wait(int tempo){ // temporisation classique
  int i; 
  for(i=tempo;i>0;i--); 
}

avec une valeur de 30000.

Remarque

Le PIC 16C57 ne possède pas d'interruptions. Il est donc impossible de gérer la temporisation avec des interruptions. Les attentes seront donc actives, c'est-à-dire gérées par des boucles d'attentes : impossible de faire une autre tâche pendant l'attente.

Programme complet de gestion simple de la balle

Nous présentons maintenant un programme simple de gestion de la balle avec des rebonds :

#include <pic16F5x.h> // Programme pour Hitech C dans MPLAB
void setX(unsigned int x); 
void setY(unsigned int y); 
void wait(unsigned char tempo); 
unsigned int posRaqu_16; 
void main(){ 
int posX,posY; 
unsigned char raqD_y=0,raqG_y=0; 
signed char deltaX=1,deltaY=1; 
	while(1){	 
		posX=113;		 
		posY=101; 
		setX(posX); 
		setY(posY); 
		while(RC2==0); // attente départ 
		while( (posX>30) && (posX<580)){ 
			posRaqu_16=raqD_y<<1; 
			if ((posX>=574) && (posY<posRaqu_16+58) &&
                                                 (posY+10>posRaqu_16) && (deltaX>0)) deltaX= -deltaX; 
			posRaqu_16=raqG_y<<1; 
			if ((posX<=32) && (posY<posRaqu_16+58) &&
                                                (posY+10>posRaqu_16) && (deltaX<0)) deltaX= -deltaX; 
			posX=posX+deltaX; 
			setX(posX); 
			if ((posY>=460) && (deltaY>0)) deltaY= -deltaY; 
			if ((posY<=10) && (deltaY<0)) deltaY= -deltaY; 
			posY=posY+deltaY; 
			setY(posY); 
// gestion des raquettes {{Unité|2|bits}} PORTC/raquette 
	 	if (RC0) if (raqG_y<215) raqG_y++; 
			if (RC1) if (raqG_y>0) raqG_y--; 
// positionnement raquette gauche
	 	PORTC=raqG_y; 
			if (RC6) if (raqD_y<215) raqD_y++; 
			if (RC7) if (raqD_y>0) raqD_y--; 
// positionnement raquette droite
			TRISC=raqD_y; 
// atttente
			wait(250); 
			wait(250); 
		} 
	} 
} 


void setX(unsigned int x){ 
  PORTA=x; 
  TRISA=x>>8; 
} 

void setY(unsigned int y){ 
  PORTB=y; 
  TRISB=y>>8; 
} 

void wait(unsigned char tempo){ 
OPTION=0x07; // div 256 et source=quartz 
TMR0 =0; 
while(TMR0<tempo); 
}

Ce programme doit être compilé dans l'environnement MPLAB de Microchip. Cela nous donnera un fichier d'extension ".hex" qu’il faudra transformer en VHDL. Un programme en C capable de faire cette transformation est donné dans l'annexe qui suit.

Annexe 1 : transformer un fichier hex en fichier VHDL

Nous donnons sans plus d'explications un programme source en C permettant de transformer un fichier d'extension ".hex" provenant d'un compilateur ou d'un assembleur en fichier ".VHD" pouvant être directement inclus dans le projet VHDL.

//from Thomas A. Coonan (tcoonan@mindspring.com) and adapted for Xilinx by S. Moutou
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

FILE *fpi, *fpo; 

#define MAX_MEMORY_SIZE 1024 
struct { 
	unsigned int nAddress; 
	unsigned int byData; 
} Memory[MAX_MEMORY_SIZE]; 

char szLine[80]; 
unsigned int start_address, address, ndata_bytes, ndata_words; 
unsigned int data; 
unsigned int nMemoryCount; 
char *MakeBinaryString (unsigned int data); 

char *szHelpLine = 
"\nThe Synthetic PIC --- HEX File to VHDL Memory Entity conversion." 
"\nUsage: HEX2VHDL <filename>" 
"\n Input file is assumed to have the extension 'hex'." 
"\n Output will go to the filename with the extension 'vhd'.\n\n"; 

char szInFilename[40]; 
char szOutFilename[40]; 

int main (int argc, char *argv[]) 
{ 
	int i; 
	if (!(argc == 2 || argc == 3)) { 
		printf (szHelpLine); 
		exit(1); 
	} 
	if ( (strcmp(argv[1], "?") == 0) || 
		 (strcmp(argv[1], "help") == 0) || 
		 (strcmp(argv[1], "-h") == 0) || 
		 (strcmp(argv[1], "-?") == 0)) { 
		printf (szHelpLine); 
		exit(1); 
	} 
	strcpy (szInFilename, argv[1]); 
	if ( strstr(szInFilename, ".") == 0) 
		strcat (szInFilename, ".hex"); 

	strcpy (szOutFilename, argv[1]); 
	strcat (szOutFilename, ".vhd"); 

	if((fpi=fopen(szInFilename, "r"))==NULL){ 
		printf("Can\'t open file %s.\n", szInFilename); 
		exit(1); 
	} 
	nMemoryCount = 0; 
	while (!feof(fpi)) { 
		fgets (szLine, 80, fpi); 
		if (strlen(szLine) >= 10) {
			sscanf (&szLine[1], "%2x%4x", &ndata_bytes, &start_address); 
			if (start_address >= 0 && start_address <= 20000 && ndata_bytes > 0) { 
				i = 9; 
				ndata_words = ndata_bytes/2; 
				start_address = start_address/2; 
				for (address = start_address; address < start_address + ndata_words; address++) { 
					sscanf (&szLine[i], "%04x", &data); 
					data = ((data >> 8) & 0x00ff) | ((data << 8) & 0xff00); 
					i += 4; 
					Memory[nMemoryCount].nAddress = address; 
					Memory[nMemoryCount].byData = data; 
					nMemoryCount++; 
				} 
			} 
		} 
	} 
	if((fpo=fopen(szOutFilename, "w"))==NULL){ 
		printf("Can't open VHDL file '%s'.\n", szOutFilename); 
		exit(1); 
	} 
	fprintf (fpo, "\nlibrary IEEE;"); 
	fprintf (fpo, "\nuse IEEE.std_logic_1164.all;"); 
	fprintf (fpo, "\n--use IEEE.std_logic_arith.all;"); 
	fprintf (fpo, "\nuse IEEE.numeric_std.all;"); 
	fprintf (fpo, "\n\nentity PIC_ROM is"); 
	fprintf (fpo, "\n port ("); 
	fprintf (fpo, "\n	 Addr : in std_logic_vector(10 downto 0);"); 
	fprintf (fpo, "\n	 Data : out std_logic_vector(11 downto 0));"); 
	fprintf (fpo, "\nend PIC_ROM;"); 
	fprintf (fpo, "\n\n\narchitecture first of PIC_ROM is"); 
	fprintf (fpo, "\nbegin"); 
	fprintf (fpo, "\n Data <= "); 
	for (i = 0; i < nMemoryCount; i++) { 
		fprintf (fpo,"\n \"%s\" When to_integer(unsigned(Addr)) = %04d Else", 
			MakeBinaryString(Memory[i].byData)+4, 
			Memory[i].nAddress 
		); 
	} 
	fprintf (fpo, "\n \"000000000000\";"); 
	fprintf (fpo, "\nend first;"); 
	fclose (fpi); 
	fclose (fpo); 
} 

char *MakeBinaryString (unsigned int data) 
{ 
	static char szBinary[20]; 
	int i; 
	for (i = 0; i < 16; i++) { 
		if (data & 0x8000) { 
			szBinary[i] = '1'; 
		} 
		else { 
			szBinary[i] = '0'; 
		} 
		data <<= 1; 
	} 
	szBinary[i] = '\0'; 
	return szBinary; 
}
Remarque

Le fichier VHDL généré par ce programme comporte parfois une erreur sur le premier when : deux fois un When to_integer(unsigned(Addr)) = 0000 . C'est d'ailleurs le cas du seul exemple donné dans ce document. Je n'ai pas vérifié la gravité de ce problème mais il me semble que je n'ai jamais rencontré de disfonctionnement de ROM dans ce projet... ce qui me laisse penser que ce n’est pas très grave... mais il serait plus sage de vérifier tout cela.

Conclusion

On est très vite confronté au problème de la petite taille mémoire RAM du cœur. L'exemple typique a été les comparaisons pour déterminer si la balle heurtait la/les raquettes. Dès que cette comparaison était faite en C avec le sous-programme wait sans timer, on n'avait pas assez de mémoire RAM. Ce calcul a failli être déporté dans la partie matérielle décrite en VHDL avant que l’on s'aperçoive que l’utilisation d'un timer résolvait le problème.

Nous n'avons personnellement pas eu le temps de comprendre exactement la façon dont étaient gérées les variables avec le compilateur HitechC que l’on a utilisé. Des économies de place RAM dans le code généré n'ont ainsi pas pu être obtenues.

Lors de la réalisation de ce projet, nous étions obsédé par la possibilité de faire "tourner" du C dans un FPGA. C'est la première fois que nous en avions l’occasion en effet. Il nous semble après coup, que les limitations RAM de cette architecture, conduisent plutôt à l’utiliser en assembleur qu'en langage C.

Remarque

Depuis la rédaction de cette conclusion, nous avons appris comment augmenter la taille de la RAM et de la ROM du point de vue du compilateur C dans l'environnement MPLAB (voir Embarquer un PIC16F84). Nous pensons qu’il n’est pas très difficile d'augmenter la taille des bus correspondant dans ce cœur, mais nous ne l'avons pas testé.

Comparaison SiliCore1657 et Picoblaze®
SiliCore1657Picoblaze®
StatutLibreIP Core Xilinx
Code sourceDisponible au téléchargementDisponible au téléchargement chez Xilinx mais illisible
Taille dans FPGA30% d'un spartan 3 - 200k4% d'un spartan 3 - 200k
RAM72 octets64 octets
Registres7 spécialisés16 d’utilisation générale
PORTS3 mais peut être étendupeut être étendu jusqu'à 256
ROM2048 x 12 bits1024 x 18 bits
Pile2 niveaux31 niveaux
Environnement de développementMPLAB gratuit et connu qui trourne aussi sous Linux-winePBlazeIDE (mediatronix) et KPicosim sous Linux
LangagesC, assembleurassembleur et pbcc un compilateur C pour picoBlaze dérivé de sdcc
InterruptionsAucuneDispo mais à gérer soi-même. Existe un gestionnaire chez opencores.org
DocumentationTrès bien documenté en anglaisTrès bien documenté en anglais

Ces deux architectures ont à peu près les mêmes champs d'application : petits programmes et grosses parties matérielles autour. Nous avons une petite préférence pour le Silicore1657 parce qu’il existe un compilateur C qui le cible. Cette remarque n'est plus d'actualité puisque c’est vrai aussi pour le picoBlaze.

Remarque finale

Nous allons classer ce chapitre comme terminé alors qu’il y aurait bien des choses à encore explorer. La raison est simple : nous n'avons plus l'intention d’utiliser cette architecture car elle est trop limitée pour l’utilisation d'un compilateur C.

Remarque finale

Les 30% pour la taille dans le spartan3 surestiment probablement la taille réelle de ce projet. Depuis la rédaction de ce chapitre nous avons appris à gérer les mémoires programmes des soft-processeurs. Un chapitre a d'ailleurs été ajouté pour cela (entre autres) : Programmer in Situ et déboguer. Mais nous n'avons pas l'intention de revenir sur ce projet et améliorer cette utilisation des mémoires. Le PIC16F84 utilisé dans le chapitre suivant sera notre base de travail pour les PICs dorénavant.

Voir aussi

Cet article est issu de Wikiversity. Le texte est sous licence Creative Commons - Attribution - Partage dans les Mêmes. Des conditions supplémentaires peuvent s'appliquer aux fichiers multimédias.