Titolo della Tesi - Università Politecnica delle Marche

Transcript

Titolo della Tesi - Università Politecnica delle Marche
Università Politecnica delle Marche
Scuola di Dottorato di Ricerca in Scienze dell’Ingegneria
Curriculum in Ingegneria elettronica, biomedica e delle telecomunicazioni
----------------------------------------------------------------------------------------
Sviluppo di strumenti di progettazione
e simulazione ad alto
livello di sistemi elettronici:
applicazione a reti wireless
Discussione di:
Cristiano Scavongelli
Tutor accademico:
Prof. Massimo Conti
Coordinatore del curriculum:
Prof. Franco Chiaraluce
XIII ciclo - nuova serie
Università Politecnica delle Marche
Dipartimento di Ingegneria dell’informazione
Via Brecce Bianche — 60131 - Ancona, Italy
Abstract
Negli ultimi anni, le reti wireless sono diventate una realtà onnipresente, affidabile e ben accetta. Questo è particolarmente vero per reti come il Bluetooth e il
Wi-Fi, che oggi si incontrano praticamente ovunque e vengono usate per le applicazioni più disparate. In molti casi, quando si usa un chip commerciale, il progettista può limitarsi a lavorare sui livelli più alti della pila dei protocolli e limitarsi ad
implementare il livello applicazione. In altri casi, il progettista deve scendere giù
lungo la pila e lavorare ad un livello più basso, sul quale può vedere e controllare
alcuni parametri di rete cui non può avere accesso dal livello applicazione. Capita,
ad esempio, quando una delle specifiche del sistema da progettare è il real-time,
oppure il data rate richiesto è elevato e l’allocazione della banda disponibile va ottimizzata al massimo. I simulatori esistenti di solito consentono al progettista di lavorare solo con descrizioni molto astratte del sistema, nascondendo alcuni parametri (come il packet error rate, la potenza sul canale, i pacchetti persi e così via) che
invece in molte applicazioni è importante conoscere. Per questo motivo, lo scopo di
questa tesi è presentare un nuovo tipo di simulatore Bluetooth che sia più vicino a
quella che sarà l’implementazione vera e propria del sistema finale. Descriveremo
alcune applicazioni, come la trasmissione di audio real-time, e vedremo come queste applicazioni possano essere adattate anche a standard diversi dal Bluetooth,
come l’802.15.4, mediante l’introduzione di un opportuno (e migliorato) vocoder.
A sostegno della progettazione del simulatore, presenteremo un ambiente di sviluppo grafico che permette di disegnare i sistemi da progettare e di visualizzare le
forme d’onda a debugging time, oltre che eseguire il comune debug su codice.
Indice
Capitolo 1 - Introduzione
1
Capitolo 2 - I Simulatori di Reti
NS-2 e NS-3
OMNeT++
OPNET
GloMoSim\QualNet
NCTUns\EstiNet
GTNetS
SSFNet
Jist\SWANS
5
6
9
11
12
13
13
14
14
Capitolo 3 - Il Simulatore Bluetooth
Il Bluetooth
Il simulatore
Lavorare a livello applicazione o LM
Lavorare a livello BaseBand
Log in uscita
17
17
23
24
31
32
Capitolo 4 - Applicazioni
Analisi del Bluetooth
Audio su Bluetooth
Lavori esistenti
Architettura del sistema
35
35
43
44
47
Capitolo 5 - Voce su 802.15.4
La rete
La compressione LPC
La rete 802.15.4
Simulazioni e test
La qualità di un segnale LPC
Simulazione della rete in SystemC
57
57
57
60
70
72
76
Implementazione dell’encoder LPC
Il problema dell’overlap-and-add
L’architettura
Capitolo 6 - SystemC IDE
L’idea
L’implementazione
Capitolo 7 - Conclusioni
82
82
84
89
89
91
103
Capitolo 1
Introduzione
Negli ultimi anni, le reti wireless sono diventate una realtà onnipresente, affidabile e ben accetta. Questo è particolarmente vero per reti come il Bluetooth e il
Wi-Fi, che oggi si incontrano praticamente ovunque e vengono usate per le applicazioni più disparate. I motivi principali dietro questa ampia disponibilità sono
probabilmente il basso costo e l’elevata affidabilità della tecnologia, e questi stessi
motivi permettono al progettista di usarle in svariati scenari.
In molti casi, quando si usa un chip commerciale, il progettista può limitarsi a
lavorare sui livelli più alti della pila dei protocolli e limitarsi ad implementare il livello applicazione. In altri casi, il progettista deve scendere giù lungo la pila e lavorare ad un livello più basso, sul quale può vedere e controllare alcuni parametri di
rete cui non può avere accesso dal livello applicazione. Pensiamo per esempio ad
una trasmissione audio real-time, molti-a-uno, su una rete a data rate da moderato a
basso. A causa della specifica sul real-time, la trasmissione e la ricostruzione del
segnale audio sul lato ricevitore non devono e non possono essere influenzate dai
ritardi introdotti dai livelli più alti del protocollo, e vanno perciò gestiti molto vicini all’hardware di trasmissione/ricezione. Lo stesso vale se il data rate diventa basso o se ci sono molti nodi nella rete: in questi casi, l’overhead introdotto dalla pila
protocollare dee essere ridotto se non eliminato del tutto.
Come altro esempio, pensiamo ad una rete di sorveglianza video. In questo caso, il progettista potrebbe voler proteggere il flusso video dal rumore con una qualche tecnica di correzione degli errori. Inoltre, il progettista potrebbe voler usare una
tecnica più forte quando il rumore è più alto, e una più debole quando il rumore è
più basso. Questo ha senso nel contesto dell’ottimizzazione dell’utilizzo della banda, dato che i codici per la correzione degli errori introducono tanto più ridondanza
quanto più errori sono in grado di correggere. In questo scenario, il progettista deve
almeno conoscere il packet error rate, e la potenza di segnale sul canale.
In più, in una applicazione real-time potrebbe non esserci ritrasmissione dei
pacchetti, e il progettista potrebbe voler tenere in ogni caso i pacchetti corrotti, in-
1
vece di scartarli. Ad esempio, nel caso dell’audio real-time, i pacchetti corrotti potrebbero essere usati “come sono”, invece di introdurre un frame vuoto. I pacchetti
corrotti sono un altro esempio di dati che il livello applicazione non vede.
Le simulazioni possono aiutare nelle prime fasi del progetto a scegliere parametri ad alto livello come la topologia di rete, il numero di slave, le dimensioni del
payload e così via. Una simulazione può aiutare a ottimizzare dettagli a più basso
livello come i tempi e ritardi di trasmissione, la protezione dal rumore, l’utilizzo
della banda e così via.
Tutti i simulatori esistenti, molti dei quali descriveremo nel Capitolo 2, utilizzano descrizioni ad alto livello del sistema, e perciò estremamente astratte. Quello
che vogliamo fare qui è presentare un simulatore Bluetooth più vicino ad una vera
implementazione hardware, e basato sul SystemC. Il SystemC ci permette di mescolare nello stesso progetto descrizioni accurate al ciclo con descrizioni a livello
più alto. Questo ci permette di creare un simulatore con blocchi tipo-hardware che
modellano l’hardware effettivo usato per trasmettere/ricevere i dati, e blocchi a livello applicazione in cui il progettista può inserire il proprio codice applicativo o
per la gestione del link. Una architettura di questo tipo può essere usata sia per simulare codice applicazione, sia per ottimizzare la gestione del link con l’accurato
controllo di ogni parametro Bluetooth. Inoltre, il simulatore può essere usato anche
come punto di partenza per il progetto di sistemi Bluetooth custom, cioè per il progetto di chip Bluetooth con caratteristiche dedicate. Descriveremo il simulatore nel
Capitolo 3, e alcune applicazioni nel Capitolo 4.
Tra le applicazioni che descriveremo c’è anche la già citata trasmissione di
audio di alta qualità su Bluetooth. Vedremo in quella sede come sia possibile dimensionare una rete del genere e quali siano i vincoli di progetto, ma il succo della
discussione sarà che comunque un progetto del genere è possibile. La conseguenza
diretta è la domanda se questo sia possibile anche su altri generi di protocollo a
basso-moderato data rate, come ad esempio l’IEEE 802.15.4. Questo standard è
stato concepito e realizzato specificatamente pensando a reti a basso data-rate e alimentate a batteria, come quelle di sensori. Con tali caratteristiche,
l’implementazione di una rete di sensori su 802.15.4 diventa abbastanza problematica non appena l’applicazione inizia a necessitare di un data rate moderato, e praticamente impossibile quando il data rate sale ulteriormente. Ciò nonostante, potrebbe essere utile tentare un simile progetto, dal momento che il basso costo e il basso
consumo di potenza potrebbero rendere una rete 802.15.4 la scelta migliore in molte situazioni. Alcuni esempi sono tutti quei casi in cui la rete deve essere installata
2
in fretta e il cablaggio e la manutenzione sono difficili da eseguire e i costi devono
essere tenuti al minimo, come potrebbe capitare in un sito archeologico o soggetto
a disastri naturali. Ovviamente, per avere una qualche speranza di realizzare questa
rete, è essenziale ottimizzare tutti i parametri del sistema, dal livello MAC a quello
applicazione. A seconda delle necessità dell’applicazione, dobbiamo scegliere accuratamente topologia e dimensioni della rete, oppure aggiungere un qualche genere di compressione alla parte di elaborazione dati del sistema, così da ridurre la
quantità di dati da trasmettere. Ancora, dobbiamo tenere conto dei problemi classici delle reti wireless, come l’elevata sensibilità al rumore, la banda limitata, e
l’accesso al canale condiviso.
La trasmissione di musica su 802.15.4 è sostanzialmente impossibile, a meno
che la rete non sia punto-punto, a causa della banda elevata richiesta da un segnale
musicale, ma la trasmissione di un segnale vocale, con il suo data-rate moderato,
rientra in questa classe di progetti “problematici”. In questo caso, un qualche genere di compressione prima della trasmissione vera e propria è inevitabile. Esistono
decine di vocoder che potrebbero essere usati in un progetto di questo tipo, ognuno
con i propri pregi e difetti, ma volendone usare uno su un dispositivo a batteria, la
soluzione migliore consiste in uno semplice e che consumi poco. La compressione
LPC è un algoritmo ben noto che produce segnali di qualità ragionevole, altamente
compressi, e in modo piuttosto semplice, ed è la nostra scelta qui. Come qualsiasi
sistema di compressione, l’LPC può essere configurato con una serie di parametri
che sostanzialmente governano il trade-off tra rapporto di compressione e qualità
del segnale compresso. Nel Capitolo 5 esamineremo entrambi gli aspetti, ossia il
dimensionamento della rete e del vocoder LPC. In più, faremo vedere come, mediante alcune semplici modifiche all’algoritmi, il fattore di compressione possa essere ulteriormente aumentato e la qualità del segnale migliorata.
Anche il simulatore 802.15.4 usato nel Capitolo 5 per il progetto della rete è in
SystemC. Il problema sostanziale dello sviluppo di simulatori di questo tipo (e in
generale di progetti di una certa complessità) in SystemC è che, in assenza di ambienti di sviluppo grafici poco costosi e semplici da usare, questo sviluppo si traduce in breve tempo in un incubo di programmazione. È per questo motivo che, nel
Capitolo 6, presenteremo un ambiente di sviluppo creato, parallelamente al simulatore Bluetooth, per risolvere questo problema, almeno nelle sue linee essenziali.
L’ambiente di sviluppo è in grado di visualizzare le forme d’onda associate a porte
e segnali SystemC a debugging time, oltre a sostenere l’usuale debug riga per riga,
ed è affiancato da un tool per il disegno dei blocchi del sistema.
3
4
Capitolo 2
I Simulatori di Reti
Le reti per lo scambio dati esistono probabilmente dagli albori dell’elettronica
(pensiamo alle reti radio), ma le reti di sensori e più in generale le reti wireless a
basso/medio datarate sono una scoperta relativamente recente. È questo il motivo
per cui, storicamente, la maggior parte dei simulatori attualmente a disposizione
nasce per la simulazione, lo studio e il progetto di reti metropolitane (MAN), o comunque su scale spaziali piuttosto ampie. Successivamente, con la comparsa e la
diffusione delle reti di sensori, sono state create delle estensioni per i simulatori esistenti che coprissero, in qualche modo, questo nuovo tipo di reti. La conseguenza
è che i simulatori di reti wireless più utilizzati oggi sono semplicemente simulatori
adattati, e non dedicati. Dal punto di vista ingegneristico è sicuramente più semplice modificare qualcosa che già esiste piuttosto che ricrearlo da zero, ma resta il fatto che la versione adattata eredita funzionalità e paradigmi di lavoro che non le sono propri. Come vedremo nella rapida carrellata di simulatori che presenteremo in
questo capitolo, tutti i simulatori di reti esistenti partono dal presupposto che la rete
che si vuole simulare implementi una qualche versione del TCP/IP, o comunque un
qualche protocollo al livello 4 della pila ISO/OSI, e che all’utente importi solo del
livello applicazione. Questo è inevitabile in una qualsiasi rete di calcolatori, ma
non lo è assolutamente nelle reti di sensori, come sarà piuttosto chiaro nei capitoli
successivi, quando discuteremo alcune applicazioni implementate sul nostro simulatore.
C’è di più. Simulare le prestazioni di una rete è solo un aspetto della creazione
di una nuova rete: ad un certo punto questa rete andrà implementata. Con molti dei
simulatori esistenti, se la simulazione è una cosa, l’implementazione è una cosa
completamente diversa. Ciò che si fa nella simulazione, va rifatto completamente
nell’implementazione, e questo agli ingegneri non piace. Un simulatore che permetta di simulare e contemporaneamente fare qualcosa che sia ragionevolmente vicino ad un’implementazione, è sicuramente gradito.
5
NS-2/
NS-3
OMNet++/
INET
OPNET
GloMoSim
QualNet
GUI
No
Sì
Sì
No
Sì
Script
OTcl/Python
NED
CShell
File config.
File config.
Bluetooth
Sì (ext.)
NO
NO
No
Sì
Core
C++
C++
C/C++
PARSEC
OpenSource
Sì
Sì
No
Sì
No
OS
Linux
Win/Lin
Win/Lin
Win/Lin
Win/Lin
Emulazione
No
No
No
No
Sì
NCTUns/
EstiNet
GTNetS
JIST/
SWANS
SSFNet
GUI
Sì
No
Sì
No
Script
Tcl
C++ main
DML
JAVA main
Bluetooth
No
No
No
No
Core
C++
C++
Java
Java
OpenSource
Sì/No
Sì
Sì
Sì
OS
Linux
Win/Lin
Win/Lin
Emulazione
Sì
No
No
No/Sì
Tabella 2.1 - Caratteristiche dei simulatori di rete più comuni.
La Tabella 2.1 riassume le caratteristiche principali dei simulatori più comunemente utilizzati, o perlomeno le caratteristiche di interesse per noi qui. Discuteremo meglio tutti questi simulatori nei paragrafi a seguire.
NS-2 e NS-3
NS-2 [1] è un simulatore rilasciato nato nel 1989 e sviluppato, dal 1995, con il
sostegno di DARPA, Xerox PARC, UCB e USC/ISI. Il limite principale di NS-2 è
sempre stato la mancanza di modelli dettagliati per i canali trasmissivi e per le reti
wireless, tanto che, nel 2006, dopo dieci anni di ricerca e sviluppo, il team decise di
riscrivere completamente il simulatore, rinunciando alla retro-compatibilità con
6
Figura 2.1 - UCBT
NS-2, e dando vita a NS-3 [2]. Si tratta di due dei simulatori più utilizzati
nell’ambito della ricerca sulle reti, e supportano molteplici protocolli, per quanto
vengano sostanzialmente utilizzati per simulare reti wireless basate su Wi-Fi, WiMax o LTE a livello fisico/MAC, e su protocolli di routing come OLSR e AODV a
livello network.
I core di elaborazione di entrambi i simulatori sono scritti in C++ e divisi in
una serie di moduli, ciascuno dei quali implementa uno o più protocolli. L’utente
crea, configura e simula la propria rete attraverso la generazione di script, in OTcl
per NS-2, e in Python per NS-3. Nuovi moduli possono essere scritti e aggiunti al
core di simulazione, appoggiandosi alla API esistente. I risultati delle simulazioni
vengono salvati su file di tracing che possono essere analizzati e/o visualizzati attraverso programmi esterni.
Il principale problema di questi simulatori è la loro difficoltà di utilizzo, dal
momento che non hanno interfacce grafiche, e che come minimo bisogna imparare
un linguaggio di scripting. Se si vuole anche aggiungere dei moduli, bisogna imparare anche la teoria delle code e svariate tecniche di modellizzazione. Come spesso
capita per i software open-source, e soprattutto in NS-2, i risultati ottenuti non
sempre sono consistenti, probabilmente a causa delle continue modifiche del codice sorgente, e certi protocolli contengono bachi inaccettabili. NS-3 invece, oltre a
non essere più compatibile con NS-2, non supporta protocolli (come WSN, MAN e
così via) presenti invece in NS-2.
7
UCBT
Livello fisico
Il canale radio e il livello fisico non sono stati modellizzati esplicitamente, ma
tramite un modello di perdita e di interferenza tra piconet.
Baseband
Il baseband è ragionevolmente completo. Genera il corretto schema per il frequency hopping, gestisce pacchetti multi-slot, i link sincroni e le derive del
clock.
LMP
Gestisce la creazione dei link, il cambio di ruolo master-slave, le modalità a
bassa potenza (Sniff, Park e Hold), e il cambio di piconet.
L2CAP
Gestisce il SAR e il Protocol Multiplexing.
BNEP
Fornisce un’interfaccia tra il MAC e livelli superiori come l’LL.
SCO
Gestisce le connessioni sincrone, facendo da ponte tra Baseband/LMP e livello applicazione.
Tabella 2.2 - Principali caratteristiche di UCBT.
Né NS-2 né NS-3, nella loro versione base, supportano il Bluetooth. Per NS-2
nel corso degli anni sono state sviluppate delle estensioni che però, appoggiandosi
al core esistente, ne ereditano vantaggi e, soprattutto, svantaggi. UCBT [3],
dell’università di Cincinnati, ad esempio, aggiunge a NS-2 lo stack protocollare del
Bluetooth, come mostrato nella Figura 2.1. UCBT simula la maggior parte delle
specifiche a livello Baseband e sopra di esso, per esempio il frequency hopping, la
scoperta dei dispositivi e la creazione della connessione, la gestione delle modalità
a bassa potenza (Sniff, Park e Hold), il cambio di ruolo master-slave e la negoziazione dei pacchetti multi-slot, i link sincroni (SCO), e così via. UCBT consente di
creare piconet e scatternet, e di adattarsi al profilo della PAN mediante il BNEP
(Bluetooth Network Encapsulation Protocol). Soprattutto nel contesto delle scatternet, UCBT contiene anche una serie di algoritmi di scheduling per l’instradamento
dei pacchetti da una piconet e l’altra. UCBT simula la deriva del clock e supporta
l’Enhanced Data Rate (EDR) aggiunto nella versione 2.0 del protocollo. La tabella
2.2 riassume le funzionalità implementate a ciascun livello dello stack mostrato
nella Figura 2.1.
8
BlueHoc\BlueWare
Livello fisico
A questo livello vengono simulati a livello molto elementare il canale radio
Bluetooth e i canali utilizzati per il frequency hopping. Il canale radio semplicemente instrada i pacchetti da un lato all’altro del link, senza particolari modelli di propagazione.
Baseband
Qui sono implementate le procedure di Inquiry e di Paging, la generazione del
pattern per il frequency hopping, la modalità a bassa potenza Hold, e il cambio di ruolo master-slave.
LMP
Gestisce la creazione dei link, il cambio di ruolo master-slave, le modalità a
bassa potenza (Sniff, Park e Hold), e la terminazione di una particolare connessione.
L2CAP
Gestisce la segmentazione e il riassemblaggio dei pacchetti applicazione.
Tabella 2.3 - Principali caratteristiche di BlueHoc e di BlueWare.
Altre estensioni che aggiungono il Bluetooth a NS-2 sono BlueHoc [4], sviluppato dall’IBM, e BlueWare [5] [6], sviluppato dal MIT, entrambe più limitate e
più semplici di UCBT. BlueHoc implementa solo i livelli fisico, Baseband, LMP e
L2CAP del protocollo, mentre BlueWare aggiunge a BlueHoc la possibilità di simulare scatternet, aggiungendo schemi per la formazione di diverse topologie di
rete, e per la gestione dei link. Entrambi si appoggiano all’implementazione di NS2 del TCP/IP per la gestione del livello trasporto. La Tabella 2.3 riassume le loro
principali caratteristiche.
OMNeT++
Più che un simulatore, OMNeT++ [7] è un framework per lo sviluppo di simulatori. OMNeT++ fornisce gli strumenti e i componenti di base per scrivere simulazioni, ma di per se stesso non fornisce alcun componente specificatamente concepito per la simulazioni di reti di calcolatori, di code, di architetture di sistemi o chi
per loro. Al contrario, queste e altre applicazioni sono supportate da progetti indipendenti. Nel caso delle reti di calcolatori, il framework in questione è INET/INETMANET [8], che contiene modelli per svariati protocolli di rete cablate
9
Figura 2.2 - IDE OMNeT++
e non, tra cui UDP, TCP, SCTP, IP, IPv6, Ethernet, PPP, 802.11, 802.15.4, MPLS,
OSPF, e così via, ma non il Bluetooth.
OMNeT++ è una libreria di classi C++ che contiene un kernel di simulazione
e una serie di classi generiche (per la generazione di numeri casuali, per la raccolta
di statistiche, per la scoperta delle topologie di rete, e così via), che si possono usare per creare componenti da simulare (semplici moduli e canali). In più, OMNeT++
fornisce degli ambienti grafici per le simulazioni, un IDE basato su Eclipse per il
progetto (nella Figura 2.2), delle interfacce di estensione per le simulazioni realtime, per l’emulazione di reti, per la simulazione parallela distribuita e così via. I
moduli creati in questo ambiente a partire dai tool di OMNeT++ possono poi essere
collegati l’uno all’altro tramite delle porte, e combinati insieme. I moduli così ottenuti costituiscono i modelli di simulazione. Il collegamento tra i moduli e perciò la
creazione dei modelli di simulazione, la creazione della rete e la sua configurazione
vengono eseguite, analogamente a NS-2/NS-3, mediante degli script in linguaggio
NED (NEtwork Description).
10
Figura 2.3 - Architettura concettuale di OPNET.
OPNET
La differenza sostanziale tra OPNET [9] e i precedenti simulatori che abbiamo
discusso è che OPNET è un tool commerciale. Si tratta in realtà di un insieme di
tool, ognuno dedicato alla modellizzazione\simulazione di un aspetto di una rete.
Oltre ai tool per l’editing dei vari componenti della rete, ci sono dei tool per la simulazione delle antenne, delle curve di modulazione, per la scelta del formato dei
pacchetti, e così via. I tool per la modellizzazione della rete sono tutti grafici, ma si
possono anche usare degli script in CShell per ridurre l’occupazione della memoria
e dedicarne di più alla simulazione vera e propria.
Concettualmente, OPNET organizza i suoi modelli di rete in modo gerarchico,
secondo lo schema mostrato nella Figura 2.3. Al livello più alto c’è il dominio di
rete, che definisce gli oggetti che compongono la rete e la loro posizione a livello
globale, oltre ai collegamenti tra di essi e le relative configurazioni. A livello di rete, OPNET permette di definire anche sottoreti per ogni nodo, e di impostare il come e dove i nodi della rete si muoveranno (se sono mobili) durante la simulazione.
A livello di nodo, OPNET permette di definire i protocolli che verranno eseguiti all’interno di quel nodo, secondo una struttura che ricalca da vicino quella del
modello ISO/OSI. Ci saranno i livelli applicazione, presentazione, trasporto (TCP e
UDP), rete (IP), link (ARP, MAC) e fisico. I singoli protocolli possono essere definiti, anch’essi mediante l’apposito tool grafico, come una macchina a stati finiti.
11
Figura 2.4 - GloMoSim.
Per ogni macchina, l’utente può scegliere gli stati e le condizioni che forzano le
transizioni tra di essi, per poi programmare il funzionamento in ogni stato in C++.
OPNET supporta molti protocolli per LAN e MAN, ma non per reti wireless o di
sensori. Il Bluetooth, ad esempio, non è supportato.
GloMoSim\QualNet
GloMoSim [10] segue, dal punto di vista dell’architettura, lo stesso approccio dei
simulatori precedenti, ossia incolonna una serie di protocolli in una pila molto simile a quella dello stack ISO/OSI (nella Figura 2.4), ma a differenza degli altri è
specificatamente concepito per le reti wireless. Un insieme di API tra due livelli
adiacenti della pila di protocolli consente di collegarli tra loro. In maniera simile
agli altri simulatori, l’utente deve creare un file di configurazione in cui specifica
dimensione e topologia della rete, protocolli per nodo (ai vari livelli), eventuale
mobilità, e così via. Sono supportati molteplici protocolli, ma non il Bluetooth.
QualNet è la versione commerciale di GloMoSim, molto usato dal dipartimento della difesa Americano. Aggiunge un’ottima interfaccia grafica, sia per il progetto, che per la simulazione, che per l’analisi della rete, ed è strutturato in modo tale
da permettere il passaggio diretto del codice dal simulatore all’implementazione
vera e propria.
12
NCTUns/EstiNet
NCTUns [11] e la sua versione commerciale EstiNet [12] basano la propria
ragion d’essere esplicitamente sulla possibilità di riutilizzare in una applicazione
reale il codice scritto all’interno del simulatore. Le reti simulate in NS-2, ad esempio, sono così astratte che le applicazioni simulate vanno completamente riscritte
per poterle interfacciare ad hardware reale. Grazie ad una versione modificate del
kernel Linux, e all’utilizzo del suo stack TCP/IP nativo, NCTUns e EstiNet consentono di interagire con dispositivi reali all’intero di reti virtuali. Il risultato è che gli
scenari sono più realistici e i risultati più affidabili, anche se le simulazioni sono
meno flessibili, dal momento che le caratteristiche dei nodi simulati sono legate a
quelle dell’hardware a disposizione. Con NCTUns ed EstiNet, ad esempio, si può
studiare il comportamento di un’applicazione reale su un nuovo protocollo, e poi
esaminare gli scambi di pacchetti all’interno di analizzatori di reti per reti reali.
Ovviamente, è anche possibile usarli come simulatori convenzionali.
NCTUns e EstiNet supportano un certo numero di protocolli per reti LAN e
MAN, come Wi-Fi, Ethernet, TCP, UDP, FTP, IP e così via. Supportano anche un
certo numero di dispositivi di base, come hub, router, switch eccetera, ma il supporto per le reti di sensori e per le PAN è ancora scarso. Il Bluetooth non è attualmente supportato.
GTNetS
GTNetS [13] è un ambiente per simulazioni di reti concepito per le reti su larga scala. Come NS-2, la struttura di GTNetS ricalca fedelmente quella della pila
ISO/OSI, con una chiara distinzione tra i vari livelli dello stack di protocolli, ma
cercando di restare la più fedele possibile alla struttura di una rete reale. Le simulazioni ragionano in termini di scambi di pacchetti, dove ogni pacchetto è formato da
una serie di PDU (Protocol Data Unit) aggiunte a e rimosse dal pacchetto man mano che questo percorre la pila protocollare. Gli oggetti di simulazione che rappresentano i nodi della rete hanno una o più interfacce, ciascuna con associato un indirizzo IP e il relativo link. Gli oggetti dei protocolli a Livello 4 sono collegati a delle
porte, proprio come i protocolli di rete reali si collegano a delle porte. Le connessioni tra oggetti del protocollo a livello trasporto vengono specificate utilizzando
un indirizzo IP di sorgente e uno di destinazione, e una porta di sorgente e una di
13
destinazione, proprio come accade con le connessioni TCP reali. L’interfaccia tra
le applicazioni e il livello trasporto utilizza le chiamate a funzioni (come la connect, la listen, la send e la sendto) molti simili a quelle delle API in ambienti Unix.
Dal punto di vista operativo, GTNetS non dispone di una interfaccia grafica,
né di un ambiente di sviluppo, e l’utente crea la sua rete riempiendo il main del simulatore. Per avviare una simulazione, quindi, bisogna prima importare il simulatore in un qualche compilatore (come VisualStudio sotto i sistemi Windows), creare la propria applicazione e compilare tutto. Non ci sono nemmeno tool integrati
per la visualizzazione dei risultati della simulazione, che vanno perciò importati in
programmi esterni.
SSFNet
SSFNet [14] è una collezione di modelli Java aggiunti al framework di simulazione SSF. Si tratta di modelli di reti e protocolli Internet al e sopra il livello IP.
Componenti a livello link e fisico vanno aggiunti a parte. Analogamente alla maggior parte dei simulatori visti fino a questo punto, anche in SSFNet le reti vengono
create, configurate e simulate mediante file di script in linguaggio DML (Domain
Modeling Language), e i risultati prodotti vanno analizzati in programmi esterni.
Di suo, SSFNet non ha un’interfaccia grafica né un ambiente di sviluppo, che sono
stati aggiunti in seguito da progetti indipendenti (per esempio in [15]). SSFNet
supporta molti protocolli a livello trasporto e rete (TCP, UDP, FTP, IP, ecc.), ma
pochi protocolli per reti wireless e di sensori.
JiST/SWANS
JiST [16] [17] è un motore di simulazione a eventi discreti che gira sopra una
comune Java virtual machine. Si tratta di un prototipo di un nuovo approccio general-purpose alla costruzione di simulatori ad eventi discreti, detto simulazione a
macchina virtuale, che unisce i sistemi tradizionali ai simulatori basati su linguaggio. La piattaforma di simulazione risultante è sorprendentemente efficiente. Le sue
prestazioni superano quelle di ambienti di simulazione altamente ottimizzati sia in
termini di tempo che di memoria. Ad esempio, JiST gestisce il doppio degli eventi
14
del motore PARSEC usato da GloMoSim e QualNet, con molta memo memoria
[18].
Il codice che gira su JiST non deve essere scritto in un qualche particolare linguaggio inventato apposta per lanciare simulazioni, né deve essere inframezzato da
specifiche chiamate al sistema. JiST semplicemente trasforma una pre-esistente
macchina virtuale in una piattaforma di simulazione, incorporando le problematiche di simulazione (concorrenza, eventi discreti, ecc.) direttamente a livello di byte
code. Dunque, le simulazioni JiST sono scritte in Java, compilate usando un comune compilatore Java, e fatte girare su una macchina virtuale standard e inalterata.
SWANS è un simulatore di reti wireless costruito sopra la piattaforma JiST.
SWANS è strutturato come una serie di componenti software indipendenti che possono essere combinati fino a formare una rete wireless o di sensori completa. Le
sue funzionalità sono simili a quelle di NS-2 e di GloMoSim, ma, appoggiandosi a
JiST, SWANS può simulare reti molto più ampie, più in fretta e occupando meno
memoria, e in più può far girare applicazioni di rete standard scritte in Java su reti
simulate.
15
16
Capitolo 3
Il Simulatore Bluetooth
IL BLUETOOTH
Il Bluetooth è un sistema master-slave a basso range e ridotto data-rate. Le
versioni 1.0 e 2.0 dello standard supportano un data rate massimo di 723.1 kbit/s,
mentre la versione 2.0 porta la velocità massima a 3 Mbit/s. Una rete Bluetooth va
sotto il nome di piconet, e può avere al massimo un master e sette slave. Opzionalmente, si può aumentare il numero degli slave mediante il concetto di scatternet,
una particolare rete in cui uno slave appartiene a più di una piconet e fa da “ponte”
tra di esse.
Analogamente a molti altri standard wireless (come il Wi-Fi e lo ZigBee), anche il Bluetooth lavora nella banda ISM a 2.4 GHz. La necessità di ridurre i problemi di coesistenza con tutti questi altri protocolli che “vivono” nella stessa banda
ha portato a quella che forse è la caratteristica più peculiare del Bluetooth: il frequency hopping (FH). L’FH è un particolare tipo di modulazione a spettro espanso
in cui l’espansione spettrale viene ottenuta dividendo la banda disponibile in 79 canali e cambiando i canali di trasmissione/ricezione in maniera pseudo-casuale.
Questo rapida cambio di canale è il motivo dietro il principale difetto dello standard Bluetooth: la relativamente lenta creazione della rete. Ovviamente, se la sequenza di hopping è pseudo-casuale, e l’istante in cui un qualche slave tenta di collegarsi alla rete è anch’esso casuale, lo slave non avrà modo di sapere qual è il canale correntemente usato e da quale punto della sequenza pseudo-casuale iniziare
l’hopping. Dnque, durante la fase di creazione della rete, lo standard [19] definisce
una particolare procedura di ricerca/scansione che può essere riassunta con il diagramma a stati mostrato nella Figura 3.1, e durante la quale master e slave si scambiano tutte le informazioni necessarie per sincronizzarsi. Prima di tutto, il master va
alla ricerca di nuovi slave nelle vicinanze con la cosiddetta fase di inquiry. Questa
fase può essere avviata in qualsiasi momento, il che permette ad un nuovo slave di
collegarsi alla rete anche dopo la sua creazione. Quando uno slave vuole collegarsi
17
Figura 3.1 - Diagramma a stati del
funzionamento di un baseband Bluetooth
ad una rete, deve eseguire la cosiddetta procedura di inquiry scan. Quando un
master trova uno slave, si connette ad esso con la procedura di paging. Ancora, se
uno slave vuole collegarsi ad un master, dovrà eseguire un page scan, alla ricercadei pacchetti di paging del master. Una volta completata la fase di paging, il master
e il nuovo slave sono connessi e sincronizzati, e possono iniziare a scambiarsi dati.
Questa lunga e complicata procedura per la creazione della rete rappresenta il
vero collo di bottiglia per le prestazioni del Bluetooth. Il problema è che né il
master, né tantomeno lo slave possono sapere cosa sta facendo l’altro. Ad esempio,
uno slave potrebbe aver ricevuto il pacchetto di inquiry dal master ed essere passato alla fase di page scan, ma il pacchetto di risposta all’inquiry potrebbe essere andato perso (per esempio a causa del rumore nel canale), e il master quindi continuerà con l’inquiry. Se uno slave in fase di page scan, dopo un po', non si vede arrivare
un pacchetto di paging dal master, tornerà alla fase di inquiry. Ancora, potrebbe
accadere che il master, dopo aver ricevuto il pacchetto di risposta all’inquiry, decida comunque di cercare qualche altro slave prima di avviare il paging. Il risultato è
che il master inizierà l’inquiry o il paging in un momento qualsiasi, e lo slave passerà da inquiry scan a page scan e viceversa in momenti qualsiasi. Dunque, potreb-
18
Figura 3.2 - Multiplexing a divisione di tempo nel Bluetooth.
Figura 3.3 - Struttura generale di un pacchetto Bluetooth.
be volerci un certo tempo per creare una piconet, specialmente se il canale è rumoroso o se ci sono altre reti che trasmettono nelle vicinanze e nella stessa banda.
L’accesso multiplo al canale condiviso è regolato dal semplice multiplexing a
divisione di tempo (TDM) mostrato nella Figura 3.2. L’asse temporale viene diviso
in una serie di time slot (TS), ciascuno lungo 625 µs, raggruppati in coppie. Nel
primo slot di ogni coppia, il master invia i suoi dati e lo slave ascolta il canale. Nel
secondo slot, lo slave può inviare i propri dati al master, che starà ascoltando il ca
nale. Il master può usare il proprio time slot per inviare nuovi dati ad uno slave o
semplicemente per interrogarli, richiedendogli nuovi dati. Come si può vedere dalla
figura, lo standard permette di utilizzare pacchetti che durano più di un time slot
(da tre a cinque TS). In questo caso, la sincronizzazione viene mantenuta semplicemente interrompendo il frequency hopping fino alla fine della trasmissione.
Qualche altra parola sui pacchetti Bluetooth è inevitabile per capire i risultati
che presenteremo nel capitolo successivo. La struttura fondamentale di uno di questi pacchetti è mostrata nella Figura 3.3. Il CAC, che sta per “Channel Access Code”, è una sorta di preambolo utilizzato per la sincronizzazione, che serve per identificare da subito il significato dei dati a seguire (se si tratta di dati di inquiry, paging o utente), e se il pacchetto appartiene alla piconet corrente o ad un’altra. Tutto
questo è possibile semplicemente perché il CAC altri non è che la versione codifi-
19
Figura 3.4 - Struttura dell’header di pacchetto.
Figura 3.5 - Struttura dell’header di payload.
cata di un particolare indirizzo a 24 bit, che cambia nei vari casi: sarà un indirizzo
generico durante la fase di inquiry, l’indirizzo dello slave durante il paging, e
l’indirizzo del master durante il trasferimento dati. Utilizzando gli indirizzi in questo modo, lo slave potrà essere trovato da un master qualsiasi, un master potrà tentare di connettersi ad uno slave qualsiasi, e uno slave potrà trasmettere dati solo
verso un particolare master. Il codice applicato all’indirizzo è un BCH allungato
(64, 30) [20], un codice per la correzione degli errori in grado di correggere fino a
sei bit sbagliati, e individuare fino a 14 errori1. Quattro ulteriori bit vengono usaticome preambolo, prima della codeword, per semplificare la compensazione DC,
portando il totale a 68 bit.
Il pacchetto usato durante l’inquiry, il cosiddetto “pacchetto ID”, contiene unicamente questo CAC, dal momento che è tutto ciò che un dispositivo deve sapere
per capire che c’è attività su un qualche canale. Tutti gli altri pacchetti hanno almeno un header dopo il CAC. In questi casi, lo standar richiede che tra il CAC e
l’header venga aggiunto un trailer, con lo stesso scopo del preambolo. Dunque, in
un pacchetto con header, la lunghezza del CAC sale a 72 bit.
L’header di pacchetto contiene diversi campi usati per gestire la trasmissione
dati, come mostrato nella Figura 3.4. Il campo LT_ADDR identifica lo slave destinatario della transazione, il campo TYPE il tipo del pacchetto corrente, mentre i bit
FLOW, ARQN e SEQN sono usati per il controllo del flusso di trasmissione. Il bit
Ovviamente, un codice allungato (64,30) va applicato ad un dato a 30 bit. L’indirizzo è lungo solo 24 bit: i 6 bit
mancanti sono i primi sei bit di una sequenza di Barker a 7 bit che viene pre-appesa all’indirizzo e usata a scopi di
sincronizzazione. I polinomi usati per la generazione di questi codici e tutti gli altri che descriveremo in seguito
sono riportati in [19].
1
20
Tipo
Lunghezza del payload
(bytes)
Slot
Header
Payload
ID
-
1
No
No
NULL
-
1
Sì
No
POLL
-
1
Sì
No
FHS
18
1
Sì
Sì
DM1
0 - 17
1
Sì
Sì
DH1
0 - 27
1
Sì
Sì
DM3
0 - 121
3
Sì
Sì
DH3
0 - 183
3
Sì
Sì
DM5
0 - 224
5
Sì
Sì
DH5
0 - 339
5
Sì
Sì
Tabella 3.1 - Pacchetti Bluetooth.
FLOW viene usato per evitare l’overflow del buffer in ricezione, l’ARQN per un
semplice protocollo di handshaking basato su acknowledge, e il SEQN per identificare la ricezione multipla di un qualche pacchetto. Il campo HEC, “Header Error
Checking”, è un semplice CRC concatenato all’header a 10 bit. L’intero header a
18 bit viene poi codificato con un semplice codice FEC (Forward Error Correction)
a ripetizione. Il rate di questo codice è 1/3, il che significa che ogni bit nell’header
viene trasmesso tre volte. La lunghezza totale dell’header passa quindi a 54 bit.
Ci sono due pacchetti formati dai soli CAC e header di pacchetto: il pacchetto
NULL, e il pacchetto POLL. Normalmente, il POLL viene usato da un master per
interrogare uno slave, magari perché il master pensa che lo slave dovrebbe trasmettere un qualche tipo di dato. Una volta ricevuto un POLL, lo slave dovrà rispondere
in qualche modo. Se ha dati da inviare, potrà usare il successivo mezzo-slot per girarli al master. Se non ne ha, può rispondere con un NULL, riempiendo opportunamente i bit ARQN e FLOW. Il pacchetto NULL non richiede una risposta esplicita, per cui di solito viene usato in questo modo.
Tutti gli altri pacchetti hanno un payload. Possiamo distinguere tra due principali tipi di pacchetto: il DM e il DH2. Entrambi i tipi possono occupare uno, tre o
Non si tratta di una lista esaustiva. Ci sono altri pacchetti usati per i link di tipo SCO e eSCO, e c’è il pacchetto
DV usato per link su cui si trasmette una combinazione di dati e voce. Il nostro simulatore non supporta questi
link, perciò non parleremo dei relativi pacchetti qui. C’è un altro particoalre pacchetto, l’FHS, usato per lo scambio di informazioni durante l’inquiry e il paging. Questo pacchetto è usato internamente dal simulatore ma mai
2
21
cinque time slot, e la loro lunghezza viene dichiarata aggiungendo un numero dopo
l’abbreviazione DM o DH: ad esempio, c’è il pacchetto DM1, che occupa uno slot,
e il DH5, che ne occupa cinque. La differenza principale tra i due tipi di pacchetto
è che i DM sono protetti dagli errori mediante un codice di Hamming accorciato
(15, 10) [20], mentre i DH non lo sono. La conseguenza della ridondanza aggiunta
da questo codice è che i pacchetti DM possono contenere meno dati dei DH. Entrambi i tipi di pacchetto sono ulteriormente protetti da un CRC, e in tutti il payload
inizia con un payload header. La struttura del payload header è mostrata nella Figura 3.5, mentre la sua lunghezza effettiva dipende dalla lunghezza del payload. Il
campo LENGTH si spiega da solo, mentre il bit FLOW viene usato per un ulteriore
controllo di flusso, e il bit LLID contiene il tipo di link logico cui il pacchetto corrente appartiene. Torneremo sulla questione dei link logici tra un attimo. La Tabella 3.1 riassume la descrizione dei pacchetti che abbiamo appena dato.
Dal punto di vista della pila protocollare, quella che abbiamo appena dato è
una descrizione molto semplificata del livello BaseBand (BB). Lavorare a questo
livello è principalmente il destino dei progettisti di hardware che devono implementare chip Bluetooth dedicati. Al di sopra di questo livello c’è il regno del progettista di reti, che deve implementare il protocollo di gestione della rete. Utilizzando la terminologia dello standard [19], si tratta del livello Link Management
(LM). Il progettista che lavora qui non vede i time slot o il protocollo di ritrasmissione, o le codifiche, e così via. Lo scopo del progettista LM è rispondere a domande tipo “come posso garantire la ricezione di un dato pacchetto in un dato lasso
di tempo?” o “Come fa uno slave a distinguere tra un master con cui vuole connettersi e un altro nelle vicinanze?” o “Come faccio a scambiare le chiavi di cifratura
in modo sicuro?” e via dicendo. Ovviamente, tutti questi problemi possono essere
risolti con un opportuno scambio di pacchetti. Per consentire al progettista di astrarre dai dettagli dell’implementazione del BB, lo standard Bluetooth introduce il
concetto di Link Logico (LL). Un LL è semplicemente una connessione tra due nodi. Ci sono diversi tipi possibili di LL, e tra un master ed uno slave se ne può stabilire più di uno. I due tipi principali sono il link asincrono (ACL) e quello sincrono
(SCO). Il link ACL è il link di default che viene stabilito al completamento del paging, e può essere usato per trasferire dati utente (link ACL-U) o di controllo
(ACL-C), necessari per la gestione della rete. I link SCO (e la controparte estesa,
eSCO) riservano dei time slot per dati utente tempo-critici, e possono essere stabiliesplicitamente dall’utente, perciò non parleremo nemmeno di lui. Ulteriori informazioni su questi pacchetti possono essere trovate in [19].
22
ti non appena creato il primo link ACL, mediante un particolare scambio di pacchetti ACL-C. Lo standard Bluetooth definisce una serie di procedure per la gestione della rete [19], che possono essere avviate in qualsiasi momento, a seconda
delle necessità dell’utente (ad esempio dopo un comando ricevuto dai livelli superiori), o della rete stessa (ad esempio, dopo un controllo sulla potenza del segnale
ricevuto).
Dal punto di vista dell’utente, anche la descrizione della rete a livello LM è
troppo dettagliata. Al progettista a livello applicazione non interessano i pacchetti
DM, le procedure necessarie per scambiare una chiave di cifratura, o l’indicatore
della potenza del segnale. Ciò che l’utente vuole è trasmettere i suoi dati da un nodo all’altro, e quindi la sua preoccupazione principale è progettare il protocollo di
cui ha bisogno per fare questo, a seconda delle informazioni contenute in un pacchetto ricevuto, oppure controllare i livelli inferiori semplicemente inoltrando qualche tipo di comando e aspettando la risposta. Lo standard Bluetooth definisce una
serie di comandi collettivamente detti comandi HCI (Host-Controller Interface)
[19]. Ovviamente, il livello LM dovrà contenere un qualche genere di interprete dei
comandi che sia in grado di esaminare i comandi passati dal livello applicazione e
avviare l’azione richiesta. Inoltre, il livello LM deve contenere un qualche genere
di “pacchettizzatore” che prenda i risultati di una transazione LM e li metta in un
evento (usando la terminologia dello standard) da restituire al livello applicazione. I
pacchetti dati ricevuti e quelli da trasmettere rientrano anch’essi in questo paradigma, indipendentemente dall’uso che l’LM ne farà. Come vedremo nella prossima sezione, questa è esattamente la struttura del nostro simulatore, e questo permette al progettista di lavorare indifferentemente a livello BB, LM o applicazione.
IL SIMULATORE
La struttura che abbiamo deciso di usare per il nostro simulatore ricalca da vicino quella di un sistema Bluetooth realistico, ed è mostrata nella Figura 3.6. Una
chiave USB con Bluetooth contiene un microcontrollore che gestisce l’effettiva trasmissione sul canale wireless, e un bus USB. Sull’altro lato ci sarà un computer,
che può essere visto come un microcontrollore molto potente, e un altro bus USB.
Astraendo questo modello, il nostro simulatore definisce un controller BT, un semplice bus, ed un semplice controller per il livello applicazione. Il bus nel mezzo è
un semplice bus parallelo a otto bit, ma può essere rimpiazzato da un bus differente
23
Figura 3.6 - Architettura del simulatore.
senza alcun problema. In una qualsiasi MCU, il codice utente gestisce le periferiche integrate scrivendo un qualche insieme di registri di memoria, e/o gestendo gli
interrupt lanciati dall’hardware. Nel nostro simulatore, i due microcontrollori possono essere “programmati” scrivendo del codice all’interno del ciclo principale o
delle interrupt service routine, e possono comunicare con le periferiche hardware
(il bus controller nel mezzo e l’hardware per il BB) scrivendo alcuni “registri” definiti in una memoria condivisa. Inoltre, in molte MCU che contengono periferiche
che ragionano in termini di pacchetti (come l’USB), la memoria principale viene
usata anche per passare alla periferica i dati da trasmettere. Abbiamo usato lo stesso approccio anche nel nostro simulatore. Prima di tutto, quindi, descriveremo in
dettaglio lo schema di gestione della memoria e tutto ciò che il progettista deve sapere per poter lavorare a livello applicazione o LM. Successivamente descriveremo
l’implementazione del livello BB.
Lavorare a livello applicazione o LM
Come abbiamo detto, i due modelli di MCU definiscono tre oggetti: un ciclo
principale, una serie di interrupt service routine (ISR), e una mappa di memori. Per
semplicità, la mappa di memoria è la stessa nelle due MCU, anche se il livello applicazione non ha un controller BB. In questa architettura, la memoria è semplicemente un array il cui puntatore viene condiviso tra tutti i moduli che devono accedervi, e un “registro” è una locazione riservata al suo interno con un significato
24
Figura 3.7 - Un esempio di registro.
particolare. Una ISR, invece, è un processo SystemC lanciato da una linea di interrupt, una porta di ingresso per il modulo MCU.
Consideriamo un esempio particolarmente importante. La Figura 3.7 mostra il
registro di controllo usato dal bus per lanciare degli interrupt. Contiene quattro bit
di
controllo
(BUS_SC1_EIE,
BUS_SC1_ECIE,
BUS_SC1_SCOIE,
BUS_SC1_ACLIE) che il progettista può usare per abilitare una particolare sorgente di interrupt. “EIE”, ad esempio, sta per “Event Interrupt Enable”: settando questo bit, la ISR verrà attivata quando il bus avrà completato la ricezione di un pacchetto evento HCI, ossia una risposta ad un comando HCI. Allo stesso modo, “ECIE” sta per “End Command Interrupt Enable”, e viene settato per permettere al
bus di lanciare un interrupt al completamento della ricezione di un nuovo comando
HCI. (Ovviamente, l’evento viene ricevuto solo dalla MCU applicazione, e il comando solo dalla MCU LM.) Il registro contiene anche quattro flag di stato che
vengono settati dal controller del bus alla ricezione di un nuovo pacchetto HCI e
che possono essere letti dal codice all’interno della ISR al fine di identificare
l’evento che ha provocato l’interrupt. La lettura o la scrittura di un bit è estremamente semplice: per ogni registro c’è un insieme di define che permettono la manipolazione diretta dei vari campi dei vari registri. Nel nostro esempio, per abilitare
le quattro sorgenti di interrupt per il bus, ci basta scrivere:
BUS_SC1_EIE
BUS_SC1_ECIE
BUS_SC1_SCOIE
BUS_SC1_ACLIE
=
=
=
=
1
1
1
1
;
;
;
;
in un punto qualsiasi del programma applicativo, come faremmo in una MCU vera
e propria. Un primo vantaggio di questo approccio è il quanto sia semplice, dal
punto di vista del programmatore, controllare l’hardware collegato alla MCU. Inoltre, in questo modo è piuttosto semplice aggiungere alla MCU altro hardware: basta aggiungere le definizioni dei nuovi registri alla mappa di memoria e
25
all’hardware (cosa che abbiamo effettivamente fatto collegando il modello
SystemC di un bus CAN, con piccole modifiche, alla MCU del livello applicazione, al fine di simulare un bridge CAN-Bluetooth). Terzo, se l’obiettivo del progetto
è lo sviluppo di un nuovo protocollo o di una nuova applicazione, questo approccio
permette al progettista di usare il simulatore senza avere idea di cosa sia il
SystemC.
Più complicato è l’approccio utilizzato per implementare lo scambio dei pacchetti HCI attraverso il bus intermedio, concepito con l’obiettivo di massimizzare
le prestazioni in trasmissione. Dal punto di vista del master, il livello applicazione
non ha modo di sapere quando una trasmissione è stata completata, dal momento
che il pacchetto NULL di solito non va oltre il livello LM. Dal punto di vista dello
slave, il livello applicazione non ha modo di sapere quando è tempo di trasmettere,
dal momento che il pacchetto POLL si ferma sul livello LM. Inoltre, anche qualora
il pacchetto POLL arrivasse al livello applicazione, è probabile che i time slot
riservati alla trasmissione dello slave vengano saltati mentre il pacchetto dati
transita attraverso il bus. Ancora, lo slave non ha idea nemmeno di quando la
trasmissione termina, perché non è detto che il master mandi un pacchetto di
risposta nel time slot successivo (dipende dall’applicazione), e un acknowledge
esplicito non è obbligatorio. L’unica soluzione a tutti questi problemi è lasciare
libero il livello applicazione di inviare sul bus un nuovo pacchetto HCI
ogniqualvolta ne abbia voglia, e permettere al firmware LM di scegliere il
momento più opportuno per la trasmissione. In questo modo, uno slave può
mandare giù un nuovo pacchetto in qualsiasi istante, e il BB lo trasmetterà non
appena ricevuto un pacchetto POLL. Allo stesso modo, il master può mandare un
nuovo comando o pacchetto dati quando vuole, e il firmware LM lo elaborerà non
appena possibile. L’idea, quindi, è di implementare nella memoria condivisa, una
coda di pacchetti e un paio di registri di controllo che i vari hardware possono usare perLaelaborarli.
Figura 3.8 mostra l’idea principale alla base di questo schema di gestione
della memoria. Tra i registri, ce ne sono alcuni usati come puntatori alla locazione
di memoria effettiva in cui sono collocati i pacchetti HCI. In un caso (per il registro
LM_BASE_ADDR), questo indirizzamento è diretto, cioè il contenuto del registro
a 16 bit rappresenta l’indirizzo base effettivo del pacchetto. Negli altri casi,
l’indirizzamento è indiretto, e il registro punta alla locazione di memoria in cui è
collocato un descrittore di buffer (BD) per il pacchetto desiderato. Questo BD, a
sua volta, contiene l’indirizzo base del pacchetto.
26
(b)
(a)
(c)
Figura 3.8 - Gestione della memoria.
27
Consideriamo prima questo secondo caso. La Figura 3.8 (c) mostra la struttura
di uno dei quattro puntatori per l’indirizzamento indiretto. I primi tre bit vengono
lasciati vuoti, il che significa che i puntatori salteranno otto byte ad ogni incremento. Questo significa anche che ciascun BD è lungo otto byte. Il campo centrale è il
vero e proprio puntatore: ciascun incremento del valore contenuto in questo campo
farà sì che il registro punti al BD successivo, e quindi selezioni il pacchetto HCI
successivo. Questo campo è lungo 16 byte, il che significa che non ci possono essere più di 16 comandi HCI in memoria in attesa di essere elaborati. IL bit successivo
deve essere inizializzato a 1 all’inizio dell’esecuzione del codice di applicazione/LM, e funge semplicemente da offset: settando questo bit a 1 si fa partire la tabella dei BD dall’indirizzo 0x80 della memoria. L’ultimo bit è un ulteriore offset
che divide la tabella dei BD in due metà, a tutti gli effetti raddoppiando il numero
di BD che possono essere in attesa in memoria, e va settato per soli due dei quattro
registri. Il motivo è spiegato nella Figura 3.8 (b). In ogni dato istante, ci sono due
moduli che scrivono nella memoria condivisa: il modulo che genera i comandi HCI
(il livello applicazione o l’LM), e il controller del bus. Questi due moduli usano
due diversi puntatori per accedere alla tabella dei BD, ma potrebbe accadere che
tentino entrambi di scrivere lo stesso BD. L’offset aggiuntivo evita il problema.
Fondamentalmente, per usare uno di questi registri, dobbiamo semplicemente
aggiungere le seguenti righe all’inizio del codice:
BUS_BDPTR1_CMD
BUS_BDPTR2_CMD
HCI_BDPTR1_CMD
HCI_BDPTR2_CMD
=
=
=
=
1;
1;
1;
1;
BUS_BDPTR1_RSVD
BUS_BDPTR2_RSVD
HCI_BDPTR1_RSVD
HCI_BDPTR2_RSVD
=
=
=
=
0;
0;
0;
0;
BUS_BDPTR2_IN
HCI_BDPTR2_IN
= 1;
= 1;
Dopo questa configurazione, per passare al BD successivo, il codice applicazione
dovrà semplicemente contenere una cosa di questo genere:
BUS_BDPTR1_BDCNT++;
28
(a)
(b)
Figura 3.9 - Descrittore di buffer.
Come si può vedere dalla Figura 3.8 (b), chi genera i comandi usa il registro
HCI_BDPTR1 per leggere la memoria condivisa, e HCI_BDPTR2 per scrivervi.
Lo stesso vale per il controller del bus. L’utilizzo di due puntatori per lato ci permette di accodare un numero arbitrario di comandi, senza dover attendere una risposta.
La Figura 3.9 (a) mostra la struttura di un BD. Ci sono quattro campi, tutti
lunghi due byte. Il primo campo contiene l’indirizzo effettivo del pacchetto il memoria, il secondo il tipo del BD (comando HCI, evento HCI, pacchetto ACL o pacchetto SCO), il terzo l’opcode per il comando o evento HCI, e il quarto la lunghezza del payload del pacchetto. Il campo tipo è necessario dal momento che il formato del pacchetto HCI definito dallo standard non contiene un campo che identifica
il pacchetto, perciò bisogna trasmettere esplicitamente questa informazione sul bus.
Il terzo campo è utile per velocizzare il parsing del payload (magari mediante un
semplice switch-case). Per ciascuno di questi campi c’è una define che rende molto
semplice accedervi. Ad esempio, se l’LM volesse scrivere un nuovo BD, dovrebbe
semplicemente fare qualcosa di questo tipo:
BD_TYPE(HCI_BDPTR2)
BD_OPCODE(HCI_BDPTR2)
BD_PARNUM(HCI_BDPTR2)
= HCI_EVENT;
= HCI_INQUIRY_COMPLETE;
= 1;
Lo stesso vale per ogni modulo che deve/vuole leggere o scrivere un BD.
29
La Figura 3.9 (b) riporta la struttura del campo indirizzo di un BD. L’idea è
simile a quella dietro i registri puntatore. Il bit più significativo è semplicemente un
offset che colloca la coda dei pacchetti HCI dopo la locazione di memoria 0x4000
(scelta arbitrariamente). Il bit OWN è bit che può essere usato sia per creare una
seconda code subito dopo la prima, sia per contrassegnare il BD come “elaborato/ancora da fare” in un protocollo di gestione dei BD più sofisticato. I quattro bit
successivi sono usati come indice, e gli ultimi nove bit possono essere usati come
offset per indirizzare un singolo byte del payload. Abbiamo riservano nove bit in
quanto la massima dimensione del payload permessa per un pacchetto ACL è 384
byte, un numero a nove bit.
Anche per questi campi è presente un insieme di define, che il progettista può
usare per comporre l’indirizzo da leggere/scrivere, ma il campo indirizzo può essere usato anche per un altro scopo. Quando si riceve un pacchetto dal canale, è molto difficile scriverlo nella coda dei pacchetti senza alcuna sovrapposizione con il
controller del bus e/o l’LM. Per gestire questo caso, avremmo bisogno di una terza
coppia di registri puntatore e una terza coda nella memoria condivisa. Questa terza
struttura è sostanzialmente inutile, dal momento che è estremamente improbabile
avere più di un pacchetto “wireless” abbandonato in memoria (potrebbe accadere
solo qualora il bus fosse estremamente lento), dunque abbiamo scelto un approccio
diverso. Il pacchetto ricevuto viene salvato su un particolare indirizzo base, puntato
dal registro LM_BASE_ADDR, dopodiché l’LM crea un nuovo BD e riempie il
campo indirizzo del buffer con il valore contenuto nel registro
LM_BASE_ADDR3. Quando il controller del bus accederà alla memoria per trasferire il pacchetto al livello applicazione, verrà rediretto dal BD all’indirizzo puntato da LM_BASE_ADDR.
Lavorare con una memoria condivisa e dei registri presenta le stesse difficoltà
che si incontrano lavorando con un nuovo microcontrollore: il progettista deve studiare la documentazione e imparare cosa toccare per fare qualcosa. In ogni caso, la
struttura a BD del simulatore è racchiusa in un insieme di primitive che possono
essere chiamate per parsare un pacchetto ricevuto o per inviare un particolare comando. Abbiamo implementato semplicemente la serie di comandi necessari per
creare la rete, e altri verranno aggiunti in lavori futuri, ma l’introduzione di un
3
In realtà ci sono due registri di questo tipo, uno usato per la trasmissione e uno per la ricezione. Il registro usato per la trasmissione viene riempito con l’indirizzo del buffer, così che il trasmettitore non debba preoccuparsi di
dove sono collocati i dati da trasmettere.
30
Figura 3.10 - Baseband.
nuovo comando o evento è semplicemente una questione di dare un’occhiata alle
definizioni dello standard in [19] e poi copiare e incollare i comandi già definiti.
Lavorare a livello BaseBand
Come abbiamo detto prima, il BB è un modello tipo-hardware che implementa
(e nasconde) i dettagli delle comunicazioni Bluetooth sul canale wireless. Il modello sviluppato non è ancora completamente sintetizzabile, ma lo stile di codifica è
quello. Una struttura ad alto livello di questo strato è mostrata nella Figura 3.10. Il
modulo BB_Manager è una macchina a stati che controlla i passi principali delle varie operazioni che il nodo può eseguire: ad esempio, è il BB_Manager che decide
quando un nodo può passare da inquiry scan a page scan. Il Link_Controller invierà poi i pacchetti appropriati rispettando le temporizzazioni definite dallo standard. Internamente, il Link_Controller contiene i moduli di ricezione e trasmissione, il generatore del frequency hopping, un generatore di clock per le temporizzazioni, e una macchina a stati di controllo. Questa macchina a stati di controllo abilita e disabilita il trasmettitore e il ricevitore, a seconda di quanto richiesto dal
BB_Manager, e rispettando la divisone in time slot dell’asse dei tempi. Il trasmetti-
31
tore e il ricevitore eseguiranno le codifiche/decodifiche, genereranno i CRC/check,
e l’effettiva trasmissione/lettura del canale.
Dal punto di vista dello stile di codifica, la differenza principale tra il livello
BB e quello LM o applicazione è che a livello BB i dati e i segnali di trigger/flag
sono portati su linee separate, scimmiottando una vera e propria implementazione
hardware. A livello LM o applicazione, le comunicazioni vengono eseguite quasi
esclusivamente attraverso registri e memoria condivisa. Ad esempio, se l’LM volesse lanciare un nuovo comando per il BB, dovrebbe prima porre il comando in un
registro dedicato, e poi attivare un flag di reset:
BB_COMMAND
BB_SC2_NCRX_BB
= INQUIRY;
= 1;
Al contrario, se il BB_Manager volesse forzare il link controller in un nuovo
stato, dovrebbe attivare una linea di reset e scrivere il comando su un bus separato:
LC_reset
LM_command
= true;
= LISTEN;
Concettualmente è praticamente la stessa cosa, ma l’utilizzare due diversi paradigmi aiuta ad avvicinarsi maggiormente ad una implementazione realistica, e
permette a diversi tipi di progettisti di lavorare nel modo in cui sono maggiormente
avvezzi.
Log in uscita
Il simulatore restituisce in uscita un paio di file di log che contengono i risultati della simulazione. Il primo contiene stringhe di questo tipo:
BER
Payload sizes
Re-transmissions
Time slots
Time elapsed (s)
32
:
:
:
:
:
0.00025
5
24
4383
313
ottenute inserendo dei contatori in punti opportuni del codice. “BER” sta per “Bit
Error Rate”, e definisce il numero di errori introdotti dal canale nel bitstream. Nella
versione corrente, il generatore di rumore è semplicemente un generatore di numeri
casuali basato su LFSR, che inverte un bit ogni 1/BER bit, ma ovviamente questo
generatore può essere sostituito con uno diverso. “Payload sizes” definisce le dimensioni del paylad degli eventuali pacchetti trasmessi, “Re-transmissions” il numero di pacchetti ritrasmessi durante il funzionamento della rete, “Time slots” il
numero di time slot necessari a completare le trasmissioni, e “Time elapsed” il
tempo reale che la simulazione impiega. Si possono comunque aggiungere altri parametri a seconda delle necessità del progettista.
Il secondo log traccia le trasmissioni dei pacchetti dati, sia applicazione che
LM. Questo è, ad esempio, un frammento raccolto durante la creazione della rete:
12520 us Master1
13146 us Slave0
started transmission of a
started transmission of a
POLL packet
NULL packet
Master1 queued LMP_HOST_CONNECTION_REQ PDU
13772 us Master1
Slave0 queued
started transmission of a
DM1 packet
LMP_ACCEPTED PDU
14398 us Slave0
started transmission of a
DM1 packet
Master1 queued LMP_SETUP_COMPLETE PDU
15024 us Master1
Slave0 queued
started transmission of a
DM1 packet
LMP_SETUP_COMPLETE PDU
15650 us Slave0
started transmission of a
DM1 packet
La colonna più a sinistra, riempita da numeri, contiene il timestamp SystemC corrispondente all’istante in cui la trasmissione descritta dalla stringa a seguire è iniziata. Il primo pacchetto inviato dal master è il primo pacchetto POLL che, come vuole lo standard, deve essere trasmesso alla fine della fase di paging. Il NULL in risposta è il primo pacchetto di traffico che, come vuole lo standard, va trasmesso
dopo il primo POLL. Dopodiché, c’è la transazione di pacchetti LM necessaria per
la creazione del primo link logico.
33
Questo tipo di log può essere utile anche per lo studio delle prestazioni della
rete su un canale rumoroso. Ad esempio, consideriamo quest’altro frammento di
log:
Master1 queued
ACL Data packet
31300 us Master1
33178 us Slave0
33804 us Master1
Slave0 queued
34430
35056
36308
36934
37560
38812
39438
us
us
us
us
us
us
us
started transmission of a
started transmission of a
started transmission of a
DM3 packet
NULL packet
POLL packet
ACL Data packet
Slave0
Master1
Master1
Slave0
Master1
Master1
Slave0
started
started
started
started
started
started
started
transmission
transmission
transmission
transmission
transmission
transmission
transmission
of
of
of
of
of
of
of
a
a
a
a
a
a
a
DM3 packet
POLL packet
POLL packet
DM3 packet
POLL packet
POLL packet
DM3 packet
In questo scenario, il master invia un pacchetto dati DM3, lo slave lo riceve
con successo e restituisce un acknowledge con il successivo NULL. A questo punto, il master riprende il suo polling ciclico. Lo slave riceve il POLL e cerca di rispondere con un pacchetto dati DM3. Sfortunatamente, questo pacchetto viene corrotto dal rumore sul canale, e lo possiamo capire dal fatto che il master continua ad
interrogare lo slave, senza accodare un nuovo pacchetto dati. Anche il primo POLL
va perso nel rumore, e anche lui va ritrasmesso. Questo gioco di ritrasmissioni continua fino a che il DM3 non viene ricevuto con successo.
34
Capitolo 4
Applicazioni
ANALISI DEL BLUETOOTH
Le Figure 4.1, 4.2 e 4.3 mostrano i segnali su alcune linee del simulatore particolarmente importanti. Ci sono i segnali di ingresso e uscita dati da/verso il
master, sui quali possiamo osservare i pacchetti dati scambiati, e le linee che
l’LC_Controller usa per abilitare il ricevitore e il trasmettitore, sia sul lato master
che sul lato slave. Queste linee possono essere usate per identificare le perdite di
sincronizzazione tra i dispositivi, come pure eventuali errori nello schedulino dei
pacchetti. Sono mostrati anche i canali usati per il frequency hopping.
La Figura 4.1 mostra cosa accade durante la fase di inquiry, nel processo di
creazione della rete. Il master manda di continuo il pacchetto ID, cambiando ogni
volta il canale di trasmissione e inviando due pacchetti per time slot, come lo standard richiede. Lo slave, al contrario, ascolta di continuo lo stesso canale. Quando il
master trasmette su un canale diverso da quello su cui lo slave è in ascolto, ovviamente sul time-slot di ricezione non ci sarà alcuna risposta, e la linea di abilitazione
del ricevitore resterà attiva solo per la durata della finestra di ricezione predefinita.
Quando il master trasmette, per puro caso, sul canale dello slave, quest’ultimo catturerà il pacchetto del master e risponderà nel time slot di ricezione appropriato.
L’attività sul canale tiene attivo il ricevitore per tutta la durata della trasmissione, e
il pacchetto viene trasferito con successo ai livelli superiori. L’intervallo di tempo
durante il quale il ricevitore è abilitato può essere usato anche per trovare pacchetti
corrotti dal rumore: quando viene trovato un errore sul CRC dell’header, o durante
la decodifica del payload (nel caso dei pacchetti DM), il ricevitore smetterà di ascoltare il canale, per risparmiare potenza. Da questo punto di vista, il tempo durante il quale questa linea è tenuta attiva può essere usato anche per stimare il consumo di potenza del ricevitore.
La Figura 4.2 mostra cosa accade al termine di una inquiry conclusa con successo, ossia durante il paging. Come nel caso dell’inquiry, il master inizia a
35
Figura 4.1 - Tracing durante l’inquiry.
Figura 4.2 - Tracing durante il paging.
Figura 4.3 - Tracing durante uno scambio dati.
trasmettere ripetutamente il pacchetto ID, cambiando canale ogni volta, mentre lo
slave ascolta sempre lo stesso canale. Quando c’è una corrispondenza, il master invia un pacchetto FHS, e dopo il primo pacchetto POLL master e slave sono sincronizzati. A questo punto può iniziare lo scambio di pacchetti LM che permette ai livelli superiori dello slave di confermare o rifiutare la connessione appena stabilita.
Nel caso mostrato, lo slave accetta la richiesta e può iniziare il trasferimento dati.
Un frammento di trasferimento dati è mostrato nella Figura 4.3, in cui il
master e lo slave si mandano ciascuno un pacchetto DM3. Il pacchetto DM3 occupa tre time slot: è questo il motivo per cui c’è così tanto spazio tra due trasmissioni
consecutive. I pacchetti inviati contengono rispettivamente 27 e 20 byte, perciò i
tre time slot sono sostanzialmente vuoti. Una volta ricevuto con successo un pacchetto dal master, lo slave deve sempre rispondere con qualcosa. Nel primo caso,
non ci sono dati pronti per la trasmissione, e quindi lo slave invia un pacchetto
NULL come acknowledge. A questo punto, il master necessita di un pacchetto dallo slave, perciò inizia ad interrogarlo. Dopo il primo polling, lo salve si ritrova con
36
un nuovo pacchetto dati pronti, e lo invia al master. Notare la diversa durata di
tempo tra i pacchetti a singolo slot POLL e NULL, e il DM3.
I tracing nelle Figure 4.1 e 4.2 evidenziano il come la fase di creazione della
rete possa richiedere molto tempo: in questo caso, lo slave riceve il pacchetto ID
del master solo dopo pochi time slot, ma si tratta di una situazione fortunata. Inoltre, il tracing nella Figura 4.3 mostra come anche i pacchetti multi-slot possano essere onerosi in termini di tempo, se contengono solo piccoli frammenti di dati. Anche se la fase di creazione della rete non può essere alterata, ha senso eseguire simulazioni complete al fine di avere un’idea di quanto tempo e lavoro richieda, e
per sapere se potrebbe essere o meno un problema per l’applicazione. Dal punto di
vista del trasferimento dati, prima di iniziare a ottimizzare il protocollo di gestione
del link, ha senso eseguire delle simulazioni complete per scoprire quanto tempo,
in media, può richiedere una trasmissione.
Come abbiamo detto nel capitolo precedente, il problema principale con il frequency hopping è che inizialmente master e slave sono fuori sincronia, e la procedura di sincronizzazione è piuttosto complicata. Il motivo per cui sono fuori sincronia è che la sequenza di hopping viene ricavata a partire dal clock interno, e in
generale i clock di master e slave non avranno lo stesso sfasamento. Il tempo necessario per creare una piconet, quindi, dipende pesantemente da quanto sfasati sono master e slave. Il simulatore ci consente di cambiare la fase iniziale del generatore di clock interno, e valutare quindi l’overhead dovuto alla creazione della rete
in modo più statistico.
Un secondo problema è che al giorno d’oggi ci sono piconet ovunque. Oltre ai
chiari problemi di interferenza, un ulteriore fattore è la scoperta, da parte del
master, di uno slave che non vuole unirsi alla sua rete, e viceversa. Quando uno
slave rifiuta la connessione ad una piconet, lo standard gli impone di attendere un
prefissato numero di time slot prima di tornare in inquiry scan. Questi periodi di
attesa aumentano ulteriormente il tempo necessario alla creazione di una piconet.
Più sono gli slave, più sono le piconet, più serio è il problema. La situazione peggiora ulteriormente se si tiene conto anche del rumore nel canale. Un pacchetto corrotto può forzare lo slave a saltare in page scan anche se la fase di inquiry non è
stata ancora completata.
Le Figure 4.4 e 4.5 mostrano rispettivamente i time slot e il tempo di simulazione necessari per creare una piconet, variando l’entità del rumore nel canale. I
risultati dati sono una media su venti simulazioni, ottenute per venti sfasamenti casuali dei clock. Come ci potevamo aspettare, il tempo aumenta con il rumore, ma si
37
Figura 4.4 - Time slot necessari alla creazione della rete.
Figura 4.5 - tempi di simulazione relativi ai risultati della Figura 4.4.
38
Figura 4.6 - Time slot necessari per la creazione della rete, con più piconet.
Figura 4.7 - Ritrasmissioni durante le simulazioni della Figura 4.6.
39
Figura 4.8 - Tempo di simulazione per i casi delle Figure 4.6 e 4.7.
tratta fondamentalmente di una tendenza. I grafici contengono diverse fluttuazioni, dovute alla casualità del processo. In effetti, in molti casi un po' di rumore
diminuisce il tempo di creazione della rete, probabilmente perché il rumore, interferendo con i meccanismi di inquiry/page, costringe gli slave a saltare più di frequente avanti e indietro tra gli stati di inquiry scan e page, riducendo così la probabilità di collisione durante la risposta ad un pacchetto del master. Il tempo di simulazione segue l’andamento dei time slot. COme possiamo vedere dalle curve, al simulatore occorrono solo dieci minuti per creare una rete.
Le Figure dalla 4.6 alla 4.8 mostrano la media su venti simulazioni, variando il
numero di slave e di piconet coesistenti, ma senza rumore nel canale. La Figura 4.6
presenta il numero di time slot necessari per creare le piconet, la Figura 4.7 quello
dei pacchetti LM da ritrasmettere a causa di collisioni sul canale, e la Figura 4.8
l’effettivo tempo impiegato dalla simulazione. Considerando che ogni time slot dura 625 µs, la Figura 4.6 ci dice che, in media, la creazione di tre piconet complete
può richiedere fino ad un massimo di venti secondi, mentre la creazione di due circa 10 secondi, e la creazione di una sola piconet leggermente meno di 2 secondi.
La Figura 4.7 spiega il perché di questi tempi: le collisioni crescono in modo significativo all’aumentare del numero di piconet. Inoltre, nonostante queste collisioni
40
Figura 4.9 - Time slot necessari per la trasmissione di 10 kbyte di dati.
Figura 4.10 - Ritrasmissioni durante le trasmissioni della Figura 4.9.
41
Figura 4.11 - Tempi di simulazione per i casi delle Figure 4.9 e 4.10.
siano praticamente indipendenti dal numero di slave in ciascuna piconet, esse aumentano considerevolmente con tre piconet. La Figura 4.8 evidenzia il problema
principale di questo tipo di simulazioni: il tempo necessario per la simulazione della creazione di tre piconet è più di quattro ore! Fortunatamente, per creare una sola
piconet il simulatore impiega al massimo dieci minuti, un tempo accettabile, e se
l’applicazione richiede più di una piconet, si può sempre crearle una dopo l’altra,
anziché contemporaneamente.
Abbiamo detto prima che il tempo necessario per trasmettere una certa quantità di dati può dipendere da molti parametri, come il tipo di pacchetto usato, quanti
dati ci sono in ogni pacchetto, e ovviamente il rumore sul canale. In molte applicazioni che richiedono la trasmissione di lunghi segmenti dati (come un’applicazione Web), è importante ottimizzare il tempo di trasmissione lavorando su questi
parametri. Le Figure dalla 4.9 alla 4.11 mostrano rispettivamente i tempi di trasmissione (in time slot), le ritrasmissioni, e il tempo di simulazione necessari per
inviare 10 kByte di dati da un master ad uno slave, per diversi tipi di pacchetto e
diversi BER. Ogni curva è la media su cinque trasmissioni a ciascun BER. Queste
trasmissioni sono state ottenute riempiendo il pacchetto al 20, 40, 60, 80 e 100%
della capacità massima.
42
Come ci potevamo aspettare, il tempo di trasmissione è minimo per il pacchetto più grande, il DH5, e massimo per il pacchetto più piccolo, il DM1. Cosa interessante, le prestazioni in trasmissione sono sostanzialmente identiche usando il
DM5 e il DH3 ai bassi BER, mentre il DH3 si dimostra essere più efficiente agli
alti BER, nonostante il suo payload non sia protetto da un FEC. Il motivo è spiegato dalla Figura 4.10, in cui si vede chiaramente come le ritrasmissioni usando un
DH3 siano minori di quelle usando un DM5. Il motivo dietro questo comportamento potrebbe essere che il DH3 è più corto del DM5, e quindi meno soggetto al rumore. Il DM5 è più lungo, ma protetto con un FEC. Essendo più corti, occorrono
più pacchetti DH3 per completare la trasmissione, e questo compensa la bassa sensibilità al rumore. Il trade-off tra sensibilità al rumore e dimensioni del payload si
annullano a vicenda fino a quando il rumore non si fa molto pesante. In questo caso, il FEC usato per proteggere il payload diventa inefficace, e i più corti DH3 iniziano a dominare. Da questi grafici, è evidente come, se volessimo usare la rete in
un ambiente molto rumoroso, la scelta migliore sarebbe sempre data dai DH, anche
se non sono protetti dal rumore.
La Figura 4.11 mostra il tempo di simulazione necessario per completare questa trasmissione. Come nell’esempio precedente, questo tempo segue da vicino il
tempo di trasmissione nella Figura 4.9. Quando il rumore è basso, possiamo vedere
come il simulatore richieda meno di un minuto per eseguire questo trasferimento
dati se usa i pacchetti multi-slot, e meno di quattro minuti negli altri casi. Quando il
rumore si fa più alto, il tempo di simulazione aumenta piuttosto rapidamente, di pari passo con le ritrasmissioni.
AUDIO SU BLUETOOTH
Un’altra applicazione simulata riguardava la trasmissione di audio musicale
real-time di alta qualità su Bluetooth. L’idea nasce dal voler liberare i gruppi musicali emergenti e i club dal problema del cablaggio, che rappresenta una buona fetta
del tempo necessario alla preparazione di un live. In commercio esistono diverse
soluzioni per la connessione wireless di strumenti elettrici e microfoni, ma si tratta
di connessioni punto-punto dall’alto costo e che vanno ad eliminare un solo cavo,
quello che va dallo strumento\microfono all’amplificatore o al mixer. Lo scopo di
questo lavoro è quello di andare ad eliminare la maggior parte delle connessioni
43
cablate, sfruttando i moderni standard wireless. Ci siamo concentrati nell’utilizzo
di standard commerciali, per ovvie ragioni di costi e per la vasta disponibilità di
apparati.
In un progetto di questo tipo, bisogna dimensionare adeguatamente due componenti: il codec audio, e il protocollo wireless. Il codec audio deve essere sufficientemente potente da comprimere il segnale musicale in modo da adattarsi alle
capacità del protocollo wireless, mantenendone alta la qualità, e contemporaneamente non introdurre troppa latenza, così da mantenere la specifica sul real-time. Il
protocollo wireless deve avere un data rate sufficientemente elevato da sostenere il
flusso audio proveniente dal codec, e anche lui introdurre meno latenza possibile.
Lavori esistenti
L’idea in sé non è nuova, e in letteratura ci sono diversi lavori analoghi, alcuni
che affrontano maggiormente il problema del real-time, altri che si preoccupano di
più della qualità audio. Ad esempio, gli autori di [21] propongono un transceiver
audio in tempo reale utilizzando la tecnologia IEEE 802.11b. Viene implementato
su un microcontrollore ad 8 bit un controllo di un trasmettitore IEEE 802.11b su
Compact Flash card. Il micro gestisce anche un ADC esterno, collegato ad esso
tramite lo standard SPI. La scheda 802.11b viene configurata per funzionare in ad
hoc mode, di modo che trasmettitore e ricevitore si colleghino tra di loro senza bisogno di un access point. Il data rate scelto è di 2 Mbps, per evitare i tempi di negoziazione della velocità ottimale. Le misure effettuate sulla latenza del sistema,
mostrano che la rete WiFi riesce a trasmettere un pacchetto contenente 5 ms di audio campionato a 44.1 kHz (equivalente a 442 byte), in un tempo tra i 640 ed i
1100 µs. Il ritardo totale però risulta essere di 15 ms per via della scarsa potenza di
calcolo del microcontrollore utilizzato.
Un altro lavoro interessante riguarda il multicasting in tempo reale su una piconet Bluetooth [22]. Il protocollo proposto in questo articolo si compone di tre fasi:
1. Real-Time Audio Notification: inizialmente, il sistema è formato da una
sola piconet. Se uno slave ha un file audio da condividere lo notifica al
Master, che poi effettua un broadcast a tutti i nodi. Uno slave che vuole
unirsi allo stream real-time deve spedire un pacchetto di richiesta. È consentito un solo flusso multicast per volta.
44
2.
3.
Piconet Partitioning: se più di un dispositivo richiede di unirsi alla trasmissione allora tutti i dispositivi vanno nella fase di divisione. La piconet corrente si divide in due, una che supporta il multicast ed una no.
Streaming: La sorgente multicast inizia a mandare pacchetti DH5 al
master, che li rimanderà in broadcast utilizzando il BNEP (Bluetooth
Network Encapsulation Protocol).
I risultati delle simulazioni dimostrano che questo metodo permette il multicasting
in tempo reale fino a 128 kbps con un collegamento ACL.
Buoni risultati per lo streaming della voce sono possibili anche su reti a basso
data rate come l’IEEE 802.15.4. In [23] gli autori sviluppano un sistema completo
di trasmissione di segnali audio. Partendo da zero sono state combinate diverse
tecniche con l’obiettivo di ottenere la migliore implementazione per un microcontrollore a basso costo e bassa potenza. Gli autori hanno usato un sistema operativo
in tempo reale, un modello psicoacustico basato sulla FFT dei segnali e la trasformata di Haar per creare un nuovo algoritmo di compressione mirato ai sistemi integrati con bassa potenza di calcolo. Il risultato è un sistema completo per lo streaming della voce in tempo reale sul IEEE 802.15.4 con una qualità accettabile.
Quasi tutti i collegamenti wireless moderni utilizzano un qualche tipo di meccanismo di ARQ (Automatic Repeat Request) per proteggere i pacchetti da un canale disturbato, e il Bluetooth non è da meno. Si tratta di un approccio vantaggioso
se le trasmissioni avvengono offline, ma va usato in modo prudente nelle applicazioni real-time, dal momento che il ritardo introdotto dalle ritrasmissioni potrebbe
introdurre ritardi eccessivi. Lavorando a partire da quest’idea, gli autori di [24]
hanno sviluppato un algoritmo di timeout adattivo per l’ARQ. Il lavoro dimostra
che un limite di tempo selezionato in modo adattivo, in base alla storia delle ritrasmissioni, ha performance migliori di un limite fisso. L’implementazione del sistema è stata fatta su un testbed Linux Bluetooth. I risultati delle simulazioni dimostrano un ritardo medio molto migliore dei pacchetti (50 ms), anche in condizioni
di elevato BER (0.004) con packet loss di meno del 10 %, mentre lo schema classico inizia a dare problemi già con BER < 0.002.
Altri lavori invece si preoccupano maggiormente della qualità audio. Ad esempio, gli autori di [25] hanno sviluppato un protocollo per la trasmissione di audio di alta qualità tramite il Bluetooth, ad uno o due casse acustiche. Attraverso
l’uso di meccanismi di adattamento del data rate della rete e del bitrate del codec,
45
viene raggiunta una qualità audio vicina a quella di un CD. Il nome del protocollo
sviluppato è BlueDAT.
La piattaforma BlueDAT consiste in due parti: un riproduttore e lo speaker. Il
primo trasmette i pacchetti audio tramite un link ACL. Si possono collegare fino a
due speaker, così da ottenere la riproduzione stereofonica. Lo stesso link ACL viene usato per la trasmissione dei parametri di configurazione utente. Tutti e due i
flussi audio sono multiplexati utilizzando il L2CAP. Le informazioni sullo stato
dello speaker vengono mandate al player regolarmente al fine di adattare il data rate, per evitare lo svuotamento del buffer oppure gli overflow. La grandezza del
buffer varia da 5 a 10 secondi di audio. All’inizio della riproduzione vengono effettuati 100 ms di pre-buffering, valori superiori risultano inaccettabili in quanto introducono un ritardo troppo elevato. Con le informazioni sullo stato del buffer viene anche variato il bitrate del codec in tempo reale, per adattare la compressione in
funzione della qualità del collegamento. Il codec utilizzato è l’MP3.
I risultati dei test dicono che una qualità di 320 kbps può essere raggiunta solo
senza interferenze. In caso di rumore i meccanismi di ritrasmissione e di adattamento permettono una riproduzione senza gap, ma riducono la qualità fino a 64
kbps. Nella riproduzione stereo è evidente la necessità di un meccanismo di sincronizzazione.
Il problema della sincronizzazione nella trasmissione in reti stereo point-tomultipoint stereo su Bluetooth viene affrontato dagli autori di [26]. I protocolli utilizzati dai dispositivi sono il profilo Headset, l’Hands-Free Profile, e l’A2DP. I
primi due usano dei collegamenti SCO, che hanno un datarate troppo basso per
l’audio Hi-Fi. L’A2DP utilizza invece i link ACL che hanno un datarate massimo
di 723.2 kbps. In più gli autori usano anche un codec SBC (Subband Codec) per
l’audio a medi bitrate.
In [27] viene sviluppato invece un metodo per lo streaming di audio memorizzato sotto forma di Ogg Vorbis ed MP3. Il setup di simulazione consiste in due
laptop con una distribuzione di linux e degli adattatori Bluetooth. Lo stack Bluetooth utilizzato è il BlueZ, mentre per la codifica viene usato Java Sound. Un server
esegue lo streaming di pacchetti UDP al client mobile sotto diverse condizioni:
1.
2.
3.
46
Ideali: senza interferenze,
Interferenze di spettro: con un interferente IEEE 820.11g,
In movimento: il client viene mosso di un 1 metro ogni 2 secondi nel raggio di 12 metri dal server.
Lo strato L2CAP esegue un numero infinito di ritrasmissioni del pacchetto in caso
di errore, attraverso il BNEP (Bluetooth Network Encapsulation Protocol) [19]. I
risultati delle simulazioni indicano che il Bluetooth è un mezzo adatto allo streaming di audio archiviato e compresso. La configurazione ottima include i pacchetti
da 56500 byte con un buffer iniziale della stessa dimensione. L’affidabilità del
L2CAP aumenta la qualità audio, anche se a prima vista sembrerebbe impedirne la
riproduzione continua per via delle ritrasmissioni. Il Bluetooth è abbastanza robusto da resistere alle interferenze a cui è stato sottoposto con solo piccoli degradi di
qualità.
Un protocollo per lo streaming di audio tra due dispositivi Bluetooth è stato
presentato in [28]. Il prototipo sviluppato utilizza l’AVDTP per riprodurre l’audio
trasmesso attraverso il Bluetooth ed una rete IP usando il BNEP. Nel sistema sviluppato il dispositivo di Sink Bluetooth avvia la sessione di streaming su un server
utilizzando il BNEP. Per selezionare il dispositivo che eseguirà la riproduzione
dell’audio, l’applicazione invocherà l’Inferface Module, che identificherà le cuffie
Bluetooth da usare e creerà un link ACL tramite lo strato AVDTP. Quando la connessione è stabilita, l’interfaccia estrae l’audio dallo strato BNEP dello Streaming
Sink, per poi mandare l’audio al corrispondere strato AVDTP, che lo trasferirà alle
cuffie Bluetooth.
Nessuno di questi sistemi è però pensato per trasmettere su una rete wireless
un flusso audio ad alta qualità prodotto all’istante da dei musicisti, come si può avere su un palco o in una sala prove. La natura “on fly” della produzione dell’audio
da parte dei musicisti non permette di effettuare pre-buffering lunghi, che andrebbero a creare un fastidioso ritardo di riproduzione. Oltretutto, per permettere ai musicisti di sincronizzarsi tra di loro occorrerà tenere sotto controllo il ritardo di trasmissione. Per ottenere sia bassi ritardi che buona qualità audio, in relazione alla
bassa banda passante dei sistemi wireless, è necessario utilizzare moderni codec a
bassa latenza, come l’Opus o il WavePack. Come sia possibile progettare, dimensionare e simulare un sistema di questo tipo è l’argomento della prossima sezione.
Architettura del sistema
Dal punto di vista delle specifiche, occorre trovare un protocollo wireless con
data rate sufficientemente elevato da supportare l’elevato bit rate richiesto da un
flusso audio ad alta qualità, e che allo stesso tempo non introduca troppa latenza.
47
Figura 4.12 - Ritardi su un palco da concerto.
Per quanto riguarda il codec audio, ci serve un codec che non introduca considerevoli latenze di buffering, né eccessivi ritardi algoritmici, e che sia in grado di comprimere il flusso audio il più possibile mantenendone la qualità la più elevata possibile.
I ritardi sono il problema peggiore. Il motivo principale per la loro presenza è
la relativamente lenta velocità di propagazione del suono. Se per esempio ci posizionassimo a 1,5 metri da una cassa audio e parlassimo in un microfono, saremmo
di fronte ad una latenza di 5 ms. A questo, va a sommarsi il ritardo introdotto dal
nostro apparato digitale. Il ritardo complessivo deve essere sufficientemente basso
da non disturbare l’ascolto e da permettere ai musicisti di tenere il tempo, ossia di
sincronizzarsi tra loro e con se stessi.
La Figura 4.12 sintetizza i ritardi presenti nel sistema. Il ritardo tra l’uscita e il
pubblico rappresenta un problema decisamente poco critico, in quanto gran parte di
esso è dato semplicemente dalla distanza tra pubblico e amplificatori. Inoltre, se il
livello di intensità sonora della sorgente diretta è molto minore di quella amplificata, al pubblico arriverà solo quest’ultima, evitando il fastidioso effetto eco che si
verifica quando due suoni (uno versione ritardata dell’altro) si sovrappongono.
Molto più problematica è la latenza sulla catena sorgente-monitor, che porta il
suono dallo strumento elettrico ai musicisti ed ha la funzione di far sentire cosa
stanno suonando gli altri membri del gruppo, così da permettere la sincronizzazione tra di essi. Come si vede dalla figura, l’obiettivo è evitare l’effetto eco tra il suono diretto e quello che esce dalla cassa spia.
Studi hanno mostrato che esiste una differenza sostanziale tra il ritardo accettabile quando si utilizzano dei monitor in-ear (delle cuffie), e quando si utilizzano
48
delle casse acustiche. Da [29] risulta che, se si utilizzano delle cuffie in-ear, per
molti musicisti già sia impossibile suonare con un ritardo superiore ai 15 ms. Il motivo è da cercarsi nelle interferenze di fase che si vanno a creare nella cavità
dell’orecchio. La sincronizzazione tra due musicisti che si trovano a suonare con
strumenti acustici, o con casse acustiche, viene mantenuta invece con ritardi superiori a 20 ms (di separazione spaziale) [30]. Gli esperimenti condotti in questo lavoro sono stati eseguiti ponendo due musicisti in stanze separate (isolati sia visualmente che uditivamente), introducendo artificialmente della latenza, con
l’obiettivo di simulare una prestazione via internet. Da questo studio risulta che due
musicisti iniziano a perdere il tempo tra di loro e rallentare il tempo di esecuzione
per latenze comprese tra 20 e 30 ms.
Dal punto di vista del protocollo wireless, questo implica che il protocollo, oltre ad avere una banda sufficiente per contenere il flusso audio, deve essere in grado di trasferire i dati provenienti dai vari strumenti/musicisti nel giro di al massimo
una ventina di millisecondi. Questo implica a sua volta che l’accesso al mezzo deve
essere deterministica, ossia non deve introdurre intervalli casuali tra la trasmissione
di un pacchetto e l’altro. Questo esclude la maggior parte dei chip 802.15.4 e
802.11: per quanto esistano versioni di questi standard che supportano un accesso
al mezzo basato su time slot, è molto difficile trovare chip commerciali che le implementino. Resta il Bluetooth, che oltre ad essere poco costoso, nella versione 2.1
porta il data rate fino a 3 Mbit/s, che con un’opportuna compressione può essere
sufficiente per i nostri scopi qui.
Note i payload massimi per pacchetto Bluetooth, possiamo calcolare il bitrate
massimo a cui far lavorare il nostro codec per reti e frame audio di diversa dimensione. Ad esempio, per un frame da 10 ms e una rete con cinque slave, ad ogni slave si potranno riservare 2 ms per la trasmissione dei propri dati. Considerato che il
Bleutooth alloca slot di 625 µs per il polling dal master e 625 µs per l’invio dei dati
dallo slave, per un totale di 1.25 ms, questo significa che con cinque slave possiamo usare solo pacchetti ad uno slot, come il DM1. Il DM1 ha come payload massimo 16 byte, per un data rate massimo di 12.8 kbit/s. I risultati per tutti i pacchetti
sono riassunti nella Tabella 4.1. I risultati per sei e sette slave sono identici al caso
a cinque slave. Questi valori sono ottenuti considerando idealmente nulli i tempi di
codifica e di bufferizzazione, ma sono indicativi della qualità audio che possiamo
aspettarci.
49
Tipo di
pacchetto
DM1
DM3
DM5
DH1
DH3
DH5
2-DH1
2-DH3
2-DH5
3-DH1
3-DH3
3-DH5
10ms
12.8
96
178
20.8
145.6
270.4
42.4
292.8
542.4
65.6
440.8
816
1 slave
5ms
25.6
192
356
41.6
291.2
540.8
84.8
585.5
1084.8
131.2
881.6
1632
2.5ms
51.2
384
83.2
582.4
169.6
1171.2
262.4
1763.2
-
10ms
12.8
96
178
20.8
145.6
270.4
42.4
292.8
542.4
65.6
440.8
816
2 slave
5ms
2.5ms
25.6
51.2
192
41.6
83.2
291.2
84.8
169.6
585.5
131.2 262.4
881.6
-
10ms
12.8
96
20.8
145.6
42.4
292.8
65.6
440.8
-
3 slave
5ms
2.5ms
25.6
41.6
84.8
131.2
-
10ms
12.8
96
20.8
145.6
42.4
292.8
65.6
440.8
-
4 slave
5ms
2.5ms
25.6
41.6
84.8
131.2
-
10ms
12.8
20.8
42.4
65.6
-
Tabella 4.1 - Bit rate per tipo di pacchetto. I valori sono in kbit/s.
Dalla Tabella 4.1, possiamo dedurre che il nostro codec, oltre a dover essere in
grado di lavorare con frame molto piccoli, dell’ordine dei 10 ms, dovrà anche fornire un’ottima qualità audio per bit rate dell’ordi-ne dei 64 kbit/s. Consideriamo i
codec attualmente più diffusi:
1.
2.
3.
4.
50
MP3: l’algoritmo di compressione audio più diffuso. La qualità audio è
ottima per alti bitrate, ma degrada velocemente al diminuire di questi
[31]. Ottimizzato per la riproduzione, l’econder risulta piuttosto pesante
dal punto di vista computazionale, e richiede un buffer di campioni piuttosto lungo per far iniziare la compressione. Questo porta a latenze
dell’ordine dei 100ms [32].
OGG: qualità audio migliore del MP3 a bit-rate bassi [31], ma presenta
gli stessi problemi dell’MP3.
WavPack: a differenza degli altri codec presenta una qualità audio ottima
già intorno ai 105kbit/sec per canale [31], può essere usato con blocchi di
campioni molto piccoli ed ogni blocco di uscita risulta indipendente dal
precedente, aumentando così la resistenza al rumore [33]. Non supporta
però bit rate inferiori a 96 kbit/s.
OPUS: algoritmo espressamente sviluppato per trasmissione di audio di
qualità su Internet, è di recente standardizzazione (12 settembre 2012).
Ha un ritardo algoritmico minimo di 2.5 ms nelle ultime versioni [34].
Nel caso di pacchetto perso, vengono utilizzate le informazioni relative al
pacchetto precedente per ricostruire una nota da riprodurre.
5 slave
5ms 2.5ms
-
Figura 4.13 - Confronto qualità oggettiva tra i codec considerati.
Figura 4.14 - Prestazioni di Opus per diverse lunghezze del frame.
51
Da questa breve carrellata si notano i limiti del MP3 e del OGG nel caso di trasmissione audio in tempo reale. Il loro elevato ritardo algoritmico e l’asimmetria
tra encoder e decoder non li rendono i candidati ideali per la nostra applicazione. Il
Wavpack e l’OPUS invece presentano caratteristiche più interessanti, se non fosse
che il Wavpack non supporta i 64 kbit/s: la scelta, a questo punto, va fatta sulla base della qualità del segnale prodotto. Qualora il Wavpack fosse notevolmente migliore dell’Opus, bisognerà porre limiti alle dimensioni della rete, per supportare i
suoi 96 kbit/s.
La Figura 4.13 mostra la qualità dei segnali prodotti da Opus, WavPack e (per
confronto) MP3. Sulle ascisse è riportato un indice di qualità oggettivo ottenuto
applicando il PEAQ ai tre algoritmi, dove 0 è il minimo e indica prestazioni pessime, e 5 è il massimo e indica un segnale compresso sostanzialmente indistinguibile
dall’originale. Opus è notevolmente migliore degli altri due ai medi bit rate, mentre
diventa leggermente peggiore ai bit rate più alti. La Figura 4.14 invece mostra la
qualità dei segnali prodotti da Opus al variare della lunghezza del frame. La qualità
migliore la si ottiene per frame di 10 ms, il che rende Opus la scelta perfetta per i
nostri scopi.
Abbiamo simulato due architetture per questo sistema, una a stella e una punto-punto. La Figura 4.15 (a) mostra l’architettura a stella, in cui un singolo master
controlla più slave e ne raccoglie i dati secondo un semplice polling ciclico. La Figura 4.15 (b) contiene una linea dei tempi che possiamo studiare per ricavare il ritardo massimo in questa configurazione. Le procedure di acquisizione e compressione avvengono parallelamente su ognuno dei dispositivi in ingresso: finita la
prima fase di acquisizione e compressione, il Master avvia la procedura di polling,
dove chiede ad ogni dispositivo di input di trasmettere il dato compresso. Ricevuto
l’ultimo pacchetto il Master avvia la decompressione e la successiva elaborazione
di mixing, per poi riprodurre il segnale attraverso il DAC. Dalla Figura 4.15 (b) è
chiaro che, dal momento in cui parte l’acquisizione del segnale audio nei dispositivi di input, al momento in cui il segnale viene riprodotto sui dispositivi di output,
passa un tempo pari alla durata di due buffer di ingresso, il che ci porta ad un ritardo massimo di 20 ms.
Nell’architettura punto-punto, invece, c’è un master ed uno slave per ogni nodo, quindi più piconet in parallelo, e un accentratore che raccolga i dati dei vari
master e li mixi insieme. Questa struttura è mostrata nella Figura 4.16 (a), e due ipotetiche linee dei tempi nella Figura 4.16 (b). Grazie al fatto che non dobbiamo
fare uno scheduling degli slave della piconet, si può usare un pacchetto più lungo
52
(a)
(b)
Figura 4.15 - Architettura a stella e relativo diagramma delle temporizzazioni.
53
(a)
(b)
Figura 4.16 - Architettura punto-punto e relativo diagramma delle temporizzazioni.
54
Figura 4.17 - Prestazioni e BER.
Figura 4.18 - Prestazioni e numero di piconet.
55
(ed aumentare perciò data rate, qualità, o protezione dal rumore) e/o prevedere la
ritrasmissione in caso di errore, cosa impossibile nell’architettura a stella.
Supponiamo di utilizzare i pacchetti da tre time slot del Bluetooth, ossia il
DM3, il DH3 e le loro rispettive versioni EDR. Avremo in tutti i casi un bitrate di
96 kbit/s o più che rispetta le nostre specifiche di qualità audio per l’Opus.
L’utilizzo del FEC (Forward Error Correction) sul DM3 ci porterebbe a considerarlo il candidato ideale per quanto riguarda la robustezza agli errori, ma i tempi di
codifica e decodifica vanno ad aggiungere ulteriore ritardo nella catena. Il DH3,
rappresenta invece un buon compromesso tra lunghezza di trasmissione e bitrate
permesso. Gli schemi dei tempi in figura sono stati disegnati assumendo che il
tempo di compressione e quello di decompressione siano pari ad un time slot del
Bluetooth ovvero a 625 µs. Quindi, nel caso senza ritrasmissioni abbiamo un ritardo di 13.125 ms, e nel caso con una singola ritrasmissione 15.625 ms. Il ritardo è
minore di quello dell’architettura precedente anche considerando una ritrasmissione del pacchetto, ma ovviamente si tratta di un sistema più costoso e più soggetto al
rumore, data la presenza di più piconet una accanto all’altra.
Queste due architetture possono essere simulate così da valutarne le prestazioni in presenza di rumore. Dal grafico nella Figura 4.17 notiamo come la qualità si
mantenga a valori elevati per i BER di 1/50000 e 1/20000 in tutti i casi. I migliori
risultati si ottengono con l’architettura punto-punto con ritrasmissioni, in quanto si
rimane sopra i 4 di ODG fino ad un BER di 1/5000. Il peggioramento causato
dall’aggiunta delle piconet è sensibile e sicuramente bisogna tener conto del numero massimo di piconet che si possono utilizzare senza incorrere in pesanti degradazioni della qualità audio. In effetti, la Figura 4.18 rende subito evidente come
l’architettura punto-punto senza ritrasmissioni inizi a degradare troppo il segnale
audio già con 4 piconet e per BER bassi.
56
Capitolo 5
Voce su 802.15.4
LA RETE
La compressione LPC
È facile capire la necessità di comprimere il segnale vocale se consideriamo la
banda richiesta da un segnale di questo tipo. Solitamente, un segnale telefonico
viene trasmesso utilizzando una PCM a 8 bit e 8 kHz, con una banda di 64 kbit/s.
Questo data rate può essere trasmesso con facilità su un canale 802.15.4, ma il data
rate nominale massimo offerto dallo standard (250 kbit/s nella banda a 2.4 GHz)
permetterebbe l’allocazione di solo 250 / 64 = 4 nodi. In uno scenario più realistico, il meccanismo a contesa usato dai nodi per accedere al canale diminuisce il data
rate effettivo, riducendo ulteriormente le dimensioni possibili per la rete [35], [36].
Come vedremo tra un attimo, il problema dell’accesso al canale può essere completamente evitato mediante un’opportuna configurazione della rete, per cui per i nostri calcoli qui supporremo di avere a disposizione l’intera banda. In ogni caso,
senza un’adeguata compressione, le reti che si possono costruire restano abbastanza limitate.
La compressione LPC appartiene alle cosiddette tecniche di “analisi-sintesi”
[37]. L’idea dietro questa classe di algoritmi è modellare l’apparato vocale del parlatore con un filtro IIR, estrarre dal segnale da comprimere i coefficienti del filtro,
e usare questi coefficienti per ricostruire, sul lato ricevitore, il segnale vocale come
uscita del filtro sintetizzato. Il filtro va pilotato con un treno di impulsi quando il
segnale contiene informazione vocale, e da rumore bianco quando non ne ha. Un
encoder LPC, quindi, deve estrarre, a partire dal segnale da comprimere, i coefficienti del filtro, il periodo del treno di impulsi (il cosiddetto “periodo di pitch”), e
un flag “voiced/unvoiced”. Ciò che a tutti gli effetti verrebbe trasmesso, quindi, sarebbe questa descrizione del filtro e un fattore di guadagno che regola l’ampiezza
del segnale ricostruito.
57
Figura 5.1 - Encoder LPC.
Tutti questi parametri vengono estratti dal segnale di ingresso mediante lo
schema di codifica mostrato nella Figura 5.1 [38]. Pensando l’apparato vocale come un filtro che lavora su un treno di impulsi (l’aria modulata dalle corde vocali), è
facile capire come il filtro debba essere tempo-variante e il segnale vocale non stazionario: la forma dell’apparato vocale cambia a seconda della particolare lettera
che il parlatore vuole emettere. Dunque, la prima cosa da fare sul lato encoder è dividere il segnale vocale in una serie di finestre sovrapposte, abbastanza corte da poter considerare stazionario il segnale al loro interno. La lunghezza della finestra costituisce un parametro di compressione chiave: una finestra troppo corta “spezzerà”
un singolo fonema in più finestre del necessario, e una finestra troppo lunga raccoglierà più fonemi di quanti il filtro ne possa sintetizzare.
Il contenuto di ciascuna finestra viene poi dato in ingresso ad un correlatore,
che genera la matrice di autocorrelazione associata. Questa matrice può essere usata per stimare il periodo di pitch andando alla ricerca, al suo interno, di pattern ripetitivi. Se viene trovato uno di questi pattern, il frame viene etichettato come
“voided” e il periodo di pitch coinciderà con il periodo della matrice di autocorrelazione. In caso contrario, il frame viene etichettato come “unvoiced” e il period di
pitch sarà nullo.
La stessa matrice può essere utilizzata per stimare i coefficienti del filtro. Nello schema LPC di base, l’apparato vocale viene modellato come un filtro a soli poli, dal momento che la sua risposta in frequenza contiene una serie di picchi (le
“formanti”) che possono essere generate mediante un filtro a soli poli. I coefficienti
di un filtro a soli poli possono essere trovati risolvendo un sistema di equazioni auto-regressivo (AR), che richiede l’inversione della matrice di autocorrelazione.
L’inversione di una matrice solitamente è un’operazione piuttosto lunga, e perciò è
58
Figura 5.2 - Decoder LPC.
importante utilizzare algoritmi di inversione veloci, come il Levinson-Durbin [38].
Un altro parametro chiave da scegliere a questo punto è la lunghezza del filtro. Non
ci sono regole ferree da seguire, ma è intuitivo che, più è lungo il filtro, migliore
sarà la qualità del segnale sintetizzato. Esamineremo meglio in seguito questo problema, ma di solito la lunghezza del filtro viene scelta in funzione della banda del
segnale d’ingresso. Approssimativamente, la lunghezza minima deve essere
dell’ordine di:
dove è la lunghezza del filtro e la banda del segnale vocale, metà della frequenza di campionamento. Frequenze di campionamento usuali sono 8 o (per segnali audio di alta qualità) 16 kHz, perciò possiamo aspettarci filtri lunghi rispettivamente 8 o 164. Per finire, si calcola il guadagno come l’errore quadratico medio
della predizione lineare utilizzata con il Levinson-Durbin [38].
Il modulo di sintesi rispetta il paradigma appena descritto, ed è mostrato nella
Figura 5.2. Se il frame non contiene informazione vocale, il decoder genera un se-
4
A rigore, è l’ordine del filtro, ossia il numero di coefficienti più uno. Il “più uno” in questo modello viene da un coefficiente costante, pari a uno. Ovviamente, non c’è bisogno di trasmettere un coefficiente costante, possiamo semplicemente aggiungerlo sul lato ricevitore, e perciò possiamo parlare
indifferentemente di “ordine” e “lunghezza” del filtro.
59
gnale di rumore bianco, altrimenti un treno di impulsi con il pitch stimato come periodo. In entrambi i casi si moltiplica il segnale generato per il guadagno e poi lo si
filtra con il filtro sintetizzato.
Descriveremo in seguito come scegliere la lunghezza del filtro e quella della
finestra, ma possiamo usare la discussione fatta fino a questo punto per ottenere
un’idea di massima dei rapporti di compressione ottenibili. Consideriamo un segnale vocale a 8 kHz, finestre di 30 ms e filtri a 8 coefficienti. Con una PCM a 8
bit, dovremmo trasmettere 240 byte. Con la compressione LPC, dovremmo trasmettere unicamente gli 8 coefficienti del filtro, il guadagno, il periodo di pitch, e il
flag voiced/unvoiced. Se il nostro sistema lavorasse con aritmetica floating-point a
32 bit, dovremmo trasmettere 10×4 + 1 = 41 byte, con un rapporto di compressione
di circa 6.
Questo rapporto di compressione può essere ulteriormente aumentato, senza
(troppa) distorsione, con un utilizzo intelligente dell’aritmetica fixed-point. Se potessimo usare numeri fixed-point con una precisione di 16 bit, dovremmo trasmettere solo 10×2 + 1 = 21 byte, con un rapporto di compressione di 11. Inoltre, possiamo usare un solo byte per il periodo di pitch e per il flag voiced/unvoiced. In
generale, la frequenza di pitch cade tra 80 e 350 Hz. A 8 kHz, una frequenza di 80
Hz corrisponde ad un periodo di 100 campioni, e una di 350 Hz ad un periodo di 23
campioni. Dunque, il periodo di pitch è sempre un intero minore di 128, e perciò ci
bastano 7 bit per codificarlo e possiamo usare l’ottavo bit per il flag voiced/unvoiced. Dunque, in tutto ci occorreranno solo 9×2 + 1 = 19 byte, e il rapporto di compressione sarà 12.6. La domanda qui è piuttosto come e quanto la riduzione nella precisione nel guadagno e nei coefficienti influirà sulla qualità del segnale
ricostruito, e questo effetto va studiato a dovere, cosa che faremo in un attimo.
La rete 802.15.4
Come abbiamo notato prima, il basso data rate dell’802.15.4 diventa ancora
più basso quando si considera il meccanismo di accesso al canale [35], [36]. Nel
meccanismo di base, non c’è sincronizzazione tra i nodi: ogni volta che un particolare nodo ha bisogno di inviare qualcosa, semplicemente prova a farlo. Il meccanismo utilizzato per ridurre le collisioni è il CSMA/CA: se più di un nodo tenta di
accedere al canale nello stesso momento, viene rilevata una collisioni e il nodo attenderà un intervallo di tempo casuale prima di tentare nuovamente di accede al
60
(a)
(b)
Figura 5.3 - Struttura del superframe.
canale. Ovviamente, più sono i nodi, più è alta la probabilità di collisione, e più è
alto il tempo passato in attesa, e minore il data rate effettivo. Inoltre, questo meccanismo introdurrà ritardi casuali tra due trasmissioni consecutive, situazione da
evitare a tutti i costi in un sistema real-time.
La soluzione viene dalla opzionale struttura a superframe definita dallo standard [39], mostrata schematicamente nella Figura 5.3 (a). Un superframe inizia con
un beacon, un particolare pacchetto che contiene importanti informazioni per la gestione e creazione della rete. Ciascun superframe viene diviso in un periodo attivo,
durante il quale avvengono le trasmissioni vere e proprie, e un periodo inattivo, durante il quale non sono permesse trasmissioni e i nodi possono essere messi in modalità sleep per risparmiare potenza. Il periodo attivo viene ulteriormente ridotto in
16 slot di pari dimensioni, con il primo parzialmente utilizzato dal beacon. Come
mostrato nella Figura 5.3 (b), questi slot sono divisi in due gruppi: quelli che appartengono al Contention Access Period (CAP), e quelli che appartengono al Conten-
61
tion Free Period (CFP). Quanti slot cadono in ciascun periodo viene deciso durante
la creazione della rete, e dipende interamente dall’applicazione. L’importanza di
questa divisione risiede nel diverso meccanismo di accesso al canale usato nei due:
nel CAP, in ogni particolare momento tutti i nodi possono tentare di acceder al canale, e le contese vengono risolte mediante il CSMA/CA. In ogni slot del CFP, invece, al canale può accedere un solo dispositivo. Lo standard permette questa “prenotazione degli slot” mediante il raggruppamento di più slot in un cosiddetto “Guaranteed Time-Slot” (GTS). L’utilizzo dei GTS rappresenta l’unico modo per implementare una rete 802.15.4 realk-time e a bassa latenza, e perciò questa allocazione è un punto chiave del progetto. Per avere un sistema funzionante, i GTS devono soddisfare un certo numero di condizioni:
1.
2.
3.
4.
ogni GTS deve essere sufficientemente lungo da consentire ad un dispositivo di inviare un pacchetto dati contenente uno (o più) frame LPC;
ogni superframe deve essere abbastanza lungo da consentire a ciascun
nodo di inviare i propri dati;
ogni pacchetto deve contenere abbastanza dati LPC da coprire la durata
temporale del superframe;
i vari superframe non possono essere troppo lungo, così da ridurre la latenza della trasmissione.
Dunque, i parametri di rete vanno scelti con molta attenzione. La Figura 5.4
riporta le primitive del livello MAC che i vari nodi possono usare a questo scopo.
In una rete a stella, il livello applicazione del coordinatore PAN usa la primitiva
START_request per istruire il livello MAC su come formare la rete: se la rete userà
i beacon, quanto lungo deve essere il superframe, e così via. Dal punto di vista dello slave, il livello applicazione può usare la SYNC_request per richiedere al MAC
di scoprire e tracciare i beacon, e la GTS_request per comunicare al MAC del coordinatore che il nodo vuole usare i GTS. Se il master accetta questa richiesta, nel
CFP verrà allocato un nuovo GTS, e il nodo riceverà una GTS_confirm [39].
Ci sono tre parametri da scegliere: la lunghezza del superframe, il numero di
GTS, e la lunghezza di ciascun GTS. Come mostrato nella Figura 5.5, lo standard
impone una lunghezza minima per il superframe, e permette al progettista di scalare questa lunghezza mediante due parametri [39]: il Beacon Order (BO), e il Superframe Order (SO). Il primo regola la lunghezza del superframe, e il secondo la lun-
62
Figura 5.4 - Scambio di primitive durante la creazione della rete.
Figura 5.5 - Configurazione del superframe.
ghezza della parte attiva. Per ridurre la latenza della trasmissione, è opportuno eliminare la parte inattiva del superframe, perciò poniamo:
BO = SO
La lunghezza minima della parte attiva può essere calcolata a partire dalla
lunghezza minima di uno slot, che è di 60 simboli, e considerando che il tempo di
63
simbolo nella banda a 2.4 GHz è di 16 µs. Dunque, la lunghezza minima per un superframe sarà:
MinSFLength = 60 symbols × 16 slots × 16 µs = 15.36 ms
Inoltre, lo standard impone di riservare un numero di simboli minimo al CAP.
Questo è inevitabile, dal momento che il CAP è l’unico intervallo in cui i nodi possono inviare comandi al coordinatore PAN. La lunghezza minima richiesta per il
CAP è di 440 simboli, e quindi:
MinCAPLength = 440 symbols × 16 µs = 7.04 ms
Possiamo ora usare questi dati per allocare i GTS. Prima di tutto, lo standard
richiede che i vari superframe non contengano più di 7 GTS, perciò la nostra rete
non potrà avere più di 7 slave [39]. Quanti GTS possiamo allocare in un superframe di una certa dimensione dipende ovviamente da quanto lungo è il payload di un
pacchetto 802.15.4. La lunghezza massima è 102 byte [39]: anche se, per una precisione a 32 bit e un filtro IIR di ordine 15 non necessiteremo di più di 72 byte,
consideriamo il caso peggiore. A questo valore bisogna sommare la lunghezza degli header e dei trailer aggiunti dal MAC e dal livello fisico. Questa lunghezza non
può superare 45 byte, perciò con un rate nominale di 250 kbps (31.25 kByte/s) nella banda a 2.4 GHz, la trasmissione di questo pacchetto richiederà:
MaxPayloadTxTime = (102 + 45) / 31.25k = 4.704 ms
A questo tempo dobbiamo aggiungere quello necessario per trasmettere
l’acknowledge (ACK), e lo spazio di interframe che separa il pacchetto dati dal
pacchetto ACK, necessario per garantire la corretta elaborazione del pacchetto dati
da parte del coordinatore PAN. L’interframe più l’ACK richiede circa 800 µs [39],
perciò arriviamo a
MaxPacketTxTime = 4.704 m + 0.8 ms = 5.504 ms
Questa è la dimensione minima di un GTS e questo insieme di regole fissa il
numero di nodi che possiamo allocate in un certo superframe. Ad esempio, utilizzando SO = 1, abbiamo un superframe lungo 30.72 ms e uno slot lungo 1.92 ms.
64
(a)
(b)
Figura 5.6 - Due esempi di allocazione dei GTS.
Come mostrato nella Figura 5.6 (a), in questo caso dobbiamo usare 4 slot per GTS,
e 4 slot per il CAP. Riducendo il payload, possiamo pensare di usare 2 slot per
GTS e quindi allocare fino a 6 nodi. Al contrario, ponendo SO = 2, abbiamo un superframe di 61.44 ms, uno slot di 3.84 ms, e possiamo usare due slot per GTS.
Come mostrato nella Figura 5.6 (b), in questo secondo caso possiamo far posto a
tutti e setti i GTS permessi dallo standard. La Tabella 5.1 riassume questo ragionamento.
65
Superframe order (SO)
0
1
2
Lunghezza Superframe
(ms)
15.36
30.72
61.44
Lunghezza slot (ms)
0.96
1.92
3.84
Slot/GTS
-
4
2
Slot/CAP
16
4
2
-
3
7
Nodi
Tabella 5.1 - Criteri di allocazione dei superframe.
SO = 1
Slot / GTS
1
2
3
4
5
6
7+
7
6
4
3
2
2
1
1.92
3.84
5.88
7.68
9.6
11.52
13.44+
15
75
102
102
102
102
102
ReTx
-
-
-
-
-
1
1+
Slot / CAP
9
4
4
4
6
4
9-
Nodi
Lunghezza GTS
(ms)
Payload massimo
(byte)
SO = 2
Slot / GTS
1
2
3
4
5
6
7
7
7
4
3
2
2
2
3.84
7.68
11.76
15.36
19.2
23.04
26.88
75
102
102
102
102
102
102
ReTx
-
-
1
2
3
4
4
Slot / CAP
9
2
4
4
6
4
2
Nodi
Lunghezza GTS
(ms)
Payload massimo
(bytes)
Tabella 5.2 – Dettagli dei criteri di allocazione del superframe.
Dunque, aumentando l’SO, aumenta anche il numero di nodi che possiamo allocare e le dimensioni del payload che possiamo trasmettere, ma aumenta anche la
66
latenza della trasmissione. Questa latenza non può superare il valore massimo che
rende il segnale fastidioso per l’orecchio umano. Dallo studio in [40], questo ritardo non può superare 150 ms, perciò dobbiamo avere
2SO × MinSFLength ≤ 150 ms
cosa che porta a SO ≤ 3.
Altri problemi si presentano quando consideriamo il comportamento real-time
della rete. Riprendiamo la situazione della Figura 5.6 (a), dove il superframe dura
30.72 ms. Dunque, in ogni GTS dobbiamo inserire tutte le finestre di segnale necessarie per ricostruire 30.72 ms di voce. A causa della sovrapposizione delle finestre, per ricostruire un segmento di 30 ms di voce occorrono tre finestre: quella
corrente, quella precedente, e quella successiva. La finestra precedente è stata ricevuta nella trasmissione precedente, ma bisogna ancora trasmettere due finestre.
Nella situazione della Figura 5.6 (b), dove il superframe dura 61.44 ms, dobbiamo
trasmettere quattro frame compressi invece di due. In generale, con finestre semisovrapposte, dovremo sempre trasmettere il doppio dei frame che se non avessimo
sovrapposizione. Ovviamente, per infilare più frame in un singolo GTS dovremo
ridurre la precisione dei coefficienti e/o la lunghezza del filtro, e questo potrebbe
introdurre ulteriore distorsione sul segnale compresso.
Da tutto questo, è facile dimostrare che la lunghezza del payload deve essere
tale per cui
N_FRAMES × [(L + 1) × PREC + 8] ≤ MaxPayloadBits = 816
Qui, N_FRAMES rappresenta il numero di finestre di segnale trasmesse nel
pacchetto corrente,
la lunghezza del filtro più il coefficiente di guadagno,
PREC la precisione in fixed-point, e 8 la lunghezza del byte che contiene il pitch e
il flag voiced/unvoiced.
Usando questa equazione, possiamo stimare la precisione richiesta per un dato
SO e per un dato filtro di sintesi, oppure la lunghezza massima del filtro per una
data precisione. Ad esempio, se SO = 1 e abbiamo due slot per GTS, il tempo di
trasmissione disponibile sarà 3.84 ms. Rimuovendo gli 0.8 ms necessari per l’ACK
e per lo spazio di interframe, rimaniamo con 3.04 ms, che ci permettono di trasmettere non più di 400 bit. Fissando la precisione (per semplificare l’implementazione)
67
Payload massimo (Bytes)
15
N_FRAMES
1
PREC
16
L
6
75
1
2
3
4
11
7
16
36
17
Payload massimo (Bytes)
102
N_FRAMES
1
2
3
4
PREC
16
24
16
24
16
24
16
24
L
49
32
24
15
15
11
11
7
Tabella 5.3 – Relazione tra il payload 802.15.4 e i parametri LPC.
ad una potenza di due, diciamo 8 o 16, otteniamo dall’equazione filtri di lunghezza
rispettivamente 23 o 11.
Come altro esempio, supponiamo di usare SO = 2. Questa scelta di permette di
usare fino a 1360 bit per il payload. La dimensione è maggiore degli 816 bit imposti dallo standard, per cui la dimensione effettiva che potremo riempire sarà di 816
bit. Inviando 4 frame per pacchetto, possiamo continuare ad utilizzare precisioni di
8 o 16 bit rispettivamente per filtri di 23 o 11 coefficienti. Altri risultati sono riassunti nella Tabella 5.3.
Il vantaggio principale dell’utilizzare SO = 2 è che abbiamo abbastanza GTS
per gestire 7 nodi, e più tempo di inattività all’interno di ciascun GTS per mandare
i vari nodi in sleep e risparmiare così potenza. Lo svantaggio principale, ovviamente, è che la perdita di un pacchetto in un superframe più lungo implica la perdita di
più frame vocali, e quindi una maggiore degradazione del segnale ricostruito. A
causa della natura real-time del sistema, non abbiamo abbastanza tempo per ritrasmettere un pacchetto, dato che ogni ritrasmissione introduce ulteriore latenza. La
soluzione più semplice a questo problema potrebbe essere sostituire il pacchetto
perduto con un pacchetto unvoiced, ma in modo più intelligente. In una rete a stella, il livello applicazione del PAN coordinator noterà la perdita di un pacchetto solo
alla ricezione del pacchetto successivo, che conterrà (come imposto dallo standard)
un numero di sequenza (DSN) diverso da quello atteso, come mostrato nella Figura
5.7 (a). A questo punto, però, sarà troppo tardi per inserire il pacchetto unvoiced.
68
(a)
(b)
(c)
Figura 5.7 - Casi possibili in presenza di rumore.
69
Un’applicazione realistica necessiterà di un buffer interno riempito con un pacchetto unvoiced regolato con il guadagno dell’ultimo pacchetto corretto (così da conservare l’ampiezza del segnale). Un timer configurato per lanciare un interrupt alla
fine di ciascun superframe potrebbe regolare il trasferimento del contenuto del
buffer ai livelli superiori. Se è stato ricevuto un pacchetto corretto, il contenuto del
buffer viene sovrascritto, mentre se il pacchetto è corrotto, viene usato il frame unvoiced.
La stessa strategia può essere applicata anche in una situazione più pericolosa.
La Figura 5.7 (b) mostra cosa succede se va perso un beacon. In questo caso, gli
slave non vedranno l’inizio del superframe e quindi nessuno di loro invierà i propri
dati. Si può usare ancora un frame unvoiced per rimpiazzare i pacchetti perduti, ma
in questo caso il coordinatore perderà un pacchetto per ciascuno slave a causa di un
unico beacon corrotto. Il livello applicazione degli slave noterà la perdita di un beacon alla ricezione del successivo, che conterrà un diverso Beacon Sequence Number (BSN). Ancora, non c’è tempo per eseguire una ritrasmissione, per cui gli slave
possono solo scartare il pacchetto che avrebbero inviato durante il superframe perduto e inviare quello successivo.
Una terza (ma più tranquilla) situazione è mostrata nella Figura 5.7 (c). Se va
perso un ACK, lo slave non riceverà la conferma dal livello MAC che la trasmissione è andata a buon fine, e passerà al pacchetto successivo solo dopo la ricezione
del beacon successivo. Ancora, non c’è abbastanza tempo per eseguire una ritrasmissione, e lo slave passerà al successivo gruppo di frame. Questa volta, tuttavia,
il pacchetto dati precedente è stato ricevuto correttamente, e questo salto non creerà
alcun problema. Nella prossima sezione valuteremo come e quanto il rumore sulla
rete influisce sulla qualità del segnale ricostruito.
Simulazioni & test
Due sono gli aspetti principali del progetto di una rete di questo tipo da analizzare con attenzione: in che modo i parametri di compressione influiscono sulla qualità del segnale trasmesso, e quali siano gli effetti della rete su di essa. La parte di
compressione del sistema è stata implementata e testata in Matlab, come mostrato
nella Figura 5.8, e la qualità misurata con uno script Matlab per il PESQ [41]. Il
PESQ rappresenta un valido sostituto per i test di ascolto, in cui ad un gruppo di
persone si chiede di fornire la propria opinione sulla qualità di un certo numero di
70
Matlab
Test Signals
LPC encoder
PESQ
LPC decoder
MOS
LPC parameters
Figura 5.8 - Flusso di simulazione per il test del vocoder LPC.
SystemC
Matlab
Test signals
MOS
Test signals
…
PESQ
LPC encoder
LPC encoder
…
LPC decoder
802.15.4
node
802.15.4
node
…
802.15.4
node
Wireless channel
noise
Figura 5.9 - Flusso di simulazione per il test della rete.
segnali. Solitamente, nei test di ascolto di risultati vengono dati nel range che va da
0 (“pessimo”) a 5 (“eccellente”): mediante una sofisticata rappresentazione psicoacustica dell’orecchio umano, il PESQ restituisce una stima dell’opinione “reale”.
Il chiaro vantaggio è che si possono eseguire molti più test in molto meno tempo e
senza alcun costo. Tutti i risultati presentati sono una media su venti segnali a 8
kHz presi dalle frasi standard IEEE [42] comunemente usate per la valutazione della qualità dei segnali vocali.
L’effetto della rete è stata invece simulata mediante il modello SystemC presentato in [36], [43], e [44], e la qualità del segnale ricevuto è stata anche lei misurata tramite il PESQ. Il flusso di simulazione è mostrato nella Figura 5.9. Anche in
questo caso, tutti i risultati presentati sono una media sugli stessi venti segnali a 8
71
kHz. Le forme d’onda presentate sono state ottenute invece mediante le funzionalità di tracing del SystemC e GTKWave.
La qualità di un segnale LPC
I parametri principali che influenzano le prestazioni di un encoder LPC sono
lo schema di finestratura e la lunghezza del filtro. Lo schema di finestratura, a sua
volta, è definito dal lunghezza e tipo della finestra, e dalla percentuale di sovrapposizione con le finestre adiacenti. La Figura 5.10 (a) riporta il Mean Opinion Score
(MOS) prodotto dal PESQ per diverse dimensioni delle finestre e per diverse lunghezze del filtro, utilizzando le classiche finestre di Hamming sovrapposte del 33
%. La Figura 5.10 (b) mostra risultati simili per finestre semi-sovrapposte. Le Figure 5.11 (a) e (b) riportano gli stessi risultati, ma con finestre di Hanning. Come abbiamo detto prima, la qualità aumenta con la lunghezza della finestra, ed è massima
per finestre di 30 ms, la durata di un singolo fonema in una frase. La qualità del segnale aumenta anch’essa con la lunghezza del filtro, ma solo fino al limite teorico
di 8 coefficienti. Oltre questo valore, il MOS satura, indicando che non ci sono
vantaggi nel trasmettere più coefficienti. Come effetto collaterale, meno coefficienti implicano un pacchetto più corto, quindi una trasmissione più resistente al rumore e meno consumo di potenza. Un payload più corto, inoltre, significa che c’è abbastanza spazio per l’aggiunta ai dati di un qualche genere di protezione dagli errori.
Inoltre, non ci sono particolari vantaggi tra i due gradi di sovrapposizione e tra
i due tipi di finestra, per cui possiamo scegliere la finestra di Hamming e frame
semi-sovrapposti. Il vantaggio di questa scelta è una semplificazione
dell’implementazione dello schema di windowing.
Nelle sezioni precedenti abbiamo visto anche come la configurazione della rete dipenda dalla precisione dei coefficienti del filtro. La Figura 5.12 riporta il MOS
per dati fixed-point a 16 e 24 bit variando la lunghezza della parte decimale. Una
precisione di 8 bit introduce una distorsione eccessivamente grave del segnale, e
per questo motivo non è stata considerata qui. Nella figura viene anche mostrato, a
titolo di confronto, il MOS di un segnale floating-point e una soluzione “del caso
migliore” ottenuta ottimizzando l’allocazione dei bit della parte decimale a seconda
del particolare coefficiente da quantizzare. In effetti, il guadagno è sempre solita-
72
(a)
(b)
Figura 5.10 - MOS per finestre di Hamming (a) sovrapposte al 33% e (b) semisovrapposte.
73
(a)
(b)
Figura 5.11 - MOS per finestre di Hanning (a) sovrapposte al 33% e (b) semi-sovrapposte.
74
(a)
(b)
Figura 5.12 - MOS per segnali fixed-pont (a) a 16 bit e (b) a 24 bit.
mente un numero compreso tra 0 e 1, e perciò possiamo riservare l’intera precisione per la parte decimale, tranne che per due bit (uno per il segno e uno per la parte
intera). I coefficienti del filtro, però, possono essere maggiori di uno, e quindi per
la parte intera ci servono più bit. Possiamo ottenere la soluzione del caso migliore
confrontando il modulo dell’errore tra i segnali floating-point e fixed-point, riportato nella Tabella 5.4 per precisioni di 16 e 24 bit.
75
Precisione della parte decimale (fixed-point a 16 bit)
9 bits
11 bits
13 bits
14 bits
Errore sui coefficienti
9.15 e-04
2.28 e-04
8.556 e-03
2.14 e-02
Errore sul guadagno
6.04 e-04
1.7 e-04
4.8 e-05
2.49 e-05
Precisione della parte decimale (fixed-point a 24 bit)
16 bits
18 bits
20 bits
22 bits
Errore sui coefficienti
7.13 e-06
1.78 e-06
3.19 e-04
2.14 e-02
Errore sul guadagno
6.75 e-06
1.8 e-06
4.81 e-07
1.18 e-07
Tabella 5.4 - Errore tra un segnale LPC floating-point e uno fixed-point.
Dai grafici nella Figura 5.12, possiamo vedere come le due precisioni siano
sostanzialmente equivalenti, perciò 16 bit sono abbastanza. In più, lo scenario “del
caso migliore” è praticamente equivalente al caso floating-point nel caso di precisioni a 24 bit, e solo leggermente meno per precisioni a 16 bit. Ancora, nel primo
caso i risultati migliori sono stati ottenuti utilizzando, per la parte decimale, solo 11
bit, e nel secondo caso 16 o 18.
Mettendo insieme questi risultati, possiamo dire che, per la trasmissione di un
frame vocale, dobbiamo trasmettere solo nove coefficienti a 16 bit e un byte per il
pitch e il flag voiced/unvoiced. Dunque, la dimensione del payload è di soli 19
byte, 12 volte meno che senza compressione. Il MOS del segnale ricostruito oscilla
attorno a 2.5, un valore non eccelso ma che ne assicura l’intelligibilità.
Simulazione della rete in SystemC
La Figura 5.13 riporta i tracing delle forme d’onda per i pacchetti trasmessi su
802.15.4 nel nostro modello simulato. La Figura 5.13 (a) mostra la situazione con
SO = 1 e quattro slot per GTS, mentre la Figura 5.13 (b) la situazione con SO = 2 e
due slot per GTS. In tutti i casi, possiamo chiaramente vedere come i GTS vengano
allocati correttamente all’interno di un superframe e come gli accessi al canale avvengano senza collisioni. Possiamo anche vedere come i vari nodi utilizzino il canale solamente per una piccola frazione del tempo totale, il che lascia ben sperare
per l’utilizzo del sistema su un dispositivo a batteria.
76
(a)
(b)
Figura 5.13 - Tracing per (a) reti a tre nodi e (b) reti a sette nodi.
Figura 5.14 - Tracing in presenza di forte rumore.
77
La Figura 5.14 riporta alcune forme d’onda in presenza di forte rumore. La
grande area vuota nel mezzo è un superframe delimitato da beacon corrotti. Come
abbiamo detto prima, quando un beacon va perso, all’interno del superframe che lo
segue non avvengono trasmissioni, e tutti i pacchetti sono persi. Nella figura, abbiamo anche identificato i pacchetti dati corrotti, ossia quelli non seguiti da un
ACK.
Per quantificare l’entità della degradazione introdotta dalla trasmissione sulla
rete, abbiamo aggiunto un generatore di rumore casual al modello di simulazione.
Questo generatore di rumore è semplicemente un registro a scorrimento retroazionato che produce una sequenza di interi fino ad un valore massimo che dipende dal
particolare BER che si vuole introdurre. La sequenza prodotta è ciclica: durante
ciascuna iterazione, il generatore va alla ricerca di un ben preciso elemento della
sequenza, scelto però a caso. Una volta trovato questo elemento, si introduce un errore e si cambia (arbitrariamente) l’elemento da cercare durante l’iterazione successiva. Abbiamo prima di tutto testato le prestazioni che si ottengono trasmettendo
coefficienti floating-point, e poi quelle che si ottengono trasmettendo coefficienti
fixed-point.
La Figura 5.15 mostra i risultati delle simulazioni per la rete descritta nella Figura 5.6 (b), e la Figura 5.16 quelle ottenute con la rete descritta nella Figura 5.6
(a). Non sono situazioni real-time, ma possono comunque essere utili per studiare
la degradazione introdotta dal canale e a titolo di confronto con la più realistica implementazione fixed-point. Durante ciascuna trasmissione abbiamo inviato 100
frame LPC semi-sovrapposti, ottenuti con finestre di Hamming da 30 ms e filtri a
15 coefficienti. Come abbiamo detto prima, non c’è un particolare vantaggio
nell’utilizzare 15 coefficienti invece di 8, ma con 15 coefficienti il pacchetto
802.15.4 sarà più soggetto al rumore e i risultati delle simulazioni saranno più conservativi. Dunque, la parte (a) di ciascuna figura contiene il MOS medio per i venti
segnali ricevuti, e la parte (b) l’error rate per i frame LPC. I numeri sui grafici nella
parte (a) di ciascuna figura rappresentano la percentuale di frame voiced corrotti.
Ovviamente, se un frame corrotto era unvoiced, il meccanismo di sostituzione che
abbiamo scelto si limiterà a cambiarlo con un altro frame unvoiced, al limite con
un guadagno differente. In questo caso, possiamo aspettarci che il peggioramento
della qualità sia trascurabile.
La degradazione introdotta dal canale è praticamente la stessa nei due casi, e
le piccole differenze sono probabilmente da imputare alla casualità intrinseca dei
frame unvoiced. Ciò che è importante notare, però, è come la degradazione diventi
78
(a)
(b)
Figura 5.15 - (a) MOS e (b) Packet error rate per una rete a sette nodi.
79
(a)
(b)
Figura 5.16 - (a) MOS e (b) Packet error rate per una rete a tre nodi.
80
(a)
(b)
Figura 5.17 - (a) MOS e (b) Packet error rate per una rete a sette nodi, fixed-point.
81
significativa solo per BER di circa 0.04, ossia quando il rumore cambia circa 1 bit
ogni 25. Si tratta di un bit error rate estremamente alto, difficile da incontrare in un
ambiente di lavoro reale. Il motivo dietro questa elevatissima robustezza al rumore
deriva dallo schema di espansione spettrale adottata dallo standard 802.15.4.
L’802.15.4 lavora su simboli a quattro bit, e li espande su sequenze di chip a 32 bit.
Quando questa sequenza viene ricevuta, un ricevitore a massima verosimiglianza
può stimare la sequenza trasmessa con più probabilità semplicemente cercando la
sequenza (corretta) più simile a quella (corrotta) ricevuta. L’utilizzo di queste sequenze così lunghe riduce di molto la probabilità di decodificare un simbolo errato
da una sequenza corrotta. Da un altro punto di vista, questo tipo di spettro espanso
produce gli stessi risultati che se utilizzassimo un codice per la correzione degli errori con rate 4/32 = 0.125. A titolo di confronto, il FEC con cui il Bluetooth protegge i propri payload è un codice con rate 10/15 = 0.666 [19]. Questa elevata resistenza al rumore dell’802.15.4 rende il sistema adatto all’utilizzo in ambienti estremamente rumorosi.
In ogni caso, come abbiamo detto prima, questo non è un approccio pratico
per una implementazione real-time. Per essere in grado di garantire il real-time,
dobbiamo riuscire ad inviare più frame per pacchetto e utilizzare dati fixed-point.
Possiamo aspettarci che la qualità del segnale ricostruito diminuisca al crescere del
BER, dato che un pacchetto perso ora implica la perdita di quattro frame LPC, ma
la situazione non è così drammatica. Come mostra la Figura 5.17, il diagramma del
MOS diminuisce più rapidamente di quelli mostrati nelle Figura 5.15 e 5.16, ma la
differenza a tutti gli effetti è piuttosto limitata. Inoltre, il diagramma del MOS si
abbassa a BER più bassi, e il motivo è che un errore in un pacchetto corrompe più
frame, degradando il MOS di più che nelle Figure 5.15 e 5.16. La diminuzione è in
ogni caso piuttosto limitata, e questo significa che l’utilizzo dello schema di trasmissione descritto è perfettamente sicuro.
IMPLEMENTAZIONE DELL’ENCODER LPC
Il problema dell’overlap-and-add
Come abbiamo appena detto, nella versione di base l’encoder LPC utilizza
uno schema di windowing con overlap, ossia preleva dal flusso audio originale
frame parzialmente sovrapposti e li riconcatena nel segnale sintetizzato mediante
82
overlap-and-add. Tutto questo viene fatto per garantire continuità e coerenza del
segnale ricostruito, ma con tre principali svantaggi:
1.
2.
3.
latenza maggiore;
minore rapporto di compressione;
difficoltà nel mantenere la coerenza del periodo di pitch nel passaggio da
un frame all’altro.
Il discorso della latenza discende dal fatto che, come abbiamo visto nel paragrafo precedente, per ricostruire un frame, il decoder in effetti necessita di tre frame. Il rapporto di compressione ovviamente diminuisce perché, con frame sovrapposti, a tutti gli effetti si va a codificare più volte la stessa parte di segnale, e questa
diminuzione sarà tanto maggiore quanto maggiore è il grado di sovrapposizione.
Il terzo problema discende dal fatto che, in generale, il periodo di pitch estratto dall’algoritmo non sarà lo stesso in due frame adiacenti, né nella zona di sovrapposizione, e senza compensazione in corrispondenza della transizione da una finestra all’altra la qualità peggiora drasticamente. Tra l’altro, questo allineamento non
è neanche sempre possibile, soprattutto quando il pitch varia rapidamente. La nostra soluzione consiste nell’eliminare completamente l’overlap e garantire la coerenza del pitch attraverso un meccanismo diverso, ossia facendo mantenere al generatore di eccitazione nel decoder uno stato interno tale da far mantenere una distanza coerente con il pitch desiderato agli impulsi generato a ridosso di frame consecutivi.
Questo approccio introduce una complicazione: i frame non possono più essere filtrati uno a uno indipendentemente, dal momento che così facendo otterremmo
tratti di segnale nullo all’inizio di ogni finestra, dato che il segnale diventa diverso
da zero solo alla comparsa del primo impulso. Queste zone nulle venivano compensate dall’overlap-and-add, che ora non abbiamo più, e discendono dal fatto che i
filtri che vengono fatti agire sulle varie finestre vengono in genere resettati tra un
finestra e l’altra, così da farli partire con uno stato iniziale nullo. Il problema si risolve in modo molto semplice non resettando il filtro, e lasciandogli come stato iniziale, lo stato finale del filtraggio precedente.
Una seconda, meno grave, complicazione introdotta da questo approccio è
che, non essendoci più lo smoothing introdotto dalla finestratura, il guadagno non
va più applicato bruscamente a tutto il frame, ma gradualmente da un frame
all’altro, fino a raggiungere il valore desiderato.
83
Se applicate correttamente, queste modifiche permettono di evitare l’overlapand-add in fase di analisi e di sintesi, con conseguente miglioramento della qualità
del segnale prodotto, abbassamento della latenza e innalzamento del rapporto di
compressione. In più, l’algoritmo si fa più semplice, visto che i blocchi di segmentazione e overlap-and-add spariscono del tutto, e gli altri sono solo leggermente più
complicate.
L’architettura
La Tabella 5.5 riporta le caratteristiche di funzionamento dell’encoder implementato. La frequenza minima di clock è quella che permette al circuito di elaborare il frame in un tempo uguale alla sua durata, condizione limite necessaria per il
funzionamento in real-time. Il valore riportato in tabella è quello teorico nel caso in
cui il circuito operi da solo, mentre in una implementazione reale ci si può aspettare un overhead di un qualche centinaio di cicli (non particolarmente significativo)
introdotto dal buffering iniziale dei dati in ingresso provenienti dal mondo esterno.
Nel caso dell’implementazione classica, l’aumento del numero di frame per unità
di tempo provoca un aumento della frequenza minima. L’overlap considerato qui è
del 30 %: nel caso di overlap del 50 %, l’aumento sarebbe stato del doppio.
I dati in tabella non dipendono dall’ordine della predizione lineare, a causa del
fatto che i tempi di lavoro sono imposti dal blocco più lento del circuito, che in
questo caso non è il blocco che estrae i coefficienti, bensì quello che estrae il pitch.
Per l’estrazione del pitch occorreranno sempre 120 cicli, per l’estrazione di coefficienti, invece, esattamente
cicli, dove sicuramente sarà minore di 119.
Confrontando i dati in tabella con quelli presenti in letteratura (ad esempio in [45]),
notiamo che la latenza dell’architettura proposta è più alta, ma questo dipende dal
fatto che il circuito in [45] utilizza un’architettura parallela che, per essere più veloce, consuma anche un numero eccessivo di risorse. Viste le basse frequenze minime necessarie per il processamento in real-time, un’architettura parallela è semplicemente uno spreco di risorse.
Bisogna sottolineare il fatto che l’encoder è identico sia nell’implementazione
classica che in quella proposta: tutto ciò che cambia è lo schema di segmentazione
(su cui torneremo tra un attimo), e la velocità con cui si passano i frame al modulo.
Possiamo perciò condensare i dati di occupazione di area in una sola tabella, la 5.6.
L’elevato numero di DSP viene instanziato dal tool di sintesi per
84
Cicli/frame
Frequenza massima di lavoro
Frequenza minima
architettura
proposta
Frequenza minima
architettura
classica
Latenza
architettura
proposta
a 200 MHz
Latenza
architettura
classica
a 200 MHz
15 ms
7270
> 200 MHz
0.485 MHz
0.693 MHz
Lunghezza frame
+ 36 µs
Lunghezza frame
+ 36 µs
20 ms
12070
> 200 MHz
0.604 MHz
0.863 MHz
Lunghezza frame
+ 60 µs
Lunghezza frame
+ 60 µs
25 ms
16870
> 200 MHz
0.675 MHz
0.964 MHz
Lunghezza frame
+ 84 µs
Lunghezza frame
+ 84 µs
30 ms
21670
> 200 MHz
0.723 MHz
1.033 MHz
Lunghezza frame
+ 108 µs
Lunghezza frame
+ 108 µs
Dimensione
del frame
Tabella 5.5 Caratteristiche dell’encoder.
Risorse occupate dall’Encoder
su Xilinx Zinq 7020
Flip-flop
2082
LUT
1651
Slice
660
RAM/FIFO
1
DSP
15
Potenza
0.113 W
Tabella 5.6 Risorse occupate dall’encoder.
l’implementazione del divisore (13 DSP su 15) e due moltiplicatori all’interno del
Levinson-Durbin, mentre tutto il resto della circuiteria viene implementato direttamente in LUT.
L’assorbimento di potenza in tabella è interamente riconducibile alle correnti
di perdita, a causa del ridotto utilizzo di risorse e della bassa frequenza di lavoro (1
MHz), due ordini di grandezza più piccola rispetto a quelle mediamente supportate
da una FPGA.
85
La logica di segmentazione (esterna al modulo encoder) rappresenta una parte
minima del progetto, e per questo non troppo influente sull’utilizzo generare di risorse. Si deve comunque notare che, nel caso dell’implementazione classica, a causa della sovrapposizione occorreranno tre buffer, per mantenere le tre finestre necessarie per la ricostruzione del frame corrente, più un ulteriore moltiplicatore e
una RAM per mantenere la funzione di finestratura. In più, la riconfigurabilità del
modulo non comporta variazioni nelle dimensioni della memoria, dal momento che
le RAM all’interno di una FPGA hanno dimensioni fisse di 128 o 256 elementi (a
seconda del numero di bit di indirizzo), e per un modulo non riconfigurabile (con
frame da 160 elementi) occorrerebbe comunque una memoria di 256 elementi.
La Tabella 5.7 riporta invece le caratteristiche del decoder. La prima cosa da
notare è che, a differenza dell’encoder, la frequenza minima di lavoro varia a seconda dell’ordine del filtro, ma non della dimensione del frame. Questo dipende
dal fatto che la complessità del filtraggio dipende dalla lunghezza del filtro ma non
dei dati da filtrare. I dati di latenza sono di nuovo riportati senza considerare la logica di segmentazione, ma c’è da dire che, a differenza dell’encoder, la logica di
ricostruzione a valle del decoder introduce una notevole latenza in più nel caso
dell’implementazione classica, come si può vedere nella Tabella 5.8, a causa
dell’overlap-and-add. Per fare un confronto, un vocoder LPC basato su implementazione tradizionale che lavora su frame da 30 ms avrà una latenza totale, tra codifica e decodifica, che va da 51 ms per frequenze di lavoro da circa 50 MHz in su,
fino a 81 ms per la frequenza minima. Analogamente, un vocoder LPC basato sulla
nostra implementazione, introdurrà, sullo stesso range di frequenze di lavoro, latenze comprese tra 30 e 60 ms.
Per finire, la Tabella 5.9 riporta l’occupazione di risorse. Anche in questo caso, l’assorbimento di potenza è quasi unicamente dovuto alle correnti di perdita,
per gli stessi motivi visti per l’encoder.
86
Dimensione
del frame
Cicli/frame
(LPC 2-15)
Frequenza massima di lavoro
Frequenza minima
architettura
proposta
(LPC 2-15)
Frequenza minima
architettura
classica
(LPC 2-15)
Latenza
architettura
proposta
a 200 MHz
(LPC 2-15)
Latenza
architettura
classica
a 200 MHz
(LPC 2-15)
15 ms
1388-2948
> 200 MHz
0.093-0.197 MHz
0.133-0.281 MHz
7-15 µs
7-15 µs
20 ms
1830-3911
> 200 MHz
0.092-0.196 MHz
0.131-0.280 MHz
9-20 µs
9-20 µs
25 ms
2272-4872
> 200 MHz
0.091-0.195 MHz
0.130-0.279 MHz
11-24 µs
11-24 µs
30 ms
2712-5832
> 200 MHz
0.091-0.195 MHz
0.130-0.279 MHz
14-29 µs
14-29 µs
Tabella 5.7 Caratteristiche del decoder.
Latenze logica di ricostruzione
Frame
Proposto
Classico
15 ms
0 ms
10.5 ms
20 ms
0 ms
14 ms
25 ms
0 ms
17 ms
30 ms
0 ms
21 ms
Tabella 5.8 Latenze logica di ricostruzione.
Risorse occupate dal Decoder
su Xilinx Zinq 7020
Flip-flop
1998
LUT
1396
Slice
687
RAM/FIFO
5
DSP
13
Potenza
0.113 W
Tabella 5.9 Risorse occupate dall’encoder.
87
88
Capitolo 6
SystemC IDE
L’IDEA
Il problema principale dello sviluppo di un simulatore SystemC per standard
complessi come il Bluetooth o lo ZigBee è lo stesso di qualsiasi progetto di una
certa dimensione: dopo un po’ diventa ingestibile. Utilizzando il SystemC così
com’è, ossia come una libreria di classi da inserire in un progetto plain-C++, fatto
unicamente di righe di codice, in breve tempo sincronizzare e gestire i moduli diventa problematico, come il ricordare quale blocco fa cosa, chi attiva quale linea,
chi legge quella particolare linea, e così via. Esistono ambienti di sviluppo grafici
che consentono di disegnare il proprio progetto e di visualizzare le forme d’onda a
debugging time, in maniera analoga a quanto fa fare, ad esempio, l’ISE Xilinx per
il VHDL, ma si tratta di programmi molto costosi e difficili da usare, e questo rende piuttosto interessante lo sviluppo di una alternativa più semplice e, perciò, a più
basso costo.
Un programma grafico che consenta di disegnare il proprio sistema non presenta particolari difficoltà a livello concettuale: si tratta semplicemente di scriverlo.
Più interessante è il problema del tracciamento delle forme d’onda. Il SystemC
contiene internamente delle funzionalità per il tracing, utilizzate ad esempio da
GTKWave, il programma che abbiamo usato per i tracing nei capitoli precedenti. Il
plotting tramite GTKWave avviene, però, offline, ossia al termine della simulazione. In questo caso, le forme d’onda sono utili ai fini della verifica delle funzionalità
del sistema (nel caso del Bluetooth, ad esempio, che la divisione in time slot è rispettata), molto meno ai fini del debug, perché è molto difficile ritrovare, su quei
grafici, i punti in cui i valori sulle varie linee non sono quelli che dovrebbero, soprattutto se le linee sono tante. Una prima soluzione potrebbe essere modificare il
SystemC, in modo da estrarre i dati man mano che le classi di tracing li accumulano e non direttamente alla fine.
89
L’approccio del modificare il SystemC è piuttosto semplice, ma si può fare
qualcosa di più intelligente. Consideriamo l’operatore di assegnazione di una qualche porta SystemC:
this_type& operator = ( const data_type& value_ )
{ (*this)->write( value_ ); return *this; }
Questo metodo richiama la funzione write della porta stessa:
inline
void
sc_signal<sc_dt::sc_logic>::write( const sc_dt::sc_logic& value_ )
{
sc_object* writer = sc_get_curr_simcontext()->get_current_writer();
if( m_writer == 0 ) {
m_writer = writer;
} else if( m_writer != writer ) {
sc_signal_invalid_writer( this, m_writer, writer );
}
m_new_val = value_;
if( !( m_new_val == m_cur_val ) ) {
request_update();
}
}
che a sua volta richiama l’operatore di assegnazione del tipo di dato associato alla
porta:
sc_logic& operator = ( const sc_logic& a )
{ m_val = a.m_val; return *this; }
Ora, supponiamo di essere fermi, all’interno di quest’ultimo operatore in modalità
di debug e all’interno di un IDE serio, tipo Microsoft VisualStudio. VisualStudio ci
consente di vedere il valore dei vari campi dell’argomento a passato all’operatore
nell’apposita WatchWindow. VisualStudio fa questo recuperando l’indirizzo virtuale dell’argomento e andando a leggere il valore del campo m_val direttamente
nella memoria di programma. Se potessimo in qualche modo automatizzare questo
procedimento, potremmo utilizzare i dati passati per il nostro tracing debuggingtime, con una serie di vantaggi in più che saranno chiari tra un attimo. L’unico modo per fare questo è scrivere un debugger che, in automatico, collochi un breakpoint su quella particolare riga di codice, vada a leggere i dati all’indirizzo occu-
90
pato dall’argomento, li plotti a schermo, e faccia riprendere l’esecuzione, in modo
totalmente trasparente per l’utente.
Non è una strada semplice, ma i vantaggi sono enormi. Per un debugger, piazzare un breakpoint su una particolare riga di codice significa andare ad inserire una
particolare istruzione macchina all’indirizzo virtuale associato a quella riga di codice. Ma per un debugger, un indirizzo virtuale vale l’altro, non è vincolato a mettere questo particolare tipo di breakpoint invisibile solo in corrispondenza
dell’operatore di assegnazione: può metterlo ovunque. E non è nemmeno vincolato
al SystemC: la procedura continua a valere per qualsiasi programma C++. Il nostro
debugger può inserire un breakpoint all’inizio di qualsiasi funzione C++ e, ad esempio, contare quante volte quel breakpoint viene incocciato. In un programma
C++, questo può servire ad individuare, ad esempio, le funzioni che vengono chiamate più spesso e che perciò necessitano di maggiore ottimizzazione. In un programma SystemC, quando la funzione ha il significato di “processo che descrive un
dato blocco”, questo conteggio, unito alla lettura delle porte di ingresso, può essere
tradotto in una stima del consumo di potenza di quel blocco. Ancora, il debugger
può inserire due breakpoint in qualsiasi punto del codice, e contare quante volte
l’esecuzione va dal breakpoint 1 al breakpoint 2. In un codice C++, questo si traduce in un esame più dettagliato delle prestazioni del codice, in un processo SystemC
in un più preciso calcolo della potenza.
Non finisce qui. Se l’esecuzione è ferma all’inizio del nostro operatore di assegnazione, chi ci vieta, invece di leggere il valore dell’argomento, di scriverlo,
bypassando il codice utente? Questo ci permette di introdurre errori su qualsiasi linea a debugging time, nel momento che preferiamo e senza complicate modifiche
del codice. Se si sta simulando una rete wireless, questa è una funzionalità non di
poco conto.
Il principale (e unico) svantaggio di questo approccio è che si tratta di scrivere
da zero un debugger completo, grafico, e con funzionalità che normalmente non si
incontrano nei debugger comuni. Come vedremo nel paragrafo successivo, il programma risultante è piuttosto complicato.
L’IMPLEMENTAZIONE
La Figura 6.1 riporta un diagramma delle classi di massima del programma.
Mancano molte delle classi dedicate alla grafica, la cui utilità è semplicemente ren-
91
CMainFrame
CViewer
CPlotDialog
CGraphTabCtrl (list)
CDataViewerFrame (list)
CDataViewerView
CDataViewer
CDataObject
CDataViewerDoc
CCodeViewFrame (list)
CCodeViewView
CDASMViewView
CCodeViewDoc
CDebugger
CDisassembler
CDBGModules_Manager
CDBGBk_Manager
CDBGSymbols_Manager
CDBGSymbolsEngine (list)
CStackWalkHelper
CMostroVisualManager
CTypesTreeFrame
CTypesTreeView
CTypesTreeDoc
CProjTreeFrame
CProjTreeConfig
CProjTreeConfig_Exclude
CProjTreeConfig_Icons
CProjTreeConfig_Categ
CProjTreeView
CProjTreeDoc
CSimCtrlFrame
CSimCtrlTools
CConsFrame
CConsView
CConsDoc
COutputFrame
COutputView
COutputDoc
CModulesFrame
CModulesView
CModulesDoc
CCallStackFrame
CCallStackView
CCallStackDoc
CWatchWindowFrame
CWatchWindowView
CWatchWindowDoc
Figura 6.1 - Diagramma delle classi dell’IDE.
92
dere il programma più bello, e qualche classe minore usata internamente dagli algoritmi. Si tratta di un programma per Windows, e per due motivi. Primo, perché
per il suo funzionamento il debugger si appoggia a molte funzionalità del sistema
operativo, e le API di Windows sono molto ben documentate e supportate da diversi forum di sviluppatori. Secondo, perché lo sviluppo di grafica è più semplice, e si
possono usare (con un minimo di pratica) le classi native per fare sostanzialmente
tutto.
La classe CMainFrame è quella che, sotto Windows, rappresenta la finestra
principale del programma, e che ha il compito di gestire e coordinare le altre finestre. Uno screenshot del main frame è mostrato nella Figura 6.2. La parte in alto a
sinistra è occupato dai visualizzatori dei tracing e del codice. Il visualizzatore dei
tracing è gestito dalla classe CViewer ed è strutturato come mostrato dalla Figura
6.3. La classe CViewer permette di creare più visualizzatori, ognuno selezionabile
tramite la propria linguetta e gestito dalla classe CGraphTabCtrl. Ogni classe
CGraphTabCtrl permette di creare più frame, gestiti dalla classe CDataViewerFrame. Questo frame, come del resto tutte le altre finestre gestite dal main frame, è gestito seguendo il modello basato su view e documenti tipico delle applicazioni
Windows native, e perciò conterrà una classe per la gestione/salvataggio dei dati
(CDataViewerDoc), e una per la loro visualizzazione (CDataViewerView). La
classe CDataViewerView permette di organizzare i dati in pannelli (CDataViewer),
e di raccogliere più curve (CDataObject) all’interno dei vari pannelli. La classe
CDataViewer può essere derivata così da generare diversi tipi di grafico, e la classe
CDataObject per gestire dati di diverso tipo. Ad esempio, esistono versioni derivate
di CDataViewer per il plotting di dati su un comune piano cartesiano (come quello
mostrato nella Figura 6.3), e per il plotting di segnali logici uno accanto all’altro,
come ad esempio quelli mostrati nella Figura 6.4. Esistono poi versione derivate di
CDataObject per il scatter plot, segnali continui, segnali logici, e chiamate a funzione. Quest’ultimo plot disegna una freccia verticale in corrispondenza dell’istante
in cui una particolare funzione (non necessariamente SystemC) viene chiamata:
nella Figura 6.4, ad esempio, la freccia marca gli istanti in cui viene chiamato
l’operatore di assegnazione relativo al grafico immediatamente sotto.
Il visualizzatore del codice (mostrato nella Figura 6.5) è gestito dalla classe
CCodeViewFrame, e rispetta il paradigma document/view di Windows, ma con
una differenza: le view sono due, una per il codice C++, e una per il codice assembly. Entrambe permettono di aggiungere e togliere breakpoint semplicemente
93
Figura 6.2 - Main window.
Figura 6.3 - Tracer.
94
Figura 6.4 - Tracing di segnali logici.
cliccando accanto alla riga desiderata, e consentono all’utente di eseguire le comuni funzioni di debug (step, step in, step out).
Immediatamente sotto i due visualizzatori, c’è un gruppo di finestre (mostrate
nella Figura 6.6) che contengono informazioni di ausilio per il debug. La Command Line (Figura 6.6 (a)) è semplicemente la finestra dei comandi nera delle applicazioni consolle, usata per l’input da tastiera e rediretta qui. La Output Window
(Figura 6.6 (b)), invece, è usata dal programma per visualizzare messaggi di una
certa importanza, come ad esempio il caricamento di una libreria, il codice di uscita
del programma in fase di debug, e così via. La Modules Window (Figura 6.6 (c))
elenca i moduli caricati dal programma in debug, siano essi DLL, librerie statiche,
altre applicazioni, eccetera. La Call stack window (Figura 6.6 (d)) elenca, ogniqualvolta l’esecuzione si ferma su un breakpoint, lo stack di chiamate, mentre la
Watch window (Figura 6.6 (e)) permette di vedere i valori delle variabili.
A destra dei visualizzatori ci sono la finestra che contiene l’albero del progetto, e quella che contiene l’elenco dei tipi di dato utilizzati nel progetto, mostrate
nella Figura 6.7. L’albero del progetto contiene (appunto) il progetto descritto in
una struttura ad albero, partendo dalle funzioni globali (come sc_main), e poi
scendendo giù nei vari moduli e sottomoduli. Per ogni modulo sono riportate le
porte di ingresso e di uscita, i sottomoduli, i segnali che interconnettono i sottomoduli, e i membri dati e i metodi principali. L’utente può sfruttare questa finestra per
95
(a)
(b)
Figura 6.5 - Visualizzatore del codice (a) C++, (b) assembly.
96
(a)
(b)
(c)
(d)
(e)
Figura 6.6 - Finestre di uscita.
97
(a)
(b)
Figura 6.7 - Alberi del progetto.
aggiungere grafici al visualizzatore o per visualizzare il codice associato ai vari
metodi.
L’albero dei tipi contiene invece l’elenco dei vari tipi utilizzati nel progetto.
Per ogni tipo è elencato solo il primo livello della gerarchia, ma si può esaminare il
contenuto di un sottotipo semplicemente facendo doppio click su di esso: penserà il
programma a spostare il cursore sulla descrizione del sottotipo selezionato.
L’utente può sfruttare questa finestra per visualizzare i metodi che implementano i
vari tipi, ma in realtà si tratta di un albero creato a uso e consumo della Watch
98
Figura 6.8 - Thread e processi nell’IDE.
Window e per velocizzare la creazione dell’albero del progetto, e aggiunta sostanzialmente perché non era difficile farlo.
Per finire, in basso a destra c’è il pannello di controllo della simulazione, gestito dalla classe CSimCtrlFrame. Si tratta sostanzialmente di una finestra di dialogo inserita in una finestra che consente all’utente di lanciare, interrompere o mettere in pausa la simulazione, o di rallentarla a piacimento. La finestrella con i numeri
contiene il tempo di simulazione corrente.
L’ultima classe, CDebugger, è quella che effettivamente gestisce tutte le operazioni che hanno a che fare con i breakpoint, inclusa la generazione dei dati da visualizzare. Utilizza al suo interno una serie di classi helper, e in più la classe CDisassembler, che genera il codice assembly x86 associato ad un qualche range di indirizzi virtuali.
A livello più alto, il programma è strutturato con una serie di thread che permettono alle varie classi di lavorare senza bloccare il programma. Questo è particolarmente importante per il frammento di codice che crea l’albero del progetto, che
richiede un certo tempo: se il programma fosse a singolo thread, tutte le finestre resterebbero bloccate in attesa del completamento della creazione di questo albero.
Di massima, invece, il programma è organizzato come mostrato nella Figura 6.8. Il
99
Figura 6.9 - Tool di disegno.
100
thread principale è quello che si occupa della gestione delle finestre, e comunica
tramite una pipe con il thread dedicato alla creazione dell’albero del progetto. Questa pipe è necessaria per il trasferimento dei dati generati dal thread alla finestra
che poi visualizzerà l’albero.
Il debugger gira su un altro thread ancora, cui è demandata anche la creazione
del processo su cui girerà il programma da debuggare, e il thread che gestisce la
consolle dei comandi (altra operazione che rischia di bloccare l’esecuzione). Un
ultimo thread gestisce l’aggiornamento dei dati plottati dal visualizzatore, e sarà
collegato tramite un’altra pipe al thread per il debugging, che genera i dati da visualizzare. Due oggetti di sincronizzazione, dbgBusy_event e graphBusy_event, evitano la perdita dei dati prodotti dal debugger.
Per finire, la Figura 6.9 mostra il tool di disegno che si affianca al debugger,
con due interfacce grafiche alternative ma perfettamente equivalenti. Con questo
tool è possibile disegnare moduli, aggiungere porte e tracciare linee tra di esse. In
più, sono state implementate le funzionalità di editing più semplici, come spostamento, ridimensionamento e cancellazione.
101
102
Capitolo 7
Conclusioni
In questo lavoro è stato presentato un nuovo tipo di simulatore Bluetooth che,
a differenza di quelli più comunemente usati, consente di lavorare in maniera molto
simile a quanto si farebbe con un vero sistema embedded a diversi livelli di astrazione. L’utente può scrivere il proprio codice applicazione o il proprio protocollo
di gestione del link, e modificare l’hardware che implementa il baseband Bluetooth
esattamente come farebbe con un sistema embedded reale. Il simulatore è stato implementato in SystemC, ma l’utente che lavori a livello applicazione o LM può farlo senza alcuna necessità di conoscere il SystemC.
Su questo simulatore è stata poi implementata una rete audio real-time e a bassa latenza per segnali musicali di alta qualità. Le simulazioni hanno dimostrato
come, con un utilizzo di un opportuno codec, e dimensionando accuratamente la
rete, il sistema possa effettivamente essere implementato e rispetti appieno le specifiche prefissate.
Una rete simile è stata simulata anche su un altro protocollo wireless a basso
data rate, l’802.15.4. Ovviamente, a causa della più bassa banda di quest’ultimo, gli
unici segnali audio trasmissibili sono quelli vocali, non quelli musicali. Ciò nonostante, anche in questo caso le simulazioni hanno mostrato come, con un’opportuna
scelta del codec, e configurando e dimensionando adeguatamente tutti i parametri
di rete, il sistema sia perfettamente funzionante nonché estremamente robusto.
Come primo passo verso un’implementazione realistica di quest’ultima rete su
802.15.4, è stata realizzata un’architettura per FPGA migliorata di un encoder LPC.
Semplicemente eliminando l’overlap-and-add e riallineando il pitch, si è dimostrato
come il circuito produca segnali compressi di qualità molto migliore rispetto al circuito classico, senza aumento delle risorse occupate, e con una riduzione della latenza e aumento del fattore di compressione.
A latere, è stato implementato un debugger dedicato per il SystemC. Questo
debugger, oltre a sostenere le comuni funzionalità di debugging offerte dai tool
grafici commerciali, consente di visualizzare le forme d’onda a debugging time, di
103
eseguire analisi statistiche sul codice e/o stime di potenza, e di introdurre rumore a
debugging time. Il debugger non è limitato al SystemC, ma funziona perfettamente
per qualsiasi programma C/C++. Inoltre, gli è stato affiancato anche un tool grafico
per il disegno degli schematici. Questo secondo tool è in grado di disegnare moduli, aggiungervi porte, e collegarle mediante fili. Supporta anche basilari funzionalità di editing come sposta, cancella e ridimensiona.
104