Very High Speed Integrated Circuit Hardware Description Language/Travail pratique/Projets pour ATMEL ATTiny861

Leçons de niveau 15
Une page de Wikiversité, la communauté pédagogique libre.
Début de la boite de navigation du travail pratique
En raison de limitations techniques, la typographie souhaitable du titre, « Travail pratique : Projets pour ATMEL ATTiny861
Very High Speed Integrated Circuit Hardware Description Language/Travail pratique/Projets pour ATMEL ATTiny861
 », n'a pu être restituée correctement ci-dessus.

L'ATTiny 861 est une architecture simplifiée par rapport à l'ATMega8. Dans ce chapitre, nous allons donc utiliser cette nouvelle architecture. Ce TP cible essentiellement les FPGA de chez Xilinx. Mais le TP suivant est dédié aux FPGA de chez Altera.

Architecture[modifier | modifier le wikicode]

Architecture des registres mémoires[modifier | modifier le wikicode]

Il existe 3 espaces distincts en mémoire :

  • Les 32 premières adresses correspondent aux 32 registres. Ces registres sont directement reliés à l'ALU et tous les calculs ne peuvent être effectués qu’à partir de ces registres (addition, soustraction, multiplication, opérations logiques, tests ).
  • L'espace IO de l'adresse 0x20 à 0x5F. Dans cet espace se trouvent beaucoup de registres de gestion du matériel (les ports, Timers, UART,...). Pour accéder à ces registres les instructions in et out utilisent l'adresse 0 pour le premier registre et non l'adresse 0x20. Les instructions cli et sbi permettent de mettre à 0 ou à 1 un bit d'un registre
  • Enfin à partir de l'adresse 0x60 se trouve la mémoire à proprement parler (le Tiny861 possède 512 octets de RAM). Pour accéder à tout l'espace mémoire (Registre et zone I/O incluse) les instructions ST,LD et STS et LDS peuvent être utilisées. L'adressage peut être direct (STS ou LDS) ou indirect (ST et LD). Un adressage indirect utilise les registres X, Y ou Z (respectivement les registres R27:R26, R29:R28 et R31:R30) pour pointer sur une zone mémoire.

Quelques registres importants du banc de registres[modifier | modifier le wikicode]

Ce tableau (partiel) des registres respecte les fichiers d'inclusion du compilateur avr-gcc. Par exemple, dans la documentation officielle, le bit b0 du PORTB s’appelle PORTB0 tandis que dans le fichier d'inclusion du GNU-C il s’appelle PB0. Comme vous pouvez le constater, chaque registre est caractérisé par deux adresses. Chacune des deux adresses est espacée de 0x20=32. C'est le nombre de registres à usage général. Notez que le fichier d'inclusion avr/io.h que l’on utilisera très vite utilise les nombres qui ne sont pas entre parenthèses :

 /* Port B */ 
 #define PINB	_SFR_IO8(0x16) 
 #define DDRB	_SFR_IO8(0x17) 
 #define PORTB	_SFR_IO8(0x18)

Comment mettre un programme en ROM/RAM[modifier | modifier le wikicode]

Commençons par nous poser la question de savoir comment on peut compiler un programme en langage assembleur ? Le lien suivant décrit plus en détail les problèmes et les solutions : Transformer HEX en VHDL

L'assemblage[modifier | modifier le wikicode]

L'assemblage se fait en ligne de commande. Son objectif est de réaliser un fichier au format elf. Voici un exemple de commande :

avr-gcc -mmcu=attiny861 exo1.S -o exo1.elf

Nous avons décidé d’utiliser « avr-gcc » pour assembler (au lieu de avr-as). Cette décision a des conséquences importantes :

  • Utilisation du pré-processeur qui permet d’utiliser les noms des registres mais aussi les #include traditionnels en langage C
  • La table des vecteurs d'interruptions est automatiquement créée même si le RESET seul est utilisé
  • Le code doit se situer dans un main

Programmation de la mémoire programme[modifier | modifier le wikicode]

