Difference between revisions of "Nu+ HowTos"

From NaplesPU Documentation
Jump to: navigation, search
Line 1: Line 1:
Tutorial
 
 
es:
 
 
tutorial creare progetto vivado
 
 
tutorial modificare parametri
 
 
 
Tutorial
 
Tutorial
  
Line 18: Line 10:
 
Sono decritti i passi necessari per poter introdurre all'interno dell'architettura nu+ una nuova unità funzionale con aggiunta di supporto lato compilatore tramite intrinsic.
 
Sono decritti i passi necessari per poter introdurre all'interno dell'architettura nu+ una nuova unità funzionale con aggiunta di supporto lato compilatore tramite intrinsic.
  
* De�finizione nuova istruzione;
+
* Definizione nuova istruzione;
 
* aggiunta dell'intrinsic nel compilatore
 
* aggiunta dell'intrinsic nel compilatore
 
* aggiunta dell'unità funzionale nell'architettura
 
* aggiunta dell'unità funzionale nell'architettura
Line 24: Line 16:
 
=== Definizione Istruzione ===
 
=== Definizione Istruzione ===
  
Come prima cosa per introdurre un nuova istruzione in nu+, bisogna defi�nire l'istruzione macchina da implementare nell'architettura e di cui fornirne il supporto tramite intrinsics. Per esempio, supponiamo di introdurre un'istruzione che calcoli il prodotto vettoriale. L'istruzione quindi deve essere di tipo R in quanto l'operazione richiede due operandi vettoriali in ingresso, il cui risultato deve essere memorizzato in un vettore. In particolare l'istruzione che andremo ad introdurre sarà chiamata '''crp'''. In generale sarà compito di chi vorrà introdurre una nuova istruzione, studiare l'[[ISA]] di nu+ e scegliere il formato adeguato.  
+
Come prima cosa per introdurre un nuova istruzione in nu+, bisogna definire l'istruzione macchina da implementare nell'architettura e di cui fornirne il supporto tramite intrinsics. Per esempio, supponiamo di introdurre un'istruzione che calcoli il prodotto vettoriale. L'istruzione quindi deve essere di tipo R in quanto l'operazione richiede due operandi vettoriali in ingresso, il cui risultato deve essere memorizzato in un vettore. In particolare l'istruzione che andremo ad introdurre sarà chiamata '''crp'''. In generale sarà compito di chi vorrà introdurre una nuova istruzione, studiare l'[[ISA]] di nu+ e scegliere il formato adeguato.  
 
A questo punto l'introduzione dell'istruzione lo sviluppo lato compilatore e quello lato architetturale possono procedere parallelamente.
 
A questo punto l'introduzione dell'istruzione lo sviluppo lato compilatore e quello lato architetturale possono procedere parallelamente.
  
Line 33: Line 25:
  
 
==== Introduzione dell'intrinsic ====
 
==== Introduzione dell'intrinsic ====
L'introduzione dell'intrinsic prevede la modi�ca di tre �files contenuti in back-end e front-end.  
+
L'introduzione dell'intrinsic prevede la modifica di tre files contenuti in back-end e front-end.  
  
Per fare in modo che il front-end Clang riconosca la nuova intrinsic bisogna aggiungere all'interno del �file "NuPlusLLVM/compiler/tools/clang/include/clang/Basic/BuiltinsNuPlus.def " la seguente istruzione:
+
Per fare in modo che il front-end Clang riconosca la nuova intrinsic bisogna aggiungere all'interno del file "NuPlusLLVM/compiler/tools/clang/include/clang/Basic/BuiltinsNuPlus.def " la seguente istruzione:
  
 
  //------ Cross Product ----------//
 
  //------ Cross Product ----------//
 
  BUILTIN ( __builtin_nuplus_crossprodv16i32 , " V16iV16iV16i ", "n")
 
  BUILTIN ( __builtin_nuplus_crossprodv16i32 , " V16iV16iV16i ", "n")
  
In tale macro va de�finita la �firma dell'intrinsic: il nome ''__builtin_nuplus_crossprodv16i32'', i tipi di ingresso e di uscita "''V16iV16iV16i''" ed eventuali attributi in questo caso "''n''". Per ulteriori informazione consultare il fi�le "NuPlusLLVM/compiler/tools/clang/include/clang/Basic/Builtins.def "
+
In tale macro va definita la firma dell'intrinsic: il nome ''__builtin_nuplus_crossprodv16i32'', i tipi di ingresso e di uscita "''V16iV16iV16i''" ed eventuali attributi in questo caso "''n''". Per ulteriori informazione consultare il file "NuPlusLLVM/compiler/tools/clang/include/clang/Basic/Builtins.def "
  
