Architektura Komputerów/Wykład 4: Struktura modelu programowego

Plan wykładu





Użytkowy model programowy




Użytkowy model programowy – to zbiór zasobów logicznych komputera widzianych przez programistę lub kompilator. Model programowy nie ma jednoznacznego i bezpośredniego związku z implementacją, czyli budową procesora i komputera. Istnieje wiele rodzin komputerów czy procesorów o wspólnym modelu programowym i wielu odmiennych implementacjach. Przykładem może to być rodzina x86 (zapoczątkowana w 1976 roku) lub IBM zSeries, wywodząca się z lat 60-tych XX wieku.


Składniki modelu programowego



  • Zestaw rejestrów
    • liczba i funkcjonalność rejestrów procesora
  • Zestaw trybów adresowania
    • sposoby specyfikacji argumentów operacji
  • Model operacji warunkowych
    • sposób realizacji konstrukcji warunkowych
  •  Lista instrukcji
    • zestaw operacji, jakie może wykonać procesor


Kompozycja modelu programowego



  • Wszystkie składniki modelu programowego są ze sobą ściśle powiązane
    • nie da się zaprojektować jednego ze składników w oderwaniu od pozostałych
  • Model programowy jest często silnie powiązany z planowaną implementacją - strukturą wewnętrzną procesora
    • często okazuje się po latach, że dany model programowy musi być zrealizowany w zupełnie innej strukturze procesora niż ta. dla jakiej był projektowany
      • np. x86


Funkcje rejestrów



  • Akumulator: A←A op src
  • Rejestr adresowy
    • Bazowy
    • Indeksowy
    • Wskaźnik stosu, ramki
  • Licznik pętli

Rejestry procesora mogą pełnić różne role w programach.


Akumulatorem nazywamy rejestr, który może być użyty jako argument źródła i równocześnie przeznaczenia dla operacji arytmetycznej lub logicznej.


Rejestr służący do uzyskania adresu danej umieszczonej w pamięci nazywa się ogólnie rejestrem adresowym. Jeśli rejestr może być użyty w trybie adresowania rejestrowym pośrednim, jest on nazywany rejestrem bazowym.


Rejestr może również służyć do odliczania iteracji pętli. Rejestr przewidziany do takiego zastosowania nazywa się licznikiem pętli.



Architektury zestawu rejestrów




  • Brak (pamięć - pamięć) Minimalny
  • Mały zestaw rejestrów specjalizowanych
  • Mały zestaw rejestrów uniwersalnych
  • Duży zestaw rejestrów uniwersalnych
  • Zestaw rejestrów jako bufor ramki stosu
  • Stosowy zestaw rejestrów

istnieje wiele różnych koncepcji organizacji zestawu rejestrów – począwszy od architektur bezrejestrowych a skończywszy na architekturach z bardzo dużym zestawem rejestrów o liczebności przekraczającej 100.


Architektury bezrejestrowe



  • Muszą zawierać 1..3 rejestry, w tym PC 
  • Operacje na danych "pamięć-pamięć" 
  • Obecnie prawie nie spotykane 
  • Architektura "move"
    • Lokacje przestrzeni adresowej odwzorowane w rejestry
      • rejestry są "ukryte" pod postacią komórek pamięci
    • Współczesny przykład: mikrokontrolery MAXQ firmy Maxim

Architektury bezrejestrowe, określane również mianem „pamięć-pamięć”, nie przechowują danych w rejestrach procesora. Procesor jest wyposażony w kilka rejestrów, które służą wyłącznie do przechowywania adresów.


Szczególną postacią architektury bezrejestrowej jest architektura MOVE. W architekturach tego typu procesor wykonuje tylko jedną lub co najwyżej kilka instrukcji, których argumentami mogą być wyłącznie stałe lub adresy pamięci. operacje arytmetyczne i logiczne są realizowane poprzez przesłania do specjalnych lokacji pamięci, służących jako rejestry argumentów. Adresy tych lokacji są używane do wyboru operacji wykonywanej przez jednostkę arytmetyczną.



Minimalny zestaw rejestrów



  • PC - licznik instrukcji
  • SP - wskaźnik stosu
  • Akumulator (pełni rolę rejestru wartości)
  • Rejestr adresowy do adresowania struktur danych
  • Przykład: mikrokontrolery 8-bitowe Freescale (dawniej Motorola) rodziny 68HC08

