Kurs Assemblera cz. 9

 

WEKTORY NA STOS!

 

Młodszy i starszy bajt

Co to jest starszy i młodszy bajt, wszyscy oczywiście dobrze wiedzą, więc ja tylko tak dla przypomnienia powiem, o co w całym tym interesie chodzi. Commodore 64 ma procesor ośmiobitowy. Za pomocą ośmiu tylko bitów można jednoznacznie (to znaczy tak, by nie można było jednej komórki pomylić z inną) zaadresować tylko 256 komórek pamięci. Trochę mato, nieprawdaż? Dlatego właśnie panowie konstruktorzy poszli po rozum do głowy i wymyślili, że szyna adresowa powinna być szesnastobitowa. Od tej pory do zaadresowania każdej komórki potrzeba aż szesnastu bitów, czyli dwóch bajtów. Daje nam to do wyboru dokładnie 65536 bajtów – czyli 64 KB, akurat tyle, ile mamy pod skorupą maszynki.

Owe bajty, które służą do określenia adresu, pewni niewątpliwie mądrzy panowie nazwali starszym i młodszym bajtem. Adres komórki obliczamy traktując obie komórki jako szesnastocyfrową liczbę zapisaną w systemie dwójkowym – bajt starszy (SB) mnożymy przez 256 i dodajemy do niego bajt młodszy (MB). Cała filozofia.

W drugą stronę jest już nieco gorzej, ale też łatwo. Bierzemy adres i dzielimy go przez 256. Najczęściej wychodzi nam jakaś liczba w przedziale od 0 do 255, a po przecinku – jakaś niepotrzebna nikomu kaszana. Kaszanę tę obcinamy bez żadnych skrupułów – jeszcze nikomu nie udała się sztuka zapisania w jednej komórce np. 65.789463… Nasza liczba po odcięciu kaszany to gotowy już do bezpośredniego spożycia SB. MB obliczamy zaś tak – od adresu odejmujemy dwustupięćdziesięciosześciokrotność SB i po zawodach. Dla ułatwienia Wam życia pokażę, jak wyglądać to będzie w BASIC-u:

10 adres=nnnnn
20 sb=int (adres/256)
30 mb=adres – 256’sb

Wszystko jasne? (spróbowałoby nie być…)
Technika młodszego/starszego bajtu jest bardzo szeroko stosowana w C-64. Dlatego właśnie wartałoby się z nią oswoić i zaznajomić. Z tego prostego powodu, że – jako się rzekło – jednym bajtem zaadresujemy najwyżej pierwsze 256 bajtów (czyli stronę zerową) pamięci. A poruszać się tylko w 256 bajtach to trochę dziwny obyczaj, kiedy do dyspozycji mamy pełne 64 KB.

 

Wektory

Wiadomość o tym, kto i kiedy wymyślił nazwę „wektor”, dawno już zginęła w pomroce dziejów. W sumie to dobrze, bo i po co zaśmiecać sobie głowę podobnymi dyrdymałami. Znacznie bardziej istotne jest, co to takiego ten „wektor” i co pożytecznego można z niego wycisnąć.

Zasadniczo, wektor to adres jakiejś komórki lub procedury umieszczony ot, tak sobie w pamięci – najpierw młodszy, potem starszy bajt. Znaczenie gospodarcze wektorów jest stosunkowo duże – dzięki nim dosłownie KAŻDY program można uczynić relokowalnym, po wektorach można skakać, z wektorów można przeliczać adresy komórek, które są nam właśnie potrzebne. Jednym z przykładów wykorzystania wektorów (fuj, paskudne słowo) jest…

 

Adresowanie pośrednie

…czyli pamiętne LDA (XX), Y. XX jest tutaj adresem komórki, w której znajduje się młodszy bajt wektora. Nie trzeba (komputerowi) dodawać, że zaraz po nim znajdzie się starszy bajt tego wektora – to jest po prostu konieczność wprost wynikająca z samej definicji wektora.

Jeszcze raz: komputer bierze adres określony przez wektor w komórkach XX i XX+1, dodaje do niego zawartość rejestru Y i dopiero liczba otrzymana po wszystkich tych przekształceniach jest adresem komórki, z której chcemy wziąć zawartość do zakumulowania w akumulatorze.

