.. _w07-axi:


==================================
Wykład 7: Komunikacja z procesorem
==================================

Data: 08.12.2020

.. toctree::

.. contents::


O rejestrach MMIO i szynach systemowych
=======================================

Częścią praktycznie każdego bardziej skomplikowanego układu cyfrowego jest
procesor (a często wiele procesorów), który steruje pracą całości bądź
części układu.  Procesor musi mieć możliwość komunikacji z każdą istotniejszą
częścią takiego układu.

Istnieją dwa podejścia do podłączenia urządzeń do procesora:

- ciasna integracja: urządzenie ma swój dedykowany interfejs i staje się
  w pewnym sensie częścią specyfikacji procesora (np. dedykowane instrukcje
  czy specjalne rejestry procesora do obsługi danego urządzenia)

- luźna integracja: urządzenie jest podłączone do procesora przez standardową
  szynę (zazwyczaj współdzieloną z innymi urządzeniami) i jest widoczne dla
  procesora za pomocą zwykłych instrukcji dostępu do pamięci — pewna część
  fizycznej przestrzeni adresowej jest wydzielona dla urządzenia i odpowiada
  ono na instrukcje procesora które piszą bądź czytają ten obszar; podejście
  takie nazywa się MMIO (memory-mapped I/O)

Ciasna integracja jest używana rzadko (najczęściej gdy projektujemy
jednocześnie procesor i jego urządzenia peryferyjne) i w przypadku
procesorów ogólnego przeznaczenia używa się wyłącznie luźnej integracji.

Szyną systemową nazywamy zbiór połączeń, który łączy ze sobą procesory
(i inne urządzenia będące w stanie inicjować transakcje) z urządzeniami
peryferyjnymi.  Może to być dość prosty bądź bardzo skompilokowany byt
— może składać się z naprawdę wielu segmentów wykonanych w wielu technologiach,
może być zawarta w ramach jednego układu scalonego, bądź też składać się z wielu
układów i połączeń (być może kablowych).

Charakteryzuje się następującymi cechami:

- szyna ma jednego lub więcej inicjatorów — urządzenia, które mogą inicjować
  transakcje na szynie; oczywistym przykładem inicjatora jest procesor, ale
  inicjatorami są też np. urządzenia peryferyjne będące w stanie wykonywać
  operacje bezpośredniego dostępu do pamięci (DMA)

- szyna ma jeden bądź więcej targetów — urządzeń, które mogą odpowiadać
  na transakcje

  - główna pamięć systemu również jest targetem (pokrywającym duży zakres adrwsów)

- standardowe transakcje na szynie to transakcje odczytu i zapisu (ale zdarzają
  się dziwniejsze typy transakcji na niektórych szynach); transakcje odczytu
  i zapisu składają się z co najmniej:

  - adresu docelowego (wysyłanego przez inicjatora)
  - rozmiaru (w bajtach) — jeśli jest większy niż 1, transakcja pokrywa
    wiele kolejnych adresĂłw
  - danych (wysyłane przez inicjatora w przypadku zapisu, przez target
    w przypadku odczytu)

— target dla danej transakcji jest wybierany na podstawie jej adresu — każdemu
  targetowi jest przydzielony jakiś zakres (bądź zakresy) adresów; w ramach
  swoich zakresów target może dowolnie interpretować resztę adresu

  - mapowanie adresów na urządzenia może być kompletnie ustalone w momencie produkcji
    układu (w przypadku szyn mieszczących się w całości w jednym układzie)
    bądź mniej lub bardziej konfigurowalne (w przypadku bardziej skomplikowanych
    szyn, jak te w komputerach klasy PC)

  - choć zazwyczaj każdy inicjator na szynie widzi takie samo mapowanie adresów,
    nie zawsze jest to prawda — mogą istnieć targety niedostępne z części szyny

  - zdarzają się szyny z tzw. IOMMU — układami tłumaczącymi adresy; w takim wypadku,
    transakcje od niektórych inicjatorów przechodzą przez tłumaczenie adresów
    bardzo podobne do stronicowania przez procesor

