Progettazione in VHDL del Vertex Shader
|
|
|
- Carlotta Giorgia Casali
- 10 anni fa
- Просмотров:
Транскрипт
1 Progettazione in VHDL del Vertex Shader Pag. 75 PARTE TERZA Progettazione in VHDL del Vertex Shader Autori: Gian Maria Ricci La presente parte è organizzata nei seguenti capitoli: Capitolo 3. Realizzare una ALU Floating Point Capitolo 4. Implementazione del Vertex Shader
2 Progettazione in VHDL del Vertex Shader Pag. 76 Capitolo 3. Realizzare una ALU Floating Point 3.1. Funzioni di conversione Uno dei punti di forza del VHDL è la possibilità di lavorare con la metodologia top-down, realizzando in un primo momento le entità con descrizioni comportamentali e scendendo nei dettagli solamente dopo che il design è stato testato e verificato. Seguendo questa metodologia è possibile progettare una FPU (Floating Point Unit), la cui realizzazione a livello circuitale è sicuramente di complessità notevole, utilizzando una descrizione comportamentale che si appoggia sulle librerie numeriche floating point del compilatore. Una volta che il design del processore è stato completato, è sufficiente sostituire la descrizione behavioural dell ALU con una descrizione comportamentale o comunque sintetizzabile dal compilatore. Per poter lavorare correttamente con ALU ed FPU a livello comportamentale è comunque necessario realizzare alcune funzioni di conversione, che consentano di trasformare la rappresentazione binaria del numero nella corrispettiva quantità reale o intera e viceversa. I segnali infatti sono definiti mediante bit_vector e non è possibile operare direttamente su di essi con gli operatori o le funzioni matematiche standard presenti nella libreria del ModelSim. Il VHDL permette infatti di definire variabili di tipo integer o real, ma non è possibile assegnare loro direttamente un valore contenuto in un segnale o in un altra variabile di tipo bit_vector. La prima cosa da fare è allora realizzare le funzioni di conversione tra i tipi interi e reali e la loro rappresentazione binaria Conversioni di quantità integer La conversione di quantità intere non presenta alcun problema: le funzioni che svolgono questo compito, oltre che molto semplici, si possono trovare in qualsiasi libro che tratti il VHDL. Per completezza è di seguito riportata la realizzazione presente nel VHDL Cookbook. function bits_to_int (bits : in bit_vector) return integer is variable temp : bit_vector(bits'range); variable result : integer := 0; if bits(bits'left) = '1' then -- negative number temp := not bits; else temp := bits; for index in bits'range loop -- sign bit of temp = '0' result := result * 2 + bit'pos(temp(index)); end loop; if bits(bits'left) = '1' then result := (-result) - 1; return result; end bits_to_int; Questa funzione effettua la conversione tra una sequenza di cifre binarie
3 Progettazione in VHDL del Vertex Shader Pag. 77 ed il numero intero da essa rappresentata, espresso naturalmente in complemento a due. La funzione è molto versatile perché non presuppone una lunghezza predeterminata della rappresentazione binaria, ma la deduce mediante l operatore di attributo 'range dal dato passatole come parametro. Viene inoltre utilizzato l attributo 'left, che permette di individuare il bit più rappresentativo che identifica il segno. Se il bit di segno è pari ad 1 (numeri negativi) è necessario negare tutte le cifre a causa del complemento a due, successivamente in un ciclo for viene calcolato il valore intero rappresentato dalla sequenza di cifre binarie. Se il numero è negativo al termine della conversione è necessario cambiare di segno e sottrarre il valore 1 come richiesto dal complemento a due. Viene inoltre realizzata la funzione che converte da binario a decimale senza segno, questa operazione si rivelerà necessaria per implementare le funzioni di conversione per i numeri in virgola mobile. Naturalmente è necessario fornire anche la funzione che effettua l operazione inversa: procedure int_to_bits (int : in integer; bits : out bit_vector) is variable temp : integer; variable result : bit_vector(bits'range); if int < 0 then temp := -(int+1); else temp := int; for index in bits'reverse_range loop result(index) := bit'val(temp rem 2); temp := temp / 2; end loop; if int < 0 then result := not result; result(bits'left) := '1'; bits := result; end int_to_bits; Questa funzione viene implementata ricorrendo ad una procedura in cui la rappresentazione binaria da calcolare viene passata come parametro per riferimento, si nota infatti che il parametro bits è definito di tipo out. Questo viene fatto principalmente per garantire la stessa versatilità della funzione precedente: quando si converte da una quantità intera alla sua rappresentazione binaria si può usare un numero qualsiasi di bit, a patto che siano sufficienti per contenere il numero. Il numero 4, ad esempio, potrebbe essere convertito a 8 bit ( ) oppure a 32 bit ( ) a seconda della rappresentazione necessaria; è quindi indispensabile che la funzione permetta di specificare il numero di bit richiesti per la conversione. Mentre per la funzione precedente il numero di bit era determinato dal bit_vector passato come parametro, in questo caso come dato di ingresso è necessario un numero intero e quindi è impossibile decidere automaticamente la grandezza della rappresentazione da utilizzare. La soluzione più conveniente è quella di accettare per riferimento un parametro bit_vector utilizzato per restituire il risultato al chiamante e per conoscere la lunghezza della rappresentazione binaria desiderata.
4 Progettazione in VHDL del Vertex Shader Pag Conversioni di quantità floating point e specifica IEEE Analogamente a quanto visto in precedenza è necessario fornire anche le funzioni di conversione da quantità in virgola mobile a rappresentazione binaria. Questa operazione non è però immediata e presenta problematiche maggiori rispetto alla semplice conversione di valori interi. Come prima cosa è chiaro che non è possibile garantire così facilmente la flessibilità della conversione; d altra parte esistono solamente 3 formati in virgola mobile che sono standard IEEE, quindi basterebbe realizzare una coppia di funzioni per ognuno di essi. La conversione non è comunque banale ed è legata alla rappresentazione utilizzata. Per non aumentare la complessità in modo eccessivo verrà utilizzato un sottoinsieme ridotto delle specifiche IEEE dettate da Intel, in particolar modo verranno fatte delle semplificazioni, che sono in parte derivate dal dover fare una descrizione behavioural evitando di scendere troppo a basso livello. Si presuppone che il lettore sia in qualche modo familiare con il formato Floating Point, in caso contrario l Intel fornisce ampia documentazione a riguardo nel suo sito internet, senza dover contattare direttamente la IEEE. La prima semplificazione è quella di limitare l utilizzo alla rappresentazione a singola precisione (32 bit), che è quella maggiormente utilizzata nella computer graphics in real-time, dove la velocità di esecuzione è il parametro fondamentale da rispettare. Questa assunzione non è in realtà una limitazione perché il Vertex Shader, come da specifiche, lavora proprio con questo tipo di dato. La seconda semplificazione è quella di considerare sempre la forma normalizzata del numero e non applicare il gradual underflow per rappresentare numeri prossimi allo zero. Anche questa assunzione non comporta grossi problemi, perché l approssimazione fatta è molto piccola. Un altro fatto che avalla questa decisione è che anche Intel, nel set di istruzioni SSE, permette di impostare un flag di round to zero per evitare la denormalizzazione, che naturalmente penalizza le prestazioni di una FPU, dato che necessita di ulteriori cicli di clock per denormalizzare il numero ed un controllo durante le operazioni sui numeri utilizzati. Come ultima semplificazione non si mantiene interamente la rappresentazione di numeri particolari come previsto dalla specifica IEEE (in questo modo si perde il significato dello zero con i due segni, delle rappresentazioni dell infinito e dei QNaN e SNaN). L implementazione di queste specifiche 33 richiederebbe di scendere ad un livello di progettazione troppo elevato. Il formato real utilizzato nel VHDL infatti non è neppure ben definito e la sua unica specifica è quella di accomodare al suo interno un numero espresso in singola precisione. L unica restrizione è quindi sull intervallo minimo rappresentabile, ma non viene neanche fornita limitazione sulla precisione richiesta. Probabilmente ogni simulatore effettua la scelta più conveniente che sicuramente risulta essere la rappresentazione del processore su cui gira il simulatore stesso. Sull architettura 80x86 viene quindi probabilmente utilizzata la rappresentazione 64 bit, ma questa è solamente una supposizione. 33 Presenti fin dall 8087.
5 Progettazione in VHDL del Vertex Shader Pag Breve descrizione del formato single precision floating point Il formato single precision floating point utilizza 24 bit per la mantissa, 8 per l esponente ed uno per il segno. Sommando queste quantità si ottiene 33 bit, ma bisogna considerare che il 24 bit della mantissa è per default considerato pari ad 1, ovvero la mantissa assume sempre la forma 1xxxxxxx xxxxxxxx xxxxxxxx dove le x rappresentano cifre binarie. La mantissa quindi codifica le prime 23 cifre dopo la virgola, assumendo che la parte intera sia pari ad 1. Con la sola mantissa quindi si possono rappresentare numeri da 1 a 2 (con 2 non compreso). Il bit più significativo della rappresentazione è il bit di segno relativo alla mantissa e vale 1 per numeri negativi e 0 per numeri positivi. Questo tipo di rappresentazione è chiamata complemento a uno ed ha la strana caratteristica di avere due rappresentazioni distinte per lo zero: +0 e 0. Questa apparente stranezza viene invece sfruttata nei processori odierni per distinguere quando si è ottenuto uno zero da un arrotondamento di una quantità negativa o da una positiva. Per rappresentare numeri minori di 1 e maggiori di 2 si utilizza l esponente, che viene memorizzato in 8 bit con il metodo excess-127, ottenuto aggiungendo 127 al vero valore dell esponente. Il range di esponenti ammesso sarebbe quindi [ 127 (rappresentato da 0), +128 (rappresentato da 255)]; questi due valori estremi vengono però utilizzati per codificare numeri particolari 34 e quindi in pratica gli esponenti ammessi sono compresi nell intervallo [-126, +127]. Il range di valori ammissibili per un numero in single precision è quindi approssimativamente compreso nell intervallo [2-126, ] che in decimale diviene [1.18E-38, 3.40E38]. L ordine in cui le tre componenti appaiono nel formato è segno-esponente-mantissa. Si supponga ora di voler calcolare la precisione in cifre decimali di questa rappresentazione. Dato che la mantissa è costituita da 24 bit, di cui 23 dopo la virgola, il più piccolo numero decimale positivo rappresentabile con essa è 2-23 ovvero 0, , quindi la precisione ottenibile è leggermente maggiore di 6 cifre decimali Funzioni di conversione: da rappresentazione binaria a reale Convertire dalla rappresentazione binaria a quella reale non presenta eccessive difficoltà, la funzione che realizza questa conversione viene qui di seguito riportata: function bits_to_float (Num : in bit_vector) return real is 0); variable Temp : real := 1.0; variable result : real; variable exponent : integer; variable T : bit_vector(7 downto 0); alias NumExp : bit_vector(7 downto 0) is Num(30 downto 23); alias Mantissa : bit_vector(22 downto 0) is Num(22 downto --0) Controllare il caso particolare dello zero if Num = X"0000_0000" then return 0.0; 34 Come: infinito, SNaN e QNaN.
6 Progettazione in VHDL del Vertex Shader Pag ) Recuperare l'esponente del numero in virgola mobile for ii in T'range loop T(ii) := Num(ii + 23); end loop; exponent := bits_to_intu(t); exponent := exponent - 127; --2) Trovare il valore della mantissa -- Il numero nella forma normale è 1,xxxxxx result := 1.0; for Index in Mantissa'range loop Temp := Temp / 2.0; --Creo le potenze di 2 negative if Mantissa(index) = '1' then result := result + Temp; end loop; --3) Moltiplicare per l'esponente e mettere il segno result := result * (2.0 ** exponent); if Num(Num'left) = '1' then Result := result * (-1.0); return result; end bits_to_float; Come prima cosa, l utilizzo degli alias in VHDL permette di identificare facilmente le varie parti del formato. La prima parte che viene elaborata è l esponente; dato che si sono trascurati i risultati denormalizzati, i QNan e le altre rappresentazioni particolari, è possibile semplicemente sottrarre 127 dal valore rappresentato per ottenere l esponente desiderato. L esponente viene convertito con una versione unsigned dalla funzione di conversione binariointero perché è appunto memorizzato senza segno. Se si fosse utilizzata la normale funzione di conversione, un esponente con valore 100 (memorizzato con il numero 227) sarebbe convertito in -29 e sottraendo ancora 127 si otterrebbe l esponente -156, palesemente non corretto. Successivamente si trova il valore della mantissa, considerando che il numero è nella forma 1.xxxxx poiché si sono trascurate le rappresentazioni denormalizzate. Il suo valore viene quindi inizializzato a 1.0 e poi vengono aggiunte le corrispondenti potenze di due se la cifra della rappresentazione è pari ad 1. Infine si applica l esponente realizzato dalla funzione Exp Conversione da real a rappresentazione binaria. Questa conversione è sostanzialmente più difficile della precedente, anche perché la conversione da decimale in binario è differente per la parte intera e per quella dopo la virgola. In più bisogna cercare la prima cifra della rappresentazione diversa da 0 e calcolare in base alla sua posizione l esponente, assicurandosi contemporaneamente di avere calcolato abbastanza cifre per completare i 23 bit assegnati alla mantissa. La funzione non viene pertanto riportata nella sua completezza, ma una sezione per volta in modo da poter commentare in modo approfondito i vari passaggi. function float_to_bits2 (Num : in real) return bit_vector is variable ParteIntera : integer; variable Numero : real := Num; 35 Si sarebbe potuto anche utilizzare l operatore **.
7 Progettazione in VHDL del Vertex Shader Pag. 81 variable result : bit_vector(31 downto 0); variable ParteIntera_Bits : bit_vector(31 downto 0); variable ExponentPart : bit_vector(7 downto 0); variable Index : integer := 22; variable ParteReale : real; variable Exponent : integer := 0; variable Position : integer; --Questo caso particolare viene trattato altrimenti viene --calcolato erratamente l esponente if Numero = 0.0 then return X"0000_0000"; --conversione float --> rappresentazione binaria --1) Determinazione del bit di segno if Numero < 0.0 then result(31) := '1'; Numero := Numero * (-1.0); else result(31) := '0'; Nell intestazione vengono dichiarate tutte le variabili necessarie alla funzione; in questo caso, a differenza della conversione con interi, è possibile evitare l utilizzo di una procedura perché si conosce a priori il numero di bit utilizzati dalla rappresentazione. Come prima cosa si controlla il segno del numero da rappresentare e si setta in modo corrispondente il 32 bit della rappresentazione. Nel caso di numeri negativi la quantità viene moltiplicata per 1 in modo da assicurarci che nelle parti successive dell algoritmo il numero da convertire sia sicuramente maggiore di zero. while Numero > real(integer'high) loop Numero := Numero / 2.0; Exponent := Exponent + 1; end loop; Per convertire correttamente il numero si ha la necessità di separare la parte intera da quella frazionaria; il VHDL purtroppo non mette a disposizione funzioni come la floor del linguaggio C, ma solamente l operazione di conversione da un tipo ad un altro. È possibile quindi eseguire la conversione da un numero reale ad uno intero, ma bisogna tenere in considerazione che una quantità intera non può superare il valore di , considerando la rappresentazione a 32 bit con segno. Per superare questa limitazione è possibile dividere per due il numero da convertire, aumentando contemporaneamente l esponente, in modo da far rientrare la parte intera del numero reale nel range ammesso da una variabile intera. In questo modo non si perde nessuna cifra significativa perché quelle che vengono scartate dallo shift a destra sono sicuramente oltre la 23 a cifra della mantissa e verrebbero comunque scartate dalla rappresentazione. Questa tecnica non funziona però se si vuole passare alla rappresentazione a doppia precisione. In realtà molti programmi, tra cui il ModelSim, possiedono delle librerie che implementano il set di funzioni standard per lavorare con quantità real; la realizzazione di questa funzione voleva però essere la più generale possibile in modo da non dipendere dalla rappresentazione. ParteIntera := integer(numero - 0.5); ParteReale := Numero - real(parteintera); if (ParteIntera /= 0) then int_to_bits(parteintera, ParteIntera_Bits); for itemp in ParteIntera_Bits'range loop
8 Progettazione in VHDL del Vertex Shader Pag. 82 if ParteIntera_Bits(iTemp) = '1' then Exponent := Exponent + itemp; Position := itemp - 1; exit; end loop; while (Position >= 0) and (index >= 0) loop result(index) := ParteIntera_Bits(Position); Position := Position - 1; index := index - 1; end loop; else --non ho nulla come parte intera. while ParteReale < 1.0 loop ParteReale := ParteReale * 2.0; Exponent := Exponent - 1; end loop; ParteReale := ParteReale - 1.0; Dopo aver separato la parte intera da quella frazionaria è necessario verificare se la parte intera è diversa da zero. In questo caso la si converte in binario con l usuale funzione, senza preoccuparsi del segno perché la quantità è sempre positiva e minore di Si cerca poi la prima cifra pari ad 1 nella rappresentazione binaria e si calcola il corrispondente esponente, considerando che la virgola va spostata dopo quella cifra. Usciti dal ciclo for si copiano semplicemente le cifre nella rappresentazione binaria. Se la parte intera è pari a zero allora bisogna iniziare a convertire la parte decimale alla ricerca della prima occorrenza della cifra 1 ; come è noto la conversione della parte decimale viene effettuata mediante moltiplicazioni successive per 2. In ogni iterazione quindi si diminuisce l esponente di uno perché si deve spostare la virgola di una ulteriore posizione a destra. Una volta trovata la cifra si può proseguire, ricordando di togliere 1 come vuole l algoritmo di conversione per la parte frazionaria di un numero da decimale a binario. if Index >= 0 then --Ho ancora bit da riempire nella mantissa while Index > 0 loop ParteReale := ParteReale * 2.0; if ParteReale > 1.0 then ParteReale := ParteReale - 1.0; result(index) := '1'; else result(index) := '0'; Index := Index - 1; end loop; --while ParteReale := ParteReale * 2.0; if ParteReale > 0.5 then result(0) := '1'; else result(0) := '0'; Sia che la parte intera esista oppure no, se la variabile index (che rappresenta la posizione corrente nella mantissa) non è minore di zero vuol dire che la mantissa non è stata ancora completata e quindi bisogna proseguire nella conversione della parte decimale. In questo caso l ultima cifra viene lasciata come caso particolare perché è necessario effettuare un arrotondamento. Si prosegue quindi con la conversione della parte frazionaria fino a che tutte le cifre della mantissa non sono state riempite correttamente.
9 Progettazione in VHDL del Vertex Shader Pag. 83 Exponent := Exponent + 127; int_to_bits(exponent, ExponentPart); for itemp in ExponentPart'range loop result(itemp + 23) := ExponentPart(iTemp); end loop; return result; end float_to_bits2; Infine si converte in binario l esponente dopo aver fatto l excess 127. In questo caso non ci si preoccupa del segno perché il valore Exponent è solamente positivo e per come è fatta la funzione int_to_bits non ci sono problemi. A questo punto si può pensare di realizzare una archittettura behavioural di una FPU utilizzando il VHDL. La realizzazione delle fuzioni base è ora particolarmente banale perché basta convertire le rappresentazioni in float, eseguire le operazioni richieste tra variabili float e convertire di nuovo il risultato Test delle funzioni di conversione e primi passi con il ModelSim Per testare in maniera appropriata le funzioni di conversione, realizzate nel precedente paragrafo, è necessario utilizzare un entità test, sempre relizzata in VHDL. La soluzione migliore è creare un entità che accetti come input la rappresentazione binaria di un numero floating point e resistuisca in output la stessa rappresentazione, dopo averla convertita prima in real e riportata in binario con le apposite funzioni. Se il risultato di uscita è lo stesso di quello di ingresso si può affermare con sufficiente certezza che le funzioni realizzate lavorano in modo corretto. Per effettuare la simulazione è necessario avere a disposizione un programma di simulazione per VHDL; la nostra scelta è caduta sul ModelSim perché supporta la sintassi standard e non altri dialetti come l AHDL dell Altera. La limitazione di questo tool è che nella sua versione base non può sintetizzare la logica all interno di una FPGA, ma questa limitazione non è troppo stringente dato che i normali tool per FPGA (ES. Quartus II dell Altera) non permettono di sintetizzare logica floating point direttamente utilizzando quantità real, perché comunque troppo complessa. Il progetto quindi non può essere simulato fino a che non verrà data una descrizione della ALU floating point che possa essere sintetizzata e che quindi lavori direttamente con le rappresentazioni binarie Lavorare con il ModelSim Per la simulazione il file di progetto, così come i sorgenti VHDL, si trovano nella cartella FloatConversion. Nel presente paragrafo però si spiegherà come creare dal nulla un progetto in modo da familiarizzare con l interfaccia del programma, che non è proprio user-friendly 36. Come prima cosa si crea un nuovo progetto con l apposito comando; questo crea una 36 L interfaccia è migliorata moltissimo nella versione 5.5. L ambiente presenta comunque seri problemi di stabilità, pertanto, per il presente lavoro, è stata utilizzata la versione 5.4SE (Ver. d).
10 Progettazione in VHDL del Vertex Shader Pag. 84 subdirectory con il nome del progetto al cui interno è possibile trovare il file principale che ha estensione.mpf (MoselSim Project File). Ora si debbono creare i due file.vhd per la simulazione; per far questo è necessario visualizzare l editor vhdl mediante il menù File->Open->Open Source. Dall editor è possibile creare nuovi file VHDL oppure aprire file già esistenti, in questo ultimo caso però i file sorgenti debbono trovarsi all interno della cartella del progetto, altrimenti non possono essere simulati. L editor messo a disposizione non è esattamente quello che si dice un tool efficiente: manca della funzionalità Undo e non permette di fare step nelle istruzioni durante il debug. Per quest ultima operazione è però disponibile un editor leggermente differente richiamabile mediante il menù view->source, che purtroppo non è collegato al compilatore per l individuazione immediata delle righe che danno errori in compilazione. Personalmente consiglio di utilizzare un editor più serio come UltraEdit32 che permette anche di definire una sintassi per fare l hilight delle keyword di un linguaggio e si rivela sicuramente più utile degli editor integrati, perlomeno nel supporto della funzione Undo. Per compilare i file, l editor presenta una voce del menù file che non fa altro che aprire la finestra di compilazione, dalla quale è possibile selezionare e compilare qualsiasi file VHDL presente nella cartella. Eventuali errori rilevati in fase di compilazione vengono riportati nella finestra principale, dalla quale, mediante doppio click, è possibile passare direttamente alla riga che ha causato l errore. Tutti i file compilati con successo aggiungono le loro entità alla libreria corrente che per default viene chiamata work. Il file con le funzioni di conversione contiene tutte le funzioni che sono state descritte precedentemente, racchiuse dentro una struttura package; in questo modo possono essere utilizzate da qualsiasi altra entità che fa parte del progetto corrente. Il file di test ha infine una struttura molto semplice e serve solamente per creare una entità che possa richiamare le funzioni da testare dal package realizzato Strutturazione di un file di test in VHDL Come prima cosa è necessario realizzare una entità che faccia uso delle funzioni da testare: use work.alu_fp.all; entity TEST is port (A : in bit_vector(31 downto 0); B : out bit_vector(31 downto 0)); end TEST; architecture only of TEST is main : process (A) variable Temp : real; Temp := bits_to_float(a); B <= float_to_bits(temp) after 10 ns; end process; end only; L entità generata accetta la DWORD A in ingresso, la trasforma in real e poi ancora in DWORD; il risultato viene poi assegnato alla porta di uscita. In questo modo il corretto funzionamento delle funzioni di conversione viene evidenziato dalla eguaglianza tra i segnali A e B.
11 Progettazione in VHDL del Vertex Shader Pag. 85 L entità testalu invece si occupa di fornire i dati in ingresso all entità TEST. entity testalu is end testalu; architecture tester of TestALU IS component TEST port( A : in bit_vector(31 downto 0); B : out bit_vector(31 downto 0) ); end component; --i segnali sono fatti perché il tester non ha nessuna porta signal A : bit_vector(31 downto 0); signal B : bit_vector(31 downto 0); signal clock : bit; --tester ALU1 : TEST port map ( A => A, B => B ); generatore_clock : process --I processi sono tutti concorrenti clock <= '0'; wait for 50 ns; clock <= '1'; wait for 50 ns; end process; test_bench : process variable state : integer := 0; wait until (clock'event and clock = '0'); case state is when 0 => A <= " "; -- 0, when 1 => A <= " "; ,5 when others => end case; state := state + 1; end process; end tester; L entità testalu ha un clock interno ed è essenzialmente una macchina a stati finiti. Lo stato viene incrementato ad ogni colpo di clock, si realizzano quindi vari stati in cui l unica operazione effettuata è quella di fornire i dati in ingresso alla nostra entità da testare. In questo caso vengono forniti in ingresso solamente due numeri float (0, e ,5). Un altro modo molto comune di procedere è includere nella entità di test i risultati corretti precalcolati. In questo modo, mediante l istruzione assert, è possibile rilevare immediatamente errori nel funzionamento dell entità sotto test. È comunque consigliabile verificare manualmente i risultati e fare un po di debug per controllare che tutte le strutture funzionino nel modo previsto. Per verificare la validità della conversione è stato anche utilizzato un piccolo tool realizzato in Visual Basic 6 che permette di risalire dalla rappresentazione binaria di un numero al suo valore floating point e viceversa.
12 Progettazione in VHDL del Vertex Shader Pag Simulazione Quando i due file compilano senza errori è possibile partire con la simulazione. Per fare questo è necessario decidere quale entità simulare tra quelle presenti nella libreria; nel caso in esame la scelta ovvia cade sul componente TestALU. Per aprire la finestra di dialogo che permette di scegliere cosa simulare è possibile utilizzare il menù Design -> Load Design, oppure il simbolo di apri file che appare accanto all icona di compilazione. Dovrebbe apparire ora la seguente finestra. Le uniche due entità nella libreria in questo caso sono solamente quelle presenti nel file di test, dato che non si sta testando una vera entità, ma solamente un set di funzioni. Si sceglie la architettura tester della entità testalu e si preme il tasto load. Ora è necessario aprire dal menù View le finestre wave e signals e trascinare i segnali di cui si vuole osservare l evoluzione dalla seconda nella prima, che appare inizialmente vuota. Per osservare tutti i segnali è possibile sezionarli tutti mediante il menu Edit -> Select all e poi trascinarli nella finestra wave. Un metodo alternativo è utilizzare il menu View -> Wave -> Signals in region. Dato che le quantità A e B sono vettori di 32 bit, non è comodo visualizzare la loro rappresentazione binaria. Per cambiare il modo in cui i valori vengono rappresentati nel visualizzatore è sufficiente selezionare un segnale, premere il tasto destro e scegliere il formato desiderato dal menù radix. Per questo esempio è conveniente visualizzare i segnali A e B in formato esadecimale, per un più facile confronto. Ora è tutto pronto per incominciare la simulazione. Il menù che consente di far partire la simulazione è rappresentato nella figura sottostante: Partendo da sinistra troviamo il tasto restart che serve a far ripartire da
13 Progettazione in VHDL del Vertex Shader Pag. 87 zero la simulazione. Successivamente c è una textbox che permette di decidere la durata di un singolo intervallo di simulazione e di seguito c è il tasto per far avanzare la simulazione di un intervallo. Per la simulazione corrente si cambi tale valore in 200 ns in modo da simulare due colpi di clock per ogni intervallo. I tasti più a destra servono per simulare ininterrottamente fino a che non si preme il tasto di stop, mentre gli ultimi due si utilizzano solamente quando si effettua il debug del codice. Se si preme due volte il tasto del run, si osserva che nella finestra wave le forme d onda dei segnali sono immediatamente aggiornate per mostrare i dati della simulazione. L output dovrebbe essere il seguente: La simulazione conferma che le funzioni sono state realizzate correttamente; naturalmente prima di affermare che il funzionamento è corretto è sicuramente necessario provare molti casi ed eseguire una sessione di debug Debug Come tutti i linguaggi di programmazione seri, il VHDL permette di eseguire il debug del codice. Per debug si intende la possibilità di interrompere l esecuzione del programma in punti ben definiti ed osservare il valore delle variabili e dei segnali presenti nella procedura, processo o funzione in esame. È altresì possibile eseguire una istruzione per volta (modalità step), in modo da controllare il corretto flusso di istruzioni, anche se in un processo le istruzioni vengono eseguite in maniera concorrente. Il primo passo da fare è mettere un BreakPoint (Punto di interruzione) nel programma; questa operazione viene effettuata semplicemente facendo click con il tasto sinistro del mouse vicino al numero di riga di codice sulla quale si vuole settare un breakpoint 37. Se il breakpoint viene settato correttamente appare un pallino rosso a fianco del numero di riga, un altro click fa apparire un cerchietto (vuoto all interno), che indica che il breakpoint in quella linea è stato disabilitato; per abilitarlo di nuovo è sufficiente fare un altro click. Per eliminare del tutto il breakpoint è necessario premere il tasto destro e scegliere la voce Remove Breakpoint dal menù a tendina che appare. Si faccia ora il reset della simulazione, si posizioni un breakpoint sulla riga del file di test e si faccia ripartire la simulazione; questa volta l elaborazione si interrompe e nella finestra principale appare la scritta: # Break at D:/.../FloatConversion/TestALU.vhd line 13 Se ora si apre la finestra denominata variables, si possono osservare le variabili interne al processo corrente, si nota inoltre che nella finestra del 37 Il ModelSim evidenzia, rappresentandole in verde, le righe sulle quali è possibile porre un breakpoint. 38 La riga contiene il codice: B <= float_to_bits(temp) after 10 ns;
14 Progettazione in VHDL del Vertex Shader Pag. 88 sorgente è apparsa una freccia azzurra, che indica l istruzione successiva. A questo punto è possibile proseguire l esecuzione istruzione per istruzione, mediante gli ultimi due comandi presenti nella barra degli strumenti relativa alla simulazione. Vale la pena ricordare che per utilizzare la funzione di step è necessario aprire l editor con il menù View->Source. Premendo di nuovo il tasto run l esecuzione riprende fino a fermarsi dopo il passaggio dei 200 ns impostati come passo base della simulazione. Facendo ripartire la simulazione, l esecuzione si arresta nuovamente nel punto di interruzione; nella finestra variables viene ora rappresentata l unica variabile interna al processo corrente, chiamata Temp. Si può verificare, come ulteriore conferma della correttezza delle funzioni di conversione implementate, che tale variabile assume proprio il valore floating point imposto alla variabile A, come rappresentato nella figura precedente La struttura ALU_FP Una volta terminata la verifica delle funzioni di conversione è possibile utilizzarle per implementare una ALU floating point mediante architettura behavioural. Nella prima simulazione si utilizzeranno le funzioni matematiche definite dallo standard IEEE, che sono racchiuse nella libreria Math_Real, analoghe a quelle contenute nella libreria math.h del linguaggio C. L utilizzo della Math_Real consente di effettuare le principali operazioni matematiche con numeri reali. La definizione della entità ALU_FP viene così effettuata: entity ALU_FP is generic (Dly : Time := Delay); port ( op1 : in DWORD; op2 : in DWORD; res : out busdword; cmd : in ALU_Command; CC : out CondCode; clk1 : in bit; clk2 : in bit; enabled : in bit; use_temp : in bit; ready : out bit ); end ALU_FP; La scelta delle porte è ovvia: si hanno due porte per gli operandi e due porte per i due segnali di clock, una porta in cui viene indicata l operazione da effettuare, chiamata cmd, ed una porta di uscita in cui viene fornito l esito delle operazioni di confronto. Per funzionare correttamente è necessario inserire anche un bit di enabled che permette di disabilitare dall esterno l ALU ed infine
15 Progettazione in VHDL del Vertex Shader Pag. 89 un bit di ready per capire quando l esecuzione dell operazione corrente è terminata. In una ALU floating point infatti il tempo di esecuzione varia molto da istruzione a istruzione. La definizione dei tipi di dato busdword, ALU_Command, CondCode sono stati preventivamente definiti all interno di un package di cui si riporta solamente la parte significativa. type ALU_Command is ( add, mul, floor, log, exp, sqrt, rcp, cmp); subtype DWORD is bit_vector (31 downto 0); subtype WORD is bit_vector (15 downto 0); subtype BYTE is bit_vector (7 downto 0); subtype CondCode is bit_vector(1 downto 0); type ADWORD is array (integer range <>) of DWORD; function resolve_dword (driver : in ADWORD) return DWORD; subtype busdword is resolve_dword DWORD; La funzione di risoluzione per il bus DWORD è semplicemente un OR perché si sta lavorando con segnali bit_vector; ben più complessa sarebbe stata la situazione se si fosse utilizzata la std_logic, ma per una prima simulazione l utilizzo di bit_vector va più che bene. Il bit use internal serve semplicemente ad utilizzare come primo operando il risultato della operazione precedente, questo viene fatto mediante un registro interno che tiene traccia del risultato dell elaborazione appena conclusa. Questa tecnica viene in aiuto per l esecuzione di operazioni composte come il reciproco della radice quadrata 39. L entità ha naturalmente un solo processo al cui interno vengono implementate tutte le operazioni che possono essere svolte: ALU_operate : process variable fop1, fop2 : real; variable Comp_factor : integer := 2; variable TempRes : real; type ALU_state is (idle, working); variable current_state : ALU_state; wait until enabled'event and enabled = '1'; Comp_factor := 2; ready <= '0' after dly; if use_temp = '1' then fop1 := TempRes; else fop1 := bits_to_float(op1); fop2 := bits_to_float(op2); La variabile Comp_factor serve a simulare la differente velocità di esecuzione delle varie operazioni: è risaputo infatti che un operazione di add è molto più veloce di un operazione di radice quadrata o trigonometrica. Naturalmente nella implementazione realizzata non esiste il concetto di pipeline e quindi le istruzioni possono essere richieste una per volta. Nei processori moderni questo non è vero e solitamente è possibile richiedere 39 In alcune situazioni (SSE di Intel) l implementazione di istruzioni molto complesse come la radice quadrata può essere effettuata semplicemente utilizzando una look-up table. Se la precisione ottenuta non è soddisfacente solitamente viene utilizzato l algoritmo di Newton-Raphson.
16 Progettazione in VHDL del Vertex Shader Pag Utilizzare file con il VHDL all ALU una operazione per ogni colpo di clock anche se l elaborazione di una istruzione necessità più di un colpo di clock per essere eseguita. La prima operazione che viene effettuata è quindi la conversione degli operandi dalla rappresentazione binaria al valore real corrispondente. Per il primo operando, come detto in precedenza, è possibile anche utilizzare il risultato della operazione precedente, memorizzato nel registro TempRes. Successivamente si esegue l operazione richiesta: case cmd is when add => --semplice addizione tra i due operandi TempRes := fop1 + fop2; --when sub => --semplice addizione tra i due operandi --TempRes := fop1 - fop2;... when exp => --elevamento a potenza 2 ^ fop1 TempRes := 2 ** fop1; Comp_factor := 20; Tutte le istruzioni vengono implementate in questo modo ed è possibile utilizzare la variabile Comp_factor per simulare una maggiore complessità della funzione. end case; if cmd /= idle then res <= float_to_bits(tempres) after Dly * Comp_factor; ready <= transport '1' after Dly * Comp_factor; end process; Quando l esecuzione è terminata, il risultato viene messo nella porta di uscita ed il bit di ready viene posto ad 1 per indicare il completamento dell istruzione. Naturalmente una ALU reale non è sintetizzabile con una rete combinatoria, ma il suo funzionamento sarà regolato da uno o più segnali di clock. Nondimeno la semplice struttura realizzata permette di avere una entità che svolge operazioni Floating Point e risponde non immediatamente, ma con un ritardo fittizio. In questo modo si è sintetizzato in maniera soddisfacente il comportamento di una ALU reale. Terminata la struttura è possibile costruire una semplice entità di test che verifica il corretto funzionamento dell ALU Simulazione mediante testbench automatici Per testare il dispositivo realizzato è possibile effettuare una simulazione e verificare che i segnali in uscita siano quelli corretti. Per semplificare questo processo è possibile scrivere un testbench automatico, il cui scopo è appunto quello di automatizzare le operazioni di test. Il compito di questo oggetto è leggere dei dati di input da file, applicarli all entità da testare e verificare i risultati ottenuti con quelli corretti, che sono stati preventivamente inseriti in un altro file. Per evidenziare le differenze tra i dati corretti e quelli effettivamente ottenuti è possibile utilizzare delle istruzioni ASSERT, oppure creare un file di testo in cui viene fatto il log di tutta la simulazione e che verrà
17 Progettazione in VHDL del Vertex Shader Pag. 91 controllato una volta terminata l esecuzione. È quindi necessario avere familiarità con le strutture di file in VHDL, che sono comunque semplici da utilizzare. Una struttura di tipo file si dichiara semplicemente nel seguente modo. type FHEX is file of integer; FILE myfile : FHEX IS OUT "values.dat"; Come prima cosa si specifica il tipo di file mediante l istruzione type e successivamente si dichiara un file di quel tipo indicando il nome dell oggetto (specificando se utilizzato in lettura o scrittura) ed infine il nome su disco. Il VHDL mette poi a disposizione le routine write e read che permettono di leggere e scrivere i valori di tipo base 40 nel file Scrivere float in un file Nel caso del Vertex Shader è necessario leggere e scrivere valori float in un file, purtroppo l unico tipo di dato floating point permesso dal VHDL è il tipo real, la cui precisione non è definita dallo standard. Se si prova infatti a fare output di valori real su di un file, ci si accorge che, come era facilmente intuibile, esso corrisponde al tipo in doppia precisione, formato standard di memorizzazione utilizzato nell architettura 80x86. Per effettuare operazioni di I/O di valori float su file è necessario allora provvedere alla conversione del numero in bit_vector (con le funzioni realizzate per l ALU) e successivamente convertire nuovamente il formato ottenuto in intero. In questo modo si ottiene la rappresentazione in float del numero cercato all interno di una variabile intera. Le funzioni che realizzano queste operazioni sono contenute nel file file_pack.vhd e la loro realizzazione è così semplice che non vale la pena riportarla in questa sede File di testo e libreria standard Durante il test automatico si ha la necessità di fare molto output in formato testo, questo perché è necessario tenere traccia di cosa accade nella simulazione e cosa molto importante, bisogna evidenziare gli eventuali errori. Ciò che serve è una funzionalità analoga a quella del printf del linguaggio C, che permetta di fare output di valori stringa e numerici e che permetta un minimo di formattazione per rendere l output leggibile. Lo standard VHDL non prevede però routine per lavorare con stringhe, ma queste funzionalità sono così richieste che sono state implementate nella libreria standard del ModelSim. Questa libreria si chiama textio e contiene funzioni utili per leggere e scrivere su file di testo. E comunque possibile fare output non in un file, ma nella finestra di output del simulatore mediante uso dell istruzione ASSERT.. SEVERITY NOTE. La libreria textio contiene un nuovo tipo di dato chiamato line, il quale non è altro che una stringa definita con l attributo access. In questo modo è possibile definire una stringa la cui lunghezza non è conosciuta a compile-time, ma decisa a run-time. Il tipo linea è quindi assimilabile ad un puntatore ad una stringa e nella libreria textio, ogni qualvolta si scrive qualche cosa nel dato 40 Integer, real, bit_vector, etc.
18 Progettazione in VHDL del Vertex Shader Pag. 92 linea, viene allocato e deallocato spazio in modo dinamico, mediante le funzioni New e Deallocate. La libreria contiene inoltre un set di funzioni in overloading che consentono di scrivere in una linea valori interi, floating point, booleani, etc. etc. Ricercando nelle librerie del ModelSim si trova nella cartella examples un file che introduce nuove funzionalità, tipo poter imporre una radice (binaria, decimale, esadecimale...) per i numeri interi che vengono stampati in una stringa. La libreria fornisce infine le funzioni readline e writeline che permettono di leggere e scrivere un dato di tipo linea su di un file di tipo text, che non è altro che un file di stringhe. Nella cartella di installazione del ModelSim è possibile reperire i sorgenti di tutte queste funzioni, che costituiscono quindi un buon esempio di come si lavori sui file e sui tipi di dato. Purtroppo non è possibile in VHDL fare liste di argomenti variabili per una funzione e questo rende impossibile realizzare una funzione analoga alla printf del C. Ci si deve allora accontentare di lavorare con le linee e realizzare la stringa di output un elemento alla volta, come mostrato nello spezzone di codice sottostante, che legge un intero da file e lo visualizza nel simulatore ad ogni ciclo del processo. USE std.textio.all; USE work.io_utils.all; entity ProvaFile is end ProvaFile; architecture Prova1 of ProvaFile is subtype DWORD is bit_vector(31 downto 0); type FHEX is file of integer; FILE myfile : FHEX IS IN "values"; try : process variable Num : integer; variable L : Line; if not endfile(myfile) then read(myfile, Num); else Num := 0; write (l, string'("il numero letto è ")); write (l, Num, base => binary); ASSERT FALSE REPORT L.all SEVERITY NOTE; wait for 110 ns; Deallocate(L); end process; end Prova1; Naturalmente bisogna aver compilato il file che contiene il package io_utils (presente nella cartella degli esempi del ModelSim) prima di compilare il programma appena presentato. L entità creata non fa altro che leggere sequenzialmente numeri interi da un file e visualizzare un messaggio nel simulatore in formato binario. Per visualizzare con ASSERT la linea creata è necessario utilizzare il suo campo all, che restituisce la stringa da visualizzare. Un esempio dell output che si ottiene da un file che contiene un solo intero è: # Loading work.io_utils(body) # Loading work.provafile(prova1) run # ** Note: Il numero letto è
19 Progettazione in VHDL del Vertex Shader Pag. 93 # Time: 0 ns Iteration: 0 Instance: /provafile run # ** Note: Il numero letto è 0 # Time: 110 ns Iteration: 0 Instance: /provafile run # ** Note: Il numero letto è 0 # Time: 220 ns Iteration: 0 Instance: /provafile VSIM 79> L intervallo di simulazione è impostato a 100 ns, mentre il processo ha una clausola wait che lo fa attendere per 110 ns. Se si preferisce è possibile inviare l output ad un file di testo, in modo da avere un log permanente dell esito della simulazione. In particolare è possibile realizzare un programma in C che genera automaticamente i file con i dati di test e alla fine della simulazione controlla nel file di output se si sono verificati degli errori. Con un poco di pratica è possibile realizzare strumenti di test veramente potenti ed automatizzati, che senza dubbio possono aiutare moltissimo durante la progettazione di un processore Differenze di versione Lo standard VHDL nel 1993 ha aggiornato la sintassi, in particolare la dichiarazione di file è cambiata notevolmente ed è bene conoscere le differenze principali per non avere problemi di compilazione. Si prenda in esame l entità presentata nel paragrafo 3.3.3; di seguito viene riportata la dichiarazione del file conforme al nuovo standard: FILE myfile : FHEX open write_mode is "values"; Al posto di IS OUT la sintassi è cambiata ed impone open write_mode, in questo modo è molto più chiaro che si sta lavorando su file e la sintassi è più coerente dato che OUT è corretto per una porta, ma non si adatta bene come attributo di un file. Una sintassi particolare rende inoltre molto semplice passare file come argomento di funzioni. Naturalmente per passare un file è necessario dichiarare il tipo di file, in modo da invocare correttamente le funzioni read e write del VHDL, che possono operare correttamente solo se in fase di compilazione si sa che tipo di dato deve essere letto/scritto. Per mostrare la sintassi viene riportata la funzione che permette la lettura di un valore floating point in singola precisione: function ReadFloat(FILE F : FloatFile) return real is Variable Temp : integer; Variable FloatNum : real := 0.0; Variable RappBin : DWORD; if not endfile(f) then read(f, Temp); int_to_bits(temp, RappBin); FloatNum := bits_to_float(rappbin); return FloatNum; end ReadFloat; Il funzionamento della routine è molto semplice e si serve di due conversioni successive per memorizzare nel file il numero in singola precisione.
20 Progettazione in VHDL del Vertex Shader Pag. 94 La doppia conversione è resa necessaria perché se si fosse chiesto al compilatore di leggere un numero di tipo real dal file, il risultato sarebbe stato un numero a doppia precisione. Per leggere correttamente un valore float è quindi necessario utilizzare questo piccolo trucco: leggere dal file il numero come se fosse un intero (32 bit), ottenere la sua rappresentazione binaria ed infine trasformarlo in un numero floating point.
21 Progettazione in VHDL del Vertex Shader Pag. 95 Capitolo 4. Implementazione del Vertex Shader 4.1. Descrizione della struttura Vertex Shader Da dove nasce il concetto di Vertex Shader (VS) Negli ultimi anni la grafica 3D ha compiuto passi da gigante e la continua innovazione dell hardware ha reso possibile la realizzazione di programmi e giochi impensabili fino a 5 anni fa. Il mondo del videogioco infatti è molto esigente dato che viene richiesta grafica 3D in real-time; al contrario, per realizzare una scena animata per un film, è possibile aspettare anche qualche giorno per calcolare il rendering di un solo minuto di animazione. Nella grafica real-time è invece necessario mantenere un frame rate dignitoso e calcolare quindi almeno 25 frame per secondo. Con questa grande limitazione il realismo della scena è limitato, poichè la complessità computazionale in molti casi è estremamente elevata per qualsiasi hardware oggi in commercio. Il primo vero esempio di gioco 3D fu Quake, che aveva un motore grafico completamente software. Le sue limitazioni erano: risoluzione non elevata, basso dettaglio delle texture, limitato numero di poligoni che potevano essere presenti contemporaneamente nel campo visivo. La prima ditta a mettere sul mercato una scheda veramente innovativa fu la 3Dfx che con la sua Voodoo1 cambiò radicalmente il modo di fare grafica. La scheda in questione infatti incorporava un chip dedicato all accelerazione 3D che permetteva di sollevare il processore da compiti gravosi come lo shading, il clipping ed il texturing. Il processore di sistema era così libero di svolgere altri compiti (calcolo collisioni, implementazione della fisica, ecc ) ed inoltre la scheda aveva hardware dedicato in grado di svolgere compiti come il Gouraud Shading in maniera estremamente più efficiente di un processore general purpose come un 80x86. Altri produttori seguirono successivamente questa strada, per prima l Intel con il suo chip i740 e poi altre case, tra cui spicca per i risultati ottenuti NVIDIA. La successiva grande innovazione si ebbe infatti con la scheda GeForce di NVIDIA, scheda che effettua in hardware anche la fase di Trasform & Lighting (T&L); in queso modo è possibile gestire un più elevato numero di poligoni e alleggerire ulteriormente il processore di sistema. La limitazione maggiore è che l hardware di tali schede utilizza solamente alcuni degli algoritmi di grafica 3D che sono stati sviluppati nel corso degli anni. Per il T&L infatti l unico algoritmo previsto è quello del Gouraud Shading (o a limite il Flat Shading) e non è possibile implementare algoritmi più sofisticati come il Phong Shading o l illuminazione BRDF (anisotropic lighting). L ultima rivoluzione in casa NVDIA è rappresentata dalla GeForce3 che contempla l implementazione in hardware del Vertex Shader e del Pixel Shader, che consentono una certa programmabilità della pipeline grafica. Con l aumento delle prestazioni infatti non è più impossibile realizzare un Phong Shading, ma è limitativo introdurre nella GPU algoritmi fissi e poco flessibili, perché in questo modo tutte le potenzialità della scheda non possono essere sfruttate al massimo. Meglio allora dare al programmatore la possibilità di un completo controllo della GPU, in questo modo chi vuole può implementare
22 Progettazione in VHDL del Vertex Shader Pag. 96 vari algoritmi di illuminazione ed effetti particolari oppure utilizzare il set di shading fisso che è comunque sempre presente. Il Vertex Shader è la parte programmabile della GPU che è preposta al T&L. Nelle schede con VS è possibile impostare le operazioni da effettuare sui vertici mediante uno pseudo linguaggio Assembly denominato Vertex Shader Assembly 41. Si può allora vedere il VS come una unità funzionale all interno della GPU, che accetta i parametri caratteristici dei vertici e restituisce in uscita i vertici trasformati ed illuminati. Vediamo allora in dettaglio come è possibile lavorare con un VS programmabile La struttura di un Vertex Shader La figura seguente mostra la struttura a blocchi di una GPU che possiede un VS programmabile. È quindi chiaro che il Vertex Shader sostituisce completamente lo stadio T&L normale di una GPU di generazione precedente, ciò significa che in uscita al Vertex Shader i vertici debbono essere stati trasformati, illuminati e proiettati. Nella figura a pagina seguente invece ne viene mostrata la struttura interna. Il VS ha un banco di 16 registri in ingresso, ognuno dei quali contiene un valore a 128 bit costituito da 4 valori float impacchettati 42. L effettiva implementazione in hardware può comunque essere molto differente. Un altro banco di registri (13 per la precisione) costituisce l output del Vertex Shader; in questi registri viene posizionato il risultato dell elaborazione sul vertice corrente. Sono presenti poi 12 registri temporanei chiamati r0-r11 e 96 registri di costanti c0-c95. Il Vertex Shader elabora dati presenti in questi 4 banchi di registri con delle restrizioni sul tipo di accesso che verranno spiegate in seguito. 41 Si parla di pseudo assembly perché il set di istruzioni non è associato effettivamente ad un preciso processore, ma riflette le operazione generiche necessarie per implementare algoritmi che operano su vertici. 42 Per chi ha familiarità con i processori Pentium III, questi registri sono formalmente identici ai registri XMM0-XMM7 presenti nell architettura SSE.
23 Progettazione in VHDL del Vertex Shader Pag. 97 INGRESSO V0.. V15 COSTANTI R0.. R11 TEMP C[0] C[95] USCITA I dati che provengono dall esterno sono: il codice del Vertex Shader, il valore delle costanti ed i vertici da elaborare. Il banco delle costanti è quindi di sola lettura per il Vertex Shader, mentre il banco dei registri temporanei non può essere acceduto dall esterno. Il codice e le costanti vengono caricati prima di elaborare un modello (mesh), successivamente vengono immessi tutti i vertici che compongono la mesh per essere trasformati. I dati di output sono prima elaborati dall algoritmo di clipping e poi ripresi dal Pixel Shader Vertex Shader in dettaglio I registri di ingresso sono denominati v0-v15, mentre le costanti vengono indicate con c[0]-c[95]; come detto in precedenza questi dati sono in sola lettura all interno del Vertex Shader, che non può quindi modificarne il contenuto ed inoltre possono essere referenziati una sola volta per istruzione. I registri temporanei r0-r11 possono essere utilizzati in lettura ed in scrittura e possono essere referenziati fino a 3 volte in una singola istruzione. Esiste inoltre un registro di indirizzamento indiretto chiamato a0 di cui si può solamente utilizzare la prima componente (a0.x) come displacement nell utilizzo delle costanti. Es. c[6 + a0.x]. Per i registri di output la situazione è leggermente differente perché ogni registro ha un preciso significato: opos: in questo registro vengono memorizzate le coordinate del vertice elaborato. od0: in questo registro viene memorizzata l informazione sul colore diffuso del vertice. Ogni componente del registro contiene una quantità float, vengono così memorizzate le 4 componenti del colore: RGBA. od1: in questo registro viene memorizzato il colore speculare del vertice. ot0-ot7: in questi registri vengono memorizzate le coordinate delle texture per tutti gli stadi di multitexturing.
24 Progettazione in VHDL del Vertex Shader Pag. 98 ofog: contiene il valore del fogging nel caso di vertex-fog. Si utilizza solamente la prima componente (x). opts: la grandezza del punto. Anche in questo caso si utilizza solamente la prima componente (x). Questo preciso ordine è necessario perché i dati di uscita debbono poter essere elaborati dal clipper che si aspetta di trovare i dati nella posizione corretta. Il codice che può essere impostato all interno del Vertex Shader ha come limitazione il numero massimo di istruzioni, pari a 128. L instruction set comprende istruzioni, come mostrato nella seguente tabella. Nella tabella la lettera d indica il registro di destinazione mentre la lettera s indica il registro sorgente. Mnemonic Add d, s0, s (Somma Component Wise) Dp3 d, s0, s1 (Dot product 3) Dp4 d, s0, s1 (Dot product 4) Description d.x = s0.x + s1.x d.y = s0.y + s1.y d.z = s0.z + s1.z d.w = s0.w + s1.w Dst d, s0, s1 d.x = 1; d.y = s0.y * s1.y d.z = s0.z d.w = s1.w Expp d, s0 Lit d, s0 (Lite) Logp d, s0 (Log 2 S0.w ) Mad d, s0, s1, s2 (Multiply & add) Max d, s0, s1 (Massimo Component Wise) Min d, s0, s1 (Minimo Component Wise) Mov d, s0 (Move CW) d.x = d.y = d.z = d.w = s0.x*s1.x +s0.y*s1.y + s0.z*s1.z d.x = d.y = d.z = d.w = s0.x*s1.x +s0.y*s1.y + s0.z*s1.z + s0.w*s1.w d.x = 2^floor(s0.w) d.y = s0.w floor(s0.w) d.z = 2^(s0.w) d.w = 1 d.x = 1 d.y = (s0.x > 0)? S0.x : 0 d.z = (s0.x > 0 && s0.y > 0)? s0.y ^ s0.w : 0 d.w = 1 d.x = d.y = d.z = d.w = (s0.w!= 0)? log(abs(s0.w))/log(2) : - d.x = s0.x*s1.x + s2.x d.y = s0.y*s1.y + s2.y d.z = s0.z*s1.z + s2.z d.w = s0.w*s1.w + s2.w d.x = (s0.x >= s1.x)? s0.x : s1.x d.y = (s0.y >= s1.y)? s0.y : s1.y d.z = (s0.z >= s1.z)? s0.z : s1.z d.w = (s0.w >= s1.w)? s0.w : s1.w d.x = (s0.x < s1.x)? s0.x : s1.x d.y = (s0.y < s1.y)? s0.y : s1.y d.z = (s0.z < s1.z)? s0.z : s1.z d.w = (s0.w < s1.w)? s0.w : s1.w d.x = s0.x d.y = s0.y d.z = s0.z d.w = s0.w 43 Se si considera anche la Nop che non è una vera e propria istruzione.
25 Progettazione in VHDL del Vertex Shader Pag. 99 Mul d, s0, s1 (Prodotto CW) d.x = s0.x * s1.x d.y = s0.y * s1.y d.z = s0.z * s1.z d.w = s0.w * s1.w Nop Rcp d, s0 (Reciproco S0.w) Rsq d, s0 (Radice quadrata S0.w) Sge d, s0, s1 (Store if greater or Equal) Slt d, s0, s1 (Store if less than) Sub d, s0, s1 (Sottrazione CW) d.x = d.y = d.z = d.w = (s0.w == 0)? : 1/s0.w d.x = d.y = d.z = d.w = (s0.w == 0)? : 1/sqrt(abs(s0.w)) d.x = (s0.x >= s1.x)? 1 : 0 d.y = (s0.y >= s1.y)? 1 : 0 d.z = (s0.z >= s1.z)? 1 : 0 d.w = (s0.w >= s1.w)? 1 : 0 d.x = (s0.x < s1.x)? 1 : 0 d.y = (s0.y < s1.y)? 1 : 0 d.z = (s0.z < s1.z)? 1 : 0 d.w = (s0.w < s1.w)? 1 : 0 d.x = s0.x - s1.x d.y = s0.y - s1.y d.z = s0.z - s1.z d.w = s0.w - s1.w La tabella precedente è stata ricavata dalla documentazione di NVIDIA; in realtà nella specifica di Microsoft sono presenti anche altre istruzioni che però non sono altro che macro espanse in due o più istruzioni base. Purtroppo ancora non si è giunti ad uno standard per il set di istruzioni che deve essere implementato e quindi per ogni produttore è necessario fare riferimento alle specifiche dei vari modelli di schede disponibili. Attualmente solo NVIDIA e ATI producono schede che supportano Vertex Shader e Pixel Shader programmabili, in futuro si spera che altri produttori si adeguino e si pervenga quindi ad uno standard. Per i registri di destinazione è possibile specificare una maschera per selezionare le componenti che devono essere modificate. Se si vuole ad esempio effettuare l operazione di mad e mettere il risultato nel registro opos solamente nelle componenti x ed y si usa la seguente istruzione: mad opos.xy, r0, v0, c0 In questo caso il risultato dell operazione mad viene memorizzato solamente nelle componenti x ed y del registro di destinazione; le componenti z e w rimangono naturalmente invariate. Per i registri sorgente è possibile effettuare invece uno swizzling completo delle componenti ed è anche possibile ripetere più di una volta la stessa componente. Es. dp3 opos, r0.xxxx, r2.xywz Infine è possibile negare con il segno - ogni componente sorgente. Infatti l istruzione sub, riportata in alcuni documenti di NVIDIA, è in realtà l istruzione add con il secondo registro sorgente negato, come viene confermato dal codice binario generato. D altra parte la sottrazione e l addizione sono esattamente la stessa operazione aritmetica.
26 Progettazione in VHDL del Vertex Shader Pag Decodificare gli opcode ed il formato binario Per realizzare e simulare efficacemente il Vertex Shader è necessario decodificare il formato binario delle istruzioni, in modo da poter utilizzare l assemblatore fornito con le DirectX8 o il NVASM fornito da NVIDIA. Quest ultimo in particolare fornisce l opzione listing (-l) che è di grande aiuto perché permette di visualizzare in un file il sorgente asm con accanto i corrispettivi opcode generati. Il formato delle istruzioni è veramente molto semplice dato che ogni DWORD nel codice riveste una specifica funzione. Il motivo per cui una intera DWORD venga utilizzata per codificare un instruction set di sole 18 istruzioni non è molto chiaro, probabilmente è da ricercarsi in motivi di performance ed allineamento dei dati. La prima DWORD identifica l istruzione da eseguire mentre nella DWORD successiva viene indicato il registro di destinazione, codificato nel seguente modo: Bit 7-0: indice del registro. Bit 19-16: maschera per il registro destinazione. Bit 31-28: identificativo del registro. Anche i registri sorgenti vengono codificati con una intera DWORD, ma la codifica è leggermente più complessa, perché si deve tenere conto di una possibile negazione, dell indirizzamento indiretto effettuato mediante a0.x (possibile nell utilizzo delle costanti) e dello swizzling. In questo caso i vari bit assumono il seguente significato: Bit 7-0: indice del registro. Bit 13: bit di indirizzamento indiretto (a0.x). Bit 23-16: maschera per lo swizzling. Bit 24: bit di segno. Bit 31-28: identificativo del registro. In particolare lo swizzling è codificato come qui di seguito spiegato. Ogni componente è identificata da due bit che hanno il seguente significato: 00 x, 01 y, 10 z, 11 w. In assenza di maschera specificata si assume sempre la maschera xyzw. Come esempio si riporta la codifica di una istruzione per mostrare il codice operativo risultante. add r0, -c[15 + a0.x], v f0000 a1e4200f 90e40000 Opcode add a (Codice registro) 1 (bit di cambio segno, bit 24) e4 (maschera.xyzw ) 20 (bit 13, ind. Indiretto a0.x) 0f (l indice del registro) 8 (Codice registro) 00 (non utilizzato) f (indica la maschera.xyzw) 00 (non utilizzato) 00 (l indice del registro)
27 Progettazione in VHDL del Vertex Shader Pag Simulazione del processo di programmazione Prima fase: la decisione di come strutturare l entità Come prima cosa è necessario decidere come strutturare l entità Vertex Shader. Per la prima simulazione è possibile implementare i registri interni con semplici variabili globali e quindi visibili in tutta l entità: in questo modo si semplifica la trattazione. Successivamente si sostituiranno le variabili con dei registri veri e propri per effettuare una simulazione più a basso livello. Si deve poi decidere l interfaccia dell entità, ovvero i suoi segnali di I/O: entity VShader is generic (Dly : Time := Delay); --definito const nel package port ( data_port : inout EIGHTWORD_BUS bus; --Porta dati read : in bit; write_en : in bit; write_sel : in sel; Addr : in bit_vector( 9 downto 0); clock_ph1 : in bit; enabled : in bit; ready : out BitBus bus; Program_mode : in bit ); end VShader; La prima porta che si incontra è del tipo EIGHTWORD_BUS, ovvero un bus composto da 4 DWORD. La scelta di questo tipo di porta discende immediatamente dalla grandezza dei dati che il Vertex Shader deve elaborare. Le quattro porte che seguono servono per la programmazione e per la lettura dei valori di output del Vertex Shader; il codice da eseguire infatti proviene dall esterno e deve essere caricato con un apposita procedura. Il bit Program_mode serve ad indicare al Vertex Shader se la modalità corrente è quella di programmazione o quella di esecuzione. Il bit ready serve a comunicare la fine dell elaborazione del vertice corrente durante l esecuzione e anche l avvenuta scrittura del dato durante la programmazione. Per ora il clock è solamente uno, dato che nella prima fase della simulazione non si è scesi ancora troppo a basso livello e quindi non è utile fin dall inizio fornire un clock bifase. Infine il bit di enabled serve per indicare al Vertex Shader se si è in una fase operativa oppure no Due processi distinti (programmazione, esecuzione) Come prima implementazione, l entità Vertex Shader contiene due processi distinti che regolano il funzionamento nei due differenti stati: programmazione ed esecuzione. Per una prima simulazione si realizzerà un disassemblatore, che ha internamente la stessa struttura di un Vertex Shader; in questo modo si verifica la correttezza del processo di programmazione e nell esecuzione si verifica la decodifica del formato binario del codice. È quindi necessaria una entità di test il cui scopo è leggere da un file compilato con il NVASM il codice binario, caricarlo all interno del Vertex Shader, che poi nel suo processo di esecuzione disassembla il codice e ne restituisce il risultato in un file di testo. Come prima cosa si esamina il processo di programmazione dalla parte del Vertex Shader e dell entità di test, che non rappresenta altro che
28 Progettazione in VHDL del Vertex Shader Pag. 102 l unità di controllo della GPU in cui il Vertex Shader opera Processo di programmazione Durante il processo di programmazione vengono caricati i registri interni del processore con dati provenienti dall esterno. La cosa migliore è realizzare un processo asincrono che comunica l avvenuta scrittura nella memoria mediante il segnale di ready. Questo modo di procedere è molto simile al ciclo di scrittura in una RAM, in effetti, se si riflette bene, si capisce che si sta effettuando proprio questo: scrivere all interno dei registri del Vertex Shader. All interno del processo di programmazione si trova la procedura Prog che in base ai segnali in ingresso carica i dati presenti nella porta di I/O all interno del pool di istruzioni, del vertice corrente oppure delle costanti. Il segnale write_sel permette di selezionare in quale banco di registri deve essere memorizzato il dato, mentre il segnale Address indica l indice del registro all interno del banco selezionato. La corrispondenza tra i valori del segnale write_sel e il tipo di dato da inserire è rappresentato nella tabella sottostante. Write_sel Componente selezionata 00 Pool istruzioni 01 Banco delle costanti 11 Vertice di ingresso procedure Prog(Address : in bit_vector( 9 downto 0)) is Variable TempAddr : integer := 0; --Durante la programmazione può essere scritto il --banco delle costanti --quello delle istruzioni oppure lo stream di vertici TempAddr := bits_to_int(address); case write_sel is when B"00" => --Programmazione del banco istruzioni ASSERT (TempAddr < 638) REPORT "Out of Range - Instr_pool" SEVERITY error; Instr_Pool(TempAddr) := DWord0; Instr_Pool(TempAddr + 1) := DWord1; Instr_Pool(TempAddr + 2) := DWord2; Instr_Pool(TempAddr + 3) := DWord3; when B"01" => --Programmazione banco costanti ASSERT (TempAddr < 96) REPORT "Out of Range - Const" SEVERITY error; Const(TempAddr) := Conv_8W_to_A4F(data_port); when B"11" => --programmazione del vertex stream ASSERT (TempAddr < 16) REPORT "Out of Range - Vertex_inl" SEVERITY error; V_in(TempAddr) := Conv_8W_to_A4F(data_port); when others => ASSERT (false) REPORT "Dati errati nella programmazione del VS" SEVERITY error; end case; end Prog; Il processo principale chiama questa funzione quando il segnale di write_en ha una transizione a livello alto ed il segnale di enabled è al valore 1. wait until(program_mode'event and Program_mode = '1' and enabled = '1'); assert false report "Inizio programmazione Vertex Shader"
29 Progettazione in VHDL del Vertex Shader Pag. 103 severity note; while Program_mode = '1' loop ready <= '0' after dly; Data_Port <= null after dly; wait until (read_en = '1') or (write_en = '1'); if write_en = '1' then Prog(Addr); Ready <= '1' after Dly; wait until write_en = '0'; elsif read_en = '1' then non implementata end loop; --Ora il Program_mode è a zero, preparo per l'esecuzione --del vertex_shader. ProgramCounter := 1; --inizializo il PC assert false report "Fine programmazione Vertex Shader" severity note; end process; Il segnale Program_mode ha lo scopo di selezionare la fase desiderata scegliendo tra quella di programmazione e quella di esecuzione. Il processo attende fino a che il segnale Program_mode non ha una transizione al livello alto, successivamente entra in un loop fino a che il Program_mode non torna al livello basso, segnalando la fine della programmazione e l inizio dell esecuzione. All interno del ciclo, il processo non fa altro che aspettare l inizio di un ciclo di lettura o scrittura; al disassemblatore naturalmente interessa solamente il ciclo di scrittura, dato che nessun dato viene effettivamente calcolato. Durante la scrittura si utilizza la procedura Prog per caricare i dati nei registri interni, si comunica l avvenuta scrittura mediante il bit di ready e si aspetta fino a che il segnale di scrittura non va a livello basso, indice che la GPU ha finito questo ciclo di scrittura, dopodiché si ritorna in attesa. Quando il program mode va a zero è necessario inizializzare il registro Program Counter; il suo valore iniziale deve però essere 1 e non 0 perché nella prima DWORD del codice non è contenuto il primo opcode istruzione, ma la versione del Vertex Shader utilizzata. Dall altra parte l entità di test (GPU) dopo aver settato correttamente i segnali di programmazione 44, entra anche essa in un ciclo per programmare il Vertex Shader, chiamando la funzione programshader: procedure ProgramShader is FILE ShaderDat : FloatFile open read_mode is "VS.vso"; variable TempEW : EIGHTWORD; variable TAddress : integer := 0; variable Address : bit_vector(9 downto 0); while not endfile(shaderdat) loop data_port <= ReadEightWord(ShaderDat) after dly; int_to_bits(taddress, Address); Addr <= Address after dly; write_en <= transport '1' after dly * 2; wait until ready = '1' and ready'event; write_en <= '0' after dly; TAddress := TAddress + 4; end loop; end ProgramShader; La procedura semplicemente legge una EIGHTWORD dal file 44 Tali segnali sono Write_sel, Program_mode.
30 Progettazione in VHDL del Vertex Shader Pag. 104 compilato con il NVASM e la posiziona nel bus di I/O del Vertex Shader (data port), imposta l indirizzo (Addr) e setta il bit di write_en, dopodiché attende che il processore comunichi l avvenuta scrittura del dato nel registro interno. A questo punto il write_en viene riportato a livello basso per comunicare al Vertex Shader che la scrittura del dato è finita, e si ricomincia da capo. Il motivo per cui nell assegnazione del valore 1 al segnale write_en si utilizzi un ritardo doppio è che necessariamente deve passare un po di tempo per recuperare il dato dal file (nella situazione reale è una lettura dalla memoria video). Naturalmente il delay dei segnali non è impostabile a livello hardware, ma dovrebbe discendere direttamente dall implementazione su silicio della logica utilizzata. Quando la simulazione verrà effettuata a livello strutturale un maggiore ritardo si traduce nell attesa del successivo colpo di clock Processo di dissassemblaggio Come prima cosa vengono dichiarate le variabili di processo che sono: un file di testo in modalità scrittura ed un dato di tipo line, che permette di realizzare la stringa da scrivere nel file mediante allocazione dinamica della memoria. Successivamente sono presenti le funzioni writedest e readsource che svolgono il compito di decodificare gli opcode dei registri. La prima di essa accetta come ingresso il codice del registro, una EIGHTWORD da scrivere nel registro indicato ed una stringa L per fare il disassemblaggio. Lo scopo di questa funzione è quello di decodificare il codice del registro, individuarlo e caricare i dati al suo interno. Per la simulazione del disassemblatore è solamente necessario scrivere nella linea il registro individuato e non eseguire ulteriori operazioni. La procedura readsource invece deve decodificare un registro sorgente e restituire il suo valore al chiamante; in questo caso si sarebbe potuta utilizzare una funzione, purtroppo la sintassi VHDL non permette il passaggio del dato di tipo linea in una funzione, ma solamente in una procedura. Il motivo della separazione delle strutture di decodifica dei registri è dovuto al fatto che per i registri sorgenti è previsto lo swizzling, l indirizzamento indiretto del banco costanti e la negazione del valore, mentre per i registri di destinazione è possibile solamente indicare la maschera. La fase di decodifica è quindi sostanzialmente differente. Il processo di esecuzione principale infine è sincrono con il clock anche se questo non è necessario ai fini della realizzazione di un disassemblatore. Ad ogni colpo di clock vengono identificati l istruzione ed i registri su cui essa opera, viene creata la linea di debug in cui viene scritta l istruzione disassemblata ed infine quest ultima viene scritta su di un file. Ad ogni iterazione il program counter viene incrementato in modo da puntare all istruzione successiva; l incremento non è naturalmente costante a causa della lunghezza differente che hanno le varie istruzioni. Ogni qualvolta che una istruzione viene riconosciuta e stampata, la linea utilizzata deve essere deallocata in modo da avere nell iterazione successiva una linea vuota.
31 Progettazione in VHDL del Vertex Shader Pag La simulazione La simulazione è ora veramente banale: si realizza un file con le istruzioni del Vertex Shader di nome vs.nvv, lo si compila e poi si mette il file vs.vso nella cartella del progetto. A questo punto si manda in esecuzione la simulazione e, leggendo l output dato dagli assert, si riconosce la fase di programmazione e di esecuzione dello shader. Al termine dell esecuzione si controlla che effettivamente nel file di uscita si abbia lo stesso file che si è compilato in precedenza. Come esempio viene mostrata la simulazione del seguente file nvv: vs.1.1 dp4 opos, v0, c25 mov a0.x, v0 dp4 r0, -c[45 + a0.x].wx, -v3 sub r1.xw, r0, v0.wzyy sub opts.x, r0.xzxx,r1 mul ofog.x, r0, r1 Controllando tutti i segnali nella finestra wave si nota che ci sono 3 cicli di clock in cui il segnale enabled è tenuto a 0 e lo shader è inattivo. Dopo 3 cicli inizia la programmazione, chiaramente visibile perché il processo è asincrono e perché viene annunciata nella finestra di output da istruzioni assert. Le forme d onda appaiono così: Si può facilmente riconoscere la fase asincrona della programmazione. Andando a visualizzare i valori nei vari istanti si vede come l entità test setti i valori corretti rispondendo al segnale di ready del Vertex Shader. Il segnale di ready si rende necessario perché non è conosciuto a priori il tempo richiesto per eseguire tutte le istruzioni impostate e quindi il Vertex Shader deve possedere un modo per comunicare all esterno quando la sua esecuzione è terminata. Facendo continuare la simulazione, la finestra wave non mostra più nulla di interessante perché tutto il lavoro di disassemblaggio avviene all interno della struttura VShader e i valori delle porte di output non cambiano. Se lo si desidera è possibile visualizzare anche i valori delle variabili interne al processo nella finestra wave. Per fare questo basta mettere un breakpoint nella routine o processo che contiene la variabile in esame e successivamente, tramite la finestra variables, selezionare i segnali che debbono essere visualizzati. Per questa simulazione è interessante vedere come l array di DWORD instr_pool, che contiene il codice dello shader, viene caricato con quattro valori per volta. Basta osservare i messaggi che compaiono nella finestra principale del programma per accorgersi della fine dell operazione di disassemblaggio. A questo punto si può osservare il risultato nel file di output disassembly.txt e verificare che sia identico al file sorgente originale che è stato compilato. Se in ingresso si ha il file binario compilato dal sorgente sopra mostrato si ha come
32 Progettazione in VHDL del Vertex Shader Pag. 106 risultato il seguente codice disassemblato: dp4 opos.xyzw, v0.xyzw, c[25].xyzw mov a0.x, v0.xyzw dp4 r0.xyzw, -c[45 + a0.x].wxxx, -v3.xyzw add r1.xw, r0.xyzw, -v0.wzyy add opts.x, r0.xzxx, -r1.xyzw mul ofog.x, r0.xyzw, r1.xyzw ;--- END SHADER --- Il codice è stato correttamente disassemblato anche se il file non corrisponde esattamente a quello iniziale. Le differenze sono da ricercarsi nel fatto che quando non si specificano maschere o swizzling nel file sorgente viene assunta come default la maschera xyzw, come viene spiegato nella documentazione delle DirectX8. Nel caso di uno swizzling del tipo wx, dato che i registri sorgenti sono sempre composti da quattro componenti, si assume la maschera wxxx. Ora che si sa come decodificare gli opcode è necessario realizzare una ALU che possa elaborare quantità EIGHTWORD e che possa eseguire le istruzioni del Vertex Shader Una ALU per il Vertex Shader Requisiti di base Nel capitolo precedente si è visto come realizzare una semplice ALU behavioural che lavora con valori float. Componendo quattro di queste ALU si può realizzare un entità in grado di svolgere le istruzioni presenti nell instruction set del Vertex Shader, ovvero elaborare quantità EIGHTWORD. Come prima cosa è necessario stabilire quali istruzioni base debbano essere svolte da questa ALU. Da una attenta analisi della tabella contenente l intero instruction set del Vertex Shader si è scelto di realizzare le seguenti operazioni di base: Add, mul, exp, rcp, sqrt, pow: operazioni di base con i numeri reali. Floor, floatpart: per reperire la parte intera e frazionaria del numero. La seconda in particolare è molto utile per l istruzione lit. Logp: è una istruzione complessa, che snellisce il codice. Pass1, pass2, const1, const0: servono per far passare inalterati gli operandi e per generare le costanti 1.0 e 0.0 richieste da molte delle istruzioni del Vertex Shader. Cmp: per effettuare dei confronti tra gli operandi. Le istruzioni sono generalmente operazioni di base sui numeri reali tranne la logp, che è stata implementata per risolvere l omonima istruzione. Alternativamente si sarebbe potuta implementare la semplice istruzione log e poi utilizzarla per calcolare la più complessa logp, ma dato che nessun altra istruzione richiede il logaritmo questa scelta snellisce la struttura. Per alcune operazioni è necessario utilizzare il risultato precedente come primo operando; a tale scopo la ALU può essere dotata di un registro interno
33 Progettazione in VHDL del Vertex Shader Pag. 107 che mantiene il valore dell ultimo risultato trovato. Un bit in ingresso indica poi se per l operazione corrente si utilizza normalmente il dato presente nella porta uno oppure il registro temporaneo. Per ottenere una simulazione più realistica è possibile implementare il diverso tempo di esecuzione delle istruzioni mediante ritardi differenti; è chiaro infatti che una istruzione di add è sicuramente più semplice dell estrazione di una radice quadrata. L ALU è costituita da un solo processo che si attiva alla transizione del segnale Enabled al valore 1 ; al termine dell operazione il segnale ready viene portato al valore 1 per indicare che il risultato è pronto nella porta di uscita. L ALU ritorna nello stato idle e rimane in attesa della prossima operazione da eseguire La struttura interna dell ALU_VS L entità da realizzare deve includere al suo interno quattro entità ALU_FP e deve essere in grado di svolgere le operazioni di base del Vertex Shader, ad eccezione della nop, la mov e la mad; quest ultima, necessitando di tre operandi, deve necessariamente essere scomposta in una moltiplicazione e in una addizione. L istruzione più difficile da implementare è senza dubbio la lit, che permette il calcolo dei coefficienti di illuminazione, perché comprende una istruzione di compare e deve far uscire in out.y il valore di in.x e in out.z il valore di in.y^in.z. Lo schema circuitale dell ALU è costituito essenzialmente da 4 ALU_FP e da alcuni multiplexer per eseguire particolari istruzioni. Sarà poi presente un unità di controllo il cui scopo è quello di selezionare i bit di selezione dei mux e pilotare le ALU_FP. È necessaria un analisi attenta delle istruzioni che debbono essere eseguite per deciderne la struttura interna. Si esamineranno ora in dettaglio le scelte fatte nella realizzazione dell entità. Istruzioni come la dp3 e la dp4 necessitano di sommare il contenuto dei risultati delle 4 ALU_FP; è necessario quindi riportare i risultati delle ALU in ingresso mediante un multiplexer. Inoltre le stesse operazioni sopra citate richiedono che il risultato di una ALU (quella che effettua la somma di tutti i risultati) possa essere replicato su tutti e quattro i valori di uscita. Per ottenere questo basta utilizzare tre multiplexer in uscita. La struttura assume quindi l aspetto mostrato nella figura a pagina seguente.
34 Progettazione in VHDL del Vertex Shader Pag. 108 OP1_1 Mux_OUT_1 OP2_1 ALU_FP_1 0 1 OUT_1 OP1_2 Mux_OUT_2 OP2_2 ALU_FP_2 0 1 OUT_2 OP1_3 0 1 Mux_OUT_3 OP2_3 ALU_FP_ OUT_3 OP1_4 0 1 OP2_4 0 AL_FP_4 OUT_4 1 Mux_ALU_1A Mux_ALU_1B Mux_ALU_2A Mux_ALU_2B Ma questo ancora non basta: bisogna prevedere di portare l ingresso Op1.w in tutti gli ingressi delle ALU; ciò è richiesto per eseguire l istruzione expp che opera solamente su tale dato. Basta quindi aggiungere altri tre multiplexer che permettano di scegliere tra l applicazione del normale operando oppure l Op1.w. Infine, per eseguire l istruzione lit, è necessario applicare l ingresso Op1.x alla seconda ALU e gli ingressi Op1.y e Op1.w alla terza ALU rispettivamente come primo e secondo operando. Queste modifiche richiedono l aggiunta di altri sei multiplexer che vanno opportunamente applicati nella sezione di ingresso prima delle ALU. Lo schema di posizionamento di questi componenti è rappresentato nella figura a pagina seguente in cui si fa riferimento ai segnali di ingresso presenti nella figura di questa pagina. Una volta terminata la struttura interna, bisogna realizzare l unità di controllo che regola tutti i segnali di selezione dei Mux e delle ALU. L unico processo dell ALU_VS è costituito da un automa a stati finiti che rappresenta appunto l unità di controllo. Questo tipo di realizzazione in VHDL viene chiamata strutturale perché si utilizza solamente la struttura dei componenti.
35 Progettazione in VHDL del Vertex Shader Pag. 109 IN1_1 Mux_W_1 OP1_1 IN2_1 OP2_1 IN1_2 Mux_W_2 Mux_X_ALU2 OP1_2 IN2_2 OP2_2 IN1_3 Mux_W_3 Mux_Y_ALU3Op1 OP1_3 IN2_3 Mux_W_ALU3Op2 OP2_3 IN1_4 OP1_4 IN2_4 OP2_4 Fare lo schema dei collegamenti è molto importante perché il ModelSim non mette a disposizione un tool grafico per interconnettere le entità e quindi bisogna fare il mapping delle porte con la sintassi del VHDL. Se non si ha uno schema da consultare è facile perdere traccia del percorso dei segnali di controllo e collegamento tra i componenti ed è difficile poi trovare eventuali errori Analisi del processo di funzionamento Il processo è sensibile al clock1 e come prima cosa si controlla se è necessario iniziare l esecuzione di un istruzione; questo avviene quando il segnale di enabled è 1 e l ALU_VS si trova in uno stato di idle, ovvero non sta eseguendo nessun altra operazione. In questo caso si azzerano i segnali di controllo dei Mux di uscita che potrebbero essere stati attivati dall istruzione precedente, a meno che il comando esterno non sia ALU_VS_Not che indica all ALU di non fare assolutamente nulla e di mantenere in uscita il risultato dell istruzione precedente. --La struttura è simile alla ALU_FP aspetto il segnale di En wait until clk1 = '1'; --solamente se sono in idle posso INIZIARE una operazione if Stato = idle and Enabled = '1' then --azzero un eventuale stato precedente se ho un nuovo --comando
36 Progettazione in VHDL del Vertex Shader Pag. 110 if CurrentCommand /= ALU_VS_Nop then Sel_Mux_OUT_1 <= '0' after dly; Sel_Mux_OUT_2 <= '0' after dly; Sel_Mux_OUT_3 <= '0' after dly; Sel_Mux_OUT_4 <= '0' after dly; Ora è sufficiente azzerare il segnale di ready e controllare l istruzione che deve essere eseguita. Le istruzioni si dividono in due categorie distinte: quelle che necessitano di una sola operazione delle ALU_FP e quelle che invece necessitano di più operazioni in sequenza. Per la prima categoria l esecuzione è molto semplice: si impongono i comandi sulle 4 ALU_FP al fronte di salita di clk1 e in corrrispondenza del fronte di salita di clk2 si controlla che le ALU abbiano completato l operazione. Quando tutte e 4 le ALU hanno completato l esecuzione dell istruzione, l unità di controllo attiva il segnale di ready per l ALU_FP comunicando all esterno che l esecuzione dell istruzione è completata e che il risultato è disponibile nella porta di output. Si prenda come esempio l istruzione add, che appartiene a questa categoria: case CurrentCommand is when add => SetAllALU_Cmd(add); EnableAllFPU('1'); stato := SimpleWaiting; La funzione SetAllALU_Cmd serve per assegnare uno stesso comando a tutte e quattro le ALU_FP. In questo caso l istruzione consiste solamente in una addizione parallela e quindi, dopo avere settato il comando, si passa nello stato SimpleWaiting: end case; --è il case di prima, quello delle istruzioni while Stato /= idle loop wait until clk2 = '1'; EnableAllFPU('0'); case Stato is when SimpleWaiting => if Ready_ALU_1 = '1' and Ready_ALU_2 = '1' and Ready_ALU_3 = '1' and Ready_ALU_4 = '1' then Ready <= '1' after dly; Stato := idle; resetcmdstate; Nel caso di SimpleWaiting si attende il segnale di ready dalle quattro ALU_FP e si torna in idle per uscire dal ciclo. Preventivamente si chiama anche la procedura resetcmdstate che non fa altro che riazzerare tutti i segnali di controllo dei Mux di ingresso, prima di iniziare l esecuzione di un nuovo comando. L istruzione dp4 è invece più complessa perché, dopo aver imposto l operazione di mul su tutte e quattro le ALU_FP, è necessario sommare tra loro i risultati. Lo stato successivo all istruzione è pertanto dp4_secondpass. when dp4 => SetAllALU_Cmd(mul); EnableAllFPU('1'); Stato := dp4_secondpass; Si entra poi nel ciclo di attesa, ma questa volta non essendo nello stato
37 Progettazione in VHDL del Vertex Shader Pag. 111 SimpleWaiting, si eseguiranno operazioni differenti. when dp4_secondpass => if Ready_ALU_1 = '1' and Ready_ALU_2 = '1' and Ready_ALU_3 = '1' and Ready_ALU_4 = '1' then SetAllALU_Cmd(add); Sel_Mux_ALU_1A <= '1' after dly; Sel_Mux_ALU_1B <= '1' after dly; Sel_Mux_ALU_2A <= '1' after dly; Sel_Mux_ALU_2B <= '1' after dly; wait until clk1 = '1'; EnableAllFPU('1'); Stato := dp4_thirdpass; Come nel caso precedente si attende che tutte le ALU_FP abbiano terminato le loro operazioni e quindi si settano tutte le ALU_FP con il comando add (in realtà sono necessarie solamente la 3 e la 4, ma è lo stesso). Contemporaneamente si attivano i quattro Mux che riportano le uscite delle ALU_FP ai loro ingressi e si attende il fronte di salita di clk1 per iniziare la prossima operazione. Lo stato successivo è il dp4_thirdpass perché bisogna ancora fare una ulteriore addizione. when dp4_thirdpass => if Ready_ALU_3 = '1' and Ready_ALU_4 = '1' then --il valore dei mux di ingr. è già settato Sel_Mux_OUT_1 <= '1' after dly; Sel_Mux_OUT_2 <= '1' after dly; Sel_Mux_OUT_3 <= '1' after dly; Sel_Mux_OUT_4 <= '1' after dly; wait until clk1 = '1'; en_alu_4 <= transport '1' after dly; Stato := WaitingW; Le ALU_FP hanno già il comando add, è allora sufficiente attivare i mux di uscita e poi abilitare la sola ALU_FP4 che andrà ad eseguire l ultima somma. Lo stato finale è chiamato WaitingW, perché deve attendere solamente il risultato dell ultima ALU_FP, dato che sono stati attivati i Mux per replicare l uscita di questa ALU su tutte le altre. È molto interessante andare ad osservare come evolvono i segnali all interno del circuito durante la fase di test (fare riferimento alla figura di pagina seguente). Si nota che quando si riportano le uscite delle ALU_FP ai loro ingressi potrebbe sorgere qualche problema perché l output non è detto che sia costante durante tutto il corso dell operazione. È lo stesso problema che accade con i registri contatori dove il valore attuale è utilizzato per calcolare il valore successivo e quindi si rende necessaria una struttura master-slave per i latch che compongono il registro. In questo caso però non ci sono problemi eccessivi; la simulazione behavioural non risente di questo problema, ma anche se si facessero le ALU_FP con una descrizione strutturale sarebbe necessario dotarle internamente di registri in ingresso perché le quantità su cui si opera devono spesso essere trasformate. Durante una addizione è infatti necessario che entrambi gli operandi abbiano lo stesso esponente, quindi in realtà non è necessario che gli ingressi mantengano lo stesso valore durante tutta la durata di esecuzione dell operazione.
38 Progettazione in VHDL del Vertex Shader Pag. 112
39 Progettazione in VHDL del Vertex Shader Pag. 113 Una forma più leggibile della simulazione è stata salvata su disco e può essere riaperta e consultata in ogni momento con il visualizzatore di forme d onda del ModelSim. Esaminando con attenzione la simulazione, si nota che al primo fronte di salita di clk2 l entità di test impone gli ingressi all ALU_VS e poi invoca l istruzione add. Al successivo fronte di salita del clk1 la ALU_VS risponde imponendo il comando di add a tutte e quattro le ALU_FP interne ed aspetta il completamento dell esecuzione. Al seguente fronte di salita di clk2 il risultato è pronto e quindi il controller pone il ready generale ad 1 (visibile nel segnale /testalu/ready). L unità di test risponde abbassando il segnale di enabled ed aspetta il successivo fronte di salita di clk2 per dare il comando dp4. In questo caso, il controllore prima impone il comando mul e successivamente, quando esso è stato completato, attiva i quattro Mux di selezione ingresso ALU per riportare le uscite delle ALU_FP nei loro ingressi. Si può vedere il momento esatto del comando quando gli enabled delle ALU_FP passano al livello alto per eseguire il comando successivo. È chiaramente visibile come nel secondo stato della dp4 vengano attivate solamente le ALU_FP numero 3 e 4, mentre nell ultimo passo viene attivata solamente la 4. Dato che l ALU_FP è solamente behavioural e che i tempi di elaborazione sono tenuti molto bassi, al di sotto di un ciclo di clock, si vede come il segnale di ready delle 4 ALU_FP si mantenga al valore basso per un tempo veramente irrisorio. In una situazione reale anche l elaborazione di una semplice addizione richiede almeno un colpo di clock per essere eseguita ed istruzioni complesse come la sqrt ne richiedono solitamente più di 10. Nel caso di istruzioni particolari come la expp, che necessitano dell attivazione dei vari mux di ingresso per utilizzare la quarta componente come ingresso delle prime tre ALU, è necessario inserire l attesa del fronte di salita di clk2 prima di abilitare le FPU per dare il tempo ai mux di rispondere correttamente Ancora sull I/O in VHDL Per simulare efficacemente l entità ALU_VS, non è sufficiente utilizzare i file come spiegato in precedenza, dato che è eccessivamente lento e scomodo realizzare file binari per la simulazione. Molto più comoda è la simulazione con file di testo, nei quali si può scrivere il numero floating point (senza preoccuparsi della sua effettiva rappresentazione binaria) con un semplice editor di testo come il notepad. Analogamente è necessario fornire un output con delle stringhe, in modo da poter controllare facilmente la correttezza dei dati di output dell ALU_VS. È necessario quindi utilizzare le librerie di I/O fornite dal ModelSim che contengono un set di funzioni per convertire una variabile (intera, reale, booleana, etc.) nella sua rappresentazione stringa e viceversa. Utilizzando queste funzioni è possibile realizzare una routine per leggere quattro valori float da un file e restituire una EIGHTWORD, ed un altra routine per scrivere in una stringa il valore delle quattro parti che costituiscono un valore EIGHTWORD. Il package che contiene le routine di I/O si chiama std_iopack ed è contenuto nella libreria std_developerskit; per poterla utilizzare è quindi necessario dichiararla nel file prima di qualsiasi istruzione che fa riferimento a funzioni in essa contenute.
40 Progettazione in VHDL del Vertex Shader Pag. 114 library std_developerskit; use std_developerskit.std_iopak.all; Ora si procede con la realizzazione di una funzione in grado di leggere da un file testo una riga contenente quattro valori float e di convertirli in una EIGHTWORD. Il tipo di file viene dichiarato come ASCII_TEXT, un nuovo tipo di file contenuto nella std_iopack che è essenzialmente un file testo con piccole diversità utili per le funzionalità della libreria. Le funzioni della std_iopack possono comunque lavorare correttamente con i file di tipo TEXT, ma Mentor consiglia vivamente di utilizzare il nuovo tipo di dato. Il trattamento delle stringhe in VHDL ricorda un poco quello del C, dato che la grandezza di una stringa può essere decisa al momento della sua dichiarazione, oppure è possibile utilizzare il tipo access string e fare allocazione dinamica. Per non appesantire la trattazione, nelle routine del Vertex Shader tutte le stringhe sono dichiarate di lunghezza massima (solitamente 255 caratteri) in modo da non avere problemi di spazio e non dover gestire dinamicamente memoria. La funzione viene così definita. function ReadEigthWordText(FILE F : ASCII_TEXT) return EIGHTWORD is variable Result : Array4Float; variable Linea : string(1 to MAX_STRING_LEN); variable SpacePos : Natural; variable strtemp : string(1 to MAX_STRING_LEN); variable NumTemp : Real; --leggo la linea fgetline(linea, F); Linea(StrLen(Linea) + 1) := NUL; for index in 0 to 3 loop SpacePos := Find_Char(Linea, ' '); if SpacePos = 0 then SpacePos := StrLen(Linea); StrNCpy(StrTemp, Linea, SpacePos); NumTemp := from_string(strtemp); Result(index) := float_to_bits(numtemp); StrCut(Linea, Linea, SpacePos); end loop; return Conv_A4F_to_8W(Result); end ReadEigthWordText; Dopo avere letto la linea è necessario inserire un terminatore (NUL), altrimenti si corre il rischio di fare confusione con successive letture, la variabile Linea infatti si comporta come se avesse l attributo static in C. Dopo aver letto la linea si procede all individuazione del carattere di spazio che separa tra di loro i gruppi di cifre; a questo punto la funzione StrNCpy può copiare solamente i caratteri che costituiscono il numero in una stringa temporanea, che viene poi convertita in un valore real dalla funzione from_string. Il valore real viene poi convertito nella sua forma binaria dalla solita funzione float_to_bits ed infine la parte di stringa elaborata viene rimossa dalla funzione StrCut che non fa parte della libreria, ma è stata appositamente realizzata in questo package. procedure strcut(s1 : inout string; S2 : inout string; N : in natural) is variable I : integer := 1; variable L1 : natural; L1 := StrLen(S1);
41 Progettazione in VHDL del Vertex Shader Pag. 115 while (I < L1) and (S2(I + N) /= NUL) loop S1(I) := S2(I + N); I := I + 1; end loop; S1(I) := NUL; end; La StrCut accetta come parametri due stringhe in ingresso ed un numero intero e copia la stringa S2 nella S1 a partire dal carattere N+1, ovvero taglia N caratteri iniziali dalla stringa S2. Con queste due funzioni siamo in grado di leggere da un file testo 4 float e convertirli in una EIGHTWORD. La funzione che scrive in una stringa la rappresentazione di una EIGHTWORD è invece più semplice, basta infatti fare uso di alias e la funzione si scrive da sola. Procedure PrintEightWord(S : inout string; Data : in eightword) is alias Dword1 : DWORD is Data(31 downto 0); alias Dword2 : DWORD is Data(63 downto 32); alias Dword3 : DWORD is Data(95 downto 64); alias Dword4 : DWORD is Data(127 downto 96); variable Temp : real; Temp := bits_to_float(dword1); strcpy(s, StrCat(S, to_string(temp, "%10.6f"))); strcpy(s, StrCat(S, " - ")); Temp := bits_to_float(dword2); strcpy(s, StrCat(S, to_string(temp, "%10.6f"))); strcpy(s, StrCat(S, " - ")); Temp := bits_to_float(dword3); StrCpy(S, StrCat(S, to_string(temp, "%10.6f"))); strcpy(s, StrCat(S, " - ")); Temp := bits_to_float(dword4); strcpy(s, StrCat(S, to_string(temp, "%10.6f"))); strcpy(s, StrCat(S, " - ")); end PrintEightWord; La stringa S che viene passata come argomento di tipo inout non deve essere azzerata, i valori convertiti in stringa debbono solamente essere aggiunti. L utilizzo della funzione StrCat è leggermente differente dal C, infatti questa funzione ritorna una stringa composta dalla concatenazione delle stringhe passate come argomenti. È necessario quindi copiare la stringa restituita dalla funzione in un altra, allo scopo di mantenere il risultato. Ecco quindi spiegata la bizzarra sintassi utilizzata per poter aggiungere ad una stringa un altra stringa. La stringa da aggiungere è naturalmente quella restituita dalla funzione to_string, in cui si specifica anche il formato da utilizzare. Il formato può anche essere omesso ed in tal caso ne viene assunto uno di default. Purtroppo queste routine non sono esenti da bug: si è notato infatti che la funzione to_string, quando si convertono numeri reali molto elevati 45, tende a dare risultati errati e creare stringhe con il valore nullo oppure con valori casuali. Bisogna quindi fare attenzione in presenza di valori molto grandi e se le simulazioni non sembrano dare i risultati previsti andare a controllare con il debug il valore effettivo presente nelle varie parti del circuito. Per fare questo è 45 Ma sempre nel range dei numeri float.
42 Progettazione in VHDL del Vertex Shader Pag. 116 consigliabile avere l ausilio di piccole utility che permettano la conversione di un numero da formato binario a valore floating point. Queste utility sono state realizzate in Visual Basic ed i sorgenti sono disponibili nel materiale accluso alla tesina. Si ricorda anche che le funzioni di conversione tra float e rappresentazione binaria possono essere leggermente approssimate, ma questo non influisce sulla correttezza delle funzioni Simulazione Con i tool di I/O che si sono realizzati fare la simulazione è ora un processo assai semplice. Come prima cosa si realizza un file di testo che contiene i valori float su cui la ALU_FP andrà ad operare. Ad esempio: Su un altro file si scrivono i risultati corretti precalcolati a mano per il confronto e poi si realizza una semplice entità di test che legge questi quattro valori dal file convertendoli in EIGHTWORD, applica i segnali di ingresso alla ALU_VS e sequenzialmente testa tutti i comandi che quest ultima può eseguire. Ogni volta che il segnale di ready dell ALU_VS sale al valore 1 si scrive il valore dei quattro float che compongono la EIGHTWORD in uscita in una stringa, che viene visualizzata nella finestra di output mediante la solita istruzione assert false report. Dopo aver dichiarato l entità di test ed avere specificato come componenti l ALU_VS si procede alla definizione dell architettura dell entità test. -- architettura tester ALU1 : ALU_VS port map ( clk1 => clk1, clk2 => clk2, CCode => CCode, Command => Command, Ready => Ready, Enabled => Enabled, In1 => In1, In2 => In2, Result => Result ); generatore_clk1 : process --I processi sono tutti concorrenti clk1 <= '1'; wait for 50 ns; clk1 <= '0'; wait for 50 ns; end process; generatore_clk2 : process --I processi sono tutti concorrenti clk2 <= '0'; wait for 50 ns; clk2 <= '1'; wait for 50 ns; end process; Come prima cosa si dichiara l entità da testare e si fa il mapping delle porte di I/O; per comodità i segnali dell architettura tester sono stati creati con lo stesso nome delle porte dell ALU_VS. Due processi distinti servono a generare i due clock; facendo iniziare il primo dal valore 1 e l altro dal valore 0 ci si assicura che i clock lavorino in controfase.
43 Progettazione in VHDL del Vertex Shader Pag. 117 Il terzo processo regola la macchina a stati che esegue il test vero e proprio: test_bench : process variable state : integer := 0; FILE myfile : ASCII_TEXT open read_mode is "try.txt"; variable DebugOut : string (1 to MAX_STRING_LEN); wait until clk2'event and clk2 = '1'; case state is when 0 => In1 <= ReadEigthWordText(MyFile) after dly; In2 <= ReadEigthWordText(MyFile) after dly; Command <= add after dly; strcpy(debugout, "Istruzione add :"); Enabled <= transport '1' after dly; wait until Ready = '1'; Enabled <= transport '0' after Dly; PrintEightWord(DebugOut, Result); assert false report DebugOut severity note; DebugOut(1) := NUL; state := state + 1; Viene dichiarata una variabile che indica lo stato e una stringa dove andranno inserite le informazioni di debug. Il test è sensibile al fronte di salita del clock clk2 e non fa altro che leggere da file i valori di ingresso, applicarli all ALU_VS, imporre una operazione ed attendere il risultato. Durante lo stato 0 viene aperto il file che contiene i valori float da utilizzare come ingresso, lo stato viene poi incrementato di uno e si passa al ciclo successivo. I valori in ingresso non vengono modificati, quindi per tutte le istruzioni saranno utilizzati sempre gli stessi operandi, tranne nel caso della lit che prevede come Op2 4 valori float nulli. Negli stati successivi al primo vengono imposte in sequenza tutte le operazioni che l ALU_VS può svolgere ed i risultati vengono mostrati nella finestra principale del ModelSim. A titolo di esempio viene mostrato lo stato relativo all istruzione CMax: when 6 => Command <= CMax after dly; strcpy(debugout, "Istruzione MAX :"); Enabled <= transport '1' after dly; wait until Ready = '1'; Enabled <= transport '0' after Dly; PrintEightWord(DebugOut, Result); assert false report DebugOut severity note; DebugOut(1) := NUL; state := state + 1; Il ciclo di operazione dell ALU inizia imponendo il segnale di enabled a 1 assieme all operazione desiderata, è poi sufficiente attendere che il segnale di ready divenga pari ad uno, indice che l operazione corrente è stata completata. A questo punto si riporta il segnale di enabled a zero e si mostra il risultato. In questo caso il processo attende e si riattiva esattamente quando il segnale di ready diviene pari ad uno. La situazione è leggermente differente se il processo non può attendere in questo modo, ma deve operare in modo sincrono con un clock. In questo caso sussiste il rischio che l ALU finisca l operazione e vada in idle. Se il processo che la controlla non si accorge che il segnale di ready è 1 prima del fronte di salita di clk1, l ALU riesegue di nuovo l operazione, abbassando come prima cosa il segnale di ready e così si giunge ad uno stallo. In questo caso il ciclo istruzione da utilizzare è leggermente differente e fa uso dell istruzione ALU_VS_Nop. Si inizia imponendo all ALU l operazione
44 Progettazione in VHDL del Vertex Shader Pag. 118 desiderata ed alzando il segnale di enabled, al successivo fronte di salita di clk1 l ALU si attiva e nel successivo fronte di salita di clk2 si impone il comando ALU_VS_Nop. In questo modo, quando l ALU termina l operazione mantiene il valore in uscita fino a che non si cambia il comando da eseguire. In questo caso il segnale di enabled viene sempre lasciato al valore 1. Nella finestra apposita si controlla poi che l ouput sia corretto. Quando tutte le istruzioni sono state testate è possibile realizzare la struttura per il Vertex Shader nella sua interezza Componenti base del Vertex Shader Disegnare lo schema Prima di iniziare a scrivere codice VHDL, come per ogni buon progetto che si rispetti, è necessario preparare la documentazione sul problema da risolvere ed una struttura di base del programma. Nel caso in esame la fase di progettazione su carta assume un ruolo importantissimo perché ci fa comprendere quali entità siano necessarie per il funzionamento globale del circuito e fornisce inotre una stima della complessità generale. Il posizionamento ed il nome delle varie interconnessioni tra i componenti e delle linee di controllo si rivela indispensabile per la realizzazione pratica del progetto. Il ModelSim infatti non possiede un editor grafico per interconnettere le varie entità, e tutte le connessioni debbono essere fatte a mano utilizzando la normale sintassi VHDL. In questo caso è di fondamentale importanza avere uno schema completo del progetto per poter in ogni momento verificare la correttezza delle interconnessioni ed il percorso dei segnali. Per la realizzazione dello schematico si possono utilizzare i vari software che si trovano in commercio come lo SmartDraw, oppure utilizzare il semplice editor grafico presente in Word. L utilizzo di tool appositi consente di applicare modifiche al progetto in modo molto semplice senza dover ridisegnare da capo tutto lo schema. La struttura del Vertex Shader è contenuta nel file grafico.ppt, dove in azzurro sono segnate le linee di controllo, per distinguerle dalle interconnessioni tra i componenti. Lo schematico è stato disegnato in formato A3 per renderlo più leggibile Latch n-bit, latch_r n-bit Il primo componente sicuramente necessario per la realizzazione del circuito è il semplice latch n-bit. Questo componente non è altro che un insieme di n flip-flop di tipo d con ingresso di enable. Il loro comportamento è molto semplice: quando il segnale di enabled è pari ad 1 i dati presenti in ingresso vengono passati nella porta di uscita, quando il segnale di enabled è 0 il segnale di uscita rimane invariato anche se il segnale di ingresso varia. L entità ha un solo processo sensibile al cambiamento dei dati di ingresso e del segnale di enabled; l unica operazione effettuata è la propagazione del segnale di ingresso sulla porta di uscita.
45 Progettazione in VHDL del Vertex Shader Pag. 119 use work.alu_fp_pack.all; entity latch is generic (width : positive; Dly : Time := Delay); port ( d : in bit_vector(width-1 downto 0); q : out bit_vector(width-1 downto 0); enabled : in bit ); end latch; architecture behaviour of latch is Latching : process (d, enabled) if enabled = '1' then q <= d after Dly; end process Latching; end behaviour; Sebbene sia sintetizzato in behaviorual, questo componente ha una semplicissima realizzazione pratica, utilizzando solamente quattro porte NAND. Nella figura seguente viene mostrato lo schema di un latch D ad un bit. Il numero di bit da cui è composto il segnale è un parametro dell entità (width), in questo modo si mantiene la maggior generalità possibile. Se la porta di uscita del latch deve confluire in un bus, è necessario sintetizzare un altra entità e non è possibile lasciare variabile il numero di bit. Un bus infatti per poter essere gestito ha bisogno di una appropriata funzione di risoluzione, che nel caso di bit_vector è solitamente una semplice operazione di OR logico tra i segnali del bus. Purtroppo la sintassi del VHDL non permette di realizzare una funzione di risoluzione per segnali di cui non è conosciuto il numero di bit. Il latch_r è una semplice variazione dell entità precedente in cui è presente anche un segnale di reset. Il funzionamento è praticamente identico ad un normale latch, ma se il segnale di reset è pari ad 1 allora in uscita è presente un segnale composto da tutti zeri. Questa entità ha il compito di imporre il secondo ingresso dell ALU a zero durante particolari operazioni. La realizzazione del componente è effettuata mediante il codice seguente: use work.alu_fp_pack.all; entity latch_r is generic (width : positive; Dly : Time := Delay); port ( d : in bit_vector(width-1 downto 0);
46 Progettazione in VHDL del Vertex Shader Pag. 120 q : out bit_vector(width-1 downto 0); enabled : in bit; reset : in bit ); end latch_r; architecture behaviour of latch_r is Latch_R : process (d, enabled, reset) variable Temp : bit_vector(width-1 downto 0); if reset = '1' then for Index in d'range loop Temp(Index) := '0'; end loop; q <= Temp after Dly; elsif enabled = '1' then q <= d after Dly; end process Latch_R; end behaviour; Il processo è sensibile anche alle variazioni del segnale di reset, ed il valore zero viene generato mediante una variabile temporanea. In questo modo viene fatta una sola assegnazione per il segnale invece di una per ogni ciclo del loop Multiplexer e demultiplexer Altri componenti molto comuni nella progettazione di un circuito digitale sono i multiplexer, il cui scopo è imporre in uscita uno tra gli n segnali presenti al loro ingresso. Una porta di controllo permette di selezionare quale dei loro n segnali di ingresso verrà propagato in uscita, la grandezza in bit di questa porta deriva dal numero n di porte di ingresso. Per il Vertex Shader sono necessari solamente multiplexer a due ingressi e quindi la lineea di controllo è composta solamente da un bit. Nella realizzazione VHDL l ampiezza in bit delle porte di ingresso è lasciata variabile per garantire la maggiore flessibilità. Il codice VHDL che realizza questo componente è il seguente: use work.alu_fp_pack.all; entity mux2 is generic ( width : positive; Dly : Time := delay); port ( D0, D1 : in bit_vector(width-1 downto 0); O : out bit_vector(width-1 downto 0); Sel : in bit ); end mux2; architecture behaviour of mux2 is with Sel select O <= D0 after Dly when '0', D1 after Dly when '1'; end behaviour; Per una realizzazione più compatta e comprensibile si è fatto uso della sintassi with.. select che automaticamente implementa un processo sensibile alla variazione dei segnali D0, D1 e Sel. Questo componente, come il latch
47 Progettazione in VHDL del Vertex Shader Pag. 121 precedente, non è sincrono e non ha bisogno di clock per funzionare. La sua struttura interna potrebbe essere la seguente: In1 Sel In2 Come si può notare la struttura è molto semplice e richiede solamente 4 porte logiche, il ritardo massimo è dato dalla somma del ritardo delle porte NOT, AND, OR. Naturalmente lo schema in questione è relativo ad un muiltiplexer con width = 1; per valori maggiori è sufficiente utilizzare una porta AND distinta per ogni bit. Il demultiplexer invece svolge l operazione inversa e permette di selezionare in quale delle n porte di uscita far passare il segnale di ingresso. Anche in questo caso il componente è chiaramente asincrono e la sua realizzazione è anche più semplice di quella del multiplexer. In Sel Out1 Out0 Le porte logiche utilizzate sono solamente 3 ed anche il ritardo massimo è diminuito essendo pari alla somma dei ritardi delle porte AND e NOT. In VHDL la realizzazione è lo stesso molto semplice: entity DeMux2_bus is generic (dly : Time := Delay); port ( In0 : in EIGHTWORD; Out0, Out1 : out EIGHTWORD_BUS bus; Sel : in bit ); end DeMux2_bus; architecture Behavioural of DeMux2_bus is process (Sel, In0) case Sel is when '0' => Out1 <= null after dly; Out0 <= In0 after dly; when '1' => Out0 <= null after dly; Out1 <= In0 after dly; end case; end process; end Behavioural; Consultando lo schema logico del Vertex Shader si nota come i due DeMux utilizzati siano connessi ai due bus degli operandi e quindi il tipo di porta in uscita è necessariamente del tipo EIGHTWORD_BUS. La porta che
48 Progettazione in VHDL del Vertex Shader Pag. 122 non è selezionata presenta in uscita il segnale null, ma dato che la funzione di risoluzione del bus è semplicemente un OR di tutti i segnali presenti, è evidente che null corrisponde esattamente ad un segnale di soli zeri, come è possibile dedurre dallo schema a porte logiche mostrato precedentemente I banchi di registri Il Vertex Shader ha come requisito primario la velocità di esecuzione ed inoltre lavora con un set molto ristretto di dati, per questo è accettabile che tutti i dati siano contenuti in banchi di registri. Un processore normale deve accedere a dati che sono solitamente contenuti in una memoria esterna al chip prima di operare su di essi. Dato che la lettura di RAM è solitamente un operazione più lenta dell esecuzione di una istruzione è necessario effettuare cicli di lettura e scrittura mediante segnali di controllo e perdere così tempo prezioso di elaborazione. In un ciclo di lettura il processore mette nel bus indirizzi l indirizzo delle celle di memoria in cui si trova il dato desiderato, abilita un bit che indica l inizio del ciclo di lettura dalla RAM e poi aspetta che la memoria segnali con un apposito bit detto ready che la RAM abbia completato la lettura e che nel bus dati sia finalmente disponibile il dato richiesto. Per migliorare le prestazioni è possibile utilizzare cache di vari livelli e di vari tipi 46, ma l accesso a dati in memoria rimane comunuqe una operazione dispendiosa. Dato l esiguo numero di dati che deve trattare il Vertex Shader, è possibile ipotizzare che tutti i dati siano contenuti in banchi di registri, oppure che sia presente una memoria interna a latenza 0. Questo assicura che dopo aver selezionato il dato desiderato mediante il bus degli indirizzi, quest ultimo sia pronto e disponibile nel successivo ciclo di clock. Questo è accettabile perché un banco di n registri potrebbe essere semplicemente realizzato con n latch, 1 mux ad n ingressi, un demux ad n uscite ed una porta and; la risposta di questa configurazione è sicuramente molto veloce. Inoltre i processori odierni hanno al loro interno grandi quantitativi di memoria (256 KB per un PIII) che risponde nel singolo colpo di clock e quindi l assunzione precedente è sicuramente valida. Il comportamento del banco di registri delle costanti e di quello del vertice in ingresso è assolutamente identico. L unica differenza di questi componenti è data dal numero di registri interni che per il vettore di ingresso è 16 mentre per le costanti è 96. Si può comunque realizzare in VHDL un unica entità per implementare entrambi, oppure due entità distinte per ragioni di debug 47. Il componente ha due porte di ingresso, due di uscita, due porte indirizzo ed infine due segnali, per abilitare la scrittura e la lettura di un registro. Questa configurazione mantiene la massima flessibilità perché permette, se necessario, di leggere e scrivere contemporaneamente, operazione che potrebbe anche essere richiesta se si implementa completamente una pipeline nel processore e non si vuole incorrere in stallo. Il codice VHDL che realizza questo componente è il seguente: entity RegBank_1In_1Out is generic (dly : time := Delay; NumOfRegister : integer; 46 Es. 1 livello 4-way associative, 2 livello fully associative, etc. 47 Istanziando la stessa entità più volte in un componente non si riesce più a distinguere tra le varie istanze, a meno di non prendere particolari accorgimenti.
49 Progettazione in VHDL del Vertex Shader Pag. 123 AddrBusWidth : integer); port ( DataIn : in EIGHTWORD; AddrIn : in bit_vector(addrbuswidth - 1 downto 0); DataOut : out EIGHTWORD_BUS bus; AddrOut : in bit_vector(addrbuswidth - 1 downto 0); EnabledIn : in Bit; EnabledOut : in Bit ); end RegBank_1In_1Out; architecture behaviuoral of RegBank_1In_1Out is type RegisterType is array(0 to NumOfRegister - 1) of EIGHTWORD; shared variable Reg : RegisterType; CostantWork : process (EnabledIn, EnabledOut, AddrIn, AddrOut) if EnabledIn = '1' then Reg(bits_to_intu(AddrIn)) := DataIn; if EnabledOut = '1' then DataOut <= Reg(bits_to_intu(AddrOut)) after dly; else DataOut <= null after dly; end process CostantWork; end behaviuoral; Il processo è sensibile al cambiamento dei segnali di enabled, dell indirizzo e del dato di ingresso. Questo comportamento è chiaro se si pensa che il banco di registri viene effettivamente implementato con dei latch. La struttura interna potrebbe essere ad esempio quella dello schema seguente. Data In Latch 1 Latch 2 Enabled Out Data out Latch n Address In Enabled in Address Out Il dato in ingresso è sempre presente in tutti i latch, un mux permette di selezionare quale registro abilitare, infine il segnale di enabledout confluisce in una porta AND assieme al valore del registro selezionato; in realtà sono presenti più porte AND, una per ogni bit di cui sono composti i registri. Per la scrittura, il segnale di enabled deve essere applicato al solo registro interessato e quindi è possibile utilizzare un DeMux che applica il segnale di enabled al latch desiderato. Tutti i registri danno come uscita il valore memorizzato al loro
50 Progettazione in VHDL del Vertex Shader Pag. 124 interno e tutti confluiscono in un multiplexer che seleziona solamente quello indicato nella porta Address_Out. Naturalmente se si scrive e si legge contemporaneamente nello stesso registro potrebbero sorgere dei problemi. Si supponga infatti che implementando la pipeline, lo stadio di scrittura individui lo stesso registro indicato dallo stadio di lettura. Questo caso deve comunque essere tenuto in considerazione perché causa un problema di sincronia conosciuto con il nome di data hazard. In questo caso è necessario stabilire se il processore gestisce autonomamente questa situazione (come nel caso della famiglia 80x86) oppure se è compito del programmatore evitare l hazard (come nel caso di alcuni processori RISC). Il banco di registri V_out viene implementato con una struttura differente perché deve poter supportare la mascheratura degli ingressi. Il dato da scrivere è infatti composto da 4 quantità float ed è possibile che nel codice si richieda la memorizzazione solamente di alcune di esse. Il modo più immediato è modificare l entità precedente in modo che accetti 4 EnabledInput, uno per ogni componente; un modo più interessante è quello di segnalare in qualche modo nel bus dei risultati che il numero non deve essere scritto. La prima soluzione è senz altro la più veloce, ma la seconda limita le linee di controllo che sono presenti nel processore ed è quindi particolarmente adatta nel caso in cui le risorse disponibili siano limitate. Lo stesso mascheramento infatti deve essere fatto anche per i registri R, che possono essere scritti dal Vertex Shader e se una futura implementazione aggiungesse un altro banco di registri bisognerebbe aggiungere altre 4 linee di enabled. Si stabilisce quindi che, se il singolo float presente nel bus ha un esponente pari a 255, non deve essere memorizzato nel registro destinazione. A questo punto basta realizzare un semplice componente che, in base ai 4 bit di controllo presenti nella codifica del registro di destinazione, impone un esponente pari a 255 nelle componenti che non debbono essere memorizzate. La scelta del valore 255 non è casuale, infatti tale valore è utilizzato solamente per la codifica dell infinito e per i QNaN e SNaN che, come detto precedentemente, non sono stati implementati nell ALU_FP. L entità che realizza il banco V_out deve semplicemente controllare se i numeri in ingresso hanno un esponente diverso da 255 e scriverli nel registro solamente in quel caso. CostantWork : process (EnabledIn, EnabledOut, AddrIn, AddrOut, DataIn) variable Temp : EIGHTWORD; variable Address : integer; alias Exp1 : bit_vector(7 downto 0) is DataIn (30 downto 23); alias Exp2 : bit_vector(7 downto 0) is DataIn (62 downto 55); alias Exp3 : bit_vector(7 downto 0) is DataIn (94 downto 87); alias Exp4 : bit_vector(7 downto 0) is DataIn (126 downto 119); if EnabledIn = '1' then Address := bits_to_intu(addrin); Temp := Reg(Address); if Exp1 /= X"FF" then Temp(31 downto 0) := DataIn(31 downto 0); if Exp2 /= X"FF" then Temp(63 downto 32) := DataIn(63 downto 32); if Exp3 /= X"FF" then Temp(95 downto 64) := DataIn(95 downto 64); if Exp4 /= X"FF" then
51 Progettazione in VHDL del Vertex Shader Pag. 125 Temp(127 downto 96) := DataIn(127 downto 96); Reg(Address) := Temp; elsif EnabledOut = '1' then DataOut <= Reg(bits_to_intu(AddrOut)) after dly; else DataOut <= X"0000_0000_0000_0000_0000_0000_0000_0000" after dly; end process CostantWork; Il banco R deve invece prevedere oltre al mascheramento anche la possibilità di avere due uscite perché può essere referenziato fino a tre volte in una istruzione, due per i registri sorgenti ed una per il registro di destinazione. La struttura è comunque analoga a quella di V_out, solo che possiede due uscite e quindi due address port di uscita e due enabled bit. Infine il banco istruzioni è leggermente differente dai precedenti anche se è sempre realizzato come banco di registri o memoria interna. La sua peculiarità è che in ingresso deve esser EIGHTWORD addressable, per mantenere una analogia con i banchi V_in e C_in, ma internamente deve sicuramente essere DWORD addressable perché il massimo comun divisore della lunghezza delle varie istruzioni è proprio una DWORD. La sua realizzazione mediante VHDL non è assolutamente difficoltosa, ma più complessa è l implementazione mediante porte logiche. Il codice VHDL è comunque il seguente: CostantWork : process (EnabledIn, EnabledOut, AddrIn, AddrOut, DataIn) variable Temp : natural; if EnabledIn = '1' then Temp := bits_to_intu(addrin) * 4; Reg(Temp) := DataIn(31 downto 0); Reg(Temp + 1) := DataIn(63 downto 32); Reg(Temp + 2) := DataIn(95 downto 64); Reg(Temp + 3) := DataIn(127 downto 96); if EnabledOut = '1' then Temp := bits_to_intu(addrout); DataOut(31 downto 0) <= Reg(Temp) after dly; DataOut(63 downto 32) <= Reg(Temp + 1) after dly; DataOut(95 downto 64) <= Reg(Temp + 2) after dly; DataOut(127 downto 96) <= Reg(Temp + 3) after dly; else DataOut <= null after dly; end process CostantWork; Si fa uso di una variabile temporanea in cui viene caricato il valore del bus indirizzi (in formato bit_vector) convertito in natural e se si sta effettuando una scrittura viene semplicemente moltiplicato per 4. La variabile che implementa il registro è formata da singoli valori DWORD e quindi internamente il banco è DWORD addressable; per dare l illusione dall esterno che sia EIGHTWORD addressable è sufficiente quindi moltiplicare l indirizzo per 4. La presenza di una moltiplicazione non complica assolutamente il circuito perché è semplicemente uno shift verso sinistra di due posizioni Program counter e circuito per il suo incremento Il program counter è un latch un po particolare perché viene realizzato
52 Progettazione in VHDL del Vertex Shader Pag. 126 mediante la tecnica master-slave. In un latch di questo tipo, quando il segnale di enabled è pari ad uno il master latch memorizza il dato all ingresso, ma l uscita è pilotata dal latch slave che ancora contiene il valore precedente; quando il segnale di enabled passa al valore basso, allora il latch slave memorizza il valore del latch master, cambiando così il valore di uscita. Questa tecnica viene utilizzata perché, durante la fase di incremento, il Program Counter deve memorizzare il nuovo valore che ha in ingresso il quale dipende dal suo valore di uscita. Senza l utilizzo di due latch non è possibile effettuare questa operazione con sicurezza. Lo schema di questo componente consiste solamente in due latch e una porta NOT. Enabled Data_in 3Master_Latch 3Slave_Latch Data_out La realizzazione in VHDL discende quindi di conseguenza: entity PC_reg is generic (Dly : Time := Delay; Width : Natural := 32); port ( d : in bit_vector(width - 1 downto 0); q : out bit_vector(width - 1 downto 0); latch_en : in bit; ); end PC_reg; reset : in bit architecture behaviour of PC_reg is process (d, latch_en, reset) variable master_pc, slave_pc : bit_vector(width - 1 downto 0); if reset = '1' then for Index in slave_pc'range loop slave_pc(index) := '0'; master_pc(index) := '0'; end loop; elsif latch_en = '1' then master_pc := d; else slave_pc := master_pc; q <= slave_pc after dly; end process; end behaviour; Naturalmente questo registro deve poter essere resettato ogni qualvolta si passa al vertice successivo e quindi deve essere presente una linea di reset che effettua questa operazione in modo veloce. Per resettare immediatamente il componente è sufficiente resettare contemporaneamente il latch master e quello slave. Il valore del Program Counter deve essere incrementato per ogni istruzione ed il valore massimo dell incremento è di 5 unità (istruzione mad). È quindi necessario implementare un full-adder per i primi 3 bit mentre per i restanti è sufficiente calcolare il riporto. In questo caso il componente può
53 Progettazione in VHDL del Vertex Shader Pag. 127 essere realizzato in VHDL mediante porte logiche, utilizzando solamente operatori logici che possono essere direttamente sintetizzati in hardware. entity INC_PC is generic (Dly : Time := Delay; width : natural := 32); port ( DataIn : in bit_vector(width-1 downto 0); DataOut : out bit_vector(width-1 downto 0); Incr : in bit_vector(2 downto 0); Enabled : in bit ); end INC_PC; architecture structural of INC_PC is Increment : process (Enabled) variable Result : bit_vector(width-1 downto 0); variable carry : bit := '0'; if Enabled = '1' then for I in 0 to DataIn'high loop if I < 3 then --Full adder Result(I) := (DataIn(I) xor Incr(I)) xor carry; carry := (DataIn(I) and Incr(I)) or (carry and (DataIn(I) xor Incr(I))); else --Incr(I) è sempre == '0' Result(I) := DataIn(I) xor Carry; carry := carry and DataIn(I); end loop; DataOut <= Result after Dly; end process Increment; end structural; Si vede come dopo il 3 bit sia sufficiente una ulteriore porta XOR che ha l unico scopo di continuare a sommare il riporto per tutti i rimanenti bit. Dato che la larghezza del bus indirizzi è di 10 bit, il sommatore realizzato è composto da 3 full-adder e da 7 half-adder Swizzler ed inverter Prima dell ingresso dell ALU gli operandi passano attraverso due componenti chiamati rispettivamente swizzler ed inverter, raggruppati nella comune denominazione di modificatori in ingresso. Gli inverter devono solamente cambiare segno ai 4 valori float presenti in ingresso se il bit di controllo è pari ad uno. Fortunatamente i numeri float utilizzano il complemento ad uno e quindi è sufficiente negare il bit di segno. Per la realizzazione sono sufficienti quattro porte XOR. Leggermente più complesso è il componente swizzler che, in base agli 8 bit di controllo presenti nella codifica del registro sorgente, deve mescolare i quattro numeri float presenti al suo ingresso in una qualsiasi combinazione. Una possibile realizzazione è quella di utilizzare quattro multiplexer a quattro ingressi dove confluiscono i quattro valori e collegare gli otto bit di controllo ai 4 multiplexer a due a due, come mostrato nell esempio seguente.
54 Progettazione in VHDL del Vertex Shader Pag. 128 Float1 Float2 Float3 Float4 Out1 Out2 Out3 Out4 Ogni segnale di abilitazione, che nel disegno proviene da sinistra, è naturalmente composto da 2 bit perché deve selezionare uno tra quattro ingressi. Il codice VHDL che realizza questo componente è il seguente entity Source_Swizzler is generic (Dly : Time := Delay); port ( SW_code : in bit_vector(7 downto 0); Data_in : in EIGHTWORD; Data_out : out EIGHTWORD ); end Source_Swizzler; architecture behaviour of Source_Swizzler is Swizzler : process (SW_code, Data_in) variable Temp : EIGHTWORD; variable Index : natural; variable CurrentData : natural; Index := 1; CurrentData := 31; while Index < 8 loop if SW_code(Index downto Index - 1) = "00" then Temp(CurrentData downto CurrentData - 31) := Data_in(31 downto 0); elsif SW_code(Index downto Index - 1) = "01" then Temp(CurrentData downto CurrentData - 31) := Data_in(63 downto 32); elsif SW_code(Index downto Index - 1) = "10" then Temp(CurrentData downto CurrentData - 31) := Data_in(95 downto 64); elsif SW_code(Index downto Index - 1) = "11" then Temp(CurrentData downto CurrentData - 31) := Data_in(127 downto 96); Index := Index + 2; CurrentData := CurrentData + 32; end loop; Data_out <= Temp after dly; end process Swizzler; end behaviour; Il ciclo while esterno serve a poter controllare tutti e quattro i codici di swizzling (ognuno di 2 bit) e dato che con una coppia di bit non è possibile utilizzare l istruzione case si ricorre alla classica cascata if.. elseif.. end if. La variabile CurrentData serve a tenere traccia della posizione nella quale inserire il dato corrente mentre la variabile Index serve per scorrere i quattro codici di swizzling.
55 Progettazione in VHDL del Vertex Shader Pag Dec_Src e Dec_Dest Questi due componenti hanno lo scopo di decodificare i registri sorgenti ed il registro di destinazione dell istruzione corrente, sono pertanto collegati direttamente al latch istruzioni in ingresso e pilotano in uscita i banchi di registri V_in, V_out, R e C. La realizzazione di questi componenti viene fatta in behavioural ed è molto simile alla struttura di una unità di controllo, infatti lo scopo è abilitare alcune linee di uscita in base al segnale presente in ingresso. L utilizzo di un latch per l opcode del registro di destinazione si rende necessario per le istruzioni la cui lunghezza complessiva è maggiore di 4 DWORD; tali istruzioni vengono eseguite in più passi distinti. La MAD è l unica istruzione che soddisfa questo requisito e la sua trattazione dettagliata verrà effettuata in sede successiva. Il codice VHDL per queste due routine viene riportato in appendice, in questa sede viene mostrato solamente il diagramma di flusso che l HDL Designer ha estratto automaticamente dal codice sorgente del progetto. Il progetto è infatti stato realizzato interamente facendo uso solamente del ModelSim e dell UltraEdit32.
56 Progettazione in VHDL del Vertex Shader Pag. 130 Nella pagina seguente viene mostrato il diagramma di flusso del DEC_DEST
57 Progettazione in VHDL del Vertex Shader Pag Ritardi In tutto il progetto si utilizza un ritardo fittizio di 5 ns; naturalmente questo è dovuto al fatto che il progetto non è legato ad una tecnologia prefissata oppure ad un modello di FPGA ben preciso. In una simulazione reale si conoscono infatti i ritardi delle porte base ed ogni componente ha quindi un tempo di risposta differente dagli altri. Per non appesantire il codice si è preferito utilizzare un ritardo standard che naturalmente dovrebbe essere modificato in previsione di una effettiva implementazione del circuito. Il massimo ritardo determina infatti la frequenza massima a cui può lavorare il circuito anche se è possibile modificare la struttura della control unit per accomodare ritardi particolarmente pesanti. Se ad esempio si trova che l implementazione reale dello swizzler ha un ritardo molto grande rispetto agli altri componenti, oltre a diminuire la frequenza di clock di tutto il circuito è possibile inserire dei cicli di attesa nella control unit solo quando è necessario attendere la risposta dello swizzler. Il progetto comunque non è stato pensato per essere effettivamente implementato data la grande difficoltà di una eventuale realizzazione dell ALU_FP che, oltre a lavorare con quantità floating
58 Progettazione in VHDL del Vertex Shader Pag. 132 point, deve implementare funzioni di complessità elevata come il logaritmo Struttura e simulazioni del Vertex Shader Progettazione strutturale La progettazione strutturale consiste nel realizzare un architettura per l entità composta solamente da entità più semplici interconnesse tra di loro e da un unità di controllo che regola il funzionamento e la sincronia di tutto l insieme. Questo modo di procedere è tipico della progettazione top-down, dove inizialmente si effettua la progettazione e la simulazione della struttura composta da macroblocchi e successivamente si vanno a realizzare internamente i singoli blocchi sempre in maggiore dettaglio, fino ad arrivare a componenti elementari presenti in qualsiasi libreria. Nel caso in esame la simulazione behaviourial del Vertex Shader non è stata effettuata perché superflua: la validità delle strutture di decodifica è stata infatti simulata e verificata nel disassemblatore ed il resto della struttura consiste nell esecuzione delle varie istruzioni del Vertex Shader che non sono altro che sequenze di operazioni floating-point. Si procede quindi alla simulazione strutturale utilizzando i componenti descritti in precedenza, alcuni dei quali sono implementati a livello comportamentale. Questo fa capire la reale potenza del linguaggio che permette di simulare il comportamento della struttura e dell unità di controllo prima di preoccuparsi effettivamente della realizzazione di tutte le unità. L importante è che tutti i componenti siano poi effettivamente realizzabili, cosa che è stata dimostrata nelle precedenti sezioni in cui sono stati mostrati gli schemi a porte logiche. L unica entità che veramente ha una implementazione behavioural è l ALU_FP, che effettua le operazioni floating point utilizzando semplicemente il tipo real del VHDL. Per implementare realmente questo circuito è necessario fornire una descrizione che si possa immediatamente tradurre in strutture elementari. Neppure il Quartus II dell Altera, infatti, permette la sintesi di operazioni che coinvolgono il tipo real, del quale inoltre (è bene ricordare) il VHDL non specifica il formato, ma solamente il range minimo. A questo punto si possiede lo schema del progetto assieme a tutte le entità VHDL in esso presenti e rimane solamente da sintetizzare l unità di controllo e fornire un entità di test flessibile che possa testare la correttezza del progetto. Come primo traguardo ci si prefigge di implementare la semplice istruzione add, in questo modo si può controllare l esecuzione di un singolo ciclo istruzione. Le altre istruzioni si implementano in maniera analoga alla add con l eccezione dell istruzione mad che richiede due cicli completi, uno in cui si esegue la mul ed il secondo in cui si esegue l add. Si proseguirà quindi con una descrizione dettagliata di come sia stato organizzato il Vertex Shader assieme alle motivazioni che hanno portato alla scelta di questa particolare struttura Bus principali In un processore sono sempre presenti dei bus in cui confluiscono i dati da elaborare ed il Vertex Shader non fa eccezione. Nella realtà, un bus è una linea di interconnessione che collega tra di loro molteplici componenti ed in cui
59 Progettazione in VHDL del Vertex Shader Pag. 133 possono confluire segnali da sorgenti differenti. In VHDL un bus è un tipo particolare di segnale che può avere in input più segnali, in un bus confluisce l output di più entità e costituisce allo stesso tempo l input di molteplici entità. La particolarità di un bus è quella di necessitare di una funzione di risoluzione che permetta di stabilire in ogni momento il valore del segnale effettivamente presente al suo interno, funzione di tutti gli ingressi ad esso applicati. Nel caso in esame si utilizza un bus di tipo bit_vector e come funzione di risoluzione un OR di tutti i segnali che confluiscono nel bus stesso. Logiche più complesse come la IEEE_Standard necessitano di funzioni più complesse perché un segnale può assumere altri valori oltre a 0 ed 1, come ad esempio il valore indefinito e l alta impedenza. Le funzioni di risoluzione non sono fornite dallo standard e questo garantisce una grande flessibilità per accomodare le varie tecnologie (TTL, CMOS, etc.). La forma generale di una funzione di risoluzione è quella di una tabella in cui sono comprese tutte le possibili combinazioni di valori. Questa tabella sarà una matrice NxN, dove N è il numero di valori differenti che può assumere un segnale. Si riporta come esempio una possibile funzione di risoluzione per logica IEEE standard Resolution function CONSTANT resolution_table : stdlogic_table := ( U X 0 1 Z W L H ( 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U', 'U' ), -- U ( 'U', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X' ), -- X ( 'U', 'X', '0', 'X', '0', '0', '0', '0', '0' ), -- 0 ( 'U', 'X', 'X', '1', '1', '1', '1', '1', '1' ), -- 1 ( 'U', 'X', '0', '1', 'Z', 'W', 'L', 'H', 'Z' ), -- Z ( 'U', 'X', '0', '1', 'W', 'W', 'W', 'W', 'W' ), -- W ( 'U', 'X', '0', '1', 'L', 'W', 'L', 'W', 'L' ), -- L ( 'U', 'X', '0', '1', 'H', 'W', 'W', 'H', 'H' ), -- H ( 'U', 'X', '0', '1', 'Z', 'W', 'L', 'H', '-' ) -- - ); FUNCTION resolved ( s : std_ulogic_vector ) RETURN std_ulogic IS VARIABLE result : std_ulogic := '-'; -- weakest state default BEGIN IF (s'length = 1) THEN RETURN s(s'low); ELSE -- Iterate through all inputs FOR i IN s'range LOOP result := resolution_table(result, s(i)); END LOOP; -- Return the resultant value RETURN result; END IF; END resolved; In un bus è necessario tenere sotto controllo in ogni momento tutti i segnali sorgenti per evitare che ci siano sovrapposizioni, con esiti sicuramente disastrosi. Bisogna per questo assicurarsi che in ogni momento solamente uno dei segnali contenga il valore da trasmettere e tutti gli altri possiedano il valore null. Il dato null è in VHDL molto particolare: può essere assegnato solamente ad un segnale bus ed indica che questo non deve alterare il contenuto del bus. Utilizzando i bit_vector infatti è sufficiente che il segnale sia costituito da tutti
60 Progettazione in VHDL del Vertex Shader Pag. 134 zero per evitare che il contenuto del bus sia alterato, ma con altre logiche questo non è garantito. Nella logica standard IEEE il valore che lascia inalterato il dato presente è il null e viene indicato con il imbolo - Il dato null è comunque presente in tutte le logiche o meglio fa parte dello standard VHDL, in questo modo ci si può svincolare dalla logica utilizzata per realizzare componenti più generici e versatili. Per assicurare la correttezza dei dati trasferiti sul bus si utilizzano dei latch particolari che prendono il nome di buffer, nei quali l uscita è pari all ingresso quando il segnale di abilitazione è 1, mentre vale null quando il segnale di abilitazione è 0. In questo modo basta assicurarsi che tutti i segnali che confluiscono nel bus passino per un buffer, l unità di controllo a questo punto attiva solamente uno di questi buffer alla volta per evitare corruzioni dei dati. Spesso inoltre le unità funzionali debbono prelevare dati dal bus e questi dati debbono essere presenti al loro ingresso per tutto il tempo della loro elaborazione. Per non tenere il bus occupato durante tutta l esecuzione, si utilizza anche in questo caso un latch che viene attivato quando nel bus è presente il dato che interessa. Il latch può quindi fornire al componente il valore in ingresso anche quando il bus contiene un valore differente, liberando il bus stesso per l utilizzo da parte di altri componenti. I bus svolgono quindi il compito di far transitare i dati dalle varie entità sotto l occhio vigile dell unità di controllo. Nel Vertex Shader i bus principali sono tre: i due operandi (detti op1 ed op2) ingresso dell ALU ed il risultato. Questa struttura viene scelta perché il Vertex Shader non è altro che un processore matematico (ottimizzato per operazioni di grafica 3D) e quindi il suo scopo principale è l elaborazione dei dati mediante l ALU_FP. Nel bus dei due operandi possono confluire i registri V_in, C ed R, che sono gli unici che possono essere utilizzati come registri sorgenti. In più ognuno di essi deve poter mettere il dato sia nel bus del primo operando che in quello del secondo. Il banco dei registri è fatto in modo che se il bit di lettura non viene abilitato l uscita è pari a null, liberandoci dalla necessità di inserire dei buffer d uscita. I registri V_in e C tuttavia necessitano di un DeMux che possa far confluire il dato in uno qualsiasi dei due bus. Dato che sia V_in che C non possono essere referenziati più di una volta in una istruzione, un solo DeMux è sufficiente. Per il registro R non ci sono problemi dato che possiede due uscite che vengono collegate rispettivamente al bus op1 e op2; è evidente infatti che non c è la necessità di far confluire contemporaneamente entrambe le uscite nello stesso bus. Nel registro risultato confluisce solamente l output dell ALU dopo essere stato mascherato dall apposito circuito, non ci sono quindi problemi di sovrapposizione dei segnali Program counter e ciclo istruzione (sezione decodifica) La sezione di decodifica vera e propria risiede all interno dell unità di controllo e nelle unità DEC_DEST e DEC_SRC; per funzionare correttamente è necessaria la presenza di un Program Counter (PC), di un adder per incrementare il PC e di un latch, che memorizza l istruzione corrente ed è connesso ai vari blocchi di decodifica. La struttura del PC è molto semplice perché in un Vertex Shader non sono previste istruzioni di branch e
61 Progettazione in VHDL del Vertex Shader Pag. 135 quindi il suo contenuto può solamente essere incrementato di una quantità pari alla lunghezza dell istruzione corrente. L uscita del PC è collegata direttamente con il sommatore e con la porta di address_out dell instruction pool. Quando il ciclo istruzione inizia, vengono attivati la porta di uscita dell instruction pool e l istruction latch, in questo modo l istruzione che si trova all indirizzo puntato dal PC viene portata nell Instruction latch. Da quest ultimo, ogni parte dell istruzione corrente viene portata alla struttura di decodifica corrispondente: la parte op viene mandata all unità di controllo, l operando di destinazione (dest) viene portato, mediante due latch 48, alla struttura che decodifica il registro destinazione ed i 4 bit di masking vengono memorizzati temporaneamente in un latch di nome Latch_mask. I due operandi sorgenti, invece, sono mandati alla struttura DEC_SRC che ha lo scopo di far confluire nei due bus op1_bus e op2_bus i valori corretti presi dai banchi di registri V_IN, R e C. Da questi operandi inoltre partono le linee che portano i bit dello swizzling ed il bit di inversione ai componenti inverter e swizzler. Quando l istruzione è terminata è sufficiente disabilitare l out_en dell instr_pool e poi andare ad abilitare il latch_en del program counter: in questo modo nel latch slave viene memorizzato l output del sommatore, che non è altro che l indirizzo della nuova istruzione; terminata questa operazione il ciclo istruzione ricomincia da capo. 48 La necessità di questi due latch diverrà chiara quando verrà analizzato il ciclo istruzione relativo all istruzione MAD.
62 Progettazione in VHDL del Vertex Shader Pag La sezione di decodifica Quando l ALU viene attivata, bisogna essere certi che nei due bus degli operandi siano presenti i valori corretti; di questo si occupa l unità DEC_SRC che ha appunto lo scopo di far confluire nel bus i registri codificati dalle DWORD che identificano i registri sorgenti. I banchi V_in e C possono essere referenziati solamente una volta per istruzione e quindi hanno una sola uscita, che deve però essere collegata ad entrambi i bus degli operandi. Per fare questo si possono utilizzare due DeMux, che permettono di selezionare in quale bus deve confluire il dato. Per il banco R questi problemi non sussistono perché può essere referenziato fino a 3 volte per istruzione; l entità possiede quindi due uscite che vengono collegate rispettivamente all Op1_bus e all Op2_bus, come detto precedentemente. Come ultima cosa è necessario gestire l indirizzamento indiretto mediante il registro A0.x. Dato che si tratta di un solo registro temporaneo al vertice corrente, è sufficiente realizzarlo mediante un semplice latch a 128 bit. L unica istruzione che può scrivere su di esso è la mov, il suo ingresso viene dunque collegato direttamente al Res_bus, in quanto l istruzione mov non fa altro che utilizzare la pass1 dell ALU_VS. L uscita di A0.x viene collegata all ingresso del DEC_SRC che la utilizza solamente nel caso di indirizzamento indiretto. Non basta questo comunque per poter dichiarare terminata la decodifica dei registri sorgenti: è necessario infatti prevedere la negazione degli operandi e lo swizzling. Inoltre per l istruzione MAD è necessario prevedere di poter utilizzare l uscita dell ALU come suo Op1. La struttura finale è quindi rappresentata nella figura a pagina seguente. I due latch servono a prelevare gli operandi dai rispettivi bus che poi verranno inviati agli invertitori di segno e agli swizzler. L uscita dell ALU, prima di confluire nel bus, passa attraverso l unità di mascheramento. Un semplice mux permette di utilizzare il risultato dell operazione precedente come primo operando di quella attuale. Bisogna fare attenzione a prelevare il risultato prima del componente masking che potrebbe alterare il risultato stesso.
63 Progettazione in VHDL del Vertex Shader Pag. 137 Gli inverter e gli swizzler vengono pilotati direttamente dai bit di controllo che sono presenti nell instruction latch, mentre per il dest_mask è previsto un ulteriore latch. Infine, quando l istruzione è stata eseguita e nel bus di destinazione è presente il risultato corretto, viene attivata l ultima sezione, quella di decodifica, che ha lo scopo di controllare dove deve essere memorizzato il risultato prodotto. Il cerchio rosso rappresenta la linea di connessione che abilita il registro temporaneo A0.x; le altre linee di uscita vanno ai segnali di address_in e di enabled_in dei banchi V_out e R, le cui linee di data_in sono collegate direttamente al Res_bus. In questo caso non sono necessari dei latch per prelevare il dato dal bus perché le unità sono regolate da un segnale di enabled e quindi si comportano esse stesse come dei latch. D altra parte, essendo banchi di registri, è presumibile che la loro implementazione sia ottenibile mediante banchi di latch L unità di controllo L unità di controllo viene realizzata come Blocco all interno del processo
64 Progettazione in VHDL del Vertex Shader Pag. 138 di funzionamento dell entità Vertex Shader; il suo scopo è pilotare i segnali di controllo di tutte le unità viste precedentemente. La realizzazione strutturale del Vertex Shader poggia infatti interamente su questo componente e le uniche righe di codice che veramente eseguono operazioni sono interne a questa unità. Tutto il funzionamento si basa poi sulla sincronia dei singoli componenti e sul rispetto dei ritardi delle porte. Su quest ultimo aspetto non ci si può soffermare molto, dato che per ottenere dati reali sarebbe necessario riferirsi ad una FPGA o ad una tecnologia su silicio che imponga i ritardi massimi delle varie porte logiche. Inoltre per parlare correttamente di ritardi è necessario che il progetto scenda molto a basso livello, ovvero tutto deve essere realizzato mediante porte logiche, da una descrizione behavioural non è infatti possibile stabilire con esattezza alcun ritardo. Per tutte le componenti di cui non si conosce il ritardo, come l ALU, è quindi necessario utilizzare un metodo di comunicazione mediante un bit di ready che comunica il completamento dell operazione richiesta. Tool particolari per le FPGA, come il Quartus II dell Altera, permettono di fornire la descrizione behevioural ed è il software che calcola i ritardi massimi basandosi sulla sintesi del componente e sulla FPGA correntemente selezionata. Questo è possibile perché per sintetizzare la logica all interno dell FPGA è necessario che il software calcoli l effettiva implementazione in porte logiche anche delle parti che hanno una descrizione comportamentale. Se si deve realizzare un chip custom, come effettuato da Nvidia, la simulazione in VHDL serve a controllare la correttezza della logica di funzionamento e non è da escludere che poi il layout venga fatto full-custom per ottenere le massime prestazioni dal sistema. Per realizzare in VHDL l unità di controllo ricorriamo ad una direttiva block end block all interno della descrizione architetturale della struttura del Vertex Shader. Questo componente si comporta in modo molto simile ad un entità perché ha porte di ingresso e di uscita ed ha un processo interno che ha lo scopo di regolare i vari segnali di controllo che sono presenti nella descrizione strutturale. Nella realizzazione in esame questo blocco viene chiamato CONTROL_UNIT e viene così realizzato: CONTROL_UNIT : block port ( clk1, clk2 : in bit; ALU_cmd : out ALU_VS_cmd; PC_Reset, PC_latch_en : out bit; INC_en : out bit; INC_num : out bit_vector(2 downto 0); Out_instr_en : out bit; IL_en : out bit; Mux_op1_sel : out bit; Latch_op1_en : out bit; Latch_Op2_reset, Latch_Op2_en : out bit; DEC_DEST_En : out bit; Latch_dest_en : out bit; Latch_Mask_en : out bit; ALU_Ready : in bit; Op_Cntr : in DWORD; --Le seguenti porte sono state inserite solamente a scopo di debug OP1 : EIGHTWORD; OP2 : EIGHTWORD; RES : EIGHTWORD; Data_out_vin : EIGHTWORD; Data_out_C : EIGHTWORD; Op1_Swizzled : EIGHTWORD;
65 Progettazione in VHDL del Vertex Shader Pag. 139 ); port map ( Op2_Swizzled : EIGHTWORD; Op1_Latched : EIGHTWORD; Op2_Latched : EIGHTWORD; Op1_Inverted : EIGHTWORD; Op2_Inverted : EIGHTWORD; Addr_Out_PC : bit_vector(pc_width - 1 downto 0); PC_in : bit_vector(pc_width - 1 downto 0); Instr_latch_in : EIGHTWORD; V_in_en : bit; R_in_en : bit; R_in_addr : bit_vector(r_width - 1 downto 0); V_in_addr : bit_vector(v_out_width - 1 downto 0) Per non complicare troppo il progetto, si preferisce dare alle porte di I/O dell unità di controllo lo stesso nome dei segnali a cui sono collegate, in questo modo il port map è assolutamente banale e per questo non è nemmeno stato riportato. Quando si simula la struttura nella sua interezza, è molto interessante osservare come i segnali varino all interno di essa, ma naturalmente è possibile settare breakpoint solamente sulle righe di codice che vengono effettivamente eseguite, ovvero all interno del processo di funzionamento dell unità di controllo. Quando il ModelSim incontra un interruzione rende possibile, mediante la finestra signal, l osservazione della variazione dei segnali 49 presenti nell entità o nel blocco in esecuzione. Per osservare anche i segnali esterni è necessario inserire a mano il path completo e questo risulta molto dispendioso in termini di tempo. Per ovviare a questo problema è sufficiente aggiungere all unità di controllo porte aggiuntive a cui vengono collegati i segnali che si vogliono osservare, in questo modo è possibile con un solo click visualizzare nella finestra wave tutti i segnali che interessa tenere sotto controllo. Nel listato questo fatto è messo in evidenza con un commento che suddivide le porte effettivamente associate a segnali di controllo da quelle inserite solamente a scopo di debug. Naturalmente, qualora fosse necessario sintetizzare il circuito una volta testato, queste porte andranno rimosse perché non svolgono effettivamente nessun ruolo nel funzionamento della struttura. L unica parte in cui si scrive effettivamente del codice per l entità Vertex Shader è nel solo processo che sintetizza l unità di controllo: state_machine : process type CONTROL_UNIT_STATE is ( Idle, --Durante la programmazione dall'esterno Fetch_instr, --Inizio ad eseguire l'istruzione waiting_result, --Aspetto il ready dell'alu Waiting_MAD --Aspetta che la fase di mul --dell'istruzione MAD sia completa ); variable Stato, ProssimoStato : CONTROL_UNIT_STATE; variable DebugOut : string (1 to MAX_STRING_LEN); wait until clk1'event and clk1 = '1'; if start_execution = '1' then --devo iniziare ad elaborare PC_Reset <= '1' after dly; wait until clk2'event and clk2 = '1'; --Resetto il PC e poi fetcho la prima istruzione PC_Reset <= '0' after dly; 49 All interno della finestra wave.
66 Progettazione in VHDL del Vertex Shader Pag. 140 stato := Fetch_instr; else --altrimenti c'è il funzionamento normale Come prima cosa vengono definiti gli stati possibili dell unità di controllo che, nel caso in esame, sono solamente quattro: lo stato idle, durante il quale l unità di controllo è inattiva, lo stato di fetch_instr, che preleva l istruzione successiva dall instruction pool e decodifica l istruzione corrente, ed infine i due stati di attesa nei quali si aspetta che l operazione corrente dell ALU_VS sia completata. Il processo inizia al fronte di salita del clock1 durante il quale si controlla se si deve iniziare ad elaborare il vertice successivo. La struttura Vertex Shader infatti iniza il suo funzionamento solamente nello stato idle, nel quale è inattiva. Durante questo periodo dall esterno vengono caricati l instruction pool, il banco di costanti ed il vertice corrente. Quando la programmazione dall esterno è terminata 50, il bit di start_execution viene impostato ad 1 e il Vertex Shader inizia il suo funzionamento ed incomincia ad eseguire le istruzioni che sono presenti nell instruction pool. Quando si raggiunge l opcode che indica la fine del set di istruzioni corrente, il Vertex Shader torna nello stato di idle ed aspetta che dall esterno vengano aggiornati i dati per riprendere l esecuzione dal vertice successivo. Naturalmente, prima di aggiornare i dati, vengono prelevati i risultati del vertice precedente che verranno passati agli stadi successivi della pipeline. Questo ciclo si ripete per tutti i vertici presenti nel frame corrente. case Stato is when idle => --Non succede nulla, lo shader è inattivo when Fetch_instr=> --Azzero i segnali di controllo per l'incremento PC_latch_en <= '0' after dly; INC_en <= '0' after dly; --inizia a decodificare il SRC_DEC IL_en <= '1' after dly; Out_instr_en <= '1' after dly; Latch_dest_en <= '1' after dly; Latch_mask_en <= '1' after dly; --Azzero dei segnali per preparare il ciclo di decodifica Mux_Op1_sel <= '0' after dly; latch_op1_en <= '0' after dly; latch_op2_en <= '0' after dly; latch_op2_reset <= '0' after dly; --Qui inizia il ciclo di decodifica degli opcode perchè --tutti i dati sono nei registri e sono subito pronti. wait until clk2'event and clk2 = '1'; Come detto precedentemente, nello stato di idle non deve essere svolta alcuna operazione. L inizio del ciclo istruzione si ha invece con lo stato Fetch_instr, nel quale, come prima cosa, bisogna accertarsi che il program counter non venga ulteriormente incrementato (INC_en <= 0 ) e che il valore memorizzato nel latch-master passi nel latch-slave mettendo a zero il suo segnale di latch_en. I successivi quattro segnali servono per far confluire nel latch istruzioni l istruzione corrente. Naturalmente è necessario anche attivare i latch che memorizzano i bit del registro destinazione e della sua mascheratura; questi ultimi due latch sono necessari a causa dell istruzione mad che verrà esaminata in seguito. Si attende poi il fronte di salita di clk2 per effettuare la 50 La programmazione dall esterno viene fatta dall unità di controllo del chip grafico di cui il Vertex Shader fa parte.
67 Progettazione in VHDL del Vertex Shader Pag. 141 decodifica dell istruzione. IL_en <= '0' after dly; Out_instr_en <= '0' after dly; DECODIFICA OPCODE Mentre decodifico l'opcode incremento il PC. INC_en <= '1' after dly; PC_latch_en <= '1' after dly; Latch_dest_en <= '0' after dly; Latch_mask_en <= '0' after dly; case Op_cntr is OPCODE ADD when op_add => --Trace dei dati che sono nel bus StrCpy(DebugOut, "Istruzione Add, Op1_bus = "); PrintEightWord(DebugOut, Op1_bus); StrCpy(DebugOut, StrCat(DebugOut, " Op2_bus = ")); PrintEightWord(DebugOut, Op2_bus); assert false report DebugOut severity note; DebugOut(1) := NUL; --Impongo il comando alla ALU, inizia al fronte di --clk1 ALU_cmd <= add after dly; ALU_en <= '1' after dly; latch_op1_en <= '1' after dly; latch_op2_en <= '1' after dly; --A questo punto non posso fare altro che aspettare --che l'alu abbia finito Stato := waiting_result; --Debbo preparare anche l'incremento per l'istruzione INC_Num <= "100" after dly; Come prima cosa ci si assicura che il contenuto del latch istruzioni non possa cambiare, ponendo a zero il suo segnale di abilitazione e contemporaneamente si inizia ad incrementare il contenuto del program counter, operazione che può tranquillamente avvenire assieme alla decodifica, dato che interessa unità funzionali differenti. Ora può essere decodificata l istruzione (nel codice sopra riportato viene mostrata l istruzione add che comunque è un prototipo per tutte le altre). Una volta individuata l istruzione si impone alla ALU_VS il comando corretto, si abilitano i latch che prelevano i dati dal bus degli operandi, e come ultima cosa è necessario indicare all INC-PC di quanto incrementare il valore del program counter. Lo stato successivo è il waiting_result, nel quale si attende semplicemente che l ALU abbia finito l elaborazione e che nel Res_bus sia presente il risultato corretto. when waiting_result => --L'ALU lavora sul fronte di salita clk1, per cui --io aspetto il risultato nel fronte 2 wait until clk2'event and clk2 = '1'; --Si riportano i segnali di controllo allo stato iniziale INC_en <= '0' after dly; PC_latch_en <= '0' after dly; --Si toglie il comando alla ALU, che ha già iniziato ALU_en <= '0' after dly; --Di ritorno dal MAd devo fare ritornare a posto il mux di --selezione op1, lo faccio pertanto per tutte le istruzioni Mux_Op1_sel <= '0' after dly; if ALU_Ready = '1' then --Faccio un po di trace
68 Progettazione in VHDL del Vertex Shader Pag. 142 StrCpy(DebugOut, "Risultato = "); PrintEightWord(DebugOut, Res_bus); assert false report DebugOut severity note; DebugOut(1) := NUL; --Scrivo nella destinazione il risultato DEC_DEST_en <= '1' after dly; --Nella fase di clk2 si disabilita il segnale di scrittura wait until clk2'event and clk2 = '1'; DEC_DEST_en <= '0' after dly; Stato := Fetch_instr; Giunti in questo stato il PC è ormai correttamente incrementato e quindi si possono disabilitare i segnali di latch_en e di inc_en: alla ALU_VS viene tolto il segnale di enabled, perché ha già iniziato ad elaborare l istruzione. A questo punto è possibile aspettare che la ALU_VS termini l esecuzione e memorizzare il risultato nel registro destinazione abilitando l unità DEC_DEST, che verrà immediatamente disabilitata nel fronte successivo di clk2. Si ritorna quindi nello stato di fetch_instr ed il ciclo istruzione è completato Opcode particolari La struttura per l opcode add è valida per quasi tutti gli opcode dell instruction set con piccole eccezioni che verranno qui esaminate. Il primo opcode particolare è il nop che non svolge assolutamente nessuna istruzione; la sua realizzazione è immediata perché basta rimanere nello stato di fetch_instr e passare all istruzione successiva (per sicurezza i corrispondenti segnali vengono comunque attivati anche se non sarebbe necessario). In questo modo, se il funzionamento del processore venisse successivamente modificato, l istruzione nop non soffrirebbe di nessun problema. when op_nop => --Trace dei dati che sono nel bus StrCpy(DebugOut, "Istruzione NOP, Op1_bus = "); PrintEightWord(DebugOut, Op1_bus); StrCpy(DebugOut, StrCat(DebugOut, " Op2_bus = ")); PrintEightWord(DebugOut, Op2_bus); assert false report DebugOut severity note; DebugOut(1) := NUL; --Impongo il comando alla ALU, tanto lei inizia al fronte --di clk1 ALU_cmd <= ALU_VS_Nop after dly; ALU_en <= '0' after dly; latch_op1_en <= '0' after dly; latch_op2_en <= '0' after dly; --Passo direttamente al fetching Stato := Fetch_instr; --Debbo preparare anche l'incremento per l'istruzione INC_Num <= "001" after dly; Un altro opcode particolare è quello che indica la fine dello shader corrente. L unica operazione svolta in questo caso è riportare il Vertex Shader nello stato di idle e comunicare all esterno che l esecuzione è terminata e che il vertice corrente è stato elaborato. when opcode_end => StrCpy(DebugOut, "END SHADER!!! "); assert false report DebugOut severity note; DebugOut(1) := NUL;
69 Progettazione in VHDL del Vertex Shader Pag. 143 ready <= '1' after dly; Stato := idle; Una trattazione particolare merita invece l istruzione mad che deve essere eseguita in due passaggi distinti, dato che l ALU_VS opera contemporaneamente solo su due numeri. La soluzione è eseguire prima l operazione mul e poi incrementare il program counter di uno; in questo modo nell instruction latch il posto dell op2 viene occupato dall op3 e il codice del registro dest va ad occupare quello dell opcode. A questo punto è sufficiente eseguire l operazione add tra il secondo operando ed il dato che si trova nel res_bus. Questa tecnica rende però necessaria l introduzione dei latch_dest e latch_mask visti in precedenza, il cui scopo è quello di mantenere le informazioni necessarie sul registro di destinazione durante la seconda parte dell operazione, quando nella posizione dest dell instruction latch è shiftato il codice del primo registro sorgente. Viene inoltre inserito un nuovo stato del controllore chiamato Waiting_mad nel quale si attende il completamento dell istruzione mul da parte dell ALU; successivamente si impone l operazione di add dopo aver preventivamente attivato il mux_op1 in modo da utilizzare l uscita dell ALU come op1. Fatto questo si passa allo stato waiting_result, e si attende che la seconda parte dell operazione sia stata completata come in qualsiasi altra istuzione. Una considerazione deve essere fatta sul collegamento diretto che c è tra l uscita dell ALU_VS ed il suo ingresso op1, che per la struttura corrente dell ALU_VS funziona correttamente, ma che potrebbe dare problemi se tale struttura fosse cambiata in futuro. Per ovviare ad eventuali malfunzionamenti è sufficiente utilizzare un latch che permetta di mantenere costante il dato all ingresso dell ALU L unità di test e la simulazione L unità di test svolge le operazioni che normalmente sarebbero svolte dall unità di controllo della GPU; per ottenere una simulazione efficiente è necessario che l entità di test sia capace di leggere e scrivere su file, in modo da avere la massima flessibilità per la simulazione. Come nelle simulazioni precedenti l entità di test è collegata a tutte le porte dell entità da testare e serve a fornire i dati in ingresso e controllare la validità dei dati in uscita. Nel caso in esame è più semplice fare effettuare un dump del contenuto dei registri di uscita in modo da controllare che il vertice corrente sia stato elaborato correttamente. Lo schema a blocchi del test è mostrato a pagina seguente:
70 Progettazione in VHDL del Vertex Shader Pag. 144 Vertex Shader 1.4 Test bench for VHDL simulation end loop; VS.vsowrite_en <= Data(128) '0' after dly; C.txtSelector <= Address(10) "000" after dly; State := state Selector(2) + 1; TEST VERTEX V.txt when 3 UNIT WriteEn(1) => SHADER ReadEn (1) start_execution <= '1' after dly; Out.txt StartExecution (1) wait until clk1 = '1'; start_execution <= '0' after dly; wait until ready = '1'; Done (1) Data <= X"0000_0000_0000_ Come prima cosa si realizza uno shader utilizzando tutte le istruzioni da testare e cercando di coprire tutta la casistica possibile. L unità di test carica il Vertex Shader ed aspetta che l esecuzione sia terminata, successivamente legge i risultati e ne fornisce l output su di un file. Come esempio si utilizzerà il seguente shader. add opos, v0, c0 Dp4 od0, v1, c0 Mul r8, v1, c0 nop Max ot0, r8, v1 add od1.xw, r8.wwww, r8.yyxx add ot1, v0, -c[2] mov a0.x, c[2] mov ot2, c[1 + a0.x].xy mov ofog, c[1 + a0.x] lit ot3, v0 expp ot2, v0 mov opts, c[1] logp ot1, v1 mov r0, c4 Mad ot0.xzw, v0, r0, c1 È da ricordare comunque che l assemblatore di NVIDIA non permette di utilizzare i registri ot4 - ot8 perché non presenti nelle loro schede. Naturalmente basta simulare i registri ot0 - ot1 per affermare che la struttura produca effettivamente i risultati corretti. Il file precedente viene compilato e debbono essere forniti anche i due file che contengono i valori del vertice corrente e del banco di costanti. (C.txt)
71 Progettazione in VHDL del Vertex Shader Pag (V.txt) Si procede quindi alla simulazione mediante l entità di test. Per controllare la correttezza delle operazioni è possibile osservare il tracing effettuato nella finestra di debug: # ** Note: Istruzione Add, Op1_bus = Op2_bus = # Time: 1550 ns Iteration: 1 Instance: /testvs/vs/control_unit # ** Note: Risultato = # Time: 1700 ns Iteration: 1 Instance: /testvs/vs/control_unit # ** Note: Istruzione DP4, Op1_bus = Op2_bus = # Time: 1850 ns Iteration: 1 Instance: /testvs/vs/control_unit run # ** Note: Risultato = # Time: 2200 ns Iteration: 1 Instance: /testvs/vs/control_unit L istruzione nop, non avendo operandi, fa comparire il messaggio di errore di primo operando non valido, perché l unità di decoding DEC_SRC cerca di trattare l opcode successivo come il codice di un registro sorgente: # ** Note: Istruzione NOP, Op1_bus = Op2_bus = # Time: 2650 ns Iteration: 1 Instance: /testvs/vs/control_unit # ** Error: Registro 1 sorgente invalido Naturalmente questo non costituisce un errore e l esecuzione non viene in nessun modo compromessa perché la nop serve solamente a far passare un colpo di clock senza svolgere nulla; il contenuto dei bus viene quindi ignorato. Si prosegue controllando la correttezza dei segnali e dei risultati presenti nei bus per tutte le rimanenti operazioni. Rimane da verificare che la mascheratura del registro destinazione funzioni correttamente, ma per fare questo è necessario andare a consultare il file out.txt in cui viene scritto il vettore di uscita del Vertex Shader: opos : od0 : od1 : ot0 : ot1 : ot2 : ot3 : ot4 : ot5 : ot6 : ot7 : ofog : opts :
72 Progettazione in VHDL del Vertex Shader Pag. 146 E molto semplice controllare i risultati in questo modo ed è inoltre molto semplice creare e compilare un altro shader per verificare le istruzioni che non sono state incluse in questo test. Come ultima verifica si deve andare a controllare gli effettivi segnali presenti nel circuito. Per controllare i segnali è sufficiente utilizzare la finestra wave del ModelSim e verificare tutti i segnali a cui si è interessati; il risultato è mostrato nella figura a pagina seguente, della quale viene qui fornita una sommaria descrizione. 1. Si attivano il latch istruzioni ed i due latch per il registro destinazione e per la sua mascheratura. 2. Il contenuto del latch istruzioni cambia e nei bus vengono posti gli operandi corretti. 3. Viene imposta l operazione all ALU_VS e viene disabilitato il latch dell istruzione corrente, dato che il program counter deve essere incrementato. 4. L ALU_VS termina l esecuzione e nel res_bus si trova il risultato dell operazione corrente. 5. Nel successivo fronte di salita di clk2 la ALU_VS comunica la fine dell istruzione e quindi l unità di controllo abilita il DEC_DEST per scrivere il risultato nel registro corretto.
73 Progettazione in VHDL del Vertex Shader Pag
4 3 4 = 4 x 10 2 + 3 x 10 1 + 4 x 10 0 aaa 10 2 10 1 10 0
Rappresentazione dei numeri I numeri che siamo abituati ad utilizzare sono espressi utilizzando il sistema di numerazione decimale, che si chiama così perché utilizza 0 cifre (0,,2,3,4,5,6,7,8,9). Si dice
lo 2 2-1 - PERSONALIZZARE LA FINESTRA DI WORD 2000
Capittol lo 2 Visualizzazione 2-1 - PERSONALIZZARE LA FINESTRA DI WORD 2000 Nel primo capitolo sono state analizzate le diverse componenti della finestra di Word 2000: barra del titolo, barra dei menu,
Corso di Informatica Generale (C. L. Economia e Commercio) Ing. Valerio Lacagnina Rappresentazione in virgola mobile
Problemi connessi all utilizzo di un numero di bit limitato Abbiamo visto quali sono i vantaggi dell utilizzo della rappresentazione in complemento alla base: corrispondenza biunivoca fra rappresentazione
Excel. A cura di Luigi Labonia. e-mail: [email protected]
Excel A cura di Luigi Labonia e-mail: [email protected] Introduzione Un foglio elettronico è un applicazione comunemente usata per bilanci, previsioni ed altri compiti tipici del campo amministrativo
Esercitazione Informatica I AA 2012-2013. Nicola Paoletti
Esercitazione Informatica I AA 2012-2013 Nicola Paoletti 4 Gigno 2013 2 Conversioni Effettuare le seguenti conversioni, tenendo conto del numero di bit con cui si rappresenta il numero da convertire/convertito.
CREAZIONE DI UN DATABASE E DI TABELLE IN ACCESS
CONTENUTI: CREAZIONE DI UN DATABASE E DI TABELLE IN ACCESS Creazione database vuoto Creazione tabella Inserimento dati A) Creazione di un database vuoto Avviamo il programma Microsoft Access. Dal menu
EXCEL PER WINDOWS95. sfruttare le potenzialità di calcolo dei personal computer. Essi si basano su un area di lavoro, detta foglio di lavoro,
EXCEL PER WINDOWS95 1.Introduzione ai fogli elettronici I fogli elettronici sono delle applicazioni che permettono di sfruttare le potenzialità di calcolo dei personal computer. Essi si basano su un area
Alessandro Pellegrini
Esercitazione sulle Rappresentazioni Numeriche Esistono 1 tipi di persone al mondo: quelli che conoscono il codice binario e quelli che non lo conoscono Alessandro Pellegrini Cosa studiare prima Conversione
Database 1 biblioteca universitaria. Testo del quesito
Database 1 biblioteca universitaria Testo del quesito Una biblioteca universitaria acquista testi didattici su indicazione dei professori e cura il prestito dei testi agli studenti. La biblioteca vuole
Siamo così arrivati all aritmetica modulare, ma anche a individuare alcuni aspetti di come funziona l aritmetica del calcolatore come vedremo.
DALLE PESATE ALL ARITMETICA FINITA IN BASE 2 Si è trovato, partendo da un problema concreto, che con la base 2, utilizzando alcune potenze della base, operando con solo addizioni, posso ottenere tutti
Testi di Esercizi e Quesiti 1
Architettura degli Elaboratori, 2009-2010 Testi di Esercizi e Quesiti 1 1. Una rete logica ha quattro variabili booleane di ingresso a 0, a 1, b 0, b 1 e due variabili booleane di uscita z 0, z 1. La specifica
Sistemi di Numerazione Binaria NB.1
Sistemi di Numerazione Binaria NB.1 Numeri e numerali Numero: entità astratta Numerale : stringa di caratteri che rappresenta un numero in un dato sistema di numerazione Lo stesso numero è rappresentato
Modulo. Programmiamo in Pascal. Unità didattiche COSA IMPAREREMO...
Modulo A Programmiamo in Pascal Unità didattiche 1. Installiamo il Dev-Pascal 2. Il programma e le variabili 3. Input dei dati 4. Utilizziamo gli operatori matematici e commentiamo il codice COSA IMPAREREMO...
BMSO1001. Virtual Configurator. Istruzioni d uso 02/10-01 PC
BMSO1001 Virtual Configurator Istruzioni d uso 02/10-01 PC 2 Virtual Configurator Istruzioni d uso Indice 1. Requisiti Hardware e Software 4 1.1 Requisiti Hardware 4 1.2 Requisiti Software 4 2. Concetti
Access. Microsoft Access. Aprire Access. Aprire Access. Aprire un database. Creare un nuovo database
Microsoft Access Introduzione alle basi di dati Access E un programma di gestione di database (DBMS) Access offre: un supporto transazionale limitato Meccanismi di sicurezza, protezione di dati e gestione
Cos è ACCESS? E un programma di gestione di database (DBMS) Access offre: un ambiente user frendly da usare (ambiente grafico)
Cos è ACCESS? E un programma di gestione di database (DBMS) Access offre: un ambiente user frendly da usare (ambiente grafico) 1 Aprire Access Appare una finestra di dialogo Microsoft Access 2 Aprire un
Mon Ami 3000 Varianti articolo Gestione di varianti articoli
Prerequisiti Mon Ami 3000 Varianti articolo Gestione di varianti articoli L opzione Varianti articolo è disponibile per le versioni Azienda Light e Azienda Pro e include tre funzionalità distinte: 1. Gestione
Definire all'interno del codice un vettore di interi di dimensione DIM, es. int array[] = {1, 5, 2, 4, 8, 1, 1, 9, 11, 4, 12};
ESERCIZI 2 LABORATORIO Problema 1 Definire all'interno del codice un vettore di interi di dimensione DIM, es. int array[] = {1, 5, 2, 4, 8, 1, 1, 9, 11, 4, 12}; Chiede all'utente un numero e, tramite ricerca
Gestione Rapporti (Calcolo Aree)
Gestione Rapporti (Calcolo Aree) L interfaccia dello strumento generale «Gestione Rapporti»...3 Accedere all interfaccia (toolbar)...3 Comandi associati alle icone della toolbar...4 La finestra di dialogo
MODULO 4: FOGLIO ELETTRONICO (EXCEL)
MODULO 4: FOGLIO ELETTRONICO (EXCEL) 1. Introduzione ai fogli elettronici I fogli elettronici sono delle applicazioni che permettono di sfruttare le potenzialità di calcolo dei Personal computer. Essi
Sistema operativo. Sommario. Sistema operativo...1 Browser...1. Convenzioni adottate
MODULO BASE Quanto segue deve essere rispettato se si vuole che le immagini presentate nei vari moduli corrispondano, con buona probabilità, a quanto apparirà nello schermo del proprio computer nel momento
Obiettivi dell Analisi Numerica. Avviso. Risoluzione numerica di un modello. Analisi Numerica e Calcolo Scientifico
M. Annunziato, DIPMAT Università di Salerno - Queste note non sono esaustive ai fini del corso p. 3/43 M. Annunziato, DIPMAT Università di Salerno - Queste note non sono esaustive ai fini del corso p.
Architettura degli Elaboratori I Esercitazione 1 - Rappresentazione dei numeri
Architettura degli Elaboratori I Esercitazione 1 - Rappresentazione dei numeri 1 Da base 2 a base 10 I seguenti esercizi richiedono di convertire in base 10 la medesima stringa binaria codificata rispettivamente
Funzioni in C. Violetta Lonati
Università degli studi di Milano Dipartimento di Scienze dell Informazione Laboratorio di algoritmi e strutture dati Corso di laurea in Informatica Funzioni - in breve: Funzioni Definizione di funzioni
. A primi passi con microsoft a.ccepss SommarIo: i S 1. aprire e chiudere microsoft access Start (o avvio) l i b tutti i pro- grammi
Capitolo Terzo Primi passi con Microsoft Access Sommario: 1. Aprire e chiudere Microsoft Access. - 2. Aprire un database esistente. - 3. La barra multifunzione di Microsoft Access 2007. - 4. Creare e salvare
Capitolo 3. L applicazione Java Diagrammi ER. 3.1 La finestra iniziale, il menu e la barra pulsanti
Capitolo 3 L applicazione Java Diagrammi ER Dopo le fasi di analisi, progettazione ed implementazione il software è stato compilato ed ora è pronto all uso; in questo capitolo mostreremo passo passo tutta
Informatica. Rappresentazione dei numeri Numerazione binaria
Informatica Rappresentazione dei numeri Numerazione binaria Sistemi di numerazione Non posizionali: numerazione romana Posizionali: viene associato un peso a ciascuna posizione all interno della rappresentazione
Gestione Risorse Umane Web
La gestione delle risorse umane Gestione Risorse Umane Web Generazione attestati di partecipazione ai corsi di formazione (Versione V03) Premessa... 2 Configurazione del sistema... 3 Estrattore dati...
Gestione delle informazioni necessarie all attività di validazione degli studi di settore. Trasmissione degli esempi da valutare.
Gestione delle informazioni necessarie all attività di validazione degli studi di settore. Trasmissione degli esempi da valutare. E stato previsto l utilizzo di uno specifico prodotto informatico (denominato
INFORMATICA 1 L. Mezzalira
INFORMATICA 1 L. Mezzalira Possibili domande 1 --- Caratteristiche delle macchine tipiche dell informatica Componenti hardware del modello funzionale di sistema informatico Componenti software del modello
Soluzione dell esercizio del 2 Febbraio 2004
Soluzione dell esercizio del 2 Febbraio 2004 1. Casi d uso I casi d uso sono riportati in Figura 1. Figura 1: Diagramma dei casi d uso. E evidenziato un sotto caso di uso. 2. Modello concettuale Osserviamo
GUIDA RAPIDA PER LA COMPILAZIONE DELLA SCHEDA CCNL GUIDA RAPIDA PER LA COMPILAZIONE DELLA SCHEDA CCNL
GUIDA RAPIDA BOZZA 23/07/2008 INDICE 1. PERCHÉ UNA NUOVA VERSIONE DEI MODULI DI RACCOLTA DATI... 3 2. INDICAZIONI GENERALI... 4 2.1. Non modificare la struttura dei fogli di lavoro... 4 2.2. Cosa significano
Come costruire una presentazione. PowerPoint 1. ! PowerPoint permette la realizzazione di presentazioni video ipertestuali, animate e multimediali
PowerPoint Come costruire una presentazione PowerPoint 1 Introduzione! PowerPoint è uno degli strumenti presenti nella suite Office di Microsoft! PowerPoint permette la realizzazione di presentazioni video
ACCESSO AL SISTEMA HELIOS...
Manuale Utente (Gestione Formazione) Versione 2.0.2 SOMMARIO 1. PREMESSA... 3 2. ACCESSO AL SISTEMA HELIOS... 4 2.1. Pagina Iniziale... 6 3. CARICAMENTO ORE FORMAZIONE GENERALE... 9 3.1. RECUPERO MODELLO
TRASMISSIONE RAPPORTO ARBITRALE IN FORMATO PDF
TRASMISSIONE RAPPORTO ARBITRALE IN FORMATO PDF Come da disposizioni di inizio stagione, alcune Delegazioni provinciali hanno richiesto la trasmissione dei referti arbitrali solo tramite fax o tramite mail.
Esame di Informatica CHE COS È UN FOGLIO ELETTRONICO CHE COS È UN FOGLIO ELETTRONICO CHE COS È UN FOGLIO ELETTRONICO. Facoltà di Scienze Motorie
Facoltà di Scienze Motorie CHE COS È UN FOGLIO ELETTRONICO Una tabella che contiene parole e numeri che possono essere elaborati applicando formule matematiche e funzioni statistiche. Esame di Informatica
SISTEMI DI NUMERAZIONE E CODICI
SISTEMI DI NUMERAZIONE E CODICI Il Sistema di Numerazione Decimale Il sistema decimale o sistema di numerazione a base dieci usa dieci cifre, dette cifre decimali, da O a 9. Il sistema decimale è un sistema
On-line Corsi d Informatica sul web
On-line Corsi d Informatica sul web Corso base di FrontPage Università degli Studi della Repubblica di San Marino Capitolo1 CREARE UN NUOVO SITO INTERNET Aprire Microsoft FrontPage facendo clic su Start/Avvio
ISTRUZIONI SULLE OPERAZIONI DI CAMBIO ANNO CONTABILE 2005/2006 LIQUIDAZIONE IVA - STAMPA REGISTRI - CHIUSURA/APERTURA CONTI
ISTRUZIONI SULLE OPERAZIONI DI CAMBIO ANNO CONTABILE 2005/2006 LIQUIDAZIONE IVA - STAMPA REGISTRI - CHIUSURA/APERTURA CONTI PREMESSA La procedura contabile consente la gestione di più anni in linea. Questo
Introduzione al MATLAB c Parte 2
Introduzione al MATLAB c Parte 2 Lucia Gastaldi Dipartimento di Matematica, http://dm.ing.unibs.it/gastaldi/ 18 gennaio 2008 Outline 1 M-file di tipo Script e Function Script Function 2 Costrutti di programmazione
ESEMPIO 1: eseguire il complemento a 10 di 765
COMPLEMENTO A 10 DI UN NUMERO DECIMALE Sia dato un numero N 10 in base 10 di n cifre. Il complemento a 10 di tale numero (N ) si ottiene sottraendo il numero stesso a 10 n. ESEMPIO 1: eseguire il complemento
Introduzione alla programmazione in C
Introduzione alla programmazione in C Testi Consigliati: A. Kelley & I. Pohl C didattica e programmazione B.W. Kernighan & D. M. Ritchie Linguaggio C P. Tosoratti Introduzione all informatica Materiale
RISOLUTORE AUTOMATICO PER SUDOKU
RISOLUTORE AUTOMATICO PER SUDOKU Progetto Prolog - Pierluigi Tresoldi 609618 INDICE 1.STORIA DEL SUDOKU 2.REGOLE DEL GIOCO 3.PROGRAMMAZIONE CON VINCOLI 4.COMANDI DEL PROGRAMMA 5.ESEMPI 1. STORIA DEL SUDOKU
LABORATORIO DI PROGRAMMAZIONE 2012 2013 EDIZIONE 1, TURNO B
LABORATORIO DI PROGRAMMAZIONE 2012 2013 EDIZIONE 1, TURNO B 23.XI.2012 VINCENZO MARRA Indice Esercizio 1 1 Menu 1 Tempo: 35 min. 2 Commento 1 2 Esercizio 2 2 Ordinamento e ricerca binaria con la classe
USO DI EXCEL CLASSE PRIMAI
USO DI EXCEL CLASSE PRIMAI In queste lezioni impareremo ad usare i fogli di calcolo EXCEL per l elaborazione statistica dei dati, per esempio, di un esperienza di laboratorio. Verrà nel seguito spiegato:
Convertitori numerici in Excel
ISTITUTO DI ISTRUZIONE SUPERIORE G. M. ANGIOY CARBONIA Convertitori numerici in Excel Prof. G. Ciaschetti Come attività di laboratorio, vogliamo realizzare dei convertitori numerici con Microsoft Excel
COLLI. Gestione dei Colli di Spedizione. Release 5.20 Manuale Operativo
Release 5.20 Manuale Operativo COLLI Gestione dei Colli di Spedizione La funzione Gestione Colli consente di generare i colli di spedizione in cui imballare gli articoli presenti negli Ordini Clienti;
FONDAMENTI di INFORMATICA L. Mezzalira
FONDAMENTI di INFORMATICA L. Mezzalira Possibili domande 1 --- Caratteristiche delle macchine tipiche dell informatica Componenti hardware del modello funzionale di sistema informatico Componenti software
E possibile modificare la lingua dei testi dell interfaccia utente, se in inglese o in italiano, dal menu [Tools
Una breve introduzione operativa a STGraph Luca Mari, versione 5.3.11 STGraph è un sistema software per creare, modificare ed eseguire modelli di sistemi dinamici descritti secondo l approccio agli stati
Guida all uso di Java Diagrammi ER
Guida all uso di Java Diagrammi ER Ver. 1.1 Alessandro Ballini 16/5/2004 Questa guida ha lo scopo di mostrare gli aspetti fondamentali dell utilizzo dell applicazione Java Diagrammi ER. Inizieremo con
I file di dati. Unità didattica D1 1
I file di dati Unità didattica D1 1 1) I file sequenziali Utili per la memorizzazione di informazioni testuali Si tratta di strutture organizzate per righe e non per record Non sono adatte per grandi quantità
Procedura Index On Line
Procedura Index On Line Società Cattolica di Assicurazione Gruppo Cattolica Assicurazioni Manuale Operativo Edizione di Gennaio 2008 Pag. 1 di 7 MANUALE UTENTE INDEX ON LINE Dopo aver selezionato la voce
Manuale NetSupport v.10.70.6 Liceo G. Cotta Marco Bolzon
NOTE PRELIMINARI: 1. La versione analizzata è quella del laboratorio beta della sede S. Davide di Porto, ma il programma è presente anche nel laboratorio alfa (Porto) e nel laboratorio di informatica della
LA TRASMISSIONE DELLE INFORMAZIONI QUARTA PARTE 1
LA TRASMISSIONE DELLE INFORMAZIONI QUARTA PARTE 1 I CODICI 1 IL CODICE BCD 1 Somma in BCD 2 Sottrazione BCD 5 IL CODICE ECCESSO 3 20 La trasmissione delle informazioni Quarta Parte I codici Il codice BCD
IRSplit. Istruzioni d uso 07/10-01 PC
3456 IRSplit Istruzioni d uso 07/10-01 PC 2 IRSplit Istruzioni d uso Indice 1. Requisiti Hardware e Software 4 1.1 Requisiti Hardware 4 1.2 Requisiti Software 4 2. Installazione 4 3. Concetti fondamentali
Esercizio data base "Biblioteca"
Rocco Sergi Esercizio data base "Biblioteca" Database 2: Biblioteca Testo dell esercizio Si vuole realizzare una base dati per la gestione di una biblioteca. La base dati conterrà tutte le informazioni
Direzione Centrale per le Politiche dell Immigrazione e dell Asilo
Direzione Centrale per le Politiche dell Immigrazione e dell Asilo Sistema inoltro telematico domande di nulla osta, ricongiungimento e conversioni Manuale utente Versione 2 Data creazione 02/11/2007 12.14.00
Informatica Generale 02 - Rappresentazione numeri razionali
Informatica Generale 02 - Rappresentazione numeri razionali Cosa vedremo: Rappresentazione binaria dei numeri razionali Rappresentazione in virgola fissa Rappresentazione in virgola mobile La rappresentazione
Visual Basic.NET La Gestione degli Errori di Federico BARBATI
Generalità Visual Basic.NET La Gestione degli Errori di Federico BARBATI La gestione degli errori, è una parte fondamentale di un codice ben progettato. Fino ad oggi, gli errori nelle applicazioni scritte
Tutorial 3DRoom. 3DRoom
Il presente paragrafo tratta il rilievo di interni ed esterni eseguito con. L utilizzo del software è molto semplice ed immediato. Dopo aver fatto uno schizzo del vano si passa all inserimento delle diagonali
FPf per Windows 3.1. Guida all uso
FPf per Windows 3.1 Guida all uso 3 Configurazione di una rete locale Versione 1.0 del 18/05/2004 Guida 03 ver 02.doc Pagina 1 Scenario di riferimento In figura è mostrata una possibile soluzione di rete
PULSANTI E PAGINE Sommario PULSANTI E PAGINE...1
Pagina 1 Sommario...1 Apertura...2 Visualizzazioni...2 Elenco...2 Testo sul pulsante e altre informazioni...3 Comandi...3 Informazioni...4 Flow chart...5 Comandi...6 Pulsanti Principali e Pulsanti Dipendenti...6
NUOVA PROCEDURA COPIA ED INCOLLA PER L INSERIMENTO DELLE CLASSIFICHE NEL SISTEMA INFORMATICO KSPORT.
NUOVA PROCEDURA COPIA ED INCOLLA PER L INSERIMENTO DELLE CLASSIFICHE NEL SISTEMA INFORMATICO KSPORT. Con l utilizzo delle procedure di iscrizione on line la società organizzatrice ha a disposizione tutti
I sistemi di numerazione
I sistemi di numerazione 01-INFORMAZIONE E SUA RAPPRESENTAZIONE Sia dato un insieme finito di caratteri distinti, che chiameremo alfabeto. Utilizzando anche ripetutamente caratteri di un alfabeto, si possono
Uso di base delle funzioni in Microsoft Excel
Uso di base delle funzioni in Microsoft Excel Le funzioni Una funzione è un operatore che applicato a uno o più argomenti (valori, siano essi numeri con virgola, numeri interi, stringhe di caratteri) restituisce
( x) ( x) 0. Equazioni irrazionali
Equazioni irrazionali Definizione: si definisce equazione irrazionale un equazione in cui compaiono uno o più radicali contenenti l incognita. Esempio 7 Ricordiamo quanto visto sulle condizioni di esistenza
RAPPRESENTAZIONE BINARIA DEI NUMERI. Andrea Bobbio Anno Accademico 1996-1997
1 RAPPRESENTAZIONE BINARIA DEI NUMERI Andrea Bobbio Anno Accademico 1996-1997 Numeri Binari 2 Sistemi di Numerazione Il valore di un numero può essere espresso con diverse rappresentazioni. non posizionali:
(71,1), (35,1), (17,1), (8,1), (4,0), (2,0), (1,0), (0,1) 0, 7155 2 = 1, 431 0, 431 2 = 0, 862 0, 896 2 = 1, 792 0, 724 2 = 1, 448 0, 448 2 = 0, 896
2 Esercizio 2.2 La rappresentazione esadecimale prevede 16 configurazioni corrispondenti a 4 bit. Il contenuto di una parola di 16 bit può essere rappresentato direttamente con 4 digit esadecimali, sostituendo
Come modificare la propria Home Page e gli elementi correlati
Come modificare la propria Home Page e gli elementi correlati Versione del documento: 3.0 Ultimo aggiornamento: 2006-09-15 Riferimento: webmaster ([email protected]) La modifica delle informazioni
PROVA INTRACORSO TRACCIA A Pagina 1 di 6
PROVA INTRACORSO DI ELEMENTI DI INFORMATICA MATRICOLA COGNOME E NOME TRACCIA A DOMANDA 1 Calcolare il risultato delle seguenti operazioni binarie tra numeri interi con segno rappresentati in complemento
Consiglio regionale della Toscana. Regole per il corretto funzionamento della posta elettronica
Consiglio regionale della Toscana Regole per il corretto funzionamento della posta elettronica A cura dell Ufficio Informatica Maggio 2006 Indice 1. Regole di utilizzo della posta elettronica... 3 2. Controllo
Registratori di Cassa
modulo Registratori di Cassa Interfacciamento con Registratore di Cassa RCH Nucleo@light GDO BREVE GUIDA ( su logiche di funzionamento e modalità d uso ) www.impresa24.ilsole24ore.com 1 Sommario Introduzione...
Codifica: dal diagramma a blocchi al linguaggio C++
Codifica: dal diagramma a blocchi al linguaggio C++ E necessario chiarire inizialmente alcuni concetti. La compilazione Il dispositivo del computer addetto all esecuzione dei programmi è la CPU La CPU
Appunti sulla Macchina di Turing. Macchina di Turing
Macchina di Turing Una macchina di Turing è costituita dai seguenti elementi (vedi fig. 1): a) una unità di memoria, detta memoria esterna, consistente in un nastro illimitato in entrambi i sensi e suddiviso
SISTEMI DI NUMERAZIONE DECIMALE E BINARIO
SISTEMI DI NUMERAZIONE DECIMALE E BINARIO Il sistema di numerazione decimale (o base dieci) possiede dieci possibili valori (0, 1, 2, 3, 4, 5, 6, 7, 8 o 9) utili a rappresentare i numeri. Le cifre possiedono
5-1 FILE: CREAZIONE NUOVO DOCUMENTO
Capittol lo 5 File 5-1 FILE: CREAZIONE NUOVO DOCUMENTO In Word è possibile creare documenti completamente nuovi oppure risparmiare tempo utilizzando autocomposizioni o modelli, che consentono di creare
WORD per WINDOWS95. Un word processor e` come una macchina da scrivere ma. con molte più funzioni. Il testo viene battuto sulla tastiera
WORD per WINDOWS95 1.Introduzione Un word processor e` come una macchina da scrivere ma con molte più funzioni. Il testo viene battuto sulla tastiera ed appare sullo schermo. Per scrivere delle maiuscole
Gestionalino-Base è un Software che gestisce altri Software Specifici progettati per
Tempi & Metodi di Giorgio Andreani Servizi di Controllo e di Organizzazione dei Processi Produttivi Iscrizione al Registro delle Imprese CCIAA di Verona REA 357269 Partita Iva 03686020235 - Cod. Fisc.
Il sofware è inoltre completato da una funzione di calendario che consente di impostare in modo semplice ed intuitivo i vari appuntamenti.
SH.MedicalStudio Presentazione SH.MedicalStudio è un software per la gestione degli studi medici. Consente di gestire un archivio Pazienti, con tutti i documenti necessari ad avere un quadro clinico completo
Codifica binaria dei numeri
Codifica binaria dei numeri Caso più semplice: in modo posizionale (spesso detto codifica binaria tout court) Esempio con numero naturale: con 8 bit 39 = Codifica in virgola fissa dei numeri float: si
Dispense di Informatica per l ITG Valadier
La notazione binaria Dispense di Informatica per l ITG Valadier Le informazioni dentro il computer All interno di un calcolatore tutte le informazioni sono memorizzate sottoforma di lunghe sequenze di
FORMULE: Operatori matematici
Formule e funzioni FORMULE Le formule sono necessarie per eseguire calcoli utilizzando i valori presenti nelle celle di un foglio di lavoro. Una formula inizia col segno uguale (=). La formula deve essere
DOL. Dealer Application System online. Manuale per l utente
DOL Dealer Application System online Manuale per l utente 1. Introduzione Il DOL (dealer application system online) è un programma che permette la gestione delle operazioni di finanziamento attraverso
Manuale Terminal Manager 2.0
Manuale Terminal Manager 2.0 CREAZIONE / MODIFICA / CANCELLAZIONE TERMINALI Tramite il pulsante NUOVO possiamo aggiungere un terminale alla lista del nostro impianto. Comparirà una finestra che permette
MS Word per la TESI. Barra degli strumenti. Rientri. Formattare un paragrafo. Cos è? Barra degli strumenti
MS Word per la TESI Barra degli strumenti Cos è? Barra degli strumenti Formattazione di un paragrafo Formattazione dei caratteri Gli stili Tabelle, figure, formule Intestazione e piè di pagina Indice e
SPRING SQ COMUNICAZIONE OPERAZIONI IVA NON INFERIORI A 3000 EURO PER L ANNO 2011
Versione aggiornata il 02 Aprile 2012 SPRING SQ COMUNICAZIONE OPERAZIONI IVA NON INFERIORI A 3000 EURO PER L ANNO 2011 PREREQUISITI *** ACCERTARSI CON L ASSISTENZA DI AVERE INSTALLATO LE ULTIME IMPLEMENTAZIONE/CORREZIONI
Manuale Utente Amministrazione Trasparente GA
Manuale Utente GA IDENTIFICATIVO DOCUMENTO MU_AMMINISTRAZIONETRASPARENTE-GA_1.0 Versione 1.0 Data edizione 03.05.2013 1 Albo Pretorio On Line TABELLA DELLE VERSIONI Versione Data Paragrafo Descrizione
Workland CRM. Workland CRM Rel 2570 21/11/2013. Attività --> FIX. Magazzino --> NEW. Nessuna --> FIX. Ordini --> FIX
Attività Attività --> FIX In alcuni casi, in precedenza, sulla finestra trova attività non funzionava bene la gestione dei limiti tra date impostati tramite il menu a discesa (Oggi, Tutte, Ultima Settimana,
Manuale Amministratore Legalmail Enterprise. Manuale ad uso degli Amministratori del Servizio Legalmail Enterprise
Manuale Amministratore Legalmail Enterprise Manuale ad uso degli Amministratori del Servizio Legalmail Enterprise Pagina 2 di 16 Manuale Amministratore Legalmail Enterprise Introduzione a Legalmail Enterprise...3
Informatica B a.a 2005/06 (Meccanici 4 squadra) PhD. Ing. Michele Folgheraiter
Informatica B a.a 2005/06 (Meccanici 4 squadra) Scaglione: da PO a ZZZZ PhD. Ing. Michele Folgheraiter Architettura del Calcolatore Macchina di von Neumann Il calcolatore moderno è basato su un architettura
SOMMARIO... 3 INTRODUZIONE...
Sommario SOMMARIO... 3 INTRODUZIONE... 4 INTRODUZIONE ALLE FUNZIONALITÀ DEL PROGRAMMA INTRAWEB... 4 STRUTTURA DEL MANUALE... 4 INSTALLAZIONE INRAWEB VER. 11.0.0.0... 5 1 GESTIONE INTRAWEB VER 11.0.0.0...
Struttura logica di un programma
Struttura logica di un programma Tutti i programmi per computer prevedono tre operazioni principali: l input di dati (cioè l inserimento delle informazioni da elaborare) il calcolo dei risultati cercati
www.filoweb.it STAMPA UNIONE DI WORD
STAMPA UNIONE DI WORD Molte volte abbiamo bisogno di stampare più volte lo stesso documento cambiando solo alcuni dati. Potremmo farlo manualmente e perdere un sacco di tempo, oppure possiamo ricorrere
FOXWave 1.0.0 Gestione gare ARDF IZ1FAL Secco Marco Sezione ARI BIELLA
FOXWave 1.0.0 Gestione gare ARDF IZ1FAL Secco Marco Sezione ARI BIELLA Redatto da IZ1FAL Secco Marco Pagina 1 di 15 INDICE 1 1- INSTALLAZIONE... 3 1-1 Scaricare i pacchetti aggiornati... 3 1-2 Startup
4. Un ambiente di sviluppo per Java
pag.15 4. Un ambiente di sviluppo per Java Esistono in commercio molti ambienti di sviluppo utilizzati dai programmatori Java, in particolare si tratta di editor complessi che mettono a disposizione tools
Linguaggio C. Fondamenti. Struttura di un programma.
Linguaggio C Fondamenti. Struttura di un programma. 1 La storia del Linguaggio C La nascita del linguaggio C fu dovuta all esigenza di disporre di un Linguaggio ad alto livello adatto alla realizzazione
GHPPEditor è un software realizzato per produrre in modo rapido e guidato un part program per controlli numerici Heidenhain.
*+33(GLWRU GHPPEditor è un software realizzato per produrre in modo rapido e guidato un part program per controlli numerici Heidenhain. Il programma si basa su un architettura di tasti funzionali presenti
Moduli (schede compilabili) in Word Esempio: scheda di alimentazione per un degente
Moduli (schede compilabili) in Word Esempio: scheda di alimentazione per un degente Vediamo come utilizzare Word per costruire un modulo compilabile, ovvero una scheda che contenga delle parti fisse di
Appunti di Sistemi Elettronici
Prof.ssa Maria Rosa Malizia 1 LA PROGRAMMAZIONE La programmazione costituisce una parte fondamentale dell informatica. Infatti solo attraverso di essa si apprende la logica che ci permette di comunicare
Modulo 3 - Elaborazione Testi 3.6 Preparazione stampa
Università degli Studi dell Aquila Corso ECDL programma START Modulo 3 - Elaborazione Testi 3.6 Preparazione stampa Maria Maddalena Fornari Impostazioni di pagina: orientamento È possibile modificare le