Il secondo �file da modi�care è "NuPlusLLVM/compiler/tools/clang/lib/CodeGen/CGBuiltin.cpp". In tale �file bisogna aggiungere al metodo ''EmitNuPlusBuiltinExpr'' un ulteriore caso al costrutto switch che vada a riconoscere la nuova intrinsic defi�nita precedentemente.
+
Il secondo file da modicare è "NuPlusLLVM/compiler/tools/clang/lib/CodeGen/CGBuiltin.cpp". In tale file bisogna aggiungere al metodo ''EmitNuPlusBuiltinExpr'' un ulteriore caso al costrutto switch che vada a riconoscere la nuova intrinsic definita precedentemente.
  
 
  // Cross Product
 
  // Cross Product
Line 49: Line 41:
 
     break ;
 
     break ;
  
Le parole chiave riprendono quanto defi�nito nel �file BuiltinsNuPlus.def. Il case deve contenere lo stesso nome della �firma preceduto da BI, mentre nella chiamata a getIntrinsic, bisogna passare tutto ciò che segue ''__builtin_'' .
+
Le parole chiave riprendono quanto definito nel file BuiltinsNuPlus.def. Il case deve contenere lo stesso nome della firma preceduto da BI, mentre nella chiamata a getIntrinsic, bisogna passare tutto ciò che segue ''__builtin_'' .
  
L'ultimo file da modi�care è "NuPlusLLVM/compiler/include/llvm/IR/IntrinsicsNuPlus.td" contenuto all'interno del back-end. Tale modi�ca consente al back-end di riconoscere la chiamata all'intrinsic generata da Clang e generare il nodo dell'AST corrispondente. Per una maggiore comprensione si richiede almeno una conoscenza di base del linguaggio Table-Gen.
+
L'ultimo file da modicare è "NuPlusLLVM/compiler/include/llvm/IR/IntrinsicsNuPlus.td" contenuto all'interno del back-end. Tale modica consente al back-end di riconoscere la chiamata all'intrinsic generata da Clang e generare il nodo dell'AST corrispondente. Per una maggiore comprensione si richiede almeno una conoscenza di base del linguaggio Table-Gen.
  
 
  // Cross Product Intrinsic
 
  // Cross Product Intrinsic
 
  def int_nuplus_crossprodv16i32 : Intrinsic <[ llvm_v16i32_ty ], [ llvm_v16i32_ty , llvm_v16i32_ty ], [ IntrNoMem ], " llvm.nuplus.__builtin_nuplus_crossprodv16i32 ">;
 
  def int_nuplus_crossprodv16i32 : Intrinsic <[ llvm_v16i32_ty ], [ llvm_v16i32_ty , llvm_v16i32_ty ], [ IntrNoMem ], " llvm.nuplus.__builtin_nuplus_crossprodv16i32 ">;
  
In questo modo si defi�nisce un'istanza (int_nuplus_crossprodv16i32) della classe TableGen Intrinsic. Essa prevede di speci�ficare rispettivamente i tipi di uscita e di ingresso (llvm_v16i32_ty), eventuali attributi (IntrNoMem) e la stringa di riconoscimento dell'IR ("llvm.nuplus.__builtin_nuplus_crossprodv16i32") che deve contenere lo stesso nome della builtin de�finita in BuiltinsNuPlus.def, da inserire dopo "llvm.nuplus.".
+
In questo modo si definisce un'istanza (int_nuplus_crossprodv16i32) della classe TableGen Intrinsic. Essa prevede di specificare rispettivamente i tipi di uscita e di ingresso (llvm_v16i32_ty), eventuali attributi (IntrNoMem) e la stringa di riconoscimento dell'IR ("llvm.nuplus.__builtin_nuplus_crossprodv16i32") che deve contenere lo stesso nome della builtin definita in BuiltinsNuPlus.def, da inserire dopo "llvm.nuplus.".
  
 
==== Introduzione dell'istruzione ====
 
