Skocz do zawartości

Przeszukaj forum

Pokazywanie wyników dla tagów 'FPGA'.

  • Szukaj wg tagów

    Wpisz tagi, oddzielając przecinkami.
  • Szukaj wg autora

Typ zawartości


Kategorie forum

  • Elektronika i programowanie
    • Elektronika
    • Arduino i ESP
    • Mikrokontrolery
    • Raspberry Pi
    • Inne komputery jednopłytkowe
    • Układy programowalne
    • Programowanie
    • Zasilanie
  • Artykuły, projekty, DIY
    • Artykuły redakcji (blog)
    • Artykuły użytkowników
    • Projekty - DIY
    • Projekty - DIY roboty
    • Projekty - DIY (mini)
    • Projekty - DIY (początkujący)
    • Projekty - DIY w budowie (worklogi)
    • Wiadomości
  • Pozostałe
    • Oprogramowanie CAD
    • Druk 3D
    • Napędy
    • Mechanika
    • Zawody/Konkursy/Wydarzenia
    • Sprzedam/Kupię/Zamienię/Praca
    • Inne
  • Ogólne
    • Ogłoszenia organizacyjne
    • Dyskusje o FORBOT.pl
    • Na luzie

Kategorie

  • Quizy o elektronice
  • Quizy do kursu elektroniki I
  • Quizy do kursu elektroniki II
  • Quizy do kursów Arduino
  • Quizy do kursu STM32L4
  • Quizy do pozostałych kursów

Szukaj wyników w...

Znajdź wyniki, które zawierają...


Data utworzenia

  • Rozpocznij

    Koniec


Ostatnia aktualizacja

  • Rozpocznij

    Koniec


Filtruj po ilości...

Data dołączenia

  • Rozpocznij

    Koniec


Grupa


Imię