- szyna może składać się z wielu różnych pod-szyn wykonanych w różnych techonologiach
  i połączonych mostkami, bądź switchami


Układy SoC i AMBA
=================

Układy SoC (system on a chip) to układy, które zawierają procesor wraz z dość kompletnym
zestawem urządzeń peryferyjnych, pozwalając na złożenie kompletnego systemu z minimalną
liczbą zewnętrznych układów (zazwyczaj tylko DRAM + flash).  Taki układ jest sercem każdego
współczesnego telefonu czy tableta.  Takim układem jest również używany przez nas Zynq.

AMBA to zbiór standardów stworzonych przez ARM opisujących technologie komunikacji
pomiędzy urządzeniami zawartymi w jednym układzie scalonym, w tym technologie służące
do realizacji szyny systemowej.  W skład AMBA wchodzą między innymi:

- szyna AXI (Advanced eXtensible Interface) — główny interfejs używany przez współczesne
  duże procesory ARM i układy SoC na nich oparte; jest to interfejs point-to-point:
  komunikacja odbywa się pomiędzy jednym inicjatorem a jednym targetem i należy użyć
  switcha AXI, by móc spiąć ze sobą więcej urządzeń; jest oparty o 5 strumieni valid-ready
  i pozwala na jednoczesne wykonywanie wielu transakcji w potoku; ma wiele wersji:

  - AXI3: pierwsza wersja (uĹźywana na Zynq)
  - AXI4
  - AXI5
  - AXI4-Lite: uproszczone AXI4
  - AXI5-Lite
  - ACE (AXI Coherency Extensions): wersja AXI4 z dodatkowymi operacjami pozwalającymi na
    śledzenie stanu pamięci cache
  - ACE-Lite
  - ACE5: ACE, ale na bazie AXI5

- szyna AHB (Advanced High-performance Bus) — poprzednik AXI, oparty o zupełnie inny schemat;
  jest to szyna wielodostępna (jest wielu inicjatorów, w każdym cyklu jeden z nich ma dostęp
  do szyny, wybrany przez arbitra), z wieloma targetami; używana w starszych (bądź mniejszych)
  procesorach ARM; istnieje teĹź wariant AHB-Lite, w ktĂłrym istnieje tylko jeden inicjator

- szyna APB (Advanced Peripherial Bus) — dość prosty interfejs; ma jednego inicjatora i wiele
  targetów, nie ma żadnej możliwości przetwarzania potokowego ani równoległego; jest używana
  na "końcówkach" szyny systemowej, które nie wymagają zbyt dużej wydajności (czyli wolne urządzenia
  peryferyjne, bądź rzadko używane rejestry MMIO)

Zynq 7000
---------

Zynq jest (poza częścią z FPGA) dość typowym układem typu SoC bazującym na procesorze ARM.  Zawiera:

- procesor ARM Cortex-A9
- 256kB wewnętrznego RAMu
- kontroler pamięci DDR pozwalający na podłączenie do 1GB zewnętrznego RAMu
- dość nietrywialną sieć szyn AXI i switchy stanawiącą szkielet szyny systemowej
- kilka szyn APB, do których podłączone są układy peryferyjne
- kilka szyn AXI łączących szynę systemową z FPGA i pozwalających na rozszerzenie jej przez układ użytkownika:

  - ``SAXI_HP[0-3]``: 4 szybkie i szerokie (64-bit) szyny AXI, w których FPGA jest inicjatorem, podłączone do switcha,
    który widzi tylko RAM (ten wbudowany i ten zewnętrzny) — pozwala na wydajny dostęp do pamięci
  - ``SAXI_GP[0-1]``: 2 32-bitowe szyny AXI, w których FPGA jest inicjatorem, podłączone do switcha, z którego dostępna
    jest cała główna przestrzeń adresowa urządzenia — pozwala na dostęp do (prawie) wszystkich targetów
  - ``SAXI_ACP``: 64-bitowa szyna AXI, w której FPGA jest inicjatorem, podłączone do kontrolera pamięci cache w procesorze
    — pozwala na dostęp do całej przestrzeni adresowej widzianej przez procesor w sposób spójny z jego cachem (pozostałe
    szyny ``SAXI_*`` pomijają cache)
  - ``MAXI_GP[0-1]``: 2 32-bitowe szyny AXI, w których FPGA jest targetem; pozwala innym urządzeniom na wysyłanie transakcji do FPGA;
    szyna ``MAXI_GP0`` ma przydzielony zakres adresów ``0x40000000:0x80000000``, a szyna ``MAXI_GP1`` — ``0x80000000:0xc0000000``