==== Introduzione dell'istruzione ====
Per poter introdurre la nuova istruzione all'interno del compilatore, è necessaria la modifi�ca di un solo �file all'interno del back-end di nu+. Il �file in particolare è "NuPlusInstrInfo.td". L'introduzione dell'istruzione richiede l'uso delle classi TableGen defi�nite all'interno del �file "NuPlusInstrFormats.td". In particolare, la classe che andremo ad usare per la crp è la FR_TwoOp_Unmasked_32, questo perchè l'istruzione che vogliamo introdurre è di tipo R con due operandi in ingresso (FR_TwoOp) di tipo vettoriale a 32 bit, senza uso di maschera (Unmasked_32).
+
Per poter introdurre la nuova istruzione all'interno del compilatore, è necessaria la modifica di un solo file all'interno del back-end di nu+. Il file in particolare è "NuPlusInstrInfo.td". L'introduzione dell'istruzione richiede l'uso delle classi TableGen definite all'interno del file "NuPlusInstrFormats.td". In particolare, la classe che andremo ad usare per la crp è la FR_TwoOp_Unmasked_32, questo perchè l'istruzione che vogliamo introdurre è di tipo R con due operandi in ingresso (FR_TwoOp) di tipo vettoriale a 32 bit, senza uso di maschera (Unmasked_32).
  
 
  // Cross Product Instruction
 
  // Cross Product Instruction
Line 72: Line 64:
 
  Fmt_V >; // definizione tipo registro sorgente 1
 
  Fmt_V >; // definizione tipo registro sorgente 1
  
Si noti che VR512W defi�nisce la classe di registri destinati per i tipi vettoriali a 16 elementi tra cui v16i32 (la de�nizione dei registri è contenuta in NuPlusRegisterInfo.td ), mentre Fmt_V va utilizzato per de�finire i bit all'interno campo FMT dell'istruzione (V per vettore, S per scalare). All'interno del pattern int_nuplus_crossprodi32 deve coincidere con quanto de�finito in IntrinsicNuPlus.td.
+
Si noti che VR512W definisce la classe di registri destinati per i tipi vettoriali a 16 elementi tra cui v16i32 (la definizione dei registri è contenuta in NuPlusRegisterInfo.td ), mentre Fmt_V va utilizzato per definire i bit all'interno campo FMT dell'istruzione (V per vettore, S per scalare). All'interno del pattern int_nuplus_crossprodi32 deve coincidere con quanto definito in IntrinsicNuPlus.td.
  
 
=== Aggiunta unità funzionale ===
 
=== Aggiunta unità funzionale ===
 
L'introduzione di una nuova unità funzionale passa per i seguenti passi:
 
L'introduzione di una nuova unità funzionale passa per i seguenti passi:
* de�finizione dell'unità funzionale e della sua interfaccia;
+
* definizione dell'unità funzionale e della sua interfaccia;
* modi�ca dello stage di decode;
+
* modifica dello stage di decode;
* modi�ca dello stage di writeback;
+
* modifica dello stage di writeback;
 
* aggancio della pipe nel core.
 
* aggancio della pipe nel core.
In alcuni casi, bisognerà anche modi�care lo stage Instruction_Buffer, ma solo in caso di necessità.
+
In alcuni casi, bisognerà anche modificare lo stage Instruction_Buffer, ma solo in caso di necessità.
  
La nuova unità funzionale riceverà gli input dall'operand_fetch per poi restituire i suoi risultati nella writeback. Ciò consente di poter prelevare dati dai registri e di poter scrivere il risultato in un'altro registro. Tale unità funzionale può eseguire le proprie operazioni in un numero di cicli di clock indefi�nito, però deve poter sempre accettare istruzioni quando l'uscita dell'operand fetch è valida ed inoltre non può modi�care il  flusso di esecuzione del thread.
+
La nuova unità funzionale riceverà gli input dall'operand_fetch per poi restituire i suoi risultati nella writeback. Ciò consente di poter prelevare dati dai registri e di poter scrivere il risultato in un'altro registro. Tale unità funzionale può eseguire le proprie operazioni in un numero di cicli di clock indefinito, però deve poter sempre accettare istruzioni quando l'uscita dell'operand fetch è valida ed inoltre non può modificare il  flusso di esecuzione del thread.
  
 
Per la gestione degli hazard su tale unità funzionale, l'unità di scheduling eseguirà tale controllo evitando l'issuing dell'istruzione. Nel caso in cui però l'unità internamente esegua operazioni per la quale, ad esempio, non possa più continuare l'esecuzione, si può richiedere il blocco del fetching di altre istruzioni all'instruction_buffer. Questo comporta anche una gestione di eventuali istruzioni dirette a tale unità funzionale, nella fattispecie di una coda di dimensioni pari a 4 (cioè il numero di stage tra l'unità di esecuzione e l'instruction_buffer) per poter assorbire, nel caso peggiore, una serie consecutiva di istruzioni diretta a tale unità funzionale che altrimenti andrebbero perse. Altre tipologie di gestione sono ovviamente ammesse, a patto che non perdano l'esecuzione di nessuna istruzione.
 