Skomplikowane to nieco, nieprawdaż? Jeśli tak, to niejasności wszelakie powinien rozjaśnić PROGRAM 1, który korzystając z adresowania pośredniego postindeksowanego… nie, nie powiem – sami sprawdźcie!

Już wpisane i uruchomione? Ekran pełen gwiazd? Pytacie dlaczego? To proste – właśnie uruchomiliście program, który najpierw wykonuje pętlę wstawiającą do wszystkich komórek ekranu wartość $2a (42), co odpowiada kodowi ASCII dla znaczka „*”, a potem sam zapętla się na amen w komórce $2729. To ostatnie zapętlenie – gwoli wyjaśnienia – zrobiłem po to tylko, by na ekran nie powyłaziły jakieś „READY.”, czy „?SYNTAX ERROR”, co zepsułoby moją artystyczną koncepcję. A teraz prześledźmy szczegółowo program.

W komórkach $2710 – $2717 bierzemy wartość $00 i wstawiamy do komórki $fb, potem bierzemy wartość $04 i wstawiamy do komórki $fc. Co nam to przypomina? Totalna wektoryzacja! Weźmy wartości z $fb (czyli 00) i $fc (04) i zamieńmy je miejscami, a przed tym wszystkim postawmy symbol naszej ulubionej waluty. Co wyszło? $0400, czyli adres początku ekranu. Następnie do akumulatora wstawiamy wartość odpowiadającą gwiazdce, a do rejestru Y – 0. Potem wydajemy rozkaz STA (wektor), Y. Co robi zdyscyplinowany komputer? Wylicza sobie wartość, która siedzi pod adresami $fb/$fc ($0400) i dodaje do niej to, co jest w Y, czyli nic. Do komórki o uzyskanym adresie – $0400 – wstawia gwiazdkę. Następnie zwiększa o 1 zawartość Y i powtarza operację. Kiedy Y dochodzi do 0 (przekręca licznik, jak w starym samochodzie), zwiększamy zawartość komórki wektor+l 0 1. Przez to wektor ten wskazywać będzie komórkę $0500. Kiedy dojdziemy do $08 w komórce wektor+l, to znak, że dalej już gwiazdek wstawiać nie należy – w tym obszarze leżeć może np. program w BASIC-u, który może nam być jeszcze potrzebny, a poza tym i tak trzeba będzie to kiedyś skończyć.

Wytłumaczone. Wartałoby jeszcze dodać, że podczas używania adresowania pośredniego postindeksowanego korzystać można tylko z wektorów na zerowej stronie pamięci.

 

Skakanie po wektorach

Po wektorach można skakać. Nie mówcie tego nauczycielom matematyki czy fizyki, bo jedyne, co możecie tą drogą osiągnąć, to widok oczu rozszerzających się do rozmiarów małych talerzyków, poza tym może się zdarzyć, że zwolnią Was wcześniej do domu „bo Krzysinek coś dzisiaj się źle czuje, bredzi”. Skakanie po wektorach to idiom, znany tylko specom od asemblera, a oznacza wykonywanie rozkazu JMP (czyli skoku) z wykorzystaniem jakiegoś wektora. Przechodząc do szczegółów: JMP ($XXXX) nie oznacza skoku do komórki $XXXX, lecz fakt, że w inkryminowanej komórce znajduje się wektor, który pokazuje, gdzie NAPRAWDĘ chciałby skoczyć Pan i Władca (czyli – dla komputera – Ty). Wypróbujmy to na przykładziku naprawdę prościutkim:

Przykładzik – jako się rzekło – prościutki, więc I

poświęcimy mu tylko parę stów, a i tak powinno wystarczyć. Najpierw bierzemy liczbę $00 i wstawiamy ją do komórki $fb, potem bierzemy liczbę $fc i odsyłamy do $fc. Dzięki temu wektor $fb/$fc wskazywać będzie adres $4000. Jeśli rzeczywiście tak jest, to skaczemy! W $4000 wstawiłem wybielanie obwódki ekranu, by było widać, że rzeczywiście skoczyliśmy tam, gdzie chcieliśmy, a nie – że tylko tak się nam wydaje.

 

