# Very High Speed Integrated Circuit Hardware Description Language/Le NIOS d'Altera

Comme l'indique le titre de ce chapitre, nous allons utiliser le processeur N.I.O.S. de chez Altera. Il s'agit d'un processeur 32 bits de type RISC propriétaire, c'est-à-dire que vous ne disposez pas de son code source.
Les cartes que l'on utilise avec les étudiants de 2ème année étant des DE2-115, l'essentiel de ce qui va être décrit ici sera destiné à ces cartes. Il n'y a aucun problème pour l'adapter à d'autres cartes. Seul le fichier de contraintes sur les broches de FPGA est différent.
Au risque de nous répéter, nous avons beaucoup utilisé de processeurs libres jusqu'à présent. Mais les petits problèmes rencontrés nous incitent à utiliser des processeurs réalisés par une équipe d'ingénieurs. Parmi les problèmes rencontrés se trouve la difficulté que nous avons à porter sur Altera, notre processeur préféré jusqu'à présent décrit dans un chapitre de ce livre sur l'ATMega8 d'une part, et son amélioration dans le chapitre suivant.
Ils sont tout simplement plus fiables mais en général sont propriétaires. L'environnement de développement lui aussi est propriétaire, mais vous pouvez le télécharger et avoir une licence WEBPACK gratuite. Tous les outils utilisés dans ce chapitre sont gratuits.

## Introduction

Malgré l'expérience accumulée par l'écriture de ce livre, il nous a fallu une dizaine d'heure pour faire tourner notre premier NIOS. Ceci est très anecdotique mais vous montre quand même que dans le domaine des SOCs rien n'est jamais simple. Vous pouvez trouver des tutoriels sur Youtube, des documents PDF sur internet. La documentation ne manque pas, mais vous êtes arrêté par de simples détails, parce que ce que vous lisez ou voyez dans ces documents, ne correspond pas ce que vous avez sous les yeux dans votre environnement de développement matériel. Nous ne pourrons malheureusement pas faire mieux que les autres car nous ignorons totalement quelle version de Quartus vous utilisez au moment où vous lisez ces lignes. La seule chose que nous pouvons préciser est que nous utilisons la version 15.0 de Quartus qui n'est déjà plus la dernière à ce jour.
Nous allons essayer d'expliquer comment les choses sont liées plutôt que de donner des copies d'écran qui seront obsolètes très vite. Si vous n'êtes pas arrivé directement ici et avez lu quelques chapitres de ce livre, vous savez que le problème des SOCs est de relier la chaîne de compilation matérielle et la chaîne de compilation logicielle.

## Mon premier exemple

La réalisation d'un SOC se fait en deux grandes étapes :
* la réalisation matérielle
* la programmation du matériel par du logiciel
Nous allons organiser la suite en respectant ces étapes, d'autant plus qu'elles utilisent des logiciels complètement différent.

### Réalisation matérielle