La programmation de la mémoire programme peut être réalisée de plusieurs manières. Si la manière la plus simple reste la transformation du programme en fichier VHDL, nous n'utiliserons pas cette méthode bien trop lente : elle nécessite une recompilation globale du projet, ce qui peut prendre quelques minutes. Nous allons plutôt utiliser un utilitaire data2mem comme indiqué dans la figure ci-après.

La commande qui fait cela est :

data2mem -bm memory.bmm -bd demo.elf -bt microcontroleur.bit -o uh microcontroleur

qui construit à partir du fichier microcontroleur.bit (résultat de la compilation de l'ISE) un ficher microcontroleur_rp.bit : c’est ce deuxième fichier que l’on va envoyer dans le FPGA. Cette commande nécessite un fichier memory.bmm qui sera fourni et d'ajouter quelques contraintes importantes dans le fichier ucf.

Pour la carte Spartan3E[modifier | modifier le wikicode]

Les contraintes dans le fichier ucf peuvent être :

INST prgmem/pe_0  LOC = RAMB16_X1Y5 ;
INST prgmem/pe_1  LOC = RAMB16_X1Y6 ;
INST prgmem/pe_2  LOC = RAMB16_X1Y4 ;
INST prgmem/pe_3  LOC = RAMB16_X1Y3 ;

Ces contraintes sont déjà présentes dans le fichier ucf donné dans les ressources mais sont en commentaires.

Le fichier memory.bmm correspondant contiendra :

ADDRESS_SPACE prgmem RAMB16 [0x00000000:0x00001FFF]
      BUS_BLOCK
        prgmem/pe_1 [7:4] PLACED = X1Y6;
        prgmem/pe_0 [3:0] PLACED = X1Y5;
        prgmem/pe_3 [15:12] PLACED = X1Y3 ;
        prgmem/pe_2 [11:8] PLACED  = X1Y4;
      END_BUS_BLOCK;
END_ADDRESS_SPACE;

Voici donc schématiquement comment cela se passe.

Pour la carte Nexys6[modifier | modifier le wikicode]

Les contraintes peuvent être :

## version microcontroleur seul
INST prgmem/pe_0 LOC = RAMB16_X0Y0;
INST prgmem/pe_1 LOC = RAMB16_X1Y4;
INST prgmem/pe_2 LOC = RAMB16_X0Y2;
INST prgmem/pe_3 LOC = RAMB16_X1Y6;

Quant au fichier memory.bmm associé, il doit alors absolument être

///////////////////////////////////////////////////////////////////////////////
//
// Address space 'prgmem' 0x00000000:0x00001FFF (8 KBytes).
//
///////////////////////////////////////////////////////////////////////////////

ADDRESS_SPACE prgmem RAMB16 [0x00000000:0x00001FFF]
    BUS_BLOCK
        prgmem/pe_1 RAMB16 [7:4] [0:4095] PLACED = X1Y4;
        prgmem/pe_0 RAMB16 [3:0] [0:4095] PLACED = X0Y0;        
        prgmem/pe_3 RAMB16 [15:12] [0:4095] PLACED = X1Y6;
        prgmem/pe_2 RAMB16 [11:8] [0:4095] PLACED = X0Y2;
    END_BUS_BLOCK;
END_ADDRESS_SPACE;

Travail introductif[modifier | modifier le wikicode]

Comprendre la notion de PORT d'entrée et de PORT de sorties Voici un premier exemple de programme assembleur

#define __SFR_OFFSET 0 //obligatoire pour fonctionnement correct de "out" et "in" 
.nolist 
#include <avr/io.h> 
.list 
      .section .text    ; denotes code section 
      .global main 
main: 
; LDI R16, 255 ; load R16 with 255 
; STS DDRB, R16 ; set all bits of port B as output 
; LDI R16, 2 ; load R16 with 2 
; LDI R17, 4 ; load R17 with 4 
; ADD R16, r17 ; R16 <- R16 + R17 
     IN     R16, PINA 
     OUT    PORTA, R16    ; result to port A 
     RJMP   main 
     .END

Ce programme ne fait que recopier l'état des interrupteurs (PINA) sur les LEDs (PORTA). Un certain nombre d'instructions ont été volontairement laissées en commentaire (qui rappelons-le, commencent par un « ; »). Intéressez-vous seulement à ce qu’il y a entre main : et .END

A ce stade, vous devez franchement être choqué par le fait que l’on ne positionne pas DDRA avant de sortir sur le PORTA. On rappelle donc que dans notre FPGA les PORTs ne seront pas bidirectionnels sauf si nécessaire (comme pour l'i2c par exemple). En clair, les fils qui arrivent sur PINA ne sont pas les mêmes que ceux qui sortent par PORTA. Cette propriété ne peut être vraie que pour un FPGA, pas pour un Tiny861 du commerce.


Travail à réaliser[modifier | modifier le wikicode]

Faites tourner le programme ci-dessus dans votre processeur (qui sera dans le FPGA). Si vous avez souvenir de quelques instructions assembleur, vous pouvez essayer de faire autre chose que la recopie sur vos LEDs

Travaux pratiques d’introduction à l'assembleur[modifier | modifier le wikicode]

Travail à réaliser (Exercice 1)[modifier | modifier le wikicode]

On réalisera un programme qui réalise une addition de 255 et de 1 et qui sort le registre de statut (SREG) sur les Leds (PORTA). Si vous voulez savoir ce qui sort, la seule solution est de sortir dans PORTB sur les afficheurs 7 segments.

Indication 1 : Le registre SREG peut être accédé comme un PORT par les instructions IN et OUT.

Indication 2 : 127 + 1 fait un overflow car on additionne deux nombres positifs et le résultat est négatif (en complément à deux)

Travail à réaliser (Exercice 2)[modifier | modifier le wikicode]

Charger R16 et R17 avec deux nombres et étudier les résultats et les bits de status (drapeaux) pour les opérations suivantes.

COM   R16    ; Complement
NEG   R16    ; 2's complement
TST    R16    ; test for zero or minus
AND  R16, R17   ; bitwise AND
OR    R16, R17   ; bitwise OR
ADD  R16, R17   ; summing

Indication : Un des deux registres peut être chargé à l'aide des interrupteurs.

Travail à réaliser (Exercice 3)[modifier | modifier le wikicode]

Additionnez un nombre et son complément à 2, et faites la même chose avec son complément à 1 et comparez les résultats

 
LDI      R16, 10    ; load number
MOV   R17, R16 
NEG   R16          ; 2's complement
ADD  R17, R16  

Travail à réaliser (Exercice 4)[modifier | modifier le wikicode]

On vous demande de réaliser un compteur binaire qui sort sur des LEDs. La seule difficulté de cet exercice est de réaliser une temporisation.

Indication : essayer de comprendre et d’utiliser ce code :

delay: 
        ldi r24, 0xff		; load 0xffff to r25:r24 
        ldi r25, 0xff 
delayloop: 
        sbiw r24,1		; decrement r25:r24 
        brne delayloop		; branch if not 0 
        ret

Montrer qu'un tel delay est encore trop rapide. Pour avoir un temps correct il faudra le faire environ 0x40 fois.

Travail à réaliser (Exercice 5)[modifier | modifier le wikicode]

On vous demande de rechercher les instructions à mettre en œuvre pour la réalisation de chenillards et de réaliser les programmes correspondants.

1°) Réaliser un chenillard simple avec l'instruction « lsl » en détectant la disparition du '1'. Indication : Si l'instruction « lsl » est appliquée dans une boucle, le '1' finit par disparaître !!!!

2°) Réaliser ce même chenillard en initialisant la SRAM avec un code du style :