Wektory w systemie

System C-64 jest do granic możliwości uzależniony od wektorów. Prawie każdą duperelę (sorry!) komcio robi dopiero po odwołaniu się do jakiegoś wektora. Wszystko, co ma jakąkolwiek ważność, idzie po wektorach – cały BASIC, przerwania, obsługa dysku, obsługa magnetofonu, RESTORE, RESET – naprawdę jest tego mnóstwo.

Właśnie dzięki temu C-64 ma BASIC, który osobiście uważam za jeden z najlepszych (oczywiście o ile amigowskiego języka AMOS do BASIC-a nie zaliczymy!). Cały interpreter działa na wektorach i wcale nie odstrasza jak Spectrum, czy Atari do robienia własnych wersji i przeróbek języka. Wręcz przeciwnie, rzec można, że zachęca. Jeden wektor gdzie indziej i zamiast „?SYNTAX ERROR” komputer wypisze „PAN SZANOWNY RACZYŁ OMYLIĆ SIĘ W SKŁADNI” albo „SPADAJ GŁĄBIE” w zależności od gustu i potrzeb użytkownika.

Słów parę o skokach z asekuracją

Po wykonaniu JMP komputer nie wie, skąd skoczył i nie istnieje prosta metoda, by znaleźć miejsce, z którego skakał. Wiemy już jednak, że istnieją skoki z asekuracją – JSR. Po JSR można wrócić w miejsce, z którego wyruszaliśmy. Czyli JSR zostawia gdzieś w pamięci jakiś ślad, po którym, jak po nici pięknej Ariadny, możemy powrócić niczym mężny Tezeusz. Ślad ten jest oczywiście wektorem. Ale wektora tego nie można wstawić do pamięci byle gdzie, bo trudno byłoby go potem znaleźć. Nie można też wstawiać go zawsze w jedno, określone miejsce, bo co zrobimy, jeśli będziemy chcieli skakać „piętrowo”, czyli JSR po JSR? Na szczęście w C-64 istnieje pewien obszar pamięci, od $0100 do $01ff, nazywany pospolicie stosem. Zajmijmy się teraz stosem. Do JSR – obiecuję! – powrócimy.

 

Stos

to miejsce, na które – jak sama nazwa wskazuje – można zrzucać wszystko: papiery, głazy, książki, czarownice – co tylko przyjdzie nam do głowy. W komputerze jesteśmy nieco ograniczeni – wstawiać możemy tylko liczby, ale za to dowolne: niech to będą adresy dwubajtowe, liczby, dane dla podprogramów, tymczasowe wartości czegokolwiek. Słowem co kto chce i lubi.

Zawartość akumulatora możemy zrzucić na stos rozkazem PHA (PusH Accumulator on stack – zepchnij akumulator na stos). Wyobraźmy sobie, że symbolizuje to układanie książek. Zrobiliśmy PHA? Zatem kładziemy na wierzch jakąś książkę.

Teraz dla odmiany chcemy zdjąć jakąś wartość ze stosu – potrzebny jest rozkaz PLA (PuLI Accumulator from stack – ściągnij akumulator ze stosu). Wróćmy do analogii z książkami. Którą książkę weźmiemy najpierw? Na pewno tą, którą położyliśmy na wierzchu, czyli ostatnio. Próba wzięcia np. pierwszej położonej książki spowodowałaby zapewne zawalenie całego stosu. W ustaleniu, która wartość jest na stosie ostatnia, pomaga nam SP – Stack Pointer, wskaźnik stosu. Sprawdźmy, jak – i czy w ogóle – stos działa.

Na ekranie, jak za dawnych, dobrych czasów, pokazała się literka „a” odpowiadająca, jak pamiętamy, liczbie $01 wstawionej do akumulatora. I to pomimo tego, że potem do akumulatora wstawialiśmy $02! Zagadkę wyjaśniają rozkazy PHA i PLA. Po włożeniu do akumulatora $01, jego zawartość zrzucaliśmy na stos. Po wzięciu $02 – ściągaliśmy ze stosu to, co było w akumulatorze poprzednio, czyli $01. Ta właśnie liczba wędrowała do $0400, co powodowało wyświetlenie „a” na ekranie.

