Optymalizacja filtrów cyfrowych w procesorach sygnałowych

| Technika

Cyfrowe przetwarzanie sygnałów za pomocą filtrów to jeden z procesów w elektronice i informatyce, który podlega pewnej popularnej w tematyce biznesowej regule, określanej jako Zasada Pareto. Polega ona na tym, że 80% czasu pracy układu skupia się na wykonywaniu 20% instrukcji kodu programu.Według niektórych opinii, zasada ta w przypadku cyfrowego przetwarzania sygnałów jest jeszcze bardziej znacząca i sprowadza się do "90-10".

Optymalizacja filtrów cyfrowych w procesorach sygnałowych

Powyższa konstatacja sprawia, że tak naprawdę za prędkość działania aplikacji odpowiedzialnych jedynie 10%÷20% kodu źródłowego programu. W związku z tym, optymalizacja względnie niedużej części całego opisu filtru cyfrowego może pozwolić na znaczące przyspieszenie pracy układu przetwarzania sygnałów.

Cechy szczególne nowoczesnych DSP

Aby optymalnie zaimplementować filtr cyfrowy w układzie DSP, konieczna jest znajomość nowoczesnych technik, jakie stosowane są w obecnie produkowanych procesorach sygnałowych. To dzięki nim możliwe jest wykonywanie operacji typowych dla zaawansowanego przetwarzania danych strumieniowych.

Jednym z najbardziej znanych udogodnień są operacje typu MAC (Multiply and Accumulate). Wywoływane są jako pojedyncze instrukcje procesora i polegają na następującym po sobie przemnożeniu dwóch wartości i dodaniu wyniku do wartości zapisanej w rejestrze sumowania. Przykładem może być poniższy kod, zapisany w instrukcjach języka asembler dla procesora sygnałowego z rodziny C5000 firmy Texas Instruments o architekturze C55x (rys. 1).

Instrukcje zapisane w sposób nieoptymalny, np. tak:
MPYM *AR2+, *AR3+, AC1
nop
ADD AC1,AC0
zajmują trzy cykle. Ich działanie polega na przemnożeniu wartości z pamięci RAM wskazywanej przez adresy zapisane w rejestrach i zapisaniu wyniku do rejestru AC1, a następnie na dodaniu wartości z AC1 do AC0 po odczekaniu jednego cyklu procesora. Wersja zoptymalizowana zrealizowana z wykorzystaniem operacji MAC wygląda następująco i wymaga jedynie jednego taktu procesora:
MAC *AR2+, *AR3+, AC0

Rys. 1. Schemat blokowy fragmentu architektury C55x

Operacja Multiply and Accumulate może być wykonywana tak szybko, dzięki zastosowaniu wielu równoległych linii odczytu i zapisu danych, jakie stosowane są w większości nowoczesnych układów DSP. Ponadto wykorzystanie odpowiednich instrukcji pozwala zrezygnować z oczekiwania na przetworzenie danych i dzięki temu jest sposobem np. na pobieranie nowych danych w czasie, gdy wykonuje się mnożenie.

Kolejnym z istotnych usprawnień jest wykonywanie operacji na całych blokach danych, zamiast oddzielnie na poszczególnych wartościach. Typowe algorytmy przetwarzania sygnałów wykonują te same operacje wielokrotnie, gdyż operują na strumieniu informacji. O ile sytuacja nie wymaga, by wszystkie pobierane próbki były przetwarzane natychmiastowo, bez żadnych opóźnień, można je zbuforować, a następnie wykonywać obliczenia blokowo. Warto zaznaczyć, że przy obecnych częstotliwościach taktowania procesorów, większość aplikacji pozwala na używanie całkiem dużych buforów danych. Przetwarzanie blokowe pozwala na wykorzystanie kolejnych mechanizmów optymalizacji.

Zalety przetwarzania blokowego

