Progettazione in VHDL del Pixel Shader v 1.4 Pag. 148 PARTE QUARTA Progettazione in VHDL del Pixel Shader v.1.4 Autori: Alessandro Giorgetti Daniele Petraccini La presente parte è organizzata nei seguenti capitoli: Capitolo 5. Specifiche di progetto del Pixel Shader v.1.4 Capitolo 6. Test bench per la simulazione in VHDL del processore Capitolo 7. Architettura interna del Pixel Shader v.1.4 Capitolo 8. Simulazioni Progettazione in VHDL del Pixel Shader v 1.4 Pag. 149 Capitolo 5. Specifiche di progetto del Pixel Shader v.1.4 5.1. Linee generali del progetto La presente parte della tesina è relativa alla progettazione in linguaggio VHDL del Pixel Shader, processore matematico del quale si è fornita una introduzione nel paragrafo 1.9.3. Prima di addentrarci nella discussione concernente la realizzazione del processore è bene esporre le linee generali del progetto, in particolar modo le specifiche, gli obiettivi e le limitazioni. Le specifiche sul funzionamento generale del processore sono state estratte dalla documentazione che le principali ditte costruttrici di schede grafiche mettono a disposizione dei programmatori: nei siti web di ATI, NVIDIA e anche nel sito Microsoft si trovano spiegazioni dettagliate del funzionamento di un pixel shader; tali informazioni sono state da noi usate come specifiche generali di progetto del Pixel Shader, versione 1.4. La documentazione di cui sopra non contiene tuttavia informazioni sull’architettura interna del processore; quest’ultima deve essere così progettata sulla base delle funzionalità note del processore, seguendo il criterio della razionalità e cercando di aumentare, ove possibile, la velocità di esecuzione delle diverse operazioni. E’ bene fin da subito mettere in evidenza un aspetto importante. Il pixel shader è per sua natura un processore molto complesso, sia per il fatto di dover compiere operazioni matematiche in virgola mobile, sia per la necessità di eseguire un grandissimo numero di istruzioni nell’unità di tempo. A fronte di tali problematiche, nell’ambito della presente tesina sono state introdotte alcune semplificazioni. Innanzitutto le ALU floating-point sono state progettate a livello comportamentale. D’altronde la possibilità di descrivere i blocchi funzionali di un sistema digitale usando diversi livelli di astrazione è una caratteristica fondamentale del linguaggio VHDL, che noi abbiamo usato a nostro vantaggio. In secondo luogo, per maggior semplicità, la struttura del processore è stata concepita per eseguire le operazioni di caricamento, decodifica ed esecuzione delle istruzioni ciascuna in un momento diverso, ovvero non si è provveduto a parallelizzare le tre fasi, come invece avviene comunemente nei processori, allo scopo di aumentarne le prestazioni. Occorre fare una precisazione relativa al periodo del clock di sistema e più in generale ai ritardi associati ai vari blocchi costituenti. Sotto tale punto di vista, il componente senza dubbio più critico è l’ALU floating-point, descritto nel nostro progetto a livello behavioural. La scorrelazione dall’hardware che deriva da una così elevata astrazione non consente di stimare i ritardi relativi alle porte logiche che implementeranno fisicamente l’ALU e quindi neanche il minimo periodo di clock, la cui scelta è indissolubilmente legata a tali ritardi. La scelta del clock è stata allora basata su una stima grossolana dei ritardi degli altri blocchi componenti, più semplici e dunque descrivibili in modo da essere maggiormente legati all’hardware. I tempi di esecuzione delle singole istruzioni dell’ALU sono stati fissati in maniera piuttosto arbitraria, ma in ogni Progettazione in VHDL del Pixel Shader v 1.4 Pag. 150 caso la scelta è stata operata in maniera coerente, tenendo conto della complessità di ciascuna operazione. Alla luce di quanto esposto finora, l’obiettivo che si vuole raggiungere con il progetto in esame è il seguente: ottenere una descrizione VHDL del Pixel Shader che garantisca una funzionalità del processore compatibile con le specifiche fornite da Microsoft, senza tuttavia ambire ad una architettura direttamente implementabile in hardware e paragonabile, per prestazioni, ai pixel shader attualmente presenti nel mercato. Seppur con le ovvie limitazioni derivanti dall’intrinseca complessità del sistema oggetto di analisi, si cercherà di progettare l’architettura del processore in maniera “intelligente”, così da ottimizzare l’esecuzione dell’intero instruction-set. 5.2. Terminologia Prima di addentrarci nell’analisi del Pixel Shader ci sembra utile spendere alcune righe per spiegare la terminologia adottata. L’espressione pixel shader non ha un significato univoco, ma può indicare in realtà cose tra loro differenti: § un progettista che parla di “pixel shader” si riferisce al processore, ovvero ad una specifica parte dell’hardware della scheda grafica che svolge funzionalità ben definite § per il programmatore, il “pixel shader” rappresenta il codice che viene caricato nella memoria interna del processore e successivamente eseguito. Allo scopo di evitare confusione, nel seguito della tesina indicheremo con l’espressione “Pixel Shader” (nota le iniziali maiuscole) il particolare processore da noi progettato in VHDL. L’espressione “pixel shader” sarà invece usata in senso lato, per intendere l’intera classe di processori. Infine, il codice caricato all’interno del processore sarà semplicemente indicato con il termine “shader”. Prima di proseguire è necessaria un’ultima precisazione. Esistono varie versioni di pixel shader, che in linea di massima si differenziano tra loro per il diverso numero di registri interni al processore e per il diverso set di istruzioni. Al momento attuale (giugno 2002) esistono le versioni 1.0, 1.1, 1.2, 1.3, 1.4. Noi abbiamo scelto di implementare l’ultima versione e di conseguenza il processore da noi progettato sarà indicato come Pixel Shader v.1.4. Le specifiche di funzionamento indicate nel resto di questo capitolo sono da considerarsi proprie di tale versione. 5.3. Schema di principio del Pixel Shader v.1.4 Dalla figura a pagina seguente, illustrante lo schema di principio del Pixel Shader v.1.4, sono visibili i blocchi di base che formano l’architettura interna del processore: l’unità logico aritmetica (ALU), i banchi di registri e l’Instruction Pool. Il funzionamento del processore può essere molto sommariamente descritto come segue: i dati sul pixel corrente provenienti dall’esterno sono memorizzati in opportuni registri in ingresso; l’ALU compie una serie di operazioni su tali dati e mette i risultati su opportuni registri. Al termine Progettazione in VHDL del Pixel Shader v 1.4 Pag. 151 dell’esecuzione delle istruzioni, i dati presenti su un apposito registro di uscita vengono inviati al resto della pipeline grafica. Color Registers PIXEL SHADER v0 v1 Constant Registers Texture Registers c0 t0 c1 t1 Pixel Shader ALU Instruction Pool Texture t5 Addressing c7 r0 r1 r5 Temporary Registers Output Analizziamo ora con maggiore dettaglio la struttura del Pixel Shader, partendo dalla descrizione dei quattro banchi di registri. 5.3.1 Banco di registri c Il banco c contiene 8 registri, indicati con c0..c7, detti registri delle costanti. Essi sono registri di sola lettura usati per contenere dei valori costanti, caricati prima dell’esecuzione dello shader. Ciascun registro c è organizzato in quattro valori reali. 5.3.2 Banco di registri r Il banco r contiene 6 registri, indicati con r0..r5, detti registri temporanei. Essi sono registri in lettura/scrittura usati per memorizzare dei dati temporanei, sotto forma di quaterne di valori reali. Il registro r0 funge inoltre da registro di uscita: al termine dell’esecuzione dello shader, il contenuto di r0 è il colore di uscita per il pixel corrente. 5.3.3 Banco di registri t Il banco t contiene 6 registri, indicati con t0..t5, detti registri delle texture. Essi sono registri di sola lettura caricati prima dell’esecuzione dello shader con le coordinate delle texture dei vari stage. Ciascun registro è formato da quattro valori reali. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 152 5.3.4 Banco di registri v Il banco v contiene i registri v0 e v1, detti registri del colore, in quanto sono comunemente caricati con i dati rispettivamente del colore diffuso e del colore speculare associati al pixel corrente. Ogni registro è organizzato in quattro valori reali, che possono essere visti come le componenti R, G, B, A del colore. I registri v sono caricati prima dell’esecuzione dello shader e possono essere usati solo in lettura. Osservazioni sui registri - Nei paragrafi precedenti abbiamo detto che ciascun registro interno al Pixel Shader contiene quattro valori reali; questa affermazione merita ora un chiarimento. Secondo le specifiche sul pixel shader alle quali abbiamo fatto riferimento nel nostro progetto, le quantità da noi sopra definite reali debbono essere implementate nel formato fixed-point; nella nostra versione del processore abbiamo invece deciso di operare una estensione, memorizzando ogni quantità reale come valore floating-point. Tale scelta è legata essenzialmente al fatto che il Pixel Shader è stato progettato in una fase successiva al Vertex Shader, il che ha permesso di riutilizzare molti componenti già progettati, ed inoltre procedure, funzioni e librerie già scritte. Essendo il Vertex Shader basato su una aritmetica a virgola mobile, il Pixel Shader ha risentito direttamente di tale scelta. Singole componenti dei registri - Ciascun registro racchiude quattro valori floating-point, che sono interpretati come le componenti R, G, B, A del dato, pensato come colore. Per fare riferimento ad una singola componente esiste una comoda notazione che fa uso dei suffissi .r, .g, .b, .a da applicare al registro. Facciamo un esempio: la componente G del registro v0 sarà indicata con la notazione v0.g. Range dei registri - Le specifiche impongono un range ai quattro banchi di registri, ovvero fissano il valore minimo e quello massimo per il dato contenuto in ciascun registro. Ad esempio, i registri delle costanti possono contenere valori compresi tra -1 e +1, mentre per i registri del colore il range è fissato nell’intervallo compreso tra 0 e +1. Nella nostra implementazione si è supposto di poter memorizzare quantità in virgola mobile senza alcuna restrizione sul range. 5.3.5 Instruction Pool Oltre ai quattro banchi di registri finora descritti esiste un ulteriore ed importante elemento di memoria all’interno del Pixel Shader: si tratta dell’Instruction Pool, blocco la cui funzione è memorizzare lo shader, ovvero la sequenza di istruzioni che dovranno essere eseguite una volta caricati i dati relativi al pixel corrente. 5.3.6 Pixel Shader ALU L’unità aritmetico logica del Pixel Shader esegue istruzioni di due tipi: § istruzioni aritmetiche: si tratta di elementari operazioni matematiche sui dati in ingresso Progettazione in VHDL del Pixel Shader v 1.4 § Pag. 153 istruzioni di texture addressing: eseguono una elaborazione delle coordinate delle texture, eventualmente usando tali dati per effettuare il campionamento di una texture (texture sampling); in questo caso entra in gioco un blocco esterno al Pixel Shader, denominato Texture Sampler. 5.3.7 Funzionamento del Pixel Shader Ora che abbiamo introdotto tutti i blocchi fondamentali che compongono il Pixel Shader, possiamo spiegare il funzionamento del processore con un maggiore grado di dettaglio. Innanzitutto dobbiamo distinguere le tre fasi di funzionamento che sono: il caricamento dello shader, il caricamento del pixel e l’esecuzione dello shader. La prima fase in ordine temporale è il caricamento dello shader, che consiste nella memorizzazione all’interno dell’Instruction Pool di tutte le istruzioni da eseguire per ciascun pixel. In tale fase si effettua anche il caricamento del banco di registri c, ovvero delle costanti che possono essere utilizzate all’interno delle varie istruzioni. La seconda fase è il caricamento del pixel. Essa consiste nella memorizzazione del colore diffuso e del colore speculare rispettivamente nei registri v0 e v1 e nella memorizzazione delle coordinate delle texture nel banco di registri t. Precisiamo che i dati sul colore e sulle texture sono riferiti al pixel corrente. La terza fase è l’esecuzione dello shader, durante la quale vengono eseguite tutte le istruzioni presenti all’interno dell’Instruction Pool. Terminato lo shader, il contenuto del registro r0 rappresenta il colore di uscita per il pixel corrente. Esaurita la terza fase è possibile caricare il pixel successivo (ovvero si ritorna alla seconda fase) e rieseguire lo shader partendo dalla prima istruzione (terza fase). Una volta che tutti i pixel sono stati elaborati con lo shader impostato, si ha la facoltà di caricare il processore con un nuovo shader e dunque ricominciare il funzionamento partendo dalla prima fase. 5.4. Architettura interna dell’ALU Ritornando alla figura di pag. 151, illustrante l’architettura del Pixel Shader, si vede che il posto centrale è occupato dall’ALU, sulla cui funzionalità abbiamo fornito alcuni cenni. In questo paragrafo vogliamo analizzare più in dettaglio la struttura interna e le funzioni svolte da tale blocco fondamentale. La figura all’inizio di pagina seguente, tratta dalla documentazione Microsoft, mostra lo schema di principio dell’ALU del Pixel Shader. Il dato principale da mettere in evidenza è la struttura a pipeline parallele dell’ALU: § l’area ombreggiata a sinistra indica la cosiddetta vector pipeline, la quale opera su dati vettoriali costituiti dalla terna di canali (R, G, B); la pipeline in questione viene anche chiamata color pipe § l’area ombreggiata a destra indica la cosiddetta scalar pipeline, che opera invece su dati scalari, rappresentati dal canale (A); ad essa Progettazione in VHDL del Pixel Shader v 1.4 Pag. 154 ci si riferisce anche con l’espressione alpha pipe. Le due pipeline operano indipendentemente tra loro e le operazioni che ciascuna di esse esegue in un certo istante possono anche essere diverse. Procediamo ora alla descrizione in ordine logico dei singoli blocchi presenti nel diagramma, le cui etichette indicano - con espressione inglese - la funzione da ciascuno di essi svolta. Input Registers - Tali registri forniscono i dati in ingresso all’ALU del Pixel Shader sotto forma di quaterne (R, G, B, A). Component Copy - Tale blocco rappresenta uno swizzler, ovvero un dispositivo che esegue la replica del contenuto di un canale specificato sugli altri canali. Ad esso ci si riferisce anche con l’espressione inglese source register selector. Modify Data - Chiamato anche source register modifier, esegue delle elementari operazioni matematiche sui dati letti dai registri sorgenti prima di essere usati per l’esecuzione delle istruzioni. Execute Instruction - Il blocco in questione rappresenta la vera e propria esecuzione delle istruzioni, il cui scopo è quello di effettuare operazioni aritmetiche o di texture addressing sui dati in ingresso. Nel caso in cui la vector pipepine e la scalar pipeline eseguano parallelamente due istruzioni diverse si parla di instruction pairing (su tale argomento ritorneremo nel paragrafo 5.11). Modify Result - Chiamato anche instruction modifier, tale blocco esegue delle elementari operazioni matematiche sul risultato di un’istruzione prima della scrittura sul registro. Masking - L’operazione di mascheratura permette di controllare, attraverso la cosiddetta destination register write mask, quali componenti del registro destinazione sono scritte dall’istruzione. Output Register - Indica il registro destinazione. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 155 5.5. Istruzioni del Pixel Shader v.1.4 Nel paragrafo precedente abbiamo visto che il dato che viene scritto sul registro destinazione dopo l’esecuzione di un’istruzione dipende non soltanto dall’istruzione stessa, ma anche dallo swizzler, dal source register modifier, dall’instruction modifier e dalla write mask. Nel presente paragrafo vogliamo fissare l’attenzione solo sulla parte di esecuzione dell’istruzione vera e propria, rimandando a paragrafi successivi la descrizione dettagliata di tutti gli altri blocchi che influenzano il dato finale. Nelle pagine che seguono esporremo con chiarezza l’intero instructionset del Pixel Shader v.1.4, precisando le regole di sintassi necessarie per la scrittura di uno shader corretto. 5.5.1 Tipi di istruzioni L’instruction-set del Pixel Shader v.1.4 può essere suddiviso in cinque tipi diversi di istruzioni: § version instruction (PS): in uno shader c’è sempre una sola istruzione atta a specificarne la versione e tale istruzione deve essere la prima. § constant instruction (DEF): serve a definire delle costanti. § phase instruction (PHASE): serve a scindere lo shader in due fasi, denominate fase 1 e fase 2; l’istruzione PHASE viene anche definita phase marker. § texture address instructions: manipolano i dati relativi alle coordinate delle texture associate ai vari stage. § arithmetic instructions: includono comuni operazioni matematiche. 5.5.2 Struttura di uno shader Le istruzioni presenti all’interno di uno shader devono apparire in un ordine ben definito ed inoltre esiste un massimo numero di istruzioni consentite. Allo scopo si osservi lo schema sottostante, che illustra il cosiddetto instruction flow dello shader 1.4. La prima istruzione serve ad indicare la versione dello shader. Successivamente ci sono le istruzioni per la definizione delle costanti. Nella prima fase dello shader possono essere usate fino a 6 istruzioni di texture addressing seguite da un massimo di 8 istruzioni aritmetiche. Segue l’istruzione PHASE, che separa la fase 1 dalla fase 2. Nella seconda fase dello shader si possono usare ancora 6 istruzioni di Progettazione in VHDL del Pixel Shader v 1.4 Pag. 156 texture addressing seguite da un massimo di 8 istruzioni aritmetiche. La struttura qui presentata definisce il “massimo” shader che può essere scritto, nel senso che non possono essere aggiunte ulteriori istruzioni al di fuori dello schema mostrato. E’ tuttavia possibile fare shader più “piccoli”, ovvero usare un numero inferiore di istruzioni. In particolare valgono le seguenti regole: § le istruzioni per la definizione di costanti non sono obbligatorie § è possibile scrivere uno shader che abbia solo la fase 2: allo scopo è sufficiente non inserire alcuna istruzione nella fase 1 e tralasciare anche l’istruzione PHASE, che resta sottintesa. 5.5.3 Istruzioni PS, DEF, PHASE Cominciamo la descrizione dettagliata dell’instruction-set del Pixel Shader v.1.4 partendo dalle seguenti istruzioni: version instruction, constant instruction, phase instruction. Istruzione PS - E’ l’istruzione che serve a definire la versione dello shader. La sua sintassi è la seguente: ps.1.4 Ricordiamo che tale istruzione è la prima dello shader. Istruzione DEF - Tale istruzione fornisce un metodo per definire delle costanti da utilizzare all’interno dello shader. La sua sintassi è la seguente: def dest, fVal0, fVal1, fVal2, fVal3 dest : rappresenta il registro destinazione, che può essere uno dei registri c0..c7, più brevemente indicato con cn. fVal0, fVal1, fVal2, fVal3 : rappresentano dei valori floatingpoint, che devono essere compresi tra -1 e +1. Ricordiamo che la DEF deve essere inserita dopo l’istruzione PS e prima di qualsiasi istruzione aritmetica o di texture addressing. Istruzione PHASE - Serve a marcare la transizione tra la fase 1 e la fase 2. Nel caso l’istruzione PHASE non sia presente, l’intero shader è considerato in fase 2. La sintassi è semplice: phase 5.5.4 Istruzioni aritmetiche Forniamo l’elenco completo delle istruzioni aritmetiche, spiegandone la sintassi e le peculiarità. Istruzione ADD - Esegue l’addizione componente per componente di due registri. Sintassi: Progettazione in VHDL del Pixel Shader v 1.4 Pag. 157 add dest, src0, src1 : il registro destinazione è un registro rn. src0, src1 : i registri sorgenti possono essere cn, rn nella fase 1; cn, rn, vn nella fase 2. L’istruzione evidenziata esegue la seguente operazione: dest dest = src0 + src1 che, per chiarezza, riportiamo sviluppata nelle singole componenti: dest.r = src0.r + src1.r dest.g = src0.g + src1.g dest.b = src0.b + src1.b dest.a = src0.a + src1.a Istruzione BEM - Applica una trasformazione che serve a simulare il cosiddetto bump environment-mapping. Sintassi: bem dest.rg, src0, src1 : il registro destinazione è un registro rn. src0 : è un registro cn o rn. src1 : è un registro rn. L’istruzione in questione esegue i seguenti calcoli: (n: destination register index) dest dest.r = src0.r + M00(stage n) * src1.r + M10(stage n) * src1.g dest.g = src0.g + M01(stage n) * src1.r + M11(stage n) * src1.g Le quantità M00, M10, M01, M11 che compaiono nelle formule precedenti sono gli elementi di una matrice 2x2, detta bump-environment matrix. Esiste una matrice per ogni stage number n, in modo tale che i coefficienti suddetti dipendono in realtà dall’indice del registro destinazione. Si può pensare che esista un modulo esterno al pixel shader, interpellato durante l’esecuzione dell’istruzione BEM, la cui funzione è quella di fornire i quattro coefficienti M00(n), M10(n), M01(n), M11(n). Come vedremo nel seguito, nel nostro progetto il modulo in questione è il blocco chiamato BEM Module. Osservazioni: l’istruzione BEM può essere usata una sola volta all’interno dello shader ed inoltre essa deve comparire nella fase1. Ai fini del conteggio del numero di istruzioni, la BEM viene considerata come 2 istruzioni aritmetiche. Istruzione CMP - Consente di effettuare una scelta condizionata tra i sorgenti src1 e src2 sulla base della condizione src0 >= 0. Sintassi: cmp dest, src0, src1, src2 : il registro destinazione è un registro rn. src0, src1, src2 : i registri sorgenti possono essere cn, rn nella fase 1; cn, rn, vn nella fase 2. Spiegazione: la condizione src0 >= 0 viene valutata componente per componente; nel caso essa sia verificata si sceglie src1, altrimenti src2. dest Progettazione in VHDL del Pixel Shader v 1.4 Pag. 158 Istruzione CND - Consente di effettuare una scelta condizionata tra i sorgenti src1 e src2 sulla base della condizione src0 > 0.5. Sintassi: cnd dest, src0, src1, src2 : il registro destinazione è un registro rn. src0, src1, src2 : i registri sorgenti possono essere cn, rn nella fase 1; cn, rn, vn nella fase 2. Spiegazione: la condizione src0 > 0.5 viene valutata componente per componente; nel caso essa sia verificata si sceglie src1, altrimenti src2. dest Istruzione DP3 - Calcola un prodotto scalare a 3 componenti (R, G, B) e replica il risultato su tutti e quattro i canali. Sintassi: dp3 dest, src0, src1 : il registro destinazione è un registro rn. src0, src1 : i registri sorgenti possono essere cn, rn nella fase 1; cn, rn, vn nella fase 2. L’istruzione evidenziata sopra esegue quanto segue: dest dest.r = dest.g = dest.b = dest.a = src0.r * src1.r + src0.g * src1.g + src0.b * src1.b Istruzione DP4 - Calcola un prodotto scalare a 4 componenti (R, G, B, A) e replica il risultato su tutti e quattro i canali. Sintassi: dp4 dest, src0, src1 : il registro destinazione è un registro rn. src0, src1 : i registri sorgenti possono essere cn, rn nella fase 1; cn, rn, vn nella fase 2. L’istruzione evidenziata sopra esegue quanto segue: dest dest.r = dest.g = dest.b = dest.a = src0.r * src1.r + src0.g * src1.g + src0.b * src1.b + src0.a * src1.a Istruzione LRP - Esegue un’interpolazione lineare. Sintassi: lrp dest, src0, src1, src2 : il registro destinazione è un registro rn. src0, src1, src2 : i registri sorgenti possono essere cn, rn nella fase 1; cn, rn, vn nella fase 2. L’istruzione evidenziata esegue il seguente calcolo: dest dest = src0 * src1 + (1-src0) * src2 Istruzione MAD - Esegue una moltiplicazione seguita da una addizione. La sintassi è la seguente: mad dest, src0, src1, src2 : il registro destinazione è un registro rn. src0, src1, src2 : i registri sorgenti possono essere cn, rn nella fase 1; dest Progettazione in VHDL del Pixel Shader v 1.4 Pag. 159 cn, rn, vn nella fase 2. L’istruzione evidenziata esegue il seguente calcolo: dest = src0 * src1 + src2 Istruzione MOV - Copia il contenuto del registro sorgente nel registro destinazione. Sintassi: mov dest, src : il registro destinazione è un registro rn. src : il registro sorgente può essere cn, rn nella fase 1; cn, rn, vn nella fase dest 2. Istruzione MUL - Esegue la moltiplicazione componente per componente di due registri. Sintassi: mul dest, src0, src1 : il registro destinazione è un registro rn. src0, src1 : i registri sorgenti possono essere cn, rn nella fase 1; cn, rn, vn nella fase 2. L’istruzione evidenziata esegue la seguente operazione: dest dest = src0 * src1 Istruzione NOP - Non esegue alcuna operazione. Sintassi: nop Istruzione SUB - Esegue la sottrazione componente per componente fra due registri. Sintassi: sub dest, src0, src1 : il registro destinazione è un registro rn. src0, src1 : i registri sorgenti possono essere cn, rn nella fase 1; cn, rn, vn nella fase 2. L’istruzione evidenziata esegue la seguente operazione: dest dest = src0 - src1 5.5.5 Istruzioni di texture addressing Istruzione TEXCRD - Copia il contenuto di un registro delle texture sul registro destinazione. Sintassi: texcrd dest, src : il registro destinazione è un registro rn. src : il registro sorgente è un registro tn. Osservazione: l’istruzione in questione equivale ad una MOV da un registro tn ad un registro rn, con la particolarità che solo le prime tre componenti lette dal registro tn sono copiate e di conseguenza la quarta componente del registro destinazione rimane non settata. dest Progettazione in VHDL del Pixel Shader v 1.4 Pag. 160 Istruzione TEXDEPTH - Calcola una quantità, detta depth value, che verrà usata nel test di confronto del depth buffer per il pixel corrente. Sintassi: texdepth dest : il registro destinazione può essere solo il registro r5. L’istruzione evidenziata esegue quanto segue: dest (r5.g <> 0) => (r5.g = 0) => r5.r = r5.r / r5.g r5.r = 1 Osservazioni: l’istruzione TEXDEPTH può essere usata solo nella fase 2. Nella nostra implementazione del processore è presente un segnale di uscita a 32 bit che corrisponde al contenuto del registro r5.r. Inoltre, qualora nello shader compaia l’istruzione TEXDEPTH, la linea di uscita del Pixel Shader chiamata PS_DepthEn viene settata ad ‘1’. Istruzione TEXKILL - Annulla il rendering del pixel corrente se almeno una delle prime tre componenti del registro sorgente è minore di zero. Sintassi: texkill src : il registro sorgente può essere rn o tn. Osservazioni: l’istruzione TEXKILL può essere usata solo nella fase 2. Nella nostra implementazione del processore, l’annullamento del rendering del pixel corrente equivale a settare ad ‘1’ la linea di uscita del Pixel Shader chiamata PS_PixelKill. src Istruzione TEXLD - Scrive nel registro destinazione i dati sul colore (R, G, B, A) che si ottengono da una operazione di texture sampling. Sintassi: texld dest, src : il registro destinazione è un registro rn. src : il registro sorgente può essere tn nella fase 1; rn, tn nella fase 2. L’istruzione evidenziata opera come segue: utilizza le prime tre componenti del registro sorgente (u, v, w) per campionare la texture il cui stage number corrisponde all’indice del registro destinazione. La quaterna di valori (R, G, B, A) che si ottiene come risultato viene scritta nel registro destinazione. Osservazione: come abbiamo già avuto modo di vedere, l’operazione di texture sampling viene eseguita da un modulo esterno al Pixel Shader che nel nostro progetto è stato chiamato Texture Sampler. dest 5.6. Source register selector (swizzler) Nel paragrafo 5.4 abbiamo visto che, nel tipico flusso di esecuzione di un’istruzione, il dato in ingresso proveniente dal registro sorgente è sottoposto ad una successione di modificazioni o trasformazioni fino all’ottenimento del dato finale, che viene scritto nel registro destinazione. Nel paragrafo 5.5 abbiamo analizzato la trasformazione centrale della catena, ovvero quella legata all’istruzione vera e propria, che può consistere in una operazione aritmetica o di texture addressing. In questo e nei prossimi paragrafi analizzeremo invece le modificazioni Progettazione in VHDL del Pixel Shader v 1.4 Pag. 161 apportate sui dati dagli altri blocchi. Cominceremo in particolare dal componente detto swizzler. Il source register selector (o swizzler) è un modificatore che prende in ingresso il dato contenuto nel registro sorgente e lo modifica replicando il singolo canale specificato su tutti e quattro i canali. L’operazione di swizzling ha la seguente sintassi: src.channel : è il registro sorgente. channel : è uno dei quattro canali r, g, b, a. L’uso del modificatore è spiegato nella tabella seguente. src Source Register Selectors Sintassi Spiegazione Red replicate src.r replica il canale rosso su tutti i canali Green replicate src.g replica il canale verde su tutti i canali Blue replicate src.b replica il canale blu su tutti i canali Alpha replicate src.a replica il canale alfa su tutti i canali Vediamo, con un esempio, come si utilizza il modificatore all’interno di un’istruzione. L’istruzione add r0, v0, v1.g esegue la somma tra il dato contenuto nel registro v0 e il dato formato da quattro canali tutti coincidenti con il canale g del registro v1. Per maggiore chiarezza riscriviamo l’operazione sviluppando le singole componenti: r0.r = v0.r + v1.g r0.g = v0.g + v1.g r0.b = v0.b + v1.g r0.a = v0.a + v1.g Osservazioni § Deve essere chiaro che lo swizzler usa il contenuto del registro come dato in ingresso e dunque non provoca alcuna modificazione del registro stesso. § Lo swizzling è in assoluto la prima modificazione che il dato letto dal registro sorgente subisce nell’esecuzione di una istruzione. § Il modificatore in questione può essere usato soltanto all’interno di istruzioni aritmetiche. 5.7. Source register modifier Il source registrer modifier permette di eseguire delle semplici operazioni matematiche sui dati a cui esso è applicato. Il modificatore in esame prende in ingresso i dati letti dai registri sorgenti, eventualmente sottoposti a swizzling, e fornisce in uscita i dati che saranno direttamente usati dall’istruzione. Esistono in realtà cinque tipi diversi di modificatori, riportati nella tabella a pagina seguente, nella quale si illustra la sintassi e il significato di ciascuno. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 162 Source Register Modifier Sintassi Descrizione Bias src_bias Sottrae 0.5 da tutte le componenti del registro: output = input – 0.5 Invert 1-src Sottrae ciascuna componente del registro dall’unità: output = 1 - input Negate -src Cambia segno a tutte le componenti del registro: output = - input Scalex2 src_x2 Raddoppia tutte le componenti del registro: output = 2 * input Signed Scaling src_bx2 Sottrae 0.5 da ogni componente e raddoppia il risultato: output = 2 * (input – 0.5 ) Osservazioni: § I modificatori descritti possono essere usati solo all’interno delle istruzioni aritmetiche. § Il modificatore applicato ad un registro non ne cambia il contenuto, ma modifica solo il dato letto. § I modificatori illustrati non possono essere applicati ai registri c. § I source register modifiers possono essere usati congiuntamente ai source register selectors; in ogni caso la prima operazione ad essere eseguita è quella di swizzling. § I cinque source register modifier illustrati possono essere combinati tra loro. Al riguardo esistono delle restrizioni: Negate può essere combinato con bias, scalex2 o signed scaling; quando è combinato, negate viene eseguito per ultimo; Invert non può essere combinato con nessun altro modificatore. Facciamo un esempio che spieghi ulteriormente l’uso dei modificatori. Nell’istruzione mul r0, 1-r1.r, -r2_bx2 il modificatore invert viene applicato al registro r1, che è preventivamente sottoposto a swizzling; al registro r2 vengono invece applicati due modificatori: il signed scaling e l’invert. L’istruzione in questione esegue allora i seguenti calcoli: r0.r = (1-r1.r)*(-2*(r2.r-0.5)) r0.g = (1-r1.r)*(-2*(r2.g-0.5)) r0.b = (1-r1.r)*(-2*(r2.b-0.5)) r0.a = (1-r1.r)*(-2*(r2.a-0.5)) 5.8. Instruction modifier L’instruction modifier permette di modificare il dato proveniente dall’esecuzione di un’istruzione prima che lo stesso venga scritto sul registro destinazione. La lista completa dei modificatori istruzione, con la sintassi e la relativa spiegazione, è mostrata nella tabella a pagina seguente. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 163 Instruction Modifier Sintassi* Descrizione _x2 instr_x2 Moltiplica per 2 il risultato dell’istruzione _x4 instr_x4 Moltiplica per 4 il risultato dell’istruzione _x8 instr_x8 Moltiplica per 8 il risultato dell’istruzione _d2 instr_d2 Divide per 2 il risultato dell’istruzione _d4 instr_d4 Divide per 4 il risultato dell’istruzione _d8 instr_d8 Divide per 8 il risultato dell’istruzione _sat instr_sat Satura il risultato dell’istruzione, eseguendo il clamping51 tra 0e1 (*) instr è l’identificativo dell’istruzione (es. add). Osservazioni Gli instruction modifiers possono essere usati soltanto all’interno di istruzioni aritmetiche. Il modificatore _sat può essere combinato con uno degli altri, in tal caso viene eseguito per secondo. Esempio: add_x2_sat r0, v0, v1 L’istruzione appena scritta viene eseguita tramite la successione dei seguenti passi: § prima di tutto vengono sommati tra loro i contenuti dei registri sorgenti v0 e v1 § la somma così ottenuta viene moltiplicata per 2 (modificatore _x2) § il risultato del passo precedente subisce il modificatore _sat, ovvero ciascuna delle quattro componenti viene sottoposta al clamping tra 0 e 1. 5.9. Destination register write mask Con l’espressione destination register write mask si indica la maschera usata per controllare quali canali (R, G, B, A) del registro destinazione vengono scritti dall’istruzione. L’insieme delle maschere permesse è riportato nella tabella a pagina seguente. 51 Con il termine clamping si indica la seguente operazione espressa in metalinguaggio: if ( input < 0 ) then output = 0 elseif ( input > 1 ) then output = 1 else output = input end if Progettazione in VHDL del Pixel Shader v 1.4 Pag. 164 Write Mask Sintassi Descrizione red, green, blue, alpha dest.rgba Tutti i canali del registro destinazione sono scritti none dest Come sopra color dest.rgb Vengono scritti i canali (R, G, B) del registro destinazione alpha dest.a Viene scritto solo il canale (A) del registro destinazione red dest.r Viene scritto solo il canale (R) del registro destinazione green dest.g Viene scritto solo il canale (G) del registro destinazione blue dest.b Viene scritto solo il canale (B) del registro destinazione arbitrary (*) (*) (*) La maschera arbitrary è una qualunque combinazione di canali, elencati però nell’ordine r, g, b, a. Ad esempio, con la maschera dest.rba vengono aggiornati i canali red, blue e alpha del registro destinazione. Osservazione Le destination register write mask riportate sopra sono usate all’interno di istruzioni aritmetiche. 5.10. Modificatori e maschere per le istruzioni TEXLD e TEXCRD I modificatori e le maschere di cui ci siamo occupati nei precedenti paragrafi sono utilizzabili, come detto, solo all’interno di istruzioni aritmetiche. In realtà esistono due istruzioni di texture addressing, TEXLD e TEXCRD, che supportano un proprio insieme di maschere e modificatori; tali argomenti sono l’oggetto del presente paragrafo. 5.10.1 Source register selectors Gli swizzlers utilizzabili con le istruzioni nella seguente tabella. TEXLD Swizzler Sintassi Spiegazione rgb src.rgb Fornisce il dato (R, G, B, B) rga src.rga Fornisce il dato (R, G, A, A) e TEXCRD sono riportati Osservazione Le due istruzioni di texture addressing prese in considerazione non leggono mai più di tre componenti dal registro sorgente; i modificatori .rgb e .rga forniscono l’opzione di prendere, quale terza componente letta, il canale blue o il canale alpha del registro sorgente. 5.10.2 Source register modifiers Riportiamo nella tabella a pagina seguente i source register modifiers utilizzabili con le istruzioni TEXLD e TEXCRD. Progettazione in VHDL del Pixel Shader v 1.4 Source Register Modifier _db Sintassi src_db _da src_da Pag. 165 Descrizione* r' g' b' a' r' g' b' = = è è = = è r/b (r' = 1.0 g/b (g' = 1.0 indefinito indefinito r/a (r' = 1.0 g/a (g' = 1.0 indefinito se b = 0) se b = 0) se a = 0) se a = 0) a' è indefinito (*) (r, g, b, a)= componenti in ingresso;(r', g', b', a')= componenti in uscita. 5.10.3 Destination register write mask Per l’istruzione TEXLD si rende necessario l’uso della maschera .rgba. Con l’istruzione TEXCRD si possono usare solo le maschere .rgb o .rg. Terminiamo qui la spiegazione delle peculiarità delle istruzioni TEXLD e TEXCRD, rimandando alla documentazione Microsoft per ulteriori dettagli. 5.11. Instruction pairing Come già accennato, all’interno del Pixel Shader vi sono due pipeline che operano in parallelo: la vector pipeline, che elabora i canali (R, G, B), e la scalar pipeline, responsabile dell’elaborazione del canale (A). Come conseguenza, il Pixel Shader è potenzialmente capace di eseguire contemporaneamente due istruzioni aritmetiche, una sulla color pipe e l’altra sull’alpha pipe; in tal caso si parla di instruction pairing. La possibilità di esecuzione contemporanea di due istruzioni è soggetta a determinati vincoli. Innanzitutto le istruzioni che possono andare in pairing sono solo una sottoclasse delle istruzioni aritmetiche e più esattamente: ADD, CMP, CND, DP3, LRP, MAD, MOV, MUL, SUB. In secondo luogo occorre specificare, mediante la destination register write mask, la modalità con la quale le due istruzioni occupano le pipeline: l’istruzione che usa la vector pipeline deve avere la maschera .rgb, quella che usa la scalar pipeline deve avere la maschera .a. L’istruzione DP3, usando la color pipe, deve necessariamente avere la maschera .rgb associata al registro destinazione. Infine, il pairing viene esplicitamente indicato nello shader facendo precedere la seconda istruzione della coppia dal segno più (+). Esempio: il codice mul r0.rgb, t0, v0 + add r1.a, r1, c2 permette di eseguire una moltiplicazione dei canali (R, G, B) e una contemporanea somma dei canali (A); si noti che sia i registri sorgenti che Progettazione in VHDL del Pixel Shader v 1.4 Pag. 166 quelli destinazione delle due istruzioni in pairing possono essere differenti. Facciamo ora un esempio con l’istruzione DP3: dp3 r0.rgb, t0, v0 + mul r0.a, r1, c2 Il prodotto scalare viene eseguito nella vector pipeline contemporaneamente, la scalar pipeline esegue una moltiplicazione. e, 5.12. Pixel Shader Assembler A questo punto della trattazione abbiamo una panoramica completa dello shader 1.4 e possiamo affermare di conoscere tutte le istruzioni e le operazioni di base che il Pixel Shader deve saper svolgere. Le specifiche sul funzionamento del nostro processore non sono tuttavia ancora esaurite: l’Instruction Pool non può infatti essere caricato direttamente con lo shader in formato testo, ma ovviamente richiede un flusso di dati in formato binario, ottenuti dalla compilazione del codice sorgente. Si osservi a tal proposito lo schema sottostante. shader (file .txt) PIXEL SHADER ASSEMBLER file assemblato (file .pso) INSTRUCTION POOL ... Lo shader in formato testo viene passato al Pixel Shader Assembler, un’utilità presente all’interno di Microsoft DirectX 8.1 SDK che assembla il sorgente producendo un file binario con estensione .pso. Tale file, visto come una successione di dati a 32 bit, viene passato in ingresso al Pixel Shader, che lo memorizza all’interno dell’Instruction Pool per poi successivamente eseguirlo. Ciò che vogliamo fare in questo paragrafo è spiegare la struttura del file .pso, ovvero capire come il Pixel Shader Assembler codifica la sequenza di istruzioni che compongono lo shader. 5.12.1 Struttura di un file .pso Il seguente comando MS-DOS psa -0 shader.txt permette di assemblare il file sorgente “shader.txt”, ottenendo come risultato il file denominato “shader.pso”. L’opzione -0 dell’assemblatore serve ad evitare che nel file generato vengano inserite informazioni di debug. Un file .pso è una sequenza di DWORD (gruppi di 4 byte, cioè 32 bit) organizzata come mostrato nello schema all’inizio di pagina seguente. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 167 Inizio shader: FF FF 01 04 10 dword con informazioni di servizio istruzioni DEF (6 dword ciascuna) sequenza istruzioni Fine shader: 00 00 FF FF La prima dword è la codifica dell’istruzione che specifica la versione dello shader: ps.1.4 Tale istruzione viene appunto tradotta nella sequenza FF FF 01 04. Osservazione - Per chiarezza, indicheremo sempre ciascuna dword come una sequenza di 4 byte, ordinati dal più significativo al meno significativo. A sua volta, ciascun byte viene rappresentato come una coppia di cifre esadecimali. Dopo la dword di inizio shader ci sono 10 dword che portano informazioni sulla versione dell’assemblatore e che devono essere separate dai dati destinati all’Instruction Pool. Seguono le istruzioni di definizione delle costanti, disposte in sequenza e senza separatori. Ciascuna istruzione DEF viene così codificata: 00 00 00 51 dest fval1 fval2 fval3 fval4 dove la prima dword rappresenta l’identificativo dell’istruzione stessa, la seconda dword codifica il registro di destinazione e fval1, fval2, fval3, fval4 sono le codifiche in formato floating-point delle costanti che compaiono nell’istruzione. Il blocco che nello schema è indicato come “sequenza istruzioni” rappresenta la codifica di tutte le istruzioni rimanenti che compaiono nello shader: queste sono le istruzioni di texture addressing, le istruzioni aritmetiche e il phase marker. Il modo in cui tali istruzioni sono codificate verrà spiegato tra breve. L’ultima dword è quella di fine shader: 00 00 FF FF. 5.12.2 Codifica delle istruzioni Ogni istruzione che compare nello shader (esclusa la DEF, che è stata già analizzata) viene codificata come una sequenza di dword così composta: § la prima dword è l’identificativo dell’istruzione § segue un insieme di dword, una per ogni registro destinazione o sorgente, disposte nello stesso ordine con cui compaiono nello shader. Ad esempio, l’istruzione add dest, src0, src1 viene tradotta in una sequenza di 4 dword che sono nell’ordine: il codice Progettazione in VHDL del Pixel Shader v 1.4 Pag. 168 operativo della ADD, la codifica del registro dest, la codifica del registro src0, la codifica del registro src1. 5.12.3 OpCode Vediamo in dettaglio la composizione di un codice operativo (OpCode) di un’istruzione. Esso è una sequenza di 32 bit organizzati come segue. 31 24 23 16 15 8 7 0 0 p 0 0 0 0 0 0 0 0 0 0 0 0 0 0 La parte significativa dell’OpCode dell’istruzione è racchiusa nei bit che vanno da 15 a 0. Forniamo qui di seguito l’elenco completo delle istruzioni dello shader con associati i 16 bit meno significativi dell’OpCode. Instr. OpCode Instr. OpCode add 00 02 mov 00 01 bem 00 59 mul 00 05 cmp 00 58 nop 00 00 cnd 00 50 phase ff fd def 00 51 sub 00 03 dp3 00 08 texcrd 00 40 dp4 00 09 texdepth 00 57 lrp 00 12 texkill 00 41 mad 00 04 texld 00 42 Il bit 30, identificato in figura dal simbolo p, è il bit di pairing. Il valore che tale bit assume nei diversi casi è ricavabile dalla seguente tabella. Tipo Istruzione Pairing Bit Istruzione Singola 0 Pairing Prima Istruzione 0 Seconda Istruzione 1 In sostanza il bit di pairing assume il valore ‘1’ solo nell’OpCode della seconda istruzione della coppia in pairing (cioè l’istruzione che nello shader è preceduta dal simbolo +). 5.12.4 Codifica dei registri Esistono due codifiche differenti per i registri: una applicata al registro che segue immediatamente l’OpCode dell’istruzione, l’altra per tutti gli eventuali registri presenti nell’istruzione. Analizziamo qui di seguito entrambe. La dword che segue l’OpCode dell’istruzione rappresenta, nella maggior parte dei casi, il registro destinazione e risulta così composta: Progettazione in VHDL del Pixel Shader v 1.4 31 28 27 Pag. 169 24 23 20 19 0 0 0 identif. registro 16 15 8 7 0 A B GR 0 0 0 0 0 0 0 0 modif. sat write mask indice registro Analizziamo i singoli gruppi di bit evidenziati nel disegno. I 4 bit più significativi, da 31 a 28, sono l’identificativo del registro. Si veda al proposito la seguente tabella. Identif. registro Codifica binaria Codifica esadecimale r 1000 8 v 1001 9 c 1010 A t 1011 B I bit dal 27 al 24 codificano l’instruction modifier secondo la tabella qui sotto riportata. Instr. modif. Codifica binaria Codifica esadecimale none 0000 0 _x2 0001 1 _x4 0010 2 _x8 0011 3 _d8 1101 D _d4 1110 E _d2 1111 F Il bit 20 è il bit di saturazione, ovvero se pari ad ‘1’ indica la presenza del modificatore _sat. I bit dal 19 al 16 rappresentano la write mask per i canali A, B, G, R. Il valore ‘1’ associato ad un certo canale indica che quella componente del registro destinazione viene scritta dall’istruzione. I bit da 7 a 0 sono la codifica binaria dell’indice del registro. Per gli ulteriori registri che compaiono all’interno dell’istruzione viene adottata una codifica diversa, illustrata nel disegno sottostante. 31 28 27 24 23 20 19 16 15 8 7 0 0 0 0 0 0 0 0 0 identif. registro modif. A B G R indice registro Per i bit da 31 a 28 valgono le stesse considerazioni fatte sopra. I bit da 27 a 24 codificano il source modifier secondo la tabella mostrata a pagina seguente. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 170 Source modif. Codifica binaria Codifica esadecimale none 0000 0 negate 0001 1 bias bias, negate 0010 2 0011 3 bx2 bx2, negate 0100 4 0101 5 invert 0110 6 x2 x2, negate 0111 7 1000 8 db 1001 A da 1010 B I bit dal 23 al 16 sono relativi al source register selector: per ciascuno dei quattro canali A, B, G, R, una coppia di bit indica da quale canale originario viene prelevato il dato. In merito esiste la convenzione mostrata nella tabella sottostante. Canale sorgente codifica R 00 G 01 B 10 A 11 Infine i bit da 7 a 0 sono la codifica binaria dell’indice del registro. 5.13. Strumenti software utilizzati nel progetto Riportiamo nella tabella che segue l’elenco dei tool utilizzati per la realizzazione del Pixel Shader, unitamente ai siti da cui essi sono prelevabili. Software (casa produttrice) Utilizzo Sito web HDL designer pro (Mentor Graphics) Progettazione VHDL www.mentor.com Modelsim (Mentor Graphics) Compilatore e simulatore VHDL www.mentor.com VHDL Simili (Symphony EDA) Compilatore e simulatore VHDL www.symphonyeda.com WaveViewer (SynaptiCAD) Visualizzatore di forme d’onda www.synapticad.com Microsoft DirectX 8.1 SDK Pixel Shader Assemblatore Assembler www.microsoft.com www.msdn.microsoft.com Progettazione in VHDL del Pixel Shader v 1.4 Pag. 171 5.14. Organizzazione del progetto Prima di procedere all’analisi dell’implementazione del Pixel Shader è opportuno presentare l’organizzazione logica che si è deciso di dare al progetto. In fase di implementazione si è deciso di separare il progetto in tre librerie distinte a seconda delle funzionalità dei singoli blocchi componenti. • Utilities: contiene al suo interno i packages relativi alla definizione dei tipi di dati utilizzati nel progetto, delle funzioni di supporto per la conversione da un tipo di dato all’altro e per l’accesso ai file. Contiene inoltre opportuni packages che si occupano di definire le costanti utilizzate per indicare i codici operativi del Pixel Shader e delle ALU. Di questa libreria fanno inoltre parte le versioni parametriche dei componenti elementari necessari allo sviluppo (quali ad esempio i multiplexer ed i demultiplexer). • PixelShader: in questa libreria sono contenute tutte le entità strettamente pertinenti al Pixel Shader, nonché la struttura complessiva del processore realizzata mediante wiring dei singoli blocchi componenti. • PixelShader_TestBench: come si evince dal nome, in questa libreria vengono raggruppati tutti gli elementi che fanno parte del banco di prova: Test Unit, Texture Sampler, BEMModule e la struttura che li collega al Pixel Shader. • Stddevelopers_kit: di questa libreria fornita con il ModelSim è necessario ricompilare, per il nuovo simulatore, i sorgenti relativi alle funzioni di accesso ai file; i sorgenti completi per questa ed altre librerie standard ieee sono fortunatamente disponibili nella directory “/vhdl_src/” contenuta nella directory di installazione del ModelSim. I sorgenti acclusi al presente volume sono quindi organizzati secondo le categorie appena indicate; la stessa suddivisione viene mantenuta anche nell’appendice in cui vengono riportati i sorgenti completi realizzati per la sintesi del Pixel Shader. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 172 Capitolo 6. Test bench per la simulazione in VHDL del processore 6.1. Introduzione Prima di procedere all’analisi del Pixel Shader vero e proprio, descrivendone i passi fondamentali per lo sviluppo e la realizzazione, è opportuno presentare il test bench (o banco di prova) nella sua totalità. In questo modo si potrà meglio comprendere la logica di funzionamento del processore oggetto della presente tesina. Come ulteriore premessa si fa notare che eventuali tipi di dato che non appartengono allo standard del VHDL vengono definiti nella libreria Utilities.PS_Types allegata al progetto; i file sorgenti di questa libreria (PS_Types_pkg.vhd e PS_Types_pkg_body.vhd) possono essere consultati in appendice e nel progetto accluso. Il test bench si compone di cinque blocchi fondamentali: un generatore di clock (Clock Generator), una unità che si occupa di reperire un’opportuna matrice di dati dalla memoria di sistema (il BEM Module), una unità che esegue il Texture Sampling (Texture Sampler), il Pixel Shader vero e proprio (il processore oggetto della prova) ed infine la Test Unit (un modulo che si occupa di pilotare adeguatamente il Pixel Shader). Nello schema a pagina seguente sono visibili le unità funzionali sopra elencate, le rispettive linee di collegamento e scambio dati ed i segnali per l’handshaking. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 173 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 174 6.2. Clock Generator Per facilitare una eventuale estensione del progetto è stata prevista la possibilità di utilizzare un clock bifase, tuttavia, per questioni di comodità, allo stato attuale è stata utilizzata una sola fase. La schematizzazione mediante VHDL è banale: si è fatto ricorso ad un processo che generi il segnale di clock per entrambe le fasi e a due assegnamenti concorrenti al processo stesso per riportare il segnale generato alle due uscite del blocco. Occorre prestare attenzione al fatto che il clock viene descritto in termini di ampiezza dell’impulso per la fase 1 e di separazione tra quest’ultimo e l’inizio del fronte di salita per la fase 2. Di seguito viene presentato il codice utilizzato ENTITY ClkGen IS GENERIC( PulseWidth : TIME := 10 NS; PulseSeparation : TIME := 2 NS ); PORT( ClkGen_clk1 : OUT bit; -- clock fase 1 ClkGen_clk2 : OUT bit -- clock fase 1 ); --due segnali di supporto SIGNAL clk1 : BIT := '0'; SIGNAL clk2 : BIT := '1'; END ClkGen ; ARCHITECTURE ClkGen2 OF ClkGen IS CONSTANT clock_period : TIME := 2*(PulseWidth+PulseSeparation); BEGIN clock_driver: PROCESS BEGIN clk1 <= '0', '1' AFTER PulseWidth+PulseSeparation; clk2 <= '1', '0' AFTER PulseWidth, '1' AFTER 2*PulseWidth+PulseSeparation; WAIT FOR clock_period; END PROCESS clock_driver; ClkGen_clk1 <= clk1; ClkGen_clk2 <= clk2; END ClkGen2; Data la semplicità dell’entità in esame se ne è riportato integralmente il codice realizzativo; per entità più complesse verranno riportati solo alcune parti essenziali del relativo sorgente, tenendo presente che le versioni complete dei listati di implementazione possono essere consultate nella relativa appendice e nei sorgenti acclusi. 6.3. Texture Sampler Sampler_En Sampler_TextureStageNumber : (2:0) Sampler_Data : (127:0) (OTHERS => '0') PixelShader_TestBench Sampler Sampler_Done TEXSAMPLER Il Texture Sampler è il blocco che dovrebbe simulare le operazioni di Texture Sampling tipiche delle moderne schede grafiche (nella presente tesina lo scopo del progetto è la realizzazione del Pixel Shader e non delle unità di sampling, per cui questa entità restituisce valori fittizi). Il funzionamento del Texture Sampler può essere schematizzato come segue. Il Sampler preleva da un apposito bus i dati relativi alla coordinata necessaria per il Progettazione in VHDL del Pixel Shader v 1.4 Pag. 175 campionamento della Texture; sulla base delle coordinate ottenute preleva i dati della texture corrispondente dalla memoria e li sottopone ad eventuali operazioni di filtraggio (es. bilinear o trilinear filtering); al termine restituisce sullo stesso bus i valori R, G, B, A calcolati. Il Texture Sampler è quindi caratterizzato dai seguenti segnali di controllo: • un segnale di abilitazione (Sampler_En) che indica all’unità il fatto che sul bus in ingresso sono presenti dati validi per le coordinate e che l’elaborazione può iniziare • un segnale di selezione del Texture Stage52 • una porta di ingresso/uscita direttamente connessa al bus dati (Sampler_Data) per lo scambio di informazioni • un segnale che indichi quando l’elaborazione è terminata (Sampler_Done). La logica di funzionamento è molto semplice e si basa su un classico meccanismo di handshaking: il Pixel Shader si occupa di fornire i dati corretti sul data bus e di specificare il Texture Stage Number per indicare quale texture andare a campionare; quando questi dati risultano corretti il Pixel Shader invia il segnale di abilitazione al Sampler. Il Sampler è ovviamente dotato al suo interno di una serie di latch per poter mantenere stabili i dati in ingresso, dato che il Pixel Shader, dopo un tempo predeterminato, libera il bus in attesa del risultato dell’elaborazione. Il segnale di abilitazione viene mantenuto alto fino a che il Sampler non risponde mandando a sua volta alto il proprio segnale di “done”. A questo punto il Pixel Shader memorizza internamente le quattro dword del risultato e di seguito spegne il Sampler abbassando il segnale di enable; in questo modo il Sampler viene informato del fatto che il risultato è stato correttamente memorizzato e libera a sua volta il bus dati. Illustriamo quanto ora esposto facendo ricorso alle forme d’onda risultato della semplice simulazione dello shader: ps.1.4 texld r0, t0 avente come ingresso per il file delle texture i seguenti valori: 0 0.1 0.2 0.3 0.0 1 0.0 0.0 0.3 0.0 3 1.0 0.0 0.2 0.0 9 0.0 0.0 0.0 0.0 In sostanza vengono passati al Sampler, come coordinate su cui campionare la texture, i valori (0.1; 0.2; 0.3). Analizzando i segnali di interesse abbiamo: 52 Possono infatti venire caricate nella memoria video o di sistema fino ad un massimo di 6 texture distinte gestibili dall’hardware per la geometria corrente; ogni texture appartiene ad uno stage differente, identificato da un indice progressivo. Progettazione in VHDL del Pixel Shader v 1.4 Coordinate a cui campionare la texture Pag. 176 Risultato delle operazioni svolte dal Texture Sampler In questa sezione vediamo rappresentato lo stato di esecuzione delle istruzioni del Pixel Shader (ps_mode = 3, “11” in codifica binaria). All’inizio dell’esecuzione il bus è libero (ovvero è caratterizzato da una sequenza di ‘0’); a 336 ns il Pixel Shader immette sul DataBus la codifica in formato floating-point corrispondente alla quaterna di dword presenti nel registro t0; il TextureStageNumber non subisce variazioni (in quanto l’indice relativo è sempre 0). In seguito (345 ns) viene attivato il Sampler e all’istante corrispondente ai 355 ns viene liberato il bus dati; si passa quindi all’attesa del termine di elaborazione che avviene quando il Sampler manda ad ‘1’ il segnale Sampler_done (445 ns) ed immette nel DataBus i valori risultato dell’elaborazione. Il Pixel Shader scrive questi valori sul registro indicato e di seguito spegne il Texture Sampler (455 ns), di conseguenza anche il segnale di done del Sampler si abbassa. Per brevità di trattazione non viene riportato il codice VHDL che può comunque essere consultato in appendice. 6.4. BEM Module Il BEM Module si occupa di reperire dalla memoria di sistema una matrice di valori, precedentemente memorizzata BEMModule_TextureStageNumber : (2:0) dal programmatore mediante opportune istruzioni esterne al PixelShader_TestBench Pixel Shader. La matrice è costituita da quattro dword53 e BEMModule viene utilizzata in una particolare istruzione dello shader BEM BEMModule_Done (l’istruzione BEM, da qui il nome del modulo) per simulare BEMModule_Data : (127:0) effetti di Environmental Mapping. Questo modulo è puramente fittizio ed è stato realizzato allo scopo di fornire dei valori di prova al Pixel Shader quando questi vengono richiesti dall’istruzione BEM. Il meccanismo di comunicazione tra Pixel Shader e BEM Module è analogo a quello indicato per il Texture Sampler. Anche per il BEM Module non viene riportata la realizzazione VHDL comunque consultabile in appendice. BEMModule_En 53 E’ possibile memorizzare una matrice per ogni stage di texture; il TextureStateNumber serve ad indicare quale quaterna di valori si vuole reperire. Pag. 177 _ T Progettazione in VHDL del Pixel Shader v 1.4 6.5. Pixel Shader S O Per il momento possiamo considerare il Pixel Shader come una unità funzionale descritta a livello comportamentale, rimandando al capitolo seguente la illustrazione della sua architettura interna. In questa sezione verrà fornita una spiegazione completa del funzionamento del complesso Pixel Shader – Test Unit e della modalità di interazione tra il processore e il resto dell’hardware della scheda grafica. Verranno inoltre illustrati i segnali di ingresso ed uscita e fornite le relative tabelle necessarie ad interpretarne il significato. PS_Mode : (1:0) PS_Done PS_DestAddr : (6:0) PS_SamplerEn PS_DestReg PS_BEMEn P ( PS_WriteEn PS_SamplerDone PS_TextureStageNumber : (2:0) PS_BEMDone PS_Clk1 PS_clk2 PS_R0 : (127:0) PixelShader PS PSHADER PS_R5R : (31:0) PS_DepthEn PS_Dump PS_PixelKill Poiché stiamo parlando di un processore completo che deve gestire fasi di caricamento delle istruzioni, di caricamento dei dati relativi ai pixel su cui operare e di esecuzione del microcodice, si è pensato di organizzarne il funzionamento in quatto fasi (o modi di funzionamento) distinte54. Nella tabella seguente viene presentata la codifica ed il significato di ogni fase. Codifica 00 01 10 11 Denominazione WaitingCommand LoadShader LoadPixel Execute Descrizione Stato di attesa / Stato iniziale dello Shader Caricamento delle istruzioni e delle costanti Caricamento dei dati relativi al pixel da elaborare Esecuzione delle istruzioni dello shader per il pixel corrente Nello schema successivo vengono indicate le condizioni sotto cui è consentito il passaggio da un modo di funzionamento all’altro del Pixel Shader. PSMode=10 WAITING WAITING COMMAND COMMAND 00 00 PSMode=01 PSMode=10 LOAD LOAD SHADER SHADER 01 01 PSMode=11 LOAD LOAD PIXEL PIXEL EXECUTE EXECUTE 10 10 more pixels no more pixels 54 Si rende quindi necessaria la presenza di appositi segnali che consentano al resto della scheda di impostare il corretto modo di funzionamento in base alla situazione (segnali PS_Mode). 11 11 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 178 Di seguito indichiamo tutti i segnali del Pixel Shader, distinguendo tra segnali di ingresso, ingresso/uscita, uscita. I segnali di ingresso sono i seguenti: • PS_Mode: setta il modo corrente di funzionamento del processore (in riferimento a quanto detto sopra). • PS_DestAddr: indirizzo in cui scrivere il dato presente sul DataBus; occorre prestare attenzione al fatto che i vari registri presenti all’interno del Pixel Shader hanno range differenti tra loro. Si è adottata una normale codifica binaria senza segno. • PS_DestReg: la presenza di questo segnale è necessaria per discriminare in quale dei registri di destinazione si intende scrivere il dato in ingresso. Questo bit assume differenti significati a seconda dello stato di funzionamento dello Shader (fare riferimento alla tabella). Codifica 0 1 PS_Mode LoadShader LoadPixel LoadShader LoadPixel Destinazione registri C registri V Instruction Pool registri T • PS_WriteEn: segnale di abilitazione per la scrittura sui registri dei dati presenti sul DataBus; deve essere inviato al Pixel Shader una volta che si è certi della validità dei dati, dell’indirizzo e del valore di PS_DestReg. • PS_SamplerDone: indica il termine dell’elaborazione da parte del Texture Sampler. • PS_BEMDone: indica il termine dell’elaborazione da parte del BEM Module. • PS_Clk1: Clock, fase 1. • PS_Clk2: Clock, fase 2 (non utilizzato nell’implementazione corrente). • PS_Dump: segnale di debug. Indica di effettuare il dump su file del contenuto dei registri. Valido solo a livello di progettazione e da escludere in fase realizzativa. Esiste un solo segnale di ingresso/uscita: • PS_Data: la linea di connessione con il bus dati esterno. I segnali di uscita sono i seguenti: • PS_Done: indica il termine dell’elaborazione del pixel corrente o l’avvenuta scrittura del dato in ingresso secondo la seguente tabella. Evento PS_Mode Descrizione LoadShader/LoadPixel Termine del caricamento dei dati presenti sul bus Execute Termine dell’elaborazione del pixel corrente • PS_SamplerEn: segnale di abilitazione per il Texture Sampler. Anche in questo caso verificare che i dati da inviare al Sampler siano validi prima di attivare l’unità. • PS_BEMEn: segnale di abilitazione per il BEM Module, valgono le precauzioni indicate per il Sampler. • PS_TextureStageNumber: fare riferimento a quanto detto a proposito di Pag. 179 t t Progettazione in VHDL del Pixel Shader v 1.4 Texture Sampler e BEM Module. PS_R0: codifica RGBA del colore in uscita per il pixel corrente. È il risultato vero e proprio dell’elaborazione delle istruzioni dello shader. • PS_R5R: valore da sostituire nel depth-buffer ottenuto in seguito ad una chiamata all’istruzione TEXDEPTH. • PS_DepthEn: indica se utilizzare o meno il valore in uscita da PS_R5R per effettuare il depth test situato più avanti nella pipeline grafica. Se il valore è ‘1’ si deve utilizzare il risultato fornito dallo shader. • PS_PixelKill: indica la necessità di annullare il rendering del pixel corrente in seguito ad una istruzione TEXKILL. Il Pixel Shader informa di questa necessità il resto dell’hardware, che deve quindi provvedere ad iniziare immediatamente l’elaborazione del pixel successivo senza inviare i dati elaborati per il pixel eliminato al resto della pipeline. e e s s • 6.6. Test Unit E’ il cuore vero e proprio del test bench: suo è il compito di gestire il Pixel Shader, fornendogli correttamente i dati, e di interpretarne TestUnit_DestReg TestUnit_WriteEn adeguatamente le risposte, evidenziando eventuali problematiche di progettazione. PixelShader_TestBench TestUnit Non è necessario soffermarsi su una descrizione degli ingressi e TU TestUnit_Dump delle uscite perché sono essenzialmente gli stessi del Pixel Shader; ad TestUnit_Clk1 eccezione del clock, un ingresso per lo shader corrisponde ad una uscita della Test Unit e vice versa. Occorre tuttavia puntualizzare che la Test Unit preleva i dati da fornire al Pixel Shader da una serie di file. Per un corretto funzionamento si consiglia di creare la directory “C:\PSDATA” in cui andranno inseriti i file di input ed in cui saranno generati i file di output e di report. I file necessari ad una corretta simulazione sono i seguenti: TestUnit_PSMode : (1:0) T T TestUnit_DestAddr : (6:0) PS_DepthEn PS_PixelKill PS_R0 : (127:0) PS_R5R : (31:0) • PS.PSO – file binario, risultato della compilazione delle istruzioni dello shader effettuata mediante l’assemblatore standard (psa.exe) fornito con il Microsoft DirectX 8.1 SDK. Questo file andrà generato con il parametro “-0” in modo da non includervi informazioni di debug (che avrebbero complicato in modo inutile la routine di caricamento dei dati nell’Instruction Pool del Pixel Shader). • C.TXT – file di testo che indica quali dati caricare nel banco costanti e in quale indirizzo. Il formato di questo file è il seguente: N F.F F.F F.F F.F • • N: numero intero compreso tra 0 e 7, indica il rispettivo banco costanti da caricare. F.F: un valore floating-point (attenzione : il VHDL non è così flessibile come altri linguaggi di programmazione per cui se le routine di input si aspettano un numero in formato floating-point non possiamo specificare ad esempio 1, ma dobbiamo fornirlo con la notazione 1.0) Esempio (questo file carica i banchi costanti 0 e 2, lasciando non inizializzati gli altri): Progettazione in VHDL del Pixel Shader v 1.4 Pag. 180 0 1.0 1.1 1.2 1.3 2 0.1 0.2 0.3 0.4 • V.TXT – file di testo che indica quali dati caricare nei registri colore e in quale indirizzo. • T.TXT – file di testo che indica quali dati caricare nei registri relativi alle coordinate delle texture e in quale indirizzo. Il formato di questi due file è il seguente: N F.F F.F F.F F.F • • • • N: numero intero compreso tra 0 e 1 per i registri V, tra 0 e 5 per i registri T; il termine dei dati relativi ad un pixel si indica inserendo come indice dei dati successivi il numero 9. In questo modo è possibile fornire dati relativi a più pixel in sequenza. F.F: un valore floating-point (attenzione : il VHDL non è così flessibile come altri linguaggi di programmazione per cui se le routine di input si aspettano un numero in formato floating-point non possiamo specificare ad esempio 1, ma dobbiamo fornirlo con la notazione 1.0). In corrispondenza dell’indice di fine pixel introdurremo sempre la quaterna di dati “9.9 9.9 9.9 9.9”. Per quanto riguarda i dati sulle texture, sebbene al massimo sia significativa una terna di valori (coordinate di texture cubica), occorre comunque fornirne quattro (per via delle funzioni di lettura dati); il quarto valore non verrà comunque considerato. Per ogni pixel che si intende far elaborare è necessario fornire le informazioni sul colore, seguite dall’indice di fine pixel; a queste devono corrispondere necessariamente delle informazioni sulle texture. Qualora queste ultime non siano necessarie si deve comunque riportare l’indice di fine dati anche per le texture. Esempio (al primo pixel sono associati dati colore e texture, al secondo solo dati colore): V.TXT 0 0.1 0.2 0.3 0.4 1 1.0 1.0 1.0 1.0 9 9.9 9.9 9.9 9.9 0 0.3 0.2 0.1 0.4 9 9.9 9.9 9.9 9.9 Primo pixel con associate coordinate di texture Secondo pixel senza T.TXT 0 0.1 0.2 0.0 0.0 coordinate di texture 9 9.9 9.9 9.9 9.9 associate 9 9.9 9.9 9.9 9.9 Tutte le funzioni necessarie alla lettura ed alla scrittura di file di testo e binari, nel formato qui presentato, sono le seguenti: • ReadDword, Read4Dword: per la lettura di file binari • ReadData: per la lettura dei file di testo • WriteString: per la scrittura di una stringa su file di testo • WriteBit: per la scrittura di un singolo bit su file di testo • WriteDword, Write4DwordCol, Write4DwordRow: per la scrittura di dati in formato floating-point su file di testo Progettazione in VHDL del Pixel Shader v 1.4 Pag. 181 • Write4DwordHex: per la scrittura di 4 dword in formato esadecimale su file di testo. Per analizzarne i sorgenti fare riferimento alla libreria Utilities.File_IO ed ai file di implementazione (file_io_pkg.vhd e file_io_pkg_body.vhd) riportati in appendice. Torniamo all’implementazione della Test Unit. Trattandosi essenzialmente di una unità di controllo, si è scelto di realizzarla mediante una macchina a stati finiti, sincrona con il clock fase 1. A differenza dell’unità di controllo del Pixel Shader, sensibile ai fronti di salita del clock, la Test Unit effettua le transizioni di stato in corrispondenza dei fronti di discesa. Prima di entrare nel dettaglio della realizzazione è necessario fornire una breve introduzione ai tool utilizzati. 6.6.1 HDL Designer e generazione di una macchina a stati finiti Dato che ci si avvale dei tool HDL Designer Series, vediamo come impostare correttamente le opzioni per la generazione automatica del codice sulla base del diagramma a stati che andremo a realizzare. Nella figura seguente riportiamo la schermata delle opzioni (raggiungibile dal menu Diagram -> State Machine Properties nella finestra di generazione della macchina a stati finiti) con i settaggi utilizzati55. Analizziamo i vari frame di opzioni: 55 • Machine: specifica se vogliamo realizzare una macchina sincrona o asincrona; in quest’ultimo caso si indica l’intervallo necessario ad effettuare gli eventuali cambiamenti di stato. • Clock: nel caso di macchina sincrona si specifica il segnale di clock e l’eventuale sensibilità ai fronti di salita o discesa. • State Variable: indica se si deve utilizzare un tipo di dato definito Gli stessi settaggi saranno poi impiegati anche nella realizzazione delle altre macchine a stati finiti, per l’implementazione delle unità di controllo vere e proprie del processore. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 182 dall’utente per la rappresentazione degli stati o se è necessario generarne uno automaticamente sulla base degli stati che vengono di volta in volta “disegnati” dall’utente. L’utilizzo di un tipo di dato definito dall’utente permette la realizzazione di codice leggermente più elegante, ma anche più complesso. • Reset: permette di indicare un segnale che consenta di riportare l’esecuzione nello stato iniziale. • Enable: specifica un segnale che consenta di abilitare o meno l’esecuzione; permette inoltre di portare temporaneamente in stallo la macchina a stati finiti. • Animation: utilizzando HDL Designer Series assieme a Modelsim, specificando questa opzione è possibile tracciare il flusso dell’esecuzione durante la simulazione. • State Signal Names: permette di specificare i nomi dei segnali che manterranno lo stato presente e futuro, in modo da poterli utilizzare all’interno del codice di ogni stato (quando necessario). • HDL Styles: è forse l’opzione maggiormente rilevante ai fini della comprensione del codice generato. Per fornirne una spiegazione chiara utilizziamo una semplice macchina con 2 soli stati: State1 (start state) e State2, ed una sola transizione dal primo al secondo. L’entità di prova che si realizza comprende il solo segnale di clock in ingresso, ed è definita come segue: ENTITY prova IS PORT( Clock : IN bit ); END prova ; Confrontiamo quindi il codice generato dalle quattro possibili combinazioni di opzioni. Si prende in esame il solo caso di macchina a stati sincrona; per quelle asincrone le differenze sono minime ed in ogni caso riguardano la logica che si occupa del solo assegnamento del valore dello stato futuro56. L’opzione IF/CASE gestisce il flusso di esecuzione delle istruzioni appartenenti ai singoli stati e/o transizioni; il numero di processi concorrenti generati indica come vengono organizzate queste istruzioni all’interno del codice. 56 È possibile identificare le zone in cui viene inserito il codice associato ad ogni stato e ad ogni transizione semplicemente controllando la posizione nei sorgenti dei commenti inseriti nel diagramma. Progettazione in VHDL del Pixel Shader v 1.4 6.6.2 IF + 3 PROCESSES Auto Generated Type State Signal Names Questo processo si occupa esclusivamente di eseguire l’assegnamento per effettuare la transizione tra gli stati. Ad ogni colpo di clock, sul fronte di salita, lo stato futuro diviene quello corrente. Questo processo si occupa esclusivamente di eseguire l’assegnamento per modificare lo stato futuro sulla base di quello corrente e delle relazioni definite in fase di progettazione. Il processo è sensibile alle variazioni dello stato corrente e si attiva solo in corrispondenza di cambiamenti di stato di quest’ultimo. È realizzato mediante IF per via delle opzioni settate precedentemente. ARCHITECTURE fsm OF prova IS -- Architecture Declarations TYPE STATE_TYPE IS ( State1, State2 ); -- Declare current and next state signals SIGNAL CurrentState : STATE_TYPE ; SIGNAL NextState : STATE_TYPE ; BEGIN ----------------------------------------------------clocked : PROCESS(Clock) ----------------------------------------------------BEGIN IF (Clock'EVENT AND Clock = '1') THEN CurrentState <= NextState; -- Default Assignment To Internals END IF; END PROCESS clocked; ----------------------------------------------------machine0_nextstate : PROCESS (CurrentState) ----------------------------------------------------BEGIN IF CurrentState = State1 THEN NextState <= State2; ELSIF CurrentState = State2 THEN NextState <= State2; END IF; END PROCESS machine0_nextstate; ----------------------------------------------------- In questo processo vengono output : PROCESS (CurrentState) eseguite le operazioni che l’utente ----------------------------------------------------ha definito in fase di progettazione BEGIN -- State Actions per ogni stato. Questa sezione si occupa di eseguire le operazioni collegate con lo stato attuale CASE CurrentState IS WHEN State1 => --codice state1 WHEN State2 => --codice state1 WHEN OTHERS => NULL; END CASE; -- State Actions Clocked On Next State In questa sezione vengono eseguite -- Transition Actions le operazioni definite sulle CASE CurrentState IS transizioni (se risulta vera una WHEN State1 => eventuale condizione imposta). --codice transition Occorre porre particolare WHEN OTHERS => attenzione al fatto che le istruzioni NULL; END CASE; connesse alla transizione vengono END PROCESS output; eseguite IMMEDIATAMENTE END fsm; dopo quelle connesse con lo stato e NON in corrispondenza del colpo di clock, come si potrebbe ipotizzare. Pag. 183 Progettazione in VHDL del Pixel Shader v 1.4 6.6.3 IF + 2 PROCESSES Auto Generated Type State Signal Names Questo processo si occupa esclusivamente di eseguire l’assegnamento per effettuare la transizione tra gli stati. Ad ogni colpo di clock, sul fronte di salita, lo stato futuro diviene quello corrente. È un processo standard ed è comune a tutte le versioni di codice generate. ARCHITECTURE fsm OF prova IS -- Architecture Declarations TYPE STATE_TYPE IS ( State1, State2 ); -- Declare current and next state signals SIGNAL CurrentState : STATE_TYPE ; SIGNAL NextState : STATE_TYPE ; BEGIN ----------------------------------------------------clocked : PROCESS(Clock) ----------------------------------------------------BEGIN IF (Clock'EVENT AND Clock = '1') THEN CurrentState <= NextState; END IF; END PROCESS clocked; ----------------------------------------------------- Poichè abbiamo scelto di realizzare machine0_nextstate : PROCESS (CurrentState) due soli processi concorrenti, in ----------------------------------------------------BEGIN questo vengono accorpati il codice -- State Actions per effettuare il cambiamento di CASE CurrentState IS stato e quello per l’esecuzione delle WHEN State1 => istruzioni. --codice state1 In questa sezione vengono eseguite WHEN State2 => le operazioni che l’utente ha --codice state1 definito in fase di progettazione per WHEN OTHERS => ogni stato. NULL; END CASE; In questa sezione vengono eseguite -- State Actions Clocked On Next State le operazioni definite sulle IF CurrentState = State1 THEN transizioni (se risulta vera una --codice transition eventuale condizione imposta). Le NextState <= State2; istruzioni connesse alla transizione ELSIF CurrentState = State2 THEN NextState <= State2; vengono eseguite END IF; IMMEDIATAMENTE dopo quelle END PROCESS machine0_nextstate; connesse con lo stato. La sequenza END fsm; viene implementata come una serie di IF, come da richiesta. Al termine delle istruzioni viene impostato il nuovo stato futuro. Pag. 184 Progettazione in VHDL del Pixel Shader v 1.4 6.6.4 CASE + 3 PROCESSES Auto Generated Type State Signal Names Questo processo si occupa esclusivamente di eseguire l’assegnamento per effettuare la transizione tra gli stati. Ad ogni colpo di clock, sul fronte di salita, lo stato futuro diviene quello corrente. È un processo standard ed è comune a tutte le versioni di codice generate. Questo processo si occupa esclusivamente di eseguire l’assegnamento per modificare lo stato futuro sulla base di quello corrente e delle relazioni definite in fase di progettazione. Il processo è sensibile alle variazioni dello stato corrente e si attiva solo in corrispondenza di cambiamenti di stato di quest’ultimo. È realizzato mediante CASE per via delle opzioni settate precedentemente. In questo processo vengono eseguite le operazioni che l’utente ha definito in fase di progettazione per ogni stato. Questa sezione si occupa di eseguire le operazioni collegate con lo stato attuale e quelle definite sulle transizioni (se risulta vera una eventuale condizione imposta). A differenza del codice presentato precedentemente, appare qui evidente che le istruzioni connesse alla transizione vengono eseguite immediatamente dopo quelle connesse con lo stato. Se avessimo inserito una condizione, la transizione sarebbe stata racchiusa da un blocco IF … END IF. ARCHITECTURE fsm OF prova IS -- Architecture Declarations TYPE STATE_TYPE IS ( State1, State2 ); -- Declare current and next state signals SIGNAL CurrentState : STATE_TYPE ; SIGNAL NextState : STATE_TYPE ; BEGIN ----------------------------------------------------clocked : PROCESS(Clock) ----------------------------------------------------BEGIN IF (Clock'EVENT AND Clock = '1') THEN CurrentState <= NextState; END IF; END PROCESS clocked; ----------------------------------------------------machine0_nextstate : PROCESS (CurrentState) ----------------------------------------------------BEGIN CASE CurrentState IS WHEN State1 => NextState <= State2; WHEN State2 => NextState <= State2; END CASE; END PROCESS machine0_nextstate; ----------------------------------------------------output : PROCESS (CurrentState) ----------------------------------------------------BEGIN CASE CurrentState IS WHEN State1 => --codice state1 --codice transition WHEN State2 => --codice state1 WHEN OTHERS => NULL; END CASE; END PROCESS output; END fsm; Pag. 185 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 186 6.6.5 CASE + 2 PROCESSES Auto Generated Type State Signal Names Questo processo si occupa esclusivamente di eseguire l’assegnamento per effettuare la transizione tra gli stati. Ad ogni colpo di clock, sul fronte di salita, lo stato futuro diviene quello corrente. È un processo standard ed è comune a tutte le versioni di codice generate. ARCHITECTURE fsm OF prova IS -- Architecture Declarations TYPE STATE_TYPE IS ( State1, State2 ); -- Declare current and next state signals SIGNAL CurrentState : STATE_TYPE ; SIGNAL NextState : STATE_TYPE ; BEGIN ----------------------------------------------------clocked : PROCESS(Clock) ----------------------------------------------------BEGIN IF (Clock'EVENT AND Clock = '1') THEN CurrentState <= NextState; END IF; END PROCESS clocked; ----------------------------------------------------- Poichè abbiamo scelto di realizzare machine0_nextstate : PROCESS (CurrentState) due soli processi concorrenti, in ----------------------------------------------------questo vengono accorpati il codice BEGIN -- Combined Actions per effettuare il cambiamento di CASE CurrentState IS stato e quello per l’esecuzione delle WHEN State1 => istruzioni. --codice state1 --codice transition In questa sezione vengono eseguite NextState <= State2; le operazioni che l’utente ha WHEN State2 => --codice state1 definito in fase di progettazione per NextState <= State2; ogni stato e, di seguito, quelle END CASE; definite sulle transizioni (se risulta END PROCESS machine0_nextstate; vera una eventuale condizione END fsm; imposta). La sequenza viene implementata mediante CASE, come da richiesta. Al termine delle istruzioni viene impostato il nuovo stato futuro. Valutando attentamente il codice generato per i quattro esempi sopra proposti, si è deciso di utilizzare l’ultima formulazione in quanto produce codice più conciso e maggiormente efficiente dal punto di vista del tempo necessario ad eseguire una lunga simulazione. 6.6.6 Test Unit – Finite State Machine (FSM) Riportiamo a pagina seguente lo schema completo della FSM. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 187 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 188 Prima di analizzare i singoli stati, è opportuno presentare la sezione dichiarativa dell’architettura, in modo da evidenziare le variabili interne definite ed esplicitarne il significato. Architecture Declarations: --Dichiaro ed apro i file per l'immissione dei dati --modificare il percorso all'occorrenza FILE PSOFile : BinFile OPEN READ_MODE IS PATH57 & "PS.PSO"; FILE CFile : ASCII_TEXT OPEN READ_MODE IS PATH & "C.TXT"; FILE VFile : ASCII_TEXT OPEN READ_MODE IS PATH & "V.TXT"; FILE TFile : ASCII_TEXT OPEN READ_MODE IS PATH & "T.TXT"; --un file per l'output FILE OutPutFile : ASCII_TEXT OPEN WRITE_MODE IS PATH & "OutPut.TXT"; --un file per tenere traccia della simulazione FILE TUFile : ASCII_TEXT OPEN WRITE_MODE IS PATH & "TestUnitTrace.TXT"; --una variabile interna per tenere traccia dell'esistenza di --dati da caricare nell'instr pool, se è 0 non ci sono dai da --scrivere SHARED VARIABLE MoreData : BIT; --se è 0 non ci sono altri pixel da elaborare SHARED VARIABLE MorePixels : BIT; --una variabile per indicare dopo quanto tempo inviare il --segnale di enable --va inviato una volta che i dati sul databus ed address bus --sono validi CONSTANT WritingDly : TIME := 0 ns; --variabili per mantenere i dati letti SHARED VARIABLE tmpPSO : BIT_VECTOR(127 DOWNTO 0); SHARED VARIABLE tmpIndex : INTEGER; SHARED VARIABLE tmpData : BIT_VECTOR(127 DOWNTO 0); SHARED VARIABLE tmpAddr : INTEGER; SHARED VARIABLE tmpDword : DWORD; --variabile per decidere se incrementare l'address per --l'instrpool o meno in base al tipo di istruzione SHARED VARIABLE isDef : BIT := '0'; SetupTestUnit L’esecuzione inizia con lo stato SetupTestUnit (marcato come StartUpState, evidenziato dalla colorazione in verde), in cui si eseguono le operazioni preliminari di preparazione alla simulazione. Di seguito viene riportato il codice VHDL eseguito nello stato: SetupTestUnit: --inizializzo alcuni dati se necessario REPORT "TESTUNIT - SetupTestUnit" SEVERITY note; WriteString("TESTUNIT - SetupTestUnit", TUFile); --imposto il PS in condizione di attesa TestUnit_PSMode <= "00"; Dapprima si esegue un report su finestra di debug del simulatore per indicare l’attuale stato in esecuzione; lo stesso report viene anche effettuato su di un file di testo (per semplicità di consultazione). L’unica operazione significativa è l’imposizione della condizione di attesa comandi al Pixel Shader (il cui stato iniziale potrebbe essere indefinito), assicurandoci in questo modo la sua corretta inizializzazione al principio della simulazione. 57 Questa costante è definita nella libreria Utilities.PS_Types come: constant PATH : string(1 to 10) := "C:\PSData\"; Progettazione in VHDL del Pixel Shader v 1.4 WaitingSetup LoadShader LoadShaderLoadShaderSetup Pag. 189 Lo stato successivo, WaitingSetup, non contiene codice se non un report analogo a quanto visto per lo stato precedente. La sua unica funzione è quella di attendere per un periodo pari ad un colpo di clock che il Pixel Shader abbia terminato la sua eventuale inizializzazione e sia tornato nel suo stato stabile di WaitingCommand58, dopodiché informa il Pixel Shader di passare allo stato di WaitingInput59 per prepararsi a ricevere i dati che gli saranno inviati negli stati successivi. Lo stato WaitingSetup è uno stato “semplice” e per questo viene riportato in colore celeste. Lo stato LoadShader si occupa, come si evince anche dal suo nome, di effettuare il caricamento dello shader nell’Instruction Pool del Pixel Shader e di riempire il banco costanti con i valori contenuti nel file C.TXT o quelli associati alle istruzioni DEF. Questo è in realtà uno stato gerarchico (rappresentato in blu scuro), il che significa che è costituito a sua volta da una collezione di stati caratterizzati da un punto di ingresso (determinato dalla transizione in ingresso) ed uno o più punti di uscita (determinati dalla transizione in uscita o da eventuali link ad altri stati del diagramma). Nella pagina seguente viene riportato lo schema completo dello stato in esame; si procederà ora ad analizzare i singoli aspetti di questa realizzazione. Nello stato LoadShaderSetup si effettuano le operazioni preliminari al caricamento dello shader file nell’Instruction Pool: • impostiamo a 0 la variabile che deve contenere l’indirizzo temporaneo in cui andare a scrivere le dword di dati nell’instruction pool • impostiamo il segnale di DestReg per indicare che vogliamo utilizzare l’Instruction Pool e non il banco costanti (siamo nello stato di LoadShader per il Pixel Shader, fare riferimento alle tabelle riportate in precedenza) • per sicurezza abbassiamo il segnale di write enable in modo da non abilitare la scrittura accidentalmente • si inizia la lettura del file binario contenete le istruzioni saltando tutte le dword di intestazione che non contengono istruzioni valide. Di seguito si riporta il codice VHDL relativo. LoadShader_setup: REPORT "TESTUNIT - LoadShader_setup" SEVERITY note; WriteString("TESTUNIT - LoadShader_setup", TUFile); --imposto le condizioni iniziali. --imposto l'indirizzo iniziale per il DestAddr (destaddr ha 7 bit) tmpAddr := 0; --imposto il DestReg per caricare lo shader TestUnit_DestReg <= '1'; --disabilito il segnale di scrittura TestUnit_WriteEn <= '0'; --salto le prime 11 dword for i in 1 to 11 loop tmpDword := ReadDwordBin(PSOFile); end loop; 58 Il periodo di attesa è dato dal colpo di clock necessario a passare dallo stato SetupTestUnit a WaitingSetup. Come già detto in precedenza, occorre non lasciarsi trarre in inganno dal fatto che il codice per settare il Pixel Shader venga rappresentato sulla transizione: esso verrà comunque eseguito non appena si entra nello stato WaitingSetup e non prima di entrare nello stato successivo. Il codice sulle transizioni viene SEMPRE inserito nello stato da cui si diramano (ed eventualmente viene eseguito più volte nel caso di valutazioni multiple dello stato corrente, come vedremo in seguito). 59 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 190 Nota: da ora in poi per brevità non verranno più riportate, negli spezzoni di codice, le istruzioni relative al report di eventuali dati su debug console e su file. Tali istruzioni sono comunque presenti (ove necessario) all’interno dei sorgenti allegati alla presente tesina. LoadShaderLoadShader_pso_load Poiché il segnale di done viene utilizzato per informare l’avvenuta scrittura, devo procedere alla lettura dei dati solamente se questo segnale è al livello ‘0’. In questo stato si esegue la lettura dei dati dal file PS.PSO: si effettua una discriminazione sul tipo di istruzione (se si tratta di una istruzione DEF, che necessita di caricare i dati nel banco delle costanti, o se si tratta di una normale istruzione dello shader); si inviano i dati letti sul DataBus, settando opportunamente anche il bus indirizzi prima di inviare il segnale di abilitazione alla scrittura. Poiché si ha a disposizione un bus esterno di ampiezza massima quattro dword, questo è il numero massimo di dati che possiamo inviare contemporaneamente al Pixel Shader per effettuare il caricamento delle istruzioni. Ecco il codice con commenti a lato. --leggo 4 dword dal file binario e le memorizzo in una --variabile temporanea Variabile usata nelle condizioni sulle IF TestUnit_PSDone = '0' THEN transizioni per indicare il fatto che ci MoreData := '0'; sono dati validi da scrivere nei registri IF EndFile(PSOFile) = false THEN --leggo una DWORD e controllo se è una istruzione def tmpDword := ReadDwordBin(PSOFile); Progettazione in VHDL del Pixel Shader v 1.4 Pag. 191 --controllo se si tratta di una istruzione DEF IF tmpDword(15 DOWNTO 0) = PSDef THEN Ho identificato una istruzione DEF: --è una def, devo leggere la dword dopo ed devo settare il DestReg per indicare --identificare l'indice del registro costanti in --cui andare a scriverla l’intenzione di scrivere sul banco --setto opportunamente il destreg costanti. Setto inoltre una variabile TestUnit_DestReg <= '0'; interna atta ad indicare il fatto che i --setto la variabile di decisione dati che verranno inviati al bus isDef := '1'; appartengono ad una DEF; ciò verrà tmpDword := ReadDwordBin(PSOFile); utilizzato in seguito per --estraggo l'indice e lo metto su tmp tmpAddr tmpAddr := bits_to_intu(tmpDword(7 DOWNTO 0)); incrementare opportunamente la --leggo le restanti 4 dword di dati e le metto sul variabile temporanea contenente --bus dati (dalla più alla meno significativa) l’indirizzo in cui scrivere i dati. --tmpPSO := Read4DwordBin(PSOFile); FOR Index IN 3 DOWNTO 0 LOOP tmpDword := ReadDwordBin(PSOFile); tmpPSO((127 - 32*index) downto (96 – 32*index)) := tmpDword; END LOOP; ELSE --setto opportunamente il dest reg La dword letta non è un’istruzione TestUnit_DestReg <= '1'; --setto la variabile di decisione DEF: setto allora la variabile interna isDef := '0'; e leggo altre tre dword di dati da --non è una def...carico le altre 3 dword w sparo inviare al Pixel Shader mediante il --tutto sul databus DataBus. In questo caso non è tmpPSO(127 DOWNTO 96) := tmpDword; necessario calcolare l’indirizzo --leggo le altre 3 Dword e le inserisco dell’Instruction Pool in cui andranno FOR Index IN 1 TO 3 LOOP tmpDword := ReadDwordBin(PSOFile); scritte, poiché se ne occupa un’altra tmpPSO((127 - 32*index) downto (96 – parte della logica del programma. 32*index)) := tmpDword; END LOOP; Abbiamo letto dei dati, END IF; occorre impostare MoreData := '1'; MoreData ad ‘1’ per ELSE soddisfare le condizioni WriteString("EndFile PS.PSO",TUFile); END IF; sulle transizioni. Se non si sono letti dati il valore di MoreData resta ‘0’. END IF; Verrà ora presentato il codice relativo alle transizioni in uscita; queste ultime sono caratterizzate da un ordine di priorità (indicato con un indice associato alle transizioni stesse) che indica la posizione nella catena di IF che il generatore di codice crea di seguito al codice relativo allo stato. Più l’indice è basso, più in alto si trovano le transizioni nella catena (di conseguenza se più condizioni risultano vere contemporaneamente viene eseguita quella a priorità più elevata, ovvero quella con indice minore). Se il file dello shader è terminato, imposto il DestReg per caricare le eventuali costanti e setto lo stato futuro per il caricamento delle costanti esterne al file. 60 IF (MoreData = '0') THEN --se il file dello shader è terminato, passo a caricare --le costanti; imposto il destreg per le costanti TestUnit_DestReg <= '0'; NextState <= LoadShader_C_load; ELSIF (MoreData = '1' AND TestUnit_PSDone = '0'60) THEN TestUnit_PSDone = ‘0’ è una condizione di sicurezza, visto che il segnale TestUnit_PSDone fa parte della sensivity list del processo che viene generato e che racchiude questo codice; in questo modo ci si protegge da eventuali rivalutazioni dello stato di load: una volta rientrati dallo stato di attesa il segnale di done potrebbe mantenersi a livello ‘1’ per un tempo eccessivo e questo potrebbe causare una transizione errata (MoreData infatti sarebbe ancora ad ‘1’, non essendo stato eseguito il codice nello stato di load poiché soggetto alla condizioni TestUnit_PSDone = ‘0’). Progettazione in VHDL del Pixel Shader v 1.4 Il file dello shader non è terminato: trasferisco sul DataBus i dati letti, imposto l’indirizzo e setto come prossimo stato quello di attesa per il termine della scrittura sui registri. Questa è una condizione di loop-back: se nessuna delle condizioni precedenti è soddisfatta si forza come prossimo stato quello corrente. LoadShaderLoadShader_pso_writing Pag. 192 --la imbocchiamo se il file non è finito... ho dei dati --validi, metto i dati sul bus dati TestUnit_Data <= tmpPSO; --imposto l'indirizzo di scrittura TestUnit_DestAddr <= int_to_bits(tmpAddr,7); --abilito la scrittura NextState <= LoadShader_pso_Writing; ELSE NextState <= LoadShader_pso_load; END IF; In questo modo vengono realizzate entrambe le transizioni in uscita. Supponendo che esistano dei dati da inserire nell’Instruction Pool o nel banco costanti, l’esecuzione passa allo stato LoadShader_pso_Writing. L’unica istruzione associata a questo stato, ad eccezione di quelle di report, è la seguente: TestUnit_WriteEn <= '1' AFTER WritingDly; Si abilita la scrittura su registro con un ritardo di un periodo di clock rispetto all’assegnazione dei dati sul DataBus e all’assegnazione del valore dell’indirizzo sul bus indirizzi; ciò allo scopo di garantire che questi valori siano entrambi validi prima di procedere. Il sistema rimane in questo stato di attesa fino al verificarsi della condizione imposta sulla transizione in uscita, TestUnit_PSDone = ‘1’, che indica l’avvenuta scrittura dei dati. Vengono in questo caso eseguite tutte le operazioni connesse con il buon esito della transizione e viene settato il valore per il prossimo stato (si ritorna a LoadShader_pso_load per eventualmente caricare altri dati). Il codice generato viene riportato di seguito. Se la scrittura è terminata… IF (TestUnit_PSDone = '1') THEN Mi baso sulla variabile interna ‘isdef’, definita per valutare come incrementare l’indirizzo per la prossima operazione di scrittura. Se sto riempiendo l’Instruction Pool procedo a blocchi di 4 dword; altrimenti ho appena scritto un banco costanti, per cui azzero la tmpAddr in previsione di iniziare a riempire il registro istruzioni. Se dovessi scrivere di nuovo delle costanti la logica nello stato di load imposterebbe l’indirizzo corretto. --incremento opportunamente l'indirizzo per la prossima --scrittura IF isDef = '0' THEN --se non è una def sto riempiendo l'instr pool tmpAddr := tmpAddr + 4; ELSE --se è una def in tmpAddr c'era l'indirizzo delle --costanti e per l'instrpool va riportato --all'inizio tmpAddr := 0; END IF; --disabilito il segnale di scrittura TestUnit_WriteEn <= '0'; NextState <= LoadShader_pso_load; Questa è la condizione di loop-back: se la scrittura non è terminata, forza ancora lo stato di waiting. ELSE NextState <= LoadShader_pso_Writing; END IF; Una volta terminati i dati disponibili nel file PS.PSO, dallo stato LoadShader_pro_load si passa allo stato LoadShader_C_load; il codice qui presente (che non viene riportato perché in massima parte analogo a quanto già Progettazione in VHDL del Pixel Shader v 1.4 LoadPixelLoadPixel_Color_load LoadPixel_Color_writing LoadPixel_Texture_load LoadPixel_Texture_writing Pag. 193 visto per lo stato LoadShader_pso_load, ad eccezione delle funzioni utilizzate per la lettura dei dati) si occupa di reperire ulteriori dati da inserire nel banco dei registri delle costanti prelevandoli dal file C.TXT. Se questi dati esistono vengono opportunamente spostati sul DataBus, viene settato l’indirizzo di scrittura e si passa quindi al corrispondente stato di attesa in cui si abilita la scrittura e si attende il completamento della stessa. Al termine del file delle costanti si comunica al Pixel Shader il passaggio al modo LoadPixel (istruzione TestUnit_PSMode = “10”) in corrispondenza della transizione tra i due stati gerarchici LoadShader e LoadPixel della macchina a stati finiti. Il codice sorgente relativo può essere consultato in appendice. Lo stato gerarchico successivo (LoadPixel) si occupa di fornire al Pixel Shader i dati relativi al singolo pixel da elaborare, sotto forma di dati sul colore e dati relativi alle coordinate delle texture eventualmente associate con il pixel corrente. Il diagramma a stati di questa porzione della macchina a stati finiti viene presentato nella figura seguente. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 194 Esso opera sostanzialmente in maniera analoga a quanto visto per la sezione relativa al caricamento dell’Instruction Pool e del banco delle costanti, con la differenza che ora le condizioni sulle transizioni non si basano più sul controllo della variabile interna MoreData, ma sul controllo del valore dell’indice letto di volta in volta in base a quanto detto all’inizio del paragrafo 6.6. Anche in questo caso, poiché il codice che si deve associare ad ogni stato e ad ogni transizione è molto simile a quello descritto in precedenza, si rimanda all’appendice per una completa consultazione del sorgente VHDL che descrive il presente stato gerarchico. Al termine dell’ultimo stato di LoadPixel viene inserito il codice relativo alla transizione che porterà verso lo stato di attesa del termine dell’esecuzione dello shader sui dati appena immessi, oppure si andrà allo stato di termine del test per mancanza di nuovi dati da elaborare. Il codice riportato di seguito sintetizza le due transizioni in uscita dallo stato LoadPixel (vedi figura all’inizio del paragrafo 6.6.6): La transizione con priorità 1 è quella che porta allo stato EndTest: qui si riporta il Pixel Shader nel suo stato di attesa comandi in quanto sono terminati i dati da elaborare. IF ((EndFile(TFile) OR tmpIndex = 9) AND (MorePixels = '0')) THEN --abbiamo terminato di caricare i dati per il pixel --corrente --se sono finiti i pixel vado al termine del test --metto in attesa il pixelshader TestUnit_PSMode <= "00"; NextState <= EndTest; La transizione con ELSIF ((EndFile(TFile) OR tmpIndex = 9) AND (TestUnit_PSDone = '0')) priorità 2 è quella che THEN porta allo stato Execute: --abbiamo terminato di caricare i dati per il pixel si riporta il Pixel Shader --corrente nello stato di esecuzione e --la condizione per la prima transazione è fallita ho un si libera il bus dati (la test --pixel da elaborare --passo ad eseguire lo shader per il pixel corrente unit non ne ha più TestUnit_PSMode <= "11"; bisogno). --azzero i dati inviati sul databus TestUnit_Data <= (others => '0'); NextState <= Execute; ELSIF … Qui di seguito vengono inserite le transizioni senza priorità (ovvero quella verso lo stato LoadPixel_texture_writing e la transizione di loop-back), visibili internamente allo stato gerarchico LoadPixel. Execute Come si può facilmente verificare la condizione da soddisfare per eseguire entrambe queste transizioni è data dall’AND logico fra la condizione sulla transizione in uscita internamente allo stato gerarchico LoadPixel e le condizioni presenti nelle transizioni in ingresso agli stati EndTest e Execute. Supponendo di avere appena inserito dei dati validi da elaborare la Test Unit passa allo stato Execute: essenzialmente si tratta di uno stato di attesa in cui non si esegue codice se non quello di report. La transizione di uscita si verifica solo quando il Pixel Shader ha terminato l’elaborazione dello shader per il pixel corrente; di seguito ne presentiamo il codice relativo. Progettazione in VHDL del Pixel Shader v 1.4 Lo shader è stato eseguito con successo… Effettuo una serie di report… Riporto il Pixel Shader nel modo LoadPixel, per poter ottenere, se presenti, i dati relativi al prossimo pixel da elaborare… Viene effettuato il report su file del risultato dell’elaborazione… Viene richiesto un dump su file del contenuto dei registri…La Test Unit viene riportata all’inizio del macrostato LoadPixel per proseguire l’elaborazione. Condizione di loop-back per rimanere nello stato Execute fino al termine dell’elaborazione. EndTest Pag. 195 IF (TestUnit_PSDone = '1') THEN --qui inviamo tutti i risultati dell'elaborazione ad un --file di testo ASSERT false REPORT "Fine elaborazione pixel" SEVERITY note; WriteString("ELABORAZIONE PIXEL TERMINATA",TUFile); --risetto il pixel shader in posizione di load pixel TestUnit_PSMode <= "10"; --scrivo R0 WriteString("R0: ", OutPutFile); Write4DwordRow(PS_R0, OutPutFile); --scrivo R5.r WriteString("R5.r: ", OutPutFile); WriteDword(PS_R5R, OutPutFile); --scrivo PixelKill WriteString("PixelKill: ", OutPutFile); WriteBit(PS_PixelKill, OutPutFile); --scrivo depthenable WriteString("DepthEnable: ", OutPutFile); WriteBit(PS_DepthEn, OutPutFile); --effettuo il dump dei registri TestUnit_Dump <= '1', '0' after 1 ns; NextState <= LoadPixel_Color_load; ELSE NextState <= Execute; END IF; Se nello stato LoadPixel non vengono letti ulteriori dati da elaborare la variabile interna MorePixels viene posta a ‘0’ e si passa allo stato EndTest. Il Pixel shader era già stato riportato nello stato di attesa (WaitingCommand) durante la precedente transizione; qui l’unica cosa che si fa è provocare un errore critico per interrompere il flusso della simulazione (ovviamente restituendo un messaggio adeguato che indichi la corretta esecuzione del test). Il codice associato allo stato è il seguente: --blocco l'esecuzione per indicare che ho terminato ASSERT false REPORT "Esecuzione del test terminata con successo" SEVERITY Failure; In questo modo si esaurisce la trattazione relativa alla Test Unit ed al test bench per il Pixel Shader; nel prossimo capitolo si illustreranno le metodologie di progetto e l’architettura realizzata per l’implementazione del Pixel Shader vero e proprio. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 196 Capitolo 7. Architettura interna del Pixel Shader v.1.4 Con il presente capitolo entriamo nel vivo del progetto, illustrando l’architettura interna del Pixel Shader. Precisiamo che essa rappresenta la nostra scelta di implementazione del processore, basata sulle specifiche esposte nel capitolo 1: in sostanza si tratta di un progetto full-custom. Nelle pagine che seguono descriveremo i singoli blocchi elementari che compongono il processore e la loro interconnessione necessaria al corretto funzionamento della struttura complessiva, giustificando di volta in volta le scelte da noi operate. 7.1. Schema a macroblocchi del Pixel Shader V R ALU R ALU G ALU B ALU A Modificatori Istruzione T Modificatori Sorgenti C Swizzling & Masking Controllo Registri e Decodifica IP Op1 BUS Op0 BUS Data l’elevata complessità dell’architettura interna del Pixel Shader, riteniamo opportuno cominciare la nostra discussione presentando uno schema a macroblocchi del processore, direttamente derivante dalle specifiche di progetto. Successivamente svilupperemo nel dettaglio ciascuno dei macroblocchi, in modo da arrivare allo schema finale. Data BUS UNITA’ DI CONTROLLO Il punto di partenza che ci ha permesso di elaborare la struttura mostrata è lo schema di principio del Pixel Shader, introdotto nel paragrafo 5.3. Comparando le due figure si notano alcuni elementi comuni, ma anche delle differenze: diamo allora un primo sguardo alla struttura cercando di capire la funzione svolta da ogni singolo macroblocco. § Cominciamo con i blocchi in comune alle due figure, che sono i banchi di registri (C, T, V, R) e l’Instruction Pool (IP). Tali elementi di memoria sono da considerarsi dei blocchi elementari, il cui funzionamento e la cui implementazione in VHDL saranno descritti rispettivamente nei paragrafi 7.3 e 7.4. § L’elemento Pixel Shader ALU presente nello schema di principio del Progettazione in VHDL del Pixel Shader v 1.4 § § § § § § § Pag. 197 processore è stato sviluppato a sua volta in una serie di macroblocchi. Nella parte dedicata alle specifiche si è infatti visto che i dati letti dai registri sorgenti subiscono, nell’ambito dell’esecuzione di un’istruzione, una catena di trasformazioni: a ciascuna di tali trasformazioni corrisponde, nella nostra struttura, un macroblocco. Il macroblocco chiamato Swizzling & Masking, di colore giallo, rappresenta l’implementazione del source register selector, ovvero effettua l’operazione di replica del canale specificato dal registro sorgente. Il macroblocco chiamato Modificatori Sorgenti, di colore verde, implementa i source register modifiers, ovvero esegue sui dati presenti al suo ingresso le operazioni di bias, invert, negate, scalex2 e signed scaling. In colore blu sono indicate le quattro ALU floating-point, una per ciascuno dei canali R, G, B, A. Tali blocchi eseguono, sui due operandi presenti al loro ingresso, operazioni aritmetiche e logiche di base necessarie per poter implementare l’intero instruction-set del processore. Il macroblocco chiamato Modificatori Istruzione, indicato in celeste, serve a modellare gli instruction modifiers, ovvero le operazioni di moltiplicazione e divisione per 2, 4, 8 e l’operazione di saturazione. In figura si nota la presenza di tre diversi bus. I bus operandi (Op0 e Op1) contengono i dati che, una volta sottoposti ai Modificatori Sorgenti, costituiranno i due operandi delle ALU. Il data bus è invece il generico bus dati, atto a contenere i risultati delle operazioni e usato anche per lo scambio di dati con l’esterno. Il macroblocco denominato Controllo Registri e Decodifica, di colore rosso, contiene al suo interno tutta la logica necessaria per la lettura/scrittura dei registri e si occupa inoltre di pilotare gli Swizzlers, i Modificatori Sorgenti ed i Modificatori Istruzione. L’ultimo macroblocco presente nello schema è l’Unità di Controllo, che supervisiona il funzionamento dell’intero sistema ed è connesso a tutti gli altri macroblocchi presenti nel processore. Riteniamo utile mettere fin d’ora in chiaro che il macroblocco Unità di Controllo è in realtà composto da tre componenti distinti, denominati MainCU, VectCU e ScalCU. La VectCU controlla la vector pipeline; la ScalCU controlla la scalar pipeline; la MainCU è l’unità di controllo principale e scambia segnali con tutti gli altri componenti all’interno del processore, comprese VectCU e ScalCU. Ora che abbiamo elencato i singoli macroblocchi vediamo quale sia il funzionamento della struttura complessiva. Durante le fasi di caricamento dello shader e caricamento del pixel, il Pixel Shader riceve, tramite il Data bus, i dati da scrivere sui registri; le informazioni sul registro destinazione e sull’indirizzo arrivano al macroblocco Controllo Registri e Decodifica. L’unità di controllo gestisce l’handshaking con l’esterno (Test Unit) e fornisce ai registri i segnali di abilitazione scrittura. Passiamo ora all’analisi della fase di esecuzione dello shader, fissando l’attenzione sul flusso di esecuzione di una semplice istruzione, quale la ADD. Innanzitutto il macroblocco Controllo Registri e Decodifica preleva dall’Instruction Pool l’OpCode da eseguire, unitamente alle informazioni sui registri destinazione e sorgenti. Lo stesso macroblocco provvede ed eseguire le seguenti operazioni: Progettazione in VHDL del Pixel Shader v 1.4 Pag. 198 § § § trasmette l’OpCode all’unità di controllo fornisce i segnali di abilitazione lettura ai registri corretti fornisce al macroblocco Swizzling & Masking le informazioni necessarie ad una corretta replicazione dei canali § decodifica il source modifier e informa il macroblocco Modificatori Sorgenti sulle operazioni da eseguire § decodifica l’instruction modifier e informa il macroblocco Modificatori Istruzione sulle operazioni da eseguire. Una volta che i dati sottoposti a swizzling sono pronti sui bus operandi, l’unità di controllo provvede ad accendere i Modificatori Sorgenti e, dopo un opportuno ritardo, ad attivare le ALU, le quali sono già state informate sull’operazione da compiere. Quando l’unità di controllo riceve la conferma dell’avvenuta esecuzione delle operazioni da parte di tutte le ALU, attende che il Modificatore Istruzione abbia completato la sua elaborazione, dopodiché fornisce il segnale di abilitazione scrittura del risultato finale. Il dato presente sul Data bus viene scritto nel registro destinazione solo per le componenti indicate nella write mask, informazione di cui dispone il macroblocco Controllo Registri e Decodifica. A questo punto possiamo ritenere conclusa l’esecuzione della singola istruzione. Osservazione Quanto esposto nel presente paragrafo ha la finalità di introdurre l’architettura del Pixel Shader nel modo più semplice possibile, seguendo un approccio che parte da uno schema a macroblocchi, per poi procedere ad una successiva descrizione dettagliata di ciascuno. In virtù di tale fatto, le spiegazioni dei singoli macroblocchi e del flusso di esecuzione di un’istruzione date poc’anzi devono ritenersi del tutto sommarie e aventi uno scopo puramente introduttivo. I macroblocchi Swizzling & Masking, Modificatori Sorgente e Modificatori Istruzione hanno in realtà una struttura interna simile all’ALU, cioè sono organizzati in maniera da operare indipendentemente e parallelamente su più canali. Il macroblocco Controllo Registri e Decodifica è particolarmente complesso in quanto deve prevedere la possibilità di caricare due istruzioni contemporaneamente. Più in generale, il pairing di istruzioni introduce una complessità in tutti i macroblocchi, comportando la duplicazione di certe strutture e l’introduzione di reti per la combinazione e selezione di dati. E’ proprio la presenza di pairing che ha determinato la scissione dell’unità di controllo in tre sotto-unità distinte! Infine, il flusso di esecuzione di una istruzione è ben più complesso di quello sopra illustrato: entreremo nei dettagli dell’argomento quando spiegheremo il funzionamento dell’unità di controllo. A partire dal prossimo paragrafo cominceremo ad analizzare ogni macroblocco in dettaglio: avremo così modo di renderci conto della complessità del Pixel Shader e al tempo stesso avremo gli strumenti per una comprensione più profonda della figura mostrata all’inizio del capitolo. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 199 7.2. ALU Il primo macroblocco che prendiamo in esame per un’analisi approfondita è la singola ALU floating-point. Come abbiamo già detto, all’interno del processore sono presenti quattro ALU, una per ciascuno dei canali R, G, B, A. La funzione dell’ALU è quella di eseguire una serie di operazioni aritmetiche e logiche di base necessarie per poter implementare l’intero instruction-set del Pixel Shader. E’ bene chiarire che ad ogni istruzione del processore non corrisponde direttamente una istruzione dell’ALU! In effetti le istruzioni aritmetiche BEM, DP3, DP4, LRP, MAD possono essere facilmente implementate sfruttando tre operazioni aritmetiche di base (addizione, sottrazione, moltiplicazione, già richieste per implementare le istruzioni ADD, SUB, MUL) e ricorrendo ad un’opportuna rete di retroazione e selezione dell’operando in ingresso. Le istruzioni di texture addressing, dal canto loro, comportano solamente l’introduzione, all’interno del set di istruzioni dell’ALU, dell’operazione di divisione (DIV), necessaria per l’istruzione TEXDEPTH e per implementare i modificatori nell’ambito delle TEXCRD e TEXLD. 7.2.1 L’entità ALUX L’entità che nell’ambito del nostro progetto in VHDL implementa l’ALU floating-point è denominata ALUX (la lettera X è usata per indicare uno dei quattro canali R, G, B, A) ed è rappresentata nella figura sottostante. Analizziamo i segnali d’ingresso: § ALUX_en: abilitazione del componente § ALUX_Op0 (32 bit): operando 0 § ALUX_Op1 (32 bit): operando 1 § ALUX_OpCode (3 bit): codice operativo dell’istruzione dell’ALU § ALUX_clk1: clock fase 1 § ALUX_clk2: clock fase 2. I segnali di uscita sono i seguenti: § ALUX_done: indica il termine dell’esecuzione dell’istruzione § ALUX_res (32 bit): risultato dell’operazione § ALUX_CompBit: risultato della comparazione. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 200 Riportiamo nella seguente tabella l’instruction-set completo dell’ALU. Codifica 000 001 010 011 100 101 110 111 OpCode ADD SUB MUL PASS0 PASS1 CMP0 CMP05 DIV Descrizione Addizione Sottrazione Moltiplicazione Riporta l’Operando 0 sull’uscita Riporta l’Operando 1 sull’uscita Setta il CompBit a 1 se l’Operando 0 è maggiore o uguale a 0 Setta il CompBit a 1 se l’Operando 0 è maggiore a 0,5 Divisione Oltre alle quattro operazioni aritmetiche elementari (addizione, sottrazione, moltiplicazione e divisione), l’ALU può eseguire due istruzioni di comparazione (con 0 e con 0,5) – il cui risultato è riportato nella linea d’uscita ALUX_CompBit – e due semplici istruzioni che riportano in uscita rispettivamente l’operando 0 e l’operando 1 (tali istruzioni permettono di implementare, ad esempio, la MOV). Data la sua estrema complessità, il componente è stato descritto a livello behavioral. Il relativo codice VHDL, riccamente commentato, è riportato in appendice. Poiché, come già accennato, l’implementazione delle istruzioni più complesse richiede l’impiego di più passi di esecuzione, particolare importanza assume anche la rete di selezione realizzata per una corretta gestione dei segnali di ingresso ed uscita delle singole ALU. Le problematiche relative all’implementazione di una tale rete verranno affrontate nel paragrafo 7.8. 7.3. Registri In questo paragrafo analizziamo il modo in cui sono stati realizzati i banchi di registri c, t, v, r. Tali componenti sono molto simili tra loro: le uniche differenze riguardano il diverso numero di registri presenti in ciascun banco e il fatto che il banco r deve riportare in uscita il contenuto di r0 e r5.r. Fissiamo allora l’attenzione sugli elementi in comune ai quattro banchi di registri, cercando di individuare quali caratteristiche debba avere l’entità VHDL che li implementa. Dalle specifiche sul funzionamento del processore, date nel capitolo 5, segue immediatamente che ogni registro è composto da quattro dword: il generico banco contiene allora n registri da 128 bit ciascuno. I registri devono ovviamente poter essere usati sia in lettura (nella fase di esecuzione dell’operazione) che in scrittura (nelle fasi di caricamento dello shader, caricamento del pixel, scrittura del risultato finale). Un elemento di complessità è dato dal fatto che il banco di registri deve poter essere indirizzato in lettura contemporaneamente da quattro linee di ingresso. Supponiamo che il processore debba eseguire la seguente coppia di istruzioni: mul r0.rgb, r1, r2 + add r0.a, r1, r3 Come si vede nell’esempio, il banco di registri r deve fornire tutti e quattro i sorgenti richiesti per l’esecuzione contemporanea delle due istruzioni. Facciamo notare che è possibile eseguire in pairing due istruzioni che richiedono tre sorgenti ciascuna, come nel seguente esempio: lrp r0.rgb, c0, c1, c1 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 201 +lrp r1.a, c0, c0, c1 Questi tipi di istruzioni vengono tuttavia eseguiti in due o più passi di elaborazione e ciascun passo richiede (al massimo) due sorgenti per istruzione. Per quanto riguarda la scrittura, l’unica difficoltà è rappresentata dal banco r: dalle specifiche sappiamo infatti che è data la possibilità di scegliere se scrivere o meno ciascun canale (R, G, B, A) del risultato finale; inoltre la parte vettoriale e quella scalare possono essere scritte in due registri destinazione diversi (es. r0.rgb, r1.a). L’implementazione fisica del banco r deve tener conto di tutte queste particolarità. 7.3.1 L’entità RegX I banchi di registri t, c, v, essendo del tutto simili tra loro, vengono implementati mediante un’unica entità, detta RegX (la lettera X deve essere intesa come l’identificativo di uno dei tre banchi indicati). Ovviamente tale entità è parametrica, essendo caratterizzata da un indice che specifica il numero dei registri componenti (2 registri per il banco v, 6 per il banco t, 8 per il banco c). Analizziamo i segnali d’ingresso dell’entità. § RegX_Addr0, RegX_Addr1, RegX_Addr2, RegX_Addr3 (3 bit): sono gli indirizzi associati alle quattro linee 0, 1, 2, 3. Questi possono essere visti come indirizzi in lettura, se i corrispondenti segnali di ReadEn sono alti, o come indirizzi in scrittura, se invece sono alti i segnali di WriteEn. § RegX_ReadEn0, RegX_ReadEn1, RegX_ReadEn2, RegX_ReadEn3: sono i segnali di abilitazione in lettura associati alle quattro linee di indirizzo in ingresso. § RegX_DataIn (128 bit): è il dato in ingresso da usare per l’operazione di scrittura. § RegX_WriteEnR, RegX_WriteEnG, RegX_WriteEnB, RegX_WriteEnA: sono i segnali di abilitazione scrittura, uno per ciascuna delle quattro linee di indirizzo in ingresso. Tali segnali permettono di scrivere, nell’ordine in cui sono riportati, i canali R, G, B, A del dato in ingresso. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 202 § RegX_Dump: è il segnale che permette di memorizzare il contenuto dell’intero banco di registri su un file. E’ usato solo in fase di progettazione. Analizziamo i segnali d’uscita dell’entità. § RegX_DataOut0, RegX_DataOut1, RegX_DataOut2, RegX_DataOut3 (128 bit): sono il risultato dell’operazione di lettura. Ciascuno di tali dati è relativo ad una delle quattro linee, avendo associato un proprio segnale di indirizzo e un proprio segnale di abilitazione lettura. Spieghiamo il funzionamento dell’operazione di lettura del banco di registri, fissando l’attenzione su una singola linea. Se il segnale di ReadEn della linea è alto, nella corrispondente uscita DataOut è riportato il contenuto del registro che ha l’indirizzo specificato dall’ingresso Addr relativo alla medesima linea. Se invece il segnale di ReadEn è basso, ogni bit dell’uscita DataOut viene posto a ‘0’. Passiamo ora a spiegare l’operazione di scrittura. Innanzitutto osserviamo che i segnali di abilitazione scrittura WriteEnR, WriteEnG, WriteEnB e WriteEnA sono associati rispettivamente alle linee 0, 1, 2 e 3. Supponiamo che il segnale WriteEnR sia alto: il canale R del dato in ingresso (DataIn) viene allora scritto nel registro che ha l’indirizzo specificato dall’ingresso Addr0. Un funzionamento analogo vale per gli altri tre segnali di abilitazione scrittura. Ovviamente se il segnale di WriteEn relativo ad una certa linea è basso, per quella linea non avviene alcuna operazione di scrittura. Il codice VHDL necessario ad implementare a livello behavioral l’entità RegX è riportato interamente in appendice. Per una possibile realizzazione circuitale dell’entità fare riferimento al paragrafo 7.14. 7.3.2 L’entità RegR Il banco di registri r viene implementato mediante l’entità RegR. Questa è del tutto simile alla RegX, ma dispone in più di due linee di uscita: RegR_R0, a 128 bit, che è il contenuto del registro r0, e RegR_R5r, a 32 bit, che è il contenuto della componente r del registro r5. 7.3.3 Le entità AddrSel1 e AddrSel2 Si è detto poc’anzi che i banchi di registri dispongono di quattro linee di indirizzo che vengono usate per operazioni di lettura e di scrittura. E’ allora evidente che deve esserci una logica esterna, la quale riceve in ingresso gli indirizzi di lettura e gli indirizzi di scrittura e invia al banco di registri l’indirizzo corretto a seconda dell’operazione effettuata. La logica in questione è detta AddressSelector ed esiste in due versioni, leggermente differenti tra loro, chiamate AddrSel1 e AddrSel2. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 203 L’entità AddrSel2 funziona come segue: § se il segnale WRSel è basso, in uscita vengono riportati gli indirizzi in lettura; più esattamente le linee out0, out1, out2, out3 assumono rispettivamente i valori R0, R1, R2, R3. § se il segnale WRSel è alto, in uscita vengono riportati gli indirizzi in scrittua; più esattamente le linee out0, out1, out2 assumono il valore presente nella linea d’ingresso WVect, la linea out3 assume il valore presente nella linea d’ingresso WScal L’entità AddrSel2 viene utilizzata per selezionare e inviare l’indirizzo corretto al banco di registri r; le linee out0, out1, out2, out3 sono connesse rispettivamente con gli ingressi Addr0, Addr1, Addr2, Addr3 del componente RegR. Una possibile implementazione circuitale dell’entità è mostrata nella figura seguente. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 204 L’entità AddrSel1 viene utilizzata per selezionare e inviare l’indirizzo corretto ai banchi di registri c, t, v. Essa può essere ottenuta dall’entità AddrSel2 semplicemente cortocircuitando gli ingressi WVect e WScal per ottenere un’unica linea d’ingresso, chiamata W. 7.4. Instruction Pool L’Instruction Pool viene modellato tramite l’entità chiamata InstrPool, mostrata nella figura sottostante. Il componente in questione dispone dei seguenti ingressi. § ReadEn: abilitazione lettura § ReadAddr (7 bit): indirizzo in lettura § WriteEn: abilitazione scrittura § WriteAddr (7 bit): indirizzo in scrittura § DataIn (128 bit): dato in ingresso (4 dword) § Dump: serve a memorizzare su un file il contenuto dell’Instruction Pool (utilizzato solo in fase di progettazione). L’entità ha una sola uscita: § DataOut (160 bit): dati in uscita (5 dword). L’Instruction Pool viene modellato come una memoria costituita da 128 dword; tale quantità è stata scelta in modo da poter memorizzare il massimo numero di dword costituenti uno shader valido. La scrittura avviene a gruppi di 4 dword (si ricordi che il Databus è a 128 bit), mentre l’operazione di lettura fornisce in uscita 5 dword. Tale scelta è motivata dal fatto che cinque è il massimo numero di dword associate ad un’istruzione eseguibile dal processore (vedi CMP, CND, LRP, MAD): con una sola lettura si ottengono l’OpCode, il registro destinazione e i registri sorgenti relativi all’istruzione. Osserviamo per inciso che l’istruzione DEF, che richiede 6 dword, non è eseguita dal processore, ma viene incorporata dalla Test Unit nel caricamento dello shader. Riportiamo qui di seguito il codice VHDL che descrive l’entità a livello behavioral. ARCHITECTURE behavioral OF InstrPool IS TYPE RAM_IP IS ARRAY (0 TO 127) OF DWORD; SIGNAL RAM : RAM_IP; FILE IPDump : ASCII_TEXT OPEN WRITE_MODE IS PATH & "IPDump.txt"; Progettazione in VHDL del Pixel Shader v 1.4 BEGIN --processo di lettura IORead : PROCESS (InstrPool_ReadEn) VARIABLE addr : INTEGER RANGE 0 TO 127; BEGIN addr := bits_to_intu(InstrPool_ReadAddr); --leggo le 5 dword in sequenza IF addr > -1 AND addr < 128 THEN IF InstrPool_ReadEn = '1' THEN InstrPool_DataOut(159 DOWNTO 128) <= RAM(addr); InstrPool_DataOut(127 DOWNTO 96) <= RAM(addr+1); InstrPool_DataOut(95 DOWNTO 64) <= RAM(addr+2); InstrPool_DataOut(63 DOWNTO 32) <= RAM(addr+3); InstrPool_DataOut(31 DOWNTO 0) <= RAM(addr+4); END IF; END IF; END PROCESS IORead; --processo di scrittura IOWrite : PROCESS(InstrPool_WriteEn) VARIABLE addr : INTEGER RANGE 0 TO 127; ALIAS DWORD3 : DWORD IS InstrPool_DataIn(127 DOWNTO 96); ALIAS DWORD2 : DWORD IS InstrPool_DataIn(95 DOWNTO 64); ALIAS DWORD1 : DWORD IS InstrPool_DataIn(63 DOWNTO 32); ALIAS DWORD0 : DWORD IS InstrPool_DataIn(31 DOWNTO 0); BEGIN addr := bits_to_intu(InstrPool_WriteAddr); IF addr > -1 AND addr < 128 THEN IF InstrPool_WriteEn = '1' THEN RAM(addr) <= DWORD3; RAM(addr+1) <= DWORD2; RAM(addr+2) <= DWORD1; RAM(addr+3) <= DWORD0; END IF; END IF; END PROCESS IOWrite; --processo di dump dump : PROCESS(InstrPool_Dump) VARIABLE temp : EIGHTWORD; BEGIN IF InstrPool_Dump = '1' THEN WriteString(" ** REGISTER_DUMP **", IPDump); FOR i IN 0 TO 31 LOOP FOR j IN 0 TO 3 LOOP temp(31 + j * 32 DOWNTO 0 + j * 32) := RAM(i * 4 + j); END LOOP; Write4DwordHex(temp,IPDump); END LOOP; END IF; END PROCESS dump; END behavioral; Pag. 205 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 206 7.5. Il macroblocco Controllo Registri e Decodifica (1° parte) In questo paragrafo prendiamo in considerazione il macroblocco che abbiamo chiamato Controllo Registri e Decodifica. Mettiamo fin da subito in evidenza che il macroblocco in questione è piuttosto complesso, essendo formato da un elevato numero di componenti elementari, ciascuno caratterizzato da una specifica funzione. L’identificativo scelto per designare il macroblocco non riesce a sintetizzare tutte le funzionalità da esso svolte, ma ne mette tuttavia in risalto le caratteristiche essenziali: i componenti presenti al suo interno gestiscono la lettura e la scrittura dei registri e dell’Instruction Pool e al tempo stesso decodificano tutte le informazioni contenute in una istruzione, inviando i risultati dell’elaborazione ai blocchi destinatari. Al fine di rendere la trattazione logica e chiara, in questo paragrafo andremo a descrivere soltanto una parte delle entità che formano il macroblocco Controllo Registri e Decodifica; le entità di interesse sono riportate nel seguente elenco: § Write Register Selector (WRS) § Program Counter (PC) § Program Counter Increment (PCIncr) § Instruction Latch First e Second § Source Information Processing (SIP) § SIP Driver First e Second § Source Decoder. Le restanti entità saranno spiegate nel paragrafo 7.10, dopo che si saranno acquisite le informazioni necessarie ad una loro piena comprensione. 7.5.1 L’entità Write Register Selector (WRS) L’entità denominata Write Register Selector (WRS) viene impiegata durante le fasi di caricamento shader e caricamento pixel e svolge la funzione di inviare al registro corretto il segnale di abilitazione scrittura ed il relativo indirizzo. La figura seguente, tratta dallo schema circuitale del Pixel Shader, mostra il WRS (istanza WriteRegSel) ed evidenzia che i suoi segnali d’ingresso derivano direttamente dall’esterno del processore. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 207 Il WRS viene attivato dall’esterno (Test Unit), portando il segnale PS_WriteEn, che coincide con WRS_WriteEn, a livello alto. Quando si verifica tale evento, il WRS, sulla base del segnale WRS_DestReg e del modo di funzionamento del processore (segnale WRS_PSMode), porta a livello alto solo uno dei 4 segnali di attivazione scrittura presenti in uscita (WRS_EnIP, WRS_EnC, WRS_EnT, WRS_EnV). La seguente tabella mostra il registro attivato in funzione dei due segnali in ingresso. WRS_DestReg WRS_PSMode LoadShader (01) LoadPixel (10) LoadShader (01) LoadPixel (10) 0 1 Registro Attivato C V IP T L’indirizzo di scrittura presente in uscita (WRS_WriteAddr) è semplicemente la replica del segnale WRS_DestAddr, presente in ingresso. Qui di seguito viene riportato il codice che descrive l’architettura behavioral dell’entità. ARCHITECTURE behavioural OF WRS IS BEGIN --riporto in uscita l’indirizzo di scrittura WRS_WriteAddr <= WRS_DestAddr; --il WRS si attiva quando il segnale WRS_WriteEn commuta a 1 wrs_process : PROCESS (WRS_WriteEn) BEGIN IF WRS_WriteEn = '1' THEN --decido quale registro abilitare per la scrittura IF WRS_PSMode = PS_LoadShader THEN IF WRS_DestReg = '0' THEN --attivo il banco C WRS_EnC <= '1'; WRS_EnIP <= '0'; WRS_EnV <= '0'; WRS_EnT <= '0'; ELSE --attivo l’Instruction Pool WRS_EnC <= '0'; WRS_EnIP <= '1'; WRS_EnV <= '0'; WRS_EnT <= '0'; END IF; ELSIF WRS_PSMode = PS_LoadPixel THEN IF WRS_DestReg = '0' THEN --attivo il banco V WRS_EnC <= '0'; WRS_EnIP <= '0'; WRS_EnV <= '1'; Progettazione in VHDL del Pixel Shader v 1.4 Pag. 208 WRS_EnT <= '0'; ELSE --attivo il banco T WRS_EnC <= '0'; WRS_EnIP <= '0'; WRS_EnV <= '0'; WRS_EnT <= '1'; END IF; END IF; ELSE --non devo abilitare alcun registro WRS_EnC <= '0'; WRS_EnIP <= '0'; WRS_EnT <= '0'; WRS_EnV <= '0'; END IF; END PROCESS wrs_process; END behavioural; 7.5.2 Le entità Program Counter (PC) e Program Counter Increment (PCIncr) Le entità Program Counter (PC) e Program Counter Increment (PCIncr) sono i componenti che, nel loro insieme, danno in uscita il puntatore all’Instruction Pool, ovvero indicano l’indirizzo della prima dword della prossima istruzione. La figura seguente mostra la loro interconnessione all’interno del Pixel Shader; si noti che i segnali in colore blu derivano dall’Unità di Controllo. Il Program Counter è essenzialmente un elemento di memoria a 7 bit, resettato e abilitato dall’Unità di Controllo rispettivamente tramite gli ingressi PC_reset e PC_en. Quando viene abilitato, il Program Counter memorizza al suo interno il dato presente in ingresso (PC_in). Il segnale d’uscita (PC_out) non è altro che il dato memorizzato dal componente; nello schema del Pixel Shader tale segnale è connesso con l’ingresso InstrPool_ReadAddr. Il Program Counter Increment è un circuito che effettua la somma tra le quantità (numeri interi) presenti in ingresso. Quando è abilitato dall’Unità di Controllo (segnale PCIncr_en), il componente esegue la somma tra le quantità codificate nei segnali PCIncr_in (7 bit) e PCIncr_num (3 bit) e riporta il Progettazione in VHDL del Pixel Shader v 1.4 Pag. 209 risultato in uscita (PCIncr_out). Si noti che il massimo valore utile per il segnale PCIncr_num coincide con il massimo numero di dword contenute in una istruzione eseguibile e tale quantità è pari a cinque: è per questo che sono sufficienti tre bit per codificare l’incremento. Data l’estrema semplicità dei componenti, per la loro descrizione in VHDL si rimanda direttamente al codice riportato in appendice. 7.5.3 Gli Instruction Latch Nel paragrafo precedente si sono descritti i componenti che forniscono l’indirizzo corretto all’Instruction Pool; ora analizziamo i componenti che sfruttano i dati in uscita da tale registro. Come sappiamo, il dato in uscita dall’Instruction Pool è composto da cinque dword, in modo da poter contenere l’istruzione più lunga. Tali dati, prima di essere sottoposti a decodifica, sono memorizzati all’interno di opportuni latch. Nello schema del Pixel Shader esistono due latch istruzioni, denominati Instruction Latch First e Instruction Latch Second. Il motivo di tale duplicazione è direttamente legato alla possibilità di eseguire due istruzioni in pairing. Spieghiamo nel dettaglio l’uso di ciascun instruction latch: § Instruction Latch First: memorizza le istruzioni non in pairing e, nel caso di pairing, la prima istruzione della coppia § Instruction Latch Second: usato in caso di pairing, memorizza la seconda istruzione della coppia. Fissiamo l’attenzione sull’Instruction Latch First, riportato nella figura sottostante. Si tratta di un latch a 160 bit, abilitato tramite il segnale InstrLatch_en, proveniente dall’Unità di Controllo. Il dato in ingresso (InstLatch_in) corrisponde all’uscita dell’Instruction Pool (InstrPool_DataOut). Il dato memorizzato dal componente, ad eccezione della prima dword, viene riportato in uscita sottoforma di quattro dword: InstrLatch_D, InstrLatch_S0, InstrLatch_S1, InstrLatch_S2. Riportiamo nella figura a inizio pagina seguente la struttura interna dell’Instruction Latch First, utilizzata nell’ambiente HDL Designer come punto di partenza per la generazione automatica del codice VHDL. Si nota la presenza di un latch di tipo d parametrico, con dimensione (parametro width) impostata a 160 bit. Il blocco denominato split non fa altro che raggruppare i bit da 127 a 0 in 4 dword, che rappresentano le uscite dell’InstrLatchFirst. Osserviamo che l’OpCode dell’istruzione (bit da 159 a 128) non viene riportato in uscita: nel progetto si è infatti preferito trasmettere l’OpCode Progettazione in VHDL del Pixel Shader v 1.4 Pag. 210 dall’Instruction Pool direttamente all’Unità di Controllo, che lo memorizza in un latch interno per poi sottoporlo ad elaborazione. L’Instruction Latch Second, atto a memorizzare le 5 dword della seconda istruzione, è del tutto simile all’Instruction Latch First: l’unica differenza riguarda la presenza di un’ulteriore uscita, denominata InstrLatch_A. Tale uscita, coincidente con il bit 19 del destination register, contiene l’informazione sul canale alpha della write mask e permettere di sapere, nel caso di pairing, se l’istruzione memorizzata nell’InstrLatchSecond è relativa alla vector pipeline (InstLatch_A = 0) o alla scalar pipeline (InstLatch_A = 1). Il componente è mostrato nella figura seguente. 7.5.4 L’entità Source Information Processing (SIP) I dati in uscita dal latch istruzioni vengono passati direttamente ad un componente denominato Source Information Processing (SIP): per la precisione, nello schema del Pixel Shader esistono due istanze dell’entità SIP, una per ciascun Instruction Latch, chiamate rispettivamente SIP First e SIP Second. Il componente svolge la funzione di selezionare, fra le 4 dword presenti al suo ingresso, le 2 dword da considerare come sorgenti per il passo corrente dell’istruzione. Useremo la convenzione di indicare i due sorgenti selezionati con le espressioni linea 0 e linea 1. Osserviamo il SIP nella figura posta all’inizio di pagina seguente. Gli ingressi SIP_D, SIP_S0, SIP_S1 e SIP_S2 sono collegati alle quattro uscite dell’Instruction Latch, identificate dai nomi corrispondenti. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 211 Gli ingressi SIP_sel0 e SIP_sel1, ciascuno a 2 bit, servono a selezionare, fra i quattro ingressi citati, quelli associati rispettivamente alla linea 0 e alla linea 1. Il significato del segnale di selezione è definito dalla tabella seguente. SIP_sel linea selezionata 00 SIP_D 01 SIP_S0 10 SIP_S1 11 SIP_S2 Per ciascuna delle due linee, il SIP riporta in uscita le seguenti informazioni: § codifica per il source modifier (4 bit) § codifica per il source swizzler (8 bit) § codifica del registro (tipo e indice) associato alla linea (12 bit). Lo schema della struttura interna del SIP è qui di seguito riportato. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 212 La selezione di ciascuna linea avviene mediante un multiplexer parametrico (width = 32 bit) a quattro ingressi (segnale di selezione a 2 bit). Ciascuno dei blocchi denominati split permette di selezionare, dalla codifica completa del registro a 32 bit, le informazioni da riportare in uscita: modificatore sorgente (bit da 27 a 24), maschera per lo swizzler (bit da 23 a 16), identificativo del tipo di registro (bit da 31 a 28) e indice del registro (bit da 7 a 0). Si riveda al proposito la codifica dei registri sorgenti, nel paragrafo 5.12.4. Nello schema è presente un blocco denominato maskgen, di cui è riportato il codice VHDL. Nel caso in cui l’ingresso SIP_D sia selezionato come sorgente da associare alla linea 0, il maskgen provvede a generare la maschera corretta per il sorgente (bit da 23 a 16). La presenza di tale blocco è resa necessaria dal fatto che la codifica del registro immediatamente seguente l’OpCode è diversa dalla codifica dei registri successivi all’interno della stessa istruzione: in particolare la differenza riguarda i bit che vanno da 23 a 16. Se come sorgente si deve usare il primo registro che segue l’OpCode, è necessario provvedere alla correzione di questi bit: a tale operazione è preposto il maskgen. 7.5.5 SIP Driver Nel paragrafo precedente abbiamo visto che i SIP hanno due ingressi (SIP_sel0 e SIP_sel1) che permettono di selezionare la linea 0 e la linea 1; i componenti che forniscono tali segnali di selezione sono detti SIP Driver. Precisiamo fin d’ora che i SIP Driver First e Second, rispettivamente associati ai SIP First e Second, sono differenti tra loro. Cominciamo con la discussione del SIP Driver First, mostrato nella figura seguente. Il componente ha un’unica uscita (a 2 bit) che va a pilotare l’ingresso di selezione associato alla linea 0 o alla linea 1 del SIP First. Il SIP Driver First riceve in ingresso i segnali di selezione da parte delle tre unità di controllo (SIPDriver_Main, SIPDriver_Vect, SIPDriver_Scal) e sceglie, in base al bit di pairing (SIPDriver_PairingBit) e all’alpha bit (SIPDriver_A), quale dei tre segnali riportare in uscita. La scelta viene fatta secondo la seguente tabella. Pairing Bit Alpha Bit 0 0/1 0 1 1 Significato SIPDriverFirst_Out Istruzione non in paring nell’Instruction Latch First SIPDriver_Main Istruzione scalare nell’Instruction Latch First, istruzione vettoriale nell’Instruction Latch Second Istruzione vettoriale nell’Instruction Latch First, istruzione scalare nell’Instruction Latch Second SIPDriver_Scal SIPDriver_Vect Il codice VHDL che implementa la funzione appena descritta è qui di seguito riportato. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 213 ARCHITECTURE behavioral OF SIPDriver IS BEGIN driver: PROCESS (SIPDriver_A, SIPDriver_Main, SIPDriver_PairingBit, SIPDriver_Scal, SIPDriver_Vect) BEGIN --controlliamo il bit di pairing IF SIPDriver_PairingBit = '0' THEN --no pairing SIPDriver_out <= SIPDriver_Main; ELSE --controlliamo l'alpha bit IF SIPDriver_A = '0' THEN --è la scalar a pilotare il SIP FIRST SIPDriver_out <= SIPDriver_Scal; ELSE SIPDriver_out <= SIPDriver_Vect; END IF; END IF; END PROCESS driver; END behavioral; Facciamo notare che ciascuna delle due linee di selezione del SIP First possiede un proprio SIP Driver First. Ciascuna linea di selezione del SIP Second viene invece pilotata dal SIP Driver Second. Esso deve semplicemente trasmettere in uscita l’informazione proveniente dalla VectCU o dalla ScalCU, a seconda che l’alpha bit assuma rispettivamente il valore 0 o 1. E’ allora evidente che il componente in questione può essere implementato con un multiplexer a due ingressi. Nella figura sottostante mostriamo il SIP Second con i due SIP Driver Second ad esso collegati. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 214 7.5.6 Source Decoder Il Source Decoder (SRCDec) è l’entità che provvede a decodificare le informazioni relative ai registri sorgenti che derivano dal SIP. All’interno del Pixel Shader è presente un Source Decoder per ciascuno dei due SIP. Il componente in questione è mostrato nella figura seguente. I segnali in ingresso SRCDec_InAddr0 e SRCDec_InAddr1, a 12 bit, corrispondono alle uscite SIP_addr0 e SIP_addr1 del SIP: essi contengono le informazioni su tipo e indice dei registri associati alle linee 0 e 1. I segnali in uscita SRCDec_OutAddr0 e SRCDec_OutAddr1, a 3 bit, rappresentano l’indice dei registri sorgenti associati rispettivamente alla linea 0 e alla linea 1. Tali segnali, ottenuti prendendo i 3 bit meno significativi dei corrispondenti indirizzi in ingresso, vengono usati per pilotare gli Address Selector. I rimanenti segnali in uscita sono le abilitazioni in lettura per le prime due linee di ciascuno dei quattro banchi di registri (C, T, V, R). Riassumendo, il Source Decoder, sulla base delle informazioni acquisite dal corrispondente SIP, provvede ad abilitare in lettura un solo registro per ciascuna delle linee 0 e 1. La figura posta all’inizio di pagina seguente, mostrante una porzione della circuiteria interna del Pixel Shader, permette di comprendere come il singolo banco di registri venga pilotato dai due Source Decoder. Si noti la corrispondenza tra le quattro linee d’ingresso del banco di registri e le due coppie di linee d’uscita dei Source Decoder. Può essere utile riepilogare la corrispondenza in una tabella. linee d’ingresso dei registri linea 0 linea 1 linea 2 linea 3 linee d’uscita dei Source Decoder linea 0 del Source Decoder First linea 1 del Source Decoder First linea 0 del Source Decoder Second linea 1 del Source Decoder Second Il codice VHDL che implementa il Source Decoder, essendo particolarmente semplice, non viene qui riportato, ma può essere consultato in appendice. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 215 7.6. Il macroblocco Swizzling & Masking Avendo finora descritto tutta la logica necessaria alla decodifica dei registri sorgenti, riteniamo utile proseguire la trattazione spostando l’attenzione sul macroblocco denominato Swizzling & Masking, preposto alla prima elaborazione dei dati letti dai registri. Il macroblocco in questione è composto dalle entità qui di seguito elencate: § Data Combiner (DC) § Source Swizzler (SS) § Source Masking First (SMFirts) e Source Masking Second (SMSecond). 7.6.1 L’entità Data Combiner (DC) Nel paragrafo 7.3 abbiamo visto che ciascun banco di registri (c, t, v, r) dispone di quattro linee d’uscita, ciascuna a 128 bit: le prime due linee sono associate ai sorgenti 0 e 1 della prima istruzione (memorizzata nell’Instruction Latch First), le altre due linee sono associate ai sorgenti 0 e 1 della seconda Progettazione in VHDL del Pixel Shader v 1.4 Pag. 216 istruzione (memorizzata nell’Instruction Latch Second). Da quanto spiegato nelle pagine precedenti deve essere chiaro che, in ogni istante, il segnale di abilitazione lettura per una linea fissata viene trasmesso solo ad uno dei quattro banchi di registri. Così, ad esempio, se la linea 0 è attivata in lettura per il banco c, deve necessariamente essere disattivata per i banchi t, v, r: questi tre banchi avranno di conseguenza uscite tutte nulle per quella linea. La funzione del Data Combiner è quella di combinare, mediante un OR logico, le uscite dei banchi c, t, v, r relative alla stessa linea. Il componente in questione è mostrato nella figura seguente. Gli ingressi DC_InC, DC_InT, DC_InV, DC_InR, a 128 bit, coincidono con le uscite dei banchi c, t, v, r relative ad una stessa linea. L’uscita DC_Out, a 128 bit, è semplicemente l’OR logico tra i quattro ingressi citati; il segnale viene poi portato al componente che effettua l’operazione di swizzling (Source Swizzler). Naturalmente, nello schematico del Pixel Shader esistono quattro istanze dell’entità DC, una per ciascuna linea. 7.6.2 L’entità Source Swizzler (SS) Nel capitolo dedicato alle specifiche di funzionamento del processore si è visto che i dati letti dai registri sorgenti possono essere sottoposti ad una serie di trasformazioni, la prima delle quali consiste nella replicazione del canale colore specificato: l’entità preposta all’esecuzione di tale operazione (swizzling) è detta Source Swizzler (SS). Essa è mostrata nella figura sottostante. Il Source Swizzler dispone di due ingressi: SS_In, a 128 bit, è il segnale proveniente dal Data Combiner, ovvero coincide con il contenuto del registro sorgente per la linea corrente; SS_mask, a 8 bit, è la maschera utilizzata per la replicazione dei canali, direttamente proveniente dal SIP. Il dato elaborato dal Source Swizzler è portato all’uscita SS_Out, a 128 bit. Sottolineiamo che nello schema del Pixel Shader esistono quattro Source Swizzler, uno per ciascuna linea. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 217 Lo schema interno dell’entità è riportato nella figura seguente. Analizziamo uno ad uno i componenti presenti nello schema circuitale. I componenti contrassegnati dai simboli I0 e I2 sono due istanze dell’entità detta Split, da noi appositamente progettata per separare un generico segnale a 4·n bit in 4 segnali a n bit, pensati come le informazioni associate ai canali R, G, B, A. In particolare, lo split I2 separa il segnale d’ingresso SS_In nei quattro canali colore, ciascuno a 32 bit; lo split I0 divide invece il segnale SS_mask in quattro segnali a 2 bit, ciascuno relativo ad un canale. I quattro componenti in colore azzurro sono i multiplexer parametrici a 32 bit, responsabili dell’operazione di swizzling vera e propria. Il singolo multiplexer, comandato dai due bit di selezione, permette di riportare in uscita una fra le quattro componenti R, G, B, A del segnale d’ingresso. Il componente I1 è un’istanza dell’entità Merge, avente la funzione di ricostruire un segnale a 4·n bit partendo da 4 segnali a n bit. Nel nostro caso il componente ricostruisce il segnale da riportare in uscita partendo dalle informazioni sui quattro canali elaborate dai multiplexer. 7.6.3 Source Masking Le informazioni fornite in uscita dai Source Swizzlers, prima di confluire nei due bus operandi, passano attraverso un blocco filtrante: il Source Masking. Per l’esattezza, all’interno del processore esistono due diverse entità, chiamate Source Masking First e Source Masking Second, che procediamo ad analizzare separatamente. Mostriamo nella figura sottostante l’entità Source Masking First (SMFirst). Progettazione in VHDL del Pixel Shader v 1.4 Pag. 218 Il funzionamento del componente può essere spiegato come segue. L’ingresso SMFirst_In, a 128 bit, proveniente dal Source Swizzler, viene internamente scomposto nei quattro canali colore. In base ai valori del bit di pairing (SMFirst_PairingBit) e del bit alpha (SMFirst_A), l’entità SMFirst decide se far passare in uscita (SMFirst_Out) o tutti e quattro i canali, o solo i canali R, G, B, o solo il canale A. Ai canali che devono essere bloccati in uscita vengono assegnate sequenze di 32 bit pari a ‘0’. Pairing Bit Alpha Bit 0 0/1 1 Significato Istruzione non in paring nell’Instruction Latch First 0 Istruzione scalare nell’Instruction Latch First 1 Istruzione vettoriale nell’Instruction Latch First Canali in Uscita R, G, B, A A R, G, B Lo schema circuitale che consente di implementare il funzionamento specificato nella tabella è mostrato nella figura in alto a pagina seguente. Si noti la presenza dello split, per separare i quattro canali del dato in ingresso e del merge, avente la funzione di ricostruire il segnale in uscita. Nello schema è presente un nuovo componente, contrassegnato dalla sigla “1:n”, detto replicator, la cui funzione è quella di generare un segnale composto da n bit, tutti coincidenti con il valore del bit presente al suo ingresso. Il replicator è impiegato nel circuito per rendere gli ingressi delle porte logiche AND ed OR della stessa dimensione (32 bit). L’entità Source Masking Second (SMSecond) è mostrata nella figura seguente. Come si può notare, gli ingressi e l’uscita sono gli stessi dell’entità Source Masking First. Il funzionamento del componente è tuttavia diverso: la scelta dei canali da riportare in uscita avviene infatti secondo la seguente tabella. Pairing Bit Alpha Bit Significato Canali in Uscita 0 0/1 Nell’Instruction Latch Second non vi sono istruzioni da eseguire - 1 0 Istruzione vettoriale nell’Instruction Latch Second 1 Istruzione scalare nell’Instruction Latch Second R, G, B A Lo schema circuitale del componente è mostrato nella figura in basso a pagina seguente. Progettazione in VHDL del Pixel Shader v 1.4 Source Masking First (schema circuitale) Source Masking Second (schema circuitale) Pag. 219 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 220 7.6.4 Swizzling & Masking: schema completo La figura in basso, tratta dallo schematico del Pixel Shader, mostra lo schema completo del macroblocco Swizzling & Masking. I segnali d’ingresso posti all’estrema sinistra provengono direttamente dai quattro banchi di registri c, t, v, r. I segnali d’uscita all’estrema destra rappresentano i dati che verranno scritti nei due bus operandi. Si osservi la presenza di quattro catene di blocchi, una per ogni linea: ricordiamo che le prime due linee sono relative ai sorgenti 0 e 1 della prima istruzione, le altre due linee sono relative ai sorgenti 0 e 1 della seconda istruzione. Come già spiegato, ciascuna catena è formata dalla cascata dei blocchi Data Combiner, Source Swizzler, Source Masking. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 221 7.7. Il macroblocco Modificatori Sorgenti Nei paragrafi precedenti si è analizzato il flusso dei dati partendo dai registri sorgenti fino ad arrivare ai bus operandi; in questo paragrafo fissiamo l’attenzione sul macroblocco Modificatori Sorgenti: una rete che elabora ulteriormente i dati presi dai bus e fornisce in uscita gli operandi da passare alle ALU. Il macroblocco oggetto di studio è composto da otto componenti identici tra loro, detti semplicemente Modifiers: per ciascuno dei quattro canali R, G, B, A vi è infatti un modificatore per l’operando 0 ed un modificatore per l’operando 1. Il singolo modifier consente di eseguire sul dato presente in ingresso le operazioni di bias, invert, negate, scalex2 e signed scaling. 7.7.1 L’entità Modifier La figura seguente mostra il blocco che elabora il singolo sorgente. Allo scopo di rendere chiaro il significato dei segnali, conviene mostrare subito lo schema interno del Modifier. Analizzando le cinque operazioni di base eseguibili dal modificatore sorgente e le varie possibilità di combinazione, siamo giunti a progettare il Modifier secondo lo schema precedente. Si noti in particolare la presenza di tre blocchi funzionali disposti in cascata: § una unità aritmetica (AU), in grado di eseguire le operazioni di invert (uscita = 1 - ingresso) e bias (uscita = ingresso - 0.5) § un blocco che esegue l’operazione di scalex2 (uscita = 2 * ingresso) § un blocco che esegue l’operazione di negate (uscita = - ingresso). La presenza dei tre multiplexer permette di scegliere se utilizzare il singolo blocco funzionale o di bypassarlo, ovvero di portare in uscita il segnale presente in ingresso senza alterazioni. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 222 Combinando opportunamente i tre blocchi funzionali elencati è possibile gestire tutte le operazioni consentite: ad esempio l’operazione di signed scaling (uscita = 2 * (ingresso – 0.5)) viene eseguita selezionando l’operazione di bias per l’AU, impiegando il blocco scalex2 e bypassando il blocco negate. Il codice per i blocchi scalax2 e negate è mostrato in figura e non necessita di commenti. Riportiamo invece la descrizione in VHDL dell’architettura del componente AU. ARCHITECTURE behavioural OF AU IS BEGIN AU_process : PROCESS --Questa variabile per stimare la complessità dell'operazione VARIABLE ComplexityFactor : INTEGER := 1; --una varibile che tenga conto dei cicli di clock trascorsi --dall'inizio dell'operazione VARIABLE ClockCycles : INTEGER := 0; --attento: sul computo dei cicli ne vanno aggiunti 2 --Gli operandi in formato floating-point VARIABLE Op : REAL; --una variabile temporanea per il risultato dell'operazione --lo memorizzo già convertito in binario VARIABLE TempRes : DWORD; TYPE Internal_State IS ( NormalOperation, Waiting ); VARIABLE AUX_State : Internal_State := NormalOperation; BEGIN --attendo il clock fase 1: --l'alu esegue tutte le operazioni nel fronte di salita del clock WAIT UNTIL AUX_clk1'EVENT AND AUX_clk1 = '1'; --controllo lo stato dell'alux: --se sono in normal operation eseguo l'operazione normalmente --se sono in waiting "attendo che l'operazione abbia termine... --è solo una schematizzazione" --alla fine del waiting assegno i segnali e torno a normal operation --metto tutti i segnali senza ritardo, --supponendo che vengano assorbiti dal clock CASE AUX_State IS WHEN NormalOperation => --controllo se l'alu è abilitata ad operare IF AUX_en = '1' THEN Progettazione in VHDL del Pixel Shader v 1.4 Pag. 223 --azzero i segnali in uscita AUX_done <= '0'; --AFTER delay; --converto i valori da binario a floating-point, --a seconda del tipo di operazioni Op := bits_to_float(AUX_Op); --posso ora elaborare le operazioni CASE AUX_OpCode IS WHEN AUXbias => TempRes := float_to_bits(Op - 0.5); ComplexityFactor := 2; WHEN AUXinvert => TempRes := float_to_bits(1.0 - Op); ComplexityFactor := 2; WHEN OTHERS => ASSERT false REPORT "AUX OpCode non consentito" SEVERITY WARNING; NULL; END CASE; --passo allo stato di waiting AUX_State := Waiting; --azzero anche il contatore dei cicli ClockCycles := 1; END IF; WHEN Waiting => --incremento il contatore fino al raggiungimento dei cicli --richiesti poi assegno i segnali ed esco dal waiting IF ClockCycles = ComplexityFactor THEN AUX_Res <= TempRes; --AFTER delay * ComplexityFactor; AUX_done <= TRANSPORT '1'; --AFTER delay * ComplexityFactor; AUX_State := NormalOperation; ELSE --incremento il numero di cicli ClockCycles := ClockCycles + 1; END IF; END CASE; END PROCESS AU_process; END behavioural; Progettazione in VHDL del Pixel Shader v 1.4 Pag. 224 A questo punto il significato dei segnali del Modifier è molto più chiaro. Analizziamo rapidamente gli ingressi. § Modifier_clk1 e Modifier_clk2: sono i segnali di clock per l’AU § Modifier_AUEn: è il segnale di abilitazione per l’AU § Modifier_AUOp: è l’OpCode dell’AU (0 : bias; 1 : invert) § Modifier_DataIn (32 bit): è il dato da elaborare, preso dal bus operando § Modifier_sel0, Modifier_sel1, Modifier_sel2: sono i segnali di selezione per i tre multiplexer. Analizziamo ora le uscite. § Modifier_AUDone: informa che la AU ha completato l’operazione § Modifier_DataOut (32 bit): dato modificato in uscita, da portare alle ALU. Terminiamo qui la discussione relativa al Modifier. Nel paragrafo 7.10.2 analizzeremo la rete che fornisce al componente studiato i segnali di selezione e l’OpCode. 7.8. Rete di multiplexer associata alle ALU I dati in uscita dal blocco Modificatori Sorgenti vengono portati in ingresso alle quattro ALU, già studiate nel paragrafo 7.2. In realtà, tra i modificatori e le ALU vi è una rete di selezione degli operandi, composta essenzialmente da multiplexer, progettata allo scopo di implementare le istruzioni che richiedono più passi di esecuzione. I dati in uscita dalle ALU, prima di raggiungere i Modificatori Istruzione, attraversano una rete di multiplexer predisposta per eseguire una eventuale replicazione dei canali, operazione richiesta per le istruzioni DP3 e DP4. In questo paragrafo ci occuperemo delle reti di multiplexer che circondano le ALU, spiegando la funzione di ogni singolo componente presente. Lo schema circuitale compreso tra i Modificatori Sorgenti e i Modificatori Istruzione è riportato nelle due figure di pagina 227 e 228. La suddivisione del circuito in due porzioni è necessaria per garantire la leggibilità dello schema; le due figure vanno comunque pensate affiancate verticalmente. Per evitare di complicare inutilmente le figure abbiamo indicato soltanto i segnali d’interesse, tralasciando i componenti e i segnali che non riguardano direttamente le reti di selezione e replicazione oggetto di studio. Come si può notare, i componenti ed i segnali presenti all’interno delle due figure sono mostrati con colori diversi, per mettere in risalto le differenti funzioni da essi svolte. Innanzitutto precisiamo che i segnali posti all’estrema sinistra delle due figure sono le uscite dei Modificatori Sorgenti, mentre i segnali che si trovano all’estrema destra rappresentano gli ingressi dei Modificatori Istruzione. I quattro blocchi indicati in colore azzurro sono le ALU floating-point. Per tali componenti abbiamo riportato soltanto i segnali relativi agli operandi e al risultato, tralasciando tutti gli altri. Gli otto multiplexer a quattro ingressi, indicati in colore verde, fanno Progettazione in VHDL del Pixel Shader v 1.4 Pag. 225 parte della rete di selezione in ingresso. Per ciascuno dei due operandi di ogni ALU un multiplexer provvede a fornire, in ogni passo dell’istruzione, l’operando corretto, scegliendolo tra i quattro possibili ingressi. Se si analizza con attenzione l’instruction-set del Pixel Shader, si noterà infatti che alcune istruzioni richiedono, quali sorgenti da applicare alla singola ALU, non i dati che provengono dai corrispondenti Modificatori Sorgenti, ma dati diversi: questi possono essere i sorgenti associati ad un canale differente, dati letti dall’esterno, oppure i risultati delle ALU al passo precedente. Allo scopo di soddisfare tutte queste esigenze si è provveduto a progettare ad hoc la rete di multiplexer mostrata nelle figure. Notiamo che tutti i multiplexer possono scegliere, come operando da portare all’ALU, sia il dato proveniente dal corrispondente modificatore sorgente, sia il risultato dell’ALU portato in retroazione. Gli ulteriori due ingressi cambiano a seconda del particolare multiplexer considerato. In ogni caso ribadiamo che la scelta fatta è una delle possibili alternative che consente di implementare tutte le istruzioni del processore. A pagina 229 viene riportata la tabella che elenca le impostazioni dei bit di selezione per l’implementazione dell’instruction set del Pixel Shader. Si noterà che l’ingresso di selezione di ciascun multiplexer verde è a sua volta l’uscita di un multiplexer a due ingressi, mostrato in colore giallo. Quest’ultimo ha la funzione di selezionare quale delle unità di controllo comanda il corrispondente multiplexer verde. Per meglio comprendere la questione si fissi l’attenzione sul multiplexer MuxIn_R0, che fornisce il corretto operando 0 all’ALU R. Tale componente può in generale essere comandato da due unità di controllo: § dalla MainCU, per istruzioni non in pairing; in tal caso infatti è l’unità di controllo principale che gestisce la vector e la scalar pipeline § dalla VectCU, per istruzioni in pairing; in tal caso la MainCU delega la VectCU al controllo della vector pipeline e la ScalCU al controllo della Scalar pipeline. In sostanza il multiplexer giallo Sel_R0, sulla base del bit di pairing (indicato in arancione), fa sì che il MuxIn_R0 sia pilotato di volta in volta dall’unità di controllo corretta. Osserviamo che i segnali provenienti direttamente da un’unità di controllo sono indicati con un colore distintivo: § i segnali blu provengono dalla MainCU § i segnali rossi provengono dalla VectCU § i segnali verdi provengono dalla ScalCU. Il componente rappresentato in colore celeste e riportato nella figura sottostante è il BEMLatch. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 226 Si tratta essenzialmente di un latch a 128 bit, abilitato tramite il segnale BEMLatch_en e il cui dato in ingresso è rappresentato dal segnale BEMLatch_DataIn. Le quattro uscite del BEMLatch, contraddistinte dai suffissi M00, M01, M10, M11, non sono altro che il segnale memorizzato dal latch, espresso come quattro dword, dalla meno alla più significativa. Il componente in questione è usato nell’ambito dell’istruzione BEM ed assolve la funzione di memorizzare le quattro costanti caricate dal Modulo BEM, esterno al processore. Una volta memorizzati, tali dati vengono trasmessi alla rete di selezione in ingresso che provvede a recapitarli alle ALU corrette. L’ultimo aspetto da analizzare è la rete di replicazione del canale, posta a valle delle ALU e rappresentata essenzialmente dai multiplexer indicati in colore fucsia. Le istruzioni DP3 e DP4 richiedono di replicare il risultato dell’elaborazione su più canali: a tale scopo, nello schema del Pixel Shader sono stati inseriti i multiplexer MuxOut_R, MuxOut_G, MuxOut_A. Tali componenti permettono di scegliere se passare in uscita il risultato proveniente dalla rispettiva ALU o il risultato dell’ALU B, in cui si trova il risultato finale delle istruzioni citate. I componenti MuxOut_R e MuxOut_G sono a loro volta pilotati da multiplexer a due ingressi di colore giallo, che selezionano quale unità di controllo (la MainCU o la VectCU) comanda il corrispondente multiplexer fucsia. Il multiplexer MulOut_A, essendo sempre pilotato dalla MainCU, non ha un corrispondente multiplexer giallo. A questo punto possiamo ritenere conclusa la descrizione delle reti di multiplexer a monte e a valle delle ALU. Nel prossimo paragrafo continueremo l’analisi del flusso dei dati interno dal processore descrivendo il funzionamento dei Modificatori Istruzione. Progettazione in VHDL del Pixel Shader v 1.4 Reti di selezione e replicazione (canali R e G) Pag. 227 Progettazione in VHDL del Pixel Shader v 1.4 Reti di selezione e replicazione (canali B e A) Pag. 228 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 229 Tabella dei segnali di selezione per la rete di multiplexer ISTRUZIONE: BEM PASSO 1 ALU Operation Linea0 (M) R M00*Src1.r 10 G M11*Src1.g 10 B M10*Src1.g 10 A M01*Src1.r 10 ISTRUZIONE: DP3 PASSO 1 ALU Operation Linea0 (Src0) R Src0.r+Src1.r 00 G Src0.g+Src1.g 00 B Src0.b+Src1.b 00 A ISTRUZIONE: DP4 PASSO 1 ALU Operation Linea0 (Src0) R Src0.r+Src1.r 00 G Src0.g+Src1.g 00 B Src0.b+Src1.b 00 A Src0.a+Src1.a 00 ISTRUZIONE: LRP PASSO 1 ALU Operation Linea0 (Src1) R Src1.r-Src2.r 00 G Src1.g-Src2.g 00 B Src1.b-Src2.b 00 A Src1.a-Src2.a 00 ISTRUZIONE: MAD PASSO 1 ALU Operation Linea0 (Src0) R Src0.r*Src1.r 00 G Src0.g*Src1.g 00 B Src0.b*Src1.b 00 A Src0.a*Src1.a 00 ISTRUZIONE: CMP PASSO 1 ALU Operation Linea0 (Src0) R Src0.r > 0 00 G Src0.g > 0 00 B Src0.b > 0 00 A Src0.a > 0 00 ISTRUZIONE: CND PASSO 1 ALU Operation Linea0 (Src0) R Src0.r > 0.5 00 G Src0.g > 0.5 00 B Src0.b > 0.5 00 A Src0.a > 0.5 00 PASSO 2 PASSO 3 Linea1 ALU Operation Linea0 Linea1 ALU Operation Linea0 Linea1 (Src1) (Src0) 00 R ResR+Src0.r 00 01 R ResR+ResB 11 01 00 G ResG+Src0.g 00 01 G ResG+ResA 01 11 10 B B 10 A A PASSO 2 PASSO 3 Linea1 ALU Operation Linea0 Linea1 ALU Operation Linea0 Linea1 (src1) 00 R R 00 G ResG+ResR 11 01 G 00 B B ResB+ResG 11 01 A A PASSO 2 PASSO 3 Linea1 ALU Operation Linea0 Linea1 ALU Operation Linea0 Linea1 (Src1) 00 R R 00 G ResR+ResG 11 01 G 00 B ResB+ResA 01 11 B ResG+ResB 11 01 00 A A PASSO 2 PASSO 3 Linea1 ALU Operation Linea0 Linea1 ALU Operation Linea0 Linea1 (Src2) (Src0) (Src2) 00 R Src0.r*ResR 00 01 R ResR+Src2.r 01 00 00 G Src0.g*ResG 00 01 G ResG+Src2.g 01 00 00 B Src0.b*ResB 00 01 B ResB+Src2.b 01 00 00 A Src0.a*ResA 00 01 A ResA+Src2.a 01 00 PASSO 2 PASSO 3 Linea1 ALU Operation Linea0 Linea1 ALU Operation Linea0 Linea1 (Src1) (Src2) 00 R ResR+Src2.r 01 00 R 00 G ResG+Src2.g 01 00 G 00 B ResB+Src2.b 01 00 B 00 A ResA+Src2.a 01 00 A PASSO 2 PASSO 3 Linea1 ALU Operation Linea0 Linea1 ALU Operation Linea0 Linea1 (Src1) (Src2) R Pass0 o Pass1 00 00 R G Pass0 o Pass1 00 00 G B Pass0 o Pass1 00 00 B A Pass0 o Pass1 00 00 A PASSO 2 PASSO 3 Linea1 ALU Operation Linea0 Linea1 ALU Operation Linea0 Linea1 (Src1) (Src2) R Pass0 o Pass1 00 00 R G Pass0 o Pass1 00 00 G B Pass0 o Pass1 00 00 B A Pass0 o Pass1 00 00 A Progettazione in VHDL del Pixel Shader v 1.4 ISTRUZIONE: ADD PASSO 1 PASSO 2 ALU Operation Linea0 Linea1 ALU Operation Linea0 (Src0) (Src1) R Src0.r+Src1.r 00 00 R G Src0.g+Src1.g 00 00 G B Src0.b+Src1.b 00 00 B A Src0.a+Src1.a 00 00 A ISTRUZIONE: MUL PASSO 1 PASSO 2 ALU Operation Linea0 Linea1 ALU Operation Linea0 (Src0) (Src1) R Src0.r*Src1.r 00 00 R G Src0.g*Src1.g 00 00 G B Src0.b*Src1.b 00 00 B A Src0.a*Src1.a 00 00 A ISTRUZIONE: SUB PASSO 1 PASSO 2 ALU Operation Linea0 Linea1 ALU Operation Linea0 (Src0) (Src1) R Src0.r-Src1.r 00 00 R G Src0.g-Src1.g 00 00 G B Src0.b-Src1.b 00 00 B A Src0.a-Src1.a 00 00 A ISTRUZIONE: MOV PASSO 1 PASSO 2 ALU Operation Linea0 Linea1 ALU Operation Linea0 (Src0) R Pass0 00 R G Pass0 00 G B Pass0 00 B A Pass0 00 A ISTRUZIONE: TEXCRD (con modificatore _db / _da) PASSO 1 PASSO 2 ALU Operation Linea0 Linea1 ALU Operation Linea0 (Src0) R Src0.r/Src0.b 00 10 R G Src0.g/Src0.b 00 10 G B B A A ISTRUZIONE: TEXLD (con modificatore _db / _da) PASSO 1 PASSO 2 ALU Operation Linea0 Linea1 ALU Operation Linea0 (Src0) R Src0.r/Src0.b 00 10 R G Srco.g/Src0.b 00 10 G B B A A ISTRUZIONE: TEXDEPTH PASSO 1 PASSO 2 ALU Operation Linea0 Linea1 ALU Operation Linea0 (Dest) R Dest.r/Dest.g 00 11 R G G B B A A Pag. 230 PASSO 3 Linea1 ALU Operation Linea0 Linea1 R G B A PASSO 3 Linea1 ALU Operation Linea0 Linea1 R G B A PASSO 3 Linea1 ALU Operation Linea0 Linea1 R G B A PASSO 3 Linea1 ALU Operation Linea0 Linea1 R G B A PASSO 3 Linea1 ALU Operation Linea0 Linea1 R G B A PASSO 3 Linea1 ALU Operation Linea0 Linea1 R G B A PASSO 3 Linea1 ALU Operation Linea0 Linea1 R G B A Progettazione in VHDL del Pixel Shader v 1.4 Pag. 231 ISTRUZIONE: TEXKILL PASSO 1 PASSO 2 PASSO 3 ALU Operation Linea0 Linea1 ALU Operation Linea0 Linea1 ALU Operation Linea0 Linea1 (Dest) R Dest.r > 0 00 R R G Dest.g > 0 00 G G B Dest.b > 0 00 B B A A A Illustrazione significato colonne: • • • ALU: indica l’ALU a cui si fa riferimento Operation: indica l’operazione da settare nell’ALU Linea0: indica come settare la rete di multiplexer relativamente all’operando 0; se necessario, tra parentesi “(…)” viene indicato quale operando deve essere portato sul’Op0Bus impostando correttamente gli ingressi di selezione dei SIP • Linea1: indica come settare la rete di multiplexer relativamente all’operando 1; se necessario, tra parentesi “(…)” viene indicato quale operando deve essere portato sul’Op1Bus impostando correttamente gli ingressi di selezione dei SIP. Nota: con il termine ResX si fa riferimento al risultato della più recente elaborazione effettuata dall’ALUX. Nota: le istruzioni TEXLD e TEXCRD prive di modificatore vengono implementate sfruttando un demultiplexer (denominato TexLdDemux) posto all’uscita della catena Swizzling & Masking per connettere il dato direttamente all’Op0Bus, in quanto non si necessita di alcuna elaborazione. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 232 7.9. Il macroblocco Modificatori Istruzione Il macroblocco Modificatori Istruzione permette di modificare il risultato di un’istruzione prima che il dato finale venga scritto sul registro destinazione. Esso è composto da quattro componenti detti Instruction Modifier (IM), aventi la funzione di applicare, al risultato di ciascuno dei canali R, G, B, A, i modificatori x2, x4, x8, d2, d4, d8 e sat, descritti nel paragrafo 5.8. 7.9.1 L’entità Instruction Modifier (IM) Il componente che implementa il modificatore istruzione per il singolo canale colore è mostrato nella figura seguente. Analizziamo i segnali d’ingresso. § IM_DataIn (32 bit): il dato in ingresso, proveniente dall’ALU dello stesso canale § IM_Exp (4 bit): la codifica delle operazioni di moltiplicazione e divisione per 2, 4, 8 (sono i bit da 27 a 24 del destination register) § IM_Sat: il bit di saturazione (bit 20 del destination register). Il componente IM dispone di un’unica uscita. § IM_DataOut (32 bit): il dato elaborato. Riportiamo qui di seguito la descrizione in VHDL dell’architettura dell’entità IM. ARCHITECTURE behavioural OF IM IS BEGIN im_process : PROCESS (IM_DataIn, IM_Exp, IM_Sat) VARIABLE TempRes : REAL; BEGIN --converto in float TempRes := bits_to_float(IM_DataIn); --considero la moltiplicazione o divisione CASE IM_Exp IS WHEN "0001" => --_x2 TempRes := TempRes * 2.0; WHEN "0010" => --_x4 TempRes := TempRes * 4.0; Progettazione in VHDL del Pixel Shader v 1.4 Pag. 233 WHEN "0011" => --_x8 TempRes := TempRes * 8.0; WHEN "1111" => --_d2 TempRes := TempRes / 2.0; WHEN "1110" => --_d4 TempRes := TempRes / 4.0; WHEN "1101" => --_d8 TempRes := TempRes / 8.0; WHEN OTHERS => --non permesso:.non devo fare nulla END CASE; --considero l'eventuale saturazione IF IM_Sat = '1' THEN --devo "saturare": fare il clamping tra 0 e 1 IF TempRes < 0.0 THEN TempRes := 0.0; ELSIF TempRes > 1.0 THEN TempRes := 1.0; END IF; END IF; --assegno il risultato IM_DataOut <= float_to_bits(TempRes); END PROCESS im_process; END behavioural; Analizzando il codice riportato e considerando la semplicità delle operazioni coinvolte, il componente IM si presta ad essere implementato mediante una cascata di due reti combinatorie: la prima responsabile delle operazioni di moltiplicazione e divisione, la seconda responsabile dell’operazione di saturazione. 7.10. Il macroblocco Controllo Registri e Decodifica (2° parte) In questo paragrafo terminiamo la descrizione dei componenti presenti all’interno del macroblocco Controllo Registri e Decodifica. Le entità prese in considerazione sono le seguenti: § Source Modifier Deliverer (SMD) § Source Modifier Decoder (SMDec) § Instruction Modifier Deliverer (IMD) § Destination Address Deliverer (DAD) Progettazione in VHDL del Pixel Shader v 1.4 § Pag. 234 Destination Mask Generator (DMG) 7.10.1 L’entità Source Modifier Deliverer (SMD) L’entità oggetto del presente paragrafo ha la funzione di ridistribuire le informazioni relative ai modificatori sorgenti: essa riceve in ingresso dai due SIP (First e Second) le codifiche del modificatore sorgente (canale 0 o 1) e, in base al bit di pairing e all’alpha bit, stabilisce quali sono le informazioni destinate alla vector pipeline e quali alla scalar pipeline. Osservazione. Ogni componente che all’interno del Pixel Shader prende informazioni dai due SIP e le ripartisce tra vector e scalar pipeline viene identificato dal termine Deliverer. Oltre all’entità SMD, nelle prossime pagine saranno analizzati gli altri blocchi deliverer. Riportiamo nella figura sottostante il componente SMD. I segnali d’ingresso SMD_FirstModif e SMD_SecondModif, entrambi a 4 bit, sono le codifiche del modificatore sorgente (linea 0 o 1) ricevute rispettivamente dal SIP First e dal SIP Second; si tratta in particolare dei bit che vanno dal 27 al 24 della codifica del registro sorgente. Il componente, sulla base del bit di pairing (SMD_PairingBit) e del bit alpha (SMD_A), stabilisce quali sono le codifiche del modificatore sorgente da inviare alla vector pipeline (segnale d’uscita SMD_VectModif) e alla scalar pipeline (segnale d’uscita SMD_ScalModif). Riportiamo in una tabella la corrispondenza in questione. Pairing Bit Alpha Bit 0 0/1 1 SMD_VectModif SMD_ScalModif SMD_FirstModif SMD_FirstModif 0 SMD_SecondModif SMD_FirstModif 1 SMD_FirstModif SMD_SecondModif L’entità SMD è stata implementata direttamente a livello strutturale; nella figura a pagina seguente se ne riporta lo schema circuitale. Per concludere osserviamo che all’interno del Pixel Shader vi sono due SMD: uno per il sorgente 0 e uno per il sorgente 1 del passo corrente. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 235 7.10.2 L’entità Source Modifier Decoder (SMDec) L’entità SMDec svolge la funzione di decodificare il modificatore sorgente ed inviare le informazioni così ottenute ai blocchi Modifier. Il componente in questione è mostrato nella figura seguente. Il segnale d’ingresso SMDec_modif, a 4 bit, è la codifica del modificatore sorgente, ricevuta dal blocco Source Modifier Deliverer. I segnali d’uscita hanno il seguente significato: § SMDec_AUOp è la codifica dell’operazione (bias o invert) da far eseguire all’unità aritmetica (AU) presente all’interno del Modifier § SMDec_sel0, SMDec_sel1, SMDec_sel2 sono gli ingressi di selezione dei tre multiplexer presenti all’interno del Modifier. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 236 Il componente è stato descritto mediante tabella di verità, a partire dalla quale il software HDL Designer permette la generazione automatica della corrispondente descrizione architetturale in VHDL. All’interno del Pixel Shader esistono quattro istanze dell’entità SMDec: § SourceModifDec_0_Vect: decodifica le informazioni destinate al canale 0 della vector pipeline § SourceModifDec_0_Scal: decodifica le informazioni destinate al canale 0 della scalar pipeline § SourceModifDec_1_Vect: decodifica le informazioni destinate al canale 1 della vector pipeline § SourceModifDec_1_Scal: decodifica le informazioni destinate al canale 1 della scalar pipeline. La figura seguente mostra una porzione dello schema circuitale del Pixel Shader nella quale si nota l’interconnessione tra i Source Modifier Deliverer, che ricevono i segnali dai SIP, e i Source Modifier Decoder, che forniscono in uscita le informazioni da portare ai Modifier. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 237 7.10.3 L’entità Instruction Modifier Deliverer (IMD) L’entità IMD ha la funzione di distribuire agli Instruction Modifier (IM) della vector e della scalar pipeline le codifiche dei modificatori istruzione ricevute dagli Instruction Latch First e Second. Analizziamo i segnali d’ingresso. § IMD_ExpFirst e IMD_ExpSecond (4 bit): sono le codifiche del modificatore moltiplicazione o divisione (bit da 27 a 24 del destination register); i due segnali sono ricevuti, nell’ordine, dall’Instruction Latch First e dall’Instruction Latch Second § IMD_SatFirst e IMD_SatSecond: sono i bit di saturazione ricevuti rispettivamente dall’Instruction Latch First e dall’Instruction Latch Second § IMD_PairingBit e IMD_A: sono rispettivamente il bit di pairing e il bit alpha. Analizziamo i segnali d’uscita. § IMD_VectExp e IMD_ScalExp (4 bit): sono le codifiche del modificatore moltiplicazione o divisione destinate rispettivamente agli Instruction Modifier della vector e della scalar pipeline § IMD_VectSat e IMD_ScalSat: sono i bit di saturazione destinati rispettivamente agli Instruction Modifier della vector e della scalar pipeline. Per la descrizione VHDL del componente IMD si rimanda all’appendice. 7.10.4 L’entità Destination Address Deliverer (DAD) L’entità Destination Address Deliverer gestisce gli indirizzi di scrittura del banco di registri r, in cui viene memorizzato il risultato finale dell’istruzione. Il componente DAD è mostrato nella figura seguente. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 238 Analizziamo i segnali d’ingresso. § DAD_InFirst (8 bit): è l’indice del registro destinazione memorizzato nell’Instruction Latch First § DAD_InSecond (8 bit): è l’indice del registro destinazione memorizzato nell’Instruction Latch Second § DAD_PairingBit: è il bit di pairing § DAD_A: è il bit alpha. Analizziamo i segnali d’uscita. § DAD_VectAddr (3 bit): è l’indice del registro destinazione per il risultato della vector pipeline (canali R, G, B) § DAD_ScalAddr (3 bit): è l’indice del registro destinazione per il risultato della scalar pipeline (canale A). La funzione del DAD è quella di distribuire all’Address Selector associato al banco r gli indirizzi di scrittura dei risultati della vector pipeline e della scalar pipeline. Il DAD riceve gli indirizzi destinazione dai due Instruction Latch e li smista agli ingressi WVect e WScal del componente AddrSel_R in base al bit di pairing e al bit alpha. Il componente funziona allo stesso modo degli altri deliverer analizzati finora; per la descrizione VHDL si rimanda all’appendice. 7.10.5 L’entità Destination Mask Generator (DMG) Il Destination Mask Generator ha la funzione di abilitare in scrittura i singoli canali R, G, B, A del banco di registri r. Il componente è riportato nella figura seguente. Analizziamo gli ingressi. § DMG_InFirst (4 bit): è la destination register write mask memorizzata nell’Instruction Latch First § DMG_InSecond (4 bit): è la destination register write mask memorizzata nell’Instruction Latch Second § DMG_PairingBit: è il bit di pairing § DMG_A: è il bit alpha § DMG_WriteEn: è il segnale di abilitazione scrittura, proveniente dalla MainCU. Analizziamo le uscite. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 239 § DMG_WriteR: segnale di abilitazione scrittura per il canale R del registro destinazione § DMG_WriteG: segnale di abilitazione scrittura per il canale G del registro destinazione § DMG_WriteB: segnale di abilitazione scrittura per il canale B del registro destinazione § DMG_WriteA: segnale di abilitazione scrittura per il canale A del registro destinazione. Studiamo ora il funzionamento del componente. Fino a che il segnale di WriteEn rimane basso, tutte le uscite sono poste a ‘0’. Quando il segnale di WriteEn diventa alto, il DMG provvede ad abilitare la scrittura dei canali corretti del registro destinazione. Da questo punto di vista, il DMG si comporta come un deliverer, in quanto provvede a ripartire le write mask provenienti dai due Instruction Latch sulla base del bit di pairing e del bit alpha. Per maggiore chiarezza riportiamo la descrizione VHDL dell’architettura del componente. ARCHITECTURE behavioural OF DMG IS BEGIN dmg_process : PROCESS (DMG_WriteEn) BEGIN IF DMG_WriteEn = '1' THEN --se è attiva la scrittura posso procedere ad --indirizzare le maschere (che sono anche i --segnali di attivazione della scrittura per i --registri destinazione) IF DMG_PairingBit = '0' THEN --devo portare la maschera --dell'instrLatchFirst DMG_WriteA <= DMG_InFirst(3); DMG_WriteB <= DMG_InFirst(2); DMG_WriteG <= DMG_InFirst(1); DMG_WriteR <= DMG_InFirst(0); ELSE --devo considerare l'alpha bit IF DMG_A = '1' THEN --le istruzioni per la scalar pipeline --sono sull'instrLatchSecond DMG_WriteA <= DMG_InSecond(3); DMG_WriteB <= DMG_InFirst(2); DMG_WriteG <= DMG_InFirst(1); DMG_WriteR <= DMG_InFirst(0); ELSE --le istruzioni per la scalar sono --sull'instrLatchFirst Progettazione in VHDL del Pixel Shader v 1.4 Pag. 240 DMG_WriteA <= DMG_InFirst(3); DMG_WriteB <= DMG_InSecond(2); DMG_WriteG <= DMG_InSecond(1); DMG_WriteR <= DMG_InSecond(0); END IF; END IF; ELSE --se la scrittura non è attiva, imposto tutte le --uscite a ‘0’ DMG_WriteA <= '0'; DMG_WriteB <= '0'; DMG_WriteG <= '0'; DMG_WriteR <= '0'; END IF; END PROCESS dmg_process; END behavioural; 7.11. I bus Come già accennato all’inizio del capitolo, all’interno del Pixel Shader sono presenti tre diversi bus: § Op0Bus e Op1Bus: sono bus a 128 bit destinati a trasferire rispettivamente l’operando 0 e l’operando 1 dai Source Masking ai Modifier § DataBus: è un bus dati generico, a 128 bit; viene usato per trasferire i risultati delle istruzioni e per lo scambio dei dati con i blocchi esterni al processore (Test Unit, Texture Sampler, BEM Module). 7.11.1 Il tipo BIT_BUS I bus presenti all’interno del processore sono stati implementati in VHDL attraverso la definizione del tipo BIT_BUS: si tratta essenzialmente di un array di bit il cui contenuto si ottiene in ogni istante combinando mediante un OR logico tutti gli array di bit che confluiscono nel bus stesso. Per una maggiore chiarezza riportiamo la porzione di codice VHDL che definisce il tipo BIT_BUS, estratta dal package PS_Types. PACKAGE PS_Types IS --funzione di risoluzione function oring (driver : in bit_vector ) return BIT; --definisco un tipo di dato che sfrutti --la funzione di risoluzione subtype ored_BIT is oring BIT; --definisco un nuovo tipo che poi --sarà vadido per i bus Progettazione in VHDL del Pixel Shader v 1.4 Pag. 241 type ored_BIT_VECTOR is array (integer range <>) of ored_BIT; --un trucco barbarico per avere un alias sul nome di --tipo subtype BIT_BUS is ored_BIT_VECTOR; END PS_Types; PACKAGE BODY PS_Types IS function oring(driver : in bit_vector) return BIT is --la funzione di risoluzione è utilizzata quando --più dati debbono confluire nello stesso bus variable result : BIT := '0'; begin for Index in driver'range loop result := result or driver(Index); end loop; return result; end oring; END PS_Types; 7.11.2 Le entità BusConnector Come detto poc’anzi, ciascuno dei tre bus è definito tramite il tipo BIT_BUS. Siccome i segnali che devono confluire nel bus sono di tipo bit_vector, è necessario un elemento che funga da interfaccia tra i due tipi: a tale scopo è stata introdotta un’apposita entità, denominata BusConnector_bv_to_bb_1Way. L’entità in questione è descritta tramite il seguente codice VHDL. ENTITY BusConnector_bv_to_bb_1Way IS GENERIC( Width : integer := 1 ); PORT( BIT_BUS_side : OUT BIT_BUS (Width-1 DOWNTO 0) BUS; bitvector_side : IN bit_vector (Width-1 DOWNTO 0) ); END BusConnector_bv_to_bb_1Way ; ARCHITECTURE behavioral OF BusConnector_bv_to_bb_1Way IS BEGIN --effettuo le necessarie conversioni --potrei invece fornire un segnale per abilitare o --meno la connessione al bus inserendo una "guardia" --adeguata per il blocco always : BLOCK (true) Progettazione in VHDL del Pixel Shader v 1.4 Pag. 242 BEGIN BIT_BUS_side <= GUARDED BIT_BUS(bitvector_side); END BLOCK always; END behavioral; In maniera analoga a quanto visto sopra, i segnali in uscita dal bus sono destinati a componenti che accettano in ingresso dati di tipo bit_vector. Allo scopo di effettuare la conversione da BIT_BUS a bit_vector è stata introdotta l’entità denominata BusConnector_bb_to_bv_1Way, della quale si riporta la descrizione in VHDL. ENTITY BusConnector_bb_to_bv_1Way IS GENERIC( Width : integer := 1 ); PORT( BIT_BUS_side : IN BIT_BUS (Width-1 DOWNTO 0); bitvector_side : OUT bit_vector (Width-1 DOWNTO 0) ); END BusConnector_bb_to_bv_1Way ; ARCHITECTURE behavioral OF BusConnector_bb_to_bv_1Way IS BEGIN --effettuo le necessarie conversioni bitvector_side <= bit_vector(BIT_BUS_side); END behavioral; All’interno dello schema circuitale del Pixel Shader, i due BusConnector vengono rappresentati con lo stesso simbolo, riportato nella figura a lato. Si osservi che questi componenti, così come gli splitter ed i merge, non hanno un corrispondente dispositivo fisico, ma sono introdotti allo scopo di semplificare la connessione tra differenti elementi del circuito. 7.11.3 Il componente Switch Tutti i componenti interni al processore sono stati basati su una logica a due soli valori: 0 logico e 1 logico. Il fatto di non poter utilizzare ulteriori valori per i segnali, come ad esempio lo stato di alta impedenza, impone di usare delle accortezze. Particolare attenzione deve essere posta a tutti i segnali che confluiscono nei bus: ricordando che la funzione di risoluzione impiegata è basata sull’OR logico dei segnali, le linee che devono essere “scollegate” dal bus vanno tutte settate a ‘0’. Fissiamo l’attenzione sui bus operandi. I componenti che immettono dati su tali bus sono i Source Masking che, come spiegato nel paragrafo 7.6.3, provvedono ad azzerare tutti i canali colore che non vanno scritti sul bus. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 243 Più problematico è il caso del DataBus, usato, oltre che dal Pixel Shader, anche dai blocchi Test Unit, Texture Sampler e BEM Module. Ciascuno di questi tre blocchi esterni è stato progettato in maniera da liberare il DataBus una volta terminato l’utilizzo. Anche il Pixel Shader deve fare la medesima cosa quando il bus dati serve ad un blocco esterno: in particolare, allo scopo di “scollegare” gli Instruction Modifier dal DataBus viene impiegato un componente detto Switch. La figura seguente mostra lo Switch inserito all’interno dello schematico del Pixel Shader. I quattro ingressi dello Switch indicati in colore nero sono i segnali, a 32 bit, provenienti dagli Instruction Modifier. Il segnale d’uscita, a 128 bit, è collegato, tramite un BusConnector, al bus dati (PS_Data). Lo Switch dispone di un ulteriore ingresso, indicato in blu, proveniente dalla MainCU. Quando tale segnale di controllo è alto, il componente riporta in uscita le quattro DWORD presenti al suo ingresso, accorpandole in un unico segnale a 128 bit. Se l’ingresso di controllo è basso, lo Switch stacca gli Instruction Modifier dal bus dati, settando tutti i bit dell’uscita a ‘0’. 7.12. Ulteriori componenti del Pixel Shader Prima di passare all’analisi dell’Unità di Controllo, riteniamo utile elencare gli altri componenti minori presenti all’interno dello schema del processore. 7.12.1 TexldDemux Il componente TexldDemux è un demultiplexer inserito all’uscita del blocco di Source Masking SM_0_First. La funzione di tale componente è quella di permettere di selezionare il bus destinazione del dato in uscita: § se l’ingresso di selezione del demuliplexer è posto a ‘0’, il dato proveniente dal Source Masking viene mandato al bus dell’operando 0 (Op0Bus); tale situazione è quella di normale funzionamento § se l’ingresso di selezione del demultiplexer è posto a ‘1’, il dato proveniente dal Source Masking viene mandato al bus dati (DataBus). Ciò risulta utile nel caso di istruzione TEXLD senza Progettazione in VHDL del Pixel Shader v 1.4 Pag. 244 modificatore: in tale situazione il dato letto dal registro sorgente può essere messo direttamente sul DataBus per l’invio al Texture Sampler, senza la necessità di passare per i modificatori sorgenti, le ALU e i modificatori istruzione. La figura sottostante mostra un particolare dello schema del processore in cui si notano il TexldDemux, il blocco di Source Masking e i bus operandi (linee verticali all’estrema destra). 7.12.2 Multiplexer per la selezione dell’Unità di Controllo Come si è già avuto modo di dire, la vector pipeline e la scalar pipeline possono essere pilotate, a seconda dei casi, da differenti unità di controllo: § la vector pipeline è comandata dalla MainCU nel caso di istruzione singola; in presenza di pairing essa riceve i comandi dalla VectCU § la scalar pipeline è comandata dalla MainCU nel caso di istruzione singola; in presenza di pairing essa riceve i comandi dalla ScalCU. Dato che i componenti interni alle pipeline (Modifier e ALU) possono ricevere i comandi da due unità di controllo, si rende necessario l’uso di selettori che, a seconda della particolare situazione, facciano passare i segnali di questa o di quella unità. I selettori in questione non sono altro che dei multiplexer, comandati dal bit di pairing. La figura sottostante mostra, ad esempio, l’ALU_R, evidenziando che i segnale di abilitazione e l’OpCode possono provenire dalla MainCU o dalla VectCU. I multiplexer per la selezione della corretta unità di controllo sono i componenti R_en e R_op. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 245 7.12.3 ExitLatch Tra le uscite del Pixel Shader ve ne sono alcune che vanno settate al termine dell’esecuzione dello shader per il pixel corrente e devono rimanere inalterate durante l’esecuzione dello shader per il pixel successivo. Tali segnali sono i seguenti: PS_R0 (128 bit), PS_R5R (32 bit), PS_DepthEn (1 bit), PS_PixelKill (1 bit). Allo scopo di soddisfare tale requisito, i quattro segnali d’uscita sono memorizzati in un opportuno latch, detto per l’appunto ExitLatch. Si tratta di una memoria a 162 bit abilitata dal segnale ExitLatch_En, proveniente dalla MainCU. 7.12.4 Ripper Nello schema del Pixel Shader è presente un componente molto utilizzato, detto ripper. Si tratta di un elemento che riporta in uscita un sottoinsieme delle linee di bit presenti al suo ingresso. Esso viene usato ogni qualvolta si debba prelevare, da un segnale di tipo bit_vector, una o più linee di bit tra loro contigue. La figura sottostante mostra un esempio di utilizzo del ripper: dal segnale in uscita dall’Instruction Pool, a 160 bit, viene estratta la DWORD più significativa (bit da 159 a 128), destinata alla MainCU. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 246 7.13. Unità di controllo Il presente paragrafo è dedicato alla descrizione dell’unità di controllo del Pixel Shader. Esso è strutturato in tre sottoparagrafi nei quali saranno analizzate, nell’ordine, la MainCU, la VectCU e la ScalCU. 7.13.1 MainCU Il componente MainCU è visibile nella figura seguente. I segnali d’ingresso e di uscita sono elencati qui di seguito sotto forma di tabelle, nelle quali si riporta il numero di bit di ogni segnale ed una breve descrizione. Nella tabella dei segnali d’ingresso è presente una ulteriore colonna, denominata “from”, nella quale è indicato il componente da cui proviene il segnale. Tabella dei segnali di ingresso della MainCU segnale n. descrizione from VectAU0_info VectAU1_info ScalAU0_info ScalAU1_info VectAU0_done VectAU1_done ScalAU0_done ScalAU1_done OpCode 1 1 1 1 1 1 1 1 SMDec SMDec SMDec SMDec Modifier Modifier Modifier Modifier InstrPool TextureSourceModif PSMode WriteEn Clk1 Clk2 SamplerDone BEMDone ALUR_done ALUG_done ALUB_done ALUA_done ALUR_CompBit ALUG_CompBit ALUB_CompBit 32 4 2 1 1 1 1 1 1 1 1 1 1 1 1 Informano la MainCU se abilitare o meno le AU (sono le linee s0 in uscita dai SMDec) Certificano che le AU hanno terminato le operazioni OpCode delle istruzioni (First, Second: memorizzati in due latch interni alla MainCU) Codifica dei modificatori (_da, _db) per le istruzioni di texture addressing (texcrd, texld) Modo corrente del pixel shader Abilita il pixel shader alla scrittura clock fase 1 clock fase 2 Indica il termine dell’elaborazione da parte del sampler Indica il termine dell’elaborazione da parte del BEM module Indica il termine dell’elaborazione da parte dell’ALUR Indica il termine dell’elaborazione da parte dell’ALUG Indica il termine dell’elaborazione da parte dell’ALUB Indica il termine dell’elaborazione da parte dell’ALUA Risultato della comparazione Risultato della comparazione Risultato della comparazione SIPFirst External External External External External External ALUR ALUG ALUB ALUA ALUR ALUG ALUB Progettazione in VHDL del Pixel Shader v 1.4 ALUA_CompBit VectCU_done ScalCU_done AlphaBit_In 1 1 1 1 Risultato della comparazione Indica il termine dell’istruzione sulla Vector Pipeline Indica il termine dell’istruzione sulla Scalar Pipeline AlphaBit in ingresso Pag. 247 ALUA VectCU ScalCU InstrLatchSecond Tabella dei segnali di uscita della MainCU segnale n. descrizione VectAU0_en VectAU1_en ScalAU0_en ScalAU1_en PC_reset PC_en PCIncr_num PCIncr_en InstrLatchFirst_en InstLatchSecond_en PairingBit SIPFirst_sel0 SIPFirst_sel1 DMG_WriteEn BEMLatch_en TexldDemux VectOpCode ScalOpCode ALUR_en ALUG_en ALUB_en ALUA_en ALUR_OpCode ALUG_OpCode ALUB_OpCode ALUA_OpCode Switch SamplerEn BEMEn VectCU_en ScalCU_en MuxInR0_sel MuxInR1_sel MuxInG0_sel MuxInG1_sel MuxInB0_sel MuxInB1_sel MuxInA0_sel MuxInA1_sel MuxOutR_sel MuxOutG_sel MuxOutA_sel WRsel PixelKill DepthEn AlphaBit PSDone InstrPool_ReadEn ExitLatch_En 1 1 1 1 1 1 3 1 1 1 1 2 2 1 1 1 16 16 1 1 1 1 3 3 3 3 1 1 1 1 1 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 Abilitazione per AU: vanno inviati alle AU dopo che sui due bus operando si trova il dato valido Program counter reset Program counter enable Codifica binaria dell’incremento del program counter PC Increment enable Segnali di abilitazione per i latch istruzione Indica la presenza di una istruzione in pairing Selezionano gli operandi (con relative maschere e modificatori) da associare alle linee 0 ed 1 in uscita dai SIP Abilita il destination mask generator per la scrittura del risultato sul registro destinazione Abilita il BEMLatch Segnale uguale a 1 solo per istruzione TEXLD senza modificatore OpCode da assegnare alla VectCU (caso di Instruction Pairing) OpCode da assegnare alla ScalCU (caso di Instruction Pairing) Abilita l’ALUR Abilita l’ALUG Abilita l’ALUB Abilita l’ALUA OpCode per l’ALUR OpCode per l’ALUG OpCode per l’ALUB OpCode per l’ALUA Abilita o disabilita la scrittura del dato elaborato sul DataBus Abilita il Texture Sampler Abilita il Modulo BEM Abilitazione VectCU Abilitazione ScalCU Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione indirizzo lettura/scrittura sui registri Se alto, annulla il rendering del pixel corrente Se alto, forza l’utilizzo di R5.r per il depth test Alpha bit Indica il termine dell’operazione di scrittura o dell’elaborazione Abilitazione scrittura dell’Instruction Pool Abilitazione ExitLatch Progettazione in VHDL del Pixel Shader v 1.4 Pag. 248 La MainCU è stata descritta come una macchina a stati finiti. Per ragguagli sulla descrizione di una Finite State Machine (FSM) all’interno dell’ambiente HDL Designer si riveda il Capitolo 6. Lo schema completo della FSM è riportato nella figura seguente. Prima di procedere alla descrizione dei singoli stati, riteniamo opportuno presentare la sezione dichiarativa dell’architettura, in modo da evidenziare le variabili interne definite ed esplicitarne il significato. --Architecture Declarations --simulano i 2 latch interni per conservare gli opcode SHARED VARIABLE OpCodeFirst32 : BIT_VECTOR(31 DOWNTO 0); SHARED VARIABLE OpCodeSecond32 : BIT_VECTOR(31 DOWNTO 0); ALIAS OpCodeFirst16 : WORD IS OpCodeFirst32(15 DOWNTO 0); ALIAS OpCodeSecond16 : WORD IS OpCodeSecond32(15 DOWNTO 0); -- una variabile per tener traccia dell'AlphaBit SHARED VARIABLE ABit : BIT; --una variabile per far sì che il processo venga eseguito --una sola volta al colpo di clock SHARED VARIABLE FirstExecution1 : BIT; SHARED VARIABLE FirstExecution2 : BIT; --file per il tracing della maincu FILE MCUFile : ASCII_TEXT OPEN WRITE_MODE IS PATH & MainCUTrace.TXT"; WaitingCommand L’esecuzione inizia con lo stato WaitingCommand (marcato come StartUpState, evidenziato dalla colorazione in verde), di cui si riporta il codice associato: --tracing REPORT "MainCU - WaitingCommand" SEVERITY note; WriteString("MainCU - WaitingCommand", MCUFile); Switch <= '0'; TexldDemux <= '0'; BEMEn <= '0'; SamplerEn <= '0'; PSDone <= '0'; Progettazione in VHDL del Pixel Shader v 1.4 Pag. 249 Dapprima si esegue un report su finestra di debug del simulatore per indicare l’attuale stato in esecuzione; lo stesso report viene anche effettuato su di un file di testo (per semplicità di consultazione). Successivamente si esegue il reset di alcuni segnali di controllo. WaitingInput Lo stato successivo in ordine logico è quello di WaitingInput, che viene raggiunto quando il segnale PSMode passa a “01” o “10”. Oltre al consueto report, in questo stato vengono settati i segnali PSDone e WRsel. Come indicato dal nome, questo è uno stato di attesa: si esce da WaitingInput solo quando si ha una richiesta di scrittura dall’esterno (WriteEn = ‘1’) oppure quando il segnale PSMode passa a “00” o “11”. REPORT "MainCU - WaitingInput" SEVERITY note; WriteString("MainCU - WaitingInput", MCUFile); --abbasso il PSDone, sono in attesa di dati PSDone <= '0'; --imposto la modalità di indirizzamento per la scrittura WRsel <= '1'; --Attendo degli eventi sui segnali PSMode e WriteEn WritingDelay Qualora nello stato di WaitingInput si riceva una richiesta di scrittura, si ha il passaggio allo stato di WritingDelay. Quest’ultimo è in realtà uno stato gerarchico, la cui struttura interna è riportata nella figura seguente. Come si osserva, WritingDelay è composto da due stati in cascata, Wait1 e Wait2, i quali non fanno altro che eseguire un report. La funzione di questo stato è quella di attendere l’avvenuta scrittura nel registro specificato o nell’Instruction Pool61. Dallo stato Wait2, al colpo di clock successivo si dà conferma del completamento della scrittura e si ritorna allo stato di WaitingInput. Execute 61 L’ultimo stato che compare nello schema completo della MainCU è quello di Execute, a cui si passa quando, dallo stato di WaitingInput, l’ingresso PSMode commuta a “11”. Il presente stato ha una struttura gerarchica piuttosto complessa, mostrata nella figura a pagina seguente. Per semplificare la schematizzazione si è ritenuto che la scrittura su qualsiasi registro venga completata entro i 2 colpi di clock. Progettazione in VHDL del Pixel Shader v 1.4 Execute Setup Pag. 250 All’entrata nello stato Execute si provvede ad azzerare il Program Counter e a disabilitare in lettura l’Instruction Pool. Il primo stato della gerarchia che viene raggiunto è quello di Setup, in cui si effettua l’inizializzazione generale dei segnali in uscita dalla MainCU. Il codice in questione è qui di seguito riportato. WriteString("MainCU - Setup", MCUFile); REPORT "MainCU - Setup" SEVERITY note; --inizializzazione generale ALUA_en <= '0'; ALUB_en <= '0'; ALUG_en <= '0'; ALUR_en <= '0'; BEMEn <= '0'; DepthEn <= '0'; BEMLatch_en <= '0'; DMG_WriteEn <= '0'; InstLatchSecond_en <= '0'; InstrLatchFirst_en <= '0'; PCIncr_en <= '0'; PixelKill <= '0'; PSDone <= '0'; SamplerEn <= '0'; ScalAU0_en <= '0'; ScalAU1_en <= '0'; ScalCU_en <= '0'; Switch <= '0'; TexldDemux <= '0'; VectAU0_en <= '0'; VectAU1_en <= '0'; VectCU_en <= '0'; WRsel <= '0'; PC_en <= '0'; Execute Fetching Dallo stato di Setup si passa a quello di Fetching, che è a sua volta uno stato gerarchico, la cui struttura interna è riportata nella figura a pagina seguente. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 251 All’entrata nello stato di Fetching si provvede ad abilitare in lettura l’Instruction Pool. Il primo stato della gerarchia che viene raggiunto è quello denominato FetchingFirst, di cui si riporta il codice con relativo commento a lato. WriteString("MainCU - FetchingFirst", MCUFile); Le variabili REPORT "MainCU - FetchingFirst" SEVERITY note; FirstExecution1 e FirstExecution2 servono --il codice racchiuso dentro l’IF viene eseguito ad impedire che --solo la prima volta che si entra nel processo IF FirstExecution1 = '0' THEN eventuali valutazioni multiple dello stato FirstExecution1 := '1'; causino letture di dati indesiderate. --Abilito l’Instruction Latch First --lo spengo subito altrimenti al cambiamento del PC cambiano --anche i dati memorizzati Carico l’istruzione, InstrLatchFirst_en <= '1', '0' AFTER 1 NS; leggo l’OpCodeOpCodeFirst32 := OpCode; proveniente dall’IP, --determino l'incremento per il program counter CASE OpCodeFirst16 IS in base all’OpCode WHEN PSadd => PCIncr_num <= "100"; calcolo l’incremento per WHEN PSmul => PCIncr_num <= "100"; il PC. WHEN PSmad => PCIncr_num <= "101"; WHEN PSbem => PCIncr_num <= "100"; WHEN PScmp => PCIncr_num <= "101"; WHEN PScnd => PCIncr_num <= "101"; WHEN PSdp3 => PCIncr_num <= "100"; WHEN PSdp4 => PCIncr_num <= "100"; WHEN PSlrp => PCIncr_num <= "101"; WHEN PSmov => PCIncr_num <= "011"; WHEN PSnop => PCIncr_num <= "001"; WHEN PSsub => PCIncr_num <= "100"; WHEN PStexcrd => PCIncr_num <= "011"; WHEN PStexdepth => PCIncr_num <= "010"; WHEN PStexkill => PCIncr_num <= "010"; WHEN PStexld => PCIncr_num <= "011"; WHEN PSphase => PCIncr_num <= "001"; WHEN PSEndShader => PCIncr_num <= "000"; Progettazione in VHDL del Pixel Shader v 1.4 Pag. 252 WHEN OTHERS => ASSERT false REPORT "BAD OPCODE!!!" SEVERITY Failure; END CASE; --abilito l'incremento del PC <= '1' AFTER 1 NS, '0' AFTER 2 NS; Posiziono il PC PCIncr_en alla --abilito il PC prima DWORD della --per ora stimiamo il ritardo del PcIncr in max 2 ns prossima istruzione. --per effettuare la somma PC_en <= '1' AFTER 3 NS, '0' AFTER 4 NS; END IF; Se l’istruzione letta è una di quelle che non possono andare in pairing (istruzioni di texture addressing, BEM, DP4, NOP, PHASE, EndShader), allora il PairingBit viene impostato a ‘0’ e nel prossimo colpo di clock si esce dal Fetching per entrare nello stato di DecodingAndExecution. In caso contrario si abilita in lettura l’Instruction Pool e si passa allo stato FetchingSecond, il cui codice è qui di seguito riportato. --il codice racchiuso dentro l’IF viene eseguito --solo la prima volta che si entra nel processo IF FirstExecution2 = '0' THEN FirstExecution2 := '1'; Carico la prossima InstLatchSecond_en <= '1', '0' AFTER 1 NS; istruzione e leggo OpCodeSecond32 := OpCode; l’OpCode. END IF; Una volta letto l’OpCode della seconda istruzione, si può controllare il bit di pairing: se questo è ‘0’, nel prossimo colpo di clock si esce dal Fetching per entrare nello stato di DecodingAndExecution; in caso contrario si provvede a posizionare correttamente il Program Counter e nel colpo di clock successivo si passa allo stato InstructionDelegation. Execute DecodingAndExecution Analizziamo ora il prossimo stato interno all’Execute, denominato DecodingAndExecution, nel quale si entra in caso di istruzione singola. Si tratta anche in questo caso di uno stato gerarchico avente la struttura mostrata nella figura a pagina seguente. In corrispondenza del punto di ingresso dello stato si esegue il controllo dell’OpCode. Le situazioni che possono verificarsi sono le seguenti: § fine shader: si memorizza nel latch di uscita il risultato dell’elaborazione del pixel corrente, si porta a livello alto la linea PSDone, dopodiché si passa allo stato di WaitingInput § istruzione BEM: si abilita il Modulo BEM e successivamente si passa allo stato BEMEnable, che è un semplice stato di attesa; quando il BEMDone commuta ad ‘1’ si abilita il BEMLatch e si passa allo stato InstrBem § negli altri casi: si passa direttamente allo stato InstructionExecution. Progettazione in VHDL del Pixel Shader v 1.4 Execute DecodingAndExecution InstructionExecution Pag. 253 Come si evince dalla figura, lo stato InstructionExecution è anch’esso uno stato gerarchico la cui struttura, peraltro piuttosto complessa, è riportata a pagina seguente. Come si può osservare, per ogni istruzione eseguibile dal Pixel Shader esiste uno stato, di tipo semplice o gerarchico. In corrispondenza del punto di ingresso dell’InstructionExecution si controlla l’OpCode e si entra nello stato corrispondente all’istruzione corrente. Dato il considerevole numero di istruzioni eseguibili, in questa sede non analizzeremo la struttura di tutti gli stati presenti in figura, ma fisseremo l’attenzione solo su alcune istruzioni, che prenderemo come esempio. In ogni caso, la struttura interna di tutti gli stati gerarchici è riportata in appendice, così come in appendice è possibile consultare il codice VHDL dell’intera macchina a stati finiti. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 254 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 255 Istruzioni NOP e PHASE. I due casi più semplici sono quelli relativi alle istruzioni NOP e PHASE. Per la natura stessa di dette istruzioni non è necessario compiere alcuna operazione, pertanto si può passare al fetching della prossima istruzione. Istruzione ADD. Analizziamo ora il flusso di esecuzione dell’istruzione ADD. Il primo stato in cui si entra è denominato InstrAdd ed ha la struttura gerarchica mostrata nella figura seguente. Il punto d’ingresso conduce allo stato Add1, in cui viene eseguito il seguente codice. WriteString("MainCU - Add1", MCUFile); REPORT "MainCU - Add1" SEVERITY note; Seleziono i dati da usare come sorgenti 0 e 1. Impostazione dei multiplexer che formano la rete di selezione a monte delle ALU. Impostazione dei multiplexer che formano la rete di replicazione, posta a valle delle ALU. Impostazione dell’OpCode per tutte e quattro le ALU. SIPFirst_sel0 <= "01"; SIPFirst_sel1 <= "10"; MuxInR0_sel MuxInR1_sel MuxInG1_sel MuxInG0_sel MuxInB0_sel MuxInB1_sel MuxInA0_sel MuxInA1_sel <= <= <= <= <= <= <= <= "00"; "00"; "00"; "00"; "00"; "00"; "00"; "00"; MuxOutR_sel <= '0'; MuxOutG_sel <= '0'; MuxOutA_sel <= '0'; ALUR_OpCode ALUG_OpCode ALUB_OpCode ALUA_OpCode <= <= <= <= ALUXAdd; ALUXAdd; ALUXAdd; ALUXAdd; Se almeno uno dei due sorgenti richiede l’utilizzo della AU interna ai Modifier, al colpo di clock successivo si provvede ad abilitare le AU necessarie Progettazione in VHDL del Pixel Shader v 1.4 Pag. 256 e dallo stato Add1 si passa a quello denominato WaitAUAdd, che funge da stato di attesa. Quando le AU attivate hanno terminato l’operazione, si può passare allo stato Add2, attivando preventivamente le quattro ALU. Nel caso in cui non sia richiesto l’uso di AU per i due sorgenti, dallo stato Add1 si passa direttamente allo stato Add2, provvedendo anche questa volta ad attivare le quattro ALU. Add2 è essenzialmente uno stato di attesa, da cui si esce solo quando tutte e quattro le ALU hanno terminato l’esecuzione dell’operazione. A questo punto le ALU possono essere disabilitate e si può uscire da InstrAdd per passare allo stato WaitModifier. Il WaitModifier, comune a molte istruzioni, è un semplice stato di attesa: si fa passare un colpo di clock per assicurarsi che gli Instruction Modifier abbiano terminato di elaborare i dati in uscita dalle ALU. In corrispondenza del prossimo colpo di clock si esce contemporaneamente da WaitModifier, dallo stato gerarchicamente superiore InstructionExecution e dallo stato ancora superiore DecodingAndExecution, per passare allo stato di WriteBack. Osserviamo, per concludere, che le istruzioni MUL, SUB e MOV hanno un flusso di esecuzione del tutto simile a quello or ora analizzato. Istruzione TEXLD. Come esempio di istruzione di texture addressing prendiamo in considerazione la TEXLD. A partire dal punto d’ingresso dell’InstructionExecution si raggiunge lo stato gerarchico InstrTexLD, la cui struttura interna è mostrata nella figura sottostante. Nel primo stato, TexLD1, viene eseguito il codice riportato all’inizio di pagina seguente. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 257 WriteString("MainCU - TexLD1", MCUFile); REPORT "MainCU - TexLD1" SEVERITY note; Seleziono il dato da usare come sorgente. SIPFirst_sel0 <= "01"; Imposto la rete di multiplexer a valle delle ALU. MuxOutR_sel <= '0'; MuxOutG_sel <= '0'; MuxOutA_sel <= '0'; --se il modificatore è attivo, passo in uno stato di attesa --per le alu che fanno l'operazione --1010 è la codifica per il modificatore _da, --1001 è la codifica per il _db Se non ho il modificatore metto il sorgente nel DataBus. Se ho il modificatore devo operare la divisione. IF (TextureSourceModif /= "1010") AND ( TextureSourceModif /= "1001") THEN --non ho modificatore --connetto direttamente al DataBus TexldDemux <= '1'; ELSE --ho il modificatore MuxInR0_sel MuxInG0_sel MuxInR1_sel MuxInG1_sel <= <= <= <= "00"; "00"; "10"; "10"; ALUR_OpCode <= ALUXdiv; ALUG_OpCode <= ALUXdiv; END IF; In presenza di modificatore si attivano le ALU che devono eseguire la divisione e si passa allo stato di attesa TexLD3. Una volta terminata l’esecuzione dell’operazione, entrambe le ALU vengono spente e si alza il segnale Switch per connettere i risultati al DataBus. Lo stato successivo è WaitModifierTexLD che, nel caso di assenza di modificatori, viene raggiunto direttamente da TexLD1. In tale stato si attende un colpo di clock per assicurarsi che il dato raggiunga il bus e si abilita il Texture Sampler. Lo stato successivo è TexLDWaitSampler, nel quale si eseguono le due istruzioni seguenti: --disattivo lo switch (ripulisco il bus --in attesa dei risultati) Switch <= '0'; --elimino la connessione diretta al databus TexldDemux <= '0'; Quando si riceve il segnale di Done da parte del Texture Sampler si esce dallo stato InstrTexLD e dallo stato gerarchicamente superiore InstructionExecution, per passare alla fase di WriteBack. Execute InstrucionDelegation Torniamo ora alla struttura interna dello stato Execute e passiamo a considerare il caso di istruzioni in pairing (fare riferimento alla figura a pag 250). In tale situazione, dopo il fetching dell’istruzione si passa allo stato InstructionDelegation, in cui viene eseguito il codice qui di seguito riportato. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 258 WriteString("MainCU - InstructionDelegation", MCUFile); REPORT "MainCU - InstructionDelegation" SEVERITY note; --invio gli opcode alle vect e scal CU IF AlphaBit_In = '1' THEN --su instrlatchsecond ho le istruzioni per la scalar pipe VectOpCode <= OpCodeFirst16; ScalOpCode <= OpCodeSecond16; ELSE -- su instrlatchsecond ho le istruzioni per la vector pipe VectOpCode <= OpCodeSecond16; ScalOpCode <= OpCodeFirst16; END IF; --abilito le vect e scal CU ScalCU_en <= '1' AFTER 1 NS; VectCU_en <= '1' AFTER 1 NS; MuxOutA_sel <= '0'; Execute WaitingVectAndScal Execute WriteBack Come si può vedere, nel presente stato, la MainCU delega la VectCU e la ScalCU all’esecuzione delle istruzioni, fornendo gli OpCode corretti ed alzando i segnali di Enable. Nel colpo di clock successivo si passa al WaitingVectAndScal, che è un semplice stato di attesa. Quando la VectCU e la ScalCU confermano l’avvenuta esecuzione delle istruzioni, la MainCU provvede a spegnere entrambe le unità ausiliarie e porta a livello alto i segnali Switch e WRsel; al prossimo colpo di clock si passa alla fase di WriteBack. L’ultimo stato che rimane da prendere in considerazione è quello di WriteBack, a cui si passa una volta terminata l’esecuzione dell’istruzione corrente (o della coppia di istruzioni correnti) per la scrittura del risultato nel registro destinazione. Si osservi che prima di raggiungere il WriteBack si provvede a collegare le uscite degli Instruction Modifier al DataBus (Switch <= ‘1’) e a settare i registri in modalità scrittura (WRsel <= ‘1’). Il presente è uno stato gerarchico avente la struttura interna mostrata in figura. Il primo dei due stati interni, WriteBackWrite, abilita la scrittura nel registro destinazione, portando a livello alto il segnale DMG_WriteEn. L’altro stato, WriteBackDelay, è uno stato di attesa, usato per tener conto del tempo di scrittura sul registro. Prima dell’uscita dal WriteBackDelay si provvede ad abbassare i segnali SamplerEn e DMG_WriteEn. Al colpo di clock successivo si passa al fetching della prossima istruzione, abbassando preventivamente i segnali Switch e WRsel. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 259 7.13.2 La VectCU La VectCU è l’unità di controllo preposta alla supervisione della vector pipeline (canali R, G, B) in caso di pairing. Il componente è mostrato nella figura seguente. Riportiamo la lista dei segnali di ingresso e di uscita della VectCU sotto forma di tabelle. Tabella dei segnali di ingresso della VectCU segnale n. descrizione from VectCU_en VectAU0_info VectAU1_info VectAU0_done VectAU1_done OpCode Clk1 Clk2 ALUR_done ALUG_done ALUB_done ALUR_CompBit ALUG_CompBit ALUB_CompBit 1 1 1 1 1 16 1 1 1 1 1 1 1 1 MainCU SMDec SMDec Modifier Modifier MainCU External External ALUR ALUG ALUB ALUR ALUG ALUB Abilitazione VectCU Informano la VectCU se abilitare o meno le AU (sono le linee s0 in uscita dai SMDec) Certificano che le AU hanno terminato le operazioni OpCode delle istruzioni clock fase 1 clock fase 2 Indica il termine dell’elaborazione da parte dell’ALUR Indica il termine dell’elaborazione da parte dell’ALUG Indica il termine dell’elaborazione da parte dell’ALUB Risultato della comparazione Risultato della comparazione Risultato della comparazione Tabella dei segnali di uscita della VectCU segnale n. descrizione VectAU0_en VectAU1_en SIP_sel0 SIP_sel1 ALUR_en ALUG_en ALUB_en ALUR_OpCode ALUG_OpCode ALUB_OpCode VectCU_done 1 1 2 2 1 1 1 3 3 3 1 Abilitazione per AU – vanno inviati alle AU dopo che sui due bus operando si trova il dato valido Selezionano gli operandi (con relative maschere e modificatori) da associare alle linee 0 ed 1 in uscita dai SIP Abilita l’ALUR Abilita l’ALUG Abilita l’ALUB OpCode per l’ALUR OpCode per l’ALUG OpCode per l’ALUB Indica il termine dell’istruzione Progettazione in VHDL del Pixel Shader v 1.4 MuxInR0_sel MuxInR1_sel MuxInG0_sel MuxInG1_sel MuxInB0_sel MuxInB1_sel MuxOutR_sel MuxOutG_sel 2 2 2 2 2 2 1 1 Pag. 260 Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer La VectCU è descritta mediante una Finite State Machine, della quale riportiamo lo schema completo nella figura sottostante. Waiting Execute Lo stato di partenza è il Waiting, in cui si permane fino a che non si riceve l’abilitazione da parte della MainCU. Quando il segnale VectCU_en commuta a livello alto, si passa allo stato Execute, abbassando preventivamente il segnale VectVU_done. Execute è uno stato gerarchico, la cui struttura interna è mostrata nella figura a pagina seguente. Come si può osservare, per ciascuna istruzione eseguibile in pairing dalla vector pipeline esiste uno stato gerarchico. In corrispondenza del punto d’ingresso dello stato Execute viene effettuato il controllo dell’OpCode, dopodiché si entra nello stato corrispondente all’istruzione corrente. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 261 Struttura interna dello stato Execute (VectCU) La struttura interna e il codice eseguito nei macrostati relativi alle varie istruzioni sono del tutto simili a quanto visto per la MainCU. La differenza è che ora le AU interne ai modificatori e le ALU che vengono abilitate sono solo quelle relative ai canali R, G, B. Al solito, il codice completo della Macchina a Stati Finiti può essere consultato in appendice. Al termine dell’esecuzione dell’istruzione si alza il segnale VectCU_done, destinato alla MainCU, e si torna allo stato iniziale di attesa (Waiting). Progettazione in VHDL del Pixel Shader v 1.4 Pag. 262 7.13.3 La ScalCU La ScalCU è l’unità di controllo avente il compito di supervisionare la scalar pipeline (canale A) nel caso di pairing. Il componente è mostrato nella figura seguente. Presentiamo le tabelle relative ai segnali in ingresso e in uscita. Tabella dei segnali di ingresso della ScalCU segnale n. descrizione from ScalCU_en ScalAU0_info ScalAU1_info ScalAU0_done ScalAU1_done OpCode Clk1 Clk2 ALUA_done ALUA_CompBit 1 1 1 1 1 16 1 1 1 1 MainCU SMDec SMDec Modifier Modifier MainCU External External ALUA ALUA Abilitazione Informano la ScalCU se abilitare o meno le AU (sono le linee s0 in uscita dai SMDec) Certificano che le AU hanno terminato le operazioni OpCode delle istruzioni clock fase 1 clock fase 2 Indica il termine dell’elaborazione da parte dell’ALUA Risultato della comparazione Tabella dei segnali di uscita della ScalCU segnale n. descrizione ScalAU0_en ScalAU1_en SIP_sel0 SIP_sel1 ALUA_en ALUA_OpCode ScalCU_done MuxInA0_sel MuxInA1_sel 1 1 2 2 1 3 1 2 2 Abilitazione per AU – vanno inviati alle AU dopo che sui due bus operando si trova il dato valido Selezionano gli operandi (con relative maschere e modificatori) da associare alle linee 0 ed 1 in uscita dai SIP Abilita l’ALUA OpCode per l’ALUA Indica il termine dell’istruzione Segnale di selezione del corrispondente multiplexer Segnale di selezione del corrispondente multiplexer La ScalCU è descritta mediante una Finite State Machine il cui schema completo è riportato a pagina seguente. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 263 Lo schema è praticamente identico a quello visto per la VectCU e non necessita di commenti. Le differenze fra le due macchine a stati finiti sono relative al macrostato Execute, che per la ScalCU assume la struttura mostrata nella figura a pagina seguente. Si noti che in questo caso manca l’istruzione DP3 la quale, necessitando di tre ALU, non può essere eseguita dalla scalar pipeline. I macrostati relativi alle altre istruzioni hanno una struttura interna analoga a quanto visto per la VectCU: in questo caso tuttavia le ALU, le unità aritmetiche dei modificatori e i multiplexer che vengono pilotati sono solo quelli relativi al canale A. Progettazione in VHDL del Pixel Shader v 1.4 Struttura interna dello stato Execute (ScalCU) Pag. 264 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 265 7.14. Una possibile realizzazione circuitale del banco di registri Nel paragrafo 7.3 si sono analizzate le caratteristiche dei quattro banchi di registri e si è detto che questi sono stati descritti in VHDL a livello behavioral. Nel presente paragrafo proponiamo una possibile realizzazione a porte logiche del singolo banco. Per l’esattezza vogliamo progettare un banco di otto registri a 128 bit con le seguenti caratteristiche: § il banco deve poter essere indirizzato in lettura da quattro linee contemporaneamente, ciascuna relativa ad un registro § il banco deve poter essere indirizzato in scrittura da quattro linee contemporaneamente, ciascuna relativa ad un canale di un qualsiasi registro. Come elemento di memoria useremo un latch di tipo D, in grado di contenere un bit di informazione. Il banco di registri può essere allora pensato come un array di latch, organizzati in 8 righe e 128 colonne, corrispondendo ciascuna riga ad un diverso registro del banco. Nell’illustrazione del progetto seguiremo un approccio di tipo bottomup, ovvero partiremo dal singolo elemento di memoria per sintetizzare, in una successione di tappe, il circuito che implementa l’intero banco di registri. Fissiamo l’attenzione sulla singola colonna dell’array. Lo schematico che contiene gli elementi di memoria unitamente alle reti per la lettura e scrittura dei dati è mostrato nella figura all’inizio di pagina seguente. La singola colonna è composta da otto latch di tipo D, uno per ciascun registro (si noti che in figura, per semplicità, si sono esplicitamente riportati soltanto i DLatch b0, b1 e b7; al posto dei puntini si devono immaginare i rimanenti Dlatch). Il segnale WE, a 8 bit, contiene le linee di abilitazione scrittura dei latch. Ciascuna linea viene portata al latch corrispondente tramite un apposito ripper. Il segnale din contiene il bit in ingresso, che è portato a tutti i latch. I segnali A0, A1, A2, A3, tutti a 8 bit, contengono gli indirizzi decodificati associati rispettivamente alle linee 0, 1, 2, 3. I quattro blocchi costituiti ciascuno da otto porte AND seguite da un OR a otto ingressi formano, nel loro complesso, la rete per la selezione dei dati da portare alle linee d’uscita. Il segnale RE, a 4 bit, contiene le linee di abilitazione lettura, prelevate tramite ripper e portate alla rete di porte AND presenti in uscita. I segnali Out0, Out1, Out2, Out3 sono i bit di uscita associati alle quattro linee. Lo schema appena descritto può essere pensato racchiuso in un unico componente, che verrà usato come elemento base per la successiva fase di progetto. Tale componente, denominato SingleBit, è mostrato nella figura a lato. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 266 . . . . . La fase successiva consiste nella connessione di 32 componenti SingleBit per costituire l’elemento che memorizza una DWORD. Lo schema circuitale è mostrato all’inizio di pagina seguente. I segnali d’ingresso WE, RE, A0, A1, A2, A3 hanno lo stesso significato dei medesimi segnali presenti nel componente SingleBit. Il segnale d’ingresso din, a 32 bit, viene distribuito tramite appositi ripper ai SingleBit presenti nello schema. I segnali d’uscita Out1, Out2, Out3, Out4, a 32 bit, si ottengono raggruppando mediante il componente merge i segnali relativi alla stessa linea provenienti dai 32 SingleBit. Lo schematico mostrato può essere racchiuso in un unico componente, denominato SingleDWORD e mostrato nella figura a lato. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 267 . . . . . . L’ultima fase consiste nella connessione di quattro componenti SingleDWORD per formare l’intero banco di registri. Lo schema è mostrato nella figura seguente. Si nota la presenza di quattro AddressDecoder (blocchi in arancione sulla sinistra) i quali decodificano gli indirizzi in ingresso a 3 bit (segnali Addr0, Addr1, Addr2, Addr3) per fornire in uscita i segnali A0, A1, A2, A3 da inviare alle SingleDWORD. Il segnale d’ingresso din, a 128 bit, viene ripartito nelle quattro DWORD componenti e ciascuna di queste viene inviata al corrispondente circuito. Il segnale RE contiene le abilitazioni in lettura relative alle quattro linee. I segnali W0, W1, W2, W3 sono le abilitazioni in scrittura relative alle quattro DWORD. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 268 I segnali d’uscita Out0, Out1, Out2, Out3, a 128 bit, si ottengono raggruppando mediante il merge le uscite delle SingleDWORD relative alla medesima linea. Lo schema mostrato rappresenta una possibile soluzione realizzativa del banco di registri avente le caratteristiche illustrate all’inizio del paragrafo e richieste nell’ambito del nostro progetto. 7.15. Schema completo del Pixel Shader v.1.4 Nel corso del presente capitolo si è illustrata l’architettura interna del Pixel Shader, partendo da uno schema a macroblocchi e sintetizzando di volta in volta i singoli componenti presenti al loro interno. Mostrare lo schema completo del processore non risulta tuttavia possibile per l’eccessiva dimensione del circuito; si rimanda pertanto alla versione elettronica e alla stampa in formato poster allegata alla presente tesina. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 269 Capitolo 8. Simulazioni 8.1. Introduzione Per verificare il corretto funzionamento del Pixel Shader ci preoccuperemo di effettuare una simulazione fornendo alla Test Unit del Test Bench un insieme completo di file di ingresso, studiato in modo da coprire una casistica abbastanza completa delle situazioni che si possono presentare in un utilizzo reale. Prima di procedere con la simulazione è tuttavia necessario preparare adeguatamente le librerie che andremo ad utilizzare e configurare correttamente i file del compilatore e del simulatore utilizzato. Poiché il progetto realizzato eccede le limitazioni imposte dalla versione di valutazione del ModelSim, si è fatto ricorso alle versioni gratuite del simulatore VHDL Simili di SymponyEDA (fare riferimento al sito web www.symphonyeda.com per reperire l’ultima versione del simulatore ed alcuni tool gratuiti che risultano molto utili per semplificare il lavoro di compilazione e simulazione). Purtroppo in questo modo si perde l’opportunità di effettuare un tracing interattivo del flusso della simulazione, cosa che invece è possibile realizzare mediante l’utilizzo congiunto di HDL Designer Series e ModelSim; questo è il motivo per cui in fase di debug del progetto si sono inserite tutta una serie di istruzioni di tracing su schermo e su file che altrimenti non avrebbero avuto ragione di esistere. 8.2. Configurazione ed utilizzo di VHDL Simili La versione gratuita messa a disposizione contempla esclusivamente il compilatore ed il simulatore a linea di comando. Fortunatamente alcuni utenti hanno pensato di realizzare una semplice interfaccia (SimTool62) che facilita alquanto le operazioni di compilazione e simulazione. Nell’immagine seguente è visibile l’interfaccia di SimTool. 62 Questa applicazione è scritta in tcl-tk, linguaggio di scripting altamente supportato da Unix. Per poterla utilizzare in Windows è necessario aprirla mediante una apposita shell (tclsh84.exe) generalmente installata assieme al VHDL Simili. La versione 8.4 è quella corrente al momento della scrittura del presente testo. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 270 Una volta generato (mediante HDL Designer) tutto il codice che costituisce le tre librerie create per la realizzazione del progetto in esame, procediamo alla compilazione di ogni singola unità funzionale. La compilazione deve avvenire nell’ordine corretto, in quanto questa semplice versione del programma non si occupa di verificare che eventuali altri moduli da cui dipende l’unità oggetto della compilazione siano a loro volta stati già compilati con successo. Prima di proseguire è tuttavia necessario specificare di volta in volta quali librerie è necessario utilizzare in modo che sia il compilatore che il simulatore sappiano dove reperire le versioni binarie dei file di interesse. Per raggiungere lo scopo è necessario editare il file symphony.ini, presente nella subdirectory “/bin” nella cartella di installazione del VHDL Simili, ed aggiungere le dichiarazioni relative alle librerie di interesse. Di seguito viene riportato il contenuto del file opportunamente modificato per utilizzare le librerie facenti parte del progetto in esame. [libraries] ieee = $SYMPHONYEDA/lib/ieee/ieee.sym std_developerskit = $SYMPHONYEDA/tcl/bin/std_developerskit.sym Utilities = $SYMPHONYEDA/tcl/bin/Utilities.sym PixelShader= $SYMPHONYEDA/tcl/bin/PixelShader.sym PixelShader_TestBench = $SYMPHONYEDA/tcl/bin/PixelShader_TestBench.sym Ogni voce appartenente ad una data libreria va inserita mano a mano che la libreria stessa viene compilata. Per eseguire la compilazione si preme il tasto “Compile” e si inseriscono i dati nella finestra di dialogo associata, la cui immagine, provvista di adeguati commenti, viene presentata di seguito. Specifica il nome del file .vhd che si intende compilare. Specifica il nome della libreria in cui si intende compilare il sorgente indicato. Le librerie vengono generate per default nella directory in cui risiede l’eseguibile del compilatore e sono costituite da directory denominate [nome_libreria].sym; queste directory a loro volta contengono altre directory che mantengono al loro interno le versioni binarie dei file compilati. Una volta effettuata correttamente la compilazione delle quattro librerie che compongono il progetto, è possibile passare alla simulazione dell’entità di test. Progettazione in VHDL del Pixel Shader v 1.4 Pag. 271 Premendo il pulsante “Simulate” in SimTool si passa alla seguente finestra di dialogo: A n a 1 l 2 3 4 5 Vediamone nel dettaglio le opzioni presenti: 1. In questa sezione si inserisce il nome dell’entità che si desidera simulare: in questo caso il nome riportato è quello dell’entità che rappresenta il TestBench nella sua interezza. 2. Nella casella di testo viene specificato il nome della libreria a cui appartiene l’entità che si desidera simulare. 3. Nella sezione Execute Do File si riporta il nome del file contenente la lista dei segnali dei quali si vuole generare la forma d’onda per un esame successivo. Per informazioni sulla sintassi del file in esame fare riferimento alla manualistica del VHDL Simili. 4. Qui viene specificato il nome del file che conterrà la rappresentazione codificata dell’evoluzione nel tempo dei segnali elencati al punto precedente. 5. La sezione Additional Options viene utilizzata per settare particolari flag del simulatore. Il flag utilizzato in questo caso esclude l’utilizzo di particolari ottimizzazioni durante la simulazione, in quanto potrebbero risultare incompatibili con alcuni costrutti del linguaggio. Per poter visualizzare le forme d’onda relative ai segnali richiesti ci occorrono un visualizzatore di forme d’onda ed eventualmente un tool che effettui la conversione tra il formato di file utilizzato da VHDL Simili ed il visualizzatore impiegato. Allo scopo la scelta è caduta su un altro tool gratuito: WaveViewer di SynaptyCAD (reperibile al sito www.synaptycad.com) e sull’utility di conversione Simi2Vcd (reperibile sul sito di SymphonyEDA). Poiché ora disponiamo di tutti i tool necessari è tempo di proseguire l’esposizione e presentare i file di ingresso utilizzati per l’esecuzione del test. 8.3. Input ed Output del Test Bench Per testare tutte le funzionalità del binomio Pixel Shader – Test Unit si ricorre ai seguenti file di input: Progettazione in VHDL del Pixel Shader v 1.4 Pag. 272 PS.TXT (assemblato in PS.PSO): ps.1.4 def c0, 0.1, 0.2, 0.3, 0.4 def c1, 0.5, 0.6, 0.7, 0.8 texld r0, t0 texcrd r1, t2 texld r2, t1 bem r3.rg, v0, r0 add_x2 r4, -r0, r1 mul r5, v0, c1 lrp r0.rgb, v0, c0, r2 +add r1.a, r2.b, v1 Ovviamente questo semplice shader (che non rappresenta alcuno degli algoritmi che vengono normalmente impiegati in computer graphics) non esaurisce tutta la gamma delle combinazioni che è possibile realizzare. Per avere degli esempi di shader maggiormente dettagliati fare riferimento alla documentazione fornita da Microsoft, Nvidia ed ATI, nonché da opportuni siti e manuali esclusivamente dedicati all’argomento63. C.TXT: 2 0.1 0.2 0.3 0.4 3 0.4 0.3 0.2 0.1 V.TXT: 0 0.0 0.2 0.4 0.6 1 0.6 0.4 0.2 0.1 9 0.0 0.0 0.0 0.0 T.TXT: 0 0.1 0.2 1 0.0 0.0 2 1.0 0.0 9 0.0 0.0 0.3 0.3 0.2 0.0 0.0 0.0 0.0 0.0 Di seguito viene presentato quello che dovrebbe essere il dump dei registri temporanei r dello shader al temine delle operazioni. Questi dati sono stati ricavati eseguendo manualmente le operazioni dello shader sopra riportato. Occorre tenere conto del fatto che l’entità Texture Sampler fornisce in uscita i quattro valori dati di ingresso moltiplicati rispettivamente per le costanti 1, 2, 3 e 0; mentre l’entità che schematizza il modulo BEM restituisce le costanti 4, 3, 2, 1 per i seguenti coefficienti della matrice: M11, M10, M01 e M00. Expected Dump: Indice Registro Componente R 0 0 1 1 2 0 3 1.3 4 1.8 5 0 63 64 Componente G 0.04 0 0 2 -0.8 0.12 Alcuni link utili: www.direct3d.net, www.shaderstudio.com, www.gamedev.net, ecc… Per via delle maschere utilizzate i valori in queste celle restano inalterati. Componente B 0.66 0.2 0.9 -64 -1.4 0.28 Componente A 0 1 0 0 0.48 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 273 Nota: nel corso delle simulazioni sono emersi strani comportamenti del simulatore soprattutto nella gestione dei dati in formato Real, tipo utilizzato soprattutto nella realizzazione behavioural delle ALU floating-point: per tale motivo nel codice si è fatto ricorso a delle soluzioni stilisticamente discutibili che tuttavia eliminano i problemi riscontrati (almeno quelli di cui si è a conoscenza). 8.4. Forme d’onda e risultato della simulazione Di seguito vengono presentati i due file di testo risultato della simulazione: il primo (OUTPUT.TXT) riporta soltanto i valori dei segnali di uscita del Pixel Shader; il secondo (RDUMP.TXT) contiene il dump del contenuto dei registri r. OUTPUT.TXT: R0: 0.000000 0.040000 R5.r: 0.000000 PixelKill: 0 DepthEnable: 0 0.660000 RDUMP.TXT: … … *** Register DUMP *** 0.000000 0.040000 0.660000 1.000000 0.000000 0.200000 0.000000 0.000000 0.900000 1.300000 2.000000 0.000000 1.800000 -0.800000 -1.400000 0.000000 0.120000 0.280000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.480000 Come possiamo facilmente verificare confrontando questa tabella con quella del paragrafo precedente, il risultato dell’elaborazione è corretto. Per una maggiore comprensione vengono di seguito riportate le evoluzioni nel tempo delle forme d’onda dei quattro componenti fondamentali del Test Bench, suddividendo i grafici in base al modo di funzionamento corrente del binomio Test Unit – Pixel Shader. Per interpretare correttamente i grafici fare riferimento a quanto esposto nei precedenti capitoli. Progettazione in VHDL del Pixel Shader v 1.4 TEST UNIT – WaitingCommand e LoadShader TEST UNIT – LoadPixel TEST UNIT – Execute Pag. 274 Progettazione in VHDL del Pixel Shader v 1.4 Pixel Shader – WaitingCommand e LoadShader Pixel Shader – LoadPixel Pixel Shader – Execute Pag. 275 Progettazione in VHDL del Pixel Shader v 1.4 Pag. 276 BEM Module Si rappresenta solamente l’intervallo di tempo in cui il modulo rimane attivo e fornisce dati al Pixel Shader. TEXTURE SAMPLER Si riporta solamente l’intervallo di tempo durante il quale il Texture Sampler viene attivato a causa dell’esecuzione delle due istruzioni TEXLD dello shader. Per brevità di trattazione non si riporta l’evoluzione dei segnali internamente al Pixel Shader, in quanto, data l’ingente mole di segnali presenti, riportare qui le forme d’onda renderebbe inutilmente tedioso il proseguo di questo capitolo. L’analisi può comunque essere effettuata sfruttando i tool acclusi al presente volume. 8.5. Conclusioni e possibili miglioramenti In questo modo si conclude la sezione della tesina dedicata al Pixel Shader. Della struttura realizzata non si esegue una analisi prestazionale per i motivi illustrati nel paragrafo 5.1, che hanno portato alla realizzazione di una architettura funzionante ma non completamente ottimizzata. Qui di seguito riportiamo le semplificazioni adottate ed i possibili miglioramenti per compensare queste “mancanze”: • La struttura del processore è stata concepita per eseguire le operazioni di caricamento, decodifica ed esecuzione delle istruzioni ciascuna in un momento diverso, ovvero non si è provveduto a parallelizzare le tre fasi. Per risolvere questo problema si potrebbero riprogettare le tre unità di controllo in modo da parallelizzare l’esecuzione: invece di una unità di controllo che si occupa di gestire le fasi sopra indicate e due unità periferiche per l’esecuzione delle istruzioni in pairing, si potrebbe pensare di ideare una sola unità di controllo, suddivisa internamente in tre macroblocchi (tra loro comunicanti mediante opportuni segnali di controllo): o Il primo che si occupi di gestire il fetching delle istruzioni (in due cicli successivi esegue il fetching della prima e della seconda istruzione: durante l’esecuzione della prima istruzione si effettua Progettazione in VHDL del Pixel Shader v 1.4 Pag. 277 anche il fetching della seconda; se questa contempla il pairing si attiva anche il macroblocco che gestisce il pairing, altrimenti al ciclo successivo si esegue uno switch dei buffer che memorizzano le istruzioni e si esegue il fetch della successiva). o Il secondo che gestisca l’esecuzione di tutte le istruzioni e la fase di write-back. o Il terzo, attivo solo nel caso di istruzioni in pairing, vada a rimpiazzare la gestione della porzione della pipeline (vector o scalar) a seconda del tipo di istruzione da eseguire; in questo caso anche la gestione del write-back va suddivisa tra il secondo ed il terzo macroblocco. • Le ALU floating-point sono state progettate a livello comportamentale. Per avere una idea accettabile delle prestazioni di questi componenti ne occorre una schematizzazione senza dubbio più approfondita: in questo modo è possibile stimare con maggiore correttezza il tempo di elaborazione piuttosto che fornire una stima assai grossolana del tempo impiegato per l’esecuzione di ciascuna istruzione. Inoltre, come già detto, la mancanza di correlazione con l’hardware che deriva da questa astrazione non consente di stimare i ritardi relativi alle porte logiche che implementeranno fisicamente l’ALU: ne deriva che neanche il minimo periodo di clock può essere stimato con correttezza. • Mancanza di conoscenza dei parametri della tecnologia utilizzata nella realizzazione fisica del chip. Per poter conoscere questi dati, quindi per avere una valutazione corretta dei ritardi associati ad ogni porta logica o ad ogni elemento che si vuole implementare (naturalmente in base alla tecnologia che si intende utilizzare), è necessario lavorare a stretto contatto con le fonderie di silicio ed avere accesso alle loro librerie di progetto e data sheets tecnici. Tenendo in considerazione tutta questa sequenza di fattori ed operando le opportune modifiche al design è possibile ottimizzarne la struttura e migliorare così le prestazioni del processore.