Nie tylko zawartość akumulatora możemy kłaść na stos. Od razu dla wyjaśnienia dodam, że np. rejestrów X ani Y na stos zrzucić się nie da – tylko przez pośrednictwo akumulatora. Oprócz akumulatora możemy magazynować na stosie stany wszystkich flag komputera – Przeniesienia, Zera, Przepełnienia etc. Służy do tego rozkaz PHP (nie PKP!), co znaczy: PusH Processor status register on stack, zepchnij na stos rejestr stanu procesora.

Rozkazem o działaniu odwrotnym, czyli po którego wydaniu komputer ochoczo weźmie ze stosu wartości wszystkich znaczników, jest PLP (PuLI Processor status register from stack – ściągnij ze stosu rejestr stanu procesora). Po wykonaniu PHP komputer zapamiętuje na stosie aktualny stan znaczników. Może się to nam przydać w jakimś programie, np. jako wynik obliczeń (flaga Zero), który wykorzystamy później, a tymczasem policzymy co innego, co przecież może źle wpłynąć na stan naszej flagi. Działa to tak:

Do $ff dodajemy $40, co, jak pamiętamy, musi zapalić znacznik C (Przeniesienia). Cały rejestr stanu (SR) zwalamy na stos. Następnie za pomocą rozkazu CLC gasimy znacznik C. A potem ściągamy cały SR ze stosu. Jeśli C jest zgaszony, pokaże się 0, jeśli zapalony – 1. Świadczyć to będzie o skuteczności wydanych przez nas rozkazów. Teraz jednak…

 

Wróćmy do JSR

Obiecałem, że powiem parę stów o śladzie, jaki zostawia za sobą JSR. Komputer, zanim skoczy tam, gdzie mu każemy, zrzuca na stos MB i SB adresu ostatniej komórki rozkazu skoku (który, jak wiemy, jest trzybajtowy). Dzięki temu po znalezieniu rozkazu RTS komputer ściąga owe wartości ze stosu i już wie, od którego miejsca powinien dalej pracować. Pokażę to Wam na prościutkim przykładzie:

Na ekranie pokaże się liczba 10002. Jasna sprawa: w 10000 siedzi $4c, czyli „JMP”, 10001 – młodszy bajt – $cd, a w 10002 – starszy bajt adresu, $bd. 10002 to ostatni bajt rozkazu JMP. Ten właśnie bajt zostanie dla potomności zapamiętany. Od następnego bajtu będzie wykonywana dalsza część programu. Dla ścisłości: BYŁABY wykonywana, gdyby nie że liczby wskazujące ten adres leżały sobie stosie, a myśmy je zdjęli nie licząc się z tym, ogłupiony tym komputer może nie znaleźć drogi powrotnej.

Co jest, jak sądzę, wystarczająco optymistycznym akcentem, bym mógł zakończyć ten odcinek intensywnego kursu asemblera.

Niezbyt młody, lecz ciągle zdolny programator BARTŁOMIEJ KACHNIARZ

Dzisiaj poznaliśmy rozkazy: PHA – przesłanie zawartości akumulatora na stos

PLA – zdjęcie zawartości akumulatora ze stosu

PHP – przesłanie na stos zawartości rejestru stanu procesora

PLP – zdjęcie ze stosu zawartości rejestru stanu procesora

SŁOWNICZEK

SB – starszy bajt adresu MB – młodszy bajt adresu SR – rejestr stanu procesora

WEKTOR – adres umieszczony w pewnym miejscu pamieci w postaci MB i SB

STOS – obszar pamięci od $0100 do $01ff, jego specyficzną cechą jest fakt, że wartość wysłana na stos jako ostatnia, wraca jako pierwsza

LINPRT – procedura umieszczona w ROM od adresu $bdcd. Jeśli przed jej wywołaniem umieścimy w akumulatorze starszy bajt, a w rejestrze X – młodszy bajt liczby, zostaną one wyświetlone na ekranie w postaci liczby w systemie dziesiętnym.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *