Progetto di sistemi digitali controllati da MSF

Giuliano Donzellini, Domenico Ponta

Trasmettitore e Ricevitore Seriale Asincrono, su FPGA

050430

 

v1.80

In questo laboratorio si chiede di completare il progetto di un sistema di comunicazione seriale asincrona (vedi la figura seguente). Dopo la verifica del progetto mediante simulazione, ne realizzeremo un prototipo su FPGA per verificarne il reale funzionamento.

Il sistema è composto da due unità identiche, A e B, interconnesse tra di loro, a distanza, attraverso due linee di comunicazione seriale. Ogni unità comprende un trasmettitore (TX) e un ricevitore (RX) di linea seriale. Ciascuna unità lavora con un clock di frequenza nominale pari a 10 MHz. I generatori di clock delle due unità sono fisicamente diversi e separati: a causa delle tolleranze di fabbricazione, le frequenze effettive dell'uno e dell'altro risulteranno un poco diverse, per cui le due unità devono essere considerate, tra di loro, asincrone.

Le due unità comunicano tra loro inviando e ricevendo sequenze seriali a pacchetto (vedi figura seguente):

Ogni pacchetto è formato da un bit di START a '1', seguito, nell'ordine, dai bit D0, D1, D2, D3, D4, D5, D6 e D7 e, infine, terminato con 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). Si noti che il clock non viene reciprocamente trasmesso: ne risulta quindi una trasmissione seriale asincrona.

Nel seguito, progetteremo dapprima separatamente il trasmettitore (TX) e poi il ricevitore (RX), verificandone il funzionamento tramite simulazione. Quindi, integreremo i due moduli in un'unica unità, comprendente sia il trasmettitore che il ricevitore: terminata la simulazione di questa unità completa, passeremo alla sua realizzazione fisica utilizzando una scheda prototipo basata su FPGA. Sarà possibile testare il sistema di comunicazione utilizzando due schede separate (le unità A e B), collegate tra loro con un cavetto.


Il trasmettitore seriale TX

La figura seguente mostra lo schema del solo trasmettitore, di cui progetteremo il controllore, dopo avere analizzato la funzionalità degli altri elementi del sistema. Compito del trasmettitore è inviare sulla linea SER_OUT, alla pressione del pulsante !TX_GO, un pacchetto seriale contenente il dato D7..D0 presente all'ingresso TX_DATA, rispettando il formato e i tempi descritti in precedenza. Al termine della trasmissione attiva l'uscita TX_END, per la durata di un tempo di bit.

In figura osserviamo, evidenziati dai riquadri colorati: un contatore del tempo di bit [A, in rosso], un registro a scorrimento [B, in blu], un contatore del numero di bit [C, in rosso], e il controllore di trasmissione [D, in marrone], realizzato con una macchina a stati finiti (MSF).

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 trasmettere i pacchetti di bit alla velocità richiesta (625 Kb/s = 10 MHz /16). Questa parte del trasmettitore è 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 l'invio dei bit da trasmettere, alla velocità richiesta. L'uscita LD del controllore, quando attivata, predispone il contatore al valore massimo '1111' (tramite gli ingressi P3..P0).

 Registro a scorrimento [B]
Il registro a scorrimento di trasmissione, suddiviso nei due componenti Univ4 e Univ8, serializza, sulla linea di uscita SER_OUT, il dato parallelo ad 8 bit ricevuto all'ingresso TX_DATA. Le linee S1 e S0 controllano il modo di lavoro del registro. Se il controllore mantiene S1 = S0 = '0', lo stato del registro non cambia; invece, se il controllore impone S1 = S0 = '1', al successivo fronte di salita del clock, il registro a scorrimento carica in parallelo gli otto ingressi D7..D0 (nell'ordine: P0 di Univ4 e P7..P1 di Univ8), il bit di start (P0 di Univ8) e il bit di stop (P1 di Univ4). I bit inutilizzati del registro (P3..P2 di Univ4) sono impostati a '0'.

Si noti che la trasmissione seriale inizia quando il dato è caricato nel registro, dal momento che l'operazione di caricamento impone un '1' (il bit di start) sulla linea SER_OUT. Il controllore poi ottiene la trasmissione dei bit successivi del pacchetto, uno dopo l'altro, facendo scorrere a destra il registro (imponendo S1 = '0' e S0 = '1'), per un ciclo di clock, al termine del tempo di ogni bit.

 Contatore dei bit [C]
