Le premier FPGA avec sa chaîne de développement open source

Magazine
Marque
Hackable
Numéro
40
Mois de parution
janvier 2022
Spécialité(s)


Résumé

Il existe ! Le premier FPGA avec une chaîne intégralement open source est désormais une réalité. Parler de FPGA est peut-être un peu prétentieux concernant le EOS S3. Disons plutôt que c’est un microcontrôleur à cœur ARM Cortex-M4 qui possède une zone configurable (appelée souvent eFPGA). Mais ce qui est intéressant ici, c’est que les outils de synthèse ainsi que le format du fichier de configuration sont documentés. Ils sont donc utilisables avec des logiciels libres !


Body

QuickLogic est spécialisé dans la conception de « briques » FPGA à intégrer dans des composants cibles. C’est un concepteur électronique sans fabrication propre (fabless) qui est notamment connu pour sa série « ArticPro ». Il est difficile de se faire un nom dans le domaine sans avoir de démonstrateur physique. C’est l’une des raisons du lancement du microcontrôleur EOS S3 qui intègre la brique FPGA « ArticPro » et un cœur ARM Cortex-M4.

quickfeather figure 01-s

Fig. 1 : Le kit de développement utilise le standard « Feather » promu par Adafruit (source : QuickLogic).

Le EOS S3 est un microcontrôleur optimisé pour les périphériques portés sur soi (wearable) et le son. Basé sur un cœur ARM Cortex-M4F cadencé à 80 MHz, le microcontrôleur possède 512 kbit de mémoire statique (SRAM) et un eFPGA de 891 cellules logiques ainsi que 8 blocs de 512x18 bits (73 728 bits) de mémoire.

Pour promouvoir son microcontrôleur, la société QuickLogic a développé une carte [1] au format Feather de Adafruit. Le format Feather est compatible avec plus d’une soixantaine de cartes d’extension proposées par Adafruit. Cette carte, visible sur la photo de la figure 1 est disponible sur le site de Crowd Supply [2] pour une cinquantaine d’euros hors frais de port. Il est remarquable de noter que les schémas et routages de la carte qui ont été faits avec KiCad sont open source. QuickLogic encourage la réutilisation de leurs schémas pour faire son propre kit.

L’occasion a été saisie au vol par SparkFun pour produire la carte « SparkFun Thing Plus – QuickLogic EOS S3 » [3], carte quasi identique à la QuickFeather, mais avec une version plus récente des capteurs ainsi que leur connecteur maison nommé Qwiic pour faire communiquer les cartes produites par SparkFun ensemble. Cette carte est également disponible sur Crowd Supply pour un prix légèrement plus bas (~40 $).

La carte QuickFeather est donc munie d’un EOS S3 communiquant par :

  • SPI/I²C ;
  • I²S pour le son ;
  • UART ;
  • USB, attention cependant, l’USB est synthétisé dans le eFPGA. Le microcontrôleur ne possède pas d’USB « codé en hard » ;
  • SWD pour la programmation et le déverminage du cœur ARM.

La carte possède une mémoire flash (NOR) de 16 Mbit, une LED trois couleurs RGB et un bouton utilisateur en plus de son bouton de reset.

QuickLogic visant le marché du son et du « porté sur soi », trois capteurs ont été ajoutés à la carte :

  • accéléromètre : mCube MC3635 ;
  • pression : Infineon DPS310 ;
  • microphone en PDM : Infineon IM69D130.

La carte est alimentée via le connecteur USB et peut également être branchée à une batterie Li-Po. Un circuit de gestion de charge batterie (Microchip MCP73831/2) lithium est également présent sur la carte.

On ne peut pas terminer ce tour d’horizon des cartes de développement sans parler de la carte Qomu, disponible également sur le site de Crowd Supply [4]. Cette carte est la petite dernière d’une longue lignée de cartes Xomu. Le principe est de faire tenir tout le nécessaire au fonctionnement et à la programmation d’un microcontrôleur ou d’un FPGA à l’intérieur d’un port USB, comme on le voit sur l’image 2. Il suffit d’insérer la carte dans le port USB de son ordinateur et les tests peuvent commencer. Ce kit est très pratique pour faire du développement en voyage.

quickfeather figure 02-s

Fig. 2 : La carte Qomu, au format « connecteur USB » (source : Crowd Supply).

Le format hybride de ce microcontrôleur/FPGA est déjà une petite nouveauté en elle-même, même s’il existe déjà des microcontrôleurs avec zone de FPGA comme le PSoC de Cypress ou le SmartFusion de Microsemi. Ce qui est vraiment nouveau ici, c’est la chaîne de développement open source qui est proposée par le constructeur.

La chaîne de développement nommée QORC pour QuickLogic Open Reconfigurable Computing est disponible sous forme d’un projet Git clonable depuis le site GitHub [5].

Le projet est un ensemble de scripts et de Makefiles permettant de télécharger et de configurer les différents projets open source pour le développement sur la carte. QORC s’occupe notamment de configurer SymbiFlow pour la partie FPGA et GCC pour compiler sur le cœur ARM.

Le but de cet article est de rassembler les différentes informations permettant de maîtriser le kit de développement. Tout d’abord, nous allons décrire le bloc eFPGA contenu dans le EOS S3, puis nous démarrerons la carte telle que sortie du carton pour la prendre en main. Nous décrirons ensuite les outils open source du SDK QORC maintenu par QuickLogic. Une fois que toutes ces bases seront posées, nous pourrons nous attaquer au développement proprement dit avec le « hello world » de l’électronique : la LED qui clignote. Nous aborderons ensuite le sujet de la communication entre le eFPGA et le CPU avec le développement d’une PWM permettant de changer dynamiquement la fréquence de clignotement, ou l’impression d’intensité, d’une LED. Et pour terminer, nous donnerons la méthode pour récupérer une carte « briquée » grâce à un adaptateur JTAG-SWD et de openOCD permettant de reprogrammer la flash.

Les exemples présentés sont publiquement disponibles sur GitHub à [6].

1. Description du eFPGA de l’EOS S3

Intéressons-nous maintenant au cœur du sujet de cet article : le eFPGA. Comme on peut le voir sur la figure 3, le « bloc FPGA » communique avec le reste du microcontrôleur via un système de FIFO ainsi qu’avec le bus « lent » de 10 MHz AHB-AON.

Le bus AHB pour Advanced High-performance Bus est un bus de type adresse/données/contrôle qui est défini dans le standard AMBA (Advanced Microcontroller Bus Architecture) par la société ARM. Le suffixe AON signifie quand à lui : Always ON. Ce bus reste donc en fonctionnement lorsque l’on passe le cœur Cortex-M4 en veille. Comme nous allons le voir dans la suite de cet article, la conversion des signaux du bus AHB vers le bus open source Wishbone est transparente et permet plus de souplesse dans le choix des composants à synthétiser dans le FPGA.

quickfeather figure 03-s

Fig. 3 : En plus des bus traditionnels comme le SPI, l'i²c... ce Cortex-M4 possède une zone de FPGA eFPGA (source QuickLogic, figure 2-1 du manuel de référence [^eoss3RM]).

Le contenu du FPGA est le suivant :

  • 891 cellules logiques ;
  • 8 blocs de mémoire vive à double port de 512x18 bits ;
  • 2 multiplieurs câblés de 32 × 32 bits ;
  • 32 entrées / sorties configurables.

La quantité de portes peut prêter à sourire dans un premier temps. En effet, avec de telles caractéristiques, nous sommes vraiment en bas de la gamme des FPGA habituels. Cependant, la diversité des éléments est tout à fait raisonnable avec ses blocs de RAM et ses deux multiplieurs câblés. Il est possible d’envisager de faire du traitement du signal ou d’y intégrer des cœurs de processeur RISC-V (SERV) (voir note si après). Les 32 entrées / sorties configurables sont en quantité impressionnante.

Bien qu’en faible quantité, les ressources de la partie eFPGA sont suffisantes pour y intégrer deux cœurs SERV. Le SERV est un cœur RISC-V sérialisé développé par Olof Kindgren. Le nombre de cœurs SERV intégrables dans un FPGA sert de base au benchmark nommé corescore http://github.com/olofk/corescore. Cette valeur donne une idée des capacités d’un FPGA. Par exemple, le corescore de la carte Colorlight présentée dans l’article [7] est de 55, celui du TinyFPGA BX (composé d’un Lattice ICE40LP8K) est de 11. En ce qui concerne l’EOS S3, l’intégration au corescore est en cours pour 2 cœurs SERV. C’est certes faible en comparaison du ICE40LP8K, mais il ne faut pas oublier que, de par la présence du Cortex-M4, certaines fonctionnalités n’ont pas à être synthétisées dans le FPGA. Par ailleurs, le ICE40LP8K est tout de même pratiquement 6 fois plus gros que celui présent sur la iCEstick, dont les capacités sont proches du eFPGA de l’EOS S3 et qui est utilisé pour des projets tels que Silice [8] et learn-fpga [9].

Les cellules logiques ne possèdent pas de LUT (Look-Up Table) comme on peut le voir assez souvent sur les FPGA. À la place, les cellules sont constituées de multiplexeurs branchés en «arbre» tels que présentés sur la figure 4.

quickfeather figure 04-s

Fig. 4 : Diagramme bloc d'une cellule logique. Le registre synchrone de l'horloge `QCK` est alimenté par la série de multiplexeurs pilotés par les signaux d'entrée `TBS`, `TAB`, `TSL`. Les signaux `TAx`, `TBx`, `BAx` et `BBx` sont des constantes alimentées par la configuration (source : QuickLogic, figure 36, Logic Cell Block Diagram de [^eoss3DS]).

L’ensemble des multiplexeurs d’une cellule logique peut donc, en première approche, être considéré comme l’équivalent d’une LUT 3 entrées.

Les blocs de RAM de 8 kbit (512 mots de 18 bits) sont munis de deux ports adresse/donnée, un port pour l’écriture et un port pour la lecture. À noter que le bus d’adresse est séparé pour la lecture et l’écriture, ce qui autorise la lecture à un endroit pendant que l’on écrit à un autre. Comme chaque port a sa propre entrée d’horloge, il est possible de se servir des RAM pour faire franchir deux domaines d’horloge à des données.

Les deux multiplieurs câblés de 32 bits peuvent être transformés au besoin en 4 multiplieurs 16 bits.

Les entrées / sorties (IO) sont configurables en entrée, en sortie ou en entrée / sortie (3 états) en fonction du besoin.

2. Prise en main

Le kit de développement est utilisable sans aucun accessoire périphérique autre qu’un câble micro USB. Ce câble permet d’alimenter la carte, de programmer le microcontrôleur ainsi que de communiquer avec le programme. Il peut être cependant prudent de se procurer un câble avec le connecteur spécifique SWD pour pouvoir se réserver un accès si jamais on « brique » le bootloader. Un adaptateur USB-Série est également intéressant lorsque l’on développe du côté FPGA et que l’on ne souhaite pas intégrer le contrôleur USB.

