Wyślij zapytanie Dołącz do Sii

Wraz z rozwojem technologii obserwujemy również wzrost oczekiwań użytkowników wobec nowych urządzeń. Coraz częściej spotykamy rozwiązania wykorzystujące algorytmy sztucznej inteligencji, zaawansowane techniki cyfrowego przetwarzania sygnałów czy strumieniowe przetwarzanie danych. Wymagana w tych obszarach moc obliczeniowa bywa trudna do osiągnięcia z wykorzystaniem nawet wielordzeniowych procesorów. Alternatywą dla procesorów w implementacji tego typu algorytmów, ze względu na możliwość zrównoleglenia obliczeń i potokowego przetwarzania danych, są układy FPGA (ang. Field Programmable Gate Array).

Na początku artykułu zaprezentuję opisy poziomów abstrakcji od sprzętu w programowaniu oraz miejsce języków VHDL i Verilog w tym zestawieniu. Następnie omówię ideę i architekturę układów FPGA. W dalszej części przedstawię sposób opisu podstawowych konstrukcji w języku VHDL.

Artykuł jest przeznaczony dla każdego, kto jest zainteresowany nauką języka VHDL i wymaga jedynie podstawowej wiedzy na temat techniki cyfrowej oraz dowolnego języka programowania.

Poziomy abstrakcji od sprzętu w programowaniu

Większość osób pracujących w obszarze systemów wbudowanych wykorzystuje w swojej pracy jeden z kilku języków programowania. Istnieje wiele technologii różniących się między innymi poziomem abstrakcji od sprzętu. W systemach wbudowanych możemy je podzielić na kilka grup:

  • Języki programowania wysokiego poziomu oraz języki skryptowe:
    • Python,
    • Java,
    • C#.
  • Języki programowania aplikacji:
    • C,
    • C++.
  • Języki programowania sterowników i firmware:
    • C,
    • Assembler.

Języki programowania wysokiego poziomu charakteryzują się najmniejszą zależnością od hardware’u, na którym uruchamiana jest aplikacja. Posiadają własne interpretery lub wirtualne środowiska. Programista nie musi się przejmować zagadnieniami niskopoziomowymi (przydzielaniem pamięci czy konfiguracją sprzętu) i może skupić się na projektowaniu algorytmów. Ta grupa języków świetnie nadaje się do tworzenia interfejsów użytkownika lub wysokopoziomowego opisu skomplikowanych algorytmów niemających wymagań dotyczących pracy w ścisłym reżimie czasowym.

Języki programowania C i C++ wykorzystuje się między innymi do tworzenia aplikacji pracujących w systemie operacyjnym. Pozwalają one na większą kontrolę nad przydzielaniem pamięci niż
w przypadku języków programowania wysokiego poziomu, a także na wykorzystanie mechanizmów systemowych, takich jak komunikacja międzyprocesowa czy metody synchronizacji. W tych językach mogą być tworzone wirtualne środowiska i interpretery dla języków programowania wysokiego poziomu.

Na niższej warstwie tworzone są sterowniki i firmware. Sterowniki to programy z jednej strony operujące bezpośrednio na sprzęcie (czyli na pamięci i rejestrach układów peryferyjnych), a z drugiej strony posiadające API (ang. Application Programming Interface) dla aplikacji tworzonych na wyższych warstwach. Są swojego rodzaju interfejsem pomiędzy aplikacjami a sprzętem.

Na podobnym poziomie abstrakcji tworzony jest firmware, z tą jednak różnicą, że wykonywany jest bez użycia systemu operacyjnego. Firmware to na przykład oprogramowanie mikrokontrolerów. Charakterystyczna dla tego typu programów jest silna zależność od sprzętu. Na tym poziomie oprogramowanie najczęściej pisane jest w języku C. Możliwe jest również tworzenie aplikacji w języku assembler, jednak wymaga to szczegółowej znajomości architektury procesora oraz jego listy instrukcji.

Na niższej warstwie niż sterowniki i firmware jest już tylko hardware. Są to na przykład układy peryferyjne procesorów i mikrokontrolerów takie jak:

  • interfejsy szeregowe (UART, SPI, I2C),
  • liczniki,
  • kontrolery PWM (ang. Pulse width Modulation),
  • układy DMA (ang. Direct Memory Access).

