Proiectarea Si Realizarea Fizica A Unui Codec – Codor Convolutional, Decodor Viterbi

Argument

Scopul lucrări de față este proiectarea și realizarea fizică a unui codec format din codor convoluțional și decodor Viterbi, dezvoltarea unui mediu dual simulare – implementare FPGA pentru validarea proiectului.

Codurile convoluționale fac parte din familia codurilor corectoare de erori, pentru decodarea lor folosindu-se cel mai frecvent algoritmul Viterbi.

Domeniul comunicațiilor wireless este unul extrem de dinamic, iar utilizarea din ce în ce mai frecventă a acestora pentru transferul informațiilor, face ca și exigențele în ce privește calitatea lor să fie în creștere. Deoarece canalele de comunicații wireless sunt mult mai susceptibile la zgomot, este necesar ca la emisie să se recurgă la aplicarea de coduri corectoare de erori.

Capitolul 2 din prezenta lucrare prezintă o serie de algoritmi de codare destinați creșterii calității transmisiilor din sistemele de comunicații prin introducerea de redundanță în vederea corecției de erori. Transmisiile se fac astfel în condiții de securitate superioare chiar în prezența zgomotelor aditive, interferențelor și fading.

Capitolul 3 descrie pe scurt algoritmul Viterbi, algoritm folosit pentru a elimina posibilele erori aleatoare dintr-un șir de biți recepționați, aceștia rezultând în urma unei codări convoluționale. Astfel, metoda presupune o codare convoluțională la emisie și decodare Viterbi la recepție.

Descrierea hardware (la nivel RTL) a codecului este cuprinsă în capitolul 4, împărțit în două secțiuni: una ce redă codorul convoluțional iar cea de-a doua detaliază blocurile funcționale ce intră în componența decodorului Viterbi.

Capitolul 5 prezintă rezultatele sintezei, inclusiv resursele hardware cosumate, căile critice, frecvența maxim, modul de configurare pe FPGA.

Pentru realizarea proiectului se pot folosi diverse platforme ca: FPGA, CPLD, ASIC, DSP, însă am ales configurarea pe FPGA. FPGA-ul oferă proiectantului flexibilitatea unei soluții programabile, costuri mai scăzute ca în cazul utilizării de ASIC-uri, un consum de putere mai mic decât în cazul utilizării unui chip DSP.

Listă cu acronime utilizate

API – Application Programming Interface

FPGA – Field – programmable gate array

PLL – Phase – Locked Loop

XML – eXtensible Markup Language

DLL – Dynamically-linked library

HDL – Hardware Description Language

VLSI – Very Large Scale Integration

TDMA – Time Division Multiple-Access

CDMA – Code Devision Multiple-Access

FDMA – Frequency Devision Multiple-Access

SSS – Spread Spectrum System

OPS – Operations per second

AWGN – Additive White Gaussian Noise

FEC – Forward Error Corection

ARQ – Automatic Repeat Request

RTL – Register Transfer Level

SNR – Signal to Noise Ratio

DSP – Digital Signal Processor

ACK – Acknowledgement

NACK – Negative Acknowledgement

LFSR – Linear Feedback Shift Register

IEEE – Institute of Electrical and Electronics Engineers

USB -Universal Serial Bus

DAB – Digital Audio Broadcasting

DVB – Digital Video Broadcasting

WCDMA – Wideband Code Division Multiple Access

GSM – Global System for Mobile communications

WiMAX – Worldwide Interoperability for Microwave Access

FSM – Finite State Machine

LED – Light Emitting Diode

LUT – Look-Up Table

CUPRINS:

Capitolul I

I.1 Introducere

Sistemele de comunicații, componente esențiale în dezvoltarea societății moderne, cunosc în prezent o evoluție spectaculoasă datorată progreselor tehnologiilor în domeniul realizării circuitelor (VLSI – Very Large Scale Integration), a mediilor de transmisie și cele ale teoriei comunicațiilor [1].

Gama aplicațiilor și numărul beneficiarilor sunt tot mai mari: transmisii digitale audio/video cu diferite nivele de calitate, televiziune de înaltă definiție (HDTV), comunicații mobile, transmisii prin satelit, poștă electronică, rețele extinse de calculatoare, baze de date, tranzacții financiare computerizate, controlul proceselor industriale în timp real, învățământ la distanță, aplicații medicale, telemetrie, etc.

Se dezvoltă sisteme și rețele digitale de comunicații de date de mare viteză, sisteme de înregistrare cu densitate mare a datelor pe suport, cu echipamente tot mai performante și costuri relativ reduse: procesoare digitale de semnal, procesoare de imagine de tip CNN (Celullar Nelinear/neural Networks) care efectuează 1 TERRA OPS (1012 operations per second), filtre de egalizare adaptive, circuite digitale de codare/decodare, modulare/demodulare, circuite de multiplexare bazate pe diferite tehnici de acces multiplu ( TDMA – Time Division Multiple-Access; CDMA- Code Devision Multiple-Access; FDMA – Frequency Devision Multiple-Access), sisteme cu spectru extins (SSS – Spread Spectrum Systems) [1].

Factorii ce au dus la o dezvoltare deosebit de dinamică a sistemelor de comunicații digitale sunt:

Convergența tehnologiilor de telecomunicații cu cele de calculatoare, calculatorul personal devenind și un terminal inteligent de telecomunicații;

Sistemele de comunicații digitale asigură o deosebită flexibilitate transmisiei, ele putând transmite informații de naturi diferite (convorbiri telefonice, muzică, imagini fixe și mobile, date) sub formă digitală și cu o calitate superioară;

Proliferarea canalelor de bandă largă (fibră optică, sateliți geostaționari de telecomunicații, linii în cablu coaxial);

Perfecționarea tehnologiilor de fabricație a circuitelor integrate, scăderea densității de integrare a componentelor împreună cu creșterea complexității sistemelor și a vitezei de lucru cu 1-2 ordine de mărime în ultima decadă;

Apariția de noi tehnici și servicii de telecomunicații (radiotelefonie celulară, transmisii de date de mare viteză, comunicații Internet, poștă electronică, videotelefonie, videoconferință, videosupraveghere, transmisii de imagini, etc.).[2]

Avantajele oferite de comunicațiile digitale sunt următoarele:

Folosirea unor circuite (hardware) digitale relativ ieftine și a tehnicilor de procesare digitală a semnalelor conduce la sisteme cu performanțe crescute și cost scăzut;

Tehnologiile digitale pot crește capacitatea sistemului de 2 până la 5 ori în comparație cu cele analogice folosind tehnici de acces multiplu, codare și modulație;

Calitate superioară oferită de tehnicile digitale (distorsiuni reduse și fiabilitate crescută);

Asigurarea secretizării mesajelor transmise utilizând tehnici digitale de criptare (cifrare);

În sistemele de comunicații digitale la mare distanță, spre deosebire de cele analogice, nu se cumulează distorsiunile și zgomotele de la repetor la repetor;

Rata erorilor este suficient de mică, chiar în condițiile unor zgomote de putere mai mare ce afectează transmisia;

Se pot elimina în mare măsură erorile prin introducerea de redundanță (codare). [2]

Ca dezavantaje ale comunicațiilor digitale putem considera:

În general, sistemele de comunicații digitale necesită o bandă mai mare în comparație cu cele analogice;

La recepție trebuie realizate operații de sincronizare (de purtătoare, de bit, de cadru, de cuvânt), ceea ce conduce la o complexitate crescută a sistemului și în special a receptoarelor.

Un sistem de comunicații vehiculează un semnal purtător de informație de la un emițător spre unul sau mai mulți destinatari, folosind în acest scop un canal de comunicații.

Dintre defectele principale și transformările suferite de un semnal amintesc:

Distorsionarea semnalului datorită operațiilor de filtrare, translare de frecvență, modulare și demodulare;

Translarea spectrului semnalului cu o frecvență variabilă Δf, produsă de mișcarea relativă între emițător și receptor, cunoscută ca efect Doppler;

Propagare necontrolată, pe căi adiacente celei principale, cunoscută și ca propagare multi-căi sau multipath.

Zgomote de tip aditiv și multiplicativ, cu caracter aleator, care afectează amplitudinea, respectiv faza semnalului;

Atenuarea aleatoare sau sub forma unei funcții de timp care nu depinde de semnalul transmis, cunoscută sub denumirea de fading;

Interferențe la intrarea sau ieșirea din canal, produse de alte sisteme de comunicații sau de fenomene naturale.

Sistemul de comunicații trebuie să prezinte o siguranță cât mai ridicată a informației între sursă și receptor în prezența perturbațiilor inerente de pe canal. Din acest motiv înainte de introducerea unor sisteme de protecție la erori trebuie făcut tot posibilul pentru optimizarea proprietăților canalului de transmisiune deoarece în acest fel scad costurile și complexitatea sistemului de detecție/corecție de erori.

Adaptarea semnalului la canalul de transmisie poate implica înlocuirea semnalului digital de date cu unul analogic; procedeul de asociere a unor forme de undă analogice secvențelor de date de intrare se numește modulare, iar schema bloc a unui sistem de TD poate lua forma din figura 1.

Codorul sursei generează un șir de biți compact în care se succed cuvintele de cod ale sursei. Prin operația de compactare se scade redundanța semnalului și debitul informațional. Dacă valoarea acestuia depășește capacitatea canalului de transmisie se poate realiza compresia datelor în limitele admise de aplicație, prin reducerea entropiei semnalului.

Blocul de criptare asigură securitatea transmisiei, prevenind preluarea și falsificarea informațiilor de către un receptor/transmițător neautorizat.

Codorul de canal crește redundanța semnalului transmis pentru a permite receptorului să realizeze detecția și corecția erorilor, respectiv adaptarea semnalului transmis la constrângerile impuse de canal.

Modulatorul digital transformă secvența de simboluri discrete, în particular binare, într-un semnal analogic, prin asocierea fiecărui simbol de date cu unul analogic din alfabetul modulatorului. Semnalul recepționat diferă de cel transmis datorită perturbațiilor de pe canalul de tranmisie: AWGN, fading, distorsiuni de amplitudine sau fază, interferențe.

Receptorul realizează operațiile inverse celor de la emisie.

Figura 1 – Schema bloc a unui sistem modern de comunicații

Decodorul de canal folosește redundanța cuvintelor de cod pentru detecția si corecția eventualelor erori. Acest fapt contribuie fie la creșterea calității transmisiei, fie la reducerea puterii emițătorului și a dimensiunilor antenelor de emisie/recepție, făcând posibilă funcționarea sistemului la rapoarte de puteri semnal/zgomot mai mici, pentru o anumită valoare, impusă, a ratei erorilor în receptor.

Codarea de canal asigură transmiterea eficientă a mesajelor, într-o formă adaptată la canalul de transmisie. Ea asigură un compromis între lățimea benzii de tranmisie și/sau complexitatea codorului/decodorului pe de o parte și micșorarea probabilității de eroare (respectiv ratei erorilor) sau scăderea raportului S/Z necesare pentru a realiza o anumită viteză de transmisie.

Împrăștierea spectrală asigură o vulnerabilitate mai mică a transmisiei la bruiaje și interferențe, o secretizare mai bună precum și un acces multiplu al utilizatorilor la canalul de comunicație caracterizat printr-o mai bună flexibilitate.

Prin multiplexare și acces multiplu pot fi transmise semnale cu debite și caracteristici diferite, provenite din surse diferite.[2]

Nu toate aceste aceste operații pot fi întâlnite într-un sistem de comunicații. Unele pot lipsi iar altele se pot combina sau distribui între ele, sau apare într-o ordine diferită de cea prezentată.

În sistemele de comunicații digitale, detecția și corecția de erori sunt procese ce stau la baza realizării de comunicații sigure.

Tehnicile bazate pe detecția de erori și cerere de retransmisie sunt mult mai simple decât metodele FEC (Forward Error Corection) de corecție a acestora, însă prezintă și o serie de dezavantaje. Unul ar fi necesitatea existenței unui mecanism ARQ (Automatic Repeat Request) care să lanseze cereri de retransmisie a acelor blocuri, segmente ori pachete în care s-a detectat prezența unei erori. Acest mecanism funcționează în baza existenței unor protocoale care să intervistenței unor protocoale care să intervină în transmisia de informații dintre două dispozitive ce comunică, și să aloce timp pentru retransmia blocurilor eronate și poziționarea lor corectă în secvența de date, reconstituită la recepție. Un alt dezavantaj al acestei metode de detecție a erorilor cu ARQ, este scăderea drastică a eficienței odată cu creșterea probabilității de apariție a erorilor pe canalul de transmisie [7]. Cu cât aceasta este mai mare cu atât crește numărul cererilor de retransmitere a unui bloc, reducând considerabil viteza.

Metodele de corecție a erorilor (FEC) prezintă multiple avantaje față de metodele bazate pe detecția erorilor, fie și numai din următoarele motive:

Absența necesității unui canal suplimentar care să fie folosit în lansarea

cererilor de retransmisie (back – channel) ,deoarece acesta ar fi inutil.

În multe comunicații, întârzierile introduse de ARQ sunt foarte mari (ex.comunicații prin satelit: ¼ s)