Il contatore dei bit (anche questo basato su di un Cnt4) è delegato a tenere il conto dei bit da trasmettere. Il controllore lo inizializza a '1010' (= 10 bit da trasmettere), attivando la linea LD; poi, attivando la linea EN, per un ciclo di clock, ne forza il decremento di uno. Il controllore, quindi, leggendo la linea TCN (termine del conteggio), generata dal contatore dei bit, saprà quando concludere le operazioni di trasmissione.

 Controllore di trasmissione [D]
Il controllore che dobbiamo progettare si occupa di inviare e/o ricevere gli opportuni segnali ai/dai componenti presenti nel sistema.

Specifiche di progetto del controllore di trasmissione

Potete scaricare qui una traccia da completare del diagramma ASM del controllore di trasmissione, dove le variabili di stato X,Y e Z sono già state definite, così come le uscite LD, S0, S1, END ed EN, e gli ingressi GO, TCB e TCN (un click sulla figura aprirà la traccia del diagramma nel d-FsM):

Il controllore riceve il comando TX_GO (sulla linea GO), il cui fronte di discesa avvia la trasmissione dei dati. Nel diagramma fornito, gli stati (a) e (b) hanno il compito non solo di attendere il fronte di discesa, ma anche di inizializzare la trasmissione, attivando la linea LD.

LD inizializza il contatore del tempo di bit, dal quale il controllore riceverà periodicamente il segnale TCB, che sarà utilizzato per emettere i bit sulla linea a cadenza regolare (ad ogni tempo di bit). L'attivazione di LD predispone anche il contatore dei bit, che informerà il controllore quando la trasmissione seriale dovrà essere conclusa.

Il controllore gestisce anche il registro a scorrimento di trasmissione: come descritto in precedenza, per iniziare la trasmissione, ordina al registro di caricare il dato parallelo TX_DATA, insieme ai bit di start e di stop. Il controllore entra poi in un ciclo in cui trasmette gli altri bit imponendo al registro un passo di scorrimento a destra, ad ogni attivazione della linea TCB.

Ogni volta che un bit viene trasmesso, il controllore attiva anche la linea EN, per un ciclo di clock, aggiornando il contatore dei bit da trasmettere. Si noti che, se non utilizzassimo un contatore per questo scopo, dovremmo complicare di molto l'algoritmo del controllore, prevedendo ripetitive sequenze di stati, quasi identiche tra loro, per ogni bit da trasmettere. In questo modo, invece, la sequenza è unica, e il ciclo si conclude alla attivazione della linea TCN del contatore (quando il numero dei bit rimanenti raggiunge lo zero).

Infine, il controllore attiva l'uscita TND, per segnalare la fine della trasmissione. Per garantire che la durata dell'uscita TND sia pari a un tempo di bit, conviene attendere un'ultima volta il segnale TCB, e poi ritornare in attesa del fronte di discesa di GO.

Verificate dapprima, utilizzando la simulazione con il d-FsM, che la funzionalità della MSF progettata corrisponda alle specifiche. Importate poi il componente ottenuto nel d-DcS, nello schema da completare qui disponibile. Nello schema sono state aggiunte alcune uscite di controllo, utili per verificarne il funzionamento mediante la simulazione temporale (è disponibile, nella finestra del diagramma temporale, la sequenza di test "TxSequence").


Il ricevitore seriale RX

La figura qui sotto mostra lo schema del solo ricevitore, il cui compito è acquisire i pacchetti seriali in arrivo sulla linea SER_IN, tenendo conto del formato e dei tempi utilizzati in trasmissione. Al termine della decodifica di un pacchetto, il ricevitore produce in parallelo, sulle uscite U7..U0, gli 8 bit che vi erano contenuti, e avvisa della loro disponibilità con l'attivazione dell'uscita RX_RDY, per la durata di un tempo di bit.

Anche in questo caso, progetteremo il solo controllore, dopo avere analizzato la funzionalità degli altri elementi. Allo scopo, osserviamo in figura: un contatore del tempo di bit [A, in rosso], un registro a scorrimento [B, in blu], un contatore nel numero di bit [C, in rosso], e il controllore di ricezione [D, in marrone].