Per la gestione degli hazard su tale unità funzionale, l'unità di scheduling eseguirà tale controllo evitando l'issuing dell'istruzione. Nel caso in cui però l'unità internamente esegua operazioni per la quale, ad esempio, non possa più continuare l'esecuzione, si può richiedere il blocco del fetching di altre istruzioni all'instruction_buffer. Questo comporta anche una gestione di eventuali istruzioni dirette a tale unità funzionale, nella fattispecie di una coda di dimensioni pari a 4 (cioè il numero di stage tra l'unità di esecuzione e l'instruction_buffer) per poter assorbire, nel caso peggiore, una serie consecutiva di istruzioni diretta a tale unità funzionale che altrimenti andrebbero perse. Altre tipologie di gestione sono ovviamente ammesse, a patto che non perdano l'esecuzione di nessuna istruzione.
  
==== De�nizione dell'unit�a funzionale e della sua interfaccia ====
+
==== Definizione dell'unità funzionale e della sua interfaccia ====
 
Di seguito è riportata l'unità funzionale e la sua interfaccia.
 
Di seguito è riportata l'unità funzionale e la sua interfaccia.
  
Line 146: Line 138:
 
Al posto dell'appellativo my_ è buona norma utilizzarne uno che richiami le prime lettere dell'unità funzionale.
 
Al posto dell'appellativo my_ è buona norma utilizzarne uno che richiami le prime lettere dell'unità funzionale.
  
Nel fi�le si possono distinguere due macro-blocchi. Il primo è combinatoriale ed ha il compito di calcolare il risultato sulla base degli operandi forniti op0 ed op1 e di eventuali condizioni dell'istruzione. Il secondo blocco è la fase in cui si registrano i risultati, la quale è praticamente prefi�ssata, a patto di fare attenzione nel porre un eventuale risultato scalare nella lane meno signifi�cativa (result[0]).
+
Nel file si possono distinguere due macro-blocchi. Il primo è combinatoriale ed ha il compito di calcolare il risultato sulla base degli operandi forniti op0 ed op1 e di eventuali condizioni dell'istruzione. Il secondo blocco è la fase in cui si registrano i risultati, la quale è praticamente prefissata, a patto di fare attenzione nel porre un eventuale risultato scalare nella lane meno significativa (result[0]).
  
==== Modi�ca dello stage di writeback ====
+
==== Modifica dello stage di writeback ====
La modi�ca della writeback consiste nell'introduzione di una nuova interfaccia dedicata per la nuova unità di esecuzione. Essa sarà identica a quella defi�nita nel �file di template per l'interfacciamento con la writeback, cambiandone l'ordine di input/output. Quindi bisogna modi�care la sezione Writeback Request FIFOs. Prima però bisogna incrementare il parametro `NUM_EX_PIPE in hdl/include/nuplus_defi�ne.sv ed aggiungere il proprio indice mnemonico al fi�le locale di writeback.
+
La modifica della writeback consiste nell'introduzione di una nuova interfaccia dedicata per la nuova unità di esecuzione. Essa sarà identica a quella definita nel file di template per l'interfacciamento con la writeback, cambiandone l'ordine di input/output. Quindi bisogna modificare la sezione Writeback Request FIFOs. Prima però bisogna incrementare il parametro `NUM_EX_PIPE in hdl/include/nuplus_define.sv ed aggiungere il proprio indice mnemonico al file locale di writeback.
 
