SwiftX: Un cross compiler per sistemi embedded

Scopriamo in questo articolo il cross compilatore SwiftX, un sistema di sviluppo interattivo basato sul linguaggio Forth in grado di supportare un’ampia gamma di microcontrollori.

Il cross compilatore SwiftX è prodotto dalla società americana FORTH Inc. (figura 1), la più grande e referenziata azienda in grado di fornire soluzioni e sistemi basati sul linguaggio Forth. FORTH, Inc., con sede a Los Angeles, California, fu fondata nel 1973 dagli stessi creatori del linguaggio Forth, tra i quali è doveroso ricordare la figura di Charles H. Moore, detto familiarmente “Chuck” (figura 2).

Figura 1: la della Forth Inc. a Los Angeles.

Figura 1: la della Forth Inc. a Los Angeles.

Figura 2: Charles H. Moore in una foto degli anni ‘60.

Figura 2: Charles H. Moore in una foto degli anni ‘60.

La prima versione del Forth fu inventata da Chuck Moore nel 1968, quando era impiegato in Arizona presso il National  Radio Astronomy Observatory (NRAO), ed il suo scopo era quello di fornire un aiuto agli astronomi per il controllo in tempo reale dei radiotelescopi. Curiosa è l’origine del nome di questo linguaggio, come raccontato dallo stesso Chuck Moore. A quei tempi egli stava lavorando su un calcolatore di “terza generazione”, un IBM 1130: le idee a cui stava dando forma gli sembrarono così innovative e potenti che egli pensò di avere creato un linguaggio per sistemi di “quarta generazione” (fourth in inglese).  Il problema fu che il sistema  1130 permetteva di creare identificativi di file di soli cinque caratteri, e così nacque il nome FORTH.  Il primo sistema Forth completo, basato su un calcolatore DEC PDP-11, vide la luce nel 1971 presso il NRAO per controllare un radiotelescopio da 11 metri. Il sistema doveva farsi carico di numerose attività da svolgere concorrentemente, tra le quali: puntamento e tracciamento della posizione, acquisizione e memorizzazione su unità magnetica dei dati del rilevamento, visualizzazione degli stessi su terminale grafico.

Il linguaggio  Forth

Lo scopo di questo paragrafo è quello di presentare  i concetti fondamentali che stanno alla base del linguaggio Forth. Il linguaggio  Forth si basa sui seguenti cinque elementi:

» il dizionario:  è una collezione di parole (“words”), dove per word si intende sostanzialmente una procedura, cioè un insieme di istruzioni. Una word può essere aggiunta al dizionario (come si dice definita) basandosi su altre word precedentemente definite: questo meccanismo conferisce al Forth un’importante caratteristica,  l’estensibilità.  I vantaggi  che ne derivano sono un notevole risparmio di tempo nella fase di testing, dato che ogni nuova word definita è composta da word che si suppongono già testate e validate in precedenza, ed il fatto che il Forth può essere ritenuto un linguaggio adatto sia al top-down design che al bottom-up coding and testing. Il dizionario  occupa la maggior parte della memoria, ed è composto da una lista concatenata di record di dimensione variabile, ciascuno dei quali corrisponde ad una word; quando una word viene referenziata,  il codice corrispondente (la sua definizione) viene ricercata nel dizionario partendo dalla definizione inserita più recentemente: ciò permette ad esempio di fornire definizioni multiple di una stessa word, nel qual caso verrà eseguita quella fornita per ultima. La definizione di una word inizia sempre con il carattere ‘:’ e termina con il carattere ‘;’. L’insieme delle word predefinite del Forth viene anche detto “kernel”;

» due stack, entrambi di tipo LIFO, uno per il passaggio parametri ed uno per il valore di ritorno. Il primo stack, detto anche “data stack” è legato ad un’importante caratteristica del Forth: esso adotta la notazione post-fissa, detta anche “notazione polacca inversa” (RPN), in cui prima vengono elencati i parametri  e successivamente l’operando da applicarvi. Come esempio, si supponga di voler definire una semplice word che esegue  il prodotto di due numeri. Una volta aggiunta la definizione al dizionario, si può eseguire il prodotto fornendo prima i due parametri (che verranno messi sul data stack) e successivamente  il nome della word; i due parametri vengono prelevati dallo stack, ne viene eseguito  il prodotto, ed il risultato depositato ancora sul data stack. Il  comando ‘.’  visualizza infine il valore in cima allo stack, che pertanto rimarrà successivamente vuoto;

