Czym jest asembler.

  Mówi się, że asembler jest językiem niskiego poziomu. Jest w tym dużo prawdy, bo każda linia programu jest tłumaczona na jeden rozkaz procesora (pomijając komentarze).


Rozkazy procesora zostały nazwane mnemonikami, które ułatwiają programiście zrozumienie zachodzących w komputerze procesów. Większość rozkazów wykonuje odczyt lub zapis danych z pamięci lub rejestrów i proste obliczenia czy operacje logiczne.

Dla uproszczenia zdefiniujmy tylko część rejestrów procesora i pamięć jako tablicę 65536 bajtów (0 - 0xffff lub po staremu 0 - 0FFFFH). Program musi być zapisany w pamięci i zwykle jest to ROM (read only memory). W dawnych czasach w seryjnych komputerach stosowano pamięci programowane w procesie produkcji, których nie można było skasować, ani przeprogramować. Do krótszych serii używano PROM - programowane jednorazowo. EPROM kasowany ultrafioletem był znacznie droższy, więc służył do prototypów w czasie dopracowywania programu. Dzisiaj mamy EEPROM i FLASH, który łatwo można zapisywać wiele razy. W CA80 na początku EPROMu jest monitor - program inicjujący wyświetlacz, zegar systemowy, obsługujący klawiaturę, zapis i odczyt programu z taśmy magnetofonowej itd. Ma on też debuger - umożliwiający uruchamianie programu, który wpisujemy ręcznie za pomocą klawiatury, lub wczytujemy z taśmy. Uruchomiony możemy zapamiętać na taśmie, lub zaprogramować EPROM.

Model Z80
Uproszczony model procesora.


Program wykonuje się sekwencyjnie rozkaz po rozkazie, ale nie zawsze są one w kolejnych komórkach pamięci. Procesor pobiera kod rozkazu wskazywanego przez rejestr PC (program counter) i w zależności od typu rozkazu, następujące po nim dane lub kolejny kod rozkazu. Po RESET lub po włączeniu komputera PC = 0 i z tego adresu zostaje pobrany pierwszy rozkaz. Spójrzmy na kilka rozkazów zapisanych w języku C.

Dekodowanie rozkazów.
Dekodowanie rozkazu.


NOP nic nie robi, ale czasem się przydaje. Kiedy ręcznie poprawiamy program i znajdziemy błąd, możemy zbędny rozkaz zastąpić właśnie NOPem. Nie będziemy musieli przepisywać programu od nowa. Jeżeli błąd polega na braku rozkazu, musimy użyć innego sposobu, ale o tym innym razem.

LD BC,nn to wpisanie wartości szesnastobitowej do pary rejestrów BC. Zarówno pamięć jak i rejestry są ośmiobitowe, więc cały rozkaz ma trzy bajty - kod rozkazu i dwa bajty danych. W programie nie dzieje się nic skomplikowanego. Po zdekodowaniu rozkazu zawartość PC jest inkrementowana, więc wskazuje kolejny bajt w programie. Z80 pracuje w little endian czyli pod mniejszym adresem zapisany jest młodszy bajt liczby. Dlatego najpierw zapisujemy rejestr C, po czym zwiększamy PC i pobieramy drugi bajt do rejestru B i ponownie inkrementujemy PC, żeby wskazywał kolejny rozkaz.

Kolejne rozkazy.
Kolejne rozkazy.


LD (BC),n to zapisanie jednego bajta do komórki RAM wskazywanej przez parę rejestrów BC. Znowu nic nadzwyczajnego - po zdekodowaniu rozkazu PC wskazuje kolejny bajt, który przepisujemy do naszej tablicy do elementu wskazywanego przez BC. Na koniec PC++, żeby wskazywał kolejny adres.