A questo punto bisogna compilare i campi del segnale input_wb_request relativa alla propria richiesta. La compilazione è immediata e può essere fatta sulla falsariga delle altre già presenti. La compilazione del campo wb_result_data del segnale wb_result deve essere collegato al segnale output_wb_request.writeback result. Questo lo si può fare aggiungendo il codice mnemonico della propria pipe a quelli preesistenti, come mostrato nell'esempio seguente.
 
A questo punto bisogna compilare i campi del segnale input_wb_request relativa alla propria richiesta. La compilazione è immediata e può essere fatta sulla falsariga delle altre già presenti. La compilazione del campo wb_result_data del segnale wb_result deve essere collegato al segnale output_wb_request.writeback result. Questo lo si può fare aggiungendo il codice mnemonico della propria pipe a quelli preesistenti, come mostrato nell'esempio seguente.
  
Line 165: Line 157:
  
 
==== Modifica dello stage di decode ====
 
==== Modifica dello stage di decode ====
La modi�ca della fase di decode è la più delicata e richiede la conoscenza dell'ISA relativa alla tipologia della nuova istruzione. Modi�ficare correttamente questa fase equivale a compilare correttamente i campi del segnale instruction_decoded_next. Prima di poter procedere alla modi�ca della pipe di decode, bisogna aggiungere la nomenclatura della propria pipe nella struttura pipeline_disp_t in hdl/include/nuplus_define.sv (es. PIPE_NEW). Nello stesso fi�le, bisogna anche aggiungere l'opcode per quella tipologia di istruzione nel tipo xxx_op_t. L'opcode deve essere in accordo con quanto speci�ficato con il compilatore. Fare attenzione ad aggiungere l'opcode '''IN CODA''' a tale tipologia di operazioni.
+
La modifica della fase di decode è la più delicata e richiede la conoscenza dell'ISA relativa alla tipologia della nuova istruzione. Modificare correttamente questa fase equivale a compilare correttamente i campi del segnale instruction_decoded_next. Prima di poter procedere alla modifica della pipe di decode, bisogna aggiungere la nomenclatura della propria pipe nella struttura pipeline_disp_t in hdl/include/nuplus_define.sv (es. PIPE_NEW). Nello stesso file, bisogna anche aggiungere l'opcode per quella tipologia di istruzione nel tipo xxx_op_t. L'opcode deve essere in accordo con quanto specificato con il compilatore. Fare attenzione ad aggiungere l'opcode '''IN CODA''' a tale tipologia di operazioni.
Tornando alla modi�ca diretta dello stage di decode, in uno dei costrutti case dove rientra la nuova istruzione (es. tipo RI o RR) deve essere modi�cato il campo pipe_sel con il nuovo codice di pipe aggiunto in pipeline disp_t all'interno di una condizione if, facendo attenzione ad azzerare gli altri segnali ancora assegnati se non è stato fatto altrove. Per esempio:
+
Tornando alla modifica diretta dello stage di decode, in uno dei costrutti case dove rientra la nuova istruzione (es. tipo RI o RR) deve essere modificato il campo pipe_sel con il nuovo codice di pipe aggiunto in pipeline disp_t all'interno di una condizione if, facendo attenzione ad azzerare gli altri segnali ancora assegnati se non è stato fatto altrove. Per esempio:
  
 
  if ( if_inst_scheduled . opcode . alu_opcode == MOVE ) begin
 
  if ( if_inst_scheduled . opcode . alu_opcode == MOVE ) begin
Line 175: Line 167:
  
 
==== Aggancio della pipe nel core ====
 
==== Aggancio della pipe nel core ====
Nel �file hdl/core/nuplus core.sv, bisogna istanziare il componente, agganciare le wires dall'operand fetch in input al proprio componente e le uscite andranno ai nuovi segnali della writeback aggiunti.
+
Nel file hdl/core/nuplus core.sv, bisogna istanziare il componente, agganciare le wires dall'operand fetch in input al proprio componente e le uscite andranno ai nuovi segnali della writeback aggiunti.

Revision as of 09:53, 22 September 2017

Tutorial

es: tutorial creare progetto vivado

tutorial modificare parametri

Introdurre una nuova istruzione dedicata

Sono decritti i passi necessari per poter introdurre all'interno dell'architettura nu+ una nuova unità funzionale con aggiunta di supporto lato compilatore tramite intrinsic.

  • Definizione nuova istruzione;
  • aggiunta dell'intrinsic nel compilatore
  • aggiunta dell'unità funzionale nell'architettura