main: 
     ; setup : initialisation de la SRAM 
     LDI R16,0x01 
     STS 0x0060,R16 ; debut mémoire SRAM ATMega8 
     LDI R16,0x02 
     STS 0x0061,R16

Puis aller chercher sans arrêt ces valeurs dans la RAM avec l'instruction « LD R16,Y+ » sachant que Y=R29:R28.

Indication : La SRAM d'un ATTiny26/46/86 commence à partir de l'adresse 0x0060. Sa fin dépend de la série (128/256/512 octets) 0x0DF/0x015F/025F

Un algorithme est donné sous forme d'organigramme un peu plus loin.

Réalisation d'une émission RS232[modifier | modifier le wikicode]

Sur le tiny861 du commerce, il n'y a pas de liaison série. Nous allons donc en ajouter une.

Un Logicore pour ne pas réinventer la roue[modifier | modifier le wikicode]

On vous donne l’ensemble matériel qui réalise la partie transmission de la liaison série.

Comment transmettre des données avec des logicores Xilinx

Réaliser la liaison série[modifier | modifier le wikicode]

Insérer ce logicore dans le composant travail à réaliser comme indiqué dans le schéma ci-dessous :

Exercice de préparation[modifier | modifier le wikicode]

Le compteur mod_m_counter est à configurer. Pour ce faire, il faut savoir que l’on va travailler à 19200 bauds et que la fréquence d'horloge est de 50 MHz. En déduire la valeur à mettre dans le generic de ce compteur.


