- Introduction
- Prérequis
- Manuel d'utilisation des périphériques sur la carte NEXYS 4
- Module de gestion de l'I2C
Cette documentation a pour objectif de détailler le principe de fonctionnement de l'architecture du processeur Plasma lors de son utilisation avec divers PMODs. Ce processeur a été instancié sur une puce FPGA Artix 7 embarquée sur carte NEXYS 4. Il est basé sur une architecture RISC 32-bit softcore et est issu d'un projet open source: Plasma. Il tourne à une fréquence d'horloge de 25 MHz. L'utilisation des PMODs repose sur une interface plus flexible codée en C et faisant abstraction du langage de description VHDL.
Ce travail a été réalisé dans le cadre d'un projet proposé par Camille Leroux en option SE et développé par Paul Kocialkowski, Henri Gouttard et Igor Papandinas.
Ci-dessous sont listé les outils requis pour l'utilisation du processeur Plasma et les PMODs ou bien l'ajout de nouveaux.
Les outils nécessaire pour l'utilisation du processeur Plasma avec les PMODs sont les suivants:
- Une carte de développement NEXYS 4 pour y instancier le plasma et autres modules à utiliser.
- Le logiciel
Vivado
pour le chargement du bitstream.
Les outils nécessaires pour l'ajout de nouveaux PMODs au processeur Plasma sont les suivants:
- Les outils cross-compilés pour une architecture MIPS-elf:
mips-elf-gcc
,mips-elf-as
,mips-elf-ld
. - Le logiciel
Vivado
pour compiler le code VHDL.
Pour commencer il faut développer en VHDL le module correspondant au PMOD que l'on souhaite ajouter. On place ensuite le fichier .vhd de la description dans le répertoire HDL/PLASMA/.
Maintenant il faut instancier le module dans l'architecture du plasma. Dans le fichier plasma.vhd, on créé donc un component et son port map correspondant avec les signaux internes nécessaires (valid, reset, ...). Puis on définit de nouvelles adresses READ/WRITE (RW) et RESET (RST) que l'on associe aux signaux valid et reset précedement créé. Cela est fait dans la description VHDL au moment de déclarer les signaux de commandes en direction du port PCIe. Enfin dans le process misc_proc, pour le cas où les bits 28 à 30 de cpu_address valent "100", on déclare l'adresse de lecture pour notre module (la même que pour valid) à laquelle on associe le signal de sortie du module.
Une fois l'instanciation faite, il ne faut pas oublier de modifier le fichier top_plasma.vhd si nécéssaire pour y ajouter les connectiques des périphériques d'entrer et de sortie sur la carte NEXYS 4.
Ensuite il faut compiler l'ensemble à partir du Makefile dans le répertoire courant du projet (cf la partie détaillant le fonctionnement du fichier Makefile).
Pour terminer, si l'ensemble des instructions précédentes ont bien été suivi, il est maintenant possible d'intéragir dans un programme en C avec le PMOD à partir des adresses utilisées et des fonctions MemoryWrite() et MemoryRead() (cf les exemples donnés ci-dessous pour d'autres modules tel que le module Charmap du PMOD Oled-RGB).
Le build system utilisé pour le SoC Plasma repose sur le Makefile
principal qui prend en charge l'ensemble des actions récurrentes relatives au Plasma.
La liste des fichiers VHDL est définie avec la variable PLASMA_SOC_FILES
, qui est utilisée pour générer les instructions de synthèse du design.
Plusieurs paramètres peuvent être configurés au moyen de variables makes passées en argument à la commande :
CONFIG_PROJECT
: le projet à utiliser, défaut :hello
CONFIG_TARGET
: la cible à utiliser (nexys4_DDR
,nexys4
), défaut :nexys4_DDR
CONFIG_PART
: le FPGA à cibler, défaut :xc7a100tcsg324-1
CONFIG_SERIAL
: le port série à utiliser pour envoyer le projet, défaut :/dev/ttyUSB1
On peut de même activer ou désactiver la prise en charge des différents contrôleurs (qui sont tous activés par défaut) : CONFIG_UART
, CONFIG_BUTTONS
, CONFIG_RGB_OLED
, CONFIG_SWITCH_LED
, CONFIG_SEVEN_SEGMENTS
, CONFIG_I2C
.
Les différentes cibles possibles pour make
sont présentées :
send
: Envoi du binaire du projet vers le Plasma par UARTflash
: Programmation du FPGA par JTAG avec OpenOCDplasma
: Préparation du bitstream du SoC Plasmasimulation
: Simulation du SoC Plasma avec le projet spécifiéclean
: Nettoyage des fichiers temporaires
Des cibles spécifiques pour chacun des projets existent également.
D'autres cibles correspondent à des fichiers internes nécessaires à la préparation du SoC Plasma, tels que les différents outils :
convert_bin
: Conversion du format binaire des exécutables MIPSprogrammer
: Outil d'envoi des binaires des exécutables vers le Plasma par UART
Afin d'ajouter un contrôleur au build system, on définit dans un premier temps une variable de configuration associée à une valeur de generic en VHDL :
CONFIG_BUTTONS ?= yes
Et on ajoute dans un second temps un bloc associant la configuration au generic :
ifeq ($(CONFIG_BUTTONS),yes)
PLASMA_SOC_GENERICS += eButtons=1'b1
PLASMA_SOC_FILES += buttons.vhd
else
PLASMA_SOC_GENERICS += eButtons=1'b0
endif
Le generic devra par la suite être répercuté dans les fichiers plasma.vhd
, top_plasma.vhd
et tbench.vhd
. Sa valeur par défaut sera (à priori) placée à 1. Un bloc spécifique à l'instantiation du composant sera alors placée dans le fichier plasma.vhd
tel que :
buttons_gen_enabled: if eButtons = '1' generate
plasma_buttons_controller: buttons_controller
port map(
clock => clk,
reset => reset,
buttons_access => enable_buttons,
btnC => btnC,
btnU => btnU,
btnD => btnD,
btnL => btnL,
btnR => btnR,
buttons_values => buttons_values,
buttons_change => buttons_change
);
end generate;buttons_gen_disabled: if eI2C = '0' generate
buttons_values <= ZERO;
buttons_change <= ZERO;
end generate;
On s'attachera en particulier à connecter les registres associés au contrôleur quand celui-ci est désactivé ainsi qu'à associer des valeurs (préférablement 'Z') aux sorties.
Pour ajouter un projet au build system, on commence tout d'abord par définir un bloc spécifiant les sources et les objets :
BUTTONS = $(BIN)/buttons.bin
BUTTONS_HDL = $(BIN)/buttons.txt
BUTTONS_FILES = main.c
BUTTONS_SOURCES =$(addprefix $ (C)/buttons/Sources/,$(BUTTONS_FILES))
BUTTONS_OBJECTS =$(addprefix $ (OBJ)/buttons/,$(BUTTONS_FILES:.c=.o))
BUILD_DIRS += $(OBJ)/buttons
BUILD_BINS += $(BIN)/buttons.bin
PROJECTS += $(BUTTONS)
Et on met en place l'association du projet par défaut si c'est celui-ci qui est sélectionné :
else ifeq ($(CONFIG_PROJECT),buttons)
PROJECT = $(BUTTONS)
PROJECT_HDL = $(BUTTONS_HDL)
On ajoute enfin les blocs de prise en charge de la compilation du projet à la fois pour l'exécution en RAM ou en ROM au sein du design (utilisé en particulier pour la simulation) :
$(BUTTONS_OBJECTS): $ (OBJ)/buttons/%.o:$(C)/buttons/Sources/%.c | $ (BUILD_DIRS)
$(CC_MIPS) $ (CFLAGS_MIPS) -o$@ $ <
$(BUTTONS): $ (SHARED_OBJECTS_ASM)$(SHARED_OBJECTS) $ (BUTTONS_OBJECTS)$(CONVERT_BIN) | $ (BUILD_DIRS)
$(LD_MIPS) -Ttext $ (ENTRY_LOAD) -eentry -Map$(OBJ)/buttons/buttons.map -s -N -o $ (OBJ)/buttons/buttons.axf$(SHARED_OBJECTS_ASM) $ (SHARED_OBJECTS) $(BUTTONS_OBJECTS)
$(CONVERT_BIN) $ (OBJ)/buttons/buttons.axf$(OBJ)/buttons/buttons.bin $ (OBJ)/buttons/buttons.txt
cp$(OBJ)/buttons/buttons.bin $ @
$(BUTTONS_HDL): $ (SHARED_OBJECTS_ASM)$(SHARED_OBJECTS) $ (BUTTONS_OBJECTS)$(CONVERT_BIN) | $ (BUILD_DIRS)
$(LD_MIPS) -Ttext $ (ENTRY_HDL) -eentry -Map$(OBJ)/buttons/buttons_hdl.map -s -N -o $ (OBJ)/buttons/buttons_hdl.axf$(SHARED_OBJECTS_ASM) $ (SHARED_OBJECTS) $(BUTTONS_OBJECTS)
$(CONVERT_BIN) $ (OBJ)/buttons/buttons_hdl.axf$(OBJ)/buttons/buttons_hdl.bin $ (OBJ)/buttons/buttons_hdl.txt
cp$(OBJ)/buttons/buttons_hdl.txt $ @.PHONY: buttons
buttons:$(BUTTONS) $ (BUTTONS_HDL)
La carte Nexys 4 est équipée de boutons poussoirs qui sont prise en charge par un contrôleur dédié. On peut ainsi relever l'état des boutons et détecter un changement d'état.
Les différents fichiers VHDL qui décrivent la gestion des boutons sont les suivants :
plasma.vhd
dans lequel est instancié le contrôleur des boutons et le cablage vers le composant.buttons.vhd
contrôleur pour la prise en charge des boutons.
Deux adresses sont utilisées pour le contrôleur de boutons :
0x40000100
pour la valeur de l'état des boutons0x40000104
pour la détection du changement de l'état des boutons (XOR avec la valeur précédente)
La carte Nexys 4 est équipée de huit afficheurs sept segments, cablés en anode commune. Il est possible d'obtenir un retour d'information sur ces afficheurs. Le bloc VHDL ajouté à l'architecture du plasma pour la gestion des afficheurs sept segments est semblable aux blocs coprocesseur présents dans le PLASMA.
Les différents fichiers VHDL qui décrivent la gestion des afficheurs sept segment sont les suivants :
plasma.vhd
dans lequel est instancié le bloc de gestion de l'afficheur sept segments, les entrées/sorties et signaux pilotant le bloc y sont cablés.ctrl_7seg.vhd
bloc principal où les différents sous-blocs nécessaires à l'affichage sont cablés.mod_7seg.vhd
pour la préparation du signal vers les 7 segments
Pour intérgir avec les afficheurs sept-segments une adresse est réservée.
0x40000200
: adresse de l'entrée sur 32 bits, il faut écrire les données à cette adresse. Le macro associé à cette adresse estSEVEN_SEGMENT
.
Il suffit d'écrire à l'adresse SEVEN_SEGMENT
, la valeur en entrée sur 32 bits et elle sera affichée en sortie, en écriture héxadécimal, sur les 8 afficheurs sept-segments disponibles sur la Nexys 4.
Un programme d'exemple est fournit, il implémente un compteur allant de 0 à 2000 (7DO en hexadécimal). Le compteur s'incrémente toute les 100ms. L'affichage de la valeur du compteur est fait sur les afficheurs sept-segment.
Ensuite le programme rentre dans un boucle infinie dans laquelle il affiche les 16 bits de données switchs, à la fois sur les quatres afficheurs de droite, et sur les quatres afficheurs de gauche.
La carte NEXYS 4 possède 16 Switchs, 16 LEDs vertes et 2 LEDs RGB. Ce module permet de contrôler l'ensemble. Il est possible d'intéragir avec le plasma et d'obtenir un retour d'information sur ces LEDs. Le bloc VHDL ajouté à l'architecture du plasma pour la gestion des Switchs/LEDs est semblable aux blocs coprocesseur présents dans le PLASMA.
Les différents fichiers VHDL qui décrivent la gestion des Switchs/LEDs sont les suivants :
plasma.vhd
dans lequel est instancié le bloc de gestion, les entrées/sorties et signaux pilotant le bloc y sont cablés.ctrl_SL.vhd
bloc principal où les différents sous-blocs nécessaires à l'affichage sont cablés.
Pour intérgir avec les afficheurs sept-segments une adresse est réservée.
0x400000C4
: adresse de l'entrée sur 32 bits, il faut écrire les données à cette adresse. Le macro associé à cette adresse estCTRL_SL_RW
.
Il suffit d'enclencher un switch pour allumer la LED verte correspondante au dessus. Un décalage de 16 bits est fait sur la valeur des switchs, ainsi on contrôle les LEDs RGB avec les switchs 1 à 6 (correspondant après décalage au bit 17 à 22). On peut récupérer la valeur des Switchs pour contrôler d'autres PMODs et on peut également écrire à l'adresse CTRL_SL_RW
, la valeur en entrée sur 32 bits qui affectera l'état des LEDs verte (sur les bits de poids faible 1 à 16) et RGB.
Un programme d'exemple est fournit dans le fichier main.c du répertoire C/switch_led/Sources/. Ce programme implémente le contrôle des LEDs à partir des switchs. Cela est fait à partir d'une boucle infinie qui lit et affiche sur le port série l'état des switchs et contrôle l'état des LEDs.
Les nombreux PMOD I2C fournits par Digilent peuvent être interfacés facilement au processeur Plasma via le module I2C. Ce module est un hybride en matériel et logiciel. En effet, il est constitué de deux parties :
- Un bloc matériel décrit en VHDL qui permet de synchroniser et de gérer bit à bit les émissions/réceptions des signaux qui assurent la communication I2C: SDA pour les données et SCL pour l'horloge.
- Un programme écrit en C bas niveau, qui permet de gérer les séquences d'écriture et de lecture propres au protocole I2C.
Les différents fichiers VHDL qui décrivent la gestion des afficheurs sept segment sont les suivants :
plasma.vhd
dans lequel est instancié le bloc VHDL du module I2C.i2c.vhd
bloc principal qui contient deux entités i2c_clock et i2c_controller.i2c.h
fichier d'entête que contient les prototype des fonctions nécessaires pour l'établissement d'une communication I2C, ainsi que les macros des adresses et masques des différents registres.i2c.c
fichier C qui contient les fonctions qui permette de gérer les séquences d'écriture et de lecture du protocole I2C.
NB: Le fichier main.c
contient un programme d'exemple qui gère une communication avec le capteur PMOD compass
0x40000300
: adresse du registre qui contient l'adresse de l'esclave ciblé dans une communication I2C.0x40000304
: adresse du registre de status du module I2C.0x40000308
: adresse du registre de contrôle du module I2C.0x4000030c
: adresse du registre de données du module I2C.
L'écriture ou la lecture sur l'une de ces adresses active le bloc VHDL du module I2C.
Séquence d'écriture dans une communication i2c:
Séquence de lecture dans une communication i2c:
Le module (partie logicielle + partie matérielle) gère ces séquences, il suffira donc pour interfacer un PMOD I2C d'utiliser, une à une, les fonctions mises à disposition (voir i2c.h
).
Cet exemple s'appuie sur le programme C/i2c/Sources/main.c
. Il s'agit là d'une utilisation spécifique au PMOD compass, mais en général la communication avec des capteurs selon le protocole I2C est assez similaire.
Pour bien comprendre ce programme il faut lire la documentation du HMC5883L
, le circuit intégré utilisé dans le PMOD Compass. L'exemple est tiré de la datasheet.
Dans ce programme on trouve deux parties : l'initialisation et la boucle principale.
Dans cette partie on écrit sur les registres appropriés, dans le but de paramétrer l'acquisition comme on le souhaite. Voici les étapes de configuration dans cet exemple :
- Ecriture sur le registre de configuration A de
0x70
: moyennage sur 8 échantillons, 15 Hz, mesure normale - Ecriture sur le registre de configuration B de
0xA0
: gain de 5 - Ecriture sur le registre de mode de
0x00
: Mode mesure continue - Attente de 6 ms au moins.
Dans cette partie on effectue les mesures à chaque tour de boucle, dans notre cas cela consiste à venir lire les registres de données du capteur un par un, puis replacer le pointeur de registre pour la prochaine mesure.
Remarque: Le pointeur de registre s'incrémente à chaque lecture. Après l'initialisation ce pointeur de registre pointe vers le premier registre de données (0x03
) et on n'aura pas besoin de l'incrémenter manuellement entre chaque registre.
Début de boucle
- Lecture des 6 registres contenant les valeurs
- Stockage en mémoire
- Placement du pointeur de registre sur l'adresse du premier registre de données pour la prochaine mesure.
- Attente de 67 ms au moins pour une fréquence de 15 Hz
Fin de boucle