Architektury z minimalnym zestawem rejestrów posiadają jeden lub dwa akumulatory i jeden lub dwa rejestry adresowe. niemal wszystkie instrukcje wymagają odwołania do pamięci, ale jeden z argumentów instrukcji zwykle znajduje się w rejestrze. Generowanie kodu dla takich procesorów jest proste, ale uzyskany program wykonuje się stosunkowo wolno wskutek dużej liczby odwołań do pamięci.


Architektury tego typu nie są stosowane w komputerach uniwersalnych, były one natomiast popularne w mikrokontrolerach.



Minimalny zestaw rejestrów - CPU08



Rysunek przedstawia zestaw rejestrów jednostki wykonawczej mikrokontrolera rodziny HC08. Argumenty i zmienne lokalne procedur są tu adresowana względem wskaźnika stosu, a wielofunkcyjny rejestr HX służy jako rozszerzenie akumulatora dla operacji mnożenia i dzielenia, rejestr adresowy i licznik pętli. Podstawowym akumulatorem jest rejestr A.


Mały zestaw rejestrów wymusza umieszczanie niektórych zmiennych roboczych (tymczasowych) w pamięci.



Mały zestaw rejestrów specjalizowanych




  • 6..8 rejestrów pełniących różne, sztywno określone funkcje
  • Przykład - x86 w trybie 16-bitowym
  • Brak możliwości efektywnego wykorzystania rejestrów przez kompilator
    • Rejestry służą wyłącznie do obliczeń i przechowywania wyników pośrednich

W architekturach z małym zestawem rejestrów specjalizowanych mamy do czynienia z zestawem kilku rejestrów, niekiedy częściowo uniwersalnych, które są argumentami domyślnymi wielu specyficznych instrukcji.


Ponieważ kompilator używa czasem instrukcji, których argumenty muszą być umieszczone w konkretnych rejestrach, rejestry nie mogą być użyte do przechowywania danych programu (argumentów i zmiennych lokalnych procedur). Służą one jedynie do przechowywania tymczasowych wyników obliczeń oraz jako argumenty instrukcji, z którymi są domyślnie związane.


Pomimo, że pojemność zestawu rejestrów jest znacząca, kompilator nie jest w stanie efektywnie korzystać z rejestrów.



Rejestry x86 (tryb 16-bitowy)



  • AX - akumulator
  • DX - rozszerzenie akumulatora dla mnożenia i dzielenia
  • CX - licznik iteracji
  • BX - główny rejestr adresowy
  • SP - wskaźnik stosu
  • BP - wskaźnik ramki
  • SI, Dl - pomocnicze rejestry adresowe
  • IP - licznik instrukcji
  • FLAGS - rejestr znaczników

Rysunek przedstawia zestaw podstawowych rejestrów jednostki stałopozycyjnej 16-bitowej wersji architektury x86, zapoczątkowanej modelem Intel 8086 w 1976 roku. Poszczególne 16-bitowe rejestry są „przywiązane” do niektórych instrukcji. Tylko cztery rejestry mogą być używane jako adresowe.


Stosunkowo najbardziej uniwersalnymi rejestrami są SI i DI, ale ich użycie jest ograniczone przez fakt, że nie jest dostępny ich najmniej znaczący bajt. Niektóre bardziej zaawansowane kompilatory dla x86 używały tych dwóch rejestrów do alokacji zmiennych lokalnych procedury.



Mały zestaw rejestrów uniwersalnych



  • 6..8 rejestrów uniwersalnych
  • Przykład - x86 w trybie 32-bitowym
    • 8 rejestrów (EAX. EDX. ECX. EBX, ESP. EBP. ESI. EDI)
    • Wszystkie rejestry mogą służyć jako akumulatory i rejestry adresowe, wszystkie poza ESP - jako rejestry indeksowe
  • 3..4 rejestry mogą być użyte dla zmiennych lokalnych lub parametrów
    • alokacja obiektów lokalnych w rejestrach przyspiesza wykonanie kodu

W architekturach z małym zestawem rejestrów uniwersalnych mamy do czynienia z zestawem około 8 rejestrów, z których przynajmniej 5 daje się użyć w dowolnym charakterze – jako akumulatory lub rejestry adresowe.


