Environnement logiciel sur FPGA - Partie 1 : Environnement de cross-compilation et amorçage du système

Publié dans: 

Les puces FPGA (field-programmable gate array) sont des puces logiques programmables qui sont couramment utilisées lors du prototypage des chipsets. Beaucoup de fabricants spécialisés tels que Xillinx fournissent des kits de design sur FPGA comportant la puce en question, les mémoires et les ports d'entrée/sortie, le tout sur un seul et unique circuit imprimé.

 

Suite à une expérimentation de conception d’un System-on-Chip (SoC) sur un kit FPGA du type Xilinx Spartan 3AN, je me suis intéressé à mettre en place un environnement logiciel rudimentaire pour exploiter les fonctionnalités de ce système.

 

Le système qui a été programmé sur la puce est basé sur un processeur softcore 32 bits à architecture MIPS implémenté dans un IP Core appelé Plasma. Ce système peut assurer des fonctionnalités relativement évoluées telles que l’affichage VGA, la connexion à un réseau Ethernet, la connexion via un port série et la synthèse sonore.

 

Dans cette série d’articles, je décrirai les différentes étapes par lesquelles je suis passé pour arriver ce résultat.

 

Cross-compilation

 

Dans la plupart des cas, il n’est pas possible d’écrire et de construire des applications directement sur le système embarqué. En effet, l’espace mémoire réduit (quelques dizaines de Ko) ne permet pas d’implémenter ni même d’exécuter convenablement le compilateur et les outils de développement. Les cross-compilateurs permettent ainsi de développer sur une plateforme hôte des applications exécutables sur une plateforme cible qui dans notre cas sera le SoC.

 

Notre choix s’est porté sur SDE lite qui est un environnement de cross-développement gratuit distribué par MIPS Technologies qui cible les systèmes à processeurs MIPS. Il inclut le cross-compilateur SDE-GCC qui est une version optimisée du compilateur GCC ainsi qu’un ensemble d’outils de test et de débogage. [MIP07]

 

Ce cross-compilateur génère un fichier exécutable au format ELF (Extensible Linking Format). C’est un format standard de fichiers exécutables pour les systèmes à base d’UNIX. Ce format a l’avantage d’être très flexible par rapport aux autres formats puisqu’il est compatible avec tous les types de processeurs et d’architectures. [For05]

 

Allocation des sections du code exécutable

 

Le code exécutable au format ELF est constitué essentiellement de 5 portions d’espace d’adressage qui sont accessibles par le processus en cours d’exécution.

 

Section Text : contient les instructions à exécuter. Sur de nombreux systèmes d’exploitation, elle est en mode lecture seule de sorte que le processus ne puisse pas modifier ces instructions. Cela permet à plusieurs instances du programme de partager une seule copie de la section.

 

Section Data : contient la partie « données » du programme. Elle est divisée en général en deux sous-sections :

– Données en lecture seule initialisées : Contient les données qui sont initialisées par le programme en lecture seule au cours de l’exécution du processus.
– Données en lecture/écriture initialisées : Contient les données qui sont initialisées par le programme et qui sont modifiées au cours de l’exécution.

 

Section BSS (Block Started Symbol) : contient les éléments qui ne sont pas initialisés par le programme et qui sont mis à zéro avant l’exécution du processus.

 

Section Stack : utilisée pour les variables locales et les piles.

 

Heap : contient la mémoire allouée dynamiquement.

 

Exemple :

int abc = 1 ; /*=>Data*/
char *str ; /*=>BSS*/
int main()
{
int ii, a = 1, b = 2, c ; /*=>Stack*/
char *ptr ;
ptr = malloc(4) ; /*=>Heap*/
c = a + b ; /*=>Text*/
return 0 ;
}

 

Dans tous les systèmes d’exploitation, les différentes sections précédemment citées sont systématiquement mappées dans la mémoire RAM. Dans notre cas, un programme en langage assembleur MIPS (start.S) devait être écrit pour accomplir cette tâche.

 

Extrait du fichier start.S :