Definizione Istruzione

Come prima cosa per introdurre un nuova istruzione in nu+, bisogna definire l'istruzione macchina da implementare nell'architettura e di cui fornirne il supporto tramite intrinsics. Per esempio, supponiamo di introdurre un'istruzione che calcoli il prodotto vettoriale. L'istruzione quindi deve essere di tipo R in quanto l'operazione richiede due operandi vettoriali in ingresso, il cui risultato deve essere memorizzato in un vettore. In particolare l'istruzione che andremo ad introdurre sarà chiamata crp. In generale sarà compito di chi vorrà introdurre una nuova istruzione, studiare l'ISA di nu+ e scegliere il formato adeguato. A questo punto l'introduzione dell'istruzione lo sviluppo lato compilatore e quello lato architetturale possono procedere parallelamente.

Aggiunta del supporto alla compilazione

L'introduzione di una nuova intrinsic all'interno del compilatore si articola in due fasi:

  • Introduzione dell'intrinsic;
  • Introduzione dell'istruzione.

Introduzione dell'intrinsic

L'introduzione dell'intrinsic prevede la modifica di tre files contenuti in back-end e front-end.

Per fare in modo che il front-end Clang riconosca la nuova intrinsic bisogna aggiungere all'interno del file "NuPlusLLVM/compiler/tools/clang/include/clang/Basic/BuiltinsNuPlus.def " la seguente istruzione:

//------ Cross Product ----------//
BUILTIN ( __builtin_nuplus_crossprodv16i32 , " V16iV16iV16i ", "n")

In tale macro va definita la firma dell'intrinsic: il nome __builtin_nuplus_crossprodv16i32, i tipi di ingresso e di uscita "V16iV16iV16i" ed eventuali attributi in questo caso "n". Per ulteriori informazione consultare il file "NuPlusLLVM/compiler/tools/clang/include/clang/Basic/Builtins.def "

Il secondo file da modicare è "NuPlusLLVM/compiler/tools/clang/lib/CodeGen/CGBuiltin.cpp". In tale file bisogna aggiungere al metodo EmitNuPlusBuiltinExpr un ulteriore caso al costrutto switch che vada a riconoscere la nuova intrinsic definita precedentemente.

// Cross Product
case NuPlus :: BI__builtin_nuplus_crossprodv16i32 :
   F = CGM . getIntrinsic ( Intrinsic :: nuplus_crossprodv16i32 );
   break ;

Le parole chiave riprendono quanto definito nel file BuiltinsNuPlus.def. Il case deve contenere lo stesso nome della firma preceduto da BI, mentre nella chiamata a getIntrinsic, bisogna passare tutto ciò che segue __builtin_ .

L'ultimo file da modicare è "NuPlusLLVM/compiler/include/llvm/IR/IntrinsicsNuPlus.td" contenuto all'interno del back-end. Tale modica consente al back-end di riconoscere la chiamata all'intrinsic generata da Clang e generare il nodo dell'AST corrispondente. Per una maggiore comprensione si richiede almeno una conoscenza di base del linguaggio Table-Gen.

// Cross Product Intrinsic
def int_nuplus_crossprodv16i32 : Intrinsic <[ llvm_v16i32_ty ], [ llvm_v16i32_ty , llvm_v16i32_ty ], [ IntrNoMem ], " llvm.nuplus.__builtin_nuplus_crossprodv16i32 ">;

In questo modo si definisce un'istanza (int_nuplus_crossprodv16i32) della classe TableGen Intrinsic. Essa prevede di specificare rispettivamente i tipi di uscita e di ingresso (llvm_v16i32_ty), eventuali attributi (IntrNoMem) e la stringa di riconoscimento dell'IR ("llvm.nuplus.__builtin_nuplus_crossprodv16i32") che deve contenere lo stesso nome della builtin definita in BuiltinsNuPlus.def, da inserire dopo "llvm.nuplus.".

Introduzione dell'istruzione

Per poter introdurre la nuova istruzione all'interno del compilatore, è necessaria la modifica di un solo file all'interno del back-end di nu+. Il file in particolare è "NuPlusInstrInfo.td". L'introduzione dell'istruzione richiede l'uso delle classi TableGen definite all'interno del file "NuPlusInstrFormats.td". In particolare, la classe che andremo ad usare per la crp è la FR_TwoOp_Unmasked_32, questo perchè l'istruzione che vogliamo introdurre è di tipo R con due operandi in ingresso (FR_TwoOp) di tipo vettoriale a 32 bit, senza uso di maschera (Unmasked_32).