Costul acestor avantaje este necesitatea unei benzi de transmisie mai mari (datorată biților de paritate necesari pentru corecție, introduși pe lângă cei de date).

Capitolul II. Coduri corectoare de erori

II.1 Introducere

Transformările de semnal destinate îmbunătățirii calității transmisiei prin creșterea imunității semnalelor la zgomote, poartă numele de codări de canal.

Codorul de canal crește redundanța semnalului transmis, în primul rând pentru a permite receptorului să realizeze detecția și corecția erorilor, iar în al doilea rand pentru a adapta semnalul de transmis la constrângerile impuse de canal.

La recepție, decodorul folosește redundanța cuvintelor de cod pentru a detecta și corecta eventualele erori apărute în timpul transmisiei. Acest fapt contribuie semnificativ la creșterea calității transmisiei; în alte cazuri face posibilă reducerea puterii de emisie, a dimensiunilor antenelor de emisie/recepție, făcând posibilă funcționarea sistemului la rapoarte de puteri semnal – zgomot (SNR) mai mici, în condițiile unei valori impuse a ratei erorilor în receptor.

Utilizarea circuitelor VLSI și a tehnicilor de procesare de semnal de mare viteză (DSP) a permis îmbunătățirea performanțelor de transmisie chiar și cu 10dB, în condițiile utilizării codurilor corectoare de erori și a codurilor de canal în general, la costuri mult inferioare utilizării unor antene de dimensiuni mai mari sau a unor puteri de emisie mai ridicate.

Codurile aplicate semnalului de date se pot clasifica, din punct de vedere al structurii lor, în trei mari categorii: coduri –bloc, coduri – prefix, coduri – arbore [1].

Codurile de tip bloc împart secvența de intrare în blocuri de k simboluri din alfabetul sursei, pe care le asociază cu secvențe de cod de n simboluri din alfabetul canalului.

Rata de codare se definește ca raport k/n.

Există coduri bloc cu lungimi ale cuvintelor de intrare sau de ieșire fixe ori variabile:

k fix – n fix;

k fix – n variabil;

k variabil – n fix;

k variabil – n variabil (în particular rata de codare poate fi constantă).

Codurile-prefix reprezintă un caz particular de cod-bloc cu lungime fixă a cuvintelor la intrare și lungime variabilă a cuvintelor la ieșire, cu proprietatea esențială că nici o combinație codată nu este prefix pentru alt cuvânt de cod (așa-numita condiție de prefix).

Codurile de tip arbore codează fiecare cadru de intrare printr-o secvență de cod în funcție de m cadre anterioare și p cadre ulterioare. Parametrii m și p reprezintă memoria, respectiv predicția codului. Codarea se va face pe baza unei diagrame de tip arbore. Codurile arbore invariante în timp se numesc coduri-trellis, întrucât funcționarea lor poate fi urmărită pe baza unei driagrame de tip grilă (trellis) [1].

Codurile trellis liniare sunt denumite coduri convoluționale.

Din punct de vedere al funcției îndeplinite, tehnicile de codare a datelor se împart în următoarele categorii:

coduri de compactare, care reduc redundanța semnalului digital fără a afecta entropia acestuia

coduri de compresie, care reduc entropia semnalului în scopul micșorării debitului binar al sursei infromaționale

coduri de criptografiere a datelor, ce permit secretizarea transmisiei

coduri corectoare de erori care, prin creșterea redundanței semnalului transmis fac posibile detecția și corecția unui anumit număr de erori de transmisie

coduri de translare a datelor în vederea adaptării acestora la constrângerile canalului de comunicație.[1]

În cazul sistemelor de transmie a datelor, o caracteristică esențială este imunitatea la zgomot. Cum în timpul transmisiilor reale de date este inevitabilă apariția de erori, pentru a le înlătura este necesar să aplicăm datelor ce urmează a fi transmise, coduri corectoare de erori. Aceste coduri presupun adăugarea de informație redundantă prin procesul de codare. Biții redundanți vor fi utilizați ulterior, la recepție, pentru detecția și corecția erorilor.

II.2 Metode de control al erorilor într-un sistem de comunicații

Cele două metode de bază, prin care introducerea de biți redundanți este folosită în vederea controlului erorilor sunt:

Detecția erorilor și cererea de retransmitere utilizează biții de paritate (biți redundanți) pentru a detecta dacă a apărut sau nu o eroare. În acest caz receptorul nu corectează erorile, ci doar lansează o cerere de retransmitere în situația în care au fost detectate erori. De subliniat este necesitatea unui al doilea canal care să asigure dialogul între emițător și receptor în vederea realizării retransmiterii.

Metode FEC (de corecție a erorilor prin adăugarea de biți redundanți la emisie – coduri corectoare de erori), metode ce necesită un singur canal de comunicație deoarece nu sunt necesare retransmiterile. În acest caz, biții de paritate sunt folosiți atât pentru detecția cât și pentru corecția erorilor.[7]

II.3 Coduri corectoare de erori (metode FEC)

În orice sistem real de comunicații informația recepționată diferă de cea de la emisie și asta deoarece orice canal este afectat de zgomot. Dacă apar prea multe erori (în comunicațiile de date sunt situații în care “prea multe” înseamnă chiar și numai o eroare, numai 1 bit), sistemul devine inutilizabil.

În 1948, Claude Shannon a demostrat că probabilitatea de apariție a unei erori poate fi, teoretic, redusă la zero, știind că rata de transmisie nu poate depăși capacitatea canalului, C. Pentru transmisia unui semnal de putere medie S, pe un canal cu banda B, canal pe care apare zgomot aditiv, alb, Gaussian (AWGN) de putere medie N, capacitatea canalului este dată de:

(1)

Pentru ca un sistem de comunicații să se apropie de această limită, nu poate transmite datele utile fără prelucrări suplimentare.

În rețelele de calculatoare, această problemă se rezolvă în general printr-o schemă ARQ (automatic repeat request). Se folosește o sumă de control, sau altă metodă de adăugare a unor biți redundanți, aplicată unui cadru sau pachet, permițând detecția erorilor la recepție. Dacă nu e detectată nici o eroare, receptorul trimite un mesaj ACK (acknowledgement) emițătorului, indicându-i că a recepționat corect pachetul sau cadrul. În schimb, dacă se detectează o eroare, receptorul răspunde cu NACK (negative acknowledgement) emițătorului, sau nu răspunde și așteaptă ca emițătorul să retransmită cadrul/pachetul eronat, după un interval de timp prestabilit în care așteaptă mesajul ACK.

Tehnica ARQ nu este o tehnică prea utilizată în cazul rețelelor wireless, spre exemplu. Un motiv e că ARQ necesită o comunicație duplex. În timp ce această cerință este ușor și relativ simplu de realizat în cazul rețelelor pe fir, pentru cele wireless duce la o creștere a complexității receptorului și a legăturii, creștere de multe ori inacceptabilă [7]. De asemenea, în rețelele de calculatoare, probabilitatea de eronare a unor biți cauzată de zgomot este mică, în timp ce în rețelele wireless biții eronați apar mult mai frecvent. Numărul retransmiterilor necesare în cazul unui sistem cu SNR mic ar fi atât de mare încât ar “gâtui” transmisia. În locul schemelor ARQ, în multe sisteme de comunicații se folosesc codurile FEC.

Codurile FEC sunt gândite astfel încât, aplicate datelor de transmis, să ofere suficientă informație la recepție încât să se realizeze nu doar detecția ci și corecția erorilor introduse de canal, lucru posibil datorită a două tehnici: introducerea de redundanță, medierea zgomotului.[7]

Medierea zgomotului se bazează pe faptul că un bit de date de la intrarea în codor afectează mai mulți biți de la ieșire, oferind receptorului posibilitatea de a media efectul zgomotului pe mai mulți biți recepționați.

Beneficiile codurilor FEC sunt asumate cu anumite costuri. Unul dintre ele este numărul suplimentar de operații efectuate asupra semnalelor atât la emisie, cât mai ales la recepție (fig.2). Dacă în general codoarele nu sunt structuri complexe, în schimb, decodoarele implică un timp de prelucrare mai ridicat, mai multe componente hardware, încât sunt cazuri în care utilizarea de decodoare optime duce la o creștere a complexității și a întârzierii de procesare inacceptabile. În aceste situații se optează pentru decodoare suboptime, ce implică timp de prelucrare inferior, complexitate mai redusă dar și performanțe mai scăzute.

Un alt dezavantaj este dat de reducerea ratei de transmisie a datelor cu un factor constant, reducere datorată bițiilor de paritate introduși de codor.

Figura 2 – Sistem de comunicații cu corecție de erori, incluzând blocuri suplimentare de prelucrare la emisie/recepție

În general, codurile corectoare utilizate sunt împărțite în coduri bloc și coduri convoluționale. Chiar dacă liniile de separare între cele două au început să se estompeze odată cu apariția unor coduri mai performante, cum sunt codurile turbo care folosesc elemente din ambele tipuri, este încă utilă studierea separată a celor două pentru o mai bună înțelegere a algoritmilor.

II.4 Codurile bloc

Codurile bloc recurg la împărțirea fluxului de date în mesaje de lungime fixă. Fiecărui mesaj de k biți de la intrarea în codor, îi corespunde un cuvânt de cod de lungime n la ieșirea codorului, prin adăugarea a (n – k) biți de paritate. Rata de codare pentru un cod bloc (n,k) este:

(2)

Cuvântul de cod x este reprezentat sub formă matricială ca x = [m p] unde

m = [m1 m2 … mk ] și p = [p1 p2 … pn-k]

sunt biții de mesaj, respectiv de paritate. În acest caz, deoarece mesajul este parte a cuvântului de cod, codul astfel format este sistematic.

Codorul formează cuvântul de cod x prin multiplicare între matricea generatoare G și m (mesaj):

(3)

Pentru codurile sistematice, matricea generatoare se obține din concatenarea matricii identitate Ik (care generează m) și matricea de paritate Z cu dimensiunile k · (n – k), care determină p:

[Ik Z] (4)

Codurile generate într-o astfel de manieră sunt coduri liniare.

Distanța Hamming dintre oricare două cuvinte de cod x1 și x2 este dată de numărul de biți diferiți din reprezentarea lor binară. Notația folosită pentru această mărime va fi dh(x1,x2).

Ponderea Hamming (Hamming weight), wh, a unui cuvânt de cod x este egală cu numărul de simboluri diferite de simbolul nul al alfabetului folosit. În particular, pentru alfabetul binar, wh este egală cu numărul biților 1 din cuvânt:

(5)

Valoarea dinstanței Hamming minime este importantă deoarece numărul de biți eronați, t, ce pot fi corectați prin folosirea unui anumit cod este dat de:

(6)

În consecință, cu cât este mai mare, cu atât capacitatea de corecție a codului crește. Însă o valoare crescută a lui necesită și o valoare mare pentru n comparativ cu k, ceea ce scade rata de codare Rc. Sunt și coduri cu număr mare de biți în cuvântul de cod, pentru care rata de codare poate fi adusă la valori acceptabile pentru o anumită , însă în afară de valoarea lui Rc, odată cu creșterea lui n crește și complexitatea codorului dar mai ales a decodorului. Astfel este limitată creșterea performanțelor codului prin creșterea lungimii cuvintelor de cod.[8]

Codurile bloc sunt coduri fără memorie (cuvântul de cod generat la un moment dat nu depinde decât de cele k simboluri aflate la acel moment la intrarea codorului) construite pe o corespondență unu-la-unu (one-to-one mapping), pe baza existenței unor tabele. Codorul folosește mesajul de la intrare, de lungime k, drept index în tabelul ce conține cuvintele de cod de lungime n (fig.3). Acest tabel trebuie menținut într-o memorie care nu poate fi oricât de mare, limitările fiind impuse nu neapărat de gradul de realizabilitate a memoriei cât scopul, destinația codorului.

Figura 3 – Tabelul de implementare a unui cod bloc (7, 4) având = 3

O categorie de coduri bloc, utilizate ca alternativă la codurile cu lungimi mari ale cuvintelor de cod, o reprezintă codurile ciclice. Un exemplu de codor ciclic sistematic este redat în figura 4. Utilizarea unui LFSR (Linear Feedback Shift Register) permite calcularea biților de paritate în timp ce biții din mesaj sunt trimiși la ieșirea codorului. Apoi, prin schimbarea poziției comutatoarelor, biții de paritate sunt transmiși la ieșire.[7]

Această implementare este mult mai puțin complexă decât marea majoritate a celorlalte coduri bloc, pentru că necesită doar (n-k) celule de memorie în LFSR din codor. Un alt avantaj este că necesită doar un polinom generator.

Binențeles, decodarea codurilor bloc este mai complexă decât codarea, din cauza erorilor produse de zgomotul existent pe canal. Decodarea folosind căutarea într-un tabel imens, pe baza valorii a n biți recepționați este posibilă din punct de vedere computațional doar în cazul codurilor scurte.

O alternativă suboptimă, dar mai accesibilă o reprezintă o serie de algoritmi, ca de exemplu Berlekamp, algoritm ce permite decodarea tuturor combinațiilor de erori și număr de erori, până la valoarea t (numărul maxim de erori corectabile). Codurile ciclice pot folosi algoritmi mai simpli ca Meggit sau Berlekamp-Massey.

