Intégration orientée SoC pour FPGA
2022-2024
Le but de cette séance et de présenter une plateforme d’intégration système orientée FPGA. Ce type d’outil permet de construire un système autour d’un cœur de processeur et utilisant une bibliothèque d’IP. L’outil permet la configuration de la map mémoire, de générer l’interconnect et même de s’interfacer avec un environnement de développement logiciel.
Platform Designer (anciennement Qsys), est un outil fourni par Intel pour cibler les FPGA qu’il produit (anciennement Altera). Il permet de construire un système autour d’un soft core processor, le Nios II ou d’un hard soc ARM pour les FPGA intégrant cette fonction.
Notez qu’il existe des équivalents de cet outil chez d’autres fabricants de circuit FPGA. Nous pouvons citer par exemple, Vivado/Vitis Composer de Xilinx ou Libero SoC de Microsemi. Les concepts abordés durant cette séance se retrouveront aussi sur ces différents outils.
Dans cette présentation nous construirons un système à base du processeur Nios II et nous ciblerons une FPGA de type Cyclone V. De plus, nous écrirons un programme en langage C pour interagir avec les différents blocs du système construit.
Le système de base que nous allons construire est représenté par la figure suivante:
Il est composé des éléments suivants:
Nous passerons par les étapes suivantes:
Pour simplifier l’intégration au flot de synthèse FPGA, nous démarrerons d’un projet pré-construit. Ce projet contient les informations suivantes:
Une archive de ce projet est disponible ici.
Il est possible d’utiliser directement Platform Designer indépendamment de Quartus. Il faudra dans ce cas, préciser le FPGA cible et ajouter des informations de mapping, puis revenir vers Quartus pour la synthèse.
Une fois l’archive décompressée, vous pouvez ouvrir le projet dans le logiciel Quartus II.
Ouvrez le projet et sélectionnant Open Project...
à
partir du menu File
puis sélectionnez le fichier du projet
DE1-SoC.qpf
.
Ce projet fait référence à un fichier RTL unique
(DE1-SoC.sv
) avec la définition des entrées/sorties.
Certaines sorties ont été affectées à des valeurs par défaut (par
exemple pour éteindre les leds). Le module principal
(DE1-SoC
) est le module de hiérarchie la plus haute du
projet. Par la suite, nous y intégrerons le système généré dans
Platform Designer.
À partir du menu Tools
lancez
Platform Designer
.
Ceci vous amènera dans l’interface de l’intégrateur et un projet d’intégration vierge semblable à ceci.
C’est le point de départ pour construire le système.
Ce projet contient par défaut un module pour l’horloge et le reset. Il permet juste de connecter des signaux venant du monde extérieur et de resynchroniser le signal de remise à zéro.
Tout système possède au minimum une horloge et une entrée de remiser
à zéro, ici, clk
et reset
. Notez que ces deux
signaux apparaissent dans la colonne export, ce qui veut dire qu’ils
viendront de l’extérieur du système.
Sur le panneau latéral gauche IP Catalog nous avons la liste des IPs disponible. Certaines ont été fournies avec l’outil, d’autres installées par la suite.
Le premier élément que nous allons ajouter au système sera le
processeur, Nios II. Nous le trouverons dans la catégorie
Processors and Peripheral/Embedded Processors ou en utilisant
la barre de recherche. Pour l’ajouter au système il suffit de double
cliquer dessus puis éventuellement de le configurer. Choisissez juste la
configuration de base (_resource optimized Nios II/e
core).
À la fin de cette étape, vous devriez voir apparaitre le processeur dans le panneau central System Contents comme le montre la figure suivante.
Notez les différentes connexions qui entrent et sortent du processeur.
Vous devriez aussi voir apparaitre des messages d’erreur qui devraient disparaitre une fois les autres éléments du système ajoutés.
Le second élément que vous devez ajouter est un bloc mémoire. Vous le trouverez dans la catégorie Basic Functions sous le nom On Chip Memory (RAM or ROM)
Cette IP configurable permet d’utiliser les blocs mémoire embarqués du FPGA. Nous pouvons préciser le type de ressources à utiliser, le comportement désiré (read only ou read/write) ainsi que la taille désirée. Le contenu de cette mémoire peut aussi être initialisé à partir d’un fichier (contenant des données ou un programme) qui sera ajouté au fichier de programmation du FPGA (bitstream).
Modifiez la configuration de base pour avoir une taille mémoire de 20Ko comme le montre la figure suivante.
Ceci permettra d’utiliser la fonction printf
de la
bibliothèque C fournie avec la chaine de compilation.
En suite nous ajouterons deux périphériques de type Parallel I/O (PIO) pour les leds et les interrupteurs (switches).
Le premier doit être configuré comme sortie (output) sur une largeur de 10 bits, comme le montre la figure suivante:
Le second doit être configuré comme entrée (inputs) sur une largeur de 10 bits aussi.
Pour la lisibilité de votre projet, renommez les différents module en utilisant des noms explicites.
Par la suite, les signaux sortant de ces modules seront exportés du projet pour être connectés aux entrées/sortie du module principal.
En fin, le dernier élément à ajouter est une JTAG UART pour qu’on puisse communiquer avec le système par l’intermédiaire de la liaison USB et interfacer le debugger.
Ici conservez la configuration par défaut.
La matrice de connexion est visible dans la colonne Connections de la fenêtre principale. Les points blancs représentent les connexions possibles. Pour activer une connexion, il suffit de cliquer sur ces points. Une fois active, le point symbolisant la connexion devient noir.
L’outil ne vous montre que les connexions entre des interfaces compatibles. Il vous interdira par exemple de connecter une sortie d’horloge sur une entrée de reset.
Voici les connexions qu’il faudra ajouter:
clk
de tous les modules doit être connectée à
la sortie clk
du bloc clk_0
clk_0
s1
) de la on-chip memory connectée
aux interfaces data_master
et
instruction_master
du processeur
Avalon Memory Mapped Slave
à la seule sortie
data_master
du processeur
Vous devriez à la fin obtenir un système semblable à celui de la figure suivante.
Remarques
<CTRL-R>
.IRQ
(dans la figure, nous avons choisi la position 5 pour
l’interruption de l’uart de façon arbitraire).La colonne Base
vous indique les adresses de base de
chaque périphérique. Par défaut, quand les instances sont créées, leur
adresse de base est 0
.
Notez aussi la colonne End
qui représente la fin de la
plage d’adresse de chaque périphérique. Cette information est obtenue à
partir des fichiers décrivant l’IP.
Pour cet exemple simple, il n’y a pas de contrainte particulière sur
les adresses de base à attribuer. Nous allons juste choisir de faire
commencer la zone réservée à la mémoire à l’adresse 0
.
Vous pouvez verouiller cette valeur (en cliquant sur le petit
cadenas).
Pour les autres périphériques, plutôt que d’affecter manuellement les différentes adresses, nous allons laisser l’outil le faire. Pour cela, choisissez Assign Base Addresses du menu System.
Une fois toutes les adresses de base assignées, l’architecture de votre système devrait être similaire à celui de la figure suivante.
Prennez le temps de noter les différentes adresses, nous en aurons besoin pour écrire le code de test logiciel.
Maintenant que les différentes connexions et adresses ont été définies, nous pouvons revenir sur la configuration du processeur pour définir l’adresse du code de reset ainsi que du handler d’interruption. Ces adresses correspondent, respectivement, à l’adresse de la première instruction exécutée après une remise à zéro (ou un démarrage) ainsi qu’à l’adresse du handler pour les interruptions et exceptions.
Bien que le processeur supporte plusieurs interruptions externes et exceptions, par défaut, celles-ci ne sont pas vectorisées. Toutes les exceptions déclenchent le même code. La détermination de l’origine de l’interruption et le choix du traitement associé se fait de façon logicielle.
Pour revenir dans l’interface de configuration du processeur, il
suffit de double cliquer sur la ligne correspondante. Dans l’onglet
Vectors
vous pouvez soit indiquer une adresse numériquement
ou choisir un des périphériques connectés à l’interface
instruction_master
. La seconde option a l’avantage de vous
garantir que l’adresse restera cohérente même si vous changer la map
d’adresses.
Ici, choisissez la mémoire embarquée et gardez les offsets par défaut.
Les entrées/sorties connectées aux contrôleurs de GPIO doivent être connectées aux entrées/sorties adéquates du module RTL principal. Pour cela, il faut demander à Platform Designer de les exporter.
Ceci ce fait dans l’interface graphique en double cliquant sur la
ligne correspondante de la colonne Export
. Vous pouvez ici
aussi modifier le nom, pour qu’il soit plus explicite. Par exemple, dans
la figure suivante, nous avons choisi leds
et
sw
.
La dernière étape est de générer l’ensemble des fichiers RTL. Cette version de Platform Designer peut générer du Verilog ou VHDL correspondants à notre système.
Sauvegardez le projet en cliquant sur Save dans le menu
File. Nommez le projet, par exemple,
cpu_system
.
Puis générez le RTL en sélectionnant Generate HDL du menu Generate. Les fichiers seront générés dans un sous dossier du répertoire dans lequel le projet a été créé.
Notez que Platform Designer peut être lancer sans interface graphique. Il inclut un interpréteur
TCL
et tout ce que nous venons de faire dans l’interface graphique peut être exprimé sous la forme d’un script. Pour les curieux, voir Export System as Platform Designer script (.tcl).
Vous pouvez quitter Platform Designer pour revenir dans Quartus.
Vous pouvez ignorer le message vous indiquant comment intégrer le système dans le projet.
Dans Quartus, sélectionner Add/Remove files in project… à partir du menu Project. Cliquez sur l’icone … pour ouvrir un explorateur et sélectionnez le fichier .qsys correspondant à votre système.
Une fois fait, vous pouvez modifier le fichier SystemVerilog du
projet pour ajouter le système. Platform Designer vous a aussi
généré un fichier _inst.v
(par exemple
cpu_system_inst.v
) contenant un exemple
d’instanciation.
Connectez l’entrée d’horloge au à l’entrée clock_50
du
module principal, et le reset sur l’un des boutons poussoirs,
key[0]
par exemple. Connectez aussi les interrupteurs et
leds aux entrées/sorties correspondants aux GPIOs.
Voici un exemple d’instanciation:
//////////////////////////////////////////////////////////////////////
// Nios-II System
//////////////////////////////////////////////////////////////////////
(
cpu_system u0 ( clock_50 ) ,
.clk_clk ( key[0] ) ,
.reset_reset_n ( sw ) ,
.sw_export ( ledr )
.leds_export );
Note
- La sortie
ledr
du module principal est déjà affectée à une constante, vous devez donc supprimer (ou commenter) cette affectation.
Une fois l’instance ajoutée, vous pouvez compiler (synthèse, placement routage et génération du fichier de programmation) dans Quartus. Puis, vous pouvez lancer le programmeur pour programmer le FPGA de la carte.
Intel/Altera fourni une chaine de compilation pour le processeur Nios-II basée sur les outils GNU (gcc, binutils,…).
L’intégration dans un environnement de développement logiciel est aussi prévu:
De plus, des bibliothèques d’abstraction sont fournies pour les différents périphériques disponibles dans Platform Designer.
Pour ce projet, nous nous limiterons à un simple environnement familier et utilisant gnu make et gdb (en ligne de commande ou à travers votre éditeur préféré).
Intel propose des IDE pour le développement logiciel pour Nios-II.
Ces deux outils (bien qu’ayant des fonctionnalités différentes) savent générer un projet logiciel à partir de la description du projet de Platform Designer. Pour ce cours, nous allons nous restreindre à des programmes simples et nous n’utiliserons pas d’IDE (juste un makefile).
Récupérer l’environnement logiciel disponible ici.
Vous y trouverez les sources d’un projet exemple pour une plateforme pré-définie. Le projet contient:
Attention cet exemple est prévu pour une plateforme matérielle différente!
Pour cela, il faudra:
make fpga-conf
make gdb-server
make terminal
make debug
Par défaut, le script d’inialisation de GDB, charge le programme et positionne un point d’arrêt sur la fonction main.
Une session de débug devrait ressembler à ceci:
Pour pouvoir tester les périphériques que nous avons ajouté au système précédemment conçu, voici un programme de test simple qui affiche en continu sur les leds de la carte l’état des interrupteurs.
// define macros to access to the data registers of
// the LEDs and SWs PIOs
#define LEDS (*(volatile unsigned int*) 0x00009010)
#define SW (*(volatile unsigned int*) 0x00009000)
int main()
{
while(1)
= SW;
LEDS
}
Bien qu’Intel/Altera, prévoit, pour ces outils de développement logiciel, la possibilité récupérer automatiquement des informations à partir des fichiers générés par Platform Designer, pour ce TP, nous allons faire les modifications manuellement.
Donc, dans le makefile, modifiez les points suivants:
.sof
) su
FPGA (il peut être relatif),printf
par
exemple)),Remplacez, finalement, le programme principal par votre programme de test.