Jedną z możliwości optymalizacji przetwarzania danych, dzięki zastosowaniu buforowania jest wykorzystanie oddzielnych szyn programu i danych. Ponieważ wiele nowoczesnych układów DSP, wyposażone została w kilka linii do odczytu i zapisu danych, możliwe jest zwielokrotnienie przetwarzania informacji w jednym cyklu zegarowym. W tym celu przydatne są dodatkowe jednostki MAC, które także wbudowywane są w kilku instancjach w najnowsze układy. Oto przykład równoległego przetwarzania dwóch części danych, w trakcie wykonywania identycznej operacji filtrowania na obu pobieranych komórkach:
AMOV #a0, XAR2
AMOV #a0+1, XAR3
AMOV #b XAR4
AMOV #x0, XCDP
MAC *AR2+, CDP+, AC0
:: MAC *AR3+, CDP+, AC1
MOV pair(hi(AC0)), dbl(*AR4+)
Dane pobierane są z komórek #a0 i a0+1, a aktualna wartość akumulatora przechowywana jest w XAR4. Parametry filtra pobierane są trzecią linią danych do XCDP. Następnie wykonywane są równolegle dwie operacje MAC, które operują odpowiednio na danych wskazywanych przez AR2 i AR3, wspólnym parametrze z CDP, a wynik działania dodawany jest odpowiednio do AC0 i AC1. W ostatniej linijce wprowadzana jest operacja sumowania wartości z AC0 i AC1 do pamięci wskazywanej przez adres zapisany w AR4. Warto zauważyć znak dwukropka, jakim została poprzedzona druga operacja MAC. To dzięki temu poleceniu układ DSP wykonuje obie operacje MAC równocześnie.

Bufory cykliczne

Innym ciekawym pomysłem nieco usprawniającym wykonywanie kodu kolejnych pętli jest zastosowanie buforowania cyklicznego. Programista musi zdefiniować długość buforu pamięci i jego początek oraz zaznaczyć, ze ma to być struktura cykliczna. Dzięki temu, po przetworzeniu odpowiedniej porcji danych, której wielkość powinna odpowiadać długości bufora i liczbie powtórzeń pętli obsługującej blok danych, wskaźnik dostępu do bufora jest automatycznie resetowany. Oznacza to, że po zakończeniu pętli, programista nie musi ręcznie deklarować wyzerowania wskaźnika, co zmniejsza objętość kodu programu oraz przyspiesza jego działanie. Aby zadeklarować bufor cykliczny na procesorach o architekturze C55x należy zastosować kod podobny do niniejszego:
AMOV #a0,XCDP
MOV #a0, BSAC
MOV #32, BKC
MOV #10, CDP
BSET CDPLC

Rys. 2. Schemat blokowy jednostki przetwarzania danych w architekturze C55x

Pierwsza z instrukcji powoduje pobranie początkowego adresu danych, a druga zapisanie go do odpowiedniej komórki. Trzecia instrukcja określa rozmiar bufora, a czwarta offset – czyli przesunięcie wskazujące na logicznie reprezentowany początek bufora. To do tego punktu, względem fizycznego początku, przesuwany będzie wskaźnik adresu bufora, po jego zresetowaniu. Ostatnia z instrukcji powoduje zapisanie deklaracji i utworzenie bufora cyklicznego.

Stosowanie tego typu bufora jest także korzystne w przypadku, gdy tylko część z pobieranych danych jest przetwarzana. Odpowiednia konfiguracja pozwala na szybkie automatyczne przeskakiwanie do początku bloku potrzebnych wartości w tablicy pobranych liczb.

Wykorzystanie wbudowanej pamięci RAM

Kolejnym ważnym zabiegiem optymalizacyjnym jest wykorzystywanie wbudowanych w układy DSP komórek pamięci RAM, zamiast korzystać z danych zapisanych w ROM. W tym celu, przed głównym fragmentem kodu filtra warto skopiować parametry, takie jak współczynniki algorytmów, bezpośrednio do pamięci RAM. Aby to zrobić, należy wykonać krótki szereg instrukcji przedstawionych poniżej w postaci kodu na procesor z architektury C55x:
AMOV #lista, XAR2
AMOV #a0, XAR3
RPT #7
MOV dbl(*ar2+),dbl(*ar3+)
RET