Figura 4 – Diagrama bloc a unui codor ce implementează un cod ciclic sistematic

Capitolul III. Coduri convoluționale și decodare Viterbi

Algoritmul Viterbi a fost propus în 1967 ca metodă de decodare a codurilor convoluționale. Acestea din urmă sunt folosite destul de mult în sistemele de comunicații, inclusiv sisteme de comunicații spațiale și comunicații wireless, ca de exemplu IEEE 802.11a/g, WiMax, DAB/DVB, WCDMA și GSM.[8]

III.1 Coduri convoluționale

Codurile convoluționale au fost introduse de Peter Elias în 1955 ca alternativă la codurile bloc, pentru transmisiile pe canale afectate de zgomot. Aceste coduri pot fi aplicate unui flux continuu de date, precum și unor blocuri de date [8].

Sunt coduri corectoare de erori ce codează șirul simbolurilor de intrare într-un șir de simboluri de ieșire. Ele generează n simboluri pentru fiecare cadru de intrare. Un cadru e format din k biți de date, iar cei n biți de ieșire sunt generați în funcție de valorile ultimelor K cadre de date (incluzând cadrul actual de la intrare), K fiind lungimea de constrângere a codului atunci când un cadru e format dintr-un singur bit. Când k ≥ 2(când cadrul e format din cel puțin 2 biți), lungimea de constrângere se exprimă K·k, indicând numărul de biți de care depind cei n generați la ieșire. În acest caz, în care k ≠ 1, în codor se introduc câte k biți simultan și, de asemenea, între registre se transferă câte k biți.

Interes practic prezintă acele coduri ce permit implementarea codorului ca automat cu stări finite, numite coduri trellis.

Parametrii codor:

k = număr biți de intrare

n = număr biți de ieșire

K = lungimea de constrângere (K*k dacă la intrare avem cel puțin câte 2 biți)

Rc = k / n = rata de codare

III.2 Metode de descriere a codurilor convoluționale

Codurile convoluționale pot fi descrise matricial și prin metode grafice.

Descrierea matricială a codurile convoluționale se face cu o matrice generatoare, având elemente polinomiale. Fiecărei ieșiri i se asociază un polinom, acesta putând fi exprimat și ca vector, elementele vectorului indicând dacă există sau nu conexiuni (1 – există conexiune; 0 – nu există conexiune) între cele K registre de câte k biți (inclusiv unul pentru intrare) și sumatoarele modulo-2.[1]

Să considerăm în cele ce urmează cazul codului pe care eu îl implemetez hardware, C(7,5). Pentru generare se folosește codorul redat în figura 5.

Figura 5 – Codor convoluțional de implementare a codului C(7,5)

Pentru un codor convoluțional C(7,3) schema s-ar modifica așa cum se poate observa din figura 6.

Figura 6 – Codor convoluțional C(7,3)

Parametrii codorului sunt: k = 1, n = 2, Rc = ½, K = 3. Polinoamele generatoare corespunzătoare celor două ieșiri: ;

;

Codorul de mai sus se notează cu C(111, 101), între paranteze specificându-se vectorii corespunzători celor 2 ieșiri; mai utilizată este notația în octal C(7,5).

Polinoamele generatoare pentru ieșiri se aleg astfel încât protecția oferită la erori să fie cât mai bună, și se determină prin simulare. Există tabele cu astfel de polinoame.

Codurile convoluționale se clasifică în recursive (există cale de reacție dinspre ieșire spre intrare) și nerecursive, sistematice (simbolurile informaționale se regăsesc în ieșire fie la începutul cuvântului de cod, fie la sfârșit) și nesistematice. Frecvent, codurile sistematice sunt și recursive, iar cele nesistematice sunt și nerecursive, dar nu e o regulă [1]. Un codor poate fi descris și prin transcrierea trellisului într‐un tabel de tipul:

Tabel 1 – Exemplu de descriere a unui codor prin tabel

Descriere grafică a codurilor convoluționale se face cu diagrame de stări, diagrame arbore și diagrame trellis.

Diagrama trellis este o descriere grafică cu evoluție în timp (momente discrete t = 0,1,2,3,..) a unui cod convoluțional, la fiecare moment fiind reprezentate cele 2k·(K-1) stări sub formă de noduri, iar sub formă de săgeți se reprezintă tranzițiile dintre ele, din fiecare nod ieșind 2k săgeți (numărul variază cu numărul k de biți din intrare). Pentru cazul codului ales de mine, C(7,5), diagrama trellis este cea din figura 7.

Figura 7 – Diagrama trellis a codului C(7,5)

Pe săgețile de tranziție sunt trecute valorile de ieșire, iar săgețile îndreptate în sus sunt tranzițiile pentru cazurile în care la intrare avem bit 0, iar cele în jos, pentru bit de intrare 1. Întotdeauna în construcția trellisului se pleacă din starea nulă, 00. Se observă că pentru lungimea de constrângere K=3 (ieșirea depinde de cei 2 biți din codor și de bitul din intrare), după nivelul 3, trellisul e periodic, se repetă.

Astfel, dacă la intrare avem succesiunea 1101…, la ieșire se generează: 11010100…. Decodarea secvenței generate se poate face prin metode secvențiale (algoritmul Fano) sau metode pe principiul corelației maxime (algoritmul Viterbi).

III.3 Algoritmul de decodare Viterbi

Este un algoritm ce lucrează pe principiul găsirii drumului cel mai probabil dintr-un graf (calea de corelație maximă – maximum likelihood path). Decodorul Viterbi fiind un estimator al acesteia, el realizează o parcurgere a trellisului astfel încât distanța Hamming între secvența recepționată și cea estimată să fie minimă. Prin decodare Viterbi se realizează simultan atât detecția cât și corecția erorilor aleatoare (nu se corectează erori grupate).

Pentru un cod convoluțional cu k=1 și lungime de constrângere K, trellisul va conține 2K-1 stări. La fiecare nivel de decodare, din fiecare stare pornesc 2 săgeți (ramuri). Pentru fiecare din acestea se calculează distanța Hamming dintre secvența recepționată și secvența corespunzătoare ramurii (branch metric – BM). O succesiune de astfel de ramuri (săgeți) formează o cale, și pentru fiecare cale, prin însumarea BM-urilor ramurilor componente, se determină metrica de cale (path metric – PM). În fiecare nod (stare) converg câte 2 căi, și dintre acestea se alege una, iar aceasta să fie cea de metrică minimă (survivor).

Pentru codurile convoluționale cu k > 1 biți de date, cu lungime de decodare K, sunt 2k·(K-1) stări/nivel din trellis, 2k săgeți ce intră în fiecare stare și un total de 2k·K săgeți/nivel din trellis. Procesul de decodare va implica, pentru fiecare nivel de decodare, calcularea a 2k·K metrici de ramuri (BM), 2k·K metrici de căi (PM), reținearea a 2k·(K-1) căi (survivors) cu metricele lor.

Figura 8 – Exemplu de codor convoluțional având k ≠ 1 (k = 2 și n = 3)

Algoritmul Viterbi cuprinde următorii pași:

1. Se stabilește ca și stare inițială starea nulă pe nivelul j = 0;

2. Se inițializează metrica de stare,

3. Se incrementează variabila de nivel: j = j+1;

4. Se calculează metrica fiecărei căi ce intră în nodurile de pe nivelul j al trellisului, pe baza metricii stării anterioare și a distanței Hamming dintre secvența recepționată și secvența corespunzătoare ramurei cu care se ajunge în actuala stare:

(7)

(8)

5. Dintre căile ce intră într-un nod (2k) se alege calea cu metrică minimă (survivor), iar metrica stării pe nivelul j al trellisului (,,, … ) ia valoarea acesteia.

6. Dacă j e mai mic decât lungimea de decizie L, se revine la pasul 3.

7. Se decide că s-a recepționat secvența corespunzătoare căii ce intră în starea cu metrică minimă.

OBS. Notația se citește: metrica stării 0 pe nivelul j din trellis. = metrica ramurii care pleacă din starea i1 și ajunge, pe nivelul j, în starea i2. = metrica stării i1 de pe nivelul j-1 din trellis (starea anterioară).

Dacă nu s-a reușit identificarea unei singure căi, se delimitează secvența în cauză, se decide că s-a realizat detecție de erori (nu și corecție) și se trimite cerere de retransmitere a acesteia.

Parcurgearea trellisului la decodare se poate face, teoretic pentru secvențe oricât de lungi, însă practic se impun anumite limite deoarece o secvență oricât de lungă ar necesita o memorie oricât de mare (pentru memorarea căilor cu corelație maximă) și o întârziere de decodare inadmisibilă pentru multe aplicații. Din această cauză și valorile pentru K și k sunt destul de mici (ex. K ≈ 3; 5; 7). Tot pentru a evita o întârziere prea mare se recurge la segmentarea procesului de decizie, ceea ce înseamnă că se iau decizii parțiale imediat ce s-a obținut o stare de metrică minimă, însă nu mai devreme de L nivele, L numindu-se lungime de decizie. Frecvent se utilizează pentru decodare o lungime finită L ≥ 5·K, și s-a observat experimental că degradarea performanțelor este neglijabilă, comparativ cu cazul unei lungimi infinite.

Lungimea de decizie reprezintă numărul minim de nivele din graf care asigură capacitatea maximă de corecție a erorilor pe baza codului analizat.

III.4 Performanțele unui cod

O metodă de a studia capacitatea unui cod convoluțional de detecție și corecție a erorilor constă în a construi un profil al distanțelor. Acesta este secvența monoton crescătoare a distanțelor minime de ordin 1, 2, 3, …, iar free Hamming distance, , reprezintă limita superioară a acestora.

Distanța minimă de ordin L a codului convoluțional este egală cu distanța Hamming minimă dintre oricare 2 secvențe de L cadre (rezultate prin parcurgerea trellisului) care nu corespund în cadru inițial. Pentru codurile liniare, calculul distanțelor codului revine la a calcula distanțele Hamming dintre secvența codată (nenulă în primul cadru) și secvența formată doar din 0 (all-zero word). [1]

Pentru un cod convoluțional având distanța minimă de ordin L, dL, se pot corecta t erori în primele L cadre dacă 2·t + 1 ≤ dL.

Figura 9 – Diagrama de stări a codorului C(7,5) reprezentat în figura 5

Pentru a determina numărul erorilor coretabile în cazul codului C(7,5) am construit profilul distanțelor după cum urmează. Am calculat distanțele Hamming minime pe fiecare nivel din trellis. Deoarece codul este liniar, e suficient să determin distanța Hamming între cuvântul de cod format doar din 0 și cuvintele de cod nenule în primul cadru. Am obținut următoarele:

Nivel1: 00 si 11 => dH = 2

Nivel2: 11 10

11 01 => dH = 3

Nivel3: 11 10 11 5 11 01 01 4

11 10 00 3 11 01 10 4 dH = 3

Nivel4: 11 10 11 00 5 11 10 00 104 11 01 01 116 11 01 10 015

11 10 11 117 11 10 00 014 11 01 01 004 11 01 10 105dH =4

….

Calculul se continuă până la nivelul 6, unde se ajunge pe calea fundamentală(doar de 0), cu calea de metrică minimă, dHmin = 5.

Profilul distanțelor: {2, 3, 3, 4, 4, 5, 5,…}

Numărul de biți corectabili se determină astfel:

2t+1 ≤ dHfree dHfree = 5 t =2

Înseamnă că pot fi corectate 2 erori în 6 cadre ( 2 biți din 12) .

Capitolul IV. Descrierea hardware a codecului

Codorul și decodorul pe care eu le-am ales pentru implementare hardware, sunt construite astfel încât datele codate de la ieșirea codorului să fie transmise serial către decodor.

Figura 10 – Schema bloc a codecului ce pune în evidență semnalele de comunicație între generator de date – codor – decodor

IV.1 Codorul

Codorul construit de mine are schema bloc redată în figura 10, implementează codul C(7,5) ai cărui parametrii sunt cuprinși în tabelul 1 și l-am descris hardware în fișierul top_encoder.v, a cărui conținut este adăugat imediat după schema bloc.

Figura 11 – Schema bloc a codorului cu ieșire serială

module top_encoder(

data_in,

data_out,

clk,

reset,

enable,// from data generator

device_ready,

en_data_out

);

input data_in, clk, reset, enable;

output data_out, device_ready, en_data_out;

wire [1:0] code;

convo_encoder v1 (.data(data_in), .code(code), .clk(clk), .reset(reset), .enable(enable));

parallel2serial_unit v2 (.data(code), .serial_data(data_out), .device_ready(device_ready),

.enable_in(enable), .reset(reset), .clk(clk), .en_data_out(en_data_out));

endmodule

Operațiile efectuate la emisie mai cuprind, pe lângă codarea convoluțională, și conversia paralel – serie a codului rezultat.

IV.1.1 Codorul convoluțional

Codorul convoluțional propriu-zis este descris de convo_encoder.v și redat în figura 12, iar codul Verilog de implementare a acestuia urmează imediat după aceasta.