Conseil, évitez de regarder la carte de trop près au moment du branchement de l’USB si vous ne voulez pas garder un point noir dans votre vision tant la LED RGB s’éclaire fortement à l’allumage.

Le premier réflexe du Linuxien est bien sûr d’aller voir les messages du noyau une fois la carte branchée. Et là, c’est la déception, rien ne s’affiche ! Le programme bootloader n’a pas encore démarré. Il faut pour cela presser le minuscule bouton reset et attendre treize clignotements en bleu de la LED pour voir apparaître un port série dans les messages noyau :

[428359.117236] usb 2-1.1.1: new full-speed USB device number 69 using xhci_hcd
[428359.218604] usb 2-1.1.1: New USB device found, idVendor=1d50, idProduct=6140, bcdDevice= 0.00
[428359.218609] usb 2-1.1.1: New USB device strings: Mfr=0, Product=0, SerialNumber=0
[428359.225704] cdc_acm 2-1.1.1:1.0: ttyACM0: USB ACM device

On se connectera au port série avec le terminal de son choix à 115200 bauds :

$ screen /dev/ttyACM0 115200
####################
Quicklogic QuickFeather LED / User Button Test
SW Version: qorc-sdk/qf_apps/qf_helloworldsw
Jun 7 2020 12:04:51
##########################
 
 
 
Hello world!!
 
#*******************
Command Line Interface
App SW Version: qorc-sdk/qf_apps/qf_helloworldsw
#*******************
[0] >

Comme on peut le voir ici, le microcontrôleur est déjà préprogrammé avec une boucle REPL (Read-Evaluate-Print Loop) permettant quelques rudiments de pilotage des périphériques :

[0] > help
help-path: (top)
diag           - QuickFeather diagnostic commands
exit           - exit/leave menu
help           - show help
?              - show help
help-end:
[0] > diag
[1] diag > red
[1] diag > blue
[1] diag > green
[1] diag > green
[1] diag > red
[1] diag > blue
[1] diag > userbutton
Not pressed
[1] diag > userbutton
Pressed
[1] diag >

Pour commuter la LED dans une couleur, il faut se mettre en mode diagnostique diag, puis taper la couleur que l’on souhaite allumer ou éteindre. De même que pour lire l’état du bouton, on tapera userbutton.

Bon, c’est intéressant pour se rassurer et voir que la carte fonctionne, mais pour le moment, nous n’avons pas touché à une once de code, et encore moins à une once de porte logique.

3. Installation des outils

Le kit de développement logiciel se nomme QORC SDK pour QuickFeather Open Reconfigurable Computing Software Development Kit et est disponible sur GitHub [5]. Par défaut, QORC installe beaucoup de projets qui ne sont pas nécessairement utiles pour un développement spécifique. Nous allons d’abord expliquer comment lancer le « gros script » qui installe tout, puis nous décomposerons les installations pour les projets qui nous intéressent. En fonction de ce que l’on cherche à faire, il n’est pas obligatoire de tout installer.

Pour utiliser QORC SDK, nous irons d’abord le cloner :

git clone --recursive https://github.com/QuickLogic-Corp/qorc-sdk

Attention au --recursive, qui signifie souvent que l’on va se retrouver avec une ribambelle de projets annexes à télécharger… ce qui est bien le cas ici :

...
Submodule 'TinyFPGA-Programmer-Application' # source de l'outil de programmation
Submodule 'qf_apps/quickfeather-initial-binaries'
Submodule 'qorc-example-apps' # exemples
Submodule 'qorc-testapps' # exemples
Submodule 'quick-feather-dev-board' # documents et fichiers kicad de la carte
Submodule 'quicklogic-fpga-toolchain' # détails pour générer les outils
Submodule 's3-gateware' # quelques IPs à destination du FPGA
Submodule 'tensorflow'
...

Les principaux sous-modules qui composent le dépôt sont les suivants :

  • BSP/ : bibliothèque spécifique à la QuickFeather (accès aux LED, structure de la flash, initialisation…) ;
  • docs/ : l’ensemble des documents sur l’environnement, le SDK, la plate-forme… ;
  • FreeRTOS/ : les sources de FreeRTOS ;
  • freertos_gateware/ : le code C de FreeRTOS permettant de communiquer avec le eFPGA ;
  • gateware/ : des binaires (bitstream) à télécharger dans le eFPGA ;
  • HAL/ : l’implémentation des périphériques (SPI, i2c, UART) disponibles dans l’EOS-S3 ;
  • Libraries/ : bibliothèques de haut niveau pour le chargement du gateware, l’audio, l’accès à la flash… ;
  • qf_apps/ : codes des projets de démonstration ;
  • qf_vr_apps/ : applications de reconnaissance vocale ;
  • qorc-example-apps/ : exemples d’application utilisant le QORC SDK ;
  • qorc-testapps/ : applications permettant de tester les fonctionnalités de la carte.

Pour le moment, nous n’avons téléchargé que le code source. Nous avons maintenant besoin d’installer et de compiler les différents outils, compilateurs et autres logiciels de synthèse.

3.1 Installation clé en main

La première approche, « officielle », consiste à « sourcer » le fichier envsetup.sh, et d’attendre une bonne dizaine de minutes ou trois quarts d’heure, suivant la machine :

$ source envsetup.sh
...
 
qorc-sdk env initialized.

Ce script installe la chaîne de compilation (même si déjà présente sur l’ordinateur) et les outils destinés à la génération du bistream à destination du eFPGA. Il a également le «bon» goût de triturer le système, d’ajouter une règle udev et de relancer le service en question.

$ du -sh .
3.8G    .

La commande du nous informe que le SDK prend 3,8 Go d’espace disque, ce qui n’est pas négligeable. Nous sommes tout de même bien loin des dizaines de Go des IDE FPGA habituels. Il faudra relancer cette commande (sourcer) à chaque fois que l’on voudra utiliser le SDK. Que le lecteur se rassure cependant, l’étape de compilation/installation n’est effectuée qu’une fois, la commande ne prend que quelques millisecondes les fois suivantes.

3.2 Installation manuelle

L’approche automatique n’est pas la seule : l’ensemble des étapes peut être réalisé manuellement. Cette seconde approche est non seulement plus pédagogique, car elle permet de mieux comprendre ce qui est réalisé et nécessaire, mais également limite l’installation d’outils aux seuls éléments qui ne seraient pas déjà installés. Pourquoi installer un cross-compilateur s’il est déjà disponible ? Et, sans doute le plus important, elle préserve son système adoré des manipulations hasardeuses faites automatiquement en catimini.

Reprenons maintenant un par un les outils qui nous intéressent.

3.2.1 Outils à destination du FPGA

Le développement sur le eFPGA se base sur la suite open source nommée SymbiFlow [10]. SymbiFlow est une suite logicielle comprenant principalement le logiciel de synthèse Verilog Yosys [11] et le logiciel de placement routage VPR (Versatile Place and Route) [12].

QuickLogic propose une version binaire des outils (il est également possible de la construire soi-même) :

# téléchargement de la dernière version
wget https://github.com/QuickLogic-Corp/quicklogic-fpga-toolchain/releases/download/v1.3.1/Symbiflow_v1.3.1.gz.run
# le répertoire d'installation est défini par une variable d'environnement
export INSTALL_DIR=/somewhere/
# exécutable
chmod +x Symbiflow_v1.3.1.gz.run
# installation
./Symbiflow_v1.3.1.gz.run

Pour avoir accès aux éléments installés dans INSTALL_DIR, la première étape est :

$ source $INSTALL_DIR/setup.sh

Ce fichier met à jour la variable $PATH pour ajouter deux répertoires de binaires et active conda qui est un outil proche de virtualenv.

Une fois cette commande effectuée, l’utilisateur aura accès aux outils nécessaires pour la synthèse (Yosys et pour le placement/routage VPR).

Attention

Que ce soit avec cette approche ou par celle présentée dans la section précédente, les outils viennent avec un demi-OS. Cela explique l’importante consommation de ressources (sur les 1,9 Go du répertoire d’installation, on peut considérer que seuls 285 Mo sont vraiment pertinents). Dans tous les cas, on devra passer par l’utilisation de conda qui est un système de virtualisation légère fonctionnant sur le même principe que virtualenv pour Python. Il est donc fondamental pour être en mesure d’utiliser ces outils de taper la commande :

conda activate

C’est le cas par exemple pour TinyFPGA-Programmer-Application (voir plus loin), sans quoi l’interpréteur Python des outils SymbiFlow ne sera pas en mesure de trouver les bibliothèques nécessaires.

Pour revenir sur le Python de sa machine, la commande suivante devra être utilisée :

conda deactivate

3.2.2 Outils à destination du microcontrôleur

L’EOS S3 disposant, en plus du FPGA, d’un cœur Cortex-M4, une chaîne de compilation croisée doit également être installée. Dans une majorité de distributions, un paquet est disponible nativement. Pour l’installer, et installer GDB en bonus, sur une distribution type Debian/GNU Linux, il suffit de faire :

$ sudo apt install gcc-arm-none-eabi gdb-multiarch

Ce paquet permet de compiler un binaire à destination d’une architecture ARM depuis une autre machine n’ayant pas nécessairement la même architecture. On parle dans ce cas de cross-compilation.

# l'architecture de la machine est du x86 (PC)
$ uname -m
x86_64
 
# si on compile avec le gcc «natif» on obtient un binaire x86
$ gcc main.c -o main
$ file main
main: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
for GNU/Linux 3.2.0, not stripped
 
# si on compile avec le gcc que nous venons d'installer on
# obtient un binaire au format ARM
$ arm-none-eabi-gcc -c main.c -o main
$ file main
main: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), not stripped
# ce dernier binaire ne pourra bien sûr pas
# s'exécuter sur la machine hôte
$ ./main
bash: ./main: cannot execute binary file: Exec format error

3.2.3 Outils de chargement

Pour écrire un firmware dans la flash de l’EOS S3, on passe par le port USB et par un protocole issu du projet tinyFPGA [13]. Le sous-répertoire TinyFPGA-Programmer-Application/ contient un script Python dédié à ce transfert, mais avant de pouvoir l’utiliser, nous avons besoin d’installer quelques paquets supplémentaires :

$ pip3 install tinyfpgab
$ pip3 install apio
$ apio drivers --serial-enable

Attention : Pensez bien à utiliser la commande conda activate avant d’installer les outils.

Ensuite, il faut donner les droits d’écriture sur le périphérique USB en mode programmation : un fichier 71-QuickFeather.rules se trouve dans le même répertoire. Celui-ci doit être copié (en root) dans /etc/udev/rules.d/ et pour être prise en compte (toujours en root), la commande suivante doit être lancée :

$ udevadm control --reload-rules && udevadm trigger

Le script peut maintenant être utilisé avec Python 3 :

python3 $(QORC-SDK)/TinyFPGA-Programmer-Application/tinyfpga-programmer-gui.py \
--port /dev/ttyACMxx --reset --mode m4 --m4app file.bin

