Scuola Politecnica e delle Scienze di Base Corso di Laurea in Ingegneria Informatica Elaborato finale in Calcolatori Elettronici I Tecniche e strumenti di testing per processori superscalari Anno Accademico 2015-2016 Candidato: Luca Pirozzi matr. N46001969 Ai miei genitori e a mio fratello per l’insostituibile supporto. A chi non c’è più ma continua ad esserci nelle nostre azioni. Indice Indice .................................................................................................................................................. III Capitolo 1: I Sistemi Safety Critical .................................................................................................... 5 1.1 Regimi di sicurezza ............................................................................................................... 6 1.2 Caratteristiche e applicazioni ................................................................................................ 7 1.3 Standard nei trasporti............................................................................................................. 7 1.4 CENELEC EN 50126 ............................................................................................................ 8 1.5 CENELEC EN 50128 ............................................................................................................ 9 1.6 CENELEC EN 50129 .......................................................................................................... 10 1.7 CENELEC EN 50155 .......................................................................................................... 11 1.8 CENELEC EN 50159 .......................................................................................................... 11 Capitolo 2: Testing dei processori ..................................................................................................... 13 2.1 Scelta del processore per un sistema critico ........................................................................ 14 2.2 Evoluzione delle metodologie di testing ............................................................................. 15 2.3 Testing software .................................................................................................................. 16 2.4 Self-testing software ............................................................................................................ 17 2.5 Pattern di Testing................................................................................................................. 22 Capitolo 3: Intel® Processor Diagnostic Tool ................................................................................... 23 3.1 Programmi di test ................................................................................................................ 23 3.2 Analisi dei codici sorgenti ................................................................................................... 25 3.2.1 Genuine Intel ................................................................................................................... 25 3.2.2 Temperature ..................................................................................................................... 27 3.2.3 Brand String ..................................................................................................................... 28 3.2.4 CPU Frequency................................................................................................................ 29 3.2.5 Floating Point................................................................................................................... 31 3.2.6 Prime number generation ................................................................................................. 31 3.2.7 Cache ............................................................................................................................... 33 3.2.8 MMXSSE ........................................................................................................................ 34 3.2.9 AVX ................................................................................................................................. 34 3.2.10 IMC .................................................................................................................................. 35 3.2.11 PCH.................................................................................................................................. 36 3.2.12 IGD .................................................................................................................................. 37 3.2.13 GFX ................................................................................................................................. 38 3.2.14 CPU Load ........................................................................................................................ 38 Conclusioni ........................................................................................................................................ 40 4.1 Difetti di Intel® Processor Diagnostic Tool ........................................................................ 40 4.2 Elementi di resistenza a un testing efficace......................................................................... 46 4.3 Soluzioni e possibili sviluppi futuri..................................................................................... 48 Bibliografia ........................................................................................................................................ 51 Capitolo 1: I Sistemi Safety Critical Un sistema informatico si definisce critico se un suo fallimento comporta perdite. In base alle conseguenze di tale fallimento, i sistemi critici vengono così classificati [1]: • Business - critical: un fallimento può portare a gravi perdite economiche e notevoli disservizi. Esempi: sistemi bancari, grandi siti di e-commerce. • Security - critical: un fallimento può portare alla perdita di informazioni sensibili. Esempi: sistemi ospedalieri e amministrativi. • Mission - critical: il fallimento non permette il corretto completamento degli obiettivi del sistema (sistemi goal – driven). Esempio: sistema di navigazione di una sonda spaziale. • Safety - critical: un fallimento può portare a infortuni gravi o alla morte, e può causare seri danni all’ambiente. Esempi: sistema di controllo di una centrale nucleare, sistema di controllo dei treni. Tra i vari tipi di sistemi critici elencati, la tipologia con i vincoli più stringenti è sicuramente quella dei safety-critical: per questo motivo ne condurremo un’analisi dettagliata al fine di rilevarne le proprietà principali. 5 1.1 Regimi di sicurezza Sono definiti diversi regimi di sicurezza per i sistemi safety-critical [2]: Fail-operational systems: il sistema deve continuare ad operare anche se la componente di controllo fallisce (ascensori, termostati, sicurezza nucleare passiva). Fail-safe systems: nel momento in cui sono impossibilitati ad operare, sono comunque sicuri. Il caso tipico è quello dei sistemi medici che quando cessano di funzionare allertano il personale di competenza, e facendo sì che l’intervallo di non operatività sia sufficiente per la sicurezza del paziente. Tale sistema non potrà operare in maniera non controllata e quindi pericolosa. Fail-secure systems: sono sicuri anche quando falliscono. Questa proprietà è tipica delle porte elettroniche: mentre una di tipo fail-safe potrebbe ancora essere aperta, una di tipo fail-secure si chiuderebbe automaticamente in caso di fallimento, in modo da mantenere un’area sicura. Fail-passive systems: continuano ad operare anche in seguito ad un fallimento del sistema di controllo. Un esempio è l’autopilota: anche se dovesse fallire il pilota potrà comunque guidare il velivolo Fault-tolerant systems: evitano il malfunzionamento del sistema anche in caso di fallimenti interni al sistema. Questo è il caso dei sistemi di controllo del reattore, in quanto gli elementi del controllo sono tipicamente distribuiti in modalità hot-spare, ovvero ve ne sono di più sulla stessa area, in modo che anche in caso di fallimenti, un altro sistema equivalente possa funzionare al posto del sistema in fallimento. 6 1.2 Caratteristiche e applicazioni L’ingegneria del software dei sistemi safety-critical è particolarmente complessa: perseguire i canonici fattori di qualità, come la correttezza o la robustezza risulta essere ben più difficile, a causa del margine di errore sostanzialmente nullo dei loro domini applicativi. Affinché possa essere raggiunta la più alta affidabilità possibile, alcuni aspetti della software engineering diventano particolarmente importanti: • Controllo del processo ingegneristico e della sua componente gestionale • Scelta di tools di sviluppo adeguati, e redazione accurata dei casi di test • Uso degli standard del dominio Tali sistemi hanno trovato applicazione in moltissimi settori, come: • Medico (defibrillatori, macchine per la dialisi, sistemi di respirazione artificiale) • Nucleare (sistemi di controllo del reattore) • Automotive (airbag, sistemi di frenata assistita, drive by wire) • Ferroviario (gestione traffico dei treni, sistemi di frenata automatica) • Aviazione (sistemi fly by wire, controllo motore, pianificazione volo) • Spaziale (sistemi di supporto vitale) • Infrastrutture (salvavita, sensori antincendio, telecomunicazioni) • Ricreativo (controllo montagne russe) 1.3 Standard nei trasporti Come abbiamo visto, uno dei settori in cui l’utilizzo dei sistemi safety-critical è più fruttuoso è quello dei trasporti. Per garantire la massima sicurezza, sono stati definiti standard per tali sistemi per i diversi settori; i più famosi sono: • Per l’aviazione il DO-178B [3], introdotto il 1° settembre 1992, rappresenta un insieme di linee guida, che anche se non è definito come uno standard vero e proprio, lo è de facto. • Per il trasporto ferroviario gli standard CENELEC EN (50126, 50128, 50129, 50155, 50159 [4]). Tali standard, che trovano larga applicazione in Europa, ma che si 7 stanno diffondendo sempre di più in altri paesi (sono stati definiti accordi con Stati Uniti e Giappone). A partire da questi, la IEC (International Electrotechnical Commission) ha definito delle normative equivalenti (62278, 62279, 62425,62280). Tratteremo quindi gli standard definiti nell’ambito dei trasporti ferroviari, studiandone i principali aspetti. Sistema Complessivo Sottosistema di segnalazione 50126 Treno 50129 50159 50126 Strumentazione Hardware Software 50128 Strumentazione Hardware Software 50155 1.4 CENELEC EN 50126 Lo standard CENELEC 50126 si basa sui fattori di qualità indicati con l’acronimo RAMS [5] (Reliability, Availability, Operation & Maintenance, Safety). Tale standard definisce un processo, in base al ciclo di vita e al compito del software, per perseguire i fattori RAMS e soluzioni efficienti nel caso i fattori siano in conflitto fra loro. Il processo descritto da tale standard è sistematico e sono definiti metodi per verificare tali fattori di qualità. Tale processo è sostanzialmente una rivisitazione del modello a V utilizzato comunemente nell’ingegneria del software, anche se può variare in base alla complessità del sistema da realizzare. Tale standard è applicabile a tutti i nuovi sistemi, sottosistemi e hardware specificamente progettati per il settore ferroviario, ma è applicabile in alcune parti anche 8 per sistemi preesistenti. Lo standard permette di valutare i sistemi ferroviari attraverso diversi parametri, come il tasso di fallimento, tempo medio di operatività, tempo medio di indisponibilità, tempo medio di fallimento, distanza media di fallimento, tempo medio fra due fallimenti, distanza media tra due fallimenti [6] . Non definisce alcuni elementi [5], come il processo di approvazione dell’autorità ferroviaria (fornisce infatti metodi interni all’azienda per valutare il raggiungimento dei parametri RAMS), che può in questo modo definirne liberamente uno proprio, in base ad altre normative nazionali o comunitarie, non specifica soluzioni per una specifica applicazione, potendo essere applicata a diversi tipi di sistemi ferroviari, né definisce come perseguire il parametro di security: infatti mentre la safety è un parametro che permette di valutare la protezione da incidenti casuali, la security riguarda attacchi intenzionali al sistema, come tentativi di hacking dei dispositivi. 1.5 CENELEC EN 50128 Lo standard CENELEC 50128 può essere applicato unicamente al software e all’interazione software – sistema [7]. Sono tipicamente interessati dallo standard la programmazione delle applicazioni, i sistemi operativi, i tools di supporto e i firmware. Anche in questo caso il ciclo di vita è riconducibile al modello a V. I linguaggi di programmazione sono catalogati in base a diversi livelli di SIL (safety integrity level), che sono stati definiti con lo standard IEC 61508: i livelli vanno da SIL-SW 0 (minore safety) a SIL-SW 4 (maggiore safety), anche se per tutto ciò che è safety-related il livello base è SIL SW 1, mentre per i safety-critical è SIL SW 3. Il linguaggio di programmazione consigliato è l’ADA, per diversi motivi [8]: è strutturato, staticamente e fortemente tipizzato, imperativo, può agire sia a basso livello che ad alto livello, è orientato agli oggetti: tipicamente solo un sottoinsieme del linguaggio viene però utilizzato, in modo da garantire il più possibile il parametro di safety del sistema. Vengono definiti inoltre standard per il codice e una guida per lo stile di programmazione. Tra le varie caratteristiche che un buon codice dovrebbe avere troviamo [8]: l’assenza di oggetti e di variabili dinamiche, uso limitato dei puntatori e della ricorsione, programmazione strutturata (one entry – one exit per funzioni e sottoprogrammi), information hiding/incapsulamento, limite al numero dei 9 parametri, uso limitato delle variabili globali, la struttura modulare, che da SIL SW 1 in poi risulta obbligatoria. CENELEC 50128 tratta anche altri linguaggi di programmazione e li classifica [8]: risultati soddisfacenti sono stati ottenuti anche dal MODULA-2 e dal PASCAL, mentre possono essere utilizzati anche il Fortran 77, JAVA, C# e un sottoinsieme del C/C++ con standard di codifica, anche se con garanzie di safety minori. Viene scartato l’utilizzo del C/C++ standard se non nel caso SIL SW 0. 1.6 CENELEC EN 50129 Lo standard CENELEC 50129 si applica a tutti i sistemi, sottosistemi e hardware per la comunicazione, la segnalazione e l’elaborazione nell’ambito della segnalazione ferroviaria per garantirne la safety [9]. Lo standard si applica a tutto il ciclo di vita del prodotto: analisi e specifica, progettazione, realizzazione, installazione, testing, manutenzione ed evoluzione, sia di un sistema di segnalazione completo, sia dei suoi sottosistemi (questo è particolarmente utile quando si devono integrare sottosistemi nuovi in un sistema di segnalazione già esistente) [9]. Tale standard categorizza i dispositivi in base ai livelli di SIL, allo stesso modo del già trattato standard 50128. Così come il CENELEC 50126 può essere applicato a tutti i sistemi e sottosistemi nuovi, ed in misura limitata a quelli già esistenti, attraverso modifiche ed estensioni. Può essere applicato a quei sistemi specificamente pensati per operare in questo dominio, ma anche, fin quanto è ragionevolmente possibile, a sistemi general purpose (ad esempio alimentatori o modem) o dispositivi di altri domini industriali utilizzati nel sistema di segnalazione: deve essere dimostrato almeno che un sistema general purpose utilizzato nella segnalazione ferroviaria o non viene utilizzato in alcuna attività safety-related oppure che risulta essere sicuro in tutte le attività safety-related in cui è coinvolto . Lo standard tratta della safety del Sistema di segnalazione, ma non della sicurezza del personale addetto, che viene tutelata da altre normative [9]. Tipicamente il CENELEC EN 50129 non può essere utilizzato da solo, se non in un numero ristretto di casi, in quanto gran parte dei concetti su cui si basa sono contenuti nello standard 50126: ad esempio, riguardo al ciclo di vita del software, in EN 50129 viene considerato implicito, in quanto contenuto già in EN 50126, così come la 10 valutazione del rischio viene solo accennata. 1.7 CENELEC EN 50155 Lo standard CENELEC EN 50155 si applica [10] a tutti i dispositivi elettronici per il controllo, alimentazione e protezione installati su un veicolo ferroviario, collegati o alla batteria del veicolo o ad un alimentatore a basso voltaggio eventualmente collegato alla linea di contatto, ma non ai dispositivi elettronici di potenza (convertitori, raddrizzatori, invertitori). Lo standard definisce come dispositivo elettronico qualsiasi sistema composto essenzialmente da dispositivi a semiconduttore e componenti a loro associati, principalmente montati su un circuito stampato [10]. Definisce le condizioni di funzionamento (intervallo di temperatura di funzionamento, umidità tollerabile, resistenza agli urti e alle vibrazioni), che devono essere rispettate, nonché regole per il progetto, la realizzazione ed il testing, requisiti hardware e software per la realizzazione di dispositivi affidabili ed efficienti. Tale standard può essere complementato da altri, nel momento in cui se ne avverta la necessità. Eventuali requisiti speciali necessari vanno definiti concordemente allo standard EN 50126. Dal punto di vista del software, il livello di integrità software deve essere definiti in base ai rischi connessi all’applicazione, seguendo i dettami di EN 50128. 1.8 CENELEC EN 50159 Lo standard CENELEC EN 50159 si applica ai sistemi elettronici per la comunicazione in applicazioni critiche, che fanno uso di modalità di trasmissione non necessariamente progettati per tali scopi, sia nel caso che tali sistemi siano progettati seguendo tale standard sia che non lo siano, persino nel caso in cui il dispositivo sia conosciuto solo parzialmente [11]. Lo standard prevede che al sistema di trasmissione possano essere collegati sia componenti critici che non. Vengono definiti i requisiti fondamentali per una comunicazione sicura tra dispositivi critici, di solito implementata nella strumentazione descritta da EN 50129 e progettata quindi secondo tale standard, è necessario far riferimento a EN 50129 anche per quanto riguarda la gestione della qualità e della sicurezza. 11 EN 50159 contempla la possibilità che i requisiti debbano essere rispettati anche da altri dispositivi del sistema di comunicazione, a patto che si applichino le opportune misure per rispettare i vincoli di sicurezza. Lo standard non specifica la struttura del sistema di comunicazione, i dispositivi che vi sono connessi, soluzioni particolari (ad esempio per l’interoperabilità), distinzione fra dati critici e non. Lo standard non può applicarsi a sistemi già esistenti, ma può essere utilizzato per estensioni di sistemi, sottosistemi e strumentazione che non sono stati progettati seguendo tali standard [12]. EN 50159 non affronta gran parte delle problematiche di sicurezza informatica: sono affrontati solo casi molto semplici ma non vengono trattati aspetti importanti come la confidenzialità delle informazioni safety-critical e il sovraccarico del sistema di comunicazione [11]. Sono definiti inoltre vincoli sul sistema di trasmissione [12]: deve essere considerato lo scenario in cui vi sono solo accessi da parte del personale autorizzato, è definito e conosciuto il numero massimo di partecipanti connessi, il mezzo trasmissivo è noto e fisso. 12 Capitolo 2: Testing dei processori I sistemi safety-critical sono tipicamente governati da micro-controllori, circuiti integrati contenente processore, memoria e periferiche di input e di output. I moderni microcontrollori possono essere dotati di funzionalità avanzate, come la possibilità di connettersi ad Internet o di poter processare segnali (DSP), anche video. Come ben sappiamo, in base al modello di Von Neumann, è il processore deputato ad eseguire tutte le elaborazioni: possiamo dire che, complessivamente, il controllo vero e proprio viene eseguito da un processore. Componenti e interconnessioni di un processore formano la cosiddetta Register Transfert (RT) level description [13], e sono sia di tipo funzionale (ALU, shifter dei registri, adder, moltiplicatori), di memoria (registri, flags) che logici (bus, multiplexer). Non tutti i componenti sono testabili facilmente: mentre alcuni possono essere direttamente stimolati per avere una risposta, altri reagiscono a stimolazioni di altri componenti, e ciò li rende difficilmente testabili, come ad esempio il registro di stato [13]. Considerando i danni che il fallimento di un sistema safety-critical può comportare, è necessario che i processori siano conosciuti nella loro completezza e che ci assicuri, nel limite della ragionevolezza, l’assenza di errori durante l’esecuzione delle istruzioni. Tale questione diventa fondamentale quando si ha un cambio di architetture, come quella che sta avvenendo con la terminazione della produzione delle schede integrate con processore Intel Atom e l’inizio dell’utilizzo di microcontrollori con processore Intel i5. 13 2.1 Scelta del processore per un sistema critico Esistono diverse opzioni nella scelta del processore per un sistema critico [14], ognuna delle quali ha punti a favore ed altri negativi. Processori scalari: Molte aziende preferiscono utilizzare processori dalle caratteristiche ben conosciute, sul mercato da decenni, di tipo scalare, ovvero che sono in grado di processare un solo dato alla volta (SISD), come ad esempio il Motorola 68020 (aereo da caccia Eurofighter Typhoon [15], treni TGV, sistema di segnalazione ferroviario TVM [16]). La ridotta complessità, unita al fatto che la quasi totalità dei difetti, dopo decenni di utilizzi, dovrebbero essere conosciuti, garantisce un livello di affidabilità tale da convincere molte imprese ad utilizzarli. Tuttavia un aspetto negativo è il termine della produzione di gran parte di tali processori, che non sono più utilizzati per l’elettronica di consumo. Alcune compagnie hanno acquistato quasi tutte le rimanenze [14], in modo da averne una scorta: si noti, però, come un processore abbia una longevità massima di 30 anni, e il 68020 ha iniziato la sua produzione nel 1984. Processori ad hoc: Un altro tipo di soluzione è quella di costruire da sé il processore necessario per il sistema critico. Una CPU progettata per un determinato ambiente di esecuzione ha la certezza di essere ottimizzata in tal senso, e di implementare tutti e soli i meccanismi necessari. Tuttavia una scelta di questo tipo è molto costosa, e non beneficia dell’enorme mole di testing “implicito” condotto dagli utilizzatori di sistemi convenzionali, risultato sotto questo aspetto paradossalmente meno affidabile, richiede lo sviluppo di tool collaterali (compilatori) [14], e non tutte le aziende hanno il personale e il know-how necessario per percorrere questa strada (o per farlo in tempi e costi sostenibili). Processori superscalari: l’ultima possibilità è rappresentata dall’utilizzo di processori recenti, di tipo superscalare (ovvero che sono in grado di processare più di un istruzione per singolo colpo di clock, SIMD o MIMD in base al numero di core). Il vantaggio è rappresentato dalle maggiori prestazioni di queste CPU e dal fatto che sono attualmente in produzione anche per elettronica di consumo. Lo svantaggio è che sono notevolmente più complessi, e che il testing utilizzato per i sistemi di consumo non può sempre essere 14 utilizzato per i sistemi critici in quanto il primo si focalizza sull’average case e il secondo sul worst case [14]. 2.2 Evoluzione delle metodologie di testing Parallelamente al rapido sviluppo dei processori, le tecniche di testing si sono evolute nel tempo. Inizialmente i processori venivano testati completamente in hardware, facendo sì che l’intero instruction set, ossia l’insieme delle istruzioni che la CPU è in grado di eseguire, venisse eseguito: ciò veniva fatto su tutti i registri: è chiaro che un test di questo tipo diviene esponenzialmente più costoso al crescere della complessità del processore. Altri elementi che rendono difficile, se non impossibile, l’applicazione di tali tecnica nella maggior parte dei casi sono: • Complessità delle CPU: dagli anni 80 in poi lo sviluppo dei transistori ha permesso la produzione di circuiti a scala di integrazione molto grande (o very large scale integration, VLSI). Tali circuiti, presenti anche nelle moderne CPU, sono caratterizzati da un numero di transistori per area elevatissimo (ad esempio si è arrivati anche a 4,3 miliardi di transistori su uno stesso chip). Si capisce come dal solo punto di vista strutturale i moderni processori siano molto complessi da testare. • Le moderne CPU sono multicore: il processore delegherà l’esecuzione dell’istruzione ad una qualsiasi delle unità di elaborazione. Anche se si volesse testare la stessa istruzione su tutti i core, il non determinismo della scelta renderebbe particolarmente costosa l’operazione. • Meccanismi di pipelining delle istruzioni: mentre i processori di tipo scalare, la cui produzione per il mercato di massa si è arrestata ormai decenni fa, sono in grado di processare una sola istruzione per ciclo, e hanno quindi un comportamento sotto questo punto di vista deterministico, i processori superscalari sono in grado di processare più istruzioni alla volta: tale processo è non deterministico. Il pipelining, introducendo non determinismo, rende molto più complesso il testing. Inoltre sono possibili errori dovuti proprio al pipelining, nel caso questo non venga gestito correttamente. 15 • La frequenza: le moderne CPU operano a frequenze dell’ordine del Gigahertz, rendendo sempre più costoso lo sviluppo della strumentazione di testing (Automatic Testing Equipment), che, secondo la Semiconductor Industry Association Roadmap già nel 1997 [17] poteva raggiungere un costo di 20 milioni di dollari. Questo problema non è aggirabile utilizzando dispositivi che operano a frequenze più basse perché possono esserci difetti della CPU che si manifestano solo alle frequenze più elevate (at-speed testing). • Le metodologie di self-testing comportano una perdita di prestazioni: per alcuni tipi di dispositivi (FPGA) e per CPU semplici è possibile delegare il testing ad una apposita circuiteria, che, una volta attivata, permette al dispositivo di “testare se stesso” (BIST o Built-in Self Testing), facendo uso di scan chains. Tale tecnica, tuttavia, richiede un aumento di area del circuito e una conseguente degradazione delle prestazioni: per le CPU complesse come quelle di oggi, tale tecnica risulta non essere fattibile. • Necessità di mercato: il testing dei processori è un’attività costosa in termini di tempo e di risorse. Se tale attività viene condotta in hardware, lo è particolarmente. Considerando che un gran numero di processori viene immesso sul mercato per essere utilizzati su normali computer, si tende a trovare un buon compromesso fra completezza dei test e costi/time to market. 2.3 Testing software Da tali problematiche sorge l’esigenza di rendere il testing dei processori meno costoso e più veloce. Una soluzione pratica può essere quella di delegare tutto o parte del testing al software. Questo è conveniente per l’intrinseca flessibilità del software, che può essere riprogrammato facilmente e a basso costo; inoltre, al contrario dell’hardware, non richiede una considerevole aggiunta di circuiteria tale da peggiorare i parametri di area e di ritardo del processore. Inoltre nel caso di testing hardware di tipo built-in, le scan chains necessarie vanno progettate insieme al circuito stesso, che risulta, già nel progetto, come un compromesso tra sicurezza e prestazioni; di contro, il testing software può essere progettato in un secondo tempo rispetto al processore, in modo che il progetto di quest’ultimo possa 16 essere orientato maggiormente verso le prestazioni. Tuttavia tipicamente le suite di test diffuse commercialmente sono di tipo funzionale, ma non di tipo strutturale, e si limiteranno a determinare il funzionamento del processore durante alcune operazioni ben definite (di solito istruzioni di un linguaggio di programmazione, non vengono utilizzate direttamente le istruzioni del processore), senza poter ad esempio individuare difetti strutturali dello stesso: ciò infatti richiede la conoscenza della struttura del processore, ovvero componenti e relative interconnessioni, che tipicamente non possono essere rese pubbliche per motivi commerciali (timore ad esempio che un concorrente possa copiare le proprie soluzioni), nonché l’intero instruction set della CPU. Sebbene esistano tecniche di testing software diverse e con differenti gradi di astrazione (dal linguaggio assembly fino a suite di test scritte in linguaggi orientati agli oggetti), in generale è possibile individuare due fasi fondamentali: • Codifica dei test: deciso il linguaggio di programmazione da usare (che può anche essere ibrido, ovvero utilizzare sia il linguaggio assembly che un linguaggio a più alto livello nello stesso programma) si procede alla stesura della suite di test. • Esecuzione dei programmi di test e confronto col risultato atteso (oracolo): un altro vantaggio del testing software dei processori è che, nel caso in cui si utilizzi una tecnica software di basso livello, che preveda l’utilizzo di dispositivi appositi per il caricamento del programma di test nel processore, tali dispositivi possono agire a frequenze di molto inferiori a quelle della CPU, e sono pertanto di gran lunga più economici dei dispositivi di at-speed-testing. 2.4 Self-testing software Abbiamo visto come il testing hardware abbia la caratteristica di poter testare direttamente i componenti strutturali della CPU, mentre quello software sia più economico e più veloce. Sono state proposte tecniche software che uniscono i vantaggi dell’uno e dell’altro tipo di testing: una delle più interessanti è sicuramente quella del self-testing software [13]. Tale tecnica si basa sulla conoscenza completa della descrizione a livello di Register 17 Transfer e dell’instruction set del processore, e finora è stata con successo applicata a processori molto semplici, come il Padawan (CPU a 8 bit con indirizzamento a 12 bit per complessivi 4 KB di memoria). Le fasi in cui si articola sono tre: 1. Estrazione dell’informazione 2. Selezione dell’istruzione 3. Selezione dell’operando Nella fase di estrazione dell’informazione, partendo dall’instruction set e dalla descrizione a livello RT, si individuano i componenti interessati dall’istruzione e gli effetti che subisce. Per ogni istruzione I e per ogni componente C vengono estratte le seguenti informazioni: L’operazione O che il componente esegue, e i segnali di controllo abilitati dall’unità di controllo per eseguire l’operazione Per ogni operazione O, i registri interni di cui O fa uso, e la memoria che impiega per controllo e per conservare i dati, insieme alle loro caratteristiche di osservabilità e di controllabilità. A partire da queste informazioni è possibile associare i segnali di controllo alle singole istruzioni del processore e catalogare le istruzioni in base alle proprietà di osservabilità e di controllabilità dei registri del processore e di memoria di cui l’istruzione fa uso [13]. La fase di selezione dell’istruzione prevede, per ogni componente C, l’insieme delle operazioni OC che è in grado di eseguire. Definiamo IC,O l’insieme delle istruzioni che durante la loro esecuzione comportano l’attivazione degli stessi segnali di controllo, in modo da far eseguire al componente C l’operazione O. È evidente che, per come sono stati definiti gli insiemi, esiste almeno un’istruzione I che faccia sì che il componente C esegua l’operazione O, ovvero 𝐼𝐶,𝑂 ≠ ∅. Le istruzioni che appartengono a IC,O avranno diverse caratteristiche di osservabilità e di controllabilità in quanto, ogni volta che l’operazione O viene eseguita, gli output del componente C saranno registri con diverse caratteristiche di osservabilità, e a loro volta gli 18 input saranno registri interni con diverse caratteristiche di controllabilità. Si sceglie quindi un’istruzione I dall’insieme 𝐼𝐶,𝑂 in base ai seguenti passaggi [13] 1. Scartare le istruzioni 𝐼 ∈ 𝐼𝐶,𝑂 tali che quando O viene eseguita, l’uscita di C non viene propagata ad un registro interno al processore. Tale criterio si basa sul fatto che, se anche un componente è in uno stato di errore, se la sua uscita non raggiunge i registri del processore, l’errore non si propagherà. 2. Ordinamento delle istruzioni in un’Instruction Priority List (IPL) secondo quanto individuato dalla fase di estrazione dell’istruzione e in base ai seguenti due passaggi. 3. Siano 𝐼𝐴 , 𝐼𝐵 ∈ 𝐼𝐶,𝑂 è prioritaria rispetto a 𝐼𝐵 (ovvero è in una posizione più alta nell’IPL) se 𝐼𝐴 richiede un minor numero di operazioni nel propagare l’uscita di C in un registro del processore (ovvero 𝐼𝐴 è più facilmente osservabile di 𝐼𝐵 . 4. Se 𝐼𝐴 e 𝐼𝐵 hanno la stessa priorità, si sceglie quella che richiede una sequenza di istruzioni più piccola per generare uno specifico pattern di test nel registro del processore che fa da input al componente C. Va sottolineato che i passaggi sono stati definiti in maniera generale, senza ottimizzazione rispetto a un determinato componente: in tal caso andrebbero modificati in modo da essere più efficienti. Nella fase di selezione dell’operando si selezionano gli operandi deterministici in modo da ottenere la più alta copertura di fallimenti strutturali. Una volta applicata l’istruzione, il processore eseguirà una STORE in modo da conserva il risultato del test in memoria. Se l’uscita del componente testato non è direttamente propagata ad uno dei registri della CPU, sarà necessario eseguire un’istruzione aggiuntiva per memorizzare l’output del componente in un registro [13]. A seconda della categoria a cui il componente appartiene (moduli funzionali, registri, 19 elementi logici) avremo bisogno di un pattern di test diverso. Anche nel caso in cui il componente sia difficile da testare, questa tecnica prevede l’uso di operazioni logico-aritmetiche molto semplici (ADD, MULTIPLY, AND) generate on-chip e organizzate in cicli efficienti, contrariamente a quello che avviene nel caso delle tradizionali tecniche pseudo-casuali, che richiedono l’uso di un LFSR (linear feedback shift register), un registro i cui dati di ingresso sono determinati da una funzione dello stato interno: tali tecniche fanno uso di operazioni complesse, come controlli di parità, operazioni bit a bit, e shifting, che producono anche programmi più lunghi. Possiamo sintetizzare le operazioni in uno pseudo-codice: 𝑝𝑒𝑟 𝑜𝑔𝑛𝑖 𝐶𝑜𝑚𝑝𝑜𝑛𝑒𝑛𝑡𝑒 𝐶 { 𝑝𝑒𝑟 𝑜𝑔𝑛𝑖 𝑂𝑝𝑒𝑟𝑎𝑧𝑖𝑜𝑛𝑒 𝑂 ∈ 𝑂𝐶 { 𝑑𝑒𝑓𝑖𝑛𝑖𝑟𝑒 𝑙 ′ 𝑖𝑛𝑠𝑖𝑒𝑚𝑒 𝐼𝐶,𝑂 𝑠𝑐𝑒𝑔𝑙𝑖𝑒𝑟𝑒 𝐼 ∈ 𝐼𝐶,𝑂 𝑖𝑛 𝑏𝑎𝑠𝑒 𝑎 𝑐𝑜𝑛𝑡𝑟𝑜𝑙𝑙𝑎𝑏𝑖𝑙𝑖𝑡à 𝑒 𝑜s𝑠𝑒𝑟𝑣𝑎𝑏𝑖𝑙𝑖𝑡à 𝑢𝑠𝑎𝑛𝑑𝑜 𝐼, 𝑎𝑝𝑝𝑙𝑖𝑐𝑎𝑟𝑒 𝑝𝑎𝑡𝑡𝑒𝑟𝑛 𝑑𝑒𝑡𝑒𝑟𝑚𝑖𝑛𝑖𝑠𝑡𝑖𝑐𝑖 𝑎𝑔𝑙𝑖 𝑖𝑛𝑝𝑢𝑡 𝑑𝑖 𝐶 } 𝑠𝑒 𝑙𝑎 𝑐𝑜𝑝𝑒𝑟𝑡𝑢𝑟𝑎 𝑑𝑒𝑖 𝑓𝑎𝑙𝑙𝑖𝑚𝑒𝑛𝑡𝑖 è ≥ 𝑑𝑖 𝑞𝑢𝑒𝑙𝑙𝑎 𝑑𝑒𝑠𝑖𝑑𝑒𝑟𝑎𝑡𝑎 𝑒𝑠𝑐𝑖 } È da notare che può esistere un’istruzione I che stimoli contemporaneamente operazioni diverse in componenti diversi, ovvero 𝐼𝐶1 ,𝑂1 ∩ 𝐼𝐶2 ,𝑂2 ≠ ∅ . Se le uscite dei componenti C1 e C2 vengono propagate ai registri della CPU, anche se il test è stato progettato per uno dei due componenti, possiamo anche controllare eventuali fallimenti dell’altro. In tal modo possiamo testare un componente anche in casi di test progettati per un altro, riducendo il numero di casi di test che si avrebbe se li progettassimo per ogni singolo componente invece che per i componenti nel complesso: pertanto per ridurre il numero di test necessari, dovrebbero essere definiti prima quelli le cui istruzioni attivano il maggior numero di altri componenti [13]. 20 Infine, una delle caratteristiche più importanti di tale metodologia di testing è che viene sì richiesta la descrizione del processore e l’instruction set, ma non la descrizione a livello di gate né la sintesi in un linguaggio di descrizione hardware (come VHDL o Verilog). Si riportano le percentuali di copertura ottenute mediante self-testing software relative ai componenti della CPU Padawan; si noti che sono maggiori sia del full scan [13] (89.39% ) che del logic BIST [13] (88.69 %) applicate sullo stesso processore, che però prevedono una modifica del circuito (e il peggioramento dei parametri di area e di prestazione). Componente Copertura (%) ALU 98.48 SHU 93.82 PC 88.10 AC 98.67 IR 98.26 MAR 97.22 SR 92.13 CTRL 85.52 Totale 91.10 Oltre al dato di copertura, è importante analizzare una tecnica di testing anche considerandone la dimensione in byte della suite di test e dei dati di output e il tempo necessario ad eseguirla. I dati sono riportati nella seguente tabella. Numero di Istruzioni 444 Dimensione programma 885 B Dimensione risposta 122 B Cicli complessivi 16572 Rispetto ad altre tecniche simili [17] la tecnica proposta ha una copertura simile (le altre arrivano fino al 91.42%), ma dimensioni e tempi di esecuzione minori [13]. 21 2.5 Pattern di Testing Indipendentemente dal modo in cui viene svolto il testing della CPU (sia esso software o hardware), viene svolto secondo un determinato modello o pattern. Esistono diversi pattern di testing fondamentali [18]: • Deterministico: sviluppato specificamente per un determinato circuito attraverso un generatore automatico di test pattern (ATPG) o in base ad una simulazione di fallimento. • Algoritmico: usato per il testing di strutture regolari e in base a modelli di fallimento definiti. • Esaustivo: tutti i possibili input (ad esempio per un contatore a n bit si possono testare tutte le possibili 2n configurazioni). Questo pattern talvolta è difficile se non impossibile da utilizzare e quando non lo è spesso ha costi molto elevati. • Pseudo – esaustivo: si divide il circuito da testare in sotto-circuiti e si applicano tecniche di testing esaustivo sui sotto-circuiti. • Casuale: testing generato in maniera completamente casuale • Pseudo – casuale: molto simile al testing casuale, ma definisce sequenze ripetibili di testing • Pseudo – casuale pesato: assegna un peso diverso alle diverse combinazioni possibili, in modo che le più rilevanti si verifichino più spesso nella sequenza. 22 Capitolo 3: Intel® Processor Diagnostic Tool Intel è l’azienda leader nel settore dei dispositivi a semiconduttore come microprocessori, memorie, dispositivi per telecomunicazioni e sistemi integrati. Fondata nel 1968 come Integrated Electronics Corporation, è uno dei maggiori produttori di processori per sistemi safety critical. Fornisce una suite di test per processori chiamata Intel Processor Diagnostic Tools, scaricabile gratuitamente dal loro sito [19], compatibile con Windows e con Linux e per architetture x86 e a 64 bit. Vengono distribuiti: un programma dotato di interfaccia grafica in grado di eseguire automaticamente tutti i test in maniera sequenziale, i singoli programmi di test, eseguibili mediante riga di comando, i cui risultati vengono raccolti in un documento di testo e i codici sorgente: in questo modo i test possono essere facilmente modificati, estesi o integrati in altri programmi. 3.1 Programmi di test La suite di test Intel Diagnostic Tool include i seguenti programmi di test: • Genuine Intel: verifica che il processore su cui si stanno eseguendo i test sia effetivamente un processore Intel originale e quindi ci assicura che il processore non è contraffatto e che non sia di un altro produttore. • Temperature: controlla la temperatura attuale del processore e, di default, il test è negativo se la temperatura è almeno un grado inferiore alla temperatura massima in cui il processore dovrebbe funzionare, positivo altrimenti. Permette di modificare la differenza rispetto alla temperatura massima (ad esempio si può modificare il criterio richiedendo che la temperatura sia almeno 30 gradi inferiore al massimo, o anche meno nel caso in cui si stia testando il processore 23 a basso carico e si voglia essere sicuri che la temperatura minima abbia un valore accettabile). • Brand string: estrae dal processore una stringa contente informazioni, come modello, generazione e frequenza di funzionamento e la confronta con quanto definito in BrandString_LocalConfig.xml: se la stringa inizia per Intel ®, è concatenata con una stringa intermedia (Core, Atom, Pentium), presenta la parola CPU ed è concatenata con una @, la stringa è ritenuta valida. • CPU Frequency: verifica che la frequenza della CPU sia nei limiti di quella prevista per il processore testato. La frequenza può essere calcolata in due modi, il primo che restituisce la massima frequenza possibile, il secondo che valuta la frequenza a cui effettivamente il processore sta operando. • Floating Point: esegue una valutazione dei FLOPS ovvero delle operazioni a virgola mobile (floating point) eseguite in un secondo. Il test esegue cicli di operazioni (14400000 ad ogni ciclo) il cui risultato viene confrontato con quello atteso per determinare la correttezza delle stesse. Il test riporta anche il numero di eventuali errori. • Prime Number generator: valuta la velocità della CPU di poter generare correttamente numeri primi. Il test considera i numeri interi fino a 10000000, applica un setaccio per eliminare i non primi e verifica che i numeri rimanenti siano primi. • Cache: accerta la presenza di cache e relative dimensioni: vengono valutate le dimensioni della cache dati e istruzioni di livello 1, e le dimensioni delle cache di livello 2 e di livello 3. • MMXSSE: esamina la compatibilità del processore con i set di istruzioni MMX e SSE (fino alla versione 4.2). Si tratta di istruzioni del tipo single instruction, multiple data, utilizzate soprattutto per la grafica e per l’elaborazione numerica dei segnali. • AVX: controlla che il processore sia compatibile con il set di istruzioni Advanced Vector Extension, di tipo SIMD, che migliorano le prestazioni siano 24 per la modellazione 3D che per il calcolo scientifico, con il set di istruzioni AES per la crittografia, e con il set PCLMULQDQ per la moltiplicazione ad operandi a 64 bit senza riporto. • IMC: misura la memoria, confronta il risultato del test con la quantità attesa (comunicata dal sistema operativo) ed effettua delle semplici operazioni sulla memoria. • PCH: restituisce il modello del chipset (circuiti integrati nella scheda madre che dirigono il traffico di informazioni tra periferiche, CPU, RAM e bus di sistema), il suo stepping (versione), elenca le porte e i dispositivi che vi sono collegati. • IGD: controlla se nel processore è presente un dispositivo grafico integrato Intel, e, nel caso affermativo, ne indica la generazione, attraverso un confronto tra la stringa letta e un archivio di valori attesi per le varie generazioni. • GFX: esercita la GPU facendo uso di due applicazioni OpenGL, una per la grafica in due dimensioni, una in 3D, • CPU load: carica la CPU al massimo con operazioni matematiche per un intervallo di tempo prefissato e verifica che l’applicazione non fallisca. 3.2 Analisi dei codici sorgenti Attraverso l’analisi dei codici sorgenti otteniamo maggiori informazioni a basso livello dei programmi di test, in particolare nelle sezioni che operano mediante linguaggio assemblativo, si procede quindi ad uno studio dei singoli programmi di test. L’analisi è stata condotta facendo uso del compilatore Intel® e di Microsoft Visual Studio e si sono applicate tecniche di reverse engineering in modo da produrre i seguenti documenti che permettono di comprendere cosa i vari test svolgano e, cosa ancora più interessante e significativa, come lo svolgano: infatti di tali test è fornita una documentazione sommaria che però spiega unicamente come usarli ma non come funzionino. 3.2.1 Genuine Intel Il programma di test GenIntel come primo passaggio effettua una pulizia del registro EAX 25 a 32 bit (accumulatore), attraverso l’istruzione: 𝑥𝑜𝑟 𝑒𝑎𝑥, 𝑒𝑎𝑥 poiché la xor di una stringa di bit con se stessa restiuisce sempre una stringa di 0 (infatti la xor restituisce 0 se due bit sono uguali): ciò è necessario in quanto la successiva istruzione: 𝑐𝑝𝑢𝑖𝑑 produce risultati diversi in base al contenuto di EAX [20]; per far sì che venga restituito il Vendor ID (la stringa “GenuineIntel”) si deve avere EAX = 0. Si tenga conto che il valore massimo che EAX può assumere dipende dal set di funzioni che si vuole usare [20]: nel caso di funzioni per informazioni di base sul processore , il valore massimo viene ricavato nel modo in cui si descrive di seguito; nel caso del set di istruzioni per informazioni avanzate sul processore, è necessaria l’istruzione 𝑚𝑜𝑣 𝑒𝑎𝑥, 8000000ℎ: il massimo valore inseribile in EAX quando si chiama CPUID per istruzioni avanzate verrà mostrato proprio in EAX (da Pentium 4 fino a Skylake il massimo valore di EAX è 8000 0008). L’effetto di tale istruzione nei registri è: • Nel registro EAX viene inserito il massimo valore che lo stesso registro può contenere all’atto della chiamata di CPUID riguardanti informazioni di base della CPU • In EBX, ECX, EDX la stringa “GenuineIntel” codificata in ASCII, secondo la seguente configurazione: Registro Bit 31-24 Bit 23-16 Bit 15-8 Bit 7-0 EBX u (75) n (6E) e (65) G (47) EDX I (49) e (65) n (6E) i (69) ECX l (6C) e (65) t (74) n (6E) Per poter correttamente ricostruire la stringa abbiamo due problemi: mentre i caratteri ce li aspetteremmo su un byte, sono contenuti in un registro da 4, e nella manipolazione dobbiamo porre attenzione nel modo con cui ricostruiamo la stringa. Per ovviare al primo problema, si fa uso, per ognuno dei registri general purpose, dei registri H (alto, bit 15-8) e L (basso, bit 7-0), ciascuno dei quali è a 8 byte, un carattere ASCII: necessariamente la stringa di bit deve però essere ruotata per poter avere il giusto carattere 26 nella posizione desiderata. Quindi, per ognuno dei registri general purpose vengono eseguite le seguenti istruzioni (a titolo di esempio si fa uso di EBX, ma gli altri due registri vengono manipolati con tecniche simili, a parte l’incremento dell’indice del vettore di caratteri e la terminazione con il carattere nullo alla fine della stessa): 𝑚o𝑣 𝑒𝑎𝑥, 𝑒𝑏𝑥 𝑚𝑜𝑣 𝑠𝑆𝑦𝑠𝑇𝑦𝑝𝑒[0], 𝑎𝑙 𝑚𝑜𝑣 𝑠𝑆𝑦𝑠𝑇𝑦𝑝𝑒[1], 𝑎ℎ 𝑠ℎ𝑟 𝑒𝑎𝑥, 16 𝑚𝑜𝑣 𝑠𝑆𝑦𝑠𝑇𝑦𝑝𝑒[2], 𝑎𝑙 𝑚𝑜𝑣 𝑠𝑆𝑦𝑠𝑇𝑦𝑝𝑒[3], 𝑎ℎ ovvero sposto il contenuto del registro general purpose in EAX, costruisco la stringa sSysType prelevando il primo carattere, poi il secondo: per utilizzare gli altri due usando registri a 8 bit effettuo lo shift a destra di 16 bit e prelevo il terzo e il quarto carattere. 3.2.2 Temperature Il programma di test temperature verifica che la temperatura del processore si trovi al di sotto di una soglia prefissata. La logica del test si trova fondamentalmente nella procedura void Temperature(unsigned long long *LSIZE) , in cui prima si procede ad un controllo del più grande opcode supportato per le funzioni standard eseguibili da CPUID come descritto prima, mediante il codice: mov eax, 0 CPUID mov Avail, eax la variabile Avail conterrà quindi l’opcode standard massimo. Successivamente dobbiamo controllare che Avail sia maggiore o uguale di 6𝐻𝐸𝑋 (ciò è vero da Pentium D in poi) per avere la sicurezza che la funzione che verifica la presenza del sensore di temperatura sia supportata. Verificato questo, allora è possibile eseguire: mov eax, 0x6 mov ecx, 0 CPUID and eax, 0x1 mov Sensor, eax 27 che, come detto, verifica la presenza del sensore di temperatura (e anche di funzionalità di gestione del consumo energetico); il primo è presente se il bit 0 di EAX è alto dopo l’esecuzione di CPUID (il valore di EAX viene memorizzato nella variabile Sensor), ne consegue che bisogna assicurarsi che Sensor = 1. Accertato questo, i passaggi successivi sono strettamente legati al modo in cui i processori Intel misurano la temperatura [21]: infatti entra in gioco la temperatura 𝑇𝐽 o temperatura di giunzione, misurata attraverso una termocoppia posta sul diffusore di calore del processore; tale temperatura solitamente varia tra gli 85 °C e i 109 °C. Un registro MSR (Model Specific Register, un registro utilizzato per il controllo o il debugging) conterrà la temperatura attuale, tuttavia espressa come ∆𝑇 rispetto a 𝑇𝐽 da cui ne deriva che la temperatura della CPU, o 𝑇𝐶𝑃𝑈 = 𝑇𝐽 − ∆𝑇 . Ritornando al codice vero e proprio, inizialmente verifichiamo che una lettura valida sia presente (si controlla che il MSB di EAX sia alto): while (valid != 0x80000000) {Rdmsr(0x19C, &RetEAX, &RetEDX); OrgEAX = RetEAX; valid = (RetEAX &= 0x80000000 } si noti la presenza dell’istruzione Rdmsr, deputata alla lettura dei registri MSR, il cui indirizzo viene specificato come parametro del metodo. Accertatisi che una lettura valida della temperatura sia presente, si procede a prelevarla: if (valid == 0x80000000) { RetEAX = OrgEAX; RetEAX &= 0x7F0000; RetEAX = RetEAX >> 16; LSIZE[0] = RetEAX;} il successo del test viene valutato attraverso un confronto tra ∆𝑇, ossia la differenza rispetto alla temperatura di giunzione, e la soglia desiderata. Se la prima è maggiore di quest’ultima vi è un successo del test, altrimenti un fallimento. 3.2.3 Brand String Il programma di test “BrandString” preleva una stringa detta Brand String dal processore facendo uso tre volte dell’istruzione CPUID, ciascuna delle quali con un valore diverso contenuto in EAX; avremo quindi un programma che può essere riassunto in questo modo: 𝑚𝑜𝑣 𝐸𝐴𝑋, 0𝑥80000002 𝐶𝑃𝑈𝐼𝐷 28 𝑝𝑟𝑒𝑙𝑒𝑣𝑜 𝑙𝑎 𝑝𝑟𝑖𝑚𝑎 𝑝𝑎𝑟𝑡𝑒 𝑑𝑒𝑙𝑙𝑎 𝐵𝑟𝑎𝑛𝑑 𝑆𝑡𝑟𝑖𝑛𝑔 𝑑𝑎 𝐸𝐴𝑋, 𝐸𝐵𝑋, 𝐸𝐶𝑋, 𝐸𝐷𝑋 𝑚𝑜𝑣 𝐸𝐴𝑋, 0𝑥80000003 𝐶𝑃𝑈𝐼𝐷 𝑝𝑟𝑒𝑙𝑒𝑣𝑜 𝑙𝑎 𝑠𝑒𝑐𝑜𝑛𝑑𝑎 𝑝𝑎𝑟𝑡𝑒 𝑑𝑒𝑙𝑙𝑎 𝐵𝑟𝑎𝑛𝑑 𝑆𝑡𝑟𝑖𝑛𝑔 𝑑𝑎 𝐸A𝑋, 𝐸𝐵𝑋, 𝐸𝐶𝑋, 𝐸𝐷𝑋 𝑚𝑜𝑣 𝐸𝐴𝑋, 0𝑥80000004 𝑝𝑟𝑒𝑙𝑒𝑣𝑜 𝑙𝑎 𝑡𝑒𝑟𝑧𝑎 𝑝𝑎𝑟𝑡𝑒 𝑑𝑒𝑙𝑙𝑎 𝐵𝑟𝑎𝑛𝑑 𝑆𝑡𝑟𝑖𝑛𝑔 𝑑𝑎 𝐸𝐴𝑋, 𝐸𝐵𝑋, 𝐸𝐶𝑋, 𝐸𝐷𝑋 Per quanto riguarda il prelievo della Brand String, il procedimento è lo stesso di quanto visto con Genuine Intel: ogni registro contiene quattro lettere; le singole lettere sono ordinate da destra a sinistra e quindi mentre le prime due possono essere sempre prelevate facendo riferimento ai “sottoregistri” alto e basso del registro in oggetto, le altre due, per essere utilizzate, hanno bisogno di uno shift a destra, analogamente a quanto abbiamo visto prima. La stringa così costruita è chiamata sBrandVal. Si verifica che tale stringa rispetti la sintassi delle Brand String Intel; ciò viene fatto applicando le regole trovate in BrandString_LocalConfig.xml, che vengono memorizzate in una variabile del tipo RapidXMLData. Le regole sintattiche prevedono che la BandString: 1. Inizi per Intel(R) 2. Deve essere composta da una delle seguenti “sottostringhe”: Core e (TM), Celeron(R), Pentium(R), Atom e (TM), Xeon(R). 3. Deve essere presente la parola “CPU” 4. Deve essere presente il carattere “@”, che viene utilizzato per indicare la frequenza a cui quel modello opera. Se tali regole sono rispettate, la Brand String risulta valida. 3.2.4 CPU Frequency Il programma di test “cpufreq” ha il compito di misurare la frequenza del processore. Esistono due metodi per farlo. Il primo è molto semplice e fa uso dell’istruzione 𝑅𝐷𝑇𝑆𝐶 29 acronimo di “Read Time Stamp Counter”, il cui effetto è leggere il contenuto di un registro a 64 bit che contiene il numero di cicli di clock compiuti dal processore. All’invocazione di tale istruzione il contenuto di tale registro viene memorizzato in due registri a 32 bit, EAX e EDX. Per memorizzare il valore iniziale del Time Stamp Counter utilizziamo una variabile del tipo _int64 cyclesStart i cui bit più alti saranno quelli contenuti in EDX e quelli più bassi quelli appartenenti a EAX, ovvero: __𝑖𝑛𝑡64 𝑐𝑦𝑐𝑙𝑒𝑠𝑆𝑡𝑎𝑟𝑡; 𝑚𝑜𝑣 𝐷𝑊𝑂𝑅𝐷 𝑃𝑇𝑅 𝑐𝑦𝑐𝑙𝑒𝑠𝑆𝑡𝑎𝑟𝑡, 𝑒𝑎𝑥 𝑚𝑜𝑣 𝐷𝑊𝑂𝑅𝐷 𝑃𝑇𝑅[𝑐𝑦𝑐𝑙𝑒𝑠𝑆𝑡𝑎𝑟𝑡 + 4], 𝑒𝑑𝑥 codice simile va eseguito quando dobbiamo prelevare il numero di cicli di clock, tali istruzioni saranno eseguite dopo un secondo utilizzando l’istruzione wait_mils(1000), ciò che cambia è l’utilizzo di una variabile cyclesStop, mentre per il resto il codice è identico. Il numero di cicli viene calcolato un numero di volte dipendente dal sistema operativo (10 volte in Windows, 1000 in Linux). La frequenza viene ottenuta a partire dai cicli misurati, che vengono divisi per il numero di misurazioni e normalizzati in modo da verificare se il suo valore rientra nell’intervallo che va dalla frequenza nominale (estratta mediante il test Brand String) meno la tolleranza al valore ottenuto sommando la frequenza nominale alla tolleranza. Così facendo misuriamo però la massima frequenza teorica. Il secondo metodo è più complesso e accurato e fa uso dei registri APERF e MPERF: il primo “conta” solo se il processore logico è attivo, il secondo sempre; pertanto il secondo metodo ci permette di ottenere una misurazione più precisa, legata all’effettivo utilizzo del processore. La frequenza viene calcolata in due passaggi: nel primo calcoliamo la frequenza TSC nominale, nel secondo moltiplichiamo la frequenza nominale per il rapporto APERF/MPERF (che rappresenta il duty cycle attuale). Il calcolo della massima frequenza nominale avviene leggendo il valore di MSR_PLATFORM_INFO e tenendo conto dell’impostazione di Power Management in cui il processore attualmente si trova. Tale valore, contenuto nella variabile Max_Ratio del codice sorgente, viene moltiplicato, a seconda del modello del processore per 100MHz o 133.33 MHz, ottenendo la massima frequenza nominale (variabile OperatingFreq). 30 Come detto, la frequenza effettiva viene calcolata come: 𝐴𝑃𝐸𝑅𝐹 𝑂𝑝𝑒𝑟𝑎𝑡𝑖𝑛𝑔𝐹𝑟𝑒𝑞 ∙ 𝑀𝑃𝐸𝑅𝐹 In questo modo, abbiamo ottenuto un valore sulla frequenza a cui il processore opera legato all’effettivo utilizzo dello stesso, e non in base ad un massimo teorico. 3.2.5 Floating Point Il programma di test “math_fp” verifica la correttezza delle operazioni in virgola mobile. Tale test consiste nella dichiarazione di un vettore i cui elementi sono numeri a sei cifre decimali; diverse operazioni vengono eseguite, il cui risultato viene confrontato con quello atteso: tale numero di confronto è presente in diverse versioni, con più o meno cifre dopo la virgola, quella maggiormente definita ha ben trentuno decimali. Le operazioni vengono eseguite per un tempo specificato dalla variabile “runtime” che può essere specificato dall’utente e il cui valore di default è 2 secondi. Durante questo tempo vengono eseguiti diversi cicli composti ognuno da 14400000 operazioni: vengono registrati eventuali errori (e se il flag corrispondente è attivo, il test può bloccarsi al primo errore), e misurati anche i MegaFLOPS (milioni di operazioni in virgola mobile eseguite al secondo) secondo la formula: 14400000 1000000 ∙ ∆𝑡 con ∆𝑡 intervallo di tempo trascorso dal termine del ciclo precedente (o dall’inizo del test se è la prima iterazione). 3.2.6 Prime number generation L’eseguibile “Math_PrimeNum” genera numeri primi e misura la velocità in cui lo fa. Il processo di generazione prevede la creazione di un vettore di interi, chiamato sieve, di cardinalità definita dal parametro sievesize, di default pari a 10000000, i cui elementi sono inizialmente tutti pari ad uno. Vengono posti a 0 gli elementi la cui posizione è maggiore o uguale di due, e multipla di un numero il cui quadrato è minore di 10000000 (si tratta di un “setaccio”, ovvero di una tecnica utilizzata per eliminare i numeri non primi), il cui codice è: 31 for (int i = 2; i * i < sievesize; i++) if (sieve[i]) for (int j = i + i; j < sievesize; j += i) sieve[j] = 0; successivamente si genera, per 1000 volte, un numero a caso, e se l’elemento la cui posizione nel vettore è pari a quel numero è uguale a 1, deve essere verificato che quel numero è un numero primo for (int randQty = 0; randQty < 10000; randQty++) { random_integer = rand() % 10000000 + 1; (si noti l’errore di utilizzare, nella divisione in modulo, un valore costante e non il parametro sievesize; un errore grave si verificherebbe nel caso di una riduzione del valore di sievesize, in quanto il vettore accederebbe ad un’area di memoria che non gli appartiene, senza neanche sollevare un’eccezione perché il controllo sulla correttezza dell’indice viene eseguito solo usando il metodo at() e non quando si opera con [], conducendo a risultati sicuramente sbagliati). Il test per verificare se il numero è primo consiste nel dividerlo per tutti gli interi compresi tra due e la parte intera della radice quadrata, in quanto se un numero non è primo ha sicuramente un fattore pari o minore della sua radice quadrata (escludendo il caso in cui il numero sia un quadrato perfetto, nel secondo caso, se entrambe le radici fossero maggiori della radice quadrata, il risultato sarebbe un numero, per assurdo, maggiore di quello di cui dovrebbero essere fattori) if (sieve[random_integer]) {bool a = true; for (double i = 2; i <= sqrt(random_integer); i++) {int numberI = static_cast<int>(random_integer); int iI = static_cast<int>(i); if (numberI%iI == 0) {a = false; break; } } if (a == false) //Non Prime Number {a = true; CycleErr += 1;} else { a = true; } 32 se trovo un divisore, esco dal ciclo e aggiungo uno al conteggio degli errori, altrimenti arrivo alla parte intera della radice ed esco. La restante parte del codice prevede l’uscita immediata dalla procedura se il flag di uscita al primo errore è alto e ho trovato un errore, oppure il calcolo della velocità nel generare i numeri primi (per il valore di sievesize di default sono 664579, numero memorizzato nella variabile TotalGenerated) diviso per il tempo impiegato a generare i numeri primi e verificarne le proprietà (timeUpdateOPS). Pertanto avremo che: 𝑂𝑝𝑒𝑟𝑎𝑡𝑖𝑜𝑛𝑠 𝑝𝑒𝑟 𝑠𝑒𝑐𝑜𝑛𝑑 = 𝑇𝑜t𝑎𝑙𝐺𝑒𝑛𝑒𝑟𝑎𝑡𝑒𝑑 𝑡𝑖𝑚𝑒𝑈𝑝𝑑𝑎𝑡𝑒𝑂𝑃𝑆 tale valore esprime proprio la velocità del processore nella generazione e nella verifica di numeri primi. 3.2.7 Cache Il programma “cache” misura la dimensione delle cache, livello per livello. Il codice prevede l’esecuzione dell’istruzione 𝐶𝑃𝑈𝐼𝐷 con EAX = 0, un check su contenuto dello stesso registro dopo l’esecuzione dell’istruzione (condizione necessaria è che il contenuto sia maggiore di 4, in quanto quello è il valore da inserire in EAX per ottenere informazioni sulla cache). Nelle seguenti operazioni: mov eax, 0x4 mov ecx, 0 CPUID mov ULorgEAX, eax and eax, 0xfc000000 shr eax, 26 add eax, 1 mov ULcores, eax mov eax, ULorgEAX and eax, 0x03FFC000 shr eax, 14 add eax, 1 mov ULThred, eax mov ecx, 0 mov eax, 0x1 CPUID and ebx, 0xFF0000 shr ebx, 16 mov ULLogIDs, ebx le variabili ULThread, ULLogIDs e UlCores vengono utilizzati per memorizzare 33 informazioni aggiuntive sul processore (cores, hyperthreading, ecc.), all’interno del vettore 𝐿𝑆𝐼𝑍𝐸, che nelle prime 4 posizioni conterrà informazioni sulla dimensione della cache, le restanti 3 dati aggiuntivi. Segue un loop sulla variabile ULorgEAX (rappresentante i livelli di cache), che inizialmente raccoglie informazioni su quattro parametri: associatività, partizioni di linee fisiche, dimensione della singola linea, set. Successivamente tali informazioni vengono utilizzate nella formula: (𝑎𝑠𝑠𝑜𝑐𝑖𝑎𝑡𝑖v𝑖𝑡à + 1)(𝑙𝑖𝑛𝑒𝑒 𝑓𝑖𝑠𝑖𝑐ℎ𝑒 + 1)(𝑑𝑖𝑚𝑒𝑛𝑠𝑖𝑜𝑛𝑒 𝑙𝑖𝑛𝑒𝑎 + 1)(𝑠𝑒𝑡 + 1) 1024 che permette di calcolare la dimensione (in KB) della cache. Si tenga conto che per la Cache di livello 1 vengono misurati sia lo spazio riservato ai dati che quello per le istruzioni. 3.2.8 MMXSSE Il controllo del supporto alle istruzioni MMX e SSE (dalla versione 1.0 alla 4.2) viene eseguito dal programma “mmxsse.exe” mediante dei metodi il cui comportamento prevede l’esecuzione di CPUID con EAX=1,e la verifica che tale istruzione abbia posto a 1 dei flag nei registri (su EDX abbiamo i flag relativi a MMX e SSE 1 e 2, su ECX quelli associati a SSE 3, SSSE3, SSE 4.1 e 4.2). Una volta raccolte queste informazioni, si procede ad un test delle istruzioni dei vari set. Tale test risulta essere completo anche per un sistema safety-critical in quanto non si limita a controllare la compatibilità del processore con l’instruction set in oggetto, ma verifica la correttezza delle singole operazioni. 3.2.9 AVX L’eseguibile “avx.exe” effettua un controllo sul supporto del processore ad AVX (istruzioni a 256 bit per ottimizzare i calcoli a virgola mobile) hardware e software, e ad AES e PCLMULQDQ in hardware. I tre controlli hardware vengono eseguiti in maniera molto simile a quella del test precedente, e sono compito di una procedura che riceve in ingresso il flag da valutare e lo controlla con l’istruzione CPUID configurata come visto prima. Il testing del supporto software di AVX avviene testandone prima quello hardware e poi eseguendo il codice assemblativo: 34 mov eax, 1 cpuid and ecx, 018000000H cmp ecx, 018000000H; jne NOT_SUPPORTED; mov ecx, 0; XGETBV; and eax, 06H cmp eax, 06H jne NOT_SUPPORTED mov eax, 1 mov val, 1 jmp DONE NOT_SUPPORTED : mov eax, 0 DONE : che valuta i flag AVX e OSXSAVE : il secondo permette al sistema operativo di far eseguire al processore il salvataggio dello stato esteso in un’area di memoria designata. Se tale flag fosse basso non potremmo usare l’istruzione XGETBV successivamente. L’and tra EAX e 06𝐻𝐸𝑋 ci permette di valutare se il sistema operativo supporta l’utilizzo dei registri XMM e YMM per operazioni rispettivamente a 128 e 256 bit (anche se si tratta di registri logici, in quanto se sono presenti anche i registri YMM, XMM sono in realtà i bit meno significativi di YMM) necessari per il supporto software delle istruzioni MMX e AVX. Dopo aver assegnato 1 a val (inizializzato a 0), usciamo. Se val vale 1 abbiamo eseguito tutte le istruzioni e quindi siamo sicuri del supporto software di AVX, altrimenti il sistema operativo non supporta tali istruzioni. In base all’esito dei controlli hardware e di quello software, il programma procede ad eseguire le istruzioni dei set supportati. 3.2.10 IMC “imc.exe” ha il compito di accertare la corretta comunicazione CPU-memoria, nonché capacità e velocità di quest’ultima. Il programma di test inizialmente carica alcune configurazioni parametriche di una CPU che non supportano il test IMC (contenuti in un file XML GlobalConfig.xml, e ricavando delle stringhe di configurazione da confrontare a quella prelevata). La configurazione della CPU corrente viene ricostruita (funzione GetProcFamilyModel) facendo uso del ben conosciuto opcode CPUID con EAX = 1; nello 35 stesso registro, avremo come output tre parametri fondamentali: famiglia, modello e stepping della CPU, che preleveremo singolarmente mediante shift. Successivamente il modello viene confrontato con quelli del file descritto precedentemente: nel caso sia uno di quelli, il test termina. Nel caso contrario, il test prosegue prima inizializzando un valore di memoria attesa e relativa tolleranza se impostato da riga di comando; in ogni caso si procede poi a misurare la memoria fisica del sistema, ovviamente in modi diversi in base al sistema operativo in esecuzione. Nel caso di riferimento di Windows, prima se ne controlla la versione con una variabile di tipo OSVERSIONINFOEX; in base alla quale la misurazione può avvenire in due modi diversi: da Windows Vista in poi con l’utilizzo della funzione GetProcAddress i cui argomenti [22] sono un handle a una libreria dinamica (in questo caso Kernel32.dll) e il nome della funzione di sistema desiderata (GetPhysicallyInstalledSystemMemory), per versioni meno recenti si fa uso della struttura dati MEMORYSTATUSEX e del metodo ullTotalPhys. Restituito il valore di memoria fisica, eventualmente confrontato con quello atteso, si procede allo stress test della memoria. Il programma di test si assicura che vi sia almeno un gigabyte di RAM libera, e prosegue con le varie operazioni. La prima (Ones and Zeros Moving Inversions) consiste nella scrittura di un vettore dinamico di char i cui elementi sono alternativamente 𝐹𝐹𝐻𝐸𝑋 e 0, con conseguente verifica di questa proprietà e misura del tempo impiegato. La seconda (32Bits Sliding Ones) consiste nella costruzione di un vettore dinamico di int in cui ogni valore è la divisione in modulo 32 della posizione occupata e successivo shift con 1, (matematicamente equivale a inserire nel vettore elementi che sono la potenza di due elevata alla posizione occupata modulo 32) a cui segue un controllo della velocità a cui viene effettuato il test. Il terzo test (32Bits Sliding Zero) riempie il vettore con 0. 3.2.11 PCH Il Test PCH ha il compito di controllare il chipset, sia per quanto concerne i dati di produttore, modello e stepping, sia per verificarne il funzionamento e controllare le periferiche che vi sono collegate. Dopo aver caricato le configurazioni possibili, contenute nel file PCH_LocalConfig.xml, la prima operazione permette di controllare se il chipset del 36 sistema supporta il test ( int IsPCHseries()); tale metodo controlla che si tratti di un dispositivo Intel (IsIntelDevice), il prelievo delle informazioni vero e proprio avviene con la funzione DeviceIoControl, una API Windows che permette di inviare uno specifico codice ad un determinato driver [23]. Ottenuto il codice che individua il bus LPC (Low Pin Count), si risale al Chipset di appartenenza, sfruttando l’associazione uno a molti che sussiste tra quest’ultimo e l’ID dei bus. Successivamente si procede al prelievo dello stepping in modo simile a quanto fatto per il bus LPC (GetPCHSteppingString). Raccolte queste informazioni, si controlla il numero di bus PCI ed eventuali bridge di tipo PCI-PCI (di solito non presenti in sistemi di uso personale, ma nei server [24]). Per ogni bus PCI si elencano i dispositivi collegati (metodo PCIe). Vengono effettuati controlli anche su dispositivi Serial Ata esterni (extSATA), sulle periferiche collegate al controller audio Intel ad alta definizione (highDefAudio), su dispositivi collegati al controllore Gigabit LAN (GigabitLan). 3.2.12 IGD Il programma di test IGD verifica la presenza di Intel® Integrated Graphics sulla CPU su cui vene eseguita: si tratta di driver grafici utilizzati nel caso di schede video integrate. La funzione principale del test (IGDQUERY) procede inizialmente ad una veloce verifica di alcune proprietà di base (verificando ad esempio che sia Intel) con la funzione che controlla la Brand String e che abbiamo già analizzato precedentemente. Tale stringa diventa l’argomento della funzione IGDMain, che prima preleva famiglia, modello e stepping del processore (GetProcFamilyModel), poi verifica la generazione dello stesso chiamando la funzione Checki3i5i7 che confronta il modello prelevato in precedenza con i parametri presenti in un file XML contenente le diverse configurazioni (IGD_LocalConfig.xml) e restituisce una variabile intera legata alla generazione. Il flusso di controllo del chiamante viene determinato da questo parametro e dal Vendor ID, letto, in ultima analisi, con DeviceIoControl; anche altri valori vengono letti, come Device ID, Revision ID, Graphics Translation Table, Graphics Memory Range, Subsystem Vendor ID, Subsystem ID, Video BIOS Rom Address, Graphic Mode, Video Disable. La presenza di IGD viene rilevata da 37 una and tra il valore della generazione (come detto, restituito da Checki3i5i7) e dal parametro Vendor ID (che deve essere 8086, codice che rappresenta un prodotto Intel). In caso affermativo, vengono riportati anche gli ulteriori parametri elencati sopra, che vengono manipolati in maniera leggermente diversa in base alla generazione. Tale test non opera in maniera corretta se è presente una scheda video dedicata: in tal caso bisognerà scollegare quest’ultima per fare in modo che operi quella integrata nella CPU. 3.2.13 GFX Il programma di test GFX effettua due controlli: il primo sulla grafica in 2D, in cui viene verificata la generazione di uno spettro di colori, e il secondo in 3D, in cui viene visualizzato un cubo sulle cui facce sono applicate delle texture. Diversi parametri sono caricati da un file XML di configurazione (come il tempo di esecuzione del test e il numero di rotazioni del cubo, nome degli eseguibili per il test in 2D e in 3D). Successivamente al prelievo di tali parametri avviene l’esecuzione dei programmi mediante la funzione int system (const char* command), contenuta nella libraria cstdlib. Tuttavia dei due programmi che vengono eseguiti per il controllo delle funzionalità grafica (vis2Dgfx.exe e visGFX1.exe) non vengono forniti i codici sorgente, ma unicamente gli eseguibili. Il successo o il fallimento dei due test viene determinato dal return code dei due sottoprogrammi. 3.2.14 CPU Load Il programma di test CPU Load ha il compito di caricare la CPU di operazioni (creando continuamente nuovi thread in modo da saturare la capacità di calcolo del processore) mediante la funzione void CPULoad(int iHours, int iMins, int iSec) che può essere utilizzata per uno stress del processore dalla durata anche di ore. Il ciclo di operazioni, che rappresenta quindi il nucleo del programma stesso è: double d1 = 2.1; double d2 = 3.3; for (; msElapsed <= msTarget;) {auto b = async(add_async, d1, 0.0001); auto c = async(add_async, d2, 0.0001); auto a = async(multiply, d1, d2); timeElapsed = clock() - start; 38 msElapsed = timeElapsed / CLOCKS_PER_MS;} in cui dopo aver dichiarato due variabili double (d1,d2), eseguiamo un ciclo che termina solo dopo un intervallo di tempo (msTarget esprime i millisecondi del tempo di esecuzione desiderato, msElapsed del tempo effettivamente trascorso). In tale ciclo calcoliamo a,b,c ottenuti mediante le operazioni add_async e multiply (tali funzioni sono definite nello stesso sorgente, ma si limitano a moltiplicare o aggiungere e restituirne il valore), e calcoliamo il tempo trascorso dall’inizio del ciclo in termini di clock e ricaviamo i millisecondi trascorsi dividendo i clock per il numero di clock per millisecondi. Poiché ogni iterazione del ciclo confronta msElapsed con msTarget, al raggiungimento del numero di millisecondi desiderato, si esce dal ciclo. 39 Conclusioni Dopo aver introdotto nel primo capitolo le caratteristiche di un sistema safety critical, aver individuato nel secondo la componente più importante del controllo digitale nel processore nel secondo, aver indicato nello stesso capitolo metodologie storiche, attuali e sperimentali di testing, e aver condotto uno studio della suite di test Intel® Processor Diagnostic Tool nel terzo, abbiamo a disposizione abbastanza informazioni per giungere a delle conclusioni sul testing dei processori superscalari particolarmente importanti nel caso vengano utilizzati per sistemi safety critical, ma che possono, tenendo conto dei requisiti meno stringenti, essere allargate anche a casi più “tradizionali”. 4.1 Difetti di Intel® Processor Diagnostic Tool Lo studio del codice sorgente di Intel® Processor Diagnostic Tool ci ha permesso non solo di capirne il funzionamento, ma anche di scoprirne alcuni difetti. Alcuni di questi difetti sono di tipo concettuali, legati quindi al test in sé e non alla modalità di implementazione e vengono elencati di seguito. Parametri significativi vengono tralasciati. Nel caso di Test Genuine Intel il contenuto del registro EAX viene inspiegabilmente ignorato: infatti se è sicuramente utile verificare l’autenticità del processore utilizzato, tramite questo test non si viene a conoscenza della generazione del processore, ottenibile consultato proprio il contenuto di EAX dopo l’esecuzione di CPUID. Basterebbe infatti controllare EAX per avere conferma se il nostro processore, ad esempio modello i5, appartiene alla sesta generazione (Skylake) o alla quinta (Ivy Bridge) e così via, in quanto in EAX, dopo l’esecuzione di CPUID, viene memorizzata una stringa univoca per generazione. La generazione è sì parte della stringa che otteniamo con il test Band String, ma quest’ultima è così piena di informazioni che richiede 40 l’intervento umano per la consultazione e una buona conoscenza delle generazioni di Intel (associazione nome-sarebbe automatizzabile: spesso è più facile avere errori sulla generazione del processore, che non dell’autenticità dello stesso. Alcuni test possono essere sostituiti dall’utilizzo di dispositivi elettronici. La temperatura del processore è un aspetto importante per un sistema safety-critical. Delegare al processore stesso tale misura oltre a farci perdere preziosi cicli di clock, nel caso in cui siamo in una situazione di errore della CPU potrebbe anche darci misurazioni sbagliate (ad esempio potrebbe darci come valida una temperatura al di sopra di quella massima a cui dovrebbe operare): inoltre sensori che misurano la temperatura in maniera autonoma sono particolarmente economici e solleverebbero il processore dall’onere, permettendo di risparmiare non solo cicli di clock, ma anche di avere misure più affidabili e indipendenti dallo stato del processore stesso. È evidente che in un sistema safety-critical la temperatura del processore sarà valutata da un sensore e non da un programma in esecuzione sul processore. Controlli blandi. Il Test BrandString non risulta particolarmente utile per un sistema safety-critical, se non una volta sola, ossia alla consegna delle board, per verificarne la presenza del processore atteso. Successivamente, essendosi accertati della corrispondenza del processore con il modello desiderato, tale test non sarà particolarmente significativo, in quanto se anche la Brand String risultasse corrotta, sarebbe, molto probabilmente, indice di uno stato di errore della CPU, che avrebbe già mostrato in altre situazioni. Inoltre, visto che il test riguarda solo la correttezza della stringa, e non la corrispondenza di tutti i parametri con i possibili modelli della Intel, risulterebbe comunque più utile il test GenuineIntel modificato in maniera tale da introdurre il test riguardo alla generazione (proprio perché Brand String si limita a valutare la correttezza sintattica). In base a quanto detto la scelta più conveniente sarebbe rimodellare Brand String in modo da confrontare una serie di parametri prelevati dalla CPU con quelli attesi, in modo da verificare la completa aderenza del modello testato con quello atteso. Per la complessità dei processori superscalari, alcuni test sono diventati meno significativi che in passato. La frequenza a cui un processore opera è uno degli aspetti che 41 è diventato più complesso all’evolversi dei processori. Basti pensare come ogni processore sia composto da più core, e come nello stesso momento un core possa essere attivo e gli altri spenti, e di come quindi il concetto di frequenza del processore sia diventato meno importante che in passato (infatti se il programma è multithreaded, le prestazioni del singolo thread sono influenzati dallo stato attuale del core su cui effettivamente sarà eseguito). Inoltre la frequenza della CPU è un limite massimo teorico, in quanto se il nostro intento è misurare le prestazioni a cui un sistema informatico può eseguire un programma, bisogna tener conto del fatto che gli altri componenti operano ad una frequenza ben minore della CPU, e operazioni come quelle sulla memoria di massa sono particolarmente lente. Per tale motivo, il test “CPUFrequency” risulta essere utile solo per una verifica iniziale della frequenza del processore, per verificarne in maniera veloce la corrispondenza con i valori dichiarati. Nel caso in cui vogliamo effettivamente verificare le prestazioni del nostro sistema, allora sarebbe ben più utile sviluppare i programmi facendo uso di Intel® VTune™ Amplifier, un tool di sviluppo sempre di Intel, che permette di raccogliere dati precisi sulla prestazione del codice in termini di utilizzo di CPU, FPU, GPU, RAM e altri componenti, con un overhead relativamente basso. Il caso peggiore viene mancato e i casi analizzati non risultano essere particolarmente significativi. Il test della FPU ricava correttamente i megaflops del processore (misurandoli, senza limitarsi a presentarne il valore nominale). Tuttavia probabilmente il test più significativo che potrebbe essere eseguito nell’ambito dei calcoli a virgola mobile è quello della divisione per un numero prossimo allo 0, con un numero di cifre decimali crescente, in modo da verificare non solo il numero massimo di cifre decimali che il processore è in grado di gestire, ma anche per assicurarci che per numeri a risoluzione minore non si incorra in un’errata approssimazione a 0. Questa considerazione è giustificata dal fatto che mentre una operazione errata su un numero a virgola mobile può produrre un output inesatto dalle conseguente più o meno importanti, una divisione per zero risulta essere una condizione critica della CPU, che sicuramente porterà al fallimento del sistema: avere una certezza di non dividere per 0 operando su numeri molto piccoli ci permette di operare con linguaggi di programmazione ad alto livello senza preoccuparci che errori di 42 questo tipo siano dovuti al processore in sé. Infine si potrebbe pensare ad un test che esegua tutte o la maggior parte delle operazioni da usare in virgola mobile, mentre sul test proposto da Intel si fa uso di somma algebrica e di moltiplicazione. Presunzione, senza alcuna verifica, della correttezza di alcune operazioni. Nel test di generazione dei numeri primi se ne controllano solo 10000 e nel caso in cui non vi sono errori in quei diecimila, si considerano come correttamente generati tutti i numeri prima (circa seicentocinquantamila nel caso di default): chiaramente in un test ci aspetteremmo un controllo su ogni numero generato, il che rallenterebbe un po’ la procedura, ma ci darebbe una garanzia assoluta sulla capacità di generare numeri primi (per non far pesare troppo l’intervallo di verifica da quello di generazione, potremmo prima generare i numeri, fermare il conteggio, verificarli e poi ottenere il numero di operazioni con il conteggio bloccato all’ultimo istante di generazione). Valutazione di parametri puramente nominali della componente in analisi senza alcun test funzionale. Il test relativo alla cache è probabilmente quello il cui nome può maggiormente trarre in inganno, in quanto la cache non viene in realtà mai testata in termini di affidabilità o di latenza, ma ne vengono solo riportate le dimensioni. Proprio per la cache, essendo un componente del processore (e non una sua funzionalità come un set di istruzioni), sarebbe necessario un test che metta effettivamente in esercizio la cache effettuando trasferimenti di dati. Per tali motivi, per un sistema safety-critical la cache andrà testata in altro modo, ad esempio utilizzando una delle tecniche descritte nel capitolo 2. Il test IGD ha il problema principale di limitarsi a verificare la presenza dei driver grafici Intel attraverso il prelievo di parametri della macchina su cui viene eseguito e il confronto con configurazioni standard, senza però verificarne il corretto funzionamento: potrebbe essere possibile che seppur i driver siano nominalmente supportati, per qualche motivo non funzionino o lo facciano solo in parte (banalmente, potrebbe succedere che delle funzionalità introdotte con driver recenti non siano supportati su versioni precedenti) : informazioni di questo tipo possono essere molto utili. Alcuni parametri non possono essere definiti in maniera assoluta, ma vanno testati eseguendo l’applicazione target. Il test IMC è completo nel valutare la memoria del 43 sistema che lo esegue, mentre i tre test funzionali su di essa possono darci informazioni sul corretto funzionamento di una parte della stessa, ma non ci assicurano la piena operatività di un programma in esecuzione sulla CPU nel caso questo vada a saturare o quasi la RAM: tale test sarebbe più utile ristrutturato per conoscere il comportamento dell’applicazione nel caso peggiore, e può essere facilmente implementato. Così come il programma CPULoad è completo nel valutare, anche se attraverso operazioni ripetitive e semplici, il corretto comportamento del processore in condizioni di carico massimo. Tuttavia bisogna avere sempre ben presente che un test del genere può non darci in maniera completa informazioni sul comportamento del processore nel generico caso di carico massimo: ad esempio potremmo avere errori se il processore è sovraccaricato con istruzioni particolari o con una determinata combinazione delle stesse, o ancora potremmo avere problemi a più alto livello in termini di tempi o correttezza di risultati dovuti a un eccessivo carico della CPU. Per avere una copertura significativa del comportamento in caso di carico notevole sul processore si consiglia quindi di verificare il funzionamento del programma nel caso di input particolarmente complessi e condizioni di funzionamento il più possibile complicate, essendo quindi sicuri di rientrare nel worst-case per fornire una stima del tempo di esecuzione e soprattutto di verificare il corretto funzionamento del programma. Terminano qui i difetti “concettuali” legati a Intel® Diagnostic Tool: ne sono stati individuati ulteriori, legati però alla modalità di implementazione. Non viene fornito il codice sorgente effettivo del test. GFX ha praticamente la totalità della logica nei due eseguibili di cui non viene fornito il codice sorgente. Per tale motivo, ciò che viene distribuito è sostanzialmente un semplice main che lancia gli eseguibili e ne preleva i codici di ritorno, senza alcuna caratteristica o funzionalità che possa essere in qualche modo legata alla grafica; pertanto, la componente “in chiaro” non risulta essere di nessun interesse. Nell’ambito del testing di processori per sistemi di qualsiasi tipo (non solo safety-critical quindi), ci si aspetta il rilascio dei sorgenti completo del codice sorgente. Hard-coding dei parametri. Nel test di generazione dei numeri primi, il parametro che rappresenta i numeri presi in considerazione (primi e non), e che quindi indica anche la dimensione del vettore su cui si opera, è indicato dalla variable “sievesize”, di default 44 uguale a 10000000. Nella prima parte del codice si opera correttamente attraverso la variabile stessa, ma quando si genera un numero casuale, la divisione in modulo non viene fatta utilizzando sievesize, ma attraverso il suo valore di default. I potenziali effetti negativi di una riduzione della variabile sievesize congiuntamente all’hard-coding (utilizzare un valore costante al posto della variabile) sono stati già discussi nel capitolo dedicato al test Prime number generation (accesso ad una locazione di memoria non riservata al vettore). Funzioni precedentemente definite non vengono utilizzate, ma ne viene copiato il codice. Ciò è stato riscontrato nel corpo della funzione di verifica al supporto software ad AVX , cioè bool DetectSWFeature(unsigned int feature). Come detto tale metodo controlla inizialmente il supporto hardware a tale set di istruzioni e poi procede alla valutazione. Tuttavia, invece di chiamare la procedura deputata alla verifica del flag indicante il supporto hardware come ci aspetteremmo, ossia DetectHWFeature (AVXHWFlag), viene ripetuto il codice di tale funzione all’interno della precedente. Questo approccio anti-modulare avrà come sgradevole side-effect che eventuali modifiche al corpo di quest’ultima funzione non avranno conseguenze sulla prima, a meno di cambiamenti del codice. Ciò è evidentemente antinomico rispetto alle corrette tecniche di programmazione. Non è definita un’unica libreria a cui i tutti sorgenti di test possano fare riferimento, ma ogni programma ridefinisce tutte le funzioni utilizzate. Ciò è vero sia per alcune funzioni di utilità, come GetProcFamilyModel, dichiarato e definito sia in IMC che in IGD che per interi test. Infatti si nota una ripetizione del test Brand String all’interno di IMC, che non viene quindi importato da una libreria di Brand String: anche in questo caso ci troviamo di fronte al problema che se modificassimo il codice di quest’ultimo test non avremmo il cambiamento desiderato anche in IMC. Sarebbe più opportuno definirne un header e un sorgente unico e poi farne riferimento nei singoli test, permettendo che i codici che la usano siano allineati ai suoi cambiamenti. Incertezza sulle convenzioni di naming di funzioni e variabili. È un problema generale, ma si nota in particola in CPULoad, infatti i nomi delle funzioni siano assegnati con delle 45 convenzioni talvolta rispettate (add_async) e talvolta ignorate (multiply, senza quindi async), generando confusione nel momento in cui si voglia far uso dei metodi dichiarati e definiti nella suite Intel Diagnostic Tools: per un’azienda così importante è singolare come non si possa avere una certezza sulle convenzioni utilizzate per descrivere funzioni. Tale incertezza si è manifestata un po’ in tutti i programmi della suite, anche sui nomi assegnati ai metodi che contengono gran parte della logica di test : in alcuni casi abbiamo avuto nomi del tipo RunTest(), in altri casi Test(), il che costringe a verificare manualmente, di volta in volta, il nome assegnato all’effettiva funzione di test. A questi fattori se ne aggiungono altri indiretti, relativi alla documentazione, scarna e che indica solo gli obiettivi dei test, senza nessun indizio su come operino, talvolta afflitta da errori tipografici [25] o evidentemente localizzati mediante l’uso di un traduttore automatico [26]. Intel® Processor Diagnostic Tool è, almeno allo stato attuale, una suite non adatta al testing di processori per sistemi safety-critical, poiché la gran parte dei test può essere utile per un processore in esecuzione su un Personal Computer, per utenti che vogliono verificare in modo rapido se i problemi che si stanno verificando sul computer siano direttamente imputabili al processore. Come visto, gran parte delle informazioni sulla CPU necessarie per sistemi safety-critical talvolta non sono raccolte oppure lo sono in maniera grossolana. La ristrutturazione di Intel® Processor Diagnostic Tool per sistemi critici può avvenire seguendo l’analisi compiuta in questo testo, ma sicuramente non può essere completa senza l’intervento e la collaborazione di Intel. 4.2 Elementi di resistenza a un testing efficace Alcuni difetti strutturali di Diagnostic Tool non sono però casuali, e rientrano in una problematica più ampia del testing in generale, soprattutto per i processori. Diverse ragioni fanno sì che il produttore non possa darci tutte le informazioni necessarie a progettare test che si avvicinino all’efficacia massima. Timore di rivelare la struttura del prodotto. Informazioni troppo dettagliate sulla struttura di un dispositivo elettronico la rendono replicabile da aziende concorrenti. Ecco 46 perché i diagrammi dei data sheet si fermano ad un livello di dettaglio che è un compromesso tra utilità e segretezza, ed inoltre si concentrano solo su aree ritenute importanti per l’utilizzo (mentre il testing richiederebbe diagrammi dettagliati dell’intero dispositivo). Volontà di non rendere pubbliche strategie e algoritmi di basso livello. Il test della cache si limita a valutare la dimensione della stessa proprio per questo: infatti se il programma di test andasse a spostare effettivamente dati sulla cache, dal codice sorgente sarebbe possibile ricavare anche solo parte dell’algoritmo di replacement, che rappresenta una parte importante del funzionamento del processore. Questo è il motivo per cui i reference manual impongono delle pratiche di programmazione senza spiegarne la motivazione, nascondendo meccanismi interni di più basso livello. A onor di cronaca, tale tecnica viene applicata da tutti i produttori su qualunque modello: i dispositivi Intel non risultano essere un’eccezione. Considerazioni di questo tipo sono valide anche per board molto economiche, come la STMicroelectronics STM32F3, dal costo di 12€ e dotata del processore a 32 bit ARM® Cortex®-M4 dalla frequenza massima di 72 MHz: nel reference manual, ad esempio viene indicato che nell’utilizzare il convertitore analogico-digitale una coppia di bit non può variare contemporaneamente [27], ma deve attraversare uno stadio di transizione; ciò nasconde meccanismi presenti a più basso livello di cui non si vuole diffondere i dettagli. Costi del testing. Il testing è un’attività molto costosa e lo diventa maggiormente all’aumentare del grado di sicurezza che si vuole raggiungere, che nel caso dei sistemi safety-critical è la massima possibile. Considerando il gran numero di processori che viene rilasciato anche solo in un anno, le stringenti richieste del time-to-market e la concorrenza, è impossibile applicare test così costosi su tutti i processori. Poca remuneratività dei sistemi safety-critical. Anche se le tecniche di testing fossero applicate con successo, i ricavi risultanti sarebbero ben inferiori rispetto a quelli di altri settori. Un processore di successo nell’ambito dei sistemi safety-critical tipicamente raggiunge una vendita di decine di migliaia di unità, mentre uno presente in uno smartphone sarà venduto in milioni di esemplari [14]. Ecco perché un produttore tenderà a investire di 47 più, ad esempio, nel settore mobile che non in quello dei sistemi critici. Alcuni difetti individuati in Diagnostic Tool e nella sua documentazione possono essere ascritti alla volontà di risparmiare il più possibile anche solo in termini di tempo impiegato alla stesura del codice. 4.3 Soluzioni e possibili sviluppi futuri Nonostante le criticità illustrate precedentemente, il problema del testing dei processori per sistemi safety-critical può comunque essere risolto, anche nel caso più complesso di CPU superscalari, in modo da garantire una copertura da fallimenti ragionevolmente elevata. Per raggiungere questo obiettivo, sono necessari interventi sia da parte del produttore che degli enti regolatori. Si avverte l’esigenza dell’azione di questi ultimi dal fatto che nessuno degli standard di larga diffusione prevede un qualsiasi tipo di certificazione o verifica delle proprietà del processore. La necessità di una verifica accurata del corretto funzionamento della CPU nei sistemi critici non è assolutamente allarmistica né strumentale a questo testo: basti pensare a quanto successo con la Floating Point Unit dei primi processori Pentium. Un difetto nella lookup table per calcoli a virgola mobile, conosciuto da Intel sin da maggio del 1994 (ma l’azienda decise di non diffondere la notizia), faceva sì che alcune operazioni a virgola mobile producessero risultati errati. Tale difetto divenne di dominio pubblico nell’ottobre dello stesso anno, ma solo su segnalazione di Thomas Nicely [28], professore universitario di Matematica, che si accorse di errori nei calcoli per individuare numeri primi, che si verificavano solo sui nuovi computer con processori Pentium. Intel si difese sostenendo che un utente normale avrebbe subito un errore di questo tipo ogni 27000 anni, ma IBM (azienda concorrente nella produzione di chip) rispose che il tasso di fallimento era 1 ogni 24 giorni; ulteriori test condotti eseguendo operazioni spesso utilizzate da utenti comuni si avvicinarono maggiormente a quest’ultima stima, 1 ogni 44 giorni [29]. Alla fine Intel fu costretta a sostituire i processori difettosi, e anche se solo parte degli acquirenti esercitarono questa opzione, la perdita fu di circa 475 milioni di dollari. La stessa architettura soffrì poi di un bug, dovuto ad un difetto di progettazione, noto come “F00F bug” [30], che alla fine fu risolto mediante accorgimenti a livello di sistema operativo. È 48 evidente che se questi errori si fossero verificati su un sistema safety-critical, le conseguenze sarebbero potute essere devastanti, considerando anche il fatto che il processore era stato immesso sul mercato da più di un anno (Marzo 1993), e c’era quindi la possibilità che fosse installato su un grande numero di macchine. Appare evidente come il primo passo per risolvere questo problema sia la creazione di standard per processori utilizzati nei sistemi critici. Un possibile punto di partenza potrebbe essere la certificazione proposta in [14], formalizzata mediante Goal Structuring Notation. Tale proposta mira a determinare l’affidabilità di un processore, partendo da affermazioni di alto livello, che vengono via via scomposte in proprietà più semplici che possono essere verificate con test, ad esempio usando un misto tra il testing proposto nel capitolo 2 (per le componenti del processore) e Diagnostic Tool modificato, in modo da avere anche una verifica funzionale della CPU. Il produttore potrebbe poi condurre in proprio gran parte dei test, diffondendo all’ente preposto solo un ristretto sottoinsieme di informazioni. Come abbiamo visto, il produttore non vuole diffondere informazioni considerate strategiche; così facendo diminuisce la copertura dei fallimenti ottenibile da una compagnia di test esterna. Considerato ciò, potrebbe essere una soluzione efficace quella di assegnare al produttore stesso il compito del testing del processore, in modo da diffondere all’esterno il minor numero di informazioni, e comunicare i risultati ottenuti all’autorità competente. Si potrebbe anche pensare di dividere il testing tra produttore e una compagnia esterna di testing e rating, in modo che il primo si limiti a testare solamente ciò che implica informazioni riservate (ad esempio test sulla cache), e la seconda tutti i restanti test (ad esempio operazioni floating point, generazione e verifica numeri primi, test di set di istruzioni…). Sarebbe opportuno poi che solo un ridotto e ben definito sottoinsieme di processori sia utilizzato nelle board. Questo perché esistono un gran numero di varianti dello stesso processore, ciascuna delle quali differisce per algoritmi di basso livello, architettura o prestazioni. Tuttavia anche elementi di questo tipo possono introdurre incertezze o nuovi errori. Inoltre il testing accurato di una CPU richiede tempo e risorse, e quindi non sarebbe conveniente e applicabile l’applicazione di questo a un gran numero di modelli e varianti. 49 Ecco perché, per evitare il caos di testing che si avrebbe con la diffusione di board con CPU simili ma diverse in alcune caratteristiche, sarebbe opportuno mantenerne ben controllato il numero e la tipologia. 50 Bibliografia [1] M. Hinchey, L. Coyle, Evolving Critical Systems: a Research Agenda for Computer- Based Systems, 2010 17th IEEE International Conference and Workshops on Engineering of Computer-Based Systems, 2, 22/03/2010 [2] I. Sommerville, Software Engineering, Pearson, 2015 [3] L. Rierson, Developing Safety-Critical Software: A Practical Guide for Aviation Software and DO-178C Compliance, CRC Press, 2013 [4] J. Boulanger, CENELEC 50128 and IEC 62279 Standards, John Wiley & Sons, 2015 [5] IEEE GlobalSpec, http://standards.globalspec.com/std/1272146/cenelec-en-50126-1, 23/12/2016 [6] British Standards Institution, The specification and demonstration of Reliability, Availability, Maintainability and Safety (RAMS), BSI, 2007 [7] IEEE GlobalSpec, http://standards.globalspec.com/std/1678027/cenelec-en-50128, 23/12/2016 [8] J. Rodriguez, 20th International Conference on Reliable Software Technologies - Ada-Europe 2015, 2015 [9] IEEE GlobalSpec, http://standards.globalspec.com/std/1266373/cenelec-en-50129, 23/12/2016 [10] IEEE GlobalSpec, http://standards.globalspec.com/std/1272813/cenelec-en-50155, 23/12/2016 [11] IEEE GlobalSpec, http://standards.globalspec.com/std/1285055/cenelec-en-50159, 23/12/2016 [12] IEEE GlobalSpec, http://standards.globalspec.com/std/1271136/cenelec-en-50159-1, 23/12/2016 51 [13] N. Kranitis, A. Paschalis D. Gizopoulos e Y. Zorian, Effective Software Self-Test Methodology for Processor Cores, Proceedings of the 2002 Design, Automation and Test in Europe Conference and Exhibition, 592, 04/03/2002 [14] I.Bate, P. Conmy, T.Kelly e J.McDermid, Use of Modern Processors in Safety-Critical Applications, The Computer Journal, 44, 531-543, 13/04/2001 [15] I. Moir, A. Seabridge e M. Jukes, Civil Avionics Systems, John Wiley & Sons, 2013 [16] Ministero delle Infrastrutture e dei Trasporti, http://www.mit.gov.it/mit/mop_all.php?p_id=05338 , 20/12/2016 [17] L. Chen, S. Dey, P. Sanchez, K. Sekar e Y. Chen, Embedded hardware and software self-testing methodologies for processor cores, Design Automation Conference, 2000. Proceedings 2000, 625, 05/06/2000 [18] C. Stroud, A Designer’s Guide to Built-In Self-Test, Springer, 2002 [19] Intel, https://downloadcenter.intel.com/it/download/19792/Intel-Processor- Diagnostic-Tool, 08/12/2016 [20] Intel, Intel® Processor Identification and the CPUID Instruction Application Note 485, Agosto 2009 [21] Intel, https://communities.intel.com/thread/43687, 17/12/2016 [22] Microsoft Developer Network, https://msdn.microsoft.com/it- it/library/windows/desktop/ms683212(v=vs.85).aspx, 15/12/2016 [23] Microsoft Developer Network, https://msdn.microsoft.com/it- it/library/windows/desktop/aa363216(v=vs.85).aspx , 15/12/2016 [24] The Linux Documentation Project, http://tldp.org/LDP/tlk/dd/pci.html, 15/12/2016 [25] Intel, The Intel® Processor Diagnostic Tool – Help Documentation, 27, 2015 [26] Intel, http://www.intel.it/content/www/it/it/support/processors/000005597.html, 19/12/2016 [27] STMicroelectronics, RM0316 Reference manual, Marzo 2013 [28] Some Results of Research in Computational Number Theory, http://www.trnicely.net/pentbug/pentbug.html, 23/12/2016 [29] Los Angeles Times, http://articles.latimes.com/1994-12-17/business/fi52 9948_1_error-rate , 23/12/2016 [30] Dartmouth College, http://www.cs.dartmouth.edu/~sergey/cs108/2009/F00FBug.html, 23/12/2016 53