Comment interfacer des PORTs/Registres dans l'ATTiny861[modifier | modifier le wikicode]

Pour information, le coeur original d'Andreas Hilvarsson qui réalise le PORTA est donné maintenant :

entity ioport is
	 Generic (BASE_ADDR	: integer := 16#3B#);
    Port ( clk : in  STD_LOGIC;
           addr : in  STD_LOGIC_VECTOR (5 downto 0);
           ioread : out  STD_LOGIC_VECTOR (7 downto 0);
           iowrite : in  STD_LOGIC_VECTOR (7 downto 0);
           rd : in  STD_LOGIC;
           wr : in  STD_LOGIC;
			 inport : in STD_LOGIC_VECTOR (7 downto 0);
           outport :	out  STD_LOGIC_VECTOR (7 downto 0));
end ioport;

architecture ioport_architecture of ioport is
begin

	im : process (clk)
		variable a_int : natural;
		variable rdwr : std_logic_vector(1 downto 0);
	begin
		if (clk'event and clk='1') then
			a_int := CONV_INTEGER(addr);
			if (a_int = BASE_ADDR) then
			 rdwr := rd & wr;
			 ioread <= (others => '0');
			 case rdwr is
				 when "10" => -- rd
					ioread <= inport;
				 when "01" => -- wr
					outport <= iowrite;
				 when others => NULL; -- do nothing
			 end case;
			end if;
		end if;
	end process im;	

end ioport_architecture;

Cet ensemble était transformé en composants puis câblé pour réaliser le PORT. Mais nous ne le garderons pas pour deux raisons :

  • son côté bidirectionnel est inutile dans un FPGA (en tout cas très souvent)
  • nous avons utilisé un ATMega8 très souvent dans ce cours et avons apprécié ses deux process séparés pour les entrées et sorties. Les PORTs et registres de l'ATMega8 restent pour nous une référence.

Nous avons donc essayé de les recopier dans ce projet en retirant donc l'aspect bidirectionnel des PORTs que nous jugeons absolument inutile dans un FPGA. Ainsi voici comment sera réalisée les entrées/sorties avec des PORTs :

Travail à réaliser (exercice 1)[modifier | modifier le wikicode]

On testera les communications avec gtkterm (application Linux) configuré comme ci-dessous (à part peut-être le PORT) :

PORT :/dev/ttyS0, vitesse : 19200, Parité : none, Bits 8, Bit de stop : 1, contrôle de flux : none

1°) Réaliser la partie matérielle complète en gérant le bit TXEN du registre UCSRB. L'écriture dans le registre UDR devra envoyer par la liaison série. De plus, les bits UDRE et TXC du registre UCSRA seront reliés au complément de "buffer_full". Pour simplifier, les registres UCSRB et UDR seront en écriture seulement et UCSRA sera en lecture seulement.

2°) Réaliser un programme qui lit sans arrêt les switchs et envoie sur la RS232 leur valeur seulement lorsqu’ils ont changé. Pour réaliser cet exercice vous allez procéder en deux temps :