- kilka układów peryferyjnych, które mają moce DMA i mają szynę AXI łączącą je (jako inicjator) z resztą systemu
- dwie szyny AHB-Lite, którymi podłączone są do głównej sieci kontrolery SDIO (bo akurat takie kupili)
- wolnostojący kontroler DMA, pozwalający na przesył danych między pamięcią a peryferiami (w przypadku Zynq, jedynym podłączonym
  urządzeniem peryferyjnym jest FPGA)


Szyna AXI
=========

Pełny opis szyny AXI można znaleźć w dokumentacji: https://developer.arm.com/documentation/ihi0022/e/AMBA-AXI3-and-AXI4-Protocol-Specification?lang=en .  Tutaj opowiemy tylko o najważniejszych elementach.

Szyna AXI3 (w wersji zaimplementowanej w Zynq) składa się z następujących połączeń:

- sygnały sterujące: ``ACLK`` (zegar) i ``ARESETN`` (zanegowany reset), wspólne dla wszystkich 5 strumieni; w przypadku interfejsów FPGA na Zynq, są sterowane
  ze strony FPGA (Zynq zawiera odpowiedni mostek, który przeniesie dane z/do wewnętrznych domen zegarowych i możemy użyć dowolnego
  zegara nie większego niż 250 MHz)

- strumień żądań odczytu (od inicjatora do targetu) — służy do rozpoczynania transakcji odczytu:

  - ``ARVALID``, ``ARREADY``
  - ``ARADDR`` (32-bitowy): adres żądanego odczytu
  - ``ARID`` (różnej długości): identyfikator inicjatora transakcji — pełny identyfikator inicjatora w całym Zynq ma 12 bitów, ale tylko część jest przydzielona do wyboru przez naszą szynę (pozostałe bity są wypełniane przez sieć połączeń i identyfikują port, z którego wysłaliśmy transakcję)

    - ``MAXI_GP*``: 12 bitów (jako target widzimy cały identyfikator inicjatora)
    - ``SAXI_ACP``: 3 bity (pozostałe bity identyfikatora wskazują na procesor, a wewnątrz procesora na port ACP)
    - ``SAXI_GP*``: 6 bitów (pozostałe bity identyfikatora wskazują na naszą szynę)
    - ``SAXI_HP*``: 6 bitów (pozostałe bity identyfikatora wskazują na naszą szynę)

  - ``ARBURST`` (2-bitowy): typ transakcji:

    - 0: ``FIXED`` — wszystkie transfery w transakcji mają ten sam adres
    - 1: ``INCR`` — kolejne transfery w transakcji mają kolejno rosnące (o rozmiar transferu) adresy
    - 2: ``WRAP`` — dość skomplikowany tryb, podobny do ``INCR``, ale ze specjalnymi zasadami zawijania; używany do wczytywania całej linii cache zaczynając od wybranego słowa

  - ``ARSIZE`` (2-bitowy): rozmiar jednego transferu danych, dekodowany następująco:

     - 0: 1 bajt
     - 1: 2 bajty
     - 2: 4 bajty
     - 3: 8 bajtĂłw (tylko na szynach 64-bitowych)

  - ``ARLEN`` (4-bitowy): rozmiar transakcji w transferach pomniejszony o 1 (czyli 0 w tym polu oznacza 1 transfer; 5 oznacza 6 transferĂłw)

  - ``ARCACHE`` (4-bitowy): atrybuty cacheowalności transakcji (można tu podać 0 jako inicjator)
  - ``ARLOCK`` (2-bitowy): atrybuty atomowości transakcji (należy tu podać 0 jako inicjator)
  - ``ARPROT`` (3-bitowy): atrybuty zabezpieczeń transakcji (można tu podać 0 jako inicjator)
  - ``ARQOS`` (4-bitowy): atrybuty priorytetu transakcji (można tu podać 0 jako inicjator)
  - ``ARUSER`` (5-bitowy, tylko na ``SAXI_ACP``): specjalne atrybuty transakcji (należy to podać 0 jako inicjator)