// Cross Product Instruction
def CROSSPROD_32 : FR_TwoOp_Unmasked_32 <
( outs VR512W : $dst ), // definizione uscita
( ins VR512W :$src0 , VR512W : $src1 ), // definizione ingressi
" crp $dst , $src0 , $src1 ", // definizione assembly da generare
[( set v16i32 :$dst , ( int_nuplus_crossprodv16i32 v16i32 :$src0 , v16i32 : $src1 ))], // definizione pattern dariconoscere
63, // definizione opcode ( non deve essere uguale ad altre istruzioni )
Fmt_V , // definizione tipo registro destinazione
Fmt_V , // definizione tipo registro sorgente 0
Fmt_V >; // definizione tipo registro sorgente 1

Si noti che VR512W definisce la classe di registri destinati per i tipi vettoriali a 16 elementi tra cui v16i32 (la definizione dei registri è contenuta in NuPlusRegisterInfo.td ), mentre Fmt_V va utilizzato per definire i bit all'interno campo FMT dell'istruzione (V per vettore, S per scalare). All'interno del pattern int_nuplus_crossprodi32 deve coincidere con quanto definito in IntrinsicNuPlus.td.

Aggiunta unità funzionale

L'introduzione di una nuova unità funzionale passa per i seguenti passi:

  • definizione dell'unità funzionale e della sua interfaccia;
  • modifica dello stage di decode;
  • modifica dello stage di writeback;
  • aggancio della pipe nel core.

In alcuni casi, bisognerà anche modificare lo stage Instruction_Buffer, ma solo in caso di necessità.

La nuova unità funzionale riceverà gli input dall'operand_fetch per poi restituire i suoi risultati nella writeback. Ciò consente di poter prelevare dati dai registri e di poter scrivere il risultato in un'altro registro. Tale unità funzionale può eseguire le proprie operazioni in un numero di cicli di clock indefinito, però deve poter sempre accettare istruzioni quando l'uscita dell'operand fetch è valida ed inoltre non può modificare il flusso di esecuzione del thread.

Per la gestione degli hazard su tale unità funzionale, l'unità di scheduling eseguirà tale controllo evitando l'issuing dell'istruzione. Nel caso in cui però l'unità internamente esegua operazioni per la quale, ad esempio, non possa più continuare l'esecuzione, si può richiedere il blocco del fetching di altre istruzioni all'instruction_buffer. Questo comporta anche una gestione di eventuali istruzioni dirette a tale unità funzionale, nella fattispecie di una coda di dimensioni pari a 4 (cioè il numero di stage tra l'unità di esecuzione e l'instruction_buffer) per poter assorbire, nel caso peggiore, una serie consecutiva di istruzioni diretta a tale unità funzionale che altrimenti andrebbero perse. Altre tipologie di gestione sono ovviamente ammesse, a patto che non perdano l'esecuzione di nessuna istruzione.

Definizione dell'unità funzionale e della sua interfaccia

Di seguito è riportata l'unità funzionale e la sua interfaccia.