2-1) Réaliser un programme qui lit sans arrêt les interrupteurs et envoie un '1' lorsqu’ils ont changés (et seulement dans ce cas). L'envoi du '1' se fera par un sous-programme. Bien faire la distinction entre '1' et 1 !!!!

Indication : il faut réserver un registre pour l'ancienne valeur des interrupteurs et un pour la nouvelle de manière à pouvoir comparer. En fin de boucle ne pas oublier de mettre la nouvelle valeur dans l'ancienne. Ceci est expliqué dans l'organigramme ci-dessus. L'envoi d'un caractère par la liaison série se fait par la simple instruction :

OUT UDR,R16

2-2) Modifier le sous programme « serial_tx » pour qu’il envoie maintenant deux caractères correspondant à la valeur hexadécimale des interrupteurs. Indication : voici comment on procède à l'envoi d'un caractère hexadécimal correspondant à la valeur des poids faibles :

   ;**** poids faible 
     MOV R18,R16 ; pour garder la valeur de R16 
     ANDI R18,0x0F 
     CPI  R18,0x0A 
     BRGE ss_3 ; if greater or equal 
     LDI R19,'0'; code ASCII de '0' 
     ADD R18,R19 
     RJMP ss_4 
ss_3: 
     LDI R19,'A'-0x0A ; code ASCII de 'A'-A 
     ADD R18,R19 
ss_4: 
     OUT UDR,R18

Faites la même chose avec les poids forts. Le fin du fin serait d'ajouter un espace pour séparer les futures valeurs. Essayez avec :

wait: 
     SBIS UCSRA,UDRE  ;attente envoi précédent terminé bit UDRE
     RJMP wait 
     LDI R18,' ' ; pour séparer la suite 
     OUT UDR,R18

Travail à réaliser (exercice 2)[modifier | modifier le wikicode]

Réaliser un compteur décimal sur deux digits sortant par la liaison série (avec une temporisation bien sûr).

Réalisation matérielle d'une réception RS232[modifier | modifier le wikicode]

Par rapport au TP précédent la liaison devra être dans l'autre sens : c’est le PC qui maintenant va fournir des valeurs au FPGA. La réalisation de cette liaison se fait pratiquement comme dans le TP précédent (_rx au lieu de _tx) :

Comment recevoir des données avec des logicores Xilinx

Travail à réaliser (exercice 1)[modifier | modifier le wikicode]

Modifier la partie matérielle de l'exercice 1 du TP précédent pour réaliser une réception maintenant. Le bit RXEN du registre UCSRB autorisera la réception. Le registre UDR sera maintenant en écriture pour envoyer par la liaison série, mais aussi en lecture pour lire ce qui est arrivé. De plus, le bit TXC du registre UCSRA sera relié au "data_present".

Indications :

Les chenillards et la RS232[modifier | modifier le wikicode]

On désire maintenant reprendre le chenillard déjà réalisé en assembleur, particulièrement celui qui utilisait une mémoire RAM pour s'exécuter. Pour simplifier tout sera maintenant réalisé en C.

Travail à réaliser (exercice 2)[modifier | modifier le wikicode]

On cherche maintenant à utiliser la réception RS232. L'objectif est de réaliser le chenillard normalement, sauf quand des données sont envoyées à partir du PC. Elle sont alors disposées dans la mémoire en lieu et place des anciennes valeurs. Pour simplifier votre travail, on adoptera le protocole suivant : quand une donnée arrive alors on sait qu’il y en aura forcément 16 et on les attendra toutes les 16. Une fois terminé on retournera tranquillement à notre chenillard.

N'oubliez pas la temporisation pour voir les chenillards.

