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

PARTE QUARTA