Microcomputer: interfacciamento parallelo e gestione delle interruzioni

Giuliano Donzellini, Domenico Ponta

Processore di dati su linea seriale asincrona, su FPGA

120100

 

v1.80

Il dispositivo rappresentato in figura è un processore di dati su linea seriale asincrona, in grado di ricevere sequenze seriali, elaborarle e quindi ritrasmetterle. L'architettura del sistema è basata su di un processore embedded e da alcuni dispositivi hardware specializzati. In questo progetto, si richiede di scrivere parte del codice assembly necessario per programmare il microcalcolatore, sulla base delle specifiche date. Infine, potrete realizzare e provare un prototipo del dispositivo su di una scheda FPGA.

Nel figura seguente sono visibili i blocchi funzionali in cui è suddiviso il nostro sistema:


Funzionalità del Ricevitore Seriale

Il compito del Ricevitore Seriale è di acquisire pacchetti di dati seriali asincroni, aventi il formato descritto nella figura seguente:

Ogni pacchetto di dati è composto di un bit di Start a '1', seguito, nell'ordine, dai bit di informazione D0, D1, D2, D3, D4, D5, D6 e D7, ed infine è terminato da un bit di STOP a '0'; il tempo di bit ("bit time") è pari a 1,6 µS, corrispondente a 625 Kb/s (10 MHz /16).

La prossima figura descrive il ricevitore seriale (un click sulla figura ne aprirà lo schema nell'editor del d-DcS):

Il sistema si compone di: un contatore del tempo di bit ("RX Bit Time Counter") [A, evidenziato in rosso], un registro a scorrimento di ricezione ("RX Shift Register") [B, in blu], un contatore del numero di bit da ricevere ("RX Bit Counter") [C, in rosso], e il controllore di ricezione ("RX Controller") [D, in marrone], realizzato con una macchina a stati finiti.

Contatore del tempo di bit  [A]
Il contatore del tempo di bit, basato sul componente Cnt4, ha il compito di dividere per 16 la frequenza del clock CK (10 MHz), al fine di ricevere i pacchetti di bit alla velocità richiesta (625 Kb/s = 10 MHz /16). Questa parte del ricevitore è alle volte indicata come generatore di baud/rate. Il contatore Cnt4 è impostato per contare all'indietro ciclicamente; ogni volta che le uscite Q3..Q0 raggiungono il numero '0000', è attivata la sua uscita TC (termine del conteggio), in modo che risulti un impulso ogni 16 cicli di clock. L'attivazione ciclica di TC è utilizzata dal controllore per sincronizzare il campionamento dei bit da ricevere, alla velocità richiesta. L'uscita LD del controllore, quando attivata, predispone il contatore al valore massimo '0110' (tramite gli ingressi P3..P0), come descritto nel seguito.

 Registro a scorrimento [B]
Il registro a scorrimento di ricezione (il componente Univ8), de-serializza i pacchetti di dato in arrivo dalla linea SER_IN, restituendo sulle linee di uscita RX_byte. Il modo di lavoro del registro è impostato dal controllore di ricezione tramite S1 e S0: se entrambi sono mantenuti a '0', lo stato del registro non cambia. L'ingresso seriale del registro (InR) è collegato direttamente alla linea SER_IN. L'ingresso nel registro di un bit di dato, e il contemporaneo scorrimento a destra degli altri, avviene quando il controllore imposta S1 = '0' e S0 = '1' (al centro di ogni tempo di bit e per un ciclo di clock). Ripetendo questa operazione per ogni bit del pacchetto ricevuto, alla fine tutti i bit di dato risulteranno disponibili, in parallelo, sulle linee RX_byte.

 Contatore dei bit [C]
Il contatore dei bit (anche questo basato su di un Cnt4) tiene il conto dei bit da ricevere. Il controllore di ricezione lo inizializza al numero 8 (attivando la linea LD); poi, ad ogni attivazione della linea EN, il conteggio decrementa di uno. Controllando la sua uscita TC, il controllore è in grado di sapere se sono stati ricevuti tutti i bit.

 Controllore di Ricezione [D]
La funzionalità del Controllore di Ricezione è descritta dal seguente diagramma ASM (un click sulla figura aprirà il file nel d-FsM):

La frequenza di clock del controllore è 16 volte la velocità del bit (bit rate) del pacchetto ricevuto. Per catturare il bit di Start, il controllore campiona il proprio ingresso LIN (ossia SER_IN) ad ogni fronte di salita del clock, nello stato (a). Quando LIN diventa 'alto', il controller continua a campionarlo, alla stessa velocità, nello stato (b). Se LIN è letto sempre 'alto', almeno fino quando la metà del tempo di bit è raggiunto, il controller dichiara valido il bit di Start, e passa nello stato (c). Al contrario, se LIN ritorna 'basso', si torna nello stato (a), in attesa di un nuovo bit di start.

Si noti che il centro del tempo di bit viene segnalato dalla linea TCB, che è generato dal contatore del tempo di bit. Questo viene inizializzato, tramite la linea LD, nello stato (a), al valore iniziale '0110'. Come risultato di questa inizializzazione, la prima occorrenza di TCB è generata alla metà del tempo di bit; le successive, invece, vengono generate ad ogni tempo di bit (ossia ogni 16 cicli di clock, pari al ciclo del contatore).

Il controllore valuta TCB anche nello stato (c), per sincronizzare le operazioni del registro a scorrimento. Quando TCB va 'alto', il controllore imposta S0 = '1' e S1 = '0', nello stato (d), causando l'acquisizione del valore di SER_IN (sull'ingresso InR del componente Univ8) e lo scalamento a destra di tutti gli altri bit del registro. Questa coppia di stati (c,d) viene ripetuta 8 volte, vale a dire fino a quando TCN risulta attivato.

TCN è generato dal contatore dei bit, quando questo raggiunge il numero '0000', cioè quando i bit di dato ricevuti sono terminati. Si noti che il contatore dei bit viene decrementato, attivando EN nello stato (d), ogni volta che un bit viene acquisito nel registro a scorrimento.

Il prossimo compito dell'algoritmo riguarda la convalida del bit di Stop. Nello stato (c), quando TCB è attivato, ma tutti i bit di dato sono già stati ricevuti (TCN = '1'), invece di acquisire il bit di Stop nel registro a scorrimento, il controllore ne verifica direttamente il valore. Se il bit di Stop è 'basso', il pacchetto viene assunto valido, e quindi il segnale RDY viene generato, nello stato (e), per la durata di un tempo di bit. Altrimenti, se il bit di Stop è 'alto', i bit di dato prima ricevuti saranno ignorati (infatti non viene generato il segnale RDY) e l'algoritmo ripartirà dallo stato (a). Lo stato di attesa (f) è un tentativo semplificato di recuperare lo stato di errore a causa del bit di stop sbagliato. Nello stato (f) il controller attende un valore 'basso' sulla LIN, prima di attendere nuovamente il prossimo bit di start (anche se questa soluzione potrebbe non essere la migliore per gestire la ricezione di un bit di stop errato).

È possibile testare il circuito nel d-DcS, con una simulazione nel tempo . Alcune sequenze di test sono disponibili nella finestra del diagramma temporale.


Funzionalità del Trasmettitore Seriale