Pour passer en mode programmation nous allons utiliser un interrupteur. En début de chaque boucle la position de cet interrupteur est testée et s'il est positionné on se dirige vers le programme de chargement. Un 'L' sera affiché sur les afficheurs dans ce cas mais un 'U' dans le cas d'un non chargement.

Exercice 2 en version assembleur[modifier | modifier le wikicode]

L'organigramme du programme à réaliser est présenté maintenant :

Réalisation d'une transmission RS232 pour carte Spartan6[modifier | modifier le wikicode]

Il est impossible d’utiliser les logicores précédents avec la carte Spartan6. La raison est que les logicores déjà utilisés sont certes écrit en VHDL, mais d'une manière très matérielle. En particulier, ils utilisent des LUT4 qui sont disponibles dans les circuits de typa Spartan3, mais pas dans les circuits Spartan6 qui utilisent des LUT6.

Le nouveau logicore adapté à notre problème présent s’appelle uart_tx6 et est disponible chez Xilinx. Il a à peu près les mêmes entrées/sorties que uart_tx, mais quelques noms ont été inversés.

Schéma de principe[modifier | modifier le wikicode]

Comme d'habitude, nous allons présenter le travail à réaliser sous forme schématique.

Réalisation matérielle[modifier | modifier le wikicode]

La solution présentée ci-dessous gère à la fois la réception et l'émission par la liaison série. Elle utilise par contre les logicores adaptés au FPGA de type Spartan6. Si vous utilisez ceux du spartan3 il vous faudra légèrement adapter le code ci-dessous car quelques entrées ont changé de nom.

Réalisation partielle du timer 1[modifier | modifier le wikicode]

Nous allons donc partiellement réaliser le timer 1.

  • Partiellement car il est normalement sur 10 bits et nous n'en garderons que 8.
  • Partiellement car nous nous contenterons d'une division sur 3 bits (du registre TCCR1B) contre 4 bits dans celui du commerce. Seules les divisions par 0, 1, 2, 4, 8, 16, 32 et 64 seront implantées. Il manque donc les divisions par 128, 256, 512, 1024, 2048, 4096, 8192 et 16384.
  • Partiellement car nous n'implémenterons ni le dépassement de capacité ni les

comparaisons....

  • Partiellement car donc seuls TCNT1 (0x2E) et TCCR1B (0x2F) seront implémentés.
Simple description du timer1 dans le Tiny861

La dernière remarque peut sembler une limitation importante, mais il vous faut savoir qu’il n'y a aucun mécanisme d'interruption dans ce cœur !

Indication 1[modifier | modifier le wikicode]

On prendra soin de définir les deux constantes TCNT1 et TCCR1B. On réalisera ensuite le célèbre :

s_wr_TCNT1 <= IO_wr when IO_A = TCNT1 else '0';

Le timer pourra être un simple process dans "microcontroleur.vhd"

process(clk) begin
  if rising_edge(clk) then
    if s_wr_TCNT1 = '1' then
      s_s_tcnt1 <= s_tcnt1; -- s_tcnt1 vient du process iowr (à ajouter)
    elsif s_en_timer1 = '1' then
      s_s_tcnt1 <= s_s_tcnt1 + 1;
    end if;
  end if;
end process;

Indication 2[modifier | modifier le wikicode]

La prédivision peut être faite par :

-- Timer 1
-- prescaler
process(clk) begin
  if rising_edge(clk) then
    s_prescaler <= s_prescaler + 1;
  end if;
