Systèmes Embarqués PROCESSEURS SPECIALISES 2ème année ESIX MeSN Responsable de Cours : [email protected] - 02 31 45 27 61 Encadrants de Travaux Pratiques : [email protected] - 02 31 45 27 56 2015-2016 Processeurs Spécialisés POLYCOPIE • TRAVAUX PRATIQUES • ANNEXES Les documents présents dans ce polycopié sont librement téléchargeables sur la plateforme d'enseignement de l'ENSICAEN via un accès anonyme. Voici ci-dessous l'URL de la plateforme, se rendre dans la section : Formation Classique >> Spécialité Informatique ou Spécialité Électronique et Physique Appliquée >> 2ème année >> Processeurs Spécialisés >> download http://foad.ensicaen.fr/ Ce document est protégé par une licence Creative Common, néanmoins les différents supports restent librement réutilisables à des fins pédagogiques. Merci cependant de me prévenir par mail ([email protected]) et de citer le nom de l'ENSICAEN dans vos documents de cours. Processeurs Spécialisés COURS Processeurs Spécialisés SOMMAIRE 1. HETEROGENEITE DES ARCHITECTURES PROCESSEURS 1.1. MCU - Micro Controller Unit 1.2. AP - Application Processor 1.3. GPP - General Purpose Processor 1.4. GPU - Graphical Processor Unit 1.5. DSP - Digital Signal Processor 1.6. FPGA - Field Programmable Gate Arrays 1.7. SoC - System on Chip 1.8. ASIC - Application Specific Integrated Circuit 2. OBJECTIFS 2.1. Architectures parallèles 2.2. Stratégies d'optimisation 2.3. Méthodologie 3. ARCHITECTURE DSP C6600 3.1. Architecture VLIW 3.1.a. Principe 3.1.b. CPU DSP C6000 3.2. Algorithme de référence 3.2.a. Filtre FIR 3.2.b. Virgule fixe 3.2.c. Virgule flottante 3.3.Instructions arithmétiques 3.3.a. Multiplication 3.3.b. Addition 3.4. Instructions de management mémoire 3.4.a. Modes d'adressage 3.4.b. CPU à adressage complexe 3.4.c. CPU à modèle Load/Store 3.4.d. Instructions Load/Store 3.4.e. Concaténation de registres Processeurs Spécialisés 34.f. Indexage 3.5. Instructions de saut 3.5.a. Instructions de contrôle 3.5.b. Saut conditionnel 3.5.c. Appel de procédure 3.5.d. Pile système 3.6. Algorithme en assembleur canonique 3.6.a. Assembleur Canonique 3.6.b. Delay slot 3.7. Limitations de l'architecture C6600 3.7.a. CPU VLIW C6600 3.7.b. Jeux d'instructions par unités 3.7.c. Chemins croisés de données 3.7.d. Formats entiers 3.7.e. Divers 3.8. Optimisation 3.8.a. Parallélisme de données 3.8.b. Vectorisation 3.8.c. Parallélisme d'instructions 3.8.d. Dépendances et branches d'exécution 3.8.e. Fonctions intrinsèques 3.8.f. Directives de compilation 3.9. Pipeline logiciel 3.9.a. Concept 3.9.b. Pipeline logiciel sur architecture C6600 4. PILELINE PROCESSEUR 4.1. Architecture in-order 4.2. Architecture superscalaire 4.2.a. Étage d'exécution 4.2.b. Exécution Out-Of-Order 4.3. Architecture VLIW 4.3.a. Étage d'exécution 4.3.b. Code Out-Of-Order 4.4. Architecture EPIC 5. CACHE PROCESSEUR … à finir ! Processeurs Spécialisés Architecture et technologie des ordinateurs TOOLCHAIN Bas niveau – C ToolChain – ELF file Un système travaillant sur une architecture à CPU peut très souvent être découpé en couche. Il s’agit d’une solution mixte logicielle (OS et couches applicatives) et matérielle. Un développeur logiciel dit ‘’bas niveau’’ travaille dans les couches basses de ce modèle : APPLICATION BINARY INTERFACE Applications Architecture et Technologie des Ordinateurs Operating System Hardware 2 – copyleft Hugo Descoubes - Novembre 2013 TOOLCHAIN Bas niveau – C ToolChain – ELF file TOOLCHAIN Bas niveau – C ToolChain – ELF file Observons en quelques chiffres, la répartition des marchés des systèmes d’exploitation sur quelques grands domaines d’application : • Windows de Microsoft : ~91% du marché des ordinateurs personnels en 2014 (56,3% pour W7 et 13,5% pour W8/8.1), ~2,5% du marché des Smartphones en 2014, 55% des serveurs en 2014 Vous aurez un enseignement dédié aux systèmes d’exploitation en 2A. • UNIX et UNIX-like (GNU/Linux, iOS, MAC OS X, Android …): 90% du marché des Smartphones en 2014 (Android ~47%), 67% des serveurs en 2014, GNU/Linux ~97% des superordinateurs en 2014 sources : StatCounter, NetApplications 3 – copyleft Chaîne de Compilation -1- 4 – copyleft Architecture et technologie des ordinateurs TOOLCHAIN Bas niveau – C ToolChain – ELF file TOOLCHAIN Bas niveau – C ToolChain – ELF file Malheureusement, développement bas niveau ne veut pas dire développement simple. Un ingénieur travaillant dans ce domaine doit notamment être compétent sur les points suivants : Effectuons quelques rappels sur une chaîne de compilation C (C toolChain ou C toolSuite). Les slides qui suivent sont à savoir par cœur. • Architectures matérielles (CPU, hiérarchie et gestion mémoire, gestion périphériques, mécanismes d’optimisations …) • Langages de programmation (essentiellement C/C++ et assembleur) • Outils de Développement Logiciel (IDE, chaîne de compilation C, outils de debuggage et de profilage, programmation concurrente, programmation parallèle …) Les exemples suivants sont donnés sous la chaîne de compilation GCC (GNU Compilation Collection, http://gcc.gnu.org/). L’architecture est la même que toute autre toolChain C, cependant les formats et extensions des fichiers intermédiaires ne sont pas standardisées et peuvent changer d’une chaîne à une autre ou d’une plateforme matérielle à une autre. 5 – copyleft 6 – copyleft TOOLCHAIN Bas niveau – C ToolChain – ELF file TOOLCHAIN Bas niveau – C ToolChain – ELF file • Processus de compilation : Réalisation respective des traitement suivants : Analyse lexicale, pré-traitement, analyse syntaxique, analyse sémantique, génération de code, ‘’optimisation’’, édition des liens main.c hello.c Preprocessor Preprocessor hello.h gcc • Analyse Lexicale : Elimination commentaires, espaces, détection des hello.i mots clés, opérateurs (!=, <= …), Parser chaînes de caractères, constantes numériques Optimiser Compiler main.i Parser Cet enseignement s’appuie sur les compétences enseignées dans les enseignements ‘’Outils de Développement Logiciel’’ et ‘’Programmation et langage C’’. Optimiser hello.s main.s staticLib.a Assembler • Assembler hello.o main.o Linker project.out (ou autre extension) 7 – copyleft Chaîne de Compilation -2- Préprocesseur : Etapes d’inclusion de code, de substitution de chaînes de caractères et de compilation conditionnelle. Directives de précompilation ‘’#’’ et opérateurs ‘’#pragma’’ depuis la norme C99 8 – copyleft Architecture et technologie des ordinateurs Bas niveau – C ToolChain – ELF file TOOLCHAIN TOOLCHAIN Bas niveau – C ToolChain – ELF file • Parser : Analyse séquentielle du code o Analyse syntaxique : analyse la structure du code, vérifie le respect des formalismes et syntaxes inhérents au langage compilé hello.c main.c hello.h o Analyse sémantique : résolution des noms, génération de la table des symboles, compatibilité des types … Preprocessor gcc Preprocessor Preprocessor hello.i main.i main.i Compiler • Génération du code : Etape dépendant Parser de l’architecture CPU cible (Instruction Optimiser Set Architecture). Nous parlons de hello.s cross-compilation (vs compilation Assembler native) lorsque l’architecture cible est différente de celle effectuant la hello.o compilation. Parser Optimiser main.s Assembler staticLib.a gcc main.o Parser Optimiser main.s Assembler staticLib.a main.o Linker Linker project.out (ou autre extension) • Optimisation (optionnelle) : étage très délicat à développer et dépendant de l’architecture CPU cible. 9 – copyleft project.out (ou autre extension) 10 – copyleft Bas niveau – C ToolChain – ELF file • make : utilitaire de programmation pour l’automatisation de procédures de compilation TOOLCHAIN TOOLCHAIN Bas niveau – C ToolChain – ELF file libFunct2.c libFunct1.c libHeader.h gcc • Archiver : construction de bibliothèques statiques. Archive réalisée à partir de fichiers objets main.c hello.c hello.h • Editeur de liens : Liens entre fichiers objets, bibliothèques statiques, Preprocessor bibliothèques dynamiques chargées à hello.i l’exécution. Résolution de la table des Parser symboles et résolution des liens avec le modèle mémoire de l’architecture cible. Optimiser hello.s Génération d’un code binaire exécutable absolu ou relogeable Assembler (dépend de l’architecture cible et de la hello.o stratégie de chargement du code exécutable) Compiler hello.h hello.c main.c • Assembleur : Génération code objet relogeable pour architecture cible (code binaire). Référencement par symboles, aucune dépendance avec le modèle mémoire de l’architecture cible. Preprocessor Preprocessor libFunct2.i libFunct1.i staticLib.a Parser Parser Optimiser Optimiser libFunct1.s • Archiver : construction de bibliothèques statiques. Archive réalisée à partir de fichiers objets libFunct2.s Assembler Assembler gcc • make : utilitaire de programmation pour l’automatisation de procédures de compilation make libFunct1.o project.out libFunct2.o Linker Archiver staticLib.a 11 – copyleft Chaîne de Compilation -3- 12 – copyleft Architecture et technologie des ordinateurs IDE Bas niveau – C ToolChain – ELF file • make : utilitaire de programmation pour l’automatisation de procédures de compilation • Archiver : construction de bibliothèques statiques. Archive réalisée à partir de fichiers objets main.c hello.c hello.h Text editor make project.out Observons les 3 trois principaux environnements de compilation utilisés sur architecture x86 : • Visual Studio proposé par Windows • Intel C++ Compiler XE proposé par Intel. Propose des outils très puissants d’optimisation et de profilage pour architecture Intel (Advisor XE, Vtune Amplifier, Inspector XE …) staticLib.a gcc TOOLCHAIN TOOLCHAIN Bas niveau – C ToolChain – ELF file • Integrated Developement Environment : Aide au développement logiciel. Intègre généralement un éditeur de texte, automatisation procédure de compilation, debugger, utilitaires divers… 13 – copyleft • GCC (GNU Compiler Collection) avec/sans IDE (Eclipse, Netbeans …) issu du monde de l’Open Source ayant vocation a être multiplateforme (cross-compilation ARM, MIPS, PPC …). Les deux principaux environnement de compilation rencontrés sous Windows sont Cygwin et MinGW. 14 – copyleft • Les 2 premières étapes de la compilation sont architecture agnostique. hello.c main.c Bas niveau – C ToolChain – ELF file TOOLCHAIN TOOLCHAIN Bas niveau – C ToolChain – ELF file • Fichier ELF (Executable and Linkable Format) : hello.h gcc Preprocessor Preprocessor hello.i main.i Parser Parser code generator code generator Optimiser Optimiser hello.s main.s staticLib.a Assembler Assembler Vous aurez un enseignement dédié à la compilation et l’étude du parser en 2A (graphes et automates) Le format de fichier ELF sert à l’enregistrement de programmes compilés (fichiers objets, exécutables, bibliothèques statiques et dynamiques, modules kernel). Prenons les principales extensions de fichiers compilés rencontrées sous GNU/Linux (tous des fichiers ELF) .o (object), .a (archive de .o), .so (shared object), .ko (kernel object). Ce format plus flexible unifie et remplace les anciens format a.out et COFF. • code generator, optimiser et assembler : dépendance avec l’architecture CPU cible mais pas du modèle mémoire (références symboliques) hello.o main.o Linker • Linker : dépend du modèle mémoire de l’architecture cible project.out 15 – copyleft Le format ELF est extrêmement répandu sur systèmes UNIXlike (GNU/Linux, FreeBSD, Solaris, OpenBSD, Android …) ainsi que sur grand nombre d’autres plateformes (PS-2, PS-3, PSP, Wii, SymbianOS v9 …). 16 – copyleft Chaîne de Compilation -4- Architecture et technologie des ordinateurs TOOLCHAIN Bas niveau – C ToolChain – ELF file TOOLCHAIN Bas niveau – C ToolChain – ELF file Avant de présenter le format de fichier ELF, présentons le cycle de vie d’un programme (en vert, fichiers ELF) : Source (Assembleur) Compilation Objet relogeable (.o) (références symboliques) Edition (statique) des liens … Bibliothèques statiques (.a) (archives de fichiers objets) Objects files .o Assemblage Objet relogeable (.o) (références symboliques) Processus de compilation … Objet relogeable (.o) (références symboliques) … Adresse de chargement Objet absolu ELF header (Relocatable format) Program header table (optionnal/ignored) linking (executable format) Program header table Section n°1 Section n°1 Objet exécutable relogeable (.out .?) (références translatables) Chargement Edition (dynamique) (réalisé par le système) des liens Executable file ELF header Section n°1 Section n°2 Section n°2 Objets partagés (.so) Section n°3 Section n°3 Section n°4 Section n°4 Section header table 17 – copyleft Section n°4 Segment n°2 Segment n°3 Section header table (optionnal/ignored) 18 – copyleft TOOLCHAIN Bas niveau – C ToolChain – ELF file TOOLCHAIN Bas niveau – C ToolChain – ELF file Segment n°1 Section n°2 Section n°3 Bibliothèques dynamiques Exécution (runtime) Source (C) Un fichier ELF est toujours constitué d’une en-tête de fichier (cf. fichier elf.h), le reste de la structure diffère en fonction du type de fichier compilé (exécutable, bibliothèque partagée, objet …) : Observons, en utilisant la commande readelf proposée avec binutils (ou la commande objdump), les en-têtes de fichiers ELF respectivement pour un fichier objet .o, exécutable et la bibliothèque standard du C, qui est un objet partagé .so : Rappelons que pour les langages compilés, deux grands types d’allocations mémoire sont rencontrés pour la gestion des variables : • Allocations statiques : Allocation mémoire à la compilation (compile-time). Prenons l’exemple des variables globales, locales qualifiées de static … Chaque chaîne de compilation C est très structurée, classe les variables statiques par famille et les range dans des sections spécifiques (sections présentent dans fichier ELF). Observons quelques-unes des principales sections : o .bss : variables statiques non-initialisées. Par définition initialisées à zéro au démarrage o .data : variables statiques initialisées o .rodata : variables statiques en lecture seule (read-only) 19 – copyleft Chaîne de Compilation -5- 20 – copyleft Architecture et technologie des ordinateurs TOOLCHAIN Bas niveau – C ToolChain – ELF file TOOLCHAIN Bas niveau – C ToolChain – ELF file • Allocations dynamiques : Allocation mémoire à l’exécution (runtime). Vu par la suite. Observons le contenu des sections d’un fichier objet élémentaire après compilation. Accès aux variables par adressage relatif aux adresses de base des sections : o Gestion par la pile (ou stack) : variables locales, paramètres, valeur de retour, adresse de retour et contexte d’exécution de fonction. Nous parlons également souvent d’allocation automatique. Section .text : binaire du programme o Gestion par le tas (ou heap) : fonctions malloc, free et variantes. Adresses relatives dans les sections (représentation hexadécimale) Contenu des sections (représentation hexadécimale) Contenu des sections (représentation sous forme de caractères) 21 – copyleft 22 – copyleft TOOLCHAIN Bas niveau – C ToolChain – ELF file TOOLCHAIN Bas niveau – C ToolChain – ELF file • Section .symtab : cette section, nommée table des symboles est essentielle. La compilation est un processus dépendant du langage et de l’architecture du CPU mais indépendant du mapping mémoire. Les binaires compilés (fichiers objets) travaillent par références symboliques, la table des symboles lie les symboles à des adresses relatives vers différentes sections. Observons l’en-tête contenant la table des sections pour le programme précédemment compilé (fichier objet). Section .data Section .text 23 – copyleft Chaîne de Compilation -6- Adresse dans la section 24 – copyleft Architecture et technologie des ordinateurs TOOLCHAIN Bas niveau – C ToolChain – ELF file Nous pouvons également rencontrer une table de relocation (liste de trous à compléter avec la table des symboles). A titre indicatif, les bibliothèques dynamiques sont liées à l’exécution par le chargeur du noyau au démarrage du programme. La section .plt (procedure linkage table) effectue une redirection à l’exécution vers l’adresse absolue de la procédure cible (printf dans notre cas). Merci de votre attention ! 25 – copyleft Chaîne de Compilation -7- ASSEMBLEUR Architecture et technologie des ordinateurs Assembleur – Architectures CPU – ISA Extensions Un langage d’assemblage ou assembleur ou ASM est un langage de programmation bas niveau représentant, sous forme lisible pour un être humain, le code binaire exécutable par un processeur (ou code machine). Prenons l’exemple d’une instruction assembleur élémentaire raccrochée à aucune architecture connue : LANGAGE D’ASSEMBLAGE Architecture et Technologie des Ordinateurs Étiquette ou Adresse en mémoire programme Mnémonique ou dénomination de l’instruction LABEL: ADD 0x7B68 0110011 001 010 011 Opérandes (source et/ou destination) opSrc1, opSrc2,opDst3 Adresse mémoire de l’instruction ASSEMBLEUR ASSEMBLEUR Assembleur – Architectures CPU – ISA Extensions Hormis label et commentaires, en général à tout champ d’une instruction assembleur correspond un champ dans le code binaire équivalent. Ce code binaire ne peut être compris et interprété que par le CPU cible. opSrc1, opSrc2,opDst3 L’assembleur est probablement le langage de programmation le moins universel au monde. Il existe autant de langage d’assemblage que de familles de CPU. Prenons l’exemple des jeux d’instructions Cortex-M de ARM. La société Anglaise ARM propose à elle seule 3 familles de CPU, cortex-M, -R, -A possédant chacune des sous familles. Ne regardons que la famille cortex-M : ;commentaires 0110011 001 010 011 N’est utilisé que par les instructions de branchement Assembleur – Architectures CPU – ISA Extensions Cortex-Mx ARM Instruction set ADD Binaire interprété par la machine cible (CPU) 2 – copyleft Hugo Descoubes - Juin 2013 LABEL: ;commentaires Opcode ou Code opératoire 3 – copyleft Langage d’assemblage -1- 4 – copyleft Assembleur – Architectures CPU – ISA Extensions ASSEMBLEUR ASSEMBLEUR Architecture et technologie des ordinateurs Observons les principaux acteurs dans le domaine des CPU’s. Chaque fondeur présenté ci-dessous propose une voire plusieurs architectures de CPU qui lui sont propres et possédant donc les jeux d’instructions associés (CPU server et mainframe non présentés) : • GPP CPU architectures : Intel (IA-32 et Intel 64), AMD (x86 et AMD64), IBM (PowerPC), Renesas (RX CPU), Zilog (Z80), Motorola (6800 et 68000) … • Embedded CPU architectures (MCU, DSP, SoC) : ARM (Cortex –M – R -A), MIPS (Rx000), Intel (Atom, 8051), Renesas, Texas Instrument (MSPxxx, C2xxx, C5xxx, C6xxx), Microchip (PICxx) , Atmel (AVR), Apple/IBM/Freescale (PowerPC) … Tout CPU est capable de décoder puis d’exécuter un jeu d’instruction qui lui est propre (ou instruction set ou ISA ou Instruction Set Architecture). Dans tous les cas, ces instructions peuvent être classées en grandes familles : • Calcul et comparaison : opérations arithmétiques et logiques (en C : +, -, *, /, &, |, ! ...) et opérations de comparaison, (en C : >=, <=, !=, == …). Les formats entiers courts seront toujours supportés nativement. En fonction de l’architecture du CPU, les formats entiers long (16bits et plus) voire flottants peuvent l’être également. • Management de données : déplacement de données dans l’architecture matérielle (CPU vers CPU, CPU vers mémoire ou mémoire vers CPU) Assembleur – Architectures CPU – ISA Extensions ASSEMBLEUR ASSEMBLEUR 5 – copyleft • Contrôle programme : saut en mémoire programme (saut dans le code). Par exemple en langage C : if, else if, else, switch, for, while, do while, appels de procédure. Nous pouvons rendre ces sauts conditionnels à l’aide d’opérations arithmétiques et logiques ou de comparaisons. Certaines architectures, comme les architectures compatibles x86-64 (Intel et AMD), possèdent des familles spécialisées : • String manipulation : manipulation au niveau assembleur de chaînes de caractères. • Divers : arithmétique lourde (sinus, cosinus…), opérations vectorielles (produit vectoriel, produit scalaire…) … Assembleur – Architectures CPU – ISA Extensions 6 – copyleft Assembleur – Architectures CPU – ISA Extensions Jeu d’instruction RISC 8051 Jeu d’instruction CISC 8086 Les jeux d’instructions et CPU associés peuvent être classés en 2 grandes familles, RISC et CISC, respectivement Reduce et Complex Instruction Set Computer. Les architectures RISC n’implémentent en général que des instructions élémentaires (CPU’s ARM, MIPS, 8051, PIC18 …). A l’inverse, les architectures CISC (CPU’s x86-64, 68xxx …) implémentent nativement au niveau assembleur des traitements pouvant être très complexes (division, opérations vectorielles, opérations sur des chaînes de caractères …). En 2012, la frontière entre ces deux familles est de plus en plus fine. Par exemple, le jeu d’instructions des processeurs spécialisés DSP RISC-like TMS320C66xx de TI compte 323 instructions. Néanmoins, les architectures compatibles x86-64 sont des architectures CISC. Nous allons rapidement comprendre pourquoi. 7 – copyleft Langage d’assemblage -2- 8 – copyleft Assembleur – Architectures CPU – ISA Extensions ASSEMBLEUR ASSEMBLEUR Architecture et technologie des ordinateurs Jeu d’instruction RISC 8051 Jeu d’instruction CISC 8086 Avantages architecture CISC : • Empreinte mémoire programme faible, donc plus d’instructions contenues en cache. Néanmoins sur CPU CISC, en moyenne près de 80% des instructions compilées sont de types RISC. • Compatibles x86-64, rétrocompatibilité des applications développées sur anciennes architectures. Inconvénients architecture CISC : • Architecture CPU complexe (mécanismes d’accélération matériels, décodeurs, Execution Units …), donc moins de place pour le cache. • Jeu d’instructions mal géré par les chaînes de compilation (mécanismes d’optimisation) Assembleur – Architectures CPU – ISA Extensions Jeu d’instruction RISC 8051 Jeu d’instruction CISC 8086 Inconvénients architecture RISC : • Empreinte mémoire programme élevée, donc moins d’instructions contenues en cache et mémoire principale. Avantages architecture RISC : • Architecture du CPU moins complexe (mécanismes d’accélération matériels, décodeurs, unités d’exécution …). • En général, tailles instructions fixes et souvent exécution en un ou deux cycles CPU. • Jeu d’instructions plus simple à appréhender pour le développeur et donc le compilateur. Jeu d’instructions très bien géré par les chaînes de compilations (mécanismes d’optimisation). Beaucoup d’architectures RISC récentes, travaillent avec de nombreux registres de travail généralistes, facilite le travail du compilateur. Assembleur – Architectures CPU – ISA Extensions 10 – copyleft ASSEMBLEUR ASSEMBLEUR 9 – copyleft Jeu d’instruction RISC 8051 Jeu d’instruction CISC 8086 Assembleur – Architectures CPU – ISA Extensions Jeu d’instruction RISC 8051 Jeu d’instruction CISC 8086 8051 Intel CPU (only CPU) (1980) 8051 Instruction set Observons le jeu d’instructions complet d’un CPU RISC 8051 proposé par Intel en 1980. En 2012, cette famille de CPU, même si elle reste très ancienne, est toujours extrêmement répandue et intégrée dans de nombreux MCU’s ou ASIC’s (licence libre). Prenons quelques exemples de fondeurs les utilisant : NXP, silabs, Atmel … MCU Silabs with 8051 CPU (2012) 11 – copyleft Langage d’assemblage -3- ACALL Absolute Call MOV Move Memory ADD, ADDC Add Accumulator (With Carry) MOVC Move Code Memory AJMP Absolute Jump MOVX Move Extended Memory ANL Bitwise AND MUL Multiply Accumulator by B CJNE Compare and Jump if Not Equal NOP No Operation CLR Clear Register ORL Bitwise OR CPL Complement Register POP Pop Value From Stack DA Decimal Adjust PUSH Push Value Onto Stack DEC Decrement Register RET Return From Subroutine DIV Divide Accumulator by B RETI Return From Interrupt DJNZ Decrement Register and Jump if Not Zero RL Rotate Accumulator Left INC Increment Register RLC Rotate Accumulator Left Through Carry JB Jump if Bit Set RR Rotate Accumulator Right JBC Jump if Bit Set and Clear Bit RRC Rotate Accumulator Right Through Carry JC Jump if Carry Set SETB Set Bit JMP Jump to Address SJMP Short Jump JNB Jump if Bit Not Set SUBB Subtract From Accumulator With Borrow JNC Jump if Carry Not Set SWAP Swap Accumulator Nibbles JNZ Jump if Accumulator Not Zero XCH Exchange Bytes JZ Jump if Accumulator Zero XCHD Exchange Digits LCALL Long Call XRL Bitwise Exclusive OR LJMP Long Jump 12 – copyleft Assembleur – Architectures CPU – ISA Extensions ASSEMBLEUR ASSEMBLEUR Architecture et technologie des ordinateurs Jeu d’instruction RISC 8051 Jeu d’instruction CISC 8086 Original 8086 Instruction set Observons le jeu d’instructions complet d’un CPU 16bits CISC 8086 proposé par Intel en 1978. Il s’agit du premier processeur de la famille x86. En 2012, un corei7 est toujours capable d’exécuter le jeu d’instruction d’un 8086. Bien sûr, la réciproque n’est pas vraie. 8086 Intel CPU (1978) Assembleur – Architectures CPU – ISA Extensions Jeu d’instruction RISC 8051 Jeu d’instruction CISC 8086 AAA ASCII adjust AL after addition HLT Enter halt state AAD ASCII adjust AX before division IDIV Signed divide AAM ASCII adjust AX after multiplication IMUL Signed multiply AAS ASCII adjust AL after subtraction IN Input from port ADC Add with carry INC Increment by 1 ADD Add INT Call to interrupt AND Logical AND INTO Call to interrupt if overflow CALL Call procedure IRET Return from interrupt CBW Convert byte to word Jcc Jump if condition CLC Clear carry flag JMP Jump CLD Clear direction flag LAHF Load flags into AH register CLI Clear interrupt flag LDS Load pointer using DS CMC Complement carry flag LEA Load Effective Address CMP Compare operands LES Load ES with pointer CMPSB Compare bytes in memory LOCK Assert BUS LOCK# signal CMPSW Compare words LODSB Load string byte CWD Convert word to doubleword LODSW Load string word DAA Decimal adjust AL after addition LOOP/LOOPx Loop control DAS Decimal adjust AL after subtraction MOV Move DEC Decrement by 1 MOVSB Move byte from string to string DIV Unsigned divide MOVSW Move word from string to string ESC Used with floating-point unit MUL Unsigned multiply Original 8086 Instruction set 14 – copyleft Assembleur – Architectures CPU – ISA Extensions ASSEMBLEUR ASSEMBLEUR 13 – copyleft Jeu d’instruction RISC 8051 Jeu d’instruction CISC 8086 NEG Two's complement negation SCASB Compare byte string NOP No operation SCASW Compare word string NOT Negate the operand, logical NOT SHL Shift left (unsigned shift left) OR Logical OR SHR Shift right (unsigned shift right) OUT Output to port STC Set carry flag POP Pop data from stack STD Set direction flag POPF Pop data from flags register STI Set interrupt flag PUSH Push data onto stack STOSB Store byte in string PUSHF Push flags onto stack STOSW Store word in string RCL Rotate left (with carry) SUB Subtraction RCR Rotate right (with carry) TEST Logical compare (AND) REPxx Repeat MOVS/STOS/CMPS/LODS/SCAS WAIT Wait until not busy RET Return from procedure XCHG Exchange data RETN Return from near procedure XLAT Table look-up translation RETF Return from far procedure XOR Exclusive OR ROL Rotate left ROR Rotate right SAHF Store AH into flags SAL Shift Arithmetically left (signed shift left) SAR Shift Arithmetically right (signed shift right) SBB Subtraction with borrow Assembleur – Architectures CPU – ISA Extensions Jeu d’instruction RISC 8051 Jeu d’instruction CISC 8086 Prenons un exemple d’instruction CISC 8086. Les deux codes qui suivent réalisent le même traitement et permettent de déplacer 100 octets en mémoire d’une adresse source vers une adresse destination : CISC MOV MOV MOV REP 15 – copyleft Langage d’assemblage -4- CX,100 DI, dst SI, src MOVSB RISC MOV MOV MOV CX,100 DI, dst SI, src MOV MOV INC INC DEC JNX AL, [SI] [DI], AL SI DI CX LOOP LOOP: 16 – copyleft Assembleur – Architectures CPU – ISA Extensions Assembleur – Architectures CPU – ISA Extensions ASSEMBLEUR ASSEMBLEUR Architecture et technologie des ordinateurs Jeu d’instruction RISC 8051 Jeu d’instruction CISC 8086 Attention, si vous lisez de l’assembleur x86-64, il existe deux syntaxes très répandues. La syntaxe Intel et la syntaxe AT&T utilisée par défaut par gcc (systèmes UNIX). Jeu d’instruction RISC 8051 Jeu d’instruction CISC 8086 Prenons un exemple de code écrit dans les 2 syntaxes : Intel Syntax Intel Syntax MOV AT&T Syntax ebx,0FAh MOV $0xFA, %ebx MOV MOV MOV CX,100 DI, dst SI, src MOV MOV INC INC DEC JNX AL, [SI] [DI], AL SI DI CX LOOP LOOP: Syntaxe AT&T : • Opérandes sources à gauche et destination à droite • Constantes préfixées par $ (adressage immédiat) • Constantes écrites avec syntaxe langage C (0x + valeur = hexadécimal) • Registres préfixés par % • Segmentation : [ds:20] devient %ds:20, [ss:bp] devient %ss:%bp … • • • Adressage indirect [ebx] devient (%ebx), [ebx + 20h] devient 0x20(%ebx), [ebx+ecx*2h-1Fh] devient -0x1F(%ebx, %ecx, 0x2) … Suffixes, b=byte=1o, w=word=2o, s=short=4o, l=long=4o, q=quad=8o, t=ten=10o, o=octo=16o=128bits (x64) … AT&T Syntax movw movw movw $100, %cx dst, %di src, %di movb movb inc inc dec jnx (%si), %al %al, (%di) %si %di %cx LOOP LOOP: 18 – copyleft Assembleur – Architectures CPU – ISA Extensions ASSEMBLEUR ASSEMBLEUR 17 – copyleft Par abus de langage, les CPU compatibles du jeu d’instruction 80x86 (8086, 80386, 80486..) sont nommés CPU x86. Depuis l’arrivée d’architectures 64bits ils sont par abus de langage nommés x64. Pour être rigoureux chez Intel, il faut nommer les jeux d’instructions et CPU 32bits associés IA-32 (depuis le 80386 en 1985) et les ISA 64bits Intel 64 ou EM64T (depuis le Pentium 4 Prescott en 2004). Assembleur – Architectures CPU – ISA Extensions Extensions x86 et x64 n’opérant que sur des formats entiers : CPU Architecture Nom extension Instructions - AAA, AAD, AAM, AAS, ADC, ADD, AND, CALL, CBW, CLC, CLD, CLI, CMC, CMP, CMPSzz, CWD, DAA, DAS, DEC, DIV, ESC, HLT, IDIV, IMUL, IN, INC, INT, INTO, IRET, Jcc, LAHF, LDS, LEA, LES, LOCK, LODSzz, LODSW, LOOPcc, MOV, MOVSzz, MUL, NEG, NOP, NOT, OR, OUT, POP, POPF, PUSH, PUSHF, RCL, RCR, REPcc, RET, RETF, ROL, ROR, SAHF, SAL, SALC, SAR, SBB, SCASzz, SHL, SAL, SHR, STC, STD, STI, STOSzz, SUB, TEST, WAIT, XCHG,XLAT, XOR 8086 Original x86 80186/80188 - BOUND, ENTER, INSB, INSW, LEAVE, OUTSB, OUTSW, POPA, PUSHA, PUSHW 80286 - ARPL, CLTS, LAR, LGDT, LIDT, LLDT, LMSW, LOADALL, LSL, LTR, SGDT, SIDT, SLDT, SMSW, STR, VERR, VERW - BSF, BSR, BT, BTC, BTR, BTS, CDQ, CMPSD, CWDE, INSD, IRETD, IRETDF, IRETF, JECXZ, LFS, LGS, LSS, LODSD, LOOPD, LOOPED, LOOPNED, LOOPNZD, LOOPZD, MOVSD, MOVSX, MOVZX, OUTSD, POPAD, POPFD, PUSHAD, PUSHD, PUSHFD, SCASD, SETA, SETAE, SETB, SETBE, SETC, SETE, SETG, SETGE, SETL, SETLE, SETNA, SETNAE, SETNB, SETNBE, SETNC, SETNE, SETNG, SETNGE, SETNL, SETNLE, SETNO, SETNP, SETNS, SETNZ, SETO, SETP, SETPE, SETPO, SETS, SETZ, SHLD, SHRD, STOSD 80386 L’une des grandes forces (et paradoxalement faiblesse) de ce jeu d’instruction est d’assurer une rétrocompatibilité avec les jeux d’instructions d’architectures antérieures. En contrepartie, il s’agit d’une architecture matérielle très complexe, difficile à accélérer imposant de fortes contraintes de consommation et d’échauffement. 19 – copyleft 80486 - BSWAP, CMPXCHG, INVD, INVLPG, WBINVD, XADD Pentium - CPUID, CMPXCHG8B, RDMSR, RDPMC, WRMSR, RSM - CMOVA, CMOVAE, CMOVB, CMOVB, CMOVE, CMOVG, CMOVGE, CMOVL, CMOVLE, CMOVNA, CMOVNAE, CMOVNB, CMOVNBE, CMOVNC, CMOVNE, CMOVNG, CMOVNGE, CMOVNL, CMOVNLE, CMOVNO, CMOVNP, CMOVNS, CMOVNZ, CMOVO, CMOVP, CMOVPE, CMOVPO, CMOVS, CMOVZ, RDPMC, SYSENTER, SYSEXIT, UD2 Pentium pro Pentium III SSE MASKMOVQ, MOVNTPS, MOVNTQ, PREFETCH0, PREFETCH1, PREFETCH2, PREFETCHNTA, SFENCE Pentium 4 SSE2 CLFLUSH, LFENCE, MASKMOVDQU, MFENCE, MOVNTDQ, MOVNTI, MOVNTPD, PAUSE Pentium 4 SSE3 Hyper Threading Pentium 4 6x2 VMX X86-64 - Pentium 4 VT-x Langage d’assemblage -5- LDDQU, MONITOR, MWAIT VMPTRLD, VMPTRST, VMCLEAR, VMREAD, VMWRITE, VMCALL, VMLAUNCH, VMRESUME, VMXOFF, VMXON CDQE, CQO, CMPSQ, CMPXCHG16B, IRETQ, JRCXZ, LODSQ, MOVSXD, POPFQ, PUSHFQ, RDTSC, SCASQ, STOSQ, SWAPGS VMPTRLD, VMPTRST, VMCLEAR, VMREAD, VMWRITE, VMCALL, VMLAUNCH, VMRESUME, VMXOFF, VMXON 20 – copyleft Assembleur – Architectures CPU – ISA Extensions ASSEMBLEUR ASSEMBLEUR Architecture et technologie des ordinateurs Les extensions x87 ci-dessous n’opèrent que sur des formats flottants. Historiquement, le 8087 était un coprocesseur externe utilisé comme accélérateur matériel pour des opérations flottantes. Ce coprocesseur fut intégré dans le CPU principal sous forme d’unité d’exécution depuis l’architecture 80486. Cette unité est souvent nommée FPU (Floating Point Unit). Assembleur – Architectures CPU – ISA Extensions Les extensions présentées ci-dessous inplémentent toutes des instructions dites SIMD (Single Instruction Multiple Data) : • MMX : MultiMedia eXtensions • SSE : Streaming SIMD Extensions CPU Architecture Nom extension Instructions - F2XM1, FABS, FADD, FADDP, FBLD, FBSTP, FCHS, FCLEX, FCOM, FCOMP, FCOMPP, FDECSTP, FDISI, FDIV, FDIVP, FDIVR, FDIVRP, FENI, FFREE, FIADD, FICOM, FICOMP, FIDIV, FIDIVR, FILD, FIMUL, FINCSTP, FINIT, FIST, FISTP, FISUB, FISUBR, FLD, FLD1, FLDCW, FLDENV, FLDENVW, FLDL2E, FLDL2T, FLDLG2, FLDLN2, FLDPI, FLDZ, FMUL, FMULP, FNCLEX, FNDISI, FNENI, FNINIT, FNOP, FNSAVE, FNSAVEW, FNSTCW, FNSTENV, FNSTENVW, FNSTSW, FPATAN, FPREM, FPTAN, FRNDINT, FRSTOR, FRSTORW, FSAVE, FSAVEW, FSCALE, FSQRT, FST, FSTCW, FSTENV, FSTENVW, FSTP, FSTSW, FSUB, FSUBP, FSUBR, FSUBRP, FTST, FWAIT, FXAM, FXCH, FXTRACT, FYL2X, FYL2XP1 8087 Original x87 80287 - 80387 Instructions MMX EMMS, MOVD, MOVQ, PACKSSDW, PACKSSWB, PACKUSWB, PADDB, PADDD, PADDSB, PADDSW, PADDUSB, PADDUSW, PADDW, PAND, PANDN, PCMPEQB, PCMPEQD, PCMPEQW, PCMPGTB, PCMPGTD, PCMPGTW, PMADDWD, PMULHW, PMULLW, POR, PSLLD, PSLLQ, PSLLW, PSRAD, PSRAW, PSRLD, PSRLQ, PSRLW, PSUBB, PSUBD, PSUBSB, PSUBSW, PSUBUSB, PSUBUSW, PSUBW, PUNPCKHBW, PUNPCKHDQ, PUNPCKHWD, PUNPCKLBW, PUNPCKLDQ, PUNPCKLWD, PXOR Pentium 4 Float Inst. Integer Inst. SSE2 - FCOS, FLDENVD, FNSAVED, FNSTENVD, FPREM1, FRSTORD, FSAVED, FSIN, FSINCOS, FSTENVD, FUCOM, FUCOMP, FUCOMPP FCMOVB, FCMOVBE, FCMOVE, FCMOVNB, FCMOVNBE, FCMOVNE, FCMOVNU, FCMOVU, FCOMI, FCOMIP, FUCOMI, FUCOMIP, FXRSTOR, FXSAVE SSE3 SSE Pentium III FSETPM Pentium pro Float Inst. Pentium 4 FISTTP SSE3 ASSEMBLEUR ASSEMBLEUR 21 – copyleft Assembleur – Architectures CPU – ISA Extensions Les instructions et opérandes usuellement manipulées par grand nombre de CPU sur le marché sont dites scalaires. Nous parlerons de processeur scalaire (PIC de Microchip, 8051 de Intel, AVR de Atmel, C5xxx de TI…). Par exemple sur 8086 de Intel , prenons l’exemple d’une addition : scalaire + scalaire = scalaire : add ADDPS, ADDSS, CMPPS, CMPSS, COMISS, CVTPI2PS, CVTPS2PI, CVTSI2SS, CVTSS2SI, CVTTPS2PI, CVTTSS2SI, DIVPS, DIVSS, LDMXCSR, MAXPS, MAXSS, MINPS, MINSS, MOVAPS, MOVHLPS, MOVHPS, MOVLHPS, MOVLPS, MOVMSKPS, MOVNTPS, MOVSS, MOVUPS, MULPS, MULSS, RCPPS, RCPSS, RSQRTPS, RSQRTSS, SHUFPS, SQRTPS, SQRTSS, STMXCSR, SUBPS, SUBSS, UCOMISS, UNPCKHPS, UNPCKLPS ANDNPS, ANDPS, ORPS, PAVGB, PAVGW, PEXTRW, PINSRW, PMAXSW, PMAXUB, PMINSW, PMINUB, PMOVMSKB, PMULHUW, PSADBW, PSHUFW, XORPS ADDPD, ADDSD, ANDNPD, ANDPD, CMPPD, CMPSD, COMISD, CVTDQ2PD, CVTDQ2PS, CVTPD2DQ, CVTPD2PI, CVTPD2PS, CVTPI2PD, CVTPS2DQ, CVTPS2PD, CVTSD2SI, CVTSD2SS, CVTSI2SD, CVTSS2SD, CVTTPD2DQ, CVTTPD2PI, CVTTPS2DQ, CVTTSD2SI, DIVPD, DIVSD, MAXPD, MAXSD, MINPD, MINSD, MOVAPD, MOVHPD, MOVLPD, MOVMSKPD, MOVSD, MOVUPD, MULPD, MULSD, ORPD, SHUFPD, SQRTPD, SQRTSD, SUBPD, SUBSD, UCOMISD, UNPCKHPD, UNPCKLPD, XORPD Integer Inst. MOVDQ2Q, MOVDQA, MOVDQU, MOVQ2DQ, PADDQ, PSUBQ, PMULUDQ, PSHUFHW, PSHUFLW, PSHUFD, PSLLDQ, PSRLDQ, PUNPCKHQDQ, PUNPCKLQDQ Float Inst. ADDSUBPD, ADDSUBPS, HADDPD, HADDPS, HSUBPD, HSUBPS, MOVDDUP, MOVSHDUP, MOVSLDUP 22 – copyleft Assembleur – Architectures CPU – ISA Extensions Cette instruction vectorielle peut notamment être très intéressante pour des applications de traitement numérique du signal : dpps signifie dot product packet single, soit produit scalaire sur un paquet de données au format flottant en simple précision (IEEE-754). Observons le descriptif de l’instruction ainsi qu’un exemple : %bl,%al A titre indicatif, les instructions MMX, SSE, AVX, AES … sont dîtes vectorielles. Les opérandes ne sont plus des grandeurs scalaires mais des grandeurs vectorielles. Nous parlerons de processeur vectoriel (d’autres architectures vectorielles existent). Prenons un exemple d’instruction vectorielle SIMD SSE4.1, vecteur . vecteur = scalaire : dpps AVX : Advanced Vector Extensions AES : Advanced Encryption Standard Nom extension Pentium MMX CPU Architecture • • http://www.intel.com 0xF1, %xmm2,%xmm1 23 – copyleft Langage d’assemblage -6- 24 – copyleft Assembleur – Architectures CPU – ISA Extensions ASSEMBLEUR ASSEMBLEUR Architecture et technologie des ordinateurs Etudions un exemple d’exécution de l’instruction dpps : dpps Etudions un exemple d’exécution de l’instruction dpps : XMMi (i = 0 à 15 with Intel 64) 128bits General Purpose Registers for SIMD Execution Units 0xF1, %xmm2,%xmm1 128 XMM1 96 a3 128 XMM2 64 a2 96 x3 64 x2 dpps XMMi (i = 0 à 15 with Intel 64) 128bits General Purpose Registers for SIMD Execution Units 0xF1, %xmm2,%xmm1 0 32 a1 Assembleur – Architectures CPU – ISA Extensions x0 128 128 Temp1 96 a3.x3 64 a2.x2 32 128 XMM2 0 Core2 Nehalem 0 32 x1 x0 ASSEMBLEUR 26 – copyleft Assembleur – Architectures CPU – ISA Extensions Instructions PSIGNW, PSIGND, PSIGNB, PSHUFB, PMULHRSW, PMADDUBSW, PHSUBW, PHSUBSW, PHSUBD, PHADDW, PHADDSW, PHADDD, PALIGNR, PABSW, PABSD, PABSB MPSADBW, PHMINPOSUW, PMULLD, PMULDQ, DPPS, DPPD, BLENDPS, BLENDPD, BLENDVPS, BLENDVPD, PBLENDVB, PBLENDW, PMINSB, PMAXSB, PMINUW, PMAXUW, PMINUD, PMAXUD, PMINSD, PMAXSD, ROUNDPS, ROUNDSS, ROUNDPD, ROUNDSD, INSERTPS, PINSRB, PINSRD/PINSRQ, EXTRACTPS, PEXTRB, PEXTRW, PEXTRD/PEXTRQ, PMOVSXBW, PMOVZXBW, PMOVSXBD, PMOVZXBD, PMOVSXBQ, PMOVZXBQ, PMOVSXWD, PMOVZXWD, PMOVSXWQ, PMOVZXWQ, PMOVSXDQ, PMOVZXDQ, PTEST, PCMPEQQ, PACKUSDW, MOVNTDQA SSE4.2 64 x2 L’instruction CPUID arrivée avec l’architecture Pentium permet de récupérer très facilement toutes les informations relatives à l’architecture matérielle du GPP (CPU’s, Caches, adressage virtuel..). L’utilitaire libre CPU-Z utilise notamment ce registre pour retourner des informations sur l’architecture : SSSE3 CRC32, PCMPESTRI, PCMPESTRM, PCMPISTRI, PCMPISTRM, PCMPGTQ AVX VFMADDPD, VFMADDPS, VFMADDSD, VFMADDSS, VFMADDSUBPD, VFMADDSUBPS, VFMSUBADDPD, VFMSUBADDPS, VFMSUBPD, VFMSUBPS, VFMSUBSD, VFMSUBSS, VFNMADDPD, VFNMADDPS, VFNMADDSD, VFNMADDSS, VFNMSUBPD, VFNMSUBPS, VFNMSUBSD, VFNMSUBSS AES AESENC, AESENCLAST, AESDEC, AESDECLAST, AESKEYGENASSIST, AESIMC Sandy Bridge Nehalem http://www.intel.com 25 – copyleft SSE4.1 Core2 (45nm) 96 x3 0 Assembleur – Architectures CPU – ISA Extensions Nom extension a0.x0 + a1.x1 + a2.x2 + a3.x3 a0.x0 + a1.x1 + a2.x2 + a3.x3 Les extensions x86-64 présentées jusqu’à maintenant ne présentent que les évolutions des jeux d’instructions apportées par Intel. Les extensions amenées par AMD ne seront pas présentées (MMX+, K6-2, 3DNow, 3DNow!+, SSE4a..). CPU Architecture 0 32 0.0 a2.x2 + a3.x3 Temp3 Temp4 64 0.0 0 a0.x0 + a1.x1 32 96 0.0 XMM1 a0.x0 32 ASSEMBLEUR 0 32 a1.x1 Temp2 http://www.intel.com a0.x0 + a1.x1 + a2.x2 + a3.x3 Temp4 0 32 x1 0 32 a0 27 – copyleft Langage d’assemblage -7- 28 – copyleft Assembleur – Architectures CPU – ISA Extensions ASSEMBLEUR ASSEMBLEUR Architecture et technologie des ordinateurs Sous Linux, vous pouvez également consulter le fichier /proc/cpuinfo listant les informations retournées par l’instruction CPUID : Assembleur – Architectures CPU – ISA Extensions De même, lorsque l’on est amené à développer sur un processeur donné, il est essentiel de travailler avec les documents de référence proposés par le fondeur, Intel dans notre cas. Vous pouvez télécharger les différents documents de référence à cette URL : http://www.intel.com/content/www/us/en/processors/architecturessoftware-developer-manuals.html 29 – copyleft Merci de votre attention ! Langage d’assemblage -8- 30 – copyleft Processeurs Spécialisés TRAVAUX PRATIQUES Digital Signal Processor Travaux Pratiques SOMMAIRE PREAMBULE 1. ALGORITHME DE FILTRAGE 1.1. Algorithme de référence 1.2. Test de performance 1.3. Intégration en C canonique 2. AUTOMATISATION DES TESTS 2.1. Test de conformité 2.2. Test de performance 2.3. Temps de compilation et empreinte mémoire 2.4. Temps de programmation 3. PROGRAMMATION VECTORIELLE 3.1. Assembleur canonique C6600 3.2. Assembleur VLIW C6600 3.3. Pipelining logiciel assembleur C6600 3.4. Assembleur vectoriel C6600 3.5. Déroulement de boucle en C 3.6. Vectorisation de code en C 3.7. C canonique sur architecture IA-64 3.8. Vectorisation SSE4.1 sur architecture IA-64 4. HIERARCHIE MEMOIRE 4.1. Mémoire locale adressable 4.2. Préchargement des données de DDR SRAM vers L2 SRAM 4.3. Préchargement des données de L2 SRAM vers L1D SRAM 5. PERIPHERIQUES D'ACCELERATION 5.1. Transferts par IDMA 5.2. Stratégie Ping Pong 5.3. Transferts par EDMA BENCHMARKING -1- Digital Signal Processor Travaux Pratiques PREAMBULE Durant cette trame de travaux pratiques, nous allons nous intéresser au workflow typiquement rencontré en milieu industriel dans le cadre d'optimisation logicielle d'algorithme pour une cible matérielle spécialisée. Ce processus de développement peut par exemple être rencontré chez les acteurs des grands domaines du traitement du signal (traitement d'antenne, traitement d'image, traitement du son …). Nous travaillons d'ailleurs à l'ENSICAEN avec plusieurs partenaires industriels utilisant ce type de méthodologie. Durant la séquence qui suit, nous nous intéresserons à l'implémentation d'un algorithme simple et standard du domaine du traitement du signal, un filtre FIR ou produit scalaire. Nous nous attarderons bien entendu aux stratégies d'optimisation sur une architecture matérielle spécialisée. Tous nos développements seront guidés par le test, étape pouvant tenir une place très importante dans le temps de développement global d'une application et le benchmarking (analyse comparative). Nous constaterons que si cette étape n'est pas délaissée, il nous ait alors possible de gagner des jours de développement sur une tâche courante. Observons le workflow de la trame de travaux pratiques : • Analyse mathématique théorique de l'algorithme • Modélisation et validation sur outil de prototypage. L'outil logiciel de prototypage rapide le plus rencontré en milieu industriel à notre époque dans le domaine de l'embarqué appliqué au traitement du signal est Matlab/Simulink. Cette étape est essentielle afin de valider la structure en pseudo code des algorithmes ainsi que les vecteurs d'entrée et de sortie pour les procédures de test à venir • Intégration sur cible d'un algorithme de référence • Intégration sur cible des procédures de test. Dans le cadre de nos développements, nous nous intéresserons essentiellement aux tests de conformité et de performance dans une optique de benchmarking • Intégrations et validations successives sur cible des stratégies d'optimisation sur architecture processeur spécialisée (vectorisation monocœur, parallélisation multicœur, gestion optimale de la hiérarchie mémoire et périphériques d'accélération) -2- Digital Signal Processor Travaux Pratiques Dans une optique pédagogique et professionnalisante, notre choix s'est porté sur l'architecture C6600 proposée par Texas Instruments. Pour information, en 2015 la famille C6000 de TI est l'architecture leader sur le marché des processeurs DSP (Digital Signal Processor). Le processeur C6678 étudié en TP étant l'un des composant haut de gamme de la famille avec ses 8 cœurs vectoriels VLIW. Cette architecture propose quelques atouts assurant une grande flexibilité et permettant une bonne compréhension des architectures processeurs actuelles. Observons là plus en détails : Le processeur DSP TMS320C6678 offre 8 cœurs vectoriels VLIW (Very Long Instruction Word) possédant chacun 32Ko caches L1 program et L1 data, ainsi que 512Ko de cache L2. Chaque cœur peut-être cadencé jusqu'à 1,4GHz. Le niveau mémoire L3 de 4Mo nommé MSM (Multi-core Shared Memory) est partagé entre cœurs. Chaque niveau mémoire peut être configurable en cache ou en mémoire adressable SRAM permettant une architecture matérielle configurable en modèle mémoire uniforme ou non-uniforme. Chose impossible sur processeur généraliste GPP Intel par exemple. De la mémoire RAM DDR3 externe peut également être ajoutée sur circuit imprimé et est alors interconnectée via le périphérique d'interface EMIF (External Memory Interface). En première année, nous avons découvert les bases du développement sur processeur numérique. Afin de faciliter cette approche nous avons développé sur MCU (Micro Controller Unit) ou microcontrôleur. Ces processeurs étant généralistes et non spécialisés pour du calcul numérique massif, nous nous sommes donc assez longuement attardés sur les périphériques et interfaces de communication : -3- Digital Signal Processor Travaux Pratiques L'enseignement de cette année est différent et doit être perçu comme une extension des compétences de première année. Cette année nous allons travailler sur machine fortement parallèle et nous nous efforcerons de comprendre puis d'exploiter au mieux les ressources matérielles proposées par notre processeur. Les périphériques d'interface ne seront donc pas vus, seule la partie processing nous intéressera. Nous nous focaliserons donc sur l'architecture vectorielle, parallèle et le pipeline de chaque cœur, les hiérarchies mémoires ainsi que les périphériques d'accélération : Même si l'exemple qui suit n'a que peu de sens, afin de bien comprendre les différences majeures entre les architectures de première année et de deuxième année, comparons les performances théoriques maximales des deux processeurs. Un MCU 8bits PIC18 de Microchip peut exécuter jusqu'à 12MIPS (soit 12 millions instructions entières 8bits par seconde). Un DSP 32bits TMS320C6678 de Texas Instruments peut exécuter jusqu'à 22,4GFLOP/core (soit 22,4 Giga instructions flottantes 32bits en simple précision par seconde par cœur). Soit jusqu'à 179,2GFLOP pour le processeur complet grâce à ses 8 cœurs, le tout avec une horloge à 1,4GHz. Avec ces quelques chiffres, nous pouvons commencer à pressentir le potentiel pour du calcul numérique flottant de cette famille de processeur. La trame de travaux pratiques sera réalisée sur la plateforme de développement TMDXEVM6678L EVM (EValuation Module) proposé par la société Advantec. Cette maquette d'évaluation embarque notamment une sonde d'émulation USB XDS100 assurant la programmation et le débogage des applicatifs : -4- Digital Signal Processor Travaux Pratiques Les outils de développement et bibliothèques sont librement téléchargeables et installables depuis internet du moment que nous n'utilisons que la sonde de programmation XDS100 présente sur la maquette ou le mode simulation. Les outils deviennent alors payants si nous souhaitons travailler avec des sondes d'émulation évoluées, telle que la sonde XDS560 également présente à l'école. Voici ci-dessous des liens vers les outils logiciels (IDE, SDK) utilisés afin de réaliser la trame de travaux pratiques. Bien entendu, nous vous conseillons vivement d'installer ces outils sur vos machines (IDE et bibliothèques spécifiées ci-dessous) : • Wiki Processeurs TI. Point d'entrée des différentes ressources en ligne proposées par Texas afin de développer sur la totalité de leurs gammes de processeurs : http://processors.wiki.ti.com/index.php/Main_Page • IDE Code Composer Studio v6.0 basé sur Eclipse et ABI associées (Application Binary Interface). Nous vous conseillons une installation hors ligne. De plus, le téléchargement des outils depuis le site officiel de Texas Instruments requiert une inscription à leur site. La validation par TI de cette étape peut prendre quelques jours, ne pas être surpris : http://processors.wiki.ti.com/index.php/Category:Code_Composer_Studio_v6 • Bibliothèques de fonctions C CSL (Chip Support Library) proposées par TI pour la gestion des périphériques internes de leurs familles de processeurs. Bien choisir la bibliothèque associée au processeur C6678 : http://processors.wiki.ti.com/index.php/Chip_support_library Après installation, CSL est accessible depuis le répertoire suivant : C:\ti\pdk_C6678_version\packages\ti\csl\ • Bibliothèque DSPLIB (Digital Signal Processing Library) développée par TI et proposant des fonctions C pour le domaine du traitement numérique du signal et optimisée pour leurs architectures processeurs. Dans notre cas, seules les bibliothèques pour la famille C6600 nous intéressent : http://www.ti.com/tool/SPRC265 Après installation, la DSPLIB est accessible depuis le répertoire suivant : C:\ti\dsplib_c66x_version\packages\ti\dsplib\ -5- Digital Signal Processor Travaux Pratiques A partir de maintenant, le travail qui nous attend consiste à implémenter un algorithme de filtrage FIR (ou produit scalaire) sur une architecture spécialisée pour du calcul numérique. Effectuons quelques rappels sur cet algorithme. Une implémentation courante de ce filtre consiste à appeler périodiquement (période d'échantillonnage) l'algorithme dans une optique de calcul en temps réel. A chaque appel, seul un échantillon de sortie est calculé puis traité : x() = vecteur d'échantillons d'entrée (taille égale à N) a() = vecteur de coefficients y(k) = échantillon courant de sortie k = indice courant N = ordre du filtre N+1 = nombre de coefficients L'implémentation vue en travaux pratiques se fera en temps différé. Nous calculerons un vecteur de sortie complet en traitant l'information depuis un vecteur d'entrée pouvant être très long et dans tous les cas de figure de taille supérieure ou égale au nombre de coefficients : x() = vecteur d'échantillons d'entrée (taille supérieure ou égale à N) a() = vecteur de coefficients y() = vecteur d'échantillons de sortie k = indice courant N = ordre du filtre N+1 = nombre de coefficients Y = taille du vecteur de sortie Y+N-1 = taille du vecteur d'entrée La phase de prototypage rapide sous environnement Matlab/Simulink ne sera pas à faire et vous est donnée (répertoire de projet /dsp/matlab/). Elle est entièrement recouverte par le programme de 1ière année en Traitement Numérique du Signal. Nous nous attarderons uniquement sur les problématiques liées à l'intégration sur DSP TMS320C6678. Observons l'implémentation en pseudo code Matlab au format flottant simple précision IEEE754 de l'algorithme de filtrage en temps différé précédemment présenté : function yk = fir_sp(xk, coeff, coeffLength, ykLength) yk = single(zeros(1,ykLength)); % output array preallocation % output array loop for i=1:ykLength yk(i) = single(0); % FIR filter algorithm - dot product for j=1:coeffLength yk(i) = single(yk(i)) + single(coeff(j)) * single(xk(i+j-1)); end end end -6- Digital Signal Processor Travaux Pratiques Voici enfin quelques compléments d'information concernant le filtre à intégrer sur cible ainsi que sur les vecteurs d'entrée et de sortie. La synthèse et la génération du filtre a été réalisée sous Matlab/Simulink via l'outil FDATool. Il s'agit d'un filtre passe bas coupant à 200Hz pour une fréquence d'échantillonnage à 10KHz, d'ordre 63 (comprenant donc 64 coefficients de symétrie paire en flottant simple précision IEEE754), offrant un gain unitaire dans la bande passante et une atténuation de -40dB dans la bande coupée. La bande d'atténuation est comprise entre 200Hz et 600Hz. Ce filtre ne vise aucune application ou domaine spécifique et ne nous servira qu'à illustrer les concepts et stratégies d'optimisation sur processeur spécialisé. Le vecteur de test d'entrée est une simple somme de deux sinusoïdes à 100Hz et 1KHz. Après filtrage, seule l'harmonique à 100Hz est clairement identifiable. Génération du vecteur sous Matlab : Fs = 10000; Ts = 1/Fs; F1 = 100; F2 = 1000; XK_LENGTH = 2048; t=0 : Ts : (XK_LENGTH-1)*Ts; % sample frequency % period frequency % harmonic n°1 of input vector % harmonic n°2 of input vector % 2K/8Kb samples : length of input vector % time vector %*** INPUT VECTOR GENERATION xk = single(sin(2*pi*F1*t) + sin(2*pi*F2*t)); -7- Digital Signal Processor Travaux Pratiques Radar de défense aérienne GM 400 développé par THALES AIR SYSTEMS Enfin pour conclure, afin de bien prendre conscience de l’intérêt de tel développement et phases d'optimisation, prenons l'exemple d'une application radar. Nous pouvons observer, ci-dessous, l'architecture matérielle typique d'un radar. Observons l'emplacement du processeur de traitement numérique du signal, dans le cas présent un DSP. Il faut savoir que d'autres familles de processeurs peuvent également remplir cette tâche (GPP, GPU ou FPGA/SoC), chaque famille offrant son lot d'avantages et d'inconvénients. Une chaîne numérique de traitement radar implémente également un produit scalaire comme celui étudié dans cette trame de travaux pratiques (algorithme FIR). A titre indicatif, à lui seul cet algorithme pèse près de 40% de la charge CPU d'une chaîne complète. Compte-tenu des contraintes temps réel excessivement lourdes imposées par ce type d'application (qqMo de données à traiter en qqms), nous pouvons pressentir tout l'intérêt de développer des bibliothèques de fonctions spécialisées pour l'architecture cible. Ce sera l'objectif premier de cet enseignement tout en respectant des méthodologies assurant un développement le plus optimal possible. -8- Digital Signal Processor Travaux Pratiques 1. ALGORITHME DE FILTRAGE Travail préparatoire • 1. (2,5pt) Il existe deux grandes familles de filtres numériques, les filtres FIR (Finite Impulse Response) et IIR (Infinite Impulse Response). Quels sont les avantages et inconvénients des filtres FIR et citer des exemples d'application dans lesquels nous pouvons les rencontrer ? • 2. (2pt) En vous aidant de l'implémentation en pseudo code Matlab présentée durant le préambule, proposer une implémentation en C canonique (sans optimisation) de l'algorithme de filtrage (temps différé) en respectant le prototype de procédure ci-dessous. /** * @brief FIR filtering function in canonical C (single precision IEEE754) * @param xk input array pointer (nyk+na-1 elements) * @param a coefficients array pointer (na elements) * @param yk output array pointer (nyk elements) * @param na number of coefficients * @param nyk output array size */ void fir_sp( const float * restrict xk, \ const float * restrict a, \ float * restrict yk, \ int na, \ int nyk); • 3. (0,5pt) Dans le prototype de procédure présenté ci-dessus, nous pouvons observer le qualificateur de type const. Rappeler son rôle et son utilité. • 4. (1pt) En vous aidant d'internet, donner également le rôle du qualificateur de type restrict. Expliquer dans quels cas nous pouvons le manipuler ainsi que son utilité dans le cadre de cette trame d'enseignement. • 5. (1,5pt) Rappeler ce qu'est un Timer (cf. programme de première année) pour un processeur numérique (MCU, DSP, GPP …). • 6. (0,5pt) Chez Texas Instruments, qu'est-ce que CSL (Chip Support Library) ? Télécharger CSL pour notre architecture processeur. • 7. (1pt) Chaque cœur de notre DSP possède un timer 64 bits mis à zéro au reset, extrêmement simple d'utilisation et travaillant à la fréquence de travail du cœur (soit 1,4Ghz dans notre cas). Ces timers se nomment TSC (Time Stamp Counter) et servent typiquement à de la mesure de performance. Pour information, tout processeur GPP Intel actuel possède également un timer nommé TSC dans chaque cœur. En vous aidant de la documentation technique de CSL présente dans le répertoire de projet (/dsp/c6678/doc/csl/docs/doxygen /html/index.html) ou sur internet, détailler l'API (Application Programming Interface) de programmation proposée par TI afin d'utiliser ces timers. • 8. (1pt) Proposer un code C permettant la mesure du temps écoulé en nombre de cycles CPU concernant l'exécution d'une zone de code ainsi que l'affichage de celui-ci via la fonction standard printf. -9- Digital Signal Processor Travaux Pratiques 1. ALGORITHME DE FILTRAGE Travail en séance 1.1. Algorithme de référence Durant toute la trame de travaux pratiques, ne pas oublier de parcourir voire lire les annexes proposant notamment un tutoriel d'utilisation de l'IDE Code Composer Studio de Texas Instruments, un exemple de création de projet sous ce même IDE, un exemple de création de bibliothèque statique … De même, durant la totalité de la trame, Nous vous demanderons de respecter impérativement les règles de codage (coding style) imposées en annexe. • Créer un projet nommé firtest dans le répertoire /dsp/c6678/test/pjct/ incluant le source /dsp/c6678/test/src/firtest_example.c. S'assurer de la bonne compilation et exécution du projet. • Sans pour autant créer un nouveau projet, retirer le fichier source /dsp/c6678/test/src/firtest_example.c puis ajouter le fichier source /dsp/c6678/test/src/firtest_main.c. S'assurer de la bonne compilation et exécution du projet. Bien penser à lire attentivement le contenu des fichiers d'en-tête. Rq : Les fichiers d'en-tête servent souvent de documentation pour une libraire ou un projet logiciel. Ils précisent notamment les services logiciel proposés par une bibliothèque ou un applicatif et donc ce que nous pouvons faire et ne pas faire avec le projet courant. • Nous allons maintenant intégrer puis exécuter un algorithme de filtrage développé par Texas Instruments et optimisé pour l'architecture DSP C6600. Celui-ci jouera le rôle d'algorithme de référence, nous permettra de valider nos futurs vecteurs de sortie et nous servira de mesure étalon pour nos benchmarks à venir (analyses comparatives). En vous aidant de la documentation technique de la bibliothèque DSPLIB de TI présente sous /dsp/c6678/doc/dsplib/DSPLIB_Users_Manual.chm ou C:\ti\dsplib_c66x_version\docs\ DSPLIB_Users_Manual.chm, appeler depuis la fonction main la procédure DSPF_sp_fir_gen en respectant le prototype imposé et en passant comme arguments les variables et macros définies dans les fichiers sources et d'en-tête actuellement présent dans notre projet (a_sp.h, xk_sp_8Kb.h et firtest.h). Le vecteur de sortie sera sauvé dans le tableau yk_sp_ti. • Visualiser les vecteurs d'entrée et de sortie après exécution du programme en utilisant l'utilitaire graphique Graph proposé par CCS depuis l'espace de débogage. Après quelques utilisations de cet utilitaire fort peu utile et performant, nous comprendrons l'utilité d'automatiser les procédures de tests afin de gagner un temps considérable dans nos développement. ◦ S'assurer que le projet soit bien configuré en mode Debug ◦ Après compilation, programmation et exécution, basculer dans l’environnement de travail CCS Debug, puis mettre le programme en pause sans pour autant stopper la session de debug. - 10 - Digital Signal Processor Travaux Pratiques ◦ Ajouter les variables xk_sp et yk_sp_ti dans la fenêtre Expressions ◦ Clic droit sur chaque variable puis cliquer sur Graph ◦ Paramétrer l'outil comme vous pouvez. Cet utilitaire étant fortement bogué, lent et peu flexible, nous nous efforcerons par la suite à l'utiliser le moins souvent possible. ◦ Valider graphiquement les vecteurs d'entrée et surtout celui de sortie (limiter le nombre d'échantillons affichés à 200). Celui-ci nous servira de vecteur étalon pour tous nos futurs développement : vecteur d'entrée (200 échantillons) vecteur de sortie (200 échantillons) 1.2. Test de performance • En vous aidant du travail préparatoire, valider l'utilisation du timer TSC puis effectuer une mesure de performance de l'algorithme développé par TI en vous aidant de la fonction standard printf. Rappelons, qu'une API d'utilisation du timer TSC est proposée par CSL. Rq : Dans le projet actuellement en cours de développement, l'utilisation de la fonction C standard printf ne pose que peu de soucis car seules certaines zones de code bien spécifiques sont en cours de test et de validation. En effet, il ne s'agit pas d'un système logiciel complet. Néanmoins, dans les années à venir, bien garder en tête que cette fonction standard est rarement optimisé pour s'exécuter sur un processeur de type MCU ou DSP et est à bannir durant vos phases de développement. Son temps d'exécution est souvent excessivement long. Par exemple, ne jamais appeler un printf depuis une ISR (Interrupt Software/Service Routine). Pour résumer, dans le domaine de l'embarqué, bannir dès que possible la fonction printf de vos développements sauf si vous développez sur processeur généraliste (GPP, SoC) sous un système d'exploitation évolué (GNU\Linux, Android …). En général, des fonctions optimisées non standards sont fournies par l'ABI, par exemple la fonction LOG_printf chez TI. - 11 - Digital Signal Processor Travaux Pratiques • Temps écoulé en nombre de cycles CPU pour l'algorithme de référence développé par TI : CPU cycles 1.3. Intégration en C canonique Jusqu'à présent, nous n'avons fait que créer un projet et appeler des fonctions développées par d'autres, en l’occurrence des ingénieurs d'application de chez Texas Instruments. Nous allons attaquer nos premières phases de développement pour à terme essayer de faire mieux que Texas. L'avenir nous dira si nous réussirons. • Sans créer de nouveau projet ni toucher au code précédemment écrit, rajouter un test de performance pour l'algorithme de filtrage fir_sp. Inclure le fichier /dsp/c6678/firlib/src/fir_sp.c au projet puis le modifier. Cet algorithme est une implémentation standard C99 canonique (sans optimisation) d'un produit scalaire. Il nous servira quant à lui de référence dans nos futures phases d'optimisation. Ajouter le source au projet puis compléter la définition de la fonction en vous aidant du travail préparatoire. S'assurer de la bonne compilation et exécution du projet. Valider également avec l'outil Graph le vecteur de Sortie. Le vecteur de sortie sera sauvé dans le tableau yk_sp. • L'outil d'analyse graphique Graph permet-il une validation rigoureuse du vecteur de sortie ? Justifier votre réponse. • Instrumenter le code afin de réaliser un mesure de performance. En vous aidant des annexes, configurer le projet en mode Release puis effectuer le test (debug retiré et options d'optimisation levées). Noter le temps écoulé en nombre de cycles CPU pour l'algorithme de référence FIR en C canonique : CPU cycles • Modifier maintenant votre projet de façon à respecter l'affichage suivant : - 12 - Digital Signal Processor Travaux Pratiques 2. AUTOMATISATION DES TESTS Travail préparatoire • 1. (3pt) En utilisant des fonctions standards du C, proposer le code d'une fonction réalisant la comparaison terme à terme de chaque élément de deux tableaux de même taille. Cette fonction retournera par pointeur le pourcentage d'erreur maximum entre chaque élément ainsi que ''1'' par valeur de retour si les deux vecteurs sont égaux et ''0'' sinon. Boolean compare( float *error_percent, float *src1, float *src2, int size); • 2. (3,5pt) Nous pouvons observer, ci-dessous, le prototype de la fonction ptFunction ayant pour tâche d'assurer les futurs tests de performance des différents algorithmes de filtrage optimisés. Nous pouvons constater que l'argument est un pointeur de fonction. La fonction réellement implémentée dans la trame de TP aura un prototype légèrement différent, mais utilisera également un pointeur de fonction. Le but de cette fonction est de réaliser une mesure de performance par indirection de pointeur. Dans un premier, l'affichage sera réalisé dans la fonction (fait dans le main par la suite). void ptFunction( void (*fir_fct) ( const float * restrict, const float * restrict, float * restrict, int, int) ); \ \ \ \ En vous aidant d'internet, écrire l'appel de la procédure ptFunction en invoquant la fonction fir_sp déjà définie. Proposer également une implémentation de la fonction ptFunction afin qu'elle puisse invoquer la procédure dont le pointeur a été passé en argument. Dans notre cas, la fonction fir_sp avec les arguments associés (cf. partie 1 – algorithme de référence). • 3. (1,5pt) Si nous les rencontrons dans un fichier source .c, quel est le rôle des directives préprocesseur #if en #endif ? Donner des exemples d'utilisation de ces directives. • 4. (2pt) Nous pouvons observer ci-dessous l'implémentation Matlab du code assurant la génération des vecteurs d'entrée (somme de 2 sinusoïdes à 100Hz et 1KHz et d'amplitude 1) Fs = 10000; F2 = 1000; F1 = 100; Ts = 1/Fs; XK_LENGTH = 2048; %XK_LENGTH = 1048576; t=0 : Ts : (XK_LENGTH-1)*Ts; % sample frequency % frequency harmonic n°2 (input vector) % frequency harmonic n°1 (input vector) % sample period % 2K/8Kb samples : length of input temporal vector % 1M/4Mb samples : length of input temporal vector % time vector %*** INPUT ARRAY GENERATION xk = single(sin(2*pi*F1*t) + sin(2*pi*F2*t)); Écrire une boucle en langage C assurant l'initialisation d'un tableau xk[XK_LENGTH] avec une somme de deux sinusoïdes comme nous pouvons l'observer dans le code Matlab cidessus. - 13 - Digital Signal Processor Travaux Pratiques 2. AUTOMATISATION DES TESTS Travail en séance Cette partie est essentielle et va nous permettre de gagner un temps non négligeable durant nos futures phases de développement. Nous avons pu constater dans la partie précédente que la validation des données de sortie d'algorithmes n'est pas forcément triviale, surtout dans le cas de larges vecteurs de données. Une validation manuelle terme à terme faite par le développeur n'est pas concevable dans un contexte industriel. Le temps ingénieur étant probablement la ressource la plus précieuse dans une optique de conduite et de suivi de projet. Les exercices qui vont suivre seront donc découpés en trois grandes parties dans une optique d'optimiser notre temps de développement durant nos travaux futurs : • • • Écriture d'un test de conformité Écriture d'un test de performance Optimisation du temps ingénieur durant les phases de test 2.1. Test de conformité Nous allons débuter par le développement d'un test de conformité assurant la validation de nos futurs vecteurs de sortie. Nous utiliserons toujours pour référence le vecteur de sortie de l'algorithme de Texas Instruments (algorithme DSPF_sp_fir_gen et vecteur de sortie yk_sp_ti). Ce test est très générique et peut très bien être réutilisé dans un cadre plus large pour d'autres algorithmes à tester. • Dans le fichier d'en-tête firtest.h présent dans le répertoire /dsp/c6678/test/h/ nous pouvons observer la déclaration d'un type structure TestSystem_obj spécifique à notre projet. Pour notre projet, il s'agit d'un objet logiciel mis à jour notamment par la fonction firtest_sys et sauvegardant temporairement les sorties de nos différents tests de conformité. Observer son contenu et le commenter exhaustivement ci-dessous. Ne pas hésiter à interroger l'enseignant encadrant si vous avez un doute : typedef struct { char uint64_t float float } TestSystem_obj; • error_status[4]; error_samples; error_percent; error_margin; // string to specify validity of test (''OK'' or ''NOK'') // number of false samples // maximum percentage error // maximum tolerated margin. Have to be initialized // conformity test object Ajouter au projet le fichier source firtest_sys.c et s'assurer de sa bonne compilation. - 14 - Digital Signal Processor Travaux Pratiques • Sachant que le champ error_margin est à initialiser au début du lancement du programme (erreur tolérée pour notre procédure de test) et en vous aidant du travail préparatoire, écrire et valider le code implémentant la fonction firtest_sys. Cette fonction est découpée en trois parties : ◦ initialisation de la boucle de test ◦ boucle de test assurant une comparaison terme à terme des différents éléments des vecteurs d'entrée, capturant le pourcentage d'erreur maximale mesurée durant la totalité du test et comptant le nombre d'échantillons ayant une erreur supérieure à la marge fixée dans le cahier des charges du projet. Dans notre cas, nous tolérerons une erreur strictement inférieure à 1%. ◦ Mise à jour de l'objet logiciel capturant les informations de sortie du test de conformité. Nous pourrons enfin quitter la procédure en retournant un booléen indiquant la validité du test. Rq : Comme dans beaucoup de développement logiciel dans l'embarqué, il est courant d'utiliser la valeur de retour d'une fonction comme indicateur de sa bonne exécution (valeur booléenne). De même, comme pour une approche orientée objet, notre procédure de test n'a pour rôle que d'appliquer un traitement à l'objet d'entrée. Aucun affichage ne doit être fait dans cette fonction. Tous les affichages et interfaces développeur seront gérées depuis la fonction main. De plus, si votre implémentation respecte des standards du C (exemple C99), ceci permet d'assurer une portabilité pour d'éventuelles réutilisations dans d'autres projets. • Modifier maintenant votre projet de façon à respecter l'affichage suivant. L'affichage des performances de l'algorithme en cours de test ne devra se faire que si la validité du vecteur de sortie associé est actée. - 15 - Digital Signal Processor Travaux Pratiques 2.2. Test de performance Nous allons maintenant nous attaquer à l'écriture d'un test de performance générique, pouvant être appliqué à la totalité des algorithmes de filtrage FIR sous tests mais également aux futurs modèles mémoires déployés dans la suite de la trame de travaux pratiques. • Dans le fichier d'en-tête firtest.h présent dans le répertoire /dsp/c6678/test/h/ nous pouvons observer la déclaration d'un type structure TestPerf_obj spécifique à notre projet. Pour notre projet, il s'agit d'un objet logiciel mis à jour notamment par la fonction firtest_perf et sauvegardant temporairement les sorties de nos différents tests de performance. Observer son contenu et le commenter exhaustivement ci-dessous. Ne pas hésiter à interroger l'enseignant encadrant si vous avez un doute : typedef struct { uint32_t CSL_Uint64 float32_t float32_t } TestPerf_obj; perf_rep; perf_nbcycles; perf_usertime_ms; perf_macs; // number of test repetitions // duration in number of CPU cycles // duration in ms // algorithm performance in MAC per CPU cycle // performance test object Rq : Pour information, dans le domaine l'embarqué appliqué au traitement numérique du signal, l'un des principaux indicateurs de performance d'un algorithme est donné par son nombre de MAC's par cycle CPU (ou GMACS, Giga Multiply Accumulate per Second). Cet indicateur étant lié à la complexité mathématique de l'algorithme (cf. enseignement d'algorithmique). Dans le cas de notre architecture, un DSP de la famille C6600 peut offrir jusqu'à 8 MAC/cy par cœur (soit un maximum de 11,2 GMACS avec une horloge à 1,4GHz par cœur et 89,6 GMACS pour le processeur). • Que vaut la complexité en MACS de notre algorithme implémentant un produit scalaire (filtre FIR) ? • Pourquoi, dans une optique de benchmarking, est-il intéressant de répéter plusieurs fois l'exécution de l'algorithme sous test puis de moyenner les résultats de sortie (uniquement sur architecture déterministe) ? Nous constaterons qu'il sera plus rigoureux de prendre la valeur maximale sur architecture non déterministe (GPP, AP à CPU superscalaires ou sur GPU). - 16 - Digital Signal Processor Travaux Pratiques • Ajouter au projet le fichier source firtest_perf.c et s'assurer de sa bonne compilation. • En vous aidant du travail préparatoire, écrire et valider le code implémentant la fonction firtest_perf. Dans un premier temps, nous ne tiendrons pas compte du paramètre memoryModel qui sera abordé par la suite. Le paramètre output permet quant à lui d'écraser le vecteur précédemment sous test par le vecteur de sortie en cours de validation. Cette fonction peut être découpée dans deux parties : ◦ Boucle fixant le nombre d'appels de l'algorithme en cours de test. La mesure et l'accumulation des temps d'exécution se feront dans la boucle. ◦ Mise à jour de l'objet logiciel capturant les informations de sortie du test de performance. /** * @brief performance test for FIR algorithms library * @param benchmark statistics about current algorithm performance * @param memoryModel memory model architecture. Accepted values : * @li UMA_L2CACHE_L1DCACHE * @li UMA_L2SRAM_L1DCACHE * @li UMA_L2SRAM_L1DSRAM * @param output output vector to overwrite * @param fir_fct pointer to function under test */ void firtest_perf( TestPerf_obj *benchmark, uint8_t memoryModel, float * restrict output, void (*fir_fct) ( const float * restrict, const float * restrict, float * restrict, int, int) ); • Modifier maintenant votre projet de façon à respecter l'affichage suivant. - 17 - Digital Signal Processor Travaux Pratiques 2.3. Temps de compilation et empreinte mémoire Afin d'optimiser le temps de compilation de notre projet ainsi que l'empreinte mémoire du code une fois chargé en mémoire interne du processeur, nous allons appliquer une astuce excessivement simple. Cette étape peut jouer un léger rôle dans les phases de benchmarking. En effet, en minimisant l'empreinte mémoire du code, nous pouvons améliorer l'usage du cache L1P (Level 1 Cache Program), limité à 32Ko sur notre architecture. Cette partie reste non critique dans le cadre de la trame de travaux pratiques compte-tenu de l'empreinte mémoire globale du projet. • En vous aidant du travail préparatoire, encapsuler la procédure de test de l'algorithme fir_sp entre deux directives de pré-compilation #if et #endif (printf's, test de performance et test de conformité). Nous pourrons ainsi ajouter ou retirer du code à la compilation en utilisant les macros ci-dessous présentes dans le fichier firtest.h. Dans un premier temps, nous n'utiliserons que la macro TEST_FIR_SP : /* algorithms and memory model under test */ #define TEST_FIR_SP #define TEST_FIR_SP_R4 #define TEST_FIR_SP_OPT_R4 #define TEST_FIR_ASM #define TEST_FIR_ASM_MANUAL #define TEST_FIR_ASM_PIPE #define TEST_FIR_ASM_R4 #define TEST_FIR_L2SRAM_L1DCACHE #define TEST_FIR_L2SRAM_L1DSRAM #define TEST_FIR_L2SRAM_L1DIDMA #define TEST_FIR_L2EDMA_L1DIDMA 1 0 0 0 0 0 0 0 0 0 0 Rq : Cette technique est très couramment utilisée dans le monde de l'embarqué dès que nous avons à manipuler des bibliothèques ou outils logiciel de taille importante (bibliothèques réseau, graphique, USB, systèmes de fichiers … ou également des systèmes d'exploitation temps réel ...). En effet, par exemple nous pouvons très bien utiliser une librairie réseau offrant un très grand nombre de services (IP, ICMP, UDP, TCP, HTTP, SMTP, FTP, DNS ...) pour n'en exploiter qu'une poignée (par exemple ICMP, IP, TCP). Auquel cas, inutile d'embarquer sur le processeur tous les autres services. La bibliothèque est alors fournie avec un header incluant des macros de configuration des services strictement nécessaires. Nous pouvons ainsi, après compilation, minimiser l'empreinte mémoire globale de notre projet dans une optique de réduction des coûts liés à la technologie processeur déployée. N'oublions pas que le domaine des systèmes embarqués, contrairement au monde des ordinateurs, nous travaillons le plus souvent en milieu contraint (ressources CPU et ressources mémoire). - 18 - Digital Signal Processor Travaux Pratiques 2.4. Temps de programmation Cette étape, quant à elle, est relativement importante. Nous allons effectuer un test simple afin de bien en comprendre l'importance. L'objectif premier étant de minimiser notre temps de développement global en jouant sur le temps de chargement du programme via la sonde de programmation. Rappelons également que nous utilisons la sonde de programmation XDS100, modèle bas de gamme porté directement sur la plateforme de développement. A titre indicatif, le coût moyen d'une sonde XDS560 (haut de gamme) en fonction du distributeur peut varier entre 1000€ et 2500€. De plus, si nous souhaitons l'interfacer avec l'IDE, nous devons alors payer la licence de celui-ci (1000-3000€). • Modifier l'inclusion du fichier d'en-tête xk_sp_8Kb.h (vecteur de 8Ko soit 2048 échantillons d'entrée au format flottant simple précision IEEE754) afin d'appeler le header xk_sp_256Kb.h (vecteur de 256Ko soit 64K échantillons d'entrée au format flottant simple précision IEEE754) également présent dans le répertoire /dsp/c6678/test/h/. Ce fichier a également été créé depuis l'environnement de prototypage Matlab/Simulink. Compiler puis expliquer le temps passé à attendre la fin de la programmation du processeur. Rq : Pour information, il faut savoir que dans le domaine des applications radar, les vecteurs d'entrée ont le plus souvent une taille de plusieurs Mo. Il en est de même pour le domaine du traitement d'image pour lequel ce processeur est également spécialisé. • Ajouter au projet le fichier source firtest_init.c et s'assurer de sa bonne compilation. • Intégrer les initialisations actuellement faites au début du main dans la fonction firtest_init (TSC timer, erreur tolérée pour le test de conformité et nombre d'exécution de l'algorithme sous test) puis s'assurer de la bonne compilation et du bon fonctionnement du projet. • Maintenant, nous n'utiliserons le vecteur d'entrée sous forme d'une allocation statique préinitialisée, mais sous forme d'un vecteur vide initialisé au démarrage de l'application par le processeur lui-même. En vous aidant du travail préparatoire, retirer les appels des fichiers d'en-têtes xk_sp_XXb.h du projet, déclarer une variable globale de type tableau nommée xk_sp puis assurer son initialisation depuis la fonction firtest_init. Compiler et valider graphiquement (outil Graph) la forme d'onde et échantillons du vecteur d'entrée. #include <firtest.h> #include <a_sp.h> /* input and ouputs vectors */ float32_t xk_sp[XK_LENGTH]; - 19 - Digital Signal Processor Travaux Pratiques Voilà, à ce stade là de la trame de travaux pratiques, nous venons de terminer les phases de développement les moins intéressantes mais qui restent néanmoins nécessaires. Nous commençons à maîtriser l'environnement de développement, les procédures de test sont écrites, automatisées et intégrées au projet. Nous sommes maintenant prêt à attaquer le vif du sujet, l'optimisation d'algorithme architecture dépendante. • Valider une dernière fois l'interface offerte par notre projet de test en respectant l'affichage cidessous ainsi que les règles de codage imposées : - 20 - Digital Signal Processor Travaux Pratiques 3. PROGRAMMATION VECTORIELLE Travail préparatoire • 1. (4,5pt) Sachant que les paramètres d'entrée de toute fonction assembleur appelée depuis un fichier source C sont respectivement passés par les registres A4, B4, A6, B6, A8 … etc sous notre chaîne de compilation et architecture, écrire en assembleur canonique C6600 (sans optimisation) le code équivalent à la fonction C canonique de filtrage fir_sp. ; file firlib\src\fir_sp_asm.s ; brief FIR filtering function in canonical C6600 assembler (single precision IEEE754) ; author ; date .global fir_sp_asm ; C prototype : match registers : ; ; void fir_sp_asm ( const float * restrict xk, -> A4 ; const float * restrict a, -> B4 ; float * restrict yk, -> A6 ; int na, -> B6 ; int nyk); -> A8 fir_sp_asm: ; user code B B3 NOP 5 ; end of fir_sp_asm procedure • 2. (5pt) Proposer une implémentation C pour la fonction de filtrage fir_sp_r4 effectuant un déroulement de boucle d'un facteur 4 sur les deux boucles internes de l'algorithme de filtrage fir_sp. Attention, ce travail est légèrement plus complexe qu'il n'y parait ! void fir_sp_r4 ( const float * restrict xk, \ const float * restrict a, \ float * restrict yk, \ int na, \ int nyk){ int i, j; float acc1, acc2, acc3, acc4; float a0, a1, a2, a3; float xk0, xk1, xk2, xk3, xk4, xk5, xk6; /* input array loop */ for (i=0; i<nyk; i+=4) { /* user code */ /* FIR filter algorithm - dot product */ for (j=0; j<na; j+=4){ /* user code */ } /* user code */ } } - 21 - Digital Signal Processor Travaux Pratiques • 3. (0,5pt) Nous constaterons dans la suite de la trame de travaux pratiques que l'implémentation précédemment écrite est bien plus performante qu'une implémentation en C canonique standard sans déroulement de boucle. Néanmoins, quels inconvénients et limitations amènent cette implémentation ? - 22 - Digital Signal Processor Travaux Pratiques 3. PROGRAMMATION VECTORIELLE Travail en séance Nous allons maintenant attaquer les premières phases d'optimisation mono-cœur architecture dépendante de notre projet (DSP C6600 de Texas Instruments). Les exercices qui suivent peuvent être découpés en trois grande parties : • • • • Optimisations assembleur C6600 Optimisations en langage C Comparatif architectures IA-64 extensions SSE4.1 Ajouter au répertoire logique src du projet un sous répertoire firlib puis y ajouter les sources élèves des algorithmes de filtrage à développer présents dans /dsp/c6678/firlib/src/. S'assurer de la bonne compilation du projet. 3.1. Assembleur canonique C6600 • Sélectionner puis copier à la suite du main le code encapsulé entre les directives préprocesseur #if ( TEST_FIR_SP !=0 ) et #endif comprises. Modifier la section de code copiée ainsi que les commentaires afin d'assurer les tests de la fonction assembleur canonique fir_sp_asm. S'assurer de la bonne compilation du projet. Cette étape sera à répéter durant nos développements futurs pour chaque implémentation avec optimisation. Nous pourrons ainsi nous focaliser sur les stratégies et problématiques d'optimisation et non plus sur les procédures de test et de benchmarking. - 23 - Digital Signal Processor Travaux Pratiques • En vous aidant du travail préparatoire, implémenter le code de la fonction fir_sp_asm puis valider son fonctionnement. Voici ci-dessous quelques conseils afin de faciliter votre développement. Dans tous les cas, il vous faudra impérativement travailler en mode Debug. Depuis l'espace de travail CCS Debug, utiliser la fenêtre Registers afin d'observer en temps réel le contenu des registres CPU. Placer des points d'arrêts (double clic dans la fenêtre d'édition sur le numéro de la ligne souhaitée) judicieusement dans votre code puis exécuter pas à pas le programme (CCS Debug > Run > Assembly Step). Maintenant, voici comment écrire ou tester efficacement la procédure : ◦ étape n°1 : s'assurer que l'appel et le retour de la procédure se déroulent bien puis vérifier les valeurs des paramètres d'entrée de la fonction. ◦ étape n°2 : implémenter la boucle vide avec la condition de sortie associée. S'assurer du bon nombre d'itérations et du fait de quitter celle-ci. ◦ étape n°3 : dans la boucle, vérifier les premiers chargements de données de la mémoire vers le cœur ainsi que la validité des données pré-chargées. ◦ étape n°4 : écrire le code correspondant au produit scalaire dans le cœur de la boucle. Cette partie est plus complexe à tester et à valider. ◦ remarque : de façon générale, toujours tester les valeurs limites : condition de sortie de boucle, débordement de tableau, valeurs de pointeurs mémoire … • Après validation, lancer une exécution en mode Release (mode Debug coupé et options d'optimisations levées), puis reporter les résultats des tests dans le tableau d'analyse comparative à la fin de ce support de travaux pratiques. • Valider l'affichage dans la console de sortie de l'IDE : - 24 - Digital Signal Processor Travaux Pratiques 3.2. Assembleur VLIW C6600 Contrairement aux processeurs superscalaires, les architectures VLIW (Very Long Instruction Word) et EPIC (Explicitly Parallel Instruction Computing) ont pour point commun d'avoir un code outof-order en mémoire (OOO ou dans le désordre) mais offrant une exécution in-order (exécution et sortie du pipeline dans l'ordre). Dans cet exercice, nous allons jouer sur ce point sans pour autant utiliser d'instructions vectorielles ni tenter d'avoir une utilisation optimale du pipeline. En effet, nous n'utiliserons que des instructions scalaires. Rappelons le principe de ce type d'optimisation, uniquement applicable sur architecture CPU VLIW et EPIC. L'exemple qui suit présente un avancement de branche d'exécution sur architecture C6600. Le code est alors dans le désordre en mémoire et pourtant les deux files d'instructions cidessous réalisent le même traitement : Canonical C6600 assembly instructions in-order in memory (processing time : 14 CPU cycles) MPYSP A3,B9,A17 NOP 3 FADDSP A17,A5,A5 NOP 2 [A1] SUB A1,1,A1 [A1] B L2 NOP 5 VLIW C6600 assembly instructions out-of-order in memory (processing time : 7 CPU cycles) MPYSP || [A1] SUB [A1] B NOP FADDSP NOP A3,B9,A17 ; instructions bundle A1,1,A1 L2 2 A17,A5,A5 2 • Comme pour l'algorithme fir_sp_asm, copier un bloc de code assurant les tests de conformité et de performance d'un algorithme puis le modifier de façon à obtenir une section de test pour l'algorithme fir_sp_asm_vliw. • Implémenter le code de la fonction fir_sp_asm_vliw, effectuant une optimisation manuelle par avancement de branches d'exécution de l'algorithme fir_sp_asm puis valider son fonctionnement. Comme pour chaque phase de développement, bien travailler en mode Debug. • Après validation, lancer une exécution en mode Release (mode Debug coupé et options d'optimisations levées), puis reporter les résultats des tests dans le tableau d'analyse comparative à la fin de ce support de travaux pratiques. - 25 - Digital Signal Processor Travaux Pratiques 3.3. Pipelining logiciel assembleur C6600 Dans cet exercice, nous allons nous efforcer d'obtenir une utilisation optimale du pipeline logiciel pour l'algorithme écrit en assembleur canonique. Cet exercice consiste à jouer sur le nombre d'unités d'exécution du cœur en essayant d'utiliser le CPU au maximum de son potentiel théorique. Dans notre cas, chaque cœur possédant 8 unités d'exécution, toutes capables de travailler en parallèle, nous chercherons à obtenir un maximum de 8 instructions exécutées par cycle CPU. Nous allons donc nous intéresser au parallélisme d'instructions. Pour notre algorithme, sans déroulement de boucle (vectorisation des données), le facteur optimal d'optimisation en terme d'accélération sera obtenu à travers cet exercice. Observons de façon graphique dans un tableau l'architecture du code à implémenter. Dans la table de programmation ci-dessous, vous trouverez une implémentation de la boucle interne, la boucle externe restant inchangée. Le prolog est exécuté une seule fois, la boucle kernel autant de fois qu'il y a d'itérations de boucles en retranchant la profondeur du prolog et l'epilog sera également exécuté qu'une seule fois. L'allocation des registres reste libre dans le cadre de cet exercice. Unités d'Exécution (occupation des pipelines hardware et software) .D1 P R O L O G K E R N E L E P I L O G .M1 .S1 .L1 .L2 .S2 .M2 .D2 LDW LDW NOP NOP NOP NOP NOP NOP NOP NOP LDW MPYSP LDW NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP LDW MPYSP BDEC FADDSP LDW NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP MPYSP FADDSP NOP NOP NOP NOP NOP NOP FADDSP NOP NOP - 26 - Digital Signal Processor Travaux Pratiques • Comme pour les algorithmes précédents, copier un bloc de code assurant les tests de conformité et de performance d'un algorithme puis le modifier de façon à obtenir une section de test pour l'algorithme fir_sp_asm_softPipeline. • Implémenter le code de la fonction fir_sp_asm_softPipeline, effectuant la technique de pilpelining logiciel vu en cours sur l'algorithme fir_sp_asm puis valider son fonctionnement. Comme pour chaque phase de développement, bien travailler en mode Debug. Voici ci-dessous quelques conseils afin de faciliter votre développement : • ◦ étape n°1 : la profondeur de la boucle kernel interne doit posséder une taille supérieure ou égale au nombre de cycles CPU nécessaires à l'exécution de l'instruction B (branch). ◦ étape n°2 : tester la condition de sortie de la boucle ainsi que le bon nombre d'itérations. ◦ étape n°3 : vérifier les données préchargées depuis la mémoire par les instructions de chargement sur plusieurs itérations. Après validation, lancer une exécution en mode Release (mode Debug coupé et options d'optimisations levées), puis reporter les résultats des tests dans le tableau d'analyse comparative à la fin de ce support de travaux pratiques. Rq : • Les NOP's (delay slot) peuvent être vus comme des espaces libres dans lesquels peuvent être insérer des instructions à exécuter en parallèle. Dans une optique de déroulement de boucle (vu dans les parties qui suivent), nous pourrons alors être amenés à charger de façon conséquente la boucle kernel actuellement sous exploitée. • sans nous en rendre compte, au fil des exercices en assembleur précédents et actuel nous nous efforçons d'entrer dans la tête de notre compilateur C. En effet, lorsque les options d'optimisation sont levées, il tente d'appliquer un maximum des techniques d'accélération que nous sommes en train de découvrir. Sans pour autant obtenir une implémentation optimale. Nous constaterons par la suite que si nous souhaitons obtenir des facteurs d'accélération optimaux en restant à l'étage du langage C, il nous faudra effectuer du refactoring de code ainsi qu'aiguiller au maximum la chaîne de compilation. Nous perdrons alors en portabilité de code. - 27 - Digital Signal Processor Travaux Pratiques 3.4. Assembleur vectoriel C6600 Durant cet exercice, nous allons nous intéresser au parallélisme des données en utilisant un maximum d'instructions vectorielles SIMD (Single Instruction Multiple Data) permettant un traitement des opérandes à l'étage assembleur par vecteur de données (2, 4, 8 …) et non plus par données scalaires (une à une). De plus, ceci nous permettra de minimiser l'usage du pipeline logiciel d'instructions et donc le nombre d'unités d'exécution utilisées. Laissant ainsi la place potentielle à d'autres instructions.Prenons l'exemple d'une même section de code avec des instructions scalaires et vectorielles. Dans les deux cas, le traitement réalisé est le même : C6600 scalars assembly instructions (processing time : 18 CPU cycles, 4 instructions) LDW NOP LDW NOP MPYSP NOP MPYSP NOP • C6600 vectorials assembly instructions (processing time : 9 CPU cycles, 2 instructions) *A19++, A25 4 *A19++, A24 4 A25, B23, B5 3 A24, B22, B4 3 LDDW NOP DMPYSP NOP *A19++, A25:A24 4 A25:A24, B23:B22, B5:B4 3 Comme pour les algorithmes précédents, copier un bloc de code assurant les tests de conformité et de performance d'un algorithme puis le modifier de façon à obtenir une section de test pour l'algorithme fir_sp_asm_r4. Nous allons maintenant effectuer un déroulement d'un facteur 4 de la boucle interne de l'algorithme (boucle externe inchangée). Voici ci-dessous une implémentation C du code assembleur à implémenter : void fir_sp_r14 ( const float * restrict xk, const float * restrict a, float * restrict yk, int na, int nyk){ int i, j; float xk0, xk1, xk2, xk3; float a0, a1, a2, a3; float acc0, acc1, acc2, acc3; \ \ \ \ /* input array loop */ for (i=0; i<nyk; i++) { acc0 = 0.0; acc1 = 0.0; acc2 = 0.0; acc3 = 0.0; /* FIR filter algorithm - dot product */ for (j=0; j<na; j+=4){ a0 = a[j]; a1 = a[j+1]; a2 = a[j+2]; a3 = a[j+3]; xk0 = xk[j+i]; - 28 - Digital Signal Processor Travaux Pratiques xk1 = xk[j+i+1]; xk2 = xk[j+i+2]; xk3 = xk[j+i+3]; acc0 += a0*xk0; acc1 += a1*xk1; acc2 += a2*xk2; acc3 += a3*xk3; } } yk[i] = acc0 + acc1 + acc2 + acc3; } Afin d'implémenter de façon optimale cet algorithme, nous allons utiliser des instructions vectorielles. En s'aidant des annexes ou de la documentation technique SPRUGH7, se documenter sur les instructions suivantes : • • • • • LDDW LDNDW DMPYSP DADDSP En quoi diffère l'instruction LDDW de l'instruction LDNDW ? Rq : Il faut savoir que notre chaîne de compilation réalise par défaut, notamment sur la pile, des alignements mémoire sur les données (adresses des données multiples de N octets, 8o dans notre cas). Observons ci-dessous un exemple de défaut d'alignement mémoire et essayons de comprendre la nécessite d'aligner nos données en mémoire dans le cas d'utilisation de fonctions intrinsèques. Un alignement mémoire garanti une utilisation optimale des accès aux ressources mais ajoute néanmoins de nombreux espaces/slots vide en mémoire cache et en RAM. Dans le cas présent, cette technique accélère donc l'exécution du code mais néanmoins dégrade l'occupation des ressources mémoire. Rappelons également que le taille des chemins/paths d'un CPU vers la mémoire L1 donnée vaut 64 bits soit 8 octets sur notre architecture. Prenons ci-dessous un exemple de jeu de données, les premières non-alignées et les secondes alignées modulo 8 octets : short foo ; float bar[3] ; - 29 - Digital Signal Processor Travaux Pratiques • Implémenter le code de la fonction fir_sp_asm_r4, effectuant un déroulement de boucle d'un facteur 4 sur la boucle interne de l'algorithme puis valider son fonctionnement. Comme pour chaque phase de développement, bien travailler en mode Debug. En observant le code élève fourni, nous pouvons constater un traitement étrange à l'entrée et à la sortie de la procédure (cf. ci-dessous). En vous aidant de la documentation technique SPRU187V – Optimizing Compiler, Chapter 7.3 Register Conventions, Table 7-2. Register Usage dédiée aux mécanismes d'optimisation sur architectures C6000, comprendre et expliquer le rôle des sections de code rajoutées. save_context .macro rsp ; save core working registers context on the top of stack MV B15,rsp ; save Stack Pointer STDW B15:B14,*rsp--[1] STDW B13:B12,*rsp--[1] STDW B11:B10,*rsp--[1] STDW A15:A14,*rsp--[1] STDW A13:A12,*rsp--[1] STDW A11:A10,*rsp--[1] MVC ILC,B15 MVC RILC,B14 STDW B15:B14,*rsp--[1] ; don't use rsp register in current assembly procedure .endm restore_context .macro rsp ; restore core working registers context from the top of stack LDDW *++rsp[1],B15:B14 MVC B14,RILC MVC B15,ILC LDDW *++rsp[1],A11:A10 LDDW *++rsp[1],A13:A12 LDDW *++rsp[1],A15:A14 LDDW *++rsp[1],B11:B10 LDDW *++rsp[1],B13:B12 LDDW *++rsp[1],B15:B14 MV rsp,B15 ; restore Stack Pointer NOP 3 .endm fir_sp_asm_r4: ; save core registers context save_context A3 ; user code ; restore core registers context and leave procedure restore_context A3 B B3 NOP 5 La directive préprocesseur assembleur .macro s'utilise comme une macro fonction en langage C (macro avec paramètres). Par exemple : #define mul(x, y) x * y // mul(1,4) équivalent à 1*4 avant compilation - 30 - Digital Signal Processor Travaux Pratiques • Quelle limitation amène l'implémentation de notre algorithme de filtrage ? • Durant l'implémentation de notre algorithme assembleur en base 4 (radix 4), quels sont les deux registres de travail du CPU que nous ne devons en aucun cas utiliser durant la totalité du traitement de la fonction ? • Après validation, lancer une exécution en mode Release (mode Debug coupé et options d'optimisations levées), puis reporter les résultats des tests dans le tableau d'analyse comparative à la fin de ce support de travaux pratiques. Rq : voilà, à ce stade là de la formation en systèmes embarqués, vous devez être apte à porter un esprit critique sur les performances annoncées d'un processeur et ne plus regarder que sa seule fréquence de travail. Un processeur, même mono-cœur, cadencé à 500MHz peut très bien offrir des performances supérieures à un processeur multi-coeurs cadencé à 2GHz. Pour une architecture mono-cœur, il vous faut notamment tenir compte : • • • de sa faculté à traiter des instructions en parallèle (nombre d'unités d'exécution pouvant travailler simultanément) de sa faculté à traiter les données par paquet (largeur des paths CPU, largeur des registres de travail et jeu d'instructions vectorielles) de la technologie de son pipeline matériel (superscalaire, VLIW ou EPIC), voire pour les plus curieux, des stratégies d'accélération interne au pipeline (le plus souvent aux étages de décodage et d'exécution) - 31 - Digital Signal Processor Travaux Pratiques 3.5. Déroulement de boucle en C A partir de maintenant et jusqu'à la fin de votre cursus, j'ai fini de vous torturer avec du développement en assembleur (en TP en tous cas). Même si ces exercices vous ont pour certains semblé inutiles, ils sont nécessaires à la compréhension fine d'une architecture matérielle donnée et des mécanismes d'accélération pouvant être utilisés. Mais se rassurer, notamment pour ceux ayant bien assimilé, en se disant que vous êtes en train de développer sur l'une des architectures de cœur pouvant être les plus complexes et performantes au monde. Tout dépend de notre degré de compréhension de l'architecture et de notre capacité à l'exploiter. A partir de maintenant, la totalité de nos développements se feront en langage C. Nous découvrirons dans un premier temps des stratégies simples d'optimisation portables et applicables à beaucoup de processeurs vectoriels. Nous conclurons par une stratégie d'accélération optimale pour notre architecture mais amenant son lot d'inconvénients. Le principe du déroulement de boucle consiste, sans mécanisme d'optimisation particulier, de ré-implémenter en C canonique notre algorithme en doublant/quadruplant/etc le nombre de chargement mémoire, d'opérations arithmétiques, etc dans nos boucles. Prenons un exemple de déroulement de boucle d'un facteur 2, souvent nommé unrolling radix 2 (déroulement en base 2) : Canonical C /* * FIR filtering function in canonical C */ void fir_sp ( const float * restrict xk, const float * restrict a, float * restrict pYk, int nbCoeff){ int i; float yk_tmp = 0.0f; Unrolling radix 2 \ \ \ /* FIR filter algorithm - dot product */ for (i=0; i<nbCoeff; i++){ yk_tmp += a[i]*xk[i]; } } /* * FIR filtering function with radix 2 unrolling */ void fir_sp_r2 ( const float * restrict xk, \ const float * restrict a, \ float * restrict pYk, \ int nbCoeff){ int i; float xk_tmp1, xk_tmp2; float a_tmp1, a_tmp2; float mul1, mul2; float add1 = 0.0f, add2 = 0.0f; /* FIR filter algorithm - dot product */ for (i=0; i<nbCoeff; i+=2){ xk_tmp1 = xk[i]; xk_tmp2 = xk[i+1]; a_tmp1 = a[i]; a_tmp2 = a[i+1]; *pYk = yk_tmp; mul1 = a_tmp1 * xk_tmp1; mul2 = a_tmp2 * xk_tmp2; add1 += mul1; add2 += mul2; } *pYk = add1 + add2; } • Quelle limitation amène l'implémentation en base 2 ci-dessus ? - 32 - Digital Signal Processor Travaux Pratiques • Comme pour les algorithmes précédents, copier un bloc de code assurant les tests de conformité et de performance d'un algorithme puis le modifier de façon à obtenir une section de test pour l'algorithme fir_sp_r4. • En vous aidant du travail préparatoire, implémenter le code de la fonction fir_sp_r4, effectuant un déroulement de boucle d'un facteur 4 sur les deux boucles internes de l'algorithme puis valider son fonctionnement. Comme pour chaque phase de développement, bien travailler en mode Debug. • Après validation, lancer une exécution en mode Release (mode Debug coupé et options d'optimisations levées), puis reporter les résultats des tests dans le tableau d'analyse comparative à la fin de ce support de travaux pratiques. - 33 - Digital Signal Processor Travaux Pratiques 3.6. Vectorisation de code en C Dans cette ultime phase d'optimisation, nous allons réutiliser la totalité de nos anciens acquis afin d'obtenir, enfin, une implémentation optimale de notre algorithme de filtrage. Nous allons comprendre que les étapes précédentes, même si contre-performantes au seul regard des benchmarking, étaient nécessaires à une bonne compréhension des stratégies présentées ci-dessous. Afin, d'exploiter au mieux l'architecture matérielle parallèle, vectorielle, VLIW de chaque cœur, sans pour autant descendre à l'étage assembleur qui peut être très chronophage en temps de développement, nous allons devoir aiguiller au maximum notre chaîne de compilation en effectuant du refactoring de code. Découvrons quelques une des techniques les plus efficaces. Pour maîtriser la totalité du potentiel d'optimisation de notre chaîne de compilation, se référer au manuel SPRU187V – Optimizing Compiler proposé par Texas Instruments : • Fonctions intrinsèques ou intrinsics functions : à l'image d'une fonction inline, une fonction intrinsèque force le compilateur à générer automatiquement une séquence d'instructions. Cependant, contrairement aux fonctions inlines, le compilateur possède une connaissance poussée de la fonction intrinsèque qui lui assure une insertion optimale pour un contexte donné. Toutes les fonctions intrinsèques supportées conjointement par notre chaîne de compilation et notre architecture CPU sont énumérées dans la documentation technique SPRU187V – Optimizing Compiler, Chapter 7.5.6 Using Intrinsics to Access Assembly Language Statements Examples of C intrinsics functions for C6600 compiler and architecture C6600 assembly equivalence (automatics registers allocations by compiler) __float2_t data10 ; // __float2_t container type const float ldData[2] = {0.0f,1.0f}; data10 = _amemd8_const(&ldData[0]); ; data10 = A1:A0 ; &ldData[0] = ldData = A2 LDDW *A2++, A1:A0 __float2_t data10, data32 ; __float2_t result31_20 ; result31_20 = _daddsp(data10, data32); ; data10 = A1:A0, data32 = A3:A2, ; result31_20 = A11:A10 DADDSP A1:A0, A3:A2, A11:A10 __float2_t data10, data32 ; __float2_t result31_20 ; result31_20 = _dmpysp(data10, data32); ; data10 = A1:A0, data32 = A3:A2, ; result31_20 = A11:A10 DMPYSP A1:A0, A3:A2, A11:A10 __float2_t data10, data32 ; __float2_t data21 ; data21 = _ftod(_lof(data32), _hif(data10)); ; data32 = A3:A2, data10 = A1:A0, data21= A11:A10 MV A2, A11 MV A1, A10 __float2_t data10 ; float stData[2] ; _amemd8(&stData[0]) = data10 ; ; data10 = A1:A0 ; &stData[0] = stData = A2 STDW A1:A0, *A2++ Observons un exemple de comparaison entre une implémentation C canonique et une solution avec fonctions intrinsèques. __float2_t est un type conteneur (container type) sur 64bits et pouvant contenir 2 flottants en simple précision sur 32 bits : Canonical C example Example of C intrinsic function float data[2] = {0.0f, 1.0f}; float data0, data1 ; const float data[2] = {0.0f, 1.0f}; __float2_t data10 ; // __float2_t container type a0 = a[0]; a1 = a[1]; // LDDW assembly instruction a10 = _amemd8_const(&data[0]); // LDW assembly instruction // LDW assembly instruction - 34 - Digital Signal Processor Travaux Pratiques • Assertion : l'objectif de cette technique est de donner un maximum d'informations (garanties par le développeur) sur une variable à la chaîne de compilation afin de l'aiguiller dans ses phases d'optimisation futures : valeur minimale, variable d'une valeur toujours multiple d'une autre valeur, alignement mémoire de données pointées … Example of program with assertions Description short i ; const float data[4] = {0.0f, 1.0f, 2.0f, 3.0f} ; __float2_t acc = 0.0f ; // __float2_t container type #pragma DATA_ALIGN(data, 8); • _nassert(i >= 2); _nassert(i % 2 == 0); _nassert((int) data % 8 == 0); • • • Force l'éditeur de lien (linker) à aligner en mémoire les données du tableau data[] modulo 8 octets i est forcément supérieur ou égale à 2 i est forcément un multiple de 2 Les données pointées par a sont alignées module 8 octets for (i=0; i<4; i+=2) { acc =_daddsp (acc, _amemd8_const(&data[i]); } • Alignement mémoire : dans l'exemple ci-dessus, la directive de compilation #pragma DATA_ALIGN force l'éditeur de lien ou linker à garantir un alignement mémoire modulo 8 octets des données visées par le pointeur passé en argument. Prenons un exemple d'alignement mémoire de données modulo 8 octets sur chaîne de compilation : /* arrays alignments - CPU data path length 64bits */ #pragma DATA_ALIGN(xk_sp, 8); • Dans l'exemple de code avec assertions et alignement mémoire ci-dessus, que vaut la variable de type conteneur acc après exécution de la boucle ? • Comme pour les algorithmes précédent, copier un bloc de code assurant les tests de conformité et de performance d'un algorithme puis le modifier de façon à obtenir une section de test pour l'algorithme fir_sp_opt_r4. • En vous aidant des conseils précédemment présentés, implémenter le code de la fonction fir_sp_opt_r4, effectuant un déroulement de boucle d'un facteur 4 sur les deux boucles internes de l'algorithme et utilisant les directives de compilation précédemment citées. Valider son fonctionnement en mode Debug. • Après validation, lancer une exécution en mode Release (mode Debug coupé et options d'optimisations levées), puis reporter les résultats des tests dans le tableau d'analyse comparative à la fin de ce support de travaux pratiques. - 35 - Digital Signal Processor Travaux Pratiques • En vous aidant de l'annexe 1, création de projet, générer une bibliothèque statique nommée firlib.lib et la placer dans le répertoire /dsp/c6678/firlib/lib/. • Voilà, à ce stade nous venons d'effectuer une implémentation optimale de notre algorithme pour notre architecture. Porter un regard critique sur nos développements et citer les avantages et inconvénients de l'algorithme final. Rq : • l'exécution de l'algorithme tel qu'il a été écrit mathématiquement dans sa forme initiale ne peut pas être plus accélérée dans une optique de vectorisation en C sur notre architecture. Si nous souhaitons améliorer le temps de calcul de notre produit scalaire, il nous faudrait alors réduire sa complexité Mathématique en nombre de MACS. Les transformations à apporter ne seraient donc plus d'ordres technologiques dans un premier temps, mais d'ordre mathématique. Une fois ces transformations appliquées, nous pourrions alors repasser sur des phases d'optimisation architectures dépendantes telles que celles présentées dans cette partie. • De plus, nous venons de le constater, du moment que nous avons une connaissance poussée de l'architecture, du jeu d'instructions et de notre chaîne de compilation, développer des bibliothèques spécialisées en assembleur peut s'avérer contreproductif au regard du ratio performance/effort. Ceci sera vrai sur toute architecture VLIW, EPIC et superscalaire, du moment que l'étage d'optimisation de la toolchain reste un minimum performant. - 36 - Digital Signal Processor Travaux Pratiques 3.7. C canonique sur architecture IA-64 Dans cette dernière partie, nous allons effectuer un benchmarking entre architectures processeurs en mettant en opposition une architecture superscalaire GPP (General Purpose Processor) Intel corei7 en micro-architecture Haswell cadencée à 3,6GHz (machines de TP) et notre architecture DSP VLIW C6600 cadencée à 1,4GHz proposée par Texas Instruments. Notre première comparaison se fera sur une implémentation en C canonique. Intel corei7 - 4790 Haswell 4th gen 3,6GHz, 105W en charge prix unitaire : 325€ Texas Instruments TMS320C6678 1,4GHz, 10W en charge prix unitaire : 240€ (160€ pour 1Ku) • Sauvegarder sur clé USB les fichiers fir_sp.c, fir_sp_r4.c et firtest_sys.c (ou directement le répertoire de travail dsp) puis démarrer une session sous GNU\Linux (Ubuntu). Si nécessaire, télécharger à nouveau l'archive dsp depuis la plateforme d'enseignement. • Copier les fichiers fir_sp.c et fir_sp_r4 dans le répertoire /dsp/ia64/firlib/src/ . • Copier le fichier firtest_sys.c dans le répertoire /dsp/ia64/test/src/ . • Ouvrir un shell, se placer dans le répertoire de travail /dsp/ia64/ (IA-64 ou Intel Architecture 64bits), ouvrir le fichier README.txt puis lancer la commande de compilation depuis le répertoire courant. Interpréter et commenter la commande. gcc -Wall -march=native -std=c99 -I./test/h -I./firlib/h -o3 ./test/src/firtest_main.c ./test/src/firtest_sys.c ./firlib/src/fir_sp.c ./firlib/src/fir_sp_r4.c ./firlib/src/fir_sp_sse_r4.c -o ./build/bin/firtest -lm • Après compilation, exécuter le fichier binaire de sortie et analyser les résultats obtenus. De même, prenez le temps d'observer les quelques modifications mineures appliquées aux différents fichiers sources et fichiers d'en-têtes du projet (firlib.h, firtest.h, firtest_main.c). Rq : nous pouvons constater qu'une implémentation en C canonique, dans notre cas respectant le standard C99, garantit une portabilité du code, même sur des architectures matérielles différentes. - 37 - Digital Signal Processor Travaux Pratiques • Dans le fichier firtest_main.c, nous pouvons observer la définition d'une fonction inline, dans notre cas implémentée en assembleur 64bits x64. Cette fonction force le CPU courant à vider son pipeline matériel puis réalise une lecture de la valeur courante du core timer TSC, comme précédemment sur architecture C6600. Sur architectures compatibles x86-x64, ce timer 64bits est démarré à la mise sous tension de la machine et compte jusqu'à débordement. En vous aidant d'internet, rappeler le rôle du spécificateur inline, préciser l'intérêt et donner des exemples d'utilisation. /* * flush pipeline and read current TSC value */ inline unsigned long long __attribute__((always_inline)) rdtsc_inline() { unsigned int hi, lo; __asm__ __volatile__( // flush core pipeline "xorl %%eax, %%eax\n\t" "cpuid\n\t" // read current TSC value "rdtsc" : "=a"(lo), "=d"(hi) : // no parameters : "rbx", "rcx"); return ((unsigned long long)hi << 32ull) | (unsigned long long)lo; } • Quelles sont les principales différences entre une fonction inline et un fonction intrinsèque ? • Répéter plusieurs exécutions successives du programme de test. Que constatons-nous ? • Après une dizaine d'exécutions, reporter les valeurs maximales obtenues dans le tableau d'analyses comparatives présent en fin de cette trame de travaux pratiques. Dans un contexte réel, répéter une centaine d'exécution en chargeant notamment le cœur hyper-threadé par un autre applicatif offrirait un résultat plus significatif. Rq : voilà, nous venons d'observer un des principal problème des processeurs superscalaires (GPP/SoC/AP), leur déterminisme. En effet, contrairement aux architectures VLIW ou aux CPU à pipeline in-order classique présents sur MCU/GPU/DSP, la complexité matérielle des pipelines superscalaires amène un non-déterminisme relatif à l'exécution (dépendant de l'application ou de l'algorithme en cours de test). Ceci peut devenir gênant dans des applications temps réel où le déterminisme est le maître mot. Néanmoins, les processeurs généralistes (essentiellement architectures x64 et ARM cortex-A) restent très utilisés dans les domaines du traitement du signal, essentiellement de part le culture des développeur et l'emprise de certains acteurs sur le marché (Intel, ARM, AMD). - 38 - Digital Signal Processor Travaux Pratiques 3.8. Vectorisation SSE4.1 sur architecture IA-64 Dans cette ultime partie, nous allons comparer ce qui peut être comparable, à savoir les performances de notre code vectorisé sur architecture C6600 à du code vectorisé sur architecture IA64. Nous nous intéresserons notamment à l'extension vectorielle SIMD SSE4.1 proposée par Intel. Pour information, courant 2014, Intel proposa sur sa micro-architecture Haswell une extension DSP (Digital Signal Processing). Cette extension, nommée FMA (Fused Multiply-Add), est donc dédiée aux applications de traitement numérique du signal mais ne sera néanmoins pas abordée en travaux pratiques. Pour les plus curieux, ne pas hésiter à aller voir sur MSDN (MicroSoft Developer Network) les quelques fonctions intrinsèques proposées. Dans cette partie, il est bien entendu hors de question de découvrir en profondeur l'architecture interne des processeurs compatibles x64 (Intel, AMD ...), notamment les architectures Intel. Néanmoins, nous allons pouvoir constater que nos précédents acquis nous permettent maintenant d'effectuer de la vectorisation de code sur toute architecture vectorielle processeur. Les concepts resteront le plus souvent les mêmes. Avant tout, il faut savoir que les architectures x64 actuelles possèdent plusieurs banques de registres vectoriels : • • • registres MMX 64bits : peut par exemple contenir 2 flottants 32bits registres XMM 128bits : peut par exemple contenir 4 flottants 32bits registres YMM 256bits: peut par exemple contenir 8 flottants 32bits Dans notre cas, les instructions de l'extension SSE4.1 travaillent avec les registres XMM 128bits et sont aptes à manipuler les flottants en simple précision (IEEE754) par paquets de 4. Observons le pré-fixage et su-fixage des fonctions intrinsèques ainsi que l'un des types conteneurs associé. Le type __m128 peut donc contenir 4 flottants simple précision : Intrinsic syntax : _mm_<intrinsic_name>_<data_type> container type (4 x 32bits floating point) : __m128 https://msdn.microsoft.com/fr-fr/library/y0dh78ez(v=vs.90).aspx • Mise à zéro des éléments d'un vecteur de flottants. Pour information, ps signifie Packet Single, soit paquet de flottants en simple précision 32bits IEEE754 : __m128 float_vector ; float_vector = _mm_setzero_ps (); https://msdn.microsoft.com/fr-fr/library/tk1t2tbz(v=VS.90).aspx • Initialise les éléments d'un vecteur de flottants : __m128 float_vector ; float_vector = _mm_set_ps (1.0, 2.2, 3.0, 7.0); https://msdn.microsoft.com/fr-fr/library/afh0zf75(v=vs.90).aspx - 39 - Digital Signal Processor Travaux Pratiques • Chargement d'un vecteur de 4 flottants alignés depuis la mémoire vers un registre XMM de destination : float tab[4] = {1.0, 3.4, 5.0, 6.0} ; __m128 float_vector ; float_vector = _mm_load_ps (&tab[0]); https://msdn.microsoft.com/fr-fr/library/zzd50xxt(v=vs.90).aspx • Chargement d'un vecteur de 4 flottants non-alignés depuis la mémoire vers un registre XMM de destination : float tab[4] = {1.0, 3.4, 5.0, 6.0} ; __m128 float_vector ; float_vector = _mm_loadu_ps (&tab[0]); https://msdn.microsoft.com/fr-fr/library/x1b16s7z(v=vs.90).aspx • Produit scalaire entre deux vecteurs de 4 flottants (result = a0.x0 + a1.x1 + a2.x2 + a3.x3). Le résultat du produit scalaire étant donc un nombre scalaire, le résultat est par défaut sauvé dans les 32bits de poids faibles du vecteur de destination : __m128 float_vector_src1; __m128 float_vector_src2; __m128 float_vector_dst; float_vector_dst = _mm_dp_ps (float_vector_src1, float_vector_src2, 0xff); https://msdn.microsoft.com/fr-fr/library/bb514054(v=vs.90).aspx • Addition de deux vecteurs de 4 flottants : __m128 float_vector_src1; __m128 float_vector_src2; __m128 float_vector_dst; float_vector = _mm_add_ps (float_vector_src1, float_vector_src2); https://msdn.microsoft.com/fr-fr/library/c9848chc(v=vs.90).aspx • Sauvegarde en mémoire d'un vecteur de 4 flottants alignés : float tab[4]; __m128 float_vector ; float_vector = _mm_set_ps (1.0, 2.2, 3.0, 7.0); _mm_store_ps (&tab[0], float_vector); https://msdn.microsoft.com/fr-fr/library/s3h4ay6y(v=vs.90).aspx - 40 - Digital Signal Processor Travaux Pratiques • Ouvrir le fichier /dsp/ia64/firlib/h/firlib.h et observer les déclarations de type et d'union présentées ci-dessous. Cette union, greffée à une déclaration de type, permet, après déclaration d'une variable conteneur, d'accéder à un vecteur XMM soit élément par élément, soit directement au vecteur 128bits complet. En vous aidant d'internet, rappeler ce qu'est une union et préciser la différence avec une structure. /* project specific types */ union xmm_t { __m128 m128_vec; float m128_f32[4]; } align16_xmm; typedef union xmm_t xmm_t; • // full access to XMM vector // access to XMM vector elements Dans l'exemple ci-dessous, comment accéder au 3ième élément de la variable data_vec de type conteneur XMM 128bits. xmm_t data_vec; • Ouvrir le fichier /dsp/ia64/firlib/src/fir_sp_sse_r4.c puis implémenter le code vectorisé pour architecture x64 correspondant à la fonction fir_sp_r4. Valider son bon fonctionnement sans oublier de s'assurer de l'alignement mémoire des différents vecteurs de données traités par l'algorithme. • Répéter plusieurs exécutions puis reporter les valeurs maximales obtenues dans le tableau d'analyses comparatives présent en fin de cette trame de travaux pratiques. Rq : • nous venons d'effectuer de la vectorisation de code tout en ayant une connaissance minime de l'architecture processeur Intel. Une connaissance plus poussée de l'architecture et du jeu d'instructions supporté permettrait un gain supplémentaire en performance, sans pour autant être conséquent (extensions FMA3, FMA4 ...). • De même, nous avons effectué nos compilations sous gcc (GNU Collection Compiler). Si nous souhaitons gagner en performance, il serait alors intéressant de travailler directement avec les outils fondeurs. Par exemple, si nous souhaitons garantir des performances optimales sur architecture Intel, il nous faudrait alors utiliser icc (Intel C++ Compiler) un compilateur développé par Intel et donc optimisé pour leurs architectures. Il en va de même pour tous les fondeurs du marché (Texas Instruments, ARM, MIPS ...). - 41 - Digital Signal Processor Travaux Pratiques Nous venons de conclure la partie vectorisation monocœur de code et nous allons essayer de synthétiser et d'analyser nos précédents résultats. • Choix d'une architecture CPU et d'une famille de processeurs : ◦ pipeline in-order classique : faible consommation, faible performance, faible empreinte silicium, sauf si intégrés par milliers, prenons l'exemple des GPU. Applications sans OS (Operating System), avec petits exécutifs ou systèmes d'exploitation temps réel (Real Time Operating System). Dans le cas des GPU, applications de calcul massivement parallèle (traitement d'image, vidéo, graphique 3D, matriciel …). Il s'agit des architectures CPU les plus rencontrées en terme de volumes autour de nous, notamment grâce au marché des MCU (MCU STM32 STMicroelectronics, MCU PIC Microchip, Intel 8051, ARM Cortex-M ...) ◦ pipeline superscalaire : grande polyvalence (généraliste), le plus souvent grande puissance de calcul, pipeline complexe, donc forte intelligence déportée dans chaque cœur avec un faible niveau de parallélisme (typiquement 1, 2, 4, 8 cœurs vectoriels) : prédiction au branchement, étage d'exécution out-of-order, étage de retirement, étage de renommage des registres, mécanismes d'accélération aux étages de décodage et d'exécution le plus souvent, unités d'exécutions vectorielles … Applications avec OS lourds. Architectures pensées pour exécuter du code faiblement optimisé notamment avec beaucoup de branchements, le cœur se charge du reste. Cependant, dû à la complexité du pipeline, ces architectures peuvent offrir quelques irrégularités au regard du déterminisme à l'exécution (x86, x64, ARM cortex-A, IBM/APPLE/Freescale PowerPC, MIPS Aptiv…). Rapport performance (parallélisme)/consommation faiblement intéressant. ◦ Pipeline VLIW/EPIC : forte puissance de calcul, pipeline relativement simple en opposition aux architectures superscalaires, donc rapport performance (parallélisme)/consommation intéressant (intelligence déportée vers le développeur et la toolchain), grand déterminisme. Néanmoins, développements dépendants de l'architecture nécessaires, problèmes de portabilité de code. Rétrocompatibilité au niveau binaire difficile à suivre pour les fondeurs (DSP TI C6000, NXP TriMedia, DSP SHARC Analog Device, ST200 STMicroelectronics, Intel Itanium ...). • Langage C vs Assembleur : dans une optique d'optimisation, un développement en langage C avec une bonne connaissance de l'architecture matérielle, du jeu d'instructions et des mécanismes d'optimisation de la toolchain peut éviter un passage à l'étage assembleur à notre époque (intrinsics, directives de compilation …) et donc accélérer le TTM du produit (Time To Market). • Tests : ne pas négliger les procédures de test (conformité et performance), notamment dans une optique de benchmarking et d'optimisation d'algorithmes. Rq : En résumant le résumé … • processeurs superscalaires : intelligence déportée dans le cœur • processeurs VLIW et EPIC : intelligence et compétences déportées dans le développeur et la chaîne de compilation - 42 - Digital Signal Processor Travaux Pratiques Dernière information, le processeur utilisé en travaux pratiques fait parti de la famille Keystone I proposée par Texas Instruments (http://processors.wiki.ti.com/index.php/Multicore). TI propose également la famille Keystone II. Il s'agit de SoC (System On Chip) intégrant 8 cœurs DSP VLIW C6600 et jusqu'à 4 cœurs ARM Cortex-A15 superscalaires afin d'embarquer un voire plusieurs systèmes d'exploitation avancés (Typiquement GNU\Linux). Cette architecture offre alors une grande flexibilité ainsi qu'une forte puissance de calcul. Néanmoins, avec la consommation et le coût qui l'accompagne. Famille Keystone I, processeur C6678 Famille Keystone II, processeur 66AK2H14 - 43 - Digital Signal Processor Travaux Pratiques 4. HIERARCHIE MEMOIRE Travail préparatoire • 1. (1,5pt) Rappeler le principe de fonctionnement et l'usage fait du cache processeur par un CPU. • 2. (0,5pt) Qu'est-ce-qu'une ligne de cache ? • 3. (1,5pt) Comme pour beaucoup d'architectures actuelles, la famille DSP C6000 utilise une politique de remplacement de ligne de cache du type LRU (Least Recently Used). Que cela signifie-t-il ? Illustrer votre réponse à l'aide d'un schéma. • 4. (1,5pt) En s'aidant de l'annexe ou de la documentation technique SPRS691E, quel intérêt pouvons-nous avoir à utiliser un ou plusieurs niveaux mémoire (L1/L2/L3) configurés en SRAM et non en cache ? Quelles différences majeures rencontrons-nous à l'usage ? • 5. (5pt) Nous pouvons observer ci-dessous une implémentation en pseudo-code Matlab d'une émulation de transferts mémoire de DDR vers L2 SRAM. Bien entendu, sous Matlab, la notion de hiérarchie n'a aucun sens, cette implémentation nous permet seulement de valider les tailles des vecteurs intermédiaires de stockage ainsi que les valeurs limites des boucles. Proposer une implémentation C en utilisant la fonction standard memcpy afin d'effectuer les transferts mémoire : %DDR_ARRAY_LENGTH = 1048576; % 1M/4Mb : array length in DDR DDR_ARRAY_LENGTH = 65536; % 64K/256Kb : array length in DDR L2_ARRAY_LENGTH = 32768; % 32K/128Kb : array length in L2 SRAM A_LENGTH = length(a_sp); % 64/256b : length of coefficients array YK_LENGTH = DDR_ARRAY_LENGTH - A_LENGTH + 1; % length of output temporal array in DDR %*** MEMORY TRANSFERS % floating point arrays lengths : % xk_sp_DDR |---------------------- 256Kb or 4Mb --------------------------| % xk_sp_L2 |------------- 128Kb + 256b - 4 -------------| overlap % a_sp_L1 |- 256b -| % yk_sp_L2 |------------- 128Kb -------------| % yk_sp_DDR |------------- (256Kb or 4Mb) - 256b + 4 ----------------| % % copy part of input array from DDR to L2 SRAM for k=1 : A_LENGTH a_sp_L1(k) = a_sp(k); end for i=1 : L2_ARRAY_LENGTH : DDR_ARRAY_LENGTH % memory copy DDR to L2 SRAM % rq : DDR_ARRAY_LENGTH % L2_ARRAY_LENGTH = 0 if i < DDR_ARRAY_LENGTH - L2_ARRAY_LENGTH for k=1 : L2_ARRAY_LENGTH + A_LENGTH - 1 xk_sp_L2(k) = xk_sp_DDR(i+k-1); end else for k=1 : L2_ARRAY_LENGTH - 44 - Digital Signal Processor Travaux Pratiques xk_sp_L2(k) = xk_sp_DDR(i+k-1); end end % FIR filtering - algorithm working with L1D cache yk_sp_L2 = fir_sp(xk_sp_L2, a_sp_L1, A_LENGTH, L2_ARRAY_LENGTH); % memory copy L2 SRAM to DDR - coherency of output DDR array % rq : YK_LENGTH % L2_ARRAY_LENGTH = L2_ARRAY_LENGTH - A_LENGTH + 1 if i < YK_LENGTH - L2_ARRAY_LENGTH for k=1 : L2_ARRAY_LENGTH yk_sp_DDR(i+k-1) = yk_sp_L2(k); end else for k=1 : L2_ARRAY_LENGTH - A_LENGTH + 1 yk_sp_DDR(i+k-1) = yk_sp_L2(k); end end end - 45 - Digital Signal Processor Travaux Pratiques 4. HIERARCHIE MEMOIRE Travail en séance Nous allons maintenant chercher à passer outre les mémoires caches L1D et L2 en ne configurant qu'une partie des niveaux L1D et L2 en cache et le reste en mémoires SRAM adressable par octet. Dans l'exemple d'un produit scalaire, l'impact restera minime. Néanmoins, pour grand nombre d'algorithmes du traitement numérique du signal (traitement d'image, traitement d'antenne ...), laisser opérer les contrôleurs cache sans ordonnancement des données avant traitement peut avoir une incidence énorme sur le temps d'exécution. Dans l'exemple qui suit, nous pouvons observer une matrice de données manipulée par indexage (chargement/lecture donnée 1 puis 2 puis 3 …) : - 46 - Digital Signal Processor Travaux Pratiques Un cache étant chargé par ligne (principe de localité spatiale), nous sommes alors amenés à charger un grand nombre de données ''potentiellement'' inutilisées en cache. Une technique couramment utilisée, consiste à ré-implémenter l'algorithme de façon à traiter des données préordonnancées en mémoire (technique du corner turn). Cette stratégie permet de diminuer le nombre d'accès au cache et assure une utilisation optimale des espaces de stockage en cache. Rappelons que l'accès aux ressources en mémoire est l'un des principal goulot d'étranglement à notre époque. Nous avons également remarqué que notre architecture possède un grand nombre de registres de travail généralistes. L'objectif étant de charger les données locales à une fonction ou une boucle dans les registres du cœur, pour les traiter par la suite en minimisant au plus les aller/retour vers le cache. Écritures différées en mémoire en fin de traitement. Dans notre cas, l'objectif est différent. L'algorithme d'un produit scalaire sans transformations mathématiques dans une optique de diminution de sa complexité, manipule les vecteurs d'entrée dans l'ordre de façon séquentielle. Cet exercice n'a donc pour objectif que d'illustrer la possibilité d'obtenir un espace de stockage adressable à accès rapide physiquement proche du cœur (L1D et L2). Nous maîtriserons alors avec certitude les données présentes aux niveaux L1D et L2 afin de garantir un déterminisme à l'exécution. Cette solution permet par exemple de s'affranchir localement du principe de corner turn précédemment présenté. En effet, nous allons manuellement effectuer le travail des contrôleurs de cache utilisant quant à eux des mécanismes de localité temporelle (LRU) pour leurs allocations/libérations mémoire. Traitement impossible sur processeur généraliste GPP. Ceci permettra de rendre notre algorithmique quasiment insensible à la charge éventuelle du cache. 4.1. Mémoire locale adressable Nous avons possibilité de configurer les différents niveaux mémoire en cache ou en SRAM adressable de façon statique à la compilation ou dynamique à l'exécution. Étudions l'approche statique. Rappelons avant tout l'architecture mémoire de notre processeur : • Dans notre projet CCS, remplacer le script C6678_unified.cmd par le fichier /dsp/c6678/test/map/C6678_unified_fir_pjct.cmd. Rq : Ce fichier est utilisé par le linker afin fixer les localisations mémoire des principaux segments (tas/heap, pile/stack et sections statiques) du projet. Sans pour autant figer les adresses mémoire (rôle de linker, résolution des liens symboliques avec la mémoire), nous pouvons fixer leur localisation dans l'architecture (DDR3 SRAM, MSM SRAM, L2 SRAM ou L1D/P SRAM). - 47 - Digital Signal Processor Travaux Pratiques • Éditer le fichier /dsp/c6678/test/map/C6678_unified_fir_pjct.cmd et analyser son contenu. -stack 0x2000 -heap 0x2000 MEMORY { LOCAL_L1P_SRAM: origin = 0x00E00000 length = 0x00008000 LOCAL_L1D_SRAM: origin = 0x00F00000 length = 0x00008000 LOCAL_L2_SRAM: origin = 0x00800000 length = 0x00080000 MSMCSRAM: origin = 0x0C000000 length = 0x00400000 EMIF16_CS2: EMIF16_CS3: EMIF16_CS4: EMIF16_CS5: origin = 0x70000000 length = 0x04000000 origin = 0x74000000 length = 0x04000000 origin = 0x78000000 length = 0x04000000 origin = 0x7C000000 length = 0x04000000 /* 32kB LOCAL L1P/SRAM */ /* 32kB LOCAL L1D/SRAM */ /* 512kB LOCAL L2/SRAM */ /* 4MB Multicore shared Memmory */ /* 64MB EMIF16 CS2 Data Memory */ /* 64MB EMIF16 CS3 Data Memory */ /* 64MB EMIF16 CS4 Data Memory */ /* 64MB EMIF16 CS5 Data Memory */ /* TMDSEVM6678L board specific */ DDR3: origin = 0x80000000 length = 0x20000000 /* 512MB external DDR3 SDRAM */ } SECTIONS { .text .stack .bss .cio .const .data .switch .sysmem .far .args .ppinfo .ppdata > MSMCSRAM > MSMCSRAM > MSMCSRAM > MSMCSRAM > MSMCSRAM > MSMCSRAM > MSMCSRAM > MSMCSRAM > DDR3 > MSMCSRAM > MSMCSRAM > MSMCSRAM /* COFF sections */ .pinit > MSMCSRAM .cinit > DDR3 /* EABI sections */ .binit > MSMCSRAM .init_array > MSMCSRAM .neardata > MSMCSRAM .fardata > DDR3 .rodata > MSMCSRAM .c6xabi.exidx > MSMCSRAM .c6xabi.extab > MSMCSRAM /* project specific sections */ /* user sections */ } • A quoi correspond la zone MEMORY du script ? - 48 - Digital Signal Processor Travaux Pratiques • En vous aidant de l'annexe ou de la documentation technique SPRU187V, préciser le rôle de la zone SECTIONS du script. • Dans quelle section est rangée la pile ou stack système (allocations automatiques) ? • Dans quelle section est rangée le tas ou heap système (allocations dynamiques) ? • Dans quelle section est rangé le code utilisateur ? • Modifier le script de façon à configurer à la compilation les niveaux mémoire L1D et L2 comme suit : ◦ 32Ko L1D : 28Ko en L1D SRAM adressable et 4Ko en L1D cache ◦ 512Ko L2 : 480Ko en L2 SRAM adressable et 32Ko en L2 cache • Créer 3 nouvelles sections propres au projet et les placer tel que précisé ci-dessous : ◦ .ddrsdram : section statique présente en DDR ◦ .l2sram : section statique présente en L2 SRAM ◦ .l1dsram : section statique présente en L1D SRAM • Compiler puis exécuter le programme. Valider son bon fonctionnement. Rq : Dans les langages compilés comme le langage C, une donnée est soit allouée statiquement à la compilation soit allouée dynamiquement/automatiquement à l'exécution. Par essence, les langages interprétés ne peuvent qu'allouer des ressources durant l'exécution (allocations dynamiques) : • Allocations statiques : allocations à la compilation et structuration des familles de variables en sections par l'éditeur de liens (variables globales ou statiques initialisées, non-initialisées , constantes …). Exemples de sections : • • • .data : section mémoire où se situe les données statiques initialisées .bss : section mémoire où se situe les données statiques non initialisées .rodata : données statiques initialisées en lecture seule (read-only) • Allocations automatiques : allocations/dés-allocations à l'exécution sur la pile ou stack (variables locales, paramètres de fonctions, adresses de retour des fonctions) • Allocations dynamiques : allocations/dés-allocations à l'exécution sur le tas ou heap (malloc, realloc, free ... et variantes). Attention à l'utilisation des fonctions malloc et surtout free d'allocation dynamiques de ressources sur processeur ne possédant pas de PMMU (Paged Memory Managment Unit). Ce qui est notre cas. En effet, l'allocation dynamique de ressources amène une fragmentation logique de la mémoire physique, palliée par l'unité de pagination sur processeur superscalaire généraliste (GPP et AP) possédant tous une MMU. - 49 - Digital Signal Processor Travaux Pratiques 4.2. Préchargement des données de DDR SRAM vers L2 SRAM • Déclarer des vecteurs statiques (variables globales) qui assureront des stockages partiels de nos vecteurs d'entrée et de sortie. Ces vecteurs stockeront temporairement en L2 SRAM et L1D SRAM des morceaux de vecteurs à traiter. Respecter les dénominations imposées dans le fichier d'en-tête firtest.h : /* arrays allocations (bytes) : * xk_sp (DDR) |------------------------- 256Kb or 4Mb --------------------------| * xk_sp_l2 |------------- 128Kb + 256b - 4 -------------| overlop * xk_sp_l1d |--- 8Kb + 256b - 4 ---| overlop * a_sp_l1d |- 256b -| * yk_sp_l1d |----- 8Kb -----| * yk_sp_l2 |------------- 128Kb -------------| * yk_sp (DDR) |------------------- (256Kb or 4Mb) - 256b + 4 -------------------| */ float32_t xk_sp_l2 [L2_ARRAY_LENGTH + A_LENGTH - 1]; float32_t yk_sp_l2 [L2_ARRAY_LENGTH]; float32_t xk_sp_l1d [L1D_ARRAY_LENGTH + A_LENGTH - 1]; float32_t yk_sp_l1d [L1D_ARRAY_LENGTH]; float32_t a_sp_l1d [A_LENGTH]; • En modifiant le script linker, nous venons de customiser l'organisation mémoire de notre architecture afin de l'adapter aux besoins de notre projet. En utilisant la directive d'édition des liens #pragma DATA_SECTION, placer les précédentes déclarations dans les sections concernées. Les vecteurs d'entrée et de sortie xk_sp, yk_sp, a_sp et yk_sp_ti seront quant à eux placés en DDR SRAM externe. Ne pas oublier d'assurer un alignement mémoire sur chaque nouveau vecteur (utilisation de l'algorithme fir_sp_opt_r4). Prenons un exemple : /* memory segmentation */ #pragma DATA_SECTION (xk_sp,".ddrsdram"); • Modifier le code de la fonction firtest_perf de façon à commuter d'un modèle mémoire à un autre en fonction de la valeur du paramètre d'entrée memoryModel. Ces modifications devront être intégrées dans la boucle assurant une répétition des tests de performance. Nous nous laisserons également la possibilité de pouvoir retirer du code à la pré-compilation : /* memory model selection */ if ( memoryModel == UMA_L2CACHE_L1DCACHE ) { /* user code – partie 2.2 – test de performance */ } #if ( TEST_FIR_L2SRAM_L1DCACHE != 0 ) else if ( memoryModel == UMA_L2SRAM_L1DCACHE ) { /* user code – partie 4.2 – préchargement des données de DDR SRAM vers L2 SRAM */ } #endif • A partir de maintenant et jusqu'à la fin de la trame de travaux pratiques, toutes nos futures optimisations (mémoire et périphériques d'accélération) utiliseront l'algorithme vectorisé fir_sp_opt_r4 précédemment développé et offrant un niveau optimum d'accélération. - 50 - Digital Signal Processor Travaux Pratiques • Comme pour les algorithmes précédents, copier dans la fonction main un bloc de code assurant les tests de conformité et de performance d'un algorithme puis le modifier de façon à obtenir une section de test pour l'algorithme fir_sp_opt_r4. Nous utiliserons dans un premier temps la macro TEST_FIR_L2SRAM_L1DCACHE qui nous permettra si nécessaire de retirer du code à la pré-compilation. De même, nous passerons maintenant la macro UMA_L2SRAM_L1DCACHE comme second argument de la fonction firtest_perf. • Notre procédure de test complète utilisant à tour de rôle des modèles mémoire pleinement cachables ou des modèles mixtes cachables/SRAM adressables, nous allons nous offrir la possibilité de modifier à l'exécution le modèle mémoire du processeur. En vous aidant de la documentation technique de CSL présente dans le répertoire de projet (/dsp/c6678/doc/csl/docs/doxygen /html/index.html) et des macros et énumérateurs présents dans le header C:\ti\pdk_C6678_<version>\packages\ti\csl\csl_cache.h, compléter la fonction firtest_perf de façon à reconfigurer les caches processeurs concernés. L'exemple suivant permet de restaurer des niveaux L2 et L1D pleinement cachable : /* memory model selection */ if ( memoryModel == UMA_L2CACHE_L1DCACHE ) { /* caches levels configurations */ CACHE_setL2Size (CACHE_256KCACHE); CACHE_setL1DSize (CACHE_L1_32KCACHE); CACHE_setL1PSize (CACHE_L1_32KCACHE); } /* user code – partie 2.2 – test de performance */ #if ( TEST_FIR_L2SRAM_L1DCACHE != 0 ) else if ( memoryModel == UMA_L2SRAM_L1DCACHE ) { /* caches levels configurations */ /* coefficients array loading */ /* user code – partie 4.2 – préchargement des données de DDR SRAM vers L2 SRAM */ #endif } • En vous aidant du travail préparatoire, effectuer manuellement les transferts mémoire partiels des différents vecteurs d'entrée et de sortie de la DDR SRAM vers la mémoire L2 SRAM. Comme pour chaque phase de développement, bien travailler en mode Debug. • Après validation, lancer une exécution en mode Release (mode Debug coupé et options d'optimisations levées), puis reporter les résultats des tests dans le tableau d'analyse comparative à la fin de ce support de travaux pratiques. - 51 - Digital Signal Processor Travaux Pratiques 4.3. Préchargement des données de L2 SRAM vers L1D SRAM • Comme pour les algorithmes précédents, copier dans la fonction main un bloc de code assurant les tests de conformité et de performance d'un algorithme puis le modifier de façon à obtenir une section de test pour l'algorithme fir_sp_opt_r4. Nous utiliserons maintenant la macro TEST_FIR_L2SRAM_L1DSRAM qui nous permettra si nécessaire de retirer du code à la pré-compilation. De même, nous passerons maintenant la macro UMA_L2SRAM_L1DSRAM comme second argument de la fonction firtest_perf. • Compléter le code de la fonction firtest_perf de façon à effectuer les traitements suivants : ◦ ◦ ◦ • Commuter d'un modèle mémoire à un autre en fonction de la valeur du paramètre d'entrée memoryModel. Configurer les caches processeurs propre au modèle mémoire courant Précharger le vecteur de coefficients a_sp de la DDR SRAM vers le vecteur a_sp_l1d présent en niveau L1D. Utiliser la fonction standard memcpy. En vous aidant du travail précédent, effectuer maintenant des copies partielles manuelles des vecteurs de la mémoire L2 SRAM vers la mémoire L1D SRAM en respectant les tailles de vecteurs suivants. Si nécessaire, s'aider du code de prototypage Matlab dans /dsp/matlab/ : /* arrays allocations (bytes) : * xk_sp (DDR) |------------------------- 256Kb or 4Mb --------------------------| * xk_sp_l2 |------------- 128Kb + 256b - 4 -------------| overlop * xk_sp_l1d |--- 8Kb + 256b - 4 ---| overlop * a_sp_l1d |- 256b -| * yk_sp_l1d |----- 8Kb -----| * yk_sp_l2 |------------- 128Kb -------------| * yk_sp (DDR) |------------------- (256Kb or 4Mb) - 256b + 4 -------------------| */ • Après validation, lancer une exécution en mode Release (mode Debug coupé et options d'optimisations levées), puis reporter les résultats des tests dans le tableau d'analyse comparative à la fin de ce support de travaux pratiques. Rq : • A ce stade là de nos développements, nous constatons une légère perte de performance mais néanmoins un déterminisme garanti à l'exécution (maîtrise totale des données présentes dans les niveaux L2 et L1D). Juste pour information, c'était déjà partiellement le cas précédemment avec un modèle pleinement cachable dans le cadre d'un produit scalaire (données manipulées séquentiellement dans l'ordre). Néanmoins, pour grand nombre d'autres algorithmes DSP avec indexages complexes (exemple de la FFT), nous aurions pu avoir de mauvaises surprises à n'utiliser que du cache. • Nous avons cependant, sans pour autant modifier notre modèle mémoire, possibilité d'accélérer nos transferts. En effet, jusqu'à présent nous avons utilisé la fonction standard memcpy réalisant les transferts via le CPU (instructions LDx/STx). Nous allons maintenant regarder de plus près les périphériques DMA (Direct Memory Access), spécialisés dans les transferts mémoire autonomes sans passer par le CPU. - 52 - Digital Signal Processor Travaux Pratiques 5. PERIPHERIQUES D'ACCELERATION Travail préparatoire • 1. (1,5pt) Qu'est-ce-qu'un DMA ? Bien entendu, Direct Memory Access n'est pas la réponse attendue ... • 2. (1,5pt) Qu'est-ce qu'un canal DMA (DMA channel) pour un DMA ? • 3. (0,5pt) En s'aidant de la documentation technique ''SPRUGW0C – CorePac'' (répertoire /dsp/c6678/doc/datasheet/ ou en ligne), combien de canaux possède chaque IDMA de notre processeur. De même, combien d'IDMA possède notre processeur DSP C6678 ? • 4. (2,5pt) En vous aidant de la documentation technique de CSL (/dsp/c6678/doc/csl/docs/doxygen /html/index.html), présenter l'API de programmation utile à l'utilisation du canal 0 de l'IDMA. La citer et expliquer brièvement son fonctionnement. • 5. (4pt) Proposer une implémentation par transferts IDMA de la fonction standard memcpy utilisant quant à elle le CPU pour effectuer des transferts mémoires (instructions LDx/STx). S'aider de la documentation technique de CSL (/dsp/c6678/doc/csl/docs/doxygen /html/index.html) ainsi que des exemples présentés. Voici le cahier des charges à respecter ainsi que le prototype de la fonction : ◦ ◦ ◦ ◦ ◦ Utiliser le canal numéro 1 IDMA priorité maximale vis à vis du CPU Attendre la fin de la totalité du transfert avant de quitter la procédure Valider la génération et l'envoi d'une interruption après la fin du transfert Les pointeurs d'entrée pointeront sur des flottants 32bits (float) /** * @brief IDMA (CorePac Internal Direct Memory Access) bytes copy * from L2 array source to destination array in L1D memory * @param dst pointer on destination array * @param src pointer on source array * @param nbBytes number of bytes to copy */ void idmacpy( void *dst, \ void *src, \ Uint16 nbBytes); - 53 - Digital Signal Processor Travaux Pratiques 5. PERIPHERIQUES D'ACCELERATION Travail en séance Dans cet ultime partie, nous allons nous intéressé à des périphériques d'accélération standards sur architectures processeurs évoluées, les DMA (Direct Memory Access). Rappelons que le travail d'un DMA est d'effectuer des transferts mémoires sans passer par le CPU. Un DMA possède ses propres bus et est, après configuration, une entité autonome de l'architecture du processeur. Un DMA classique effectue des copies d'une zone mémoire à une autre et est capable, comme tout périphérique, de prévenir le CPU de la fin d'un transfert par l'envoi d'une interruption matérielle (IRQ, Interrupt ReQuest). Des DMA plus évolués (exemple des Enhanced DMA chez TI ou des Smart DMA chez Freescale) sont capables d'effectuer des transferts avec modes d'adressages et indexations complexes (manipulation de matrices, de cubes de données), peuvent synchroniser des transferts sur événements matériels issus de périphériques, peuvent capturer et garder des grands nombres de préconfigurations … Une étude approfondie de ces types de DMA peut alors prendre des mois. Dans un premier temps, nous travaillerons avec les IMDA (Internal DMA) de notre processeur. Ces IMDA sont présents dans chaque cœur et ne proposent que des services standards de copies : Rq : dans le schéma ci-dessus nous pouvons observer plus finement l'architecture interne d'un corePac (ensemble CPU/cœur, caches associés et utilitaires matériels spécifiques, exemple des IDMA). Bien faire la distinction entre les mémoires caches (espaces de stockages) et les contrôleurs de caches (DMA autonome travaillant dans notre cas avec des mécanismes de localités temporelles LRU pour le remplacement des lignes de caches). Un contrôleur de cache effectuant des copies de mémoires à mémoires, il n'est donc qu'un DMA autonome. - 54 - Digital Signal Processor Travaux Pratiques 5.1. Transferts par IDMA • Comme pour les algorithmes précédents, copier dans la fonction main un bloc de code assurant les tests de conformité et de performance d'un algorithme puis le modifier de façon à obtenir une section de test pour l'algorithme fir_sp_opt_r4. Nous utiliserons maintenant la macro TEST_FIR_L2SRAM_L1DIDMA qui nous permettra si nécessaire de retirer du code à la pré-compilation. De même, nous passerons maintenant la macro UMA_L2SRAM_L1DIDMA comme second argument de la fonction firtest_perf. • Compléter le code de la fonction firtest_perf de façon à effectuer les traitements suivants : ◦ ◦ ◦ Commuter d'un modèle mémoire à un autre en fonction de la valeur du paramètre d'entrée memoryModel. Configurer les caches processeurs propre au modèle mémoire courant Précharger le vecteur de coefficients a_sp de la DDR SRAM vers le vecteur a_sp_l1d présent en niveau L1D • Ajouter au projet le fichier source /dsp/c6678/idmalib/src/idmacpy.c puis s'assurer de la bonne compilation du projet. • En vous aidant du travail préparatoire, compléter le code de la fonction idmacpy de façon à effectuer des transferts par IDMA et non plus par le CPU. Modifier le code de la fonction firtest_perf (section UMA_L2SRAM_L1DIDMA) de façon à effectuer les transferts mémoires L2 SRAM / L1D SRAM / L2 SRAM par IDMA. • Après validation, lancer une exécution en mode Release (mode Debug coupé et options d'optimisations levées), puis reporter les résultats des tests dans le tableau d'analyse comparative à la fin de ce support de travaux pratiques. Rq : cette solution est légèrement plus rapide que la précédente car n'oublions pas qu'un DMA reste un périphérique spécialisé et qu'en aucun cas il n'est capable d'exécuter des instructions contrairement à un CPU (pipeline complexe : fetch, decode, execute et writeback). Même si dans notre cas ses bus/paths restent moins larges que ceux du CPU, une fois configuré, il les utilise exclusivement afin d'effectuer la fonction de copie pour laquelle il a été détaché. - 55 - Digital Signal Processor Travaux Pratiques 5.1. Stratégie Ping Pong Même si la solution précédente reste plus rapide qu'une copie par CPU, elle n'est pas optimale. Notre IMDA possède 2 canaux indépendants par cœurs. Ces canaux peuvent alors être utilisés en parallèles en manipulant chacun deux vecteurs temporaires de stockage en L1D SRAM. Cette stratégie très générique de copie se nomme Ping Pong, lorsque le côté Ping est en cours de traitement (algorithme de traitement du signal), le côté Pong est en cours de chargement/sauvegarde, etc. Rappelons d'ailleurs que nous possédons 24Ko de cache L1D SRAM adressable, nous autorisant ainsi à pouvoir définir de nouveaux vecteurs temporaires de stockage. Observons par exemple une séquence de transferts Ping Pong en 6 étapes : • • Canal 0 : Dédié aux chargements mémoires L2 SRAM vers L1D SRAM • Canal 1 : Dédié aux sauvegardes mémoires L1D SRAM vers L2 SRAM • Étape 1 : Chargement donnée xk_sp du côté Ping de L2 SRAM vers L1D SRAM • Étape 2 : Chargement donnée xk_sp du côté Pong de L2 SRAM vers L1D SRAM et si chargement du côté Ping terminé, application de l'algorithme de filtrage sur les données précédemment chargées du côté Ping • Étape 3 : Si traitement algorithmique terminé, sauvegarde des données de sortie yk_sp du côté Ping de L1D SRAM vers L2 SRAM et si chargement du côté Pong terminé, application de l'algorithme de filtrage sur les données précédemment chargées du côté Pong • Étape 4 : Chargement donnée xk_sp du côté Ping de L2 SRAM vers L1D SRAM • Étape 5 : Si traitement algorithmique terminé, sauvegarde des données de sortie yk_sp du côté Pong de L1D SRAM vers L2 SRAM • Étape 6 : Si traitement algorithmique terminé, sauvegarde des données de sortie yk_sp du côté Ping de L1D SRAM vers L2 SRAM Voilà, pour cette année la trame de travaux pratiques s'arrête ici ! Si vous le souhaitez et si vous avez le temps, vous pouvez tenter d'implémenter la stratégie Ping Pong. Bien entendu, cela entraînera un refactoring non négligeable de code. - 56 - Digital Signal Processor Travaux Pratiques • Cœur de métier Voilà, nous venons de faire un rapide tour d'horizon du potentiel de notre architecture processeur. Nous comprenons mieux maintenant le sens du nom de l'enseignement ''Processeurs Spécialisés''. Beaucoup des mécanismes d'accélération déterministes présentés sont impossibles ou en tous cas bien moins performants sur architectures généralistes (MCU, AP, GPP). Néanmoins, une bonne compréhension des stratégies présentées précédemment vous assurera une adaptabilité relativement forte sur la plupart des architectures processeurs parallèles du marché (AP, GPP, DSP, GPU). Il faut maintenant savoir que le plus souvent en milieu industriel, notamment dans les grands groupes, les ingénieurs bas niveaux chargés du développement des bibliothèques spécialisées sont différents des ingénieurs assurant l'intégration logicielle : • Développeurs temps réel bas niveaux : ingénieurs spécialisés dans les architectures matérielles processeurs et le développement de bibliothèques spécialisées • Développeurs logiciels hauts niveaux : intégrateurs système hauts niveaux travaillant dans les couches hautes de l'applicatif et utilisant les bibliothèques en modes boîtes noires. Par exemple, il ne nous reste plus qu'à faire un copier/coller du code proposant les copies mémoires L2 SRAM / L1D SRAM / L2 SRAM par transferts EDMA/IDMA dans le code source de la fonction fir_sp_r4 ainsi que d'inclure le code vectorisé développé dans la fonction fir_sp_opt_r4 afin d'offrir une boîte noire utilisable par un développeur haut niveau dans une optique d'intégration logicielle. Cette boîte noire offre alors une abstraction totale de l'architecture matérielle du processeur et masque la grande complexité des mécanismes d'accélérations appliqués : - 57 - Digital Signal Processor Travaux Pratiques • Architecture multi-coeurs Nous allons maintenant présenter un exemple d'utilisation d'architecture multi-coeurs, par exemple la famille C6678 de TI. Nous constaterons alors tout le potentiel d'une programmation vectorielle monocœur efficace. Une architecture multi-coeurs peut-être détournée à d'autres fins qu'une simple parallélisation d'un algorithme sur plusieurs cœurs. Prenons l'exemple d'une chaîne de traitement numérique du signal. Ce type de chaîne algorithmique peut par exemple être rencontrée dans les domaines du traitement d'image, applications radars, applications télécoms ... Supposons que nous travaillons sur une architecture 4 cœurs vectoriels et que nous avons de lourdes contraintes temps réel afin d'exécuter la chaîne complète. Après instrumentations et mesures sur la chaîne, l'algorithme imposant le plus long temps d'exécution est le numéro 4 (produit scalaire par exemple). Observons deux exemples d'implémentations possibles, la plus efficace n'étant pas forcément celle que l'on croit. • Optimisations vectorielles monocœurs et parallèles multi-coeurs appliquées à chaque algorithme afin de minimiser leurs temps d'exécutions respectifs : Cette solution implique donc une vectorisation ainsi qu'une parallélisation de code appliquées à chaque algorithme. Tous les cœurs du processeur voient passer la chaîne algorithmique optimisée complète. Cette solution, qui nécessite de nombreux allez/retours en mémoire principale, est la plus couramment rencontrée sur processeurs GPP. - 58 - Digital Signal Processor Travaux Pratiques • Optimisations vectorielles monocœurs et pipelining matériel algorithmique : Chaque CPU est dédié à l'exécution d'un algorithme ou d'une série d'algorithmes. Nous utilisons alors nos CPU comme un pipeline algorithmique. Les premières données traverseront le pipeline complet, néanmoins celles qui suivent seront chaînées et ne dureront que le temps d'exécution d'un étage du pipeline. Néanmoins, un ordonnanceur ou scheduler est nécessaire pour la synchronisation des différents échanges. Sur notre processeur DSP C6678, nous pouvons nous projeter avec 7 cœurs dédiés au traitement numérique de la chaîne et 1 cœur pour l'ordonnancement et les échanges avec l'extérieur. - 59 - Digital Signal Processor Travaux Pratiques BENCHMARKING ANALYSE COMPARATIVE Algorithme Temps (vecteur d'entré 64Ko) Architecture d'exécution (ms) Performances Temps de (MACS per cycle, Développement maximum 8) (heures) Observations et Limitations cf. documentation DSPLIB TI 1,096ms DSPF_sp_fir_gen 2,73 0 TI C6600 Genuine Intel corei7 IA-64 (Haswell) - 60 - nr and nh are a multiples of 4 and greaters than or equals to 4. x, h and r are double-word aligned. Interruptible code. Processeurs Spécialisés ANNEXES Processeurs Spécialisés SOMMAIRE 1. CREATION DE PROJET 2. INTEGRATED DEVELOPMENT ENVIRONMENT 3. EXTRAITS – SPRU187V – OPTIMIZING COMPILER 4. EXTRAITS – SPRS691E – TMS320C6678 5. EXTRAITS – SPRABK5A1 – THROUGHPUT PEFORMANCE 6. EXTRAITS – SPRUGH7 – CPU AND INSTRUCTION SET 7. REGLES DE CODAGE GLOSSAIRE Digital Signal Processor Annexes Digital Signal Processor Annexes SOMMAIRE 1. CREATION DE PROJET ET COMPILATION 1. CREATION DE PROJET ET COMPILATION L'annexe qui suit présente la procédure à appliquer pour la création de projet sous Code Composer Studio v6 afin de travailler sur notre plateforme de développement (TMDXEVM6678L EVM). 2. INTEGRATED DEVELOPMENT ENVIRONMENT • Ouvrir l'IDE CCS v6. Répertoire d'installation de l'exécutable : C:\ti\ccsv6\eclipse\ccstudio.exe • Créer un nouveau projet : File > New > CCS Project • Configuration du projet pour notre plateforme de développement. Bien entendu, il vous faut adapter les différents chemins et noms pour le projet en cours et la machine host. Une fois cette étape réalisée, cliquer sur Finish. S'assurer de la bonne création du projet. 3. EXTRAITS – SPRU187V – OPTIMIZING COMPILER 4. EXTRAITS – SPRS691E – TMS320C6678 5. EXTRAITS – SPRABK5A1 – THROUGHPUT PEFORMANCE 6. EXTRAITS – SPRUGH7 – CPU AND INSTRUCTION SET 7. REGLES DE CODAGE GLOSSAIRE -1- -2- Digital Signal Processor Annexes • Créer une arborescence de projet sous l'IDE. Respecter celle imposée ci-dessous : clic droit sur le projet > New > Folder • Ajouter un script linker propre à notre architecture. Ce script permet de fixer le modèle mémoire du processeur, notamment les localisations mémoires des principaux segments et sections du programme (sections concernant les allocations statiques, le code, la pile, le tas ...). Ces scripts sont fournis par Texas Instruments et sont à adapter en fonction du projet en cours. Pour le moment, nous garderons le script par défaut offrant un modèle unifié. Ne pas hésiter à regarder son contenu, nous constaterons que celui-ci reste relativement simple à lire et à interpréter. Lien de téléchargement : Digital Signal Processor Annexes • Ajouter le fichier source firtest_example.c, compiler puis exécuter le programme. Pour la première compilation, ne lancer le code que sur le cœur n°0. S'assurer du bon fonctionnement du projet. Utilisation de fonctions standards du C et affichage de ''Hello World !'' sur la console de l'IDE. • Retirer le fichier firtest_example.c du projet (clic droit sur le fichier > delete), ajouter le fichier firtest_main.c puis Compiler. Vous constaterez des erreurs de compilation. Il s'agit en fait d'erreurs retournées par le pré-processeur de notre chaîne de compilation. En effet, le fichier fait appel à des fichiers d'en-têtes, mais à aucun moment nous n'avons indiquer au compilateur où aller les chercher. http://processors.wiki.ti.com/index.php/Linker_CMD_Files_for_CCS • Utiliser dans le script présent dans /dsp/c6678/test/map/C6678_unified.cmd ou celui présent sur internet : clic droit sur le projet > Add Files... > Link to Files > puis sélectionner et faire glisser dans le répertoire memoryMap • Autre solution à déprécier, ajouter un script par défaut depuis les options de compilation. Accéder aux options de compilation, puis ajouter le script C6678_unified.cmd : clic droit sur le projet > Properties > General > Linker commed file -3- -4- Digital Signal Processor Annexes • Ajouter les différents chemins vers les fichiers d'en-tête nécessaires à une bonne compilation du projet. Attention, un header peu appeler d'autres headers. Tous les chemins doivent être spécifiés à la chaîne de compilation. Accéder aux options de compilation, puis ajouter les chemins : clic droit sur le projet > Properties > Include Options > Add... ◦ Chemin vers les headers spécifiques à notre projet : /dsp/c6678/test/h/ /dsp/c6678/firlib/h/ /dsp/c6678/idma/h/ /dsp/c6678/edma/h/ ◦ Chemins vers les headers pour la manipulation de la bibliothèque de traitement numérique du signal développée par TI (DSPLIB). Attention, les chemins ci-dessous peuvent changer d'une version de la DSPLIB à une autre : C:\ti\dsplib_c66x_latest_version\inc C:\ti\dsplib_c66x_latest_version\packages ◦ • Ajouter les bibliothèques statiques pré-compilées nécessaires au projet. Extensions .ae66 si spécifiques à l'architecture C6600 au format ELF (Editable Linkable Format) ou .a si génériques à la famille C6000. Vous constaterez probablement que différents formats de bibliothèques statiques sont possibles (.a66, .a66e, .ae66 et .ae66e). Rappelons également qu'une librairie statique n'est qu'une concaténation de fichiers objets. La différence entre ces différents formats de bibliothèque est le format des fichiers objets concaténés. Par exemple, les extensions .a66e utilise des fichiers hérités COFF (Common Object File Format). Format en cours d'obsolescence. Dans notre cas, nous utiliserons une EABI (ELF Application Binary Interface) à la compilation, utilisant des fichiers binaire ELF durant le processus de compilation. Format standard sur système UNIX/UNIX-like et utilisé par exemple sous système GNU\Linux. Voici les chemins vers les bibliothèques statiques à utiliser pour notre projet : ◦ Bibliothèque dédiée au traitement numérique du signal (DSPLIB). Attention, le chemin ci-dessous peut changer d'une version de la DSPLIB à une autre : C:\ti\dsplib_c66x_latest_version\lib\dsplib.ae66 Chemin vers les headers pour la manipulation de la bibliothèque de gestion des périphériques internes au processeur (CSL). Attention, le chemin ci-dessous peut changer d'une version de CSL à une autre : C:\ti\pdk_C6678_latest_version\packages Au fil du projet, nous serons amenés à développer de nouvelles bibliothèques et donc à ajouter de nouveaux fichiers d'en-tête. Bien penser à rajouter les chemins au fur et à mesure de nos développement. A ce niveau là, le projet compile mais ne peut pas encore être exécuté (erreurs à l'édition des liens). En effet, nous allons devoir ajouter les définitions ou binaires des fonctions utilisées dans notre projet en spécifiant à l'éditeur des liens l'emplacement des bibliothèques statiques à utiliser. • Digital Signal Processor Annexes ◦ Bibliothèque de gestion des périphériques internes (CSL). Attention, le chemin cidessous peut changer d'une version de CSL à une autre : C:\ti\pdk_C6678_latest_version\packages\ti\csl\lib\ti.csl.ae66 • Durant les phases de développement, nous vous conseillons de travaillons en mode Debug afin d'avoir accès aux différents outils de débogage proposés par l'IDE et l'ABI. Néanmoins, durant les phases de benchmarking, il vous faudra utiliser le mode Release en coupant toute forme de Debug et en levant les options d'optimisation à la compilation. Clic droit sur le projet > Properties > Optimization > Optimization level > 3 et Debug options > Debugging model > Suppress all symbolic debug generation De façon général, lorsque nous travaillons sur des projets complexes avec plusieurs bibliothèques sujettes à différentes versions. Nous pouvons rapidement nous retrouver avec plusieurs fichiers du même nom sur une même machine (headers et bibliothèques). Il est donc essentiel de spécifier lesquels utiliser à notre chaîne de compilation. Lancer alors une recherche système (Windows, GNU\Linux …) afin de récupérer le chemin souhaité. -5- -6- Digital Signal Processor Annexes Attention, cette partie n'est à faire que si vous souhaitez générer une bibliothèque statique. Nous allons donc maintenant nous intéresser au processus de génération de bibliothèque statique sous Code Composer Studio (environnement Eclipse). Rappelons qu'une bibliothèque statique n'est qu'une archive (concaténation) de fichiers binaires objets pré-compilés (fichiers ELF dans le cas de notre ABI). Toujours préférer une ou quelques fonctions par fichier source plutôt que d'inclure toutes les fonctions d'une bibliothèque dans un seul fichier avant compilation. Ainsi par la suite, le linker sera apte à n'inclure à l'édition des liens que les binaires pré-compilés (objets relogeables) des fonctions réellement appelées par l'applicatif et non tout le contenu de la bibliothèque statique. Sélection des fichiers objets utiles. • Créer un nouveau projet dans un nouveau répertoire propre à la génération de la bibliothèque statique. Dans le cadre de cette trame de travaux pratiques, placer ce projet par exemple dans /dsp/c6678/firlib/pjct/. Sélectionner néanmoins dans les options avancées le format du fichier de sortie (sélection de l'archiver plutôt que du linker) : Advanced settings > Static Library -7- Digital Signal Processor Annexes • Créer un répertoire logique src et y placer les sources propres à la génération de la bibliothèque statique. Bien entendu, retirer le fichier source de test contenant la fonction main : • Dans les options de compilation du projet, lever les options d'optimisation -O3 et couper toute forme de génération de code de Debug : • Compiler le projet et voilà, c'est fini. Effectuer une recherche Windows ou GNU\Linux afin de rechercher le fichier nom_projet.lib puis le copier dans le répertoire /dsp/c6678/firlib/lib/. • Dans vos futurs projets, vous pourrez maintenant retirer les fichiers sources des projets pour n'inclure que la bibliothèque statique (options de compilation, partie linker). -8- Digital Signal Processor Annexes 2. INTEGRATED DEVELOPMENT ENVIRONMENT Digital Signal Processor Annexes Cette annexe présente également un tutoriel succinct des workspaces (espaces de travail) proposés par l'IDE Code Composer Studio v6. Le framework présenté est basé sur le plugin CDT (C/C+ + Development Tools) classiquement utilisé sous IDE éclipse pour du développement C/C++. Commençons par l'espace de travail d'édition et d'aide à la compilation : Les outils de développement et bibliothèques sont librement téléchargeables et installables depuis internet du moment que nous n'utilisons que la sonde de programmation XDS100 présente sur la maquette ou le mode simulation. Les outils deviennent alors payants si nous souhaitons travailler avec des sondes d'émulation évoluées, telle que la sonde XDS560 également présente à l'école. Voici ci-dessous des liens vers les outils logiciels (IDE, SDK) utilisés afin de réaliser la trame de travaux pratiques. Bien entendu, nous vous conseillons vivement d'installer ces outils sur vos machines (IDE et bibliothèques spécifiées ci-dessous) : http://processors.wiki.ti.com/index.php/Main_Page • IDE Code Composer Studio v6.0 basé sur Eclipse et ABI associées (Application Binary Interface). Nous vous conseillons une installation hors ligne. De plus, le téléchargement des outils depuis le site officiel de Texas Instruments requiert une inscription à leur site. La validation par TI de cette étape peut prendre quelques jours, ne pas être surpris : http://processors.wiki.ti.com/index.php/Category:Code_Composer_Studio_v6 • Bibliothèques CSL (Chip Support Library) proposées par TI pour la gestion des périphériques internes de leurs familles de processeurs. Bien choisir la bibliothèque associée au processeur C6678 : http://processors.wiki.ti.com/index.php/Chip_support_library Après installation, CSL est accessible depuis le répertoire suivant : C:\ti\pdk_C6678_version\packages\ti\csl\ • Bibliothèque DSPLIB (Digital Signal Processing Library) développée par TI et proposant des fonctions C pour le domaine du traitement numérique du signal et optimisée pour leurs architectures processeurs. Dans notre cas, seules les bibliothèques pour la famille C6600 nous intéressent : http://www.ti.com/tool/SPRC265 Après installation, la DSPLIB est accessible depuis le répertoire suivant : C:\ti\dsplib_c66x_version\packages\ti\dsplib\ -9- - 10 - Digital Signal Processor Annexes Présentons maintenant l'espace de travail d'aide au débogage sur cible, d'analyse et de communication avec la sonde XDS100 : Digital Signal Processor Annexes 3. EXTRAITS – SPRU187V – OPTIMIZING COMPILER Le schéma ci-dessous présente le workflow typique de la chaîne de compilation C6000 développée par Texas Instruments et utilisé durant la trame de travaux pratiques. Elle reprend bien entendu la trame de toute chaîne de compilation C (standards C89 et C99) : - 11 - - 12 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 13 - - 14 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 15 - - 16 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 17 - - 18 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 19 - - 20 - Digital Signal Processor Annexes Digital Signal Processor Annexes 4. EXTRAITS – SPRS691E – TMS320C6678 http://www.ti.com/product/tms320c6678 http://www.ti.com/lit/ds/symlink/tms320c6678.pdf - 21 - - 22 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 23 - - 24 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 25 - - 26 - Digital Signal Processor Annexes Digital Signal Processor Annexes 5. EXTRAITS – SPRABK5A1 – THROUGHPUT PERFORMANCE - 27 - - 28 - Digital Signal Processor Annexes Digital Signal Processor Annexes 6. EXTRAITS – SPRUGH7 – CPU AND INSTRUCTION SET - 29 - - 30 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 31 - - 32 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 33 - - 34 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 35 - - 36 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 37 - - 38 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 39 - - 40 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 41 - - 42 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 43 - - 44 - Digital Signal Processor Annexes Digital Signal Processor Annexes - 45 - - 46 - Digital Signal Processor Annexes Digital Signal Processor Annexes 7. REGLES DE CODAGE Ce document à pour objectif de fixer un cadre et des règles de codage en langage C pour les enseignements de Systèmes Embarqués à l'ENSICAEN. Cette démarche reflète les contraintes que vous aurez d'ici quelques années à suivre dans le monde de l'entreprise. La plupart des entreprises travaillant dans le domaine du développement logiciel, dont l'embarqué fait parti, imposent à leurs développeurs des règles semblables à celles-ci (beaucoup plus exhaustives en général). L'objectif étant de standardiser, clarifier, cadrer et faciliter le partage de codes sources au sein d'une équipe voir de l'entreprise. Les règles de codage imposées sont inspirées du ''Linux Kernel Coding Style'' et du ''GNU Coding Standard''. Voici le sommaire de ce document : 1. Indentation 2. Dénomination 3. Commentaire 4. Divers INDENTATION ● Tabulation : 8 caractères (à régler dans les préférences de l'éditeur de texte). switch (data) { case 'A': case 'B': /* comment */ data <<= 20; break; default: break; } ● Nombres de niveaux d'indentation : Éviter plus de 3 niveaux d'indentation imbriqués. Facilite la lisibilité du code. ● Nombres de colonnes par page : 80 caractères/colonnes par page (à régler dans les préférences de l'éditeur de texte). ● procédures : cas spécial pour le fonctions, ouvrir l’accolade au début de la ligne suivante int function (int x, int y) { int z ; ... return 0; } - 47 - - 48 - Digital Signal Processor Annexes ● Structures de contrôle : règles spécifiant l'indentation ainsi que les espaces à respecter pour l'écriture de structures de contrôle. Digital Signal Processor Annexes ● Macros : Toujours en Majuscule. Utiliser comme séparateur un ''_'' pour les noms complexes. Pour les macros fonctions, appliquer les même règles de dénomination que pour les fonctions. do { if (a == b) { ... } else if (a > b) { ... } else { ... } #define UART_BAUDRATE 0xFFFF #define mul (x, y) x*y COMMENTAIRES /* single statement */ if (condition) action1(); else action2(); } while (condition); DENOMINATION ● Fonctions : Toujours débuter par une minuscule. Pour donner un nom complexe, utiliser comme séparateur une Majuscule ou un ''_''. ● Variables locales : Toujours débuter par une minuscule. Les variables servant de compteur de boucle peuvent avoir un nom sans sens précis, par exemple ''i''. Sinon, toujours donner à une variable un nom court permettant d'identifier clairement son rôle. Déclarez vos variables locales en début de fonction (portabilité de code). ● Variables globales : Toujours débuter par une minuscule. Toujours donner à une variable globale un nom permettant d'identifier clairement son rôle (séparateurs ''_'' ou Majuscules). Bien évidemment, éviter tant que faire se peut les variables globales (ressources partagées). ● Toujours penser à expliquer ce que votre code fait et non comment il le fait ! ● C99 style : Ne pas utiliser ''//'' (problème de portabilité) ● C89 style : Toujours utiliser ''/* ... */'' (solution portable) ● Balise pour la documentation de code (exemple de Doxygen) : Insérer dans vos cartouches quelques tags standards, par exemple ceux de Doxygen. Attention, seuls les fichiers d'en-tête doivent contenir des balises pour la génération de documentation. Doxygen permet la génération automatique de documentation vers différents formats (HTML, PDF, CHM ...). Ne pas utiliser de caractères accentués dans vos commentaires. /** * @file example.h * @brief demo header file * @author */ int temp_conv; int tempProcessA, temp_process_B; void lm4567_codec_ init (void) { temp_process_B++; … } ● Définition de type : Toujours débuter par une Majuscule. Toujours définir un nom permettant d'identifier clairement le type des variables déclarés. HandleSpiModule a; Hmod a; /* converted data from temperature sensor */ /** * @struct Str_data_buffer * @brief Objet latch converted data from temperature sensor */ typedef struct { int buffSize int* allocCnvTab } Str_data_buffer; /** * @brief read data codec LM4567 controller * @param resetVal imput value * @param cnvResult converted value * @return null if data conversion error */ Handle_spi_module lm4567_codec_read (char resetVal, float cnvResult); // Bien ! // Pas Bien ! - 49 - - 50 - Digital Signal Processor Annexes ● Quelques tags supplémentaires (cf. www.doxygen.org ): /** * @example example insertion * @warning warning insertion * @li bullet point insertion * @mainpage main page comments insertion * @image picture insertion * @include code insertion */ Digital Signal Processor Annexes GLOSSAIRE A • • • • • • DIVERS • • ● Valeur de retour : Essayer de faire en sorte que la valeur de retour d'une fonction soit représentative de son bon traitement. Par exemple, retourne zéro (ou pointeur nul) en cas d'échec et différent de zéro en cas de succès. Les résultats des procédures seront retournés par pointeur via les paramètres d'entrée. • ABI : Application Binary Interface ADC : Analog to Digital Converter ALU : Arithmetic and Logical Unit AMD : Advanced Micro Devices ANSI : American National Standards Institute API : Application Programming Interface APU : Accelerrated Processor Unit ARM : société anglaise proposant des architectures CPU RISC 32bits ASCII : American Standar Code for Information Interchange B • • BP : Base Pointer BSL : Board Support Library C • • • • • CCS : Code Composer Studio CEM : Compatibilité ElectroMagnétique CISC : Complex Instruction Set Computer CPU : Central Processing Unit CSL : Chip Support Library D • • • • • • DAC : Digital to Analog Converter DDR : Double Data Rate DDR SDRAM: Double Data Rate Synchronous Dynamic Random Access Memory DMA : Direct Memory Access DSP : Digital Signal Processor DSP : Digital Signal Processing E • EDMA : Enhanced Direct Memory Access EUSART : Enhanced Universal Synchronous Asynchronous Receiver Transmitter EMIF : External Memory Interface EPIC : Explicitly Parallel Instruction Computing • FPU : Floating Point Unit • • • F - 51 - - 52 - Digital Signal Processor Annexes • • FLOPS : Floating-Point Operations Per Second FMA: Fused Multiply-Add Digital Signal Processor Annexes P • G • • • • • • • • GCC : Gnu Collection Compiler GLCD : Graphical Liquid Crytal Display GNU : GNU's Not UNIX GPIO : General Purpose Input Output GPP : General Purpose Processor GPU : Graphical Processing Unit • • • R • I • • • • • • • • • • IA-64 : Intel Architecture 64bits I2C : Inter Integrated Circuit ICC : Intel C++ Compiler IDE : Integrated Development Environment IDMA : Internal Direct memory Access IRQ : Interrupt ReQuest ISR : Interrupt Software Routine ISR : Interrupt Service Routine • • • • • • • • • • • • • L1D : Level 1 Data Memory L1I : Level 1 Instruction Memory (idem L1P) L1P : Level 1 Program Memory (idem L1I) Lx : Level x Memory LCD : Liquid Crytal Display LRU : Least Recently Used • • • • • • • • • • • • • MAC: Multiply Accumulate MCU : Micro Controller Unit MIMD : Multiple Instructions on Multiple Data MIPS : Mega Instructions Per Second MMU : Memory Managment Unit MPLABX : MicrochiP LABoratory 10, IDE Microchip MPU : Micro Processor Unit ou GPP MPU : Memory Protect Unit SDK : Software Development Kit SIMD : Single Instruction Multiple Date SOB : System On Board SOC : System On Chip SOP : Sums of products SP : Stack Pointer SP : Serial Port SPI : Serial Peripheral Interface SRAM : Static Random Access Memory SSE : Streaming SIMD Extensions STM32 : STMicroelectronics 32bits MCU T M • RAM : Random Access Memory RISC : Reduced Instruction Set Computer RS232 : Norme standardisant un protocole de communication série asynchrone RTOS : Real Time Operating System S L • PC : Program Counter PC : Personal Computer PIC18 : Famille MCU 8bits Microchip PLD : Programmable Logic Device POSIX : Portable Operating System Interface, héritage d'UNIX (norme IEEE 1003) PPC : Power PC • • TI : Texas Instruments TNS : Traitement Numérique du Signal TSC : Time Stamp Counter TTM : Time To Market U • • UART : Universal Asynchronous Receiver Transmitter USB : Universal Serial Bus V O • • OS : Operating System • • - 53 - VHDL : VHSIC Hardware Description langage VHSIC : Very High Speed Integrated Circuit VLIW : Very Long Intruction Word - 54 -