Przykładem jest tu 32-bitowa wersja x86, zapoczątkowana przez model 80386 w roku 1985. Rejestry odziedziczone po wersji 16-bitowej zostały rozszerzone do 32 bitów. Wprowadzono również nowy zestaw trybów adresowania, umożliwiający wykorzystanie do adresowania wszystkich rejestrów. Wprowadzenie nowych instrukcji mnożenia umożliwiło zmniejszenie „przywiązania rejestrów do instrukcji. W ten sposób, pomimo, że na pierwszy rzut oka model programowy wygląda bardzo podobnie do wcześniejszych wersji, kompilator może używać rejestrów znacznie elastyczniej niż we wcześniejszych procesorach tej rodziny.



Duży zestaw rejestrów uniwersalnych




  • 16 lub 32 rejestry uniwersalne 
  • Przykłady:
    • MIPS. IBM Power - 32 rejestry
    • AMD64, IBM S/360...zSeries - 16 rejestrów
  • Rejestry używane do przekazywania kilku parametrów i przechowywania kilku zmiennych lokalnych
    • Istotna redukcja liczby odwołań do pamięci

W architekturach z dużym zestawem rejestrów mamy do czynienia z wieloma rejestrami, które mogł pełnić dowolną rolę. Umożliwia to kompilatorom używanie rejestrów do przekazywania argumentów wywołania i zmiennych lokalnych procedury.


Odwołania do pamięci zostają skupione w prologu i epilogu procedury, gdzie zachodzi przeładowanie ramki stosu pomiędzy rejestrami i stosem w pamięci.



Bufor wierzchołka stosu



  • Duży zestaw rejestrów (32 ..128), który z założenia ma mieścić prawie całą ramkę stosu (bez zmiennych strukturalnych)
  • Dwa rozwiązania: 
    • Okna rejestrów - SPARC
    • Bufor stosu - Am29k. IA-64 (Itanium)
  • Wykonanie procedury niemal bez odwołań do pamięci

W niektórych architekturach bardzo duży zestaw rejestrów jest używany do zbuforowania wewnątrz procesora wierzchołka stosu umieszczonego w pamięci. W ten sposób uzyskuje się znaczną redukcję liczby odwołań do pamięci, również w prologu i epilogu procedury.


Ponieważ zestaw rejestrów mieści kilka ramek stosu, odwołania do pamięci zachodzą przy przepełnieniu lub niedopełnieniu stosu w rejestrach, co ma miejsce raz na kilka poziomów wywołań procedur.



Stosowy zestaw rejestrów



  • 3..8 rejestrów tworzących stos 
  • Często brak nazw rejestrów
  • Operacje wykonywane na wierzchołku stosu rejestrów
    • Bezargumentowe lub jednoargumentowe
    • Argumenty pobierane z wierzchołka stosu, wynik odkładany na stos 
  • Przykłady: 
    • Transputery
    • Jednostka zmiennopozycyjna x87

W architekturach ze stosowym zestawem rejestrów rejestry są zorganizowane w postaci niewielkiego stosu. Ponieważ operacje są domyślnie wykonywane na wierzchołku stosu, procesory o takich zestawach rejestrów mają zwykle instrukcje bezargumentowe lub jednoargumentowe.


Taka architektura zestawu rejestrów znacząco upraszcza konstrukcję kompilatora, jest ona jednak bardzo trudna do efektywnej realizacji przy typowych współczesnych strukturach jednostek wykonawczych.



Tryby adresowania




  • Pojęcie "tryb adresowania" oznacza sposób specyfikacji argumentu operacji
    • W zawężonym ujęciu oznacza sposób określenia (obliczenia) adresu danej w pamięci
    • Szerzej - odnosi się również do rejestrów i stałych

Drugim elementem modelu programowego jest zestaw trybów adresowania. Samo pojęcie trybu adresowania ma dwa znaczenia.


Tryby adresowania nie odnoszące się do pamięci



  • Natychmiastowy - wartość danej zapisana w instrukcji
  • Rejestrowy bezpośredni - argument operacji w rejestrze


Tryby rejestrowe pośrednie



  • Argument w pamięci, adres argumentu lub jego składnik zapisany w rejestrze
    • Rejestr ten jest nazywany r. bazowym 
  • Warianty:
    • r.p. (prosty) - adres w rejestrze
    • r.p. z przemieszczeniem - adres jest sumą zawartości rejestru i stałej zapisanej w instrukcji
    • Dwurejestrowy pośredni - adres jest sumą zawartości dwóch rejestrów


Minimalny zestaw trybów adresowania



  • Do efektywnej realizacji HLL są potrzebne tryby: 
    • Natychmiastowy 
    • Rejestrowy bezpośredni
    • Jeden z rejestrowych pośrednich - zwykle r.p. z przemieszczeniem 
      • Niezbędny do wyliczania zmiennych adresów (np. tablice. ramka stosu)


Można zauważyć, że w celu umożliwienia efektywnej implementacji języków wysokiego poziomu procesor powinien posiadać trzy tryby adresowania.


Tryb natychmiastowy służy do ładowania do rejestrów stałych, w tym również adresów danych statycznych. Tryb rejestrowy bezpośredni umożliwia użycie zawartości rejestru jako argumentu operacji. Do adresowania danych w pamięci wystarczy jeden z trybów rejestrowych pośrednich. W ten sposób uzyskujemy możliwość adresowania zmiennym adresem, wyliczonym uprzednio w rejestrze. Adresowanie danych stałym adresem może być potraktowane jako szczególny przypadek adresowania ze zmiennym adresem. Najwygodniejszym trybem adresowania pamięci jest tryb rejestrowy pośredni z przemieszczeniem.



Tryb absolutny (bezpośredni)



  • Dana w pamięci, adres zapisany w instrukcji
    • Najprostszy sposób adresowania skalarnych danych statycznych
    • Nie jest niezbędny — może być zastąpiony przez tryb rejestrowy pośredni z przemieszczeniem


Tryby z bazą w PC



  • Tryby rejestrowe pośrednie, dla których rejestrem bazowym jest PC
    • Wygodne do adresowania tablic adresów kodu (np. konstrukcja switch)
    • Dostępne w niektórych architekturach (MSP430. M68k)
    • Mogą zastępować tryb absolutny
    • Mogą być pożyteczne w procesorach 64-bitowych (AMD64)

Tryby rejestrowe pośrednie z bazą w liczniku instrukcji umożliwiają adresowanie danych względem adresu bieżącej instrukcji. Mogą one być używane do adresowania danych wplecionych w kod i logicznie należących do kodu programu (np. tablice adresów). W procesorach 64-bitowych tryby te umożliwiają ograniczenie długości adresów absolutnych zawartych w zapisie instrukcji, gdyż dane, przynajmniej statyczne, są zwykle położone niezbyt daleko od kodu programu. (W przeciwnym razie zamiast 32-bitowych przemieszczeń względem PC do adresowania danych należałoby używać 64-bitowych adresów absolutnych.)


Problem obliczania zmieniających się z każdą instrukcją przemieszczeń danych liczonych względem PC przejmuje na siebie asembler.



Tryby indeksowe



  • Adres powstaje przez zsumowanie adresu efektywnego odnoszącego się do pamięci (uzyskanego z innego trybu adresowania) z wartością rejestru, opcjonalnie pomnożoną przez stałą (skalę) będącą potęgą liczby 2
    • Rejestr jest nazywany r. Indeksowym
    • Skala: 1,2, 4, 8, ew. 16
    • Mnożenie przez przesuwanie w lewo
    • Przy skali <> 1 tryb nazywamy i. skalowanym


Tryby z automodyfikacją bazy



  • Tryby rejestrowe pośrednie, w których wartość rejestru bazowego jest modyfikowana o długość przesyłanej danej przed lub po wykonaniu przesłania danej
    • Preinkrementacja, postinkrementacja, predekrementacja, postdekrementacja
    • Niejawnie korzystają z nich operacje stosowe (przy realizacji stosu pełnego schodzącego: PUSH - predekrementacja, POP-postinkrementacja)


Tryby pamięciowe pośrednie



  • Dana jest zawarta w pamięci po adresem, którego składnik jest zawarty w pamięci
    • Dwukrotne odwołanie do pamięci
    • Pożyteczne przy np. tablicach wskaźników
    • Możliwość modyfikacji adresu przez dodanie przemieszczenia lub indeksowanie
    • Obecnie rzadko spotykane - kosztowne czasowo
    • Dostępne w klasycznych architekturach CISC. np. VAX. M68k od modelu MC68020

W trybach pamięciowych pośrednich jeden z elementów adresu jest pobierany z pamięci, a uzyskany adres jest wykorzystywany do odwołania do danej w pamięci. Odwołanie do danej wymaga więc dwukrotnego sięgnięcia do pamięci.


W trybach pamięciowych pośrednich występują dwa adresy efektywne, do obliczenia których można korzystać z operacji indeksowania lub dodawania przemieszczenia. Pierwszy adres, wyznaczony z jednego z omówionych wcześniej trybów adresowania pamięci, jest używany do odczytania wartości, służącej jako adres bazowy dla drugiego adresu. Do pozyskanego w ten sposób adresu bazowego można następnie dodać przemieszczenie lub przeskalowaną zawartość rejestru indeksowego.


Tryby pamięciowe pośrednie były charakterystyczne dla złożonych architektur CISC. Obecnie są one rzadko spotykane, gdyż opisana sekwencja operacji może być łatwo, a zarazem w sposób bardziej elastyczny, zrealizowana na drodze programowej.



Model operacji warunkowych




  • Określa sposób realizacji przez procesor operacji warunkowych 
  • Warianty:
    • Model ze znacznikami
    • Model bez znaczników ("porównaj i ...")
    • Model z predykatami

Model operacji warunkowych jest trzecim składnikiem modelu programowego. Współczesne procesory implementują jeden z trzech modeli opisanych w dalszym ciągu wykładu.


Model ze znacznikiem



  • Znaczniki - jednobitowe rejestry atrybutów wyniku ostatnio wykonanej operacji
    • Zwykle zgrupowane w jeden kilkubitowy rejestr
  • Operacja warunkowa realizowana za pomocą dwóch instrukcji:
    • Ustawienie znaczników
    • Instrukcja warunkowa zależna od ustawienia znaczników

Model ze znacznikami wprowadza dwufazową realizację operacji warunkowych, przy czym instrukcje procesora realizujące obie fazy mogą być rozsunięte w czasie.


W pierwszej fazie w wyniku wykonania operacji arytmetycznej lub logicznej zostają ustawione znaczniki – jednobitowe rejestry atrybutów wyniku. W drugiej fazie następuje wykonanie operacji zależne od stanu znaczników.



Znaczniki




  • Z - zero
    • przyjmuje stan 1 jeżeli wynik operacji wynosi 0
  • N/M (negative/minus)- znak
    • kopia najbardziej znaczącego bitu wyniku operacji
  • C/CY (carry) - przeniesienie/pożyczka
    • przeniesienie wychodzące z najbardziej znaczącego bitu wyniku
  • 0/V/0V (overflow) - nadmiar
    • nadmiar w kodzie U2
  • AC/HC (auxillary/half carry) - przeniesienie pomocnicze BCD
    • przeniesienie pomiędzy najmniej znaczącymi tetradami
  • P (parity) - parzystość
    • przyjmuje stan 1 jeśli liczba jedynek w najmniej znaczącym bajcie wyniku operacji jest parzysta

W poszczególnych architekturach występuje od dwóch do sześciu znaczników. typowa liczba znaczników wynosi 4.Dwa ostatnie – przeniesienie pomocnicze i parzystość, występują głównie w architekturach wywodzących się z lat 70-tych XX wieku.


Znacznik przeniesienia pomocniczego wspomaga realizację operacji na liczbach zapisanych w kodzie BCD. Przechowuje on wartość przeniesienia pomiędzy tetradami pojedynczego bajtu reprezentującymi cyfry dziesiętne.


Znacznik parzystości był używany do podstawowej kontroli poprawności transmisji danych znakowych w kodzie ASCII, w tym do kontroli danych odczytywanych z taśm perforowanych. Nawet w procesorach 32- i 64-bitowych znacznik parzystości zawsze dotyczy wyłącznie najmniej znaczącego bajtu wyniku operacji.



Zasady ustawiania znaczników



  • Szczegółowe zasady ustawiania znaczników są zdefiniowane w dokumentacji modelu programowego każdego procesora
  • Wszystkie znaczniki są ustawianie przez podstawowe dwuargumentowe instrukcje arytmetyczne i logiczne
  • Inne instrukcje (np. jednoargumentowe) - nie zawsze wszystkie znaczniki
  • W niektórych architekturach znaczniki zera i znaku są ustawiane przez instrukcje przesłań
    • np. M68k. HC08

