Tworzenie i debugowanie programów dla QPU
Tworzenie i debugowanie programów dla rdzeni QPU wydaje się być bardzo kłopotliwe, a to ze względu na wielo-rdzeniowość,
to że programy pracują poza głównym CPU, a także na niebezpośredni dostęp do wewnętrznej pamięci VideoCore'a oraz
rejestrów. Brakuje też ogólnie znanego debugowania. Coś tam musi być, niestety dokumentacja nie jest dostępna (w dokumentacji układu
pojawia się przerwanie BRPT, co może wskazywać, że da się wstrzymać program rdzenia QPU).
Niemniej jednak, możliwe poradzenie sobie z debugowaniem przed odpowiednie postępowanie:
- wykonanie zrzutu rejestrów i pamięci na żądanie,
- podejście przyrostowe do programowania,
- posiadanie wzorca wyniku obliczeń,
Zrzut rejestrów może być zaimplementowany jako minimalne makro, stąd łatwo jest wstawić go w dowolne miejsce w programie QPU.
Jeśli będzie potrzebne sprawdzenie zawartości rejestrów po jakiejś instrukcji, to makro można wpisać w odpowiedniej linii w kodzie źródłowym.
Rys. 6. Przykład breakpointu wstawionego w kod źródłowy.
Kiedy spojrzy się na implementację makra wywołania, to wygląda ono jak poniżej.
Rys. 7. Implementacja makra wywołania Breakpointu.
Główny kod używa dwóch makr wspomagających do zrzutu zestawu rejestrów (dump_regs) oraz
and zapisu z pamięci VPM do SDRAM (store_dump). Wykonują one również niezbędne oczekiwanie na zakończenie transferu danych.
Pełny kod źródłowy jest dostępny do pobrania jako część przykładowego projektu dla QPU (zobacz poniżej) -
plik qasm/breakpoint.qinc.
Rys. 8. Główny kod makra breakpoint'u.
Kod makra (_breakpoint) zapisuje zrzucane rejestry (zestaw rejestrów oraz akumulatory) do pamięci VPM (Vertex Pipe Memory)
a potem przepisuje to do pamięci współdzielonej z CPU.
Adresy buforów są przekazywane przez tabelę uniforms i zapamiętywane w dwóch rejestrach z zestawu, ukrywanymi pod nazwami
symbolicznymi: dbg_rfile_dump and dbg_accus_dump.
Po wykonaniu niezbędnych zapisów makro zakończy program QPU, więc oczekujący program CPU zostanie zwolniony i może wypisać
zawartość buforów w czytelnej formie.
Przykład takiego loga jest przedstawiony poniżej:
Rys. 9. Przykład zrzutu rejstrów procesora QPU - prosta technika debugowania.
UWAGA
Powyższy zrzut jest wykonany dla jednego rdzenia QPU. Sklada się on z 2 części: treści akumulatorów oraz zestawu rejestrów (zestaw rejestrów rb
jest widoczny tylko w części na tym rysunku). Każdy rejestr jest wypisany poziomo, z kolejnymi 16-ma elementami wektora SIMD od lewej do prawej.
Akumulatory są wypisane dwukrotnie, raz jako zmiennoprzecinkowe, a drugi raz jako 32-bitowe liczby całkowite szestnastkowe.
Akumulatory specjalne r4 i r5 są pominięte. Dla każdego rejestru z zestawu jego format i nazwa symboliczna jest definiowana w aplikacji.
W tym przypadku, podejście przyrostowe do programowania oznacza, że zaczyna się od programu dla jednego QPU, który przetwarza
małą cześć danych, a następnie skaluje ten program na wiele rdzeni oraz duzy zestaw danych.
W pierwszej fazie można skupic się na tworzeniu programu w asemblerze i sprawdzaniu specyficznego zachowania procesora QPU.
Jeśli jest to pierwszy program, który się tworzy dla QPU, z pewnością okaże się, że niektóre instrukcje działają inaczej niż się tego spodziewało.
'Odkryje' się też, że pewne kombinacje argumentów są niedopuszczalne, więc instrukcje nie są aż tak elastyczne jakby się mogło wydawać.
W programowaniu QPU, są też pewne zależności pomiędzy kolejnymi instrukcjami, których trzeba przestrzegać, bo w przeciwnym przypadku
wynik będzie niewłaściwy (np. zapis do rejestru zestawu nie jest jeszcze dostępny w następnej instrukcji).
To jest oczywiście faza debugowania, kiedy koryguje się program. Nie będzie to zbytnio klopotliwe, jeśli korzysta się
ze zrzutu rejestrów.
Kod może być w tym momencie również poddany optymalizacji, szczególnie poprzez użycie dwubieżności ALU.
W kolejnej fazie można dodać pętlę, która przetwarza następne paczki danych. Makro do zrzutu rejestrów kończy program, więc
nie jest zbyt dobre do badania zachowania pętli. Niemniej jednak możliwe jest obejście tej niedogodności.
Można wykonać skopiowac treść pętli, więc będzie wtedy pierwsza i druga iteracja w osobnych miejscach kodu.
W ten sposób możliwe stanie się sprawdzenie zachowania pętli (tzn. sprawdzenie czy zmienne pętli sa własciwie modyfikowane
z jednej iteracji na drugą).
Kolejny etap jest rtudniejszy, trzeba przejść jednego QPU na wiele. Można zacząć od programu na dwa QPU a kiedy będzie działać
rozszerzyć na więcej QPU. Trzeba pamięteć, że powinno się mieć jeden program dla wielu QPU, moga być jego wersje ale kod
powinien być jeden - rozmiar pamięci cache dla instrukcji QPU jest dość ograniczony!
Należy przydzielić każdemu QPU inną porcję danych. Nowym elementem programu dla wielu rdzeni będzie synchronizacja pomiędzy rdzeniami.
Można nauczyć się jak to zrobić obserwując gotowe przykłady.
W tej fazie wyniki będą zapisywane do pamięci VPM a potem gdy wszystkie QPU zakończą obliczenia do pamięci współdzielonej z CPU.
W ten sposób będą juz dostępne do zweryfikowania.
Wzorzec wyników obliczeń
Nawet przy debugowaniu programu dla jednego QPU jest to trudniejsze niż dla zwykłego procesora (jak np. jednordzeniowego ARM).
Problemem jest duży zestaw danych i jego wektorowa natura. Aby poradzić sobie z tym zaleca się przygotowanie wzorcowego wyniku obliczeń.
Taki wzorzec może być wyliczony przez program napisany w C lub C++ a powinien on dostarczyć wzorca do weryfikacji: jakiś danych wejściowych
oraz poprawnych danych wyjściowych dla kluczowych etapów obliczeń.
Wygodnie jest używać formy tekstowej, bo wyniki moga być łatwo porównane z tymi uzyskanymi na QPU i wypisanymi w logu.
Porównanie można wykonać w jakimś arkuszu kalkulacyjnym (OpenOffice Calc, itp.), a wtedy dane będa widoczne jako wektorowe.
Dane można przenosić z konsoli loga do arkusza za pomocą schowka. Alternatywnie można wbudować weryfikację w aplikację.
Rys. 10. Przykład weryfikacji SIMD16 w OpenOffice Calc.
Przykładowy projekt programu używającego QPU
Można pobrać przyklad programu używającego i potraktować go jako podstawę do eksperymentów z rdzeniami QPU.
Ten przykład oblicza Dyskretna Transformatę Falkową (Discrete Wavelet Transform) wykorzytując cztery rdzenie QPU oraz równoległe
obliczanie w oparciu o SIMD16.
Ten kod zawiera metody opisane na tej stronie, szczególnie użyteczne dla innych programistów może być makro do breakpointu.
Zobacz README.txt w tym pakiecie aby zapoznać się ze szczegółami.
To oprogramowanie zostało opracowane na podstawie przykładu hello_fft, przez wprowadzenie pewnych zmian i dostosowania.
Kod QPU został napisany od początku ale używa on pewnych metod pochodzących z przykładu obliczania FFT.
Proszę pamiętać, że to oprogramowanie jest tylko demonstracją technik programowania i jest udostępniane na zasadach "TAKIE JAKIE JEST"
bez żadnej gwarancji.
Licencje wykorzystania można znaleźć w plikach źródłowych.
Pobierz projekt Eclipse zawierający obliczanie Discrete Wavelet Transform na rdzeniach QPU (zawiera kod źródłowy dla QPU).