Hardware’em jest również sam procesor składający się między innymi z:

  • dekodera instrukcji,
  • licznika programu,
  • licznika instrukcji,
  • rejestrów procesora,
  • jednostki arytmetyczno-logicznej (ALU).

To właśnie na tym poziomie abstrakcji – czyli w samej warstwie sprzętowej – tworzone są projekty w układach FPGA.

Czym są układy FPGA?

Skrót FPGA oznacza Field-Programmable Gate Array. Nazwa ta bardzo dobrze oddaje istotę tych układów. Są to macierze bramek (Gate Array) i przerzutników, które możemy łączyć w zdefiniowany przez nas sposób. Fraza „Field-Programmable” odnosi się do możliwości zmiany tych połączeń po przeprogramowaniu. Z wykorzystaniem języka VHDL lub Verilog opisujemy struktury złożone
z pojedynczych układów cyfrowych. Opis ten może być tworzony na jednym z trzech poziomów abstrakcji:

  • Poziom bramek logicznych – to najniższy poziom, w którym definiujemy układy cyfrowe poprzez połączenia pomiędzy poszczególnymi bramkami i przerzutnikami. Jest to dobry punkt startowy w nauce języka VHDL lub Verilog ponieważ pozwala zrozumieć jak opisywane struktury są realizowane w sprzęcie.
  • Poziom transferu między rejestrowego (ang. Register Transfer Level, RTL) – to najczęściej wybierany poziom abstrakcji w projektach FPGA. Pozwala skupić się na tworzeniu bardziej złożonych konstrukcji (np. liczników czy maszyn stanów). Ta metoda z jednej strony wymaga od projektanta rozumienia realizacji sprzętowej opisywanych struktur, co pozwala na optymalne wykorzystanie dostępnych zasobów sprzętowych, a z drugiej strony nie wymaga planowania każdego pojedynczego połączenia pomiędzy elementami układu.
  • Poziom algorytmiczny – nazywany też High-Level Synthesis, jest to poziom abstrakcji pozwalający na tworzenie algorytmów w językach programowania (takich jak C czy C++), które są tłumaczone na odpowiadające im struktury sprzętowe. Niestety, takie podejście nie pozwala na precyzyjne kontrolowanie zależności czasowych (co jest jedną z kluczowych zalet FPGA), a utworzone w ten sposób konstrukcje nie są optymalne pod względem zużycia zasobów sprzętowych.

Aby zrozumieć, w jaki sposób możliwa jest konwersja opisu tekstowego na hardware, konieczna jest znajomość architektury układów FPGA.

Architektura układów FPGA

Przykładową architekturę układów FPGA przedstawia Ryc. 1.

Możemy wyróżnić kilka podstawowych elementów:

  • Konfigurowalne bloki logiczne (ang. Configurable Logic Blocks, CLB) – są to grupy przerzutników, bramek logicznych i multiplekserów potrzebnych do realizacji podstawowych układów kombinacyjnych i sekwencyjnych.
  • Sieć połączeń – są to fizyczne ścieżki pomiędzy wszystkimi CLB, których połączenie jest konfigurowane za pomocą kluczy.
  • Klucze programowalne – są to elementy pozwalające na sterowanie połączeniami pomiędzy różnymi CLB w celu budowania większych struktur z pojedynczych bramek
    i przerzutników.
  • Sieć doprowadzenia zegarów – doprowadza do każdego CLB zegar w sposób zapewniający jak najmniejsze różnice w czasach propagacji.
  • Bloki wejść/wyjść – umożliwiają komunikację z urządzeniami zewnętrznymi.

Przykład budowy konfigurowalnych bloków logicznych przedstawia Ryc. 2. CLB składa się
z części kombinacyjnej, widocznej po lewej stronie, złożonej z bramek logicznych (LUT, ang. Look-Up Table). W środkowej części rysunku umieszczony jest przerzutnik posiadający, poza wejściem i wyjściem, połączenia do sieci zegarowej i sygnału resetu. Po prawej stronie grafiki jest multiplekser, który wybiera sygnał wyjściowy w zależności od tego czy wykorzystano tylko część kombinacyjną CLB czy również sekwencyjną.

Bloki te w układach różnych producentów mogą różnić się liczbą i konfiguracją przerzutników, bramek czy multiplekserów. Pozwala to na efektywniejsze wykorzystanie zasobów sprzętowych w procesach tłumaczenia opisu sprzętu na połączenia pomiędzy blokami (procesy syntezy i implementacji).

Etapy konwersji opisu tekstowego

Konwersja tekstowego opisu w języku VHDL lub Verilog na wynikowy plik wgrywany do matrycy składa się z dwóch głównych etapów. Dostarczane przez producentów matryc IDE (ang. Integrated Development Environment) zwykle pozwala na wywołanie obu etapów przy użyciu jednego przycisku.

Pierwszym z nich jest synteza, która polega na zamianie tekstu na netlistę zawierającą strukturę opartą na przerzutnikach i bramkach, jednak bez przydzielania ich w konkretne obszary układu scalonego. Ten etap można porównać do rysowania schematu ideowego przez projektanta obwodów elektronicznych.

Kolejnym krokiem jest implementacja. Ten etap polega na pobraniu netlisty i przydzieleniu przerzutników, multiplekserów i bramek do konkretnych CLB oraz utworzeniu połączeń między nimi. Można to porównać do rysowania PCB (ang. Printed Circuit Board) na podstawie schematu ideowego. Jest to proces iteracyjny, zwykle trwający najdłużej. Po ustaleniu wstępnego rozmieszczenia i połączeń obliczane są opóźnienia między sygnałami i zegarami.

W przypadku, gdy któreś połączenie wprowadza zbyt duże opóźnienie, zmieniane jest wstępnie przydzielone rozmieszczenie i proces powtarza się do uzyskania optymalnego wyniku. Następnie generowany jest plik, którym należy zaprogramować układ FPGA.

Język VHDL

VHDL (ang. Very High Speed Integrated Circuit Hardware Description Language) jest językiem opisu sprzętu wykorzystywanym do projektowania kombinacyjnych i sekwencyjnych układów cyfrowych w układach FPGA, a także cyfrowych układach scalonych (ASIC, ang. Application-Specific Integrated Circuit). Składnią przypomina nieco język programowania Pascal. Charakterystyczną cechą VHDL-a jest brak wrażliwości na wielkość liter. W dalszej części omówimy kolejne sekcje w przykładowym pliku projektu w VHDL.[3]

Na początku należy zadeklarować wykorzystywane w projekcie biblioteki. Podstawową biblioteką jest IEEE.STD_LOGIC_1164. Zawiera ona definicje podstawowych typów (np. std_logic i std_logic_vector, natural, integer) oraz interpretację wykonywanych na nich operacji.

library IEEE

Kolejna sekcja jest definicją portów opisywanego komponentu. W tej części opisujemy powiązanie wejść i wyjść komponentu. W przyszłości zostaną one połączone z portami samej matrycy FPGA lub innymi komponentami w projekcie. Można powiedzieć, że jest to swojego rodzaju API.

entity

Następna sekcja dotyczy architektury komponentu. Składa się ona z dwóch części oddzielonych słowem kluczowym begin. Przed jego wystąpieniem umieszczamy definicje sygnałów, niestandardowych typów oraz komponentów opisanych w oddzielnych plikach. Sygnały możemy wyobrażać sobie jako połączenia elektryczne pomiędzy bramkami i przerzutnikami. Możemy je zdefiniować jako pojedyncze linie (std_logic) lub wektory złożone z kilku lini (std_logic_vector). Dla załączanych komponentów konieczne jest zadeklarowanie portów.

architecture Behavioral of DFlipFlop is

Po słowie begin rozpoczyna się sekcja opisu sprzętu. Zawiera ona konstrukcje i połączenia układów cyfrowych oraz osadzanie (czyli powiązanie portów z sygnałami) zewnętrznych komponentów. Połączenia wykonywane poza procesem przypisują wejścia i wyjścia poszczególnych elementów do sygnałów wewnętrznych. Możliwe w tej sekcji jest również definiowanie układów kombinacyjnych złożonych z bramek logicznych.

begin