Avec :

  • --reset pour lancer automatiquement le nouveau firmware au lieu d’avoir à appuyer manuellement sur le bouton reset ;
  • --mode pour spécifier quelle zone de la flash doit être mise à jour (m4, fpga, fpga-m4 voir 5) ;
  • --m4app pour spécifier le fichier à charger dans la zone m4app ;
  • --appfpga le fichier destiné à être écrit dans la zone appfpga ;
  • --bootloader pour la zone bootloader du m4 ;
  • --bootfpga pour la zone bootloader du eFPGA.

quickfeather figure 05-s

Fig. 5 : Structure de la mémoire non volatile (source : QuickLogic).

4. Structure des projets QORC

Pour ne pas perdre le lecteur qui souhaiterait pousser et utiliser les démonstrations proposées par QuickLogic, nous allons respecter, dans la suite de cet article, la structure standard. Mais avant de commencer à réaliser des applications, voyons de quoi la structure d’un projet QORC est faite.

Chaque projet se compose ainsi :

$ tree project
project
├── fpga
│   └── rtl
│       ├── xx.v
│       └── quickfeather.pcf
├── fsm
│   └── fsm.h
├── GCC_Project
│   ├── config-GCC.mk
│   ├── config.mk
│   ├── Makefile
│   └── makefiles
│       ├── [...]
│       └── Makefile_xx
├── inc
│   ├── Fw_global_config.h
│   ├── [...]
│   └── xxx.h
└── src
    ├── main.c
    ├── pincfg_table.c
    ├── s3x_pwrcfg.c
   ├── [...]
    └── xxx.c

Dans cette arborescence, les répertoires src/ et inc/ ont un rôle aisé à déterminer, puisqu’ils contiennent les .c (ceux spécifiques à un projet et des fichiers imposés par l’infrastructure, comme la configuration des broches ou celle des horloges), il en va de même pour inc qui est le stockage pour les en-têtes (encore une fois, certains sont imposés comme celui dédié à l’activation / désactivation de certaines fonctionnalités / périphériques). Nous décrirons dans le premier exemple la nature des fichiers imposés.

Le répertoire fsm/ contient des en-têtes pour les machines d’état.

Le répertoire fpga/ est l’endroit où est stocké tout ce qui concerne le eFPGA (il peut être absent si le projet ne fait pas usage de la partie logique) : le sous-répertoire rtl/ est obligatoire et contient le(s) fichier(s) HDL ainsi que le fichier de contraintes (lien entre les signaux du fichier de plus haut niveau et les broches physiques). À ce répertoire peuvent s’ajouter src/ et inc/ pour stocker le(s) pilote(s) pour les blocs du projet.

Venons-en au dernier : GCC_Project/. Celui-ci est sans doute l’un des plus importants à comprendre, c’est dans celui-ci que sont faites toutes les opérations pour générer le gateware et le firmware. Il contient l’ensemble des règles nécessaires afin d’obtenir un fichier binaire prêt à être chargé. Ces opérations reposent sur l’utilisation d’un Makefile principal avec les règles pour construire les éléments contenus dans le BSP (en faisant appel à un Makefile_xxx se trouvant dans le sous-répertoire makefiles/), il inclut config.mk dont le rôle est de définir quelques variables d’environnement (principalement, le chemin absolu du cross-compilateur) en fonction du système utilisé (Windows ou GNU/Linux), ce .mk incluant à son tour config-GCC.mk qui définit les flags passés à GCC au moment de la compilation.

Le Makefile délègue toutes les opérations à d’autres fichiers se trouvant dans le répertoire makefiles/. Sans rentrer dans le cheminement global, voyons-en une portion pour comprendre le mécanisme qui pourra ou devra être adapté en fonction du projet et des besoins.

Les pilotes des différents périphériques font partie du HAL (Hardware Abstraction Layer), ils sont contenus dans le répertoire HAL/ qui doit être compilé avec le reste du projet. Dans le Makefile, nous avons les lignes :

HAL:
    make -f makefiles/Makefile_HAL
clean_HAL:
    make -f makefiles/Makefile_HAL clean
CLEAN_TARGET += HAL

Ces lignes définissent une règle pour la compilation et une seconde pour le nettoyage (la variable CLEAN_TARGET est utilisée plus loin dans le fichier, afin de déterminer la liste des règles de nettoyage devant être appelées).

Dans les deux cas, un appel à make est fait en lui précisant que les règles se trouvent dans le fichier (-f) makefiles/Makefile_HAL.

Si l’on regarde le contenu du fichier makefiles/Makefile_HAL, nous y trouvons :

