...

di stefano_ TECNICHE_numeri casuali

by user

on
Category: Documents
7

views

Report

Comments

Transcript

di stefano_ TECNICHE_numeri casuali
TECNICHE
Tecniche di Generazione
di Numeri Casuali
di Antonio Di Stefano
La generazione di numeri casuali è un argomento spesso trascurato e poco conosciuto, ma di
grande importanza pratica. In questo articolo verranno presentate alcune delle più comuni tecniche
per la generazione di numeri casuali, che per la loro semplicità si prestano bene ad essere implementazione in software su microcontrollore o in hardware tramite logiche programmabili.
INTRODUZIONE
I recenti dispositivi utilizzati nel campo dell’elettronica
embedded offrono capacità di elaborazione e risorse
sempre maggiori, a prezzi sempre più bassi. Questo
rende possibile eseguire calcoli ed algoritmi notevolmente complessi, in molti casi impensabili fino a poco
tempo fa. Però a fronte di queste capacità esiste un
compito che risulta ancora piuttosto difficile da implementare in maniera ottimale: la generazione di
numeri casuali di buona qualità. Può sembrare strano,
ma una cosa che appare banale, in realtà risulta
ancora tutt’altro che semplice! Il motivo principale è
che il concetto di casualità è l’opposto di quello di
determinismo e sequenzialità tipico dei programmi o
dei circuiti logici, e perfino il “disordine” tipico del
mondo reale è troppo ordinato per risultare veramente casuale! Può un programma o un circuito
logico generare dei numeri casuali? E se può farlo,
con quali limitazioni? A queste domande verrà fornita
una risposta nei seguenti paragrafi.
Si può notare da subito che la disponibilità di numeri
casuali è un requisito necessario in molte applicazioni.
In alcuni casi la qualità stessa dell’applicazione finale
dipende strettamente dalla possibilità di generare
numeri casuali di buona qualità, si pensi ad esempio
ad applicazioni quali generazione di effetti visivi o
sonori, giochi, crittografia, elaborazione dei segnali,
telecomunicazioni, simulazioni, ottimizzazioni, etc.
Prima di passare alla descrizione delle tecniche e dei
metodi usati per la generazione di numeri casuali, è
il caso di soffermarsi sulla loro caratterizzazione, per
poter meglio comprendere i vantaggi, i limiti e le
prestazioni delle tecniche presentate.
CARATTERIZZAZIONE DEI NUMERI
CASUALI
Non esiste una definizione unica e generale di numero
casuale, essa infatti dipende spesso dal contesto. Il
concetto stesso di numero casuale non è assoluto, in
quanto un numero o una sequenza di numeri possono
essere casuali per un osservatore, ma non esserlo per
un altro (che conosce la legge con cui essi vengono
generati). Per semplicità di seguito indicheremo con
2
FIRMWARE - NOVEMBRE 2006
“numero casuale” un numero, selezionato grazie ad
un processo aleatorio, da un insieme finito di numeri.
Con questa definizione abbiamo spostato il concetto
di casualità dal numero (o dalla sequenza di numeri)
al processo di selezione. Questo particolare risulterà
utile in seguito. Va notato che nella precedente definizione avremmo potuto sostituire il termine “numero” con “simbolo” o con “cifra”. In molti casi infatti
il problema della generazione di numeri casuali,
viene ricondotto alla generazione casuale di una
sequenza di 0 e di 1, da cui si possono ricavare
numeri in qualsiasi formato (interi, con segno in
complemento a due, fixed-point, floating-point,
etc.) o stringhe di lunghezza arbitraria.
E’ possibile caratterizzare un processo aleatorio da
diversi punti di vista. Una delle caratteristiche più
importanti è la sua distribuzione di probabilità. Questa
fornisce informazioni su qual è la probabilità che ciascun elemento dell’insieme venga selezionato. In
molti casi si considerano e si utilizzano dei processi
caratterizzati da una distribuzione uniforme. Questo
significa che ogni elemento ha la stessa probabilità
degli altri di essere selezionato (asintoticamente, cioè
se si suppone di condurre un numero infinito di estrazioni). Se si rappresentano su un grafico gli elementi e
la loro rispettiva probabilità di essere estratti, si ottiene
un grafico “rettangolare” (Figura 1a). Dal momento
che la probabilità viene espressa come un numero
reale compreso tra 0 ed 1, in cui 0 rappresenta
l’evento impossibile, ed 1 quello certo, in una distribuzione uniforme ciascun elemento avrà una probabilità
1/N di essere selezionato, dove N è il numero di elementi (la somma di tutte le probabilità deve fare 1, dal
momento che in un’estrazione almeno uno degli elementi viene scelto di sicuro). La distribuzione uniforme
è tipica dei processi aleatori “artificiali” come lancio di
dadi, lotterie, roulette, ed è anche la più utilizzata in
pratica nella applicazioni.
Un’altra distribuzione di probabilità molto comune è
quella gaussiana o “normale”, dalla tipica forma a
campana (Figura 1b). In questo caso i valori più piccoli (o comunque più vicini al centro della curva)
hanno maggiore probabilità di essere estratti rispetto
▲ Figura 1
Distribuzione di probabilità uniforme (a)
e gaussiana (b)
GENERAZIONE DI NUMERI
PSEUDO-CASUALI
Come è facile immaginare, non è possibile generare
delle sequenze casuali vere utilizzando degli algoritmi deterministici (implementati via software o tramite circuiti logici), si possono al massimo generare
delle sequenze “pseudo-casuali”, cioè sequenze
apparentemente random, ma che in realtà sono
perfettamente prevedibili, e che possono anche
ripetersi dopo un certo numero di estrazioni. Di solito
in questi casi si parte da un valore iniziale detto
“seme”, che determina interamente la sequenza
che verrà estratta di seguito. In molti casi il seme è
scelto con un meccanismo casuale esterno, e quindi
la sequenza risulta relativamente imprevedibile.
Questo tipo di generatori ha due grossi vantaggi: 1)
la qualità della distribuzione ottenuta è molto
buona, di solito perfettamente uniforme; 2) è possibile ottenere una sequenza identica impostando lo
stesso valore di seme. Questo risulta utile in alcune
applicazioni e consente di ottenere un maggiore
controllo dei programmi e degli algoritmi eseguiti.
Esistono moltissime tecniche per generare numeri
pseudo-casuali con distribuzioni uniformi, alcune di
esse sono anche molto antiche. Il problema è che la
maggior parte di queste tecniche non si presta bene
ad essere implementata in software o in hardware
su sistemi embedded, in quanto richiede operazioni
abbastanza complesse (radici quadrate, divisioni,
moltiplicazioni, etc.) per l’estrazione di ogni singolo
numero, e può risultare quindi piuttosto lenta.
Invece una delle tecniche più semplici, ma al tempo
stesso più efficienti ed utilizzate per generare numeri
pseudo-casuali fa uso dei “registri a scorrimento con
retroazione lineare” (LFSR, Linear Feedback Shift
Registers). Si tratta in pratica di normali registri a
scorrimento, in cui lo stato futuro è dato da una
combinazione lineare dei bit dello stato presente. La
combinazione lineare è ottenuta con l’operatore
XOR o XNOR. La Figura 2 mostra la struttura di un
LFSR a 8 bit realizzato con XOR: lo scorrimento
avviene verso destra, e ad ogni aggiornamento il bit
più significativo riceve la combinazione lineare dei
bit presenti in alcune posizioni. Queste posizioni
particolari si chiamano “tap”, e devono essere scelte
con cura, come spiegato nella figura 2.
Il funzionamento del circuito è il seguente: si imposta un valore iniziale (diverso da 0), che costituisce
il seme e si aggiorna il registro (facendo scorrere i bit
di una posizione) ogni volta che si vuole ottenere un
nuovo numero casuale. Il circuito funziona praticamente come un contatore, fornendo sempre la stessa
sequenza di valori pseudo-casuali, che inizia dal
▲ Figura 2
TECNICHE
a quelli più grandi (distanti dal centro). La distribuzione gaussiana è importante perché è tipica dei processi naturali (es. rumore di molti componenti elettronici, o di molti canali di telecomunicazione, errori
nelle misure e conversioni A/D e D/A, etc.), e viene
quindi spesso utilizzata per simulare questi ultimi (per
esempio nel campo delle telecomunicazioni o dell’elaborazione dei segnali).
Un’altra importante caratteristica di una sequenza
casuale è l’indipendenza statistica dei suoi termini, cioè
la misura di quanto l’estrazione di un numero dipende
da quello o quelli estratti precedentemente.
Chiaramente in una sequenza random ideale gli elementi dovrebbero essere statisticamente indipendenti.
Una misura dell’indipendenza statistica dei vari
numeri generati è ottenibile ad esempio calcolando
l’autocorrelazione o l’entropia (di vario ordine) della
sequenza. Senza entrare nei dettagli matematici, è sufficiente sapere che l’entropia fornisce una misura di
quanto è probabile (o prevedibile) trovare un numero
nella sequenza, o di trovarlo dopo un altro o una certa
stringa di numeri estratti. Più l’entropia è alta, più la
sequenza è imprevedibile, e quindi “casuale” nel senso
comune del termine (anche se contiene più “informazione” secondo Shannon). Purtroppo come vedremo
in pratica ottenere sequenze non prevedibili e con una
buona distribuzione è abbastanza difficile.
Struttura di un LFSR ad 8 bit
FIRMWARE - NOVEMBRE 2006
3
LUNGHEZZA LFSR
POSIZIONE DEI TAP
4
0,1
7
0,1
8
0,2,3,4
11
0,2
12
0,6,8,11
15
0,1
16
0,1,3,12
23
0,5
24
0,1,2,7
28
0,3
31
0,3
32
0,10,30,31
▲ Tabella 1 Posizione dei tap per LFSR di lunghezza diversa
Tecniche di Generazione di Numeri Casuali
▲ Figura 3
4
CPLD, FPGA o addirittura logica discreta. Di seguito
considereremo questi casi singolarmente con riferimento all’LFSR ad 8 bit di Figura 3.
Nel caso di una codifica in C l’unico aspetto particolare da rilevare è che la variabile che memorizza il
contenuto del registro deve essere globale o dichiarata
come statica, in modo da mantenere il suo valore (lo
stato del registro) durante l’esecuzione del programma. Inoltre la variabile dovrà essere dichiarata di tipo
intero unsigned, in modo da evitare inserimenti di
bit di segno nelle operazioni di scorrimento.
Occorrerà inizializzazione il registro con il valore del
seme, e poi richiamare la funzione di aggiornamento ogni volta che occorre un nuovo numero casuale.
Questa funzione può restituire il valore della variabile,
oppure se il registro è stato dichiarato come variabile
globale, non sarà necessario passarlo esplicitamene,
basterà leggerlo direttamente
quando
serve (si risparmia
qualche accesso alla
memoria). Un codice
LFSR “di Galois” ad 8 bit
valore dato come seme. Dopo un certo numero di
iterazioni, la sequenza ricomincia daccapo: la massima lunghezza della sequenza per un LFSR di lunghezza n bit è 2n-1, ma questo solo se i tap sono
stati scelti opportunamente, altrimenti si ottengono
delle sequenze più corte. I numeri ottenuti in questo modo sono apparentemente casuali, ed hanno
una buona distribuzione uniforme. Una particolarità
(a volte utile) è che i numeri estratti non sono mai
ripetuti prima che la sequenza ricominci, a meno di
non cambiare il seme. Le posizioni dei tap da utilizzare per ottenere la sequenza di lunghezza massima si possono ricavare da apposite tabelle, in cui
sono riportati i valori da utilizzare per ogni data
lunghezza dell’LFSR (determinare questi valori analiticamente è piuttosto laborioso). In Tabella 1 sono
riportati alcuni valori comuni (nota: a volte le posizioni dei tap sono numerate da 1 ad n e nel verso
opposto rispetto a quello riportato qui). Una variante
dello schema classico dell’LFSR è quello riportato in
Figura 3, in cui è mostrata la struttura di un “LFSR di
Galois”. La differenza sostanziale è che gli XOR
sono inseriti nel registro, tra un tap e la posizione
successiva, e non nel percorso di feedback. La posizione dei tap è comunque la stessa di prima. Il vantaggio di questa seconda configurazione è che risulta
più facile da implementare via software, perché
richiede mediamente meno istruzioni, e più veloce
via hardware, in quanto i ritardi di propagazione
degli XOR non si sommano.
Implementazione degli LFSR
L’implementazione di un LFSR nella versione “di
Galois” risulta alquanto semplice, sia nella versione
software in C che in assembler, sia in hardware su
FIRMWARE - NOVEMBRE 2006
[Listato 1]
#include <stdio.h>
// Registro
unsigned char LFSR;
// Funzione di aggiornamento
void NextRand(void);
//—————————
main()
{
int i;
// Inizializzazione (seme)
LFSR=0x01;
for(i=0; i<280; i++) {
printf(“%d: %X”, i, LFSR);
NextRand();
}
}
//—————————
void NextRand(void)
{
unsigned char temp;
temp=0;
if (LFSR&0x01) temp=0x8E;
LFSR=LFSR>>1;
LFSR=LFSR^temp;
}
di esempio è riportato nel Listato 1.
Il risultato sarà il seguente:
[Listato 3]
NextRand:
0: 01
1: 8E
2: 47
3: AD
4: D8
5: 6C
6: 36
7: 1B
8: 83
9: CF
10: E9
11: FA
12: 7D
13: B0
14: 58
…
252: 08
253: 04
254: 02
255: 01 <—
256: 8E
257: 47
258: AD
…
Fine
[Listato 2]
NextRand
CLRW
BTFSC LFSR,0
MOVLW 0x8E
BCF STATUS,C
RRF LFSR,F
XORWF LFSR,F
RETURN
in memoria e leggerli con un debugger.
L’implementazione in assembler dell’LFSR è ancora
più semplice ed efficiente che in C. Nei listati 2 e 3
sono riportati i codici relativi ad un PIC e ad un AVR.
L’implementazione hardware dell’LFSR non presenta
particolari problemi e può essere fatta riproducendo
il circuito di Figura 2 o 3 con qualsiasi tecnologia
digitale (porte logiche discrete, CPLD, FPGA, etc.),
usando un registro a scorrimento e degli XOR esterni, o dei flip-flop D e porte XOR dopo i tap. Ci sono
due cose interessanti da notare in questo caso: l’implementazione avrà un’occupazione di area piccolissima (in generale n Flip-Flop D + da 1 a 3 porte XOR)
e potrà funzionare a frequenze di clock molto elevate.
Il codice VHDL adatto all’implementazione su FPGA e
CPLD dell’LFSR ad 8 bit visto prima è presentato nel
listato 4. Il modulo riceve in ingresso un valore ad 8
bit per il seme, un clock, ed un reset (asincrono),
oltre che un segnale (NUOVO), che quando alto abilita lo scorrimento del registro, producendo un
nuovo numero ad ogni fronte di clock. Il codice
implementato su una FPGA Xilinx Spartan-2E (un
dispositivo non recentissimo) ha richiesto soltanto 12
slices ed ha raggiunto velocità di funzionamento di
più di 256MHz!
Generazione di distribuzioni gaussiane
Se si vuole ottenere una distribuzione pseudo-casuale
gaussiana invece che uniforme è possibile utilizzare
diverse tecniche. La migliore (come qualità dei campioni ottenibili) consiste nell’utilizzo della trasformata
di Box-Muller. Grazie a questa relazione matematica
è infatti possibile trasformare una distribuzione uniforme in una gaussiana. Sarebbe sufficiente quindi
applicare tale relazione ai campioni generati con un
LFSR. Purtroppo le equazioni relative alla trasformata
comprendono funzioni difficili da implementare in
maniera efficiente, come seno, coseno, logaritmo e
radice quadrata.
La soluzione più semplice è invece quella di sfruttare
il “Teorema del Limite Centrale”, secondo cui (semplificando) sotto certe condizioni la somma di un
numero elevato di variabili aleatorie statisticamente
indipendenti, tende ad assumere una distribuzione
gaussiana al crescere del numero di variabili sommate
(questo spiega perché molti processi naturali hanno
distribuzioni gaussiane…).
In base a questo teorema, se utilizziamo come campioni la somma di N numeri pseudo-casuali generati
FIRMWARE - NOVEMBRE 2006
Tecniche di Generazione di Numeri Casuali
La funzione che aggiorna il valore del registro è
NextRand. In essa viene testato il bit meno significativo, se questo è uguale a 1 allora gli XOR posti a valle
dei tap produrranno un effetto, inoltre il bit meno
significativo dovrà essere riportato al più significativo.
Se l’LSB è 0 invece ci sarà un semplice shift. Le operazioni di XOR e l’inserimento del bit nell’MSB sono
ottenute facendo un XOR con 0x8E (in binario
10001110). Questa operazione ha l’effetto di invertire
i bit nelle posizioni corrispondenti agli 1 (e quindi
anche aggiungere un 1 all’MSB). Va notato che la
posizione dei tre 1 del nibble meno significativo è
spostata di uno rispetto alla posizione dei tap perché
l’XOR viene fatto dopo lo shift.
Il programma visualizza 280 numeri pseudo-casuali,
permettendo di osservare che dal 255-esimo la
sequenza ricomincia. Se si intende provare il programma su un sistema a microcontrollore privo di un
dispositivo di visualizzazione, è possibile inviare i
numeri ad una UART e visualizzarli su un PC o scriverli
LDI TEMP,0x8E
LSR LFSR
BRCC Fine
EOR LFSR,TEMP
RET
5
[Listato 4]
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity LFSR8 is
Port (SEME
VALORE
NUOVO
CLK
RESET
end LFSR8;
: in std_logic_vector(7 downto 0);
: out std_logic_vector(7 downto 0);
: in std_logic;
: in std_logic;
: in std_logic);
architecture RTL of LFSR8 is
signal LFSR : std_logic_vector(7 downto 0);
begin
process(CLK, RESET, NUOVO, SEME)
begin
Tecniche di Generazione di Numeri Casuali
if RESET=’1’ then
LFSR <= SEME;
elsif (CLK’event and CLK=’1’) then
if NUOVO=’1’ then
— Versione ‘classica’
— LFSR(6 downto 0) <= LFSR(7 downto 1);
— LFSR(7) <= LFSR(0) xor LFSR(2) xor LFSR(3) xor LFSR(4);
6
— Versione
LFSR(7) <=
LFSR(6) <=
LFSR(5) <=
LFSR(4) <=
LFSR(3) <=
LFSR(2) <=
LFSR(1) <=
LFSR(0) <=
end if;
end if;
‘di Galois’
LFSR(0);
LFSR(7);
LFSR(6);
LFSR(5);
LFSR(4) xor LFSR(0);
LFSR(3) xor LFSR(0);
LFSR(2) xor LFSR(0);
LFSR(1);
end process;
VALORE <= LFSR;
end RTL;
utilizzando degli LFSR, otteniamo una distribuzione
che approssima quella gaussiana. In particolare se i
numeri di partenza coprono un intervallo compreso
tra 0 e M, si otterrà una distribuzione (quasi) gaussiana con media c=M/2, e varianza Û2=1/sqr(12/N).
Lo svantaggio di questo approccio è che la distribuzione ottenuta non si estende tra -infinito e +infinito,
ma solo tra 0 ed M. Va notato che non si può utilizzare un solo LFSR per generare i campioni da sommare, perché questi non sarebbero statisticamente
indipendenti! Occorre usare N LFSR separati (anche
della stessa lunghezza) avviati con semi casuali e staFIRMWARE - NOVEMBRE 2006
tisticamente indipendenti.
Il Listato 5 riporta un frammento di codice utilizzabile
per calcolare i campioni (x) di una distribuzione
gaussiana, dati un LFSR ed un array che contiene i
semi (sd[…]), che poi è usato per mantenere lo stato
dei vari registri utilizzati (è un metodo semplice per
implementare N LFSR usando una sola routine di
aggiornamento).
Generatori crittograficamente sicuri
Gli approcci considerati fino ad ora sono in assoluto
i più utili nelle applicazioni comuni, però come
[Listato 5]
for(j=0; j<256; j++)
{
for (i=1; i<N; i++)
{
LFSR=sd[i];
NextRand();
x = x + LFSR;
sd[i]=LFSR;
}
x=x/N;
abbiamo visto le sequenze generate hanno un
discreto livello di correlazione, e quindi sono facilmente prevedibili. Per gli LFSR ad esempio esiste
addirittura un algoritmo esatto per ottenere la struttura del generatore a partire da alcuni campioni raccolti (algoritmo di Berlekamp-Massey)! Questo
rende molti generatori pseudo-casuali non adatti ad
essere impiegati in campo crittografico, dove la
sicurezza è fondamentale. Questa situazione può
essere migliorata, come detto prima variando frequentemente i semi e selezionandoli con un meccanismo casuale autentico (esterno all’algoritmo).
Tra gli algoritmi di generazione pseudo-casuali ritenuti migliori e spesso utilizzati nel campo della crittografia meritano una citazione gli algoritmi
Mersenne Twister, Fortuna, ISAAC e Blum Blum
Shub (BBS). I primi sono un po’ troppo complessi
per essere spiegati dettagliatamente qui (e risultano
piuttosto pensati da implementare su piccoli sistemi
embedded), l’ultimo, che peraltro è uno dei più
usati, è relativamente più semplice. Esso si basa sulla
seguente relazione:
xn+1 = xn2 mod M
GENERAZIONE DI NUMERI CASUALI VERI
Per generare dei numeri casuali veri occorre utilizzare
un processo aleatorio autentico esterno al programma/circuito. Sono state proposte moltissime sorgenti
di casualità: decadimento radioattivo, diversi tipi di
Misure di tempo
Come si può inizializzare un LFSR all’avvio di un’applicazione, in modo che la sequenza non sia sempre
la stessa? Una tecnica semplicissima e abbastanza
efficace consiste nell’utilizzare come seme una misura
di tempo legata a qualche evento esterno. Ad esempio
una tecnica molto utilizzata nel campo dei videogame
consiste nel misurare il tempo che impiega l’utente per
premere il pulsante che avvia la nuova partita. Se il
tempo è misurato ad una velocità molto più alta di
quella tipica degli eventi tenuti sotto controllo, si può
ottenere una buona distribuzione uniforme. Un esempio di codice C per eseguire la misura è riportato nel
listato 6.
Se nell’applicazione non sono previsti pulsanti o altre
forme di interazione con l’utente, oppure se serve
generare spesso nuovi numeri casuali o semi, è possibile utilizzare lo stesso identico metodo, ma applicato alla misura del periodo di un oscillatore esterno.
L’oscillatore deve avere un periodo sufficientemente
grande, ed una pessima stabilità (quindi avere o Q
molto basso o meglio essere di tipo RC), quindi essere
realizzato con componenti molto sensibili alla temperatura ed alla tensione di alimentazione. Il fatto che il
periodo debba essere abbastanza più grande della
frequenza di conteggio è dovuto al fatto che normalmente i bit più significativi della misura saranno sempre
[Listato 6]
//…
unisgned char cont;
//…
// Attesa pressione pulsante
// (su PORTB.0, attivo alto)
cont=0;
while(PORTB&0x01) cont++;
LFSR=cont;
//…
FIRMWARE - NOVEMBRE 2006
Tecniche di Generazione di Numeri Casuali
in cui un nuovo numero è ottenuto calcolando il
quadrato del precedente, modulo M, in cui M è un
numero intero ottenuto come prodotto di due
numeri primi (in genere grandi). Il termine di partenza x0 è chiaramente il seme. Spesso come risultato non viene considerato il numero in se, ma la
sua parità (cioè l’AND bitwise con 1), in modo da
ottenere uno stream pseudo-casuale binario.
L’implementazione di questo algoritmo non è particolarmente difficoltosa, anche se richiede una moltiplicazione (per calcolare il quadrato) ed una divisione (per calcolare il modulo). Forse il problema
più grosso è che la larghezza dei dati può essere
decisamente grande (in alcuni casi anche più di
1024bit!).
fenomeni quantistici, variazione di lunimosità
ambientale o di temperatura, instabilità di oscillatori,
rumore termico, elettromagnetico o acustico, e perfino variazioni nella velocità di rotazione dei dischi
causate da turbolenze locali dell’aria! Per quanto
incredibile comunque, quasi nessuno di questi sistemi
fornisce risultati soddisfacenti: in genere le distribuzioni di probabilità e di potenza spettrale non sono
uniformi (ci sono degli sbilanciamenti a favore di certi
valori), c’è un certo margine di prevedibilità, oppure
la generazione risulta troppo lenta. Per questo motivo
i valori ottenuti con questi metodi devono essere elaborati in qualche modo per potere essere utilizzati in
pratica. Considereremo di seguito alcuni metodi
semplici da implementare, e le tecniche per correggere eventuali asimmetrie nelle distribuzioni.
7
▲ Figura 4
Schema per il calcolo del CRC16
uguali, mentre quelli più bassi varieranno casualmente. Il valore dovrebbe quindi essere ricavato
mettendo assieme un po’ di bit bassi presi da varie
misure (per esempio 4 misure da 2 bit).
Tecniche di Generazione di Numeri Casuali
Misure di tensione
Un’altra fonte di casualità è rappresentata dalle misure
di tensioni esterne, possibilmente soggette a variazioni ambientali o rumore (sensori di temperatura, di
luce, tensione su diodi zener polarizzati inversamente,
etc.). In questo caso occorre utilizzare un convertitore
analogico digitale piuttosto preciso (almeno 10 bit)
e/o un amplificatore esterno ad alto guadagno e
accoppiato in AC. Anche in questo caso una parte
dei bit della misura si manterrà stabile, mentre uno o
due dei bit meno significativi potranno essere utilizzati per “assemblare” un valore casuale (eventualmente da usare come seme).
Quando si utilizza questa tecnica occorre prestare
attenzione al fatto che spesso le oscillazioni di tensione che vengono registrate dall’ADC sono causate da un sorgenti periodiche! Un tipico esempio è
la tensione di rete a 50Hz, che può essere indotta e
misurata facilmente su circuiti ad alta impedenza.
Questi fenomeni possono rendere la sequenza più
prevedibile o sbilanciare la distribuzione verso certi
valori. Per evitare questi problemi è sempre bene
accoppiare questo metodo con l’elaborazioni
descritte nel prossimo paragrafo.
8
Miglioramento e correzione delle statistiche
Tutti i metodi “fisici” descritti fino a qui sono affetti
da qualche inconveniente che ne altera la distribuzione o aumenta correlazione tra valori generati.
Occorre pertanto applicare qualche tipo di elaborazione
per
migliorarne
le
caratteristiche.
Un’elaborazione molto semplice, ed al tempo stesso
efficace per eliminare le asimmetrie nelle distribuzioni (bias o skew), è stata proposta negli anni ‘50
da John Von Neumann. Essa consiste nel considerare lo stream binario generato, raggruppare i dati a
blocchi di due bit, ignorare le sequenze 00 ed 11,
ed associare a quelle 01 e 10 i valori 0 e 1. In questo modo si ottiene un numero di bit inferiore, ma
si può dimostrare che la distribuzione risulta perfettamente riequilibrata. Un’altra tecnica molto comune per eliminare gli squilibri e cancellare gli effetti
di interferenze periodiche o deterministiche è quella
di utilizzare una funzione di “mixing”. Queste funzioni hanno la proprietà di “scombinare” i valori, in
genere spargendoli su un intervallo numerico più
FIRMWARE - NOVEMBRE 2006
grande. In un certo senso gli LFSR alimentati da un
seme casuale costituiscono una funzione di mixing,
così come le funzioni di hash (SHA, MD5, etc.).
Alcune funzioni relativamente semplici da implementare sono gli “scrambler” o le funzioni per il
calcolo del CRC (Cyclic Redundancy Check). Queste
funzioni non sono altro che degli LFSR dotati di un
ingresso per i dati lungo il percorso di reazione.
L’uscita può essere presa ovunque, anche su più bit
in parallelo (questo permette di ricavare un numero
casuale a più bit, fornendo in ingresso solo un bit
alla volta). In Figura 4 è mostrato lo schema del circuito per il calcolo del CRC a 16 bit (lo stesso usato
tra l’altro in molti protocolli di comunicazione).
Come si può vedere la sua struttura è quasi identica
a quella degli LFSR di Galois visti in precedenza (i
tap non sono quelli dei normali LFSR perché la funzione originaria del circuito è quella di eseguire una
divisione di polinomi sul campo di Galois, non
generare
un
conteggio
pseudo-casuale).
L’implementazione di questo circuito è identica a
quella vista in precedenza, l’unica differenza è che
in questo caso le operazione sono su due byte.
Esistono funzioni CRC con lunghezze diverse, che
possono soddisfare diverse esigenze.
CONCLUSIONE
La breve panoramica sulla generazione di numeri
casuali presentata in questo articolo costituisce
un’utile introduzione all’argomento, e permette di
ottenere delle implementazioni funzionanti e di
discreta qualità per molte applicazioni comuni. Data
la vastità dell’argomento però quanto detto non può
certo considerarsi esaustivo, è possibile comunque
trovare maggiori dettagli sull’argomento nei documenti riportati in bibliografia.
Bibliografia
[1] Patrick Billingsley: “Probability and Measure”, John
Wiley and Sons, New York 1995, ISBN 0-47100710-2.
[2] Bryan Hayes: “Randomness as a Resource”,
American Scientist, vol 89, n.4, Luglio/Agosto
2001.
[3] RFC4086: “On Randomness Recommendations for
Security” - http://tools.ietf.org/html/rfc4086.
[4] Xilinx XAPP052: “Efficient Shift Registers, LFSR
Counters, and Long Pseudo-Random Sequenze
Generators” (contiene tabella dei tap per LFSR lunghi da 3 a 168 bit) – http://www.xilinx.com.
Codice MIP 010022
Fly UP