`include " user_define .sv"
`include " nuplus_define .sv"
/*
* You cannot modify directly the PC ( modify the execution flow ),
* but you can stop the instruction issuing of the current thread
* asserting the ith bit of my_stop signal .
*/
module my_pipe (
   input clk ,
   input reset ,
   // To Instruction buffer
// output thread_mask_t my_stop ;
// From Operand Fetch
   input opf_valid ,
   input instruction_decoded_t opf_inst_scheduled ,
   input vec_reg_size_t opf_fecthed_op0 ,
   input vec_reg_size_t opf_fecthed_op1 ,
   input hw_lane_mask_t opf_hw_lane_mask ,
   // To Writeback
   output logic my_valid ,
   output instruction_decoded_t my_inst_scheduled ,
   output vec_reg_size_t my_result ,
   output hw_lane_mask_t my_hw_lane_mask
);
genvar i;
generate
   for ( i = 0; i < `HW_LANE ; i ++ ) begin
     /*....
     * Use these signals : opf_inst_scheduled , opf_fecthed_op0 /1, opf_hw_lane_mask
  */
  end
endgenerate
always_ff @ ( posedge clk , posedge reset ) begin
if ( reset ) begin
   my_valid <= 1'b0;
   my_inst_scheduled <= opf_inst_scheduled ;
  my_hw_lane_mask <= opf_hw_lane_mask ;
end else begin
   my_inst_scheduled <= opf_inst_scheduled ;
   my_hw_lane_mask <= opf_hw_lane_mask ;
   my_valid <= opf_valid & ( /* your condition */) ;
   if (/* scalar result */)
      my_result [0] <= /* my result */;
   else if ( /* vectorial result */ )
      my_result <= /* my result */;
   end
end
`ifdef SIMULATION
    $error (" Opcode error ! Time : %t \t PC: %h", $time () , opf_inst_scheduled .pc);
   /*
   * Add your condition
   */
`endif
endmodule

Al posto dell'appellativo my_ è buona norma utilizzarne uno che richiami le prime lettere dell'unità funzionale.

Nel file si possono distinguere due macro-blocchi. Il primo è combinatoriale ed ha il compito di calcolare il risultato sulla base degli operandi forniti op0 ed op1 e di eventuali condizioni dell'istruzione. Il secondo blocco è la fase in cui si registrano i risultati, la quale è praticamente prefissata, a patto di fare attenzione nel porre un eventuale risultato scalare nella lane meno significativa (result[0]).

Modifica dello stage di writeback

La modifica della writeback consiste nell'introduzione di una nuova interfaccia dedicata per la nuova unità di esecuzione. Essa sarà identica a quella definita nel file di template per l'interfacciamento con la writeback, cambiandone l'ordine di input/output. Quindi bisogna modificare la sezione Writeback Request FIFOs. Prima però bisogna incrementare il parametro `NUM_EX_PIPE in hdl/include/nuplus_define.sv ed aggiungere il proprio indice mnemonico al file locale di writeback. A questo punto bisogna compilare i campi del segnale input_wb_request relativa alla propria richiesta. La compilazione è immediata e può essere fatta sulla falsariga delle altre già presenti. La compilazione del campo wb_result_data del segnale wb_result deve essere collegato al segnale output_wb_request.writeback result. Questo lo si può fare aggiungendo il codice mnemonico della propria pipe a quelli preesistenti, come mostrato nell'esempio seguente.

always_comb begin
   case ( output_wb_request [ selected_pipe ]. pipe_sel )
      ...
      PIPE_INT ,
      PIPE_CR ,
      PIPE_NEW , // Nuova PIPE
      PIPE_FP : wb_next . wb_result_data = output_wb_request [ -
      selected_pipe ]. writeback_result ;
      default : wb_next . wb_result_data = 0;
   endcase
end

Modifica dello stage di decode

La modifica della fase di decode è la più delicata e richiede la conoscenza dell'ISA relativa alla tipologia della nuova istruzione. Modificare correttamente questa fase equivale a compilare correttamente i campi del segnale instruction_decoded_next. Prima di poter procedere alla modifica della pipe di decode, bisogna aggiungere la nomenclatura della propria pipe nella struttura pipeline_disp_t in hdl/include/nuplus_define.sv (es. PIPE_NEW). Nello stesso file, bisogna anche aggiungere l'opcode per quella tipologia di istruzione nel tipo xxx_op_t. L'opcode deve essere in accordo con quanto specificato con il compilatore. Fare attenzione ad aggiungere l'opcode IN CODA a tale tipologia di operazioni. Tornando alla modifica diretta dello stage di decode, in uno dei costrutti case dove rientra la nuova istruzione (es. tipo RI o RR) deve essere modificato il campo pipe_sel con il nuovo codice di pipe aggiunto in pipeline disp_t all'interno di una condizione if, facendo attenzione ad azzerare gli altri segnali ancora assegnati se non è stato fatto altrove. Per esempio:

if ( if_inst_scheduled . opcode . alu_opcode == MOVE ) begin
  instruction_decoded_next . pipe_sel = PIPE_NEW ;
  instruction_decoded_next . is_int = 1'b0;
  instruction_decoded_next . is_fp = 1'b0;
   ...

Aggancio della pipe nel core

Nel file hdl/core/nuplus core.sv, bisogna istanziare il componente, agganciare le wires dall'operand fetch in input al proprio componente e le uscite andranno ai nuovi segnali della writeback aggiunti.