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