Contatore del tempo di bit  [A]
Il funzionamento del contatore del tempo di bit del ricevitore è quasi identico a quello del trasmettitore, ma con una significativa differenza nella sua inizializzazione. Anche in questo caso, il contatore genera un impulso ogni 16 cicli di clock (1,6 µS, il tempo di bit) sulla sua uscita Tc. Tc (TCB per il controllore) è utilizzata per sincronizzare l'acquisizione dei bit di dato, uno alla volta, al centro dell'intervallo temporale riservato a ciascun bit. Attivando LD, il controllore inizializza il contatore al valore '0110', e non al valore massimo, in modo che, iniziando il conteggio a partire dalla ricezione del bit di start, l'attivazione del primo Tc avvenga al centro del tempo di bit.

 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. 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_DATA.

 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 (TCN per il controllore), siamo in grado di sapere se sono stati ricevuti tutti i bit.

 Controllore di ricezione [D]
Dobbiamo progettare il controllore di ricezione, che gestisce la temporizzazione dei componenti sopra descritti.

Specifiche di progetto del controllore di ricezione

Riassumendo, il controllore a riposo attende l'arrivo di un pacchetto sulla linea seriale SER_IN; non appena individuato il bit di start, ne verifica la validità e, grazie a questo, sincronizza le operazioni del registro a scorrimento in modo da acquisire correttamente i successivi bit del pacchetto e, infine, effettuare la verifica del bit di stop (qui di seguito esaminiamo più nel dettaglio queste operazioni). Come nel caso del trasmettitore, anche qui riusciamo a contenere l'algoritmo del controllore in un numero ragionevole di stati, poiché temporizzazioni e conteggi sono delegati a dispositivi esterni.

E' disponibile una traccia del diagramma ASM del controllore. Nel file sono già state definite le variabili di stato X, Y e Z, le uscite LD, S0, S1, RDY e EN, gli ingressi LIN, TCB e TCN e, infine, sono già impostati gli stati (a) e (b)(un click sulla figura aprirà il file da completare nel d-FsM):

Osservando il diagramma ASM, nello stato di attesa (a) si controlla la linea seriale tramite l'ingresso LIN, attendendo il bit di start. Durante la permanenza in (a), l'attivazione di LD inizializza i contatori del tempo di bit e del numero di bit: uscendo da questo stato, i contatori saranno poi liberi di contare, a partire dal valore di inizializzazione. All'occorrenza del bit di start, passiamo nello stato (b), dove rimaniamo in attesa che trascorra metà del tempo di bit (e cioè che si attivi TCB). Se nel frattempo LIN ritorna basso, il formato del bit di start non è da considerarsi valido e quindi ritorniamo nello stato iniziale, in attesa di un nuovo bit di start.

Ricordiamoci che, come detto prima, il contatore del tempo di bit è stato inizializzato in modo da generare il segnale TCB, la prima volta, alla metà del tempo di bit: mentre siamo nello stato (b), il contatore del tempo di bit sta decrementando il suo valore. All'arrivo di TCB, dichiariamo valido il bit di start, proseguendo oltre.

L'attesa di TCB nello stato (b) ci permette inoltre di sincronizzare, rispetto al bit di start, l'acquisizione dei successivi bit di dato. Infatti, dopo la prima attivazione di TCB, avvenuta al centro del bit di start, il contatore del tempo di bit continuerà ad attivare ciclicamente TCB ogni 16 fronti di salita del clock, e quindi sempre in corrispondenza del centro del tempo di tutti i successivi bit.

L'acquisizione degli 8 bit di dato è gestita da un ciclo di stati in cui il controllore:

  1. attende che TCB si attivi (perché quando TCB si attiva, è trascorso un tempo di bit e siamo al centro di tale intervallo);
  2. se TCB è attivo, ma TCN non lo è ancora (cioè abbiamo almeno uno o più bit da ricevere), ordina al registro di acquisire il bit e di scorrere a destra di un passo, mentre richiede al contatore dei bit di decrementare il numero di bit ancora da ricevere;
  3. ritorna al passo 1.