Wśród pozostałych zabiegów wartych zastosowania jest wykorzystywanie kanałów DMA do pobierania danych z zewnętrznych interfejsów. Innym ciekawym sposobem może być zastosowanie automatycznego ogranicznika liczby wykonań pętli, jaki uzyskuje się dzięki wpisaniu danych konfiguracyjnych do odpowiednich rejestrów procesora i wykonaniu operacji RPT lub RPTB.

Pomocne formaty liczb

Programując aplikacje DSP nie należy zapominać o znaczeniu, jakie niesie ze sobą wykorzystanie odpowiedniego formatu liczb. W znaczącej większości przypadków, produkowane obecnie układy DSP posługują się zapisem stałoprzecinkowym. Wynika to ze znacznego wzrostu komplikacji obliczeń związanych z przekształcaniem liczb zmiennoprzecinkowych przed i po ich przetwarzaniu. Niestety, wady formatów stałoprzecinkowych, takie jak ograniczony zakres dynamiczny przyjmowanych wartości, może być problemem przy wykonywaniu takich obliczeń jak np. mnożenie. Z tego względu stosuje się często format Q, określany np. jako Q1.14. W podanym przykładzie oznacza on, że jeden najstarszy bit reprezentuje znak liczby, a pozostałe 14 bitów stanowi część ułamkową liczby. Wykorzystywanie formatu Q pozwala na uzyskanie stałej rozdzielczości liczb oraz zapewnia, że wartość każdego z wyników mnożenia nie wykroczy poza przedział <-1;1>. Co więcej, format ten sprzętowo realizuje odcinanie najmniej znaczących cyfr liczby, bez konieczności przesuwania, czy też przycinania wyników.

Ograniczony zakres dynamicznych jest też szczególnie ważny w przypadku następujących po sobie iloczynów, których wyniki sumowane są w jednym rejestrze. Tymczasem, tak jak to zostało wcześniej wspomniane, to właśnie tego typu operacje są jednymi z najczęstszych stosowanych w aplikacjach DSP. Z tego powodu rejestry, w których przechowywane są wartości wynikowe operacji MAC są zazwyczaj znacznie większej długości niż inne rejestry. Przykładowo, przemnożenie dwóch liczb 16-bitowych może dać w wyniku wartość 32-bitową. Tymczasem dobrą i powszechną praktyką projektantów układów DSP jest tworzenie np. 40-bitowych rejestrów akumulacyjnych. Dzięki temu są one zabezpieczone przed zbyt szybkim przepełnieniem.

Nie oznacza to, że takie przepełnienie nie może wystąpić. Aby jego skutki nie prowadziły do poważnych problemów, takich jak zamiana znaku liczby lub też tzw. „przekręcenie się licznika”, w układach DSP stosuje się zabieg nasycania. Jest on uzasadniony także od strony teoretyczno-praktycznej, gdyż bardzo wiele sygnałów w przyrodzie, po ich zsumowaniu, faktycznie nie mogłoby przekroczyć wartości nasycenia. Proces nasycania (saturacji) może być realizowany na dwa sposoby – ręcznie i automatycznie. Wprowadzenie odpowiedniego ustawienia w konfiguracji programu DSP pozwala na szybkie sprzętowe saturowanie wyników.

Podsumowanie

Poprawnie napisany kod filtra cyfrowego przetwarzania sygnałów powinien być zoptymalizowany. Nie oznacza to jednak, że cała treść programu musi być napisana w asemblerze, który jest dosyć trudnym językiem dla wielu inżynierów. Z tego względu warto stosować język wyższego poziomu, taki jak np. C, w celu opisania ogólnych procedur operacyjnych DSP, oraz tworzenie wstawek asemblerowych, które realizują najbardziej krytyczne czasowo operacje. Pozwoli to na uzyskanie wysokowydajnych aplikacji przy minimalnym nakładzie czasu i kosztów.

Marcin Karbowniczek