end process;
-- tick generation
s_div2 <= '1' when s_prescaler(0) = '1' else '0';
s_div4 <= '1' when s_prescaler(1 downto 0) = "11" else '0';
s_div8 <= '1' when s_prescaler(2 downto 0) = "111" else '0';
s_div16 <= '1' when s_prescaler(3 downto 0) = "1111" else '0';
s_div32 <= '1' when s_prescaler(4 downto 0) = "11111" else '0';
s_div64 <= '1' when s_prescaler(5 downto 0) = "111111" else '0';
-- grand multiplexeur maintenant
s_en_timer1 <= '0' when s_tccr1b(2 downto 0) = "000" else -- arrêt timer
               '1' when s_tccr1b(2 downto 0) = "001" else -- division par 1
               s_div2 when s_tccr1b(2 downto 0) = "010" else
               s_div4 when s_tccr1b(2 downto 0) = "011" else
               s_div8 when s_tccr1b(2 downto 0) = "100" else
               s_div16 when s_tccr1b(2 downto 0) = "101" else
               s_div32 when s_tccr1b(2 downto 0) = "110" else
               s_div64 when s_tccr1b(2 downto 0) = "111";

Vous complétez correctement les deux process iowr et iord et c’est tout.

Voici maintenant la solution partielle.

ANNEXE I : les fichiers nécessaires aux TPs de ce chapitre (Version pour Spartan 6)[modifier | modifier le wikicode]

Le cœur ATTiny861 est disponible ICI chez Opencores.org. Nous en donnons une version modifiée pour un fonctionnement avec data2mem ce qui simplifie sa programmation mais le rend très dépendant des outils Xilinx.

La mémoire programme modifiée[modifier | modifier le wikicode]

Nous avons modifié la mémoire programme par rapport au cœur original. En voici la nouvelle version destinée à réaliser 8 ko pour la version ATTiny861 donc.

La mémoire donnée modifiée[modifier | modifier le wikicode]

Nous avons modifié la mémoire donnée par rapport au cœur original. En voici la nouvelle version destinée à réaliser 512 octets pour la version ATTiny861 donc.


Le fichier ucf[modifier | modifier le wikicode]

Cœur complet[modifier | modifier le wikicode]

Recherchez ICI chez Opencores.org et prenez le fichier mcu_core.vhd

Nous ne le reproduisons pas ici car nous n'y avons fait aucune modification.

Microcontroleur complet[modifier | modifier le wikicode]

Il ne vous manque maintenant que les outils pour travailler du point de vue logicielle.

Outils pour programmer[modifier | modifier le wikicode]

Si vous voulez utiliser l'outil Xilinx data2mem, il vous faut le fichier bmm. En voici un pour la carte Nexys 3 :

// BMM LOC annotation file.
//
// Release 14.5 -  P.58f, build 3.0.7 Mar 3, 2013
// Copyright (c) 1995-2015 Xilinx, Inc.  All rights reserved.


///////////////////////////////////////////////////////////////////////////////
//
// Address space 'prgmem' 0x00000000:0x00001FFF (8 KBytes).
//
///////////////////////////////////////////////////////////////////////////////

ADDRESS_SPACE prgmem RAMB16 [0x00000000:0x00001FFF]
    BUS_BLOCK
        prgmem/pe_1 RAMB16 [7:4] [0:4095] PLACED = X1Y4;
        prgmem/pe_0 RAMB16 [3:0] [0:4095] PLACED = X0Y0;
        
        prgmem/pe_3 RAMB16 [15:12] [0:4095] PLACED = X1Y6;
        prgmem/pe_2 RAMB16 [11:8] [0:4095] PLACED = X0Y2;
    END_BUS_BLOCK;
END_ADDRESS_SPACE;

et voici un exemple de script pour assembler et télécharger :

#!/bin/bash
#export PATH=$PATH:/usr/bin
export PATH=$PATH:/opt/Xilinx/14.5/ISE_DS/ISE/bin/lin64:/usr/local/bin
cp ../microcontroleur.bit .
avr-gcc -mmcu=attiny861 Td7exo5.S -o Td7exo5.elf
avr-objdump -h -S Td7exo5.elf > Td7exo5.lss
data2mem -bm memoryS6.bmm -bd Td7exo5.elf -bt microcontroleur.bit -o uh microcontroleur
djtgcfg prog -d Nexys3 --index 0 --file microcontroleur_rp.bit

que vous devrez adapter à votre configuration. Nous travaillons toujours dans un sous répertoire du projet que l’on appelle soft. Cela explique pourquoi le script commence par aller chercher le fichier .bit dans un répertoire parent (cp ../microcontroleur.bit .). Ce script compile (assemble plutôt) un fichier appelé Td7exo5.S met le programme dans le fichier .bit pour le télécharger avec l'outil de Digilent djtgcfg.

ANNEXE II : les fichiers nécessaires aux TPs de ce chapitre (Version pour Spartan 3E)[modifier | modifier le wikicode]

Le fichier global microcontroleur.vhd de la section précédente est identique.

Notre mémoire donnée[modifier | modifier le wikicode]

La mémoire donnée VHDL ci-dessous peut être utilisée sans problème. Elle est réalisée avec des RAMB16_S4 propre à Xilinx et ne peut donc pas être portée telle quelle.

Cette mémoire de données est bien plus grande que celle du 861 du commerce (512 octets) puisqu'elle a une taille de 4 ko. Est-ce gênant ? Il est clair que c’est du gaspillage. Pour information, nous avons déjà utilisé des surplus de mémoire RAM à l'aide de pointeurs que l’on initialise nous même. Si l’on tente de réaliser cela, il nous faudra modifier la première ligne de l'architecture :

begin
  add <= "000" & addr(8 downto 0);

qui limite la mémoire RAM à 512 octets. Ceci est laissé comme exercice pour le lecteur.

Venons-en à la mémoire programme.

Notre mémoire programme[modifier | modifier le wikicode]

Commençons par donner la mémoire programme pour la carte Spartan 3E :

Cette mémoire programme a une taille de 8 ko, ce qui est sa taille dans le Tiny861 du commerce.

Les contraintes dans le fichier ucf peuvent être :

INST prgmem/pe_0  LOC = RAMB16_X1Y5 ;
INST prgmem/pe_1  LOC = RAMB16_X1Y6 ;
INST prgmem/pe_2  LOC = RAMB16_X1Y4 ;
INST prgmem/pe_3  LOC = RAMB16_X1Y3 ;

et elles doivent être associées au fichier attiny861.bmm suivant :

ADDRESS_SPACE prgmem RAMB16 [0x00000000:0x00001FFF]
      BUS_BLOCK
        prgmem/pe_1 [7:4] PLACED = X1Y6;
        prgmem/pe_0 [3:0] PLACED = X1Y5;
        prgmem/pe_3 [15:12] PLACED = X1Y3 ;
        prgmem/pe_2 [11:8] PLACED  = X1Y4;
      END_BUS_BLOCK;
END_ADDRESS_SPACE;

Cœur complet[modifier | modifier le wikicode]

Recherchez ICI chez Opencores.org et prenez le fichier mcu_core.vhd

Nous ne le reproduisons pas ici car nous n'y avons fait aucune modification.

Script d'assemblage[modifier | modifier le wikicode]

Nous avons laissé un certain nombre de commentaires qui montrent que nous utilisons une carte Nexys3 et l'ISE 14.5, mais que pour les besoins de l'enseignement, l'ISE 11.1 est utilisé avec une carte Spartan3E500

#!/bin/bash
#export PATH=$PATH:/usr/bin
#export PATH=$PATH:/opt/Xilinx/14.5/ISE_DS/ISE/bin/lin64:/usr/local/bin
export PATH=$PATH:/usr/bin:/opt/Xilinx/11.1/ISE/bin/lin
cp ../microcontroleur.bit .
avr-gcc -mmcu=attiny861 tp7.S -o tp7.elf
avr-objdump -h -S tp7.elf > tp7.lss
data2mem -bm attiny861.bmm -bd tp7.elf -bt microcontroleur.bit -o uh microcontroleur
#djtgcfg prog -d Nexys3 --index 0 --file microcontroleur_rp.bit
djtgcfg prog -d DCabUsb --index 0 --file microcontroleur_rp.bit

Commentez la dernière ligne si vous n'avez pas le cable USB JTAG et utilisez alors IMPACT pour télécharger!