» interprete  a linea di comando: si suddivide in due livelli: interprete del testo e interprete  dell’indirizzo.  Il primo (in pratica un “parser”) si pone in attesa di istruzioni sulla linea di comando, e verifica se i caratteri inseriti corrispondono  alla definizione di qualche word nel dizionario. Quando ciò avviene, passa il controllo all’interprete dell’indirizzo, che manda in esecuzione  il codice corrispondente alla definizione della word;

» un assembler:  il Forth mette a disposizione dello sviluppatore un tipo speciale di word, definita dalla parola chiave “CODE”. Attraverso di essa, è possibile inserire direttamente  il codice macchina per la CPU su cui si sta sviluppando: il macro assembler del Forth provvederà poi a generare  il codice. Questo attributo è utile per codificare parti critiche quali gestione dell’I/O, timer, interrupt, ecc...

» l’unità di massa in Forth è gestita come memoria virtuale e viene utilizzata per memorizzare sia i dati che il programma. Il disco viene suddiviso in blocchi da 1Kb ciascuno, ed uno o più blocchi vengono automaticamente caricati in memoria quando l’applicazione  referenzia i dati o il codice in essa contenuti. Se il contenuto di un buffer viene modificato in memoria, il suo contenuto viene scritto su disco non appena il buffer deve essere riutilizzato: non esistono primitive esplicite di lettura e scrittura su disco, poichè queste operazioni vengono gestite automaticamente dal programma. Il Forth supporta inoltre un multitasking molto essenziale, basato su uno sche

dulatore cooperativo di tipo round-robin. La word predefinita PAUSE serve proprio a sospendere l’esecuzione del task corrente, a memorizzare  il suo contesto, e a cedere il controllo al task successivo

in attesa di esecuzione.

Applicazioni del Forth

Il linguaggio Forth, tenendo soprattutto conto delle sue origini storiche, si è nel tempo ritagliato un’importante nicchia nel settore delle applicazioni astronomiche e spaziali. Tra le altre applicazioni del Forth ricordiamo l’Open Firmware boot ROM, adottato come boot loader da importanti produttori di hardware tra cui Apple, Sun, ed IBM. Inoltre, nel 1978, il Forth venne usato per scrivere il primo software residente per il neonato microprocessore 8086, e nel 1984 MacFORTH fu il primo ambiente di sviluppo adottato per il Macintosh.

Le versioni  disponibili di SwiftX

Sono disponibili tre versioni di SwiftX:

» versione Evaluation: permette di conoscere e prendere confidenza con l’ambiente SwiftX. Il  codice compilato con questa versione è limitato pesantemente nella dimensione massima, e non è possibile memorizzare  i file oggetto generati. Questa versione può essere scaricata per il microcontrollore  desiderato dal sito di Forth, Inc;

» versione Lite: è l’entry level dell’ambiente SwiftX, ideale per gli hobbysti e per gli sviluppatori intraprendenti che vogliono un sistema di sviluppo completo. L’assistenza è limitata ad un periodo di 60 giorni, ma è sempre possibile effettuare l’aggiornamento  alla versione Pro;

» versione  Pro: rispetto alla versione precedente aggiunge il  supporto alla gestione dei progetti, il code stripper, ottimizzazioni a livello di compilazione, e il codice sorgente per il cross compilatore e per il target. Prevede un periodo di assistenza di un anno ed è adatto agli utilizzatori professionisti. I componenti comuni a tutte le versioni di SwiftX comprendono: un ambiente di sviluppo integrato (IDE), un cross-compilatore, un assembler, un ker nel multitasking (SwiftOS), supporto per il crosstarget link (XTL), tool di supporto alla programmazione (disassembler, crossreference, dump della memoria), sorgenti Forth del kernel SwiftX, immagine precompilata del kernel SwiftX, che può successivamente essere ricompilata e rigenerata modificando i sorgenti del kernel. Ogni sistema SwiftX si compone di due elementi: un sistema host (la parte su PC del sistema di sviluppo) che supporta il cross-compilatore  SwiftX e gli strumenti di sviluppo interattivi, ed un sistema target che esegue il  codice sulla scheda target e supporta il debugging interattivo con l’host.

I punti di forza di SwiftX

Le principali caratteristiche di SwiftX possono essere così elencate:

» aumenta  la produttività degli sviluppatori attraverso un ambiente di sviluppo completamente integrato. L’ambiente di sviluppo integrato di SwiftX supporta l’attività di sviluppo in modo efficiente ed interattivo senza richiedere l’utilizzo di costosi emulatori o debugger ester ni. Ciò avviene grazie all’accesso diretto ed immediato a tutte le funzioni presenti nel codice digitandone  semplicemente  il nome ed i parametri  richiesti; è persino possibile inserire al volo delle nuove funzioni, compilarle e scaricarle sul target per un test immediato;