HAL_SRCS:=$(wildcard $(HAL_DIR)/*.c )
 
filters=eoss3_hal_audio.c eoss3_hal_ffe.c eoss3_hal_fpga_adc.c \
    eoss3_hal_i2s_master_assp.c
SRCS:=$(filter-out $(filters),$(notdir $(HAL_SRCS)))
 
OBJS:=$(addprefix $(OUTPUT_PATH)/,$(SRCS:.c=.o))
SRC_PATH:= $(HAL_DIR)
 
include $(COMMON_STUB)

En ligne 1, l’ensemble des fichiers se trouvant dans le répertoire pointé par HAL_DIR est stocké dans une variable. La ligne 5 réalise deux opérations :

  1. Extraire le nom du fichier sans le chemin.
  2. Supprimer de la liste les fichiers se trouvant dans filters.

Une fois toutes ces étapes effectuées, une inclusion de COMMON_STUB (qui pointe sur makefiles/Makefile_common) est réalisée pour générer un .o à partir de la liste des .c (ou pour exécuter ql_symbiflow dans le cas du FPGA).

Cette description, un peu longue, est nécessaire pour bien comprendre la méthode mise en œuvre par QuickLogic. Par exemple, si l’utilisateur souhaite faire usage d’un périphérique, mais que la compilation échoue, il peut être pertinent de regarder le Makefile correspondant pour s’assurer que le support n’est pas filtré.

L’ensemble des exemples fournis dans les sources de QORC SDK utilise des liens relatifs. Pour être en mesure de travailler en dehors de ce dépôt, il est nécessaire de modifier config.mk et de modifier le chemin absolu vers le BSP :

export PROJ_ROOT=$(PROJ_DIR)/../../..

Par :

export PROJ_ROOT?=$(PROJ_DIR)/../../..

Le ? précise que la variable ne sera affectée que si elle n’est pas déjà déclarée. Il ne reste plus qu’à la définir comme variable d’environnement pour ne plus avoir d’erreurs.

5. Clignotons, braves gens !

Qui dit processeur et FPGA dit que deux approches sont disponibles pour faire clignoter une LED (et pour voir comment accéder aux ressources depuis l’un ou l’autre) :

  • depuis le microcontrôleur, avec un programme en C ;
  • depuis le eFPGA, avec un porte-gramme en Verilog.

Pour illustrer ces deux approches, nous allons réaliser un programme incluant un gateware qui fera clignoter une LED rouge dans la partie microcontrôleur et une LED bleue dans la partie eFPGA.

5.1 Depuis le microcontrôleur

Cet exemple, uniquement à destination du microcontrôleur, va être l’occasion de découvrir la façon d’utiliser l’environnement fourni par QuickLogic, mais également de se familiariser, plus précisément, avec la structure d’un projet.

Le projet complet se trouve à https://github.com/trabucayre/quickfeather-notes/tree/master/examples/blinkCPU, mais il est également possible de repartir d’un des projets d’exemple existants, tels que qf_apps/qf_helloworldsw (avec un minimum de nettoyage pour ne conserver que les parties pertinentes).

Il existe au niveau de qorc_sdk/qf_apps un script Python nommé create_newapp.py pour créer une nouvelle application à partir d’une existante.

Toutefois, la ligne de commande suivante :

create_newapp.py --source old_prj_name --dest new_prj_name

Est, musculairement, plus éprouvante que :

cp -r old_prj_name new_prj_name

On se plaira donc à préférer la seconde option et à oublier ce script, qui ne fait rien de plus que la copie d’un répertoire.

Commençons par src/main.c.

D’abord les en-têtes :

#include "Fw_global_config.h" // This defines application
                              //specific characteristics
#include "qf_hardwaresetup.h"
#include "eoss3_hal_gpio.h"
  • le fichier Fw_global_config.h (présent dans inc/) contient un ensemble de définitions nécessaires à d’autres parties du SDK pour activer des fonctionnalités, il doit toujours se placer en premier ;
  • eoss3_hal_gpio.h (contenu dans la partie HAL/ du SDK) fournit les fonctions nécessaires pour piloter les broches ;
  • qf_hardwaresetup.h donne accès à la fonction d’initialisation du microcontrôleur. Le fichier est contenu dans la sous-structure BSP/ du SDK ;
void Delay(uint32_t nCount) {
    volatile uint32_t count = nCount;
    for (; count != 0; count--) __asm__("nop");
}
 
int main(void) {
    qf_hardwareSetup();
 
    while(1) {
        HAL_GPIO_Write(6, 1);
        Delay(0x3fFFFF);
        HAL_GPIO_Write(6, 0);
        Delay(0x3fFFFF);
    }
}
 
void SystemInit(void){} // init @ Reset_Handler
  • qf_hardwareSetup() se charge principalement de configurer les horloges et les broches de manière complètement transparente ; cette fonction doit être exécutée en premier dans le main() ;
  • la fonction SystemInit() (vide) doit obligatoirement être présente, car appelée de manière inconditionnelle au niveau du Reset_Handler (démarrage) avant main (HAL/startup/startup_EOSS3B_GCC.c).

Le reste du code se limite à utiliser HAL_GPIO_Write() avec le numéro de la broche en premier paramètre et la valeur en second. Un simple délai suit pour laisser un peu de temps pour voir la LED clignoter.

Un point peut surprendre dans ce code : hormis l’appel à qf_hardwareSetup(), il n’y a rien de relatif à une quelconque configuration spécifique à l’application. Habituellement avec un microcontrôleur, les horloges doivent être configurées ainsi que les broches. En fait, la fonction qf_hardwareSetup() effectue cette étape non pas sur une base statique, mais en parcourant une première structure contenant pour chaque broche sa configuration et une seconde structure fournissant pour chaque horloge son paramétrage.

Pour les GPIO, la configuration est définie au niveau de l’application dans le fichier src/pincfg_table.c. Pour la configuration de la broche 6 pour la LED rouge (PAD_22 du QFN), le nœud est le suivant :

  { // setup red LED
    .ucPin = PAD_22,
    .ucFunc = PAD22_FUNC_SEL_GPIO_6,
    .ucCtrl = PAD_CTRL_SRC_A0,
    .ucMode = PAD_MODE_OUTPUT_EN,
    .ucPull = PAD_NOPULL,
    .ucDrv = PAD_DRV_STRENGTH_4MA,
    .ucSpeed = PAD_SLEW_RATE_SLOW,
    .ucSmtTrg = PAD_SMT_TRIG_DIS,
  },

La définition des fonctions alternatives se trouve dans [14] chapitre 10 et dans [15] section 19.6 pour la configuration et section 19.5 pour les fonctions des broches.

La configuration des horloges respecte la même règle, mais les définitions se trouvent dans src/s3x_pwrcfg.c (voir [15] chapitre 11 pour les détails sur l’arbre des horloges, la page [16] présente un schéma très précis de cet arbre).

La suite des opérations se fait dans le répertoire GCC_Project/.

Dans le cas où l’utilisateur repart d’un projet existant, en vue de gagner un peu de temps, le Makefile peut être allégé afin de ne conserver comme règles que :

  • Startup HAL FreeRTOS Libraries BSP pour la règle principale external_srcs ;
  • appdir pour la cross-compilation des .c du répertoire src/.

Startup concerne le démarrage, HAL est nécessaire (entre autres) pour l’accès aux GPIO, FreeRTOS, car nombre de pilotes sont intimement liés à cet élément, Libraries pour la configuration des horloges et finalement BSP pour qf_hardwareSetup(). Oui, ça fait beaucoup de dépendances pour simplement faire clignoter une LED…

Après avoir exécuté la commande make, un fichier *.bin doit être présent dans le sous-répertoire output/bin/. Il ne reste plus qu’à flasher la carte : d’abord la faire passer en mode programmation par l’appui sur le bouton reset, puis quand la LED bleue clignote, il faut appuyer sur le bouton user juste à côté. Une fois que la LED verte clignote et que /dev/ttyACMx est apparu sur son PC, l’ultime étape est de taper :

$ python3 $(PROJ_ROOT)/TinyFPGA-Programmer-Application/tinyfpga-programmer-gui.py \
    --port /dev/ttyACM0 --reset --mode m4 --m4app output/bin/blinkCPU.bin

À la fin de la programmation, la LED bleue devrait à nouveau clignoter, puis laisser sa place à la rouge, signifiant que le code précédent est fonctionnel.

5.2 Depuis le FPGA

Ce second exemple est une variation sur le précédent. Nous allons laisser le microcontrôleur continuer à gérer la LED rouge et allons attribuer au FPGA la LED bleue. Les modifications au niveau applicatif vont ainsi être limitées à la configuration du eFPGA, de la broche utilisée par la partie logique et de son horloge de cadencement.

Pour créer le projet blinkCPU_FPGA, on pourra copier le projet qf_helloworldhw de la manière suivante :

$ cp -r qf_helloworldhw blinkCPU_FPGA

Ou récupérer le projet se trouvant à l’adresse https://github.com/trabucayre/quickfeather-notes/tree/master/examples/pwmCPU_FPGACLI.

Pour faire clignoter la LED, nous allons compter de 0 à N-1. N sera la moitié de la valeur de la fréquence de cadencement pour changer l’état de la LED deux fois par seconde et avoir une période de 1 Hz.

Le contenu du fichier source Verilog nommé blinkfpga.v est donné ci-dessous :

module blinkFPGA(
    output reg blueled
);
    wire clk, rst;
    /* CPU <-> FPGA interface
     * here only one of the two clock sources
     * and corresponding reset signal are used
     */
    qlal4s3b_cell_macro u_qlal4s3b_cell_macro (
        .Sys_Clk0 (clk),
        .Sys_Clk0_Rst(rst)
    );
 
    reg [23:0] cnt;
    wire        rst_cnt = cnt == 6000000-1;
 
    always @(posedge clk) begin
        /* increment cnt until rst or rst_cnt
         * are asserted
         */
        if (rst || rst_cnt)
            cnt <= 0;
        else
            cnt <= cnt + 1;
        /* when rst is asserted set default LED state
         * and when rst_cnt is asserted change state
         */
        if (rst)
            blueled <= 1'b0;
        else if (rst_cnt)
            blueled <= ~blueled;
    end
endmodule

Le code est assez classique :

  • un compteur est incrémenté jusqu’à atteindre une certaine valeur, ici codée en dur à 6.10^6 ;
  • lorsque la valeur est atteinte, le compteur est réinitialisé et l’état du signal associé à la LED voit son état inversé.

Le point le plus important est l’utilisation de qlal4s3b_cell_macro qui est l’interface entre le Cortex-M4 et le eFPGA. Comme nous le verrons plus loin, cette macro expose l’ensemble des fonctionnalités entre les deux mondes, mais dans le cas présent, seuls une horloge et un signal de réinitialisation nous sont utiles.

Comme pour tout projet sur FPGA, un fichier de contraintes est nécessaire pour fixer la relation entre les signaux de l’interface du module de plus haut niveau et les broches d’entrées / sorties (pinout). Ce fichier de contraintes au format PCF se nomme quickfeather.pcf et se trouve dans le même répertoire que les sources Verilog fpga/rtl/.

Pour configurer notre unique sortie, on ajoutera la ligne suivante dans le fichier :

set_io blueled      38

La position de la LED bleue est donnée dans [17] page 8. La correspondance avec le signal IO_18 de la broche 38 du boîtier QFN est donnée dans [14] Table 31.

Bien que ce code puisse être manipulé directement au travers des Makefile du projet, il est également possible de lancer les étapes manuellement. D’une part, il est ainsi possible de vérifier qu’il est valide, d’autre part, il est possible de charger la configuration (bitstream) directement avec openOCD.

Une commande rassemble toutes les étapes :

$ ql_symbiflow -compile -src ../fpga/rtl/ \
    -d ql-eos-s3 -t blinkFPGA -v blinkFPGA.v \
    -p quickfeather.pcf -P PU64 -dump header

Une alternative, si les variables d’environnements sont correctement configurées est :

ql_symbiflow -compile -src $(MAIN_FPGA_RTL_DIR) \
    -d ql-eos-s3 -t $(RTL_TOP_MODULE) \
    -v $(FPGA_RTL_SRCS) -p quickfeather.pcf \
    -P PU64 -dump header | grep -v $(SYMBIFLOW_WARN_PATTERN)

Avec :

  • -compile pour informer que toutes les étapes doivent être réalisées (synth limite à la synthèse) ;
  • -src le répertoire où se trouvent les sources ;
  • -d la cible ;
  • -t le nom du module de plus haut niveau (top) ;
  • -v la liste des fichiers Verilog à manipuler ;
  • -p le fichier de contraintes ;
  • -P le type du boîtier ;
  • -dump le type de fichier de sortie. header signifie que le bitstream sera converti en un en-tête pour inclusion dans l’application.

Cette ligne se trouve dans le fichier GCC_Project/makefiles/Makefile_common. Le script Shell ql_symbiflow produit, dans un premier temps, un fichier Makefile.symbiflow dans fpga/rtl/ qui rassemble l’ensemble des étapes de génération du bitstream :

  • appel à Yosys pour la synthèse ;
  • appel à VPR (Versatile Place and Route), issu de VTR (Verilog To Routing) pour le placement et le routage ;
  • en fonction de la valeur passée à dump un appel est fait à un script qui convertit le .bit en .openocd, _bit.h, .jlink ou .bin.

ql_symbiflow n’est pas un binaire, mais un script qui lance séquentiellement la synthèse, le placement, puis le routage.

Puisque SymbiFlow se base par défaut sur du Verilog pour la synthèse, cette manière de faire ne fonctionnera évidemment pas avec du VHDL, ce qui n’écarte pas pour autant le VHDL. En effet, avec Yosys et l’extension ghdl-yosys-plugin [18], il est tout à fait possible de faire de la synthèse VHDL. Il faut cependant adapter les scripts fournis par SymbiFlow.

5.3 Chargement du eFPGA depuis le processeur

Le bitstream ainsi généré va être intégré à notre premier projet de clignotement de LED rouge sur le microcontrôleur. Pour configurer le eFPGA avec le bitstream que nous venons de générer, nous allons donc reprendre le précédent projet blinkCPU pour y ajouter le chargement.

En repartant du main.c de l’exemple précédent, quelques modifications doivent être appliquées afin de configurer le FPGA :

[...]
#include "fpga_loader.h" // fonction to load gateware
#include "blinkFPGA_bit.h" // FPGA gateware
#include "s3x_clock.h" // required to enable/disable FPGA clk
[...]
qf_hardwareSetup();
 
/* disable FPGA clock before load */
S3x_Clk_Disable(S3X_FB_21_CLK);
S3x_Clk_Disable(S3X_FB_16_CLK);
/* load gateware */
load_fpga(sizeof(axFPGABitStream),axFPGABitStream);
/* re-enable FPGA clock */
S3x_Clk_Enable(S3X_FB_21_CLK);
S3x_Clk_Enable(S3X_FB_16_CLK);
[...]
  • le fichier blinkFPGA_bit.h est le fichier généré par ql_symbiflow contenant la configuration du eFPGA sous la forme d’un tableau de caractères ;
  • les horloges S3X_FB_16_CLK et S3X_FB_21_CLK sont les deux sources précédemment citées ([14] chapitre 7.8 table 22). La première horloge correspond à Sys_Clk0 de qlal4s3b_cell_macro, la seconde à Sys_Clk1 (non utilisée dans cet exemple) ;
  • la fonction load_fpga prend le tableau de caractères contenant la configuration du FPGA, générée par ql_symbiflow et réalise le chargement.

La seconde modification est à réaliser au niveau de la configuration des broches (fichier src/pincfg_table.c). En effet, utiliser la broche depuis le FPGA est considéré comme une fonction alternative (au même titre que son attribution à un contrôleur matériel).

  { // setup blue LED
    .ucPin = PAD_18,
    /* CPU access */
    //.ucFunc = PAD18_FUNC_SEL_GPIO_4,
    //.ucCtrl = PAD_CTRL_SRC_A0,
    /* FPGA access */
    .ucFunc = PAD18_FUNC_SEL_FBIO_18,
    .ucCtrl = PAD_CTRL_SRC_FPGA,
    .ucMode = PAD_MODE_OUTPUT_EN,
    .ucPull = PAD_NOPULL,
    .ucDrv = PAD_DRV_STRENGTH_4MA,
    .ucSpeed = PAD_SLEW_RATE_SLOW,
    .ucSmtTrg = PAD_SMT_TRIG_DIS,
  },

Toujours en ce qui concerne les modifications dans le répertoire src/, s3_pwrcfg.c doit être adapté pour que C16 ait son champ init_state à INIT_STATE(F_12MHZ, 1, INIT_GATE_OFF).

La dernière étape, par comparaison à l’exemple précédent, consiste à intégrer la génération du bitstream dans le flux de création du binaire final.

Dans GCC_Project/config-GCC.mk :

export INCLUDE_DIRS=-I"$(PROJ_DIR)" \
    -I"$(APP_DIR)/inc" \
    -I"$(APP_DIR)/fpga/rtl \
    -I"$(PROJ_ROOT)/Libraries/FPGA/inc" \
    [...]
export MAIN_FPGA_RTL_DIR= $(APP_DIR)$(DIR_SEP)fpga$(DIR_SEP)rtl
export RTL_TOP_MODULE=blinkFPGA
export FPGA_DIR = $(LIB_DIR)$(DIR_SEP)FPGA$(DIR_SEP)src
  • la variable INCLUDE_DIRS est mise à jour pour pointer sur le répertoire fpga/rtl/ afin d’autoriser l’inclusion du fichier header généré par ql_symbiflow et sur le répertoire $(PROJ_ROOT)/Libraries/FPGA/inc pour la définition de la fonction load_fpga ;
  • MAIN_FPGA_RTL_DIR est introduite pour informer le système d’où se trouve les sources du projet FPGA ;
  • FPGA_DIR est ajoutée pour compiler la bibliothèque dédiée au chargement du FPGA depuis le MCU ;
  • RTL_TOP_MODULE est également introduite pour donner le nom du module de plus haut niveau. Cette variable servira également à nommer le fichier résultant des opérations de synthèse, placement et routage.

Ensuite, la règle principale doit être changée pour ajouter une dépendance :

-all: _makedirs_ external_srcs appdir
+all: _makedirs_ external_srcs appfpga appdir

La règle appfpga est également insérée :

.PHONY: appfpga
appfpga:
   make -f makefiles/Makefile_appfpga
clean_appfpga:
   make -f makefiles/Makefile_appfpga clean
CLEAN_TARGET += appfpga

Pour intégrer les opérations relatives au FPGA.

Si l’utilisateur souhaite changer la période de la LED, il est bien évidemment possible de jouer sur la valeur maximale du compteur dans les sources Verilog, mais il est également possible de modifier dans src/s3x_pwrcfg.c la valeur passée en premier paramètre de la macro INIT_STATE. Par exemple, pour passer C16 (associée à Sys_Clk0) à 24 MHz au lieu des 12 d’origine :

[CLK_C16] = {
    .name = "C16",
    .clkd_id = CLK_C16,
    .type = SRC_CLK,
    .sync_clk = SYNC_CLKD (0, 0, 0),
    .cru_ctrl = CRU_CTRL (0x20, 0x1fe, 9, 0x24, 0x64, 0x01, 5),
    .def_max_rate = (F_24MHZ),
    //.init_state = INIT_STATE(F_12MHZ, 1, INIT_GATE_OFF),
    .init_state = INIT_STATE(F_24MHZ, 1, INIT_GATE_OFF),

Après avoir lancé la commande make, le répertoire output/bin devrait contenir un .bin qu’il sera possible de charger. La LED tricolore devrait ensuite montrer séquentiellement du bleu, rouge et violet (bleu et rouge allumés).

La consommation d’un tel code Verilog est de 33 cellules sur les 891 à disposition (l’information se trouve dans fpga/rtl/build/route.log dans la section Resource usage).

6. Communication CPU-FPGA

Jusqu’à présent, nous avons vu les bases de l’utilisation du SDK, comment écrire, compiler et charger un firmware pour le Cortex-M4, puis comment intégrer un gateware pour la partie logique. Dans ce dernier cas, les interactions se limitant à charger le bitstream et à activer les horloges. Mais parler d’un composant disposant à la fois d’un CPU et d’un FPGA, sans traiter le cas de la communication ne ferait pas de sens et ferait perdre un des intérêts principaux de l’EOS S3.

Pour illustrer cet aspect, nous allons considérer l’ajout d’un périphérique matériel (côté FPGA) accessible depuis le processeur (comme n’importe quel contrôleur matériel se trouvant dans un SoC).

Ce composant, simple, consiste en une PWM : nous pourrons agir sur la période, le rapport cyclique, son activation et son inversion.

Pourquoi se compliquer la vie à ajouter un tel composant au lieu d’utiliser le Timer disponible dans la partie processeur? Au-delà du simple aspect pédagogique, la réponse est simple : le Timer de l’EOS S3 est basique et ne propose que deux fonctions :

  1. Un systick utilisé pour produire une interruption régulière au niveau du processeur.
  2. Un watchdog fournissant un compteur qui décrémente jusqu’à 0 et qui reset le contrôleur si le logiciel ne vient pas le rafraîchir.

L’EOS S3 ne propose donc pas véritablement de PWM dans le sens où on l’entend. Il faut écrire un code spécifique pour pouvoir reproduire le comportement souhaité d’une PWM. Quitte à écrire du code spécifique, pourquoi ne pas le faire en Verilog et créer le périphérique manquant.

Le projet complet se trouve à https://github.com/trabucayre/quickfeather-notes/tree/master/examples/pwmCPU_FPGACLI.

6.1 Bus Wishbone

Pour commencer, nous allons avoir besoin d’un bus de communication entre le Cortex-M4 et le eFPGA.

Bien que, classiquement, le Cortex-M4 utilise en interne un bus ([15] figure 2-1) AHB pour l’accès aux périphériques; en amont de l’interface avec le FPGA, une conversion est réalisée ([15] figure 27-14) pour exposer du côté FPGA un bus Wishbone [19].

Ce protocole fonctionne sur le principe maître-esclave, un composant initie l’ensemble des opérations et le périphérique concerné va agir en fonction de la requête. Il est possible d’avoir plusieurs esclaves, la sélection reposant sur l’adresse.

Comparé aux bus AXI (utilisés dans les Zynq et SoCFPGA) ou Avalon (Intel/Altera), le bus Wishbone dans la forme utilisée ici présente moins de signaux et est plus simple à utiliser. La figure 6 schématise, de manière minimale, une opération d’écriture (haut) et une opération de lecture (bas). Ces opérations sont toujours du point de vue du maître, ici le Cortex-M4.

quickfeather figure 06-s

Fig. 6 : Principe des transactions sur bus Wishbone.

Les signaux sont les suivants :

  • ADR : adresse dans la zone mémoire ;
  • DAT_O : (point de vue initiateur) utilisé lors d’une requête d’écriture. Contient la valeur écrite depuis le processeur ;
  • DAT_I : (point de vue initiateur) utilisé lors d’une requête de lecture. Va contenir la valeur correspondant à l’adresse associée.

Le bus fournit également des signaux de contrôle :

  • CYC : indique qu’une transaction est en cours sur le bus, le signal reste à l’état haut jusqu’à la fin de celle-ci ;
  • STB : indique qu’un transfert de données est en cours ;
  • ACK : acquittement de la part de l’esclave, sur le bus ;
  • WE : indique que le transfert en cours est en écriture ;
  • SEL : il a une taille de log2(datasize) avec datasize la taille du bus de donnée. Il permet de déterminer quels sont les octets qui sont impactés. Ce signal autorise des écritures en utilisant une taille inférieure à la taille du bus (par exemple 1 ou 2 octets dans un bus 32 bits).

La macro _cell, présentée plus loin, dispose également d’un signal WBs_RD qui est le pendant, pour l’écriture, du signal WE. Ce signal ne faisant pas partie de la spécification de ce bus, il semble préférable de ne pas en faire usage pour garantir une portabilité du code.

6.2 Partie FPGA

Dans la section précédente, nous avons utilisé qlal4s3b_cell_macro pour avoir accès à une source d’horloge, toutefois cet élément ne se limite pas à deux horloges et deux reset : elle fournit l’ensemble des signaux entre les deux environnements ([14] section 6.3, malheureusement incomplète) avec, pour ne citer que les interfaces les plus importantes  :

  • Wishbone pour la communication en lecture/écriture ;
  • SDMA pour transférer efficacement des flux de données ;
  • interruptions pour avertir le microcontrôleur d’un événement ;
  • packet FIFO pour, encore une fois, du transfert de données.

Pour notre application, nous n’allons faire usage que du bus Wishbone pour la configuration de la PWM et des horloges pour la communication et le cadencement de la PWM.

6.2.1 Implémentation de la PWM

Le code de la PWM est le suivant :

/* pwm control: set by MCU */
reg [23:0] pwm_cpt, pwm_duty_s, pwm_period_s;
reg [ 1:0] pwm_cfg_s;
/* take cfg independently */
wire pwm_en = pwm_cfg_s[0];
wire pwm_inv = pwm_cfg_s[1];
 
/* count from 0 to pwm_period_s */
always @(posedge Sys_Clk0) begin
    if (Sys_Clk0_Rst || (pwm_cpt == pwm_period_s - 1'b1)) begin
        pwm_cpt <= 0;
    end else begin
        pwm_cpt <= pwm_cpt + 1'b1;
    end
end
 
/* set pwm_o to high when pwm_cpt < duty cyle and pwm_en is set
* invert the signal if pwm_inv is set
*/
assign pwm_o = ((pwm_cpt < pwm_duty_s - 1'b1) & pwm_en) ^ pwm_inv;

Le code, simple, ne présente que peu de difficultés :

  • un compteur, dont la taille permet d’aller jusqu’à 16 secondes, est incrémenté tous les cycles d’horloge jusqu’à atteindre la valeur de la période fixée depuis le microcontrôleur. Quand cette valeur est atteinte, le compteur est réinitialisé ;
  • la sortie est mise à l’état haut tant que le compteur n’a pas atteint la valeur, fixée depuis le microcontrôleur, contenue dans pwm_duty_s. Comme nous souhaitons activer/désactiver cette sortie, un et logique est réalisé avec pwm_en (également configurable), et cette valeur peut être inversée par un ou exclusif avec pwm_inv (configurable).

Ce code est exclusivement dédié à la gestion de la PWM, la seconde étape est donc de gérer les interactions avec le Cortex-M4 au travers du protocole Wishbone.

6.2.2 Gestion des registres pour la configuration

La gestion d’une requête se traduit par :

wire wbs_ack_q = WBs_CYC_i & WBs_STB_i & (~WBs_ACK_o);

quickfeather figure 07-s

Fig. 7 : Interactions entre signaux de contrôle.

Qui revient à rendre l’opération atomique en s’assurant que les signaux de contrôle sont à l’état haut, et grâce à WBs_ACK_o, que c’est le premier cycle de la requête en cours. Comme cette ligne se repose sur un wire, cette opération est instantanée (figure 7).

wire wbs_wen = wbs_ack_q & WBs_WE_i;

La détection d’une écriture, toujours atomique et combinatoire, consiste simplement à utiliser le précédent signal combiné au WBs_WE_i issu du Cortex-M4.

Le bus d’adresse est d’une taille de 17 bits. Toutefois, seuls deux bits sont nécessaires pour coder 3 registres (la période, la durée de l’état haut et la configuration) et comme les accès sont alignés sur 32 bits, l’adresse est découpée pour ne conserver que la partie pertinente :

wire wbs_addr_s = WBs_ADDR_i[3:2];

Finalement, un bloc est écrit pour :

  • synchroniser le signal d’acquittement (l.3) ;
  • gérer les requêtes d’écriture (l.6-19) ;
  • remplir le bus FPGA vers Cortex-M4 avec le signal correspondant à l’adresse.
always @(posedge WBs_CLK_i) begin
    /* ack */
    WBs_ACK_o <= wbs_ack_q;
 
    /* write request */
    if (WBs_RST_i) begin
        pwm_duty_s <= 24'b0;
        pwm_period_s <= 24'b0;
        pwm_cfg_s <= 2'b0;
    end else if (wbs_wen) begin
        case (wbs_addr_s)
        2'b00:
            pwm_duty_s <= WBs_DAT_i[23:0];
        2'b01:
            pwm_period_s <= WBs_DAT_i[23:0];
        2'b10:
            pwm_cfg_s <= WBs_DAT_i[1:0];
        endcase
    end
 
    /* read */
    case (wbs_addr_s)
    2'b00:
        WBs_DAT_o <= {8'b0, pwm_duty_s};
    2'b01:
        WBs_DAT_o <= {8'b0, pwm_period_s};
    2'b10:
        WBs_DAT_o <= {30'b0}, pwm_cfg_s};
    default:
        WBs_DAT_o <= {32'b0};
    endcase
end

La première ligne du bloc always consiste à synchroniser, de manière inconditionnelle, le signal d’acquittement avec la prise en compte de la donnée ou avec l’affectation du bus de donnée pour la lecture.

La partie suivante gère l’écriture. Cette opération est conditionnée par le wbs_wen afin de se garantir que la mise à jour n’est faite que si la requête est bien une écriture.

Dans le cas de la lecture, comme il n’y a pas de réelles contraintes (dans cet exemple), le code se contente de toujours affecter la valeur d’un des signaux en fonction de l’adresse. Si lors d’une lecture, un incrément par exemple devait être réalisé, il deviendrait nécessaire de prendre en considération le signal de lecture. Ceci revient à écrire un code identique à celui de wbs_en, mais en prenant la valeur opposée pour WBs_WE_i.

À ce stade, le code de la PWM est prêt, ainsi que la gestion des interactions avec le bus Wishbone, il ne reste plus qu’à raccorder l’ensemble à l’interface Cortex-M4 <-> FPGA :

wire WB_CLK, WB_RST, WB_RST_FPGA;
wire Sys_Clk0, Sys_Clk0_Rst, Sys_Clk1, Sys_Clk1_Rst;
wire clk, reset;
 
/* use clk0 as ref clock for wishbone */
gclkbuff u_gclkbuff_reset(.A(Sys_Clk0_Rst | WB_RST), .Z(WB_RST_FPGA));
gclkbuff u_gclkbuff_clock(.A(Sys_Clk0), .Z(WB_CLK));
/* use clk1 for the rest */
gclkbuff u_gclkbuff_reset1(.A(Sys_Clk1_Rst), .Z(reset));
gclkbuff u_gclkbuff_clock1(.A(Sys_Clk1), .Z(clk));
 
qlal4s3b_cell_macro u_qlal4s3b_cell_macro (
    // AHB-To-FPGA Bridge
    .WBs_ADR     (WBs_ADR     ), // out [16:0] | Address Bus       to   FPGA
    .WBs_CYC     (WBs_CYC     ), // out        | Cycle Chip Select to   FPGA
    .WBs_BYTE_STB(WBs_BYTE_STB), // out [3:0]  | Byte Select       to   FPGA
    .WBs_WE      (WBs_WE      ), // out        | Write Enable      to   FPGA
    .WBs_RD      (WBs_RD      ), // out        | Read Enable       to   FPGA
    .WBs_STB     (WBs_STB     ), // out        | Strobe Signal     to   FPGA
    .WBs_WR_DAT (WBs_WR_DAT ),   // out [31:0] | Write Data Bus    to   FPGA
    .WB_CLK      (WB_CLK      ), // in         | FPGA Clock        from FPGA
    .WB_RST      (WB_RST      ), // out        | FPGA Reset        to   FPGA
    .WBs_RD_DAT (WBs_RD_DAT ),   // in [31:0]  | Read Data Bus     from FPGA
    .WBs_ACK     (WBs_ACK     ), // in         | xfer Ack          from FPGA
    [...]
    /* FB Clocks */
    .Sys_Clk0    (Sys_Clk0    ), // output
    .Sys_Clk0_Rst(Sys_Clk0_Rst), // output
    .Sys_Clk1    (Sys_Clk1    ), // output
    .Sys_Clk1_Rst(Sys_Clk1_Rst), // output
    [...]
);

Pour des raisons de compacité, seules les parties importantes sont données. Peu de choses nécessitent d’être détaillées ici, hormis l’utilisation de Sys_Clk0 pour cadencer l’interface Wishbone (connexion à WB_CLK).

Le gateware représente une consommation de 147 cellules sur les 891 à disposition.

6.3 Partie Cortex-M4

Dans la partie logique, nous avons sélectionné la première horloge comme source pour la partie communication et la seconde pour le cadencement de la PWM à proprement parler.

Comme le bus côté CPU ne peut pas dépasser 10 MHz (figure 11.1, page 90 [15]), l’entrée correspondant à C16 (première horloge) est modifiée par :

.init_state = INIT_STATE(F_10MHZ, 1, INIT_GATE_OFF),

Par mesure de simplicité, pour obtenir une base de temps de la microseconde, C21 doit être modifiée par :

.init_state = INIT_STATE(F_1MHZ, 1, INIT_GATE_OFF),

Le second point est l’accès, en lecture et écriture, au FPGA. Les registres dédiés sont (point de vue du code dans le FPGA) :

  • 0x00 : rapport cyclique ;
  • 0x04 : période ;
  • 0x08 : registre de configuration.

En profitant de la puissance du C, il est possible de créer la structure :

typedef struct fpga_pwm_regs {
    uint32_t pwm_duty;   // 0x00
    uint32_t pwm_period; // 0x04
    uint32_t pwm_cfg;    // 0x08
} fpga_pwm_regs_t;

Que l’on peut ensuite mapper sur la mémoire :

fpga_pwm_regs_t* pwm_regs = (fpga_pwm_regs_t*)(FPGA_PERIPH_BASE);

Avec FPGA_PERIPH_BASE valant 0x40020000 ([15] Table 3.13, ligne Fabric).

Grâce à cela, accéder au registre relatif à la période reviendra à faire :

pwm_regs->pwm_period = 10; // écriture
xxx = pwm_regs->pwm_period; // lecture

C’est bien entendu pratique de pouvoir configurer en dur les paramètres de la PWM, mais il pourrait être plus ludique de les changer directement au travers de la liaison UART (dans un premier temps).

Au lieu de réinventer un nouveau protocole pour gérer la PWM, nous allons profiter de la CLI (Command Line Interface) proposée dans le SDK (et utilisée dans le firmware par défaut de la QuickFeather).

Avant toute chose, il est nécessaire de s’assurer que le pilote UART configuré de manière implicite par qf_hardwareSetup() et par la CLI, pointe bien sur le périphérique matériel du MCU et non sur l’USB et/ou l’UART synthétisés dans le FPGA (avec le risque de plantage). Ceci se fait en éditant le fichier inc/Fw_global_config.h :

#define FEATURE_FPGA_UART 0 // FPGA UART not present
#define FEATURE_USBSERIAL 0 // USBSERIAL port no present
#define UART_ID_CONSOLE UART_ID_HW // console use the physical peripheral

L’en-tête de la CLI, ainsi que celle de FreeRTOS doivent être ajoutés (cette interface reposant sur cette couche, il sera nécessaire de lancer son ordonnanceur) :

#include "cli.h"
#include "FreeRTOS.h"

Sachant que la CLI repose sur l’UART et que la réception des caractères se fait sur interruption, celle-ci doit donc être activée par :

NVIC_SetPriority(Uart_IRQn, configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY);

Ce mécanisme repose sur la définition d’une ou plusieurs structures pour décrire les (sous)menus, il nous faut donc écrire et remplir ladite structure :

const struct cli_cmd_entry my_main_menu[] = {
    CLI_CMD_SIMPLE("period", cfg_period, "set PWM period" ),
    CLI_CMD_SIMPLE("duty", cfg_duty, "set PWM duty cycle" ),
    CLI_CMD_SIMPLE("invert", cfg_invert, "invert PWM states" ),
    CLI_CMD_SIMPLE("enable", cfg_enable, "enable/disable PWM" ),
    CLI_CMD_TERMINATE()
};

CLI_CMD_SIMPLE (ainsi que d’autres variantes) permet de définir une entrée :

  • le premier paramètre est le nom qu’aura le menu ou l’option ;
  • le second paramètre est le nom de la fonction appelée quand l’utilisateur tape le nom de l’option ;
  • le dernier est un message de description.

Il existe deux variantes :

  1. CLI_CMD_WITH_ARG qui prend en plus un argument qui sera passé à la fonction.
  2. CLI_CMD_SUBMENU qui permet de créer un sous-menu (tel que vu dans le firmware par défaut).

La macro CLI_CMD_TERMINATE sert à définir la fin de la liste d’options. Nous avons défini l’ensemble des fonctions à utiliser dans le cadre de cette démonstration, ne reste plus qu’à en écrire le contenu :

/* period configuration */
static void cfg_period(const struct cli_cmd_entry *pEntry) {
    uint32_t uxValue;
 
    (void)pEntry;
 
    CLI_uint32_getshow( "value", &uxValue );
    pwm_regs->period = uxValue;
}
/* invert bit configuration */
static void cfg_invert(const struct cli_cmd_entry *pEntry) {
    uint32_t uxValue, reg;
 
   (void)pEntry;
    /* get command param */
    CLI_uint32_getshow( "value", &uxValue );
 
    /* read prev status, unset invert bit */
    reg = pwm_regs->cfg & (~(1 << 1));
    reg |= ((uxValue & 0x01) << 1);
 
    /* set new cfg */
    pwm_regs->cfg = reg;
}

Le paramètre pEntry contient principalement un attribut cookie qui correspond au paramètre passé dans le cas de CLI_CMD_WITH_ARG et est, en fait, la structure remplie par les macros CLI_CMD_XX. Dans le cas présent, ce paramètre est juste ignoré (l’utilisation de (void)pEntry; permet d’éviter des avertissements de la part de GCC).

Pour obtenir la valeur (sur 32 bits non signés) fournie par l’utilisateur, la macro CLI_uint32_getshow doit être utilisée. Il ne reste plus qu’à l’écrire dans le registre correspondant. Le cas du invert et du enable est légèrement plus compliqué, car il est nécessaire de connaître le contenu du registre, de masquer le bit devant être modifié avant d’écrire la nouvelle valeur.

La dernière étape au niveau de la fonction main consiste à activer la CLI en fournissant la structure et à lancer l’ordonnanceur :

CLI_start_task(my_main_menu);
vTaskStartScheduler();

Avant de pouvoir compiler notre nouvelle application, le Makefile est une fois de plus à adapter, pour que la bibliothèque soit compilée :

.PHONY:CLI
CLI:
    make -f makefiles/Makefile_cli
clean_CLI:
    make -f makefiles/Makefile_cli clean
CLEAN_TARGET += CLI

Ici, rien de nouveau à détailler, puisque cette étape devient la coutume. Il faut également ajouter CLI comme dépendance à une des règles appelées automatiquement, par exemple external_srcs :

external_srcs: Startup HAL FreeRTOS Libraries BSP CLI

Il en va de même pour GCC_Project/config-GCC.mk pour ajouter le répertoire contenant les en-têtes de cette bibliothèque et définir une variable nécessaire à Makefile_cli :

export INCLUDE_DIRS=-I"$(PROJ_DIR)" \
    [...]
    -I"$(PROJ_ROOT)/Libraries/cli/inc" \
    [...]
export CLI_DIR = $(LIB_DIR)$(DIR_SEP)cli$(DIR_SEP)src

Un adaptateur USB-Série doit être connecté sur l’UART matériel du Cortex-M4 (voir [17]) :

header

broche

fonction

J3.2

IO_44

UART TX

J3.3

IO_45

UART RX

Une fois le programme cross-compilé et chargé, l’interface s’affiche sur le port série :

#*******************
Command Line Interface
App SW Version: pwmCPU_FPGACLI
#*******************
[0] > period 1000000
value = 1000000
[0] > duty 500000
value = 500000
[0] > invert 1
value = 1
[0] >

Comme C21 est configuré à 1 MHz, les durées sont exprimées en microsecondes, la LED bleue va donc en fonction de la valeur de la période et de l’état haut clignoter plus ou moins vite ou bien sur des périodes faibles sembler être plus ou moins lumineuse.

7. Récupérer d’une mauvaise écriture dans la flash

Il peut arriver que l’utilisateur soit amené à mettre à jour le bootloader. Cette opération n’est pas sans risque et si celle-ci devait échouer, ou qu’un problème dans le code apparaissait, la carte ne pourrait plus être programmée au travers de l’interface USB. Dans cette situation, la seule option est une récupération en mode SWD (en utilisant le connecteur J6).

quickfeather figure 08-s

Fig. 8 : Jumper J1 et J7 mis en place pour le mode SWD et détail du connecteur J6.

Le protocole SWD ainsi que l’EOS S3 sont supportés par openOCD depuis la version 0.11.0. Bien que la documentation officielle ne fasse référence qu’aux programmeurs J-Link, il est possible de faire usage de n’importe quelle sonde compatible avec ce protocole ou tout simplement d’utiliser un FT2232 (ou d’une quelconque sonde reposant sur ce composant), voire même un FT232RL.

Il existe une foule d’adaptateurs JTAG/SWD utilisant le composant FT2232. Voici quelques cartes disponibles sur le Web qui ont été testées avec succès sur la QuickFeather.

  • FT2232H-56Q Mini Module : la carte de développement officielle de FTDI, produite par Future ;
  • Tigard, https://www.crowdsupply.com/securinghw/tigard : une carte de « hacking » conçue spécifiquement pour s’adapter à tous les protocoles (tensions, connecteurs et logiciels) ;
  • Sipeed USB-JTAG/TTL RISC-V Debugger, https://www.seeedstudio.com : petit adaptateur qui malgré son nom peut fonctionner avec autre chose que du RISC-V. Attention cependant, certains adaptateurs semblent mal fabriqués, ils méritent un petit coup de fer à souder pour s’assurer de bons contacts.

7.1 Adaptateur USB-SWD

Dans le cas d’une sonde capable de travailler nativement en SWD (Bus Blaster V4 ou Tigard, par exemple) et si le fichier de configuration est disponible, rien de plus n’est à faire et l’utilisateur peut directement passer à la section suivante. Par contre, dans le cas d’une sonde type JTAG, un montage est nécessaire : en effet, le SWD repose sur deux signaux SWDCLK et SWDIO. Le premier est généré par le convertisseur et est une entrée de l’EOS S3, le second, par contre, est un signal de données bidirectionnel. Cet aspect n’est pas matériellement supporté par le FT2232. Pour pallier cette limitation, la solution (figure 9) est de connecter SWDIO en direct sur TDO (broche ADBUS2) et de connecter TDI (broche ADBUS1) sur ce même signal au travers d’une résistance entre 330 et 470 Ohm.

quickfeather figure 09

Fig. 9 : Schéma d'adaptation de signaux.

En fonction de l’adaptateur utilisé et de la présence/absence de support dans openOCD, il peut être nécessaire de créer la configuration. L’exemple suivant correspond à la Sipeed USB-JTAG/TTL RISC-V Debugger (mais convient pour bon nombre de sondes et de convertisseurs nus).

# select ftdi
adapter driver ftdi
# VID/PID
ftdi_vid_pid 0x0403 0x6010
# select interfaceA (0)
# note use 1 for interfaceB
ftdi_channel 0
# speed
adapter speed 2000
# first parameter default pin state (data)
# second parameter direction
# for each high bits yCBUSx
#          low bits yDBUSx
ftdi_layout_init 0x0008 0x001b
 
ftdi_layout_signal SWD_EN -data 0
ftdi_layout_signal nSRST -ndata 0x0010 -noe 0x0010
 
# enable SWD
transport select swd

Le point le plus important dans cette configuration est la dernière ligne qui informe openOCD que le protocole est SWD. Le reste est assez classique pour openOCD et les FT2232 puisque sont définis les identifiants USB (ftdi_vid_pid), l’interface utilisée (ftdi_channel) et la configuration des broches (ftdi_layout_init) (direction et valeurs par défaut) ainsi que la broche utilisée pour le signal de réinitialisation (ftdi_layout_signal).

Le connecteur J6 est au pas de 1,27 mm, le passage en 2,54 mm peut se faire avec une carte telle que [20] ainsi qu’une nappe telle que [21].

7.2 Chargement d’un firmware de récupération

Pour utiliser l’EOS S3 en mode SWD, il faut placer les jumpers J1 et J7 (attention, ils doivent être placés perpendiculairement à l’axe de la plate-forme) tel que présenté en figure 8.

Maintenant que le matériel est fonctionnel, il ne reste plus qu’à charger en mémoire le firmware dédié à la récupération.

Dans un terminal, se placer dans le répertoire $QORC_SDK/qf_apps/quickfeather-initial-binaries et tapez :

$ openocd -f /somewhere_fichier_config_interface.cfg \
    -f board/quicklogic_quickfeather.cfg \
    -c "init" -c "soft_reset_halt; reset halt" \
    -c "load_image output/bin/$(OUTPUT_FILE).bin 0 bin" \
    -c "reset run" -c "shutdown"

Pour :

  • arrêter le processeur ;
  • charger en mémoire le fichier qf_loadflash.bin ;
  • relancer son exécution à partir du nouveau firmware ;
  • arrêter openOCD.

À ce stade, l’EOS S3 dispose d’un firmware fonctionnel en mesure de remettre à jour les zones de la flash liées au bootloader (dmesg devrait annoncer l’apparition de l’interface ACM). Par contre, il ne survivra pas à un cycle d’alimentation, donc l’étape immédiate est de remettre en place les composantes du bootloader.

7.3 Réécriture de la flash

Toujours dans $QORC_SDK/qf_apps/quickfeather-initial-binaries, tapez :

python3 $(QORC_SDK/TinyFPGA-Programmer-Application/tinyfpga-programmer-gui.py \
    --port /dev/ttyACM0 \
    --mfgpkg . --mode fpga-m4

L’argument --mfgpkg a pour rôle d’aller chercher dans le répertoire pointé les fichiers qf_bootloader.bin, qf_bootfpga.bin et qf_helloworldsw.bin qui sont respectivement le firmware du Cortex-M4, le gateware et l’application par défaut.

Une fois que tinyfpga-programmer-gui.py a fini, il ne reste qu’à débrancher la carte, enlever le connecteur de J6 et les deux jumpers pour retrouver une plate-forme fonctionnelle.

7.4 Option bonus

Pour récupérer la plate-forme d’une erreur, nous avons chargé en mémoire un firmware. Cette possibilité ne se limite pas à la récupération, mais également au chargement d’un projet (attention, celui-ci est mis en mémoire volatile). L’avantage de cette approche est :

  • d’accélérer le cycle de développement ;
  • d’éviter d’avoir à faire usage des deux microboutons ;
  • de déverminer le firmware en cours de développement.

De base, la ligne présentée plus haut, après changement du nom du .bin, suffira. Par contre, pour du déverminage, l’utilisation conjointe de openOCD et gdb est nécessaire.

Comme pour la précédente étape, l’utilisateur doit dans un premier terminal lancer openOCD en mode serveur par :

$ openocd -f /somewhere_fichier_config_interface.cfg \
    -f board/quicklogic_quickfeather.cfg -c "init"

Et dans un second terminal gdb-multiarch (ou arm-none-eabi-gdb) :

$ gdb-multiarch
(gdb) target extended-remote localhost:3333
Remote debugging using localhost:3333
[...]
(gdb) monitor soft_reset_halt
(gdb) load output/bin/mon_firmware.elf
Loading section .text, size 0x576f lma 0x0
Loading section .ARM.exidx, size 0x8 lma 0x5770
Loading section .data, size 0x96c lma 0x5778
Start address 0x3148, load size 24803
Transfer rate: 263 KB/sec, 6200 bytes/write.
(gdb) continue
Continuing.
  • la première commande permet de se connecter à openOCD ;
  • la seconde d’arrêter le MCU ;
  • la dernière de charger le binaire en mémoire.

Le déverminage se fait de manière classique (et dépasse le cadre de cet article).

Nous avons mentionné la possibilité de charger le bitstream sans l’intégrer dans une application, pour ce faire, il est nécessaire de produire un fichier contenant la suite d’opérations dédiées à openOCD. Ce fichier résultat est produit par ql_symbiflow en remplaçant le type de sortie :

ql_symbiflow -compile -src $(MAIN_FPGA_RTL_DIR) \
    -d ql-eos-s3 -t $(RTL_TOP_MODULE) \
    -v $(FPGA_RTL_SRCS) -p quickfeather.pcf -P PU64 -dump openocd

Avec :

  • MAIN_FPGA_RTL_DIR : le répertoire contenant les sources du gateware ;
  • RTL_TOP_MODULE : le nom du module principal ;
  • FPGA_RTL_SRCS : la liste des fichiers Verilog du projet.

Mais le plus important est l’option -dump, qui jusqu’à présent était fixée à header et dans le cas présent à openocd.

Ne reste plus qu’à lancer openOCD comme précédemment, mais en ajoutant -f fpga/rtl/mon_projet.openocd puis dans telnet de taper la commande :

> load_bitstream

Conclusion

C’est la première fois que nous nous trouvons en possession d’un (e)FPGA avec des outils open source fournis par le constructeur.

Habituellement, les outils constructeurs sont fournis sous forme de belles interfaces pleines de couleurs à cliquer. Une fois passée l’épreuve des licences, les exemples sont faciles à mettre en œuvre rapidement. Les choses se corsent dès que l’on veut commencer à faire un vrai projet qui sort du chemin tout tracé. Et les gros projets gérés en clic-clic se transforment petit à petit en cauchemars gaziers : difficiles à versionner, car basés sur des interfaces graphiques, difficiles à déverminer au fil des mises à jour du logiciel et très peu portables.

Avec QORC SDK, c’est une tout autre musique. Ici tout est géré en texte, ce qui est très bien pour suivre un projet et le versionner. Cela peu paraître un peu « rustre » en premier contact, mais surtout, aucun des logiciels fournis n’est développé en natif par la société QuickLogic : le SDK est un agglomérat de différents logiciels libres permettant de développer chaque sous-partie. Le côté positif est de voir une société privée contribuer à des projets open source déjà « installés » dans la FPGA-sphère. Cela conforte des projets comme Yosys ou VPR en logiciels de référence. Le côté négatif est une impression de « pas fini ». Le SDK donne l’impression que QuickLogic se repose sur la communauté open source pour l’aider à faire la maintenance de ses outils. Ce côté négatif est tout de même contrebalancé par un forum officiel [22] sur lequel tout le monde peu s’inscrire et les employés de QuickLogic répondent.

Comme on l’a vu, cet « agglomérat logiciel » est fourni sous la forme d’un script qui va télécharger et configurer les différents outils nécessaires au développement. Pour éviter les problèmes de configuration entre les versions des logiciels, le script va télécharger et installer beaucoup de logiciels que l’utilisateur possède souvent déjà sur sa machine. Les 3,8 Go d’installation ne sont donc pas nécessaires au bon fonctionnement de l’ensemble, mais il faut « mettre les mains dans le cambouis » si l’on veut lui faire subir une cure d’amaigrissement.

Le SDK ainsi que la structure des projets sont donc assez laborieux à appréhender. Certes, distribuer les tâches dans de petits Makefiles est une bonne idée… À la condition que ce ne soit pas pour les dupliquer ou en abuser. Devoir retrouver dans trois niveaux du .mk la bonne variable qui permet de définir le nom du gateware n’est pas une bonne idée.

Les documentations sont également d’un niveau variable, certaines parties étant très claires et bien documentées, d’autres laissées à l’interprétation de l’utilisateur, d’autres parties encore sont clairement totalement absentes, comme pour l’interface CPU-FPGA (dans la plupart des projets, on trouve des commentaires ?? qui laissent songeurs).

Mais nous n’allons pas finir sur une note négative. Une fois les outils pris en main, il est réellement agréable de jouer avec ce composant, non seulement le fait que les outils soient open source est un plus, mais ils fonctionnent bien, utiliser conjointement les deux environnements est très facile et beaucoup plus naturel que dans nombre de cas où un block design doit être réalisé, avec un manque de souplesse évident.

QuickLogic ne compte pas s’arrêter en si bon chemin dans la libération des FPGA puisqu’elle lance la fondation OSFPGA (Open Source FPGA) avec quelques membres d’universités réputées comme l’EPFL, l’université d’Utah ou la société de Zero ASIC. Cette fondation a pour objectif de contribuer aux outils open source dans le monde du FPGA. Elle a également pour objectif de proposer des productions de FPGA à moindres frais pour ses membres, et même gratuite si le cœur du FPGA est open source.

Références

[1] La page QuickFeather sur le site officiel, https://www.quicklogic.com/products/eos-s3/quickfeather-development-kit/

[2] La page de la QuickFeather sur Crowd Supply, https://www.crowdsupply.com/quicklogic/quickfeather

[3] SparkFun Thing Plus – QuickLogic EOS S3, https://www.crowdsupply.com/sparkfun/thing-plus-quicklogic-eos-s3

[4] Qomu sur Crowd Supply, https://www.crowdsupply.com/quicklogic/qomu

[5] QORC SDK, https://github.com/QuickLogic-Corp/qorc-sdk

[6] Dépôt contenant les exemples de cet article, https://github.com/trabucayre/quickfeather-notes/

[7] F. Marteau, Une carte pilote de LED RGB hackée en kit de développement FPGA à bas coût, Hackable n°35, oct./nov./déc. 2020, https://connect.ed-diamond.com/Hackable/hk-035/une-carte-pilote-de-led-rgb-hackee-en-kit-de-developpement-fpga-a-bas-cout

[8] A language for hardcoding Algorithms into FPGA hardware, https://github.com/sylefeb/Silice

[9] Learning FPGA, Yosys, nextpnr, and RISC-V, https://github.com/BrunoLevy/learn-fpga

[10] SymbiFlow, https://symbiflow.github.io

[11] Yosys, http://www.clifford.at/yosys/

[12] VPR est un sous projet de VTR (Verilog To Routing), http://verilogtorouting.org

[13] tinyFPGA, https://tinyfpga.com

[14] EOSS3 Datasheet, https://www.quicklogic.com/wp-content/uploads/2020/12/QL-EOS-S3-Ultra-Low-Power-multicore-MCU-Datasheet-2020.pdf

[15] EOSS3 Reference-Manual, https://www.quicklogic.com/wp-content/uploads/2020/06/QL-S3-Technical-Reference-Manual.pdf

[16] EOSS3 clocks, https://qorc-sdk.readthedocs.io/en/latest/guides/clock-power/clock-power-basics.html

[17] QuickFeather User Guide, https://github.com/QuickLogic-Corp/quick-feather-dev-board/blob/master/doc/QuickFeather_UserGuide.pdf

[18] GHDL Yosys Plugin, https://github.com/ghdl/ghdl-yosys-plugin

[19] Wishbone 4 specification, https://cdn.opencores.org/downloads/wbspec_b4.pdf

[20] JTAG (2x10 2,54 mm) to SWD (2x5 1,27 mm) Cable Adapter Board, https://www.adafruit.com/product/2094

[21] 10-pin 2x5 Socket-Socket 1.27mm IDC (SWD) Cable - 150 mm de long, https://www.adafruit.com/product/1675

[22] Le forum officiel de QuickLogic, http://forum.quicklogic.com

Pour aller plus loin

Cet article n’a fait qu’effleurer les possibilités de la plate-forme, sachez que le microcontrôleur EOS S3 est supporté nativement par les OS temps réel NuttX, Zephyr et même FreeRTOS, ce qui facilite le portage des applications vers cette carte.

Pour la communication entre le CPU et le FPGA, nous n’avons abordé que la partie bus parallèle avec le Wishbone. Sachez qu’il est possible d’augmenter nettement le débit en lecture écriture si l’on passe par les interfaces SDMA et packet fifo.

Tout le code FPGA présenté dans cet article était en Verilog, il est parfaitement possible d’utiliser du VHDL grâce à l’extension yosys-ghdl qui fait la conversion VHDL->Verilog. Le Verilog est vu comme un langage « assembleur pour FPGA » pour de nombreux langages de descriptions HDL comme nMigen, SpinalHDL, Chisel, Litex, MyHDL, Silice… Il est donc tout à fait possible d’utiliser tous ces langages pour développer sur la QuickFeather.



Article rédigé par

Par le(s) même(s) auteur(s)

Verilator, le simulateur Verilog le plus rapide du monde

Magazine
Marque
Hackable
Numéro
43
Mois de parution
juillet 2022
Spécialité(s)
Résumé

Concevoir des composants numériques en Verilog passe nécessairement par la simulation. Pour simuler du Verilog, il existe un logiciel open source nommé Icarus qui remplit bien sa fonction. Il existe également des simulateurs non libres qui sont généralement plus performants. Mais tous ces simulateurs ont le même défaut, ils sont lents. Verilator est un simulateur un peu particulier qui se concentre sur la partie synthétisable du Verilog et génère un objet C++ que l’on va simuler au moyen d’un programme écrit dans ce même langage. Cette approche permet un gain de l’ordre d’une trentaine de fois plus rapide qu’Icarus dans l’exemple que nous allons voir. Il est également nettement plus rapide que tous les simulateurs commerciaux.

De la preuve formelle en VHDL, librement

Magazine
Marque
Hackable
Numéro
42
Mois de parution
mai 2022
Spécialité(s)
Résumé

Dans cet article, on se propose d’aborder la méthode de vérification formelle pour le VHDL. Cette méthode a récemment été rendue possible avec GHDL et Yosys grâce au projet d’extension ghdl-yosys-plugin qui fait le lien entre les deux logiciels. Nous allons également découvrir le langage PSL (Properties Specification Langage) qui permet de décrire efficacement les propriétés utilisées en preuve formelle. Le support du PSL ayant été ajouté dans GHDL, il sera possible de l’utiliser librement en VHDL.

Chisel, construire du matériel en langage Scala

Magazine
Marque
Hackable
Numéro
40
Mois de parution
janvier 2022
Spécialité(s)
Résumé

Chisel est un langage de description de matériel (HDL pour Hardware Description Language) née à l’université de Berkeley en même temps que le jeu d’instructions RISC-V. Chisel est basé sur le langage de programmation multiparadigme Scala. Ce langage permet de décrire un système synchrone et de générer du code Verilog pour la synthèse sur FPGA ou ASIC.

Les derniers articles Premiums

Les derniers articles Premium

Stubby : protection de votre vie privée via le chiffrement des requêtes DNS

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Depuis les révélations d’Edward Snowden sur l’espionnage de masse des communications sur Internet par la NSA, un effort massif a été fait pour protéger la vie en ligne des internautes. Cet effort s’est principalement concentré sur les outils de communication avec la généralisation de l’usage du chiffrement sur le web (désormais, plus de 90 % des échanges se font en HTTPS) et l’adoption en masse des messageries utilisant des protocoles de chiffrement de bout en bout. Cependant, toutes ces communications, bien que chiffrées, utilisent un protocole qui, lui, n’est pas chiffré par défaut, loin de là : le DNS. Voyons ensemble quels sont les risques que cela induit pour les internautes et comment nous pouvons améliorer la situation.

Surveillez la consommation énergétique de votre code

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Être en mesure de surveiller la consommation énergétique de nos applications est une idée attrayante, qui n'est que trop souvent mise à la marge aujourd'hui. C'est d'ailleurs paradoxal, quand on pense que de plus en plus de voitures permettent de connaître la consommation instantanée et la consommation moyenne du véhicule, mais que nos chers ordinateurs, fleurons de la technologie, ne le permettent pas pour nos applications... Mais c'est aussi une tendance qui s'affirme petit à petit et à laquelle à terme, il devrait être difficile d'échapper. Car même si ce n'est qu'un effet de bord, elle nous amène à créer des programmes plus efficaces, qui sont également moins chers à exécuter.

Donnez une autre dimension à vos logs avec Vector

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Avoir des informations précises et détaillées sur ce qu’il se passe dans une infrastructure, et sur les applications qu'elle héberge est un enjeu critique pour votre business. Cependant, ça demande du temps, temps qu'on préfère parfois se réserver pour d'autres tâches jugées plus prioritaires. Mais qu'un système plante, qu'une application perde les pédales ou qu'une faille de sécurité soit découverte et c'est la panique à bord ! Alors je vous le demande, qui voudrait rester aveugle quand l'observabilité a tout à vous offrir ?

Les listes de lecture

7 article(s) - ajoutée le 01/07/2020
La SDR permet désormais de toucher du doigt un domaine qui était jusqu'alors inaccessible : la réception et l'interprétation de signaux venus de l'espace. Découvrez ici différentes techniques utilisables, de la plus simple à la plus avancée...
8 article(s) - ajoutée le 01/07/2020
Au-delà de l'aspect nostalgique, le rétrocomputing est l'opportunité unique de renouer avec les concepts de base dans leur plus simple expression. Vous trouverez ici quelques-unes des technologies qui ont fait de l'informatique ce qu'elle est aujourd'hui.
9 article(s) - ajoutée le 01/07/2020
S'initier à la SDR est une activité financièrement très accessible, mais devant l'offre matérielle il est parfois difficile de faire ses premiers pas. Découvrez ici les options à votre disposition et les bases pour aborder cette thématique sereinement.
Voir les 28 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous