Steven Derrien Équipe R2D2 Conception logicielle et matérielle d’un décodeur MP3 sur la plate-forme NIOS-Stratix Steven Derrien Équipe R2D2
Contexte Module CSE en DIIC 2 (TP) TP « décodeur MP3 » Conception de circuits en VHDL Approche mixte logiciel-matériel Utilisation d’une plate-forme reconfigurable TP « décodeur MP3 » Exploiter les possibilités de la plate-forme NIOS Utiliser des méthodologies de conception Travailler sur une « véritable » application Objectif du mini-projet Obtenir une implémentation « temps-réel » de référence, qui sera utilisé pour les TP. (compat elect, perf, consommation)
Chaîne de décodage MP3
Travail déjà effectué Portage du code « C » référence Passage des calculs flottants en entiers Restructuration du code Portage sur le NIOS (pas de SGF) Mise en œuvre sur un OS temps-réel Utilisation de micro-OS II. Découpage de l’application en tâches Communications par files d’attente Version à 4 tâches fonctionnelle
Chaîne de décodage MP3 Tâche 1 Tâche 2 Tâche 3 Tâche 4
Résultats Etape durée %CPU Huffman 15% Dequantize ? Reorder Stereo IMDCT 28% SubBand 33% Implémentation 5x trop lente pour du temps-réel. Deux tâches utilisent 80% des ressources CPU Accélération matérielle de IMDCT et SubBand
Architecture visée Tâche 2 IMDCT Tâche 1 Tâche 4 SubBand Tâche 3 CNA Co-processeur (VHDL) FIFO Tâche 2 IMDCT Nios CPU + OS temps-réel FIFO Tâche 1 Co-processeur (VHDL) FIFO Tâche 4 SubBand Tâche 3 FIFO CNA FIFO Bus Avalon
Travail à réaliser Filtre IMDCT à finaliser Filtre SubBand à réaliser Mise au point de bancs de tests Intégration du composant dans l’appli Validation du fonctionnement Filtre SubBand à réaliser Conception VHDL de l’accélérateur Définition de l’interface soft-hard
Méthodologie de conception en VHDL Décomposition UT/UC Définition d’un squelette d’UT Mémoires, registres Opérateurs (*,+) Interconnexions Dérivation du contrôleur Associer à chaque calcul un instant d’exécution et une ressource de traitement. Plusieurs opération en parallèle Respects des dépendances de données Vérifier l’absence de conflits En déduire l’automate de contrôle.
Définition de l’interface On utilisera une interface à base de FIFOs matérielles, qui seront directement connectées au bus Avalon. Le contrôle de flux étant géré par le processeur NIOS 1 adx = 0 2 while(1) { 3 X[adx]=read_fifo(); 4 y=0;i=0; 5 adx=(adx++) % 8; 6 while(i<8) { y=y+X[adx]*C[i]; adx=(adx++) % 8; i++; } write_fifo(y); 11 };
Choix des ressources de mémorisation On alloue un registre pour chaque variable, et on utilise des mémoire (RAM ou ROM) pour stocker les tableaux. 1 adx = 0 2 while(1) { 3 X[adx]=read_fifo(); 4 y=0;i=0; 5 adx=(adx++) % 8; 6 while(i<8) { y=y+X[adx]*C[i]; adx=(adx++) % 8; i++; } write_fifo(y); 11 };
Choix des ressources de traitement On alloue ensuite des unités fonctionnelles à notre chemin de données en fonction des opérations présentes dans le bloc de traitement à accélérer. La duplication de certaines unités de traitement permet parfois de tirer parti d’un parallélisme au niveau instruction. 1 adx = 0 2 while(1) { 3 X[adx]=read_fifo(); 4 y=0;i=0; 5 adx=(adx++) % 8; 6 while(i<8) { y=y+X[adx]*C[i]; adx=(adx++) % 8; i++; } write_fifo(y); 11 };
Signaux de contrôles de l’UT On dispose alors d’une représentation presque complète de l’unité de traitement à laquelle il faut ajouter les signaux de contrôles. 1 adx = 0 2 while(1) { 3 X[adx]=read_fifo(); 4 y=0;i=0; 5 adx=(adx++) % 8; 6 while(i<8) { y=y+X[adx]*C[i]; adx=(adx++) % 8; i++; } write_fifo(y); 11 };
Allocation et ordonnancement On effectue ensuite l’allocation et l’ordonnancement des calculs : À chaque opération, on associe un opérateur matériel, et un instant d’exécution. 1 adx = 0 2 while(1) { 3 X[adx]=read_fifo(); 4 y=0;i=0; 5 adx=(adx++) % 8; 6 while(i<8) { y=y+X[adx]*C[i]; adx=(adx++) % 8; i++; } write_fifo(y); 11 }; T 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 rd_fifo X wr_en adx_rst adx_en i_rst i_en y_rst y_En wr_fifo
Dérivation du contrôleur Une fois l’allocation et l’ordonnancement effectués, on peut dériver l’automate de contrôle qui commandera l’unité de traitement.
Exercices Ordonnancement pour des opérateurs pipelinés Latence Add =1 cycle, Mul=2 cycles Reprendre l’exercice pour un filtre symétrique: 1 adx = 0 2 while(1) { 3 X[adx]=read_fifo(); 4 y=0;i=0; 5 adx=(adx++) % 4; 6 while(i<4) { y=y+(X[adx]+X[7-adx])*C[i]; adx=(adx++) % 4; i++; } write_fifo(y); 11 };
Méthodologie Traces d’exécution pour un fichier MP3 Testbench C fonctionnant sur traces Architecture VHDL TestBench pour chaque bloc important Testbench VHDL Simulation des E/S bus, et utilisation des traces TestBench C « in situ » intégrant le co-processeur Mise en œuvre de l’interface logicielle Mesure de performances (speed-up) Intégration à l’application
Exercice 1 Reprendre l’exemple SlavePeriph_fifo.vhd L’intégrer à un système NIOS. Écrire un programme C de test pour le NIOS. Valider le fonctionnement de l’ensemble.
Exercice 2 Se baser sur le code de IMDCT pour proposer un squelette d’unité de traitement avec les caractéristiques suivantes : Double multiplieur-accumulateur pipeliné ROM à double port pour iCOS[] et iWin[] ROM et RAM synchrones Réfléchir à un ordonnancement. Exploiter la présence de 2 MAC Attention aux délais : Mémoire synchrone MAC pipeliné
Code IMDCT while(1) { block_type=get_fifo(); for(i= 0;i<36;i++) iin[i]=get_fifo(); for(p= 0;p<9;p++){ isum = 0; isum2= 0; addr_a = 19+(p<<1); addr_b = 55+(p<<1); step_a = ((p<<2)+38); step_b = ((p<<2)+110); if (step_b>=144) step_b -= 144; for(m=0;m<18;m++) { isum += iin[m]*iCOS[addr_a]; isum2 += iin[m]*iCOS[addr_b]; addr_a = addr_a + step_a; if (addr_a>=144) addr_a -= 144; addr_b = addr_b + step_b; if (addr_b>=144) addr_b -= 144; } iout[p] = isum*iwin[block_type][p]; iout[17-p] = -isum*iwin[block_type][17-p]; iout[18+p] = isum2*iwin[block_type][18+p]; iout[35-p] = isum2*iwin[block_type][35-p]; for(i= 0;i<36;i++) put_fifo(iout[i]);
Unité de traitement pour l’IMDCT
Exercice 3 Se baser sur le code de SUBBAND pour compléter l’unité de traitement : Réfléchir à un ordonnancement. Dériver le contrôleur
Code SubBand while(1) { bufOffset[channel] = (bufOffset[channel] - 64) & 0x3ff; ibufferPtr = &(ibuf[channel][0]); // @ du début de buffer for (k=0; k<32; k++) bandPtr[k] = get_fifo(); for (i=0; i<64; i++) { isum = 0; for (k=0; k<32; k++) { isum += bandPtr[k]*filterInt[i][k]; } addr_ibuf = i+bufOffset[channel]; ibuffferPtr[addr_ibuf] = isum; for (k=0; k<32; j++) { for (i=0; i<16; i++) { addr_iwin = k + (i<<5); addr_ibuf = k + ((i<<5) + (((i+1)>>1)<<6)) + bufOffset[channel]) & 0x3FF; isum += iwindow[addr_iwin]*ibuffferPtr[addr_ibuf]; put_fifo(isum); channel= 1-channel;
Unité de traitement pour SubBand