Il trasmettitore seriale serializza i dati forniti in parallelo (8 bit), in pacchetti seriali asincroni composti da 10 bit. Il formato è lo stesso del ricevitore. La figura successiva rappresenta il trasmettitore (un click sulla figura aprirà lo schema nell'editor).

Il sistema è composto di un contatore del tempo di bit ('TX Bit Time Counter') [A, in rosso], un registro a scorrimento ('TX Shift Register') [B, in blu], un contatore nel numero di bit ('TX Bit Counter') [C, in rosso], e il controllore di trasmissione ('TX Controller') [D, in marrone], basato su macchina a stati finiti.

Contatore del tempo di bit [A]
Il funzionamento del contatore del tempo di bit è quasi identico a quello del ricevitore, ma con una differenza significativa nella sua inizializzazione. Anche in questo caso, il contatore genera un impulso ogni 16 cicli di clock (1,6 µS, il tempo di bit) sull'uscita Tc. Questa linea, rinominata TCB all'ingresso del controllore di trasmissione, viene utilizzata per sincronizzare la trasmissione dei bit di dato alla corretta velocità di bit. Il segnale LD predispone il contatore al valore '1111' (tramite gli ingressi P3..P0).

 Registro a scorrimento [B]
Il registro a scorrimento di trasmissione (basato sui componenti Univ4 e Univ8) serializza il dato parallelo TX_DATA sulla linea di uscita SER_OUT. S1 e S0 controllano il modo di funzionamento del registro: quando il controllore fornisce S1 = S0 = '0', lo stato del registro non cambia. Quando S1 = S0 = '1', il registro carica in parallelo gli otto bit di dato (TX_DATA), il bit di Start (P0 di Univ8) e il bit di Stop (P1 di Univ4). I bit del registro non utilizzati sono impostati a '0'. La trasmissione seriale inizia quando il dato è caricato, dal momento che l'operazione di caricamento pone a '1' il bit di start su SER_OUT. I successivi bit del pacchetto saranno trasmessi, uno dopo l'altro, ad ogni scorrimento a destra del registro (cioè ogni volta che il controllore pone S1 = '0' e S0 = '1').

 Contatore dei bit [C]
Il contatore dei bit (anche esso basato su Cnt4) conta il numero di bit da trasmettere. Il controllore lo inizializza a '1010', attivando la linea LD. Ogni volta che il controllore attiva la linea EN, il conteggio è decrementato di uno. L'uscita di termine di conteggio (TC) segnala al controllore che la trasmissione deve essere terminata.

 Controllore di trasmissione [D]
La funzionalità del controllore è descritta dal seguente diagramma ASM (un click sulla figura aprirà il file nel d-FsM):

L'ingresso !GO comanda la trasmissione: i primi due stati (a) e (b) hanno il compito di attendere la pressione del pulsante (fornisce un valore 'basso' quando viene premuto), e anche di attivare LD, predisponendo tutti i contatori nello stato iniziale. Nello stato (c), l'attivazione di S0 e S1 carica il dato parallelo nel registro a scorrimento, mentre EN ottiene il decremento del numero di bit da trasmettere. Nello stato (d), il controllore attende l'arrivo del segnale TCB dal contatore del tempo di bit: quando è attivato (ogni 1,6 µS), il controllore imposta (e) lo scorrimento verso destra del registro (S1 = '0' , S0 = '1') e il decremento del numero di bit da trasmettere (EN = '1') . Quando il numero di bit da trasmettere raggiunge lo zero (TCN = '1') , il controllore si sposta nello stato (f), dove genera l'uscita TND, per segnalare la fine della trasmissione. Infine, il controllore ritorna allo stato (a).

È possibile provare il circuito nel d-DcS, con una simulazione nel tempo . Alcune sequenze di test sono disponibili nella finestra del diagramma temporale.
ow.


Il sistema

Nella figura seguente è rappresentato lo schema del sistema completo (un click sulla figura lo ingrandirà in un'altra finestra):

La figura è piuttosto complessa, per cui abbiamo evidenziato, in colore, quattro aree funzionali. Il ricevitore seriale [RX] e il trasmettitore [TX], che abbiamo analizzato in precedenza, sono chiaramente riconoscibili, così come il microcomputer [MC]. La quarta area, la logica di interruzione [IL], include un flip-flop E-PET, un temporizzatore di interruzione (interrupt timer) e alcune porte logiche.


Il microcomputer

Il microcomputer DMC8 legge i dati, resi paralleli dal ricevitore seriale, li elabora, e quindi li invia al trasmettitore. Il porto IC legge gli 8 bit di dato provenienti dal ricevitore. Il porto ID legge una chiave (Key, definita dall'utente tramite un array di interruttori), che verrà utilizzata per crittografare i dati, prima della loro trasmissione. Il porto IB è utilizzato per riconoscere chi ha richiesto l'interruzione, e il porto IA legge un gruppo di interruttori che permettono di impostare alcuni modi di test, come spiegato più avanti. Il porto OA consente di ripristinare la richiesta di interruzione da parte del timer. Il porto OB invia i dati al trasmettitore; il porto OC è collegato alla linea di uscita Error; il porto OD è utilizzato solo a scopo di test.

Il ricevitore, quando un nuovo pacchetto di dati è stato de-serializzato, invia una richiesta di interrupt al microcomputer, che leggerà i dati attraverso il porto IC. Il microcomputer memorizza temporaneamente il byte ricevuto nella RAM, in una coda circolare (descritta di seguito), in attesa di essere elaborato e ritrasmesso. Se, durante il funzionamento del sistema, nella coda non ci sarà più spazio per contenere i dati in arrivo, il sistema attiverà l'uscita Error. Dal momento che questo è considerato un errore grave, la sua segnalazione, una volta attivata, potrà essere rimossa solo riavviando il sistema con un Reset.

Il temporizzatore di interruzione è impostato per richiedere, al microcomputer, una interruzione ogni 0,1 mS. Quando interrotto dal temporizzatore, il microcomputer controlla se la coda circolare contiene dati da trasmettere: in caso affermativo, preleva un byte dalla coda, lo crittografa e lo invia al trasmettitore seriale, tramite il porto OB. A fini di test, il numero di byte da trasmettere, in attesa nella coda, è visualizzato su di un display esadecimale, collegato al porto OD. Il microcomputer cripta i dati con una semplice operazione EXOR (bit a bit) tra il dato ricevuto e la chiave (in questo modo, se la chiave è zero, nessuna modifica viene effettuata sui dati).

La coda circolare

Una coda (queue) è un tipo di struttura dati, dove questi sono trattati in modalità "primo ad entrare primo ad uscire" (First In First Out, FIFO): l'elemento che "accodiamo" per primo è il primo che "toglieremo" dalla coda. Nel nostro sistema, usiamo una coda per memorizzare i dati man mano che arrivano, per poi ritrasmetterli a intervalli regolari. Se i dati arrivano più rapidamente di quanto non siano trasmessi, la coda progressivamente si riempie, per poi svuotarsi man mano quando i dati in entrata diminuiscono o non arrivano. Si noti che la lunghezza della coda è finita, per cui può verificarsi di non avere più spazio per memorizzare i dati in arrivo (coda piena).

Usiamo il puntatore 'IN_ptr' per indirizzare, nel buffer della coda, la prima posizione, per scrivere i dati in arrivo. Dopo che abbiamo accodato un nuovo byte, il puntatore 'IN_ptr' viene incrementato, indirizzando quindi la successiva posizione a scrivere. La coda è "circolare": quando scriviamo nell'ultima posizione del buffer, il puntatore è riportato indietro ad indirizzare la prima.

Il puntatore 'OUT_ptr', invece, indirizza il byte da prelevare per primo dalla coda. L'operazione di prelievo di un byte, formalmente, lo elimina dalla coda; il puntatore 'OUT_ptr' viene quindi incrementato per indirizzare il successivo byte da prelevare. Quando si preleva il byte in fondo al buffer di memoria, anche in questo caso, il puntatore viene ripristinato all'inizio del buffer, per via della "circolarità" della coda. Nell'esempio della figura seguente, quattro byte sono stati aggiunti alla coda: 'IN_ptr' punta alla successiva posizione da scrivere, mentre 'OUT_ptr' punta al primo byte da prelevare.

In un certo senso, possiamo dire che 'OUT_ptr' insegue 'IN_ptr': la coda è vuota quando 'IN_ptr' raggiunge 'OUT_ptr'. Se sono differenti, come nella figura ora vista, nella coda sono presenti dati da prelevare e trasmettere. Quando i puntatori sono uguali, non vi sono più dati da prelevare (vedi la figura seguente).

Si noti che deve essere gestito un caso particolare, perché la condizione di "coda vuota" potrebbe essere confusa con quella di "coda piena". Per evitare questa ambiguità, consideriamo la coda "piena" quando 'IN_ptr' punta alla posizione (vuota) che precede quella indirizzata da 'OUT_ptr', come nell'esempio riportato nella figura seguente. In questo modo, sacrifichiamo una posizione nella coda, ma evitiamo di introdurre altre variabili.

La logica di interruzione

Nella seguente figura è evidenziata una parte della logica di interruzione, quella del ricevitore. Un flip-flop E-PET viene attivato quando il ricevitore genera RDY (segnalando che un nuovo pacchetto di dati è stato ricevuto). L'attivazione dell'uscita !Q del flip-flop (visualizzata come !RXINT) interrompe il microcomputer, attraverso la porta AND, attivando il suo ingresso !Int. L'uscita del flip-flop è collegata anche al bit 0 del porto IB, per consentire al gestione delle interruzioni di riconoscere il ricevitore come il dispositivo che ha richiesto interrupt. Inoltre, l'uscita di segnalazione !rC del porto IC, attraverso alcune porte logiche, è collegata all'ingresso E del flip-flop: quando i dati ricevuti vengono letti sul porto IC, il flip-flop viene automaticamente ripristinato dalla attivazione di !rC e la richiesta di interruzione disattivata.

La logica di interruzione del trasmettitore, evidenziata nella figura qui sotto, è strutturata in modo differente. L'attivazione ciclica dell'uscita del temporizzatore delle interruzioni garantisce una regolare trasmissione dei byte, se presenti nella coda. L'uscita del temporizzatore delle interruzioni, visualizzata come !TINT, è collegata (attraverso la porta AND) all'ingresso !Int del microcomputer e al bit 1 del porto IB, in modo che il gestore delle interruzioni possa riconoscere il temporizzatore come il richiedente l'interruzione. Per cancellare la richiesta, poi, microcomputer scriverà sul porto OA un valore qualunque (scrivendo il porto, si attiva la segnalazione !wA, indipendentemente dal valore che viene scritto).

Modalità di Test

La figura successiva mette in evidenza il componente di ingresso TEST, con i suoi quattro interruttori collegati al porto IA. Questi permettono di impostare alcune modalità di test, utili per verificare la funzionalità della coda dei dati.

La seguente tabella definisce la funzionalità degli interruttori:

Interruttori di test

Funzione

Tutti = 'Off '

Nessun test abilitato

Interruttore 0 = 'On'

"Invio automatico" del trasmettitore

Interruttore 1 = 'On'

Trasmissione inibita

Interruttore 2 = 'On'

Ignora i dati ricevuti (non li memorizza nella coda)

Interruttore 3 = 'On'

"Auto riempimento" della coda di ricezione

Quando il modo di "Invio automatico" del trasmettitore è impostato, ad ogni richiesta del temporizzatore di interruzione, il trasmettitore è forzato a trasmettere un valore di test, ignorando i dati contenuti nella coda. Il valore parte da '11111111' e decrementa di uno dopo ogni trasmissione di test. Se la funzione "Trasmissione inibita" è impostata, nessun dato viene prelevato dalla coda e nessuna trasmissione viene eseguita. L'impostazione di questa modalità inibisce anche l'eventuale "Invio automatico" del trasmettitore, se impostato.

Se la funzione che permette di ignorare i dati ricevuti è attiva, i dati provenienti dal ricevitore seriale vengono scartati e non inseriti nella coda. Quando la funzione di "Auto riempimento" della coda di ricezione è impostata, ad ogni richiesta di interruzione da parte del temporizzatore, un nuovo byte di test viene generato e aggiunto alla coda, come se tale dato fosse stato ricevuto dalla linea seriale. Il valore del byte parte da '00000000' e incrementa di uno dopo ciascun inserimento nella coda.


Si chiede

di scrivere il programma assembly di gestione del sistema, secondo le specifiche descritte in precedenza. Terminata la scrittura e il test funzionale del programma nel d-McE, è necessario programmare la ROM del microcomputer (con un click qui, è possibile aprire lo schema, già pronto, nel d-DcS). Si raccomanda di usare lo schema fornito, senza cancellare o modificare le terminazioni di ingresso e uscita, perché sono state impostate per l'esportazione del progetto sulla scheda FPGA. Nella finestra del diagramma temporale del d-DcS sono disponibili alcune appropriate sequenze di test.

Qui di seguito sono disponibili tre soluzione parziali da completare, in codice assembly DMC8, in ordine discendente di difficoltà.

Nel primo file, il più impegnativo, viene suggerita la struttura generale della soluzione, e sono presenti le definizioni dei simboli, l'inizializzazione delle variabili, le operazioni base per la gestione delle interruzioni e alcuni sotto-programmi di gestione della coda. Rimane da scrivere il codice dei moduli di ricezione e di trasmissione, organizzati in sotto-programmi, e aggiungere tutte le altre istruzioni utili per testare il sistema.

La seconda soluzione parziale aggiunge alla precedente il sotto-programma 'RXDATA', che inserisce i dati ricevuti nella coda.

Il terzo file aggiunge anche il modulo 'TXDATA', che preleva, elabora e trasmette i dati a partire dalla coda; a questo file deve essere aggiunto il codice di test.

Chi desiderasse confrontarsi solo con una analisi di codice già scritto, la simulazione e l'implementazione su FPGA del sistema, troverà qui una soluzione completa.


A questo punto inizia la procedura per realizzare fisicamente il progetto sulla scheda FPGA. La procedura generale è descritta passo passo nei tutorial introduttivi:

   Realizzazione di un prototipo di circuito su scheda Altera DE2
   Test di una rete sequenziale su scheda Altera DE2
   Test di microcomputer su scheda Altera DE2

Il comando "Test on FPGA" (del d-DcS) apre la finestra di dialogo visibile qui sotto:

Si noti che tutte le associazioni degli ingressi e uscite con i corrispondenti dispositivi della scheda FPGA sono state già predefinite, e non è necessario modificarle. Il clock del microcomputer è impostato a 500 Hz (anziché 10 MHz), per permettere un esame visivo del comportamento del sistema. La "modalità passo-passo", impostata come mostrato dalla figura di cui sopra, sarà attivabile in fase di esecuzione agendo sull'interruttore SW[17], ed è predisposta per una esecuzione automatica (eseguendo 5 istruzioni al secondo). Per ottimizzare la sperimentazione di quanto descritto sulla scheda FPGA, le associazioni sono evidenziate in modo riassuntivo nella figura seguente (il "pannello di controllo" del nostro sistema):

Per testare il sistema abbiamo bisogno di utilizzare una o più schede separate. Le colleghiamo utilizzando i due connettori a 40 pin disponibili su ogni scheda, uniti con un cavo a due fili. L'uscita seriale SER_OUT è generata dal pin 1 del connettore GPIO_1, e l'ingresso seriale SER_IN è ricevuto sul pin 2 del GPIO_0. (vedi lo schema di collegamento). Se disponiamo di una sola scheda, dobbiamo collegare il cavo tra i due connettori presenti sulla scheda (come si vede nella figura seguente): il trasmettitore seriale, impostato nella modalità di "Invio automatico", genera i pacchetti di dati che, attraverso il cavetto, tornano al proprio ricevitore.

Se sono disponibili più schede, è interessante interconnetterle come suggerito nella figura seguente. Impostando il trasmettitore della scheda 'A' nella modalità di "Invio automatico", manderemo i dati alla scheda 'B', che a sua volta processerà e re-invierà i dati alla scheda 'C', e così via. La combinazione di diverse modalità di test su ogni scheda, renderà possibile verificare estensivamente il comportamento del sistema (ad esempio per riempire temporaneamente la coda di una scheda, per poi lasciarla svuotare nuovamente).

Si faccia molta attenzione nel piazzare e rimuovere il ponticello, per evitare di danneggiare meccanicamente i piedini del connettore. Per evitare possibili danni al chip FPGA, i cui piedini sono direttamente collegati al connettore, è necessario eseguire tale operazione a scheda non alimentata (spenta).