INC BC w moim nieudolnym symulatorze jest trochę skomplikowany, bo model ma tylko rejestry ośmiobitowe. Zawartość pary rejestrów jest obliczana przez makro #define BC 256*B + C. W prawdziwym procesorze BC jest traktowany jako szesnastobitowy, więc jest prościej. Co prawda zdefiniowałem rejestry jako ośmiobitowe, ale dla lepszego zobrazowania traktuję je jako większe. Gdyby C == 0xff to po inkrementacji powinien "przekręcić się" na zero. Wykrycie zera w C powoduje inkrementację B. Rozkaz jest jednobajtowy, więc nie musimy modyfikować PC.

INC B zwiększa rejestr B o jeden, przy czym po 0xff następuje 0.

DEC B działa analogicznie do poprzedniego, ale w drugą stronę.

LD B,n przepisuje jeden bajt wskazywany przez PC do rejestru B.

ADD HL,BC
Nadal nic specjalnego.

ADD HL,BC (pominąłem dwa rozkazy, których na tym etapie nie mogę wyjaśnić) to pierwszy rozkaz arytmetyki szesnastobitowej. Najpierw dodajemy młodsze bajty, potem starsze z ewentualnym przeniesieniem. Rozkaz jest jednobajtowy mimo że operuje liczbami szesnastobitowymi.

LD A,(BC) przepisuje zawartość komórki pamięci wskazywanej przez BC do akumulatora (rejestru A).

DEC BC, INC C, DEC C oraz LD C,n działają analogicznie do już opisanych (INC BC oraz rozkazy z rejestrem B).

DJNZ e
Jakaś odmiana.

Wszystkie opisane wyżej rozkazy mają swoje odpowiedniki w procesorze i8080. To znaczy działają tak samo, mają takie same kody maszynowe, ale Intel stosował inne mnemoniki. Kolejny rozkaz dodano do Z80 - DJNZ e. Umożliwia on proste tworzenie pętli for. Iteratorem jest rejestr B, który jest dekrementowany. Jeżeli nowa wartość jest różna od zera, do PC dodawana jest wartość "e" w kodzie U2. Umożliwia to wykonanie skoku w programie w granicach <-128 +127>.

JP nn
Skoki bezwarunkowe.

Przyjrzyjmy się skokom. Wcześniej opisałem "krótki" skok nazywany relatywnym. Nie podaje się w nim wartości nowego adresu w programie, ale wartość o którą należy zwiększyć lub zmniejszyć PC. "Długie" skoki działają inaczej - w drugim i trzecim bajcie rozkazu jest nowa wartość PC. Specjalnie dla tego typu rozkazów zdefiniowałem rejestry tymczasowe (pośrednie), bo natychmiastowe wpisanie pierwszego bajta do PC_low uniemożliwiłoby poprawne pobranie PC_high. Jak inaczej można nazwać JP nn? Działa zupełnie tak samo, jak LD BC,nn - LD PC,nn jest poprawne i zgodne z konwencją asemblera Z80, ale nic nie mówi o działaniu tego rozkazu. Początkujący programista mógłby spodziewać się, że wykona się kolejny rozkaz...

CALL nn jest kolejnym skokiem, ale tym razem "ze śladem". JP nn po prostu powodował zmianę kolejności wykonywanych rozkazów. Tym razem "stary" PC jest zapisywany na stosie. Stos również znajduje się w pamięci, ale musi być w RAM. Gdyby SP wskazywał ROM, doszłoby do zakłócenia pracy programu. CALL służy do wywoływania podprogramów. "Ślad" odłożony na stosie jest potrzebny do powrotu do programu wywołującego. Podprogram (procedura) musi być zakończony rozkazem RET, który przywraca PC wartość sprzed jego wywołania.

RET można w uproszczeniu zapisać jako LD PC,(SP). Jak widać skoki to zmiany wartości PC, czyli znowu nic nadzwyczajnego.

Na razie pominąłem rejestr stanu procesora. Zawiera on "flagi" czyli informacje o przebiegu poprzednich rozkazów. Najczęściej używane są Z i C, często zapisywane jako ZF i CF w celu odróżnienia ich od rejestrów.


Komentarze

Popularne posty z tego bloga

Uruchamiamy CA80 na RCbus.

Magnetofon CA80.