Se TCB e TCN risultano attivi entrambi, non ci sono più bit di dato da ricevere, e in quel momento su LIN è disponibile il bit di stop. Non occorre memorizzare questo bit, ma solo verificare il suo valore. Se il bit di stop è correttamente a zero, dobbiamo solo attivare RDY per la durata di un tempo di bit (aspettando che TCB si attivi di nuovo) e poi tornare nuovamente in attesa di un nuovo pacchetto, nello stato (a). Invece, se il valore del bit di stop è sbagliato, attendiamo, senza generare RDY, che LIN torni a zero, prima di tornare nello stato (a).

La funzionalità della MSF può essere verificata dapprima utilizzando la simulazione del d-FsM; poi il componente ottenuto deve essere importato nel d-DcS, in questo schema da completare. Anche in questo caso, lo schema è stato arricchito di alcune uscite di controllo, per rendere più leggibile la simulazione temporale (nella finestra del diagramma temporale è utilizzabile la sequenza di test "RxSequence").


L'unità completa (TX + RX)

Nella figura seguente appare lo schema della unità completa (trasmettitore e ricevitore):

Il trasmettitore e il ricevitore sono esattamente quelli definiti in precedenza. Il clock CK e il !RESET sono stati unificati e compaiono nella parte in fondo dello schema. E' stato aggiunto un selettore "2-1", evidenziato nello schema con un riquadro rosso. Il nuovo ingresso TEST, quando attivato, consente di collegare il ricevitore direttamente al trasmettitore, per permettere il test indipendente di una sola unità, senza ricorrere al collegamento di due differenti sistemi tra loro. In questo modo, in simulazione riusciremo a inviare un pacchetto e a riceverlo direttamente nello stesso sistema.

Ai fini della simulazione dell'intero sistema di trasmissione e ricezione, è qui disponibile uno schema da completare con le due MSF progettate in precedenza. Lo schema, come nei casi precedenti, è stato arricchito di alcune uscite di controllo, per rendere più completa la simulazione temporale (nella finestra del diagramma temporale è disponibile la sequenza di test "TestSequence"). Le uscite aggiunte aiuteranno anche la sperimentazione del sistema sulla scheda FPGA. Si raccomanda di usare lo schema fornito senza eliminare o modificare le terminazioni di ingresso e di uscita perché sono state predisposte per la esportazione del progetto sulla scheda FPGA.


Terminata la simulazione dell'intero sistema, passiamo alla sua realizzazione fisica 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:

Le associazioni tra schema d-DcS e scheda FPGA, per quanto riguarda gli ingressi e le uscite, sono state già definite nella traccia d-DcS fornita, per cui non è necessario modificarle. Il clock CK è stato impostato a 100 Hz (invece che a 10 MHz), per consentire l'esame visivo del comportamento del sistema. E' stata anche scelta la modalità "Slow Clock Mode": come risulta dai parametri impostati, visibili nella figura, tale modalità è attivabile tramite l'interruttore SW[17]. Lo "Slow Clock" è impostato in modo manuale, comandato dal pulsante Key[03], ed è visualizzato dal led rosso LEDR[17]. In base a questa impostazione, se l'interruttore è a zero, il clock CK lavorerà normalmente a 100 Hz, ma se l'interruttore è a uno, il clock sarà generato in modo manuale, un impulso alla volta, premendo il pulsante.

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):

Possiamo verificare il funzionamento del sistema da solo, attivando la modalità di test (interruttore di TEST verso l'alto), oppure utilizzando due schede separate, interconnesse mediante un cavo adeguato, tale da scambiare dati in entrambe le direzioni:

Realizziamo il collegamento tramite uno dei due connettori da 40 piedini disponibili su ciascuna scheda FPGA. Come si vede nella figura qui sopra, utilizziamo un collegamento incrociato tra il trasmettitore e il ricevitore, in entrambe le direzioni, per un totale di 3 fili (compreso il riferimento di massa GND). Qui lo schema della connessione, che fa riferimento al connettore di espansione "GPIO_1" presente sulla scheda.

Anche senza utilizzare l'interruttore TEST, possiamo eseguire test preliminari con una sola scheda, utilizzando un semplice ponticello ("Jumper") inserito sul connettore, come si vede nella figura seguente (freccia gialla), in modo da collegare insieme trasmettitore e ricevitore:

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).