Tecniche e strumenti di testing per processori superscalari

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