Strona

  1. Chciałbym zaprosić wszystkich zainteresowanych do mojego kursu FPGA Lattice w języku Verilog. W kursie wykorzystujemy FPGA typu MachXO2 z uwagi na jego niską cenę, obudowę łatwą do lutowania i duży potencjał w wykorzystaniu na potrzeby hobbystycznych projektów. Kurs jest publikowany w Elektronice Praktycznej, a także dostępny jest na stronie ep.com.pl. Wszystkie odcinki starsze niż 3 miesiące dostępne są bezpłatnie. Wciąż piszę kolejne odcinki i co miesiąc będzie pojawiać się kolejny. Będę wdzięczny za wszystkie uwagi i sugestie, a także propozycje tematów do kolejnych odcinków kursu. Repozytorium modułów używanych w kursie Tutaj znajdują się różne moduły wraz z opisem oraz z testbenchami. Kurs jest zorganizowany w taki sposób, że moduły opracowane w poprzednich odcinkach kursu (jak np. sterownik wyświetlacza) są wykorzystywane później w bardziej zaawansowanych odcinkach. Aby ułatwić dostęp do tych modułów wszystko jest opublikowane również na GitHubie. https://github.com/leonow32/verilog-fpga Płytki rozwojowe do kursu FPGA Lattice https://ep.com.pl/projekty/projekty-ep/15788-plytki-rozwojowe-do-kursu-fpga-lattice Niedrogi programator JTAG https://ep.com.pl/projekty/projekty-ep/15365-niedrogi-programator-jtag-do-ukladow-fpga Wszystkie odcinki kursu: EP 2022/11 - Wstęp EP 2022/12 - Pierwszy projekt EP 2023/01 - Podstawy języka Verilog EP 2023/02 - Generator, dzielnik i licznik EP 2023/03 - IP Express i inne gotowce EP 2023/04 - Parametry i ćwiczenia EP 2023/05 - Analizator logiczny Reveal EP 2023/06 - Symulacja w EDA Playground EP 2023/07 - Wyświetlacz LED multipleksowany EP 2023/08 - Klawiatura matrycowa i maszyna stanów EP 2023/09 - Statyczna analiza czasowa i maksymalna częstotliwość zegara EP 2023/10 - Symulacja w Icarus Verilog i GTK Wave EP 2023/11 - Wyświetlacz LCD multipleksowany EP 2023/12 - Enkoder obrotowy EP 2024/01 - Pamięć EBR EP 2024/02 - Generowanie dźwięków EP 2024/03 - Odtwarzacz melodii EP 2024/04 - Nadajnik UART EP 2024/05 - Odbiornik UART EP 2024/06 - 14-segmentowy wyświetlacz LCD EP 2024/07 - Terminal UART z 14-segmentowym wyświetlaczem LCD EP 2024/08 - Sekwencyjny algorytm Double Dabble EP 2024/09 - Kombinacyjny algorytm Double Dabble Będę wdzięczny za wszystkie uwagi i sugestie, a także propozycje tematów do kolejnych odcinków kursu.
  2. Cześć, próbował może ktoś z użytkowników forum tworzyć własne moduły biblioteczne i pakować je jako "IP Core" w Xilinx Vivado? Chodzi mi głównie o tworzenie większych modułów z wykorzystaniem magistrali AXI (zarówno Stream, jak i Memory Mapped), bo raczej bez wykorzystania tej magistrali, nie da się za dużo zrobić w technologii Xilinx'a. Chodzi mi o tworzenie własnych "IP Core's" zarówno jako RTL (języki: VHDL i Verilog"), jak i za pomocą HLS (w języku C++). Możecie polecić jakieś dobre tutoriale związane z tym zagadnieniem? Pozdrawiam
  3. Witam serdecznie! Osoby, które chciałyby zainstalować środowisko ISE Xilinx w wersji 14.7, np. tak jak to jest opisane w Kursie FPGA, mogą napotkać następujący problem: przy próbie otwarcia jakiegokolwiek pliku, już w czasie instalacji licencji - po kliknięciu przycisku "Load License..." w Xilinx License Configuration Manager, czy później już w czasie używania aplikacji ISE Design Suite przy próbie otwarcia pliku .vhd - aplikacja zamyka się bez żadnego komunikatu - po prostu okienko znika bez śladu... Chwilę mi zajęło znalezienie rozwiązania tego problemu, ale ostatecznie udało się go rozwiązać, więc może podzielę się tutaj tą informacją z nadzieją, że dzięki temu ktoś zaoszczędzi kilka minut googlania... 😉 1) W samym Xilinx License Configuration Managerze rozwiązanie jest proste - zamiast klikać "Load License..." wystarczy wpisać ścieżkę do pliku z licencją w polu opisanym "XILINXD_LICENSE_FILE", a następnie kliknąć przycisk "Set". 2) Aby "naprawić" ISE Design Suite trzeba podmienić plik libPortability.dll znajdujący się w folderze ...\Xilinx\14.7\ISE_DS\ISE\lib\nt64 (gdzie "..." oznacza tutaj miejsce, gdzie znajduje się folder Xilinx z zainstalowanym środowiskiem - np. C:\) oraz w folderze ...\Xilinx\14.7\ISE_DS\common\lib\nt64 plikiem libPortabilityNOSH.dll, który znajduje się w folderze ...\Xilinx\14.7\ISE_DS\ISE\lib\nt64. Czyli np. zmieniamy nazwy plików libPortability.dll na libPortability.dll.backup, robimy kopię pliku libPortabilityNOSH.dll z tego pierwszego folderu, nadajemy jej nazwę libPortability.dll, a następnie umieszczamy tak spreparowany plik w obu w.w. folderach. Oczywiście opisaną tu operację robić należy przy zamkniętych wszystkich aplikacjach ze środowiska Xilinx. Rozwiązanie opisane wyżej wydedukowałem z tego wpisu na stronie Xilinx.com. Wprawdzie problem tam opisywany był trochę inny, a wyżej opisana metoda stanowiła tylko część tego rozwiązania, które tam się pojawiło, ale jej zastosowanie w moim przypadku dało dobry rezultat (przynajmniej na ten moment nie widzę już problemów - udało mi się utworzyć projekt, napisać moduł VHDL, utworzyć na jego podstawie plik binarny do zaprogramowania FPGA i wgrać ten plik na płytkę). Pozdrowienia dla wszystkich fanów układów programowalnych! 🙂
  4. Cześć, już od jakiegoś czasu myślę nad własnym zestawem uruchomieniowym na układzie FPGA firmy Gowin. Postanowiłem ten zestaw oprzeć na układzie GW1N-LV9LQ144. Jest to układ zawierający 8640 LUT i 6480 Flip_Flop - patrz porównanie zasobów układów FPGA Gowin serii GW1N: https://www.gowinsemi.com/en/product/detail/46/ Dzięki temu na tym zestawie będzie można wypróbować Soft-CPU (np. z rodziny RISC-V) oraz bardziej skomplikowane IP Cores firmy Gowin: https://www.gowinsemi.com/en/support/ip/ Dzisiaj miałem trochę czasu, aby usiąść nad tym projektem. Udało mi się zaprojektować układy potrzebne do setup'u Układu FPGA, zasilanie banków układu FPGA, układ USB 2 JTAG (układ będzie można programować przez programator JTAG oparty na układzie scalonym FT2232, oraz przez header JTAG). Pamieć RAM (PSRAM 8MB) i zewnętrzny SPI Flash. W dalszej części będę dodawał peryferia do tego zestawu FPGA: diody LED, DIP-Switch'e, switche, DAC VGA, czytnik kart uSD, przetwornik ADC i DAC. Zamieszczam początkowy schemat zestawu FPGA (który będę aktualizował). W przyszłości zamieszczę także projket płytki PCB dla tego zestawu. Schematic_Gowin_GW!N_LV9_LQ144_DevBoard_2023-04-30.pdf Zależało mi aby ten zestaw posiadał wewnętrzny programator JTAG (z portem USB), tak aby można go programować bez zewnętrznego programatora "Gowin cable", który jest drogi. Robiłem próby z pamięcią SRAM 2Mx16-bit i działa ona OK, ale jest bardzo droga, więc postanowiłem użyć dużo tańszej pamieci PSRAM (8 MB).Dodałem też zewnętrzny SPI Fash do zestawu. Pozdrawiam
  5. Cześć, postanowiłem dzisiaj wypróbować generację kodu VHDL z użyciem "chat GPT". Założenia były takie, że mamy tablicę (bufor obrazu) o rozdzielczości VGA (640x480 pikseli, gdzie kazdy piksel ma 16 bitów). Aby kod był niezależny od konkretnej kości FPGA załozyłem, że opis obrazu jest w tablicy języka VHDL: signal image : array(0 to 639, 0 to 479) of STD_LOGIC_VECTOR(15 downto 0); a obraz wyjściowy (krawędzie) są w tablicy: signal edge_buffer : array(0 to 639, 0 to 479) of STD_LOGIC; Jest to po to aby abstrahować od konkretnego miejsca przechowywania obrazu (bufor obrazu), który docelowo może być w pamięci BLOCK_RAM układu FPGA lub w zewnętrznej pamięci RAM. Na początku poprosiłem "chatGPT" o wygenerowanie prostego przykładu filtrowania obrazu filtrem Sobel'a. Kod został szybko wygenerowany i wydawał się być całkiem poprawny (porównałem podstawową implementację filtru Sobel'a w języku C++). Nastepnie prosiłem chatGPT o dodawanie kolejnych opcji zrównoleglania - po kolei: 1) Zrównoleglanie liczby jednocześnie przetwarzanych pikseli - stała: constant PARALLELISM : natural := 4; 2) Podział obrazu FPGA na bloki przetwarzane równolegle: constant NUM_BLOCKS_X : integer := 160; -- Ilość bloków pikseli w poziomie constant NUM_BLOCKS_Y : integer := 120; -- Ilość bloków pikseli w pionie 3) Dodanie pipeline do przetwarzania Tutaj cały wygenerowany kod VHDL: library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity SobelEdgeDetection is Port ( clk : in STD_LOGIC; reset : in STD_LOGIC; pixel_in : in STD_LOGIC_VECTOR(15 downto 0); edge_out : out STD_LOGIC ); end SobelEdgeDetection; architecture ParallelPipelinedBehavior of SobelEdgeDetection is type PixelBlock is array(0 to 3, 0 to 3) of STD_LOGIC_VECTOR(15 downto 0); signal block_x : integer := 0; signal block_y : integer := 0; signal image : array(0 to 639, 0 to 479) of STD_LOGIC_VECTOR(15 downto 0); signal edge_buffer : array(0 to 639, 0 to 479) of STD_LOGIC; signal sobel_x : integer; signal sobel_y : integer; signal gradient : integer; signal gradient_reg : integer := 0; constant sobel_mask_x : array(0 to 2, 0 to 2) of integer := ((-1, 0, 1), (-2, 0, 2), (-1, 0, 1)); constant sobel_mask_y : array(0 to 2, 0 to 2) of integer := ((-1, -2, -1), ( 0, 0, 0), ( 1, 2, 1)); constant NUM_BLOCKS_X : integer := 160; -- Ilość bloków pikseli w poziomie constant NUM_BLOCKS_Y : integer := 120; -- Ilość bloków pikseli w pionie constant PARALLELISM : natural := 4; signal processing_stage : integer := 0; signal sobel_x_stage : integer; signal sobel_y_stage : integer; signal gradient_stage : integer; signal edge_stage : STD_LOGIC; signal pipeline_ready : boolean := false; begin process(clk) begin if rising_edge(clk) then if reset = '1' then block_x <= 0; block_y <= 0; processing_stage <= 0; for i in 0 to 639 loop for j in 0 to 479 loop image(i, j) <= (others => '0'); edge_buffer(i, j) <= '0'; end loop; end loop; else -- Przetwarzanie równoległe z pipeliningiem if not pipeline_ready then case processing_stage is when 0 => -- Wczytywanie bloku pikseli for i in 0 to 3 loop for j in 0 to 3 loop image(block_x * 4 + i, block_y * 4 + j) <= pixel_in; end loop; end loop; if block_x = NUM_BLOCKS_X - 1 and block_y = NUM_BLOCKS_Y - 1 then pipeline_ready <= true; processing_stage <= 0; -- Reset etapu przetwarzania elsif block_x = NUM_BLOCKS_X - 1 then block_x <= 0; block_y <= block_y + 1; else block_x <= block_x + 1; end if; when 1 => -- Obliczenia gradientów sobel_x_stage <= (to_integer(unsigned(image(block_x * 4 - 1, block_y * 4 - 1))) * sobel_mask_x(0, 0)) + (to_integer(unsigned(image(block_x * 4, block_y * 4 - 1))) * sobel_mask_x(0, 1)) + (to_integer(unsigned(image(block_x * 4 + 1, block_y * 4 - 1))) * sobel_mask_x(0, 2)) + (to_integer(unsigned(image(block_x * 4 - 1, block_y * 4))) * sobel_mask_x(1, 0)) + (to_integer(unsigned(image(block_x * 4 + 1, block_y * 4))) * sobel_mask_x(1, 2)) + (to_integer(unsigned(image(block_x * 4 - 1, block_y * 4 + 1))) * sobel_mask_x(2, 0)) + (to_integer(unsigned(image(block_x * 4, block_y * 4 + 1))) * sobel_mask_x(2, 1)) + (to_integer(unsigned(image(block_x * 4 + 1, block_y * 4 + 1))) * sobel_mask_x(2, 2)); sobel_y_stage <= (to_integer(unsigned(image(block_x * 4 - 1, block_y * 4 - 1))) * sobel_mask_y(0, 0)) + (to_integer(unsigned(image(block_x * 4, block_y * 4 - 1))) * sobel_mask_y(0, 1)) + (to_integer(unsigned(image(block_x * 4 + 1, block_y * 4 - 1))) * sobel_mask_y(0, 2)) + (to_integer(unsigned(image(block_x * 4 - 1, block_y * 4))) * sobel_mask_y(1, 0)) + (to_integer(unsigned(image(block_x * 4 + 1, block_y * 4))) * sobel_mask_y(1, 2)) + (to_integer(unsigned(image(block_x * 4 - 1, block_y * 4 + 1))) * sobel_mask_y(2, 0)) + (to_integer(unsigned(image(block_x * 4, block_y * 4 + 1))) * sobel_mask_y(2, 1)) + (to_integer(unsigned(image(block_x * 4 + 1, block_y * 4 + 1))) * sobel_mask_y(2, 2)); gradient_stage <= abs(sobel_x_stage) + abs(sobel_y_stage); if gradient_stage > 1000 then edge_stage <= '1'; else edge_stage <= '0'; end if; processing_stage <= 2; -- Przejdź do etapu 2 when 2 => -- Wczytywanie wyników do bufora krawędzi edge_buffer(block_x * 4 + 3, block_y * 4 + 3) <= edge_stage; if block_x = NUM_BLOCKS_X - 1 and block_y = NUM_BLOCKS_Y - 1 then pipeline_ready <= false; -- Zakończenie przetwarzania bloku elsif block_x = NUM_BLOCKS_X - 1 then block_x <= 0; block_y <= block_y + 1; else block_x <= block_x + 1; end if; end case; end if; end if; end if; end process; end ParallelPipelinedBehavior; Co sądzicie o wygenerowanym kodzie - wstepnie go przeanalizowałem i na rzaie nie mogę znaleźć błędu. Co sądzicie ogólnie o generacji kodu w róznych językach programowania za pomocą "chatGPT" i ogólnie AI. Tutaj link do dużo potężniejszego narzędzia AI o nazwie kodowej "Gorilla" : https://lablab.ai/tech/gorilla Projekt "Gorilla" można za darmo testować na "Google Clolab". Pozdrawiam
  6. Cześć, trafiłem na kolejny wart polecenia kompletny kurs języka Verilog (w języku angielskim). Tutaj link do całej serii na Youtube: https://www.youtube.com/watch?v=33PAoJGm2Fo&list=PL_3xKnVkfI2itQhCyfnamNYSCHd2KHi4k&index=1 Pozdrawiam
  7. Cześć, ponieważ, przy szukaniu kodu dla gry "Snake" natrafiłem na fajne repozytorium kodu dla płytki FPGA "Pepino" (Xilinx Spartan6), postanowiłem spróbować uruchomić jeszcze jedną "oldschool'ową" grę "Pacaman" na zestawie FPGA. W tym przypadku była wymaganą minimalna ilość pracy w kodzie projektu pobranego z repozytorium GIT(dla płytki Pepino): zamiana pliku "user constraints" "pacman.ucf". Tutaj prawidłowa postać tego pliku dla płytki Mimas V.2: # NUMATO Mimas V2 ucf file ## http://numato.com/ ## PACMAN UCF by Alex CONFIG VCCAUX = 3.3; ##Clock signal 100 MHz NET "SYS_CLK" LOC = V10 | IOSTANDARD = LVCMOS33 | PERIOD = 100MHz; NET "SYS_CLK" TNM_NET = "sys_clk_pin"; TIMESPEC TS_sys_clk_pin = PERIOD "sys_clk_pin" 100 MHz HIGH 50 %; # LEDS NET "LED[3]" LOC = P15 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "LED[2]" LOC = U18 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "LED[1]" LOC = T17 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "LED[0]" LOC = T18 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; # DIP Switches for one player, two player and credit #DP 8 NET "SW[0]" LOC = C17 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; NET "SW[1]" LOC = C18 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; NET "SW[2]" LOC = D17 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; NET "SW[3]" LOC = D18 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; # Push Buttons Switches For Joystick #SW6 - FIRE NET "JOYSTICK[4]" LOC = P1 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; #SW1 RIGHT NET "JOYSTICK[3]" LOC = M18 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; #SW2 LEFT NET "JOYSTICK[2]" LOC = L18 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; #SW1 DOWN NET "JOYSTICK[1]" LOC = L17 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; #SW3 UP NET "JOYSTICK[0]" LOC = M16 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; # Audio # # Audio Left NET "audio_l" LOC = B16 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; # Audio Right NET "audio_r" LOC = A16 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; # VGA Connections NET "VGA_HSYNC" LOC = B12 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "VGA_VSYNC" LOC = A12 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "VGA_RED[2]" LOC = C9 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "VGA_RED[1]" LOC = B9 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "VGA_RED[0]" LOC = A9 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "VGA_GREEN[2]" LOC = C11 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "VGA_GREEN[1]" LOC = A10 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "VGA_GREEN[0]" LOC = C10 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "VGA_BLUE[1]" LOC = A11 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "VGA_BLUE[0]" LOC = B11 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; Poza tym trzeba było podmienić kawałek kodu w głównym entity projektu (związanego z głównym zegarem projektu) i dokonać zamiany typu układu FPGA Spartan6 (obudowa CSG324) we właściwościach projektu. No i dokonać syntezy i implementacji układu oraz generacji binarnego pliku konfiguracji układu FPGA ("pacman_top.bin" w katalogu "synth" projektu. Tutaj link do repozytorium kodu dla płytki FPGA Pepino (zawierającego miedzy innymi kod gry "Pacman"): https://github.com/Saanlima/Pepino/tree/master/Projects A tutaj link do tutoriala jakie zmiany trzeba wykonać, aby uruchomić grę "Pacman" na zestawie FPGA "Mimas V.2" (postępowałem dokladnie według tego tutorial'a): http://langster1980.blogspot.com/2016/04/pacman-running-on-mimas-v2-fpga.html Tak gra "Pacman prezentuje się na zestawie FPGA "Mimas V.2 (z małym monitorkiem 10 cali VGA): Myślę, że moim następnym krokiem będzie uruchomienie sprzętowego emulatora (na zestawie FPGA) komputera "Commodore C64" a w przyszłości "Amigi 600". No ale na to trzeba więcej czasu a i stopień skomplikowania projektu jest dużo większy. W archiwum zip spakowany projekt gry Pacman (dla Mimas V.2) dla "ISE 14.7" : Pepino-pacman.zip BTW: po namyśle, zdecydowałem, że "zabawę" z emulacją jakiejś prostej konsoli do gier rozpocznę od "NES" (Nintendo), bo w tym repozytorium dla płytki FPGA "Pepino" jest do jej emulacji fajny kod źródłowy: https://github.com/Saanlima/Pepino/tree/master/Projects/Pepino_NES_smb , tylko muszę najpierw kupić taki JoyPad (z oryginalnym złączem do konsoli NES) do tej konsoli (użyty w tym projekcie): https://allegro.pl/oferta/pad-do-konsoli-nintendo-entertainment-system-nes-11505068172 Pozdrawiam
  8. Cześć, chciałbym prosić o pomoc w kwestii podpięcia modułu Pmod BLE do płytki Zybo Z7-10. Opis projektu Chciałbym podłączyć Pmod BLE do płytki Zybo Z7-10. Chciałbym by testowa aplikacja mogła wysyłać i odbierać dane po bluetooth (do testowania chciałbym użyć jakiejś aplikacji z AppStore). Na początku mogłoby to być wysyłanie zwykłego ciągu znaków, jak np: "Cześć Zybo" i "Cześć telefon" czy coś podobnego. Próby podłączenia Znalazłem podobny przykład w intrnecie (tutaj -> link autorstwa osoby o nicku: ralphjy). Próbowałem odtworzyć projekt, który opisał w tym artykule jednak nie udało mi się to. Poniżej zamieszczam swój block diagram, który jest podobny do tego który prezentuje ralphjy. Dołączam jeszcze constraints mojego projektu. Niestety nie wiem jak dokładnie powinny być one podpięte. Na końcu problemu zamieszczam link do constraint dla Zybo. Oczywiście próbowałem wygenerować bitstreama, ale bezskutecznie :(. Może ktoś z Was miał już do czynienia z modułem Pmod BLE i byłby w stanie pomóc :)? Btw. dzięki wielkie za jakiekolwiek wskazówki! Sprzęt Zybo Z7-10 -> link Pmod BLE -> link Software Zybo-Z7 Constraints -> link pliki źródłowe Pmod BLE-> link
  9. Cześć, jestem w trakcie prac nad zbieraniem sampli sygnału audio na zestawie FPGA "Sipeed Tang Nano 4K" i potrzebuje w celu debugowania układu jakiegoś kanału do komunikacji z portem szeregowym, lub Arduino. Chciałem użyć któregoś z IP Cores z "Gowin EDA" ale zarówno UART jak i "SPI Master" są tak przekombinowane, że ich użycie wymaga napisania sporej ilości kodu. Postanowiłem więc użyć zestawu FPGA "Elbert v.2" w celu napisania prostego kodu do komunikacji za pomocą protokołu SPI . Powstał więc bardzo prosty "SPI Master", który jest uruchomiony na zestawie FPGA i połączony za pomocą protokołu SPI z "Arduino UNO", które odbiera dane i wyświetla je za pomocą "Monitora portu szeregowego". Do samego kodu "SPI Master'a" został dołączony "debouncer" do przycisku i generator pojedynczego impulsu (o długości okresu zegara), który inicjuje wysłanie jednego bajtu danych. W celu wygenerowania jednego bardzo krótkiego impulsu napisałem (za pomocą kilku przerzutników typu D) i bramki AND układ generatora monostabilnego. Tutaj wynik symulacji działania generatora monostabilnego: Układ działa w ten sposób, że naciśnięcie przycisku (SW1 z Elbert'a) powoduje wysłanie jednego bajtu danych do Arduino za pomocą SPI. Tak wygląda układ testowy (potrzebny był poza FPGA i Arduino 4-liniowy konwerter poziomów logicznych): Tutaj kod w VHDL głównego modułu (a właściwie entity) projektu - plik "lw_spi_master.vhd ": library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; -- Uncomment the following library declaration if using -- arithmetic functions with Signed or Unsigned values --use IEEE.NUMERIC_STD.ALL; -- Uncomment the following library declaration if instantiating -- any Xilinx primitives in this code. --library UNISIM; --use UNISIM.VComponents.all; entity lw_spi_master is generic ( c_clkfreq : integer := 12_000_000; c_sclkfreq : integer := 5_000_000; c_cpol : std_logic := '0'; c_cpha : std_logic := '0' ); Port ( clk_i : in STD_LOGIC; en_i_Low : in STD_LOGIC; mosi_data_i : in STD_LOGIC_VECTOR (7 downto 0); miso_data_o : out STD_LOGIC_VECTOR (7 downto 0); data_ready_o : out STD_LOGIC; cs_o : out STD_LOGIC; sclk_o : out STD_LOGIC; mosi_o : out STD_LOGIC; miso_i : in STD_LOGIC ); end lw_spi_master; architecture Behavioral of lw_spi_master is component debounce is port ( clk : IN STD_LOGIC; --input clock button : IN STD_LOGIC; --input signal to be debounced result : OUT STD_LOGIC); --debounced signal end component; component monostable_ff is port( mono_pulse : out std_logic; clk : in std_logic; reset: in std_logic; key :in std_logic ); end component; signal write_reg : std_logic_vector (7 downto 0) := (others => '0'); signal read_reg : std_logic_vector (7 downto 0) := (others => '0'); signal en_i : std_logic := '0'; signal sclk_en : std_logic := '0'; signal sclk : std_logic := '0'; signal sclk_prev : std_logic := '0'; signal sclk_rise : std_logic := '0'; signal sclk_fall : std_logic := '0'; signal pol_phase : std_logic_vector (1 downto 0) := (others => '0'); signal mosi_en : std_logic := '0'; signal miso_en : std_logic := '0'; signal en_i_DB : std_logic; signal cnt : std_logic_vector (10 downto 0) := (others => '0'); constant c_edgecntrlimdiv2 : integer := c_clkfreq/(c_sclkfreq*2); signal edgecntr : integer range 0 to c_edgecntrlimdiv2 := 0; signal cntr : integer range 0 to 15 := 0; signal small_cnt : integer range 0 to 1000 := 0; signal blokada : integer range 0 to 3 := 0; type states is (S_IDLE, S_TRANSFER); signal state : states := S_IDLE; -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- begin C1: debounce port map (clk_i,en_i_Low, en_i_DB); C2: monostable_ff port map ( mono_pulse => en_i, clk => clk_i, reset => '1', key => not en_i_DB ); --en_i <= not(en_i_DB); pol_phase <= c_cpol & c_cpha; P_SAMPLE_EN : process (pol_phase, sclk_fall, sclk_rise) begin case pol_phase is when "00" => mosi_en <= sclk_fall; miso_en <= sclk_rise; when "01" => mosi_en <= sclk_rise; miso_en <= sclk_fall; when "10" => mosi_en <= sclk_rise; miso_en <= sclk_fall; when "11" => mosi_en <= sclk_fall; miso_en <= sclk_rise; when others => end case; end process; P_RISEFALL_DETECT : process (sclk, sclk_prev) begin if (sclk = '1' and sclk_prev = '0') then sclk_rise <= '1'; else sclk_rise <= '0'; end if; if (sclk = '0' and sclk_prev = '1') then sclk_fall <= '1'; else sclk_fall <= '0'; end if; end process; P_MAIN : process (clk_i) begin if (rising_edge(clk_i)) then sclk_prev <= sclk; case state is when S_IDLE => cs_o <= '1'; mosi_o <= '0'; data_ready_o <= '0'; sclk_en <= '0'; cntr <= 0; if (c_cpol = '0') then sclk_o <= '0'; else sclk_o <= '1'; end if; if (en_i = '1') then state <= S_TRANSFER; sclk_en <= '1'; write_reg <= mosi_data_i; mosi_o <= mosi_data_i(7); read_reg <= x"00"; end if; when S_TRANSFER => cs_o <= '0'; mosi_o <= write_reg(7); if (c_cpha = '1') then if (cntr = 0) then sclk_o <= sclk; if (miso_en = '1') then read_reg(0) <= miso_i; read_reg(7 downto 1) <= read_reg(6 downto 0); cntr <= cntr + 1; end if; elsif (cntr = 8) then data_ready_o <= '1'; miso_data_o <= read_reg; if (mosi_en = '1') then data_ready_o <= '0'; if (en_i = '1') then write_reg <= mosi_data_i; mosi_o <= mosi_data_i(7); sclk_o <= sclk; cntr <= 0; else state <= S_IDLE; cs_o <= '1'; end if; end if; elsif (cntr = 9) then if (miso_en = '1') then state <= S_IDLE; cs_o <= '1'; end if; else sclk_o <= sclk; if (miso_en = '1') then read_reg(0) <= miso_i; read_reg(7 downto 1) <= read_reg(6 downto 0); cntr <= cntr + 1; end if; if (mosi_en = '1') then mosi_o <= write_reg(7); write_reg(7 downto 1) <= write_reg(6 downto 0); end if; end if; else -- c_cpha = '0' if (cntr = 0) then sclk_o <= sclk; if (miso_en = '1') then read_reg(0) <= miso_i; read_reg(7 downto 1) <= read_reg(6 downto 0); cntr <= cntr + 1; end if; elsif (cntr = 8) then data_ready_o <= '1'; miso_data_o <= read_reg; sclk_o <= sclk; if (mosi_en = '1') then data_ready_o <= '0'; if (en_i = '1') then write_reg <= mosi_data_i; mosi_o <= mosi_data_i(7); cntr <= 0; else cntr <= cntr + 1; end if; if (miso_en = '1') then state <= S_IDLE; cs_o <= '1'; end if; end if; elsif (cntr = 9) then if (miso_en = '1') then state <= S_IDLE; cs_o <= '1'; end if; else sclk_o <= sclk; if (miso_en = '1') then read_reg(0) <= miso_i; read_reg(7 downto 1) <= read_reg(6 downto 0); cntr <= cntr + 1; end if; if (mosi_en = '1') then write_reg(7 downto 1) <= write_reg(6 downto 0); end if; end if; end if; end case; end if; end process; P_SCLK_GEN : process (clk_i) begin if (rising_edge(clk_i)) then if (sclk_en = '1') then if edgecntr = c_edgecntrlimdiv2-1 then sclk <= not sclk; edgecntr <= 0; else edgecntr <= edgecntr + 1; end if; else edgecntr <= 0; if (c_cpol = '0') then sclk <= '0'; else sclk <= '1'; end if; end if; end if; end process; end Behavioral; W tym pliku jest zawarta cała implementacja "SPI Master'a". Widać też użycia komponentów "debouncer'a" i generatora monostabilnego. Tutaj kod "deuboncer'a": LIBRARY ieee; USE ieee.std_logic_1164.all; USE ieee.std_logic_unsigned.all; ENTITY debounce IS GENERIC( counter_size : INTEGER := 20); --counter size (19 bits gives 10.5ms with 50MHz clock) PORT( clk : IN STD_LOGIC; --input clock button : IN STD_LOGIC; --input signal to be debounced result : OUT STD_LOGIC); --debounced signal END debounce; ARCHITECTURE logic OF debounce IS SIGNAL flipflops : STD_LOGIC_VECTOR(1 DOWNTO 0); --input flip flops SIGNAL counter_set : STD_LOGIC; --sync reset to zero SIGNAL counter_out : STD_LOGIC_VECTOR(counter_size DOWNTO 0) := (OTHERS => '0'); --counter output BEGIN counter_set <= flipflops(0) xor flipflops(1); --determine when to start/reset counter PROCESS(clk) BEGIN IF(clk'EVENT and clk = '1') THEN flipflops(0) <= button; flipflops(1) <= flipflops(0); If(counter_set = '1') THEN --reset counter because input is changing counter_out <= (OTHERS => '0'); ELSIF(counter_out(counter_size) = '0') THEN --stable input time is not yet met counter_out <= counter_out + 1; ELSE --stable input time is met result <= flipflops(1); END IF; END IF; END PROCESS; END logic; Tutaj kod VHDL generatora krótkiego impulsu "monostable_ff.vhd": library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity monostable_ff is port( mono_pulse : out std_logic; clk : in std_logic; reset: in std_logic; key :in std_logic ); end monostable_ff; architecture Behavioral of monostable_ff is component DFF_AsyncReset is port( Q : out std_logic; QNot : out std_logic; clk : in std_logic; reset: in std_logic; D :in std_logic ); end component; component nand2 is port(a,b : in std_logic; y : out std_logic); end component; signal Q1,Q2,Q3,Q4,Q5 : std_logic; signal Q1N,Q2N,Q3N,Q4N,Q5N : std_logic; signal s11 : std_logic; begin DFF1: DFF_AsyncReset port map ( Q => Q1, QNot => Q1N, clk => clk, reset => reset, D => key ); DFF2: DFF_AsyncReset port map ( Q => Q2, QNot => Q2N, clk => clk, reset => reset, D => Q1 ); DFF3: DFF_AsyncReset port map ( Q => Q3, QNot => Q3N, clk => clk, reset => reset, D => Q2 ); DFF4: DFF_AsyncReset port map ( Q => Q4, QNot => Q4N, clk => clk, reset => reset, D => Q3 ); DFF5: DFF_AsyncReset port map ( Q => Q5, QNot => Q5N, clk => clk, reset => reset, D => Q4 ); mono_pulse <= (Q1 and Q5N); end Behavioral; Układ jest zrobiony na "liniI opóźniającej" z kilku przerzutników typu D i dwu-wejściowej bramki "AND" (kiedyś w mojej przeszłości budowało się podobne układy na układach scalonych z serii TTL). Do budowy tego generatora monostabilnego użyłem przerzutników typu D (wejście zegarowe reaguje na zbocze narastające) z asynchronicznym resetem stanem niskim. Tutaj kod przerzutnika typu D ---------------------------------------------------------------------------------- library IEEE; use IEEE.STD_LOGIC_1164.ALL; -- Uncomment the following library declaration if using -- arithmetic functions with Signed or Unsigned values --use IEEE.NUMERIC_STD.ALL; -- Uncomment the following library declaration if instantiating -- any Xilinx primitives in this code. --library UNISIM; --use UNISIM.VComponents.all; entity DFF_AsyncReset is port( Q : out std_logic; QNot : out std_logic; clk : in std_logic; reset: in std_logic; D :in std_logic ); end DFF_AsyncReset; architecture Behavioral of DFF_AsyncReset is begin process(clk,reset) begin if(reset='0') then Q <= '0'; QNot <= '1'; elsif(rising_edge(clk)) then Q <= D; QNot <= (not D); end if; end process; end Behavioral; A tutaj plik "user constraints" "lw-spi_master.ucf" dla zestawu Elbert v.2: CONFIG VCCAUX = "3.3" ; # Clock 12 MHz NET "clk_i" LOC = P129 | IOSTANDARD = LVCMOS33 | PERIOD = 12MHz; NET "en_i_Low" LOC = P80 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; #SW1 # mosi_data_i : in STD_LOGIC_VECTOR (7 downto 0); DIPSwitch NET "mosi_data_i[7]" LOC = P70 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[6]" LOC = P69 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[5]" LOC = P68 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[4]" LOC = P64 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[3]" LOC = P63 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[2]" LOC = P60 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[1]" LOC = P59 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[0]" LOC = P58 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; # miso_data_o : out STD_LOGIC_VECTOR (7 downto 0); LEDs NET "miso_data_o[7]" LOC = P46 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[6]" LOC = P47 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[5]" LOC = P48 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[4]" LOC = P49 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[3]" LOC = P50 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[2]" LOC = P51 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[1]" LOC = P54 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[0]" LOC = P55 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; #################################################################################################### # HEADER P1 #################################################################################################### # sclk_o : out STD_LOGIC; NET "sclk_o" LOC = P31 | IOSTANDARD = LVCMOS33 | SLEW = FAST | DRIVE = 12; #1 # mosi_o : out STD_LOGIC; NET "mosi_o" LOC = P32 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; #2 # miso_i : in STD_LOGIC NET "miso_i" LOC = P28 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; #3 # cs_o : out STD_LOGIC; NET "cs_o" LOC = P30 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; #4 # data_ready_o : out STD_LOGIC; NET "data_ready_o" LOC = P27 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; #5 #Port ( # clk_i : in STD_LOGIC; # en_i_Low : in STD_LOGIC; # mosi_data_i : in STD_LOGIC_VECTOR (7 downto 0); # miso_data_o : out STD_LOGIC_VECTOR (7 downto 0); # data_ready_o : out STD_LOGIC; # cs_o : out STD_LOGIC; # sclk_o : out STD_LOGIC; # mosi_o : out STD_LOGIC; # miso_i : in STD_LOGIC #); Jak widać w pliku dane (bajt), które są wysyłane magistralą SPI do Arduino UNO, są podłączone do 8-miu DIP Switchy z zestawu Elbert, a przycisk "SW1" powoduje jednorazowa wysyłkę bajtu danych. Tutaj zrzut ekranu z "monitora portu szeregowego" z ARDUINO IDE z programem do odbioru danych po SPI.: Na koniec wklejam spakowany zip'em projekt dla Xilinx "ISE 14.7". Implementacja całego projektu zajmuje poniżej 100 LUT. Wklejam opis projektu bo takie funkcje komunikacyjne przydają się w przyszłości do bardziej skomplikowanych projektów. Teraz pozostaje uruchomienie kodu na zestawie FPGA "Sipeed Tang nano 4K". LW_SPI_master_01.zip Pozdrawiam
  10. Cześć, jak wspomniałem wcześniej w wątku: Chciałbym porównać szybkość działania algorytmu FFT z użyciem "IP Core FFT" Xilinx'a. Mam zamiar wykonać próby na trzech zestawach FPGA (1024 próbki - 16 bit na próbkę - fixed point): 1) Elbert V.2 - Spartan3A (XC3S50A): brak bloków DSP i trzy sprzętowe układy mnożące 2) Mimas V.2 - Spartan6 (XC6SLX9) : 16 bloków DSP48 3) CMod A7-35 - Artix7 -(XC7A35T): 90 bloków DSP48 Z trzema różnymi częstotliwościami zegara układów FPGA. Rozpocząłem od projektu dla układu FPGA Spartan6(XC6SLX9 in CSG324 package) z zestawu MImas V.2: https://numato.com/product/mimas-v2-spartan-6-fpga-development-board-with-ddr-sdram/ Parametry IP Core FFT w wersji 8.0 Xilinx'a są tak dobrane tak, aby transformata FFT miała długość 1024 punkty i precyzję próbek 16-bit "fixed point" (z zegarem 100 MHz). Po ustawieniu parametrów IP Core jak jest przedstawione na kolejnych zrzutach ekranu: ekran 1 - długość transformaty 1024 punkty 1 kanał i opcja implementacji: "Radix-4, Burst I/O" (optymalizacja ze względu na czas wykonania(). ekran 2 - Precyzja 16-bit "scaled" ekran 3 - użycie "Block RAM", "Use 4-multipler structure" (optymalizacja z użyciem bloków DSP), "Arytmetyka Butterfly" z użyciem bloków CLB. Aby zorientować się jak działają konkretne wybrane opcje polecam przeczytać ten artykuł: https://www.allaboutcircuits.com/technical-articles/xilinx-fft-ip-core-fast-fourier-transform-software-walkthrough/ Po zakończeniu wyboru opcji dla "IP Core FFT V.8" i generacji jego modułów dodałem "top entity" (o nazwie: FFT_Top) w którym wywołuje(komponent) instancję IP Core FFT o nazwie " FFT_IP8". Na tym etapie jest to mi potrzebne wyłącznie w tym celu, aby móc uruchomić syntezę i implementację projektu w celu oszacowania liczby zasobów układu FPGA zużywanych przez projekt. I po zakończeniu fazy syntezy i implementacji można już podać liczbę podstawowych zużywanych zasobów: 1) Liczba przerzutników (FF) 1925 2) Liczba LUT 1468 3) Liczba użytych bloków DSP48 - 12 z 16 dostępnych (dla tej wersji układu Spartan6) Nie jest to mało - patrz zrzuty ekranu z ISE: Tutaj podaję kod głównego entity projektu (okazało się, że w przypadku tego IP Core jedyny wybór to język VHDL): ---------------------------------------------------------------------------------- -- Company: -- Engineer: -- -- Create Date: 13:32:16 12/31/2021 -- Design Name: -- Module Name: FFT_Top - Behavioral -- Project Name: -- Target Devices: -- Tool versions: -- Description: -- -- Dependencies: -- -- Revision: -- Revision 0.01 - File Created -- Additional Comments: -- ---------------------------------------------------------------------------------- library IEEE; use IEEE.STD_LOGIC_1164.ALL; -- Uncomment the following library declaration if using -- arithmetic functions with Signed or Unsigned values --use IEEE.NUMERIC_STD.ALL; -- Uncomment the following library declaration if instantiating -- any Xilinx primitives in this code. --library UNISIM; --use UNISIM.VComponents.all; entity FFT_Top is port ( aclk : in STD_LOGIC := 'X'; s_axis_config_tvalid : in STD_LOGIC := 'X'; s_axis_data_tvalid : in STD_LOGIC := 'X'; s_axis_data_tlast : in STD_LOGIC := 'X'; m_axis_data_tready : in STD_LOGIC := 'X'; s_axis_config_tready : out STD_LOGIC; s_axis_data_tready : out STD_LOGIC; m_axis_data_tvalid : out STD_LOGIC; m_axis_data_tlast : out STD_LOGIC; event_frame_started : out STD_LOGIC; event_tlast_unexpected : out STD_LOGIC; event_tlast_missing : out STD_LOGIC; event_status_channel_halt : out STD_LOGIC; event_data_in_channel_halt : out STD_LOGIC; event_data_out_channel_halt : out STD_LOGIC; s_axis_config_tdata : in STD_LOGIC_VECTOR ( 15 downto 0 ); s_axis_data_tdata : in STD_LOGIC_VECTOR ( 31 downto 0 ); m_axis_data_tdata : out STD_LOGIC_VECTOR ( 31 downto 0 ) ); end FFT_Top; architecture Behavioral of FFT_Top is ------------- Begin Cut here for COMPONENT Declaration ------ COMP_TAG COMPONENT FFT_IP8 PORT ( aclk : IN STD_LOGIC; s_axis_config_tdata : IN STD_LOGIC_VECTOR(15 DOWNTO 0); s_axis_config_tvalid : IN STD_LOGIC; s_axis_config_tready : OUT STD_LOGIC; s_axis_data_tdata : IN STD_LOGIC_VECTOR(31 DOWNTO 0); s_axis_data_tvalid : IN STD_LOGIC; s_axis_data_tready : OUT STD_LOGIC; s_axis_data_tlast : IN STD_LOGIC; m_axis_data_tdata : OUT STD_LOGIC_VECTOR(31 DOWNTO 0); m_axis_data_tvalid : OUT STD_LOGIC; m_axis_data_tready : IN STD_LOGIC; m_axis_data_tlast : OUT STD_LOGIC; event_frame_started : OUT STD_LOGIC; event_tlast_unexpected : OUT STD_LOGIC; event_tlast_missing : OUT STD_LOGIC; event_status_channel_halt : OUT STD_LOGIC; event_data_in_channel_halt : OUT STD_LOGIC; event_data_out_channel_halt : OUT STD_LOGIC ); END COMPONENT; -- COMP_TAG_END ------ End COMPONENT Declaration ------------ begin ------------- Begin Cut here for INSTANTIATION Template ----- INST_TAG FFT_1024_16 : FFT_IP8 PORT MAP ( aclk => aclk, s_axis_config_tdata => s_axis_config_tdata, s_axis_config_tvalid => s_axis_config_tvalid, s_axis_config_tready => s_axis_config_tready, s_axis_data_tdata => s_axis_data_tdata, s_axis_data_tvalid => s_axis_data_tvalid, s_axis_data_tready => s_axis_data_tready, s_axis_data_tlast => s_axis_data_tlast, m_axis_data_tdata => m_axis_data_tdata, m_axis_data_tvalid => m_axis_data_tvalid, m_axis_data_tready => m_axis_data_tready, m_axis_data_tlast => m_axis_data_tlast, event_frame_started => event_frame_started, event_tlast_unexpected => event_tlast_unexpected, event_tlast_missing => event_tlast_missing, event_status_channel_halt => event_status_channel_halt, event_data_in_channel_halt => event_data_in_channel_halt, event_data_out_channel_halt => event_data_out_channel_halt ); -- INST_TAG_END ------ End INSTANTIATION Template ------------ end Behavioral; Podczas generacji modułów "IP Core FFT V.8" został także utworzony test-bench, który zamierzam wykorzystać do pierwszych testów FFT - oto jego kod: --------------------------------------------------------------------------- -- -- (c) Copyright 2010 Xilinx, Inc. All rights reserved. -- -- This file contains confidential and proprietary information -- of Xilinx, Inc. and is protected under U.S. and -- international copyright and other intellectual property -- laws. -- -- DISCLAIMER -- This disclaimer is not a license and does not grant any -- rights to the materials distributed herewith. Except as -- otherwise provided in a valid license issued to you by -- Xilinx, and to the maximum extent permitted by applicable -- law: (1) THESE MATERIALS ARE MADE AVAILABLE "AS IS" AND -- WITH ALL FAULTS, AND XILINX HEREBY DISCLAIMS ALL WARRANTIES -- AND CONDITIONS, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING -- BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON- -- INFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE; and -- (2) Xilinx shall not be liable (whether in contract or tort, -- including negligence, or under any other theory of -- liability) for any loss or damage of any kind or nature -- related to, arising under or in connection with these -- materials, including for any direct, or any indirect, -- special, incidental, or consequential loss or damage -- (including loss of data, profits, goodwill, or any type of -- loss or damage suffered as a result of any action brought -- by a third party) even if such damage or loss was -- reasonably foreseeable or Xilinx had been advised of the -- possibility of the same. -- -- CRITICAL APPLICATIONS -- Xilinx products are not designed or intended to be fail- -- safe, or for use in any application requiring fail-safe -- performance, such as life-support or safety devices or -- systems, Class III medical devices, nuclear facilities, -- applications related to the deployment of airbags, or any -- other applications that could lead to death, personal -- injury, or severe property or environmental damage -- (individually and collectively, "Critical -- Applications"). Customer assumes the sole risk and -- liability of any use of Xilinx products in Critical -- Applications, subject only to applicable laws and -- regulations governing limitations on product liability. -- -- THIS COPYRIGHT NOTICE AND DISCLAIMER MUST BE RETAINED AS -- PART OF THIS FILE AT ALL TIMES. -- --------------------------------------------------------------------------- -- Description: -- This is an example testbench for the Fast Fourier Transform (FFT) LogiCORE module. -- The testbench has been generated by the Xilinx CORE Generator software -- to accompany the netlist you have generated. -- -- This testbench is for demonstration purposes only. See note below for -- instructions on how to use it with the netlist created for your core. -- -- See the FFT datasheet for further information about this core. -- --------------------------------------------------------------------------- -- Using this testbench -- -- This testbench instantiates your generated FFT core named "FFT_IP8". -- -- If your CORE Generator project options were set to generate a structural -- model, a VHDL netlist named FFT_IP8.vhd was generated. -- If this file is not present, execute the following command in the directory -- containing your CORE Generator output files, to create a VHDL netlist: -- -- netgen -sim -ofmt vhdl FFT_IP8.ngc FFT_IP8.vhd -- -- Compile FFT_IP8.vhd into the work library. See your simulator -- documentation for more information on how to do this. -- --------------------------------------------------------------------------- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use ieee.math_real.all; entity tb_FFT_IP8 is end tb_FFT_IP8; architecture tb of tb_FFT_IP8 is ----------------------------------------------------------------------- -- Timing constants ----------------------------------------------------------------------- constant CLOCK_PERIOD : time := 100 ns; constant T_HOLD : time := 10 ns; constant T_STROBE : time := CLOCK_PERIOD - (1 ns); ----------------------------------------------------------------------- -- DUT signals ----------------------------------------------------------------------- -- General signals signal aclk : std_logic := '0'; -- the master clock -- Config slave channel signals signal s_axis_config_tvalid : std_logic := '0'; -- payload is valid signal s_axis_config_tready : std_logic := '1'; -- slave is ready signal s_axis_config_tdata : std_logic_vector(15 downto 0) := (others => '0'); -- data payload -- Data slave channel signals signal s_axis_data_tvalid : std_logic := '0'; -- payload is valid signal s_axis_data_tready : std_logic := '1'; -- slave is ready signal s_axis_data_tdata : std_logic_vector(31 downto 0) := (others => '0'); -- data payload signal s_axis_data_tlast : std_logic := '0'; -- indicates end of packet -- Data master channel signals signal m_axis_data_tvalid : std_logic := '0'; -- payload is valid signal m_axis_data_tready : std_logic := '1'; -- slave is ready signal m_axis_data_tdata : std_logic_vector(31 downto 0) := (others => '0'); -- data payload signal m_axis_data_tlast : std_logic := '0'; -- indicates end of packet -- Event signals signal event_frame_started : std_logic := '0'; signal event_tlast_unexpected : std_logic := '0'; signal event_tlast_missing : std_logic := '0'; signal event_status_channel_halt : std_logic := '0'; signal event_data_in_channel_halt : std_logic := '0'; signal event_data_out_channel_halt : std_logic := '0'; ----------------------------------------------------------------------- -- Aliases for AXI channel TDATA and TUSER fields -- These are a convenience for viewing data in a simulator waveform viewer. -- If using ModelSim or Questa, add "-voptargs=+acc=n" to the vsim command -- to prevent the simulator optimizing away these signals. ----------------------------------------------------------------------- -- Config slave channel alias signals signal s_axis_config_tdata_fwd_inv : std_logic := '0'; -- forward or inverse signal s_axis_config_tdata_scale_sch : std_logic_vector(9 downto 0) := (others => '0'); -- scaling schedule -- Data slave channel alias signals signal s_axis_data_tdata_re : std_logic_vector(15 downto 0) := (others => '0'); -- real data signal s_axis_data_tdata_im : std_logic_vector(15 downto 0) := (others => '0'); -- imaginary data -- Data master channel alias signals signal m_axis_data_tdata_re : std_logic_vector(15 downto 0) := (others => '0'); -- real data signal m_axis_data_tdata_im : std_logic_vector(15 downto 0) := (others => '0'); -- imaginary data ----------------------------------------------------------------------- -- Constants, types and functions to create input data ----------------------------------------------------------------------- constant IP_WIDTH : integer := 16; constant MAX_SAMPLES : integer := 2**10; -- maximum number of samples in a frame type T_IP_SAMPLE is record re : std_logic_vector(IP_WIDTH-1 downto 0); im : std_logic_vector(IP_WIDTH-1 downto 0); end record; type T_IP_TABLE is array (0 to MAX_SAMPLES-1) of T_IP_SAMPLE; -- Zeroed input data table, for reset and initialization constant IP_TABLE_CLEAR : T_IP_TABLE := (others => (re => (others => '0'), im => (others => '0'))); -- Function to generate input data table -- Data is a complex sinusoid exp(-jwt) with a frequency 2.6 times the frame size -- added to another with a lower magnitude and a higher frequency function create_ip_table return T_IP_TABLE is variable result : T_IP_TABLE; variable theta : real; variable theta2 : real; variable re_real : real; variable im_real : real; variable re_int : integer; variable im_int : integer; begin for i in 0 to MAX_SAMPLES-1 loop theta := real(i) / real(MAX_SAMPLES) * 2.6 * 2.0 * MATH_PI; re_real := cos(-theta); im_real := sin(-theta); theta2 := real(i) / real(MAX_SAMPLES) * 23.2 * 2.0 * MATH_PI; re_real := re_real + (cos(-theta2) / 4.0); im_real := im_real + (sin(-theta2) / 4.0); re_int := integer(round(re_real * real(2**(IP_WIDTH-2)))); im_int := integer(round(im_real * real(2**(IP_WIDTH-2)))); result(i).re := std_logic_vector(to_signed(re_int, IP_WIDTH)); result(i).im := std_logic_vector(to_signed(im_int, IP_WIDTH)); end loop; return result; end function create_ip_table; -- Call the function to create the input data constant IP_DATA : T_IP_TABLE := create_ip_table; ----------------------------------------------------------------------- -- Testbench signals ----------------------------------------------------------------------- -- Communication between processes regarding DUT configuration type T_DO_CONFIG is (NONE, IMMEDIATE, AFTER_START, DONE); shared variable do_config : T_DO_CONFIG := NONE; -- instruction for driving config slave channel type T_CFG_FWD_INV is (FWD, INV); signal cfg_fwd_inv : T_CFG_FWD_INV := FWD; type T_CFG_SCALE_SCH is (ZERO, DEFAULT); signal cfg_scale_sch : T_CFG_SCALE_SCH := DEFAULT; -- Recording output data, for reuse as input data signal op_sample : integer := 0; -- output sample number signal op_sample_first : std_logic := '1'; -- indicates first output sample of a frame signal ip_frame : integer := 0; -- input / configuration frame number signal op_data : T_IP_TABLE := IP_TABLE_CLEAR; -- recorded output data signal op_frame : integer := 0; -- output frame number (incremented at end of frame output) begin ----------------------------------------------------------------------- -- Instantiate the DUT ----------------------------------------------------------------------- dut : entity work.FFT_IP8 port map ( aclk => aclk, s_axis_config_tvalid => s_axis_config_tvalid, s_axis_config_tready => s_axis_config_tready, s_axis_config_tdata => s_axis_config_tdata, s_axis_data_tvalid => s_axis_data_tvalid, s_axis_data_tready => s_axis_data_tready, s_axis_data_tdata => s_axis_data_tdata, s_axis_data_tlast => s_axis_data_tlast, m_axis_data_tvalid => m_axis_data_tvalid, m_axis_data_tready => m_axis_data_tready, m_axis_data_tdata => m_axis_data_tdata, m_axis_data_tlast => m_axis_data_tlast, event_frame_started => event_frame_started, event_tlast_unexpected => event_tlast_unexpected, event_tlast_missing => event_tlast_missing, event_status_channel_halt => event_status_channel_halt, event_data_in_channel_halt => event_data_in_channel_halt, event_data_out_channel_halt => event_data_out_channel_halt ); ----------------------------------------------------------------------- -- Generate clock ----------------------------------------------------------------------- clock_gen : process begin aclk <= '0'; wait for CLOCK_PERIOD; loop aclk <= '0'; wait for CLOCK_PERIOD/2; aclk <= '1'; wait for CLOCK_PERIOD/2; end loop; end process clock_gen; ----------------------------------------------------------------------- -- Generate data slave channel inputs ----------------------------------------------------------------------- data_stimuli : process -- Variables for random number generation variable seed1, seed2 : positive; variable rand : real; -- Procedure to drive an input sample with specific data -- data is the data value to drive on the tdata signal -- last is the bit value to drive on the tlast signal -- valid_mode defines how to drive TVALID: 0 = TVALID always high, 1 = TVALID low occasionally procedure drive_sample ( data : std_logic_vector(31 downto 0); last : std_logic; valid_mode : integer := 0 ) is begin s_axis_data_tdata <= data; s_axis_data_tlast <= last; if valid_mode = 1 then uniform(seed1, seed2, rand); -- generate random number if rand < 0.25 then s_axis_data_tvalid <= '0'; uniform(seed1, seed2, rand); -- generate another random number wait for CLOCK_PERIOD * integer(round(rand * 4.0)); -- hold TVALID low for up to 4 cycles s_axis_data_tvalid <= '1'; -- now assert TVALID else s_axis_data_tvalid <= '1'; end if; else s_axis_data_tvalid <= '1'; end if; loop wait until rising_edge(aclk); exit when s_axis_data_tready = '1'; end loop; wait for T_HOLD; s_axis_data_tvalid <= '0'; end procedure drive_sample; -- Procedure to drive an input frame with a table of data -- data is the data table containing input data -- valid_mode defines how to drive TVALID: 0 = TVALID always high, 1 = TVALID low occasionally procedure drive_frame ( data : T_IP_TABLE; valid_mode : integer := 0 ) is variable samples : integer; variable index : integer; variable sample_data : std_logic_vector(31 downto 0); variable sample_last : std_logic; begin samples := data'length; index := 0; while index < data'length loop -- Look up sample data in data table, construct TDATA value sample_data(15 downto 0) := data(index).re; -- real data sample_data(31 downto 16) := data(index).im; -- imaginary data -- Construct TLAST's value index := index + 1; if index >= data'length then sample_last := '1'; else sample_last := '0'; end if; -- Drive the sample drive_sample(sample_data, sample_last, valid_mode); end loop; end procedure drive_frame; variable op_data_saved : T_IP_TABLE; -- to save a copy of recorded output data begin -- Drive inputs T_HOLD time after rising edge of clock wait until rising_edge(aclk); wait for T_HOLD; -- Drive a frame of input data ip_frame <= 1; drive_frame(IP_DATA); -- Allow the result to emerge wait until m_axis_data_tlast = '1'; wait until rising_edge(aclk); wait for T_HOLD; -- Take a copy of the result, to use later as input op_data_saved := op_data; -- Now perform an inverse transform on the result to get back to the original input -- Set up the configuration (config_stimuli process handles the config slave channel) ip_frame <= 2; cfg_fwd_inv <= INV; do_config := IMMEDIATE; while do_config /= DONE loop wait until rising_edge(aclk); end loop; wait for T_HOLD; -- Configuration is done. Set up another configuration to return to forward transforms, -- and make the configuration occur as soon as the next frame has begun ip_frame <= 3; cfg_fwd_inv <= FWD; do_config := AFTER_START; -- Now drive the input data, using the output data of the last frame drive_frame(op_data); wait until m_axis_data_tlast = '1'; wait until rising_edge(aclk); wait for T_HOLD; -- The frame is complete, and the configuration to forward transforms has already been done, -- so drive the input data, using the output data of the last frame, -- which is the same as the original input (excepting scaling and finite precision effects). -- This time, deassert the data slave channel TVALID occasionally to illustrate AXI handshaking effects: -- as the core is configured to use Non Real Time throttle scheme, it will pause when TVALID is low. drive_frame(op_data, 1); -- During the output of this frame, deassert the data master channel TREADY occasionally: -- as the core is configured to use Non Real Time throttle scheme, it will pause when TREADY is low. wait until m_axis_data_tvalid = '1'; wait until rising_edge(aclk); while m_axis_data_tlast /= '1' loop wait for T_HOLD; uniform(seed1, seed2, rand); -- generate random number if rand < 0.25 then m_axis_data_tready <= '0'; else m_axis_data_tready <= '1'; end if; wait until rising_edge(aclk); end loop; wait for T_HOLD; m_axis_data_tready <= '1'; wait for CLOCK_PERIOD; -- Now run 4 back-to-back transforms, as quickly as possible. -- First queue up 2 configurations: these will be applied successively over the next 2 transforms. -- 1st configuration ip_frame <= 4; cfg_fwd_inv <= FWD; -- forward transform cfg_scale_sch <= DEFAULT; -- default scaling schedule do_config := IMMEDIATE; while do_config /= DONE loop wait until rising_edge(aclk); end loop; wait for T_HOLD; -- 2nd configuration: same as 1st, except: ip_frame <= 5; cfg_fwd_inv <= INV; -- inverse transform cfg_scale_sch <= ZERO; -- no scaling do_config := IMMEDIATE; while do_config /= DONE loop wait until rising_edge(aclk); end loop; wait for T_HOLD; -- Drive the 1st data frame drive_frame(IP_DATA); -- Request a 3rd configuration, to be sent after 2nd data frame starts ip_frame <= 6; cfg_fwd_inv <= FWD; -- forward transform cfg_scale_sch <= ZERO; -- no scaling do_config := AFTER_START; -- Drive the 2nd data frame drive_frame(op_data_saved); -- Request a 4th configuration, to be sent after 3rd data frame starts: same as 3rd, except: ip_frame <= 7; cfg_fwd_inv <= INV; -- inverse transform cfg_scale_sch <= DEFAULT; -- default scaling schedule do_config := AFTER_START; -- Drive the 3rd data frame drive_frame(IP_DATA); -- Drive the 4th data frame drive_frame(op_data_saved); -- Wait until all the output data from all frames has been produced wait until op_frame = 7; wait for CLOCK_PERIOD * 10; -- End of test report "Not a real failure. Simulation finished successfully." severity failure; wait; end process data_stimuli; ----------------------------------------------------------------------- -- Generate config slave channel inputs ----------------------------------------------------------------------- config_stimuli : process variable scale_sch : std_logic_vector(9 downto 0); begin -- Drive a configuration when requested by data_stimuli process wait until rising_edge(aclk); while do_config = NONE or do_config = DONE loop wait until rising_edge(aclk); end loop; -- If the configuration is requested to occur after the next frame starts, wait for that event if do_config = AFTER_START then wait until event_frame_started = '1'; wait until rising_edge(aclk); end if; -- Drive inputs T_HOLD time after rising edge of clock wait for T_HOLD; -- Construct the config slave channel TDATA signal s_axis_config_tdata <= (others => '0'); -- clear unused bits -- Format the transform direction if cfg_fwd_inv = FWD then s_axis_config_tdata(0) <= '1'; -- forward elsif cfg_fwd_inv = INV then s_axis_config_tdata(0) <= '0'; -- inverse end if; -- Format the scaling schedule if cfg_scale_sch = ZERO then -- no scaling scale_sch := (others => '0'); elsif cfg_scale_sch = DEFAULT then -- default scaling, for largest magnitude output with no overflow guaranteed scale_sch(1 downto 0) := "11"; -- largest scaling at first stage for s in 2 to 5 loop scale_sch(s*2-1 downto s*2-2) := "10"; -- less scaling at later stages end loop; end if; s_axis_config_tdata(10 downto 1) <= scale_sch; -- Drive the transaction on the config slave channel s_axis_config_tvalid <= '1'; loop wait until rising_edge(aclk); exit when s_axis_config_tready = '1'; end loop; wait for T_HOLD; s_axis_config_tvalid <= '0'; -- Tell the data_stimuli process that the configuration has been done do_config := DONE; end process config_stimuli; ----------------------------------------------------------------------- -- Record outputs, to use later as inputs for another frame ----------------------------------------------------------------------- record_outputs : process (aclk) -- Function to digit-reverse an integer, to convert output to input ordering function digit_reverse_int ( fwd, width : integer ) return integer is variable rev : integer; variable fwd_slv : std_logic_vector(width-1 downto 0); variable rev_slv : std_logic_vector(width-1 downto 0); begin fwd_slv := std_logic_vector(to_unsigned(fwd, width)); for i in 0 to width/2-1 loop -- reverse in digit groups (2 bits at a time) rev_slv(i*2+1 downto i*2) := fwd_slv(width-i*2-1 downto width-i*2-2); end loop; if width mod 2 = 1 then -- width is odd: LSB moves to MSB rev_slv(width-1) := fwd_slv(0); end if; rev := to_integer(unsigned(rev_slv)); return rev; end function digit_reverse_int; variable index : integer := 0; begin if rising_edge(aclk) then if m_axis_data_tvalid = '1' and m_axis_data_tready = '1' then -- Record output data such that it can be used as input data index := op_sample; -- Digit-reverse output sample number, to get actual sample index as outputs are in digit-reversed order index := digit_reverse_int(index, 10); op_data(index).re <= m_axis_data_tdata(15 downto 0); op_data(index).im <= m_axis_data_tdata(31 downto 16); -- Increment output sample counter if m_axis_data_tlast = '1' then -- end of output frame: reset sample counter and increment frame counter op_sample <= 0; op_frame <= op_frame + 1; op_sample_first <= '1'; -- for next output frame else op_sample_first <= '0'; op_sample <= op_sample + 1; end if; end if; end if; end process record_outputs; ----------------------------------------------------------------------- -- Check outputs ----------------------------------------------------------------------- check_outputs : process variable check_ok : boolean := true; -- Previous values of data master channel signals variable m_data_tvalid_prev : std_logic := '0'; variable m_data_tready_prev : std_logic := '0'; variable m_data_tdata_prev : std_logic_vector(31 downto 0) := (others => '0'); begin -- Check outputs T_STROBE time after rising edge of clock wait until rising_edge(aclk); wait for T_STROBE; -- Do not check the output payload values, as this requires a numerical model -- which would make this demonstration testbench unwieldy. -- Instead, check the protocol of the data master channel: -- check that the payload is valid (not X) when TVALID is high -- and check that the payload does not change while TVALID is high until TREADY goes high if m_axis_data_tvalid = '1' then if is_x(m_axis_data_tdata) then report "ERROR: m_axis_data_tdata is invalid when m_axis_data_tvalid is high" severity error; check_ok := false; end if; if m_data_tvalid_prev = '1' and m_data_tready_prev = '0' then -- payload must be the same as last cycle if m_axis_data_tdata /= m_data_tdata_prev then report "ERROR: m_axis_data_tdata changed while m_axis_data_tvalid was high and m_axis_data_tready was low" severity error; check_ok := false; end if; end if; end if; assert check_ok report "ERROR: terminating test with failures." severity failure; -- Record payload values for checking next clock cycle if check_ok then m_data_tvalid_prev := m_axis_data_tvalid; m_data_tready_prev := m_axis_data_tready; m_data_tdata_prev := m_axis_data_tdata; end if; end process check_outputs; ----------------------------------------------------------------------- -- Assign TDATA / TUSER fields to aliases, for easy simulator waveform viewing ----------------------------------------------------------------------- -- Config slave channel alias signals s_axis_config_tdata_fwd_inv <= s_axis_config_tdata(0); s_axis_config_tdata_scale_sch <= s_axis_config_tdata(10 downto 1); -- Data slave channel alias signals s_axis_data_tdata_re <= s_axis_data_tdata(15 downto 0); s_axis_data_tdata_im <= s_axis_data_tdata(31 downto 16); -- Data master channel alias signals m_axis_data_tdata_re <= m_axis_data_tdata(15 downto 0); m_axis_data_tdata_im <= m_axis_data_tdata(31 downto 16); end tb; A tutaj spakowany kod projektu dla "ISE 14.7 Webpack": FFT_IPCore8_01.zip Zachęcam do własnych prób z tym projektem. Ciąg dalszy nastąpi ... Pozdrawiam
  11. Cześć, ponieważ w ciągu tego roku byłem tak obciążony projektami elektronicznymi w pracy (i ich programowaniem), że nie mam w czasie wolnym ochoty na ambitne projekty. Ponieważ mam teraz kilka dni wolnych postanowiłem zająć się jakimś małym projektem, który da mi trochę rozrywki. Jak już kiedyś pisałem w latach 80-tych lubiłem grać w proste gry na "automatach", dlatego wybór padł znów na prostą grę "Snake"(wąż). Wybór padł na implementację tej gry na płytkę FPGA "Pepino" (także Spartan6) - niestety różni się sporo od Mimas V.2. Tutaj link do żródeł gry na Githgub'ie: https://github.com/Saanlima/Pepino/tree/master/Projects/Snake Przystępując do implementacji gry myślałem, że powinno udać się ją uruchomić na zestawie FPGA "Elbert V.2" (Spartan3) - niestety okazało się, iż liczba wymaganych zasobów jest ponad dwukrotnie większa od tej którą posiada ta płytka. Gra wymaga 1957 przerzutników (FF) i 1703 LUTs - patrz zrzut erranu z ISE: Tak, że mój wybór padł na zestaw FPGA "Mimas V.2", który posiadam od kilku lat. Tutaj linki do tego zestawu w Botland'zie oraz do producenta firmy "Numato Labs": https://botland.com.pl/moduly-i-zestawy-fpga/8603-mimas-v2-spartan-6-plytka-rozwojowa-fpga-5904422311964.html https://numato.com/product/mimas-v2-spartan-6-fpga-development-board-with-ddr-sdram/ https://numato.com/docs/mimas-v2-spartan-6-fpga-development-board-with-ddr-sdram/ Podstawową różnicą pomiędzy oryginalnym projektem na zestaw FPGA "Pepino" jest fakt, iż "Pepino" ma główny zegar 50 Mhz a Mimas v.2 100 Mhz. Poza tym ruch węża był realizowany poprzez klawiaturę PS2 podłączoną do plytki FPGA - ponieważ nie posiadam takiej klawiatury postanowiłem kontrolować ruch węża za pomocą przycisków na płytce Mimas. Z tego powodu można było usunąć dwa sygnały wejściowe: KB_clk i KB_data z głównego modułu aplikacji(Snake.v), i sam moduł obsługi klawiatury: kbInput(VGA_clk, KB_clk, KB_data, direction) Trzeba też było zmodyfikować plik "user constraints" (ucf) tak aby pasował do zestawu FPGA 'Mimas v.2". Oto lista wykonanych w projekcie zmian: 1) Zmodyfikowanie portów głównego modułu Snake.v: było: module Snake(start, master_clk, KB_clk, KB_data, VGA_R, VGA_G, VGA_B, VGA_hSync, VGA_vSync); input start; input master_clk, KB_clk, KB_data; output reg [2:0] VGA_R; output reg [2:0] VGA_G; output reg [1:0] VGA_B; output VGA_hSync, VGA_vSync; po modyfikacji: module Snake(start, clk100, VGA_R, VGA_G, VGA_B, VGA_hSync, VGA_vSync, btnUp, btnDown, btnLeft, btnRight); input start; input clk100; input btnUp, btnDown, btnLeft, btnRight; output reg [2:0] VGA_R; output reg [2:0] VGA_G; output reg [1:0] VGA_B; output VGA_hSync, VGA_vSync; 2) Usunięto moduł zegarowy w głównym module: // Use a CDM to generate VGA clock (25 MHz) from input clock (50 MHz) DCM #(.CLKIN_DIVIDE_BY_2("TRUE"), .CLKIN_PERIOD(20.000)) dcm(.CLKIN(master_clk), .CLKFB(VGA_clk), .RST(1'b0), .PSEN(1'b0), .PSINCDEC(1'b0), .PSCLK(1'b0), .DSSEN(1'b0), .CLK0(VGA_clk)); a dodano DCM (IPCore PLL_Clock), który teraz z zegara Mimas'a 100 MHz generuje dwa zegary (50 Mhz i 25 Mhz do taktowania modułu VGA): // Instantiate the module PLL_Clock PLL1 (// Clock in ports .CLK_IN1(clk100), // IN 100Mhz // Clock out ports .CLK_OUT1(master_clk), // OUT 50Mhz .CLK_OUT2(VGA_clk)); // OUT 25MHz 3) Usunięte zostało wywołanie modułu: kbInput i kod samego modułu: VGA_gen gen1(VGA_clk, xCount, yCount, displayArea, VGA_hSync, VGA_vSync); randomGrid rand1(VGA_clk, rand_X, rand_Y); //kbInput kbIn(VGA_clk, KB_clk, KB_data, direction); updateClk UPDATE(VGA_clk, update); module kbInput(VGA_clk, KB_clk, KB_data, direction); input VGA_clk; input KB_clk; input KB_data; output reg [3:0] direction = 4'b0000; reg Q0, Q1; reg [10:0] shreg; reg [7:0] code; wire endbit; assign endbit = ~shreg[0]; assign shift = Q1 & ~Q0; always @ (posedge VGA_clk) begin Q0 <= KB_clk; Q1 <= Q0; shreg <= (endbit) ? 11'h7FF : shift ? {KB_data, shreg[10:1]} : shreg; if (endbit) code <= shreg[8:1]; if(code == 8'h1D) direction <= 4'b0001; else if(code == 8'h1C) direction <= 4'b0010; else if(code == 8'h1B) direction <= 4'b0100; else if(code == 8'h23) direction <= 4'b1000; end endmodule zamiast tego został dodany fragment kodu obsługujący przyciski z płytki Mimas w module głównym (linie od 79 do 87): end else if (~game_over) begin if (update) begin for(count = 1; count < 128; count = count + 1) begin if(size > count) begin snakeX[count] <= snakeX[count - 1]; snakeY[count] <= snakeY[count - 1]; end end //obsluga przycisow z Mimasa if(btnDown == 1'b0) direction <= 4'b0001; else if(btnLeft == 1'b0) direction <= 4'b0010; else if(btnUp == 1'b0) direction <= 4'b0100; else if(btnRight == 1'b0) direction <= 4'b1000; case(direction) 4'b0001: snakeY[0] <= (snakeY[0] - 1); 4'b0010: snakeX[0] <= (snakeX[0] - 1); 4'b0100: snakeY[0] <= (snakeY[0] + 1); 4'b1000: snakeX[0] <= (snakeX[0] + 1); endcase Poniżej rysunek z przyciskami z Mimas'a użytymi do kontrolowania ruchu węża i gry: Przycisk SW5 to start nowej gry a przyciski:SW!, SW2, SW3, SW4 kontrolują ruch węża. Poniżej zdjęcie z gry uruchomionej na zestawie Mimas v.2 i małym monitorku VGA: Bym zapomniał: był jeszcze błąd języka Verilog w tym fragmencie kodu: module randomGrid(VGA_clk, rand_X, rand_Y); input VGA_clk; output reg [6:0] rand_X; output reg [6:0] rand_Y; always @(posedge VGA_clk) begin //rand_X <= ((rand_X + 3) % 78) + 1; //rand_Y <= ((rand_Y + 5) % 58) + 1; rand_X <= ((rand_X + 3) % 64) + 1; rand_Y <= ((rand_Y + 5) % 32) + 1; end endmodule dwie linie zostały wykomentowane i dodane dwie poniżej (zmienione) - Verilog wymaga, aby drugi argument operatora modulo % był potęgą liczby dwa. Tutaj plik "Snake.ucf" (z user constraints) dla płytki Mimas v.2: NET "*" IOSTANDARD=LVCMOS33; CONFIG VCCAUX = "3.3" ; # Clock 100 MHz NET "clk100" LOC = V10 | IOSTANDARD = LVCMOS33 | PERIOD = 100MHz ; NET "start" LOC = K17 | PULLUP; #SW5 # VGA port NET "VGA_hSync" LOC = B12; NET "VGA_vSync" LOC = A12; NET "VGA_R<0>" LOC = A9; NET "VGA_R<1>" LOC = B9; NET "VGA_R<2>" LOC = C9; NET "VGA_G<0>" LOC = C10; NET "VGA_G<1>" LOC = A10; NET "VGA_G<2>" LOC = C11; NET "VGA_B<0>" LOC = B11; NET "VGA_B<1>" LOC = A11; # Przyciski Mimas V.2 NET "btnLeft" LOC = M18 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; #SW1 NET "btnRight" LOC = L18 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; #SW2 NET "btnDown" LOC = L17 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; #SW3 NET "btnUp" LOC = M16 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; #SW4 Zamieszczam spakowany projekt gry dla "ISE 14.7 Webpack": SnakeGame.zip Plik binarny do wgrania konfiguracji FPGA to "snake.bin" (jeśli ktoś nie jest zainteresowany kodem źródłowym). Pozdrawiam
  12. W internecie można znaleźć mnóstwo projektów zegarów opartych na NIXIE, które sterowane są najczęściej za pomocą Arduino. Ja jednak postanowiłem nie powielać jednego z wielu już dostępnych projektów, a zrobić coś samodzielnie i tak narodził się pomysł zegara NIXIE sterowanego z FPGA. Mój projekt zegara NIXIE składa się z czterech elementów - przetwornicy wysokiego napięcia, płytki z układem FPGA, płytki sterującej oraz samych lampach. Całość zasilana jest napięciem 12V, które przez przetwornicę HV podawane jest na anodę lamp NIXIE. Płytka FPGA zasilana jest napięciem o wartości 5V, które wytwarzane jest przez stabilizator 7805. Jednym z założeń projektu było również ni używanie modułów czasu rzeczywistego RTC. Wyświetlany przez zegar czas jest generowany tylko i wyłącznie przez układ FPGA. FPGA wytwarza również sygnały sterujące poszczególnymi katodami lamp, które podawane są na bazy tranzystorów z płytki sterującej. Przetwornica wysokiego napięcia to typowa konstrukcja z dalekiego wschodu. Posiada ona wejście, na które podajemy napięcie z zakresu 12-24V i wyjście, na którym za pomocą potencjometru możemy ustawić napięcie rzędu 85-235V. Wydajność prądowa urządzenia zależna jest od napięcia wyjściowego, ale przy zasilaniu lamp NIXIE (około 170V) wynosi 55mA. Mózgiem zegara jest płytka Mimas wyposażona w układ XC6LX9 taktowany sygnałem o częstotliwości 100MHz. Tej płytki użyłem głównie ze względu na niewielki rozmiary i dość sporą ilość wyjść. Płytka zasilana jest z portu external power, w takim przypadku należy pamiętać o zmianie położenia zworki wyboru zasilania z 1-2 na 2-3. Płytki sterujące, których zadaniem jest zapalanie poszczególnych cyfr w lampach NIXIE, oparte zostały na wysokonapięciowych tranzystorach BF458, BF459 oraz KT604BM, które są ich radzieckimi odpowiednikami. Same lampy NIXIE to konstrukcje pochodzące jeszcze z byłego związku radzieckiego oznaczone symbolem IN-12B. Mogą one wyświetlać cyfry od 0 do 9, napięcie pracy wynosi około 170V natomiast pobierany prąd około 3mA. Powyżej umieściłem schemat budowy zegara.. Napięcie wejściowe 12V podłączone jest do stabilizatora 7805, który zasila płytkę FPGA. Przetwornica wysokiego napięcia również zasilana jest napięciem 12V, natomiast wyjściowe 170V trafia przez rezystor 47kΩ na anodę każdej lampy. Wysokonapięciowy tranzystor sterowany jest przez FPGA i zależnie od napięcia na bazie załącza pojedynczą cyfrę lampy. Jak już wcześniej wspomniałem, jednym z założeń projektu było generowanie czasu tylko przy pomocy FPGA. Oczywiście rodzi to pewien problem, który może pojawić się po jakimś czasie, a jest nim rozkalibrowanie zegara. Mówiąc w wprost wyświetlana godzina, może różnić się względem czasu rzeczywistego, ale będąc szczerym, sam jestem ciekaw jak szybko to nastąpi. Przygotowany przeze mnie kod opiera się na sygnale zegarowym o wartości 100MHz, który generowany jest bezpośrednio na płytce z układem FPGA. Czas wyznaczany jest w najłatwiejszy możliwy sposób - przez kilka liczników. Poza tym sygnały odpowiadające minutą i godziną dekodowane są na odpowiednie sygnały załączające odpowiednie tranzystory. Ustawienie zegara możliwe jest bezpośrednio z poziomu kodu. Modyfikując wartość początkową sygnałów hour oraz minute można ustawić na zegarze dowolną godzinę. Cały kod VHDL wraz z zawartością pliku .ufc umieszczam poniżej. library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; use IEEE.std_logic_unsigned.all; entity clock is port( clk : in std_logic; --wejscie zegara reset : in std_logic; --wejscie reset lamp1 : out std_logic_vector(9 downto 0); --wyjscie dla lampy 1 lamp2 : out std_logic_vector(9 downto 0); --wyjscie dla lampy 2 lamp3 : out std_logic_vector(9 downto 0); --wyjscie dla lampy 3 lamp4 : out std_logic_vector(9 downto 0) --wyjscie dla lampy 4 ); end clock; architecture Behavioral of clock is signal one_second_counter: STD_LOGIC_VECTOR (27 downto 0); --licznik wyznaczajacy 1 sekunde signal one_second_enable: std_logic; --sygnal 1 sekundowy signal minute : std_logic_vector(5 downto 0):="000000"; --licznik minut signal second : std_logic_vector(5 downto 0); --licznik sekund signal hour : std_logic_vector(4 downto 0):="10010"; --licznik godzin begin process(clk, reset) --licznik 1 sekundowy begin if(reset='0') then one_second_counter <= (others => '0'); elsif(rising_edge(clk)) then if(one_second_counter>=x"5F5E0FF") then one_second_counter <= (others => '0'); else one_second_counter <= one_second_counter + "0000001"; end if; end if; end process; one_second_enable <= '1' when one_second_counter=x"5F5E0FF" else '0'; process(clk, reset, one_second_enable) --licznik sekund begin if(reset='0') then second<="000000"; elsif(rising_edge(clk)) then if(one_second_enable='1') then second<=second + "000001"; elsif(second = x"3C") then second<="000000"; end if; end if; end process; process(clk, reset, second) --licznik minut begin if(reset='0') then minute<="000000"; elsif(rising_edge(clk)) then if(second = x"3C") then minute<=minute+"000001"; elsif(minute = x"3C") then minute<="000000"; end if; end if; end process; process(clk, reset, minute) --licznik godzin begin if(reset='0') then hour<="00000"; elsif(rising_edge(clk)) then if(minute = x"3C") then hour<=hour+"00001"; elsif(hour = x"18") then hour<="00000"; end if; end if; end process; with minute select --dekoder lampa 4 lamp4 <= "0000000001" when "000000", "0000000010" when "000001", "0000000100" when "000010", "0000001000" when "000011", "0000010000" when "000100", "0000100000" when "000101", "0001000000" when "000110", "0010000000" when "000111", "0100000000" when "001000", "1000000000" when "001001", "0000000001" when "001010", "0000000010" when "001011", "0000000100" when "001100", "0000001000" when "001101", "0000010000" when "001110", "0000100000" when "001111", "0001000000" when "010000", "0010000000" when "010001", "0100000000" when "010010", "1000000000" when "010011", "0000000001" when "010100", "0000000010" when "010101", "0000000100" when "010110", "0000001000" when "010111", "0000010000" when "011000", "0000100000" when "011001", "0001000000" when "011010", "0010000000" when "011011", "0100000000" when "011100", "1000000000" when "011101", "0000000001" when "011110", "0000000010" when "011111", "0000000100" when "100000", "0000001000" when "100001", "0000010000" when "100010", "0000100000" when "100011", "0001000000" when "100100", "0010000000" when "100101", "0100000000" when "100110", "1000000000" when "100111", "0000000001" when "101000", "0000000010" when "101001", "0000000100" when "101010", "0000001000" when "101011", "0000010000" when "101100", "0000100000" when "101101", "0001000000" when "101110", "0010000000" when "101111", "0100000000" when "110000", "1000000000" when "110001", "0000000001" when "110010", "0000000010" when "110011", "0000000100" when "110100", "0000001000" when "110101", "0000010000" when "110110", "0000100000" when "110111", "0001000000" when "111000", "0010000000" when "111001", "0100000000" when "111010", "1000000000" when "111011", "0000000000" when others; with minute select --dekoder lampa 3 lamp3 <= "0000000001" when "000000", "0000000001" when "000001", "0000000001" when "000010", "0000000001" when "000011", "0000000001" when "000100", "0000000001" when "000101", "0000000001" when "000110", "0000000001" when "000111", "0000000001" when "001000", "0000000001" when "001001", "0000000010" when "001010", "0000000010" when "001011", "0000000010" when "001100", "0000000010" when "001101", "0000000010" when "001110", "0000000010" when "001111", "0000000010" when "010000", "0000000010" when "010001", "0000000010" when "010010", "0000000010" when "010011", "0000000100" when "010100", "0000000100" when "010101", "0000000100" when "010110", "0000000100" when "010111", "0000000100" when "011000", "0000000100" when "011001", "0000000100" when "011010", "0000000100" when "011011", "0000000100" when "011100", "0000000100" when "011101", "0000001000" when "011110", "0000001000" when "011111", "0000001000" when "100000", "0000001000" when "100001", "0000001000" when "100010", "0000001000" when "100011", "0000001000" when "100100", "0000001000" when "100101", "0000001000" when "100110", "0000001000" when "100111", "0000010000" when "101000", "0000010000" when "101001", "0000010000" when "101010", "0000010000" when "101011", "0000010000" when "101100", "0000010000" when "101101", "0000010000" when "101110", "0000010000" when "101111", "0000010000" when "110000", "0000010000" when "110001", "0000100000" when "110010", "0000100000" when "110011", "0000100000" when "110100", "0000100000" when "110101", "0000100000" when "110110", "0000100000" when "110111", "0000100000" when "111000", "0000100000" when "111001", "0000100000" when "111010", "0000100000" when "111011", "0000000000" when others; with hour select --dekoder lampa 2 lamp2 <= "0000000001" when "00000", "0000000010" when "00001", "0000000100" when "00010", "0000001000" when "00011", "0000010000" when "00100", "0000100000" when "00101", "0001000000" when "00110", "0010000000" when "00111", "0100000000" when "01000", "1000000000" when "01001", "0000000001" when "01010", "0000000010" when "01011", "0000000100" when "01100", "0000001000" when "01101", "0000010000" when "01110", "0000100000" when "01111", "0001000000" when "10000", "0010000000" when "10001", "0100000000" when "10010", "1000000000" when "10011", "0000000001" when "10100", "0000000010" when "10101", "0000000100" when "10110", "0000001000" when "10111", "0000000000" when others; with hour select --dekoder lampa 1 lamp1 <= "0000000001" when "00000", "0000000001" when "00001", "0000000001" when "00010", "0000000001" when "00011", "0000000001" when "00100", "0000000001" when "00101", "0000000001" when "00110", "0000000001" when "00111", "0000000001" when "01000", "0000000001" when "01001", "0000000010" when "01010", "0000000010" when "01011", "0000000010" when "01100", "0000000010" when "01101", "0000000010" when "01110", "0000000010" when "01111", "0000000010" when "10000", "0000000010" when "10001", "0000000010" when "10010", "0000000010" when "10011", "0000000100" when "10100", "0000000100" when "10101", "0000000100" when "10110", "0000000100" when "10111", "0000000000" when others; end Behavioral; Plik .ufc NET "clk" LOC = P126; NET "reset" LOC = P124 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST | PULLUP; NET "lamp1[0]" LOC = P10 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp1[1]" LOC = P35 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp1[2]" LOC = P33 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp1[3]" LOC = P30 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp1[4]" LOC = P27 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp1[5]" LOC = P24 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp1[6]" LOC = P22 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp1[7]" LOC = P17 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp1[8]" LOC = P15 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp1[9]" LOC = P12 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp2[0]" LOC = P23 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp2[1]" LOC = P8 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp2[2]" LOC = P6 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp2[3]" LOC = P2 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp2[4]" LOC = P142 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp2[5]" LOC = P140 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp2[6]" LOC = P34 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp2[7]" LOC = P32 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp2[8]" LOC = P29 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp2[9]" LOC = P26 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp3[0]" LOC = P139 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp3[1]" LOC = P21 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp3[2]" LOC = P16 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp3[3]" LOC = P14 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp3[4]" LOC = P11 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp3[5]" LOC = P9 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp3[6]" LOC = P7 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp3[7]" LOC = P5 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp3[8]" LOC = P1 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp3[9]" LOC = P141 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp4[0]" LOC = P43 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp4[1]" LOC = P44 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp4[2]" LOC = P46 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp4[3]" LOC = P48 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp4[4]" LOC = P51 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp4[5]" LOC = P56 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp4[6]" LOC = P55 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp4[7]" LOC = P50 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp4[8]" LOC = P47 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; NET "lamp4[9]" LOC = P45 | IOSTANDARD = LVCMOS33 | DRIVE = 8 | SLEW = FAST; Fizycznie zegar umieszczony został w obudowie o wymiarach 14x14x6cm wykonanej ze spienionego PCV pomalowanego na czarno. Otwory, w których umieściłem lampy, wycięte zostały ręcznie. Płytka FPGA wraz z przetwornicą umieszczona została na samym dole, powyżej znajdują się płytki z tranzystorami. Poniżej umieszczam kilka zdjęć z procesu budowy oraz samego działania zegara. Jeśli ktoś chciałby poczytać więcej o moich projektach to zapraszam https://rafal-bartoszak.blogspot.com/ 😉
  13. Cześć, jakiś czas temu opisywałem na forum moją próbę podłączenia prostej kamery CMOS o rozdzielczości VGA do zestawu FPGA Artix-7 (OV7670). Tutaj jest link: Chociaż kamera miała rozdzielczość tylko VGA (640x480 16-bit kolor) to do utworzenia frame-buffor'a (bufor jednej ramki obrazu) potrzebny był relatywnie "duży" układ FPGA (w tym przypadku chip FPGA: XC7A100T-2FGG677i). Było to spowodowane faktem iż projekt tworzył bufor obrazu w wewnętrznej pamięci "Block RAM" zawartej w chipie FPGA. Niestety zestawy FPGA z takimi "dużymi" chipami FPGA są dużo droższe. Dużo zestawów FPGA (tanich) ma obecnie "na pokładzie" jakiś układ scalony z zewnętrzną pamięcią RAM, jednak przeważnie jest to pamięć DDR RAM (dynamiczna pamięć RAM), która ma dużo wyższe czasy dostępu i wymaga cykli odświeżania. Sterowniki takich pamięci są mocno skomplikowane, dlatego pomyślałem aby utworzyć frame-buffer (bufor obrazu) w statycznej pamięci SRAM, ponieważ sterownik do takiej pamięci jest dużo prostszy (płytka z zewnętrznym scalakiem podłączona do zestawu FPGA). Docelowo chciałbym użyć pamięci SRAM o organizacji 2Mega x 16-bit i czasie dostępu 10 ns o symbolu IS61WV204816BLL-10TLI -patrz link: https://www.digikey.pl/products/en/integrated-circuits-ics/memory/774?k=&pkeyword=&sv=0&pv142=188516&pv142=188519&pv2042=74127&pv2042=87441&pv2042=103394&pv2042=143502&pv2042=159094&pv2192=u10ns&pv2192=u12ns&pv2192=u15ns&pv2192=u20ns&pv2192=u25ns&pv1291=165794&pv1291=165833&pv1291=165834&pv1291=226104&pv1291=240848&pv1291=242592&pv1291=68821&pv1291=69617&sf=1&FV=961|390622%2C961|407300%2C-8|774&quantity=&ColumnSort=0&page=1&stock=1&datasheet=1&pageSize=25 Taka pamięć pozwala utworzyć bufor obrazu o rozdzielczości 1600x1200 i 16-nasto bitowym kolorze (z przeplotem). Umożliwia to podłączenie takiego sensora CMOS (OV2640) - patrz link: https://www.banggood.com/XD-95-OV2640-Camera-Module-200W-Pixel-STM32F4-Driver-Support-JPEG-Output-p-1403106.html?rmmds=myorder&cur_warehouse=CN Jednak taka duża kostka pamięci z przesyłką kosztuje około 100 PLN, dlatego zdecydowałem, że pierwszą próbę podłączenia zewnętrznej pamięci SRAM do zestawu FPGA przeprowadzę z dużo mniejszą kostką pamięci SRAM o organizacji 256Kx16-bit. Mo wybór padł na układ scalony SRAM o symbolu: CY7C1041CV33-10ZSXI ponieważ mam dwie takie kostki "w szufladzie" (kupione kilka lat temu).Tutaj jest link do data-shit'a pamięci CY7C1041CV33: https://datasheet.octopart.com/CY7C1041DV33-10ZSXI-Cypress-Semiconductor-datasheet-17703221.pdf Co prawda te kostki na dzień dzisiejszy są już "obsolete", ale ciągle są w sprzedaży ich nowsze wersje o symbolu:727-CY7C1041G10ZSXIT (takie same wyprowadzenia i bardzo zbliżone parametry) - kosztują aktualnie około 25 PLN za jeden scalak. Tutaj link do tego produktu: https://pl.mouser.com/ProductDetail/Cypress-Semiconductor/CY7C1041G-10ZSXIT?qs=%2Fha2pyFaduh0yemDg4h8iPRcd6uZg6jBuQMJjKFwlztWmTNJRjtfFg%3D%3D , a tututaj data-sheet: https://pl.mouser.com/datasheet/2/100/cypress_semiconductor_cypr-s-a0005492749-1-1734674.pdf Bufor obrazu na pamięci SRAM CY7C1041CV33-10ZSXI pozwalał by na wyświetlanie obrazu o rozdzielczości 640x480 16-bit kolor (z przeplotem). Postanowiłem więc narysować schemat ideowy takiej płytki zewnętrznej PCB z buforem obrazu. Oto ten schemat: Schematic_CY7C1041_SRAM-Cpy_2020-10-17_17-58-53.pdf W ciągu kilku najbliższych dni zamierzam zaprojektować płytkę drukowaną i dokonać jej podłączenia do zestawu FPGA. Znalazłem nawet kod sterownika pamięci SRAM CY7C1041CV33-10ZSXI napisany w języku VHDL udostepniony przez firmę "Cypress" (producenta pamięci), ale zawiera on nie-syntetyzowalne konstrukcje (bardziej wygląda na kod do symulacji takiego sterownika). Kod ten na pewno będzie pomocny w napisaniu sterownika tej pamięci. Tutaj link do tego kodu: https://www.cypress.com/documentation/models/cy7c1041dv33-vhdl Przykładowe sterowniki pamięci SRAM są dobrze opisane w darmowej książce pt. "FPGA Prototyping by VHDL Examples: Xilinx Spartan™‐3 Version" napisanej przez doktora Pong P. Chu. Tutaj link do tej książki: https://misp.mui.ac.ir/sites/misp.mui.ac.ir/files/ebooksclub.org__FPGA_Prototyping_by_VHDL_Examples__Xilinx_Spartan_3_Version.pdf Jak będę miał jakieś rezultaty, będę pisał w tym wątku. Zapomniałbym pod tym linkiem jest całkiem fajny artykuł dot. pamięci SRAM w połączeniu z FPGA: https://www.hackster.io/salvador-canas/a-practical-introduction-to-sram-memories-using-an-fpga-i-3f3992 Pozdrawiam
  14. Cześć, jakiś czas temu na Aliexpress.com pojawiły się płyty z Układem SoC "ZYNQ 7000" z rozmontowanych koparek Bitcoin'ow (Ebit E9+ BTC miner). Tutaj link do takiej płyty na Aliexpress.com: https://pl.aliexpress.com/item/1005001308030569.html?spm=a2g0o.cart.pcrcomd.5.78bf3c00Dec6Rh&gps-id=pcShopcartBuyagain&scm=1007.13440.197696.0&scm_id=1007.13440.197696.0&scm-url=1007.13440.197696.0&pvid=7efab647-1d43-4a23-ad3e-42413c948605&_t=gps-id:pcShopcartBuyagain,scm-url:1007.13440.197696.0,pvid:7efab647-1d43-4a23-ad3e-42413c948605,tpp_buckets:668%230%23131923%2342_668%23808%233772%23206_668%23888%233325%2318_668%234328%2319931%23466_668%232846%238113%23645_668%232717%237559%2396__668%233374%2315176%23826 Ta płyta kosztuje mniej niż 70 PLN (przesyłka za darmo). Biorąc pod uwagę że np. "Digilent ZYBO Z7-10" kosztuje ponad 1000 PLN (w kamami): https://kamami.pl/zestawy-uruchomieniowe/567065-digilent-zybo-z7-10-z-voucherem-zynq-sdsoc-471-014.html to zakup takiej płyty jest bardzo opłacalny. Na Githubie jest dostępny schemat i obraz Linuxa (dla karty SD): https://github.com/xjtuecho/EBAZ4205 https://github.com/xjtuecho/EBAZ4205/blob/master/HW/EBAZ4205.pdf Nie jest to na pewno zestaw FPGA dla początkujących, ale zaawansowani użytkownicy dużo na nim zaoszczędzą. Ja dokonałem zakupu tego zestawu FPGA i mój zestaw jest sprawny (na tyle na ile mogłem go przetestować). Pozdrawiam
  15. Cześć, ponieważ I2C jest jednym z najprostszych szeregowych protokołów komunikacyjnych (i jest bardzo często stosowany) postanowiłem sprawdzić jak wygląda przykładowa implementacja na zestawie FPGA. Zacząłem od prostszej rzeczy, czyli implementacji "I2C slave" (poniekąd też slave jest mi bardziej przydatny do moich eksperymentów). Ponieważ projekt jest stosunkowo prosty i nie zajmuje dużo zasobów postanowiłem go przetestować na zestawie FPGA ElbertV.2 (ten używany w kursie FPGA Forbot'a). Masterem I2C będzie zwykłe "Arduino UNO" - z poziomu Arduino bardzo prosto można oprogramować komunikację po I2C z użyciem biblioteki Wire. Punktem wyjścia do implementacji "I2C Slave" jest darmowy projekt zamieszczony na stronie opencores.org. Tutaj link do projektu: https://opencores.org/projects/i2cslave Projekt ma stosunkowo prostą budowę i jest właściwie tylko szkieletem do budowy na jego podstawie bardziej skomplikowanych układów (będzie to wyjaśnione dalej). Jak przeważnie się to zdarza trzeba było dodać nowy zegar (DCMs 1 ze Spartan3A) 48 MHz (pętla PLL), ponieważ oryginalny projekt pracował z takim zegarem. Po wykonaniu potrzebnych przeróbek projekt zajmuje niecałe 30% zasobów układu Spartan3 z Elberta - patrz screen: Jak można zobaczyć na podglądzie RTL - top entity projektu ma bardzo prosty interfejs - patrz screen: Tutaj kod w Verilog'u głównego modułu projektu (już po dodaniu zegara PLL 48 MHz) `include "i2cSlave_define.v" module i2cSlaveTop ( clk, rst, sda, scl, myReg0 ); input clk; input rst; inout sda; input scl; output [7:0] myReg0; wire clk48Mhz, clkBufg; // Instantiate the module PLL clock PLL_clock PLL_48Mhz ( .CLKIN_IN(clk), .RST_IN(~rst), .CLKFX_OUT(clk48Mhz), .CLKIN_IBUFG_OUT(clkBufg) ); i2cSlave u_i2cSlave( .clk(clk48Mhz), .rst(~rst), .sda(sda), .scl(scl), .myReg0(myReg0), .myReg1(), .myReg2(), .myReg3(), .myReg4(8'h12), .myReg5(8'h34), .myReg6(8'h56), .myReg7(8'h78) ); endmodule Jak widać "i2cSlaveTop" ma trzy wejścia: clk - główny zegar projektu (w Elbercie 12 Mhz) rst - reset asynchroniczny - stanem High więc dla naszej płytki FPGA trzeba go było zanegować scl - zegar magistrali I2c , jedno wejście/wyjście (inout) sda - dane magistrali I2C i jedno wyjście (wektor 8 bitów) myReg0 - na tym wyjściu równoległym będą się pojawiać dane wysłane przez mastera I2C (Arduino UNO), dlatego podłączymy je do diod LED w zestawie Elbert, aby móc te dane łatwo obserwować. Tutaj treść pliku ucf (user constraints) dla płytki FPGA Elbert: NET "clk" LOC = P129 | IOSTANDARD = LVCMOS33 | PERIOD = 12MHz; NET "rst" PULLUP; NET "rst" LOC = P76; NET "sda" LOC = P31 | IOSTANDARD = LVCMOS33 | SLEW = FAST | DRIVE = 12; NET "scl" LOC = P32 | IOSTANDARD = LVCMOS33 | SLEW = FAST | DRIVE = 12; # myReg0 - 8 bit data NET "myReg0[7]" LOC = P46 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "myReg0[6]" LOC = P47 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "myReg0[5]" LOC = P48 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "myReg0[4]" LOC = P49 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "myReg0[3]" LOC = P50 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "myReg0[2]" LOC = P51 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "myReg0[1]" LOC = P54 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "myReg0[0]" LOC = P55 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; Jak widać zdefiniowany jest domyślny pin zegara (12 MHz), reset - dolny z pięciu switchy, diody LED (myReg0 - dane przesłane po I2C do płytki Elbert), scl i sda 2 piny magistarli I2C (pierwsze dwa piny złącza P1 na płytce Elbert - patrz rysunek: Jeśli chodzi o piny magistrali I2C (scl, sda) od strony Arduino UNO to zaznaczyłem je fioletową obwódką na rysunku pinout'u dla UNO: Oczywiście pomiędzy zestawem FPGA (poziom logiki 3,3V) a Arduini UNO (poziom logiki 5V) trzeba wstawić na tych dwóch liniach (scl, sda) konwertery poziomów logicznych.Ja zrobiłem to za pomocą takiego konwertera poziomów (bo tylko taki miałem pod ręką) https://botland.com.pl/pl/konwertery-napiec/2523-konwerter-poziomow-logicznych-dwukierunkowy-4-kanalowy-pololu.html Tutaj zdjęcie całego układu (konwerter poziomów na płytce stykowej): Skoro połączenia mamy omówione wróćmy może do kodu projektu (Verilog). Przejdżmy do pliku "i2cSlave_define.v" plik ten jest dołączany (dyrektywa include) za pomocą linii: `include "i2cSlave_define.v" do wszystkich plików żródłowych projektu (Verilog) podobnie jak w języku C. W pliku tym mamy zdefiniowane bardzo ważne parametry modulów - patrz źródła: // ----------------------- i2cSlave_define.v -------------------- // stream states `define STREAM_IDLE 2'b00 `define STREAM_READ 2'b01 `define STREAM_WRITE_ADDR 2'b10 `define STREAM_WRITE_DATA 2'b11 // start stop detection states `define NULL_DET 2'b00 `define START_DET 2'b01 `define STOP_DET 2'b10 // i2c ack and nak `define I2C_NAK 1'b1 `define I2C_ACK 1'b0 // ---------------------------------------------------------------- // ------------- modify constants below this line ----------------- // ---------------------------------------------------------------- // i2c device address `define I2C_ADDRESS 7'h3c // System clock frequency in MHz // If you are using a clock frequency below 24MHz, then the macro // for SDA_DEL_LEN will result in compile errors for i2cSlave.v // you will need to hand tweak the SDA_DEL_LEN constant definition `define CLK_FREQ 48 // Debounce SCL and SDA over this many clock ticks // The rise time of SCL and SDA can be up to 1000nS (in standard mode) // so it is essential to debounce the inputs. // The spec requires 0.05V of hysteresis, but in practise // simply debouncing the inputs is sufficient // I2C spec requires suppresion of spikes of // maximum duration 50nS, so this debounce time should be greater than 50nS // Also increases data hold time and decreases data setup time // during an I2C read operation // 10 ticks = 208nS @ 48MHz `define DEB_I2C_LEN (10*`CLK_FREQ)/48 // Delay SCL for use as internal sampling clock // Using delayed version of SCL to ensure that // SDA is stable when it is sampled. // Not entirely citical, as according to I2C spec // SDA should have a minimum of 100nS of set up time // with respect to SCL rising edge. But with the very slow edge // speeds used in I2C it is better to err on the side of caution. // This delay also has the effect of adding extra hold time to the data // with respect to SCL falling edge. I2C spec requires 0nS of data hold time. // 10 ticks = 208nS @ 48MHz `define SCL_DEL_LEN (10*`CLK_FREQ)/48 // Delay SDA for use in start/stop detection // Use delayed SDA during start/stop detection to avoid // incorrect detection at SCL falling edge. // From I2C spec start/stop setup is 600nS with respect to SCL rising edge // and start/stop hold is 600nS wrt SCL falling edge. // So it is relatively easy to discriminate start/stop, // but data setup time is a minimum of 100nS with respect to SCL rising edge // and 0nS hold wrt to SCL falling edge. // So the tricky part is providing robust start/stop detection // in the presence of regular data transitions. // This delay time should be less than 100nS // 4 ticks = 83nS @ 48MHz `define SDA_DEL_LEN (4*`CLK_FREQ)/48 Dla nas najważniejsze są dwa parametry: `define I2C_ADDRESS 7'h3c Pierwszy to adres naszego slave'a w magistrali I2C (jest dostepnych 128 różnych adresów) - wynosi on 3C zapisany heksadecymalnie. Drugi to częstotliwość zegara - 48 MHz: `define CLK_FREQ 48 Teraz drugi plik "registerInterface": `include "i2cSlave_define.v" module registerInterface ( clk, addr, dataIn, writeEn, dataOut, myReg0, myReg1, myReg2, myReg3, myReg4, myReg5, myReg6, myReg7 ); input clk; input [7:0] addr; input [7:0] dataIn; input writeEn; output [7:0] dataOut; output [7:0] myReg0; output [7:0] myReg1; output [7:0] myReg2; output [7:0] myReg3; input [7:0] myReg4; input [7:0] myReg5; input [7:0] myReg6; input [7:0] myReg7; reg [7:0] dataOut; reg [7:0] myReg0; reg [7:0] myReg1; reg [7:0] myReg2; reg [7:0] myReg3; // --- I2C Read always @(posedge clk) begin case (addr) 8'h00: dataOut <= myReg0; 8'h01: dataOut <= myReg1; 8'h02: dataOut <= myReg2; 8'h03: dataOut <= myReg3; 8'h04: dataOut <= myReg4; 8'h05: dataOut <= myReg5; 8'h06: dataOut <= myReg6; 8'h07: dataOut <= myReg7; default: dataOut <= 8'h00; endcase end // --- I2C Write always @(posedge clk) begin if (writeEn == 1'b1) begin case (addr) 8'h00: myReg0 <= dataIn; 8'h01: myReg1 <= dataIn; 8'h02: myReg2 <= dataIn; 8'h03: myReg3 <= dataIn; endcase end end endmodule Plik ten jest o tyle ważny, że widać w nim iż w projekcie jest używanych 8 rejestrów 8-mio bitowych (cztery wejściwe i cztery wyjściowe). Ale tylko jeden rejestr jest w głównym module (implementacja jest trochę niepełna - o czym autor pisze, że jest to punkt wyjścia do własnych implementacji). Dla nas jest jedynie istotne, że dla adresu 8'h00 (czyli 0 dziesiętnie rejestr myReg0 jest obsługiwany poprawnie), będzie to istotne przy pisaniu mastera I2C na Arduino UNO. Tutaj zamieszczam działający projekt " I2C slave" (dla Elbert'a) - Xilinx ISE 14.7: I2CSlave01.zip Jeśli ktoś nie chce uruchamiać syntezy może od razu załadować za pomocą programu "Elbert V2 Configurator" plik "i2cslavetop.bin" do zestawu FPGA. Po wgraniu do zestawu FPGA Elbert pliku konfiguracji i wykonaniu połączeń z Arduino UNO możemy przejść do programu dla Arduino. Najpierw warto programem "I2C scanner" sprawdzić, czy Arduino z załadowanym programem skanera wykryje nam slave'a i2C pod adresem 0x3C. Tutaj kod progrmu "I2C scanner" - program należy skompilować za pomocą "Arduino IDE" i wgrać do płytki Arduino UNO. Kod programu: #include <Wire.h> void setup() { Wire.begin(); Serial.begin(9600); while (!Serial); // Leonardo: wait for serial monitor Serial.println("\nI2C Scanner"); } void loop() { byte error, address; int nDevices; Serial.println("Scanning..."); nDevices = 0; for(address = 1; address < 127; address++ ) { // The i2c_scanner uses the return value of // the Write.endTransmisstion to see if // a device did acknowledge to the address. Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("I2C device found at address 0x"); if (address<16) Serial.print("0"); Serial.print(address,HEX); Serial.println(" !"); nDevices++; } else if (error==4) { Serial.print("Unknown error at address 0x"); if (address<16) Serial.print("0"); Serial.println(address,HEX); } } if (nDevices == 0) Serial.println("No I2C devices found\n"); else Serial.println("done\n"); delay(5000); // wait 5 seconds for next scan } Tak wygląda prawidłowe wykrycie slave'a (FPGA) pod adresem 0x3C na magistrali I2C przez Arduino: Jeśli to zadziała, to możemy wgrać taki krótki program mastera I2C na Arduino UNO: #include <Wire.h> void setup() { Wire.begin(); // join i2c bus (address optional for master) } byte x = 0; void loop() { Wire.beginTransmission(0x3C); // transmit to device #3C Wire.write(0); //wysylamy adres rejestru 0 (ten odczytany w kodzie Verilog) Wire.write(x); // wysylamy jeden bajt - wartosc x Wire.endTransmission(); // stop transmitting x++; delay(500); } Jeśli wszystko poszło OK, możemy obserwować na diodach zestawu Elbert zmienijącą się wartość x (inkrementowaną co jeden obieg pętli głównej programu) przesyłaną z mastera (Arduino) do slave'a (FPGA). Patrz filmik (sorry nie mogę przesłać na forbot pliku mp4 z filmem). Pozdrawiam
  16. Cześć, po pierwszej próbie podłączenia prostego sensora kamery VGA (OV7670) do zestawu FPGA postanowiłem spróbować czegoś trochę bardziej skomplikowanego - wykrywania krawędzi obrazu z kamery. Jeśli kogoś interesują podstawowe informacje dot. teorii działania jednego z najczęściej używanych filtrów do detekcji krawędzi w obrazie "Filtru Sobl'a", to tutaj podaję linki: https://en.wikipedia.org/wiki/Sobel_operator https://medium.com/datadriveninvestor/understanding-edge-detection-sobel-operator-2aada303b900 https://homepages.inf.ed.ac.uk/rbf/HIPR2/sobel.htm Do implementacji wybrałem projekt z archiwum firmy Digilentic.com oryginalnie zaimplementowany dla zestawu FPGA "Nexys Video" - oto link do projektu: https://projects.digilentinc.com/cc-ad/cmos-sensor-camera-system-9f74f8 Ja dostosowałem projekt do posiadanej przeze mnie płytki z FPGA Artix 7 firmy QMTECH ponieważ posiada ona wystarczająco dużą ilość pamięci BRAM do implementacji pełnego frame-bufora dla rozdzielczości VGA - oto link do tej płytki: https://www.aliexpress.com/item/4000170042795.html?spm=a2g0o.productlist.0.0.1bbc3a488cWayC&algo_pvid=b8e2d6de-11a7-4045-be1d-dc82c5229b85&algo_expid=b8e2d6de-11a7-4045-be1d-dc82c5229b85-3&btsid=39edaf33-09cc-4522-882b-0168a91a733d&ws_ab_test=searchweb0_0,searchweb201602_4,searchweb201603_55 Aby dostosować projekt do mojej płytki trzeba było dodać jeden IPcore "Clock Wizard" bo płytka Nexys Video miała zegar 100MHz a QMTECH 50 MHz. Poza tym sygnały Reset miały zanegowany poziom w stosunku do Nexys. Zmiena dwóch linii w pliku "top.v (70 i 71). Oryginalnie było: debounce_inputs #(.NR(1))dbi(CLK,~RESETn,RESETn_clean); debounce_inputs #(.NR(1))dbi_reset_test(CLK,~cam_RESETn,cam_RESETn_clean); Jest po zmianie: debounce_inputs #(.NR(1))dbi(CLK,RESETn,RESETn_clean); debounce_inputs #(.NR(1))dbi_reset_test(CLK,cam_RESETn,cam_RESETn_clean); Oczywiście cały plki "User constraints" (xdc) trzeba było dostosować do mojej płytki. Fazy syntezy i implementacji nawet kończyły się z niedużą ilością ostrzeżeń. Patrz zrzut ekranu: Projekt zadziałał (jest wykrywanie krawędzi) ale obraz dość mocno "smuży", na razie nie udało mi się ustalić co jest przyczyną (faktem jest że próby robiłem na bardzo małym monitorku z wejściem HDMI). Całość tak wygląda na zdjęciach: Zamieszczam w załączniku cały projekt dla płytki QMTECH dla Vivado HLS 2020.1 (właśnie przy okazji tego projektu zrobiłem upgrade Vivado do najnowszej wersji OS WIn10), może komuś się przyda. Odchudziłem projekt o trzy niepotrzebne fazy implementacji i teraz się zmieścił .CMOSCameraArtix7My.zip Pozdrawiam
  17. Cześć, dzisiaj wykonałem próbę podłączenia prostej kamery VGA (sensor CMOS) do zestawu FPGA firmy QMTECH z Artix-7 (chip FPGA: XC7A100T-2FGG677i). Tutaj jest link do tego zestawu: https://pl.aliexpress.com/item/4000170042795.html?spm=a2g0o.productlist.0.0.1bbc3a488cWayC&algo_pvid=b8e2d6de-11a7-4045-be1d-dc82c5229b85&algo_expid=b8e2d6de-11a7-4045-be1d-dc82c5229b85-3&btsid=39edaf33-09cc-4522-882b-0168a91a733d&ws_ab_test=searchweb0_0,searchweb201602_4,searchweb201603_55i Zestaw ten kupiłem jakiś czas temu i postanowiłem go użyć w tym projekcie ze względu na dużą ilość zasobów - szczególnie dużą ilość BRAM 4,660Kb (potrzebna na frame-buffer). Pewnie łatwiej i sensowniej byłoby podłączać jakąś kamerę z interfejsem MIPI (i lepszym sensorem), ale nie posiadam żadnego zestawu FPGA, który by miał wbudowany taki interfejs 😉 Jest to moja pierwsza próba podłączenia jakiejkolwiek kamery do zestawu FPGA. Wybrałem tanią kamerę "OV7670" (sensor firmy Omnivision) - tutaj link do sklepu (chińskiego): https://www.banggood.com/Wareshare-OV7670-Camera-Module-CMOS-Acquisition-Board-Adjustable-Focus-300000-Pixel-p-1478355.html?rmmds=search&cur_warehouse=CN Tutaj link do datasheet do tego sensora: https://www.voti.nl/docs/OV7670.pdf Wybrałem projekt ze strony "FPGA4Student" ponieważ z kilku branych pod uwagę wydał mi się najprostszy:był https://www.fpga4student.com/2018/08/basys-3-fpga-ov7670-camera.html Projekt był zrobiny dla zestawu FPGA "Basys 3" z Artixem-7 (XC7A35T-1CPG236C) który posiada tylko 1800 Kb wewnętrznej pamieci BRAM - stąd frame-buffer obsługuje tylko max. rozdzielczość 320x240. Można by go też odpalić na zestawach "Artix-7 35T Arty" lub "Digilent Cmod At-35T" - tutaj linki do tych zestawów: https://kamami.pl/zestawy-uruchomieniowe/560134-artix-7-35t-arty-zestaw-ewaluacyjny-dla-fpga-artix-7.html https://kamami.pl/zestawy-uruchomieniowe/562401-digilent-cmod-a7-35t-modul-uruchomieniowy-z-fpga-artix-7-410-328-35.html Ale wracając do projektu - moja płytka FPGA firmy QMTECH (XC7A100T) nie posiada wyjściowego interfejsu VGA (ma HDMI które wypróbowałem i które działa dobrze), stąd wynikła potrzeba zbudowania go samemu. Wybrałem prosty interfejs (12) rezystorów wzorując się na układzie z zestawu "Basys 3". Tutaj schemat tego interfejsu: Zmontowałem do na płytce prototypowej (potrzebne jest gniazdo VGA 15 pinowe). Tak wygląda cały układ z kamerą, interfejsem VGA (na zielonej płytce) i zestawem FPGA QMTECH. A tu obraz na monitorze (tylko składowa niebieska): Jak widzicie próba nie zakończyła się pełnym sukcesem obraz jest o rozdzielczości 320x240 pikseli (i tak miało być) natomiast jest widoczna na ekranie tylko składowa niebieska.Podejrzewam, że błąd jest na zielonej płytce z interfejsem VGA - bo była ona dzisiaj "na szybko" polutowana a nie była najpierw przetestowana (może pomyliłem się przy gnieździe VGA). Zamieszczam tu pełen projekt dla Vivado 2018.3. Plik constraints dla mojej płytki QMETCH, ale na oryginalnej stronie projektu mo\zna pobrać pełen projekt dla "Basys 3". basys3_ov7670_v1.zip W najbliższym czasie zamierzam usunąć błędy z interfejsu VGA oraz zwiększyć pojemność frame-buffer'a do pełnej rozdzielczości VGA (na mojej płytce powinno wystarczyć pamieci BRAM w układzie FPGA). Zamieszczam projekt, bo może ktoś będzie także chciał spróbować podłączyć taki model kamery do układu FPGA. Zachęcam wszystkich do własnych prób z układami FPGA, bo w dziale "Układy programowalne" na Forbocie ostatnio bardzo mało się dzieje. Pozdrawiam 🙂
  18. Układy FPGA wydają się trochę bardziej tajemnicze niż mikrokontrolery. Można jednak dość szybko rozpocząć z nimi przygodę za pomocą narzędzi graficznych. W tutorialu przyjrzymy się układowi z rodziny Max10 znanej firmy Intel. Wykorzystamy go do sterowania silnikiem krokowym. Gotowy prototyp przedstawia Rys. 1. Rys. 1. Zmontowany prototyp. Składamy sprzęt W projekcie wykorzystamy płytkę Rysino z stosunkowo niewielkim układem z rodziny 10M04. Podłączymy do niej silnik krokowy 28BYJ-48 poprzez sterownik zbudowany z wykorzystaniem popularnego układu ULN2003. Nasz silnik składa się z dwóch uzwojeń. Każde z nich ma w połowie wyprowadzony odczep. Kolory z rysunku odpowiadają kolorom przewodów zamontowanych w użytym modelu. Obrót silnika następuje, gdy zmieniamy ich polaryzację poprzez zasilenie złącza +, albo złącza -. W tym projekcie skorzystamy z najprostszego typu sterowania zwanego pełno krokowym. Rys. 2. Stany na cewkach silnika. Rys. 3. Przebiegi czasowe na cewkach silnika. Na Rys. 2. widzimy, że aby obracać silnik musimy po kolei ustawiać cztery różne kombinację napięcia na cewkach. Natomiast Rys. 3. pokazuje jak będą wyglądały przebiegi przedstawione w tabelce. Jeżeli będziemy przechodzić „z góry na dół” nasz silnik będzie wykonywał obroty zgodnie z kierunkiem ruchu zegara. Aby zmienić kierunek obrotu wystarczy przełączać stany w przeciwnym kierunku. Pojawia się tu słowo klucz: stan, które pozwoli nam wybrać sposób implementacji sterownika. Zbudujemy maszynę stanów. Ale najpierw podłączmy silnik do układu FPGA. Schemat połączenia pokazuje Rys. 4. To jego wykonania potrzebujemy przewodów połączeniowych. Rys. 4. Sposób połączenia płytki Rysino z sterownikiem silnika. Wsad dla układu FPGA Aby zbudować wsad dla układu FGPA potrzebujemy zintegrowane środowisko dostarczone przez producenta. Najpierw musimy jednak się zarejestrować. Następnie ze strony producenta pobieramy środowisko Quartus Prime Lite Edition. Ja korzystałem z wersji 18.1. Pobieramy: Quartus Prime (includes Nios II EDS) (1.7GB) MAX 10 FPGA device support. (330MB) Jak widzimy niestety są to dość duże pliki... Po instalacji możemy przejść do tworzenia projektu. Przygotujemy go w całości w narzędziach graficznych. Składa się on z dwóch głównych części. Najpierw przygotujemy maszynę stanów, którą widzimy na Rys. 5. Rys. 5. Maszyna stanów. Następnie umieścimy gotową maszynę w projekcie, dołożymy licznik zmniejszający szybkość przełączania stanów oraz wejścia i wyjścia. Gotowy schemat pokazuje Rys. 6. Rys. 6. Diagram gotowego projektu. Jednak opisywanie „wyklikiwania” poszczególnych elementów było by dość zagmatwane, dlatego przygotowałem film, w którym zobaczymy wszystko krok po kroku. A na samym końcu znajdziemy demonstrację działania gotowego projektu. Na końcu pozostaje już tylko podłączenie płytki do komputera za pomocą przewodu miniUSB. Do złącza JTAG podłączamy programator USB Blaster i wgrywamy nasz projekt. Cały projekt jest także dostępny w repozytorium.
  19. Witam, po wgraniu przykładu( zgodnie z tym co jest na forum) płytka Elbert v2 przestała odpowiadać, jest jakiś sposób aby naprawić ? Po podłączeniu w windows wyświetla się "Nie rozpoznano urządzenia USB( żadanie deskryptora nie powiodło się)" , natomiast wskaźnik led( to inaczej te segmentowe ledy) się świeci. Załączam plik mojego projektu, aby udowodnić, wszystko jest tak samo jak w kursie Forbot( oprócz tego że mryganie_led zmieniłem na blink_led). EDIT( Jakiś czas później): Problem został naprawiony dzięki zrobieniu jednej prostej rzeczy. Jeśli ktoś też ma problemy to zalecam spojrzeć na: Unbricking Elbert . project_test.zip
  20. Witam, mam do sprzedania w pełni sprawny zestaw startowy Terasic DE0-Nano-SoC. Używałem go sporadycznie do nauki systemów wbudowanych i emulacji projektów w FPGA. Cena 300zł, sprzedaję razem z oryginalnym pudełkiem i dowodem zakupu ze sklepu Kamami. Odbiór osobisty w Gdańsku lub wysyłka kurierem.
  21. Jakiś czas temu na forum zaprezentowałem projekt procesora zrealizowanego na układzie FPGA. Był to układ zrealizowany na dwóch płytkach Elbert v2. Aby go uruchomić należało wykonać dość sporo pracy. Dzisiaj prezentuję pierwszą wersję układu DCE Q816, czyli bezpośredniej kontynuacji poprzedniego projektu. Można powiedzieć, że pierwsza wersja Q818 to tak naprawdę Q816 zrealizowany tylko na jednej płytce Elbert v2. Układ jest w pełni kompatybilny z poprzednim projektem oraz posiada identyczną listę rozkazów. Na ten moment nie będę skupiał się na budowie procesora, ponieważ jest ona praktycznie identyczna jak w układzie Q816. Jedyną różnicą jest to, że pamięć ROM oraz procesor zaimplementowane została na tym samym układzie FPGA. W związku z tym każdy, kto posiada płytkę Elbert v2 może pobrać projekt, który dostępny jest tutaj, a następnie uruchomić procesor Q818 w swoim domu. Po implementacji projektu na płytce FPGA powinniśmy zobaczyć następujący efekt świetlny. Należy również wspomnieć, że porty procesora skonfigurowane zostały w następujący sposób. przełączniki DIP Switch - wejście IN procesora (logika odwrotna) przycisk SW1 - RESET port P1 - wyjście OUT1 procesora diody LED - wyjście OUT2 procesora Programowanie procesora odbywa się poprzez zmianę zawartości pamięci ROM procesora. Aby tego dokonać musimy otworzyć plik ROM.vhd i następnie wpisać binarną zawartość kolejnych komórek pamięci. Najmłodsze 5-bitów to rozkaz dla procesora. Kolejne 8 to tzw. dana bezpośrednia, którą możemy przesłać do innych rejestrów. Poniżej umieszczam spis dostępnych rozkazów procesora oraz instrukcje dla ALU zapisywane w rejestrze C. Na ten moment jest to pierwsza wersja układu Q816 w przyszłości postaram się o uporządkowanie samego kodu, jak i dodanie kolejnych funkcji. O zmianach i kolejnych wersjach postaram się informować w komentarzach do tego posta.
  22. Posiadam kamerę Sony FCB-EV7520 (dokumentacja w załączniku), której wyjściem wideo jest sygnał w postaci YPbPr 4:2:2 wysyłany za pomocą interfejsu LVDS. To czego potrzebuje to napisanie programu na układ FPGA, który odbierze sygnał LVDS, przekształci go i wypuści sygnał zgodny ze standardem HDMI lub USB. Nie jest to coś nowego, bo tego typu urządzenia są w sprzedaży, lecz są drogie 350€ i mają już konkretny kształt, a w przyszłości chciałbym móc to zamknąć na swojej PCB. Jeżeli masz doświadczenie z FPGA, szczególnie z przetwarzaniem wyżej wymienionych sygnałów wideo to zapraszam do kontaktu. Jestem zainteresowany kompleksowym rozwiązaniem, ale również jestem otwarty na współpracę na zasadzie wskazania kierunku, zasugerowania możliwego rozwiązania i pomocy w wyborze odpowiedniego sprzętu. FCB-EV7520_Manual.pdf 22_FCB-EV7520A_S.pdf
  23. Cześć jestem tu nowy zakupiłem niedawno płytkę Elbert V2 do Waszego kursu, mam pytanie dotyczące instalacji środowiska ISE, która wersja będzie dla mnie najbardziej kompatybilna z zestawem ( mam windows 10 ) w instrukcji dotyczącej kursu jest podana wersja dla windowsa 7 powinienem ją zainstalować, czy pobrać wersję dla mojego windowsa 10? aktualizowaną 2018r. czy pobranie nowszej wersji znacząco utrudni mi "przejście" kursu? Pozdrawiam
  24. W ramach pewnego projektu muszę obsłużyć komunikację za pomocą interfejsu LVDS w układzie programowalnym FPGA (lub w zasadzie, w tej chwili, SoC). Interesuje mnie zarówno nadajnik, jak również odbiornik. Szukam materiałów na ten temat w internecie i w zasadzie jedyne co znalazłem to odpowiednie skonfigurowanie pinów i użycie prymitywów (np. IBUFDS - dla Xilinxa) w kodzie VHDL (tego języka używam). Czy jest jakaś (w miarę prosta) możliwość zastąpienia tych prymitywów własnym kodem? Nie mam pomysłu na napisanie takiego elementu, niewprowadzającego zbędnych opóźnień. Zależy mi na zapewnieniu możliwie jak największej „przenoszalności" kodu. Czy jest możliwość sprawdzenia, co tak naprawdę znajduje się w tych prymitywach (chciałbym zobaczyć, co „siedzi" w środku)? Czy może jest to po prostu fizyczny element, „zaszyty" wewnątrz FPGA? Czy jeżeli opiszę własny komponent, mający dwa wejścia (sygnały różnicowe zanegowany i nie) i jedno wyjście (które będzie po prostu podłączone do wejścia niezanegowanego lub będzie działać według załączonej tabeli zaczerpniętej z dokumentacji Xilinxa), to narzędzie do syntezy „domyśli się", że trzeba wstawić w to miejsce wejściowy bufor różnicowy? Czy nie będzie problemu z przeniesieniem projektu na inny układ (innego producenta)? Przepraszam za poziom pytań, ale pierwszy raz będę korzystał z transmisji różnicowej i nie wszystko jest dla mnie jasne. Ponadto, nie mam dużego doświadczenia z układami programowalnymi.
  25. Chciałbym krótko opisać projekt który obecnie realizuję a jest to procesor zrealizowany na układzie FPGA. DCE Q816 (nazwa własna bez większego znaczenia) to 8 bitowa jednostka o architekturze mojego własnego pomysłu. Procesor może działać przy maksymalnej częstotliwości taktowania 12MHz. Dodatkowym układem pełniącym funkcję pamięci ROM oraz koprocesora jest DCE Q817 również zrealizowany na układzie FPGA. Pierwszy jak i drugi układ powstały na płytce Elbert v2 a jest to płytka z kursu FPGA na Forbot. Kolejnymi elementami wchodzącymi w skład projektu jest wyświetlacz 7 segmentowy oraz LCD dzięki którym mamy możliwość komunikowania się ze światem zewnętrznym. Jak widać na zdjęciu płytki z układami FPGA przeszły kilka modyfikacji. Odlutowane zostały wyświetlacze LED, część przycisków oraz diody LED. Dzięki temu do dyspozycji miałem więcej pinów. Procesor może obsłużyć prosty program zapisany w pamięci ROM pełna lista rozkazów dostępna jest poniżej. Proces budowy oraz projektowania układu opisuję na moim blogu oraz Fanpagu. Jeżeli kogoś zainteresował temat zapraszam do zapoznania się z większą liczbą informacji. Dodatkowo poniżej umieszczam filmy pokazujące możliwości procesora DCE Q186.
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.