Wewnątrz procesu możemy definiować zarówno układy synchroniczne (wykorzystujące przerzutniki taktowane zegarem) jak i kombinacyjne (złożone z bramek logicznych). W tym przypadku przedstawiono strukturę przerzutnika typu D. Lista czułości umieszczona w nawiasach za słowem kluczowym process nie ma znaczenia dla realizacji sprzętowej, ale niesie ważne informacje dla symulatora. Informuje, kiedy przeprowadzić symulację procesu. Brak sygnału na liście czułości oznacza, że jego zmiana nie wpływa na wykonanie procesu i nie jest wymagana jego analiza. Pozwala to przyspieszyć symulacje unikając sytuacji, w których symulator sprawdza każdy proces w każdym kroku czasowym.

W dalszej części artykułu przyjrzymy się bardziej szczegółowo opisom podstawowych układów w sekcji architektury.

Podstawowe bloki i przykłady w kodzie

Znając strukturę plików projektu, możemy przejść do przedstawienia implementacji podstawowych bloków. Ich znajomość jest konieczna do tworzenia bardziej skomplikowanych struktur i na ich wyjaśnieniu skupia się niniejszy artykuł.

Bramki logiczne

Bramki logiczne to układy cyfrowe posiadające jedno wyjście, którego stan jest zależny od jednego lub większej liczby wejść. Poniższy fragment kodu w VHDL przedstawia konstrukcje używane do implementacji poszczególnych bramek logicznych.

architecture Behavioral of Gates is

W tabelach poniżej przedstawiono zależność wyjść poszczególnych bramek logicznych od stanów na ich wejściach. Wyjście bramki NOT jest negacją wejścia. W przypadku bramki AND na wyjściu pojawi się stan wysoki jedynie w sytuacji, gdy na obu wejściach również będzie stan wysoki. Wyjście bramki OR będzie logiczną jedynką, gdy przynajmniej jedno z jej wejść będzie jedynką. Podobnie działa bramka XOR (eXclusive OR) z wykluczeniem przypadku, gdy oba wejścia będą w stanie wysokim.

Tablica prawdy dla bramki NOT
Tab. 1 Tablica prawdy dla bramki NOT
Tablica prawdy dla bramki AND
Tab. 2 Tablica prawdy dla bramki AND
Tablica prawdy dla bramki OR
Tab. 3 Tablica prawdy dla bramki OR
Tablica prawdy dla bramki XOR
Tab. 4 Tablica prawdy dla bramki XOR

Na Ryc. 3 przedstawiono realizację sprzętową umieszczonego wcześniej opisu w VHDL. Podstawowe funkcje logiczne zrealizowano z użyciem słów kluczowych not, and, or i xor. Do wejść bramek przypisano wejścia projektowanego komponentu o nazwach Input1, Input2A, Input2B, Input3A, Input3B, Input4A, Input4B. Operator przypisania <= służy do połączenia wyjść bramek opisanych w omawianym fragmencie kodu po prawej stronie operatora do sygnału po jego lewej stronie.

Realizacja sprzętowa funkcji logicznych w FPGA
Ryc. 3 Realizacja sprzętowa funkcji logicznych w FPGA

Przerzutniki

Przerzutniki to układy posiadające dwa stabilne stany wyjściowe. Dzięki temu mogą przechowywać informacje i bywają nazywane elementami pamięciowymi. W układach FPGA najczęściej stosowany jest przerzutnik typu D. Jego działanie polega na przepisaniu stanu wejścia (D) na wyjście (Q) tylko w ściśle określonych momentach – na każdym narastającym zboczu zegara (CLK). Stan wyjścia zostaje podtrzymany na cały kolejny cykl sygnału taktującego. Dodatkowo przerzutnik taki ma wejście kasujące (CLR), które pozwala na wykonanie resetu. Przykład procesu w VHDL opisującego przerzutnik wygląda następująco:

process

Charakterystyczna dla przerzutnika struktura to umieszczona wewnątrz procesu instrukcja warunkowa. Jedna z gałęzi dotyczy resetu przerzutnika – definiuje, czy reset będzie wywoływany stanem niskim, czy wysokim oraz jaki stan zostanie ustawiony na wyjściu.

Druga gałąź zawiera w warunku funkcję rising_edge(clk). Jest to informacja dla syntezera, jaki sygnał ma zostać podłączony do wejścia zegarowego przerzutnika. W gałęzi tej zdefiniowano, jaki sygnał zostanie podłączony do wejścia D przerzutnika, a jaki do jego wyjścia Q. Podobnie jak w przypadku bramek, po lewej stronie operatora przypisania mamy wyjście przerzutnika, a po prawej stronie jego wejście.