- strumień danych odczytu (od targetu do inicjatora) — służy do przesyłania odczytanych danych w transakcji odczytu i kończenia transakcji odczytu:

  - ``RVALID``, ``RREADY``
  - ``RDATA`` (32-bitowe lub 64-bitowe): właściwe dane
  - ``RID`` (różnej długości): równe ``ARID`` odczytu, którego dane dotyczą
  - ``RLAST`` (1-bitowe): prawda, jeśli to ostatni transfer danych z transakcji, kończy transakcję
  - ``RRESP`` (2-bitowe): typ odpowiedzi:

    - 0: ``OKAY`` — udana transakcja
    - 1: ``EXOKAY`` — udana transakcja z wyłącznością
    - 2: ``SLVERR`` — transakcja odrzucona przez target
    - 3: ``DECERR`` — transakcja odrzucona przez szynę (adres nie odpowiada żadnemu targetowi)

- strumień żądań zapisu (od inicjatora do targetu) — służy do rozpoczynania transakcji zapisu:

  - ``AWVALID``, ``AWREADY``
  - ``AWADDR`` (32-bitowy): adres żądanego zapisu
  - ``AWID`` (różnej długości): analogicznie do ``ARID``
  - ``AWBURST`` (2-bitowy): analogicznie do ``ARBURST``
  - ``AWSIZE`` (2-bitowy): analogicznie do ``ARSIZE``
  - ``AWLEN`` (4-bitowy): analogicznie do ``ARLEN``

  - ``AWCACHE`` (4-bitowy): analogicznie do ``ARCACHE``
  - ``AWLOCK`` (2-bitowy): analogicznie do ``ARLOCK``
  - ``AWPROT`` (3-bitowy): analogicznie do ``ARPROT``
  - ``AWQOS`` (4-bitowy): analogicznie do ``ARQOS``
  - ``AWUSER`` (5-bitowy, tylko na ``SAXI_ACP``): analogicznie do ``ARUSER``

- strumień danych zapisu (od inicjatora do targetu) — służy do przesyłania zapisywanych danych w transakcji zapisu:

  - ``WVALID``, ``WREADY``
  - ``WDATA`` (32-bitowe lub 64-bitowe): właściwe dane
  - ``WID`` (różnej długości): równe ``ARID`` odczytu, którego dane dotyczą
  - ``WLAST`` (1-bitowe): prawda, jeśli to ostatni transfer danych z transakcji, kończy transakcję
  - ``WSTRB`` (4-bitowe dla 32-bitowej szyny, 8-bitowe dla 64-bitowej szyny): sygnały byte enable dla poszczególnych bajtów, wybierają które bajty w słowie faktycznie należy zapisać

- strumień odpowiedzi na zapisy (od targetu do inicjatora) — kończy transakcję zapisu:

  - ``BVALID``, ``BREADY``
  - ``BID`` (różnej długości): identyfikator inicjatora transakcji — równe ``AWID`` zapisu, na który odpowiada
  - ``BRESP`` (2-bitowe): typ odpowiedzi, analogiczne do ``RRESP``

Strumienie w AXI są wariantem opisanych wcześniej interfejsów valid-ready z następującymi wymaganiami:

- gdy wysyłający ustawi sygnał valid na 1, nie wolno mu ustawić go na 0 ani zmienić pakietu dopóki pakiet nie zostanie zaakceptowany przez odbierającego (czyli gdy ready będzie równe 1) — nie wolno "zrezygnować" z przesyłu pakietu
- nie wolno mieć żadnych ścieżek kombinacyjnych między żadną parą sygnałów w interfejsie

Zasady działania transakcji AXI są następujące:

1. Transakcja składa się z jednego lub więcej transferów, gdzie transfer ma dowolną długość będącą potęgą dwójki od 1 bajtu do szerokości szyny.
2. Bajt o adresie X jest zawsze transferowany na bitach ``(X * 8 % DATA_WIDTH) : (X * 8 % DATA_WIDTH) + 8`` szyny danych.
3. Wszystkie adresy w ramach jednej transakcji muszą zawierać się w tym samym wyrównanym bloku 4kB.
4. Jeśli adres transakcji nie jest wyrównany do rozmiaru transferu, tylko część bajtów w pierwszym transferze zostanie przetransferowana; w pozostałych transferach zostanie już przetransferowany pełny rozmiar.
5. Targety będące pamięcią muszą wspierać wszystkie typy i rozmiary transakcji.  Targety będące rejestrami MMIO mogą ograniczyć się do wybranego podzbioru.
6. Zawsze przesyłane jest dokładnie tyle danych, ile zostało wcześniej zadeklarowane (nawet w przypadku błędu).
7. Zapis wygląda następująco:

   1. Inicjator wysyła na strumieniu ``AW*`` pakiet z adresem i metadanymi transakcji.
   2. Inicjator wysyła na strumieniu ``W*`` pakiety z danymi transakcji (tyle pakietów, ile jest transferów w transakcji).  Ostatni pakiet w transakcji (i tylko on) ma ustawiony bit ``WLAST``.  Przesył danych może odbywać się równolegle z przesyłem adresu.
   3. Wszystkie pakiety na strumieniu ``W*`` muszą być wysyłane w takiej samej kolejności co odpowiadające pakiety na strumieniu ``AW*``.
   4. Gdy target odbierze wszystkie pakiety odpowiadające jednej transakcji, wysyła na strumieniu ``B*`` jeden pakiet z odpowiedzią (nie wolno zrobić tego przed otrzymaniem wszystkich danych).
   5. Inicjator nie musi czekać na odpowiedź przed rozpoczęciem kolejnego zapisu — tempo wysyłania transakcji ograniczone jest tylko sygnałami gotowości targetu.
   6. W ramach jednego identyfikatora ``AWID/WID/BID``, odpowiedzi będą zawsze przychodzić w takiej samej kolejności, w jakiej zapisy zostały wysłane.
   7. Switchom i targetom wolno jednak dowolnie przestawiać zapisy o różnych identyfikatorach ``AWID/WID/BID`` — odpowiedzi mogą też przyjść w dowolnej kolejności.

8. Odczyt wygląda następująco:

   1. Inicjator wysyła na strumieniu ``AR*`` pakiet z adresem i metadanymi transakcji.
   2. Target wysyła na strumieniu ``R*`` pakiety z danymi transakcji (tyle pakietów, ile ma być transferów w transakcji).  Ostatni pakiet w transakcji (i tylko on) ma ustawiony bit ``RLAST``.
   3. Inicjator nie musi czekać na odpowiedź przed rozpoczęciem kolejnego odczytu — tempo wysyłania transakcji ograniczone jest tylko sygnałami gotowości targetu.
   4. W ramach jednego identyfikatora ``ARID/RID``, odpowiedzi będą zawsze przychodzić w takiej samej kolejności, w jakiej odczyty zostały wysłane.
   5. Switchom i targetom wolno jednak dowolnie przestawiać odczyty o różnych identyfikatorach ``ARID/RID`` — odpowiedzi mogą też przyjść w dowolnej kolejności.

9. Odczyty i zapisy są niezależnymi strumieniami i mogą być dowolnie przestawiane między sobą — jeśli inicjator chce, by miały ustaloną kolejność, powinien poczekać na odpowiedź na pierwszą transakcję przed wysłaniem drugiej.