/* copier la section .data de la ROM vers la RAM */
lw t0, 0(t7) /* t0 <- sdata */
lw t1, 4(t7) /* t1 <- edata */
lw t2, 8(t7) /* t2 <- data rom start */
loop data :
beq t0, t1, end loop data
lw t3, 0(t2)
sw t3, 0(t0)
addi t0, t0, 4
addi t2, t2, 4
j loop data
end loop data :
/* mise à zero de la section .bss dans la RAM */
lw t0, 12(t7) /* t0 <- sbss */
lw t1, 16(t7) /* t1 <- ebss */
loop bss :
beq t0, t1, end loop bss
sw zero, 0(t0)
addi t0, t0, 4
j loop bss
end loop bss :
/* saut vers main() en C */
jal main
loop end :
j loop end
nop
table :
/* les valeurs des variables suivantes seront */
/* exportées par le linker */
.word sdata
.word edata
.word data rom start
.word sbss
.word ebss
.word ramend
.word gp

 

LD est le linker GNU dont la syntaxe des scripts est très proche de celle du langage C. Le fichier barebone.lds (LD Script) qui sera exploité par LD contient les positions de ces différentes sections dans la ROM du système. [Usi09]

 

Extrait du fichier barebone.lds :

OUTPUT_FORMAT("elf32-tradbigmips")
OUTPUT_ARCH(mips)
ENTRY(_start)
MEMORY {
      
        eram : ORIGIN = 0x00023000, LENGTH = 1
        ram : ORIGIN = 0x00020000, LENGTH = 0x00003000
        rom : ORIGIN = 0x00000000, LENGTH = 0x00004000
}
SECTIONS {
        .text : {
                _stext = . ;
                *(.text)
                *(.rodata)
    *(.rodata1)
    *(.rodata.str1.4)
                _etext = . ;
                __data_rom_start = ALIGN(4);
        } > rom
        _gp = ALIGN(16) + 0x7ff0;
        .data : {
                _sdata = . ;
                *(.data)
                _edata = . ;
        } > ram          
        .bss : {
                _sbss = . ;
                *(.bss)
                *(.sbss)
                *(.dynbss)
                *(.scommon)
                *(COMMON)
                _ebss = . ;
        } > ram
        .eram : {
                _ramend = .;
        } > eram
      
        .reginfo : {
          *(.reginfo)
        }
} 

 

Génération de la ROM

 

Le programme de démarrage en langage assembleur MIPS est chargé dans la ROM du système pour permettre d’exécuter des programmes C sur le SoC. Un script servira quant à lui à construire l’image d’une ROM compatible à partir du fichier exécutable ELF issu de la compilation.

 

Un fichier Makefile servira à créer les dépendances nécessaires entre les différents fichiers de code tels que main.c (programme exécutable) et start.S (programme d’amorçage), et les macros (appel du script en Python pour la génération de la ROM, concaténation de fichiers...) afin de construire les fichiers cibles (cf. fig. 1).

 

Pour générer l’image de la ROM, le fichier barebone.bin est créé à partir des sections .data et .text du fichier exécutable ELF et transformé par le script en un fichier au format COE qui sera implémenté dans la mémoire ROM du système. [COE11]

 

Extrait du fichier Makefile :

elf : barebone.elf (1)
barebone.elf : start.o main test.o mips soc.o (2)
barebone.bin : barebone.elf
objcopy -O binary --only-section=.text barebone.elf part.text (3)
objcopy -O binary --only-section=.data barebone.elf part.data
cat part.text part.data > barebone.bin (4)
python bin2coe.py barebone.bin > ../rom.coe (5)

 

 

FIGURE 1Fichiers intervenants dans la construction de la ROM
– (1) : dépendances de la règle elf (appelée par la commande make elf)
– (2) : dépendances de la règle barebone.elf
– (3) : extraction de la section .data en binaire
– (4) : concaténation de .data et .text en binaire
– (5) : génération de l’image de la ROM au format .coe

 

Au final, nous disposons de tous les outils nécessaires pour commencer à manipuler le SoC implémenté sur FPGA. Dans la prochaine partie, nous verrons ensemble comment assurer la gestion des interruptions qui seront, en réalité, le point de liaison primordial pour faire communiquer de façon non-bloquante les périphériques externes avec le CPU.


Documentation :
[For05] FORMAT OF ELF EXECUTABLE BINARY FILES. FreeBSD File Formats Manual . The FreeBSD Project, Décembre 2005.
[MIP07] MIPS Technologies, Inc. MIPS SDE Programmer’s Guide , Avril 2007.
[Usi09] Using LD, the GNU linker
[COE11] COE File Syntax