La réalisation matérielle utilise deux logiciels complètement différent :
* Quartus pour la synthèse finale
* QSys (ancien SOPC) pour la définition du NIOS (deveindra Platform Designer)
C'est l'utilisation de deux logiciels différents qui rend les choses délicates pour une première utilisation. L'objectif de QSys est de générer un fichier d'extension .qip dans lequel se trouve un fichier VHDL (ce n'est pas VHDL par défaut mais verilog). Ce fichier doit avoir 1 seule entrée, l'horloge et 8 sorties. Cela correspond à notre cahier des charges : les chenillars étant courant dans les tutoriels trouvés sur Internet, nous allons nous intéresser à un compteur qui sortira sa valeur sur un afficheur sept segments.

#### Quartus

On commence par lancer Quartus et par réaliser un projet Quartus. Ce projet s'appellera pour nous niosii, et par défaut nécessitera donc une entité niosii, celle la même qui sera générée automatiquement par QSys. À part positionner le FPGA cible, on n'a donc pas grand chose à faire à ce stade. Basculons donc dans QSys où le gros du travail est à faire.

#### QSys

Quand vous lancez QSys, vous devez voir apparaître un schéma qui laisse penser qu'on va utiliser un module spécifique pour l'horloge. La suite c'est à vous de la composer. L'ordre dans lequel on fait les choses a son importance. À ce stade la plupart d'entre-vous seront tentés de commencer par l'élément le plus important : le processeur. Il n'empêche que vous éviterez un aller et retour dans QSys en commençant par la mémoire.

##### Mémoire

Nous allons prendre une mémoire unique qui servira de mémoire programme et de mémoire données. Les programmes destinés à un processeur 32 bits étant en général plus gros que ceux que l'on a utilisé jusqu'à présent nous allons choisir une mémoire de 32ko.
Pour se faire le travail suivant est nécessaire :
À partir de maintenant la description partielle des menus et sous menu se fera à l'aide de décalages. Par exemple :
* Basic Functions
** onchip Memory
*** on chip Memory (RAM or ROM)
**** Size 32768
signifie que vous allez dans Basic function, puis dans on chip Memory puis ....
Nous utiliserons toujours cette convention dans la suite.
Il n'y a que deux adresses possibles pour celle-ci : 0x0 ou 0x8000. Pour une taille de 32 ko nous choisissons 0x8000.
Nous garderons le nom d'instance par défaut : onchip_memory2_0.

##### Processeur

Nous allons choisir le processeur NIOS le plus basique. Il s'appelle NIOS II/e.
* Processors and Peripherals
** Nios ii (classic) Processor
*** nios II/e
**** Positionner Reset vector Memory dans la mémoire onchip_memory2_0
**** Positionner Exception vector Memory dans la mémoire onchip_memory2_0

##### Périphérique

Au vu de notre cahier des charges, un seul port de 8 bits suffit.
* Processors and Peripherals
** Peripheral
*** PIO (Parallel I/O)
**** 8 bits OUTPUT

##### Câbler et donner des adresses et exporter les sorties

À ce stade, il ne reste plus qu'à câbler l'ensemble et à fixer les adresses de la mémoire (à 0x8000) et du PORT (à 0x1000). Pour ce PORT il faut positionner un nom dans external connexion. Nous avons choisi "s7segs". Cet export est lié au fait que nous voulons sortir directement les informations du PORT sur notre afficheur sept segments.
Comme toujours un schéma comporte des conventions. Il nous a fallu un peu de temps pour nous familiariser avec la partie gauche de cette figure. Ceci fait vous vous apercevrez que les liaisons sont toute à fait normales :
* il est logique d'avoir une liaison entre data_master et l'entrée s1 de la RAM ainsi qu'entre instruction_master et cette même entrée. Ceci est lié à l'utilisation de la RAM en mémoire programme ainsi qu'en RAM du processeur. Autrement dit des données comme des instructions sont susceptibles de transiter entre cette RAM et le processeur NIOSII.
* il est logique d'avoir une liaison entre data_master et l'entrée s1 du PIO. Ce qui nous surprend c'est la possibilité de relier aussi instruction_master à l'entrée s1 de ce même PIO. Nous ne l'avons pas fait car nous ne voyons vraiment pas à quoi cela peut servir.
* Les resets sont reliés ensembles.
Il n'y a plus qu'à exporter l'ensemble de ce projet matériel :
En bas à droite, cliquer sur GENERATE HDL en prenant soin choisir VHDL (qui n'est pas le langage par défaut).
Donner comme nom au fichier .sopc le même nom que votre projet Quartus (pour nous niosii.qsys). Ceci n'est pas obligatoire mais c'est plus simple dans le cas d'un projet qui est entièrement géré par QSys.
En final un message doit apparaître dans lequel on vous dit qu'il faudra importer manuellement un fichier d'extension .qip dans votre projet Quartus... et donc retour à Quartus.

#### Retour dans Quartus

Cherchez votre fichier .qip dans un sous-répertoire "synthesis" et importez-le dans votre projet. Une fois votre réalisation matérielle importée dans Quartus, vous devez trouver un fichier VHDL. En voici l'entité :
Vous retrouvez, conformément au cahier des charges, une entrée horloge et une sortie "s7seg_export". Il vous resta à ajouter les contraintes.

##### Fichier de contraintes

Ce fichier de contrainte câble le segment g en poids faible et le segment a en poids 6. Ceci est important pour être capable de réaliser le trancodage correct.
Vous avez certainement remarqué la différence entre le nom que vous avez donné s7segs et celui que vous trouvez dans les contraintes s7segs_export. C'est QSys qui fait automatiquement cet ajout de suffixe "_export". On l'a appris en examinant l'entité du fichier VHDL automatiquement généré.
Vous pouvez lancer la compilation matérielle... et il n'y a plus qu'à mettre des programmes dans tout cela.

### Réalisation logicielle

Nous donnons d'abord le programme d'exemple que nous allons expliquer.
La compréhension des valeurs trouvées dans le tableau nécessite de connaître que l'organisation des segments est supposée être abcdefg, avec g poids faible. Il faut savoir aussi que pour allumer un segment sur notre carte, il faut un zéro logique.
Dans l'instruction IOWR_ALTERA_AVALON_PIO_DATA(0x1000,transcod[count & 0x0F]) apparaît la valeur 0x1000. C'est tout simplement l'adresse qui a été choisie pour l'adresse de notre PIO (voir figure ci-dessus).
À ce stade plusieurs questions peuvent vous venir à l'esprit mais nous y répondrons dans la section suivante :
* où dois-je écrire mon programme ?
* d'où viennent les fichiers d'inclusion system.h et altera_avalon_pio_regs.h ?
* d'où vient l'instruction IOWR_ALTERA_AVALON_PIO_DATA ?

### Mettre le programme dans le NIOS

Nous avons assez d'expérience maintenant avec les processeurs enfouis pour savoir que c'est là que nous allons passer du temps. Comme les concepteurs de ces logiciels font tout pour nous cacher au maximum la complexité inhérente au matériel, au moment de l'écriture de ces lignes nous n'avons aucune idée sur la façon dont les choses se passent. Après quelques essais, il semble que le fichier .sof soit modifié lors d'une compilation, mais il nous faudra bien des investigations complémentaires pour tout comprendre. L'utilisateur final que vous êtes n'a pas besoin de comprendre ces détails.
Commençons par répondre aux questions de la section précédente.
* Où dois-je écrire mon programme ? Dans Eclipse. Vous y accéder de QSys dans Tools -> Nios II Software Buid Tools for Eclipse.
Eclipse, pour ceux qui ne connaissent pas, est un environnement où la notion de projet est centrale. Il est impossible de compiler un programme qui n'est pas dans un projet.
* D'où viennent les fichier d'inclusion ? C'est là où le fonctionnement est un peu particulier. Toutes les bibliothèques sont forcément dépendantes du matériel. Elles seront donc fabriquées au coup par coup, pour chaque projet matériel. Pour cela il faut faire un projet spécial à côté du projet précédent qui aura un nom forcément terminé par _bsp. C'est ce projet qui sera chargé de compiler toutes les bibliothèques et de générer tous les fichiers d'inclusion.
La réalisation des deux projets évoqués ci-dessus en une seule fois se fait avec :
* File
** New
*** Nios II Application and BSP from template
*** Il vous faudra alors renseigner le fichier .sopcinfo que vous désirez utiliser.
*** Application Project
**** Project name à renseigner
**** Finish
Garder le projet Hello_world proposé par défaut. Cela vous évitera d'ajouter un fichier dans le projet.
Évidemment changer le fichier Hello_world.c, par exemple avec le fichier C proposé plus haut.
* Clic droit sur le nom du projet
** Run as
*** NiosII Hardware
Cette commande compile le ficher .c fait une édition de lien avec les librairies nécessaires, et surtout charge automatiquement dans le FPGA.
Tout ce que l'on vient d'expliquer aboutit sur un fonctionnement assez proche à celui d'un microcontrôleur : on compile et l'ensemble se télécharge automatiquement. C'est ce que font les environnements à succès, comme Arduino, Energia, ATMEL studio, MPLABX, ...
Les choses semblent simples une fois que tout fonctionne et on se demande toujours pourquoi on y a passé autant de temps. En ce qui nous concerne c'est le téléchargement qui envoyait un message d'erreur. Même quand USB Blaster est correctement configuré, il faut un peu de patience pour que cela fonctionne... La relation USB Blaster et Eclipse est donc parfois un peu compliquée et demande donc du simple doigté et de la patience.

### Aller plus loin

Que se passe-t-il si l'on désire ajouter un simple PIO matériel tout en restant dans le même projet. Que faut-il changer ?
La démarche est évidemment :
* de changer le projet QSys
* de générer le nouveau fichier .qip
* de retourner dans Quartus et d'ajouter les nouvelles contraintes pour le nouveau PIO et de compiler
* de télécharger le nouveau matériel dans la carte DE-115
* de retourner dans Eclipse
* de cliquer droit sur le projet *_bsp et choisir NIOS II -> generate BSP
* de modifier le fichier source pour tenir compte du nouveau matériel
* de compiler et charger dans la carte FPGA
Toutes ces étapes montrent qu'il faut mieux correctement prévoir son matériel dès le début. La modification logicielle est facile. La modification matérielle est elle aussi facile pour qui a un peu d'expérience. Mais il est facile d'attraper le tournis pour les modifications des deux en même temps.

### Problèmes rencontrés

* Ceux qui ont examinés avec attention la figure proposée pour illustrer la partie matérielle réalisée avec QSys auront remarqué que le N.I.O.S. apparaît avant la RAM, contrairement à ce qui se passe si vous suivez l'ordre des explications dans le texte ci-dessus. Si vous faites le nios en premier, il faudra revenir dessus une fois la RAM choisie pour positionner correctement le Reset vector Memory et le Exception_vector. Sinon, il vous restera deux erreurs en rouge.
* Si Eclipse vous dit qu'il a rencontré un problème pour mettre le fichier ELF dans le processeur c'est que probablement il n'a pas réussi à utiliser USB Blaster. Personnellement nous chargeons le processeur avec Quartus et ne fermons pas le Programmer et Eclipse le retrouve.
* À ce stade de nos investigations, nous ne savons pas s'il faut que le processeur soit dans la carte pour charger le programme en mémoire.

## Timer en interruption

Nous avons comme objectif, dans cette section de faire fonctionner un simple TIMER avec une interruption. Après avoir présenté sa réalisation matérielle, nous nous intéresserons au fonctionnement de celui-ci puis, pour finir, nous présenterons un programme de mise en œuvre.

### Le matériel : réalisation avec QSys

L'ajout d'un Timer se fait dans QSys. La première utilisation d'un timer est un peu déroutante même si (surtout si) vous avez de l'expérience avec les Timers des petits microcontrôleurs.
* Ceux-ci sont dotés de pré-diviseurs, mais ce n'est pas le cas ici. Cela est lié au fait qu'on est dans le domaine des 32 bits. 32 bits commandés par 50 MHz font une période de 86 secondes environ, ce qui est largement suffisant pour ce que l'on a à faire couramment.
* Le panneau de configuration du timer que l'on veut ajouter fait apparaître une période configurable. Celle-ci sera prise en compte seulement si vous cochez Fixed period un peu plus bas.
Une première image vous montre la configuration de notre timer.
La partie de droite de la figure vous montre une configuration de la période à 100 ms. Pour la deuxième fois nous répétons que cette période est prise en compte dans le seul cas où vous avez coché dans Registers l'option Fixed period ce qui n'est pas notre cas.
La partie Output signals vous permet
* de choisir un fonctionnement de type chien de garde (watchdog)
* de sortir un bit qui est à un pendant une période d'horloge lors d'un dépassement de capacité
Aucune de ces options n'est utilisée par notre projet.
Voici ce que cela donne une fois réalisé dans QSys.
Remarquez à droite l'interruption n°31 qui sera déclenchée par le dépassement de capacité du timer. La priorité des interruptions décroit quand le numéro d'interruption croit. On a donc choisi une faible priorité car cette interruption est la seule pour le moment.
Maintenant que le TIMER est présent dans le matériel, il nous faut comprendre comment il fonctionne.

### Fonctionnement et description du TIMER

Le timer est composé de registres. Leur nombre dépend du choix du type de timer : timer sur 32 bits ou timer sur 64 bits. En ce qui nous concerne nous allons nous intéresser au timer sur 32 bits. Il est composé des registres 16 bits suivants :
* status
* registre de contrôle
* registre PERIODL
* registre PERIODH
* registre SNAPL
* registre SNAPH
Comme vous faites du codesign (matériel et logiciel) il est important de savoir naviguer dans les fichiers d'entête pour savoir comment les choses ont été définies pour vous. Comme nous parlons du timer, l'ajout d'un tel périphérique provoquera automatiquement l'apparition d'un fichier d'entête appelé altera_avalon_timer_regs.h. Cette dernière phrase pourrait laisser entendre que ce fichier est automatiquement ajouté à votre programme mais ce n'est pas le cas. C'est à vous de l'ajouter dans les #include. Une fois ajouté, vous le voyez dans la partie droite d'Eclipse, vous double cliquez pour l'ouvrir.
Les accès aux registres demandent simplement de connaître l'adresse de base de votre périphérique. Comme nous n'avons rien changé à ce qui nous était proposé par défaut, notre Timer se trouve en adresse 0. Notre base sera donc 0.

#### Registre de status

La partie du fichier d'entête altera_avalon_timer_regs.h correspondante est :
La deuxième ligne indique qu'il est en adresse 0 par rapport à la base.
La cinquième et septième ligne indiquent que ce registre est accessible en lecture IORD_ALTERA_AVALON_TIMER_STATUS(base) et en écriture IOWR_ALTERA_AVALON_TIMER_STATUS(base, data). Les lignes suivantes définissent des noms symboliques pour les deux bits utiles du registre :
* ALTERA_AVALON_TIMER_STATUS_TO_MSK définit le bit de poids faible qui s'appelle TO (Timer Overflow)
* ALTERA_AVALON_TIMER_STATUS_RUN_MSK définit le bit à sa gauche qui est à un quand le timer est en marche.
Vous pouvez utiliser les OFST avec des décalages en lieu et place des masques.
Réaliser un code qui ne modifie qu'un seul bit ne se fait qu'en trois étapes :
Ici comme un seul bit est accessible en écriture on peut se contenter de :

#### Registre de contrôle

La partie du fichier d'entête altera_avalon_timer_regs.h correspondante est :
Il montre que ce registre contient 4 bits de contrôle (donnés ici du poids faible vers le poids fort) :
* ITO : Interrupt Timer Overflow est un bit d'autorisation d'interruption
* CONT : permet l'utilisation du timer en continu s'il est positionné à 1, autrement le timer s'arrête au premier dépassement de capacité
* START : démarre le TIMER, comme son nom l'indique, dès qu'il est positionné à 1
* STOP : arrête le timer.

#### Registres PERIODL et PERIODH

Comme leurs noms l'indiquent, ces deux registres sont là pour déterminer le début de décomptage. En effet ce timer fonctionne avec rechargement de sa valeur avec $(PERIODH,PERIODL)_{2}$ lorsqu'il y a eu dépassement de capacité.
Ces deux registres sont disponibles en écriture et en lecture.
L'écriture d'une valeur particulière se fait donc par :
qui met 0x00FFFFFF comme valeur à charger à chaque dépassement de capacité.

#### Registres SNAPL et SNAPH

Ces deux registres contiennent la valeur courante du TIMER.
Ces deux registres sont disponibles en écriture et en lecture.

#### Résumé du fonctionnement du Timer

La figure ci-contre résume les registres à utiliser pour mettre œuvre le périphérique Interval Timer. Ce qu'il est important d'avoir à l'esprit, est que les sous-programmes montrés à droite de chaque registre ont comme premier paramètre l'adresse que vous avez choisie dans QSys. Pour nous c'est 0, mais cela peut être différent pour vous.
L'autre point important à retenir qui n'apparaît pas dans la figure est que le bit TO du registre status est un drapeau (flag). Comme toujours, ce genre de bit est mis à un par le matériel, mais c'est au programmeur de le mettre à 0.
Enfin dernier point, ces registres sont sur 16 bits comme le montre la figure.
Il est grand temps d'écrire le programme complet maintenant.

### Le logiciel

Les constantes utilisées dans ce code sont définies dans "system.h".
Le code présenté ci-dessous fonctionne correctement. Il est possible de changer la rapidité de comptage en changeant le paramètre de l'instruction
Le débit des interruptions est donc maintenant réglable par une simple valeur dans un registre.
Nous avons gardé l'attente passive dans le programme principal mais ce n'est plus elle qui rythme le comptage, c'est l'interruption. En effet l'instruction count++ est bien dans l'interruption. L'affichage par contre, est réalisé dans le programme principal.

## Périphérique d'écriture LCD pour carte DE2-115

Nous allons essayer dans cette section un périphérique de gestion d'afficheur LCD 2 lignes de 16 caractères. Ce périphérique est présent sur la carte DE2-115 de Terasic. Un gestionnaire matériel de ce périphérique est disponible lorsque l'on utilise QSys. Seule sa documentation laisse à désirer, et peut être aussi sa librairie C. Mais ce dernier point est peut être dû à un manque d'expérience de notre part.

### Réalisation matérielle

Après avoir fait le processeur et sa mémoire on ajoute un PIO. Il servira essentiellement à allumer des LEDs pour voir si le programme C tourne normalement. Il est positionné en adresse 0x0000.
On ajoutera en plus un périphérique destiné à la gestion de l'écran lcd.
* processors and peripheral
** Peripherals
*** Altera Avalon LCD 16207
Il sera positionné en adresse 0x0020.

### Réalisation logicielle

Le code source donné ci-dessous explore les possibilités d'utilisation de l'afficheur lcd. Il est complètement fonctionnel. Mais faute de documentation convaincante, nous avons tenté de reproduire les commandes classiques d'un gestionnaire LCD en nous aidant de la librairie Arduino...
Le programme donné en exemple affiche un "BONJOUR" qui se déplace sur la première ligne et un " A TOUS" sur la deuxième ligne.
* PROJET_BSP -> drivers -> src -> altera_avalon_lcd_16207.c
Nous avons un peu enrichi la librairie en proposant un "scrollDisplayLeft" et un "scrollDisplayRight" qui ont l'avantage de déplacer l'affichage sans changer le contenu de la mémoire d'affichage.
Notre façon de définir les constantes juste avant leurs utilisations n'est pas habituelle. Mais la partie de sous-programmes utiles est tellement petite que nous n'avons pas décidé de regrouper toutes les constantes en début de programme.
La seule primitive manquante serait le "createChar" de la librairie Arduino qui permettrait de réaliser ses propres caractères d'affichage. Nous laissons cela au lecteur intéressé sous forme d'exercice.

#### Exercice

On vous demande de modifier les programmes d'exemple ci-dessus pour ajouter la primitive "createChar" à vos sous-programmes utiles. Voici son code source dans la librairie Arduino :
On ne gardera pas la notion d'objet utilisée dans cette librairie Arduino.