Tabel 2 – Parametrii codorului convoluțional C(7,5)

Figura 12 – Schema bloc a codorului convoluțional C(7,5)

module convo_encoder(

data,

code,

clk,

reset,

enable

);

input data;

input clk, reset, enable;

output [1:0] code;

wire data;

reg [1:0] code;

wire [1:0]q;

dff_en dff1 (.D(data), .Q(q[1]), .clk(clk), .reset(reset), .enable(enable));

dff_en dff2 (.D(q[1]), .Q(q[0]), .clk(clk), .reset(reset), .enable(enable));

always @(posedge clk)

begin

if(enable == 1'b1)

begin

code[0] <= data ^ q[1] ^ q[0];

code[1] <= data ^ q[0];

end

end

endmodule

IV.1.2 Conversia paralel – serie

Operația de conversie efectuată de blocul parallel2serial_unit este controlată de o mașină secvențială instanțiată în parallel2serial_unit.v, iar diagrama de stări a acesteia este redată în figura 13.

Figura 13 – Diagrama de stări a FSM ce controlează serializarea datelor codate

Codul Verilog ce descrie funcționarea blocului de conversie este:

module parallel2serial_unit(

serial_data,

device_ready,

data,

clk,

reset,

enable_in,

en_data_out

);

parameter SIZE = 2;

input [SIZE-1:0] data;

input clk, reset, enable_in;

output serial_data;

output device_ready;//to data generator

output en_data_out; //to decoder

wire [SIZE-1:0] q1;

wire mux_out;

wire sel;

//input register on SIZE bits

dff#(SIZE) ff_1 (.Q(q1), .D(data), .clk(clk), .reset(reset));

function mux;

input b0, b1;

input sel;

begin

if (sel)

mux = b1;

else

mux = b0;

end

endfunction

assign mux_out = mux(q1[0], q1[1], sel);

dff ff_out (.Q(serial_data), .D(mux_out), .clk(clk), .reset(reset));

ctrl_p2s fsm (.reset(reset), .clk(clk), .sel(sel), .device_ready(device_ready),

.enable(enable_in), .en_data_out(en_data_out));

endmodule

Schema bloc a acestuia:

Figura 14 – Schema bloc a convertorului paralel – serie

Codul Verilog de implementare a FSM ce controlează serializarea datelor, cu diagrama de stări din figura 13, este următorul:

module ctrl_p2s(

reset,

enable,

clk,

sel,

device_ready,

en_data_out

);

input reset, enable, clk;

output sel, device_ready, en_data_out;

parameter [2:0]IDLE = 3'b000;

parameter [2:0]READY= 3'b001;

parameter [2:0]ST0 = 3'b010;

parameter [2:0]ST1 = 3'b011;

parameter [2:0]ST2 = 3'b100;

parameter [2:0]ST3 = 3'b101;

reg [2:0] state, next_state;

reg sel, device_ready, en_data_out;

always @(state or reset or enable)

begin

next_state = 3'b000;

sel = 1'bx;

device_ready = 1'bx;

en_data_out = 1'bx;

case(state)

IDLE: begin

sel = 1'bx;

device_ready = 1'b0;

en_data_out = 1'b0;

if(reset)

begin

next_state = IDLE; end

else

begin

next_state = READY; end

end

READY: begin

sel = 1'bx;

device_ready = 1'b1;

en_data_out = 1'b0;

if(reset)

begin

next_state = IDLE; end

else

if (enable == 1'b1)

begin

next_state = ST0; end

else

begin

next_state = READY; end

end

ST0: begin

sel = 1'bx;

device_ready = 1'b1;

en_data_out = 1'b0;

if(reset)

begin

next_state = IDLE; end

else

begin

next_state = ST1; end

end

ST1: begin

sel = 1'b0;

device_ready = 1'b1;

en_data_out = 1'b0;

if(reset)

begin

next_state = IDLE; end

else

begin

next_state = ST2; end

end

ST2: begin

sel = 1'b1;

device_ready = 1'b1;

en_data_out = 1'b1;

if(reset)

begin

next_state = IDLE; end

else

begin

next_state = ST3; end

end

ST3: begin

sel = 1'b0;

device_ready = 1'b1;

en_data_out = 1'b1;

if(reset)

begin

next_state = IDLE; end

else

begin

next_state = ST2; end

end

default: next_state = IDLE;

endcase

end

// present state logic

always @(posedge clk)

begin

if (reset)

state <= IDLE;

else

state <= next_state;

end

endmodule

IV.2 Decodorul Viterbi

În cele ce urmează prezint modul de realizare a decodorului Viterbi [5] necesar decodării cu corecție a datelor codate C(7,5), generate de codorul descris în secțiunea anterioară. Blocurile componente ale decodorului se pot observa din figura 15 și le voi detalia pe parcurs. Pentru a-mi putea atinge scopul este necesar să reconstitui într-o oarecare masură, diagrama trellis a codului C(7,5), redată în figura 7 (pag.18). Deoarece datele sunt recepționate serial, înainte de a începe efectuarea operațiilor corespunzătoare algoritmului Viterbi, se efectuează mai întâi operația de conversie serie – paralel. Procesul de conversie este pornit din momentul în care en_data_in = 1L, semnal ce indică decodorului (mai exact blocului serial2parallel) faptul că datele prezente la intrare sunt valide. Semnalul en_data_in al decodorului coincide cu en_data_out de la codor, semnal ce devine activ în momentul în care începe furnizarea de date la ieșire codor (aceste momente sunt fronturi pozitive de ceas, front ales ca front activ).

Întregul circuit este construit astfel încât să fie perfect sincron, funcționând pe baza unui singur semnal de ceas și a unui singur front activ, cel pozitiv.

Codul Verilog de descriere a circuitului de decodare are următoarea formă:

module top_viterbi(

decoded_data,

error,

en_data_in,

rcv,

clk,

reset

);

input rcv;

input clk, reset, en_data_in;

output decoded_data;

output error;

wire [1:0] parallel_rcv, min_ctrl, BM_0, BM_1, BM_2, BM_3;

wire [4:0] p_00, p_02, p_10, p_12, p_21, p_23, p_31, p_33,

p_0, p_1, p_2, p_3, smr0, smr1, smr2, smr3,

sm0, sm1, sm2, sm3;

wire rcv, reset_mem, en_metric_path_memory, en_BM, error;

wire ACS0, ACS1, ACS2, ACS3;

wire [3:0] path_in;

wire p0, p1, p2, p3; // bitii de date coresp. fiecarei stari;

// dintre ei se alege out_data

// in fctie de val. min_ctrl

wire decoded_data;

serial2parallel u0 (.parallel_data(parallel_rcv), .reset_mem(reset_mem),

.serial_data(rcv), .clk(clk), .reset(reset),

.en_data_in(en_data_in),.en_BM(en_BM),

.en_metric_path_memory(en_metric_path_memory));

BM_unit u1 (.rcv_code(parallel_rcv), .BM_0(BM_0), .BM_1(BM_1),

.BM_2(BM_2), .BM_3(BM_3), .clk(clk), .reset(reset),

.enable(en_BM));

compute_path_metric u2 (.error(error), .p_0(p_0), .p_1(p_1), .p_2(p_2),

.p_3(p_3),.BM_0(BM_0),.BM_1(BM_1), .BM_2(BM_2),

.BM_3(BM_3), .p_00(p_00), .p_02(p_02), .p_10(p_10),

.p_12(p_12), .p_21(p_21), .p_23(p_23),

.p_31(p_31), .p_33(p_33));

compare_select u3 (.ACS0(ACS0), .ACS1(ACS1), .ACS2(ACS2),.ACS3(ACS3),

.sm0(sm0), .sm1(sm1), .sm2(sm2), .sm3(sm3),

. p_00(p_00), .p_02(p_02), .p_10(p_10), .p_12(p_12),

.p_21(p_21), .p_23(p_23), .p_31(p_31),

.p_33(p_33));

reduce_metric u4 (.min_ctrl(min_ctrl), .smr_0(smr0), .smr_1(smr1),

.smr_2(smr2), .smr_3(smr3), .sm_0(sm0), .sm_1(sm1),

.sm_2(sm2), .sm_3(sm3));

metric_memory u5 (.m_out0(p_0), .m_out1(p_1), .m_out2(p_2), .m_out3(p_3),

.m_in0(smr0), .m_in1(smr1), .m_in2(smr2), .m_in3(smr3),

.clk(clk), .reset(reset), .reset_mem(reset_mem),

.enable(en_metric_path_memory));

pathin u6 (.path0(path_in), .ACS0(ACS0), .ACS1(ACS1), .ACS2(ACS2),

.ACS3(ACS3));

path_memory u7 (.p_in(path_in), .p0(p0), .p1(p1), .p2(p2), .p3(p3),

.ACS0(ACS0), .ACS1(ACS1), .ACS2(ACS2), .ACS3(ACS3),

.clk(clk), .reset(reset), .enable(en_metric_path_memory));

output_decision u8 (.mesaj_decod(decoded_data), .p0(p0), .p1(p1), .p2(p2),

.p3(p3), .min_path(min_ctrl));

endmodule

Figura 15 – Schema bloc a decodorului Viterbi

IV.2.1 Conversia serie – paralel

Datele pe comunicația serială circulă cu o viteză dublă față de cea la care sunt furnizate de generator, codorului și de către decodor la ieșire. Acest aspect face ca pentru a avea un circuit util, să fie necesară conversia paralel – serie la emisie (codorul convoluțional furnizează cuvinte de cod pe 2 biți în mod paralel) și serie – paralel la recepție (decodorul lucrează cunoscând cuvântul de cod de 2 biți, recepționat).

Figura 16 – Schema blocului de conversie serie – paralel

Operația de conversie este controlată de ctrl_unit, un FSM ce furnizează semnalul de selecție sel a cărui rol urmează să îl explic. La primul tact, la ieșirea primului bistabil vom avea prezent bitul LSB al cuvântului de cod recepționat, iar la al doilea tact, când valoarea bitului LSB se regăsește la ieșirea celui de-al doilea bistabil iar cea a bitului MSB la ieșirea primului, sel = 1L, lucru ce face ca la ieșire, parallel_data[1:0] sa fie o concatenare a celor două valori. Un alt rol al acestui FSM este de a furniza semnalele de autorizare necesare deocodorului pentru a funcționa corespunzător. Mai exact:

en_BM, semnal de enable pentru BM_unit, care este activ o perioadă de ceas(datele după conversie își păstrează valoarea două perioade de ceas), asfel încât unitatea de calcul a metricelor de ramuri (BM_unit) să nu calculeze metricile corespunzătoare, pentru același cuvânt recepționat, de două ori.

reset_mem, semnal de reset necesar blocului metric_memory, semnal ce este activ timp de o perioadă de ceas, înainte ca metric_memory să preia primele metrici de stare calculate.

en_metric_path_memory, semnal de autorizare pentru metric_memory și pentru path_memory, necesar acestor blocuri din același motiv ca și en_BM.

Desrierea RTL (Register Transfer Level) a blocului serial2parallel:

module serial2parallel(

parallel_data,

reset_mem,

en_BM,

en_metric_path_memory,

serial_data,

en_data_in,

clk,

reset

);

output [1:0]parallel_data;

output reset_mem, en_BM, en_metric_path_memory;

input serial_data, en_data_in;

input clk, reset;

wire bit1, bit0;

wire sel;

reg [1:0] parallel_data;

dff ff_1 (.D(serial_data), .Q(bit1), .clk(clk), .reset(reset));

dff ff_0 (.D(bit1), .Q(bit0), .clk(clk), .reset(reset));

always @(posedge clk)

begin

if(sel)

begin

parallel_data <= {bit1, bit0};

end

end

ctrl_s2p ctrl_unit (.en_metric_path_memory(en_metric_path_memory),

.en_BM(en_BM), .reset_mem(reset_mem), .sel(sel),

.clk(clk), .reset(reset), .en_data_in(en_data_in));

endmodule

Figura 17 – Diagrama de stări a FSM ctrl_s2p

FSM a cărui diagramă de stări e reprezentată în figura 17, are următorul cod Verilog:

module ctrl_s2p(

en_BM,

en_metric_path_memory,

sel,

reset_mem,

en_data_in,

reset,

clk

);

input reset, clk, en_data_in;

output sel, reset_mem, en_BM, en_metric_path_memory;

parameter [2:0] IDLE = 3'b000;

parameter [2:0] READY = 3'b001;

parameter [2:0] _1TCK = 3'b010;

parameter [2:0] _2TCK = 3'b011;

parameter [2:0] _3TCK = 3'b100;

parameter [2:0] ST0 = 3'b101;

parameter [2:0] ST1 = 3'b110;

reg [2:0] state, next_state;

reg sel, reset_mem, en_BM, en_metric_path_memory;

always @(reset or state or en_data_in)

begin

next_state = 0;

sel = 1'bx;

reset_mem = 1'bx;

en_BM = 1'bx;

en_metric_path_memory = 1'bx;

case(state)

IDLE: begin

sel = 1'bx;

reset_mem = 1'b0;

en_BM = 1'b0;

en_metric_path_memory = 1'b0;

if (reset)

next_state = IDLE;

else

next_state = READY;

end

READY: begin

sel = 1'bx;

reset_mem = 1'b0;

en_BM = 1'b0;

en_metric_path_memory = 1'b0;

if (reset)

next_state = IDLE;

else

begin

if (en_data_in)

next_state = _1TCK;

else

next_state = READY;

end

end

_1TCK: begin

sel =1'b0;

reset_mem = 1'b0;

en_BM = 1'b1;

en_metric_path_memory = 1'b0;

if (reset)

next_state = IDLE;

else

begin

if (en_data_in)

next_state = _2TCK;

else

next_state = READY;

end

end

_2TCK: begin

sel = 1'b1;

reset_mem = 1'b0;

en_BM = 1'b0;

en_metric_path_memory = 1'b1;

if (reset)

next_state = IDLE;

else

begin

if (en_data_in)

next_state = _3TCK;

else

next_state = READY;

end

end

_3TCK: begin

sel = 1'b0;

reset_mem = 1'b1;

en_BM = 1'b1;

en_metric_path_memory = 1'b0;

if (reset)

next_state = IDLE;

else

begin

if (en_data_in)

next_state = ST0;

else

next_state = READY;

end

end

ST0: begin

sel = 1'b1;

reset_mem = 1'b0;

en_BM = 1'b0;

en_metric_path_memory = 1'b1;

if (reset)

next_state = IDLE;

else

begin

if (en_data_in)

next_state = ST1;

else

next_state = READY;

end

end

ST1: begin

sel = 1'b0;

reset_mem = 1'b0;

en_BM = 1'b1;

en_metric_path_memory = 1'b0;

if (reset)

next_state = IDLE;

else

begin

if (en_data_in)

next_state = ST0;

else

next_state = READY;

end

end

default: next_state = IDLE;

endcase

end

// present state logic

always @(posedge clk)

begin

if (reset)

state <= IDLE;

else

state <= next_state;

end

endmodule

IV.2.2 BM_unit – blocul de calcul al metricelor de ramuri

Pentru a putea explica funcționalitatea acestui bloc, mă voi referi la diagrama trellis a codului în cauză, C(7,5) din figura 7 (pag.18).

În fiecare din cele 4 stări sunt câte 2 săgeți de intrare, săgeți ce pleacă din 2 stări diferite de pe nivelul anterior al trellis-ului. În total sunt 8 săgeți care fac tranziția între două nivele oarecare ale trellis-ului, fiecare din acestea având indicat pe ea cuvântul de cod corespunzător ei. Sunt 4 cuvinte diferite, posibile de 2 biți, iar rolul lui BM_unit este de a calcula distanța Hamming dintre cuvântul recepționat și cele 4 posibile cuvinte: 00, 01, 10, 11. Metricele rezultate, numite metrici de ramuri deoarece corespund câte unei ramuri (săgeți), BM_0, BM_1, BM_2, BM_3 sunt ieșirile acestui bloc și vor fi utilizate în continuare de compute_path_metric.

Figura 18 – Reprezentarea semnalelor de ieșire și de intrare pentru BM_unit

module BM_unit(

rcv_code,

BM_0,

BM_1,

BM_2,

BM_3,

clk,

reset,

enable

);

input [1:0] rcv_code;

input clk, reset, enable;

//metricile calculate intre codul receptionat si cele 4 posibile combinatii

// de 2 biti de pe ramurile trellisului

output [1:0] BM_0, BM_1, BM_2, BM_3;

wire [1:0] BM_0, BM_1, BM_2, BM_3, rcv_out;

parameter B_0 = 2'b00;

parameter B_1 = 2'b01;

parameter B_2 = 2'b10;

parameter B_3 = 2'b11;

dff_en#(2) mem(.D(rcv_code), .Q(rcv_out), .clk(clk), .reset(reset), .enable(enable));

assign BM_0 = hamm_dist(rcv_out, B_0),

BM_1 = hamm_dist(rcv_out, B_1),

BM_2 = hamm_dist(rcv_out, B_2),

BM_3 = hamm_dist(rcv_out, B_3);

// functie de calcul a distantei Hamming intre secventa receptionata si cele 4

// combinatii posibile de 2 biti

function [1:0] hamm_dist;

input [1:0] code, branch;

hamm_dist = (code[1] ^ branch[1]) + (code[0] ^ branch[0]);

endfunction

endmodule

IV.2.3 Calculul metricelor de cale – compute_path_metric

O altă etapă a decodării, având la bază refacerea operațiilor necesare parcurgerii trellis-ului, este determinarea metricelor de cale. Acestea reprezintă o cumulare a metricelor de ramuri , ramuri parcurse până la un anumit nivel din trellis. În fiecare din cele 4 stări de pe un nivel din trellis, intră prin intermediul a 2 ramuri, 2 căi. În total sunt 8 căi și fiecare cale pleacă dintr-o anumită stare de pe nivelul anterior din trellis, stare caracterizată la rândul ei de o metrică, metrică de stare. Metricele celor 8 căi se determină prin sumarea metricii stării anterioare (de pe nivelul anterior din trellis) cu metrica ramurii ce ajunge în starea de pe noul nivel din trellis, rezultând: p_00, p_02 (metrica pentru calea ce pleacă din starea 0 și ajunge în starea 2), p_12, p_10, p_21, p_23, p_31, p_33.

Figura 19 – Semnalele de intrare și de ieșire din compute_path_metric

Fiecare metrică are asignat un bus de 5 biti, din care 4 ne indică o valoare validă a acesteia. Al 5-lea bit indică o eventuală depășire a valorii maxim admise (25-1). În cazul în care, în urma sumării dintre o metrică de cale și una de ramură, rezultă o valoare ce presupune setarea bitului 4 din reprezentarea binară, semnalul error semnalează acest lucru la ieșirea din decodor, luând valoarea 1L[5].

Codul RTL de descriere hardware a acestor operații este:

module compute_path_metric(

error,

p_00,

p_02,

p_10,

p_12,

p_21,

p_23,

p_31,

p_33,

p_0,

p_1,

p_2,

p_3,

BM_0,

BM_1,

BM_2,

BM_3

);

output error;

output [4:0] p_00, p_02, p_10, p_12, p_21, p_23, p_31, p_33;

input [1:0] BM_0, BM_1, BM_2, BM_3;

input [4:0] p_0, p_1, p_2, p_3;

wire error;

wire [4:0] p_00, p_02, p_10, p_12, p_21, p_23, p_31, p_33;

assign p_00 = p_0 + BM_0,

p_02 = p_0 + BM_3,

p_10 = p_1 + BM_3,

p_12 = p_1 + BM_0,

p_21 = p_2 + BM_1,

p_23 = p_2 + BM_2,

p_31 = p_3 + BM_2,

p_33 = p_3 + BM_1;

function overflow;

input a, b, c, d, e, f, g, h;

if(a | b | c | d | e | f | g | h)

overflow = 1'b1;

else

overflow = 1'b0;

endfunction

// se testeaza daca s-a depasit valoarea maxim admisa pt metrica de cale

assign error = overflow (p_00[4], p_02[4], p_10[4], p_12[4], p_21[4],

p_23[4], p_31[4], p_33[4]);

endmodule

IV.2.4 Determinarea metricilor de stare – compare_select

Cele 8 metrici de cale calculate de blocul compute_path_metric sunt în continuare procesate de compare_select. Astfel, știm că în fiecare stare intră câte 2 căi. Dintre acestea se alege câte una ca surviever și anume cea de metrică minimă și care devine noua metrică de stare. La ieșirea blocului voi avea cele 4 noi metrici de stare. Pe lângă aceste semnale, se remarcă prezența altor 4, ACS0 – 3, rolul acestora fiind de a arăta care dintre cele două căi posibile a fost aleasă. Aceste semnale vor fi ulterior folosite de alte blocuri în determinarea datelor decodate de la ieșirea decodorului [5].

Figura 20– Semnalele de intrare și de ieșire din compare_select

Codul Verilog de descriere a acestui bloc:

module compare_select(

ACS0,

ACS1,

ACS2,

ACS3,

sm0,

sm1,

sm2,

sm3,

p_00,

p_02,

p_10,

p_12,

p_21,

p_23,

p_31,

p_33

);

input [4:0] p_00, p_02, p_10, p_12, p_21, p_23, p_31, p_33;

output ACS0, ACS1, ACS2, ACS3;

output [4:0] sm0, sm1, sm2, sm3; // metrici asociate celor 4 stari

//functie pt determinarea metrcii de stare

function [4:0] find_min_metric;

input [4:0] a, b;

begin

if (a <= b)

find_min_metric = a;

else

find_min_metric = b;

end

endfunction

//functie pt indicarea carei cai din cele 2 de in intr-o stare

//este aleasa ca si cale de metrica minima

function select;

input [4:0] a, b;

begin

if (a <= b)

select = 1'b0;

else

select = 1'b1;

end

endfunction

assign sm0 = find_min_metric (p_00, p_10),

sm1 = find_min_metric (p_21, p_31),

sm2 = find_min_metric (p_02, p_12),

sm3 = find_min_metric (p_23, p_33),

ACS0 = select (p_00, p_10),

ACS1 = select (p_21, p_31),

ACS2 = select (p_02, p_12),

ACS3 = select (p_23, p_33);

endmodule

IV.2.5 Evitarea depășirii valorii maxim admise pentru metrici – reduce_metric

Deoarece metricile calculate de la un nivel la altul (al diagramei trellis) cresc, iar decodorul funcționează atâta timp cât la intrare sunt introduse date recepționate, este necesar un modul care să limiteze valoarea acestora astfel încât procesul de decodare să nu fie alterat.

Blocul determină minima dintre cele 4 metrici de stare calculate de blocul compare_select, indicată prin semnalul de ieșire min_path[1:0]. Valoarea metricii minime se scade din toate cele 4, deci diferența dintre ele nu e afectată, iar noile metrici de stare, reduse sunt furnizate la ieșire: smr0 – 3.

Figura 21 – Semnalele de intrare și de ieșire din reduce_metric

Codul Verilog al reduce_metric:

module reduce_metric(

min_ctrl,

smr_0,

smr_1,

smr_2,

smr_3,

sm_0,

sm_1,

sm_2,

sm_3

);

input [4:0] sm_0, sm_1, sm_2, sm_3;

output [1:0] min_ctrl;

output [4:0] smr_0, smr_1, smr_2, smr_3;

wire [4:0] minim;

function [4:0] find_minim;

input [4:0] m0, m1, m2, m3;

reg [4:0] min1, min2;

begin

if (m0 <= m1)

min1 = m0;

else

min1 = m1;

if (m2 <= m3)

min2 = m2;

else

min2 = m3;

if (min1 <= min2)

find_minim = min1;

else

find_minim = min2;

end

endfunction

function [1:0] minim_no;

input [4:0] in0, in1, in2, in3, minim;

begin

if (minim == in0)

minim_no = 0;

else if (minim == in1)

minim_no = 1;

else if (minim == in2)

minim_no = 2;

else

minim_no = 3;

end

endfunction

assign minim = find_minim (sm_0, sm_1, sm_2, sm_3);

assign smr_0 = sm_0 – minim,

smr_1 = sm_1 – minim,

smr_2 = sm_2 – minim,

smr_3 = sm_3 – minim;

assign min_ctrl = minim_no (sm_0, sm_1, sm_2, sm_3, minim);

endmodule

IV.2.6 Memorarea metricelor – metric_memory

Metricele de ramuri, BM, calculate de BM_unit, au valori menținute neschimbate timp de o perioadă de ceas. În schimb, metricele de cale și de stare (p_00 – p_33, sm0 –m3,smr0 – smr3) sunt determinate ori de câte ori una din valorile de care depind se modifică. Dacă ar lipsi acest circuit de memorare, metricele de stare de la intrarea în compute_path_metric s-ar actualiza necontrolat, de mai multe ori cu metricele de ramuri (care se modifică la 2 perioade de tact) calculate pentru un același cuvânt recepționat. Rezultatele ar fi inutilizabile pentru decodare.

Figura 22- Semnalele de intrare și de ieșire din reduce_metric

Codul Verilog corespunzător e dat mai jos.

module metric_memory(

m_out0,

m_out1,

m_out2,

m_out3,

m_in0,

m_in1,

m_in2,

m_in3,

clk,

reset,

reset_mem,

enable

);

input clk, reset, reset_mem, enable;

input [4:0] m_in0, m_in1, m_in2, m_in3;

output [4:0] m_out0, m_out1, m_out2, m_out3;

wire reset_ff;

assign reset_ff = reset ? reset : reset_mem;

dff_en #(5) u0 (.D(m_in0), .Q(m_out0), .clk(clk), .reset(reset_ff), .enable(enable));

dff_en #(5) u1 (.D(m_in1), .Q(m_out1), .clk(clk), .reset(reset_ff), .enable(enable));

dff_en #(5) u2 (.D(m_in2), .Q(m_out2), .clk(clk), .reset(reset_ff), .enable(enable));

dff_en #(5) u3 (.D(m_in3), .Q(m_out3), .clk(clk), .reset(reset_ff), .enable(enable));

endmodule

IV.2.7 Estimarea biților de date – pathin

Semnalele ACS0-3 arată care din cele 2 căi posibile de intrare într-o stare (de pe un nivel al diagramei trellis) este cea de metrică minimă (survivor path). De exemplu, în starea 0 se ajunge cu o cale dinspre starea 0 a nivelului anterior și o alta dinspre starea 1 a nivelului anterior. Dacă ACS=0 înseamnă că survivor path este calea ce vine dinspre starea 0 anterioară; dacă ACS = 1 atunci starea anterioară ar fi fost 1.

Fiecare din cele 2 ramuri are asociat un cuvânt de cod pe 2 biți, generat la codare pentru o anumită valoare a bitului de la intrare. Dacă, pentru exemplul considerat, calea de metrică minimă ar fi cea dinspre starea 0 anterioară, atunci cuvântul de cod corespunzător ramurii este 00, generat la codare de bitul de date 0. Pentru cazul în care calea dinspre starea 1 anterioară ar fi cea de metrică minimă, atunci cuvântul de cod ar fi 11 și bitul de date ar fi 0(aceste valori se pot ușor observa fie pe diagrama de stări a codului fie pe diagrama trellis). Acest bit de date este furnizat la ieșire ca path_in[0]. Fiecărei stări din cele 4 îi corespunde un bit din bus-ul de ieșire path_in[3:0]. Practic path_in[3:0] conține biții de date estimați pentru un nivel din trellis.

Figura 23 – Semnalele de intrare și de ieșire din pathin

module pathin(

path0,

ACS0,

ACS1,

ACS2,

ACS3

);

input ACS0, ACS1, ACS2, ACS3;

output [3:0] path0;

wire [3:0]path0;

parameter B_00 = 1'b0,

B_10 = 1'b0,

B_21 = 1'b0,

B_31 = 1'b0,

B_02 = 1'b1,

B_12 = 1'b1,

B_23 = 1'b1,

B_33 = 1'b1;

function branch;

input a, b;

input sel;

begin

if (sel == 1)

branch = b;

else

branch = a;

end

endfunction

assign path0[0] = branch(B_00, B_10, ACS0), //in starea 0 se ajunge doar cu 0

path0[1] = branch(B_21, B_31, ACS1), //in starea 1 se ajunge doar cu 0

path0[2] = branch(B_02, B_12, ACS2),

path0[3] = branch(B_23, B_33, ACS3);

endmodule

IV.2.8 Blocul path

Reprezintă celula de bază a procesului de reconstituire a datelor, din modulul path_memory, în care va fi instanțiată de un număr de ori egal cu lungimea de decizie[5].

Figura 24 – Semnalele de intrare și de ieșire din path

Structura internă este formată dintr-un registru de 4 biți (câte unul pentru fiecare stare) și o logică de decizie cu funcționalitate identică unui multiplexor, pentru fiecare din cei 4 biți. În figura 24 este redată structura de decizie asociată stării 0.

Figura 25– Logica de decizie corespunzătoare stării 0

Codul Verilog ce descrie blocul path este:

module path(

out,

in,

ACS0,

ACS1,

ACS2,

ACS3,

clk,

reset,

enable

);

input [3:0] in;

input ACS0, ACS1, ACS2, ACS3, clk, reset, enable;

output [3:0] out;

wire [3:0] p_in;

function shift_path;

input a, b, ctrl;

if (ctrl)

shift_path = b;

else

shift_path = a;

endfunction

assign p_in[0] = shift_path (in[0], in[1], ACS0),

p_in[1] = shift_path (in[2], in[3], ACS1),

p_in[2] = shift_path (in[0], in[1], ACS2),

p_in[3] = shift_path (in[2], in[3], ACS3);

dff_en #(4) dff_1 (.D(p_in), .Q(out), .clk(clk), .reset(reset), .enable(enable));

endmodule

IV.2.9 Memorarea biților estimați – path_memory

Blocul e format prin instanțierea de 15 ori a modulului path. Am ales 15 instanțe, pentru că lungimea de decizie pe care am considerat-o este egală cu 15 (5*K, unde K este lungimea de constrângere). Are o structură matricială cu 4 linii și 15 coloane, o coloană fiind echivalentă cu un modul path (modul compus din 4 instanțe cu structura din figura 25).

Structura redă un trellis cu 15 nivele. Fiecărei stări din trellis îi corespunde un rând de 15 celule, fiecare celulă fiind formată din o celulă de memorie cu intrarea de date conectată la ieșirea unui multiplexor. Cele două intrări de date ale multiplexorului sunt conectate la ieșirile a 2 celule anterioare de pe rândurile corspunzătoare celor 2 posibile stări anterioare.

Toate celulele de pe un rând au același semnal de selecție: ACS0, ACS1, ACS2 sau ACS3, indicele fiind același cu cel al stării.

Cum un rând de celule este asociat unei stări, el va conține toți biții decodați, corespunzători acelei căi de metrică minimă (survivor path) care are ca stare finală, starea corespunzătoare rândului. Conținutul celulelor se actualizează la fiecare moment semnificativ de timp, deplasându-se o celulă pe orizontală, iar pe verticală conținutul rândului se deplasează pe rândul ce corespunde noii stări finale a căii de metrică minimă (survivor path).

Prima coloană are intrări fixe, mai exact: prima instanță path are intrările de date conectate la path_in[3:0], semnal ce conține biții decodați corespunzători fiecărei stări din cele 4. Ultima coloană conține bitul decodat corespunzător fiecărei stări, rămânând ca decizia finală să o facă output_decision.

Figura 26 – Semnalele de intrare și de ieșire din path_memory

Descrierea în cod Verilog a circuitului este:

module path_memory(

p0,

p1,

p2,

p3,

p_in,

ACS0,

ACS1,

ACS2,

ACS3,

clk,

reset,

enable

);

input [3:0] p_in;

input ACS0, ACS1, ACS2, ACS3, clk, reset, enable;

output p0, p1, p2, p3;

wire [3:0] out1, out2, out3, out4, out5, out6, out7, out8, out9,

out10, out11, out12, out13, out14, out15;

path u1 (out1, p_in, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u2 (out2, out1, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u3 (out3, out2, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u4 (out4, out3, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u5 (out5, out4, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u6 (out6, out5, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u7 (out7, out6, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u8 (out8, out7, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u9 (out9, out8, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u10 (out10, out9, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u11 (out11, out10, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u12 (out12, out11, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u13 (out13, out12, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u14 (out14, out13, ACS0, ACS1, ACS2, ACS3, clk, reset, enable),

u15 (out15, out14, ACS0, ACS1, ACS2, ACS3, clk, reset, enable);

assign p0 = out15[0],

p1 = out15[1],

p2 = out15[2],

p3 = out15[3];

endmodule

IV.2.10 Alegerea bitului decodat – output_decision

Semnalul min_path[1:0] (semnalul este furnizat de reduce_metric) indică starea în care se ajunge cu calea de metrică minimă dintre cele 4 disponibile, astfel că output_decision va alege ca bit decodat cel conținut de a 15-a celulă de memorie din rândul asociat stării de metrică minimă (mă refer la path_memory). Valoarea ieșirilor din coloana 15, ultima din path_memory, se regăsește în semnalele p0, p1, p2, p3.

Figura 27- Semnalele de intrare și de ieșire din output_decision

Codul Verilog de descriere a funcționalității blocului:

module output_decision(

mesaj_decod,

p0,

p1,

p2,

p3,

min_path

);

input p0, p1, p2, p3;

input [1:0] min_path;

output mesaj_decod;

function decision;

input p0, p1, p2, p3;

input [1:0] min_path;

begin

if (min_path == 2'b00)

decision = p0;

else if (min_path == 2'b01)

decision = p1;

else if (min_path == 2'b10)

decision = p2;

else

decision = p3;

end

endfunction

assign mesaj_decod = decision (p0, p1, p2, p3, min_path);

endmodule

Toate modulele descrise până în acest punct, instanțiate în modulele din vârful ierarhiei design-ului (așa cum am indicat în primele pagini ale capitolului de față) alcătuiesc codecul meu Viterbi, ce implementează codul C(7,5). Este un cod simplu, însă poate fi modificat pentru coduri de dimensiuni mai mari. În cazul de față lungimea de constrângere este K = 3, însă codecurile existente pe piață au constrângeri de până la K = 10. Limitarea este impusă de creșterea necesarului de memorie și a duratei de procesare a datelor odată cu creșterea lungimii de constrângere.

Pentru a verifica corectitudinea celor descrise anterior, mai ales a codului, am făcut o serie de simulări sub ModelSim XE III 6.2g. Rezultatul uneia dintre acestea este redat în următoarea figură, figura 28. Semnificația formelor de undă este următoarea:

clk semnal de ceas cu frecvența de 25Mhz, activ pe front pozitiv

reset reset activ pe 1L

device_ready semnal ce indică generatorului de date că a avut loc resetarea codorului și se pot introduce date în acesta

en_coder autorizează codorul să preia date o dată la 2 perioade de ceas (datele sunt furnizate cu o frecvență 1/2Tck)

data_in semnal date de intrare în codor, generat aleator cu funcîia $random

decoded_data datele de ieșire din decoder; coincid cu data_in de la codor, cu o întârziere de 1520 ns (38*Tclk), timp necesar procesării de la emisie și recepție

serial_code semnalul de ieșire din codor

code_with_noise codul recepționat, afectat de zgomot;

en_data_out indică decodorului că datele de pe bus sunt valide

flag semnalizează poziția bitului afectat de zgomot;

Așa cum am precizat și în capitolul teoretic, codul C(7,5) are capacitate de a corecta 2 biți eronați din 12 biți recepționați. Am făcut simulări și pentru cazul în care ar apărea erori cu frecvență mai mare, dar bineînțeles capacitatea de corecție a codului este depășită. Întârzierea de procesare începând de la intrarea bitului de date în codor și până la ieșirea din decodor este de 1520ns, echivalentul a 38 perioade de ceas.

În figura 29 este reprezentat rezulatul simulării pentru cazul în care datele recepționate de decodor au 3 biți eronați dintr-o succesiune de 12. Conform calculelor codul C(7, 5) corectează cel mult 2 biți din 6 cadre a câte 2 biți fiecare. În cazul din figura 29 capacitatea de corecție a codului este depășită, efectul acestei depășiri se observă asupra datelor obținute la ieșirea decodorului. Se observă că forma de undă decoded_data nu mai urmărește forma de undă a datelor de la intrarea în codor, data_in.

Figura 28 – Simulare pentru cazul în care codul are 2 biți eronați (se receptioneaza code_with_noise)

Figura 29 – Simulare pentru cazul în care codul are 3 biți eronați într-o succesiune de 12 biți

Capitolul V Sinteza logică și configurare pe FPGA

V.1 Modulul integrat Opal Kelly XEM3001

Opal Kelly XEM3001 este un modul integrat ce dispune de un FPGA Spartan-3 (XC3S400-4PQ208C), FPGA ce suportă implementarea de structuri logice mari cât echivalentul a 400.000 de porți[11].

Figura 30- Modulul Opal Kelly XEM3001

Alimentarea plăcii se face implicit de la tensiunea de 5V furnizată de conexiunea prin USB. Aceasta este în continuare divizată astfel încât se obțin tensiuni de 3.3V, 2.5V și 1.8V, circuitul FPGA având nevoie de o tensiune de alimentare de 3.3V. Putem opta pentru o alimentare exterioară aplicată prin intermediul unuia din pinii de 3.3V din conectorii de extindere: JP1, JP2, JP3, cu condiția ca jumper-ul J1 să fie înlăturat [11].

În condiții normale de operare și cu FPGA neconfigurat, către XEM3001 se livrează un current de 125mA. În cazul alimentării prin USB, curentul maxim este de 500mA.

Interfața USB este asigurată de microcontrollerul USB Cypress CY68013 FX2. Astfel dispozitivul XEM3001 este recunoscut ca periferic (USB) plug-and-play de majoritatea calculatoarelor.

Deși este gândit ca dispozitiv low-cost, XEM3001 dispune de periferice on-board esențiale.

O memorie EEPROM în care sunt stocate datele necesare microcontrollerilui la boot-are, informații de configurare a PLL și un cod de identificare a dispozitivului (în cazul în care la PC sunt conectate mai multe plăci XEM).

Informațiile de configurare a PLL sunt preluate din EEPROM și folosite pentru reconfigurare PLL astfel încât de fiecare dată când FPGA-ul iese din procesul de configurare, la pinii săi se regăsește semnal activ și stabil de ceas. Informațiile de configurare a PLL pot fi modificate în funcție de necesități, prin intermediul interfeței puse la dispoziție de FrontPanel.

Codul (numele) de identificare memorat în EEPROM poate fi și el modificat, tot din FrontPanel și este util atunci când sunt conectate mai multe dispozitive XEM.

Generatorul de ceas, Cypress CY22150 are ca element principal o buclă PLL, și poate genera până la 5 semnale de ceas, 3 către FPGA și 2 către conectorii de extindere: JP2 și JP3. Bucla PLL are ca semnal de referință un semnal cu frecvența 48-MHz provenit de la microcontrollerul USB. Semnalele de tact generate pot avea frecvențe cuprinse în plaja de valori [1MHz,150MHz], valori ce pot fi indicate prin FrontPanel sau FrontPanel API.

XEM3001 ne mai pune la dispoziție 8 LED-uri și 4 comuntatoare cu revenire, care pot fi folosite ca intrări/ieșiri către/dinspre design-ul prezent în FPGA.

Atunci când perifericele de pe plăcuță și cele virtuale puse la dispoziție prin FrontPanel, nu sunt suficiente implementării unui design, putem atașa dispozitivului XEM, o serie de module integrate prin intermediul conectorilor de extindere: JP1, JP2, JP3. Acestea oferă conexiuni către alimentarea de 3.3V, GND, semnale de ceas de la PLL și 88 pini de I/O ai FPGA.

V.1.1 Modalități de configurare a XEM3001

Pentru configurarea FPGA-ului putem folosi atât FrontPanel XML cât și propiile aplicații C++, Java sau Python.[12]

În paragraful anterior am indicat puținele periferice on-board, neajuns anulat de multiplele variante de periferice virtuale ce pot fi conectate design-ului nostru configurat pe FPGA, prin intermediul FrontPanel. Între perifericele ce pot fi adăugate se numără diverse variante de LED-uri, comutatoare, afișoare (zecimale sau hexazecimale), texte de control, adăugarea lor făcându-se de către utilizator prin scrierea unui fișier .xfp în XML.

Programul FrontPanel al celor de la Opal Kelly este construit pentru a oferi o cale facilă de control și observare asupra design-ului configurat pe FPGA. Interfețele FrontPanel sunt descrise prin etichete XML, ele putând fi create cu orice editor de text și salvate ca fișere .xfp. Astfel, adăugarea de noi componente hardware virtuale înseamnă scrierea câtorva noi linii într-un fișier text.

De partea dispozitivului FPGA, componentele din FrontPanel (descrise într-un fișer XML) sunt conectate la semnalele design-ului meu prin intermediul unor așa numite “puncte terminale” (endpoints) instanțiate alături de design-ul meu în fișierul top al proiectului. Un endpoint este conectat, pe de o parte, la unul sau mai multe semnale din design (pe care doresc să le controlez/observ) iar pe de altă parte, prin intermediul unui bus (comun și celorlalte endpoint-uri), la un modul HostInterface, deasemenea instanțiat în fișierul top al proiectului. O schemă a legăturii dintre interfața FrontPanel de pe calculatorul gazdă și design-ul configurat pe FPGA este redată în figura 31.

Figura 31 – Schemă intuitivă a legăturii interfața FrontPanel – design FPGA

După o astfel de conexiune, modulul HostInterface, FrontPanel și driver-ele asociate plăcii se ocupă de comunicații astfel încât semnalul dorit să fie vizibil din FrontPanel. Noi puncte terminale pot fi adăugate (fișierului Verilog din vârful ierarhiei proiectului considerat) prin simpla instanțiere a modulelor corespunzătoare.

Se spune despre un design configurat pe FPGA că este “FrontPanel Enabled” dacă există instanțiate module HDL care să asigure comunicația cu FrontPanel. Astfel, orice design trebuie să conțină un modul okHostInterface care se conectează direct la pini FPGA și la bus-ul comun “punctelor terminale”. Orice punct terminal este identificat printr-o adresă (dacă aparține sau nu unui anumit interval), adresă ce indică tipul acestuia (Wire, Trigger, Pipe) și direcția (intrare/ieșire) [12].

Un punct terminal wire materializează o conexiune asincronă ce transferă un semnal către (WireIn) sau dinspre (WireOut) design.

Un punct terminal trigger este o conexiune sincronă. Un TriggerIn transferă către design un semnal ce se activează pentru o singură perioadă de ceas. Un TriggerOut, conexiune de ieșire din design, indică aplicației de pe calculatorul gazdă apariția unui eveniment. El transportă un semnal căruia i se detectează apariția fronturilor pozitive (rising edge). Un front pozitiv, în acest caz, se traduce printr-o schimbare de stare a semnalului de la un front activ de ceas la următorul.

Tot conexiune sincronă este și un Pipe, utilizat pentru transferul de date multi-byte înspre (PipeIn) și dinspre (PipeOut) design (ex. Citirea, scrierea unei memorii).

FrontPanel API (interfața de programare ce permite construirea de aplicații care să comunice cu design-ul compatibil FrontPanel) conține metode ce comunică prin intemediul conexiunii USB cu microcontrollerul dispozitivului XEM. Această interfață este sub forma unei librării C++ ce se include în proiectul destinat aplicației mele. Sub forma unei librării DLL, oferă metode de comunicare cu modulele HDL ce fac ca design-ul din FPGA să fie compatibil FrontPanel (okWireIn, okWreOut, okPipeIn, kkTriggerOut, etc.), câteva clase ce vor fi instanțiate în codul aplicației [12].

Tabel 3 – Clase puse la dispoziție de librăria okFrontPanelDLL

Înainte de a folosi orice funcție a librăriei de interfațare, aceasta trebuie încărcată, ceea ce se face prin apelul okFrontPanelDLL_LoadLib, apel realizat din aplicația prin intermediul căreia se dorește configurarea dispozitivului FPGA. Funcția are un argument (ex.okFrontPanelDLL_LoadLib (NULL)), care dacă este NULL, va încărca fișierul DLL din calea curentă. În loc de NULL putem furniza calea completă către DLL de încărcat.

Așa cum și tabelul 3 indică, clasa okCUsbFrontPanel conține majoritatea metodelor folosite. O oarecare ordine a etapelor necesare în aplicația ce se dorește a fi folosită pentru comunicarea cu dispozitivul XEM arată în felul următor:

Instanțierea clasei okUsbFrontPanel

Identificarea dispozitivului XEM corespunzător și inițializarea acestuia

Configurare PLL

Configurarea FPGA cu ajutorul metodei ConfigureFPGA(numefisier.bit)

Apelarea metodelor specifice comunicării cu FPGA-ul, în vederea realizării acțiunilor dorite asupra design-ului configurat

În cele ce am scris în paragrafele anterioare am dorit să arăt că dispozitivul XEM3001 oferă o mare libertate utilizatorului în a-și alege modul de lucru: fie printr-o interfață grafică pusă la dispoziție de FrontPanel, fie prin utilizarea unei aplicație construite în unul din limbajele C++, Java sau Python (prin utilizarea API). Alegerea perifericelor de control, semnalizare, de introducere date este deasemenea lăsată în seama utilizatorului. Astfel, XEM3001 este o unealtă flexibilă.

În cadrul design-ului circuitelor integrate, descrierea RTL este un mod de a expune modul de funcționare al unui circuit digital sincron.

Absractizarea RTL este folosită de limbajele de descriere hardware (HDL) ca Verilog și VHDL pentru a crea o reprezentare de nivel înalt a unui circuit, pe baza căreia se generează reprezentări de nivel mai scăzut.

Un circuit sincron conține regiștrii și logică combinațională. Regiștrii au rolul de a sincroniza operațiile efectuate, cu frontul activ de ceas, fiind și singurii care au capacitate de memorare, iar logica combinațională alcătuită din porți logice execută toate operațiile efectuate de circuit.

Procesul de sinteză logică generează pe baza descrierii la nivel RTL, un fișier de tip netlist care conține schema electrică a sistemului descris în cod HDL.

V.1 Rezultatele sintezei logice

În paragrafele anterioare am prezentat și modalitățile de configurare a FPGA-ului Spartan-3 de care dispune dispozitivul XEM3001. Am arătat că pentru ca un design să fie compatibil FrontPanel este nevoie să conțină anumite module HDL, care permit comunicația între design-ul configurat și aplicația API, ori interfața grafică XML. De aceea o fost nevoie să descriu un nou modul în care să instanțiez codecul meu, okHostInterface și o serie de module ca okPipeIn (transfer date de intrare), okPipeOut (preluarea datelor decodate), okWireIn (pentru preluare semnale reset extern), okTriggerIn, okTriggerOut (indică dacă operația de decodare a blocul de date de la intrare a fost realizată corespunzător).

Ținând cont de cele spuse, codul RTL pe care îl folosesc pentru sinteză este următorul:

// Description:

// Verilog source for performing Viterbi encoding/decoding,

// which includes top_CODEC module, HDL modules that makes my

// design Front Panel enabled. Host Interface conects directly to

// FPGA's pins. I instantiated 2 dual-port RAM blocks for interfaceing

// between pipeIn/pipeOut and my functional block: top_CODEC.

// Dual-port RAM are present because data on pipes are driven

// with another speed than data exchanges into the coder and the decoder.

///////////////////////////////////////////////////////////////////////////

`default_nettype none

`timescale 1ns / 1ps

module xem3001_CODEC(

input wire [7:0] hi_in,

output wire [1:0] hi_out,

inout wire [15:0] hi_inout,

input wire clk1,

output wire [7:0] led

);

wire ti_clk;

wire [30:0] ok1;

wire [16:0] ok2;

wire data_in;

wire [15:0] WireIn00;

wire [15:0] WireIn01;

wire [15:0] TrigIn40;

wire [15:0] TrigOut60;

wire pipeI_write;

wire pipeO_read;

wire [15:0] pipeI_data;

wire [15:0] pipeO_data;

wire ram_reset;

wire start;

wire reset;

wire device_ready;

//wire [15:0] TrigIn41;

wire decoded_data;

wire error;

reg [9:0] ramI_addrB;

reg [9:0] ramO_addrB;

reg done;

reg [13:0] ramI_addrA;

reg [13:0] ramO_addrA;

reg en_coder;

reg ramO_write;

assign reset = WireIn00[0];

assign ram_reset = TrigIn40[0];

assign start = WireIn01[0];

assign led = 8'b1111_1111;

assign TrigOut60[0] = done;

// Pipe <–> RAM addressing

//

// The PipeIn and PipeOut are connected directly to one port of each

// block RAM. The only thing we need to take care of is the address

// pointers. They are reset on RAM_RESET (a TriggerIn) and incremented

// on write and read operations, respectively.

always @(posedge ti_clk)

begin

if (ram_reset) begin

ramI_addrB <= 10'd0;

ramO_addrB <= 10'd0;

end else

if (pipeI_write)

ramI_addrB <= ramI_addrB + 1;

else if (pipeO_read)

ramO_addrB <= ramO_addrB + 1;

end

// – State machine –

// will generate en_coder, done, ramI_addrA, ramO_addrA

parameter s_idle = 0,

s_ready = 1,

s_0 = 10,

s_1 = 11,

s_2 = 12,

s_3 = 13,

s_4 = 14,

s_done = 3;

integer state;

integer k=0;

reg i=0;

always @(posedge clk1)

begin

if (reset == 1'b1) begin

done <= 1'b0;

en_coder <= 1'b0;

ramO_write <= 1'b0;

state <= s_idle;

end else begin

case (state)

s_idle: begin

if (device_ready == 1'b1)

state <= s_ready;

else

state <= s_idle;

done <= 1'b0;

en_coder <= 1'b0;

ramO_write <= 1'b0;

ramI_addrA <= 14'd0;

ramO_addrA <= 14'd0;

end

s_ready: begin

if (start == 1'b1)

state <= s_0;

else

state <= s_ready;

done <= 1'b0;

en_coder <= 1'b0;

ramO_write <= 1'b0;

end

s_0: begin

state <= s_1;

en_coder <= 1'b1;

done <= 1'b0;

ramO_write <= 1'b0;

end

s_1: begin

if (k >= 38) begin

state <= s_3;

ramO_write <= 1'b1;

end else begin

state <= s_2;

ramO_write <= 1'b0;

end

en_coder <= 1'b0;

done <= 1'b0;

i <= 1'b0;

k <= k+1;

end

s_2: begin

state <= s_1;

ramI_addrA <= ramI_addrA + 1;

i <= i + 1;

k <= k + 1;

ramO_write <= 1'b0;

en_coder <= 1'b1;

done <= 1'b0;

state <= s_1;

end

s_3: begin

if (ramI_addrA == 14'd0) begin

state <= s_done;

end

else

begin

state <= s_4;

end

en_coder <= 1'b1;

ramO_write <= 1'b1;

done <=1'b0;

ramI_addrA <= ramI_addrA + 1;

end

s_4: begin

state <= s_3;

ramO_addrA <= ramO_addrA + 1;

en_coder <= 1'b0;

ramO_write <=1'b1;

done <= 1'b0;

end

s_done: begin

state <= s_idle;

done <= 1'b1;

en_coder <= 1'b0;

ramO_write <= 1'b0; end

endcase

end

end

// Instantiate the input block RAM

RAMB16_S1_S18 ram_I(.CLKA(clk1), .SSRA(WireIn00[0]), .ENA(device_ready),

.WEA(1'b0), .ADDRA(ramI_addrA),

.DIA(1'b0), .DOA(data_in),

.CLKB(ti_clk), .SSRB(WireIn00[0]), .ENB(1'b1),

.WEB(pipeI_write), .ADDRB(ramI_addrB),

.DIB(pipeI_data), .DIPB(2'b0), .DOB(), .DOPB());

// Instantiate the output block RAM

RAMB16_S1_S18 ram_O(.CLKA(clk1), .SSRA(WireIn00[0]), .ENA(1'b1),

.WEA(ramO_write), .ADDRA(ramO_addrA),

.DIA(decoded_data), .DOA(),

.CLKB(ti_clk), .SSRB(WireIn00[0]), .ENB(pipeO_read),

.WEB(1'b0), .ADDRB(ramO_addrB),

.DIB(16'b0), .DIPB(2'b0), .DOB(pipeO_data), .DOPB());

//Instantiate the CODEC block

top_CODEC CODEC(.data_in(data_in), .decoded_data(decoded_data),

.device_ready(device_ready),

.error(error), .clk(clk1), .reset(WireIn00[0]),

.en_coder(en_coder));

// Instantiate the okHostInterface and connect endpoints to

// the target interface.

okHostInterface HI (.hi_in(hi_in), .hi_out(hi_out), .hi_inout(hi_inout),

.ti_clk(ti_clk), .ok1(ok1), .ok2(ok2));

//reset

okWireIn ep00 (.ok1(ok1), .ok2(ok2), .ep_addr(8'h00),

.ep_dataout(WireIn00));

//start

okWireIn ep01 (.ok1(ok1), .ok2(ok2), .ep_addr(8'h01),

.ep_dataout(WireIn01));

// ram_reset

okTriggerIn ep40 (.ok1(ok1), .ok2(ok2), .ep_addr(8'h40), .ep_clk(clk1),

.ep_trigger(TrigIn40));

//done

okTriggerOut ep60 (.ok1(ok1), .ok2(ok2), .ep_addr(8'h60), .ep_clk(clk1),

.ep_trigger(TrigOut60));

//Instantiate the pipe for getting data into the device

okPipeIn ep80 (.ok1(ok1), .ok2(ok2), .ep_addr(8'h80),

.ep_write(pipeI_write), .ep_dataout(pipeI_data));

//Instatiate the pipe for getting decoded data out of the device

okPipeOut epA0 (.ok1(ok1), .ok2(ok2), .ep_addr(8'hA0),

.ep_read(pipeO_read), .ep_datain(pipeO_data));

Endmodule

Pentru sinteză am folosit ca instrument software Xilinx ISE 8.2i, iar dispozitivului selectat pentru sinteză este Spartan-3 XC3S400-4PQ208C. Rezultatele obținute sunt prezentate într-un raport care cuprinde informații legate de: elementele de circuit inferate(recunoscute de către instrumentul software și sintetizate), estimare a consumului de resurse logice (se poate anticipa dacă sistemul poate fi implementat pe dispozitivul selectat), estimare a performanței (întârzieri)[3].

Tabel 4 – Consum estimat de resurse logice pentru dispozitivul selectat

Informații de timing:

Frecvența maximă a semnalului de ceas: 45.895MHz (Tclkmin = 21.789 ns)

Timp minim de set_up: 8.677ns

Hold time: 9.272ns

Frecvența maximă este limitată de calea critică a circuitului, de întârziere 21.789ns. Întârzierea se înregistrează între portul Q de ieșire al CODEC/u1/u5/u3 și ram_O. Un sumar al întârzierilor introduse de diversele elemente de pe calea dintre sursa și destinația căii critice e redat în tabelul 5.

Tabel 5 – calea critică

Tabel 6 – Sumar al primitivelor utilizate de design-ul sintetizat

VI.2 Configurarea design-ului pe FPGA

Înainte de a putea face configurarea dispozitivului XEM3001, trebuie parcurse etapele de implementare a sistemului, lucru efectuat tot cu ajutorul Xilinx ISE 8.2i. Pe baza rezultatelor obținute după această etapă, se generează fișierul .bit de configurare a FPGA. Pentru configurare am folosit o aplicație construită în C++ și care va fi rulată în linie de comandă.

Prin metodele furnizate de API, am posibilitatea de a identifica dispozitivul XEM conectat la calculator, de a-l configura și de a prelua datele prelucrate de acesta, toate aceste operații fiind rezolvate din aceeași aplicație API. Conținutul fișierului C++ este următorul:

#include <iostream>

#include <fstream>

#include <stdio.h>

#include <conio.h>

#include "okFrontPanelDLL.h"

#define VITERBI_FPGA_CONFIGURATION_FILE "xem3001_codec.bit"

bool

performViterbi(okCUsbFrontPanel *xem,

char *infilename, char *outfilename)

{

unsigned char buf[2048];

long len;

int i, j;

std::ifstream f_in;

std::ofstream f_out;

int got;

// Assert then deassert RESET.

xem->SetWireInValue(0x00, 0xff, 0x01);

xem->UpdateWireIns();

xem->SetWireInValue(0x00, 0x00, 0x01);

xem->UpdateWireIns();

// Open our input and output files.

f_in.open(infilename, std::ios::binary);

if (false == f_in.is_open()) {

printf("Error: Input file could not be opened.\n");

return(false);

}

f_out.open(outfilename, std::ios::binary);

if (false == f_out.is_open()) {

printf("Error: Output file could not be opened.\n");

return(false);

}

// Reset the RAM address pointer.

xem->ActivateTriggerIn(0x40, 0);

// This block works through the file in 2048-byte chunks, writing

// each one to the FPGA and initiating the Viterbi processing. It then

// reads out the results and stores them in the output file.

while (1) {

// Read a block from the file.

if (f_in.eof())

break;

f_in.read((char *)buf, 2048);

// Zero-pad to the end of a block.

got = f_in.gcount();

if (got == 0)

break;

if (got < 2048)

for (i=got; i<2048; buf[i++] = 0);

// Write a block of data.

xem->WriteToPipeIn(0x80, 2048, buf);

// Perform Viterbi on the block(activate start)

xem->SetWireInValue(0x01, 0xff, 0x01);

xem->UpdateWireIns();

xem->SetWireInValue(0x01, 0x00, 0x01);

xem->UpdateWireIns();

// Wait for the TriggerOut indicating DONE.

for (j=0; j<10; j++) {

xem->UpdateTriggerOuts();

if (xem->IsTriggered(0x60, 1))

break;

}

if (j==10) {

printf("Viterbi did not complete properly.\n");

return(false);

}

// Read the resulting RAM block.

len = 2048;

xem->ReadFromPipeOut(0xA0, len, buf);

// Write the block to the output file.

f_out.write((char *)buf, 2048);

}

f_in.close();

f_out.close();

return(true);

}

okCUsbFrontPanel *

initializeFPGA()

{

okCUsbFrontPanel *dev;

// Open the first XEM – try all board types.

dev = new okCUsbFrontPanel;

if (okCUsbFrontPanel::NoError != dev->OpenBySerial()) {

delete dev;

printf("Device could not be opened. Is one connected?\n");

return(NULL);

}

switch (dev->GetBoardModel()) {

case okCUsbFrontPanel::brdXEM3001v1:

printf("Found a device: XEM3001v1\n");

break;

case okCUsbFrontPanel::brdXEM3001v2:

printf("Found a device: XEM3001v2\n");

break;

case okCUsbFrontPanel::brdXEM3005:

printf("Found a device: XEM3005\n");

break;

case okCUsbFrontPanel::brdXEM3010:

printf("Found a device: XEM3010\n");

break;

}

dev->LoadDefaultPLLConfiguration();

// Get some general information about the XEM.

std::string str;

printf("Device firmware version: %d.%d\n", dev->GetDeviceMajorVersion(), dev->GetDeviceMinorVersion());

str = dev->GetSerialNumber();

printf("Device serial number: %s\n", str.c_str());

str = dev->GetDeviceID();

printf("Device device ID: %s\n", str.c_str());

// Download the configuration file.

if (okCUsbFrontPanel::NoError != dev->ConfigureFPGA(VITERBI_FPGA_CONFIGURATION_FILE)) {

printf("FPGA configuration failed.\n");

delete dev;

return(NULL);

}

// Check for FrontPanel support in the FPGA configuration.

if (dev->IsFrontPanelEnabled())

printf("FrontPanel support is enabled.\n");

else

printf("FrontPanel support is not enabled.\n");

return(dev);

}

static void

printUsage(char *progname)

{

printf("Usage: %s infile outfile\n", progname);

printf(" infile – an input file to encode+decode.\n");

printf(" outfile – destination output file.\n");

}

int

main(int argc, char *argv[])

{

char infilename[128];

char outfilename[128];

//char dll_date[32], dll_time[32];

printf("–- Opal Kelly –- FPGA-Viterbi Application –-\n");

if (FALSE == okFrontPanelDLL_LoadLib(NULL)) {

printf("FrontPanel DLL could not be loaded.\n");

return(-1);

}

//okFrontPanelDLL_GetVersion(dll_date, dll_time);

//printf("FrontPanel DLL loaded. Built: %s %s\n", dll_date, dll_time);

if (argc != 3) {

printUsage(argv[0]);

return(-1);

}

strncpy_s(infilename, argv[1], 128);

strncpy_s(outfilename, argv[2], 128);

// Initialize the FPGA with our configuration bitfile.

okCUsbFrontPanel *xem;

xem = initializeFPGA();

if (NULL == xem) {

printf("FPGA could not be initialized.\n");

return(-1);

}

// Now perform the codinng/decoding process.

if (performViterbi(xem, infilename, outfilename) == false) {

printf("Viterbi process failed.\n");

return(-1);

} else {

printf("Viterbi process succeeded!\n");

}

return(0);

}

Funcțiile apelate din funcția main și definite anterior acesteia sunt următoarele:

printUsage – are ca parametru un pointer către șirul ce reprezintă numele programului și la apelarea ei, pe ecran se afișează modul de utilizare al aplicației

initializeFPGA – nu are parametrii de apel, în schimb returnează pointer către clasa okCUsbFrontPanel. Operațiile executate de această funsție:

definește un obiect al clasei okCUsbFrontPanel

alocare de memorie către obiect

Identificarea dispozitivului XEM

Configurarea PLL

Preluarea de diverse informații despre dispozitiv (ex. Versiune firmware) și afișarea lor

Configurarea FPGA (se încarcă fișierul .bit corespunzător aplicației)

se verifică dacă dispozitivul este compatibil FrontPanel (IsFrontPanelEnabled)

se returnează pointer către obiectul creat

performViterbi – după configurarea dispozitivului XEM identificat cu initializeFPGA, aceasta este funcția ce se ocupă de preluare/trimitere date dinspre/către FPGA.

apelare metode pentru realizare reset

deschidere fișiere de intrare, rspectiv de ieșire

se resetează valorile adreselor RAM

citirea unui bloc de date de 2048 octeți

încărcarea datelor citite pe pipe-ul de intrare (cu adresa 0x80)

lansarea procesului de codareși decodare Viterbi (activare trigger start)

când semnalul done se activează înseamnă că procesul de codare/decodare asupra blocului de date de 2048 octeți, încărcat în RAM de intrare, s-a finalizat, datele s-au memorat în RAM de ieșire, și se începe preluarea datelor astfel obținute pe pipe-ul de ieșire (cu adresa 0xA0), într-o zonă de memorie tip buffer (ReadFromPipeOut(0xA0,len,buf))

scrierea datelor în fișierul de ieșire (f_out.write((char *)buf, 2048))

se inchid fișierele deschise

În main:

se apelează funcția de încărcare a okFrontPanelDLL (okFrontPanelDLL_LoadLib); dacă aceasta nu poate fi încărcată se afișează un mesaj de eroare și se încheie execuția programului

se verifică dacă în linia de comandă s-au introdus 3 argumente; dacă nu, se apelează printUsage

se declară un pointer către clasa okCUsbFrontPanel

se apelează initializeFPGA, iar valoarea returnată se atribuie pointerului declarat anterior

apelez performViterbi, funcție ce trimite către XEM, semnalele necesare pentru ca design-ul configurat în FPGA să poată executa operațiile aferente

În urma execuției aplicației, conținutul fișierului de intrare și cel al fișierului de ieșire coincid.

Bibliografie:

Scripcariu Luminița – Sisteme de comunicații digitale, Ed. GH ASACHI, Iași, 1999.

Alexandru N.D. – Note de curs Sisteme de comunicații

Țigăeru Liviu – Circuite VLSI reconfigurabile, note de laborator

Imbrea Damian – Circuite logice combinaționale, Ed. GH ASACHI, Iași, 2004

Smith Michael J.S. – Application-Specific Integrated Circuits, ADDISON-WESLEY, 1997

Palnitkar Samir – Verilog HDL, A guide to Digital Design and Synthesis, UNSOFT PRESS, 1996

Sharma Ajay – Design and Implementation of Viterbi Decoder using FPGAs, Universitatea TAPAR (India), 2008

Chen Wei – RTL implemetation of Viterbi decoder, Universitatea LINKÖPING (Suedia), 2006

Hendrix Henry – Viterbi Decoding Techniques for the TMS320C54x (Application Report), Texas Instruments, 2002

Stojanovic Vladimir, Rao Ketaki – VLSI Implementation of Viterbi Decoder, www-vlsi.stanford.edu/ee272/proj99/babyviterbi/

Manualul de utilizare al Opal Kelly XEM3001

Manualul de utilizare al FrontPanel

www.asic-world.com/verilog/veritut.html

www.sunburst-design.com/papers/

Similar Posts