» accelera  il processo di test dell’hardware e del software, in quanto permette di interagire con il target ed accedere direttamente alle sue risorse. Ciò è reso possibile dal “Cross-Target Link” (detto anche XTL), un collegamento tra sistema host e target che a seconda del tipo di microcontrollore impiegato può essere realizzato tramite linea seriale oppure tramite interfaccia BDM o JTAG. La versione Pro di SwiftX include inoltre la funzionalità di “strip” del codice: il codice inutilizzato a livello sia di kernel che di applicativo viene rimosso, ottenendo un eseguibile maggiormente compatto. Lo strip viene ottenuto per approssimazioni successive, rimuovendo ad ogni passo il codice inutilizzato, ricompilando, e verificando se c’è stato un miglioramento in termini di dimensione dell’oggetto generato. E’ consigliabile eseguire lo strip del kernel solo al termine dello sviluppo, altrimenti si corre il rischio di rimuovere delle funzioni che successivamente potrebbero essere necessarie. La versione Pro permette inoltre di aggiungere un interprete e persino un compilatore Forth di alto livello direttamente sul target: ciò è particolarmente utile per la manutenzione in campo dell’apparato, dove ci si può collegare al target con un portatile o un palmare ed eseguire il debug dell’applicazione;

» consente di generare un codice estremamente compatto e performante:  il kernel configurabile di SwitftX (SwiftOS) fa sì che l’applicazione richieda un minor numero di risorse rispetto ai linguaggi di sviluppo tradizionali quali il C. SwiftOS viene inoltre fornito completo di file sorgenti, permettendo allo sviluppatore di selezionare solo le funzionalità che effettivamente sono richieste dal sistema sviluppato;

» agevola la fase di manutenzione del software: il codice scritto con SwiftX è conciso, modulare, e facilmente portabile su un’altra piattaforma hardware. In figura 3 è riportata la struttura di un’applicazione in SwiftX, partendo dal livello più basso, il core, fino a giungere al livello più esterno, cioè l’applicativo.

Figura 3: i livelli di un’applicazione in SwiftX.

Figura 3: i livelli di un’applicazione in SwiftX.

XTL

Il  cross-target link è una delle feature chiave del prodotto SwiftX.  Il cross-compilatore può funzionare sia in modalità connessa, in cui interagisce con il target tramite il protocollo XTL, sia in modalità disconnessa (detta anche modalità batch), in cui XTL non viene utilizzato e si opera solamente sul sistema host. Questa notevole proprietà di SwiftX permette di generare l’immagine da scaricare sul target in entrambe le modalità, in quanto il cross-compilatore  crea e mantiene su host un’immagine della memoria del target (per la precisione, vengono gestiti i segmenti  cData e iData). La modalità batch ha il vantaggio di rendere il processo di compilazione estremamente veloce. In modalità connessa è possibile accedere a tutte le risorse di memoria del target, tra cui ad esempio, il  segmento uData. L’interfaccia XTL supporta le seguenti funzioni di debug:

» visualizzazione  e modifica della memoria del sistema target;

» download di codice e dati sul target;

» test interattivo delle word sul target;

» esame del data stack attraverso  il comando .S.

SwiftOS

SwiftOS supporta due tipi di task: i task background e i task terminale.  Il secondo tipo di task è un estensione del primo, concepito appositamente per gestire dispositivi con interfaccia di tipo seriale. Ogni task possiede uno spazio di memoria di tipo uData contenente  i dati privati ed il return stack, ed un altro spazio di memoria detto “user data”. Quest’ultimo serve a memorizzare la copia locale al task delle variabili utilizzate nelle routine pubbliche, che in questo modo vengono rese rientranti: se infatti una routine deve mantenere lo stato di alcune variabili, è sempre possibile renderla rientrante mantenendo una copia privata di tali variabili in ognuno dei task che utilizzano tale routine. SwiftOS adotta una politica di schedulazione dei task di tipo round-robin: ogni task mantiene  il controllo sino al momento in cui esegue una chiamata ad una delle primitive PAUSE, STOP, oppure WAIT. Occorre notare che la maggiorparte delle word Forth riguardanti la gestione dell’I/O contiene al proprio interno delle chiamate a PAUSE o WAIT, per cui se un task si sospende in attesa di un’operazione di I/O, il controllo viene trasferito in modo trasparente ad un altro task. Se inoltre nell’applicazione è presente un task che esegue un utilizzo massiccio di CPU, è possibile comunque garantire la corretta schedulazione degli altri task inserendo in punti opportuni del codice delle chiamate alla funzione PAUSE. SwiftOS fornisce anche un supporto per l’accesso a risorse condivise (unità disco o nastro, stampanti, routine non rientranti, aree dati condivise, ecc...) che devono necessariamente essere gestite da un task alla volta. La concorrenza viene regolata in SwiftOS tramite le word GET e RELEASE, molto simili alle operazioni P e V sui semafori introdotte da Dijkstra.

