Projekt oscyloskopu cyfrowego. Trochę czasu minęło odkąd wszystko zacząłem, jakiś miesiąc - przez ten czas do ogarnięcia było sporo dużych i drobnych problemów, które potrafiły zająć cały dzień.. Zdecydowanie projektowanie czegoś na FPGA wymaga dużej trzeźwości umysłu. W sumie spędziłem może nad tym projektem z 100 roboczogodzin.
W ostatnim wpisie pokazałem Wam połączony moduł ADC, pamięć RAM i interfejs VGA, wraz z niewielką ilością systemu napisanego w VHDL. Dało to w efekcie prosty oscyloskop z buforem obrazu, który później rozwinąłem o obsługę dwóch enkoderów i przycisków do skalowania/przesuwania w czasie i zdałem jako projekt z elektroniki na zaliczenie na 5.0 ;-)
Projekt pozostawiony na tamtym etapie nie nazwał bym rozwojowym - implementacja kolejnych funkcjonalności nie należała do przyjemnych i zdecydowanie brakowało już na tym etapie architektury mikroprocesora, który zajął by się renderowaniem obrazu. Oczywiście poszedłem w tym kierunku ;-)
Altera dostarcza do swoich układów architekturę procesora zwanego Nios - spędziłem relatywnie mało czasu na pierwszej szybkiej implementacji tego rozwiązania, która dała mi możliwość wykonywania własnego kodu napisanego w C! :-) Do tego w Quartusie dodany jest Eclipse z odpowiednimi pluginami do programowania. To zdecydowanie był krok do przodu, ale pociągnął za sobą horrendalne ilości czasu spędzonego na łączeniu wszystkiego "bez zgrzytów" - o nich później. Ponieważ przy implementacji takiej architektury zaczyna się tu pojawiać multum sygnałów które jakoś trzeba ogarniać i łączyć, Altera wystawiła do tego celu zestaw interfejsów Avalon który określa protokoły komunikacyjne pomiędzy modułami opracowanymi przez ich samych (np. pamięć, ADC, mikroprocesor) oraz narzędzie Qsys - pozwalające na łączenie tego wszystkiego w sposób graficzny. W moim przypadku wygląda to tak:
Oczywiście można tworzyć swoje własne moduły, które będzie można dołączyć do tej struktury. Nie skorzystałem jeszcze z takiej opcji aby zmieścić tu to co sam pisałem - obsługę VGA, cały glue-logic itd, ale wygląda to na przyjemne rozwiązanie i w przyszłości może bym z niego skorzystał, zamiast pisać dziesiątki linijek port mapów w VHDL.
To co było dalej, to próba połączenia wszystkiego na nowo. Tutaj sytuacja zaczęła się lekko pieprzyć - nie mogłem i nie mogę dojść do porządku. Z sukcesem wykonałem jeszcze parę zabiegów takich jak:
1) Opracowanie nowego sposobu przechowywania ramki obrazu - teraz nie miałem do dyspozycji wygodnych 3-bitowych komórek pamięci, tylko grube 32-bitowe. Pod każdym adresem kryje się dane 16 pixeli, po 2 bity na kolor. W VHDL wykonanie odczytu konkretnego pixela, po otrzymaniu 32-bitowego ciągu (a także obliczenie która to jest komórka pamięci), jest wykonywane w sumie "w locie" - do takich rzeczy jest stworzone FPGA, natomiast zapisywanie w C pod wskazanymi współrzędnymi pixela to trochę bardziej wymagająca sprawa:
void writePixel(int x, int y, int color) { int block = y*80 + x / 4; int data = IORD_8DIRECT(0x0, block); ``` int colorMask = color << ((x % 4)*2);```
``` data &= ~colorMask;```
``` IOWR_8DIRECT(0, block, data);``` }`
2) Organizacja pamięci z podziałem na bufor VGA, próbki ADC, a dopiero na końcu obszar mikroprocesora - ustawienie całego BSP. Trochę zajęło zgranie tego z ustawieniami w Qsysie, bo nagle okazuje się, że nie mogłem zarezerwować całej pamięci układu, bo brakuje jakichś bloków M9K. A przebudowanie wszystkiego z powrotem to kolejne 5 minut... Ostatecznie wyglądało to tak:
3) Implementacja dwuoperacyjnej pamięci RAM. Ponieważ jeden port dwuportowej pamięci RAM użytej w Qsysie jest w całości wykorzystywany przez mikroprocesor, to drugi musi zostać do użytku przez część kodu w VHDL. A ten z kolei musi odczytywać i zapisywać na przemian - a interfejs Avalon-MM daje tylko możliwość zapisu lub odczytu w jednym takcie. Postanowiłem napisać jednostkę, która biorąc co najmniej 3 razy szybszy zegar taktujący niż najwyższa z częstotliwości wykonujących operacje w pamięci. Ma to działać na tej zasadzie, że jednostka na zmianę raz zapisuje.
Niby się udało, w symulatorze działa ok - ale w praktyce dzieją się rzeczy niestworzone i mieszają się adresy zapisywania wraz z adresami odczytywania. Tutaj macie kod jakbyście chcieli przeanalizować, wraz z komentarzami "co autor miał na myśli":
Ponieważ moja jednostka nie chce działać jak należy, wszystko dalej się sypie. Ekran miga sobie w dobre, mieszając próbki z ADC wraz z danymi z bufora VGA. Gdy wyłączę zapisywanie próbek z ADC, mam fajną możliwość renderowania obrazu na monitorze, prosto z kodu w C :)
Lekko straciłem siły, przede wszystkim bo sesja, zaliczenia, przeprowadzka. Zobaczymy co dalej, zbiorę się po tym wszystkim i pewnie będę próbował dalej, choć zaczyna to męczyć... Do tego pojawiają się pewnie ograniczenia - Nios2 wcale nie działa tak szybko jak moja szybka implementacja renderowania, nie mogę go taktować z częstotliwościami kilkuset MHz - póki co ograniczam się do 100 MHz. Wydłuża to znacząco czas renderowania, dodatkowo ilość instrukcji ze względu na uP przyrasta znacząco. Nie będzie już na tyle czasu aby renderować obraz w wolnym czasie pomiędzy kolejnymi odświeżeniami obrazu o których pisałem w poprzednim wpisie. Z resztą, jest to słaby pomysł, bo wykorzystywanie 20% obszaru monitora żeby przez pozostały czas renderować obraz.. Bezsensowne. Rozwiązanie jest, ale wprowadza kolejne ograniczenie - bo rozwiązaniem jest podwójne buforowanie obrazu. A to wymaga pamięci, której mam teraz na styk na te 320x240 px.
@Visher: nie doczytałem do końca, ale w normalnych oscyloskopach dane obrabia jedno fpga, a obraz wyświetlany jest za pomocą drugiego. Zaoszczedziloby ci to sporo zasobów.
@Visher: Fajny projekt, też dłubałem w FPGA jak kupiłem MAX10 ale w Verilogu. Mozesz dać więcej info na temat obsługi wbudowanego ramu? Napisałem sterownik VGA, ale nie miał bufora ramki - strasznie miało informacji było w necie na temat tego fpga.
Ogółem sprawa jest bardzo prosta. W Quartusie można to zrobić na dwa sposoby: z wykorzystaniem qsysa i bez.
Qsys tak jak we wpisie pisałem to narzędzie które pozwalała łączyć różne IP (w uproszczeniu IP = taki moduł, entity) w przyjemny graficzny sposób. Można w nim dodać moduł "Altera On-Chip RAM" i w zależności od potrzeb go skonfigurować. Ale, i tu jest pierwsza różnica, z jakiejś przyczyny, sposoby konfiguracji są ograniczone.
współczuje normikom, którzy są z rocznika podobnego do tych dziewczynek i wychowały się na fagacie xD, powodzenia na rynku matrymonialnym #tinder #famemma #p0lka
W ostatnim wpisie pokazałem Wam połączony moduł ADC, pamięć RAM i interfejs VGA, wraz z niewielką ilością systemu napisanego w VHDL. Dało to w efekcie prosty oscyloskop z buforem obrazu, który później rozwinąłem o obsługę dwóch enkoderów i przycisków do skalowania/przesuwania w czasie i zdałem jako projekt z elektroniki na zaliczenie na 5.0 ;-)
Projekt pozostawiony na tamtym etapie nie nazwał bym rozwojowym - implementacja kolejnych funkcjonalności nie należała do przyjemnych i zdecydowanie brakowało już na tym etapie architektury mikroprocesora, który zajął by się renderowaniem obrazu. Oczywiście poszedłem w tym kierunku ;-)
Altera dostarcza do swoich układów architekturę procesora zwanego Nios - spędziłem relatywnie mało czasu na pierwszej szybkiej implementacji tego rozwiązania, która dała mi możliwość wykonywania własnego kodu napisanego w C! :-) Do tego w Quartusie dodany jest Eclipse z odpowiednimi pluginami do programowania. To zdecydowanie był krok do przodu, ale pociągnął za sobą horrendalne ilości czasu spędzonego na łączeniu wszystkiego "bez zgrzytów" - o nich później. Ponieważ przy implementacji takiej architektury zaczyna się tu pojawiać multum sygnałów które jakoś trzeba ogarniać i łączyć, Altera wystawiła do tego celu zestaw interfejsów Avalon który określa protokoły komunikacyjne pomiędzy modułami opracowanymi przez ich samych (np. pamięć, ADC, mikroprocesor) oraz narzędzie Qsys - pozwalające na łączenie tego wszystkiego w sposób graficzny. W moim przypadku wygląda to tak:
http://i.imgur.com/HIAxbSz.png
Oczywiście można tworzyć swoje własne moduły, które będzie można dołączyć do tej struktury. Nie skorzystałem jeszcze z takiej opcji aby zmieścić tu to co sam pisałem - obsługę VGA, cały glue-logic itd, ale wygląda to na przyjemne rozwiązanie i w przyszłości może bym z niego skorzystał, zamiast pisać dziesiątki linijek port mapów w VHDL.
To co było dalej, to próba połączenia wszystkiego na nowo. Tutaj sytuacja zaczęła się lekko pieprzyć - nie mogłem i nie mogę dojść do porządku. Z sukcesem wykonałem jeszcze parę zabiegów takich jak:
1) Opracowanie nowego sposobu przechowywania ramki obrazu - teraz nie miałem do dyspozycji wygodnych 3-bitowych komórek pamięci, tylko grube 32-bitowe. Pod każdym adresem kryje się dane 16 pixeli, po 2 bity na kolor. W VHDL wykonanie odczytu konkretnego pixela, po otrzymaniu 32-bitowego ciągu (a także obliczenie która to jest komórka pamięci), jest wykonywane w sumie "w locie" - do takich rzeczy jest stworzone FPGA, natomiast zapisywanie w C pod wskazanymi współrzędnymi pixela to trochę bardziej wymagająca sprawa:
void writePixel(int x, int y, int color) {
int block = y*80 + x / 4;
int data = IORD_8DIRECT(0x0, block);
}```` int colorMask = color << ((x % 4)*2);```
``` data &= ~colorMask;```
``` IOWR_8DIRECT(0, block, data);```
2) Organizacja pamięci z podziałem na bufor VGA, próbki ADC, a dopiero na końcu obszar mikroprocesora - ustawienie całego BSP. Trochę zajęło zgranie tego z ustawieniami w Qsysie, bo nagle okazuje się, że nie mogłem zarezerwować całej pamięci układu, bo brakuje jakichś bloków M9K. A przebudowanie wszystkiego z powrotem to kolejne 5 minut... Ostatecznie wyglądało to tak:
http://imgur.com/8u10Znh
3) Implementacja dwuoperacyjnej pamięci RAM. Ponieważ jeden port dwuportowej pamięci RAM użytej w Qsysie jest w całości wykorzystywany przez mikroprocesor, to drugi musi zostać do użytku przez część kodu w VHDL. A ten z kolei musi odczytywać i zapisywać na przemian - a interfejs Avalon-MM daje tylko możliwość zapisu lub odczytu w jednym takcie. Postanowiłem napisać jednostkę, która biorąc co najmniej 3 razy szybszy zegar taktujący niż najwyższa z częstotliwości wykonujących operacje w pamięci. Ma to działać na tej zasadzie, że jednostka na zmianę raz zapisuje.
Niby się udało, w symulatorze działa ok - ale w praktyce dzieją się rzeczy niestworzone i mieszają się adresy zapisywania wraz z adresami odczytywania. Tutaj macie kod jakbyście chcieli przeanalizować, wraz z komentarzami "co autor miał na myśli":
http://pastebin.com/ZmZfj5MQ
(nazwa zmiennej doubleClk powinna być w sumie tripleClk)
Ponieważ moja jednostka nie chce działać jak należy, wszystko dalej się sypie. Ekran miga sobie w dobre, mieszając próbki z ADC wraz z danymi z bufora VGA. Gdy wyłączę zapisywanie próbek z ADC, mam fajną możliwość renderowania obrazu na monitorze, prosto z kodu w C :)
Lekko straciłem siły, przede wszystkim bo sesja, zaliczenia, przeprowadzka. Zobaczymy co dalej, zbiorę się po tym wszystkim i pewnie będę próbował dalej, choć zaczyna to męczyć... Do tego pojawiają się pewnie ograniczenia - Nios2 wcale nie działa tak szybko jak moja szybka implementacja renderowania, nie mogę go taktować z częstotliwościami kilkuset MHz - póki co ograniczam się do 100 MHz. Wydłuża to znacząco czas renderowania, dodatkowo ilość instrukcji ze względu na uP przyrasta znacząco. Nie będzie już na tyle czasu aby renderować obraz w wolnym czasie pomiędzy kolejnymi odświeżeniami obrazu o których pisałem w poprzednim wpisie. Z resztą, jest to słaby pomysł, bo wykorzystywanie 20% obszaru monitora żeby przez pozostały czas renderować obraz.. Bezsensowne.
Rozwiązanie jest, ale wprowadza kolejne ograniczenie - bo rozwiązaniem jest podwójne buforowanie obrazu. A to wymaga pamięci, której mam teraz na styk na te 320x240 px.
Jeżeli ktoś to przeczytał, to podziwiam :P
Spamlista (wołajcie jak chcecie): @syn_admina, @printf, @olewales
#visherdev przede wszystkim - zapraszam
#vga #monitory #fpga #synteza #maximator #vhdl #altera #elektronika
@Visher:
Ogółem sprawa jest bardzo prosta. W Quartusie można to zrobić na dwa sposoby: z wykorzystaniem qsysa i bez.
Qsys tak jak we wpisie pisałem to narzędzie które pozwalała łączyć różne IP (w uproszczeniu IP = taki moduł, entity) w przyjemny graficzny sposób. Można w nim dodać moduł "Altera On-Chip RAM" i w zależności od potrzeb go skonfigurować. Ale, i tu jest pierwsza różnica, z jakiejś przyczyny, sposoby konfiguracji są ograniczone.