Wynikową strukturę przedstawiono na kolejnym rysunku.

Realizacja sprzętowa przerzutnika w FPGA
Ryc. 4 Realizacja sprzętowa przerzutnika w FPGA

Multipleksery

Multipleksery to układy pozwalające na wybór jednego spośród kilku sygnałów wejściowych i połączenie go z wyjściem. To, który z sygnałów wejściowych zostanie wybrany, zależy od stanu wejścia sterującego (S). Wejście to jest wektorem złożonym z takiej ilości bitów, jaka jest wymagana do zaadresowania wszystkich wejść. Definicja multipleksera w VHDL może wyglądać następująco:

with select

Pozwala na realizację zależności opisanej w Tab. 5. Efekt utworzenia takiej struktury można zaobserwować na Ryc. 5.

Zależność komutacji sygnałów w multiplekserze od stanu na wejściu sterującym
Tab. 5 Zależność komutacji sygnałów w multiplekserze od stanu na wejściu sterującym
Realizacja sprzętowa multipleksera w FPGA
Ryc. 5 Realizacja sprzętowa multipleksera w FPGA

Łączenie podstawowych bloków

Przedstawione wcześniej podstawowe bloki możemy łączyć w większe układy w celu realizacji bardziej złożonych funkcji. Przykładowy projekt zawierający kilka podstawowych konstrukcji omówionych w tym artykule jest dostępny w załączniku pod nazwą GatesAndFlipFlop.vhd.

Na początku pliku umieszczono deklaracje bibliotek oraz definicję portów. Komponent posiada kilka wejść (Input1Input4B, SelectInput) oraz jedno wyjście (Output1). Porty clk i rstn to odpowiednio sygnał zegarowy i sygnał resetu dla części sekwencyjnej układu. Połączenia wewnętrzne wykonano za pomocą sygnałów, których deklaracje umieszczono przed słowem kluczowym begin w sekcji architektury.   

W dalszej części wejścia komponentu zostały przypisane do wejść czterech bramek logicznych, a porty wyjściowe bramek przypisano do sygnałów wewnętrznych. Następnie wyjścia z bramek trafiają na wejścia multipleksera. W zależności od stanu wejścia sterującego SelectInput na wyjściu multipleksera pojawi się wynik jednej z czterech funkcji logicznych. W ostatniej części architektury wyjście multipleksera jest „zatrzaskiwane” w takt zegara przez umieszczony za nim przerzutnik.

Uzyskaną strukturę przedstawiono na Ryc. 6.

Realizacja sprzętowa układu złożonego z bramek, multipleksera i przerzutnika
Ryc. 6 Realizacja sprzętowa układu złożonego z bramek, multipleksera i przerzutnika

Dla powyższego projektu przeprowadzono symulację, a jej wyniki przedstawiono na Ryc. 7.

Do przerzutnika doprowadzony jest sygnał zegarowy (clk) o stałej częstotliwości oraz sygnał resetu (rstn). Reset jest aktywowany poziomem niskim na linii rstn i utrzymywany przez pierwsze 100 nanosekund symulacji. Po zmianie stanu linii rstn na wysoki przerzutnik przenosi na wyjście stan swojego wejścia na każdym narastającym zboczu zegara clk.

Do każdej z bramek doprowadzono sygnały (Input1Input4B) zmieniające się w przedstawiony na wykresie sposób, powodując zmianę stanu podłączonych do multipleksera wyjść bramek (InternalSignal1InternalSignal4). Wejście sterujące multipleksera (SelectInput) jest zmieniane w taki sposób, aby po kolei łączyć wyjścia poszczególnych bramek z wejściem przerzutnika. Na symulacji momenty zmiany wartości wejścia sterującego oznaczono z wykorzystaniem pionowych kursorów.

W okresie pomiędzy 100 nanosekund a 140 nanosekund do wyjścia multipleksera doprowadzony jest sygnał z wyjścia bramki NOT. W kolejnych przedziałach czasowych – z wyjść odpowiednio bramek AND, OR i XOR. Przebiegi sygnałów na wejściach bramek dobrano tak, aby podać na nie każdą możliwą kombinację wejść.            

