Akademia Górniczo Hutnicza im. Stanisława Staszica w Krakowie Wydział IET Katedra Elektroniki Projektowanie systemów za pomocą języków wysokiego poziomu ESL Ćwiczenie 1 Wprowadzenie do języka Impulse C Zespół Rekonfigurowalnych Systemów Obliczeniowych Maciej Wielgosz, Grzegorz Gancarczyk http://home.agh.edu.pl/~wielgosz/ 09.10.2013
Wstęp 1. Założenia projektowe Projekt realizowany w trakcie zajęć laboratoryjnych polegać będzie na stworzeniu generatora liczb pseudolosowym (PseudoRandom Number Generator). Układ generujący tworzyć będzie 16 bitowy rejestr przesuwny ze sprzężeniem zwrotnym (Linear Feedback Shift Register). Sprzężenie zwrotne należy zrealizować przy użyciu funkcji/bramki ex-or oraz wielomianu generującego Galoisa. Poprawnie zaprojektowany generator charakteryzuje się okresem równym 2 16 1. Ze względu na słabe właściwości statystyczne danych wynikowych, nie jest on jednak wykorzystywany w zaawansowanych systemach elektronicznych, jak np. systemy kryptograficzne. Powszechnie wykorzystuje się go jako scrambler, element składowy systemu kontroli sum kontrolnych CRC, czy też generator wymuszenia wykorzystywanych na potrzeby pomiaru odpowiedzi impulsowej układów liniowych. 2
2. Szkielet projektu w narzędziu CoDeveloper 2.1. Tworzenie nowego projektu Na pulpicie znajduje się ikona, przy pomocy której należy uruchamiać programu CoDeveloper. W nowo otwartym oknie startowym programu należy kliknąć na przycisk Create a New Project. 3
2.2. Szablon projektu Pojawi się okno o nazwie Create New Project. Należy zaznaczyć w nim kolejno: Project types General Applications Templates, Templates One input stream, one output stream, 1 2 a następnie: nazwać stosownie projekt (np. generator) Project name: wybrać lokalizację projektu na dysku (np. C:\MyCoDesign) Location: zaznaczyć opcję Create a folder for the project kliknąć przycisk OK. 4
1 2 3 4 2.3. Czarodziej W oknie czarodzieja kliknąć przycisk Dalej. W nowym oknie kolejno: nazwać proces (np. generator) Process name: nazwać strumień wejściowy (np. input) Input stream name: nazwać strumień wyjściowy (np. random) Output stream name: określić szerokość bitową strumienia Stream width 16 określić głębokość strumienia Stream depth 1 kliknąć przycisk Dalej. 5
1 3 2 4 5 6 W ostatnim oknie dialogowym czarodzieja kliknąć przycisk Zakończ. 6
3. Adaptacja szkieletu do projektu Adaptacja utworzonego wcześniej szkieletu będzie polegać m.in. na eliminacji nadmiarowego kodu, nielogicznych z punktu widzenia projektu strumienia, bloku wejściowego oraz edycji niektórych wartości. 3.1. Plik nagłówka generator.h Po otwarciu pliku nagłówkowego ukazać powinien się następujący kod. //////////////////////////////////////////////////////////////////////////// /// // // Generated by Impulse CoDeveloper // Impulse C is Copyright(c) 2003-2006 Impulse Accelerated Technologies, Inc. // #define STREAMDEPTH 1 /* buffer size for FIFO in hardware */ #define STREAMWIDTH 16 /* buffer width for FIFO in hardware */ #define INPUT_FILE "filter_in.dat" #define OUTPUT_FILE "filter_out.dat" Usunąć należy z niego wskazaną strzałką linijkę, a następnie zapisać plik (kombinacja klawiszy ctrl + s). Deklarację pliku wejściowego należy usunąć z oczywistego powodu generator nie wymaga żadnych danych wejściowych, wręcz przeciwnie, sam generuje dane. W pliku nagłówkowym znajdują się następujące deklaracje: głębokość strumienia wejściowego i wyjściowego, szerokość strumienia wejściowego i wyjściowego, nazwa pliku wejściowego, z którego wczytywane są dane do strumienia wejściowego, nazwa pliku wyjściowego, do którego zapisywane są dane ze strumienia wyjściowego. 3.2. Plik sprzętowy generator_hw.c Po otwarciu pliku generator_hw.c należy odnaleźć i usunąć następujące fragmenty kodu, wskazane przy pomocy strzałek. // Software process declarations (see generator_sw.c) extern void Producer(co_stream input); extern void Consumer(co_stream random); 1 ( ) // // This is the hardware process. 7
// void generator(co_stream input, co_stream random) 2 1. Blok Producera nie będzie wykorzystywany, gdyż generator liczb pseudolosowych nie przyjmuje żadnych danych wejściowych. Wytwarza on natomiast dane wyjściowe, pobierane przez blok Consumera. 2. W deklaracji procesu sprzętowego (tzw. Filter) należy usunąć strumień wejściowy, pozostawiając jednocześnie strumień wyjściowy. IF_SIM(int samplesread; int sampleswritten;) ( ) do { // Hardware processes run forever IF_SIM(samplesread=0; sampleswritten=0;) 3 4 co_stream_open(input, O_RDONLY, INT_TYPE(STREAMWIDTH)); co_stream_open(random, O_WRONLY, INT_TYPE(STREAMWIDTH)); 5 3. Brak danych wejściowych oznacza, że zbędny jest licznik danych wejściowych. 4. Inicjalizacja licznika danych wejściowych również jest zbyteczna. 5. W całości należy usunąć funkcję otwierającą strumień wejściowy, gdyż nie będzie on wykorzystywany. 8
while ( co_stream_read(input, &nsample, sizeof(co_int16)) == co_err_none ){ #pragma CO PIPELINE IF_SIM(samplesread++;) 7 ( ) co_stream_close(input); co_stream_close(random); IF_SIM(cosim_logwindow_fwrite(log, 8 6 "Closing filter process, samples read: %d, samples written: %d\n", samplesread, sampleswritten);) 9 6. Warunek pętli tymczasowo należy zmienić na 1. Pozwoli to skompilować projekt na próbę. 7. Należy usunąć linijkę kodu opisującą inkrementację licznika danych wejściowych. 8. Usunąć funkcję zamykającą nieistniejący strumień danych wejściowych. 9. Usunąć partie kodu odpowiedzialne za odczyt stanu licznika danych wejściowych. Proszę zwrócić uwagę na dyrektywę kompilatora (#pragma) CO PIPELINE. Informuje ona kompilator, by utworzyć potok obejmujący wszystkie operacje znajdujące się wewnątrz pętli while. Potok taki znajdzie odzwierciedlenie w wygenerowanym kodzie HDL. Pragma CO PIPELINE jest jedną z podstawowych dyrektyw kompilatora języka Impulse C. void config_generator(void *arg) { co_stream input; co_stream random; 10 co_process generator_process; co_process producer_process; co_process consumer_process; 11 10. W funkcji konfigurującej moduł generatora należy usunąć deklarację strumienia wejściowego. 11. Należy również usunąć moduł/proces producera. 9
input = co_stream_create("input", INT_TYPE(STREAMWIDTH), STREAMDEPTH); random = co_stream_create("random", INT_TYPE(STREAMWIDTH), STREAMDEPTH); producer_process = co_process_create("producer", (co_function)producer, 1, 13 input); generator_process = co_process_create("generator", (co_function)generator, 2, input, 15 14 random); 12 12. Usunąć w całości funkcję tworzącą i deklarującą właściwości strumienia wejściowego. 13. Usunąć w całości funkcję tworzącą i deklarującą właściwości procesu Producera. 14. Zmienić na 1 ilość strumieni dołączonych do procesu generator. 15. Usunąć strumień wejściowy dla procesu generator. Zapisać plik. 3.3. Plik programowy generator_sw.c Po otwarciu pliku generator_sw.c należy znaleźć i usunąć w całości funkcję Producer. void Producer(co_stream input) { ( ) } Zapisać plik. Dokonać próbnej kompilacji projektu w celu sprawdzenia, czy wszystkie zmiany zostały wykonane poprawnie i nie zaburzają struktury szkieletu projektu Jeśli wszystkie zmiany zostały wprowadzone poprawnie, projekt zostanie zbudowany i pojawi się komunikat succeeded. W tym momencie należy przejść do następnej części ćwiczenia. W przypadku pojawienia się informacja o niepowodzeniu kompilacji (failed) pojawią się również komunikaty wskazujące jakie są jego przyczyny. Bazując na nich należy spróbować samodzielnie wyeliminować przyczyny błędów. Jeśli okaże się to zbyt trudne, proszę zwrócić się o pomoc do osoby prowadzącej zajęcia. 4. Sprzętowy generator liczb pseudolosowych W pliku generator_hw.c należy odnaleźć następujący fragment kodu // Sample is now in variable nsample. 10
// Add your processing code here. co_stream_write(random, &nsample, sizeof(co_int16)); a następnie podmienić go na lfsr = (lfsr >> 1) ^ (-(lfsr & 1u) & 0xB400u); ++period; co_stream_write(random, &lfsr, sizeof(co_int16)); Teoria dotycząca LFSR znajduje się pod adresem: http://en.wikipedia.org/wiki/linear_feedback_shift_register Pojawienie się nowych zmiennych (lfsr oraz period) wymaga ich zadeklarowania. Po odnalezieniu void generator(co_stream random) { co_int16 nsample; należy zmienić kod na void generator(co_stream random) { uint16 lfsr; uint16 period; Zmienna nsample pochodziła z pierwotnego szkieletu projektu i jest ona zbędna. Zmienna lfsr pełnić będzie rolę 16 bitowego rejestru przesuwnego ze sprzężeniem zwrotnym. Zmienna period używana będzie jako 16 bitowy licznik. Do czego posłuży licznik, zostanie wyjaśnione później. Wartość obu zmiennych interpretowana będzie jako liczba całkowita bez znaku. Generator należy zainicjalizować. Po odnalezieniu fragmentu kodu co_stream_open(random, O_WRONLY, INT_TYPE(STREAMWIDTH)); // Read values from the stream 11
podmienić go na: co_stream_open(random, O_WRONLY, INT_TYPE(STREAMWIDTH)); lfsr = 0xACE1u; period = 0; // Read values from the stream W ten sposób rejestr lfsr zostaje zainicjalizowany wartością ACE1, a licznik wyzerowany. Aby nie generować danych w nieskończoność, zmienić należy warunek pętli while z while ( 1 ){ #pragma CO PIPELINE na while ( period < 0xFFFFu ){ #pragma CO PIPELINE Ostatni krok polega na kompilacji projektu. W przypadku pomyślnej kompilacji należy przejść dalej. Jeśli kompilacja nie powiodła się, należy samodzielnie postarać się wyeliminować wszystkie błędy w projekcie. Jeśli okaże się to zbyt trudne, proszę poprosić o pomoc osobę prowadzącą zajęcia. 12
5. Generacja kodu HDL Przed przystąpieniem do generacji wynikowego kodu HDL, należy poprawnie ustawić wszystkie opcje dotyczące projektu (wynikowy język HDL, platforma docelowa, itp.) Dopiero gdy to wszystko zostanie zrobione, można przystąpić do wygenerowania kodu, a następnie analizy raportu końcowego i schematu blokowego stworzonego układu. W realizowanym projekcie użyte zostają dwa 16 bitowe sumatory i jeden 16 bitowy komparator. Maksymalne opóźnienie układu wynosi 32 takty zegara. Jest to wartość najgorsza (worst case), która wystąpi jeśli operacja dodawania i/lub porównywania będzie przeprowadzana sekwencyjnie bit po bicie. W rzeczywistości zewnętrzny program, który przeprowadzi operacje syntezy i implementacji (np. Xilinx ISE) zrealizuje sumator/komparator w taki sposób, by nie wnosił on żadnego opóźnienia. Analiza raportu na tym poziomie pozwala w łatwy sposób stwierdzić, czy otrzymany kod HDL jest optymalny. W celu generacji kodu należy kliknąć na ikonę opisaną jako EXE (wskazaną przez czerwoną strzałkę na rysunku poniżej). W oknie Build pojawią się komunikaty. Jeśli wszystkie zmiany zostały wprowadzone poprawnie, projekt zostanie skompilowany i pojawią się komunikaty Processing completed successfuly oraz Build of target build_exe complete. W tym momencie należy przejść do następnej części ćwiczenia. W przypadku pojawienia się informacja o niepowodzeniu kompilacji (komentarz Error) pojawią się również komunikaty wskazujące jakie są jego przyczyny. Bazując na nich należy spróbować samodzielnie wyeliminować przyczyny błędów. Jeśli okaże się to zbyt trudne, proszę zwrócić się o pomoc do osoby prowadzącej zajęcia. Praktyczna symulacja pracy krokowej generatora jest niemożliwa do wykonania przy użyciu CoDevelopera. Można sobie wyobrazić co prawda sytuację, gdy po każdej linijce kodu zapisanego w języku C pojawia się instrukcja zatrzymująca pracę programu, a w oknie wiersza poleceń pojawiają się aktualne wartości interesujących nas zmiennych. Podejście takie jest jednakże bardzo kłopotliwe i nie gwarantuje, że wyniki symulacji pokrywać się będą z rzeczywistymi stanami układu. Możliwa jest jednak programowa emulacja działania projektowanego układu. Przed jej wykonaniem, należy jednak dopilnować, by poprawnie ustawione zostały wszystkie opcje kompilacji. W tym celu należy kliknąć przycisk Options. 13
Większość rubryk uzupełnia za nas automatycznie sam CoDeveloper. Może się jednak czasami zdarzyć, że pewne opcje nie zostaną poprawnie skonfigurowane. Należy upewnić się, że w zakładce Build poprawnie dołączone zostały następujące pliki: CoBuilder Hardware Files Source (.c) files: generator_hw.c Include (.h) files: generator.h CoBuilder Software Files Source (.c) files: generator_sw.c Include (.h) files: generator.h Desktop Simulation (.EXE) Build Options Source (.c) files: generator_hw.c generator_sw.c Include (.h) files: generator.h Zaznaczone powinny zostać również następujące opcje: Generate makefile Build for debug (-g) 1 2 6 3 7 4 5 9 8 W zakładce Simulate dopilnować, by załączone zostały następujące pliki: Software simulation executable name: generator.exe 14
Update hardware simulation executable: generator.exe Zaznaczone powinny zostać również opcje: Update software simulation executable Update hardware simulation executable Jeśli chcielibyśmy dokonać jeszcze weryfikacji poprawności działania układu opisanego językiem HDL przy pomocy ModelSima, należałoby zaznaczyć opcję Generate ModelSim testbench (na tym ćwiczeniu nie będziemy wykonywać takiej symulacji), która doprowadzi do wygenerowania stosownego testbencha dla zaprojektowane przez nas układu. Wektory wymuszeń testbencha będą identyczne z danymi wejściowymi dla symulacji software owej. 1 4 5 2 3 W tym miejscu skonfigurowane zostaną również od razu opcje dotyczące generacji wynikowego kodu HDL. W zakładce Generate należy upewnić się, że uzupełnione poprawnie zostały następujące pola: Output Directories Hardware build directory: hw Software build directory: sw Hardware export directory: export_hw Software export directory: export_sw Ponadto wybrane powinny zostać następujące opcje: Platform Support Package: Generic (VHDL) 15
Hardware Optimization and Generation Enable constant propagation Scalarize array variables Relocate loop invariant expressions Use std_logic types for VHDL interfaces Do not include co_ports interface Gdy wszystko zostało już poprawnie skonfigurowane, należy kliknąć przycisk Zastosuj, a potem OK. 1 6 11 8 7 9 10 2 3 4 5 13 12 16
W tym momencie można wykonać symulację software ową. Aby tego dokonać należy nacisnąć przycisk Launch wskazany na poniższym rysunku. Z wynikiem symulacji można się zapoznać w nowo otwartym oknie wiersza poleceń. Jeśli wynik jest zgodny z oczekiwanym, należy przejść dalej. W przeciwnym wypadku należy zastanowi się, co sprawia, że układ działa niepoprawnie, a następnie wyeliminować przyczynę tej awarii. Jeśli wyniki symulacji są zgodne z oczekiwanymi, można śmiało wygenerować pliki opisujące zaprojektowany przez nas układ w języku VHDL lub Verilog. Aby dokonać generacji kodu HDL wystarczy kliknąć na ikonę Generate HDL zaznaczoną poniżej. W oknie Build pojawi się komunikat o powodzeniu lub niepowodzeniu kompilacji. Jeśli generacja przebiegła poprawnie, prócz stosownego komunikatu widoczną również będzie informacja o wygenerowaniu pliku generator.xic. Jeśli kompilacja nie powiodła się, należy postarać się wyeliminować błędy. Jeśli zadanie to okaże się za trudne, należy zwrócić się o pomoc do osoby prowadzącej zajęcia. Przybliżona analiza zasobów sprzętowych, jakie zajmuje stworzony moduł generatora również pojawia się w oknie raportu (Build). Aby mu się przyjrzeć, należy przewinąć raport do góry. 5.1. Schemat blokowy Schemat blokowy generatora znajduje się we wspomnianym wcześniej pliku z rozszerzeniem xic. Do jego podglądu służy program o nazwie Stage Master Explorer. W tym celu w Co- Developerze należy rozwinąć zakładkę Tools i wybrać Stage Master Explorer. 17
W nowo otwartym oknie należy wybrać plik z rozszerzeniem xic i kliknąć przycisk Otwórz. 1 2 18
Zaznaczając kolejne bloki zauważyć można w zakładce Source Code podświetlenie różnych partii pseudokodu (kodu pośredniego). Dolne indeksy przy poszczególnych zmiennych i funkcjach odnoszą się do numeru taktu zegara, w którym dana zmienna lub funkcja są wykorzystywane. W tym miejscu należy zastanowić się nad funkcjonalnością pseudokodu i możliwością samodzielnego przełożeni go na wygenerowaną logikę. W zakładce Datapath widoczny jest schemat blokowy zaznaczonego bloku (schemat przepływu). 19
Przeglądając diagramy przepływu każdego z bloków należy: zastanowić się na ich funkcjonalnością, przyjrzeć się jak realizowane są poszczególne operacje arytmetyczne i logiczne, zwrócić uwagę na opóźnienie wnoszone przez poszczególne bloki, zastanowić się nad wpływem bloków konwersji typu danych. 20
6. Optymalizacja projektu Zredagować kod Impulse C tak, by: operować jedynie na danych typu całkowitoliczbowego (int), używać jedynie typów dedykowanych dla sprzętu (co_), używać jedynie operacji dedykowanych dla danego typu danych (np. IADDxx(arg1, arg2)). Należy zwrócić uwagę na to jaką wartością inicjalizujemy zmienne i dopilnować, by nadal został wygenerowany pojedynczy okres generatora. Wskazówki odnośnie poszczególnych typów i funkcji można znaleźć w User Guide CoDevelopera. 7. Sprzętowa implementacja generatora Implementacja generatora zostanie wykonana z użyciem programu ISE Design Suite. W pierwszym kroku należy uruchomić aplikację poprzez dwukrotne kliknięcie ikony znajdującej się na pulpicie. Wygląd ikon dla poszczególnych wersji ISE może różnić się między sobą. 7.1. Tworzenie projektu W zakładce File wybieramy New Project 21
W oknie czarodzieja należy stosownie nazwać projekt (np. generator) Name:, a następnie kliknąć przycisk Next. 1 2 22
W następnym oknie należy wybrać: rodzinę układów Virtex 5 (Family Virtex5), układ xc5vlx220 (Device XC5VLX220), typ obudowy ff1760 (Package FF1760), opóźnienie (Speed -2), narzędzie do syntezy (Synthesis Tool XST (VHDL/Verilog)), symulator ModelSim (Simulator Modelsim-xx VHDL), preferowany język VHDL (Preferred Language VHDL), standard języka (VHDL Source Analysis Standard VHDL-93), a następnie kolejno kliknąć przyciski Next i Finish. 4 3 2 1 7 6 5 8 9 23
7.2. Dołączanie źródeł Gdy już nowy projekt zostanie utworzony, koniecznie należy dołączyć do niego pliki źródłowe. W tym celu kliknąć należy na ikonę Add Source, a następnie dodać wszystkie pliki znajdujące się w podkatalogu hw w katalogu export_hw (katalog eksportowy CoDevelopera). 24
W oknie informującym o statusie dołączanych plików należy kliknąć przycisk OK. 7.3. Tworzenie biblioteki Aby projekt mógł zostać zsyntezowany, a następnie zaimplementowany, koniecznym jest dołączenie do niego wszystkich plików charakterystycznych dla języka Impulse C i aktualnie używanego PSP (Platform Support Package). Pliki te zebrane zostać powinny w bibliotekę o nazwie impulse. 25
W programie ISE DS należy wybrać zakładkę Libraries, a następnie z menu kontekstowego wybrać New Source 2 1 26
W nowym oknie wybrać VHDL Library, a następnie nazwać bibliotekę (File name:) impulse. Nazwa impulse jest obligatoryjna! Gdy zostanie to zrobione należy kliknąć przyciski Next, a następnie Finish. 1 2 3 Do nowo stworzonej biblioteki koniecznie należy dodać pliki. W tym celu klikając prawym przyciskiem myszy na nazwie biblioteki impulse, wybieramy z menu kontekstowego opcję Add Source, 27
a następnie dodajemy wszystkie pliki zawarte w podkatalogu impulse_lib w katalogu export_hw (katalog eksportowy CoDevelopera). W oknie informującym o statusie dołączanych plików należy kliknąć przycisk OK. 28
7.4. Synteza Przejść do zakładki Design i uruchomić syntezę. W tym celu kliknąć dwukrotnie lewym przyciskiem myszy na opcji Syntesize XST lub umieścić nad nim kursor myszy, kliknąć prawym przyciskiem myszy wywołując menu kontekstowe, a następnie wybrać z niego opcję Run. Synteza powinna się zakończyć po paru minutach. 2 3 1 29
Gdy synteza zostanie zakończona, należy zapoznać się z jej wynikami maksymalną częstotliwością pracy systemu, zajętymi zasobami logicznymi, schematem RTL układu, itp. 7.5. Implementacja Aby przeprowadzić implementację układu, należy wykonać identyczny szereg czynności, jak w przypadku syntezy. W zakładce Design dwukrotnie klikamy lewym przyciskiem myszy na opcji Implement Design. Po kilku minutach implementacja powinna zostać zakończona. 2 3 1 Po zakończeniu implementacji proszę zapoznać się z jej wynikami. Zwrócić szczególną uwagę na wyniki analizy czasowej Static Timing. 7.6. Podsumowanie Proszę zastanowić się nad różnicami wyników (pochodzących z syntezy oraz implementacji) dotyczących maksymalnej częstotliwości pracy układu/minimalnego okresu sygnału zegarowego. Czy częstotliwość pracy układu jest duża, czy mała? Proszę zastanowić się nad ilością zajętych przez układ zasobów logicznych. Czy jest to duża ilość zasobów? Proszę zastanowić się nad szybkością projektowania całego systemu, wykonania testów poprawności działania układu, a na końcu przeprowadzenia operacji sprzętowej implementacji. Czy poświęcony na to czas był długi? Raport z syntezy i implementacji w postaci pliku html znajduje się w katalogu roboczym projektu. Proszę go zachować, gdyż będzie on jeszcze potrzebny. Uwaga! Oglądane wyniki syntezy i implementacji dotyczą samego układu generatora. Samego jego rdzenia. Aby móc uruchomić zaprojektowany system cyfrowy na platformie sprzętowej (np. DRC AC2020), konieczną jest jeszcze implementacja interfejsów komunikacyjnych. Spowoduje to dodatkowo powiększenie zajętych zasobów logicznych, eliminując jednocześnie problem interfejsu komunikacyjnego pomiędzy układem FGPA a światem. Ilość zajętych przez interfejs komunikacyjny zasobów jest praktycznie stała i zależna od platformy sprzętowej 30