Ponieważ w różnych architekturach obowiązują różne konwencje ustawiania znaczników, rozpoczynając programowanie procesora w asemblerze należy dokładnie zapoznać się z dokumentacją poszczególnych instrukcji.


Instrukcje warunkowe



  • Instrukcja specyfikuje warunek wykonania
    • jeśli warunek nie jest spełniony instrukcja wykonuje się jako pusta
  • Skoki warunkowe - dostępne we wszystkich architekturach
  • Przesłania warunkowe - dostępne w nowszych procesorach
    • umożliwiają eliminację części skoków i przyspieszenie wykonania kodu
  • Ustawienie (SETcc)
    • zamienia wartość logiczną warunku na wartość danej całkowitej
  • Architektury z warunkowym wykonaniem większości instrukcji – np.ARM
    • eliminacja znacznej części skoków w rozwinięciach krótkich konstrukcji typu if-then-else

Instrukcja warunkowa realizuje drugą fazę operacji warunkowej w modelu ze znacznikami. Jest to instrukcja, której sposób wykonania zależy od wartości znaczników. Instrukcja warunkowa specyfikuje warunek wykonania. Jeśli warunek (wyrażenie logiczne na znacznikach) jest spełniony, instrukcja wykonuje zadaną operację (np. skok lub przesłanie). W przeciwnym razie instrukcja wykonuje się jako pusta.


Instrukcja ustawienia danej (np. SETcc w x86) wpisuje do rejestru wartość logiczną warunku wyrażoną jako stałą całkowitą.


W niektórych architekturach większość instrukcji jest wykonywanych jako warunkowe.



Warunki wykonania instrukcji




  • Specyfikowane jako wartość jednego znacznika lub wyrażenie logiczne na wartościach kilku znaczników
  • Symboliczne oznaczenie nazwy warunku stanowi część nazwy instrukcji warunkowej, np. JNZ - "jump if not zero"

Warunek wykonania jest jednym z predefiniowanych w danej architekturze wyrażeń logicznych na znacznikach. Poszczególne warunki mają swoje nazwy mnemoniczne.


Rejestr znaczników - x86



Rysunek przedstawia rozmieszczenie znaczników w rejestrze stanu procesorów rodziny x86.


Warunki w x86



Tabela zawiera nazwy symboliczne warunków dostępnych w x86 i specyfikację odpowiadających im wyrażeń logicznych na znacznikach.


Należy zwrócić uwagę na istnienie wielu nazw tych samych warunków, co ułatwia ich użycie w programach.


Stosowane w nazwach warunków skróty A, B, G i L (above, below, greater, less) oznaczają odpowiednio relacje większości i mniejszości dla liczb bez znaku (A, B) i ze znakiem (G, L).



Model operacji warunkowych bez znaczników



  • Pojedyncza instrukcja ewaluuje relację i wykonuje operację jeśli relacja jest spełniona 
    • np. "skocz jeśli zawartości rejestrów równe"
  • Charakterystyczny dla prostych procesorów RISC, np. MIPS

W modelu operacji warunkowych bez znaczników operacja warunkowa jest realizowana przez pojedynczą instrukcję. instrukcja ta ewaluuje wartość logiczną relacji, a następnie wykonuje jakąś czynność, jeśli relacja jest spełniona.


Taki model operacji warunkowych jest prosty i wydajny w implementacji. jest on stosowany w prostych architekturach RISCowych.



Model z predykatami




  • Predykaty - uogólnione znaczniki, mogą przechowywać wartość logiczną dowolnej wcześniej obliczonej relacji
  • Duża liczba predykatów w procesorze - możliwość równoczesnego przechowywania wartości wielu relacji
  • Instrukcje w większości warunkowe, specyfikują numer predykatu jako warunek wykonania
  • Model zaimplementowany w architekturze IA-64 (Itanium)

Model z predykatami jest spotykany w nowych architekturach o dużej równoległości wykonania instrukcji, dużych zestawach rejestrów i wysokich kosztach skoków. praktycznie jedyną architekturą komercyjną implementującą ten model jest IA-64.