W ostatnim etapie wyjście multipleksera (MuxOutput) trafia na wejście D przerzutnika. W efekcie możemy zaobserwować przesunięcie pomiędzy wyjściem multipleksera (MuxOutput) a wyjściem Q przerzutnika (Output1) o dokładnie jeden cykl zegara.

Z wykorzystaniem takich symulacji możliwa jest analiza działania układu w poszczególnych punktach projektu. Ze względu na długi czas trwania procesów syntezy i implementacji, których wejściem jest projekt w VHDL, a wyjściem plik wgrywany do układu FPGA, symulacje są kluczowym narzędziem pozwalającym na weryfikację działania komponentu jeszcze przed rozpoczęciem wspomnianych procesów.

Wyniki symulacji układu złożonego z bramek, multipleksera i przerzutnika
Ryc. 7 Wyniki symulacji układu złożonego z bramek, multipleksera i przerzutnika

Zakończenie

W artykule, poza kwestiami związanymi bezpośrednio z FPGA, zostało przedstawione zestawienie różnych poziomów abstrakcji i wskazanie granicy pomiędzy sprzętem a oprogramowaniem. Istotne jest, aby nie traktować kodu w VHDL tak, jak programu wykonywanego sekwencyjnie, a raczej jako metodę na modelowanie i łączenie układów cyfrowych.

Mając na uwadze te aspekty, dużo łatwiej zrozumieć, czym są układy FPGA, a znając podstawowe konstrukcje, możliwe jest tworzenie układów cyfrowych na poziomie RTL, co powala grupować przerzutniki w rejestry, a bramki w funkcje logiczne.

Umożliwia to tworzenie struktur takich jak liczniki, mnożniki czy maszyny stanów, a nawet procesory czy bloki obliczeniowe. Z wykorzystaniem odpowiednio dużej liczby CLB można zaprojektować hardware, pozwalający zrównoleglić obliczenia w algorytmach sztucznej inteligencji [4] czy budować tory przetwarzania sygnałów złożone z kilku modułów połączonych kaskadowo jak w OFDM [5] czy innych systemach radiowych [6].

Bibliografia

[1] FPGA Design, Architecture and Applications, Dheeraj Punia, 07.12.2021

[2] FPGAkey.com

[3] IEEE Standard VHDL Language Reference Manual

[4] S. Yin et al., A high throughput acceleration for hybrid neural networks with efficient resource management on FPGA, IEEE Trans. Comput.-Aided Design Integr. Circuits Syst., vol. 38, no. 4, pp. 678–691, Apr. 2019

[5] Mohamed Salah, Abdel-Halim Zekry, Mohammed Kamel, An efficient FPGA implementation of OFDM physical layer for SDR-based applications, 2014 10th International Computer Engineering Conference (ICENCO)

[6] Q. Zhang, L. Cheng and C. Yin, Design of Multichannel Dual-frequency Digital Receiver based on FPGA, 2014 12th International Conference on Signal Processing, (ICSP), Hangzhou, 2014, pp. 403-407

***

Załącznik:

***

Na naszym blogu znajdziesz również inne artykuły obszaru Embedded.

5/5 ( głosy: 2)
Ocena:
5/5 ( głosy: 2)
Autor
Avatar
Michał Leśniak

Absolwent Politechniki Gdańskiej i Uniwersytetu Morskiego w Gdyni. Ma kilkuletnie doświadczenie w projektowaniu systemów wbudowanych i układów cyfrowych w FPGA. Pracował w branży radiokomunikacyjnej. W ostatnim czasie FPGA Engineer. Prywatnie zainteresowany motoryzacją, fizyką oraz grami komputerowymi.

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Może Cię również zainteresować

Pokaż więcej artykułów

Bądź na bieżąco

Zasubskrybuj naszego bloga i otrzymuj informacje o najnowszych wpisach.

Otrzymaj ofertę

Jeśli chcesz dowiedzieć się więcej na temat oferty Sii, skontaktuj się z nami.

Wyślij zapytanie Wyślij zapytanie

Natalia Competency Center Director

Get an offer

Dołącz do Sii

Znajdź idealną pracę – zapoznaj się z naszą ofertą rekrutacyjną i aplikuj.

Aplikuj Aplikuj

Paweł Process Owner

Join Sii

ZATWIERDŹ

This content is available only in one language version.
You will be redirected to home page.

Are you sure you want to leave this page?