: PRODOTTO ( n1 n2 – n3 ) * . ; ok
5 6 PRODOTTO 30 ok
.s empty ok
Listato 1

Hardware supportato

SwiftX supporta un’ampia gamma di microcontrollori, come riportato nella tabella 1 in cui è riportata anche il tipo di interfaccia di debug.

Tabella 1

Tabella 1

Un esempio

Come esempio, supponiamo di dover modellare in Forth il funzionamento di una lavatrice. La definizione di alto livello sarà del tipo:
: LAVATRICE LAVAGGIO CENTRIFUGA
RISCIACQUO CENTRIFUGA ;
Il carattere ‘:’ indica che una nuova word sta per essere definita, mentre il carattere ‘;’ indica il completamento della definizione. L’approccio seguito è quello tipico della progettazione top-down, tuttavia occorre ricordare che in Forth è possibile definire una nuova word solo se le word a cui essa fa riferimento sono già state definite in precedenza, pertanto nella fase di sviluppo occorrerà necessariamente seguire l’approccio bottom-up, partendo dalle definizioni dei blocchi elementari per arrivare a quelli di più alto livello. La word RISCIACQUO potrà avere una definizione di questo tipo:
:RISCIACQUO RIEMPI AGITA ASCIUGA ;
Mentre RIEMPI sarà definita nel seguente modo:
:RIEMPI RUBINETTO ON ATTESA-PIENO
RUBINETTO OFF;
dove RUBINETTO è una costante che restituisce la maschera relativa alla porta che gestisce l’I/O, mentre ON è una word che pone a 1 il bit selezionato. Da questo semplice esempio traspare la caratteristica di estrema modularità ed estensibilità del linguaggio Forth; partendo dalla definizione delle word elementari, si ottiene il beneficio di poter procedere subito con l’attività di test e validazione, e nello stesso tempo si possono riutilizzare le word già definite e validate per definirne altre di livello superiore. Il codice completo è riportato nel listato 2.

(Applicazione lavatrice)
\ Assegnamento delle porte
01 CONSTANT PORT
\bit-mask nome bit-mask nome
1 CONSTANT MOTORE 8 CONSTANT RUBINETTO
2 CONSTANT FRIZIONE 16 CONSTANT DETERGENTE
4 CONSTANT POMPA 32 CONSTANT LIVELLO
\ Controllo dell’I/O
: ON ( mask — ) PORT C@ OR PORT C! ;
: OFF ( mask — ) INVERT PORT C@ and PORT C! ;
\ Funzioni di temporizzazione
: SECONDI ( n — ) 0 ?DO 1000 MS LOOP ;
: MINUTI ( n — ) 60 * SECONDI ;
: FINCHE-PIENO ( — ) BEGIN PORT C@ LIVELLO AND UNTIL;
\ Funzioni della lavatrice
: AGGIUNGI ( mask — ) DUP ON 10 SECONDI OFF ;
: ASCIUGA ( — ) POMPA ON 3 MINUTI ;
: AGITA ( — ) MOTORE ON 10 MINUTI MOTORE OFF ;
: CENTRIFUGA ( — ) FRIZIONE ON MOTORE ON 5 MINUTI
                   MOTORE OFF FRIZIONE OFF POMPA OFF ;
: RIEMPI ( —) RUBINETTO ON FINCHE-PIENO RUBINETTO OFF ;
\ Cicli della lavatrice
: LAVAGGIO ( —) RIEMPI DETERGENTE AGGIUNGI AGITA ASCIUGA ;
: RISCIACQUO ( —) RIEMPI AGITA ASCIUGA ;
\ Definizione di alto livello
: LAVATRICE ( — ) LAVAGGIO CENTRIFUGA RISCIACQUO CENTRIFUGA ;
Listato 2

Scrivi un commento

EOS-Academy

Ricevi GRATIS le pillole di Elettronica

Ricevi via EMAIL 10 articoli tecnici di approfondimento sulle ultime tecnologie